summaryrefslogtreecommitdiff
path: root/ui
diff options
context:
space:
mode:
authorbiao716.wang <biao716.wang@samsung.com>2023-08-28 14:40:25 +0900
committerbiao716.wang <biao716.wang@samsung.com>2023-08-28 14:40:25 +0900
commitd0ba767e7dc0fded6daae31c11d13b538ed8d53f (patch)
treeeaaffe8ef043ee1803a7bbff50a9946e1613f58f /ui
parent0889ee8339e51dfdf78c39a8f15b6e5fe5ab49d5 (diff)
downloadqemu-arm-static-d0ba767e7dc0fded6daae31c11d13b538ed8d53f.tar.gz
qemu-arm-static-d0ba767e7dc0fded6daae31c11d13b538ed8d53f.tar.bz2
qemu-arm-static-d0ba767e7dc0fded6daae31c11d13b538ed8d53f.zip
Change-Id: I79199e4f7e9f27b498cd41113833a6158d4f07a2 Signed-off-by: biao716.wang <biao716.wang@samsung.com>
Diffstat (limited to 'ui')
-rw-r--r--ui/Makefile.objs22
-rw-r--r--ui/cocoa.m1856
-rw-r--r--ui/console-gl.c149
-rw-r--r--ui/console.c1561
-rw-r--r--ui/curses.c598
-rw-r--r--ui/curses_keys.h119
-rw-r--r--ui/cursor.c62
-rw-r--r--ui/d3des.c424
-rw-r--r--ui/d3des.h55
-rw-r--r--ui/egl-context.c42
-rw-r--r--ui/egl-headless.c216
-rw-r--r--ui/egl-helpers.c472
-rw-r--r--ui/gtk-egl.c308
-rw-r--r--ui/gtk-gl-area.c224
-rw-r--r--ui/gtk.c2269
-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.c741
-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.c903
-rw-r--r--ui/kbd-state.c137
-rw-r--r--ui/keycodemapdb/LICENSE.BSD27
-rw-r--r--ui/keycodemapdb/LICENSE.GPL2339
-rw-r--r--ui/keycodemapdb/README114
-rw-r--r--ui/keycodemapdb/data/README89
-rw-r--r--ui/keycodemapdb/data/keymaps.csv539
-rw-r--r--ui/keycodemapdb/tests/.gitignore11
-rw-r--r--ui/keycodemapdb/tests/Makefile118
-rwxr-xr-xui/keycodemapdb/tests/javascript53
-rwxr-xr-xui/keycodemapdb/tests/python23
-rwxr-xr-xui/keycodemapdb/tests/python33
-rw-r--r--ui/keycodemapdb/tests/stdc++.cc40
-rw-r--r--ui/keycodemapdb/tests/stdc.c64
-rw-r--r--ui/keycodemapdb/tests/test.py30
-rw-r--r--ui/keycodemapdb/thirdparty/LICENSE-argparse.txt20
-rw-r--r--ui/keycodemapdb/thirdparty/__init__.py0
-rw-r--r--ui/keycodemapdb/thirdparty/argparse.py2392
-rwxr-xr-xui/keycodemapdb/tools/keymap-gen977
-rw-r--r--ui/keymaps.c310
-rw-r--r--ui/keymaps.h43
-rw-r--r--ui/meson.build145
-rw-r--r--ui/qemu-pixman.c158
-rw-r--r--ui/qemu.desktop8
-rw-r--r--ui/sdl.c953
-rw-r--r--ui/sdl2-2d.c168
-rw-r--r--ui/sdl2-gl.c251
-rw-r--r--ui/sdl2-input.c59
-rw-r--r--ui/sdl2.c918
-rw-r--r--ui/sdl_keysym.h277
-rw-r--r--ui/sdl_zoom.c96
-rw-r--r--ui/sdl_zoom.h25
-rw-r--r--ui/sdl_zoom_template.h219
-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.c223
-rw-r--r--ui/spice-core.c423
-rw-r--r--ui/spice-display.c790
-rw-r--r--ui/spice-input.c115
-rw-r--r--ui/spice-module.c85
-rw-r--r--ui/trace-events110
-rw-r--r--ui/trace.h1
-rw-r--r--ui/vnc-auth-sasl.c236
-rw-r--r--ui/vnc-auth-sasl.h19
-rw-r--r--ui/vnc-auth-vencrypt.c116
-rw-r--r--ui/vnc-auth-vencrypt.h7
-rw-r--r--ui/vnc-enc-hextile-template.h268
-rw-r--r--ui/vnc-enc-hextile.c1
-rw-r--r--ui/vnc-enc-tight.c310
-rw-r--r--ui/vnc-enc-tight.h6
-rw-r--r--ui/vnc-enc-zlib.c12
-rw-r--r--ui/vnc-enc-zrle.c92
-rw-r--r--ui/vnc-enc-zrle.c.inc (renamed from ui/vnc-enc-zrle-template.c)4
-rw-r--r--ui/vnc-enc-zrle.h4
-rw-r--r--ui/vnc-enc-zywrle-template.c5
-rw-r--r--ui/vnc-enc-zywrle.h400
-rw-r--r--ui/vnc-jobs.c113
-rw-r--r--ui/vnc-jobs.h3
-rw-r--r--ui/vnc-palette.c3
-rw-r--r--ui/vnc-palette.h3
-rw-r--r--ui/vnc-stubs.c25
-rw-r--r--ui/vnc-tls.c492
-rw-r--r--ui/vnc-tls.h76
-rw-r--r--ui/vnc-ws.c374
-rw-r--r--ui/vnc-ws.h76
-rw-r--r--ui/vnc.c3503
-rw-r--r--ui/vnc.h163
-rw-r--r--ui/vnc_keysym.h376
-rw-r--r--ui/win32-kbd-hook.c102
-rw-r--r--ui/x_keymap.c254
-rw-r--r--ui/x_keymap.h8
106 files changed, 21450 insertions, 8185 deletions
diff --git a/ui/Makefile.objs b/ui/Makefile.objs
deleted file mode 100644
index 6ddc0def6..000000000
--- a/ui/Makefile.objs
+++ /dev/null
@@ -1,22 +0,0 @@
-vnc-obj-y += vnc.o d3des.o
-vnc-obj-y += vnc-enc-zlib.o vnc-enc-hextile.o
-vnc-obj-y += vnc-enc-tight.o vnc-palette.o
-vnc-obj-y += vnc-enc-zrle.o
-vnc-obj-$(CONFIG_VNC_TLS) += vnc-tls.o vnc-auth-vencrypt.o
-vnc-obj-$(CONFIG_VNC_SASL) += vnc-auth-sasl.o
-vnc-obj-$(CONFIG_VNC_WS) += vnc-ws.o
-vnc-obj-y += vnc-jobs.o
-
-common-obj-y += keymaps.o console.o cursor.o input.o qemu-pixman.o
-common-obj-$(CONFIG_SPICE) += spice-core.o spice-input.o spice-display.o
-common-obj-$(CONFIG_SDL) += sdl.o sdl_zoom.o x_keymap.o
-common-obj-$(CONFIG_COCOA) += cocoa.o
-common-obj-$(CONFIG_CURSES) += curses.o
-common-obj-$(CONFIG_VNC) += $(vnc-obj-y)
-common-obj-$(CONFIG_GTK) += gtk.o x_keymap.o
-
-$(obj)/sdl.o $(obj)/sdl_zoom.o: QEMU_CFLAGS += $(SDL_CFLAGS)
-
-$(obj)/cocoa.o: $(SRC_PATH)/$(obj)/cocoa.m
-
-$(obj)/gtk.o: QEMU_CFLAGS += $(GTK_CFLAGS) $(VTE_CFLAGS)
diff --git a/ui/cocoa.m b/ui/cocoa.m
index be491794d..f32adc307 100644
--- a/ui/cocoa.m
+++ b/ui/cocoa.m
@@ -22,23 +22,40 @@
* THE SOFTWARE.
*/
+#include "qemu/osdep.h"
+
#import <Cocoa/Cocoa.h>
#include <crt_externs.h>
#include "qemu-common.h"
#include "ui/console.h"
+#include "ui/input.h"
#include "sysemu/sysemu.h"
-
-#ifndef MAC_OS_X_VERSION_10_4
-#define MAC_OS_X_VERSION_10_4 1040
-#endif
-#ifndef MAC_OS_X_VERSION_10_5
-#define MAC_OS_X_VERSION_10_5 1050
-#endif
-#ifndef MAC_OS_X_VERSION_10_6
-#define MAC_OS_X_VERSION_10_6 1060
+#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/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
@@ -49,14 +66,6 @@
#endif
#define cgrect(nsrect) (*(CGRect *)&(nsrect))
-#define COCOA_MOUSE_EVENT \
- if (isTabletEnabled) { \
- kbd_mouse_event((int)(p.x * 0x7FFF / (screen.width - 1)), (int)((screen.height - p.y) * 0x7FFF / (screen.height - 1)), 0, buttons); \
- } else if (isMouseGrabed) { \
- kbd_mouse_event((int)[event deltaX], (int)[event deltaY], 0, buttons); \
- } else { \
- [NSApp sendEvent:event]; \
- }
typedef struct {
int width;
@@ -65,190 +74,205 @@ typedef struct {
int bitsPerPixel;
} QEMUScreen;
-NSWindow *normalWindow;
+NSWindow *normalWindow, *about_window;
static DisplayChangeListener *dcl;
+static int last_buttons;
+static int cursor_hide = 1;
int gArgc;
char **gArgv;
+bool stretch_video;
+NSTextField *pauseLabel;
+NSArray * supportedImageFileTypes;
+
+static QemuSemaphore display_init_sem;
+static QemuSemaphore app_started_sem;
+static bool allow_events;
+
+// Utility functions to run specified code block with iothread lock held
+typedef void (^CodeBlock)(void);
+typedef bool (^BoolCodeBlock)(void);
-// keymap conversion
-int keymap[] =
+static void with_iothread_lock(CodeBlock block)
{
-// SdlI macI macH SdlH 104xtH 104xtC sdl
- 30, // 0 0x00 0x1e A QZ_a
- 31, // 1 0x01 0x1f S QZ_s
- 32, // 2 0x02 0x20 D QZ_d
- 33, // 3 0x03 0x21 F QZ_f
- 35, // 4 0x04 0x23 H QZ_h
- 34, // 5 0x05 0x22 G QZ_g
- 44, // 6 0x06 0x2c Z QZ_z
- 45, // 7 0x07 0x2d X QZ_x
- 46, // 8 0x08 0x2e C QZ_c
- 47, // 9 0x09 0x2f V QZ_v
- 0, // 10 0x0A Undefined
- 48, // 11 0x0B 0x30 B QZ_b
- 16, // 12 0x0C 0x10 Q QZ_q
- 17, // 13 0x0D 0x11 W QZ_w
- 18, // 14 0x0E 0x12 E QZ_e
- 19, // 15 0x0F 0x13 R QZ_r
- 21, // 16 0x10 0x15 Y QZ_y
- 20, // 17 0x11 0x14 T QZ_t
- 2, // 18 0x12 0x02 1 QZ_1
- 3, // 19 0x13 0x03 2 QZ_2
- 4, // 20 0x14 0x04 3 QZ_3
- 5, // 21 0x15 0x05 4 QZ_4
- 7, // 22 0x16 0x07 6 QZ_6
- 6, // 23 0x17 0x06 5 QZ_5
- 13, // 24 0x18 0x0d = QZ_EQUALS
- 10, // 25 0x19 0x0a 9 QZ_9
- 8, // 26 0x1A 0x08 7 QZ_7
- 12, // 27 0x1B 0x0c - QZ_MINUS
- 9, // 28 0x1C 0x09 8 QZ_8
- 11, // 29 0x1D 0x0b 0 QZ_0
- 27, // 30 0x1E 0x1b ] QZ_RIGHTBRACKET
- 24, // 31 0x1F 0x18 O QZ_o
- 22, // 32 0x20 0x16 U QZ_u
- 26, // 33 0x21 0x1a [ QZ_LEFTBRACKET
- 23, // 34 0x22 0x17 I QZ_i
- 25, // 35 0x23 0x19 P QZ_p
- 28, // 36 0x24 0x1c ENTER QZ_RETURN
- 38, // 37 0x25 0x26 L QZ_l
- 36, // 38 0x26 0x24 J QZ_j
- 40, // 39 0x27 0x28 ' QZ_QUOTE
- 37, // 40 0x28 0x25 K QZ_k
- 39, // 41 0x29 0x27 ; QZ_SEMICOLON
- 43, // 42 0x2A 0x2b \ QZ_BACKSLASH
- 51, // 43 0x2B 0x33 , QZ_COMMA
- 53, // 44 0x2C 0x35 / QZ_SLASH
- 49, // 45 0x2D 0x31 N QZ_n
- 50, // 46 0x2E 0x32 M QZ_m
- 52, // 47 0x2F 0x34 . QZ_PERIOD
- 15, // 48 0x30 0x0f TAB QZ_TAB
- 57, // 49 0x31 0x39 SPACE QZ_SPACE
- 41, // 50 0x32 0x29 ` QZ_BACKQUOTE
- 14, // 51 0x33 0x0e BKSP QZ_BACKSPACE
- 0, // 52 0x34 Undefined
- 1, // 53 0x35 0x01 ESC QZ_ESCAPE
- 0, // 54 0x36 QZ_RMETA
- 0, // 55 0x37 QZ_LMETA
- 42, // 56 0x38 0x2a L SHFT QZ_LSHIFT
- 58, // 57 0x39 0x3a CAPS QZ_CAPSLOCK
- 56, // 58 0x3A 0x38 L ALT QZ_LALT
- 29, // 59 0x3B 0x1d L CTRL QZ_LCTRL
- 54, // 60 0x3C 0x36 R SHFT QZ_RSHIFT
- 184,// 61 0x3D 0xb8 E0,38 R ALT QZ_RALT
- 157,// 62 0x3E 0x9d E0,1D R CTRL QZ_RCTRL
- 0, // 63 0x3F Undefined
- 0, // 64 0x40 Undefined
- 0, // 65 0x41 Undefined
- 0, // 66 0x42 Undefined
- 55, // 67 0x43 0x37 KP * QZ_KP_MULTIPLY
- 0, // 68 0x44 Undefined
- 78, // 69 0x45 0x4e KP + QZ_KP_PLUS
- 0, // 70 0x46 Undefined
- 69, // 71 0x47 0x45 NUM QZ_NUMLOCK
- 0, // 72 0x48 Undefined
- 0, // 73 0x49 Undefined
- 0, // 74 0x4A Undefined
- 181,// 75 0x4B 0xb5 E0,35 KP / QZ_KP_DIVIDE
- 152,// 76 0x4C 0x9c E0,1C KP EN QZ_KP_ENTER
- 0, // 77 0x4D undefined
- 74, // 78 0x4E 0x4a KP - QZ_KP_MINUS
- 0, // 79 0x4F Undefined
- 0, // 80 0x50 Undefined
- 0, // 81 0x51 QZ_KP_EQUALS
- 82, // 82 0x52 0x52 KP 0 QZ_KP0
- 79, // 83 0x53 0x4f KP 1 QZ_KP1
- 80, // 84 0x54 0x50 KP 2 QZ_KP2
- 81, // 85 0x55 0x51 KP 3 QZ_KP3
- 75, // 86 0x56 0x4b KP 4 QZ_KP4
- 76, // 87 0x57 0x4c KP 5 QZ_KP5
- 77, // 88 0x58 0x4d KP 6 QZ_KP6
- 71, // 89 0x59 0x47 KP 7 QZ_KP7
- 0, // 90 0x5A Undefined
- 72, // 91 0x5B 0x48 KP 8 QZ_KP8
- 73, // 92 0x5C 0x49 KP 9 QZ_KP9
- 0, // 93 0x5D Undefined
- 0, // 94 0x5E Undefined
- 0, // 95 0x5F Undefined
- 63, // 96 0x60 0x3f F5 QZ_F5
- 64, // 97 0x61 0x40 F6 QZ_F6
- 65, // 98 0x62 0x41 F7 QZ_F7
- 61, // 99 0x63 0x3d F3 QZ_F3
- 66, // 100 0x64 0x42 F8 QZ_F8
- 67, // 101 0x65 0x43 F9 QZ_F9
- 0, // 102 0x66 Undefined
- 87, // 103 0x67 0x57 F11 QZ_F11
- 0, // 104 0x68 Undefined
- 183,// 105 0x69 0xb7 QZ_PRINT
- 0, // 106 0x6A Undefined
- 70, // 107 0x6B 0x46 SCROLL QZ_SCROLLOCK
- 0, // 108 0x6C Undefined
- 68, // 109 0x6D 0x44 F10 QZ_F10
- 0, // 110 0x6E Undefined
- 88, // 111 0x6F 0x58 F12 QZ_F12
- 0, // 112 0x70 Undefined
- 110,// 113 0x71 0x0 QZ_PAUSE
- 210,// 114 0x72 0xd2 E0,52 INSERT QZ_INSERT
- 199,// 115 0x73 0xc7 E0,47 HOME QZ_HOME
- 201,// 116 0x74 0xc9 E0,49 PG UP QZ_PAGEUP
- 211,// 117 0x75 0xd3 E0,53 DELETE QZ_DELETE
- 62, // 118 0x76 0x3e F4 QZ_F4
- 207,// 119 0x77 0xcf E0,4f END QZ_END
- 60, // 120 0x78 0x3c F2 QZ_F2
- 209,// 121 0x79 0xd1 E0,51 PG DN QZ_PAGEDOWN
- 59, // 122 0x7A 0x3b F1 QZ_F1
- 203,// 123 0x7B 0xcb e0,4B L ARROW QZ_LEFT
- 205,// 124 0x7C 0xcd e0,4D R ARROW QZ_RIGHT
- 208,// 125 0x7D 0xd0 E0,50 D ARROW QZ_DOWN
- 200,// 126 0x7E 0xc8 E0,48 U ARROW QZ_UP
-/* completed according to http://www.libsdl.org/cgi/cvsweb.cgi/SDL12/src/video/quartz/SDL_QuartzKeys.h?rev=1.6&content-type=text/x-cvsweb-markup */
-
-/* Aditional 104 Key XP-Keyboard Scancodes from http://www.computer-engineering.org/ps2keyboard/scancodes1.html */
-/*
- 219 // 0xdb e0,5b L GUI
- 220 // 0xdc e0,5c R GUI
- 221 // 0xdd e0,5d APPS
- // E0,2A,E0,37 PRNT SCRN
- // E1,1D,45,E1,9D,C5 PAUSE
- 83 // 0x53 0x53 KP .
-// ACPI Scan Codes
- 222 // 0xde E0, 5E Power
- 223 // 0xdf E0, 5F Sleep
- 227 // 0xe3 E0, 63 Wake
-// Windows Multimedia Scan Codes
- 153 // 0x99 E0, 19 Next Track
- 144 // 0x90 E0, 10 Previous Track
- 164 // 0xa4 E0, 24 Stop
- 162 // 0xa2 E0, 22 Play/Pause
- 160 // 0xa0 E0, 20 Mute
- 176 // 0xb0 E0, 30 Volume Up
- 174 // 0xae E0, 2E Volume Down
- 237 // 0xed E0, 6D Media Select
- 236 // 0xec E0, 6C E-Mail
- 161 // 0xa1 E0, 21 Calculator
- 235 // 0xeb E0, 6B My Computer
- 229 // 0xe5 E0, 65 WWW Search
- 178 // 0xb2 E0, 32 WWW Home
- 234 // 0xea E0, 6A WWW Back
- 233 // 0xe9 E0, 69 WWW Forward
- 232 // 0xe8 E0, 68 WWW Stop
- 231 // 0xe7 E0, 67 WWW Refresh
- 230 // 0xe6 E0, 66 WWW Favorites
-*/
+ 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
+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_Shift] = Q_KEY_CODE_SHIFT,
+ [kVK_RightShift] = Q_KEY_CODE_SHIFT_R,
+ [kVK_Control] = Q_KEY_CODE_CTRL,
+ [kVK_RightControl] = Q_KEY_CODE_CTRL_R,
+ [kVK_Option] = Q_KEY_CODE_ALT,
+ [kVK_RightOption] = Q_KEY_CODE_ALT_R,
+ [kVK_Command] = Q_KEY_CODE_META_L,
+ [0x36] = Q_KEY_CODE_META_R, /* There is no kVK_RightCommand */
+ [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,
+
+ /*
+ * 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((sizeof(keymap)/sizeof(int)) <= keycode)
- {
- printf("(cocoa) warning unknow keycode 0x%x\n", keycode);
+ if (ARRAY_SIZE(mac_to_qkeycode_map) <= keycode) {
+ fprintf(stderr, "(cocoa) warning unknown keycode 0x%x\n", keycode);
return 0;
}
- return keymap[keycode];
+ 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);
+ }
+}
/*
------------------------------------------------------
@@ -261,23 +285,40 @@ static int cocoa_keycode_to_qemu(int keycode)
NSWindow *fullScreenWindow;
float cx,cy,cw,ch,cdx,cdy;
CGDataProviderRef dataProviderRef;
- int modifiers_state[256];
- BOOL isMouseGrabed;
+ pixman_image_t *pixman_image;
+ BOOL modifiers_state[256];
+ BOOL isMouseGrabbed;
BOOL isFullscreen;
BOOL isAbsoluteEnabled;
- BOOL isTabletEnabled;
+ BOOL isMouseDeassociated;
}
-- (void) switchSurface:(DisplaySurface *)surface;
+- (void) switchSurface:(pixman_image_t *)image;
- (void) grabMouse;
- (void) ungrabMouse;
- (void) toggleFullScreen:(id)sender;
-- (void) handleEvent:(NSEvent *)event;
+- (void) handleMonitorInput:(NSEvent *)event;
+- (bool) handleEvent:(NSEvent *)event;
+- (bool) handleEventLocked:(NSEvent *)event;
- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled;
-- (BOOL) isMouseGrabed;
+/* 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.
+ * isMouseDeassociated tracks whether we've told MacOSX to disassociate
+ * the mouse and mouse cursor position by calling
+ * CGAssociateMouseAndMouseCursorPosition(FALSE)
+ * (which basically happens if we grab in non-absolute mode).
+ */
+- (BOOL) isMouseGrabbed;
- (BOOL) isAbsoluteEnabled;
+- (BOOL) isMouseDeassociated;
- (float) cdx;
- (float) cdy;
- (QEMUScreen) gscreen;
+- (void) raiseAllKeys;
@end
QemuCocoaView *cocoaView;
@@ -303,8 +344,10 @@ QemuCocoaView *cocoaView;
{
COCOA_DEBUG("QemuCocoaView: dealloc\n");
- if (dataProviderRef)
+ if (dataProviderRef) {
CGDataProviderRelease(dataProviderRef);
+ pixman_image_unref(pixman_image);
+ }
[super dealloc];
}
@@ -314,17 +357,79 @@ QemuCocoaView *cocoaView;
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] graphicsPort];
+ CGContextRef viewContextRef = [[NSGraphicsContext currentContext] CGContext];
+
CGContextSetInterpolationQuality (viewContextRef, kCGInterpolationNone);
CGContextSetShouldAntialias (viewContextRef, NO);
// draw screen bitmap directly to Core Graphics context
- if (dataProviderRef) {
+ if (!dataProviderRef) {
+ // 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 {
CGImageRef imageRef = CGImageCreate(
screen.width, //width
screen.height, //height
@@ -343,40 +448,26 @@ QemuCocoaView *cocoaView;
0, //interpolate
kCGRenderingIntentDefault //intent
);
-// test if host supports "CGImageCreateWithImageInRect" at compile time
-#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4)
- if (CGImageCreateWithImageInRect == NULL) { // test if "CGImageCreateWithImageInRect" is supported on host at runtime
-#endif
- // compatibility drawing code (draws everything) (OS X < 10.4)
- CGContextDrawImage (viewContextRef, CGRectMake(0, 0, [self bounds].size.width, [self bounds].size.height), imageRef);
-#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4)
- } else {
- // selective drawing code (draws only dirty rectangles) (OS X >= 10.4)
- const NSRect *rectList;
-#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
- NSInteger rectCount;
-#else
- int rectCount;
-#endif
- 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)screen.height - (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);
- }
+ // 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)screen.height - (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);
}
-#endif
CGImageRelease (imageRef);
}
}
@@ -388,6 +479,18 @@ QemuCocoaView *cocoaView;
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;
@@ -402,37 +505,55 @@ QemuCocoaView *cocoaView;
}
}
-- (void) switchSurface:(DisplaySurface *)surface
+- (void) switchSurface:(pixman_image_t *)image
{
COCOA_DEBUG("QemuCocoaView: switchSurface\n");
- int w = surface_width(surface);
- int h = surface_height(surface);
+ int w = pixman_image_get_width(image);
+ int h = pixman_image_get_height(image);
+ pixman_format_code_t image_format = pixman_image_get_format(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 (dataProviderRef)
+ if (dataProviderRef) {
CGDataProviderRelease(dataProviderRef);
+ pixman_image_unref(pixman_image);
+ }
//sync host window color space with guests
- screen.bitsPerPixel = surface_bits_per_pixel(surface);
- screen.bitsPerComponent = surface_bytes_per_pixel(surface) * 2;
+ screen.bitsPerPixel = PIXMAN_FORMAT_BPP(image_format);
+ screen.bitsPerComponent = DIV_ROUND_UP(screen.bitsPerPixel, 8) * 2;
- dataProviderRef = CGDataProviderCreateWithData(NULL, surface_data(surface), w * 4 * h, NULL);
+ pixman_image = image;
+ dataProviderRef = CGDataProviderCreateWithData(NULL, pixman_image_get_data(image), w * 4 * h, NULL);
// update windows
if (isFullscreen) {
[[fullScreenWindow contentView] setFrame:[[NSScreen mainScreen] frame]];
- [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + screen.height, w, h + [normalWindow frame].size.height - screen.height) display:NO animate:NO];
+ [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 + screen.height, w, h + [normalWindow frame].size.height - screen.height) display:YES animate:NO];
+ [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];
}
- screen.width = w;
- screen.height = h;
- [normalWindow center];
- [self setContentDimensions];
- [self setFrame:NSMakeRect(cx, cy, cw, ch)];
}
- (void) toggleFullScreen:(id)sender
@@ -443,227 +564,390 @@ QemuCocoaView *cocoaView;
isFullscreen = FALSE;
[self ungrabMouse];
[self setContentDimensions];
-// test if host supports "exitFullScreenModeWithOptions" at compile time
-#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
if ([NSView respondsToSelector:@selector(exitFullScreenModeWithOptions:)]) { // test if "exitFullScreenModeWithOptions" is supported on host at runtime
[self exitFullScreenModeWithOptions:nil];
} else {
-#endif
[fullScreenWindow close];
[normalWindow setContentView: self];
[normalWindow makeKeyAndOrderFront: self];
[NSMenu setMenuBarVisible:YES];
-#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
}
-#endif
} else { // switch from desktop to fullscreen
isFullscreen = TRUE;
+ [normalWindow orderOut: nil]; /* Hide the window */
[self grabMouse];
[self setContentDimensions];
-// test if host supports "enterFullScreenMode:withOptions" at compile time
-#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
if ([NSView respondsToSelector:@selector(enterFullScreenMode:withOptions:)]) { // test if "enterFullScreenMode:withOptions" is supported on host at runtime
[self enterFullScreenMode:[NSScreen mainScreen] withOptions:[NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:NO], NSFullScreenModeAllScreens,
[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO], kCGDisplayModeIsStretched, nil], NSFullScreenModeSetting,
nil]];
} else {
-#endif
[NSMenu setMenuBarVisible:NO];
fullScreenWindow = [[NSWindow alloc] initWithContentRect:[[NSScreen mainScreen] frame]
- styleMask:NSBorderlessWindowMask
+ styleMask:NSWindowStyleMaskBorderless
backing:NSBackingStoreBuffered
defer:NO];
+ [fullScreenWindow setAcceptsMouseMovedEvents: YES];
[fullScreenWindow setHasShadow:NO];
- [fullScreenWindow setContentView:self];
+ [fullScreenWindow setBackgroundColor: [NSColor blackColor]];
+ [self setFrame:NSMakeRect(cx, cy, cw, ch)];
+ [[fullScreenWindow contentView] addSubview: self];
[fullScreenWindow makeKeyAndOrderFront:self];
-#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
}
-#endif
}
}
-- (void) handleEvent:(NSEvent *)event
+- (void) toggleModifier: (int)keycode {
+ // Toggle the stored state.
+ modifiers_state[keycode] = !modifiers_state[keycode];
+ // Send a keyup or keydown depending on the state.
+ qemu_input_event_send_key_qcode(dcl->con, keycode, modifiers_state[keycode]);
+}
+
+- (void) toggleStatefulModifier: (int)keycode {
+ // Toggle the stored state.
+ modifiers_state[keycode] = !modifiers_state[keycode];
+ // Generate keydown and keyup.
+ qemu_input_event_send_key_qcode(dcl->con, keycode, true);
+ qemu_input_event_send_key_qcode(dcl->con, keycode, false);
+}
+
+// Does the work of sending input to the monitor
+- (void) handleMonitorInput:(NSEvent *)event
{
- COCOA_DEBUG("QemuCocoaView: handleEvent\n");
+ 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;
- NSPoint p = [event locationInWindow];
+ 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];
switch ([event type]) {
- case NSFlagsChanged:
- keycode = cocoa_keycode_to_qemu([event keyCode]);
- if (keycode) {
- if (keycode == 58 || keycode == 69) { // emulate caps lock and num lock keydown and keyup
- kbd_put_keycode(keycode);
- kbd_put_keycode(keycode | 0x80);
- } else if (qemu_console_is_graphic(NULL)) {
- if (keycode & 0x80)
- kbd_put_keycode(0xe0);
- if (modifiers_state[keycode] == 0) { // keydown
- kbd_put_keycode(keycode & 0x7f);
- modifiers_state[keycode] = 1;
- } else { // keyup
- kbd_put_keycode(keycode | 0x80);
- modifiers_state[keycode] = 0;
+ case NSEventTypeFlagsChanged:
+ if ([event keyCode] == 0) {
+ // When the Cocoa keyCode is zero that means keys should be
+ // synthesized based on the values in in the eventModifiers
+ // bitmask.
+
+ if (qemu_console_is_graphic(NULL)) {
+ NSUInteger modifiers = [event modifierFlags];
+
+ if (!!(modifiers & NSEventModifierFlagCapsLock) != !!modifiers_state[Q_KEY_CODE_CAPS_LOCK]) {
+ [self toggleStatefulModifier:Q_KEY_CODE_CAPS_LOCK];
+ }
+ if (!!(modifiers & NSEventModifierFlagShift) != !!modifiers_state[Q_KEY_CODE_SHIFT]) {
+ [self toggleModifier:Q_KEY_CODE_SHIFT];
+ }
+ if (!!(modifiers & NSEventModifierFlagControl) != !!modifiers_state[Q_KEY_CODE_CTRL]) {
+ [self toggleModifier:Q_KEY_CODE_CTRL];
+ }
+ if (!!(modifiers & NSEventModifierFlagOption) != !!modifiers_state[Q_KEY_CODE_ALT]) {
+ [self toggleModifier:Q_KEY_CODE_ALT];
+ }
+ if (!!(modifiers & NSEventModifierFlagCommand) != !!modifiers_state[Q_KEY_CODE_META_L]) {
+ [self toggleModifier:Q_KEY_CODE_META_L];
}
}
+ } else {
+ keycode = cocoa_keycode_to_qemu([event keyCode]);
}
- // release Mouse grab when pressing ctrl+alt
- if (!isFullscreen && ([event modifierFlags] & NSControlKeyMask) && ([event modifierFlags] & NSAlternateKeyMask)) {
- [self ungrabMouse];
+ if ((keycode == Q_KEY_CODE_META_L || keycode == Q_KEY_CODE_META_R)
+ && !isMouseGrabbed) {
+ /* Don't pass command key changes to guest unless mouse is grabbed */
+ keycode = 0;
}
- break;
- case NSKeyDown:
- // forward command Key Combos
- if ([event modifierFlags] & NSCommandKeyMask) {
- [NSApp sendEvent:event];
- return;
+ if (keycode) {
+ // emulate caps lock and num lock keydown and keyup
+ if (keycode == Q_KEY_CODE_CAPS_LOCK ||
+ keycode == Q_KEY_CODE_NUM_LOCK) {
+ [self toggleStatefulModifier:keycode];
+ } else if (qemu_console_is_graphic(NULL)) {
+ if (switched_to_fullscreen) {
+ switched_to_fullscreen = false;
+ } else {
+ [self toggleModifier:keycode];
+ }
+ }
}
- // default
+ break;
+ case NSEventTypeKeyDown:
keycode = cocoa_keycode_to_qemu([event keyCode]);
- // handle control + alt Key Combos (ctrl+alt is reserved for QEMU)
- if (([event modifierFlags] & NSControlKeyMask) && ([event modifierFlags] & NSAlternateKeyMask)) {
- switch (keycode) {
-
- // enable graphic console
- case 0x02 ... 0x0a: // '1' to '9' keys
- console_select(keycode - 0x02);
- break;
+ // 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;
+ }
- // handle keys for graphic console
- } else if (qemu_console_is_graphic(NULL)) {
- if (keycode & 0x80) //check bit for e0 in front
- kbd_put_keycode(0xe0);
- kbd_put_keycode(keycode & 0x7f); //remove e0 bit in front
+ // default
- // handlekeys for Monitor
- } else {
- int keysym = 0;
- switch([event keyCode]) {
- case 115:
- keysym = QEMU_KEY_HOME;
- break;
- case 117:
- keysym = QEMU_KEY_DELETE;
- break;
- case 119:
- keysym = QEMU_KEY_END;
- break;
- case 123:
- keysym = QEMU_KEY_LEFT;
- break;
- case 124:
- keysym = QEMU_KEY_RIGHT;
- break;
- case 125:
- keysym = QEMU_KEY_DOWN;
- break;
- case 126:
- keysym = QEMU_KEY_UP;
- break;
- default:
- {
- NSString *ks = [event characters];
- if ([ks length] > 0)
- keysym = [ks characterAtIndex:0];
+ // 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 (keysym)
- kbd_put_keysym(keysym);
+ }
+
+ if (qemu_console_is_graphic(NULL)) {
+ qemu_input_event_send_key_qcode(dcl->con, keycode, true);
+ } else {
+ [self handleMonitorInput: event];
}
break;
- case NSKeyUp:
+ 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)) {
- if (keycode & 0x80)
- kbd_put_keycode(0xe0);
- kbd_put_keycode(keycode | 0x80); //add 128 to signal release of key
+ qemu_input_event_send_key_qcode(dcl->con, keycode, false);
}
break;
- case NSMouseMoved:
+ case NSEventTypeMouseMoved:
if (isAbsoluteEnabled) {
- if (p.x < 0 || p.x > screen.width || p.y < 0 || p.y > screen.height || ![[self window] isKeyWindow]) {
- if (isTabletEnabled) { // if we leave the window, deactivate the tablet
- [NSCursor unhide];
- isTabletEnabled = FALSE;
+ // 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 (!isTabletEnabled) { // if we enter the window, activate the tablet
- [NSCursor hide];
- isTabletEnabled = TRUE;
+ if (!isMouseGrabbed) {
+ [self grabMouse];
}
}
}
- COCOA_MOUSE_EVENT
+ mouse_event = true;
break;
- case NSLeftMouseDown:
- if ([event modifierFlags] & NSCommandKeyMask) {
+ case NSEventTypeLeftMouseDown:
+ if ([event modifierFlags] & NSEventModifierFlagCommand) {
buttons |= MOUSE_EVENT_RBUTTON;
} else {
buttons |= MOUSE_EVENT_LBUTTON;
}
- COCOA_MOUSE_EVENT
+ mouse_event = true;
break;
- case NSRightMouseDown:
+ case NSEventTypeRightMouseDown:
buttons |= MOUSE_EVENT_RBUTTON;
- COCOA_MOUSE_EVENT
+ mouse_event = true;
break;
- case NSOtherMouseDown:
+ case NSEventTypeOtherMouseDown:
buttons |= MOUSE_EVENT_MBUTTON;
- COCOA_MOUSE_EVENT
+ mouse_event = true;
break;
- case NSLeftMouseDragged:
- if ([event modifierFlags] & NSCommandKeyMask) {
+ case NSEventTypeLeftMouseDragged:
+ if ([event modifierFlags] & NSEventModifierFlagCommand) {
buttons |= MOUSE_EVENT_RBUTTON;
} else {
buttons |= MOUSE_EVENT_LBUTTON;
}
- COCOA_MOUSE_EVENT
+ mouse_event = true;
break;
- case NSRightMouseDragged:
+ case NSEventTypeRightMouseDragged:
buttons |= MOUSE_EVENT_RBUTTON;
- COCOA_MOUSE_EVENT
+ mouse_event = true;
break;
- case NSOtherMouseDragged:
+ case NSEventTypeOtherMouseDragged:
buttons |= MOUSE_EVENT_MBUTTON;
- COCOA_MOUSE_EVENT
+ mouse_event = true;
break;
- case NSLeftMouseUp:
- if (isTabletEnabled) {
- COCOA_MOUSE_EVENT
- } else if (!isMouseGrabed) {
- if (p.x > -1 && p.x < screen.width && p.y > -1 && p.y < screen.height) {
+ 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];
- } else {
- [NSApp sendEvent:event];
}
- } else {
- COCOA_MOUSE_EVENT
}
break;
- case NSRightMouseUp:
- COCOA_MOUSE_EVENT
+ case NSEventTypeRightMouseUp:
+ mouse_event = true;
break;
- case NSOtherMouseUp:
- COCOA_MOUSE_EVENT
+ case NSEventTypeOtherMouseUp:
+ mouse_event = true;
break;
- case NSScrollWheel:
- if (isTabletEnabled || isMouseGrabed) {
- kbd_mouse_event(0, 0, -[event deltaY], 0);
- } else {
- [NSApp sendEvent:event];
+ 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:
- [NSApp sendEvent:event];
+ 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
@@ -672,13 +956,16 @@ QemuCocoaView *cocoaView;
if (!isFullscreen) {
if (qemu_name)
- [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s - (Press ctrl + alt to release Mouse)", qemu_name]];
+ [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s - (Press ctrl + alt + g to release Mouse)", qemu_name]];
else
- [normalWindow setTitle:@"QEMU - (Press ctrl + alt to release Mouse)"];
+ [normalWindow setTitle:@"QEMU - (Press ctrl + alt + g to release Mouse)"];
}
- [NSCursor hide];
- CGAssociateMouseAndMouseCursorPosition(FALSE);
- isMouseGrabed = TRUE; // while isMouseGrabed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:]
+ [self hideCursor];
+ if (!isAbsoluteEnabled) {
+ isMouseDeassociated = TRUE;
+ CGAssociateMouseAndMouseCursorPosition(FALSE);
+ }
+ isMouseGrabbed = TRUE; // while isMouseGrabbed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:]
}
- (void) ungrabMouse
@@ -691,17 +978,42 @@ QemuCocoaView *cocoaView;
else
[normalWindow setTitle:@"QEMU"];
}
- [NSCursor unhide];
- CGAssociateMouseAndMouseCursorPosition(TRUE);
- isMouseGrabed = FALSE;
+ [self unhideCursor];
+ if (isMouseDeassociated) {
+ CGAssociateMouseAndMouseCursorPosition(TRUE);
+ isMouseDeassociated = FALSE;
+ }
+ isMouseGrabbed = FALSE;
}
- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled {isAbsoluteEnabled = tIsAbsoluteEnabled;}
-- (BOOL) isMouseGrabed {return isMouseGrabed;}
+- (BOOL) isMouseGrabbed {return isMouseGrabbed;}
- (BOOL) isAbsoluteEnabled {return isAbsoluteEnabled;}
+- (BOOL) isMouseDeassociated {return isMouseDeassociated;}
- (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
+{
+ const int max_index = ARRAY_SIZE(modifiers_state);
+
+ with_iothread_lock(^{
+ int index;
+
+ for (index = 0; index < max_index; index++) {
+ if (modifiers_state[index]) {
+ modifiers_state[index] = 0;
+ qemu_input_event_send_key_qcode(dcl->con, index, false);
+ }
+ }
+ });
+}
@end
@@ -712,13 +1024,27 @@ QemuCocoaView *cocoaView;
------------------------------------------------------
*/
@interface QemuCocoaAppController : NSObject
+ <NSWindowDelegate, NSApplicationDelegate>
{
}
-- (void)startEmulationWithArgc:(int)argc argv:(char**)argv;
-- (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo;
+- (void)doToggleFullScreen:(id)sender;
- (void)toggleFullScreen:(id)sender;
- (void)showQEMUDoc:(id)sender;
-- (void)showQEMUTec:(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
@@ -738,19 +1064,37 @@ QemuCocoaView *cocoaView;
// create a window
normalWindow = [[NSWindow alloc] initWithContentRect:[cocoaView frame]
- styleMask:NSTitledWindowMask|NSMiniaturizableWindowMask|NSClosableWindowMask
+ styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskClosable
backing:NSBackingStoreBuffered defer:NO];
if(!normalWindow) {
fprintf(stderr, "(cocoa) can't create window\n");
exit(1);
}
[normalWindow setAcceptsMouseMovedEvents:YES];
- [normalWindow setTitle:[NSString stringWithFormat:@"QEMU"]];
+ [normalWindow setTitle:@"QEMU"];
[normalWindow setContentView:cocoaView];
- [normalWindow useOptimizedDrawing:YES];
[normalWindow makeKeyAndOrderFront:self];
- [normalWindow center];
-
+ [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;
}
@@ -767,38 +1111,16 @@ QemuCocoaView *cocoaView;
- (void)applicationDidFinishLaunching: (NSNotification *) note
{
COCOA_DEBUG("QemuCocoaAppController: applicationDidFinishLaunching\n");
-
- // Display an open dialog box if no argument were passed or
- // if qemu was launched from the finder ( the Finder passes "-psn" )
- if( gArgc <= 1 || strncmp ((char *)gArgv[1], "-psn", 4) == 0) {
- NSOpenPanel *op = [[NSOpenPanel alloc] init];
- [op setPrompt:@"Boot image"];
- [op setMessage:@"Select the disk image you want to boot.\n\nHit the \"Cancel\" button to quit"];
- NSArray *filetypes = [NSArray arrayWithObjects:@"img", @"iso", @"dmg",
- @"qcow", @"cow", @"cloop", @"vmdk", nil];
-#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6)
- [op setAllowedFileTypes:filetypes];
- [op beginSheetModalForWindow:normalWindow
- completionHandler:^(NSInteger returnCode)
- { [self openPanelDidEnd:op
- returnCode:returnCode contextInfo:NULL ]; } ];
-#else
- // Compatibility code for pre-10.6, using deprecated method
- [op beginSheetForDirectory:nil file:nil types:filetypes
- modalForWindow:normalWindow modalDelegate:self
- didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:) contextInfo:NULL];
-#endif
- } else {
- // or launch QEMU, with the global args
- [self startEmulationWithArgc:gArgc argv:(char **)gArgv];
- }
+ 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();
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
exit(0);
}
@@ -807,99 +1129,376 @@ QemuCocoaView *cocoaView;
return YES;
}
-- (void)startEmulationWithArgc:(int)argc argv:(char**)argv
+- (NSApplicationTerminateReply)applicationShouldTerminate:
+ (NSApplication *)sender
{
- COCOA_DEBUG("QemuCocoaAppController: startEmulationWithArgc\n");
+ COCOA_DEBUG("QemuCocoaAppController: applicationShouldTerminate\n");
+ return [self verifyQuit];
+}
- int status;
- status = qemu_main(argc, argv, *_NSGetEnviron());
- exit(status);
+/* 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;
}
-- (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
+/* Called when QEMU goes into the background */
+- (void) applicationWillResignActive: (NSNotification *)aNotification
{
- COCOA_DEBUG("QemuCocoaAppController: openPanelDidEnd\n");
+ COCOA_DEBUG("QemuCocoaAppController: applicationWillResignActive\n");
+ [cocoaView raiseAllKeys];
+}
- if(returnCode == NSCancelButton) {
- exit(0);
- } else if(returnCode == NSOKButton) {
- const char *bin = "qemu";
- char *img = (char*)[ [ [ sheet URL ] path ] cStringUsingEncoding:NSASCIIStringEncoding];
+/* 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];
+}
- char **argv = (char**)malloc( sizeof(char*)*3 );
+- (void)toggleFullScreen:(id)sender
+{
+ COCOA_DEBUG("QemuCocoaAppController: toggleFullScreen\n");
- [sheet close];
+ [cocoaView toggleFullScreen:sender];
+}
- argv[0] = g_strdup_printf("%s", bin);
- argv[1] = g_strdup_printf("-hda");
- argv[2] = g_strdup_printf("%s", img);
+/* 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;
+
+ /* 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];
+ if ([[NSWorkspace sharedWorkspace] openFile: full_file_path] == 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");
- printf("Using argc %d argv %s -hda %s\n", 3, bin, img);
+ [self openDocumentation: @"index.html"];
+}
- [self startEmulationWithArgc:3 argv:(char**)argv];
+/* 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];
}
}
-- (void)toggleFullScreen:(id)sender
+
+/* Displays the console on the screen */
+- (void)displayConsole:(id)sender
{
- COCOA_DEBUG("QemuCocoaAppController: toggleFullScreen\n");
+ console_select([sender tag]);
+}
- [cocoaView toggleFullScreen:sender];
+/* Pause the guest */
+- (void)pauseQEMU:(id)sender
+{
+ with_iothread_lock(^{
+ qmp_stop(NULL);
+ });
+ [sender setEnabled: NO];
+ [[[sender menu] itemWithTitle: @"Resume"] setEnabled: YES];
+ [self displayPause];
}
-- (void)showQEMUDoc:(id)sender
+/* Resume running the guest operating system */
+- (void)resumeQEMU:(id) sender
{
- COCOA_DEBUG("QemuCocoaAppController: showQEMUDoc\n");
+ with_iothread_lock(^{
+ qmp_cont(NULL);
+ });
+ [sender setEnabled: NO];
+ [[[sender menu] itemWithTitle: @"Pause"] setEnabled: YES];
+ [self removePause];
+}
- [[NSWorkspace sharedWorkspace] openFile:[NSString stringWithFormat:@"%@/../doc/qemu/qemu-doc.html",
- [[NSBundle mainBundle] resourcePath]] withApplication:@"Help Viewer"];
+/* 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];
}
-- (void)showQEMUTec:(id)sender
+/* Removes the word pause from the screen */
+- (void)removePause
{
- COCOA_DEBUG("QemuCocoaAppController: showQEMUTec\n");
+ [pauseLabel removeFromSuperview];
+}
- [[NSWorkspace sharedWorkspace] openFile:[NSString stringWithFormat:@"%@/../doc/qemu/qemu-tech.html",
- [[NSBundle mainBundle] resourcePath]] withApplication:@"Help Viewer"];
+/* Restarts QEMU */
+- (void)restartQEMU:(id)sender
+{
+ with_iothread_lock(^{
+ qmp_system_reset(NULL);
+ });
}
-@end
+/* 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;
+ }
-int main (int argc, const char * argv[]) {
+ __block Error *err = NULL;
+ with_iothread_lock(^{
+ qmp_eject(true, [drive cStringUsingEncoding: NSASCIIStringEncoding],
+ false, NULL, false, false, &err);
+ });
+ handleAnyDeviceErrors(err);
+}
- gArgc = argc;
- gArgv = (char **)argv;
- int i;
+/* 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;
+ }
- /* In case we don't need to display a window, let's not do that */
- for (i = 1; i < argc; i++) {
- const char *opt = argv[i];
+ /* 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;
+ }
- if (opt[0] == '-') {
- /* Treat --foo the same as -foo. */
- if (opt[1] == '-') {
- opt++;
- }
- if (!strcmp(opt, "-h") || !strcmp(opt, "-help") ||
- !strcmp(opt, "-vnc") ||
- !strcmp(opt, "-nographic") ||
- !strcmp(opt, "-version") ||
- !strcmp(opt, "-curses") ||
- !strcmp(opt, "-qtest")) {
- return qemu_main(gArgc, gArgv, *_NSGetEnviron());
+ __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);
+
+ /* Get the path to the QEMU binary */
+ NSString *binary_name = [NSString stringWithCString: gArgv[0]
+ encoding: NSASCIIStringEncoding];
+ binary_name = [binary_name lastPathComponent];
+ NSString *program_path = [[NSString alloc] initWithFormat: @"%@/%@",
+ [[NSBundle mainBundle] bundlePath], binary_name];
+
+ /* Make the picture of QEMU */
+ NSImageView *picture_view = [[NSImageView alloc] initWithFrame:
+ picture_rect];
+ NSImage *qemu_image = [[NSWorkspace sharedWorkspace] iconForFile:
+ program_path];
+ [picture_view setImage: qemu_image];
+ [picture_view setImageScaling: NSImageScaleProportionallyUpOrDown];
+ [superView addSubview: picture_view];
+
+ /* Make the name label */
+ 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 = [[NSString alloc] initWithCString: gArgv[0]
+ encoding: NSASCIIStringEncoding];
+ qemu_name = [qemu_name 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;
}
}
}
- NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
+ // check the menu item
+ [sender setState: NSControlStateValueOn];
- // 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);
+ // 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(), '%');
+}
- [NSApplication sharedApplication];
+@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;
@@ -908,11 +1507,11 @@ int main (int argc, const char * argv[]) {
// Application menu
menu = [[NSMenu alloc] initWithTitle:@""];
- [menu addItemWithTitle:@"About QEMU" action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; // About QEMU
+ [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:(NSAlternateKeyMask|NSCommandKeyMask)];
+ [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"];
@@ -921,13 +1520,54 @@ int main (int argc, const char * argv[]) {
[[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(toggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen
+ [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
@@ -939,17 +1579,186 @@ int main (int argc, const char * argv[]) {
// Help menu
menu = [[NSMenu alloc] initWithTitle:@"Help"];
[menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Documentation" action:@selector(showQEMUDoc:) keyEquivalent:@"?"] autorelease]]; // QEMU Help
- [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Technology" action:@selector(showQEMUTec:) 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);
+}
+
+/*
+ * 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");
+ exit(status);
+}
+
+int main (int argc, const char * argv[]) {
+ QemuThread thread;
+
+ COCOA_DEBUG("Entered main()\n");
+ gArgc = argc;
+ gArgv = (char **)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];
@@ -967,17 +1776,19 @@ static void cocoa_update(DisplayChangeListener *dcl,
COCOA_DEBUG("qemu_cocoa: cocoa_update\n");
- 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];
+ 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];
}
@@ -986,9 +1797,19 @@ 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 switchSurface:surface];
+
+ // 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];
}
@@ -997,27 +1818,18 @@ static void cocoa_refresh(DisplayChangeListener *dcl)
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n");
+ graphic_hw_update(NULL);
- if (kbd_mouse_is_absolute()) {
- if (![cocoaView isAbsoluteEnabled]) {
- if ([cocoaView isMouseGrabed]) {
- [cocoaView ungrabMouse];
+ if (qemu_input_is_absolute()) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (![cocoaView isAbsoluteEnabled]) {
+ if ([cocoaView isMouseGrabbed]) {
+ [cocoaView ungrabMouse];
+ }
}
- }
- [cocoaView setAbsoluteEnabled:YES];
+ [cocoaView setAbsoluteEnabled:YES];
+ });
}
-
- NSDate *distantPast;
- NSEvent *event;
- distantPast = [NSDate distantPast];
- do {
- event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:distantPast
- inMode: NSDefaultRunLoopMode dequeue:YES];
- if (event != nil) {
- [cocoaView handleEvent:event];
- }
- } while(event != nil);
- graphic_hw_update(NULL);
[pool release];
}
@@ -1034,10 +1846,26 @@ static const DisplayChangeListenerOps dcl_ops = {
.dpy_refresh = cocoa_refresh,
};
-void cocoa_display_init(DisplayState *ds, int full_screen)
+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;
+ }
+
dcl = g_malloc0(sizeof(DisplayChangeListener));
// register vga output callbacks
@@ -1047,3 +1875,15 @@ void cocoa_display_init(DisplayState *ds, int full_screen)
// register cleanup function
atexit(cocoa_cleanup);
}
+
+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..0a6478161
--- /dev/null
+++ b/ui/console-gl.c
@@ -0,0 +1,149 @@
+/*
+ * 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));
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
+ surface_width(surface),
+ surface_height(surface),
+ 0, surface->glformat, surface->gltype,
+ surface_data(surface));
+
+ 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
index e3e82979d..53dee8e26 100644
--- a/ui/console.c
+++ b/ui/console.c
@@ -21,16 +21,22 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
-#include "qemu-common.h"
+
+#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/module.h"
+#include "qemu/option.h"
#include "qemu/timer.h"
-#include "qmp-commands.h"
-#include "sysemu/char.h"
+#include "chardev/char-fe.h"
+#include "trace.h"
+#include "exec/memory.h"
+#include "io/channel-file.h"
+#include "qom/object.h"
-//#define DEBUG_CONSOLE
#define DEFAULT_BACKSCROLL 512
-#define MAX_CONSOLES 12
#define CONSOLE_CURSOR_PERIOD 500
typedef struct TextAttributes {
@@ -121,9 +127,15 @@ struct QemuConsole {
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;
@@ -141,8 +153,6 @@ struct QemuConsole {
TextCell *cells;
int text_x[2], text_y[2], cursor_invalidate;
int echo;
- bool cursor_visible_phase;
- QEMUTimer *cursor_timer;
int update_x0;
int update_y0;
@@ -153,15 +163,18 @@ struct QemuConsole {
int esc_params[MAX_ESC_PARAMS];
int nb_esc_params;
- CharDriverState *chr;
+ Chardev *chr;
/* fifo for key pressed */
QEMUFIFO out_fifo;
uint8_t out_fifo_buf[16];
QEMUTimer *kbd_timer;
+ CoQueue dump_queue;
+
+ QTAILQ_ENTRY(QemuConsole) next;
};
struct DisplayState {
- struct QEMUTimer *gui_timer;
+ QEMUTimer *gui_timer;
uint64_t last_update;
uint64_t update_interval;
bool refreshing;
@@ -173,12 +186,16 @@ struct DisplayState {
static DisplayState *display_state;
static QemuConsole *active_console;
-static QemuConsole *consoles[MAX_CONSOLES];
-static int nb_consoles = 0;
+static QTAILQ_HEAD(, QemuConsole) consoles =
+ QTAILQ_HEAD_INITIALIZER(consoles);
+static bool cursor_visible_phase;
+static QEMUTimer *cursor_timer;
-static void text_console_do_init(CharDriverState *chr, DisplayState *ds);
+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)
{
@@ -186,7 +203,7 @@ static void gui_update(void *opaque)
uint64_t dcl_interval;
DisplayState *ds = opaque;
DisplayChangeListener *dcl;
- int i;
+ QemuConsole *con;
ds->refreshing = true;
dpy_refresh(ds);
@@ -201,15 +218,15 @@ static void gui_update(void *opaque)
}
if (ds->update_interval != interval) {
ds->update_interval = interval;
- for (i = 0; i < nb_consoles; i++) {
- if (consoles[i]->hw_ops->update_interval) {
- consoles[i]->hw_ops->update_interval(consoles[i]->hw, 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_get_clock_ms(rt_clock);
- qemu_mod_timer(ds->gui_timer, ds->last_update + 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)
@@ -232,12 +249,12 @@ static void gui_setup_refresh(DisplayState *ds)
}
if (need_timer && ds->gui_timer == NULL) {
- ds->gui_timer = qemu_new_timer_ms(rt_clock, gui_update, ds);
- qemu_mod_timer(ds->gui_timer, qemu_get_clock_ms(rt_clock));
+ 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) {
- qemu_del_timer(ds->gui_timer);
- qemu_free_timer(ds->gui_timer);
+ timer_del(ds->gui_timer);
+ timer_free(ds->gui_timer);
ds->gui_timer = NULL;
}
@@ -245,16 +262,49 @@ static void gui_setup_refresh(DisplayState *ds)
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) {
- con = active_console;
+ return;
}
- if (con && con->hw_ops->gfx_update) {
+ 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);
+ }
+}
+
+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) {
@@ -265,67 +315,103 @@ void graphic_hw_invalidate(QemuConsole *con)
}
}
-static void ppm_save(const char *filename, struct DisplaySurface *ds,
- Error **errp)
+static bool ppm_save(int fd, pixman_image_t *image, Error **errp)
{
- int width = pixman_image_get_width(ds->image);
- int height = pixman_image_get_height(ds->image);
- int fd;
- FILE *f;
+ 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;
- int ret;
- pixman_image_t *linebuf;
- trace_ppm_save(filename, ds);
- fd = qemu_open(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;
- }
- f = fdopen(fd, "wb");
- ret = fprintf(f, "P6\n%d %d\n%d\n", width, height, 255);
- if (ret < 0) {
- linebuf = NULL;
- goto write_err;
+ 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, ds->image, width, 0, y);
- clearerr(f);
- ret = fwrite(pixman_image_get_data(linebuf), 1,
- pixman_image_get_stride(linebuf), f);
- (void)ret;
- if (ferror(f)) {
- goto write_err;
+ 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;
}
}
-out:
- qemu_pixman_image_unref(linebuf);
- fclose(f);
- return;
+ return true;
+}
-write_err:
- error_setg(errp, "failed to write to file '%s': %s", filename,
- strerror(errno));
- unlink(filename);
- goto out;
+static void graphic_hw_update_bh(void *con)
+{
+ graphic_hw_update(con);
}
-void qmp_screendump(const char *filename, Error **errp)
+/* 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)
{
- QemuConsole *con = qemu_console_lookup_by_index(0);
+ g_autoptr(pixman_image_t) image = NULL;
+ QemuConsole *con;
DisplaySurface *surface;
+ int fd;
- if (con == NULL) {
- error_setg(errp, "There is no QemuConsole I can screendump from.");
- return;
+ 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;
+ }
}
- graphic_hw_update(con);
+ 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);
- ppm_save(filename, surface, errp);
+ 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)
@@ -370,78 +456,32 @@ static void vga_bitblt(QemuConsole *con,
#include "vgafont.h"
-#ifndef CONFIG_CURSES
-enum color_names {
- COLOR_BLACK = 0,
- COLOR_RED = 1,
- COLOR_GREEN = 2,
- COLOR_YELLOW = 3,
- COLOR_BLUE = 4,
- COLOR_MAGENTA = 5,
- COLOR_CYAN = 6,
- COLOR_WHITE = 7
-};
-#endif
-
#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_RGB(0x00, 0x00, 0x00), /* black */
- QEMU_RGB(0xaa, 0x00, 0x00), /* red */
- QEMU_RGB(0x00, 0xaa, 0x00), /* green */
- QEMU_RGB(0xaa, 0xaa, 0x00), /* yellow */
- QEMU_RGB(0x00, 0x00, 0xaa), /* blue */
- QEMU_RGB(0xaa, 0x00, 0xaa), /* magenta */
- QEMU_RGB(0x00, 0xaa, 0xaa), /* cyan */
- QEMU_RGB(0xaa, 0xaa, 0xaa), /* white */
+ [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_RGB(0x00, 0x00, 0x00), /* black */
- QEMU_RGB(0xff, 0x00, 0x00), /* red */
- QEMU_RGB(0x00, 0xff, 0x00), /* green */
- QEMU_RGB(0xff, 0xff, 0x00), /* yellow */
- QEMU_RGB(0x00, 0x00, 0xff), /* blue */
- QEMU_RGB(0xff, 0x00, 0xff), /* magenta */
- QEMU_RGB(0x00, 0xff, 0xff), /* cyan */
- QEMU_RGB(0xff, 0xff, 0xff), /* white */
+ [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 */
}
};
-#ifdef DEBUG_CONSOLE
-static void console_print_text_attributes(TextAttributes *t_attrib, char ch)
-{
- if (t_attrib->bold) {
- printf("b");
- } else {
- printf(" ");
- }
- if (t_attrib->uline) {
- printf("u");
- } else {
- printf(" ");
- }
- if (t_attrib->blink) {
- printf("l");
- } else {
- printf(" ");
- }
- if (t_attrib->invers) {
- printf("i");
- } else {
- printf(" ");
- }
- if (t_attrib->unvisible) {
- printf("n");
- } else {
- printf(" ");
- }
-
- printf(" fg: %d bg: %d ch:'%2X' '%c'\n", t_attrib->fgcol, t_attrib->bgcol, ch, ch);
-}
-#endif
-
static void vga_putcharxy(QemuConsole *s, int x, int y, int ch,
TextAttributes *t_attrib)
{
@@ -477,7 +517,7 @@ static void text_console_resize(QemuConsole *s)
if (s->width < w1)
w1 = s->width;
- cells = g_malloc(s->width * s->total_height * sizeof(TextCell));
+ 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) {
@@ -506,6 +546,9 @@ static inline void text_update_xy(QemuConsole *s, int x, int 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)
@@ -521,25 +564,23 @@ static void update_xy(QemuConsole *s, int x, int y)
TextCell *c;
int y1, y2;
- if (!qemu_console_is_visible(s)) {
- return;
- }
-
if (s->ds->have_text) {
text_update_xy(s, x, y);
}
- if (s->ds->have_gfx) {
- y1 = (s->y_base + y) % s->total_height;
- y2 = y1 - s->y_displayed;
- if (y2 < 0)
- y2 += s->total_height;
- if (y2 < s->height) {
- c = &s->cells[y1 * s->width + x];
- vga_putcharxy(s, x, y2, c->ch,
- &(c->t_attrib));
- invalidate_xy(s, x, y2);
+ 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);
}
}
@@ -549,33 +590,28 @@ static void console_show_cursor(QemuConsole *s, int show)
int y, y1;
int x = s->x;
- if (!qemu_console_is_visible(s)) {
- return;
- }
-
if (s->ds->have_text) {
s->cursor_invalidate = 1;
}
- if (s->ds->have_gfx) {
- 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 && s->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);
+ 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);
}
}
@@ -585,10 +621,6 @@ static void console_refresh(QemuConsole *s)
TextCell *c;
int x, y, y1;
- if (!qemu_console_is_visible(s)) {
- return;
- }
-
if (s->ds->have_text) {
s->text_x[0] = 0;
s->text_y[0] = 0;
@@ -597,25 +629,23 @@ static void console_refresh(QemuConsole *s)
s->cursor_invalidate = 1;
}
- if (s->ds->have_gfx) {
- vga_fill_rect(s, 0, 0, surface_width(surface), surface_height(surface),
- color_table_rgb[0][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;
- }
+ 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));
}
+ 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)
@@ -671,7 +701,7 @@ static void console_put_lf(QemuConsole *s)
c->t_attrib = s->t_attrib_default;
c++;
}
- if (qemu_console_is_visible(s) && s->y_displayed == s->y_base) {
+ if (s->y_displayed == s->y_base) {
if (s->ds->have_text) {
s->text_x[0] = 0;
s->text_y[0] = 0;
@@ -679,18 +709,16 @@ static void console_put_lf(QemuConsole *s)
s->text_y[1] = s->height - 1;
}
- if (s->ds->have_gfx) {
- 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;
- }
+ 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;
}
}
}
@@ -740,53 +768,53 @@ static void console_handle_escape(QemuConsole *s)
break;
/* set foreground color */
case 30:
- s->t_attrib.fgcol=COLOR_BLACK;
+ s->t_attrib.fgcol = QEMU_COLOR_BLACK;
break;
case 31:
- s->t_attrib.fgcol=COLOR_RED;
+ s->t_attrib.fgcol = QEMU_COLOR_RED;
break;
case 32:
- s->t_attrib.fgcol=COLOR_GREEN;
+ s->t_attrib.fgcol = QEMU_COLOR_GREEN;
break;
case 33:
- s->t_attrib.fgcol=COLOR_YELLOW;
+ s->t_attrib.fgcol = QEMU_COLOR_YELLOW;
break;
case 34:
- s->t_attrib.fgcol=COLOR_BLUE;
+ s->t_attrib.fgcol = QEMU_COLOR_BLUE;
break;
case 35:
- s->t_attrib.fgcol=COLOR_MAGENTA;
+ s->t_attrib.fgcol = QEMU_COLOR_MAGENTA;
break;
case 36:
- s->t_attrib.fgcol=COLOR_CYAN;
+ s->t_attrib.fgcol = QEMU_COLOR_CYAN;
break;
case 37:
- s->t_attrib.fgcol=COLOR_WHITE;
+ s->t_attrib.fgcol = QEMU_COLOR_WHITE;
break;
/* set background color */
case 40:
- s->t_attrib.bgcol=COLOR_BLACK;
+ s->t_attrib.bgcol = QEMU_COLOR_BLACK;
break;
case 41:
- s->t_attrib.bgcol=COLOR_RED;
+ s->t_attrib.bgcol = QEMU_COLOR_RED;
break;
case 42:
- s->t_attrib.bgcol=COLOR_GREEN;
+ s->t_attrib.bgcol = QEMU_COLOR_GREEN;
break;
case 43:
- s->t_attrib.bgcol=COLOR_YELLOW;
+ s->t_attrib.bgcol = QEMU_COLOR_YELLOW;
break;
case 44:
- s->t_attrib.bgcol=COLOR_BLUE;
+ s->t_attrib.bgcol = QEMU_COLOR_BLUE;
break;
case 45:
- s->t_attrib.bgcol=COLOR_MAGENTA;
+ s->t_attrib.bgcol = QEMU_COLOR_MAGENTA;
break;
case 46:
- s->t_attrib.bgcol=COLOR_CYAN;
+ s->t_attrib.bgcol = QEMU_COLOR_CYAN;
break;
case 47:
- s->t_attrib.bgcol=COLOR_WHITE;
+ s->t_attrib.bgcol = QEMU_COLOR_WHITE;
break;
}
}
@@ -795,12 +823,40 @@ static void console_handle_escape(QemuConsole *s)
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)
{
@@ -823,9 +879,9 @@ static void set_cursor(QemuConsole *s, int x, int y)
static void console_putchar(QemuConsole *s, int ch)
{
- TextCell *c;
- int y1, i;
+ int i;
int x, y;
+ char response[40];
switch(s->state) {
case TTY_STATE_NORM:
@@ -861,17 +917,7 @@ static void console_putchar(QemuConsole *s, int ch)
s->state = TTY_STATE_ESC;
break;
default:
- 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++;
+ console_put_one(s, ch);
break;
}
break;
@@ -897,12 +943,11 @@ static void console_putchar(QemuConsole *s, int ch)
} else {
if (s->nb_esc_params < MAX_ESC_PARAMS)
s->nb_esc_params++;
- if (ch == ';')
+ if (ch == ';' || ch == '?') {
break;
-#ifdef DEBUG_CONSOLE
- fprintf(stderr, "escape sequence CSI%d;%d%c, %d parameters\n",
- s->esc_params[0], s->esc_params[1], ch, s->nb_esc_params);
-#endif
+ }
+ 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':
@@ -986,7 +1031,7 @@ static void console_putchar(QemuConsole *s, int ch)
break;
case 1:
/* clear from beginning of line */
- for (x = 0; x <= s->x; x++) {
+ for (x = 0; x <= s->x && x < s->width; x++) {
console_clear_xy(s, x, s->y);
}
break;
@@ -1002,8 +1047,19 @@ static void console_putchar(QemuConsole *s, int ch)
console_handle_escape(s);
break;
case 'n':
- /* report cursor position */
- /* TODO: send ESC[row;colR */
+ 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 */
@@ -1016,9 +1072,7 @@ static void console_putchar(QemuConsole *s, int ch)
s->y = s->y_saved;
break;
default:
-#ifdef DEBUG_CONSOLE
- fprintf(stderr, "unhandled escape character '%c'\n", ch);
-#endif
+ trace_console_putchar_unhandled(ch);
break;
}
break;
@@ -1031,17 +1085,11 @@ void console_select(unsigned int index)
DisplayChangeListener *dcl;
QemuConsole *s;
- if (index >= MAX_CONSOLES)
- return;
-
trace_console_select(index);
s = qemu_console_lookup_by_index(index);
if (s) {
DisplayState *ds = s->ds;
- if (active_console && active_console->cursor_timer) {
- qemu_del_timer(active_console->cursor_timer);
- }
active_console = s;
if (ds->have_gfx) {
QLIST_FOREACH(dcl, &ds->listeners, next) {
@@ -1052,24 +1100,38 @@ void console_select(unsigned int index)
dcl->ops->dpy_gfx_switch(dcl, s->surface);
}
}
- dpy_gfx_update(s, 0, 0, surface_width(s->surface),
- surface_height(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);
}
- if (s->cursor_timer) {
- qemu_mod_timer(s->cursor_timer,
- qemu_get_clock_ms(rt_clock) + CONSOLE_CURSOR_PERIOD / 2);
- }
+ text_console_update_cursor(NULL);
}
}
-static int console_puts(CharDriverState *chr, const uint8_t *buf, int len)
+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)
{
- QemuConsole *s = chr->opaque;
+ 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;
@@ -1105,18 +1167,17 @@ static void kbd_send_chars(void *opaque)
/* characters are pending: we send them a bit later (XXX:
horrible, should change char device API) */
if (s->out_fifo.count > 0) {
- qemu_mod_timer(s->kbd_timer, qemu_get_clock_ms(rt_clock) + 1);
+ timer_mod(s->kbd_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1);
}
}
/* called when an ascii key is pressed */
-void kbd_put_keysym(int keysym)
+void kbd_put_keysym_console(QemuConsole *s, int keysym)
{
- QemuConsole *s;
uint8_t buf[16], *q;
+ CharBackend *be;
int c;
- s = active_console;
if (!s || (s->console_type == GRAPHIC_CONSOLE))
return;
@@ -1149,15 +1210,16 @@ void kbd_put_keysym(int keysym)
*q++ = '[';
*q++ = keysym & 0xff;
} else if (s->echo && (keysym == '\r' || keysym == '\n')) {
- console_puts(s->chr, (const uint8_t *) "\r", 1);
+ vc_chr_write(s->chr, (const uint8_t *) "\r", 1);
*q++ = '\n';
} else {
*q++ = keysym;
}
if (s->echo) {
- console_puts(s->chr, buf, q - buf);
+ vc_chr_write(s->chr, buf, q - buf);
}
- if (s->chr->chr_read) {
+ be = s->chr->be;
+ if (be && be->chr_read) {
qemu_fifo_write(&s->out_fifo, buf, q - buf);
kbd_send_chars(s);
}
@@ -1165,6 +1227,56 @@ void kbd_put_keysym(int keysym)
}
}
+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;
@@ -1184,11 +1296,13 @@ static void text_console_update(void *opaque, console_ch_t *chardata)
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 ++, s->cells[src].ch |
- (s->cells[src].t_attrib.fgcol << 12) |
- (s->cells[src].t_attrib.bgcol << 8) |
- (s->cells[src].t_attrib.bold << 21));
+ 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;
@@ -1202,20 +1316,23 @@ static void text_console_update(void *opaque, console_ch_t *chardata)
}
}
-static QemuConsole *new_console(DisplayState *ds, console_type_t console_type)
+static QemuConsole *new_console(DisplayState *ds, console_type_t console_type,
+ uint32_t head)
{
- Error *local_err = NULL;
Object *obj;
QemuConsole *s;
int i;
- if (nb_consoles >= MAX_CONSOLES)
- return NULL;
-
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, &local_err);
+ (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))) {
@@ -1223,96 +1340,106 @@ static QemuConsole *new_console(DisplayState *ds, console_type_t console_type)
}
s->ds = ds;
s->console_type = console_type;
- if (console_type != GRAPHIC_CONSOLE) {
- s->index = nb_consoles;
- consoles[nb_consoles++] = s;
+ s->window_id = -1;
+
+ if (QTAILQ_EMPTY(&consoles)) {
+ s->index = 0;
+ QTAILQ_INSERT_TAIL(&consoles, s, next);
+ } else if (console_type != GRAPHIC_CONSOLE || qdev_hotplug) {
+ QemuConsole *last = QTAILQ_LAST(&consoles);
+ s->index = last->index + 1;
+ QTAILQ_INSERT_TAIL(&consoles, s, next);
} else {
- /* HACK: Put graphical consoles before text consoles. */
- for (i = nb_consoles; i > 0; i--) {
- if (consoles[i - 1]->console_type == GRAPHIC_CONSOLE)
- break;
- consoles[i] = consoles[i - 1];
- consoles[i]->index = i;
+ /*
+ * 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;
+ }
}
- s->index = i;
- consoles[i] = s;
- nb_consoles++;
}
return s;
}
-static void qemu_alloc_display(DisplaySurface *surface, int width, int height,
- int linesize, PixelFormat pf, int newflags)
+static void qemu_alloc_display(DisplaySurface *surface, int width, int height)
{
- surface->pf = pf;
-
qemu_pixman_image_unref(surface->image);
surface->image = NULL;
- surface->format = qemu_pixman_get_format(&pf);
- assert(surface->format != 0);
+ surface->format = PIXMAN_x8r8g8b8;
surface->image = pixman_image_create_bits(surface->format,
width, height,
- NULL, linesize);
+ NULL, width * 4);
assert(surface->image != NULL);
- surface->flags = newflags | QEMU_ALLOCATED_FLAG;
-#ifdef HOST_WORDS_BIGENDIAN
- surface->flags |= QEMU_BIG_ENDIAN_FLAG;
-#endif
+ surface->flags = QEMU_ALLOCATED_FLAG;
}
DisplaySurface *qemu_create_displaysurface(int width, int height)
{
DisplaySurface *surface = g_new0(DisplaySurface, 1);
- int linesize = width * 4;
trace_displaysurface_create(surface, width, height);
- qemu_alloc_display(surface, width, height, linesize,
- qemu_default_pixelformat(32), 0);
+ qemu_alloc_display(surface, width, height);
return surface;
}
-DisplaySurface *qemu_create_displaysurface_from(int width, int height, int bpp,
- int linesize, uint8_t *data,
- bool byteswap)
+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, bpp, byteswap);
- if (byteswap) {
- surface->pf = qemu_different_endianness_pixelformat(bpp);
- } else {
- surface->pf = qemu_default_pixelformat(bpp);
- }
-
- surface->format = qemu_pixman_get_format(&surface->pf);
- assert(surface->format != 0);
+ 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);
-#ifdef HOST_WORDS_BIGENDIAN
- surface->flags = QEMU_BIG_ENDIAN_FLAG;
-#endif
+ 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;
}
-static DisplaySurface *qemu_create_dummy_surface(void)
+DisplaySurface *qemu_create_message_surface(int w, int h,
+ const char *msg)
{
- static const char msg[] =
- "This VM has no graphic display device.";
- DisplaySurface *surface = qemu_create_displaysurface(640, 480);
- pixman_color_t bg = color_table_rgb[0][COLOR_BLACK];
- pixman_color_t fg = color_table_rgb[0][COLOR_WHITE];
+ 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 = (640/FONT_WIDTH - len) / 2;
- y = (480/FONT_HEIGHT - 1) / 2;
+ 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,
@@ -1332,11 +1459,36 @@ void qemu_free_displaysurface(DisplaySurface *surface)
g_free(surface);
}
+bool console_has_gl(QemuConsole *con)
+{
+ return con->gl != NULL;
+}
+
+bool console_has_gl_dmabuf(QemuConsole *con)
+{
+ return con->gl != NULL && con->gl->ops->dpy_gl_scanout_dmabuf != NULL;
+}
+
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;
+ }
+
trace_displaychangelistener_register(dcl, dcl->ops->dpy_name);
dcl->ds = get_alloc_displaystate();
QLIST_INSERT_HEAD(&dcl->ds->listeners, dcl, next);
@@ -1352,11 +1504,12 @@ void register_displaychangelistener(DisplayChangeListener *dcl)
dcl->ops->dpy_gfx_switch(dcl, con->surface);
} else {
if (!dummy) {
- dummy = qemu_create_dummy_surface();
+ dummy = qemu_create_message_surface(640, 480, nodev);
}
dcl->ops->dpy_gfx_switch(dcl, dummy);
}
}
+ text_console_update_cursor(NULL);
}
void update_displaychangelistener(DisplayChangeListener *dcl,
@@ -1366,7 +1519,7 @@ void update_displaychangelistener(DisplayChangeListener *dcl,
dcl->update_interval = interval;
if (!ds->refreshing && ds->update_interval > interval) {
- qemu_mod_timer(ds->gui_timer, ds->last_update + interval);
+ timer_mod(ds->gui_timer, ds->last_update + interval);
}
}
@@ -1378,16 +1531,62 @@ void unregister_displaychangelistener(DisplayChangeListener *dcl)
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)
+{
+ return con->hw_ops->ui_info != NULL;
+}
+
+const QemuUIInfo *dpy_get_ui_info(const QemuConsole *con)
+{
+ assert(con != NULL);
+
+ return &con->ui_info;
+}
+
+int dpy_set_ui_info(QemuConsole *con, QemuUIInfo *info)
+{
+ assert(con != NULL);
+
+ 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 = surface_width(con->surface);
- int height = surface_height(con->surface);
+ 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);
@@ -1408,6 +1607,16 @@ void dpy_gfx_update(QemuConsole *con, int x, int y, int w, int 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)
{
@@ -1415,6 +1624,8 @@ void dpy_gfx_replace_surface(QemuConsole *con,
DisplaySurface *old_surface = con->surface;
DisplayChangeListener *dcl;
+ assert(old_surface != surface || surface == NULL);
+
con->surface = surface;
QLIST_FOREACH(dcl, &s->listeners, next) {
if (con != (dcl->con ? dcl->con : active_console)) {
@@ -1427,34 +1638,38 @@ void dpy_gfx_replace_surface(QemuConsole *con,
qemu_free_displaysurface(old_surface);
}
-void dpy_refresh(DisplayState *s)
+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->ops->dpy_refresh) {
- dcl->ops->dpy_refresh(dcl);
+ 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 whitelist native 32 bpp only */
+ if (format != qemu_default_pixman_format(32, true)) {
+ return false;
+ }
}
}
+ return true;
}
-void dpy_gfx_copy(QemuConsole *con, int src_x, int src_y,
- int dst_x, int dst_y, int w, int h)
+static void dpy_refresh(DisplayState *s)
{
- 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_gfx_copy) {
- dcl->ops->dpy_gfx_copy(dcl, src_x, src_y, dst_x, dst_y, w, h);
- } else { /* TODO */
- dcl->ops->dpy_gfx_update(dcl, dst_x, dst_y, w, h);
+ if (dcl->ops->dpy_refresh) {
+ dcl->ops->dpy_refresh(dcl);
}
}
}
@@ -1498,7 +1713,7 @@ void dpy_text_update(QemuConsole *con, int x, int y, int w, int h)
void dpy_text_resize(QemuConsole *con, int w, int h)
{
DisplayState *s = con->ds;
- struct DisplayChangeListener *dcl;
+ DisplayChangeListener *dcl;
if (!qemu_console_is_visible(con)) {
return;
@@ -1562,6 +1777,102 @@ bool dpy_cursor_define_supported(QemuConsole *con)
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);
+}
+
+QEMUGLContext dpy_gl_ctx_get_current(QemuConsole *con)
+{
+ assert(con->gl);
+ return con->gl->ops->dpy_gl_ctx_get_current(con->gl);
+}
+
+void dpy_gl_scanout_disable(QemuConsole *con)
+{
+ assert(con->gl);
+ if (con->gl->ops->dpy_gl_scanout_disable) {
+ con->gl->ops->dpy_gl_scanout_disable(con->gl);
+ } else {
+ con->gl->ops->dpy_gl_scanout_texture(con->gl, 0, false, 0, 0,
+ 0, 0, 0, 0);
+ }
+}
+
+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 */
@@ -1570,6 +1881,8 @@ 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;
}
@@ -1580,79 +1893,173 @@ static DisplayState *get_alloc_displaystate(void)
*/
DisplayState *init_displaystate(void)
{
- Error *local_err = NULL;
gchar *name;
- int i;
-
- if (!display_state) {
- display_state = g_new0(DisplayState, 1);
- }
+ QemuConsole *con;
- for (i = 0; i < nb_consoles; i++) {
- if (consoles[i]->console_type != GRAPHIC_CONSOLE &&
- consoles[i]->ds == NULL) {
- text_console_do_init(consoles[i]->chr, display_state);
+ 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]", i);
+ name = g_strdup_printf("console[%d]", con->index);
object_property_add_child(container_get(object_get_root(), "/backend"),
- name, OBJECT(consoles[i]), &local_err);
+ name, OBJECT(con));
g_free(name);
}
return display_state;
}
-QemuConsole *graphic_console_init(DeviceState *dev,
+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)
{
- Error *local_err = NULL;
+ 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();
- trace_console_gfx_new();
- s = new_console(ds, GRAPHIC_CONSOLE);
- s->hw_ops = hw_ops;
- s->hw = opaque;
+ 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), OBJECT(dev),
- "device", &local_err);
+ object_property_set_link(OBJECT(s), "device", OBJECT(dev),
+ &error_abort);
}
- s->surface = qemu_create_displaysurface(width, height);
+ surface = qemu_create_message_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_message_surface(width, height, unplugged);
+ dpy_gfx_replace_surface(con, surface);
+}
+
QemuConsole *qemu_console_lookup_by_index(unsigned int index)
{
- if (index >= MAX_CONSOLES) {
+ 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 consoles[index];
+
+ return con;
}
-QemuConsole *qemu_console_lookup_by_device(DeviceState *dev)
+QemuConsole *qemu_console_lookup_unused(void)
{
- Error *local_err = NULL;
+ QemuConsole *con;
Object *obj;
- int i;
- for (i = 0; i < nb_consoles; i++) {
- if (!consoles[i]) {
+ QTAILQ_FOREACH(con, &consoles, next) {
+ if (con->hw_ops != &unused_ops) {
continue;
}
- obj = object_property_get_link(OBJECT(consoles[i]),
- "device", &local_err);
- if (DEVICE(obj) == dev) {
- return consoles[i];
+ obj = object_property_get_link(OBJECT(con),
+ "device", &error_abort);
+ if (obj != NULL) {
+ continue;
}
+ return con;
}
return NULL;
}
@@ -1678,21 +2085,98 @@ bool qemu_console_is_fixedsize(QemuConsole *con)
return con && (con->console_type != TEXT_CONSOLE);
}
-static void text_console_set_echo(CharDriverState *chr, bool echo)
+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;
+}
+
+QemuUIInfo *qemu_console_get_ui_info(QemuConsole *con)
+{
+ assert(con != NULL);
+ return &con->ui_info;
+}
+
+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_set_echo(Chardev *chr, bool echo)
{
- QemuConsole *s = chr->opaque;
+ 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 = 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);
+ }
- s->cursor_visible_phase = !s->cursor_visible_phase;
- graphic_hw_invalidate(s);
- qemu_mod_timer(s->cursor_timer,
- qemu_get_clock_ms(rt_clock) + CONSOLE_CURSOR_PERIOD / 2);
+ if (count) {
+ text_console_update_cursor_timer();
+ }
}
static const GraphicHwOps text_console_ops = {
@@ -1700,19 +2184,16 @@ static const GraphicHwOps text_console_ops = {
.text_update = text_console_update,
};
-static void text_console_do_init(CharDriverState *chr, DisplayState *ds)
+static void text_console_do_init(Chardev *chr, DisplayState *ds)
{
- QemuConsole *s;
+ VCChardev *drv = VC_CHARDEV(chr);
+ QemuConsole *s = drv->console;
int g_width = 80 * FONT_WIDTH;
int g_height = 24 * FONT_HEIGHT;
- s = chr->opaque;
-
- chr->chr_write = console_puts;
-
s->out_fifo.buf = s->out_fifo_buf;
s->out_fifo.buf_size = sizeof(s->out_fifo_buf);
- s->kbd_timer = qemu_new_timer_ms(rt_clock, kbd_send_chars, s);
+ s->kbd_timer = timer_new_ms(QEMU_CLOCK_REALTIME, kbd_send_chars, s);
s->ds = ds;
s->y_displayed = 0;
@@ -1728,9 +2209,6 @@ static void text_console_do_init(CharDriverState *chr, DisplayState *ds)
s->surface = qemu_create_displaysurface(g_width, g_height);
}
- s->cursor_timer =
- qemu_new_timer_ms(rt_clock, text_console_update_cursor, s);
-
s->hw_ops = &text_console_ops;
s->hw = s;
@@ -1740,36 +2218,36 @@ static void text_console_do_init(CharDriverState *chr, DisplayState *ds)
s->t_attrib_default.blink = 0;
s->t_attrib_default.invers = 0;
s->t_attrib_default.unvisible = 0;
- s->t_attrib_default.fgcol = COLOR_WHITE;
- s->t_attrib_default.bgcol = COLOR_BLACK;
+ 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[128];
- int len;
+ char *msg;
- s->t_attrib.bgcol = COLOR_BLUE;
- len = snprintf(msg, sizeof(msg), "%s console\r\n", chr->label);
- console_puts(chr, (uint8_t*)msg, len);
+ 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_generic_open(chr);
- if (chr->init)
- chr->init(chr);
+ qemu_chr_be_event(chr, CHR_EVENT_OPENED);
}
-static CharDriverState *text_console_init(ChardevVC *vc)
+static void vc_chr_open(Chardev *chr,
+ ChardevBackend *backend,
+ bool *be_opened,
+ Error **errp)
{
- CharDriverState *chr;
+ ChardevVC *vc = backend->u.vc.data;
+ VCChardev *drv = VC_CHARDEV(chr);
QemuConsole *s;
unsigned width = 0;
unsigned height = 0;
- chr = g_malloc0(sizeof(CharDriverState));
-
if (vc->has_width) {
width = vc->width;
} else if (vc->has_cols) {
@@ -1784,41 +2262,28 @@ static CharDriverState *text_console_init(ChardevVC *vc)
trace_console_txt_new(width, height);
if (width == 0 || height == 0) {
- s = new_console(NULL, TEXT_CONSOLE);
+ s = new_console(NULL, TEXT_CONSOLE, 0);
} else {
- s = new_console(NULL, TEXT_CONSOLE_FIXED_SIZE);
+ s = new_console(NULL, TEXT_CONSOLE_FIXED_SIZE, 0);
s->surface = qemu_create_displaysurface(width, height);
}
if (!s) {
- g_free(chr);
- return NULL;
+ error_setg(errp, "cannot create text console");
+ return;
}
s->chr = chr;
- chr->opaque = s;
- chr->chr_set_echo = text_console_set_echo;
- /* console/chardev init sometimes completes elsewhere in a 2nd
- * stage, so defer OPENED events until they are fully initialized
- */
- chr->explicit_be_open = true;
+ drv->console = s;
if (display_state) {
text_console_do_init(chr, display_state);
}
- return chr;
-}
-
-static VcHandler *vc_handler = text_console_init;
-CharDriverState *vc_init(ChardevVC *vc)
-{
- return vc_handler(vc);
-}
-
-void register_vc_handler(VcHandler *handler)
-{
- vc_handler = handler;
+ /* 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)
@@ -1826,179 +2291,135 @@ 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);
}
-void qemu_console_copy(QemuConsole *con, int src_x, int src_y,
- int dst_x, int dst_y, int w, int h)
-{
- assert(con->console_type == GRAPHIC_CONSOLE);
- dpy_gfx_copy(con, src_x, src_y, dst_x, dst_y, w, h);
-}
-
DisplaySurface *qemu_console_surface(QemuConsole *console)
{
return console->surface;
}
-DisplayState *qemu_console_displaystate(QemuConsole *console)
+PixelFormat qemu_default_pixelformat(int bpp)
{
- return console->ds;
+ pixman_format_code_t fmt = qemu_default_pixman_format(bpp, true);
+ PixelFormat pf = qemu_pixelformat_from_pixman(fmt);
+ return pf;
}
-PixelFormat qemu_different_endianness_pixelformat(int bpp)
-{
- PixelFormat pf;
+static QemuDisplay *dpys[DISPLAY_TYPE__MAX];
- memset(&pf, 0x00, sizeof(PixelFormat));
+void qemu_display_register(QemuDisplay *ui)
+{
+ assert(ui->type < DISPLAY_TYPE__MAX);
+ dpys[ui->type] = ui;
+}
- pf.bits_per_pixel = bpp;
- pf.bytes_per_pixel = DIV_ROUND_UP(bpp, 8);
- pf.depth = bpp == 32 ? 24 : bpp;
+bool qemu_display_find_default(DisplayOptions *opts)
+{
+ static DisplayType prio[] = {
+ DISPLAY_TYPE_GTK,
+ DISPLAY_TYPE_SDL,
+ DISPLAY_TYPE_COCOA
+ };
+ int i;
- switch (bpp) {
- case 24:
- pf.rmask = 0x000000FF;
- pf.gmask = 0x0000FF00;
- pf.bmask = 0x00FF0000;
- pf.rmax = 255;
- pf.gmax = 255;
- pf.bmax = 255;
- pf.rshift = 0;
- pf.gshift = 8;
- pf.bshift = 16;
- pf.rbits = 8;
- pf.gbits = 8;
- pf.bbits = 8;
- break;
- case 32:
- pf.rmask = 0x0000FF00;
- pf.gmask = 0x00FF0000;
- pf.bmask = 0xFF000000;
- pf.amask = 0x00000000;
- pf.amax = 255;
- pf.rmax = 255;
- pf.gmax = 255;
- pf.bmax = 255;
- pf.ashift = 0;
- pf.rshift = 8;
- pf.gshift = 16;
- pf.bshift = 24;
- pf.rbits = 8;
- pf.gbits = 8;
- pf.bbits = 8;
- pf.abits = 8;
- break;
- default:
- break;
+ for (i = 0; i < 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 pf;
+ return false;
}
-PixelFormat qemu_default_pixelformat(int bpp)
+void qemu_display_early_init(DisplayOptions *opts)
{
- PixelFormat pf;
+ 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);
+ }
+}
- memset(&pf, 0x00, sizeof(PixelFormat));
+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);
+}
- pf.bits_per_pixel = bpp;
- pf.bytes_per_pixel = DIV_ROUND_UP(bpp, 8);
- pf.depth = bpp == 32 ? 24 : bpp;
+void qemu_display_help(void)
+{
+ int idx;
- switch (bpp) {
- case 15:
- pf.bits_per_pixel = 16;
- pf.rmask = 0x00007c00;
- pf.gmask = 0x000003E0;
- pf.bmask = 0x0000001F;
- pf.rmax = 31;
- pf.gmax = 31;
- pf.bmax = 31;
- pf.rshift = 10;
- pf.gshift = 5;
- pf.bshift = 0;
- pf.rbits = 5;
- pf.gbits = 5;
- pf.bbits = 5;
- break;
- case 16:
- pf.rmask = 0x0000F800;
- pf.gmask = 0x000007E0;
- pf.bmask = 0x0000001F;
- pf.rmax = 31;
- pf.gmax = 63;
- pf.bmax = 31;
- pf.rshift = 11;
- pf.gshift = 5;
- pf.bshift = 0;
- pf.rbits = 5;
- pf.gbits = 6;
- pf.bbits = 5;
- break;
- case 24:
- pf.rmask = 0x00FF0000;
- pf.gmask = 0x0000FF00;
- pf.bmask = 0x000000FF;
- pf.rmax = 255;
- pf.gmax = 255;
- pf.bmax = 255;
- pf.rshift = 16;
- pf.gshift = 8;
- pf.bshift = 0;
- pf.rbits = 8;
- pf.gbits = 8;
- pf.bbits = 8;
- break;
- case 32:
- pf.rmask = 0x00FF0000;
- pf.gmask = 0x0000FF00;
- pf.bmask = 0x000000FF;
- pf.rmax = 255;
- pf.gmax = 255;
- pf.bmax = 255;
- pf.rshift = 16;
- pf.gshift = 8;
- pf.bshift = 0;
- pf.rbits = 8;
- pf.gbits = 8;
- pf.bbits = 8;
- break;
- default:
- break;
+ 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));
+ }
}
- return pf;
}
-static void qemu_chr_parse_vc(QemuOpts *opts, ChardevBackend *backend,
- Error **errp)
+void qemu_chr_parse_vc(QemuOpts *opts, ChardevBackend *backend, Error **errp)
{
int val;
+ ChardevVC *vc;
- backend->vc = g_new0(ChardevVC, 1);
+ 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) {
- backend->vc->has_width = true;
- backend->vc->width = val;
+ vc->has_width = true;
+ vc->width = val;
}
val = qemu_opt_get_number(opts, "height", 0);
if (val != 0) {
- backend->vc->has_height = true;
- backend->vc->height = val;
+ vc->has_height = true;
+ vc->height = val;
}
val = qemu_opt_get_number(opts, "cols", 0);
if (val != 0) {
- backend->vc->has_cols = true;
- backend->vc->cols = val;
+ vc->has_cols = true;
+ vc->cols = val;
}
val = qemu_opt_get_number(opts, "rows", 0);
if (val != 0) {
- backend->vc->has_rows = true;
- backend->vc->rows = val;
+ vc->has_rows = true;
+ vc->rows = val;
}
}
@@ -2009,12 +2430,34 @@ static const TypeInfo qemu_console_info = {
.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_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);
- register_char_driver_qapi("vc", CHARDEV_BACKEND_KIND_VC,
- qemu_chr_parse_vc);
}
type_init(register_types);
diff --git a/ui/curses.c b/ui/curses.c
index 289a9558d..e4f9588c3 100644
--- a/ui/curses.c
+++ b/ui/curses.c
@@ -21,34 +21,72 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
-#include <curses.h>
+
+#include "qemu/osdep.h"
#ifndef _WIN32
#include <sys/ioctl.h>
#include <termios.h>
#endif
+#include <locale.h>
+#include <wchar.h>
+#include <iconv.h>
-#include "qemu-common.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
#include "ui/console.h"
+#include "ui/input.h"
#include "sysemu/sysemu.h"
+/* 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[160 * 100];
+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)
{
- chtype *line;
-
- line = ((chtype *) screen) + y * width;
- for (h += y; y < h; y ++, line += width)
- mvwaddchnstr(screenpad, y, 0, line, width);
+ 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();
@@ -106,9 +144,9 @@ static void curses_resize(DisplayChangeListener *dcl,
curses_calc_pad();
}
-#ifndef _WIN32
-#if defined(SIGWINCH) && defined(KEY_RESIZE)
-static void curses_winch_handler(int signum)
+#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;
@@ -117,18 +155,34 @@ static void curses_winch_handler(int signum)
unsigned short ws_ypixel; /* unused */
} ws;
- /* terminal size changed */
- if (ioctl(1, TIOCGWINSZ, &ws) == -1)
+ if (!got_sigwinch) {
+ return;
+ }
+ got_sigwinch = false;
+
+ if (ioctl(1, TIOCGWINSZ, &ws) == -1) {
return;
+ }
resize_term(ws.ws_row, ws.ws_col);
- curses_calc_pad();
invalidate = 1;
+}
- /* some systems require this */
- signal(SIGWINCH, curses_winch_handler);
+static void curses_winch_handler(int signum)
+{
+ got_sigwinch = true;
}
-#endif
+
+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,
@@ -159,9 +213,58 @@ static void curses_cursor_position(DisplayChangeListener *dcl,
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, nextchr, keysym, keycode, keycode_alt;
+ int chr, keysym, keycode, keycode_alt;
+ enum maybe_keycode maybe_keycode = CURSES_KEYCODE;
+
+ curses_winch_check();
if (invalidate) {
clear();
@@ -173,22 +276,16 @@ static void curses_refresh(DisplayChangeListener *dcl)
graphic_hw_text_update(NULL, screen);
- nextchr = ERR;
while (1) {
/* while there are any pending key strokes to process */
- if (nextchr == ERR)
- chr = getch();
- else {
- chr = nextchr;
- nextchr = ERR;
- }
+ chr = console_getch(&maybe_keycode);
- if (chr == ERR)
+ if (chr == -1)
break;
#ifdef KEY_RESIZE
/* this shouldn't occur when we use a custom SIGWINCH handler */
- if (chr == KEY_RESIZE) {
+ if (maybe_keycode != CURSES_CHAR && chr == KEY_RESIZE) {
clear();
refresh();
curses_calc_pad();
@@ -197,18 +294,19 @@ static void curses_refresh(DisplayChangeListener *dcl)
}
#endif
- keycode = curses2keycode[chr];
+ keycode = curses2keycode(chr, maybe_keycode);
keycode_alt = 0;
- /* alt key */
+ /* alt or esc key */
if (keycode == 1) {
- nextchr = getch();
+ enum maybe_keycode next_maybe_keycode = CURSES_KEYCODE;
+ int nextchr = console_getch(&next_maybe_keycode);
- if (nextchr != ERR) {
+ if (nextchr != -1) {
chr = nextchr;
+ maybe_keycode = next_maybe_keycode;
keycode_alt = ALT;
- keycode = curses2keycode[nextchr];
- nextchr = ERR;
+ keycode = curses2keycode(chr, maybe_keycode);
if (keycode != -1) {
keycode |= ALT;
@@ -228,9 +326,7 @@ static void curses_refresh(DisplayChangeListener *dcl)
}
if (kbd_layout) {
- keysym = -1;
- if (chr < CURSES_KEYS)
- keysym = curses2keysym[chr];
+ keysym = curses2keysym(chr, maybe_keycode);
if (keysym == -1) {
if (chr < ' ') {
@@ -242,7 +338,8 @@ static void curses_refresh(DisplayChangeListener *dcl)
keysym = chr;
}
- keycode = keysym2scancode(kbd_layout, keysym & KEYSYM_MASK);
+ keycode = keysym2scancode(kbd_layout, keysym & KEYSYM_MASK,
+ NULL, false);
if (keycode == 0)
continue;
@@ -256,34 +353,46 @@ static void curses_refresh(DisplayChangeListener *dcl)
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)
- kbd_put_keycode(SHIFT_CODE);
- if (keycode & CNTRL)
- kbd_put_keycode(CNTRL_CODE);
- if (keycode & ALT)
- kbd_put_keycode(ALT_CODE);
+ 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) {
- kbd_put_keycode(SCANCODE_EMUL0);
- kbd_put_keycode(ALT_CODE);
+ qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, true);
+ qemu_input_event_send_key_delay(0);
}
- if (keycode & GREY)
- kbd_put_keycode(GREY_CODE);
- kbd_put_keycode(keycode & KEY_MASK);
- if (keycode & GREY)
- kbd_put_keycode(GREY_CODE);
- kbd_put_keycode((keycode & KEY_MASK) | KEY_RELEASE);
+
+ 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) {
- kbd_put_keycode(SCANCODE_EMUL0);
- kbd_put_keycode(ALT_CODE | KEY_RELEASE);
+ 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);
}
- if (keycode & ALT)
- kbd_put_keycode(ALT_CODE | KEY_RELEASE);
- if (keycode & CNTRL)
- kbd_put_keycode(CNTRL_CODE | KEY_RELEASE);
- if (keycode & SHIFT)
- kbd_put_keycode(SHIFT_CODE | KEY_RELEASE);
} else {
- keysym = curses2qemu[chr];
+ keysym = curses2qemu(chr, maybe_keycode);
if (keysym == -1)
keysym = chr;
@@ -295,13 +404,332 @@ static void curses_refresh(DisplayChangeListener *dcl)
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] = {
- COLOR_BLACK, COLOR_BLUE, COLOR_GREEN, COLOR_CYAN,
- COLOR_RED, COLOR_MAGENTA, COLOR_YELLOW, COLOR_WHITE,
+ [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
@@ -309,9 +737,18 @@ static void curses_setup(void)
initscr(); noecho(); intrflush(stdscr, FALSE);
nodelay(stdscr, TRUE); nonl(); keypad(stdscr, TRUE);
start_color(); raw(); scrollok(stdscr, FALSE);
+ set_escdelay(25);
- for (i = 0; i < 64; i ++)
+ /* 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)
@@ -322,9 +759,8 @@ static void curses_keyboard_setup(void)
keyboard_layout = "en-us";
#endif
if(keyboard_layout) {
- kbd_layout = init_keyboard_layout(name2keysym, keyboard_layout);
- if (!kbd_layout)
- exit(1);
+ kbd_layout = init_keyboard_layout(name2keysym, keyboard_layout,
+ &error_fatal);
}
}
@@ -336,7 +772,7 @@ static const DisplayChangeListenerOps dcl_ops = {
.dpy_text_cursor = curses_cursor_position,
};
-void curses_display_init(DisplayState *ds, int full_screen)
+static void curses_display_init(DisplayState *ds, DisplayOptions *opts)
{
#ifndef _WIN32
if (!isatty(1)) {
@@ -345,21 +781,33 @@ void curses_display_init(DisplayState *ds, int full_screen)
}
#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);
-#ifndef _WIN32
-#if defined(SIGWINCH) && defined(KEY_RESIZE)
- /* some curses implementations provide a handler, but we
- * want to be sure this is handled regardless of the library */
- signal(SIGWINCH, curses_winch_handler);
-#endif
-#endif
+ curses_winch_init();
- dcl = (DisplayChangeListener *) g_malloc0(sizeof(DisplayChangeListener));
+ 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
index 18ce6dcee..71e04acdc 100644
--- a/ui/curses_keys.h
+++ b/ui/curses_keys.h
@@ -23,14 +23,13 @@
*/
#ifndef QEMU_CURSES_KEYS_H
-#define QEMU_CURSES_KEYS_H 1
+#define QEMU_CURSES_KEYS_H
#include <curses.h>
#include "keymaps.h"
-#define KEY_RELEASE 0x80
-#define KEY_MASK 0x7f
+#define KEY_MASK SCANCODE_KEYMASK
#define GREY_CODE 0xe0
#define GREY SCANCODE_GREY
#define SHIFT_CODE 0x2a
@@ -50,21 +49,29 @@
/* 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_KEYS] = {
- [0 ... (CURSES_KEYS - 1)] = -1,
+static const int _curses2keysym[CURSES_CHARS] = {
+ [0 ... (CURSES_CHARS - 1)] = -1,
[0x7f] = KEY_BACKSPACE,
['\r'] = KEY_ENTER,
['\n'] = KEY_ENTER,
[27] = 27,
- [KEY_BTAB] = '\t' | KEYSYM_SHIFT,
};
-static const int curses2keycode[CURSES_KEYS] = {
+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,
@@ -79,7 +86,6 @@ static const int curses2keycode[CURSES_KEYS] = {
['-'] = 12,
['='] = 13,
[0x07f] = 14, /* Backspace */
- [KEY_BACKSPACE] = 14, /* Backspace */
['\t'] = 15, /* Tab */
['q'] = 16,
@@ -96,7 +102,6 @@ static const int curses2keycode[CURSES_KEYS] = {
[']'] = 27,
['\n'] = 28, /* Return */
['\r'] = 28, /* Return */
- [KEY_ENTER] = 28, /* Return */
['a'] = 30,
['s'] = 31,
@@ -125,30 +130,6 @@ static const int curses2keycode[CURSES_KEYS] = {
[' '] = 57,
- [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 */
-
['!'] = 2 | SHIFT,
['@'] = 3 | SHIFT,
['#'] = 4 | SHIFT,
@@ -162,7 +143,6 @@ static const int curses2keycode[CURSES_KEYS] = {
['_'] = 12 | SHIFT,
['+'] = 13 | SHIFT,
- [KEY_BTAB] = 15 | SHIFT, /* Shift + Tab */
['Q'] = 16 | SHIFT,
['W'] = 17 | SHIFT,
['E'] = 18 | SHIFT,
@@ -201,19 +181,6 @@ static const int curses2keycode[CURSES_KEYS] = {
['>'] = 52 | SHIFT,
['?'] = 53 | SHIFT,
- [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 */
-
['Q' - '@'] = 16 | CNTRL, /* Control + q */
['W' - '@'] = 17 | CNTRL, /* Control + w */
['E' - '@'] = 18 | CNTRL, /* Control + e */
@@ -245,13 +212,67 @@ static const int curses2keycode[CURSES_KEYS] = {
};
-static const int curses2qemu[CURSES_KEYS] = {
+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,
@@ -476,7 +497,9 @@ static const name2keysym_t name2keysym[] = {
{ "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 },
diff --git a/ui/cursor.c b/ui/cursor.c
index 2b8dd3fa5..1d62ddd4d 100644
--- a/ui/cursor.c
+++ b/ui/cursor.c
@@ -1,4 +1,4 @@
-#include "qemu-common.h"
+#include "qemu/osdep.h"
#include "ui/console.h"
#include "cursor_hidden.xpm"
@@ -18,11 +18,11 @@ static QEMUCursor *cursor_parse_xpm(const char *xpm[])
if (sscanf(xpm[line], "%u %u %u %u",
&width, &height, &colors, &chars) != 4) {
fprintf(stderr, "%s: header parse error: \"%s\"\n",
- __FUNCTION__, xpm[line]);
+ __func__, xpm[line]);
return NULL;
}
if (chars != 1) {
- fprintf(stderr, "%s: chars != 1 not supported\n", __FUNCTION__);
+ fprintf(stderr, "%s: chars != 1 not supported\n", __func__);
return NULL;
}
line++;
@@ -40,7 +40,7 @@ static QEMUCursor *cursor_parse_xpm(const char *xpm[])
}
}
fprintf(stderr, "%s: color parse error: \"%s\"\n",
- __FUNCTION__, xpm[line]);
+ __func__, xpm[line]);
return NULL;
}
@@ -80,18 +80,12 @@ void cursor_print_ascii_art(QEMUCursor *c, const char *prefix)
QEMUCursor *cursor_builtin_hidden(void)
{
- QEMUCursor *c;
-
- c = cursor_parse_xpm(cursor_hidden_xpm);
- return c;
+ return cursor_parse_xpm(cursor_hidden_xpm);
}
QEMUCursor *cursor_builtin_left_ptr(void)
{
- QEMUCursor *c;
-
- c = cursor_parse_xpm(cursor_left_ptr_xpm);
- return c;
+ return cursor_parse_xpm(cursor_left_ptr_xpm);
}
QEMUCursor *cursor_alloc(int width, int height)
@@ -123,7 +117,7 @@ void cursor_put(QEMUCursor *c)
int cursor_get_mono_bpl(QEMUCursor *c)
{
- return (c->width + 7) / 8;
+ return DIV_ROUND_UP(c->width, 8);
}
void cursor_set_mono(QEMUCursor *c,
@@ -133,13 +127,25 @@ void cursor_set_mono(QEMUCursor *c,
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) {
- *data = 0x00000000;
+ 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) {
@@ -155,6 +161,32 @@ void cursor_set_mono(QEMUCursor *c,
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)
diff --git a/ui/d3des.c b/ui/d3des.c
deleted file mode 100644
index 60c840ed5..000000000
--- a/ui/d3des.c
+++ /dev/null
@@ -1,424 +0,0 @@
-/*
- * This is D3DES (V5.09) by Richard Outerbridge with the double and
- * triple-length support removed for use in VNC. Also the bytebit[] array
- * has been reversed so that the most significant bit in each byte of the
- * key is ignored, not the least significant.
- *
- * These changes are:
- * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
- *
- * 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.
- */
-
-/* D3DES (V5.09) -
- *
- * A portable, public domain, version of the Data Encryption Standard.
- *
- * Written with Symantec's THINK (Lightspeed) C by Richard Outerbridge.
- * Thanks to: Dan Hoey for his excellent Initial and Inverse permutation
- * code; Jim Gillogly & Phil Karn for the DES key schedule code; Dennis
- * Ferguson, Eric Young and Dana How for comparing notes; and Ray Lau,
- * for humouring me on.
- *
- * Copyright (c) 1988,1989,1990,1991,1992 by Richard Outerbridge.
- * (GEnie : OUTER; CIS : [71755,204]) Graven Imagery, 1992.
- */
-
-#include "d3des.h"
-
-static void scrunch(unsigned char *, unsigned long *);
-static void unscrun(unsigned long *, unsigned char *);
-static void desfunc(unsigned long *, unsigned long *);
-static void cookey(unsigned long *);
-
-static unsigned long KnL[32] = { 0L };
-
-static const unsigned short bytebit[8] = {
- 01, 02, 04, 010, 020, 040, 0100, 0200 };
-
-static const unsigned long bigbyte[24] = {
- 0x800000L, 0x400000L, 0x200000L, 0x100000L,
- 0x80000L, 0x40000L, 0x20000L, 0x10000L,
- 0x8000L, 0x4000L, 0x2000L, 0x1000L,
- 0x800L, 0x400L, 0x200L, 0x100L,
- 0x80L, 0x40L, 0x20L, 0x10L,
- 0x8L, 0x4L, 0x2L, 0x1L };
-
-/* Use the key schedule specified in the Standard (ANSI X3.92-1981). */
-
-static const unsigned char pc1[56] = {
- 56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17,
- 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35,
- 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21,
- 13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3 };
-
-static const unsigned char totrot[16] = {
- 1,2,4,6,8,10,12,14,15,17,19,21,23,25,27,28 };
-
-static const unsigned char pc2[48] = {
- 13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9,
- 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1,
- 40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47,
- 43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31 };
-
-/* Thanks to James Gillogly & Phil Karn! */
-void deskey(unsigned char *key, int edf)
-{
- register int i, j, l, m, n;
- unsigned char pc1m[56], pcr[56];
- unsigned long kn[32];
-
- for ( j = 0; j < 56; j++ ) {
- l = pc1[j];
- m = l & 07;
- pc1m[j] = (key[l >> 3] & bytebit[m]) ? 1 : 0;
- }
- for( i = 0; i < 16; i++ ) {
- if( edf == DE1 ) m = (15 - i) << 1;
- else m = i << 1;
- n = m + 1;
- kn[m] = kn[n] = 0L;
- for( j = 0; j < 28; j++ ) {
- l = j + totrot[i];
- if( l < 28 ) pcr[j] = pc1m[l];
- else pcr[j] = pc1m[l - 28];
- }
- for( j = 28; j < 56; j++ ) {
- l = j + totrot[i];
- if( l < 56 ) pcr[j] = pc1m[l];
- else pcr[j] = pc1m[l - 28];
- }
- for( j = 0; j < 24; j++ ) {
- if( pcr[pc2[j]] ) kn[m] |= bigbyte[j];
- if( pcr[pc2[j+24]] ) kn[n] |= bigbyte[j];
- }
- }
- cookey(kn);
- return;
- }
-
-static void cookey(register unsigned long *raw1)
-{
- register unsigned long *cook, *raw0;
- unsigned long dough[32];
- register int i;
-
- cook = dough;
- for( i = 0; i < 16; i++, raw1++ ) {
- raw0 = raw1++;
- *cook = (*raw0 & 0x00fc0000L) << 6;
- *cook |= (*raw0 & 0x00000fc0L) << 10;
- *cook |= (*raw1 & 0x00fc0000L) >> 10;
- *cook++ |= (*raw1 & 0x00000fc0L) >> 6;
- *cook = (*raw0 & 0x0003f000L) << 12;
- *cook |= (*raw0 & 0x0000003fL) << 16;
- *cook |= (*raw1 & 0x0003f000L) >> 4;
- *cook++ |= (*raw1 & 0x0000003fL);
- }
- usekey(dough);
- return;
- }
-
-void cpkey(register unsigned long *into)
-{
- register unsigned long *from, *endp;
-
- from = KnL, endp = &KnL[32];
- while( from < endp ) *into++ = *from++;
- return;
- }
-
-void usekey(register unsigned long *from)
-{
- register unsigned long *to, *endp;
-
- to = KnL, endp = &KnL[32];
- while( to < endp ) *to++ = *from++;
- return;
- }
-
-void des(unsigned char *inblock, unsigned char *outblock)
-{
- unsigned long work[2];
-
- scrunch(inblock, work);
- desfunc(work, KnL);
- unscrun(work, outblock);
- return;
- }
-
-static void scrunch(register unsigned char *outof, register unsigned long *into)
-{
- *into = (*outof++ & 0xffL) << 24;
- *into |= (*outof++ & 0xffL) << 16;
- *into |= (*outof++ & 0xffL) << 8;
- *into++ |= (*outof++ & 0xffL);
- *into = (*outof++ & 0xffL) << 24;
- *into |= (*outof++ & 0xffL) << 16;
- *into |= (*outof++ & 0xffL) << 8;
- *into |= (*outof & 0xffL);
- return;
- }
-
-static void unscrun(register unsigned long *outof, register unsigned char *into)
-{
- *into++ = (unsigned char)((*outof >> 24) & 0xffL);
- *into++ = (unsigned char)((*outof >> 16) & 0xffL);
- *into++ = (unsigned char)((*outof >> 8) & 0xffL);
- *into++ = (unsigned char)(*outof++ & 0xffL);
- *into++ = (unsigned char)((*outof >> 24) & 0xffL);
- *into++ = (unsigned char)((*outof >> 16) & 0xffL);
- *into++ = (unsigned char)((*outof >> 8) & 0xffL);
- *into = (unsigned char)(*outof & 0xffL);
- return;
- }
-
-static const unsigned long SP1[64] = {
- 0x01010400L, 0x00000000L, 0x00010000L, 0x01010404L,
- 0x01010004L, 0x00010404L, 0x00000004L, 0x00010000L,
- 0x00000400L, 0x01010400L, 0x01010404L, 0x00000400L,
- 0x01000404L, 0x01010004L, 0x01000000L, 0x00000004L,
- 0x00000404L, 0x01000400L, 0x01000400L, 0x00010400L,
- 0x00010400L, 0x01010000L, 0x01010000L, 0x01000404L,
- 0x00010004L, 0x01000004L, 0x01000004L, 0x00010004L,
- 0x00000000L, 0x00000404L, 0x00010404L, 0x01000000L,
- 0x00010000L, 0x01010404L, 0x00000004L, 0x01010000L,
- 0x01010400L, 0x01000000L, 0x01000000L, 0x00000400L,
- 0x01010004L, 0x00010000L, 0x00010400L, 0x01000004L,
- 0x00000400L, 0x00000004L, 0x01000404L, 0x00010404L,
- 0x01010404L, 0x00010004L, 0x01010000L, 0x01000404L,
- 0x01000004L, 0x00000404L, 0x00010404L, 0x01010400L,
- 0x00000404L, 0x01000400L, 0x01000400L, 0x00000000L,
- 0x00010004L, 0x00010400L, 0x00000000L, 0x01010004L };
-
-static const unsigned long SP2[64] = {
- 0x80108020L, 0x80008000L, 0x00008000L, 0x00108020L,
- 0x00100000L, 0x00000020L, 0x80100020L, 0x80008020L,
- 0x80000020L, 0x80108020L, 0x80108000L, 0x80000000L,
- 0x80008000L, 0x00100000L, 0x00000020L, 0x80100020L,
- 0x00108000L, 0x00100020L, 0x80008020L, 0x00000000L,
- 0x80000000L, 0x00008000L, 0x00108020L, 0x80100000L,
- 0x00100020L, 0x80000020L, 0x00000000L, 0x00108000L,
- 0x00008020L, 0x80108000L, 0x80100000L, 0x00008020L,
- 0x00000000L, 0x00108020L, 0x80100020L, 0x00100000L,
- 0x80008020L, 0x80100000L, 0x80108000L, 0x00008000L,
- 0x80100000L, 0x80008000L, 0x00000020L, 0x80108020L,
- 0x00108020L, 0x00000020L, 0x00008000L, 0x80000000L,
- 0x00008020L, 0x80108000L, 0x00100000L, 0x80000020L,
- 0x00100020L, 0x80008020L, 0x80000020L, 0x00100020L,
- 0x00108000L, 0x00000000L, 0x80008000L, 0x00008020L,
- 0x80000000L, 0x80100020L, 0x80108020L, 0x00108000L };
-
-static const unsigned long SP3[64] = {
- 0x00000208L, 0x08020200L, 0x00000000L, 0x08020008L,
- 0x08000200L, 0x00000000L, 0x00020208L, 0x08000200L,
- 0x00020008L, 0x08000008L, 0x08000008L, 0x00020000L,
- 0x08020208L, 0x00020008L, 0x08020000L, 0x00000208L,
- 0x08000000L, 0x00000008L, 0x08020200L, 0x00000200L,
- 0x00020200L, 0x08020000L, 0x08020008L, 0x00020208L,
- 0x08000208L, 0x00020200L, 0x00020000L, 0x08000208L,
- 0x00000008L, 0x08020208L, 0x00000200L, 0x08000000L,
- 0x08020200L, 0x08000000L, 0x00020008L, 0x00000208L,
- 0x00020000L, 0x08020200L, 0x08000200L, 0x00000000L,
- 0x00000200L, 0x00020008L, 0x08020208L, 0x08000200L,
- 0x08000008L, 0x00000200L, 0x00000000L, 0x08020008L,
- 0x08000208L, 0x00020000L, 0x08000000L, 0x08020208L,
- 0x00000008L, 0x00020208L, 0x00020200L, 0x08000008L,
- 0x08020000L, 0x08000208L, 0x00000208L, 0x08020000L,
- 0x00020208L, 0x00000008L, 0x08020008L, 0x00020200L };
-
-static const unsigned long SP4[64] = {
- 0x00802001L, 0x00002081L, 0x00002081L, 0x00000080L,
- 0x00802080L, 0x00800081L, 0x00800001L, 0x00002001L,
- 0x00000000L, 0x00802000L, 0x00802000L, 0x00802081L,
- 0x00000081L, 0x00000000L, 0x00800080L, 0x00800001L,
- 0x00000001L, 0x00002000L, 0x00800000L, 0x00802001L,
- 0x00000080L, 0x00800000L, 0x00002001L, 0x00002080L,
- 0x00800081L, 0x00000001L, 0x00002080L, 0x00800080L,
- 0x00002000L, 0x00802080L, 0x00802081L, 0x00000081L,
- 0x00800080L, 0x00800001L, 0x00802000L, 0x00802081L,
- 0x00000081L, 0x00000000L, 0x00000000L, 0x00802000L,
- 0x00002080L, 0x00800080L, 0x00800081L, 0x00000001L,
- 0x00802001L, 0x00002081L, 0x00002081L, 0x00000080L,
- 0x00802081L, 0x00000081L, 0x00000001L, 0x00002000L,
- 0x00800001L, 0x00002001L, 0x00802080L, 0x00800081L,
- 0x00002001L, 0x00002080L, 0x00800000L, 0x00802001L,
- 0x00000080L, 0x00800000L, 0x00002000L, 0x00802080L };
-
-static const unsigned long SP5[64] = {
- 0x00000100L, 0x02080100L, 0x02080000L, 0x42000100L,
- 0x00080000L, 0x00000100L, 0x40000000L, 0x02080000L,
- 0x40080100L, 0x00080000L, 0x02000100L, 0x40080100L,
- 0x42000100L, 0x42080000L, 0x00080100L, 0x40000000L,
- 0x02000000L, 0x40080000L, 0x40080000L, 0x00000000L,
- 0x40000100L, 0x42080100L, 0x42080100L, 0x02000100L,
- 0x42080000L, 0x40000100L, 0x00000000L, 0x42000000L,
- 0x02080100L, 0x02000000L, 0x42000000L, 0x00080100L,
- 0x00080000L, 0x42000100L, 0x00000100L, 0x02000000L,
- 0x40000000L, 0x02080000L, 0x42000100L, 0x40080100L,
- 0x02000100L, 0x40000000L, 0x42080000L, 0x02080100L,
- 0x40080100L, 0x00000100L, 0x02000000L, 0x42080000L,
- 0x42080100L, 0x00080100L, 0x42000000L, 0x42080100L,
- 0x02080000L, 0x00000000L, 0x40080000L, 0x42000000L,
- 0x00080100L, 0x02000100L, 0x40000100L, 0x00080000L,
- 0x00000000L, 0x40080000L, 0x02080100L, 0x40000100L };
-
-static const unsigned long SP6[64] = {
- 0x20000010L, 0x20400000L, 0x00004000L, 0x20404010L,
- 0x20400000L, 0x00000010L, 0x20404010L, 0x00400000L,
- 0x20004000L, 0x00404010L, 0x00400000L, 0x20000010L,
- 0x00400010L, 0x20004000L, 0x20000000L, 0x00004010L,
- 0x00000000L, 0x00400010L, 0x20004010L, 0x00004000L,
- 0x00404000L, 0x20004010L, 0x00000010L, 0x20400010L,
- 0x20400010L, 0x00000000L, 0x00404010L, 0x20404000L,
- 0x00004010L, 0x00404000L, 0x20404000L, 0x20000000L,
- 0x20004000L, 0x00000010L, 0x20400010L, 0x00404000L,
- 0x20404010L, 0x00400000L, 0x00004010L, 0x20000010L,
- 0x00400000L, 0x20004000L, 0x20000000L, 0x00004010L,
- 0x20000010L, 0x20404010L, 0x00404000L, 0x20400000L,
- 0x00404010L, 0x20404000L, 0x00000000L, 0x20400010L,
- 0x00000010L, 0x00004000L, 0x20400000L, 0x00404010L,
- 0x00004000L, 0x00400010L, 0x20004010L, 0x00000000L,
- 0x20404000L, 0x20000000L, 0x00400010L, 0x20004010L };
-
-static const unsigned long SP7[64] = {
- 0x00200000L, 0x04200002L, 0x04000802L, 0x00000000L,
- 0x00000800L, 0x04000802L, 0x00200802L, 0x04200800L,
- 0x04200802L, 0x00200000L, 0x00000000L, 0x04000002L,
- 0x00000002L, 0x04000000L, 0x04200002L, 0x00000802L,
- 0x04000800L, 0x00200802L, 0x00200002L, 0x04000800L,
- 0x04000002L, 0x04200000L, 0x04200800L, 0x00200002L,
- 0x04200000L, 0x00000800L, 0x00000802L, 0x04200802L,
- 0x00200800L, 0x00000002L, 0x04000000L, 0x00200800L,
- 0x04000000L, 0x00200800L, 0x00200000L, 0x04000802L,
- 0x04000802L, 0x04200002L, 0x04200002L, 0x00000002L,
- 0x00200002L, 0x04000000L, 0x04000800L, 0x00200000L,
- 0x04200800L, 0x00000802L, 0x00200802L, 0x04200800L,
- 0x00000802L, 0x04000002L, 0x04200802L, 0x04200000L,
- 0x00200800L, 0x00000000L, 0x00000002L, 0x04200802L,
- 0x00000000L, 0x00200802L, 0x04200000L, 0x00000800L,
- 0x04000002L, 0x04000800L, 0x00000800L, 0x00200002L };
-
-static const unsigned long SP8[64] = {
- 0x10001040L, 0x00001000L, 0x00040000L, 0x10041040L,
- 0x10000000L, 0x10001040L, 0x00000040L, 0x10000000L,
- 0x00040040L, 0x10040000L, 0x10041040L, 0x00041000L,
- 0x10041000L, 0x00041040L, 0x00001000L, 0x00000040L,
- 0x10040000L, 0x10000040L, 0x10001000L, 0x00001040L,
- 0x00041000L, 0x00040040L, 0x10040040L, 0x10041000L,
- 0x00001040L, 0x00000000L, 0x00000000L, 0x10040040L,
- 0x10000040L, 0x10001000L, 0x00041040L, 0x00040000L,
- 0x00041040L, 0x00040000L, 0x10041000L, 0x00001000L,
- 0x00000040L, 0x10040040L, 0x00001000L, 0x00041040L,
- 0x10001000L, 0x00000040L, 0x10000040L, 0x10040000L,
- 0x10040040L, 0x10000000L, 0x00040000L, 0x10001040L,
- 0x00000000L, 0x10041040L, 0x00040040L, 0x10000040L,
- 0x10040000L, 0x10001000L, 0x10001040L, 0x00000000L,
- 0x10041040L, 0x00041000L, 0x00041000L, 0x00001040L,
- 0x00001040L, 0x00040040L, 0x10000000L, 0x10041000L };
-
-static void desfunc(register unsigned long *block, register unsigned long *keys)
-{
- register unsigned long fval, work, right, leftt;
- register int round;
-
- leftt = block[0];
- right = block[1];
- work = ((leftt >> 4) ^ right) & 0x0f0f0f0fL;
- right ^= work;
- leftt ^= (work << 4);
- work = ((leftt >> 16) ^ right) & 0x0000ffffL;
- right ^= work;
- leftt ^= (work << 16);
- work = ((right >> 2) ^ leftt) & 0x33333333L;
- leftt ^= work;
- right ^= (work << 2);
- work = ((right >> 8) ^ leftt) & 0x00ff00ffL;
- leftt ^= work;
- right ^= (work << 8);
- right = ((right << 1) | ((right >> 31) & 1L)) & 0xffffffffL;
- work = (leftt ^ right) & 0xaaaaaaaaL;
- leftt ^= work;
- right ^= work;
- leftt = ((leftt << 1) | ((leftt >> 31) & 1L)) & 0xffffffffL;
-
- for( round = 0; round < 8; round++ ) {
- work = (right << 28) | (right >> 4);
- work ^= *keys++;
- fval = SP7[ work & 0x3fL];
- fval |= SP5[(work >> 8) & 0x3fL];
- fval |= SP3[(work >> 16) & 0x3fL];
- fval |= SP1[(work >> 24) & 0x3fL];
- work = right ^ *keys++;
- fval |= SP8[ work & 0x3fL];
- fval |= SP6[(work >> 8) & 0x3fL];
- fval |= SP4[(work >> 16) & 0x3fL];
- fval |= SP2[(work >> 24) & 0x3fL];
- leftt ^= fval;
- work = (leftt << 28) | (leftt >> 4);
- work ^= *keys++;
- fval = SP7[ work & 0x3fL];
- fval |= SP5[(work >> 8) & 0x3fL];
- fval |= SP3[(work >> 16) & 0x3fL];
- fval |= SP1[(work >> 24) & 0x3fL];
- work = leftt ^ *keys++;
- fval |= SP8[ work & 0x3fL];
- fval |= SP6[(work >> 8) & 0x3fL];
- fval |= SP4[(work >> 16) & 0x3fL];
- fval |= SP2[(work >> 24) & 0x3fL];
- right ^= fval;
- }
-
- right = (right << 31) | (right >> 1);
- work = (leftt ^ right) & 0xaaaaaaaaL;
- leftt ^= work;
- right ^= work;
- leftt = (leftt << 31) | (leftt >> 1);
- work = ((leftt >> 8) ^ right) & 0x00ff00ffL;
- right ^= work;
- leftt ^= (work << 8);
- work = ((leftt >> 2) ^ right) & 0x33333333L;
- right ^= work;
- leftt ^= (work << 2);
- work = ((right >> 16) ^ leftt) & 0x0000ffffL;
- leftt ^= work;
- right ^= (work << 16);
- work = ((right >> 4) ^ leftt) & 0x0f0f0f0fL;
- leftt ^= work;
- right ^= (work << 4);
- *block++ = right;
- *block = leftt;
- return;
- }
-
-/* Validation sets:
- *
- * Single-length key, single-length plaintext -
- * Key : 0123 4567 89ab cdef
- * Plain : 0123 4567 89ab cde7
- * Cipher : c957 4425 6a5e d31d
- *
- * Double-length key, single-length plaintext -
- * Key : 0123 4567 89ab cdef fedc ba98 7654 3210
- * Plain : 0123 4567 89ab cde7
- * Cipher : 7f1d 0a77 826b 8aff
- *
- * Double-length key, double-length plaintext -
- * Key : 0123 4567 89ab cdef fedc ba98 7654 3210
- * Plain : 0123 4567 89ab cdef 0123 4567 89ab cdff
- * Cipher : 27a0 8440 406a df60 278f 47cf 42d6 15d7
- *
- * Triple-length key, single-length plaintext -
- * Key : 0123 4567 89ab cdef fedc ba98 7654 3210 89ab cdef 0123 4567
- * Plain : 0123 4567 89ab cde7
- * Cipher : de0b 7c06 ae5e 0ed5
- *
- * Triple-length key, double-length plaintext -
- * Key : 0123 4567 89ab cdef fedc ba98 7654 3210 89ab cdef 0123 4567
- * Plain : 0123 4567 89ab cdef 0123 4567 89ab cdff
- * Cipher : ad0d 1b30 ac17 cf07 0ed1 1c63 81e4 4de5
- *
- * d3des V5.0a rwo 9208.07 18:44 Graven Imagery
- **********************************************************************/
diff --git a/ui/d3des.h b/ui/d3des.h
deleted file mode 100644
index 70cb6b57e..000000000
--- a/ui/d3des.h
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * This is D3DES (V5.09) by Richard Outerbridge with the double and
- * triple-length support removed for use in VNC.
- *
- * These changes are:
- * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved.
- *
- * 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.
- */
-#ifndef D3DES_H
-#define D3DES_H 1
-
-/* d3des.h -
- *
- * Headers and defines for d3des.c
- * Graven Imagery, 1992.
- *
- * Copyright (c) 1988,1989,1990,1991,1992 by Richard Outerbridge
- * (GEnie : OUTER; CIS : [71755,204])
- */
-
-#define EN0 0 /* MODE == encrypt */
-#define DE1 1 /* MODE == decrypt */
-
-void deskey(unsigned char *, int);
-/* hexkey[8] MODE
- * Sets the internal key register according to the hexadecimal
- * key contained in the 8 bytes of hexkey, according to the DES,
- * for encryption or decryption according to MODE.
- */
-
-void usekey(unsigned long *);
-/* cookedkey[32]
- * Loads the internal key register with the data in cookedkey.
- */
-
-void cpkey(unsigned long *);
-/* cookedkey[32]
- * Copies the contents of the internal key register into the storage
- * located at &cookedkey[0].
- */
-
-void des(unsigned char *, unsigned char *);
-/* from[8] to[8]
- * Encrypts/Decrypts (according to the key currently loaded in the
- * internal key register) one block of eight bytes at address 'from'
- * into the block at address 'to'. They can be the same.
- */
-
-/* d3des.h V5.09 rwo 9208.04 15:06 Graven Imagery
- ********************************************************************/
-
-#endif
diff --git a/ui/egl-context.c b/ui/egl-context.c
new file mode 100644
index 000000000..4aa1cbb50
--- /dev/null
+++ b/ui/egl-context.c
@@ -0,0 +1,42 @@
+#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);
+}
+
+QEMUGLContext qemu_egl_get_current_context(DisplayChangeListener *dcl)
+{
+ return eglGetCurrentContext();
+}
diff --git a/ui/egl-headless.c b/ui/egl-headless.c
new file mode 100644
index 000000000..fe2a0d1ea
--- /dev/null
+++ b/ui/egl-headless.c
@@ -0,0 +1,216 @@
+#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_ctx_get_current = qemu_egl_get_current_context,
+
+ .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);
diff --git a/ui/egl-helpers.c b/ui/egl-helpers.c
new file mode 100644
index 000000000..7c530c282
--- /dev/null
+++ b/ui/egl-helpers.c
@@ -0,0 +1,472 @@
+/*
+ * 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 y1, y2;
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, src->framebuffer);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dst->framebuffer);
+ glViewport(0, 0, dst->width, dst->height);
+ y1 = flip ? src->height : 0;
+ y2 = flip ? 0 : src->height;
+ glBlitFramebuffer(0, y1, src->width, 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_OPENGL_DMABUF
+
+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;
+}
+
+#endif /* CONFIG_OPENGL_DMABUF */
+
+/* ---------------------------------------------------------------------- */
+
+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;
+}
+
+/* ---------------------------------------------------------------------- */
+
+/*
+ * 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
+}
+
+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-egl.c b/ui/gtk-egl.c
new file mode 100644
index 000000000..99231a359
--- /dev/null
+++ b/ui/gtk-egl.c
@@ -0,0 +1,308 @@
+/*
+ * 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 "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;
+ 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) {
+ 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;
+ } 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);
+ }
+}
+
+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);
+
+ 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_create_texture(vc->gfx.gls, vc->gfx.ds);
+ }
+ }
+
+ 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;
+ }
+
+ 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_OPENGL_DMABUF
+ 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);
+#endif
+}
+
+void gd_egl_cursor_dmabuf(DisplayChangeListener *dcl,
+ QemuDmaBuf *dmabuf, bool have_hot,
+ uint32_t hot_x, uint32_t hot_y)
+{
+#ifdef CONFIG_OPENGL_DMABUF
+ 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_release_dmabuf(DisplayChangeListener *dcl,
+ QemuDmaBuf *dmabuf)
+{
+#ifdef CONFIG_OPENGL_DMABUF
+ egl_dmabuf_release_texture(dmabuf);
+#endif
+}
+
+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);
+ }
+
+ eglSwapBuffers(qemu_egl_display, vc->gfx.esurface);
+}
+
+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..98c22d23f
--- /dev/null
+++ b/ui/gtk-gl-area.c
@@ -0,0 +1,224 @@
+/*
+ * 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 "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)
+{
+ int ww, wh, y1, y2;
+
+ if (!vc->gfx.gls) {
+ return;
+ }
+
+ gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area));
+ ww = gtk_widget_get_allocated_width(vc->gfx.drawing_area);
+ wh = gtk_widget_get_allocated_height(vc->gfx.drawing_area);
+
+ if (vc->gfx.scanout_mode) {
+ if (!vc->gfx.guest_fb.framebuffer) {
+ return;
+ }
+
+ 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);
+ } 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);
+ }
+}
+
+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);
+
+ 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_flush(DisplayChangeListener *dcl,
+ uint32_t x, uint32_t y, uint32_t w, uint32_t h)
+{
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+
+ gtk_gl_area_queue_render(GTK_GL_AREA(vc->gfx.drawing_area));
+}
+
+void gtk_gl_area_init(void)
+{
+ display_opengl = 1;
+}
+
+QEMUGLContext gd_gl_area_get_current_context(DisplayChangeListener *dcl)
+{
+ return gdk_gl_context_get_current();
+}
+
+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
index c38146f80..a752aa22b 100644
--- a/ui/gtk.c
+++ b/ui/gtk.c
@@ -6,118 +6,120 @@
* Authors:
* Anthony Liguori <aliguori@us.ibm.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.
+ * 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.
*
- * Portions from gtk-vnc:
+ * 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>
- *
- * 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.0 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, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#define GETTEXT_PACKAGE "qemu"
#define LOCALEDIR "po"
-#include "qemu-common.h"
+#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"
-#ifdef CONFIG_PRAGMA_DIAGNOSTIC_AVAILABLE
-/* Work around an -Wstrict-prototypes warning in GTK headers */
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wstrict-prototypes"
-#endif
-#include <gtk/gtk.h>
-#ifdef CONFIG_PRAGMA_DIAGNOSTIC_AVAILABLE
-#pragma GCC diagnostic pop
+#include "ui/console.h"
+#include "ui/gtk.h"
+#ifdef G_OS_WIN32
+#include <gdk/gdkwin32.h>
#endif
+#include "ui/win32-kbd-hook.h"
-
-#include <gdk/gdkkeysyms.h>
#include <glib/gi18n.h>
#include <locale.h>
+#if defined(CONFIG_VTE)
#include <vte/vte.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <sys/wait.h>
+#endif
#include <math.h>
-#include "ui/console.h"
+#include "trace.h"
+#include "qemu/cutils.h"
+#include "ui/input.h"
+#include "sysemu/runstate.h"
#include "sysemu/sysemu.h"
-#include "qmp-commands.h"
-#include "x_keymap.h"
#include "keymaps.h"
-#include "sysemu/char.h"
+#include "chardev/char.h"
+#include "qom/object.h"
-//#define DEBUG_GTK
+#define MAX_VCS 10
+#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"
-#ifdef DEBUG_GTK
-#define DPRINTF(fmt, ...) printf(fmt, ## __VA_ARGS__)
-#else
-#define DPRINTF(fmt, ...) do { } while (0)
+/* Gtk2 compat */
+#ifndef GDK_IS_X11_DISPLAY
+#define GDK_IS_X11_DISPLAY(dpy) (dpy != NULL)
+#endif
#endif
-#define MAX_VCS 10
+
+#ifdef GDK_WINDOWING_WAYLAND
+/* Gtk2 compat */
+#ifndef GDK_IS_WAYLAND_DISPLAY
+#define GDK_IS_WAYLAND_DISPLAY(dpy) (dpy != NULL)
+#endif
+#endif
-/* Compatibility define to let us build on both Gtk2 and Gtk3 */
-#if GTK_CHECK_VERSION(3, 0, 0)
-static inline void gdk_drawable_get_size(GdkWindow *w, gint *ww, gint *wh)
-{
- *ww = gdk_window_get_width(w);
- *wh = gdk_window_get_height(w);
-}
+#ifdef GDK_WINDOWING_WIN32
+/* Gtk2 compat */
+#ifndef GDK_IS_WIN32_DISPLAY
+#define GDK_IS_WIN32_DISPLAY(dpy) (dpy != NULL)
#endif
+#endif
+
-#if !GTK_CHECK_VERSION(2, 20, 0)
-#define gtk_widget_get_realized(widget) GTK_WIDGET_REALIZED(widget)
+#ifdef GDK_WINDOWING_BROADWAY
+/* Gtk2 compat */
+#ifndef GDK_IS_BROADWAY_DISPLAY
+#define GDK_IS_BROADWAY_DISPLAY(dpy) (dpy != NULL)
#endif
+#endif
+
-#ifndef GDK_KEY_0
-#define GDK_KEY_0 GDK_0
-#define GDK_KEY_1 GDK_1
-#define GDK_KEY_2 GDK_2
-#define GDK_KEY_f GDK_f
-#define GDK_KEY_g GDK_g
-#define GDK_KEY_plus GDK_plus
-#define GDK_KEY_minus GDK_minus
+#ifdef GDK_WINDOWING_QUARTZ
+/* Gtk2 compat */
+#ifndef GDK_IS_QUARTZ_DISPLAY
+#define GDK_IS_QUARTZ_DISPLAY(dpy) (dpy != NULL)
+#endif
#endif
-#define HOTKEY_MODIFIERS (GDK_CONTROL_MASK | GDK_MOD1_MASK)
-#define IGNORE_MODIFIER_MASK \
- (GDK_MODIFIER_MASK & ~(GDK_LOCK_MASK | GDK_MOD2_MASK))
-static const int modifier_keycode[] = {
- /* shift, control, alt keys, meta keys, both left & right */
- 0x2a, 0x36, 0x1d, 0x9d, 0x38, 0xb8, 0xdb, 0xdd,
-};
+#if !defined(CONFIG_VTE)
+# define VTE_CHECK_VERSION(a, b, c) 0
+#endif
-typedef struct VirtualConsole
-{
- GtkWidget *menu_item;
- GtkWidget *terminal;
- GtkWidget *scrolled_window;
- CharDriverState *chr;
- int fd;
-} VirtualConsole;
+#define HOTKEY_MODIFIERS (GDK_CONTROL_MASK | GDK_MOD1_MASK)
-typedef struct GtkDisplayState
-{
+static const guint16 *keycode_map;
+static size_t keycode_maplen;
+
+struct GtkDisplayState {
GtkWidget *window;
GtkWidget *menu_bar;
@@ -134,32 +136,32 @@ typedef struct GtkDisplayState
GtkWidget *view_menu_item;
GtkWidget *view_menu;
GtkWidget *full_screen_item;
+ GtkWidget *copy_item;
GtkWidget *zoom_in_item;
GtkWidget *zoom_out_item;
GtkWidget *zoom_fixed_item;
GtkWidget *zoom_fit_item;
GtkWidget *grab_item;
GtkWidget *grab_on_hover_item;
- GtkWidget *vga_item;
int nb_vcs;
VirtualConsole vc[MAX_VCS];
GtkWidget *show_tabs_item;
+ GtkWidget *untabify_item;
+ GtkWidget *show_menubar_item;
GtkWidget *vbox;
GtkWidget *notebook;
- GtkWidget *drawing_area;
- cairo_surface_t *surface;
- pixman_image_t *convert;
- DisplayChangeListener dcl;
- DisplaySurface *ds;
int button_mask;
+ gboolean last_set;
int last_x;
int last_y;
+ int grab_x_root;
+ int grab_y_root;
+ VirtualConsole *kbd_owner;
+ VirtualConsole *ptr_owner;
- double scale_x;
- double scale_y;
gboolean full_screen;
GdkCursor *null_cursor;
@@ -168,13 +170,67 @@ typedef struct GtkDisplayState
bool external_pause_update;
- bool modifier_pressed[ARRAY_SIZE(modifier_keycode)];
-} GtkDisplayState;
+ DisplayOptions *opts;
+};
+
+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)
-static GtkDisplayState *global_state;
+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));
@@ -185,22 +241,22 @@ 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 bool gd_on_vga(GtkDisplayState *s)
-{
- return gtk_notebook_get_current_page(GTK_NOTEBOOK(s->notebook)) == 0;
-}
-
-static void gd_update_cursor(GtkDisplayState *s, gboolean override)
+static void gd_update_cursor(VirtualConsole *vc)
{
+ GtkDisplayState *s = vc->s;
GdkWindow *window;
- bool on_vga;
- window = gtk_widget_get_window(GTK_WIDGET(s->drawing_area));
+ if (vc->type != GD_VC_GFX ||
+ !qemu_console_is_graphic(vc->gfx.dcl.con)) {
+ return;
+ }
- on_vga = gd_on_vga(s);
+ if (!gtk_widget_get_realized(vc->gfx.drawing_area)) {
+ return;
+ }
- if ((override || on_vga) &&
- (s->full_screen || kbd_mouse_is_absolute() || gd_is_grab_active(s))) {
+ 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);
@@ -210,11 +266,20 @@ static void gd_update_cursor(GtkDisplayState *s, gboolean override)
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 (gd_is_grab_active(s)) {
+ 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");
}
@@ -226,73 +291,148 @@ static void gd_update_caption(GtkDisplayState *s)
is_paused);
s->external_pause_update = false;
- if (qemu_name) {
- title = g_strdup_printf("QEMU (%s)%s%s", qemu_name, status, grab);
- } else {
- title = g_strdup_printf("QEMU%s%s", status, grab);
- }
-
+ 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_windowsize(GtkDisplayState *s)
+static void gd_update_geometry_hints(VirtualConsole *vc)
{
- if (!s->full_screen) {
- GtkRequisition req;
- double sx, sy;
-
+ 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) {
- sx = s->scale_x;
- sy = s->scale_y;
-
- s->scale_y = 1.0;
- s->scale_x = 1.0;
+ 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 {
- sx = 1.0;
- sy = 1.0;
+ 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;
}
-
- gtk_widget_set_size_request(s->drawing_area,
- surface_width(s->ds) * s->scale_x,
- surface_height(s->ds) * s->scale_y);
-#if GTK_CHECK_VERSION(3, 0, 0)
- gtk_widget_get_preferred_size(s->vbox, NULL, &req);
+ 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
- gtk_widget_size_request(s->vbox, &req);
+ {
+ 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
+ }
- gtk_window_resize(GTK_WINDOW(s->window),
- req.width * sx, req.height * sy);
+ 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(GtkDisplayState *s)
+static void gd_update_full_redraw(VirtualConsole *vc)
{
+ GtkWidget *area = vc->gfx.drawing_area;
int ww, wh;
- gdk_drawable_get_size(gtk_widget_get_window(s->drawing_area), &ww, &wh);
- gtk_widget_queue_draw_area(s->drawing_area, 0, 0, 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_GTK_GL)
+ 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)
{
- int i, keycode;
+ VirtualConsole *vc = gd_vc_find_current(s);
- if (!gd_on_vga(s)) {
+ if (vc->type != GD_VC_GFX ||
+ !qemu_console_is_graphic(vc->gfx.dcl.con)) {
return;
}
- for (i = 0; i < ARRAY_SIZE(modifier_keycode); i++) {
- keycode = modifier_keycode[i];
- if (!s->modifier_pressed[i]) {
- continue;
- }
- if (keycode & SCANCODE_GREY) {
- kbd_put_keycode(SCANCODE_EMUL0);
- }
- kbd_put_keycode(keycode | SCANCODE_UP);
- s->modifier_pressed[i] = false;
- }
+ 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 **/
@@ -300,29 +440,40 @@ static void gtk_release_modifiers(GtkDisplayState *s)
static void gd_update(DisplayChangeListener *dcl,
int x, int y, int w, int h)
{
- GtkDisplayState *s = container_of(dcl, GtkDisplayState, dcl);
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+ GdkWindow *win;
int x1, x2, y1, y2;
int mx, my;
int fbw, fbh;
int ww, wh;
- DPRINTF("update(x=%d, y=%d, w=%d, h=%d)\n", x, y, w, h);
+ trace_gd_update(vc->label, x, y, w, h);
- if (s->convert) {
- pixman_image_composite(PIXMAN_OP_SRC, s->ds->image, NULL, s->convert,
+ 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 * s->scale_x);
- y1 = floor(y * s->scale_y);
+ x1 = floor(x * vc->gfx.scale_x);
+ y1 = floor(y * vc->gfx.scale_y);
- x2 = ceil(x * s->scale_x + w * s->scale_x);
- y2 = ceil(y * s->scale_y + h * s->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(s->ds) * s->scale_x;
- fbh = surface_height(s->ds) * s->scale_y;
+ fbw = surface_width(vc->gfx.ds) * vc->gfx.scale_x;
+ fbh = surface_height(vc->gfx.ds) * vc->gfx.scale_y;
- gdk_drawable_get_size(gtk_widget_get_window(s->drawing_area), &ww, &wh);
+ 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) {
@@ -332,7 +483,8 @@ static void gd_update(DisplayChangeListener *dcl,
my = (wh - fbh) / 2;
}
- gtk_widget_queue_draw_area(s->drawing_area, mx + x1, my + y1, (x2 - x1), (y2 - y1));
+ gtk_widget_queue_draw_area(vc->gfx.drawing_area,
+ mx + x1, my + y1, (x2 - x1), (y2 - y1));
}
static void gd_refresh(DisplayChangeListener *dcl)
@@ -340,83 +492,83 @@ static void gd_refresh(DisplayChangeListener *dcl)
graphic_hw_update(dcl->con);
}
-#if GTK_CHECK_VERSION(3, 0, 0)
-static void gd_mouse_set(DisplayChangeListener *dcl,
- int x, int y, int visible)
+static GdkDevice *gd_get_pointer(GdkDisplay *dpy)
{
- GtkDisplayState *s = container_of(dcl, GtkDisplayState, dcl);
- GdkDisplay *dpy;
- GdkDeviceManager *mgr;
- gint x_root, y_root;
-
- dpy = gtk_widget_get_display(s->drawing_area);
- mgr = gdk_display_get_device_manager(dpy);
- gdk_window_get_root_coords(gtk_widget_get_window(s->drawing_area),
- x, y, &x_root, &y_root);
- gdk_device_warp(gdk_device_manager_get_client_pointer(mgr),
- gtk_widget_get_screen(s->drawing_area),
- x, y);
+ return gdk_seat_get_pointer(gdk_display_get_default_seat(dpy));
}
-#else
+
static void gd_mouse_set(DisplayChangeListener *dcl,
int x, int y, int visible)
{
- GtkDisplayState *s = container_of(dcl, GtkDisplayState, dcl);
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
+ GdkDisplay *dpy;
gint x_root, y_root;
- gdk_window_get_root_coords(gtk_widget_get_window(s->drawing_area),
+ 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_display_warp_pointer(gtk_widget_get_display(s->drawing_area),
- gtk_widget_get_screen(s->drawing_area),
- 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;
}
-#endif
static void gd_cursor_define(DisplayChangeListener *dcl,
QEMUCursor *c)
{
- GtkDisplayState *s = container_of(dcl, GtkDisplayState, dcl);
+ 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(s->drawing_area),
- pixbuf, c->hot_x, c->hot_y);
- gdk_window_set_cursor(gtk_widget_get_window(s->drawing_area), cursor);
+ 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);
-#if !GTK_CHECK_VERSION(3, 0, 0)
- gdk_cursor_unref(cursor);
-#else
g_object_unref(cursor);
-#endif
}
static void gd_switch(DisplayChangeListener *dcl,
DisplaySurface *surface)
{
- GtkDisplayState *s = container_of(dcl, GtkDisplayState, dcl);
+ VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl);
bool resized = true;
- DPRINTF("resize(width=%d, height=%d)\n",
- surface_width(surface), surface_height(surface));
+ trace_gd_switch(vc->label,
+ surface ? surface_width(surface) : 0,
+ surface ? surface_height(surface) : 0);
- if (s->surface) {
- cairo_surface_destroy(s->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 (s->ds &&
- surface_width(s->ds) == surface_width(surface) &&
- surface_height(s->ds) == surface_height(surface)) {
+ if (vc->gfx.ds && surface &&
+ surface_width(vc->gfx.ds) == surface_width(surface) &&
+ surface_height(vc->gfx.ds) == surface_height(surface)) {
resized = false;
}
- s->ds = surface;
+ vc->gfx.ds = surface;
- if (s->convert) {
- pixman_image_unref(s->convert);
- s->convert = NULL;
+ if (!surface) {
+ return;
}
if (surface->format == PIXMAN_x8r8g8b8) {
@@ -426,7 +578,7 @@ static void gd_switch(DisplayChangeListener *dcl,
* No need to convert, use surface directly. Should be the
* common case as this is qemu_default_pixelformat(32) too.
*/
- s->surface = cairo_image_surface_create_for_data
+ vc->gfx.surface = cairo_image_surface_create_for_data
(surface_data(surface),
CAIRO_FORMAT_RGB24,
surface_width(surface),
@@ -434,29 +586,90 @@ static void gd_switch(DisplayChangeListener *dcl,
surface_stride(surface));
} else {
/* Must convert surface, use pixman to do it. */
- s->convert = pixman_image_create_bits(PIXMAN_x8r8g8b8,
- surface_width(surface),
- surface_height(surface),
- NULL, 0);
- s->surface = cairo_image_surface_create_for_data
- ((void *)pixman_image_get_data(s->convert),
+ 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(s->convert),
- pixman_image_get_height(s->convert),
- pixman_image_get_stride(s->convert));
- pixman_image_composite(PIXMAN_OP_SRC, s->ds->image, NULL, s->convert,
+ 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(s->convert),
- pixman_image_get_height(s->convert));
+ pixman_image_get_width(vc->gfx.convert),
+ pixman_image_get_height(vc->gfx.convert));
}
if (resized) {
- gd_update_windowsize(s);
+ gd_update_windowsize(vc);
} else {
- gd_update_full_redraw(s);
+ 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)
+
+/** DisplayState Callbacks (opengl version) **/
+
+#if defined(CONFIG_GTK_GL)
+
+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_ctx_get_current = gd_gl_area_get_current_context,
+ .dpy_gl_scanout_texture = gd_gl_area_scanout_texture,
+ .dpy_gl_update = gd_gl_area_scanout_flush,
+};
+
+#endif /* CONFIG_GTK_GL */
+
+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_ctx_get_current = qemu_egl_get_current_context,
+ .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_release_dmabuf = gd_egl_release_dmabuf,
+ .dpy_gl_update = gd_egl_scanout_flush,
+};
+
+#endif /* CONFIG_OPENGL */
+
/** QEMU Events **/
static void gd_change_runstate(void *opaque, int running, RunState state)
@@ -468,74 +681,146 @@ static void gd_change_runstate(void *opaque, int running, RunState state)
static void gd_mouse_mode_change(Notifier *notify, void *data)
{
- gd_update_cursor(container_of(notify, GtkDisplayState, mouse_mode_notifier),
- FALSE);
+ 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_key_event(GtkWidget *widget, GdkEventKey *key, void *opaque)
+static gboolean gd_window_close(GtkWidget *widget, GdkEvent *event,
+ void *opaque)
{
GtkDisplayState *s = opaque;
- gboolean handled = FALSE;
+ bool allow_close = true;
- if (!gd_is_grab_active(s) ||
- (key->state & IGNORE_MODIFIER_MASK) == HOTKEY_MODIFIERS) {
- handled = gtk_window_activate_key(GTK_WINDOW(widget), key);
+ if (s->opts->has_window_close && !s->opts->window_close) {
+ allow_close = false;
}
- if (handled) {
- gtk_release_modifiers(s);
- } else {
- handled = gtk_window_propagate_key_event(GTK_WINDOW(widget), key);
+
+ if (allow_close) {
+ qmp_quit(NULL);
}
- return handled;
+ return TRUE;
}
-static gboolean gd_window_close(GtkWidget *widget, GdkEvent *event,
+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_GTK_GL)
+
+static gboolean gd_render_event(GtkGLArea *area, GdkGLContext *context,
void *opaque)
{
- GtkDisplayState *s = opaque;
+ VirtualConsole *vc = opaque;
- if (!no_quit) {
- unregister_displaychangelistener(&s->dcl);
- qmp_quit(NULL);
- return FALSE;
+ 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 refresh rate of the display in milli-Hertz,
+ * else return 0.
+ */
+static int gd_refresh_rate_millihz(GtkWidget *window)
+{
+#ifdef GDK_VERSION_3_22
+ GdkWindow *win = gtk_widget_get_window(window);
+
+ if (win) {
+ GdkDisplay *dpy = gtk_widget_get_display(window);
+ GdkMonitor *monitor = gdk_display_get_monitor_at_window(dpy, win);
+
+ return gdk_monitor_get_refresh_rate(monitor);
+ }
+#endif
+ return 0;
+}
+
static gboolean gd_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque)
{
- GtkDisplayState *s = opaque;
+ VirtualConsole *vc = opaque;
+ GtkDisplayState *s = vc->s;
int mx, my;
int ww, wh;
int fbw, fbh;
+ int refresh_rate_millihz;
+
+#if defined(CONFIG_OPENGL)
+ if (vc->gfx.gls) {
+ if (gtk_use_gl_area) {
+ /* invoke render callback please */
+ return FALSE;
+ } else {
+ gd_egl_draw(vc);
+ return TRUE;
+ }
+ }
+#endif
if (!gtk_widget_get_realized(widget)) {
return FALSE;
}
+ if (!vc->gfx.ds) {
+ return FALSE;
+ }
- fbw = surface_width(s->ds);
- fbh = surface_height(s->ds);
+ refresh_rate_millihz = gd_refresh_rate_millihz(vc->window ?
+ vc->window : s->window);
+ if (refresh_rate_millihz) {
+ vc->gfx.dcl.update_interval = MILLISEC_PER_SEC / refresh_rate_millihz;
+ }
- gdk_drawable_get_size(gtk_widget_get_window(widget), &ww, &wh);
+ 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) {
- s->scale_x = (double)ww / fbw;
- s->scale_y = (double)wh / fbh;
+ 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;
- s->scale_x = s->scale_y = MIN(sx, sy);
+ vc->gfx.scale_x = vc->gfx.scale_y = MIN(sx, sy);
}
- fbw *= s->scale_x;
- fbh *= s->scale_y;
+ fbw *= vc->gfx.scale_x;
+ fbh *= vc->gfx.scale_y;
mx = my = 0;
if (ww > fbw) {
@@ -556,50 +841,33 @@ static gboolean gd_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque)
-1 * fbw, fbh);
cairo_fill(cr);
- cairo_scale(cr, s->scale_x, s->scale_y);
- cairo_set_source_surface(cr, s->surface, mx / s->scale_x, my / s->scale_y);
+ 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;
}
-#if !GTK_CHECK_VERSION(3, 0, 0)
-static gboolean gd_expose_event(GtkWidget *widget, GdkEventExpose *expose,
- void *opaque)
-{
- cairo_t *cr;
- gboolean ret;
-
- cr = gdk_cairo_create(gtk_widget_get_window(widget));
- cairo_rectangle(cr,
- expose->area.x,
- expose->area.y,
- expose->area.width,
- expose->area.height);
- cairo_clip(cr);
-
- ret = gd_draw_event(widget, cr, opaque);
-
- cairo_destroy(cr);
-
- return ret;
-}
-#endif
-
static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion,
void *opaque)
{
- GtkDisplayState *s = opaque;
- int dx, dy;
+ VirtualConsole *vc = opaque;
+ GtkDisplayState *s = vc->s;
int x, y;
int mx, my;
int fbh, fbw;
int ww, wh;
- fbw = surface_width(s->ds) * s->scale_x;
- fbh = surface_height(s->ds) * s->scale_y;
+ 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;
- gdk_drawable_get_size(gtk_widget_get_window(s->drawing_area), &ww, &wh);
+ ww = gdk_window_get_width(gtk_widget_get_window(vc->gfx.drawing_area));
+ wh = gdk_window_get_height(gtk_widget_get_window(vc->gfx.drawing_area));
mx = my = 0;
if (ww > fbw) {
@@ -609,38 +877,44 @@ static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion,
my = (wh - fbh) / 2;
}
- x = (motion->x - mx) / s->scale_x;
- y = (motion->y - my) / s->scale_y;
-
- if (x < 0 || y < 0 ||
- x >= surface_width(s->ds) ||
- y >= surface_height(s->ds)) {
- return TRUE;
- }
+ x = (motion->x - mx) / vc->gfx.scale_x;
+ y = (motion->y - my) / vc->gfx.scale_y;
- if (kbd_mouse_is_absolute()) {
- dx = x * 0x7FFF / (surface_width(s->ds) - 1);
- dy = y * 0x7FFF / (surface_height(s->ds) - 1);
- } else if (s->last_x == -1 || s->last_y == -1) {
- dx = 0;
- dy = 0;
- } else {
- dx = x - s->last_x;
- dy = y - s->last_y;
+ 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 (kbd_mouse_is_absolute() || gd_is_grab_active(s)) {
- kbd_mouse_event(dx, dy, 0, s->button_mask);
- }
+ 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 screen_width, screen_height;
- if (!kbd_mouse_is_absolute() && gd_is_grab_active(s)) {
- GdkScreen *screen = gtk_widget_get_screen(s->drawing_area);
int x = (int)motion->x_root;
int y = (int)motion->y_root;
+ gdk_monitor_get_geometry(monitor, &geometry);
+ screen_width = geometry.width;
+ screen_height = geometry.height;
+
/* In relative mode check to see if client pointer hit
* one of the screen edges, and if so move it back by
* 200 pixels. This is important because the pointer
@@ -654,23 +928,17 @@ static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion,
if (y == 0) {
y += 200;
}
- if (x == (gdk_screen_get_width(screen) - 1)) {
+ if (x == (screen_width - 1)) {
x -= 200;
}
- if (y == (gdk_screen_get_height(screen) - 1)) {
+ if (y == (screen_height - 1)) {
y -= 200;
}
if (x != (int)motion->x_root || y != (int)motion->y_root) {
-#if GTK_CHECK_VERSION(3, 0, 0)
GdkDevice *dev = gdk_event_get_device((GdkEvent *)motion);
gdk_device_warp(dev, screen, x, y);
-#else
- GdkDisplay *display = gtk_widget_get_display(widget);
- gdk_display_warp_pointer(display, screen, x, y);
-#endif
- s->last_x = -1;
- s->last_y = -1;
+ s->last_set = FALSE;
return FALSE;
}
}
@@ -680,87 +948,253 @@ static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion,
static gboolean gd_button_event(GtkWidget *widget, GdkEventButton *button,
void *opaque)
{
- GtkDisplayState *s = opaque;
- int dx, dy;
- int n;
+ 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) {
- n = 0x01;
+ btn = INPUT_BUTTON_LEFT;
} else if (button->button == 2) {
- n = 0x04;
+ btn = INPUT_BUTTON_MIDDLE;
} else if (button->button == 3) {
- n = 0x02;
+ btn = INPUT_BUTTON_RIGHT;
+ } else if (button->button == 8) {
+ btn = INPUT_BUTTON_SIDE;
+ } else if (button->button == 9) {
+ btn = INPUT_BUTTON_EXTRA;
} else {
- n = 0x00;
+ return TRUE;
}
- if (button->type == GDK_BUTTON_PRESS) {
- s->button_mask |= n;
- } else if (button->type == GDK_BUTTON_RELEASE) {
- s->button_mask &= ~n;
- }
+ qemu_input_queue_btn(vc->gfx.dcl.con, btn,
+ button->type == GDK_BUTTON_PRESS);
+ qemu_input_event_sync();
+ return TRUE;
+}
- if (kbd_mouse_is_absolute()) {
- dx = s->last_x * 0x7FFF / (surface_width(s->ds) - 1);
- dy = s->last_y * 0x7FFF / (surface_height(s->ds) - 1);
+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 {
- dx = 0;
- dy = 0;
+ return TRUE;
}
- kbd_mouse_event(dx, dy, 0, s->button_mask);
-
+ 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 gboolean gd_key_event(GtkWidget *widget, GdkEventKey *key, void *opaque)
+
+static const guint16 *gd_get_keymap(size_t *maplen)
{
- GtkDisplayState *s = opaque;
- int gdk_keycode;
- int qemu_keycode;
- int i;
+ GdkDisplay *dpy = gdk_display_get_default();
- gdk_keycode = key->hardware_keycode;
-
- if (gdk_keycode < 9) {
- qemu_keycode = 0;
- } else if (gdk_keycode < 97) {
- qemu_keycode = gdk_keycode - 8;
- } else if (gdk_keycode < 158) {
- qemu_keycode = translate_evdev_keycode(gdk_keycode - 97);
- } else if (gdk_keycode == 208) { /* Hiragana_Katakana */
- qemu_keycode = 0x70;
- } else if (gdk_keycode == 211) { /* backslash */
- qemu_keycode = 0x73;
- } else {
- qemu_keycode = 0;
+#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
- DPRINTF("translated GDK keycode %d to QEMU keycode %d (%s)\n",
- gdk_keycode, qemu_keycode,
- (key->type == GDK_KEY_PRESS) ? "down" : "up");
+#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
- for (i = 0; i < ARRAY_SIZE(modifier_keycode); i++) {
- if (qemu_keycode == modifier_keycode[i]) {
- s->modifier_pressed[i] = (key->type == GDK_KEY_PRESS);
- }
+#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);
- if (qemu_keycode & SCANCODE_GREY) {
- kbd_put_keycode(SCANCODE_EMUL0);
+ /* translate Windows native scancodes to atset1 keycodes */
+ switch (scancode & (KF_EXTENDED | 0xff)) {
+ case 0x145: /* NUMLOCK */
+ return scancode & 0xff;
}
- if (key->type == GDK_KEY_PRESS) {
- kbd_put_keycode(qemu_keycode & SCANCODE_KEYCODEMASK);
- } else if (key->type == GDK_KEY_RELEASE) {
- kbd_put_keycode(qemu_keycode | SCANCODE_UP);
+ 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 {
- g_assert_not_reached();
+ 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)
@@ -795,266 +1229,355 @@ static void gd_menu_quit(GtkMenuItem *item, void *opaque)
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;
- if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->vga_item))) {
- gtk_notebook_set_current_page(GTK_NOTEBOOK(s->notebook), 0);
- } else {
- int i;
-
- gtk_release_modifiers(s);
- for (i = 0; i < s->nb_vcs; i++) {
- if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->vc[i].menu_item))) {
- gtk_notebook_set_current_page(GTK_NOTEBOOK(s->notebook), i + 1);
- break;
- }
- }
+ 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;
+ 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);
+ 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_set_size_request(s->menu_bar, 0, 0);
- gtk_widget_set_size_request(s->drawing_area, -1, -1);
- gtk_window_fullscreen(GTK_WINDOW(s->window));
- if (gd_on_vga(s)) {
- gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item), TRUE);
+ 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);
- gtk_widget_set_size_request(s->menu_bar, -1, -1);
- gtk_widget_set_size_request(s->drawing_area,
- surface_width(s->ds),
- surface_height(s->ds));
- gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item), FALSE);
+ 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;
- s->scale_x = 1.0;
- s->scale_y = 1.0;
+ 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(s, FALSE);
+ 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);
- s->scale_x += .25;
- s->scale_y += .25;
+ vc->gfx.scale_x += VC_SCALE_STEP;
+ vc->gfx.scale_y += VC_SCALE_STEP;
+
+ gd_update_windowsize(vc);
+}
- gd_update_windowsize(s);
+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);
- s->scale_x -= .25;
- s->scale_y -= .25;
+ vc->gfx.scale_x -= VC_SCALE_STEP;
+ vc->gfx.scale_y -= VC_SCALE_STEP;
- s->scale_x = MAX(s->scale_x, .25);
- s->scale_y = MAX(s->scale_y, .25);
+ 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(s);
+ gd_update_windowsize(vc);
}
static void gd_menu_zoom_fixed(GtkMenuItem *item, void *opaque)
{
GtkDisplayState *s = opaque;
+ VirtualConsole *vc = gd_vc_find_current(s);
- s->scale_x = 1.0;
- s->scale_y = 1.0;
+ vc->gfx.scale_x = 1.0;
+ vc->gfx.scale_y = 1.0;
- gd_update_windowsize(s);
+ 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;
- s->scale_x = 1.0;
- s->scale_y = 1.0;
- gd_update_windowsize(s);
- }
-
- gd_update_full_redraw(s);
-}
-
-static void gd_grab_keyboard(GtkDisplayState *s)
-{
-#if GTK_CHECK_VERSION(3, 0, 0)
- GdkDisplay *display = gtk_widget_get_display(s->drawing_area);
- GdkDeviceManager *mgr = gdk_display_get_device_manager(display);
- GList *devices = gdk_device_manager_list_devices(mgr,
- GDK_DEVICE_TYPE_MASTER);
- GList *tmp = devices;
- while (tmp) {
- GdkDevice *dev = tmp->data;
- if (gdk_device_get_source(dev) == GDK_SOURCE_KEYBOARD) {
- gdk_device_grab(dev,
- gtk_widget_get_window(s->drawing_area),
- GDK_OWNERSHIP_NONE,
- FALSE,
- GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK,
- NULL,
- GDK_CURRENT_TIME);
+ 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);
}
- tmp = tmp->next;
}
- g_list_free(devices);
-#else
- gdk_keyboard_grab(gtk_widget_get_window(s->drawing_area),
- FALSE,
- GDK_CURRENT_TIME);
-#endif
+
+ 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)
{
-#if GTK_CHECK_VERSION(3, 0, 0)
- GdkDisplay *display = gtk_widget_get_display(s->drawing_area);
- GdkDeviceManager *mgr = gdk_display_get_device_manager(display);
- GList *devices = gdk_device_manager_list_devices(mgr,
- GDK_DEVICE_TYPE_MASTER);
- GList *tmp = devices;
- while (tmp) {
- GdkDevice *dev = tmp->data;
- if (gdk_device_get_source(dev) == GDK_SOURCE_KEYBOARD) {
- gdk_device_ungrab(dev,
- GDK_CURRENT_TIME);
- }
- tmp = tmp->next;
+ VirtualConsole *vc = s->kbd_owner;
+
+ if (vc == NULL) {
+ return;
}
- g_list_free(devices);
-#else
- gdk_keyboard_ungrab(GDK_CURRENT_TIME);
-#endif
+ 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(GtkDisplayState *s)
-{
-#if GTK_CHECK_VERSION(3, 0, 0)
- GdkDisplay *display = gtk_widget_get_display(s->drawing_area);
- GdkDeviceManager *mgr = gdk_display_get_device_manager(display);
- GList *devices = gdk_device_manager_list_devices(mgr,
- GDK_DEVICE_TYPE_MASTER);
- GList *tmp = devices;
- while (tmp) {
- GdkDevice *dev = tmp->data;
- if (gdk_device_get_source(dev) == GDK_SOURCE_MOUSE) {
- gdk_device_grab(dev,
- gtk_widget_get_window(s->drawing_area),
- GDK_OWNERSHIP_NONE,
- FALSE, /* All events to come to our
- window directly */
- GDK_POINTER_MOTION_MASK |
- GDK_BUTTON_PRESS_MASK |
- GDK_BUTTON_RELEASE_MASK |
- GDK_BUTTON_MOTION_MASK |
- GDK_SCROLL_MASK,
- s->null_cursor,
- GDK_CURRENT_TIME);
+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);
}
- tmp = tmp->next;
}
- g_list_free(devices);
-#else
- gdk_pointer_grab(gtk_widget_get_window(s->drawing_area),
- FALSE, /* All events to come to our window directly */
- GDK_POINTER_MOTION_MASK |
- GDK_BUTTON_PRESS_MASK |
- GDK_BUTTON_RELEASE_MASK |
- GDK_BUTTON_MOTION_MASK |
- GDK_SCROLL_MASK,
- NULL, /* Allow cursor to move over entire desktop */
- s->null_cursor,
- GDK_CURRENT_TIME);
-#endif
+
+ 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)
{
-#if GTK_CHECK_VERSION(3, 0, 0)
- GdkDisplay *display = gtk_widget_get_display(s->drawing_area);
- GdkDeviceManager *mgr = gdk_display_get_device_manager(display);
- GList *devices = gdk_device_manager_list_devices(mgr,
- GDK_DEVICE_TYPE_MASTER);
- GList *tmp = devices;
- while (tmp) {
- GdkDevice *dev = tmp->data;
- if (gdk_device_get_source(dev) == GDK_SOURCE_MOUSE) {
- gdk_device_ungrab(dev,
- GDK_CURRENT_TIME);
- }
- tmp = tmp->next;
+ VirtualConsole *vc = s->ptr_owner;
+ GdkDisplay *display;
+
+ if (vc == NULL) {
+ return;
}
- g_list_free(devices);
-#else
- gdk_pointer_ungrab(GDK_CURRENT_TIME);
-#endif
+ 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(s);
- gd_grab_pointer(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_caption(s);
- gd_update_cursor(s, FALSE);
+ gd_update_cursor(vc);
}
static void gd_change_page(GtkNotebook *nb, gpointer arg1, guint arg2,
gpointer data)
{
GtkDisplayState *s = data;
- guint last_page;
+ VirtualConsole *vc;
gboolean on_vga;
if (!gtk_widget_get_realized(s->notebook)) {
return;
}
- last_page = gtk_notebook_get_current_page(nb);
-
- if (last_page) {
- gtk_widget_set_size_request(s->vc[last_page - 1].terminal, -1, -1);
+ vc = gd_vc_find_by_page(s, arg2);
+ if (!vc) {
+ return;
}
-
- on_vga = arg2 == 0;
-
+ 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);
@@ -1062,210 +1585,339 @@ static void gd_change_page(GtkNotebook *nb, gpointer arg1, guint arg2,
gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item),
TRUE);
}
-
- if (arg2 == 0) {
- gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->vga_item), TRUE);
- } else {
- VirtualConsole *vc = &s->vc[arg2 - 1];
- VteTerminal *term = VTE_TERMINAL(vc->terminal);
- int width, height;
-
- width = 80 * vte_terminal_get_char_width(term);
- height = 25 * vte_terminal_get_char_height(term);
-
- gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(vc->menu_item), TRUE);
- gtk_widget_set_size_request(vc->terminal, width, height);
- }
-
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_cursor(s, TRUE);
+ gd_update_windowsize(vc);
+ gd_update_cursor(vc);
}
-static gboolean gd_enter_event(GtkWidget *widget, GdkEventCrossing *crossing, gpointer data)
+static gboolean gd_enter_event(GtkWidget *widget, GdkEventCrossing *crossing,
+ gpointer opaque)
{
- GtkDisplayState *s = data;
+ VirtualConsole *vc = opaque;
+ GtkDisplayState *s = vc->s;
- if (!gd_is_grab_active(s) && gd_grab_on_hover(s)) {
- gd_grab_keyboard(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 data)
+static gboolean gd_leave_event(GtkWidget *widget, GdkEventCrossing *crossing,
+ gpointer opaque)
{
- GtkDisplayState *s = data;
+ VirtualConsole *vc = opaque;
+ GtkDisplayState *s = vc->s;
- if (!gd_is_grab_active(s) && gd_grab_on_hover(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,
- GdkEventCrossing *crossing, gpointer data)
+ GdkEventFocus *event, gpointer opaque)
{
- GtkDisplayState *s = data;
+ 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 int gd_vc_chr_write(CharDriverState *chr, const uint8_t *buf, int len)
+static GSList *gd_vc_menu_init(GtkDisplayState *s, VirtualConsole *vc,
+ int idx, GSList *group, GtkWidget *view_menu)
{
- VirtualConsole *vc = chr->opaque;
+ 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);
- return write(vc->fd, buf, len);
-}
+ 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);
-static int nb_vcs;
-static CharDriverState *vcs[MAX_VCS];
+ return gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(vc->menu_item));
+}
-static CharDriverState *gd_vc_handler(ChardevVC *unused)
+#if defined(CONFIG_VTE)
+static void gd_menu_copy(GtkMenuItem *item, void *opaque)
{
- CharDriverState *chr;
+ GtkDisplayState *s = opaque;
+ VirtualConsole *vc = gd_vc_find_current(s);
- chr = g_malloc0(sizeof(*chr));
- chr->chr_write = gd_vc_chr_write;
- /* defer OPENED events until our vc is fully initialized */
- chr->explicit_be_open = true;
+#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
+}
- vcs[nb_vcs++] = chr;
+static void gd_vc_adjustment_changed(GtkAdjustment *adjustment, void *opaque)
+{
+ VirtualConsole *vc = opaque;
- return chr;
+ 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);
+ }
}
-void early_gtk_display_init(void)
+static int gd_vc_chr_write(Chardev *chr, const uint8_t *buf, int len)
{
- register_vc_handler(gd_vc_handler);
+ VCChardev *vcd = VC_CHARDEV(chr);
+ VirtualConsole *vc = vcd->console;
+
+ vte_terminal_feed(VTE_TERMINAL(vc->vte.terminal), (const char *)buf, len);
+ return len;
}
-static gboolean gd_vc_in(GIOChannel *chan, GIOCondition cond, void *opaque)
+static void gd_vc_chr_set_echo(Chardev *chr, bool echo)
{
- VirtualConsole *vc = opaque;
- uint8_t buffer[1024];
- ssize_t len;
+ VCChardev *vcd = VC_CHARDEV(chr);
+ VirtualConsole *vc = vcd->console;
- len = read(vc->fd, buffer, sizeof(buffer));
- if (len <= 0) {
- return FALSE;
+ if (vc) {
+ vc->vte.echo = echo;
+ } else {
+ vcd->echo = echo;
}
+}
- qemu_chr_be_write(vc->chr, buffer, len);
+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;
+ }
- return TRUE;
+ 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 GSList *gd_vc_init(GtkDisplayState *s, VirtualConsole *vc, int index, GSList *group,
- GtkWidget *view_menu)
+static void char_gd_vc_class_init(ObjectClass *oc, void *data)
{
- const char *label;
- char buffer[32];
- char path[32];
-#if VTE_CHECK_VERSION(0, 26, 0)
- VtePty *pty;
-#endif
- GIOChannel *chan;
- GtkWidget *scrolled_window;
- GtkAdjustment *vadjustment;
- int master_fd, slave_fd;
+ 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_set_echo = gd_vc_chr_set_echo;
+}
- snprintf(buffer, sizeof(buffer), "vc%d", index);
- snprintf(path, sizeof(path), "<QEMU>/View/VC%d", index);
+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,
+};
- vc->chr = vcs[index];
+static gboolean gd_vc_in(VteTerminal *terminal, gchar *text, guint size,
+ gpointer user_data)
+{
+ VirtualConsole *vc = user_data;
- if (vc->chr->label) {
- label = vc->chr->label;
- } else {
- label = buffer;
+ 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);
+ }
+ }
}
- vc->menu_item = gtk_radio_menu_item_new_with_mnemonic(group, label);
- group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(vc->menu_item));
- gtk_menu_item_set_accel_path(GTK_MENU_ITEM(vc->menu_item), path);
- gtk_accel_map_add_entry(path, GDK_KEY_2 + index, HOTKEY_MODIFIERS);
+ qemu_chr_be_write(vc->vte.chr, (uint8_t *)text, (unsigned int)size);
+ return TRUE;
+}
- vc->terminal = vte_terminal_new();
+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;
+ 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
- master_fd = qemu_openpty_raw(&slave_fd, NULL);
- g_assert(master_fd != -1);
+ 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, 26, 0)
- pty = vte_pty_new_foreign(master_fd, NULL);
- vte_terminal_set_pty_object(VTE_TERMINAL(vc->terminal), pty);
+#if VTE_CHECK_VERSION(0, 28, 0)
+ vadjustment = gtk_scrollable_get_vadjustment
+ (GTK_SCROLLABLE(vc->vte.terminal));
#else
- vte_terminal_set_pty(VTE_TERMINAL(vc->terminal), master_fd);
+ vadjustment = vte_terminal_get_adjustment(VTE_TERMINAL(vc->vte.terminal));
#endif
- vte_terminal_set_scrollback_lines(VTE_TERMINAL(vc->terminal), -1);
+ box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2);
+ scrollbar = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, vadjustment);
- vadjustment = vte_terminal_get_adjustment(VTE_TERMINAL(vc->terminal));
+ gtk_box_pack_end(GTK_BOX(box), scrollbar, FALSE, FALSE, 0);
+ gtk_box_pack_end(GTK_BOX(box), vc->vte.terminal, TRUE, TRUE, 0);
- scrolled_window = gtk_scrolled_window_new(NULL, vadjustment);
- gtk_container_add(GTK_CONTAINER(scrolled_window), vc->terminal);
+ vc->vte.box = box;
+ vc->vte.scrollbar = scrollbar;
- vte_terminal_set_size(VTE_TERMINAL(vc->terminal), 80, 25);
+ g_signal_connect(vadjustment, "changed",
+ G_CALLBACK(gd_vc_adjustment_changed), vc);
- vc->fd = slave_fd;
- vc->chr->opaque = vc;
- vc->scrolled_window = scrolled_window;
+ 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));
- gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(vc->scrolled_window),
- GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ qemu_chr_be_event(vc->vte.chr, CHR_EVENT_OPENED);
- gtk_notebook_append_page(GTK_NOTEBOOK(s->notebook), scrolled_window, gtk_label_new(label));
- g_signal_connect(vc->menu_item, "activate",
- G_CALLBACK(gd_menu_switch_vc), s);
+ return group;
+}
- gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), vc->menu_item);
+static void gd_vcs_init(GtkDisplayState *s, GSList *group,
+ GtkWidget *view_menu)
+{
+ int i;
- qemu_chr_be_generic_open(vc->chr);
- if (vc->chr->init) {
- vc->chr->init(vc->chr);
+ 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++;
}
-
- chan = g_io_channel_unix_new(vc->fd);
- g_io_add_watch(chan, G_IO_IN, gd_vc_in, vc);
-
- return group;
}
+#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_GTK_GL)
+ 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, "key-press-event",
- G_CALLBACK(gd_window_key_event), s);
g_signal_connect(s->window, "delete-event",
G_CALLBACK(gd_window_close), s);
-#if GTK_CHECK_VERSION(3, 0, 0)
- g_signal_connect(s->drawing_area, "draw",
- G_CALLBACK(gd_draw_event), s);
-#else
- g_signal_connect(s->drawing_area, "expose-event",
- G_CALLBACK(gd_expose_event), s);
-#endif
- g_signal_connect(s->drawing_area, "motion-notify-event",
- G_CALLBACK(gd_motion_event), s);
- g_signal_connect(s->drawing_area, "button-press-event",
- G_CALLBACK(gd_button_event), s);
- g_signal_connect(s->drawing_area, "button-release-event",
- G_CALLBACK(gd_button_event), s);
- g_signal_connect(s->drawing_area, "key-press-event",
- G_CALLBACK(gd_key_event), s);
- g_signal_connect(s->drawing_area, "key-release-event",
- G_CALLBACK(gd_key_event), s);
-
g_signal_connect(s->pause_item, "activate",
G_CALLBACK(gd_menu_pause), s);
g_signal_connect(s->reset_item, "activate",
@@ -1274,6 +1926,10 @@ static void gd_connect_signals(GtkDisplayState *s)
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",
@@ -1284,28 +1940,19 @@ static void gd_connect_signals(GtkDisplayState *s)
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->vga_item, "activate",
- G_CALLBACK(gd_menu_switch_vc), 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);
- g_signal_connect(s->drawing_area, "enter-notify-event",
- G_CALLBACK(gd_enter_event), s);
- g_signal_connect(s->drawing_area, "leave-notify-event",
- G_CALLBACK(gd_leave_event), s);
- g_signal_connect(s->drawing_area, "focus-out-event",
- G_CALLBACK(gd_focus_out_event), s);
}
-static GtkWidget *gd_create_menu_machine(GtkDisplayState *s, GtkAccelGroup *accel_group)
+static GtkWidget *gd_create_menu_machine(GtkDisplayState *s)
{
GtkWidget *machine_menu;
GtkWidget *separator;
- GtkStockItem item;
machine_menu = gtk_menu_new();
- gtk_menu_set_accel_group(GTK_MENU(machine_menu), accel_group);
+ 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);
@@ -1313,61 +1960,151 @@ static GtkWidget *gd_create_menu_machine(GtkDisplayState *s, GtkAccelGroup *acce
separator = gtk_separator_menu_item_new();
gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), separator);
- s->reset_item = gtk_image_menu_item_new_with_mnemonic(_("_Reset"));
+ 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_image_menu_item_new_with_mnemonic(_("Power _Down"));
+ 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_image_menu_item_new_from_stock(GTK_STOCK_QUIT, NULL);
- gtk_stock_lookup(GTK_STOCK_QUIT, &item);
+ 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", item.keyval, item.modifier);
+ 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;
}
-static GtkWidget *gd_create_menu_view(GtkDisplayState *s, GtkAccelGroup *accel_group)
+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 defined(CONFIG_GTK_GL)
+ if (gtk_use_gl_area) {
+ vc->gfx.drawing_area = gtk_gl_area_new();
+ vc->gfx.dcl.ops = &dcl_gl_area_ops;
+ } else
+#endif /* CONFIG_GTK_GL */
+ {
+ 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;
+ }
+ } 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;
- int i;
+ QemuConsole *con;
+ int vc;
view_menu = gtk_menu_new();
- gtk_menu_set_accel_group(GTK_MENU(view_menu), accel_group);
+ gtk_menu_set_accel_group(GTK_MENU(view_menu), s->accel_group);
- s->full_screen_item =
- gtk_image_menu_item_new_from_stock(GTK_STOCK_FULLSCREEN, NULL);
- gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->full_screen_item),
- "<QEMU>/View/Full Screen");
- gtk_accel_map_add_entry("<QEMU>/View/Full Screen", GDK_KEY_f,
- HOTKEY_MODIFIERS);
+ 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_image_menu_item_new_from_stock(GTK_STOCK_ZOOM_IN, NULL);
+ 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_image_menu_item_new_from_stock(GTK_STOCK_ZOOM_OUT, NULL);
+ 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_image_menu_item_new_from_stock(GTK_STOCK_ZOOM_100, NULL);
+ 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,
@@ -1393,36 +2130,52 @@ static GtkWidget *gd_create_menu_view(GtkDisplayState *s, GtkAccelGroup *accel_g
separator = gtk_separator_menu_item_new();
gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator);
- s->vga_item = gtk_radio_menu_item_new_with_mnemonic(group, "_VGA");
- group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(s->vga_item));
- gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->vga_item),
- "<QEMU>/View/VGA");
- gtk_accel_map_add_entry("<QEMU>/View/VGA", GDK_KEY_1, HOTKEY_MODIFIERS);
- gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->vga_item);
-
- for (i = 0; i < nb_vcs; i++) {
- VirtualConsole *vc = &s->vc[i];
-
- group = gd_vc_init(s, vc, i, group, view_menu);
+ /* 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)
{
- GtkAccelGroup *accel_group;
+ GtkSettings *settings;
- accel_group = gtk_accel_group_new();
- s->machine_menu = gd_create_menu_machine(s, accel_group);
- s->view_menu = gd_create_menu_view(s, accel_group);
+ 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),
@@ -1433,84 +2186,76 @@ static void gd_create_menus(GtkDisplayState *s)
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", accel_group);
- gtk_window_add_accel_group(GTK_WINDOW(s->window), accel_group);
- s->accel_group = accel_group;
+ 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 const DisplayChangeListenerOps dcl_ops = {
- .dpy_name = "gtk",
- .dpy_gfx_update = gd_update,
- .dpy_gfx_switch = gd_switch,
- .dpy_refresh = gd_refresh,
- .dpy_mouse_set = gd_mouse_set,
- .dpy_cursor_define = gd_cursor_define,
-};
-void gtk_display_init(DisplayState *ds, bool full_screen)
+static gboolean gtkinit;
+
+static void gtk_display_init(DisplayState *ds, DisplayOptions *opts)
{
+ VirtualConsole *vc;
+
GtkDisplayState *s = g_malloc0(sizeof(*s));
- char *filename;
+ GdkDisplay *window_display;
+ GtkIconTheme *theme;
+ char *dir;
- gtk_init(NULL, NULL);
+ if (!gtkinit) {
+ fprintf(stderr, "gtk initialization failed\n");
+ exit(1);
+ }
+ assert(opts->type == DISPLAY_TYPE_GTK);
+ s->opts = opts;
- s->dcl.ops = &dcl_ops;
- s->dcl.con = qemu_console_lookup_by_index(0);
+ 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);
-#if GTK_CHECK_VERSION(3, 2, 0)
s->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
-#else
- s->vbox = gtk_vbox_new(FALSE, 0);
-#endif
s->notebook = gtk_notebook_new();
- s->drawing_area = gtk_drawing_area_new();
s->menu_bar = gtk_menu_bar_new();
- s->scale_x = 1.0;
- s->scale_y = 1.0;
s->free_scale = FALSE;
- setlocale(LC_ALL, "");
- bindtextdomain("qemu", CONFIG_QEMU_LOCALEDIR);
+ /* 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");
- s->null_cursor = gdk_cursor_new(GDK_BLANK_CURSOR);
+ 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_notebook_append_page(GTK_NOTEBOOK(s->notebook), s->drawing_area, gtk_label_new("VGA"));
-
- filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, "qemu_logo_no_text.svg");
- if (filename) {
- GError *error = NULL;
- GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(filename, &error);
- if (pixbuf) {
- gtk_window_set_icon(GTK_WINDOW(s->window), pixbuf);
- } else {
- g_error_free(error);
- }
- g_free(filename);
- }
+ gtk_window_set_icon_name(GTK_WINDOW(s->window), "qemu");
gd_create_menus(s);
gd_connect_signals(s);
- gtk_widget_add_events(s->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_KEY_PRESS_MASK);
- gtk_widget_set_double_buffered(s->drawing_area, FALSE);
- gtk_widget_set_can_focus(s->drawing_area, TRUE);
-
gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE);
gtk_notebook_set_show_border(GTK_NOTEBOOK(s->notebook), FALSE);
@@ -1523,11 +2268,81 @@ void gtk_display_init(DisplayState *ds, bool full_screen)
gtk_widget_show_all(s->window);
- if (full_screen) {
+ 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));
+ }
+}
+
+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(CONFIG_GTK_GL) && defined(GDK_WINDOWING_WAYLAND)
+ if (GDK_IS_WAYLAND_DISPLAY(gdk_display_get_default())) {
+ gtk_use_gl_area = true;
+ gtk_gl_area_init();
+ } else
+#endif
+ {
+ DisplayGLMode mode = opts->has_gl ? opts->gl : DISPLAYGL_MODE_ON;
+ gtk_egl_init(mode);
+ }
+#endif
+ }
- register_displaychangelistener(&s->dcl);
+ keycode_map = gd_get_keymap(&keycode_maplen);
- global_state = s;
+#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);
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..81b8d04ec
--- /dev/null
+++ b/ui/input-barrier.c
@@ -0,0 +1,741 @@
+/*
+ * 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.
+ */
+
+#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
index 92c44ca81..4791b089c 100644
--- a/ui/input.c
+++ b/ui/input.c
@@ -1,520 +1,594 @@
-/*
- * 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 "sysemu/sysemu.h"
-#include "monitor/monitor.h"
-#include "ui/console.h"
#include "qapi/error.h"
-#include "qmp-commands.h"
-#include "qapi-types.h"
-#include "ui/keymaps.h"
-
-struct QEMUPutMouseEntry {
- QEMUPutMouseEvent *qemu_put_mouse_event;
- void *qemu_put_mouse_event_opaque;
- int qemu_put_mouse_event_absolute;
- char *qemu_put_mouse_event_name;
-
- int index;
-
- /* used internally by qemu for handling mice */
- QTAILQ_ENTRY(QEMUPutMouseEntry) node;
-};
-
-struct QEMUPutKbdEntry {
- QEMUPutKBDEvent *put_kbd;
- void *opaque;
- QTAILQ_ENTRY(QEMUPutKbdEntry) next;
+#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;
};
-struct QEMUPutLEDEntry {
- QEMUPutLEDEvent *put_led;
- void *opaque;
- QTAILQ_ENTRY(QEMUPutLEDEntry) next;
+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(, QEMUPutLEDEntry) led_handlers =
- QTAILQ_HEAD_INITIALIZER(led_handlers);
-static QTAILQ_HEAD(, QEMUPutKbdEntry) kbd_handlers =
- QTAILQ_HEAD_INITIALIZER(kbd_handlers);
-static QTAILQ_HEAD(, QEMUPutMouseEntry) mouse_handlers =
- QTAILQ_HEAD_INITIALIZER(mouse_handlers);
+static QTAILQ_HEAD(, QemuInputHandlerState) handlers =
+ QTAILQ_HEAD_INITIALIZER(handlers);
static NotifierList mouse_mode_notifiers =
NOTIFIER_LIST_INITIALIZER(mouse_mode_notifiers);
-static const int key_defs[] = {
- [Q_KEY_CODE_SHIFT] = 0x2a,
- [Q_KEY_CODE_SHIFT_R] = 0x36,
-
- [Q_KEY_CODE_ALT] = 0x38,
- [Q_KEY_CODE_ALT_R] = 0xb8,
- [Q_KEY_CODE_ALTGR] = 0x64,
- [Q_KEY_CODE_ALTGR_R] = 0xe4,
- [Q_KEY_CODE_CTRL] = 0x1d,
- [Q_KEY_CODE_CTRL_R] = 0x9d,
-
- [Q_KEY_CODE_MENU] = 0xdd,
-
- [Q_KEY_CODE_ESC] = 0x01,
-
- [Q_KEY_CODE_1] = 0x02,
- [Q_KEY_CODE_2] = 0x03,
- [Q_KEY_CODE_3] = 0x04,
- [Q_KEY_CODE_4] = 0x05,
- [Q_KEY_CODE_5] = 0x06,
- [Q_KEY_CODE_6] = 0x07,
- [Q_KEY_CODE_7] = 0x08,
- [Q_KEY_CODE_8] = 0x09,
- [Q_KEY_CODE_9] = 0x0a,
- [Q_KEY_CODE_0] = 0x0b,
- [Q_KEY_CODE_MINUS] = 0x0c,
- [Q_KEY_CODE_EQUAL] = 0x0d,
- [Q_KEY_CODE_BACKSPACE] = 0x0e,
-
- [Q_KEY_CODE_TAB] = 0x0f,
- [Q_KEY_CODE_Q] = 0x10,
- [Q_KEY_CODE_W] = 0x11,
- [Q_KEY_CODE_E] = 0x12,
- [Q_KEY_CODE_R] = 0x13,
- [Q_KEY_CODE_T] = 0x14,
- [Q_KEY_CODE_Y] = 0x15,
- [Q_KEY_CODE_U] = 0x16,
- [Q_KEY_CODE_I] = 0x17,
- [Q_KEY_CODE_O] = 0x18,
- [Q_KEY_CODE_P] = 0x19,
- [Q_KEY_CODE_BRACKET_LEFT] = 0x1a,
- [Q_KEY_CODE_BRACKET_RIGHT] = 0x1b,
- [Q_KEY_CODE_RET] = 0x1c,
-
- [Q_KEY_CODE_A] = 0x1e,
- [Q_KEY_CODE_S] = 0x1f,
- [Q_KEY_CODE_D] = 0x20,
- [Q_KEY_CODE_F] = 0x21,
- [Q_KEY_CODE_G] = 0x22,
- [Q_KEY_CODE_H] = 0x23,
- [Q_KEY_CODE_J] = 0x24,
- [Q_KEY_CODE_K] = 0x25,
- [Q_KEY_CODE_L] = 0x26,
- [Q_KEY_CODE_SEMICOLON] = 0x27,
- [Q_KEY_CODE_APOSTROPHE] = 0x28,
- [Q_KEY_CODE_GRAVE_ACCENT] = 0x29,
-
- [Q_KEY_CODE_BACKSLASH] = 0x2b,
- [Q_KEY_CODE_Z] = 0x2c,
- [Q_KEY_CODE_X] = 0x2d,
- [Q_KEY_CODE_C] = 0x2e,
- [Q_KEY_CODE_V] = 0x2f,
- [Q_KEY_CODE_B] = 0x30,
- [Q_KEY_CODE_N] = 0x31,
- [Q_KEY_CODE_M] = 0x32,
- [Q_KEY_CODE_COMMA] = 0x33,
- [Q_KEY_CODE_DOT] = 0x34,
- [Q_KEY_CODE_SLASH] = 0x35,
-
- [Q_KEY_CODE_ASTERISK] = 0x37,
-
- [Q_KEY_CODE_SPC] = 0x39,
- [Q_KEY_CODE_CAPS_LOCK] = 0x3a,
- [Q_KEY_CODE_F1] = 0x3b,
- [Q_KEY_CODE_F2] = 0x3c,
- [Q_KEY_CODE_F3] = 0x3d,
- [Q_KEY_CODE_F4] = 0x3e,
- [Q_KEY_CODE_F5] = 0x3f,
- [Q_KEY_CODE_F6] = 0x40,
- [Q_KEY_CODE_F7] = 0x41,
- [Q_KEY_CODE_F8] = 0x42,
- [Q_KEY_CODE_F9] = 0x43,
- [Q_KEY_CODE_F10] = 0x44,
- [Q_KEY_CODE_NUM_LOCK] = 0x45,
- [Q_KEY_CODE_SCROLL_LOCK] = 0x46,
-
- [Q_KEY_CODE_KP_DIVIDE] = 0xb5,
- [Q_KEY_CODE_KP_MULTIPLY] = 0x37,
- [Q_KEY_CODE_KP_SUBTRACT] = 0x4a,
- [Q_KEY_CODE_KP_ADD] = 0x4e,
- [Q_KEY_CODE_KP_ENTER] = 0x9c,
- [Q_KEY_CODE_KP_DECIMAL] = 0x53,
- [Q_KEY_CODE_SYSRQ] = 0x54,
-
- [Q_KEY_CODE_KP_0] = 0x52,
- [Q_KEY_CODE_KP_1] = 0x4f,
- [Q_KEY_CODE_KP_2] = 0x50,
- [Q_KEY_CODE_KP_3] = 0x51,
- [Q_KEY_CODE_KP_4] = 0x4b,
- [Q_KEY_CODE_KP_5] = 0x4c,
- [Q_KEY_CODE_KP_6] = 0x4d,
- [Q_KEY_CODE_KP_7] = 0x47,
- [Q_KEY_CODE_KP_8] = 0x48,
- [Q_KEY_CODE_KP_9] = 0x49,
-
- [Q_KEY_CODE_LESS] = 0x56,
-
- [Q_KEY_CODE_F11] = 0x57,
- [Q_KEY_CODE_F12] = 0x58,
-
- [Q_KEY_CODE_PRINT] = 0xb7,
-
- [Q_KEY_CODE_HOME] = 0xc7,
- [Q_KEY_CODE_PGUP] = 0xc9,
- [Q_KEY_CODE_PGDN] = 0xd1,
- [Q_KEY_CODE_END] = 0xcf,
-
- [Q_KEY_CODE_LEFT] = 0xcb,
- [Q_KEY_CODE_UP] = 0xc8,
- [Q_KEY_CODE_DOWN] = 0xd0,
- [Q_KEY_CODE_RIGHT] = 0xcd,
-
- [Q_KEY_CODE_INSERT] = 0xd2,
- [Q_KEY_CODE_DELETE] = 0xd3,
-#ifdef NEED_CPU_H
-#if defined(TARGET_SPARC) && !defined(TARGET_SPARC64)
- [Q_KEY_CODE_STOP] = 0xf0,
- [Q_KEY_CODE_AGAIN] = 0xf1,
- [Q_KEY_CODE_PROPS] = 0xf2,
- [Q_KEY_CODE_UNDO] = 0xf3,
- [Q_KEY_CODE_FRONT] = 0xf4,
- [Q_KEY_CODE_COPY] = 0xf5,
- [Q_KEY_CODE_OPEN] = 0xf6,
- [Q_KEY_CODE_PASTE] = 0xf7,
- [Q_KEY_CODE_FIND] = 0xf8,
- [Q_KEY_CODE_CUT] = 0xf9,
- [Q_KEY_CODE_LF] = 0xfa,
- [Q_KEY_CODE_HELP] = 0xfb,
- [Q_KEY_CODE_META_L] = 0xfc,
- [Q_KEY_CODE_META_R] = 0xfd,
- [Q_KEY_CODE_COMPOSE] = 0xfe,
-#endif
-#endif
- [Q_KEY_CODE_MAX] = 0,
-};
+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;
-int index_from_key(const char *key)
+QemuInputHandlerState *qemu_input_handler_register(DeviceState *dev,
+ QemuInputHandler *handler)
{
- int i;
+ QemuInputHandlerState *s = g_new0(QemuInputHandlerState, 1);
+ static int id = 1;
- for (i = 0; QKeyCode_lookup[i] != NULL; i++) {
- if (!strcmp(key, QKeyCode_lookup[i])) {
- break;
- }
- }
+ s->dev = dev;
+ s->handler = handler;
+ s->id = id++;
+ QTAILQ_INSERT_TAIL(&handlers, s, node);
- /* Return Q_KEY_CODE_MAX if the key is invalid */
- return i;
+ qemu_input_check_mode_change();
+ return s;
}
-int index_from_keycode(int code)
+void qemu_input_handler_activate(QemuInputHandlerState *s)
{
- int i;
-
- for (i = 0; i < Q_KEY_CODE_MAX; i++) {
- if (key_defs[i] == code) {
- break;
- }
- }
-
- /* Return Q_KEY_CODE_MAX if the code is invalid */
- return i;
+ QTAILQ_REMOVE(&handlers, s, node);
+ QTAILQ_INSERT_HEAD(&handlers, s, node);
+ qemu_input_check_mode_change();
}
-static int *keycodes;
-static int keycodes_size;
-static QEMUTimer *key_timer;
-
-static int keycode_from_keyvalue(const KeyValue *value)
+void qemu_input_handler_deactivate(QemuInputHandlerState *s)
{
- if (value->kind == KEY_VALUE_KIND_QCODE) {
- return key_defs[value->qcode];
- } else {
- assert(value->kind == KEY_VALUE_KIND_NUMBER);
- return value->number;
- }
+ QTAILQ_REMOVE(&handlers, s, node);
+ QTAILQ_INSERT_TAIL(&handlers, s, node);
+ qemu_input_check_mode_change();
}
-static void free_keycodes(void)
+void qemu_input_handler_unregister(QemuInputHandlerState *s)
{
- g_free(keycodes);
- keycodes = NULL;
- keycodes_size = 0;
+ QTAILQ_REMOVE(&handlers, s, node);
+ g_free(s);
+ qemu_input_check_mode_change();
}
-static void release_keys(void *opaque)
+void qemu_input_handler_bind(QemuInputHandlerState *s,
+ const char *device_id, int head,
+ Error **errp)
{
- while (keycodes_size > 0) {
- if (keycodes[--keycodes_size] & SCANCODE_GREY) {
- kbd_put_keycode(SCANCODE_EMUL0);
- }
- kbd_put_keycode(keycodes[keycodes_size] | SCANCODE_UP);
+ QemuConsole *con;
+ Error *err = NULL;
+
+ con = qemu_console_lookup_by_device_name(device_id, head, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
}
- free_keycodes();
+ s->con = con;
}
-void qmp_send_key(KeyValueList *keys, bool has_hold_time, int64_t hold_time,
- Error **errp)
+static QemuInputHandlerState*
+qemu_input_find_handler(uint32_t mask, QemuConsole *con)
{
- int keycode;
- KeyValueList *p;
+ QemuInputHandlerState *s;
- if (!key_timer) {
- key_timer = qemu_new_timer_ns(vm_clock, release_keys, NULL);
+ QTAILQ_FOREACH(s, &handlers, node) {
+ if (s->con == NULL || s->con != con) {
+ continue;
+ }
+ if (mask & s->handler->mask) {
+ return s;
+ }
}
- if (keycodes != NULL) {
- qemu_del_timer(key_timer);
- release_keys(NULL);
+ QTAILQ_FOREACH(s, &handlers, node) {
+ if (s->con != NULL) {
+ continue;
+ }
+ if (mask & s->handler->mask) {
+ return s;
+ }
}
+ return NULL;
+}
- if (!has_hold_time) {
- hold_time = 100;
+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;
+ }
}
- for (p = keys; p != NULL; p = p->next) {
- /* key down events */
- keycode = keycode_from_keyvalue(p->value);
- if (keycode < 0x01 || keycode > 0xff) {
- error_setg(errp, "invalid hex keycode 0x%x", keycode);
- free_keycodes();
+ 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;
}
+ }
- if (keycode & SCANCODE_GREY) {
- kbd_put_keycode(SCANCODE_EMUL0);
- }
- kbd_put_keycode(keycode & SCANCODE_KEYCODEMASK);
+ for (e = events; e != NULL; e = e->next) {
+ InputEvent *evt = e->value;
- keycodes = g_realloc(keycodes, sizeof(int) * (keycodes_size + 1));
- keycodes[keycodes_size++] = keycode;
+ 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);
+ }
}
- /* delayed key up events */
- qemu_mod_timer(key_timer, qemu_get_clock_ns(vm_clock) +
- muldiv64(get_ticks_per_sec(), hold_time, 1000));
+ qemu_input_event_sync();
}
-QEMUPutKbdEntry *qemu_add_kbd_event_handler(QEMUPutKBDEvent *func, void *opaque)
+static int qemu_input_transform_invert_abs_value(int value)
{
- QEMUPutKbdEntry *entry;
-
- entry = g_malloc0(sizeof(QEMUPutKbdEntry));
- entry->put_kbd = func;
- entry->opaque = opaque;
- QTAILQ_INSERT_HEAD(&kbd_handlers, entry, next);
- return entry;
+ return (int64_t)INPUT_EVENT_ABS_MAX - value + INPUT_EVENT_ABS_MIN;
}
-void qemu_remove_kbd_event_handler(QEMUPutKbdEntry *entry)
+static void qemu_input_transform_abs_rotate(InputEvent *evt)
{
- QTAILQ_REMOVE(&kbd_handlers, entry, next);
+ 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 check_mode_change(void)
+static void qemu_input_event_trace(QemuConsole *src, InputEvent *evt)
{
- static int current_is_absolute, current_has_absolute;
- int is_absolute;
- int has_absolute;
+ 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;
+ }
+}
- is_absolute = kbd_mouse_is_absolute();
- has_absolute = kbd_mouse_has_absolute();
+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);
+ }
+}
- if (is_absolute != current_is_absolute ||
- has_absolute != current_has_absolute) {
- notifier_list_notify(&mouse_mode_notifiers, NULL);
+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);
}
+}
- current_is_absolute = is_absolute;
- current_has_absolute = has_absolute;
+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++;
}
-QEMUPutMouseEntry *qemu_add_mouse_event_handler(QEMUPutMouseEvent *func,
- void *opaque, int absolute,
- const char *name)
+static void qemu_input_queue_sync(QemuInputEventQueueHead *queue)
{
- QEMUPutMouseEntry *s;
- static int mouse_index = 0;
+ QemuInputEventQueue *item = g_new0(QemuInputEventQueue, 1);
- s = g_malloc0(sizeof(QEMUPutMouseEntry));
+ item->type = QEMU_INPUT_QUEUE_SYNC;
+ QTAILQ_INSERT_TAIL(queue, item, node);
+ queue_count++;
+}
- s->qemu_put_mouse_event = func;
- s->qemu_put_mouse_event_opaque = opaque;
- s->qemu_put_mouse_event_absolute = absolute;
- s->qemu_put_mouse_event_name = g_strdup(name);
- s->index = mouse_index++;
+void qemu_input_event_send_impl(QemuConsole *src, InputEvent *evt)
+{
+ QemuInputHandlerState *s;
- QTAILQ_INSERT_TAIL(&mouse_handlers, s, node);
+ qemu_input_event_trace(src, evt);
- check_mode_change();
+ /* pre processing */
+ if (graphic_rotate && (evt->type == INPUT_EVENT_KIND_ABS)) {
+ qemu_input_transform_abs_rotate(evt);
+ }
- return s;
+ /* 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_activate_mouse_event_handler(QEMUPutMouseEntry *entry)
+void qemu_input_event_send(QemuConsole *src, InputEvent *evt)
{
- QTAILQ_REMOVE(&mouse_handlers, entry, node);
- QTAILQ_INSERT_HEAD(&mouse_handlers, entry, node);
+ /* 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;
+ }
- check_mode_change();
+ replay_input_event(src, evt);
}
-void qemu_remove_mouse_event_handler(QEMUPutMouseEntry *entry)
+void qemu_input_event_sync_impl(void)
{
- QTAILQ_REMOVE(&mouse_handlers, entry, node);
+ QemuInputHandlerState *s;
- g_free(entry->qemu_put_mouse_event_name);
- g_free(entry);
+ trace_input_event_sync();
- check_mode_change();
+ QTAILQ_FOREACH(s, &handlers, node) {
+ if (!s->events) {
+ continue;
+ }
+ if (s->handler->sync) {
+ s->handler->sync(s->dev);
+ }
+ s->events = 0;
+ }
}
-QEMUPutLEDEntry *qemu_add_led_event_handler(QEMUPutLEDEvent *func,
- void *opaque)
+void qemu_input_event_sync(void)
{
- QEMUPutLEDEntry *s;
+ if (!runstate_is_running() && !runstate_check(RUN_STATE_SUSPENDED)) {
+ return;
+ }
- s = g_malloc0(sizeof(QEMUPutLEDEntry));
+ replay_input_sync_event();
+}
- s->put_led = func;
- s->opaque = opaque;
- QTAILQ_INSERT_TAIL(&led_handlers, s, next);
- return s;
+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_remove_led_event_handler(QEMUPutLEDEntry *entry)
+void qemu_input_event_send_key(QemuConsole *src, KeyValue *key, bool down)
{
- if (entry == NULL)
- return;
- QTAILQ_REMOVE(&led_handlers, entry, next);
- g_free(entry);
+ 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 kbd_put_keycode(int keycode)
+void qemu_input_event_send_key_number(QemuConsole *src, int num, bool down)
{
- QEMUPutKbdEntry *entry = QTAILQ_FIRST(&kbd_handlers);
+ 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 (entry) {
- entry->put_kbd(entry->opaque, keycode);
+
+ 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 kbd_put_ledstate(int ledstate)
+void qemu_input_queue_btn(QemuConsole *src, InputButton btn, bool down)
{
- QEMUPutLEDEntry *cursor;
-
- QTAILQ_FOREACH(cursor, &led_handlers, next) {
- cursor->put_led(cursor->opaque, ledstate);
- }
+ InputBtnEvent bevt = {
+ .button = btn,
+ .down = down,
+ };
+ InputEvent evt = {
+ .type = INPUT_EVENT_KIND_BTN,
+ .u.btn.data = &bevt,
+ };
+
+ qemu_input_event_send(src, &evt);
}
-void kbd_mouse_event(int dx, int dy, int dz, int buttons_state)
+void qemu_input_update_buttons(QemuConsole *src, uint32_t *button_map,
+ uint32_t button_old, uint32_t button_new)
{
- QEMUPutMouseEntry *entry;
- QEMUPutMouseEvent *mouse_event;
- void *mouse_event_opaque;
- int width, height;
+ InputButton btn;
+ uint32_t mask;
- if (!runstate_is_running() && !runstate_check(RUN_STATE_SUSPENDED)) {
- return;
- }
- if (QTAILQ_EMPTY(&mouse_handlers)) {
- return;
+ 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);
}
+}
- entry = QTAILQ_FIRST(&mouse_handlers);
+bool qemu_input_is_absolute(void)
+{
+ QemuInputHandlerState *s;
- mouse_event = entry->qemu_put_mouse_event;
- mouse_event_opaque = entry->qemu_put_mouse_event_opaque;
+ s = qemu_input_find_handler(INPUT_EVENT_MASK_REL | INPUT_EVENT_MASK_ABS,
+ NULL);
+ return (s != NULL) && (s->handler->mask & INPUT_EVENT_MASK_ABS);
+}
- if (mouse_event) {
- if (entry->qemu_put_mouse_event_absolute) {
- width = 0x7fff;
- height = 0x7fff;
- } else {
- width = graphic_width - 1;
- height = graphic_height - 1;
- }
+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;
- switch (graphic_rotate) {
- case 0:
- mouse_event(mouse_event_opaque,
- dx, dy, dz, buttons_state);
- break;
- case 90:
- mouse_event(mouse_event_opaque,
- width - dy, dx, dz, buttons_state);
- break;
- case 180:
- mouse_event(mouse_event_opaque,
- width - dx, height - dy, dz, buttons_state);
- break;
- case 270:
- mouse_event(mouse_event_opaque,
- dy, height - dx, dz, buttons_state);
- break;
- }
+ if (range_in < 1) {
+ return min_out + range_out / 2;
}
+ return ((int64_t)value - min_in) * range_out / range_in + min_out;
}
-int kbd_mouse_is_absolute(void)
+void qemu_input_queue_rel(QemuConsole *src, InputAxis axis, int value)
{
- if (QTAILQ_EMPTY(&mouse_handlers)) {
- return 0;
- }
+ InputMoveEvent move = {
+ .axis = axis,
+ .value = value,
+ };
+ InputEvent evt = {
+ .type = INPUT_EVENT_KIND_REL,
+ .u.rel.data = &move,
+ };
+
+ qemu_input_event_send(src, &evt);
+}
- return QTAILQ_FIRST(&mouse_handlers)->qemu_put_mouse_event_absolute;
+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);
}
-int kbd_mouse_has_absolute(void)
+void qemu_input_check_mode_change(void)
{
- QEMUPutMouseEntry *entry;
+ static int current_is_absolute;
+ int is_absolute;
- QTAILQ_FOREACH(entry, &mouse_handlers, node) {
- if (entry->qemu_put_mouse_event_absolute) {
- return 1;
- }
+ 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);
}
- return 0;
+ 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;
- QEMUPutMouseEntry *cursor;
+ MouseInfoList *info;
+ QemuInputHandlerState *s;
bool current = true;
- QTAILQ_FOREACH(cursor, &mouse_handlers, node) {
- MouseInfoList *info = g_malloc0(sizeof(*info));
- info->value = g_malloc0(sizeof(*info->value));
- info->value->name = g_strdup(cursor->qemu_put_mouse_event_name);
- info->value->index = cursor->index;
- info->value->absolute = !!cursor->qemu_put_mouse_event_absolute;
+ QTAILQ_FOREACH(s, &handlers, node) {
+ if (!(s->handler->mask &
+ (INPUT_EVENT_MASK_REL | INPUT_EVENT_MASK_ABS))) {
+ continue;
+ }
+
+ info = g_new0(MouseInfoList, 1);
+ info->value = g_new0(MouseInfo, 1);
+ info->value->index = s->id;
+ info->value->name = g_strdup(s->handler->name);
+ info->value->absolute = s->handler->mask & INPUT_EVENT_MASK_ABS;
info->value->current = current;
current = false;
-
info->next = mice_list;
mice_list = info;
}
@@ -522,38 +596,29 @@ MouseInfoList *qmp_query_mice(Error **errp)
return mice_list;
}
-void do_mouse_set(Monitor *mon, const QDict *qdict)
+void hmp_mouse_set(Monitor *mon, const QDict *qdict)
{
- QEMUPutMouseEntry *cursor;
+ QemuInputHandlerState *s;
int index = qdict_get_int(qdict, "index");
int found = 0;
- if (QTAILQ_EMPTY(&mouse_handlers)) {
- monitor_printf(mon, "No mouse devices connected\n");
- return;
- }
-
- QTAILQ_FOREACH(cursor, &mouse_handlers, node) {
- if (cursor->index == index) {
- found = 1;
- qemu_activate_mouse_event_handler(cursor);
- break;
+ 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) {
- monitor_printf(mon, "Mouse at given index not found\n");
+ error_report("Mouse at index '%d' not found", index);
}
- check_mode_change();
-}
-
-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);
+ 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/keycodemapdb/LICENSE.BSD b/ui/keycodemapdb/LICENSE.BSD
new file mode 100644
index 000000000..ec1a29d34
--- /dev/null
+++ b/ui/keycodemapdb/LICENSE.BSD
@@ -0,0 +1,27 @@
+Copyright (c) Individual contributors.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ 2. 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.
+
+ 3. Neither the name of PyCA Cryptography 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 COPYRIGHT OWNER 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.
diff --git a/ui/keycodemapdb/LICENSE.GPL2 b/ui/keycodemapdb/LICENSE.GPL2
new file mode 100644
index 000000000..d511905c1
--- /dev/null
+++ b/ui/keycodemapdb/LICENSE.GPL2
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/ui/keycodemapdb/README b/ui/keycodemapdb/README
new file mode 100644
index 000000000..a3c3c9f86
--- /dev/null
+++ b/ui/keycodemapdb/README
@@ -0,0 +1,114 @@
+ Key code / scan code / key symbol mapping database
+ ==================================================
+
+This module provides a database that maps between different
+key code / scan code / key symbol sets:
+
+ - Linux evdev
+ - OS-X
+ - AT Set 1
+ - AT Set 2
+ - AT Set 3
+ - XT
+ - Linux XT KBD driver
+ - USB HID
+ - Win32
+ - XWin XT
+ - XKBD XT
+ - Xorg Evdev
+ - Xorg KBD
+ - Xorg OS-X
+ - XOrg Cygwin
+ - RFB
+
+Licensing
+---------
+
+The contents of this package are dual licensed under the terms of:
+
+ - GNU General Public License (version 2 or later)
+ - 3-clause BSD License
+
+The output files generated by keymap-gen may be distributed & used under
+the terms of either of the above licenses.
+
+Data formats
+------------
+
+The following output formats are possible
+
+ - Code map
+
+ An array mapping between key code sets values
+
+ Indexes in the array are values from the source code set.
+ Entries in the array are values from the target code set
+
+
+ - Code table
+
+ An array listing all values in a key code set
+
+ Indexes in the array are simply a numeric counter
+ Entries in the array are values from the key code set
+
+ The size of the array matches the total number of entries in
+ the keycode database.
+
+
+ - Name map
+
+ An array mapping between key code sets values and names
+
+ Indexes in the array are values from the source code set
+ Entries in the array are names from the target code set
+
+
+ - Name table
+
+ An array listing all names in a key code set
+
+ Indexes in the array are simply a numeric counter
+ Entries in the array are values from the key code set
+
+ The size of the array matches the total number of entries in
+ the keycode database.
+
+
+Output languages
+----------------
+
+The tool is capable of generating data tables for the following
+programming languages / environments
+
+ - Standard C
+ - GLib2 (standard C, but with GLib2 data types)
+ - Python
+ - Perl
+
+
+Usage
+-----
+
+Map values from AT Set 1 to USB HID, generating tables for the
+C programming language
+
+ $ keymap-gen --lang stdc code-map data/keymaps.csv atset1 usb
+
+Generate a tables of names for Linux key codes, OS-X key codes,
+in python - equivalent array indexes map between the two sets.
+A variable name override is used
+
+ $ keymap-gen --varname linux_keycodes --lang stdc \
+ code-table data/keymaps.csv linux
+ $ keymap-gen --varname osx_keycodes --lang stdc \
+ code-table data/keymaps.csv os-x
+
+Generate a mapping from XOrg XWin values to Win32 names
+
+ $ keymap-gen --lang perl name-map data/keymaps.csv xorgxwin win32
+
+Generate a table of names for Linux key codes in Perl
+
+ $ keymap-gen --lang perl name-table data/keymaps.csv linux
+
diff --git a/ui/keycodemapdb/data/README b/ui/keycodemapdb/data/README
new file mode 100644
index 000000000..6b5653495
--- /dev/null
+++ b/ui/keycodemapdb/data/README
@@ -0,0 +1,89 @@
+This directory contains the raw data for mapping between different
+keyboard codes. Naming if often based on the US keyboard layout, but
+does not indicate the symbol actually generated by the key.
+
+The columns currently in this data set are:
+
+Linux
+-----
+
+Name and value of the hardware independent keycodes used by the linux
+kernel and exposed through the input subsystem.
+
+References: linux/input.h
+
+macOS
+-----
+
+Low level key codes as exposed by Mac OS X/macOS.
+
+References: Carbon/HIToolbox/Events.h
+
+PC scan code sets
+-----------------
+
+Scan codes for the three orignal PC keyboard generations:
+
+ Set 1: XT
+ Set 2: AT
+ Set 3: PS/2
+
+The sets include codes for modern keys as well and not just the keys
+present on those original keyboards.
+
+References: linux/drivers/input/keyboard/atkbd.c
+
+USB HID
+-------
+
+Codes as specified by the HID profile in USB.
+
+References: linux/drivers/hid/usbhid/usbkbd.c
+
+Windows Virtual-key codes
+-------------------------
+
+The low level, hardware independent "VKEYs" exposed by Windows.
+
+References: mingw32/winuser.h
+
+XWin XT
+-------
+
+X11 keycodes generated by the XWin server. Based on the XT scan code
+set.
+
+References: xorg-server/hw/xwin/{winkeybd.c,winkeynames.h}
+
+Xfree86 KBD XT
+--------------
+
+X11 keycodes generated by the Xfree86 keyboard drivers. Based on the XT
+scan code set.
+
+References: xf86-input-keyboard/src/at_scancode.c
+
+X11 keysyms
+-----------
+
+Corresponding X11 keysym value(s) for a US keyboard layout.
+
+WARNING: These columns represent symbols, not physical keys, and should
+ be used with extreme care.
+
+References: http://cgit.freedesktop.org/xorg/proto/x11proto/plain/keysymdef.h
+
+HTML KeyboardEvent.code
+-----------------------
+
+Key codes seen in the KeyboardEvent.code attribute as part of the
+UI Events specification.
+
+References: https://www.w3.org/TR/uievents-code/
+
+XKEYBOARD key names
+-------------------
+
+Hardware independent key names as used in the XKEYBOARD extension.
+
+References: /usr/share/X11/xkb/keycodes/
diff --git a/ui/keycodemapdb/data/keymaps.csv b/ui/keycodemapdb/data/keymaps.csv
new file mode 100644
index 000000000..bc2376c8e
--- /dev/null
+++ b/ui/keycodemapdb/data/keymaps.csv
@@ -0,0 +1,539 @@
+"Linux Name","Linux Keycode","OS-X Name","OS-X Keycode","AT set1 keycode","AT set2 keycode","AT set3 keycode","USB Keycodes","Win32 Name","Win32 Keycode","Xwin XT","Xfree86 KBD XT","X11 keysym name","X11 keysym","HTML code","XKB key name","QEMU QKeyCode","Sun KBD","Apple ADB"
+KEY_RESERVED,0,,0xff,,,,,,,,,,,,,unmapped,,0xff
+KEY_ESC,1,Escape,0x35,0x01,0x76,0x08,41,VK_ESCAPE,0x1b,1,1,XK_Escape,0xff1b,Escape,ESC,esc,0x1d,0x35
+KEY_1,2,ANSI_1,0x12,0x02,0x16,0x16,30,VK_1,0x31,2,2,XK_1,0x0031,Digit1,AE01,1,0x1e,0x12
+KEY_1,2,ANSI_1,0x12,0x02,0x16,0x16,30,VK_1,0x31,2,2,XK_exclam,0x0021,Digit1,AE01,1,0x1e,0x12
+KEY_2,3,ANSI_2,0x13,0x03,0x1e,0x1e,31,VK_2,0x32,3,3,XK_2,0x0032,Digit2,AE02,2,0x1f,0x13
+KEY_2,3,ANSI_2,0x13,0x03,0x1e,0x1e,31,VK_2,0x32,3,3,XK_at,0x0040,Digit2,AE02,2,0x1f,0x13
+KEY_3,4,ANSI_3,0x14,0x04,0x26,0x26,32,VK_3,0x33,4,4,XK_3,0x0033,Digit3,AE03,3,0x20,0x14
+KEY_3,4,ANSI_3,0x14,0x04,0x26,0x26,32,VK_3,0x33,4,4,XK_numbersign,0x0023,Digit3,AE03,3,0x20,0x14
+KEY_4,5,ANSI_4,0x15,0x05,0x25,0x25,33,VK_4,0x34,5,5,XK_4,0x0034,Digit4,AE04,4,0x21,0x15
+KEY_4,5,ANSI_4,0x15,0x05,0x25,0x25,33,VK_4,0x34,5,5,XK_dollar,0x0024,Digit4,AE04,4,0x21,0x15
+KEY_5,6,ANSI_5,0x17,0x06,0x2e,0x2e,34,VK_5,0x35,6,6,XK_5,0x0035,Digit5,AE05,5,0x22,0x17
+KEY_5,6,ANSI_5,0x17,0x06,0x2e,0x2e,34,VK_5,0x35,6,6,XK_percent,0x0025,Digit5,AE05,5,0x22,0x17
+KEY_6,7,ANSI_6,0x16,0x07,0x36,0x36,35,VK_6,0x36,7,7,XK_6,0x0036,Digit6,AE06,6,0x23,0x16
+KEY_6,7,ANSI_6,0x16,0x07,0x36,0x36,35,VK_6,0x36,7,7,XK_asciicircum,0x005e,Digit6,AE06,6,0x23,0x16
+KEY_7,8,ANSI_7,0x1a,0x08,0x3d,0x3d,36,VK_7,0x37,8,8,XK_7,0x0037,Digit7,AE07,7,0x24,0x1a
+KEY_7,8,ANSI_7,0x1a,0x08,0x3d,0x3d,36,VK_7,0x37,8,8,XK_ampersand,0x0026,Digit7,AE07,7,0x24,0x1a
+KEY_8,9,ANSI_8,0x1c,0x09,0x3e,0x3e,37,VK_8,0x38,9,9,XK_8,0x0038,Digit8,AE08,8,0x25,0x1c
+KEY_8,9,ANSI_8,0x1c,0x09,0x3e,0x3e,37,VK_8,0x38,9,9,XK_asterisk,0x002a,Digit8,AE08,8,0x25,0x1c
+KEY_9,10,ANSI_9,0x19,0x0a,0x46,0x46,38,VK_9,0x39,10,10,XK_9,0x0039,Digit9,AE09,9,0x26,0x19
+KEY_9,10,ANSI_9,0x19,0x0a,0x46,0x46,38,VK_9,0x39,10,10,XK_parenleft,0x0028,Digit9,AE09,9,0x26,0x19
+KEY_0,11,ANSI_0,0x1d,0x0b,0x45,0x45,39,VK_0,0x30,11,11,XK_0,0x0030,Digit0,AE10,0,0x27,0x1d
+KEY_0,11,ANSI_0,0x1d,0x0b,0x45,0x45,39,VK_0,0x30,11,11,XK_parenright,0x0029,Digit0,AE10,0,0x27,0x1d
+KEY_MINUS,12,ANSI_Minus,0x1b,0x0c,0x4e,0x4e,45,VK_OEM_MINUS,0xbd,12,12,XK_minus,0x002d,Minus,AE11,minus,0x28,0x1b
+KEY_MINUS,12,ANSI_Minus,0x1b,0x0c,0x4e,0x4e,45,VK_OEM_MINUS,0xbd,12,12,XK_underscore,0x005f,Minus,AE11,minus,0x28,0x1b
+KEY_EQUAL,13,ANSI_Equal,0x18,0x0d,0x55,0x55,46,VK_OEM_PLUS,0xbb,13,13,XK_equal,0x003d,Equal,AE12,equal,0x29,0x18
+KEY_EQUAL,13,ANSI_Equal,0x18,0x0d,0x55,0x55,46,VK_OEM_PLUS,0xbb,13,13,XK_plus,0x002b,Equal,AE12,equal,0x29,0x18
+KEY_BACKSPACE,14,Delete,0x33,0x0e,0x66,0x66,42,VK_BACK,0x08,14,14,XK_BackSpace,0xff08,Backspace,BKSP,backspace,0x2b,0x33
+KEY_TAB,15,Tab,0x30,0x0f,0x0d,0x0d,43,VK_TAB,0x09,15,15,XK_Tab,0xff09,Tab,TAB,tab,0x35,0x30
+KEY_Q,16,ANSI_Q,0xc,0x10,0x15,0x15,20,VK_Q,0x51,16,16,XK_Q,0x0051,KeyQ,AD01,q,0x36,0xc
+KEY_Q,16,ANSI_Q,0xc,0x10,0x15,0x15,20,VK_Q,0x51,16,16,XK_q,0x0071,KeyQ,AD01,q,0x36,0xc
+KEY_W,17,ANSI_W,0xd,0x11,0x1d,0x1d,26,VK_W,0x57,17,17,XK_W,0x0057,KeyW,AD02,w,0x37,0xd
+KEY_W,17,ANSI_W,0xd,0x11,0x1d,0x1d,26,VK_W,0x57,17,17,XK_w,0x0077,KeyW,AD02,w,0x37,0xd
+KEY_E,18,ANSI_E,0xe,0x12,0x24,0x24,8,VK_E,0x45,18,18,XK_E,0x0045,KeyE,AD03,e,0x38,0xe
+KEY_E,18,ANSI_E,0xe,0x12,0x24,0x24,8,VK_E,0x45,18,18,XK_e,0x0065,KeyE,AD03,e,0x38,0xe
+KEY_R,19,ANSI_R,0xf,0x13,0x2d,0x2d,21,VK_R,0x52,19,19,XK_R,0x0052,KeyR,AD04,r,0x39,0xf
+KEY_R,19,ANSI_R,0xf,0x13,0x2d,0x2d,21,VK_R,0x52,19,19,XK_r,0x0072,KeyR,AD04,r,0x39,0xf
+KEY_T,20,ANSI_T,0x11,0x14,0x2c,0x2c,23,VK_T,0x54,20,20,XK_T,0x0054,KeyT,AD05,t,0x3a,0x11
+KEY_T,20,ANSI_T,0x11,0x14,0x2c,0x2c,23,VK_T,0x54,20,20,XK_t,0x0074,KeyT,AD05,t,0x3a,0x11
+KEY_Y,21,ANSI_Y,0x10,0x15,0x35,0x35,28,VK_Y,0x59,21,21,XK_Y,0x0059,KeyY,AD06,y,0x3b,0x10
+KEY_Y,21,ANSI_Y,0x10,0x15,0x35,0x35,28,VK_Y,0x59,21,21,XK_y,0x0079,KeyY,AD06,y,0x3b,0x10
+KEY_U,22,ANSI_U,0x20,0x16,0x3c,0x3c,24,VK_U,0x55,22,22,XK_U,0x0055,KeyU,AD07,u,0x3c,0x20
+KEY_U,22,ANSI_U,0x20,0x16,0x3c,0x3c,24,VK_U,0x55,22,22,XK_u,0x0075,KeyU,AD07,u,0x3c,0x20
+KEY_I,23,ANSI_I,0x22,0x17,0x43,0x43,12,VK_I,0x49,23,23,XK_I,0x0049,KeyI,AD08,i,0x3d,0x22
+KEY_I,23,ANSI_I,0x22,0x17,0x43,0x43,12,VK_I,0x49,23,23,XK_i,0x0069,KeyI,AD08,i,0x3d,0x22
+KEY_O,24,ANSI_O,0x1f,0x18,0x44,0x44,18,VK_O,0x4f,24,24,XK_O,0x004f,KeyO,AD09,o,0x3e,0x1f
+KEY_O,24,ANSI_O,0x1f,0x18,0x44,0x44,18,VK_O,0x4f,24,24,XK_o,0x006f,KeyO,AD09,o,0x3e,0x1f
+KEY_P,25,ANSI_P,0x23,0x19,0x4d,0x4d,19,VK_P,0x50,25,25,XK_P,0x0050,KeyP,AD10,p,0x3f,0x23
+KEY_P,25,ANSI_P,0x23,0x19,0x4d,0x4d,19,VK_P,0x50,25,25,XK_p,0x0070,KeyP,AD10,p,0x3f,0x23
+KEY_LEFTBRACE,26,ANSI_LeftBracket,0x21,0x1a,0x54,0x54,47,VK_OEM_4,0xdb,26,26,XK_bracketleft,0x005b,BracketLeft,AD11,bracket_left,0x40,0x21
+KEY_LEFTBRACE,26,ANSI_LeftBracket,0x21,0x1a,0x54,0x54,47,VK_OEM_4,0xdb,26,26,XK_braceleft,0x007b,BracketLeft,AD11,bracket_left,0x40,0x21
+KEY_RIGHTBRACE,27,ANSI_RightBracket,0x1e,0x1b,0x5b,0x5b,48,VK_OEM_6,0xdd,27,27,XK_bracketright,0x005d,BracketRight,AD12,bracket_right,0x41,0x1e
+KEY_RIGHTBRACE,27,ANSI_RightBracket,0x1e,0x1b,0x5b,0x5b,48,VK_OEM_6,0xdd,27,27,XK_braceright,0x007d,BracketRight,AD12,bracket_right,0x41,0x1e
+KEY_ENTER,28,Return,0x24,0x1c,0x5a,0x5a,40,VK_RETURN,0x0d,28,28,XK_Return,0xff0d,Enter,RTRN,ret,0x59,0x24
+KEY_LEFTCTRL,29,Control,0x3b,0x1d,0x14,0x11,224,VK_LCONTROL,0xa2,29,29,XK_Control_L,0xffe3,ControlLeft,LCTL,ctrl,0x4c,0x36
+KEY_LEFTCTRL,29,Control,0x3b,0x1d,0x14,0x11,224,VK_CONTROL,0x11,29,29,XK_Control_L,0xffe3,ControlLeft,LCTL,ctrl,0x4c,0x36
+KEY_A,30,ANSI_A,0x0,0x1e,0x1c,0x1c,4,VK_A,0x41,30,30,XK_A,0x0041,KeyA,AC01,a,0x4d,0x0
+KEY_A,30,ANSI_A,0x0,0x1e,0x1c,0x1c,4,VK_A,0x41,30,30,XK_a,0x0061,KeyA,AC01,a,0x4d,0x0
+KEY_S,31,ANSI_S,0x1,0x1f,0x1b,0x1b,22,VK_S,0x53,31,31,XK_S,0x0053,KeyS,AC02,s,0x4e,0x1
+KEY_S,31,ANSI_S,0x1,0x1f,0x1b,0x1b,22,VK_S,0x53,31,31,XK_s,0x0073,KeyS,AC02,s,0x4e,0x1
+KEY_D,32,ANSI_D,0x2,0x20,0x23,0x23,7,VK_D,0x44,32,32,XK_D,0x0044,KeyD,AC03,d,0x4f,0x2
+KEY_D,32,ANSI_D,0x2,0x20,0x23,0x23,7,VK_D,0x44,32,32,XK_d,0x0064,KeyD,AC03,d,0x4f,0x2
+KEY_F,33,ANSI_F,0x3,0x21,0x2b,0x2b,9,VK_F,0x46,33,33,XK_F,0x0046,KeyF,AC04,f,0x50,0x3
+KEY_F,33,ANSI_F,0x3,0x21,0x2b,0x2b,9,VK_F,0x46,33,33,XK_f,0x0066,KeyF,AC04,f,0x50,0x3
+KEY_G,34,ANSI_G,0x5,0x22,0x34,0x34,10,VK_G,0x47,34,34,XK_G,0x0047,KeyG,AC05,g,0x51,0x5
+KEY_G,34,ANSI_G,0x5,0x22,0x34,0x34,10,VK_G,0x47,34,34,XK_g,0x0067,KeyG,AC05,g,0x51,0x5
+KEY_H,35,ANSI_H,0x4,0x23,0x33,0x33,11,VK_H,0x48,35,35,XK_H,0x0048,KeyH,AC06,h,0x52,0x4
+KEY_H,35,ANSI_H,0x4,0x23,0x33,0x33,11,VK_H,0x48,35,35,XK_h,0x0068,KeyH,AC06,h,0x52,0x4
+KEY_J,36,ANSI_J,0x26,0x24,0x3b,0x3b,13,VK_J,0x4a,36,36,XK_J,0x004a,KeyJ,AC07,j,0x53,0x26
+KEY_J,36,ANSI_J,0x26,0x24,0x3b,0x3b,13,VK_J,0x4a,36,36,XK_j,0x006a,KeyJ,AC07,j,0x53,0x26
+KEY_K,37,ANSI_K,0x28,0x25,0x42,0x42,14,VK_K,0x4b,37,37,XK_K,0x004b,KeyK,AC08,k,0x54,0x28
+KEY_K,37,ANSI_K,0x28,0x25,0x42,0x42,14,VK_K,0x4b,37,37,XK_k,0x006b,KeyK,AC08,k,0x54,0x28
+KEY_L,38,ANSI_L,0x25,0x26,0x4b,0x4b,15,VK_L,0x4c,38,38,XK_L,0x004c,KeyL,AC09,l,0x55,0x25
+KEY_L,38,ANSI_L,0x25,0x26,0x4b,0x4b,15,VK_L,0x4c,38,38,XK_l,0x006c,KeyL,AC09,l,0x55,0x25
+KEY_SEMICOLON,39,ANSI_Semicolon,0x29,0x27,0x4c,0x4c,51,VK_OEM_1,0xba,39,39,XK_semicolon,0x003b,Semicolon,AC10,semicolon,0x56,0x29
+KEY_SEMICOLON,39,ANSI_Semicolon,0x29,0x27,0x4c,0x4c,51,VK_OEM_1,0xba,39,39,XK_colon,0x003a,Semicolon,AC10,semicolon,0x56,0x29
+KEY_APOSTROPHE,40,ANSI_Quote,0x27,0x28,0x52,0x52,52,VK_OEM_7,0xde,40,40,XK_apostrophe,0x0027,Quote,AC11,apostrophe,0x57,0x27
+KEY_APOSTROPHE,40,ANSI_Quote,0x27,0x28,0x52,0x52,52,VK_OEM_7,0xde,40,40,XK_quotedbl,0x0022,Quote,AC11,apostrophe,0x57,0x27
+KEY_GRAVE,41,ANSI_Grave,0x32,0x29,0x0e,0x0e,53,VK_OEM_3,0xc0,41,41,XK_grave,0x0060,Backquote,TLDE,grave_accent,0x2a,0x32
+KEY_GRAVE,41,ANSI_Grave,0x32,0x29,0x0e,0x0e,53,VK_OEM_3,0xc0,41,41,XK_grave,0x0060,Backquote,AB00,grave_accent,0x2a,0x32
+KEY_GRAVE,41,ANSI_Grave,0x32,0x29,0x0e,0x0e,53,VK_OEM_3,0xc0,41,41,XK_asciitilde,0x007e,Backquote,TLDE,grave_accent,0x2a,0x32
+KEY_GRAVE,41,ANSI_Grave,0x32,0x29,0x0e,0x0e,53,VK_OEM_3,0xc0,41,41,XK_asciitilde,0x007e,Backquote,AB00,grave_accent,0x2a,0x32
+KEY_SHIFT,42,Shift,0x38,0x2a,0x12,0x12,225,VK_SHIFT,0x10,42,42,XK_Shift_L,0xffe1,ShiftLeft,LFSH,shift,0x63,0x38
+KEY_LEFTSHIFT,42,Shift,0x38,0x2a,0x12,0x12,225,VK_LSHIFT,0xa0,42,42,XK_Shift_L,0xffe1,ShiftLeft,LFSH,shift,0x63,0x38
+KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,49,VK_OEM_5,0xdc,43,43,XK_backslash,0x005c,Backslash,BKSL,backslash,0x58,0x2a
+KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,49,VK_OEM_5,0xdc,43,43,XK_backslash,0x005c,Backslash,AC12,backslash,0x58,0x2a
+KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,49,VK_OEM_5,0xdc,43,43,XK_bar,0x007c,Backslash,BKSL,backslash,0x58,0x2a
+KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,49,VK_OEM_5,0xdc,43,43,XK_bar,0x007c,Backslash,AC12,backslash,0x58,0x2a
+KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,50,VK_OEM_5,0xdc,43,43,XK_backslash,0x005c,Backslash,BKSL,backslash,0x58,0x2a
+KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,50,VK_OEM_5,0xdc,43,43,XK_backslash,0x005c,Backslash,AC12,backslash,0x58,0x2a
+KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,50,VK_OEM_5,0xdc,43,43,XK_bar,0x007c,Backslash,BKSL,backslash,0x58,0x2a
+KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,50,VK_OEM_5,0xdc,43,43,XK_bar,0x007c,Backslash,AC12,backslash,0x58,0x2a
+KEY_Z,44,ANSI_Z,0x6,0x2c,0x1a,0x1a,29,VK_Z,0x5a,44,44,XK_Z,0x005a,KeyZ,AB01,z,0x64,0x6
+KEY_Z,44,ANSI_Z,0x6,0x2c,0x1a,0x1a,29,VK_Z,0x5a,44,44,XK_z,0x007a,KeyZ,AB01,z,0x64,0x6
+KEY_X,45,ANSI_X,0x7,0x2d,0x22,0x22,27,VK_X,0x58,45,45,XK_X,0x0058,KeyX,AB02,x,0x65,0x7
+KEY_X,45,ANSI_X,0x7,0x2d,0x22,0x22,27,VK_X,0x58,45,45,XK_x,0x0078,KeyX,AB02,x,0x65,0x7
+KEY_C,46,ANSI_C,0x8,0x2e,0x21,0x21,6,VK_C,0x43,46,46,XK_C,0x0043,KeyC,AB03,c,0x66,0x8
+KEY_C,46,ANSI_C,0x8,0x2e,0x21,0x21,6,VK_C,0x43,46,46,XK_c,0x0063,KeyC,AB03,c,0x66,0x8
+KEY_V,47,ANSI_V,0x9,0x2f,0x2a,0x2a,25,VK_V,0x56,47,47,XK_V,0x0056,KeyV,AB04,v,0x67,0x9
+KEY_V,47,ANSI_V,0x9,0x2f,0x2a,0x2a,25,VK_V,0x56,47,47,XK_v,0x0076,KeyV,AB04,v,0x67,0x9
+KEY_B,48,ANSI_B,0xb,0x30,0x32,0x32,5,VK_B,0x42,48,48,XK_B,0x0042,KeyB,AB05,b,0x68,0xb
+KEY_B,48,ANSI_B,0xb,0x30,0x32,0x32,5,VK_B,0x42,48,48,XK_b,0x0062,KeyB,AB05,b,0x68,0xb
+KEY_N,49,ANSI_N,0x2d,0x31,0x31,0x31,17,VK_N,0x4e,49,49,XK_N,0x004e,KeyN,AB06,n,0x69,0x2d
+KEY_N,49,ANSI_N,0x2d,0x31,0x31,0x31,17,VK_N,0x4e,49,49,XK_n,0x006e,KeyN,AB06,n,0x69,0x2d
+KEY_M,50,ANSI_M,0x2e,0x32,0x3a,0x3a,16,VK_M,0x4d,50,50,XK_M,0x004d,KeyM,AB07,m,0x6a,0x2e
+KEY_M,50,ANSI_M,0x2e,0x32,0x3a,0x3a,16,VK_M,0x4d,50,50,XK_m,0x006d,KeyM,AB07,m,0x6a,0x2e
+KEY_COMMA,51,ANSI_Comma,0x2b,0x33,0x41,0x41,54,VK_OEM_COMMA,0xbc,51,51,XK_comma,0x002c,Comma,AB08,comma,0x6b,0x2b
+KEY_COMMA,51,ANSI_Comma,0x2b,0x33,0x41,0x41,54,VK_OEM_COMMA,0xbc,51,51,XK_less,0x003c,Comma,AB08,comma,0x6b,0x2b
+KEY_DOT,52,ANSI_Period,0x2f,0x34,0x49,0x49,55,VK_OEM_PERIOD,0xbe,52,52,XK_period,0x002e,Period,AB09,dot,0x6c,0x2f
+KEY_DOT,52,ANSI_Period,0x2f,0x34,0x49,0x49,55,VK_OEM_PERIOD,0xbe,52,52,XK_greater,0x003e,Period,AB09,dot,0x6c,0x2f
+KEY_SLASH,53,ANSI_Slash,0x2c,0x35,0x4a,0x4a,56,VK_OEM_2,0xbf,53,53,XK_slash,0x002f,Slash,AB10,slash,0x6d,0x2c
+KEY_SLASH,53,ANSI_Slash,0x2c,0x35,0x4a,0x4a,56,VK_OEM_2,0xbf,53,53,XK_question,0x003f,Slash,AB10,slash,0x6d,0x2c
+KEY_RIGHTSHIFT,54,RightShift,0x3c,0x36,0x59,0x59,229,VK_RSHIFT,0xa1,54,54,XK_Shift_R,0xffe2,ShiftRight,RTSH,shift_r,0x6e,0x7b
+KEY_KPASTERISK,55,ANSI_KeypadMultiply,0x43,0x37,0x7c,0x7e,85,VK_MULTIPLY,0x6a,55,55,XK_multiply,0x00d7,NumpadMultiply,KPMU,asterisk,0x2f,0x43
+KEY_KPASTERISK,55,ANSI_KeypadMultiply,0x43,0x37,0x7c,0x7e,85,VK_MULTIPLY,0x6a,55,55,XK_multiply,0x00d7,NumpadMultiply,KPMU,kp_multiply,0x2f,0x43
+KEY_LEFTALT,56,Option,0x3a,0x38,0x11,0x19,226,VK_LMENU,0xa4,56,56,XK_Alt_L,0xffe9,AltLeft,LALT,alt,0x13,0x3a
+KEY_LEFTALT,56,Option,0x3a,0x38,0x11,0x19,226,VK_MENU,0x12,56,56,XK_Alt_L,0xffe9,AltLeft,LALT,alt,0x13,0x3a
+KEY_SPACE,57,Space,0x31,0x39,0x29,0x29,44,VK_SPACE,0x20,57,57,XK_space,0x0020,Space,SPCE,spc,0x79,0x31
+KEY_CAPSLOCK,58,CapsLock,0x39,0x3a,0x58,0x14,57,VK_CAPITAL,0x14,58,58,XK_Caps_Lock,0xffe5,CapsLock,CAPS,caps_lock,0x77,0x39
+KEY_F1,59,F1,0x7a,0x3b,0x05,0x07,58,VK_F1,0x70,59,59,XK_F1,0xffbe,F1,FK01,f1,0x05,0x7a
+KEY_F2,60,F2,0x78,0x3c,0x06,0x0f,59,VK_F2,0x71,60,60,XK_F2,0xffbf,F2,FK02,f2,0x06,0x78
+KEY_F3,61,F3,0x63,0x3d,0x04,0x17,60,VK_F3,0x72,61,61,XK_F3,0xffc0,F3,FK03,f3,0x08,0x63
+KEY_F4,62,F4,0x76,0x3e,0x0c,0x1f,61,VK_F4,0x73,62,62,XK_F4,0xffc1,F4,FK04,f4,0x0a,0x76
+KEY_F5,63,F5,0x60,0x3f,0x03,0x27,62,VK_F5,0x74,63,63,XK_F5,0xffc2,F5,FK05,f5,0x0c,0x60
+KEY_F6,64,F6,0x61,0x40,0x0b,0x2f,63,VK_F6,0x75,64,64,XK_F6,0xffc3,F6,FK06,f6,0x0e,0x61
+KEY_F7,65,F7,0x62,0x41,0x83,0x37,64,VK_F7,0x76,65,65,XK_F7,0xffc4,F7,FK07,f7,0x10,0x62
+KEY_F8,66,F8,0x64,0x42,0x0a,0x3f,65,VK_F8,0x77,66,66,XK_F8,0xffc5,F8,FK08,f8,0x11,0x64
+KEY_F9,67,F9,0x65,0x43,0x01,0x47,66,VK_F9,0x78,67,67,XK_F9,0xffc6,F9,FK09,f9,0x12,0x65
+KEY_F10,68,F10,0x6d,0x44,0x09,0x4f,67,VK_F10,0x79,68,68,XK_F10,0xffc7,F10,FK10,f10,0x07,0x6d
+KEY_NUMLOCK,69,ANSI_KeypadClear,0x47,0x45,0x77,0x76,83,VK_NUMLOCK,0x90,69,69,XK_Num_Lock,0xff7f,NumLock,NMLK,num_lock,0x62,0x47
+KEY_SCROLLLOCK,70,,,0x46,0x7e,0x5f,71,VK_SCROLL,0x91,70,70,XK_Scroll_Lock,0xff14,ScrollLock,SCLK,scroll_lock,0x17,0x6b
+KEY_KP7,71,ANSI_Keypad7,0x59,0x47,0x6c,0x6c,95,VK_NUMPAD7,0x67,71,71,XK_KP_7,0xffb7,Numpad7,KP7,kp_7,0x44,0x59
+KEY_KP8,72,ANSI_Keypad8,0x5b,0x48,0x75,0x75,96,VK_NUMPAD8,0x68,72,72,XK_KP_8,0xffb8,Numpad8,KP8,kp_8,0x45,0x5b
+KEY_KP9,73,ANSI_Keypad9,0x5c,0x49,0x7d,0x7d,97,VK_NUMPAD9,0x69,73,73,XK_KP_9,0xffb9,Numpad9,KP9,kp_9,0x46,0x5c
+KEY_KPMINUS,74,ANSI_KeypadMinus,0x4e,0x4a,0x7b,0x4e,86,VK_SUBTRACT,0x6d,74,74,XK_KP_Subtract,0xffad,NumpadSubtract,KPSU,kp_subtract,0x47,0x4e
+KEY_KP4,75,ANSI_Keypad4,0x56,0x4b,0x6b,0x6b,92,VK_NUMPAD4,0x64,75,75,XK_KP_4,0xffb4,Numpad4,KP4,kp_4,0x5b,0x56
+KEY_KP5,76,ANSI_Keypad5,0x57,0x4c,0x73,0x73,93,VK_NUMPAD5,0x65,76,76,XK_KP_5,0xffb5,Numpad5,KP5,kp_5,0x5c,0x57
+KEY_KP6,77,ANSI_Keypad6,0x58,0x4d,0x74,0x74,94,VK_NUMPAD6,0x66,77,77,XK_KP_6,0xffb6,Numpad6,KP6,kp_6,0x5d,0x58
+KEY_KPPLUS,78,ANSI_KeypadPlus,0x45,0x4e,0x79,0x7c,87,VK_ADD,0x6b,78,78,XK_KP_Add,0xffab,NumpadAdd,KPAD,kp_add,0x7d,0x45
+KEY_KP1,79,ANSI_Keypad1,0x53,0x4f,0x69,0x69,89,VK_NUMPAD1,0x61,79,79,XK_KP_1,0xffb1,Numpad1,KP1,kp_1,0x70,0x53
+KEY_KP2,80,ANSI_Keypad2,0x54,0x50,0x72,0x72,90,VK_NUMPAD2,0x62,80,80,XK_KP_2,0xffb2,Numpad2,KP2,kp_2,0x71,0x54
+KEY_KP3,81,ANSI_Keypad3,0x55,0x51,0x7a,0x7a,91,VK_NUMPAD3,0x63,81,81,XK_KP_3,0xffb3,Numpad3,KP3,kp_3,0x72,0x55
+KEY_KP0,82,ANSI_Keypad0,0x52,0x52,0x70,0x70,98,VK_NUMPAD0,0x60,82,82,XK_KP_0,0xffb0,Numpad0,KP0,kp_0,0x5e,0x52
+KEY_KPDOT,83,ANSI_KeypadDecimal,0x41,0x53,0x71,0x71,99,VK_DECIMAL,0x6e,83,83,XK_KP_Decimal,0xffae,NumpadDecimal,KPDL,kp_decimal,0x32,0x41
+KEY_KPDOT,83,ANSI_KeypadDecimal,0x41,0x53,0x71,0x71,99,VK_DECIMAL,0x6e,83,83,XK_KP_Decimal,0xffae,NumpadDecimal,KPDC,kp_decimal,0x32,0x41
+,84,,,0x54,,,,,,,,,,,,,,
+KEY_ZENKAKUHANKAKU,85,,,0x76,0x5f,,148,,,,,,,Lang5,HZTG,,,
+KEY_102ND,86,,,0x56,0x61,0x13,100,VK_OEM_102,0xe1,86,86,,,IntlBackslash,LSGT,less,0x7c,
+KEY_F11,87,F11,0x67,0x57,0x78,0x56,68,VK_F11,0x7a,87,87,XK_F11,0xffc8,F11,FK11,f11,0x09,0x67
+KEY_F12,88,F12,0x6f,0x58,0x07,0x5e,69,VK_F12,0x7b,88,88,XK_F12,0xffc9,F12,FK12,f12,0x0b,0x6f
+KEY_RO,89,,,0x73,0x51,,135,,,,,,,IntlRo,AB11,ro,,
+KEY_KATAKANA,90,JIS_Kana,0x68,0x78,0x63,,146,VK_KANA,0x15,,,,,Katakana,KATA,,,
+KEY_KATAKANA,90,JIS_Kana,0x68,0x78,0x63,,146,VK_KANA,0x15,,,,,Lang3,KATA,,,
+KEY_HIRAGANA,91,,,0x77,0x62,0x87,147,,,,,,,Hiragana,HIRA,hiragana,,
+KEY_HIRAGANA,91,,,0x77,0x62,0x87,147,,,,,,,Lang4,HIRA,hiragana,,
+KEY_HENKAN,92,,,0x79,0x64,0x86,138,,,,,,,Convert,HENK,henkan,,
+KEY_KATAKANAHIRAGANA,93,,,0x70,0x13,0x87,136,,,0xc8,0xc8,,,KanaMode,HKTG,katakanahiragana,,
+KEY_MUHENKAN,94,,,0x7b,0x67,0x85,139,,,,,,,NonConvert,NFER,muhenkan,,
+KEY_MUHENKAN,94,,,0x7b,0x67,0x85,139,,,,,,,NonConvert,MUHE,muhenkan,,
+KEY_KPJPCOMMA,95,JIS_KeypadComma,0x5f,0x5c,0x27,,140,,,,,XK_KP_Separator,0xffac,,KPSP,,,
+KEY_KPJPCOMMA,95,JIS_KeypadComma,0x5f,0x5c,0x27,,140,,,,,XK_KP_Separator,0xffac,,JPCM,,,
+KEY_KPENTER,96,ANSI_KeypadEnter,0x4c,0xe01c,0xe05a,0x79,88,,,0x64,0x64,XK_KP_Enter,0xff8d,NumpadEnter,KPEN,kp_enter,0x5a,0x4c
+KEY_RIGHTCTRL,97,RightControl,0x3e,0xe01d,0xe014,0x58,228,VK_RCONTROL,0xa3,0x65,0x65,XK_Control_R,0xffe4,ControlRight,RCTL,ctrl_r,0x4c,0x7d
+KEY_KPSLASH,98,ANSI_KeypadDivide,0x4b,0xe035,0xe04a,0x4a,84,VK_DIVIDE,0x6f,0x68,0x68,XK_KP_Divide,0xffaf,NumpadDivide,KPDV,kp_divide,0x2e,0x4b
+KEY_SYSRQ,99,,,0x54,0x7f,0x57,70,VK_SNAPSHOT,0x2c,0x67,0x67,XK_Sys_Req,0xff15,PrintScreen,PRSC,print,0x16,0x69
+KEY_SYSRQ,99,,,0x54,0x7f,0x57,70,VK_SNAPSHOT,0x2c,0x67,0x67,XK_Sys_Req,0xff15,PrintScreen,SYRQ,sysrq,0x16,0x69
+KEY_RIGHTALT,100,RightOption,0x3d,0xe038,0xe011,0x39,230,VK_RMENU,0xa5,0x69,0x69,XK_Alt_R,0xffea,AltRight,ALGR,alt_r,0x0d,0x7c
+KEY_RIGHTALT,100,RightOption,0x3d,0xe038,0xe011,0x39,230,VK_RMENU,0xa5,0x69,0x69,XK_Alt_R,0xffea,AltRight,RALT,alt_r,0x0d,0x7c
+KEY_LINEFEED,101,,,0x5b,,,,,,,,,,,LNFD,lf,0x6f,
+KEY_HOME,102,Home,0x73,0xe047,0xe06c,0x6e,74,VK_HOME,0x24,0x59,0x59,XK_Home,0xff50,Home,HOME,home,0x34,0x73
+KEY_UP,103,UpArrow,0x7e,0xe048,0xe075,0x63,82,VK_UP,0x26,0x5a,0x5a,XK_Up,0xff52,ArrowUp,UP,up,0x14,0x3e
+KEY_PAGEUP,104,PageUp,0x74,0xe049,0xe07d,0x6f,75,VK_PRIOR,0x21,0x5b,0x5b,XK_Page_Up,0xff55,PageUp,PGUP,pgup,0x60,0x74
+KEY_LEFT,105,LeftArrow,0x7b,0xe04b,0xe06b,0x61,80,VK_LEFT,0x25,0x5c,0x5c,XK_Left,0xff51,ArrowLeft,LEFT,left,0x18,0x3b
+KEY_RIGHT,106,RightArrow,0x7c,0xe04d,0xe074,0x6a,79,VK_RIGHT,0x27,0x5e,0x5e,XK_Right,0xff53,ArrowRight,RGHT,right,0x1c,0x3c
+KEY_END,107,End,0x77,0xe04f,0xe069,0x65,77,VK_END,0x23,0x5f,0x5f,XK_End,0xff57,End,END,end,0x4a,0x77
+KEY_DOWN,108,DownArrow,0x7d,0xe050,0xe072,0x60,81,VK_DOWN,0x28,0x60,0x60,XK_Down,0xff54,ArrowDown,DOWN,down,0x1b,0x3d
+KEY_PAGEDOWN,109,PageDown,0x79,0xe051,0xe07a,0x6d,78,VK_NEXT,0x22,0x61,0x61,XK_Page_Down,0xff56,PageDown,PGDN,pgdn,0x7b,0x79
+KEY_INSERT,110,,,0xe052,0xe070,0x67,73,VK_INSERT,0x2d,0x62,0x62,XK_Insert,0xff63,Insert,INS,insert,0x2c,0x72
+KEY_DELETE,111,ForwardDelete,0x75,0xe053,0xe071,0x64,76,VK_DELETE,0x2e,0x63,0x63,XK_Delete,0xffff,Delete,DEL,delete,0x42,0x75
+KEY_DELETE,111,ForwardDelete,0x75,0xe053,0xe071,0x64,76,VK_DELETE,0x2e,0x63,0x63,XK_Delete,0xffff,Delete,DELE,,0x42,0x75
+KEY_MACRO,112,,,0xe06f,0xe06f,0x8e,,,,,,,,,I120,,,
+KEY_MUTE,113,Mute,0x4a,0xe020,0xe023,0x9c,127,VK_VOLUME_MUTE,0xad,,,,,AudioVolumeMute,MUTE,audiomute,,
+KEY_MUTE,113,Mute,0x4a,0xe020,0xe023,0x9c,239,VK_VOLUME_MUTE,0xad,,,,,AudioVolumeMute,MUTE,audiomute,,
+KEY_VOLUMEDOWN,114,VolumeDown,0x49,0xe02e,0xe021,0x9d,129,VK_VOLUME_DOWN,0xae,,,,,AudioVolumeDown,VOL-,volumedown,,
+KEY_VOLUMEDOWN,114,VolumeDown,0x49,0xe02e,0xe021,0x9d,238,VK_VOLUME_DOWN,0xae,,,,,AudioVolumeDown,VOL-,volumedown,,
+KEY_VOLUMEUP,115,VolumeUp,0x48,0xe030,0xe032,0x95,128,VK_VOLUME_UP,0xaf,,,,,AudioVolumeUp,VOL+,volumeup,,
+KEY_VOLUMEUP,115,VolumeUp,0x48,0xe030,0xe032,0x95,237,VK_VOLUME_UP,0xaf,,,,,AudioVolumeUp,VOL+,volumeup,,
+KEY_POWER,116,,,0xe05e,0xe037,,102,,,,,,,Power,POWR,power,,0x7f7f
+KEY_KPEQUAL,117,ANSI_KeypadEquals,0x51,0x59,0x0f,,103,,,0x76,0x76,XK_KP_Equal,0xffbd,NumpadEqual,KPEQ,kp_equals,0x2d,0x51
+KEY_KPPLUSMINUS,118,,,0xe04e,0xe079,,,,,,,,,,I126,,,
+KEY_PAUSE,119,,,0xe046,0xe077,0x62,72,VK_PAUSE,0x013,0x66,0x66,XK_Pause,0xff13,Pause,PAUS,pause,0x15,0x71
+KEY_SCALE,120,,,0xe00b,,,,,,,,,,,I128,,,
+KEY_KPCOMMA,121,,,0x7e,0x6d,,133,VK_SEPARATOR??,0x6c,,,,,NumpadComma,KPCO,kp_comma,,
+KEY_KPCOMMA,121,,,0x7e,0x6d,,133,VK_SEPARATOR??,0x6c,,,,,NumpadComma,I129,,,
+KEY_HANGEUL,122,,,,,,144,VK_HANGEUL,0x15,,,,,,HNGL,,,
+KEY_HANJA,123,,,0xe00d,,,145,VK_HANJA,0x19,,,,,,HJCV,,,
+KEY_YEN,124,JIS_Yen,0x5d,0x7d,0x6a,0x5d,137,,,0x7d,0x7d,,,IntlYen,AE13,yen,,
+KEY_LEFTMETA,125,Command,0x37,0xe05b,0xe01f,0x8b,227,VK_LWIN,0x5b,0x6b,0x6b,XK_Meta_L,0xffe7,MetaLeft,LMTA,meta_l,0x78,0x37
+KEY_LEFTMETA,125,Command,0x37,0xe05b,0xe01f,0x8b,227,VK_LWIN,0x5b,0x6b,0x6b,XK_Meta_L,0xffe7,MetaLeft,LWIN,meta_l,0x78,0x37
+KEY_RIGHTMETA,126,RightCommand,0x36,0xe05c,0xe027,0x8c,231,VK_RWIN,0x5c,0x6c,0x6c,XK_Meta_R,0xffe8,MetaRight,RMTA,meta_r,0x7a,0x37
+KEY_RIGHTMETA,126,RightCommand,0x36,0xe05c,0xe027,0x8c,231,VK_RWIN,0x5c,0x6c,0x6c,XK_Meta_R,0xffe8,MetaRight,RWIN,meta_r,0x7a,0x37
+KEY_COMPOSE,127,,0x6e,0xe05d,0xe02f,0x8d,101,VK_APPS,0x5d,0x6d,0x6d,,,ContextMenu,MENU,compose,0x43,
+KEY_COMPOSE,127,,0x6e,0xe05d,0xe02f,0x8d,101,VK_APPS,0x5d,0x6d,0x6d,,,ContextMenu,COMP,compose,0x43,
+KEY_STOP,128,,,0xe068,0xe028,0x0a,120,VK_BROWSER_STOP,0xa9,,,,,BrowserStop,STOP,stop,0x01,
+KEY_STOP,128,,,0xe068,0xe028,0x0a,243,VK_BROWSER_STOP,0xa9,,,,,BrowserStop,STOP,stop,0x01,
+KEY_AGAIN,129,,,0xe005,,0x0b,121,,,,,,,Again,AGAI,again,0x03,
+KEY_PROPS,130,,,0xe006,,0x0c,,,,,,,,Props,PROP,props,0x19,
+KEY_UNDO,131,,,0xe007,,0x10,122,,,,,,,Undo,UNDO,undo,0x1a,
+KEY_FRONT,132,,,0xe00c,,,119,,,,,,,,FRNT,front,0x31,
+KEY_COPY,133,,,0xe078,,0x18,124,,,,,,,Copy,COPY,copy,0x33,
+KEY_OPEN,134,,,0x64,,0x20,116,,,,,,,Open,OPEN,open,0x48,
+KEY_PASTE,135,,,0x65,,0x28,125,,,,,,,Paste,PAST,paste,0x49,
+KEY_FIND,136,,,0xe041,,0x30,126,,,,,,,Find,FIND,find,0x5f,
+KEY_FIND,136,,,0xe041,,0x30,244,,,,,,,Find,FIND,find,0x5f,
+KEY_CUT,137,,,0xe03c,,0x38,123,,,,,,,Cut,CUT,cut,0x61,
+KEY_HELP,138,Help,0x72,0xe075,,0x09,117,VK_HELP,0x2f,,,XK_Help,0xff6a,Help,HELP,help,0x76,
+KEY_MENU,139,,,0xe01e,,0x91,118,,,,,,,,I147,menu,,
+KEY_CALC,140,,,0xe021,0xe02b,0xa3,251,,,,,,,LaunchApp2,I148,calculator,,
+KEY_SETUP,141,,,0x66,,,,,,,,,,,I149,,,
+KEY_SLEEP,142,,,0xe05f,0xe03f,,248,VK_SLEEP,0x5f,,,,,Sleep,I150,sleep,,
+KEY_WAKEUP,143,,,0xe063,0xe05e,,,,,,,,,WakeUp,I151,wake,,
+KEY_FILE,144,,,0x67,,,,,,,,,,,I152,,,
+KEY_SENDFILE,145,,,0x68,,,,,,,,,,,I153,,,
+KEY_DELETEFILE,146,,,0x69,,,,,,,,,,,I154,,,
+KEY_XFER,147,,,0xe013,,0xa2,,,,,,,,,XFER,,,
+KEY_XFER,147,,,0xe013,,0xa2,,,,,,,,,I155,,,
+KEY_PROG1,148,,,0xe01f,,0xa0,,,,,,,,,I156,,,
+KEY_PROG2,149,,,0xe017,,0xa1,,,,,,,,,I157,,,
+KEY_WWW,150,,,0xe002,,,240,,,,,,,,I158,,,
+KEY_MSDOS,151,,,0x6a,,,,,,,,,,,I159,,,
+KEY_SCREENLOCK,152,,,0xe012,,0x96,249,,,,,,,,I160,,,
+KEY_DIRECTION,153,,,0x6b,,,,,,,,,,,I161,,,
+KEY_CYCLEWINDOWS,154,,,0xe026,,0x9b,,,,,,,,,I162,,,
+KEY_MAIL,155,,,0xe06c,0xe048,,,,,,,,,LaunchMail,I163,mail,,
+KEY_BOOKMARKS,156,,,0xe066,0xe018,,,,,,,,,BrowserFavorites,I164,ac_bookmarks,,
+KEY_COMPUTER,157,,,0xe06b,0xe040,,,,,,,,,LaunchApp1,I165,computer,,
+KEY_BACK,158,,,0xe06a,0xe038,,241,VK_BROWSER_BACK,0xa6,,,,,BrowserBack,I166,ac_back,,
+KEY_FORWARD,159,,,0xe069,0xe030,,242,VK_BROWSER_FORWARD,0xa7,,,,,BrowserForward,I167,ac_forward,,
+KEY_CLOSECD,160,,,0xe023,,0x9a,,,,,,,,,I168,,,
+KEY_EJECTCD,161,,,0x6c,,,236,,,,,,,,I169,,,
+KEY_EJECTCLOSECD,162,,,0xe07d,,,,,,,,,,Eject,I170,,,
+KEY_NEXTSONG,163,,,0xe019,0xe04d,0x93,235,VK_MEDIA_NEXT_TRACK,0xb0,,,,,MediaTrackNext,I171,audionext,,
+KEY_PLAYPAUSE,164,,,0xe022,0xe034,,232,VK_MEDIA_PLAY_PAUSE,0xb3,,,,,MediaPlayPause,I172,audioplay,,
+KEY_PREVIOUSSONG,165,,,0xe010,0xe015,0x94,234,VK_MEDIA_PREV_TRACK,0xb1,,,,,MediaTrackPrevious,I173,audioprev,,
+KEY_STOPCD,166,,,0xe024,0xe03b,0x98,233,VK_MEDIA_STOP,0xb2,,,,,MediaStop,I174,audiostop,,
+KEY_RECORD,167,,,0xe031,,0x9e,,,,,,,,,I175,,,
+KEY_REWIND,168,,,0xe018,,0x9f,,,,,,,,,I176,,,
+KEY_PHONE,169,,,0x63,,,,,,,,,,,I177,,,
+KEY_ISO,170,ISO_Section,0xa,,,,,,,,,,,,I178,,,
+KEY_CONFIG,171,,,0xe001,,,,,,,,,,,I179,,,
+KEY_HOMEPAGE,172,,,0xe032,0xe03a,0x97,,VK_BROWSER_HOME,0xac,,,,,BrowserHome,I180,ac_home,,
+KEY_REFRESH,173,,,0xe067,0xe020,,250,VK_BROWSER_REFRESH,0xa8,,,,,BrowserRefresh,I181,ac_refresh,,
+KEY_EXIT,174,,,0x71,,,,,,,,,,,I182,,,
+KEY_MOVE,175,,,0x72,,,,,,,,,,,I183,,,
+KEY_EDIT,176,,,0xe008,,,247,,,,,,,,I184,,,
+KEY_SCROLLUP,177,,,0x75,,,245,,,,,,,,I185,,,
+KEY_SCROLLDOWN,178,,,0xe00f,,,246,,,,,,,,I186,,,
+KEY_KPLEFTPAREN,179,,,0xe076,,,182,,,,,,,NumpadParenLeft,I187,,,
+KEY_KPRIGHTPAREN,180,,,0xe07b,,,183,,,,,,,NumpadParenRight,I188,,,
+KEY_NEW,181,,,0xe009,,,,,,,,,,,I189,,,
+KEY_REDO,182,,,0xe00a,,,,,,,,,,,I190,,,
+KEY_F13,183,F13,0x69,0x5d,0x2f,0x7f,104,VK_F13,0x7c,0x6e,0x6e,,,F13,FK13,,,0x69
+KEY_F14,184,F14,0x6b,0x5e,0x37,0x80,105,VK_F14,0x7d,0x6f,0x6f,,,F14,FK14,,,0x6b
+KEY_F15,185,F15,0x71,0x5f,0x3f,0x81,106,VK_F15,0x7e,0x70,0x70,,,F15,FK15,,,0x71
+KEY_F16,186,F16,0x6a,0x55,,0x82,107,VK_F16,0x7f,0x71,0x71,,,F16,FK16,,,
+KEY_F17,187,F17,0x40,0xe003,,0x83,108,VK_F17,0x80,0x72,0x72,,,F17,FK17,,,
+KEY_F18,188,F18,0x4f,0xe077,,,109,VK_F18,0x81,,,,,F18,FK18,,,
+KEY_F19,189,F19,0x50,0xe004,,,110,VK_F19,0x82,,,,,F19,FK19,,,
+KEY_F20,190,F20,0x5a,0x5a,,,111,VK_F20,0x83,,,,,F20,FK20,,,
+KEY_F21,191,,,0x74,,,112,VK_F21,0x84,,,,,F21,FK21,,,
+KEY_F22,192,,,0xe079,,,113,VK_F22,0x85,,,,,F22,FK22,,,
+KEY_F23,193,,,0x6d,,,114,VK_F23,0x86,,,,,F23,FK23,,,
+KEY_F24,194,,,0x6f,,,115,VK_F24,0x87,,,,,F24,FK24,,,
+,195,,,0xe015,,,,,,,,,,,,,,
+,196,,,0xe016,,,,,,,,,,,,,,
+,197,,,0xe01a,,,,,,,,,,,,,,
+,198,,,0xe01b,,,,,,,,,,,,,,
+,199,,,0xe027,,,,,,,,,,,,,,
+KEY_PLAYCD,200,,,0xe028,,,,,,,,,,,I208,,,
+KEY_PAUSECD,201,,,0xe029,,,,,,,,,,,I209,,,
+KEY_PROG3,202,,,0xe02b,,,,,,,,,,,I210,,,
+KEY_PROG4,203,,,0xe02c,,,,,,,,,,,I211,,,
+KEY_DASHBOARD,204,,,0xe02d,,,,,,,,,,,I212,,,
+KEY_SUSPEND,205,,,0xe025,,,,,,,,,,Suspend,I213,,,
+KEY_CLOSE,206,,,0xe02f,,,,,,,,,,,I214,,,
+KEY_PLAY,207,,,0xe033,,,,VK_PLAY,0xfa,,,,,,I215,,,
+KEY_FASTFORWARD,208,,,0xe034,,,,,,,,,,,I216,,,
+KEY_BASSBOOST,209,,,0xe036,,,,,,,,,,,I217,,,
+KEY_PRINT,210,,,0xe039,,,,VK_PRINT,0x2a,,,,,,I218,,,
+KEY_HP,211,,,0xe03a,,,,,,,,,,,I219,,,
+KEY_CAMERA,212,,,0xe03b,,,,,,,,,,,I220,,,
+KEY_SOUND,213,,,0xe03d,,,,,,,,,,,I221,,,
+KEY_QUESTION,214,,,0xe03e,,,,,,,,,,,I222,,,
+KEY_EMAIL,215,,,0xe03f,,,,VK_LAUNCH_MAIL,0xb4,,,,,,I223,,,
+KEY_CHAT,216,,,0xe040,,,,,,,,,,,I224,,,
+KEY_SEARCH,217,,,0xe065,0xe010,,,VK_BROWSER_SEARCH,0xaa,,,,,BrowserSearch,I225,,,
+KEY_CONNECT,218,,,0xe042,,,,,,,,,,,I226,,,
+KEY_FINANCE,219,,,0xe043,,,,,,,,,,,I227,,,
+KEY_SPORT,220,,,0xe044,,,,,,,,,,,I228,,,
+KEY_SHOP,221,,,0xe045,,,,,,,,,,,I229,,,
+KEY_ALTERASE,222,,,0xe014,,,,,,,,,,,I230,,,
+KEY_CANCEL,223,,,0xe04a,,,,,,,,,,,I231,,,
+KEY_BRIGHTNESSDOWN,224,,,0xe04c,,,,,,,,,,,I232,,,
+KEY_BRIGHTNESSUP,225,,,0xe054,,,,,,,,,,,I233,,,
+KEY_MEDIA,226,,,0xe06d,0xe050,,,,,,,,,MediaSelect,I234,mediaselect,,
+KEY_SWITCHVIDEOMODE,227,,,0xe056,,,,,,,,,,,I235,,,
+KEY_KBDILLUMTOGGLE,228,,,0xe057,,,,,,,,,,,I236,,,
+KEY_KBDILLUMDOWN,229,,,0xe058,,,,,,,,,,,I237,,,
+KEY_KBDILLUMUP,230,,,0xe059,,,,,,,,,,,I238,,,
+KEY_SEND,231,,,0xe05a,,,,,,,,,,,I239,,,
+KEY_REPLY,232,,,0xe064,,,,,,,,,,,I240,,,
+KEY_FORWARDMAIL,233,,,0xe00e,,,,,,,,,,,I241,,,
+KEY_SAVE,234,,,0xe055,,,,,,,,,,,I242,,,
+KEY_DOCUMENTS,235,,,0xe070,,,,,,,,,,,I243,,,
+KEY_BATTERY,236,,,0xe071,,,,,,,,,,,I244,,,
+KEY_BLUETOOTH,237,,,0xe072,,,,,,,,,,,I245,,,
+KEY_WLAN,238,,,0xe073,,,,,,,,,,,I246,,,
+KEY_UWB,239,,,0xe074,,,,,,,,,,,I247,,,
+KEY_UNKNOWN,240,,,,,,,,,,,,,,I248,,,
+KEY_VIDEO_NEXT,241,,,,,,,,,,,,,,I249,,,
+KEY_VIDEO_PREV,242,,,,,,,,,,,,,,I250,,,
+KEY_BRIGHTNESS_CYCLE,243,,,,,,,,,,,,,,I251,,,
+KEY_BRIGHTNESS_ZERO,244,,,,,,,,,,,,,,I252,,,
+KEY_DISPLAY_OFF,245,,,,,,,,,,,,,,I253,,,
+KEY_WIMAX,246,,,,,,,,,,,,,,,,,
+,247,,,,,,,,,,,,,,,,,
+,248,,,,,,,,,,,,,,,,,
+,249,,,,,,,,,,,,,,,,,
+,250,,,,,,,,,,,,,,,,,
+,251,,,,,,,,,,,,,,,,,
+,252,,,,,,,,,,,,,,,,,
+,253,,,,,,,,,,,,,,,,,
+,254,,,,,,,,,,,,,,,,,
+,255,,,,0xe012,,,,,,,,,,,,,
+BTN_MISC,0x100,,,,,,,,,,,,,,,,,
+BTN_0,0x100,,,,,,,VK_LBUTTON,0x01,,,,,,,,,
+BTN_1,0x101,,,,,,,VK_RBUTTON,0x02,,,,,,,,,
+BTN_2,0x102,,,,,,,VK_MBUTTON,0x04,,,,,,,,,
+BTN_3,0x103,,,,,,,VK_XBUTTON1,0x05,,,,,,,,,
+BTN_4,0x104,,,,,,,VK_XBUTTON2,0x06,,,,,,,,,
+BTN_5,0x105,,,,,,,,,,,,,,,,,
+BTN_6,0x106,,,,,,,,,,,,,,,,,
+BTN_7,0x107,,,,,,,,,,,,,,,,,
+BTN_8,0x108,,,,,,,,,,,,,,,,,
+BTN_9,0x109,,,,,,,,,,,,,,,,,
+BTN_MOUSE,0x110,,,,,,,,,,,,,,,,,
+BTN_LEFT,0x110,,,,,,,,,,,,,,,,,
+BTN_RIGHT,0x111,,,,,,,,,,,,,,,,,
+BTN_MIDDLE,0x112,,,,,,,,,,,,,,,,,
+BTN_SIDE,0x113,,,,,,,,,,,,,,,,,
+BTN_EXTRA,0x114,,,,,,,,,,,,,,,,,
+BTN_FORWARD,0x115,,,,,,,,,,,,,,,,,
+BTN_BACK,0x116,,,,,,,,,,,,,,,,,
+BTN_TASK,0x117,,,,,,,,,,,,,,,,,
+BTN_JOYSTICK,0x120,,,,,,,,,,,,,,,,,
+BTN_TRIGGER,0x120,,,,,,,,,,,,,,,,,
+BTN_THUMB,0x121,,,,,,,,,,,,,,,,,
+BTN_THUMB2,0x122,,,,,,,,,,,,,,,,,
+BTN_TOP,0x123,,,,,,,,,,,,,,,,,
+BTN_TOP2,0x124,,,,,,,,,,,,,,,,,
+BTN_PINKIE,0x125,,,,,,,,,,,,,,,,,
+BTN_BASE,0x126,,,,,,,,,,,,,,,,,
+BTN_BASE2,0x127,,,,,,,,,,,,,,,,,
+BTN_BASE3,0x128,,,,,,,,,,,,,,,,,
+BTN_BASE4,0x129,,,,,,,,,,,,,,,,,
+BTN_BASE5,0x12a,,,,,,,,,,,,,,,,,
+BTN_BASE6,0x12b,,,,,,,,,,,,,,,,,
+BTN_DEAD,0x12f,,,,,,,,,,,,,,,,,
+BTN_GAMEPAD,0x130,,,,,,,,,,,,,,,,,
+BTN_A,0x130,,,,,,,,,,,,,,,,,
+BTN_B,0x131,,,,,,,,,,,,,,,,,
+BTN_C,0x132,,,,,,,,,,,,,,,,,
+BTN_X,0x133,,,,,,,,,,,,,,,,,
+BTN_Y,0x134,,,,,,,,,,,,,,,,,
+BTN_Z,0x135,,,,,,,,,,,,,,,,,
+BTN_TL,0x136,,,,,,,,,,,,,,,,,
+BTN_TR,0x137,,,,,,,,,,,,,,,,,
+BTN_TL2,0x138,,,,,,,,,,,,,,,,,
+BTN_TR2,0x139,,,,,,,,,,,,,,,,,
+BTN_SELECT,0x13a,,,,,,,,,,,,,,,,,
+BTN_START,0x13b,,,,,,,,,,,,,,,,,
+BTN_MODE,0x13c,,,,,,,,,,,,,,,,,
+BTN_THUMBL,0x13d,,,,,,,,,,,,,,,,,
+BTN_THUMBR,0x13e,,,,,,,,,,,,,,,,,
+BTN_DIGI,0x140,,,,,,,,,,,,,,,,,
+BTN_TOOL_PEN,0x140,,,,,,,,,,,,,,,,,
+BTN_TOOL_RUBBER,0x141,,,,,,,,,,,,,,,,,
+BTN_TOOL_BRUSH,0x142,,,,,,,,,,,,,,,,,
+BTN_TOOL_PENCIL,0x143,,,,,,,,,,,,,,,,,
+BTN_TOOL_AIRBRUSH,0x144,,,,,,,,,,,,,,,,,
+BTN_TOOL_FINGER,0x145,,,,,,,,,,,,,,,,,
+BTN_TOOL_MOUSE,0x146,,,,,,,,,,,,,,,,,
+BTN_TOOL_LENS,0x147,,,,,,,,,,,,,,,,,
+BTN_TOUCH,0x14a,,,,,,,,,,,,,,,,,
+BTN_STYLUS,0x14b,,,,,,,,,,,,,,,,,
+BTN_STYLUS2,0x14c,,,,,,,,,,,,,,,,,
+BTN_TOOL_DOUBLETAP,0x14d,,,,,,,,,,,,,,,,,
+BTN_TOOL_TRIPLETAP,0x14e,,,,,,,,,,,,,,,,,
+BTN_TOOL_QUADTAP,0x14f,,,,,,,,,,,,,,,,,
+BTN_WHEEL,0x150,,,,,,,,,,,,,,,,,
+BTN_GEAR_DOWN,0x150,,,,,,,,,,,,,,,,,
+BTN_GEAR_UP,0x151,,,,,,,,,,,,,,,,,
+KEY_OK,0x160,,,,,,,,,,,,,,,,,
+KEY_SELECT,0x161,,,,,,,VK_SELECT,0x29,,,XK_Select,0xff60,Select,SELE,,,
+KEY_GOTO,0x162,,,,,,,,,,,,,,,,,
+KEY_CLEAR,0x163,,,,,,,,,,,,,NumpadClear,CLR,,,
+KEY_POWER2,0x164,,,,,,,,,,,,,,,,,
+KEY_OPTION,0x165,,,,,,,,,,,,,,,,,
+KEY_INFO,0x166,,,,,,,,,,,,,,,,,
+KEY_TIME,0x167,,,,,,,,,,,,,,,,,
+KEY_VENDOR,0x168,,,,,,,,,,,,,,,,,
+KEY_ARCHIVE,0x169,,,,,,,,,,,,,,,,,
+KEY_PROGRAM,0x16a,,,,,,,,,,,,,,,,,
+KEY_CHANNEL,0x16b,,,,,,,,,,,,,,,,,
+KEY_FAVORITES,0x16c,,,,,,,VK_BROWSER_FAVOURITES,0xab,,,,,,,,,
+KEY_EPG,0x16d,,,,,,,,,,,,,,,,,
+KEY_PVR,0x16e,,,,,,,,,,,,,,,,,
+KEY_MHP,0x16f,,,,,,,,,,,,,,,,,
+KEY_LANGUAGE,0x170,,,,,,,,,,,,,,,,,
+KEY_TITLE,0x171,,,,,,,,,,,,,,,,,
+KEY_SUBTITLE,0x172,,,,,,,,,,,,,,,,,
+KEY_ANGLE,0x173,,,,,,,,,,,,,,,,,
+KEY_ZOOM,0x174,,,,,,,VK_ZOOM,0xfb,,,,,,,,,
+KEY_MODE,0x175,,,,,,,,,,,,,,,,,
+KEY_KEYBOARD,0x176,,,,,,,,,,,,,,,,,
+KEY_SCREEN,0x177,,,,,,,,,,,,,,,,,
+KEY_PC,0x178,,,,,,,,,,,,,,,,,
+KEY_TV,0x179,,,,,,,,,,,,,,,,,
+KEY_TV2,0x17a,,,,,,,,,,,,,,,,,
+KEY_VCR,0x17b,,,,,,,,,,,,,,,,,
+KEY_VCR2,0x17c,,,,,,,,,,,,,,,,,
+KEY_SAT,0x17d,,,,,,,,,,,,,,,,,
+KEY_SAT2,0x17e,,,,,,,,,,,,,,,,,
+KEY_CD,0x17f,,,,,,,,,,,,,,,,,
+KEY_TAPE,0x180,,,,,,,,,,,,,,,,,
+KEY_RADIO,0x181,,,,,,,,,,,,,,,,,
+KEY_TUNER,0x182,,,,,,,,,,,,,,,,,
+KEY_PLAYER,0x183,,,,,,,,,,,,,,,,,
+KEY_TEXT,0x184,,,,,,,,,,,,,,,,,
+KEY_DVD,0x185,,,,,,,,,,,,,,,,,
+KEY_AUX,0x186,,,,,,,,,,,,,,,,,
+KEY_MP3,0x187,,,,,,,,,,,,,,,,,
+KEY_AUDIO,0x188,,,,,,,,,,,,,,,,,
+KEY_VIDEO,0x189,,,,,,,,,,,,,,,,,
+KEY_DIRECTORY,0x18a,,,,,,,,,,,,,,,,,
+KEY_LIST,0x18b,,,,,,,,,,,,,,,,,
+KEY_MEMO,0x18c,,,,,,,,,,,,,,,,,
+KEY_CALENDAR,0x18d,,,,,,,,,,,,,,,,,
+KEY_RED,0x18e,,,,,,,,,,,,,,,,,
+KEY_GREEN,0x18f,,,,,,,,,,,,,,,,,
+KEY_YELLOW,0x190,,,,,,,,,,,,,,,,,
+KEY_BLUE,0x191,,,,,,,,,,,,,,,,,
+KEY_CHANNELUP,0x192,,,,,,,,,,,,,,,,,
+KEY_CHANNELDOWN,0x193,,,,,,,,,,,,,,,,,
+KEY_FIRST,0x194,,,,,,,,,,,,,,,,,
+KEY_LAST,0x195,,,,,,,,,,,,,,,,,
+KEY_AB,0x196,,,,,,,,,,,,,,,,,
+KEY_NEXT,0x197,,,,,,,,,,,,,,,,,
+KEY_RESTART,0x198,,,,,,,,,,,,,,,,,
+KEY_SLOW,0x199,,,,,,,,,,,,,,,,,
+KEY_SHUFFLE,0x19a,,,,,,,,,,,,,,,,,
+KEY_BREAK,0x19b,,,,,,,,,,,,,,BREA,,,
+KEY_BREAK,0x19b,,,,,,,,,,,,,,BRK,,,
+KEY_PREVIOUS,0x19c,,,,,,,,,,,,,,,,,
+KEY_DIGITS,0x19d,,,,,,,,,,,,,,,,,
+KEY_TEEN,0x19e,,,,,,,,,,,,,,,,,
+KEY_TWEN,0x19f,,,,,,,,,,,,,,,,,
+KEY_VIDEOPHONE,0x1a0,,,,,,,,,,,,,,,,,
+KEY_GAMES,0x1a1,,,,,,,,,,,,,,,,,
+KEY_ZOOMIN,0x1a2,,,,,,,,,,,,,,,,,
+KEY_ZOOMOUT,0x1a3,,,,,,,,,,,,,,,,,
+KEY_ZOOMRESET,0x1a4,,,,,,,,,,,,,,,,,
+KEY_WORDPROCESSOR,0x1a5,,,,,,,,,,,,,,,,,
+KEY_EDITOR,0x1a6,,,,,,,,,,,,,,,,,
+KEY_SPREADSHEET,0x1a7,,,,,,,,,,,,,,,,,
+KEY_GRAPHICSEDITOR,0x1a8,,,,,,,,,,,,,,,,,
+KEY_PRESENTATION,0x1a9,,,,,,,,,,,,,,,,,
+KEY_DATABASE,0x1aa,,,,,,,,,,,,,,,,,
+KEY_NEWS,0x1ab,,,,,,,,,,,,,,,,,
+KEY_VOICEMAIL,0x1ac,,,,,,,,,,,,,,,,,
+KEY_ADDRESSBOOK,0x1ad,,,,,,,,,,,,,,,,,
+KEY_MESSENGER,0x1ae,,,,,,,,,,,,,,,,,
+KEY_DISPLAYTOGGLE,0x1af,,,,,,,,,,,,,,,,,
+KEY_SPELLCHECK,0x1b0,,,,,,,,,,,,,,,,,
+KEY_LOGOFF,0x1b1,,,,,,,,,,,,,,,,,
+KEY_DOLLAR,0x1b2,,,,,,,,,,,,,,,,,
+KEY_EURO,0x1b3,,,,,,,,,,,,,,,,,
+KEY_FRAMEBACK,0x1b4,,,,,,,,,,,,,,,,,
+KEY_FRAMEFORWARD,0x1b5,,,,,,,,,,,,,,,,,
+KEY_CONTEXT_MENU,0x1b6,,,,,,,,,,,,,,,,,
+KEY_MEDIA_REPEAT,0x1b7,,,,,,,,,,,,,,,,,
+KEY_DEL_EOL,0x1c0,,,,,,,,,,,,,,,,,
+KEY_DEL_EOS,0x1c1,,,,,,,,,,,,,,,,,
+KEY_INS_LINE,0x1c2,,,,,,,,,,,,,,,,,
+KEY_DEL_LINE,0x1c3,,,,,,,,,,,,,,,,,
+KEY_FN,0x1d0,Function,0x3f,,,,,,,,,,,Fn,,,,
+KEY_FN_ESC,0x1d1,,,,,,,,,,,,,,,,,
+KEY_FN_F1,0x1d2,,,,,,,,,,,,,,,,,
+KEY_FN_F2,0x1d3,,,,,,,,,,,,,,,,,
+KEY_FN_F3,0x1d4,,,,,,,,,,,,,,,,,
+KEY_FN_F4,0x1d5,,,,,,,,,,,,,,,,,
+KEY_FN_F5,0x1d6,,,,,,,,,,,,,,,,,
+KEY_FN_F6,0x1d7,,,,,,,,,,,,,,,,,
+KEY_FN_F7,0x1d8,,,,,,,,,,,,,,,,,
+KEY_FN_F8,0x1d9,,,,,,,,,,,,,,,,,
+KEY_FN_F9,0x1da,,,,,,,,,,,,,,,,,
+KEY_FN_F10,0x1db,,,,,,,,,,,,,,,,,
+KEY_FN_F11,0x1dc,,,,,,,,,,,,,,,,,
+KEY_FN_F12,0x1dd,,,,,,,,,,,,,,,,,
+KEY_FN_1,0x1de,,,,,,,,,,,,,,,,,
+KEY_FN_2,0x1df,,,,,,,,,,,,,,,,,
+KEY_FN_D,0x1e0,,,,,,,,,,,,,,,,,
+KEY_FN_E,0x1e1,,,,,,,,,,,,,,,,,
+KEY_FN_F,0x1e2,,,,,,,,,,,,,,,,,
+KEY_FN_S,0x1e3,,,,,,,,,,,,,,,,,
+KEY_FN_B,0x1e4,,,,,,,,,,,,,,,,,
+KEY_BRL_DOT1,0x1f1,,,,,,,,,,,,,,,,,
+KEY_BRL_DOT2,0x1f2,,,,,,,,,,,,,,,,,
+KEY_BRL_DOT3,0x1f3,,,,,,,,,,,,,,,,,
+KEY_BRL_DOT4,0x1f4,,,,,,,,,,,,,,,,,
+KEY_BRL_DOT5,0x1f5,,,,,,,,,,,,,,,,,
+KEY_BRL_DOT6,0x1f6,,,,,,,,,,,,,,,,,
+KEY_BRL_DOT7,0x1f7,,,,,,,,,,,,,,,,,
+KEY_BRL_DOT8,0x1f8,,,,,,,,,,,,,,,,,
+KEY_BRL_DOT9,0x1f9,,,,,,,,,,,,,,,,,
+KEY_BRL_DOT10,0x1fa,,,,,,,,,,,,,,,,,
+KEY_NUMERIC_0,0x200,,,,,,,,,,,,,,,,,
+KEY_NUMERIC_1,0x201,,,,,,,,,,,,,,,,,
+KEY_NUMERIC_2,0x202,,,,,,,,,,,,,,,,,
+KEY_NUMERIC_3,0x203,,,,,,,,,,,,,,,,,
+KEY_NUMERIC_4,0x204,,,,,,,,,,,,,,,,,
+KEY_NUMERIC_5,0x205,,,,,,,,,,,,,,,,,
+KEY_NUMERIC_6,0x206,,,,,,,,,,,,,,,,,
+KEY_NUMERIC_7,0x207,,,,,,,,,,,,,,,,,
+KEY_NUMERIC_8,0x208,,,,,,,,,,,,,,,,,
+KEY_NUMERIC_9,0x209,,,,,,,,,,,,,,,,,
+KEY_NUMERIC_STAR,0x20a,,,,,,,,,,,,,NumpadStar,,,,
+KEY_NUMERIC_POUND,0x20b,,,,,,,,,,,,,NumpadHash,,,,
+KEY_RFKILL,0x20c,,,,,,,,,,,,,,,,,
diff --git a/ui/keycodemapdb/tests/.gitignore b/ui/keycodemapdb/tests/.gitignore
new file mode 100644
index 000000000..05623054a
--- /dev/null
+++ b/ui/keycodemapdb/tests/.gitignore
@@ -0,0 +1,11 @@
+osx2win32.*
+osx2win32_name.*
+osx2xkb.*
+osx2xkb_name.*
+html2win32.*
+html2win32_name.*
+osx.*
+osx_name.*
+stdc
+stdc++
+node_modules/
diff --git a/ui/keycodemapdb/tests/Makefile b/ui/keycodemapdb/tests/Makefile
new file mode 100644
index 000000000..b25c77caa
--- /dev/null
+++ b/ui/keycodemapdb/tests/Makefile
@@ -0,0 +1,118 @@
+TESTS := stdc stdc++ python2 python3 javascript
+
+check: $(TESTS)
+ @set -e; for fn in $(TESTS); do \
+ ./$$fn; \
+ echo $$fn: OK; \
+ done
+ @echo Done.
+
+GEN := ../tools/keymap-gen
+DATA := ../data/keymaps.csv
+SOURCES := $(GEN) $(DATA)
+
+.DELETE_ON_ERROR:
+
+stdc: stdc.c osx2win32.h osx2win32_name.h \
+ osx2xkb.h osx2xkb_name.h \
+ html2win32.h html2win32_name.h \
+ osx.h osx_name.h
+ $(CC) -Wall -o $@ $^
+osx2win32.h: $(SOURCES)
+ $(GEN) --lang stdc code-map $(DATA) osx win32 > $@
+osx2win32_name.h: $(SOURCES)
+ $(GEN) --lang stdc name-map $(DATA) osx win32 > $@
+osx2xkb.h: $(SOURCES)
+ $(GEN) --lang stdc code-map $(DATA) osx xkb > $@
+osx2xkb_name.h: $(SOURCES)
+ $(GEN) --lang stdc name-map $(DATA) osx xkb > $@
+html2win32.h: $(SOURCES)
+ $(GEN) --lang stdc code-map $(DATA) html win32 > $@
+html2win32_name.h: $(SOURCES)
+ $(GEN) --lang stdc name-map $(DATA) html win32 > $@
+osx.h: $(SOURCES)
+ $(GEN) --lang stdc code-table $(DATA) osx > $@
+osx_name.h: $(SOURCES)
+ $(GEN) --lang stdc name-table $(DATA) osx > $@
+
+stdc++: stdc++.cc osx2win32.hh osx2win32_name.hh \
+ osx2xkb.hh osx2xkb_name.hh \
+ html2win32.hh html2win32_name.hh \
+ osx.hh osx_name.hh
+ $(CXX) -Wall -std=c++11 -o $@ $^
+osx2win32.hh: $(SOURCES)
+ $(GEN) --lang stdc++ code-map $(DATA) osx win32 > $@
+osx2win32_name.hh: $(SOURCES)
+ $(GEN) --lang stdc++ name-map $(DATA) osx win32 > $@
+osx2xkb.hh: $(SOURCES)
+ $(GEN) --lang stdc++ code-map $(DATA) osx xkb > $@
+osx2xkb_name.hh: $(SOURCES)
+ $(GEN) --lang stdc++ name-map $(DATA) osx xkb > $@
+html2win32.hh: $(SOURCES)
+ $(GEN) --lang stdc++ code-map $(DATA) html win32 > $@
+html2win32_name.hh: $(SOURCES)
+ $(GEN) --lang stdc++ name-map $(DATA) html win32 > $@
+osx.hh: $(SOURCES)
+ $(GEN) --lang stdc++ code-table $(DATA) osx > $@
+osx_name.hh: $(SOURCES)
+ $(GEN) --lang stdc++ name-table $(DATA) osx > $@
+
+python2: osx2win32.py osx2win32_name.py \
+ osx2xkb.py osx2xkb_name.py \
+ html2win32.py html2win32_name.py \
+ osx.py osx_name.py
+osx2win32.py: $(SOURCES)
+ $(GEN) --lang python2 code-map $(DATA) osx win32 > $@
+osx2win32_name.py: $(SOURCES)
+ $(GEN) --lang python2 name-map $(DATA) osx win32 > $@
+osx2xkb.py: $(SOURCES)
+ $(GEN) --lang python2 code-map $(DATA) osx xkb > $@
+osx2xkb_name.py: $(SOURCES)
+ $(GEN) --lang python2 name-map $(DATA) osx xkb > $@
+html2win32.py: $(SOURCES)
+ $(GEN) --lang python2 code-map $(DATA) html win32 > $@
+html2win32_name.py: $(SOURCES)
+ $(GEN) --lang python2 name-map $(DATA) html win32 > $@
+osx.py: $(SOURCES)
+ $(GEN) --lang python2 code-table $(DATA) osx > $@
+osx_name.py: $(SOURCES)
+ $(GEN) --lang python2 name-table $(DATA) osx > $@
+
+javascript: node_modules/babel-core \
+ node_modules/babel-plugin-transform-es2015-modules-commonjs \
+ osx2win32.js osx2win32_name.js \
+ osx2xkb.js osx2xkb_name.js \
+ html2win32.js html2win32_name.js \
+ osx.js osx_name.js
+node_modules/babel-core:
+ npm install babel-core
+node_modules/babel-plugin-transform-es2015-modules-commonjs:
+ npm install babel-plugin-transform-es2015-modules-commonjs
+osx2win32.js: $(SOURCES)
+ $(GEN) --lang js code-map $(DATA) osx win32 > $@
+osx2win32_name.js: $(SOURCES)
+ $(GEN) --lang js name-map $(DATA) osx win32 > $@
+osx2xkb.js: $(SOURCES)
+ $(GEN) --lang js code-map $(DATA) osx xkb > $@
+osx2xkb_name.js: $(SOURCES)
+ $(GEN) --lang js name-map $(DATA) osx xkb > $@
+html2win32.js: $(SOURCES)
+ $(GEN) --lang js code-map $(DATA) html win32 > $@
+html2win32_name.js: $(SOURCES)
+ $(GEN) --lang js name-map $(DATA) html win32 > $@
+osx.js: $(SOURCES)
+ $(GEN) --lang js code-table $(DATA) osx > $@
+osx_name.js: $(SOURCES)
+ $(GEN) --lang js name-table $(DATA) osx > $@
+
+clean:
+ rm -rf node_modules
+ rm -f osx2win32.*
+ rm -f osx2win32_name.*
+ rm -f osx2xkb.*
+ rm -f osx2xkb_name.*
+ rm -f html2win32.*
+ rm -f html2win32_name.*
+ rm -f osx.*
+ rm -f osx_name.*
+ rm -f stdc stdc++
diff --git a/ui/keycodemapdb/tests/javascript b/ui/keycodemapdb/tests/javascript
new file mode 100755
index 000000000..5179db2ce
--- /dev/null
+++ b/ui/keycodemapdb/tests/javascript
@@ -0,0 +1,53 @@
+#!/usr/bin/env node
+/*
+ * Keycode Map Generator JavaScript Tests
+ *
+ * Copyright 2017 Pierre Ossman for Cendio AB
+ *
+ * This file is dual license under the terms of the GPLv2 or later
+ * and 3-clause BSD licenses.
+ */
+
+"use strict";
+
+var assert = require('assert');
+var babel = require('babel-core');
+var fs = require('fs');
+
+function include(fn) {
+ var options = {
+ plugins: ["transform-es2015-modules-commonjs"]
+ };
+
+ var code = babel.transformFileSync(fn, options).code;
+ fs.writeFileSync("." + fn + "_nodejs.js", code);
+ var imp = require("./." + fn + "_nodejs.js");
+ fs.unlinkSync("./." + fn + "_nodejs.js");
+
+ return imp
+}
+
+var code_map_osx_to_win32 = include("osx2win32.js").default;
+var name_map_osx_to_win32 = include("osx2win32_name.js").default;
+
+var code_map_osx_to_xkb = include("osx2xkb.js").default;
+var name_map_osx_to_xkb = include("osx2xkb_name.js").default;
+
+var code_map_html_to_win32 = include("html2win32.js").default;
+var name_map_html_to_win32 = include("html2win32_name.js").default;
+
+var code_table_osx = include("osx.js").default;
+var name_table_osx = include("osx_name.js").default;
+
+assert.equal(code_map_osx_to_win32[0x1d], 0x30);
+assert.equal(name_map_osx_to_win32[0x1d], "VK_0");
+
+assert.equal(code_map_osx_to_xkb[0x1d], "AE10");
+assert.equal(name_map_osx_to_xkb[0x1d], "AE10");
+
+assert.equal(code_map_html_to_win32["ControlLeft"], 0x11);
+assert.equal(name_map_html_to_win32["ControlLeft"], "VK_CONTROL");
+
+assert.equal(code_table_osx[0x1d], 0x3b);
+assert.equal(name_table_osx[0x1d], "Control");
+
diff --git a/ui/keycodemapdb/tests/python2 b/ui/keycodemapdb/tests/python2
new file mode 100755
index 000000000..28a5b0346
--- /dev/null
+++ b/ui/keycodemapdb/tests/python2
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+python ./test.py
diff --git a/ui/keycodemapdb/tests/python3 b/ui/keycodemapdb/tests/python3
new file mode 100755
index 000000000..ded1f6806
--- /dev/null
+++ b/ui/keycodemapdb/tests/python3
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+python3 ./test.py
diff --git a/ui/keycodemapdb/tests/stdc++.cc b/ui/keycodemapdb/tests/stdc++.cc
new file mode 100644
index 000000000..5e3e8f551
--- /dev/null
+++ b/ui/keycodemapdb/tests/stdc++.cc
@@ -0,0 +1,40 @@
+/*
+ * Keycode Map Generator C++ Tests
+ *
+ * Copyright 2017 Pierre Ossman for Cendio AB
+ *
+ * This file is dual license under the terms of the GPLv2 or later
+ * and 3-clause BSD licenses.
+ */
+
+#include <assert.h>
+#include <string.h>
+
+#include "osx2win32.hh"
+#include "osx2win32_name.hh"
+
+#include "osx2xkb.hh"
+#include "osx2xkb_name.hh"
+
+#include "html2win32.hh"
+#include "html2win32_name.hh"
+
+#include "osx.hh"
+#include "osx_name.hh"
+
+int main(int argc, char** argv)
+{
+ assert(code_map_osx_to_win32[0x1d] == 0x30);
+ assert(strcmp(name_map_osx_to_win32[0x1d], "VK_0") == 0);
+
+ assert(strcmp(code_map_osx_to_xkb[0x1d], "AE10") == 0);
+ assert(strcmp(name_map_osx_to_xkb[0x1d], "AE10") == 0);
+
+ assert(code_map_html_to_win32.at("ControlLeft") == 0x11);
+ assert(strcmp(name_map_html_to_win32.at("ControlLeft"), "VK_CONTROL") == 0);
+
+ assert(code_table_osx[0x1d] == 0x3b);
+ assert(strcmp(name_table_osx[0x1d], "Control") == 0);
+
+ return 0;
+}
diff --git a/ui/keycodemapdb/tests/stdc.c b/ui/keycodemapdb/tests/stdc.c
new file mode 100644
index 000000000..e4946fa53
--- /dev/null
+++ b/ui/keycodemapdb/tests/stdc.c
@@ -0,0 +1,64 @@
+/*
+ * Keycode Map Generator C Tests
+ *
+ * Copyright 2017 Pierre Ossman for Cendio AB
+ *
+ * This file is dual license under the terms of the GPLv2 or later
+ * and 3-clause BSD licenses.
+ */
+
+#include <assert.h>
+#include <string.h>
+
+#include "osx2win32.h"
+#include "osx2win32_name.h"
+
+#include "osx2xkb.h"
+#include "osx2xkb_name.h"
+
+#include "html2win32.h"
+#include "html2win32_name.h"
+
+#include "osx.h"
+#include "osx_name.h"
+
+#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))
+
+int main(int argc, char** argv)
+{
+ unsigned i;
+
+ assert(code_map_osx_to_win32_len == ARRAY_SIZE(code_map_osx_to_win32));
+ assert(code_map_osx_to_win32[0x1d] == 0x30);
+ assert(name_map_osx_to_win32_len == ARRAY_SIZE(name_map_osx_to_win32));
+ assert(strcmp(name_map_osx_to_win32[0x1d], "VK_0") == 0);
+
+ assert(code_map_osx_to_xkb_len == ARRAY_SIZE(code_map_osx_to_xkb));
+ assert(strcmp(code_map_osx_to_xkb[0x1d], "AE10") == 0);
+ assert(name_map_osx_to_xkb_len == ARRAY_SIZE(name_map_osx_to_xkb));
+ assert(strcmp(name_map_osx_to_xkb[0x1d], "AE10") == 0);
+
+ assert(code_map_html_to_win32_len == ARRAY_SIZE(code_map_html_to_win32));
+ for (i = 0;i < code_map_html_to_win32_len;i++) {
+ if (strcmp(code_map_html_to_win32[i].from, "ControlLeft") == 0) {
+ assert(code_map_html_to_win32[i].to == 0x11);
+ break;
+ }
+ }
+ assert(i != code_map_html_to_win32_len);
+ assert(name_map_html_to_win32_len == ARRAY_SIZE(name_map_html_to_win32));
+ for (i = 0;i < name_map_html_to_win32_len;i++) {
+ if (strcmp(name_map_html_to_win32[i].from, "ControlLeft") == 0) {
+ assert(strcmp(name_map_html_to_win32[i].to, "VK_CONTROL") == 0);
+ break;
+ }
+ }
+ assert(i != name_map_html_to_win32_len);
+
+ assert(code_table_osx_len == ARRAY_SIZE(code_table_osx));
+ assert(code_table_osx[0x1d] == 0x3b);
+ assert(name_table_osx_len == ARRAY_SIZE(name_table_osx));
+ assert(strcmp(name_table_osx[0x1d], "Control") == 0);
+
+ return 0;
+}
diff --git a/ui/keycodemapdb/tests/test.py b/ui/keycodemapdb/tests/test.py
new file mode 100644
index 000000000..f26514558
--- /dev/null
+++ b/ui/keycodemapdb/tests/test.py
@@ -0,0 +1,30 @@
+# Keycode Map Generator Python Tests
+#
+# Copyright 2017 Pierre Ossman for Cendio AB
+#
+# This file is dual license under the terms of the GPLv2 or later
+# and 3-clause BSD licenses.
+
+import osx2win32
+import osx2win32_name
+
+import osx2xkb
+import osx2xkb_name
+
+import html2win32
+import html2win32_name
+
+import osx
+import osx_name
+
+assert osx2win32.code_map_osx_to_win32[0x1d] == 0x30
+assert osx2win32_name.name_map_osx_to_win32[0x1d] == "VK_0"
+
+assert osx2xkb.code_map_osx_to_xkb[0x1d] == "AE10"
+assert osx2xkb_name.name_map_osx_to_xkb[0x1d] == "AE10"
+
+assert html2win32.code_map_html_to_win32["ControlLeft"] == 0x11
+assert html2win32_name.name_map_html_to_win32["ControlLeft"] == "VK_CONTROL"
+
+assert osx.code_table_osx[0x1d] == 0x3b;
+assert osx_name.name_table_osx[0x1d] == "Control";
diff --git a/ui/keycodemapdb/thirdparty/LICENSE-argparse.txt b/ui/keycodemapdb/thirdparty/LICENSE-argparse.txt
new file mode 100644
index 000000000..640bc7809
--- /dev/null
+++ b/ui/keycodemapdb/thirdparty/LICENSE-argparse.txt
@@ -0,0 +1,20 @@
+argparse is (c) 2006-2009 Steven J. Bethard <steven.bethard@gmail.com>.
+
+The argparse module was contributed to Python as of Python 2.7 and thus
+was licensed under the Python license. Same license applies to all files in
+the argparse package project.
+
+For details about the Python License, please see doc/Python-License.txt.
+
+History
+-------
+
+Before (and including) argparse 1.1, the argparse package was licensed under
+Apache License v2.0.
+
+After argparse 1.1, all project files from the argparse project were deleted
+due to license compatibility issues between Apache License 2.0 and GNU GPL v2.
+
+The project repository then had a clean start with some files taken from
+Python 2.7.1, so definitely all files are under Python License now.
+
diff --git a/ui/keycodemapdb/thirdparty/__init__.py b/ui/keycodemapdb/thirdparty/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ui/keycodemapdb/thirdparty/__init__.py
diff --git a/ui/keycodemapdb/thirdparty/argparse.py b/ui/keycodemapdb/thirdparty/argparse.py
new file mode 100644
index 000000000..70a77cc02
--- /dev/null
+++ b/ui/keycodemapdb/thirdparty/argparse.py
@@ -0,0 +1,2392 @@
+# Author: Steven J. Bethard <steven.bethard@gmail.com>.
+# Maintainer: Thomas Waldmann <tw@waldmann-edv.de>
+
+"""Command-line parsing library
+
+This module is an optparse-inspired command-line parsing library that:
+
+ - handles both optional and positional arguments
+ - produces highly informative usage messages
+ - supports parsers that dispatch to sub-parsers
+
+The following is a simple usage example that sums integers from the
+command-line and writes the result to a file::
+
+ parser = argparse.ArgumentParser(
+ description='sum the integers at the command line')
+ parser.add_argument(
+ 'integers', metavar='int', nargs='+', type=int,
+ help='an integer to be summed')
+ parser.add_argument(
+ '--log', default=sys.stdout, type=argparse.FileType('w'),
+ help='the file where the sum should be written')
+ args = parser.parse_args()
+ args.log.write('%s' % sum(args.integers))
+ args.log.close()
+
+The module contains the following public classes:
+
+ - ArgumentParser -- The main entry point for command-line parsing. As the
+ example above shows, the add_argument() method is used to populate
+ the parser with actions for optional and positional arguments. Then
+ the parse_args() method is invoked to convert the args at the
+ command-line into an object with attributes.
+
+ - ArgumentError -- The exception raised by ArgumentParser objects when
+ there are errors with the parser's actions. Errors raised while
+ parsing the command-line are caught by ArgumentParser and emitted
+ as command-line messages.
+
+ - FileType -- A factory for defining types of files to be created. As the
+ example above shows, instances of FileType are typically passed as
+ the type= argument of add_argument() calls.
+
+ - Action -- The base class for parser actions. Typically actions are
+ selected by passing strings like 'store_true' or 'append_const' to
+ the action= argument of add_argument(). However, for greater
+ customization of ArgumentParser actions, subclasses of Action may
+ be defined and passed as the action= argument.
+
+ - HelpFormatter, RawDescriptionHelpFormatter, RawTextHelpFormatter,
+ ArgumentDefaultsHelpFormatter -- Formatter classes which
+ may be passed as the formatter_class= argument to the
+ ArgumentParser constructor. HelpFormatter is the default,
+ RawDescriptionHelpFormatter and RawTextHelpFormatter tell the parser
+ not to change the formatting for help text, and
+ ArgumentDefaultsHelpFormatter adds information about argument defaults
+ to the help.
+
+All other classes in this module are considered implementation details.
+(Also note that HelpFormatter and RawDescriptionHelpFormatter are only
+considered public as object names -- the API of the formatter objects is
+still considered an implementation detail.)
+"""
+
+__version__ = '1.4.0' # we use our own version number independant of the
+ # one in stdlib and we release this on pypi.
+
+__external_lib__ = True # to make sure the tests really test THIS lib,
+ # not the builtin one in Python stdlib
+
+__all__ = [
+ 'ArgumentParser',
+ 'ArgumentError',
+ 'ArgumentTypeError',
+ 'FileType',
+ 'HelpFormatter',
+ 'ArgumentDefaultsHelpFormatter',
+ 'RawDescriptionHelpFormatter',
+ 'RawTextHelpFormatter',
+ 'Namespace',
+ 'Action',
+ 'ONE_OR_MORE',
+ 'OPTIONAL',
+ 'PARSER',
+ 'REMAINDER',
+ 'SUPPRESS',
+ 'ZERO_OR_MORE',
+]
+
+
+import copy as _copy
+import os as _os
+import re as _re
+import sys as _sys
+import textwrap as _textwrap
+
+from gettext import gettext as _
+
+try:
+ set
+except NameError:
+ # for python < 2.4 compatibility (sets module is there since 2.3):
+ from sets import Set as set
+
+try:
+ basestring
+except NameError:
+ basestring = str
+
+try:
+ sorted
+except NameError:
+ # for python < 2.4 compatibility:
+ def sorted(iterable, reverse=False):
+ result = list(iterable)
+ result.sort()
+ if reverse:
+ result.reverse()
+ return result
+
+
+def _callable(obj):
+ return hasattr(obj, '__call__') or hasattr(obj, '__bases__')
+
+
+SUPPRESS = '==SUPPRESS=='
+
+OPTIONAL = '?'
+ZERO_OR_MORE = '*'
+ONE_OR_MORE = '+'
+PARSER = 'A...'
+REMAINDER = '...'
+_UNRECOGNIZED_ARGS_ATTR = '_unrecognized_args'
+
+# =============================
+# Utility functions and classes
+# =============================
+
+class _AttributeHolder(object):
+ """Abstract base class that provides __repr__.
+
+ The __repr__ method returns a string in the format::
+ ClassName(attr=name, attr=name, ...)
+ The attributes are determined either by a class-level attribute,
+ '_kwarg_names', or by inspecting the instance __dict__.
+ """
+
+ def __repr__(self):
+ type_name = type(self).__name__
+ arg_strings = []
+ for arg in self._get_args():
+ arg_strings.append(repr(arg))
+ for name, value in self._get_kwargs():
+ arg_strings.append('%s=%r' % (name, value))
+ return '%s(%s)' % (type_name, ', '.join(arg_strings))
+
+ def _get_kwargs(self):
+ return sorted(self.__dict__.items())
+
+ def _get_args(self):
+ return []
+
+
+def _ensure_value(namespace, name, value):
+ if getattr(namespace, name, None) is None:
+ setattr(namespace, name, value)
+ return getattr(namespace, name)
+
+
+# ===============
+# Formatting Help
+# ===============
+
+class HelpFormatter(object):
+ """Formatter for generating usage messages and argument help strings.
+
+ Only the name of this class is considered a public API. All the methods
+ provided by the class are considered an implementation detail.
+ """
+
+ def __init__(self,
+ prog,
+ indent_increment=2,
+ max_help_position=24,
+ width=None):
+
+ # default setting for width
+ if width is None:
+ try:
+ width = int(_os.environ['COLUMNS'])
+ except (KeyError, ValueError):
+ width = 80
+ width -= 2
+
+ self._prog = prog
+ self._indent_increment = indent_increment
+ self._max_help_position = max_help_position
+ self._width = width
+
+ self._current_indent = 0
+ self._level = 0
+ self._action_max_length = 0
+
+ self._root_section = self._Section(self, None)
+ self._current_section = self._root_section
+
+ self._whitespace_matcher = _re.compile(r'\s+')
+ self._long_break_matcher = _re.compile(r'\n\n\n+')
+
+ # ===============================
+ # Section and indentation methods
+ # ===============================
+ def _indent(self):
+ self._current_indent += self._indent_increment
+ self._level += 1
+
+ def _dedent(self):
+ self._current_indent -= self._indent_increment
+ assert self._current_indent >= 0, 'Indent decreased below 0.'
+ self._level -= 1
+
+ class _Section(object):
+
+ def __init__(self, formatter, parent, heading=None):
+ self.formatter = formatter
+ self.parent = parent
+ self.heading = heading
+ self.items = []
+
+ def format_help(self):
+ # format the indented section
+ if self.parent is not None:
+ self.formatter._indent()
+ join = self.formatter._join_parts
+ for func, args in self.items:
+ func(*args)
+ item_help = join([func(*args) for func, args in self.items])
+ if self.parent is not None:
+ self.formatter._dedent()
+
+ # return nothing if the section was empty
+ if not item_help:
+ return ''
+
+ # add the heading if the section was non-empty
+ if self.heading is not SUPPRESS and self.heading is not None:
+ current_indent = self.formatter._current_indent
+ heading = '%*s%s:\n' % (current_indent, '', self.heading)
+ else:
+ heading = ''
+
+ # join the section-initial newline, the heading and the help
+ return join(['\n', heading, item_help, '\n'])
+
+ def _add_item(self, func, args):
+ self._current_section.items.append((func, args))
+
+ # ========================
+ # Message building methods
+ # ========================
+ def start_section(self, heading):
+ self._indent()
+ section = self._Section(self, self._current_section, heading)
+ self._add_item(section.format_help, [])
+ self._current_section = section
+
+ def end_section(self):
+ self._current_section = self._current_section.parent
+ self._dedent()
+
+ def add_text(self, text):
+ if text is not SUPPRESS and text is not None:
+ self._add_item(self._format_text, [text])
+
+ def add_usage(self, usage, actions, groups, prefix=None):
+ if usage is not SUPPRESS:
+ args = usage, actions, groups, prefix
+ self._add_item(self._format_usage, args)
+
+ def add_argument(self, action):
+ if action.help is not SUPPRESS:
+
+ # find all invocations
+ get_invocation = self._format_action_invocation
+ invocations = [get_invocation(action)]
+ for subaction in self._iter_indented_subactions(action):
+ invocations.append(get_invocation(subaction))
+
+ # update the maximum item length
+ invocation_length = max([len(s) for s in invocations])
+ action_length = invocation_length + self._current_indent
+ self._action_max_length = max(self._action_max_length,
+ action_length)
+
+ # add the item to the list
+ self._add_item(self._format_action, [action])
+
+ def add_arguments(self, actions):
+ for action in actions:
+ self.add_argument(action)
+
+ # =======================
+ # Help-formatting methods
+ # =======================
+ def format_help(self):
+ help = self._root_section.format_help()
+ if help:
+ help = self._long_break_matcher.sub('\n\n', help)
+ help = help.strip('\n') + '\n'
+ return help
+
+ def _join_parts(self, part_strings):
+ return ''.join([part
+ for part in part_strings
+ if part and part is not SUPPRESS])
+
+ def _format_usage(self, usage, actions, groups, prefix):
+ if prefix is None:
+ prefix = _('usage: ')
+
+ # if usage is specified, use that
+ if usage is not None:
+ usage = usage % dict(prog=self._prog)
+
+ # if no optionals or positionals are available, usage is just prog
+ elif usage is None and not actions:
+ usage = '%(prog)s' % dict(prog=self._prog)
+
+ # if optionals and positionals are available, calculate usage
+ elif usage is None:
+ prog = '%(prog)s' % dict(prog=self._prog)
+
+ # split optionals from positionals
+ optionals = []
+ positionals = []
+ for action in actions:
+ if action.option_strings:
+ optionals.append(action)
+ else:
+ positionals.append(action)
+
+ # build full usage string
+ format = self._format_actions_usage
+ action_usage = format(optionals + positionals, groups)
+ usage = ' '.join([s for s in [prog, action_usage] if s])
+
+ # wrap the usage parts if it's too long
+ text_width = self._width - self._current_indent
+ if len(prefix) + len(usage) > text_width:
+
+ # break usage into wrappable parts
+ part_regexp = r'\(.*?\)+|\[.*?\]+|\S+'
+ opt_usage = format(optionals, groups)
+ pos_usage = format(positionals, groups)
+ opt_parts = _re.findall(part_regexp, opt_usage)
+ pos_parts = _re.findall(part_regexp, pos_usage)
+ assert ' '.join(opt_parts) == opt_usage
+ assert ' '.join(pos_parts) == pos_usage
+
+ # helper for wrapping lines
+ def get_lines(parts, indent, prefix=None):
+ lines = []
+ line = []
+ if prefix is not None:
+ line_len = len(prefix) - 1
+ else:
+ line_len = len(indent) - 1
+ for part in parts:
+ if line_len + 1 + len(part) > text_width:
+ lines.append(indent + ' '.join(line))
+ line = []
+ line_len = len(indent) - 1
+ line.append(part)
+ line_len += len(part) + 1
+ if line:
+ lines.append(indent + ' '.join(line))
+ if prefix is not None:
+ lines[0] = lines[0][len(indent):]
+ return lines
+
+ # if prog is short, follow it with optionals or positionals
+ if len(prefix) + len(prog) <= 0.75 * text_width:
+ indent = ' ' * (len(prefix) + len(prog) + 1)
+ if opt_parts:
+ lines = get_lines([prog] + opt_parts, indent, prefix)
+ lines.extend(get_lines(pos_parts, indent))
+ elif pos_parts:
+ lines = get_lines([prog] + pos_parts, indent, prefix)
+ else:
+ lines = [prog]
+
+ # if prog is long, put it on its own line
+ else:
+ indent = ' ' * len(prefix)
+ parts = opt_parts + pos_parts
+ lines = get_lines(parts, indent)
+ if len(lines) > 1:
+ lines = []
+ lines.extend(get_lines(opt_parts, indent))
+ lines.extend(get_lines(pos_parts, indent))
+ lines = [prog] + lines
+
+ # join lines into usage
+ usage = '\n'.join(lines)
+
+ # prefix with 'usage:'
+ return '%s%s\n\n' % (prefix, usage)
+
+ def _format_actions_usage(self, actions, groups):
+ # find group indices and identify actions in groups
+ group_actions = set()
+ inserts = {}
+ for group in groups:
+ try:
+ start = actions.index(group._group_actions[0])
+ except ValueError:
+ continue
+ else:
+ end = start + len(group._group_actions)
+ if actions[start:end] == group._group_actions:
+ for action in group._group_actions:
+ group_actions.add(action)
+ if not group.required:
+ if start in inserts:
+ inserts[start] += ' ['
+ else:
+ inserts[start] = '['
+ inserts[end] = ']'
+ else:
+ if start in inserts:
+ inserts[start] += ' ('
+ else:
+ inserts[start] = '('
+ inserts[end] = ')'
+ for i in range(start + 1, end):
+ inserts[i] = '|'
+
+ # collect all actions format strings
+ parts = []
+ for i, action in enumerate(actions):
+
+ # suppressed arguments are marked with None
+ # remove | separators for suppressed arguments
+ if action.help is SUPPRESS:
+ parts.append(None)
+ if inserts.get(i) == '|':
+ inserts.pop(i)
+ elif inserts.get(i + 1) == '|':
+ inserts.pop(i + 1)
+
+ # produce all arg strings
+ elif not action.option_strings:
+ part = self._format_args(action, action.dest)
+
+ # if it's in a group, strip the outer []
+ if action in group_actions:
+ if part[0] == '[' and part[-1] == ']':
+ part = part[1:-1]
+
+ # add the action string to the list
+ parts.append(part)
+
+ # produce the first way to invoke the option in brackets
+ else:
+ option_string = action.option_strings[0]
+
+ # if the Optional doesn't take a value, format is:
+ # -s or --long
+ if action.nargs == 0:
+ part = '%s' % option_string
+
+ # if the Optional takes a value, format is:
+ # -s ARGS or --long ARGS
+ else:
+ default = action.dest.upper()
+ args_string = self._format_args(action, default)
+ part = '%s %s' % (option_string, args_string)
+
+ # make it look optional if it's not required or in a group
+ if not action.required and action not in group_actions:
+ part = '[%s]' % part
+
+ # add the action string to the list
+ parts.append(part)
+
+ # insert things at the necessary indices
+ for i in sorted(inserts, reverse=True):
+ parts[i:i] = [inserts[i]]
+
+ # join all the action items with spaces
+ text = ' '.join([item for item in parts if item is not None])
+
+ # clean up separators for mutually exclusive groups
+ open = r'[\[(]'
+ close = r'[\])]'
+ text = _re.sub(r'(%s) ' % open, r'\1', text)
+ text = _re.sub(r' (%s)' % close, r'\1', text)
+ text = _re.sub(r'%s *%s' % (open, close), r'', text)
+ text = _re.sub(r'\(([^|]*)\)', r'\1', text)
+ text = text.strip()
+
+ # return the text
+ return text
+
+ def _format_text(self, text):
+ if '%(prog)' in text:
+ text = text % dict(prog=self._prog)
+ text_width = self._width - self._current_indent
+ indent = ' ' * self._current_indent
+ return self._fill_text(text, text_width, indent) + '\n\n'
+
+ def _format_action(self, action):
+ # determine the required width and the entry label
+ help_position = min(self._action_max_length + 2,
+ self._max_help_position)
+ help_width = self._width - help_position
+ action_width = help_position - self._current_indent - 2
+ action_header = self._format_action_invocation(action)
+
+ # ho nelp; start on same line and add a final newline
+ if not action.help:
+ tup = self._current_indent, '', action_header
+ action_header = '%*s%s\n' % tup
+
+ # short action name; start on the same line and pad two spaces
+ elif len(action_header) <= action_width:
+ tup = self._current_indent, '', action_width, action_header
+ action_header = '%*s%-*s ' % tup
+ indent_first = 0
+
+ # long action name; start on the next line
+ else:
+ tup = self._current_indent, '', action_header
+ action_header = '%*s%s\n' % tup
+ indent_first = help_position
+
+ # collect the pieces of the action help
+ parts = [action_header]
+
+ # if there was help for the action, add lines of help text
+ if action.help:
+ help_text = self._expand_help(action)
+ help_lines = self._split_lines(help_text, help_width)
+ parts.append('%*s%s\n' % (indent_first, '', help_lines[0]))
+ for line in help_lines[1:]:
+ parts.append('%*s%s\n' % (help_position, '', line))
+
+ # or add a newline if the description doesn't end with one
+ elif not action_header.endswith('\n'):
+ parts.append('\n')
+
+ # if there are any sub-actions, add their help as well
+ for subaction in self._iter_indented_subactions(action):
+ parts.append(self._format_action(subaction))
+
+ # return a single string
+ return self._join_parts(parts)
+
+ def _format_action_invocation(self, action):
+ if not action.option_strings:
+ metavar, = self._metavar_formatter(action, action.dest)(1)
+ return metavar
+
+ else:
+ parts = []
+
+ # if the Optional doesn't take a value, format is:
+ # -s, --long
+ if action.nargs == 0:
+ parts.extend(action.option_strings)
+
+ # if the Optional takes a value, format is:
+ # -s ARGS, --long ARGS
+ else:
+ default = action.dest.upper()
+ args_string = self._format_args(action, default)
+ for option_string in action.option_strings:
+ parts.append('%s %s' % (option_string, args_string))
+
+ return ', '.join(parts)
+
+ def _metavar_formatter(self, action, default_metavar):
+ if action.metavar is not None:
+ result = action.metavar
+ elif action.choices is not None:
+ choice_strs = [str(choice) for choice in action.choices]
+ result = '{%s}' % ','.join(choice_strs)
+ else:
+ result = default_metavar
+
+ def format(tuple_size):
+ if isinstance(result, tuple):
+ return result
+ else:
+ return (result, ) * tuple_size
+ return format
+
+ def _format_args(self, action, default_metavar):
+ get_metavar = self._metavar_formatter(action, default_metavar)
+ if action.nargs is None:
+ result = '%s' % get_metavar(1)
+ elif action.nargs == OPTIONAL:
+ result = '[%s]' % get_metavar(1)
+ elif action.nargs == ZERO_OR_MORE:
+ result = '[%s [%s ...]]' % get_metavar(2)
+ elif action.nargs == ONE_OR_MORE:
+ result = '%s [%s ...]' % get_metavar(2)
+ elif action.nargs == REMAINDER:
+ result = '...'
+ elif action.nargs == PARSER:
+ result = '%s ...' % get_metavar(1)
+ else:
+ formats = ['%s' for _ in range(action.nargs)]
+ result = ' '.join(formats) % get_metavar(action.nargs)
+ return result
+
+ def _expand_help(self, action):
+ params = dict(vars(action), prog=self._prog)
+ for name in list(params):
+ if params[name] is SUPPRESS:
+ del params[name]
+ for name in list(params):
+ if hasattr(params[name], '__name__'):
+ params[name] = params[name].__name__
+ if params.get('choices') is not None:
+ choices_str = ', '.join([str(c) for c in params['choices']])
+ params['choices'] = choices_str
+ return self._get_help_string(action) % params
+
+ def _iter_indented_subactions(self, action):
+ try:
+ get_subactions = action._get_subactions
+ except AttributeError:
+ pass
+ else:
+ self._indent()
+ for subaction in get_subactions():
+ yield subaction
+ self._dedent()
+
+ def _split_lines(self, text, width):
+ text = self._whitespace_matcher.sub(' ', text).strip()
+ return _textwrap.wrap(text, width)
+
+ def _fill_text(self, text, width, indent):
+ text = self._whitespace_matcher.sub(' ', text).strip()
+ return _textwrap.fill(text, width, initial_indent=indent,
+ subsequent_indent=indent)
+
+ def _get_help_string(self, action):
+ return action.help
+
+
+class RawDescriptionHelpFormatter(HelpFormatter):
+ """Help message formatter which retains any formatting in descriptions.
+
+ Only the name of this class is considered a public API. All the methods
+ provided by the class are considered an implementation detail.
+ """
+
+ def _fill_text(self, text, width, indent):
+ return ''.join([indent + line for line in text.splitlines(True)])
+
+
+class RawTextHelpFormatter(RawDescriptionHelpFormatter):
+ """Help message formatter which retains formatting of all help text.
+
+ Only the name of this class is considered a public API. All the methods
+ provided by the class are considered an implementation detail.
+ """
+
+ def _split_lines(self, text, width):
+ return text.splitlines()
+
+
+class ArgumentDefaultsHelpFormatter(HelpFormatter):
+ """Help message formatter which adds default values to argument help.
+
+ Only the name of this class is considered a public API. All the methods
+ provided by the class are considered an implementation detail.
+ """
+
+ def _get_help_string(self, action):
+ help = action.help
+ if '%(default)' not in action.help:
+ if action.default is not SUPPRESS:
+ defaulting_nargs = [OPTIONAL, ZERO_OR_MORE]
+ if action.option_strings or action.nargs in defaulting_nargs:
+ help += ' (default: %(default)s)'
+ return help
+
+
+# =====================
+# Options and Arguments
+# =====================
+
+def _get_action_name(argument):
+ if argument is None:
+ return None
+ elif argument.option_strings:
+ return '/'.join(argument.option_strings)
+ elif argument.metavar not in (None, SUPPRESS):
+ return argument.metavar
+ elif argument.dest not in (None, SUPPRESS):
+ return argument.dest
+ else:
+ return None
+
+
+class ArgumentError(Exception):
+ """An error from creating or using an argument (optional or positional).
+
+ The string value of this exception is the message, augmented with
+ information about the argument that caused it.
+ """
+
+ def __init__(self, argument, message):
+ self.argument_name = _get_action_name(argument)
+ self.message = message
+
+ def __str__(self):
+ if self.argument_name is None:
+ format = '%(message)s'
+ else:
+ format = 'argument %(argument_name)s: %(message)s'
+ return format % dict(message=self.message,
+ argument_name=self.argument_name)
+
+
+class ArgumentTypeError(Exception):
+ """An error from trying to convert a command line string to a type."""
+ pass
+
+
+# ==============
+# Action classes
+# ==============
+
+class Action(_AttributeHolder):
+ """Information about how to convert command line strings to Python objects.
+
+ Action objects are used by an ArgumentParser to represent the information
+ needed to parse a single argument from one or more strings from the
+ command line. The keyword arguments to the Action constructor are also
+ all attributes of Action instances.
+
+ Keyword Arguments:
+
+ - option_strings -- A list of command-line option strings which
+ should be associated with this action.
+
+ - dest -- The name of the attribute to hold the created object(s)
+
+ - nargs -- The number of command-line arguments that should be
+ consumed. By default, one argument will be consumed and a single
+ value will be produced. Other values include:
+ - N (an integer) consumes N arguments (and produces a list)
+ - '?' consumes zero or one arguments
+ - '*' consumes zero or more arguments (and produces a list)
+ - '+' consumes one or more arguments (and produces a list)
+ Note that the difference between the default and nargs=1 is that
+ with the default, a single value will be produced, while with
+ nargs=1, a list containing a single value will be produced.
+
+ - const -- The value to be produced if the option is specified and the
+ option uses an action that takes no values.
+
+ - default -- The value to be produced if the option is not specified.
+
+ - type -- The type which the command-line arguments should be converted
+ to, should be one of 'string', 'int', 'float', 'complex' or a
+ callable object that accepts a single string argument. If None,
+ 'string' is assumed.
+
+ - choices -- A container of values that should be allowed. If not None,
+ after a command-line argument has been converted to the appropriate
+ type, an exception will be raised if it is not a member of this
+ collection.
+
+ - required -- True if the action must always be specified at the
+ command line. This is only meaningful for optional command-line
+ arguments.
+
+ - help -- The help string describing the argument.
+
+ - metavar -- The name to be used for the option's argument with the
+ help string. If None, the 'dest' value will be used as the name.
+ """
+
+ def __init__(self,
+ option_strings,
+ dest,
+ nargs=None,
+ const=None,
+ default=None,
+ type=None,
+ choices=None,
+ required=False,
+ help=None,
+ metavar=None):
+ self.option_strings = option_strings
+ self.dest = dest
+ self.nargs = nargs
+ self.const = const
+ self.default = default
+ self.type = type
+ self.choices = choices
+ self.required = required
+ self.help = help
+ self.metavar = metavar
+
+ def _get_kwargs(self):
+ names = [
+ 'option_strings',
+ 'dest',
+ 'nargs',
+ 'const',
+ 'default',
+ 'type',
+ 'choices',
+ 'help',
+ 'metavar',
+ ]
+ return [(name, getattr(self, name)) for name in names]
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ raise NotImplementedError(_('.__call__() not defined'))
+
+
+class _StoreAction(Action):
+
+ def __init__(self,
+ option_strings,
+ dest,
+ nargs=None,
+ const=None,
+ default=None,
+ type=None,
+ choices=None,
+ required=False,
+ help=None,
+ metavar=None):
+ if nargs == 0:
+ raise ValueError('nargs for store actions must be > 0; if you '
+ 'have nothing to store, actions such as store '
+ 'true or store const may be more appropriate')
+ if const is not None and nargs != OPTIONAL:
+ raise ValueError('nargs must be %r to supply const' % OPTIONAL)
+ super(_StoreAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ nargs=nargs,
+ const=const,
+ default=default,
+ type=type,
+ choices=choices,
+ required=required,
+ help=help,
+ metavar=metavar)
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ setattr(namespace, self.dest, values)
+
+
+class _StoreConstAction(Action):
+
+ def __init__(self,
+ option_strings,
+ dest,
+ const,
+ default=None,
+ required=False,
+ help=None,
+ metavar=None):
+ super(_StoreConstAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ nargs=0,
+ const=const,
+ default=default,
+ required=required,
+ help=help)
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ setattr(namespace, self.dest, self.const)
+
+
+class _StoreTrueAction(_StoreConstAction):
+
+ def __init__(self,
+ option_strings,
+ dest,
+ default=False,
+ required=False,
+ help=None):
+ super(_StoreTrueAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ const=True,
+ default=default,
+ required=required,
+ help=help)
+
+
+class _StoreFalseAction(_StoreConstAction):
+
+ def __init__(self,
+ option_strings,
+ dest,
+ default=True,
+ required=False,
+ help=None):
+ super(_StoreFalseAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ const=False,
+ default=default,
+ required=required,
+ help=help)
+
+
+class _AppendAction(Action):
+
+ def __init__(self,
+ option_strings,
+ dest,
+ nargs=None,
+ const=None,
+ default=None,
+ type=None,
+ choices=None,
+ required=False,
+ help=None,
+ metavar=None):
+ if nargs == 0:
+ raise ValueError('nargs for append actions must be > 0; if arg '
+ 'strings are not supplying the value to append, '
+ 'the append const action may be more appropriate')
+ if const is not None and nargs != OPTIONAL:
+ raise ValueError('nargs must be %r to supply const' % OPTIONAL)
+ super(_AppendAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ nargs=nargs,
+ const=const,
+ default=default,
+ type=type,
+ choices=choices,
+ required=required,
+ help=help,
+ metavar=metavar)
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ items = _copy.copy(_ensure_value(namespace, self.dest, []))
+ items.append(values)
+ setattr(namespace, self.dest, items)
+
+
+class _AppendConstAction(Action):
+
+ def __init__(self,
+ option_strings,
+ dest,
+ const,
+ default=None,
+ required=False,
+ help=None,
+ metavar=None):
+ super(_AppendConstAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ nargs=0,
+ const=const,
+ default=default,
+ required=required,
+ help=help,
+ metavar=metavar)
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ items = _copy.copy(_ensure_value(namespace, self.dest, []))
+ items.append(self.const)
+ setattr(namespace, self.dest, items)
+
+
+class _CountAction(Action):
+
+ def __init__(self,
+ option_strings,
+ dest,
+ default=None,
+ required=False,
+ help=None):
+ super(_CountAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ nargs=0,
+ default=default,
+ required=required,
+ help=help)
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ new_count = _ensure_value(namespace, self.dest, 0) + 1
+ setattr(namespace, self.dest, new_count)
+
+
+class _HelpAction(Action):
+
+ def __init__(self,
+ option_strings,
+ dest=SUPPRESS,
+ default=SUPPRESS,
+ help=None):
+ super(_HelpAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ default=default,
+ nargs=0,
+ help=help)
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ parser.print_help()
+ parser.exit()
+
+
+class _VersionAction(Action):
+
+ def __init__(self,
+ option_strings,
+ version=None,
+ dest=SUPPRESS,
+ default=SUPPRESS,
+ help="show program's version number and exit"):
+ super(_VersionAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ default=default,
+ nargs=0,
+ help=help)
+ self.version = version
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ version = self.version
+ if version is None:
+ version = parser.version
+ formatter = parser._get_formatter()
+ formatter.add_text(version)
+ parser.exit(message=formatter.format_help())
+
+
+class _SubParsersAction(Action):
+
+ class _ChoicesPseudoAction(Action):
+
+ def __init__(self, name, aliases, help):
+ metavar = dest = name
+ if aliases:
+ metavar += ' (%s)' % ', '.join(aliases)
+ sup = super(_SubParsersAction._ChoicesPseudoAction, self)
+ sup.__init__(option_strings=[], dest=dest, help=help,
+ metavar=metavar)
+
+ def __init__(self,
+ option_strings,
+ prog,
+ parser_class,
+ dest=SUPPRESS,
+ help=None,
+ metavar=None):
+
+ self._prog_prefix = prog
+ self._parser_class = parser_class
+ self._name_parser_map = {}
+ self._choices_actions = []
+
+ super(_SubParsersAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ nargs=PARSER,
+ choices=self._name_parser_map,
+ help=help,
+ metavar=metavar)
+
+ def add_parser(self, name, **kwargs):
+ # set prog from the existing prefix
+ if kwargs.get('prog') is None:
+ kwargs['prog'] = '%s %s' % (self._prog_prefix, name)
+
+ aliases = kwargs.pop('aliases', ())
+
+ # create a pseudo-action to hold the choice help
+ if 'help' in kwargs:
+ help = kwargs.pop('help')
+ choice_action = self._ChoicesPseudoAction(name, aliases, help)
+ self._choices_actions.append(choice_action)
+
+ # create the parser and add it to the map
+ parser = self._parser_class(**kwargs)
+ self._name_parser_map[name] = parser
+
+ # make parser available under aliases also
+ for alias in aliases:
+ self._name_parser_map[alias] = parser
+
+ return parser
+
+ def _get_subactions(self):
+ return self._choices_actions
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ parser_name = values[0]
+ arg_strings = values[1:]
+
+ # set the parser name if requested
+ if self.dest is not SUPPRESS:
+ setattr(namespace, self.dest, parser_name)
+
+ # select the parser
+ try:
+ parser = self._name_parser_map[parser_name]
+ except KeyError:
+ tup = parser_name, ', '.join(self._name_parser_map)
+ msg = _('unknown parser %r (choices: %s)' % tup)
+ raise ArgumentError(self, msg)
+
+ # parse all the remaining options into the namespace
+ # store any unrecognized options on the object, so that the top
+ # level parser can decide what to do with them
+ namespace, arg_strings = parser.parse_known_args(arg_strings, namespace)
+ if arg_strings:
+ vars(namespace).setdefault(_UNRECOGNIZED_ARGS_ATTR, [])
+ getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).extend(arg_strings)
+
+
+# ==============
+# Type classes
+# ==============
+
+class FileType(object):
+ """Factory for creating file object types
+
+ Instances of FileType are typically passed as type= arguments to the
+ ArgumentParser add_argument() method.
+
+ Keyword Arguments:
+ - mode -- A string indicating how the file is to be opened. Accepts the
+ same values as the builtin open() function.
+ - bufsize -- The file's desired buffer size. Accepts the same values as
+ the builtin open() function.
+ """
+
+ def __init__(self, mode='r', bufsize=None):
+ self._mode = mode
+ self._bufsize = bufsize
+
+ def __call__(self, string):
+ # the special argument "-" means sys.std{in,out}
+ if string == '-':
+ if 'r' in self._mode:
+ return _sys.stdin
+ elif 'w' in self._mode:
+ return _sys.stdout
+ else:
+ msg = _('argument "-" with mode %r' % self._mode)
+ raise ValueError(msg)
+
+ try:
+ # all other arguments are used as file names
+ if self._bufsize:
+ return open(string, self._mode, self._bufsize)
+ else:
+ return open(string, self._mode)
+ except IOError:
+ err = _sys.exc_info()[1]
+ message = _("can't open '%s': %s")
+ raise ArgumentTypeError(message % (string, err))
+
+ def __repr__(self):
+ args = [self._mode, self._bufsize]
+ args_str = ', '.join([repr(arg) for arg in args if arg is not None])
+ return '%s(%s)' % (type(self).__name__, args_str)
+
+# ===========================
+# Optional and Positional Parsing
+# ===========================
+
+class Namespace(_AttributeHolder):
+ """Simple object for storing attributes.
+
+ Implements equality by attribute names and values, and provides a simple
+ string representation.
+ """
+
+ def __init__(self, **kwargs):
+ for name in kwargs:
+ setattr(self, name, kwargs[name])
+
+ __hash__ = None
+
+ def __eq__(self, other):
+ return vars(self) == vars(other)
+
+ def __ne__(self, other):
+ return not (self == other)
+
+ def __contains__(self, key):
+ return key in self.__dict__
+
+
+class _ActionsContainer(object):
+
+ def __init__(self,
+ description,
+ prefix_chars,
+ argument_default,
+ conflict_handler):
+ super(_ActionsContainer, self).__init__()
+
+ self.description = description
+ self.argument_default = argument_default
+ self.prefix_chars = prefix_chars
+ self.conflict_handler = conflict_handler
+
+ # set up registries
+ self._registries = {}
+
+ # register actions
+ self.register('action', None, _StoreAction)
+ self.register('action', 'store', _StoreAction)
+ self.register('action', 'store_const', _StoreConstAction)
+ self.register('action', 'store_true', _StoreTrueAction)
+ self.register('action', 'store_false', _StoreFalseAction)
+ self.register('action', 'append', _AppendAction)
+ self.register('action', 'append_const', _AppendConstAction)
+ self.register('action', 'count', _CountAction)
+ self.register('action', 'help', _HelpAction)
+ self.register('action', 'version', _VersionAction)
+ self.register('action', 'parsers', _SubParsersAction)
+
+ # raise an exception if the conflict handler is invalid
+ self._get_handler()
+
+ # action storage
+ self._actions = []
+ self._option_string_actions = {}
+
+ # groups
+ self._action_groups = []
+ self._mutually_exclusive_groups = []
+
+ # defaults storage
+ self._defaults = {}
+
+ # determines whether an "option" looks like a negative number
+ self._negative_number_matcher = _re.compile(r'^-\d+$|^-\d*\.\d+$')
+
+ # whether or not there are any optionals that look like negative
+ # numbers -- uses a list so it can be shared and edited
+ self._has_negative_number_optionals = []
+
+ # ====================
+ # Registration methods
+ # ====================
+ def register(self, registry_name, value, object):
+ registry = self._registries.setdefault(registry_name, {})
+ registry[value] = object
+
+ def _registry_get(self, registry_name, value, default=None):
+ return self._registries[registry_name].get(value, default)
+
+ # ==================================
+ # Namespace default accessor methods
+ # ==================================
+ def set_defaults(self, **kwargs):
+ self._defaults.update(kwargs)
+
+ # if these defaults match any existing arguments, replace
+ # the previous default on the object with the new one
+ for action in self._actions:
+ if action.dest in kwargs:
+ action.default = kwargs[action.dest]
+
+ def get_default(self, dest):
+ for action in self._actions:
+ if action.dest == dest and action.default is not None:
+ return action.default
+ return self._defaults.get(dest, None)
+
+
+ # =======================
+ # Adding argument actions
+ # =======================
+ def add_argument(self, *args, **kwargs):
+ """
+ add_argument(dest, ..., name=value, ...)
+ add_argument(option_string, option_string, ..., name=value, ...)
+ """
+
+ # if no positional args are supplied or only one is supplied and
+ # it doesn't look like an option string, parse a positional
+ # argument
+ chars = self.prefix_chars
+ if not args or len(args) == 1 and args[0][0] not in chars:
+ if args and 'dest' in kwargs:
+ raise ValueError('dest supplied twice for positional argument')
+ kwargs = self._get_positional_kwargs(*args, **kwargs)
+
+ # otherwise, we're adding an optional argument
+ else:
+ kwargs = self._get_optional_kwargs(*args, **kwargs)
+
+ # if no default was supplied, use the parser-level default
+ if 'default' not in kwargs:
+ dest = kwargs['dest']
+ if dest in self._defaults:
+ kwargs['default'] = self._defaults[dest]
+ elif self.argument_default is not None:
+ kwargs['default'] = self.argument_default
+
+ # create the action object, and add it to the parser
+ action_class = self._pop_action_class(kwargs)
+ if not _callable(action_class):
+ raise ValueError('unknown action "%s"' % action_class)
+ action = action_class(**kwargs)
+
+ # raise an error if the action type is not callable
+ type_func = self._registry_get('type', action.type, action.type)
+ if not _callable(type_func):
+ raise ValueError('%r is not callable' % type_func)
+
+ return self._add_action(action)
+
+ def add_argument_group(self, *args, **kwargs):
+ group = _ArgumentGroup(self, *args, **kwargs)
+ self._action_groups.append(group)
+ return group
+
+ def add_mutually_exclusive_group(self, **kwargs):
+ group = _MutuallyExclusiveGroup(self, **kwargs)
+ self._mutually_exclusive_groups.append(group)
+ return group
+
+ def _add_action(self, action):
+ # resolve any conflicts
+ self._check_conflict(action)
+
+ # add to actions list
+ self._actions.append(action)
+ action.container = self
+
+ # index the action by any option strings it has
+ for option_string in action.option_strings:
+ self._option_string_actions[option_string] = action
+
+ # set the flag if any option strings look like negative numbers
+ for option_string in action.option_strings:
+ if self._negative_number_matcher.match(option_string):
+ if not self._has_negative_number_optionals:
+ self._has_negative_number_optionals.append(True)
+
+ # return the created action
+ return action
+
+ def _remove_action(self, action):
+ self._actions.remove(action)
+
+ def _add_container_actions(self, container):
+ # collect groups by titles
+ title_group_map = {}
+ for group in self._action_groups:
+ if group.title in title_group_map:
+ msg = _('cannot merge actions - two groups are named %r')
+ raise ValueError(msg % (group.title))
+ title_group_map[group.title] = group
+
+ # map each action to its group
+ group_map = {}
+ for group in container._action_groups:
+
+ # if a group with the title exists, use that, otherwise
+ # create a new group matching the container's group
+ if group.title not in title_group_map:
+ title_group_map[group.title] = self.add_argument_group(
+ title=group.title,
+ description=group.description,
+ conflict_handler=group.conflict_handler)
+
+ # map the actions to their new group
+ for action in group._group_actions:
+ group_map[action] = title_group_map[group.title]
+
+ # add container's mutually exclusive groups
+ # NOTE: if add_mutually_exclusive_group ever gains title= and
+ # description= then this code will need to be expanded as above
+ for group in container._mutually_exclusive_groups:
+ mutex_group = self.add_mutually_exclusive_group(
+ required=group.required)
+
+ # map the actions to their new mutex group
+ for action in group._group_actions:
+ group_map[action] = mutex_group
+
+ # add all actions to this container or their group
+ for action in container._actions:
+ group_map.get(action, self)._add_action(action)
+
+ def _get_positional_kwargs(self, dest, **kwargs):
+ # make sure required is not specified
+ if 'required' in kwargs:
+ msg = _("'required' is an invalid argument for positionals")
+ raise TypeError(msg)
+
+ # mark positional arguments as required if at least one is
+ # always required
+ if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]:
+ kwargs['required'] = True
+ if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs:
+ kwargs['required'] = True
+
+ # return the keyword arguments with no option strings
+ return dict(kwargs, dest=dest, option_strings=[])
+
+ def _get_optional_kwargs(self, *args, **kwargs):
+ # determine short and long option strings
+ option_strings = []
+ long_option_strings = []
+ for option_string in args:
+ # error on strings that don't start with an appropriate prefix
+ if not option_string[0] in self.prefix_chars:
+ msg = _('invalid option string %r: '
+ 'must start with a character %r')
+ tup = option_string, self.prefix_chars
+ raise ValueError(msg % tup)
+
+ # strings starting with two prefix characters are long options
+ option_strings.append(option_string)
+ if option_string[0] in self.prefix_chars:
+ if len(option_string) > 1:
+ if option_string[1] in self.prefix_chars:
+ long_option_strings.append(option_string)
+
+ # infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x'
+ dest = kwargs.pop('dest', None)
+ if dest is None:
+ if long_option_strings:
+ dest_option_string = long_option_strings[0]
+ else:
+ dest_option_string = option_strings[0]
+ dest = dest_option_string.lstrip(self.prefix_chars)
+ if not dest:
+ msg = _('dest= is required for options like %r')
+ raise ValueError(msg % option_string)
+ dest = dest.replace('-', '_')
+
+ # return the updated keyword arguments
+ return dict(kwargs, dest=dest, option_strings=option_strings)
+
+ def _pop_action_class(self, kwargs, default=None):
+ action = kwargs.pop('action', default)
+ return self._registry_get('action', action, action)
+
+ def _get_handler(self):
+ # determine function from conflict handler string
+ handler_func_name = '_handle_conflict_%s' % self.conflict_handler
+ try:
+ return getattr(self, handler_func_name)
+ except AttributeError:
+ msg = _('invalid conflict_resolution value: %r')
+ raise ValueError(msg % self.conflict_handler)
+
+ def _check_conflict(self, action):
+
+ # find all options that conflict with this option
+ confl_optionals = []
+ for option_string in action.option_strings:
+ if option_string in self._option_string_actions:
+ confl_optional = self._option_string_actions[option_string]
+ confl_optionals.append((option_string, confl_optional))
+
+ # resolve any conflicts
+ if confl_optionals:
+ conflict_handler = self._get_handler()
+ conflict_handler(action, confl_optionals)
+
+ def _handle_conflict_error(self, action, conflicting_actions):
+ message = _('conflicting option string(s): %s')
+ conflict_string = ', '.join([option_string
+ for option_string, action
+ in conflicting_actions])
+ raise ArgumentError(action, message % conflict_string)
+
+ def _handle_conflict_resolve(self, action, conflicting_actions):
+
+ # remove all conflicting options
+ for option_string, action in conflicting_actions:
+
+ # remove the conflicting option
+ action.option_strings.remove(option_string)
+ self._option_string_actions.pop(option_string, None)
+
+ # if the option now has no option string, remove it from the
+ # container holding it
+ if not action.option_strings:
+ action.container._remove_action(action)
+
+
+class _ArgumentGroup(_ActionsContainer):
+
+ def __init__(self, container, title=None, description=None, **kwargs):
+ # add any missing keyword arguments by checking the container
+ update = kwargs.setdefault
+ update('conflict_handler', container.conflict_handler)
+ update('prefix_chars', container.prefix_chars)
+ update('argument_default', container.argument_default)
+ super_init = super(_ArgumentGroup, self).__init__
+ super_init(description=description, **kwargs)
+
+ # group attributes
+ self.title = title
+ self._group_actions = []
+
+ # share most attributes with the container
+ self._registries = container._registries
+ self._actions = container._actions
+ self._option_string_actions = container._option_string_actions
+ self._defaults = container._defaults
+ self._has_negative_number_optionals = \
+ container._has_negative_number_optionals
+
+ def _add_action(self, action):
+ action = super(_ArgumentGroup, self)._add_action(action)
+ self._group_actions.append(action)
+ return action
+
+ def _remove_action(self, action):
+ super(_ArgumentGroup, self)._remove_action(action)
+ self._group_actions.remove(action)
+
+
+class _MutuallyExclusiveGroup(_ArgumentGroup):
+
+ def __init__(self, container, required=False):
+ super(_MutuallyExclusiveGroup, self).__init__(container)
+ self.required = required
+ self._container = container
+
+ def _add_action(self, action):
+ if action.required:
+ msg = _('mutually exclusive arguments must be optional')
+ raise ValueError(msg)
+ action = self._container._add_action(action)
+ self._group_actions.append(action)
+ return action
+
+ def _remove_action(self, action):
+ self._container._remove_action(action)
+ self._group_actions.remove(action)
+
+
+class ArgumentParser(_AttributeHolder, _ActionsContainer):
+ """Object for parsing command line strings into Python objects.
+
+ Keyword Arguments:
+ - prog -- The name of the program (default: sys.argv[0])
+ - usage -- A usage message (default: auto-generated from arguments)
+ - description -- A description of what the program does
+ - epilog -- Text following the argument descriptions
+ - parents -- Parsers whose arguments should be copied into this one
+ - formatter_class -- HelpFormatter class for printing help messages
+ - prefix_chars -- Characters that prefix optional arguments
+ - fromfile_prefix_chars -- Characters that prefix files containing
+ additional arguments
+ - argument_default -- The default value for all arguments
+ - conflict_handler -- String indicating how to handle conflicts
+ - add_help -- Add a -h/-help option
+ """
+
+ def __init__(self,
+ prog=None,
+ usage=None,
+ description=None,
+ epilog=None,
+ version=None,
+ parents=[],
+ formatter_class=HelpFormatter,
+ prefix_chars='-',
+ fromfile_prefix_chars=None,
+ argument_default=None,
+ conflict_handler='error',
+ add_help=True):
+
+ if version is not None:
+ import warnings
+ warnings.warn(
+ """The "version" argument to ArgumentParser is deprecated. """
+ """Please use """
+ """"add_argument(..., action='version', version="N", ...)" """
+ """instead""", DeprecationWarning)
+
+ superinit = super(ArgumentParser, self).__init__
+ superinit(description=description,
+ prefix_chars=prefix_chars,
+ argument_default=argument_default,
+ conflict_handler=conflict_handler)
+
+ # default setting for prog
+ if prog is None:
+ prog = _os.path.basename(_sys.argv[0])
+
+ self.prog = prog
+ self.usage = usage
+ self.epilog = epilog
+ self.version = version
+ self.formatter_class = formatter_class
+ self.fromfile_prefix_chars = fromfile_prefix_chars
+ self.add_help = add_help
+
+ add_group = self.add_argument_group
+ self._positionals = add_group(_('positional arguments'))
+ self._optionals = add_group(_('optional arguments'))
+ self._subparsers = None
+
+ # register types
+ def identity(string):
+ return string
+ self.register('type', None, identity)
+
+ # add help and version arguments if necessary
+ # (using explicit default to override global argument_default)
+ if '-' in prefix_chars:
+ default_prefix = '-'
+ else:
+ default_prefix = prefix_chars[0]
+ if self.add_help:
+ self.add_argument(
+ default_prefix+'h', default_prefix*2+'help',
+ action='help', default=SUPPRESS,
+ help=_('show this help message and exit'))
+ if self.version:
+ self.add_argument(
+ default_prefix+'v', default_prefix*2+'version',
+ action='version', default=SUPPRESS,
+ version=self.version,
+ help=_("show program's version number and exit"))
+
+ # add parent arguments and defaults
+ for parent in parents:
+ self._add_container_actions(parent)
+ try:
+ defaults = parent._defaults
+ except AttributeError:
+ pass
+ else:
+ self._defaults.update(defaults)
+
+ # =======================
+ # Pretty __repr__ methods
+ # =======================
+ def _get_kwargs(self):
+ names = [
+ 'prog',
+ 'usage',
+ 'description',
+ 'version',
+ 'formatter_class',
+ 'conflict_handler',
+ 'add_help',
+ ]
+ return [(name, getattr(self, name)) for name in names]
+
+ # ==================================
+ # Optional/Positional adding methods
+ # ==================================
+ def add_subparsers(self, **kwargs):
+ if self._subparsers is not None:
+ self.error(_('cannot have multiple subparser arguments'))
+
+ # add the parser class to the arguments if it's not present
+ kwargs.setdefault('parser_class', type(self))
+
+ if 'title' in kwargs or 'description' in kwargs:
+ title = _(kwargs.pop('title', 'subcommands'))
+ description = _(kwargs.pop('description', None))
+ self._subparsers = self.add_argument_group(title, description)
+ else:
+ self._subparsers = self._positionals
+
+ # prog defaults to the usage message of this parser, skipping
+ # optional arguments and with no "usage:" prefix
+ if kwargs.get('prog') is None:
+ formatter = self._get_formatter()
+ positionals = self._get_positional_actions()
+ groups = self._mutually_exclusive_groups
+ formatter.add_usage(self.usage, positionals, groups, '')
+ kwargs['prog'] = formatter.format_help().strip()
+
+ # create the parsers action and add it to the positionals list
+ parsers_class = self._pop_action_class(kwargs, 'parsers')
+ action = parsers_class(option_strings=[], **kwargs)
+ self._subparsers._add_action(action)
+
+ # return the created parsers action
+ return action
+
+ def _add_action(self, action):
+ if action.option_strings:
+ self._optionals._add_action(action)
+ else:
+ self._positionals._add_action(action)
+ return action
+
+ def _get_optional_actions(self):
+ return [action
+ for action in self._actions
+ if action.option_strings]
+
+ def _get_positional_actions(self):
+ return [action
+ for action in self._actions
+ if not action.option_strings]
+
+ # =====================================
+ # Command line argument parsing methods
+ # =====================================
+ def parse_args(self, args=None, namespace=None):
+ args, argv = self.parse_known_args(args, namespace)
+ if argv:
+ msg = _('unrecognized arguments: %s')
+ self.error(msg % ' '.join(argv))
+ return args
+
+ def parse_known_args(self, args=None, namespace=None):
+ # args default to the system args
+ if args is None:
+ args = _sys.argv[1:]
+
+ # default Namespace built from parser defaults
+ if namespace is None:
+ namespace = Namespace()
+
+ # add any action defaults that aren't present
+ for action in self._actions:
+ if action.dest is not SUPPRESS:
+ if not hasattr(namespace, action.dest):
+ if action.default is not SUPPRESS:
+ setattr(namespace, action.dest, action.default)
+
+ # add any parser defaults that aren't present
+ for dest in self._defaults:
+ if not hasattr(namespace, dest):
+ setattr(namespace, dest, self._defaults[dest])
+
+ # parse the arguments and exit if there are any errors
+ try:
+ namespace, args = self._parse_known_args(args, namespace)
+ if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR):
+ args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR))
+ delattr(namespace, _UNRECOGNIZED_ARGS_ATTR)
+ return namespace, args
+ except ArgumentError:
+ err = _sys.exc_info()[1]
+ self.error(str(err))
+
+ def _parse_known_args(self, arg_strings, namespace):
+ # replace arg strings that are file references
+ if self.fromfile_prefix_chars is not None:
+ arg_strings = self._read_args_from_files(arg_strings)
+
+ # map all mutually exclusive arguments to the other arguments
+ # they can't occur with
+ action_conflicts = {}
+ for mutex_group in self._mutually_exclusive_groups:
+ group_actions = mutex_group._group_actions
+ for i, mutex_action in enumerate(mutex_group._group_actions):
+ conflicts = action_conflicts.setdefault(mutex_action, [])
+ conflicts.extend(group_actions[:i])
+ conflicts.extend(group_actions[i + 1:])
+
+ # find all option indices, and determine the arg_string_pattern
+ # which has an 'O' if there is an option at an index,
+ # an 'A' if there is an argument, or a '-' if there is a '--'
+ option_string_indices = {}
+ arg_string_pattern_parts = []
+ arg_strings_iter = iter(arg_strings)
+ for i, arg_string in enumerate(arg_strings_iter):
+
+ # all args after -- are non-options
+ if arg_string == '--':
+ arg_string_pattern_parts.append('-')
+ for arg_string in arg_strings_iter:
+ arg_string_pattern_parts.append('A')
+
+ # otherwise, add the arg to the arg strings
+ # and note the index if it was an option
+ else:
+ option_tuple = self._parse_optional(arg_string)
+ if option_tuple is None:
+ pattern = 'A'
+ else:
+ option_string_indices[i] = option_tuple
+ pattern = 'O'
+ arg_string_pattern_parts.append(pattern)
+
+ # join the pieces together to form the pattern
+ arg_strings_pattern = ''.join(arg_string_pattern_parts)
+
+ # converts arg strings to the appropriate and then takes the action
+ seen_actions = set()
+ seen_non_default_actions = set()
+
+ def take_action(action, argument_strings, option_string=None):
+ seen_actions.add(action)
+ argument_values = self._get_values(action, argument_strings)
+
+ # error if this argument is not allowed with other previously
+ # seen arguments, assuming that actions that use the default
+ # value don't really count as "present"
+ if argument_values is not action.default:
+ seen_non_default_actions.add(action)
+ for conflict_action in action_conflicts.get(action, []):
+ if conflict_action in seen_non_default_actions:
+ msg = _('not allowed with argument %s')
+ action_name = _get_action_name(conflict_action)
+ raise ArgumentError(action, msg % action_name)
+
+ # take the action if we didn't receive a SUPPRESS value
+ # (e.g. from a default)
+ if argument_values is not SUPPRESS:
+ action(self, namespace, argument_values, option_string)
+
+ # function to convert arg_strings into an optional action
+ def consume_optional(start_index):
+
+ # get the optional identified at this index
+ option_tuple = option_string_indices[start_index]
+ action, option_string, explicit_arg = option_tuple
+
+ # identify additional optionals in the same arg string
+ # (e.g. -xyz is the same as -x -y -z if no args are required)
+ match_argument = self._match_argument
+ action_tuples = []
+ while True:
+
+ # if we found no optional action, skip it
+ if action is None:
+ extras.append(arg_strings[start_index])
+ return start_index + 1
+
+ # if there is an explicit argument, try to match the
+ # optional's string arguments to only this
+ if explicit_arg is not None:
+ arg_count = match_argument(action, 'A')
+
+ # if the action is a single-dash option and takes no
+ # arguments, try to parse more single-dash options out
+ # of the tail of the option string
+ chars = self.prefix_chars
+ if arg_count == 0 and option_string[1] not in chars:
+ action_tuples.append((action, [], option_string))
+ char = option_string[0]
+ option_string = char + explicit_arg[0]
+ new_explicit_arg = explicit_arg[1:] or None
+ optionals_map = self._option_string_actions
+ if option_string in optionals_map:
+ action = optionals_map[option_string]
+ explicit_arg = new_explicit_arg
+ else:
+ msg = _('ignored explicit argument %r')
+ raise ArgumentError(action, msg % explicit_arg)
+
+ # if the action expect exactly one argument, we've
+ # successfully matched the option; exit the loop
+ elif arg_count == 1:
+ stop = start_index + 1
+ args = [explicit_arg]
+ action_tuples.append((action, args, option_string))
+ break
+
+ # error if a double-dash option did not use the
+ # explicit argument
+ else:
+ msg = _('ignored explicit argument %r')
+ raise ArgumentError(action, msg % explicit_arg)
+
+ # if there is no explicit argument, try to match the
+ # optional's string arguments with the following strings
+ # if successful, exit the loop
+ else:
+ start = start_index + 1
+ selected_patterns = arg_strings_pattern[start:]
+ arg_count = match_argument(action, selected_patterns)
+ stop = start + arg_count
+ args = arg_strings[start:stop]
+ action_tuples.append((action, args, option_string))
+ break
+
+ # add the Optional to the list and return the index at which
+ # the Optional's string args stopped
+ assert action_tuples
+ for action, args, option_string in action_tuples:
+ take_action(action, args, option_string)
+ return stop
+
+ # the list of Positionals left to be parsed; this is modified
+ # by consume_positionals()
+ positionals = self._get_positional_actions()
+
+ # function to convert arg_strings into positional actions
+ def consume_positionals(start_index):
+ # match as many Positionals as possible
+ match_partial = self._match_arguments_partial
+ selected_pattern = arg_strings_pattern[start_index:]
+ arg_counts = match_partial(positionals, selected_pattern)
+
+ # slice off the appropriate arg strings for each Positional
+ # and add the Positional and its args to the list
+ for action, arg_count in zip(positionals, arg_counts):
+ args = arg_strings[start_index: start_index + arg_count]
+ start_index += arg_count
+ take_action(action, args)
+
+ # slice off the Positionals that we just parsed and return the
+ # index at which the Positionals' string args stopped
+ positionals[:] = positionals[len(arg_counts):]
+ return start_index
+
+ # consume Positionals and Optionals alternately, until we have
+ # passed the last option string
+ extras = []
+ start_index = 0
+ if option_string_indices:
+ max_option_string_index = max(option_string_indices)
+ else:
+ max_option_string_index = -1
+ while start_index <= max_option_string_index:
+
+ # consume any Positionals preceding the next option
+ next_option_string_index = min([
+ index
+ for index in option_string_indices
+ if index >= start_index])
+ if start_index != next_option_string_index:
+ positionals_end_index = consume_positionals(start_index)
+
+ # only try to parse the next optional if we didn't consume
+ # the option string during the positionals parsing
+ if positionals_end_index > start_index:
+ start_index = positionals_end_index
+ continue
+ else:
+ start_index = positionals_end_index
+
+ # if we consumed all the positionals we could and we're not
+ # at the index of an option string, there were extra arguments
+ if start_index not in option_string_indices:
+ strings = arg_strings[start_index:next_option_string_index]
+ extras.extend(strings)
+ start_index = next_option_string_index
+
+ # consume the next optional and any arguments for it
+ start_index = consume_optional(start_index)
+
+ # consume any positionals following the last Optional
+ stop_index = consume_positionals(start_index)
+
+ # if we didn't consume all the argument strings, there were extras
+ extras.extend(arg_strings[stop_index:])
+
+ # if we didn't use all the Positional objects, there were too few
+ # arg strings supplied.
+ if positionals:
+ self.error(_('too few arguments'))
+
+ # make sure all required actions were present, and convert defaults.
+ for action in self._actions:
+ if action not in seen_actions:
+ if action.required:
+ name = _get_action_name(action)
+ self.error(_('argument %s is required') % name)
+ else:
+ # Convert action default now instead of doing it before
+ # parsing arguments to avoid calling convert functions
+ # twice (which may fail) if the argument was given, but
+ # only if it was defined already in the namespace
+ if (action.default is not None and
+ isinstance(action.default, basestring) and
+ hasattr(namespace, action.dest) and
+ action.default is getattr(namespace, action.dest)):
+ setattr(namespace, action.dest,
+ self._get_value(action, action.default))
+
+ # make sure all required groups had one option present
+ for group in self._mutually_exclusive_groups:
+ if group.required:
+ for action in group._group_actions:
+ if action in seen_non_default_actions:
+ break
+
+ # if no actions were used, report the error
+ else:
+ names = [_get_action_name(action)
+ for action in group._group_actions
+ if action.help is not SUPPRESS]
+ msg = _('one of the arguments %s is required')
+ self.error(msg % ' '.join(names))
+
+ # return the updated namespace and the extra arguments
+ return namespace, extras
+
+ def _read_args_from_files(self, arg_strings):
+ # expand arguments referencing files
+ new_arg_strings = []
+ for arg_string in arg_strings:
+
+ # for regular arguments, just add them back into the list
+ if arg_string[0] not in self.fromfile_prefix_chars:
+ new_arg_strings.append(arg_string)
+
+ # replace arguments referencing files with the file content
+ else:
+ try:
+ args_file = open(arg_string[1:])
+ try:
+ arg_strings = []
+ for arg_line in args_file.read().splitlines():
+ for arg in self.convert_arg_line_to_args(arg_line):
+ arg_strings.append(arg)
+ arg_strings = self._read_args_from_files(arg_strings)
+ new_arg_strings.extend(arg_strings)
+ finally:
+ args_file.close()
+ except IOError:
+ err = _sys.exc_info()[1]
+ self.error(str(err))
+
+ # return the modified argument list
+ return new_arg_strings
+
+ def convert_arg_line_to_args(self, arg_line):
+ return [arg_line]
+
+ def _match_argument(self, action, arg_strings_pattern):
+ # match the pattern for this action to the arg strings
+ nargs_pattern = self._get_nargs_pattern(action)
+ match = _re.match(nargs_pattern, arg_strings_pattern)
+
+ # raise an exception if we weren't able to find a match
+ if match is None:
+ nargs_errors = {
+ None: _('expected one argument'),
+ OPTIONAL: _('expected at most one argument'),
+ ONE_OR_MORE: _('expected at least one argument'),
+ }
+ default = _('expected %s argument(s)') % action.nargs
+ msg = nargs_errors.get(action.nargs, default)
+ raise ArgumentError(action, msg)
+
+ # return the number of arguments matched
+ return len(match.group(1))
+
+ def _match_arguments_partial(self, actions, arg_strings_pattern):
+ # progressively shorten the actions list by slicing off the
+ # final actions until we find a match
+ result = []
+ for i in range(len(actions), 0, -1):
+ actions_slice = actions[:i]
+ pattern = ''.join([self._get_nargs_pattern(action)
+ for action in actions_slice])
+ match = _re.match(pattern, arg_strings_pattern)
+ if match is not None:
+ result.extend([len(string) for string in match.groups()])
+ break
+
+ # return the list of arg string counts
+ return result
+
+ def _parse_optional(self, arg_string):
+ # if it's an empty string, it was meant to be a positional
+ if not arg_string:
+ return None
+
+ # if it doesn't start with a prefix, it was meant to be positional
+ if not arg_string[0] in self.prefix_chars:
+ return None
+
+ # if the option string is present in the parser, return the action
+ if arg_string in self._option_string_actions:
+ action = self._option_string_actions[arg_string]
+ return action, arg_string, None
+
+ # if it's just a single character, it was meant to be positional
+ if len(arg_string) == 1:
+ return None
+
+ # if the option string before the "=" is present, return the action
+ if '=' in arg_string:
+ option_string, explicit_arg = arg_string.split('=', 1)
+ if option_string in self._option_string_actions:
+ action = self._option_string_actions[option_string]
+ return action, option_string, explicit_arg
+
+ # search through all possible prefixes of the option string
+ # and all actions in the parser for possible interpretations
+ option_tuples = self._get_option_tuples(arg_string)
+
+ # if multiple actions match, the option string was ambiguous
+ if len(option_tuples) > 1:
+ options = ', '.join([option_string
+ for action, option_string, explicit_arg in option_tuples])
+ tup = arg_string, options
+ self.error(_('ambiguous option: %s could match %s') % tup)
+
+ # if exactly one action matched, this segmentation is good,
+ # so return the parsed action
+ elif len(option_tuples) == 1:
+ option_tuple, = option_tuples
+ return option_tuple
+
+ # if it was not found as an option, but it looks like a negative
+ # number, it was meant to be positional
+ # unless there are negative-number-like options
+ if self._negative_number_matcher.match(arg_string):
+ if not self._has_negative_number_optionals:
+ return None
+
+ # if it contains a space, it was meant to be a positional
+ if ' ' in arg_string:
+ return None
+
+ # it was meant to be an optional but there is no such option
+ # in this parser (though it might be a valid option in a subparser)
+ return None, arg_string, None
+
+ def _get_option_tuples(self, option_string):
+ result = []
+
+ # option strings starting with two prefix characters are only
+ # split at the '='
+ chars = self.prefix_chars
+ if option_string[0] in chars and option_string[1] in chars:
+ if '=' in option_string:
+ option_prefix, explicit_arg = option_string.split('=', 1)
+ else:
+ option_prefix = option_string
+ explicit_arg = None
+ for option_string in self._option_string_actions:
+ if option_string.startswith(option_prefix):
+ action = self._option_string_actions[option_string]
+ tup = action, option_string, explicit_arg
+ result.append(tup)
+
+ # single character options can be concatenated with their arguments
+ # but multiple character options always have to have their argument
+ # separate
+ elif option_string[0] in chars and option_string[1] not in chars:
+ option_prefix = option_string
+ explicit_arg = None
+ short_option_prefix = option_string[:2]
+ short_explicit_arg = option_string[2:]
+
+ for option_string in self._option_string_actions:
+ if option_string == short_option_prefix:
+ action = self._option_string_actions[option_string]
+ tup = action, option_string, short_explicit_arg
+ result.append(tup)
+ elif option_string.startswith(option_prefix):
+ action = self._option_string_actions[option_string]
+ tup = action, option_string, explicit_arg
+ result.append(tup)
+
+ # shouldn't ever get here
+ else:
+ self.error(_('unexpected option string: %s') % option_string)
+
+ # return the collected option tuples
+ return result
+
+ def _get_nargs_pattern(self, action):
+ # in all examples below, we have to allow for '--' args
+ # which are represented as '-' in the pattern
+ nargs = action.nargs
+
+ # the default (None) is assumed to be a single argument
+ if nargs is None:
+ nargs_pattern = '(-*A-*)'
+
+ # allow zero or one arguments
+ elif nargs == OPTIONAL:
+ nargs_pattern = '(-*A?-*)'
+
+ # allow zero or more arguments
+ elif nargs == ZERO_OR_MORE:
+ nargs_pattern = '(-*[A-]*)'
+
+ # allow one or more arguments
+ elif nargs == ONE_OR_MORE:
+ nargs_pattern = '(-*A[A-]*)'
+
+ # allow any number of options or arguments
+ elif nargs == REMAINDER:
+ nargs_pattern = '([-AO]*)'
+
+ # allow one argument followed by any number of options or arguments
+ elif nargs == PARSER:
+ nargs_pattern = '(-*A[-AO]*)'
+
+ # all others should be integers
+ else:
+ nargs_pattern = '(-*%s-*)' % '-*'.join('A' * nargs)
+
+ # if this is an optional action, -- is not allowed
+ if action.option_strings:
+ nargs_pattern = nargs_pattern.replace('-*', '')
+ nargs_pattern = nargs_pattern.replace('-', '')
+
+ # return the pattern
+ return nargs_pattern
+
+ # ========================
+ # Value conversion methods
+ # ========================
+ def _get_values(self, action, arg_strings):
+ # for everything but PARSER args, strip out '--'
+ if action.nargs not in [PARSER, REMAINDER]:
+ arg_strings = [s for s in arg_strings if s != '--']
+
+ # optional argument produces a default when not present
+ if not arg_strings and action.nargs == OPTIONAL:
+ if action.option_strings:
+ value = action.const
+ else:
+ value = action.default
+ if isinstance(value, basestring):
+ value = self._get_value(action, value)
+ self._check_value(action, value)
+
+ # when nargs='*' on a positional, if there were no command-line
+ # args, use the default if it is anything other than None
+ elif (not arg_strings and action.nargs == ZERO_OR_MORE and
+ not action.option_strings):
+ if action.default is not None:
+ value = action.default
+ else:
+ value = arg_strings
+ self._check_value(action, value)
+
+ # single argument or optional argument produces a single value
+ elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]:
+ arg_string, = arg_strings
+ value = self._get_value(action, arg_string)
+ self._check_value(action, value)
+
+ # REMAINDER arguments convert all values, checking none
+ elif action.nargs == REMAINDER:
+ value = [self._get_value(action, v) for v in arg_strings]
+
+ # PARSER arguments convert all values, but check only the first
+ elif action.nargs == PARSER:
+ value = [self._get_value(action, v) for v in arg_strings]
+ self._check_value(action, value[0])
+
+ # all other types of nargs produce a list
+ else:
+ value = [self._get_value(action, v) for v in arg_strings]
+ for v in value:
+ self._check_value(action, v)
+
+ # return the converted value
+ return value
+
+ def _get_value(self, action, arg_string):
+ type_func = self._registry_get('type', action.type, action.type)
+ if not _callable(type_func):
+ msg = _('%r is not callable')
+ raise ArgumentError(action, msg % type_func)
+
+ # convert the value to the appropriate type
+ try:
+ result = type_func(arg_string)
+
+ # ArgumentTypeErrors indicate errors
+ except ArgumentTypeError:
+ name = getattr(action.type, '__name__', repr(action.type))
+ msg = str(_sys.exc_info()[1])
+ raise ArgumentError(action, msg)
+
+ # TypeErrors or ValueErrors also indicate errors
+ except (TypeError, ValueError):
+ name = getattr(action.type, '__name__', repr(action.type))
+ msg = _('invalid %s value: %r')
+ raise ArgumentError(action, msg % (name, arg_string))
+
+ # return the converted value
+ return result
+
+ def _check_value(self, action, value):
+ # converted value must be one of the choices (if specified)
+ if action.choices is not None and value not in action.choices:
+ tup = value, ', '.join(map(repr, action.choices))
+ msg = _('invalid choice: %r (choose from %s)') % tup
+ raise ArgumentError(action, msg)
+
+ # =======================
+ # Help-formatting methods
+ # =======================
+ def format_usage(self):
+ formatter = self._get_formatter()
+ formatter.add_usage(self.usage, self._actions,
+ self._mutually_exclusive_groups)
+ return formatter.format_help()
+
+ def format_help(self):
+ formatter = self._get_formatter()
+
+ # usage
+ formatter.add_usage(self.usage, self._actions,
+ self._mutually_exclusive_groups)
+
+ # description
+ formatter.add_text(self.description)
+
+ # positionals, optionals and user-defined groups
+ for action_group in self._action_groups:
+ formatter.start_section(action_group.title)
+ formatter.add_text(action_group.description)
+ formatter.add_arguments(action_group._group_actions)
+ formatter.end_section()
+
+ # epilog
+ formatter.add_text(self.epilog)
+
+ # determine help from format above
+ return formatter.format_help()
+
+ def format_version(self):
+ import warnings
+ warnings.warn(
+ 'The format_version method is deprecated -- the "version" '
+ 'argument to ArgumentParser is no longer supported.',
+ DeprecationWarning)
+ formatter = self._get_formatter()
+ formatter.add_text(self.version)
+ return formatter.format_help()
+
+ def _get_formatter(self):
+ return self.formatter_class(prog=self.prog)
+
+ # =====================
+ # Help-printing methods
+ # =====================
+ def print_usage(self, file=None):
+ if file is None:
+ file = _sys.stdout
+ self._print_message(self.format_usage(), file)
+
+ def print_help(self, file=None):
+ if file is None:
+ file = _sys.stdout
+ self._print_message(self.format_help(), file)
+
+ def print_version(self, file=None):
+ import warnings
+ warnings.warn(
+ 'The print_version method is deprecated -- the "version" '
+ 'argument to ArgumentParser is no longer supported.',
+ DeprecationWarning)
+ self._print_message(self.format_version(), file)
+
+ def _print_message(self, message, file=None):
+ if message:
+ if file is None:
+ file = _sys.stderr
+ file.write(message)
+
+ # ===============
+ # Exiting methods
+ # ===============
+ def exit(self, status=0, message=None):
+ if message:
+ self._print_message(message, _sys.stderr)
+ _sys.exit(status)
+
+ def error(self, message):
+ """error(message: string)
+
+ Prints a usage message incorporating the message to stderr and
+ exits.
+
+ If you override this in a subclass, it should not return -- it
+ should either exit or raise an exception.
+ """
+ self.print_usage(_sys.stderr)
+ self.exit(2, _('%s: error: %s\n') % (self.prog, message))
diff --git a/ui/keycodemapdb/tools/keymap-gen b/ui/keycodemapdb/tools/keymap-gen
new file mode 100755
index 000000000..a374eb255
--- /dev/null
+++ b/ui/keycodemapdb/tools/keymap-gen
@@ -0,0 +1,977 @@
+#!/usr/bin/python
+# -*- python -*-
+#
+# Keycode Map Generator
+#
+# Copyright (C) 2009-2017 Red Hat, Inc.
+#
+# This file is dual license under the terms of the GPLv2 or later
+# and 3-clause BSD licenses.
+#
+
+# Requires >= 2.6
+from __future__ import print_function
+
+import csv
+try:
+ import argparse
+except:
+ import os, sys
+ sys.path.append(os.path.join(os.path.dirname(__file__), "../thirdparty"))
+ import argparse
+import hashlib
+import os
+import time
+import sys
+
+class Database:
+
+ # Linux: linux/input.h
+ MAP_LINUX = "linux"
+
+ # OS-X: Carbon/HIToolbox/Events.h
+ MAP_OSX = "osx"
+
+ # AT Set 1: linux/drivers/input/keyboard/atkbd.c
+ # (atkbd_set2_keycode + atkbd_unxlate_table)
+ MAP_ATSET1 = "atset1"
+
+ # AT Set 2: linux/drivers/input/keyboard/atkbd.c
+ # (atkbd_set2_keycode)
+ MAP_ATSET2 = "atset2"
+
+ # AT Set 3: linux/drivers/input/keyboard/atkbd.c
+ # (atkbd_set3_keycode)
+ MAP_ATSET3 = "atset3"
+
+ # Linux RAW: linux/drivers/char/keyboard.c (x86_keycodes)
+ MAP_XTKBD = "xtkbd"
+
+ # USB HID: linux/drivers/hid/usbhid/usbkbd.c (usb_kbd_keycode)
+ MAP_USB = "usb"
+
+ # Win32: mingw32/winuser.h
+ MAP_WIN32 = "win32"
+
+ # XWin XT: xorg-server/hw/xwin/{winkeybd.c,winkeynames.h}
+ # (xt + manually transcribed)
+ MAP_XWINXT = "xwinxt"
+
+ # X11: http://cgit.freedesktop.org/xorg/proto/x11proto/plain/keysymdef.h
+ MAP_X11 = "x11"
+
+ # XKBD XT: xf86-input-keyboard/src/at_scancode.c
+ # (xt + manually transcribed)
+ MAP_XKBDXT = "xkbdxt"
+
+ # Xorg with evdev: linux + an offset
+ MAP_XORGEVDEV = "xorgevdev"
+
+ # Xorg with kbd: xkbdxt + an offset
+ MAP_XORGKBD = "xorgkbd"
+
+ # Xorg with OS-X: osx + an offset
+ MAP_XORGXQUARTZ = "xorgxquartz"
+
+ # Xorg + Cygwin: xwinxt + an offset
+ MAP_XORGXWIN = "xorgxwin"
+
+ # QEMU key numbers: xtkbd + special re-encoding of high bit
+ MAP_QNUM = "qnum"
+
+ # HTML codes
+ MAP_HTML = "html"
+
+ # XKB key names
+ MAP_XKB = "xkb"
+
+ # QEMU keycodes
+ MAP_QCODE = "qcode"
+
+ # Sun / Sparc scan codes
+ # Reference: "SPARC International Keyboard Spec 1", page 7 "US scan set"
+ MAP_SUN = "sun"
+
+ # Apple Desktop Bus
+ # Reference: http://www.archive.org/stream/apple-guide-macintosh-family-hardware/Apple_Guide_to_the_Macintosh_Family_Hardware_2e#page/n345/mode/2up
+ MAP_ADB = "adb"
+
+ MAP_LIST = (
+ MAP_LINUX,
+ MAP_OSX,
+ MAP_ATSET1,
+ MAP_ATSET2,
+ MAP_ATSET3,
+ MAP_USB,
+ MAP_WIN32,
+ MAP_XWINXT,
+ MAP_XKBDXT,
+ MAP_X11,
+ MAP_HTML,
+ MAP_XKB,
+ MAP_QCODE,
+ MAP_SUN,
+ MAP_ADB,
+
+ # These are derived from maps above
+ MAP_XTKBD,
+ MAP_XORGEVDEV,
+ MAP_XORGKBD,
+ MAP_XORGXQUARTZ,
+ MAP_XORGXWIN,
+ MAP_QNUM,
+ )
+
+ CODE_COLUMNS = {
+ MAP_LINUX: 1,
+ MAP_OSX: 3,
+ MAP_ATSET1: 4,
+ MAP_ATSET2: 5,
+ MAP_ATSET3: 6,
+ MAP_USB: 7,
+ MAP_WIN32: 9,
+ MAP_XWINXT: 10,
+ MAP_XKBDXT: 11,
+ MAP_X11: 13,
+ MAP_HTML: 14,
+ MAP_XKB: 15,
+ MAP_SUN: 17,
+ MAP_ADB: 18,
+ }
+
+ ENUM_COLUMNS = {
+ MAP_QCODE: 14,
+ }
+
+ NAME_COLUMNS = {
+ MAP_LINUX: 0,
+ MAP_OSX: 2,
+ MAP_WIN32: 8,
+ MAP_X11: 12,
+ MAP_HTML: 14,
+ MAP_XKB: 15,
+ MAP_QCODE: 16,
+ }
+
+ ENUM_BOUND = {
+ MAP_QCODE: "Q_KEY_CODE__MAX",
+ }
+
+ def __init__(self):
+
+ self.mapto = {}
+ self.mapfrom = {}
+ self.mapname = {}
+ self.mapchecksum = None
+
+ for name in self.MAP_LIST:
+ # Key is a MAP_LINUX, value is a MAP_XXX
+ self.mapto[name] = {}
+ # key is a MAP_XXX, value is a MAP_LINUX
+ self.mapfrom[name] = {}
+
+ for name in self.NAME_COLUMNS.keys():
+ # key is a MAP_LINUX, value is a string
+ self.mapname[name] = {}
+
+ def _generate_checksum(self, filename):
+ hash = hashlib.sha256()
+ with open(filename, "rb") as f:
+ for chunk in iter(lambda: f.read(4096), b""):
+ hash.update(chunk)
+ self.mapchecksum = hash.hexdigest()
+
+ def load(self, filename):
+ self._generate_checksum(filename)
+
+ with open(filename, 'r') as f:
+ reader = csv.reader(f)
+
+ first = True
+
+ for row in reader:
+ # Discard column headings
+ if first:
+ first = False
+ continue
+
+ # We special case MAP_LINUX since that is out
+ # master via which all other mappings are done
+ linux = self.load_linux(row)
+
+ # Now load all the remaining master data values
+ self.load_data(row, linux)
+
+ # Then load all the keycode names
+ self.load_names(row, linux)
+
+ # Finally calculate derived key maps
+ self.derive_data(row, linux)
+
+ def load_linux(self, row):
+ col = self.CODE_COLUMNS[self.MAP_LINUX]
+ linux = row[col]
+
+ if linux.startswith("0x"):
+ linux = int(linux, 16)
+ else:
+ linux = int(linux, 10)
+
+ self.mapto[self.MAP_LINUX][linux] = linux
+ self.mapfrom[self.MAP_LINUX][linux] = linux
+
+ return linux
+
+
+ def load_data(self, row, linux):
+ for mapname in self.CODE_COLUMNS:
+ if mapname == self.MAP_LINUX:
+ continue
+
+ col = self.CODE_COLUMNS[mapname]
+ val = row[col]
+
+ if val == "":
+ continue
+
+ if val.startswith("0x"):
+ val = int(val, 16)
+ elif val.isdigit():
+ val = int(val, 10)
+
+ self.mapto[mapname][linux] = val
+ self.mapfrom[mapname][val] = linux
+
+ def load_names(self, row, linux):
+ for mapname in self.NAME_COLUMNS:
+ col = self.NAME_COLUMNS[mapname]
+ val = row[col]
+
+ if val == "":
+ continue
+
+ self.mapname[mapname][linux] = val
+
+
+ def derive_data(self, row, linux):
+ # Linux RAW is XT scan codes with special encoding of the
+ # 0xe0 scan codes
+ if linux in self.mapto[self.MAP_ATSET1]:
+ at1 = self.mapto[self.MAP_ATSET1][linux]
+ if at1 > 0x7f:
+ assert((at1 & ~0x7f) == 0xe000)
+ xtkbd = 0x100 | (at1 & 0x7f)
+ else:
+ xtkbd = at1
+ self.mapto[self.MAP_XTKBD][linux] = xtkbd
+ self.mapfrom[self.MAP_XTKBD][xtkbd] = linux
+
+ # Xorg KBD is XKBD XT offset by 8
+ if linux in self.mapto[self.MAP_XKBDXT]:
+ xorgkbd = self.mapto[self.MAP_XKBDXT][linux] + 8
+ self.mapto[self.MAP_XORGKBD][linux] = xorgkbd
+ self.mapfrom[self.MAP_XORGKBD][xorgkbd] = linux
+
+ # Xorg evdev is Linux offset by 8
+ self.mapto[self.MAP_XORGEVDEV][linux] = linux + 8
+ self.mapfrom[self.MAP_XORGEVDEV][linux + 8] = linux
+
+ # Xorg XQuartx is OS-X offset by 8
+ if linux in self.mapto[self.MAP_OSX]:
+ xorgxquartz = self.mapto[self.MAP_OSX][linux] + 8
+ self.mapto[self.MAP_XORGXQUARTZ][linux] = xorgxquartz
+ self.mapfrom[self.MAP_XORGXQUARTZ][xorgxquartz] = linux
+
+ # Xorg Xwin (aka Cygwin) is XWin XT offset by 8
+ if linux in self.mapto[self.MAP_XWINXT]:
+ xorgxwin = self.mapto[self.MAP_XWINXT][linux] + 8
+ self.mapto[self.MAP_XORGXWIN][linux] = xorgxwin
+ self.mapfrom[self.MAP_XORGXWIN][xorgxwin] = linux
+
+ # QNUM keycodes are XT scan codes with a slightly
+ # different encoding of 0xe0 scan codes
+ if linux in self.mapto[self.MAP_ATSET1]:
+ at1 = self.mapto[self.MAP_ATSET1][linux]
+ if at1 > 0x7f:
+ assert((at1 & ~0x7f) == 0xe000)
+ qnum = 0x80 | (at1 & 0x7f)
+ else:
+ qnum = at1
+ self.mapto[self.MAP_QNUM][linux] = qnum
+ self.mapfrom[self.MAP_QNUM][qnum] = linux
+
+ # Hack for compatibility with previous mistakes in handling
+ # Print/SysRq. The preferred qnum for Print/SysRq is 0x54,
+ # but QEMU previously allowed 0xb7 too
+ if qnum == 0x54:
+ self.mapfrom[self.MAP_QNUM][0xb7] = self.mapfrom[self.MAP_QNUM][0x54]
+
+ if linux in self.mapname[self.MAP_QCODE]:
+ qcodeenum = self.mapname[self.MAP_QCODE][linux]
+ qcodeenum = "Q_KEY_CODE_" + qcodeenum.upper()
+ self.mapto[self.MAP_QCODE][linux] = qcodeenum
+ self.mapfrom[self.MAP_QCODE][qcodeenum] = linux
+
+class LanguageGenerator(object):
+
+ def _boilerplate(self, lines):
+ raise NotImplementedError()
+
+ def generate_header(self, database, args):
+ sde = os.getenv("SOURCE_DATE_EPOCH")
+ if sde:
+ today = time.strftime("%Y-%m-%d %H:%M", time.gmtime(int(sde)))
+ else:
+ today = time.strftime("%Y-%m-%d %H:%M")
+ self._boilerplate([
+ "This file is auto-generated from keymaps.csv on %s" % today,
+ "Database checksum sha256(%s)" % database.mapchecksum,
+ "To re-generate, run:",
+ " %s" % args,
+ ])
+
+class LanguageSrcGenerator(LanguageGenerator):
+
+ TYPE_INT = "integer"
+ TYPE_STRING = "string"
+ TYPE_ENUM = "enum"
+
+ def _array_start(self, varname, length, defvalue, fromtype, totype):
+ raise NotImplementedError()
+
+ def _array_end(self, fromtype, totype):
+ raise NotImplementedError()
+
+ def _array_entry(self, index, value, comment, fromtype, totype):
+ raise NotImplementedError()
+
+ def generate_code_map(self, varname, database, frommapname, tomapname):
+ if frommapname not in database.mapfrom:
+ raise Exception("Unknown map %s, expected one of %s" % (
+ frommapname, ", ".join(database.mapfrom.keys())))
+ if tomapname not in database.mapto:
+ raise Exception("Unknown map %s, expected one of %s" % (
+ tomapname, ", ".join(database.mapto.keys())))
+
+ tolinux = database.mapfrom[frommapname]
+ fromlinux = database.mapto[tomapname]
+
+ if varname is None:
+ varname = "code_map_%s_to_%s" % (frommapname, tomapname)
+
+ if frommapname in database.ENUM_COLUMNS:
+ fromtype = self.TYPE_ENUM
+ elif type(list(tolinux.keys())[0]) == str:
+ fromtype = self.TYPE_STRING
+ else:
+ fromtype = self.TYPE_INT
+
+ if tomapname in database.ENUM_COLUMNS:
+ totype = self.TYPE_ENUM
+ elif type(list(fromlinux.values())[0]) == str:
+ totype = self.TYPE_STRING
+ else:
+ totype = self.TYPE_INT
+
+ keys = list(tolinux.keys())
+ keys.sort()
+ if fromtype == self.TYPE_INT:
+ keys = range(keys[-1] + 1)
+
+ if fromtype == self.TYPE_ENUM:
+ keymax = database.ENUM_BOUND[frommapname]
+ else:
+ keymax = len(keys)
+
+ defvalue = fromlinux.get(0, None)
+ if fromtype == self.TYPE_ENUM:
+ self._array_start(varname, keymax, defvalue, fromtype, totype)
+ else:
+ self._array_start(varname, keymax, None, fromtype, totype)
+
+ for src in keys:
+ linux = tolinux.get(src, None)
+ if linux is None:
+ dst = None
+ else:
+ dst = fromlinux.get(linux, defvalue)
+
+ comment = "%s -> %s -> %s" % (self._label(database, frommapname, src, linux),
+ self._label(database, Database.MAP_LINUX, linux, linux),
+ self._label(database, tomapname, dst, linux))
+ self._array_entry(src, dst, comment, fromtype, totype)
+ self._array_end(fromtype, totype)
+
+ def generate_code_table(self, varname, database, mapname):
+ if mapname not in database.mapto:
+ raise Exception("Unknown map %s, expected one of %s" % (
+ mapname, ", ".join(database.mapto.keys())))
+
+ keys = list(database.mapto[Database.MAP_LINUX].keys())
+ keys.sort()
+ names = [database.mapname[Database.MAP_LINUX].get(key, "unnamed") for key in keys]
+
+ if varname is None:
+ varname = "code_table_%s" % mapname
+
+ if mapname in database.ENUM_COLUMNS:
+ totype = self.TYPE_ENUM
+ elif type(list(database.mapto[mapname].values())[0]) == str:
+ totype = self.TYPE_STRING
+ else:
+ totype = self.TYPE_INT
+
+ self._array_start(varname, len(keys), None, self.TYPE_INT, totype)
+
+ defvalue = database.mapto[mapname].get(0, None)
+ for i in range(len(keys)):
+ key = keys[i]
+ dst = database.mapto[mapname].get(key, defvalue)
+ self._array_entry(i, dst, names[i], self.TYPE_INT, totype)
+
+ self._array_end(self.TYPE_INT, totype)
+
+ def generate_name_map(self, varname, database, frommapname, tomapname):
+ if frommapname not in database.mapfrom:
+ raise Exception("Unknown map %s, expected one of %s" % (
+ frommapname, ", ".join(database.mapfrom.keys())))
+ if tomapname not in database.mapname:
+ raise Exception("Unknown map %s, expected one of %s" % (
+ tomapname, ", ".join(database.mapname.keys())))
+
+ tolinux = database.mapfrom[frommapname]
+ fromlinux = database.mapname[tomapname]
+
+ if varname is None:
+ varname = "name_map_%s_to_%s" % (frommapname, tomapname)
+
+ keys = list(tolinux.keys())
+ keys.sort()
+ if type(keys[0]) == int:
+ keys = range(keys[-1] + 1)
+
+ if type(keys[0]) == int:
+ fromtype = self.TYPE_INT
+ else:
+ fromtype = self.TYPE_STRING
+
+ self._array_start(varname, len(keys), None, fromtype, self.TYPE_STRING)
+
+ for src in keys:
+ linux = tolinux.get(src, None)
+ if linux is None:
+ dst = None
+ else:
+ dst = fromlinux.get(linux, None)
+
+ comment = "%s -> %s -> %s" % (self._label(database, frommapname, src, linux),
+ self._label(database, Database.MAP_LINUX, linux, linux),
+ self._label(database, tomapname, dst, linux))
+ self._array_entry(src, dst, comment, fromtype, self.TYPE_STRING)
+ self._array_end(fromtype, self.TYPE_STRING)
+
+ def generate_name_table(self, varname, database, mapname):
+ if mapname not in database.mapname:
+ raise Exception("Unknown map %s, expected one of %s" % (
+ mapname, ", ".join(database.mapname.keys())))
+
+ keys = list(database.mapto[Database.MAP_LINUX].keys())
+ keys.sort()
+ names = [database.mapname[Database.MAP_LINUX].get(key, "unnamed") for key in keys]
+
+ if varname is None:
+ varname = "name_table_%s" % mapname
+
+ self._array_start(varname, len(keys), None, self.TYPE_INT, self.TYPE_STRING)
+
+ for i in range(len(keys)):
+ key = keys[i]
+ dst = database.mapname[mapname].get(key, None)
+ self._array_entry(i, dst, names[i], self.TYPE_INT, self.TYPE_STRING)
+
+ self._array_end(self.TYPE_INT, self.TYPE_STRING)
+
+ def _label(self, database, mapname, val, linux):
+ if mapname in database.mapname:
+ return "%s:%s (%s)" % (mapname, val, database.mapname[mapname].get(linux, "unnamed"))
+ else:
+ return "%s:%s" % (mapname, val)
+
+class LanguageDocGenerator(LanguageGenerator):
+
+ def _array_start_name_doc(self, varname, namemap):
+ raise NotImplementedError()
+
+ def _array_start_code_doc(self, varname, namemap, codemap):
+ raise NotImplementedError()
+
+ def _array_end(self):
+ raise NotImplementedError()
+
+ def _array_name_entry(self, value, name):
+ raise NotImplementedError()
+
+ def _array_code_entry(self, value, name):
+ raise NotImplementedError()
+
+ def generate_name_docs(self, varname, database, mapname):
+ if mapname not in database.mapname:
+ raise Exception("Unknown map %s, expected one of %s" % (
+ mapname, ", ".join(database.mapname.keys())))
+
+ keys = list(database.mapto[Database.MAP_LINUX].keys())
+ keys.sort()
+ names = [database.mapname[Database.MAP_LINUX].get(key, "unnamed") for key in keys]
+
+ if varname is None:
+ varname = mapname
+
+ self._array_start_name_doc(varname, mapname)
+
+ for i in range(len(keys)):
+ key = keys[i]
+ dst = database.mapname[mapname].get(key, None)
+ self._array_name_entry(key, dst)
+
+ self._array_end()
+
+
+ def generate_code_docs(self, varname, database, mapname):
+ if mapname not in database.mapfrom:
+ raise Exception("Unknown map %s, expected one of %s" % (
+ mapname, ", ".join(database.mapfrom.keys())))
+
+ tolinux = database.mapfrom[mapname]
+ keys = list(tolinux.keys())
+ keys.sort()
+ if mapname in database.mapname:
+ names = database.mapname[mapname]
+ namemap = mapname
+ else:
+ names = database.mapname[Database.MAP_LINUX]
+ namemap = Database.MAP_LINUX
+
+ if varname is None:
+ varname = mapname
+
+ self._array_start_code_doc(varname, mapname, namemap)
+
+ for i in range(len(keys)):
+ key = keys[i]
+ self._array_code_entry(key, names.get(tolinux[key], "unnamed"))
+
+ self._array_end()
+
+class CLanguageGenerator(LanguageSrcGenerator):
+
+ def __init__(self, inttypename, strtypename, lentypename):
+ self.inttypename = inttypename
+ self.strtypename = strtypename
+ self.lentypename = lentypename
+
+ def _boilerplate(self, lines):
+ print("/*")
+ for line in lines:
+ print(" * %s" % line)
+ print("*/")
+
+ def _array_start(self, varname, length, defvalue, fromtype, totype):
+ self._varname = varname;
+ totypename = self.strtypename if totype == self.TYPE_STRING else self.inttypename
+ if fromtype in (self.TYPE_INT, self.TYPE_ENUM):
+ if type(length) == str:
+ print("const %s %s[%s] = {" % (totypename, varname, length))
+ else:
+ print("const %s %s[%d] = {" % (totypename, varname, length))
+ else:
+ print("const struct _%s {" % varname)
+ print(" const %s from;" % self.strtypename)
+ print(" const %s to;" % totypename)
+ print("} %s[] = {" % varname)
+
+ if defvalue != None:
+ if totype == self.TYPE_ENUM:
+ if type(length) == str:
+ print(" [0 ... %s-1] = %s," % (length, defvalue))
+ else:
+ print(" [0 ... 0x%x-1] = %s," % (length, defvalue))
+ else:
+ if type(length) == str:
+ print(" [0 ... %s-1] = 0x%x," % (length, defvalue))
+ else:
+ print(" [0 ... 0x%x-1] = 0x%x," % (length, defvalue))
+
+ def _array_end(self, fromtype, totype):
+ print("};")
+ print("const %s %s_len = sizeof(%s)/sizeof(%s[0]);" %
+ (self.lentypename, self._varname, self._varname, self._varname))
+
+ def _array_entry(self, index, value, comment, fromtype, totype):
+ if value is None:
+ return
+ if fromtype == self.TYPE_INT:
+ indexfmt = "0x%x"
+ elif fromtype == self.TYPE_ENUM:
+ indexfmt = "%s"
+ else:
+ indexfmt = "\"%s\""
+
+ if totype == self.TYPE_INT:
+ valuefmt = "0x%x"
+ elif totype == self.TYPE_ENUM:
+ valuefmt = "%s"
+ else:
+ valuefmt = "\"%s\""
+
+ if fromtype != self.TYPE_STRING:
+ print((" [" + indexfmt + "] = " + valuefmt + ", /* %s */") % (index, value, comment))
+ else:
+ print((" {" + indexfmt + ", " + valuefmt + "}, /* %s */") % (index, value, comment))
+
+class CppLanguageGenerator(CLanguageGenerator):
+
+ def _array_start(self, varname, length, defvalue, fromtype, totype):
+ if fromtype == self.TYPE_ENUM:
+ raise NotImplementedError("Enums not supported as source in C++ generator")
+ totypename = "const " + self.strtypename if totype == self.TYPE_STRING else self.inttypename
+ if fromtype == self.TYPE_INT:
+ print("#include <vector>")
+ print("const std::vector<%s> %s = {" % (totypename, varname))
+ else:
+ print("#include <map>")
+ print("#include <string>")
+ print("const std::map<const std::string, %s> %s = {" % (totypename, varname))
+
+ def _array_end(self, fromtype, totype):
+ print("};")
+
+ # designated initializers not available in C++
+ def _array_entry(self, index, value, comment, fromtype, totype):
+ if fromtype == self.TYPE_STRING:
+ return super(CppLanguageGenerator, self)._array_entry(index, value, comment, fromtype, totype)
+
+ if value is None:
+ print(" 0, /* %s */" % comment)
+ elif totype == self.TYPE_INT:
+ print(" 0x%x, /* %s */" % (value, comment))
+ elif totype == self.TYPE_ENUM:
+ print(" %s, /* %s */" % (value, comment))
+ else:
+ print(" \"%s\", /* %s */" % (value, comment))
+
+class StdCLanguageGenerator(CLanguageGenerator):
+
+ def __init__(self):
+ super(StdCLanguageGenerator, self).__init__("unsigned short", "char *", "unsigned int")
+
+class StdCppLanguageGenerator(CppLanguageGenerator):
+
+ def __init__(self):
+ super(StdCppLanguageGenerator, self).__init__("unsigned short", "char *", "unsigned int")
+
+class GLib2LanguageGenerator(CLanguageGenerator):
+
+ def __init__(self):
+ super(GLib2LanguageGenerator, self).__init__("guint16", "gchar *", "guint")
+
+class PythonLanguageGenerator(LanguageSrcGenerator):
+
+ def _boilerplate(self, lines):
+ print("#")
+ for line in lines:
+ print("# %s" % line)
+ print("#")
+
+ def _array_start(self, varname, length, defvalue, fromtype, totype):
+ if fromtype == self.TYPE_ENUM:
+ raise NotImplementedError("Enums not supported as source in Python generator")
+
+ if fromtype != self.TYPE_STRING:
+ print("%s = [" % varname)
+ else:
+ print("%s = {" % varname)
+
+ def _array_end(self, fromtype, totype):
+ if fromtype != self.TYPE_STRING:
+ print("]")
+ else:
+ print("}")
+
+ def _array_entry(self, index, value, comment, fromtype, totype):
+ if fromtype == self.TYPE_INT:
+ if value is None:
+ print(" None, # %s" % (comment))
+ elif totype == self.TYPE_INT:
+ print(" 0x%x, # %s" % (value, comment))
+ elif totype == self.TYPE_ENUM:
+ print(" %s, # %s" % (value, comment))
+ else:
+ print(" \"%s\", # %s" % (value, comment))
+ else:
+ if value is None:
+ print(" \"%s\": None, # %s" % (index, comment))
+ elif totype == self.TYPE_INT:
+ print(" \"%s\": 0x%x, # %s" % (index, value, comment))
+ elif totype == self.TYPE_ENUM:
+ print(" \"%s\": %s, # %s" % (index, value, comment))
+ else:
+ print(" \"%s\": \"%s\", # %s" % (index, value, comment))
+
+class PerlLanguageGenerator(LanguageSrcGenerator):
+
+ def _boilerplate(self, lines):
+ print("#")
+ for line in lines:
+ print("# %s" % line)
+ print("#")
+
+ def _array_start(self, varname, length, defvalue, fromtype, totype):
+ if fromtype == self.TYPE_ENUN:
+ raise NotImplementedError("Enums not supported as source in Python generator")
+ if fromtype == self.TYPE_INT:
+ print("my @%s = (" % varname)
+ else:
+ print("my %%%s = (" % varname)
+
+ def _array_end(self, fromtype, totype):
+ print(");")
+
+ def _array_entry(self, index, value, comment, fromtype, totype):
+ if fromtype == self.TYPE_INT:
+ if value is None:
+ print(" undef, # %s" % (comment))
+ elif totype == self.TYPE_INT:
+ print(" 0x%x, # %s" % (value, comment))
+ elif totype == self.TYPE_ENUM:
+ print(" %s, # %s" % (value, comment))
+ else:
+ print(" \"%s\", # %s" % (value, comment))
+ else:
+ if value is None:
+ print(" \"%s\", undef, # %s" % (index, comment))
+ elif totype == self.TYPE_INT:
+ print(" \"%s\", 0x%x, # %s" % (index, value, comment))
+ elif totype == self.TYPE_ENUM:
+ print(" \"%s\", 0x%x, # %s" % (index, value, comment))
+ else:
+ print(" \"%s\", \"%s\", # %s" % (index, value, comment))
+
+class JavaScriptLanguageGenerator(LanguageSrcGenerator):
+
+ def _boilerplate(self, lines):
+ print("/*")
+ for line in lines:
+ print(" * %s" % line)
+ print("*/")
+
+ def _array_start(self, varname, length, defvalue, fromtype, totype):
+ print("export default {")
+
+ def _array_end(self, fromtype, totype):
+ print("};")
+
+ def _array_entry(self, index, value, comment, fromtype, totype):
+ if value is None:
+ return
+
+ if fromtype == self.TYPE_INT:
+ fromfmt = "0x%x"
+ elif fromtype == self.TYPE_ENUM:
+ fromfmt = "%s"
+ else:
+ fromfmt = "\"%s\""
+
+ if totype == self.TYPE_INT:
+ tofmt = "0x%x"
+ elif totype == self.TYPE_ENUM:
+ tofmt = "%s"
+ else:
+ tofmt = "\"%s\""
+
+ print((" " + fromfmt + ": " + tofmt + ", /* %s */") % (index, value, comment))
+
+class PodLanguageGenerator(LanguageDocGenerator):
+
+ def _boilerplate(self, lines):
+ print("#")
+ for line in lines:
+ print("# %s" % line)
+ print("#")
+
+ def _array_start_name_doc(self, varname, namemap):
+ print("=head1 %s" % varname)
+ print("")
+ print("List of %s key code names, with corresponding key code values" % namemap)
+ print("")
+ print("=over 4")
+ print("")
+
+ def _array_start_code_doc(self, varname, codemap, namemap):
+ print("=head1 %s" % varname)
+ print("")
+ print("List of %s key code values, with corresponding %s key code names" % (codemap, namemap))
+ print("")
+ print("=over 4")
+ print("")
+
+ def _array_end(self):
+ print("=back")
+ print("")
+
+ def _array_name_entry(self, value, name):
+ print("=item %s" % name)
+ print("")
+ print("Key value %d (0x%x)" % (value, value))
+ print("")
+
+ def _array_code_entry(self, value, name):
+ print("=item %d (0x%x)" % (value, value))
+ print("")
+ print("Key name %s" % name)
+ print("")
+
+SRC_GENERATORS = {
+ "stdc": StdCLanguageGenerator(),
+ "stdc++": StdCppLanguageGenerator(),
+ "glib2": GLib2LanguageGenerator(),
+ "python2": PythonLanguageGenerator(),
+ "python3": PythonLanguageGenerator(),
+ "perl": PerlLanguageGenerator(),
+ "js": JavaScriptLanguageGenerator(),
+}
+DOC_GENERATORS = {
+ "pod": PodLanguageGenerator(),
+}
+
+def code_map(args):
+ database = Database()
+ database.load(args.keymaps)
+
+ cliargs = ["keymap-gen", "--lang=%s" % args.lang]
+ if args.varname is not None:
+ cliargs.append("--varname=%s" % args.varname)
+ cliargs.extend(["code-map", "keymaps.csv", args.frommapname, args.tomapname])
+ SRC_GENERATORS[args.lang].generate_header(database, " ".join(cliargs))
+
+ SRC_GENERATORS[args.lang].generate_code_map(args.varname, database, args.frommapname, args.tomapname)
+
+def code_table(args):
+ database = Database()
+ database.load(args.keymaps)
+
+ cliargs = ["keymap-gen", "--lang=%s" % args.lang]
+ if args.varname is not None:
+ cliargs.append("--varname=%s" % args.varname)
+ cliargs.extend(["code-table", "keymaps.csv", args.mapname])
+ SRC_GENERATORS[args.lang].generate_header(database, " ".join(cliargs))
+
+ SRC_GENERATORS[args.lang].generate_code_table(args.varname, database, args.mapname)
+
+def name_map(args):
+ database = Database()
+ database.load(args.keymaps)
+
+ cliargs = ["keymap-gen", "--lang=%s" % args.lang]
+ if args.varname is not None:
+ cliargs.append("--varname=%s" % args.varname)
+ cliargs.extend(["name-map", "keymaps.csv", args.frommapname, args.tomapname])
+ SRC_GENERATORS[args.lang].generate_header(database, " ".join(cliargs))
+
+ SRC_GENERATORS[args.lang].generate_name_map(args.varname, database, args.frommapname, args.tomapname)
+
+def name_table(args):
+ database = Database()
+ database.load(args.keymaps)
+
+
+ cliargs = ["keymap-gen", "--lang=%s" % args.lang]
+ if args.varname is not None:
+ cliargs.append("--varname=%s" % args.varname)
+ cliargs.extend(["name-table", "keymaps.csv", args.mapname])
+ SRC_GENERATORS[args.lang].generate_header(database, " ".join(cliargs))
+
+ SRC_GENERATORS[args.lang].generate_name_table(args.varname, database, args.mapname)
+
+def code_docs(args):
+ database = Database()
+ database.load(args.keymaps)
+
+
+ cliargs = ["keymap-gen", "--lang=%s" % args.lang]
+ if args.varname is not None:
+ cliargs.append("--varname=%s" % args.varname)
+ cliargs.extend(["code-docs", "keymaps.csv", args.mapname])
+ DOC_GENERATORS[args.lang].generate_header(database, " ".join(cliargs))
+
+ DOC_GENERATORS[args.lang].generate_code_docs(args.varname, database, args.mapname)
+
+def name_docs(args):
+ database = Database()
+ database.load(args.keymaps)
+
+
+ cliargs = ["keymap-gen", "--lang=%s" % args.lang]
+ if args.varname is not None:
+ cliargs.append("--varname=%s" % args.varname)
+ cliargs.extend(["name-docs", "keymaps.csv", args.mapname])
+ DOC_GENERATORS[args.lang].generate_header(database, " ".join(cliargs))
+
+ DOC_GENERATORS[args.lang].generate_name_docs(args.varname, database, args.mapname)
+
+def usage():
+ print ("Please select a command:")
+ print (" 'code-map', 'code-table', 'name-map', 'name-table', 'docs'")
+ sys.exit(1)
+
+def main():
+ parser = argparse.ArgumentParser()
+
+ parser.add_argument("--lang", default="stdc",
+ help="Output language, (src=%s, doc=%s)" % (
+ ",".join(SRC_GENERATORS.keys()),
+ ",".join(DOC_GENERATORS.keys())))
+ parser.add_argument("--varname", default=None,
+ help="Data variable name")
+
+ subparsers = parser.add_subparsers(help="sub-command help")
+
+ codemapparser = subparsers.add_parser("code-map", help="Generate a mapping between code tables")
+ codemapparser.add_argument("keymaps", help="Path to keymap CSV data file")
+ codemapparser.add_argument("frommapname", help="Source code table name")
+ codemapparser.add_argument("tomapname", help="Target code table name")
+ codemapparser.set_defaults(func=code_map)
+
+ codetableparser = subparsers.add_parser("code-table", help="Generate a flat code table")
+ codetableparser.add_argument("keymaps", help="Path to keymap CSV data file")
+ codetableparser.add_argument("mapname", help="Code table name")
+ codetableparser.set_defaults(func=code_table)
+
+ namemapparser = subparsers.add_parser("name-map", help="Generate a mapping to names")
+ namemapparser.add_argument("keymaps", help="Path to keymap CSV data file")
+ namemapparser.add_argument("frommapname", help="Source code table name")
+ namemapparser.add_argument("tomapname", help="Target name table name")
+ namemapparser.set_defaults(func=name_map)
+
+ nametableparser = subparsers.add_parser("name-table", help="Generate a flat name table")
+ nametableparser.add_argument("keymaps", help="Path to keymap CSV data file")
+ nametableparser.add_argument("mapname", help="Name table name")
+ nametableparser.set_defaults(func=name_table)
+
+ codedocsparser = subparsers.add_parser("code-docs", help="Generate code documentation")
+ codedocsparser.add_argument("keymaps", help="Path to keymap CSV data file")
+ codedocsparser.add_argument("mapname", help="Code table name")
+ codedocsparser.set_defaults(func=code_docs)
+
+ namedocsparser = subparsers.add_parser("name-docs", help="Generate name documentation")
+ namedocsparser.add_argument("keymaps", help="Path to keymap CSV data file")
+ namedocsparser.add_argument("mapname", help="Name table name")
+ namedocsparser.set_defaults(func=name_docs)
+
+ args = parser.parse_args()
+ if hasattr(args, "func"):
+ args.func(args)
+ else:
+ usage()
+
+
+main()
diff --git a/ui/keymaps.c b/ui/keymaps.c
index f373cc53d..4e5fca57a 100644
--- a/ui/keymaps.c
+++ b/ui/keymaps.c
@@ -22,193 +22,251 @@
* THE SOFTWARE.
*/
+#include "qemu/osdep.h"
+#include "qemu-common.h"
#include "keymaps.h"
-#include "sysemu/sysemu.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 char *name)
{
const name2keysym_t *p;
for(p = table; p->name != NULL; p++) {
- if (!strcmp(p->name, name))
+ 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_to_key_range(struct key_range **krp, int code) {
- struct key_range *kr;
- for (kr = *krp; kr; kr = kr->next) {
- if (code >= kr->start && code <= kr->end)
- break;
- if (code == kr->start - 1) {
- kr->start--;
- break;
- }
- if (code == kr->end + 1) {
- kr->end++;
- break;
- }
- }
- if (kr == NULL) {
- kr = g_malloc0(sizeof(*kr));
- kr->start = kr->end = code;
- kr->next = *krp;
- *krp = kr;
- }
-}
+static void add_keysym(char *line, int keysym, int keycode, kbd_layout_t *k)
+{
+ struct keysym2code *keysym2code;
-static void add_keysym(char *line, int keysym, int keycode, kbd_layout_t *k) {
- if (keysym < MAX_NORMAL_KEYCODE) {
- //fprintf(stderr,"Setting keysym %s (%d) to %d\n",line,keysym,keycode);
- k->keysym2keycode[keysym] = keycode;
- } else {
- if (k->extra_count >= MAX_EXTRA_COUNT) {
- fprintf(stderr,
- "Warning: Could not assign keysym %s (0x%x) because of memory constraints.\n",
- line, keysym);
- } else {
-#if 0
- fprintf(stderr, "Setting %d: %d,%d\n",
- k->extra_count, keysym, keycode);
-#endif
- k->keysym2keycode_extra[k->extra_count].
- keysym = keysym;
- k->keysym2keycode_extra[k->extra_count].
- keycode = keycode;
- k->extra_count++;
- }
+ 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 kbd_layout_t *parse_keyboard_layout(const name2keysym_t *table,
- const char *language,
- kbd_layout_t * k)
+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) {
- fprintf(stderr,
- "Could not read keymap file: '%s'\n", language);
- return NULL;
+ error_setg(errp, "could not read keymap file: '%s'", language);
+ return -1;
}
- if (!k)
- k = g_malloc0(sizeof(kbd_layout_t));
-
for(;;) {
- if (fgets(line, 1024, f) == NULL)
+ if (fgets(line, 1024, f) == NULL) {
break;
+ }
len = strlen(line);
- if (len > 0 && line[len - 1] == '\n')
+ 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)) {
- parse_keyboard_layout(table, line + 8, k);
+ }
+ 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 {
- char *end_of_keysym = line;
- while (*end_of_keysym != 0 && *end_of_keysym != ' ')
- end_of_keysym++;
- if (*end_of_keysym) {
- int keysym;
- *end_of_keysym = 0;
- keysym = get_keysym(table, line);
- if (keysym == 0) {
- // fprintf(stderr, "Warning: unknown keysym %s\n", line);
- } else {
- const char *rest = end_of_keysym + 1;
+ 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, "numlock")) {
- add_to_key_range(&k->keypad_range, keycode);
- add_to_key_range(&k->numlock_range, keysym);
- //fprintf(stderr, "keypad keysym %04x keycode %d\n", keysym, keycode);
- }
-
if (strstr(rest, "shift")) {
- keycode |= SCANCODE_SHIFT;
+ keycode |= SCANCODE_SHIFT;
}
if (strstr(rest, "altgr")) {
- keycode |= SCANCODE_ALTGR;
+ keycode |= SCANCODE_ALTGR;
}
if (strstr(rest, "ctrl")) {
- keycode |= SCANCODE_CTRL;
+ keycode |= SCANCODE_CTRL;
}
- add_keysym(line, keysym, keycode, k);
+ add_keysym(line, keysym, keycode, k);
if (strstr(rest, "addupper")) {
- char *c;
- for (c = line; *c; c++)
- *c = qemu_toupper(*c);
- keysym = get_keysym(table, line);
- if (keysym)
- add_keysym(line, keysym, keycode | SCANCODE_SHIFT, k);
- }
- }
- }
- }
+ 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 k;
+ return ret;
}
-void *init_keyboard_layout(const name2keysym_t *table, const char *language)
+kbd_layout_t *init_keyboard_layout(const name2keysym_t *table,
+ const char *language, Error **errp)
{
- return parse_keyboard_layout(table, language, NULL);
+ 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(void *kbd_layout, int keysym)
+int keysym2scancode(kbd_layout_t *k, int keysym,
+ QKbdState *kbd, bool down)
{
- kbd_layout_t *k = kbd_layout;
- if (keysym < MAX_NORMAL_KEYCODE) {
- if (k->keysym2keycode[keysym] == 0)
- fprintf(stderr, "Warning: no scancode found for keysym %d\n",
- keysym);
- return k->keysym2keycode[keysym];
- } else {
- int i;
+ 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;
+ if (keysym == XK_ISO_Left_Tab) {
+ keysym = XK_Tab;
+ }
#endif
- for (i = 0; i < k->extra_count; i++)
- if (k->keysym2keycode_extra[i].keysym == keysym)
- return k->keysym2keycode_extra[i].keycode;
+
+ 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;
}
- return 0;
-}
-int keycode_is_keypad(void *kbd_layout, int keycode)
-{
- kbd_layout_t *k = kbd_layout;
- struct key_range *kr;
+ if (keysym2code->count == 1) {
+ return keysym2code->keycodes[0];
+ }
- for (kr = k->keypad_range; kr; kr = kr->next)
- if (keycode >= kr->start && keycode <= kr->end)
- return 1;
- return 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 keysym_is_numlock(void *kbd_layout, int keysym)
+int keycode_is_keypad(kbd_layout_t *k, int keycode)
{
- kbd_layout_t *k = kbd_layout;
- struct key_range *kr;
+ if (keycode >= 0x47 && keycode <= 0x53) {
+ return true;
+ }
+ return false;
+}
- for (kr = k->numlock_range; kr; kr = kr->next)
- if (keysym >= kr->start && keysym <= kr->end)
- return 1;
- return 0;
+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
index a7600d575..647340548 100644
--- a/ui/keymaps.h
+++ b/ui/keymaps.h
@@ -22,35 +22,16 @@
* THE SOFTWARE.
*/
-#ifndef __QEMU_KEYMAPS_H__
-#define __QEMU_KEYMAPS_H__
+#ifndef QEMU_KEYMAPS_H
+#define QEMU_KEYMAPS_H
-#include "qemu-common.h"
+#include "ui/kbd-state.h"
typedef struct {
- const char* name;
- int keysym;
+ const char* name;
+ int keysym;
} name2keysym_t;
-struct key_range {
- int start;
- int end;
- struct key_range *next;
-};
-
-#define MAX_NORMAL_KEYCODE 512
-#define MAX_EXTRA_COUNT 256
-typedef struct {
- uint16_t keysym2keycode[MAX_NORMAL_KEYCODE];
- struct {
- int keysym;
- uint16_t keycode;
- } keysym2keycode_extra[MAX_EXTRA_COUNT];
- int extra_count;
- struct key_range *keypad_range;
- struct key_range *numlock_range;
-} kbd_layout_t;
-
/* scancode without modifiers */
#define SCANCODE_KEYMASK 0xff
/* scancode without grey or up bit */
@@ -59,6 +40,7 @@ typedef struct {
/* "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
@@ -68,10 +50,13 @@ typedef struct {
#define SCANCODE_ALT 0x400
#define SCANCODE_ALTGR 0x800
+typedef struct kbd_layout_t kbd_layout_t;
-void *init_keyboard_layout(const name2keysym_t *table, const char *language);
-int keysym2scancode(void *kbd_layout, int keysym);
-int keycode_is_keypad(void *kbd_layout, int keycode);
-int keysym_is_numlock(void *kbd_layout, int keysym);
+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__ */
+#endif /* QEMU_KEYMAPS_H */
diff --git a/ui/meson.build b/ui/meson.build
new file mode 100644
index 000000000..013258a01
--- /dev/null
+++ b/ui/meson.build
@@ -0,0 +1,145 @@
+softmmu_ss.add(pixman)
+specific_ss.add(when: ['CONFIG_SOFTMMU'], if_true: pixman) # for the include path
+
+softmmu_ss.add(files(
+ '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: 'CONFIG_LINUX', if_true: files('input-linux.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_ss.add(zlib, png, jpeg)
+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(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_DMABUF')
+ egl_headless_ss = ss.source_set()
+ egl_headless_ss.add(when: [opengl, pixman, 'CONFIG_OPENGL_DMABUF'],
+ if_true: files('egl-headless.c'))
+ ui_modules += {'egl-headless' : egl_headless_ss}
+endif
+
+if config_host.has_key('CONFIG_GTK')
+ 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_ss.add(when: [x11, 'CONFIG_X11'], if_true: files('x_keymap.c'))
+ gtk_ss.add(when: [opengl, 'CONFIG_OPENGL'], if_true: files('gtk-egl.c'))
+ gtk_ss.add(when: [opengl, 'CONFIG_GTK_GL'], if_true: files('gtk-gl-area.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, 'CONFIG_X11'], if_true: files('x_keymap.c'))
+ ui_modules += {'sdl' : sdl_ss}
+endif
+
+if config_host.has_key('CONFIG_SPICE')
+ 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 config_host.has_key('CONFIG_SPICE') 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
+
+keymap_gen = find_program('keycodemapdb/tools/keymap-gen')
+
+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.full_path(), files('keycodemapdb/tools/keymap-gen'),
+ '--lang', 'glib2',
+ '--varname', 'qemu_input_map_@0@_to_@1@'.format(e[0], e[1]),
+ 'code-map', '@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
index 254bd8ce1..85f2945e8 100644
--- a/ui/qemu-pixman.c
+++ b/ui/qemu-pixman.c
@@ -3,8 +3,111 @@
* See the COPYING file in the top-level directory.
*/
-#include "qemu-common.h"
+#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 */
+pixman_format_code_t qemu_drm_format_to_pixman(uint32_t drm_format)
+{
+ static const struct {
+ uint32_t drm_format;
+ pixman_format_code_t pixman;
+ } map[] = {
+ { DRM_FORMAT_RGB888, PIXMAN_LE_r8g8b8 },
+ { DRM_FORMAT_ARGB8888, PIXMAN_LE_a8r8g8b8 },
+ { DRM_FORMAT_XRGB8888, PIXMAN_LE_x8r8g8b8 }
+ };
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(map); i++) {
+ if (drm_format == map[i].drm_format) {
+ return map[i].pixman;
+ }
+ }
+ return 0;
+}
int qemu_pixman_get_type(int rshift, int gshift, int bshift)
{
@@ -14,17 +117,13 @@ int qemu_pixman_get_type(int rshift, int gshift, int bshift)
if (bshift == 0) {
type = PIXMAN_TYPE_ARGB;
} else {
-#if PIXMAN_VERSION >= PIXMAN_VERSION_ENCODE(0, 21, 8)
type = PIXMAN_TYPE_RGBA;
-#endif
}
} else if (rshift < gshift && gshift < bshift) {
if (rshift == 0) {
type = PIXMAN_TYPE_ABGR;
} else {
-#if PIXMAN_VERSION >= PIXMAN_VERSION_ENCODE(0, 16, 0)
type = PIXMAN_TYPE_BGRA;
-#endif
}
}
return type;
@@ -44,6 +143,33 @@ pixman_format_code_t qemu_pixman_get_format(PixelFormat *pf)
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)
{
@@ -52,6 +178,7 @@ pixman_image_t *qemu_pixman_linebuf_create(pixman_format_code_t format,
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)
{
@@ -59,17 +186,22 @@ void qemu_pixman_linebuf_fill(pixman_image_t *linebuf, pixman_image_t *fb,
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)
{
- pixman_image_t *mirror;
-
- mirror = pixman_image_create_bits(format,
- pixman_image_get_width(image),
- pixman_image_get_height(image),
- NULL,
- pixman_image_get_stride(image));
- return mirror;
+ 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)
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/sdl.c b/ui/sdl.c
deleted file mode 100644
index 39a42d6b0..000000000
--- a/ui/sdl.c
+++ /dev/null
@@ -1,953 +0,0 @@
-/*
- * 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.
- */
-
-/* Avoid compiler warning because macro is redefined in SDL_syswm.h. */
-#undef WIN32_LEAN_AND_MEAN
-
-#include <SDL.h>
-#include <SDL_syswm.h>
-
-#include "qemu-common.h"
-#include "ui/console.h"
-#include "sysemu/sysemu.h"
-#include "x_keymap.h"
-#include "sdl_zoom.h"
-
-static DisplayChangeListener *dcl;
-static DisplaySurface *surface;
-static SDL_Surface *real_screen;
-static SDL_Surface *guest_screen = NULL;
-static int gui_grab; /* if true, all keyboard/mouse events are grabbed */
-static int last_vm_running;
-static bool gui_saved_scaling;
-static int gui_saved_width;
-static int gui_saved_height;
-static int gui_saved_grab;
-static int gui_fullscreen;
-static int gui_noframe;
-static int gui_key_modifier_pressed;
-static int gui_keysym;
-static int gui_grab_code = KMOD_LALT | KMOD_LCTRL;
-static uint8_t modifiers_state[256];
-static SDL_Cursor *sdl_cursor_normal;
-static SDL_Cursor *sdl_cursor_hidden;
-static int absolute_enabled = 0;
-static int guest_cursor = 0;
-static int guest_x, guest_y;
-static SDL_Cursor *guest_sprite = NULL;
-static SDL_PixelFormat host_format;
-static int scaling_active = 0;
-static Notifier mouse_mode_notifier;
-
-static void sdl_update(DisplayChangeListener *dcl,
- int x, int y, int w, int h)
-{
- // printf("updating x=%d y=%d w=%d h=%d\n", x, y, w, h);
- SDL_Rect rec;
- rec.x = x;
- rec.y = y;
- rec.w = w;
- rec.h = h;
-
- if (guest_screen) {
- if (!scaling_active) {
- SDL_BlitSurface(guest_screen, &rec, real_screen, &rec);
- } else {
- if (sdl_zoom_blit(guest_screen, real_screen, SMOOTHING_ON, &rec) < 0) {
- fprintf(stderr, "Zoom blit failed\n");
- exit(1);
- }
- }
- }
- SDL_UpdateRect(real_screen, rec.x, rec.y, rec.w, rec.h);
-}
-
-static void do_sdl_resize(int width, int height, int bpp)
-{
- int flags;
-
- // printf("resizing to %d %d\n", w, h);
-
- flags = SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_HWACCEL;
- if (gui_fullscreen) {
- flags |= SDL_FULLSCREEN;
- } else {
- flags |= SDL_RESIZABLE;
- }
- if (gui_noframe)
- flags |= SDL_NOFRAME;
-
- real_screen = SDL_SetVideoMode(width, height, bpp, flags);
- if (!real_screen) {
- fprintf(stderr, "Could not open SDL display (%dx%dx%d): %s\n", width,
- height, bpp, SDL_GetError());
- exit(1);
- }
-}
-
-static void sdl_switch(DisplayChangeListener *dcl,
- DisplaySurface *new_surface)
-{
-
- /* temporary hack: allows to call sdl_switch to handle scaling changes */
- if (new_surface) {
- surface = new_surface;
- }
-
- if (!scaling_active) {
- do_sdl_resize(surface_width(surface), surface_height(surface), 0);
- } else if (real_screen->format->BitsPerPixel !=
- surface_bits_per_pixel(surface)) {
- do_sdl_resize(real_screen->w, real_screen->h,
- surface_bits_per_pixel(surface));
- }
-
- if (guest_screen != NULL) {
- SDL_FreeSurface(guest_screen);
- }
- guest_screen = SDL_CreateRGBSurfaceFrom
- (surface_data(surface),
- surface_width(surface), surface_height(surface),
- surface_bits_per_pixel(surface), surface_stride(surface),
- surface->pf.rmask, surface->pf.gmask,
- surface->pf.bmask, surface->pf.amask);
-}
-
-/* generic keyboard conversion */
-
-#include "sdl_keysym.h"
-
-static kbd_layout_t *kbd_layout = NULL;
-
-static uint8_t sdl_keyevent_to_keycode_generic(const SDL_KeyboardEvent *ev)
-{
- int keysym;
- /* workaround for X11+SDL bug with AltGR */
- keysym = ev->keysym.sym;
- if (keysym == 0 && ev->keysym.scancode == 113)
- keysym = SDLK_MODE;
- /* For Japanese key '\' and '|' */
- if (keysym == 92 && ev->keysym.scancode == 133) {
- keysym = 0xa5;
- }
- return keysym2scancode(kbd_layout, keysym) & SCANCODE_KEYMASK;
-}
-
-/* specific keyboard conversions from scan codes */
-
-#if defined(_WIN32)
-
-static uint8_t sdl_keyevent_to_keycode(const SDL_KeyboardEvent *ev)
-{
- return ev->keysym.scancode;
-}
-
-#else
-
-#if defined(SDL_VIDEO_DRIVER_X11)
-#include <X11/XKBlib.h>
-
-static int check_for_evdev(void)
-{
- SDL_SysWMinfo info;
- XkbDescPtr desc = NULL;
- int has_evdev = 0;
- char *keycodes = NULL;
-
- SDL_VERSION(&info.version);
- if (!SDL_GetWMInfo(&info)) {
- return 0;
- }
- desc = XkbGetKeyboard(info.info.x11.display,
- XkbGBN_AllComponentsMask,
- XkbUseCoreKbd);
- if (desc && desc->names) {
- keycodes = XGetAtomName(info.info.x11.display, desc->names->keycodes);
- if (keycodes == NULL) {
- fprintf(stderr, "could not lookup keycode name\n");
- } else if (strstart(keycodes, "evdev", NULL)) {
- has_evdev = 1;
- } else if (!strstart(keycodes, "xfree86", NULL)) {
- fprintf(stderr, "unknown keycodes `%s', please report to "
- "qemu-devel@nongnu.org\n", keycodes);
- }
- }
-
- if (desc) {
- XkbFreeKeyboard(desc, XkbGBN_AllComponentsMask, True);
- }
- if (keycodes) {
- XFree(keycodes);
- }
- return has_evdev;
-}
-#else
-static int check_for_evdev(void)
-{
- return 0;
-}
-#endif
-
-static uint8_t sdl_keyevent_to_keycode(const SDL_KeyboardEvent *ev)
-{
- int keycode;
- static int has_evdev = -1;
-
- if (has_evdev == -1)
- has_evdev = check_for_evdev();
-
- keycode = ev->keysym.scancode;
-
- if (keycode < 9) {
- keycode = 0;
- } else if (keycode < 97) {
- keycode -= 8; /* just an offset */
- } else if (keycode < 158) {
- /* use conversion table */
- if (has_evdev)
- keycode = translate_evdev_keycode(keycode - 97);
- else
- keycode = translate_xfree86_keycode(keycode - 97);
- } else if (keycode == 208) { /* Hiragana_Katakana */
- keycode = 0x70;
- } else if (keycode == 211) { /* backslash */
- keycode = 0x73;
- } else {
- keycode = 0;
- }
- return keycode;
-}
-
-#endif
-
-static void reset_keys(void)
-{
- int i;
- for(i = 0; i < 256; i++) {
- if (modifiers_state[i]) {
- if (i & SCANCODE_GREY)
- kbd_put_keycode(SCANCODE_EMUL0);
- kbd_put_keycode(i | SCANCODE_UP);
- modifiers_state[i] = 0;
- }
- }
-}
-
-static void sdl_process_key(SDL_KeyboardEvent *ev)
-{
- int keycode, v;
-
- if (ev->keysym.sym == SDLK_PAUSE) {
- /* specific case */
- v = 0;
- if (ev->type == SDL_KEYUP)
- v |= SCANCODE_UP;
- kbd_put_keycode(0xe1);
- kbd_put_keycode(0x1d | v);
- kbd_put_keycode(0x45 | v);
- return;
- }
-
- if (kbd_layout) {
- keycode = sdl_keyevent_to_keycode_generic(ev);
- } else {
- keycode = sdl_keyevent_to_keycode(ev);
- }
-
- switch(keycode) {
- case 0x00:
- /* sent when leaving window: reset the modifiers state */
- reset_keys();
- return;
- case 0x2a: /* Left Shift */
- case 0x36: /* Right Shift */
- case 0x1d: /* Left CTRL */
- case 0x9d: /* Right CTRL */
- case 0x38: /* Left ALT */
- case 0xb8: /* Right ALT */
- if (ev->type == SDL_KEYUP)
- modifiers_state[keycode] = 0;
- else
- modifiers_state[keycode] = 1;
- break;
-#define QEMU_SDL_VERSION ((SDL_MAJOR_VERSION << 8) + SDL_MINOR_VERSION)
-#if QEMU_SDL_VERSION < 0x102 || QEMU_SDL_VERSION == 0x102 && SDL_PATCHLEVEL < 14
- /* SDL versions before 1.2.14 don't support key up for caps/num lock. */
- case 0x45: /* num lock */
- case 0x3a: /* caps lock */
- /* SDL does not send the key up event, so we generate it */
- kbd_put_keycode(keycode);
- kbd_put_keycode(keycode | SCANCODE_UP);
- return;
-#endif
- }
-
- /* now send the key code */
- if (keycode & SCANCODE_GREY)
- kbd_put_keycode(SCANCODE_EMUL0);
- if (ev->type == SDL_KEYUP)
- kbd_put_keycode(keycode | SCANCODE_UP);
- else
- kbd_put_keycode(keycode & SCANCODE_KEYCODEMASK);
-}
-
-static void sdl_update_caption(void)
-{
- 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 to exit mouse grab";
- else if (ctrl_grab)
- status = " - Press Right-Ctrl to exit mouse grab";
- else
- status = " - Press Ctrl-Alt to exit mouse grab";
- }
-
- if (qemu_name) {
- snprintf(win_title, sizeof(win_title), "QEMU (%s)%s", qemu_name, 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");
- }
-
- SDL_WM_SetCaption(win_title, icon_title);
-}
-
-static void sdl_hide_cursor(void)
-{
- if (!cursor_hide)
- return;
-
- if (kbd_mouse_is_absolute()) {
- SDL_ShowCursor(1);
- SDL_SetCursor(sdl_cursor_hidden);
- } else {
- SDL_ShowCursor(0);
- }
-}
-
-static void sdl_show_cursor(void)
-{
- if (!cursor_hide)
- return;
-
- if (!kbd_mouse_is_absolute() || !qemu_console_is_graphic(NULL)) {
- SDL_ShowCursor(1);
- if (guest_cursor &&
- (gui_grab || kbd_mouse_is_absolute() || absolute_enabled))
- SDL_SetCursor(guest_sprite);
- else
- SDL_SetCursor(sdl_cursor_normal);
- }
-}
-
-static void sdl_grab_start(void)
-{
- /*
- * 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_GetAppState() & SDL_APPINPUTFOCUS)) {
- return;
- }
- if (guest_cursor) {
- SDL_SetCursor(guest_sprite);
- if (!kbd_mouse_is_absolute() && !absolute_enabled)
- SDL_WarpMouse(guest_x, guest_y);
- } else
- sdl_hide_cursor();
- SDL_WM_GrabInput(SDL_GRAB_ON);
- gui_grab = 1;
- sdl_update_caption();
-}
-
-static void sdl_grab_end(void)
-{
- SDL_WM_GrabInput(SDL_GRAB_OFF);
- gui_grab = 0;
- sdl_show_cursor();
- sdl_update_caption();
-}
-
-static void absolute_mouse_grab(void)
-{
- int mouse_x, mouse_y;
-
- SDL_GetMouseState(&mouse_x, &mouse_y);
- if (mouse_x > 0 && mouse_x < real_screen->w - 1 &&
- mouse_y > 0 && mouse_y < real_screen->h - 1) {
- sdl_grab_start();
- }
-}
-
-static void sdl_mouse_mode_change(Notifier *notify, void *data)
-{
- if (kbd_mouse_is_absolute()) {
- if (!absolute_enabled) {
- absolute_enabled = 1;
- if (qemu_console_is_graphic(NULL)) {
- absolute_mouse_grab();
- }
- }
- } else if (absolute_enabled) {
- if (!gui_fullscreen) {
- sdl_grab_end();
- }
- absolute_enabled = 0;
- }
-}
-
-static void sdl_send_mouse_event(int dx, int dy, int dz, int x, int y, int state)
-{
- int buttons = 0;
-
- if (state & SDL_BUTTON(SDL_BUTTON_LEFT)) {
- buttons |= MOUSE_EVENT_LBUTTON;
- }
- if (state & SDL_BUTTON(SDL_BUTTON_RIGHT)) {
- buttons |= MOUSE_EVENT_RBUTTON;
- }
- if (state & SDL_BUTTON(SDL_BUTTON_MIDDLE)) {
- buttons |= MOUSE_EVENT_MBUTTON;
- }
-
- if (kbd_mouse_is_absolute()) {
- dx = x * 0x7FFF / (real_screen->w - 1);
- dy = y * 0x7FFF / (real_screen->h - 1);
- } else if (guest_cursor) {
- x -= guest_x;
- y -= guest_y;
- guest_x += x;
- guest_y += y;
- dx = x;
- dy = y;
- }
-
- kbd_mouse_event(dx, dy, dz, buttons);
-}
-
-static void sdl_scale(int width, int height)
-{
- int bpp = real_screen->format->BitsPerPixel;
-
- if (bpp != 16 && bpp != 32) {
- bpp = 32;
- }
- do_sdl_resize(width, height, bpp);
- scaling_active = 1;
-}
-
-static void toggle_full_screen(void)
-{
- int width = surface_width(surface);
- int height = surface_height(surface);
- int bpp = surface_bits_per_pixel(surface);
-
- gui_fullscreen = !gui_fullscreen;
- if (gui_fullscreen) {
- gui_saved_width = real_screen->w;
- gui_saved_height = real_screen->h;
- gui_saved_scaling = scaling_active;
-
- do_sdl_resize(width, height, bpp);
- scaling_active = 0;
-
- gui_saved_grab = gui_grab;
- sdl_grab_start();
- } else {
- if (gui_saved_scaling) {
- sdl_scale(gui_saved_width, gui_saved_height);
- } else {
- do_sdl_resize(width, height, 0);
- }
- if (!gui_saved_grab || !qemu_console_is_graphic(NULL)) {
- sdl_grab_end();
- }
- }
- graphic_hw_invalidate(NULL);
- graphic_hw_update(NULL);
-}
-
-static void handle_keydown(SDL_Event *ev)
-{
- int mod_state;
- int keycode;
-
- if (alt_grab) {
- mod_state = (SDL_GetModState() & (gui_grab_code | KMOD_LSHIFT)) ==
- (gui_grab_code | KMOD_LSHIFT);
- } else if (ctrl_grab) {
- mod_state = (SDL_GetModState() & KMOD_RCTRL) == KMOD_RCTRL;
- } else {
- mod_state = (SDL_GetModState() & gui_grab_code) == gui_grab_code;
- }
- gui_key_modifier_pressed = mod_state;
-
- if (gui_key_modifier_pressed) {
- keycode = sdl_keyevent_to_keycode(&ev->key);
- switch (keycode) {
- case 0x21: /* 'f' key on US keyboard */
- toggle_full_screen();
- gui_keysym = 1;
- break;
- case 0x16: /* 'u' key on US keyboard */
- if (scaling_active) {
- scaling_active = 0;
- sdl_switch(dcl, NULL);
- graphic_hw_invalidate(NULL);
- graphic_hw_update(NULL);
- }
- gui_keysym = 1;
- break;
- case 0x02 ... 0x0a: /* '1' to '9' keys */
- /* Reset the modifiers sent to the current console */
- reset_keys();
- console_select(keycode - 0x02);
- gui_keysym = 1;
- if (gui_fullscreen) {
- break;
- }
- if (!qemu_console_is_graphic(NULL)) {
- /* release grab if going to a text console */
- if (gui_grab) {
- sdl_grab_end();
- } else if (absolute_enabled) {
- sdl_show_cursor();
- }
- } else if (absolute_enabled) {
- sdl_hide_cursor();
- absolute_mouse_grab();
- }
- break;
- case 0x1b: /* '+' */
- case 0x35: /* '-' */
- if (!gui_fullscreen) {
- int width = MAX(real_screen->w + (keycode == 0x1b ? 50 : -50),
- 160);
- int height = (surface_height(surface) * width) /
- surface_width(surface);
-
- sdl_scale(width, height);
- graphic_hw_invalidate(NULL);
- graphic_hw_update(NULL);
- gui_keysym = 1;
- }
- default:
- break;
- }
- } else if (!qemu_console_is_graphic(NULL)) {
- int keysym = 0;
-
- if (ev->key.keysym.mod & (KMOD_LCTRL | KMOD_RCTRL)) {
- switch (ev->key.keysym.sym) {
- case SDLK_UP:
- keysym = QEMU_KEY_CTRL_UP;
- break;
- case SDLK_DOWN:
- keysym = QEMU_KEY_CTRL_DOWN;
- break;
- case SDLK_LEFT:
- keysym = QEMU_KEY_CTRL_LEFT;
- break;
- case SDLK_RIGHT:
- keysym = QEMU_KEY_CTRL_RIGHT;
- break;
- case SDLK_HOME:
- keysym = QEMU_KEY_CTRL_HOME;
- break;
- case SDLK_END:
- keysym = QEMU_KEY_CTRL_END;
- break;
- case SDLK_PAGEUP:
- keysym = QEMU_KEY_CTRL_PAGEUP;
- break;
- case SDLK_PAGEDOWN:
- keysym = QEMU_KEY_CTRL_PAGEDOWN;
- break;
- default:
- break;
- }
- } else {
- switch (ev->key.keysym.sym) {
- case SDLK_UP:
- keysym = QEMU_KEY_UP;
- break;
- case SDLK_DOWN:
- keysym = QEMU_KEY_DOWN;
- break;
- case SDLK_LEFT:
- keysym = QEMU_KEY_LEFT;
- break;
- case SDLK_RIGHT:
- keysym = QEMU_KEY_RIGHT;
- break;
- case SDLK_HOME:
- keysym = QEMU_KEY_HOME;
- break;
- case SDLK_END:
- keysym = QEMU_KEY_END;
- break;
- case SDLK_PAGEUP:
- keysym = QEMU_KEY_PAGEUP;
- break;
- case SDLK_PAGEDOWN:
- keysym = QEMU_KEY_PAGEDOWN;
- break;
- case SDLK_BACKSPACE:
- keysym = QEMU_KEY_BACKSPACE;
- break;
- case SDLK_DELETE:
- keysym = QEMU_KEY_DELETE;
- break;
- default:
- break;
- }
- }
- if (keysym) {
- kbd_put_keysym(keysym);
- } else if (ev->key.keysym.unicode != 0) {
- kbd_put_keysym(ev->key.keysym.unicode);
- }
- }
- if (qemu_console_is_graphic(NULL) && !gui_keysym) {
- sdl_process_key(&ev->key);
- }
-}
-
-static void handle_keyup(SDL_Event *ev)
-{
- int mod_state;
-
- if (!alt_grab) {
- mod_state = (ev->key.keysym.mod & gui_grab_code);
- } else {
- mod_state = (ev->key.keysym.mod & (gui_grab_code | KMOD_LSHIFT));
- }
- if (!mod_state && gui_key_modifier_pressed) {
- gui_key_modifier_pressed = 0;
- if (gui_keysym == 0) {
- /* exit/enter grab if pressing Ctrl-Alt */
- if (!gui_grab) {
- if (qemu_console_is_graphic(NULL)) {
- sdl_grab_start();
- }
- } else if (!gui_fullscreen) {
- sdl_grab_end();
- }
- /* SDL does not send back all the modifiers key, so we must
- * correct it. */
- reset_keys();
- return;
- }
- gui_keysym = 0;
- }
- if (qemu_console_is_graphic(NULL) && !gui_keysym) {
- sdl_process_key(&ev->key);
- }
-}
-
-static void handle_mousemotion(SDL_Event *ev)
-{
- int max_x, max_y;
-
- if (qemu_console_is_graphic(NULL) &&
- (kbd_mouse_is_absolute() || absolute_enabled)) {
- max_x = real_screen->w - 1;
- max_y = real_screen->h - 1;
- if (gui_grab && (ev->motion.x == 0 || ev->motion.y == 0 ||
- ev->motion.x == max_x || ev->motion.y == max_y)) {
- sdl_grab_end();
- }
- if (!gui_grab &&
- (ev->motion.x > 0 && ev->motion.x < max_x &&
- ev->motion.y > 0 && ev->motion.y < max_y)) {
- sdl_grab_start();
- }
- }
- if (gui_grab || kbd_mouse_is_absolute() || absolute_enabled) {
- sdl_send_mouse_event(ev->motion.xrel, ev->motion.yrel, 0,
- 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;
- int dz;
-
- if (!qemu_console_is_graphic(NULL)) {
- return;
- }
-
- bev = &ev->button;
- if (!gui_grab && !kbd_mouse_is_absolute()) {
- if (ev->type == SDL_MOUSEBUTTONUP && bev->button == SDL_BUTTON_LEFT) {
- /* start grabbing all events */
- sdl_grab_start();
- }
- } else {
- dz = 0;
- if (ev->type == SDL_MOUSEBUTTONDOWN) {
- buttonstate |= SDL_BUTTON(bev->button);
- } else {
- buttonstate &= ~SDL_BUTTON(bev->button);
- }
-#ifdef SDL_BUTTON_WHEELUP
- if (bev->button == SDL_BUTTON_WHEELUP &&
- ev->type == SDL_MOUSEBUTTONDOWN) {
- dz = -1;
- } else if (bev->button == SDL_BUTTON_WHEELDOWN &&
- ev->type == SDL_MOUSEBUTTONDOWN) {
- dz = 1;
- }
-#endif
- sdl_send_mouse_event(0, 0, dz, bev->x, bev->y, buttonstate);
- }
-}
-
-static void handle_activation(SDL_Event *ev)
-{
-#ifdef _WIN32
- /* Disable grab if the window no longer has the focus
- * (Windows-only workaround) */
- if (gui_grab && ev->active.state == SDL_APPINPUTFOCUS &&
- !ev->active.gain && !gui_fullscreen) {
- sdl_grab_end();
- }
-#endif
- if (!gui_grab && ev->active.gain && qemu_console_is_graphic(NULL) &&
- (kbd_mouse_is_absolute() || absolute_enabled)) {
- absolute_mouse_grab();
- }
- if (ev->active.state & SDL_APPACTIVE) {
- if (ev->active.gain) {
- /* Back to default interval */
- update_displaychangelistener(dcl, GUI_REFRESH_INTERVAL_DEFAULT);
- } else {
- /* Sleeping interval. Not using the long default here as
- * sdl_refresh does not only update the guest screen, but
- * also checks for gui events. */
- update_displaychangelistener(dcl, 500);
- }
- }
-}
-
-static void sdl_refresh(DisplayChangeListener *dcl)
-{
- SDL_Event ev1, *ev = &ev1;
-
- if (last_vm_running != runstate_is_running()) {
- last_vm_running = runstate_is_running();
- sdl_update_caption();
- }
-
- graphic_hw_update(NULL);
- SDL_EnableUNICODE(!qemu_console_is_graphic(NULL));
-
- while (SDL_PollEvent(ev)) {
- switch (ev->type) {
- case SDL_VIDEOEXPOSE:
- sdl_update(dcl, 0, 0, real_screen->w, real_screen->h);
- break;
- case SDL_KEYDOWN:
- handle_keydown(ev);
- break;
- case SDL_KEYUP:
- handle_keyup(ev);
- break;
- case SDL_QUIT:
- if (!no_quit) {
- no_shutdown = 0;
- qemu_system_shutdown_request();
- }
- break;
- case SDL_MOUSEMOTION:
- handle_mousemotion(ev);
- break;
- case SDL_MOUSEBUTTONDOWN:
- case SDL_MOUSEBUTTONUP:
- handle_mousebutton(ev);
- break;
- case SDL_ACTIVEEVENT:
- handle_activation(ev);
- break;
- case SDL_VIDEORESIZE:
- sdl_scale(ev->resize.w, ev->resize.h);
- graphic_hw_invalidate(NULL);
- graphic_hw_update(NULL);
- break;
- default:
- break;
- }
- }
-}
-
-static void sdl_mouse_warp(DisplayChangeListener *dcl,
- int x, int y, int on)
-{
- if (on) {
- if (!guest_cursor)
- sdl_show_cursor();
- if (gui_grab || kbd_mouse_is_absolute() || absolute_enabled) {
- SDL_SetCursor(guest_sprite);
- if (!kbd_mouse_is_absolute() && !absolute_enabled)
- SDL_WarpMouse(x, y);
- }
- } else if (gui_grab)
- sdl_hide_cursor();
- guest_cursor = on;
- guest_x = x, guest_y = y;
-}
-
-static void sdl_mouse_define(DisplayChangeListener *dcl,
- QEMUCursor *c)
-{
- uint8_t *image, *mask;
- int bpl;
-
- if (guest_sprite)
- SDL_FreeCursor(guest_sprite);
-
- bpl = cursor_get_mono_bpl(c);
- image = g_malloc0(bpl * c->height);
- mask = g_malloc0(bpl * c->height);
- cursor_get_mono_image(c, 0x000000, image);
- cursor_get_mono_mask(c, 0, mask);
- guest_sprite = SDL_CreateCursor(image, mask, c->width, c->height,
- c->hot_x, c->hot_y);
- g_free(image);
- g_free(mask);
-
- if (guest_cursor &&
- (gui_grab || kbd_mouse_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_ops = {
- .dpy_name = "sdl",
- .dpy_gfx_update = sdl_update,
- .dpy_gfx_switch = sdl_switch,
- .dpy_refresh = sdl_refresh,
- .dpy_mouse_set = sdl_mouse_warp,
- .dpy_cursor_define = sdl_mouse_define,
-};
-
-void sdl_display_init(DisplayState *ds, int full_screen, int no_frame)
-{
- int flags;
- uint8_t data = 0;
- const SDL_VideoInfo *vi;
- char *filename;
-
-#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);
- if (!kbd_layout)
- exit(1);
- }
-
- if (no_frame)
- gui_noframe = 1;
-
- if (!full_screen) {
- setenv("SDL_VIDEO_ALLOW_SCREENSAVER", "1", 0);
- }
-#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.
- */
- setenv("SDL_VIDEODRIVER", "x11", 0);
-#endif
-
- /* Enable normal up/down events for Caps-Lock and Num-Lock keys.
- * This requires SDL >= 1.2.14. */
- setenv("SDL_DISABLE_LOCK_KEYS", "1", 1);
-
- flags = SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE;
- if (SDL_Init (flags)) {
- fprintf(stderr, "Could not initialize SDL(%s) - exiting\n",
- SDL_GetError());
- exit(1);
- }
- vi = SDL_GetVideoInfo();
- host_format = *(vi->vfmt);
-
- /* Load a 32x32x4 image. White pixels are transparent. */
- filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, "qemu-icon.bmp");
- if (filename) {
- SDL_Surface *image = SDL_LoadBMP(filename);
- if (image) {
- uint32_t colorkey = SDL_MapRGB(image->format, 255, 255, 255);
- SDL_SetColorKey(image, SDL_SRCCOLORKEY, colorkey);
- SDL_WM_SetIcon(image, NULL);
- }
- g_free(filename);
- }
-
- if (full_screen) {
- gui_fullscreen = 1;
- sdl_grab_start();
- }
-
- dcl = g_malloc0(sizeof(DisplayChangeListener));
- dcl->ops = &dcl_ops;
- register_displaychangelistener(dcl);
-
- mouse_mode_notifier.notify = sdl_mouse_mode_change;
- qemu_add_mouse_mode_change_notifier(&mouse_mode_notifier);
-
- sdl_update_caption();
- SDL_EnableKeyRepeat(250, 50);
- gui_grab = 0;
-
- sdl_cursor_hidden = SDL_CreateCursor(&data, &data, 8, 1, 0, 0);
- sdl_cursor_normal = SDL_GetCursor();
-
- atexit(sdl_cleanup);
-}
diff --git a/ui/sdl2-2d.c b/ui/sdl2-2d.c
new file mode 100644
index 000000000..a2ea85127
--- /dev/null
+++ b/ui/sdl2-2d.c
@@ -0,0 +1,168 @@
+/*
+ * 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 = qemu_console_surface(dcl->con);
+ SDL_Rect rect;
+ size_t surface_data_offset;
+ assert(!scon->opengl);
+
+ if (!surf) {
+ return;
+ }
+ 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 (!new_surface) {
+ 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..c73d273bf
--- /dev/null
+++ b/ui/sdl2-gl.c
@@ -0,0 +1,251 @@
+/*
+ * 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);
+}
+
+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 (!new_surface) {
+ 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->surface) {
+ 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);
+}
+
+QEMUGLContext sdl2_gl_get_current_context(DisplayChangeListener *dcl)
+{
+ SDL_GLContext sdlctx;
+
+ sdlctx = SDL_GL_GetCurrentContext();
+ return (QEMUGLContext)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);
+}
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..189d26e2a
--- /dev/null
+++ b/ui/sdl2.c
@@ -0,0 +1,918 @@
+/*
+ * 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/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),
+ };
+ 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) {
+ no_shutdown = 0;
+ 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) {
+ no_shutdown = 0;
+ 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_ctx_get_current = sdl2_gl_get_current_context,
+ .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.
+ */
+ g_setenv("SDL_VIDEODRIVER", "x11", 0);
+#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);
diff --git a/ui/sdl_keysym.h b/ui/sdl_keysym.h
deleted file mode 100644
index ee904805d..000000000
--- a/ui/sdl_keysym.h
+++ /dev/null
@@ -1,277 +0,0 @@
-
-#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", SDLK_EURO},
-
- /* modifiers */
-{"Control_L", SDLK_LCTRL},
-{"Control_R", SDLK_RCTRL},
-{"Alt_L", SDLK_LALT},
-{"Alt_R", SDLK_RALT},
-{"Caps_Lock", SDLK_CAPSLOCK},
-{"Meta_L", SDLK_LMETA},
-{"Meta_R", SDLK_RMETA},
-{"Shift_L", SDLK_LSHIFT},
-{"Shift_R", SDLK_RSHIFT},
-{"Super_L", SDLK_LSUPER},
-{"Super_R", SDLK_RSUPER},
-
- /* special keys */
-{"BackSpace", SDLK_BACKSPACE},
-{"Tab", SDLK_TAB},
-{"Return", SDLK_RETURN},
-{"Right", SDLK_RIGHT},
-{"Left", SDLK_LEFT},
-{"Up", SDLK_UP},
-{"Down", SDLK_DOWN},
-{"Page_Down", SDLK_PAGEDOWN},
-{"Page_Up", SDLK_PAGEUP},
-{"Insert", SDLK_INSERT},
-{"Delete", SDLK_DELETE},
-{"Home", SDLK_HOME},
-{"End", SDLK_END},
-{"Scroll_Lock", SDLK_SCROLLOCK},
-{"F1", SDLK_F1},
-{"F2", SDLK_F2},
-{"F3", SDLK_F3},
-{"F4", SDLK_F4},
-{"F5", SDLK_F5},
-{"F6", SDLK_F6},
-{"F7", SDLK_F7},
-{"F8", SDLK_F8},
-{"F9", SDLK_F9},
-{"F10", SDLK_F10},
-{"F11", SDLK_F11},
-{"F12", SDLK_F12},
-{"F13", SDLK_F13},
-{"F14", SDLK_F14},
-{"F15", SDLK_F15},
-{"Sys_Req", SDLK_SYSREQ},
-{"KP_0", SDLK_KP0},
-{"KP_1", SDLK_KP1},
-{"KP_2", SDLK_KP2},
-{"KP_3", SDLK_KP3},
-{"KP_4", SDLK_KP4},
-{"KP_5", SDLK_KP5},
-{"KP_6", SDLK_KP6},
-{"KP_7", SDLK_KP7},
-{"KP_8", SDLK_KP8},
-{"KP_9", SDLK_KP9},
-{"KP_Add", SDLK_KP_PLUS},
-{"KP_Decimal", SDLK_KP_PERIOD},
-{"KP_Divide", SDLK_KP_DIVIDE},
-{"KP_Enter", SDLK_KP_ENTER},
-{"KP_Equal", SDLK_KP_EQUALS},
-{"KP_Multiply", SDLK_KP_MULTIPLY},
-{"KP_Subtract", SDLK_KP_MINUS},
-{"help", SDLK_HELP},
-{"Menu", SDLK_MENU},
-{"Power", SDLK_POWER},
-{"Print", SDLK_PRINT},
-{"Mode_switch", SDLK_MODE},
-{"Multi_Key", SDLK_COMPOSE},
-{"Num_Lock", SDLK_NUMLOCK},
-{"Pause", SDLK_PAUSE},
-{"Escape", SDLK_ESCAPE},
-
-{NULL, 0},
-};
diff --git a/ui/sdl_zoom.c b/ui/sdl_zoom.c
deleted file mode 100644
index 2625c4557..000000000
--- a/ui/sdl_zoom.c
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * SDL_zoom - surface scaling
- *
- * Copyright (c) 2009 Citrix Systems, Inc.
- *
- * Derived from: SDL_rotozoom, LGPL (c) A. Schiffler from the SDL_gfx library.
- * Modifications by Stefano Stabellini.
- *
- * This work is licensed under the terms of the GNU GPL version 2.
- * See the COPYING file in the top-level directory.
- *
- */
-
-#include "sdl_zoom.h"
-#include "qemu/osdep.h"
-#include <glib.h>
-#include <stdint.h>
-#include <stdio.h>
-
-static void sdl_zoom_rgb16(SDL_Surface *src, SDL_Surface *dst, int smooth,
- SDL_Rect *dst_rect);
-static void sdl_zoom_rgb32(SDL_Surface *src, SDL_Surface *dst, int smooth,
- SDL_Rect *dst_rect);
-
-#define BPP 32
-#include "sdl_zoom_template.h"
-#undef BPP
-#define BPP 16
-#include "sdl_zoom_template.h"
-#undef BPP
-
-int sdl_zoom_blit(SDL_Surface *src_sfc, SDL_Surface *dst_sfc, int smooth,
- SDL_Rect *in_rect)
-{
- SDL_Rect zoom, src_rect;
- int extra;
-
- /* Grow the size of the modified rectangle to avoid edge artefacts */
- src_rect.x = (in_rect->x > 0) ? (in_rect->x - 1) : 0;
- src_rect.y = (in_rect->y > 0) ? (in_rect->y - 1) : 0;
-
- src_rect.w = in_rect->w + 1;
- if (src_rect.x + src_rect.w > src_sfc->w)
- src_rect.w = src_sfc->w - src_rect.x;
-
- src_rect.h = in_rect->h + 1;
- if (src_rect.y + src_rect.h > src_sfc->h)
- src_rect.h = src_sfc->h - src_rect.y;
-
- /* (x,y) : round down */
- zoom.x = (int)(((float)(src_rect.x * dst_sfc->w)) / (float)(src_sfc->w));
- zoom.y = (int)(((float)(src_rect.y * dst_sfc->h)) / (float)(src_sfc->h));
-
- /* (w,h) : round up */
- zoom.w = (int)( ((double)((src_rect.w * dst_sfc->w) + (src_sfc->w - 1))) /
- (double)(src_sfc->w));
-
- zoom.h = (int)( ((double)((src_rect.h * dst_sfc->h) + (src_sfc->h - 1))) /
- (double)(src_sfc->h));
-
- /* Account for any (x,y) rounding by adding one-source-pixel's worth
- * of destination pixels and then edge checking.
- */
-
- extra = ((dst_sfc->w-1) / src_sfc->w) + 1;
-
- if ((zoom.x + zoom.w) < (dst_sfc->w - extra))
- zoom.w += extra;
- else
- zoom.w = dst_sfc->w - zoom.x;
-
- extra = ((dst_sfc->h-1) / src_sfc->h) + 1;
-
- if ((zoom.y + zoom.h) < (dst_sfc->h - extra))
- zoom.h += extra;
- else
- zoom.h = dst_sfc->h - zoom.y;
-
- /* The rectangle (zoom.x, zoom.y, zoom.w, zoom.h) is the area on the
- * destination surface that needs to be updated.
- */
- if (src_sfc->format->BitsPerPixel == 32)
- sdl_zoom_rgb32(src_sfc, dst_sfc, smooth, &zoom);
- else if (src_sfc->format->BitsPerPixel == 16)
- sdl_zoom_rgb16(src_sfc, dst_sfc, smooth, &zoom);
- else {
- fprintf(stderr, "pixel format not supported\n");
- return -1;
- }
-
- /* Return the rectangle of the update to the caller */
- *in_rect = zoom;
-
- return 0;
-}
-
diff --git a/ui/sdl_zoom.h b/ui/sdl_zoom.h
deleted file mode 100644
index 74955bc94..000000000
--- a/ui/sdl_zoom.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * SDL_zoom - surface scaling
- *
- * Copyright (c) 2009 Citrix Systems, Inc.
- *
- * Derived from: SDL_rotozoom, LGPL (c) A. Schiffler from the SDL_gfx library.
- * Modifications by Stefano Stabellini.
- *
- * This work is licensed under the terms of the GNU GPL version 2.
- * See the COPYING file in the top-level directory.
- *
- */
-
-#ifndef SDL_zoom_h
-#define SDL_zoom_h
-
-#include <SDL.h>
-
-#define SMOOTHING_OFF 0
-#define SMOOTHING_ON 1
-
-int sdl_zoom_blit(SDL_Surface *src_sfc, SDL_Surface *dst_sfc,
- int smooth, SDL_Rect *src_rect);
-
-#endif /* SDL_zoom_h */
diff --git a/ui/sdl_zoom_template.h b/ui/sdl_zoom_template.h
deleted file mode 100644
index 3bb508b51..000000000
--- a/ui/sdl_zoom_template.h
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * SDL_zoom_template - surface scaling
- *
- * Copyright (c) 2009 Citrix Systems, Inc.
- *
- * Derived from: SDL_rotozoom, LGPL (c) A. Schiffler from the SDL_gfx library.
- * Modifications by Stefano Stabellini.
- *
- * This work is licensed under the terms of the GNU GPL version 2.
- * See the COPYING file in the top-level directory.
- *
- */
-
-#if BPP == 16
-#define SDL_TYPE Uint16
-#elif BPP == 32
-#define SDL_TYPE Uint32
-#else
-#error unsupport depth
-#endif
-
-/*
- * Simple helper functions to make the code looks nicer
- *
- * Assume spf = source SDL_PixelFormat
- * dpf = dest SDL_PixelFormat
- *
- */
-#define getRed(color) (((color) & spf->Rmask) >> spf->Rshift)
-#define getGreen(color) (((color) & spf->Gmask) >> spf->Gshift)
-#define getBlue(color) (((color) & spf->Bmask) >> spf->Bshift)
-#define getAlpha(color) (((color) & spf->Amask) >> spf->Ashift)
-
-#define setRed(r, pcolor) do { \
- *pcolor = ((*pcolor) & (~(dpf->Rmask))) + \
- (((r) & (dpf->Rmask >> dpf->Rshift)) << dpf->Rshift); \
-} while (0);
-
-#define setGreen(g, pcolor) do { \
- *pcolor = ((*pcolor) & (~(dpf->Gmask))) + \
- (((g) & (dpf->Gmask >> dpf->Gshift)) << dpf->Gshift); \
-} while (0);
-
-#define setBlue(b, pcolor) do { \
- *pcolor = ((*pcolor) & (~(dpf->Bmask))) + \
- (((b) & (dpf->Bmask >> dpf->Bshift)) << dpf->Bshift); \
-} while (0);
-
-#define setAlpha(a, pcolor) do { \
- *pcolor = ((*pcolor) & (~(dpf->Amask))) + \
- (((a) & (dpf->Amask >> dpf->Ashift)) << dpf->Ashift); \
-} while (0);
-
-static void glue(sdl_zoom_rgb, BPP)(SDL_Surface *src, SDL_Surface *dst, int smooth,
- SDL_Rect *dst_rect)
-{
- int x, y, sx, sy, *sax, *say, *csax, *csay, csx, csy, ex, ey, t1, t2, sstep, sstep_jump;
- SDL_TYPE *c00, *c01, *c10, *c11, *sp, *csp, *dp;
- int d_gap;
- SDL_PixelFormat *spf = src->format;
- SDL_PixelFormat *dpf = dst->format;
-
- if (smooth) {
- /* For interpolation: assume source dimension is one pixel.
- * Smaller here to avoid overflow on right and bottom edge.
- */
- sx = (int) (65536.0 * (float) (src->w - 1) / (float) dst->w);
- sy = (int) (65536.0 * (float) (src->h - 1) / (float) dst->h);
- } else {
- sx = (int) (65536.0 * (float) src->w / (float) dst->w);
- sy = (int) (65536.0 * (float) src->h / (float) dst->h);
- }
-
- sax = g_new(int, dst->w + 1);
- say = g_new(int, dst->h + 1);
-
- sp = csp = (SDL_TYPE *) src->pixels;
- dp = (SDL_TYPE *) (dst->pixels + dst_rect->y * dst->pitch +
- dst_rect->x * dst->format->BytesPerPixel);
-
- csx = 0;
- csax = sax;
- for (x = 0; x <= dst->w; x++) {
- *csax = csx;
- csax++;
- csx &= 0xffff;
- csx += sx;
- }
- csy = 0;
- csay = say;
- for (y = 0; y <= dst->h; y++) {
- *csay = csy;
- csay++;
- csy &= 0xffff;
- csy += sy;
- }
-
- d_gap = dst->pitch - dst_rect->w * dst->format->BytesPerPixel;
-
- if (smooth) {
- csay = say;
- for (y = 0; y < dst_rect->y; y++) {
- csay++;
- sstep = (*csay >> 16) * src->pitch;
- csp = (SDL_TYPE *) ((Uint8 *) csp + sstep);
- }
-
- /* Calculate sstep_jump */
- csax = sax;
- sstep_jump = 0;
- for (x = 0; x < dst_rect->x; x++) {
- csax++;
- sstep = (*csax >> 16);
- sstep_jump += sstep;
- }
-
- for (y = 0; y < dst_rect->h ; y++) {
- /* Setup colour source pointers */
- c00 = csp + sstep_jump;
- c01 = c00 + 1;
- c10 = (SDL_TYPE *) ((Uint8 *) csp + src->pitch) + sstep_jump;
- c11 = c10 + 1;
- csax = sax + dst_rect->x;
-
- for (x = 0; x < dst_rect->w; x++) {
-
- /* Interpolate colours */
- ex = (*csax & 0xffff);
- ey = (*csay & 0xffff);
- t1 = ((((getRed(*c01) - getRed(*c00)) * ex) >> 16) +
- getRed(*c00)) & (dpf->Rmask >> dpf->Rshift);
- t2 = ((((getRed(*c11) - getRed(*c10)) * ex) >> 16) +
- getRed(*c10)) & (dpf->Rmask >> dpf->Rshift);
- setRed((((t2 - t1) * ey) >> 16) + t1, dp);
- t1 = ((((getGreen(*c01) - getGreen(*c00)) * ex) >> 16) +
- getGreen(*c00)) & (dpf->Gmask >> dpf->Gshift);
- t2 = ((((getGreen(*c11) - getGreen(*c10)) * ex) >> 16) +
- getGreen(*c10)) & (dpf->Gmask >> dpf->Gshift);
- setGreen((((t2 - t1) * ey) >> 16) + t1, dp);
- t1 = ((((getBlue(*c01) - getBlue(*c00)) * ex) >> 16) +
- getBlue(*c00)) & (dpf->Bmask >> dpf->Bshift);
- t2 = ((((getBlue(*c11) - getBlue(*c10)) * ex) >> 16) +
- getBlue(*c10)) & (dpf->Bmask >> dpf->Bshift);
- setBlue((((t2 - t1) * ey) >> 16) + t1, dp);
- t1 = ((((getAlpha(*c01) - getAlpha(*c00)) * ex) >> 16) +
- getAlpha(*c00)) & (dpf->Amask >> dpf->Ashift);
- t2 = ((((getAlpha(*c11) - getAlpha(*c10)) * ex) >> 16) +
- getAlpha(*c10)) & (dpf->Amask >> dpf->Ashift);
- setAlpha((((t2 - t1) * ey) >> 16) + t1, dp);
-
- /* Advance source pointers */
- csax++;
- sstep = (*csax >> 16);
- c00 += sstep;
- c01 += sstep;
- c10 += sstep;
- c11 += sstep;
- /* Advance destination pointer */
- dp++;
- }
- /* Advance source pointer */
- csay++;
- csp = (SDL_TYPE *) ((Uint8 *) csp + (*csay >> 16) * src->pitch);
- /* Advance destination pointers */
- dp = (SDL_TYPE *) ((Uint8 *) dp + d_gap);
- }
-
-
- } else {
- csay = say;
-
- for (y = 0; y < dst_rect->y; y++) {
- csay++;
- sstep = (*csay >> 16) * src->pitch;
- csp = (SDL_TYPE *) ((Uint8 *) csp + sstep);
- }
-
- /* Calculate sstep_jump */
- csax = sax;
- sstep_jump = 0;
- for (x = 0; x < dst_rect->x; x++) {
- csax++;
- sstep = (*csax >> 16);
- sstep_jump += sstep;
- }
-
- for (y = 0 ; y < dst_rect->h ; y++) {
- sp = csp + sstep_jump;
- csax = sax + dst_rect->x;
-
- for (x = 0; x < dst_rect->w; x++) {
-
- /* Draw */
- *dp = *sp;
-
- /* Advance source pointers */
- csax++;
- sstep = (*csax >> 16);
- sp += sstep;
-
- /* Advance destination pointer */
- dp++;
- }
- /* Advance source pointers */
- csay++;
- sstep = (*csay >> 16) * src->pitch;
- csp = (SDL_TYPE *) ((Uint8 *) csp + sstep);
-
- /* Advance destination pointer */
- dp = (SDL_TYPE *) ((Uint8 *) dp + d_gap);
- }
- }
-
- g_free(sax);
- g_free(say);
-}
-
-#undef SDL_TYPE
-
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..4325ac2d9
--- /dev/null
+++ b/ui/spice-app.c
@@ -0,0 +1,223 @@
+/*
+ * 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 "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 CONFIG_OPENGL
+ 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);
diff --git a/ui/spice-core.c b/ui/spice-core.c
index bd7a248f9..eea52f538 100644
--- a/ui/spice-core.c
+++ b/ui/spice-core.c
@@ -15,28 +15,27 @@
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
+#include "qemu/osdep.h"
#include <spice.h>
-#include <spice-experimental.h>
-#include <netdb.h>
#include "sysemu/sysemu.h"
-
-#include "qemu-common.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 "qmp-commands.h"
-#include "qapi/qmp/qint.h"
-#include "qapi/qmp/qbool.h"
-#include "qapi/qmp/qstring.h"
-#include "qapi/qmp/qjson.h"
+#include "qapi/error.h"
+#include "qapi/qapi-commands-ui.h"
+#include "qapi/qapi-events-ui.h"
#include "qemu/notify.h"
-#include "migration/migration.h"
-#include "monitor/monitor.h"
-#include "hw/hw.h"
+#include "qemu/option.h"
+#include "migration/misc.h"
+#include "hw/pci/pci_bus.h"
#include "ui/spice-display.h"
/* core bits */
@@ -47,53 +46,46 @@ static const char *auth = "spice";
static char *auth_passwd;
static time_t auth_expires = TIME_MAX;
static int spice_migration_completed;
-int using_spice = 0;
-int spice_displays;
+static int spice_display_is_running;
+static int spice_have_target_host;
static QemuThread me;
struct SpiceTimer {
QEMUTimer *timer;
- QTAILQ_ENTRY(SpiceTimer) next;
};
-static QTAILQ_HEAD(, SpiceTimer) timers = QTAILQ_HEAD_INITIALIZER(timers);
static SpiceTimer *timer_add(SpiceTimerFunc func, void *opaque)
{
SpiceTimer *timer;
timer = g_malloc0(sizeof(*timer));
- timer->timer = qemu_new_timer_ms(rt_clock, func, opaque);
- QTAILQ_INSERT_TAIL(&timers, timer, next);
+ timer->timer = timer_new_ms(QEMU_CLOCK_REALTIME, func, opaque);
return timer;
}
static void timer_start(SpiceTimer *timer, uint32_t ms)
{
- qemu_mod_timer(timer->timer, qemu_get_clock_ms(rt_clock) + ms);
+ timer_mod(timer->timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + ms);
}
static void timer_cancel(SpiceTimer *timer)
{
- qemu_del_timer(timer->timer);
+ timer_del(timer->timer);
}
static void timer_remove(SpiceTimer *timer)
{
- qemu_del_timer(timer->timer);
- qemu_free_timer(timer->timer);
- QTAILQ_REMOVE(&timers, timer, next);
+ timer_del(timer->timer);
+ timer_free(timer->timer);
g_free(timer);
}
struct SpiceWatch {
int fd;
- int event_mask;
SpiceWatchFunc func;
void *opaque;
- QTAILQ_ENTRY(SpiceWatch) next;
};
-static QTAILQ_HEAD(, SpiceWatch) watches = QTAILQ_HEAD_INITIALIZER(watches);
static void watch_read(void *opaque)
{
@@ -112,11 +104,10 @@ static void watch_update_mask(SpiceWatch *watch, int event_mask)
IOHandler *on_read = NULL;
IOHandler *on_write = NULL;
- watch->event_mask = event_mask;
- if (watch->event_mask & SPICE_WATCH_EVENT_READ) {
+ if (event_mask & SPICE_WATCH_EVENT_READ) {
on_read = watch_read;
}
- if (watch->event_mask & SPICE_WATCH_EVENT_WRITE) {
+ if (event_mask & SPICE_WATCH_EVENT_WRITE) {
on_write = watch_write;
}
qemu_set_fd_handler(watch->fd, on_read, on_write, watch);
@@ -130,7 +121,6 @@ static SpiceWatch *watch_add(int fd, int event_mask, SpiceWatchFunc func, void *
watch->fd = fd;
watch->func = func;
watch->opaque = opaque;
- QTAILQ_INSERT_TAIL(&watches, watch, next);
watch_update_mask(watch, event_mask);
return watch;
@@ -139,7 +129,6 @@ static SpiceWatch *watch_add(int fd, int event_mask, SpiceWatchFunc func, void *
static void watch_remove(SpiceWatch *watch)
{
qemu_set_fd_handler(watch->fd, NULL, NULL, NULL);
- QTAILQ_REMOVE(&watches, watch, next);
g_free(watch);
}
@@ -173,39 +162,32 @@ static void channel_list_del(SpiceChannelEventInfo *info)
}
}
-static void add_addr_info(QDict *dict, struct sockaddr *addr, int len)
+static void add_addr_info(SpiceBasicInfo *info, struct sockaddr *addr, int len)
{
char host[NI_MAXHOST], port[NI_MAXSERV];
- const char *family;
getnameinfo(addr, len, host, sizeof(host), port, sizeof(port),
NI_NUMERICHOST | NI_NUMERICSERV);
- family = inet_strfamily(addr->sa_family);
- qdict_put(dict, "host", qstring_from_str(host));
- qdict_put(dict, "port", qstring_from_str(port));
- qdict_put(dict, "family", qstring_from_str(family));
+ info->host = g_strdup(host);
+ info->port = g_strdup(port);
+ info->family = inet_netfamily(addr->sa_family);
}
-static void add_channel_info(QDict *dict, SpiceChannelEventInfo *info)
+static void add_channel_info(SpiceChannel *sc, SpiceChannelEventInfo *info)
{
int tls = info->flags & SPICE_CHANNEL_EVENT_FLAG_TLS;
- qdict_put(dict, "connection-id", qint_from_int(info->connection_id));
- qdict_put(dict, "channel-type", qint_from_int(info->type));
- qdict_put(dict, "channel-id", qint_from_int(info->id));
- qdict_put(dict, "tls", qbool_from_int(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)
{
- static const int qevent[] = {
- [ SPICE_CHANNEL_EVENT_CONNECTED ] = QEVENT_SPICE_CONNECTED,
- [ SPICE_CHANNEL_EVENT_INITIALIZED ] = QEVENT_SPICE_INITIALIZED,
- [ SPICE_CHANNEL_EVENT_DISCONNECTED ] = QEVENT_SPICE_DISCONNECTED,
- };
- QDict *server, *client;
- QObject *data;
+ SpiceServerInfo *server = g_malloc0(sizeof(*server));
+ SpiceChannel *client = g_malloc0(sizeof(*client));
/*
* Spice server might have called us from spice worker thread
@@ -221,36 +203,47 @@ static void channel_event(int event, SpiceChannelEventInfo *info)
qemu_mutex_lock_iothread();
}
- client = qdict_new();
- server = qdict_new();
-
if (info->flags & SPICE_CHANNEL_EVENT_FLAG_ADDR_EXT) {
- add_addr_info(client, (struct sockaddr *)&info->paddr_ext,
+ add_addr_info(qapi_SpiceChannel_base(client),
+ (struct sockaddr *)&info->paddr_ext,
info->plen_ext);
- add_addr_info(server, (struct sockaddr *)&info->laddr_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__);
}
- if (event == SPICE_CHANNEL_EVENT_INITIALIZED) {
- qdict_put(server, "auth", qstring_from_str(auth));
+ 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);
- }
- if (event == SPICE_CHANNEL_EVENT_DISCONNECTED) {
+ 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;
}
- data = qobject_from_jsonf("{ 'client': %p, 'server': %p }",
- QOBJECT(client), QOBJECT(server));
- monitor_protocol_event(qevent[event], data);
- qobject_decref(data);
-
if (need_lock) {
qemu_mutex_unlock_iothread();
}
+
+ qapi_free_SpiceServerInfo(server);
+ qapi_free_SpiceChannel(client);
}
static SpiceCoreInterface core_interface = {
@@ -271,14 +264,6 @@ static SpiceCoreInterface core_interface = {
.channel_event = channel_event,
};
-typedef struct SpiceMigration {
- SpiceMigrateInstance sin;
- struct {
- MonitorCompletion *cb;
- void *opaque;
- } connect_complete;
-} SpiceMigration;
-
static void migrate_connect_complete_cb(SpiceMigrateInstance *sin);
static void migrate_end_complete_cb(SpiceMigrateInstance *sin);
@@ -291,20 +276,16 @@ static const SpiceMigrateInterface migrate_interface = {
.migrate_end_complete = migrate_end_complete_cb,
};
-static SpiceMigration spice_migrate;
+static SpiceMigrateInstance spice_migrate;
static void migrate_connect_complete_cb(SpiceMigrateInstance *sin)
{
- SpiceMigration *sm = container_of(sin, SpiceMigration, sin);
- if (sm->connect_complete.cb) {
- sm->connect_complete.cb(sm->connect_complete.opaque, NULL);
- }
- sm->connect_complete.cb = NULL;
+ /* nothing, but libspice-server expects this cb being present. */
}
static void migrate_end_complete_cb(SpiceMigrateInstance *sin)
{
- monitor_protocol_event(QEVENT_SPICE_MIGRATE_COMPLETED, NULL);
+ qapi_event_send_spice_migrate_completed();
spice_migration_completed = true;
}
@@ -383,23 +364,19 @@ static SpiceChannelList *qmp_query_spice_channels(void)
struct sockaddr *paddr;
socklen_t plen;
+ assert(item->info->flags & SPICE_CHANNEL_EVENT_FLAG_ADDR_EXT);
+
chan = g_malloc0(sizeof(*chan));
chan->value = g_malloc0(sizeof(*chan->value));
- if (item->info->flags & SPICE_CHANNEL_EVENT_FLAG_ADDR_EXT) {
- paddr = (struct sockaddr *)&item->info->paddr_ext;
- plen = item->info->plen_ext;
- } else {
- paddr = &item->info->paddr;
- plen = item->info->plen;
- }
-
+ 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->value->host = g_strdup(host);
chan->value->port = g_strdup(port);
- chan->value->family = g_strdup(inet_strfamily(paddr->sa_family));
+ chan->value->family = inet_netfamily(paddr->sa_family);
chan->value->connection_id = item->info->connection_id;
chan->value->channel_type = item->info->type;
@@ -421,6 +398,7 @@ static SpiceChannelList *qmp_query_spice_channels(void)
static QemuOptsList qemu_spice_opts = {
.name = "spice",
.head = QTAILQ_HEAD_INITIALIZER(qemu_spice_opts.head),
+ .merge_lists = true,
.desc = {
{
.name = "port",
@@ -437,6 +415,11 @@ static QemuOptsList qemu_spice_opts = {
},{
.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,
@@ -497,21 +480,37 @@ static QemuOptsList qemu_spice_opts = {
},{
.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 */ }
},
};
-SpiceInfo *qmp_query_spice(Error **errp)
+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;
- char version_string[20]; /* 12 = |255.255.255\0| is the max */
+ unsigned int major;
+ unsigned int minor;
+ unsigned int micro;
info = g_malloc0(sizeof(*info));
@@ -531,14 +530,13 @@ SpiceInfo *qmp_query_spice(Error **errp)
info->auth = g_strdup(auth);
info->has_host = true;
- info->host = g_strdup(addr ? addr : "0.0.0.0");
+ info->host = g_strdup(addr ? addr : "*");
info->has_compiled_version = true;
- snprintf(version_string, sizeof(version_string), "%d.%d.%d",
- (SPICE_SERVER_VERSION & 0xff0000) >> 16,
- (SPICE_SERVER_VERSION & 0xff00) >> 8,
- SPICE_SERVER_VERSION & 0xff);
- info->compiled_version = g_strdup(version_string);
+ 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;
@@ -564,29 +562,35 @@ 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)) {
+ } 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,
- MonitorCompletion *cb, void *opaque)
+ const char *subject)
{
int ret;
- spice_migrate.connect_complete.cb = cb;
- spice_migrate.connect_complete.opaque = opaque;
ret = spice_server_migrate_connect(spice_server, hostname,
port, tls_port, subject);
+ spice_have_target_host = true;
return ret;
}
-static int add_channel(const char *name, const char *value, void *opaque)
+static int add_channel(void *opaque, const char *name, const char *value,
+ Error **errp)
{
int security = 0;
int rc;
@@ -594,9 +598,9 @@ static int add_channel(const char *name, const char *value, void *opaque)
if (strcmp(name, "tls-channel") == 0) {
int *tls_port = opaque;
if (!*tls_port) {
- error_report("spice: tried to setup tls-channel"
- " without specifying a TLS port");
- exit(1);
+ error_setg(errp, "spice: tried to setup tls-channel"
+ " without specifying a TLS port");
+ return -1;
}
security = SPICE_CHANNEL_SECURITY_SSL;
}
@@ -612,8 +616,9 @@ static int add_channel(const char *name, const char *value, void *opaque)
rc = spice_server_set_channel_security(spice_server, value, security);
}
if (rc != 0) {
- error_report("spice: failed to set channel security for %s", value);
- exit(1);
+ error_setg(errp, "spice: failed to set channel security for %s",
+ value);
+ return -1;
}
return 0;
}
@@ -623,14 +628,12 @@ static void vm_change_state_handler(void *opaque, int running,
{
if (running) {
qemu_spice_display_start();
- spice_server_vm_start(spice_server);
- } else {
- spice_server_vm_stop(spice_server);
+ } else if (state != RUN_STATE_PAUSED) {
qemu_spice_display_stop();
}
}
-void qemu_spice_init(void)
+static void qemu_spice_init(void)
{
QemuOpts *opts = QTAILQ_FIRST(&qemu_spice_opts.head);
const char *password, *str, *x509_dir, *addr,
@@ -640,7 +643,7 @@ void qemu_spice_init(void)
char *x509_key_file = NULL,
*x509_cert_file = NULL,
*x509_cacert_file = NULL;
- int port, tls_port, len, addr_flags;
+ int port, tls_port, addr_flags;
spice_image_compression_t compression;
spice_wan_compression_t wan_compr;
bool seamless_migration;
@@ -652,10 +655,6 @@ void qemu_spice_init(void)
}
port = qemu_opt_get_number(opts, "port", 0);
tls_port = qemu_opt_get_number(opts, "tls-port", 0);
- if (!port && !tls_port) {
- error_report("neither port nor tls-port specified for spice");
- exit(1);
- }
if (port < 0 || port > 65535) {
error_report("spice port is out of range");
exit(1);
@@ -668,33 +667,32 @@ void qemu_spice_init(void)
if (tls_port) {
x509_dir = qemu_opt_get(opts, "x509-dir");
- if (NULL == x509_dir) {
+ if (!x509_dir) {
x509_dir = ".";
}
- len = strlen(x509_dir) + 32;
str = qemu_opt_get(opts, "x509-key-file");
if (str) {
x509_key_file = g_strdup(str);
} else {
- x509_key_file = g_malloc(len);
- snprintf(x509_key_file, len, "%s/%s", x509_dir, X509_SERVER_KEY_FILE);
+ 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_malloc(len);
- snprintf(x509_cert_file, len, "%s/%s", x509_dir, X509_SERVER_CERT_FILE);
+ 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_malloc(len);
- snprintf(x509_cacert_file, len, "%s/%s", x509_dir, X509_CA_CERT_FILE);
+ x509_cacert_file = g_strdup_printf("%s/%s", x509_dir,
+ X509_CA_CERT_FILE);
}
x509_key_password = qemu_opt_get(opts, "x509-key-password");
@@ -708,6 +706,10 @@ void qemu_spice_init(void)
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();
@@ -725,14 +727,14 @@ void qemu_spice_init(void)
tls_ciphers);
}
if (password) {
- spice_server_set_ticket(spice_server, password, 0, 0, 0);
+ qemu_spice.set_passwd(password, false, false);
}
if (qemu_opt_get_bool(opts, "sasl", 0)) {
- if (spice_server_set_sasl_appname(spice_server, "qemu") == -1 ||
- spice_server_set_sasl(spice_server, 1) == -1) {
+ 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";
@@ -744,13 +746,7 @@ void qemu_spice_init(void)
}
if (qemu_opt_get_bool(opts, "disable-agent-file-xfer", 0)) {
-#if SPICE_SERVER_VERSION >= 0x000c04
spice_server_set_agent_file_xfer(spice_server, false);
-#else
- error_report("this qemu build does not support the "
- "\"disable-agent-file-xfer\" option");
- exit(1);
-#endif
}
compression = SPICE_IMAGE_COMPRESS_AUTO_GLZ;
@@ -778,6 +774,8 @@ void qemu_spice_init(void)
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
@@ -785,14 +783,15 @@ void qemu_spice_init(void)
spice_server_set_playback_compression
(spice_server, qemu_opt_get_bool(opts, "playback-compression", 1));
- qemu_opt_foreach(opts, add_channel, &tls_port, 0);
+ qemu_opt_foreach(opts, add_channel, &tls_port, &error_fatal);
- spice_server_set_name(spice_server, qemu_name);
- spice_server_set_uuid(spice_server, qemu_uuid);
+ 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);
- if (0 != spice_server_init(spice_server, &core_interface)) {
+ 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);
};
@@ -800,25 +799,37 @@ void qemu_spice_init(void)
migration_state.notify = migration_state_notifier;
add_migration_state_change_notifier(&migration_state);
- spice_migrate.sin.base.sif = &migrate_interface.base;
- spice_migrate.connect_complete.cb = NULL;
- qemu_spice_add_interface(&spice_migrate.sin.base);
+ spice_migrate.base.sif = &migrate_interface.base;
+ qemu_spice.add_interface(&spice_migrate.base);
qemu_spice_input_init();
- qemu_spice_audio_init();
qemu_add_vm_change_state_handler(vm_change_state_handler, NULL);
+ qemu_spice_display_stop();
g_free(x509_key_file);
g_free(x509_cert_file);
g_free(x509_cacert_file);
-#if SPICE_SERVER_VERSION >= 0x000c02
- qemu_spice_register_ports();
+#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
}
-int qemu_spice_add_interface(SpiceBaseInstance *sin)
+static int qemu_spice_add_interface(SpiceBaseInstance *sin)
{
if (!spice_server) {
if (QTAILQ_FIRST(&qemu_spice_opts.head) != NULL) {
@@ -833,15 +844,82 @@ int qemu_spice_add_interface(SpiceBaseInstance *sin)
* 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);
}
- if (strcmp(sin->sif->type, SPICE_INTERFACE_QXL) == 0) {
- spice_displays++;
+ 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);
}
- return spice_server_add_interface(spice_server, sin);
+ 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)
@@ -863,21 +941,25 @@ static int qemu_spice_set_ticket(bool fail_if_conn, bool disconnect_if_conn)
fail_if_conn, disconnect_if_conn);
}
-int qemu_spice_set_passwd(const char *passwd,
- bool fail_if_conn, bool 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);
}
-int qemu_spice_set_pw_expire(time_t expires)
+static int qemu_spice_set_pw_expire(time_t expires)
{
auth_expires = expires;
return qemu_spice_set_ticket(false, false);
}
-int qemu_spice_display_add_client(int csock, int skipauth, int tls)
+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);
@@ -886,8 +968,45 @@ int qemu_spice_display_add_client(int csock, int skipauth, int tls)
}
}
+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);
}
-machine_init(spice_register_config);
+opts_init(spice_register_config);
diff --git a/ui/spice-display.c b/ui/spice-display.c
index 82d8b9f9a..0178d5766 100644
--- a/ui/spice-display.c
+++ b/ui/spice-display.c
@@ -15,29 +15,19 @@
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
-#include "qemu-common.h"
+#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 "monitor/monitor.h"
#include "ui/console.h"
-#include "sysemu/sysemu.h"
#include "trace.h"
#include "ui/spice-display.h"
-static int debug = 0;
-
-static void GCC_FMT_ATTR(2, 3) dprint(int level, const char *fmt, ...)
-{
- va_list args;
-
- if (level <= debug) {
- va_start(args, fmt);
- vfprintf(stderr, fmt, args);
- va_end(args);
- }
-}
+bool spice_opengl;
int qemu_spice_rect_is_empty(const QXLRect* r)
{
@@ -83,14 +73,14 @@ void qemu_spice_add_memslot(SimpleSpiceDisplay *ssd, QXLDevMemSlot *memslot,
(uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO,
QXL_IO_MEMSLOT_ADD_ASYNC));
} else {
- ssd->worker->add_memslot(ssd->worker, memslot);
+ 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);
- ssd->worker->del_memslot(ssd->worker, gid, sid);
+ spice_qxl_del_memslot(&ssd->qxl, gid, sid);
}
void qemu_spice_create_primary_surface(SimpleSpiceDisplay *ssd, uint32_t id,
@@ -103,7 +93,7 @@ void qemu_spice_create_primary_surface(SimpleSpiceDisplay *ssd, uint32_t id,
(uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO,
QXL_IO_CREATE_PRIMARY_ASYNC));
} else {
- ssd->worker->create_primary_surface(ssd->worker, id, surface);
+ spice_qxl_create_primary_surface(&ssd->qxl, id, surface);
}
}
@@ -116,31 +106,14 @@ void qemu_spice_destroy_primary_surface(SimpleSpiceDisplay *ssd,
(uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO,
QXL_IO_DESTROY_PRIMARY_ASYNC));
} else {
- ssd->worker->destroy_primary_surface(ssd->worker, id);
+ spice_qxl_destroy_primary_surface(&ssd->qxl, id);
}
}
void qemu_spice_wakeup(SimpleSpiceDisplay *ssd)
{
trace_qemu_spice_wakeup(ssd->qxl.id);
- ssd->worker->wakeup(ssd->worker);
-}
-
-static int spice_display_is_running;
-
-void qemu_spice_display_start(void)
-{
- spice_display_is_running = true;
-}
-
-void qemu_spice_display_stop(void)
-{
- spice_display_is_running = false;
-}
-
-int qemu_spice_display_is_running(SimpleSpiceDisplay *ssd)
-{
- return spice_display_is_running;
+ spice_qxl_wakeup(&ssd->qxl);
}
static void qemu_spice_create_one_update(SimpleSpiceDisplay *ssd,
@@ -170,7 +143,7 @@ static void qemu_spice_create_one_update(SimpleSpiceDisplay *ssd,
drawable->bbox = *rect;
drawable->clip.type = SPICE_CLIP_TYPE_NONE;
drawable->effect = QXL_EFFECT_OPAQUE;
- drawable->release_info.id = (uintptr_t)update;
+ drawable->release_info.id = (uintptr_t)(&update->ext);
drawable->type = QXL_DRAW_COPY;
drawable->surfaces_dest[0] = -1;
drawable->surfaces_dest[1] = -1;
@@ -195,7 +168,7 @@ static void qemu_spice_create_one_update(SimpleSpiceDisplay *ssd,
image->bitmap.palette = 0;
image->bitmap.format = SPICE_BITMAP_FMT_32BIT;
- dest = pixman_image_create_bits(PIXMAN_x8r8g8b8, bw, bh,
+ 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,
@@ -214,9 +187,9 @@ static void qemu_spice_create_one_update(SimpleSpiceDisplay *ssd,
static void qemu_spice_create_update(SimpleSpiceDisplay *ssd)
{
static const int blksize = 32;
- int blocks = (surface_width(ssd->ds) + blksize - 1) / blksize;
+ int blocks = DIV_ROUND_UP(surface_width(ssd->ds), blksize);
int dirty_top[blocks];
- int y, yoff, x, xoff, blk, bw;
+ int y, yoff1, yoff2, x, xoff, blk, bw;
int bpp = surface_bytes_per_pixel(ssd->ds);
uint8_t *guest, *mirror;
@@ -224,12 +197,6 @@ static void qemu_spice_create_update(SimpleSpiceDisplay *ssd)
return;
};
- if (ssd->surface == NULL) {
- ssd->surface = pixman_image_ref(ssd->ds->image);
- ssd->mirror = qemu_pixman_mirror_create(ssd->ds->format,
- ssd->ds->image);
- }
-
for (blk = 0; blk < blocks; blk++) {
dirty_top[blk] = -1;
}
@@ -237,13 +204,14 @@ static void qemu_spice_create_update(SimpleSpiceDisplay *ssd)
guest = surface_data(ssd->ds);
mirror = (void *)pixman_image_get_data(ssd->mirror);
for (y = ssd->dirty.top; y < ssd->dirty.bottom; y++) {
- yoff = y * surface_stride(ssd->ds);
+ 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 + yoff + xoff,
- mirror + yoff + xoff,
+ if (memcmp(guest + yoff1 + xoff,
+ mirror + yoff2 + xoff,
bw * bpp) == 0) {
if (dirty_top[blk] != -1) {
QXLRect update = {
@@ -281,6 +249,52 @@ static void qemu_spice_create_update(SimpleSpiceDisplay *ssd)
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
@@ -297,8 +311,6 @@ void qemu_spice_create_host_memslot(SimpleSpiceDisplay *ssd)
{
QXLDevMemSlot memslot;
- dprint(1, "%s:\n", __FUNCTION__);
-
memset(&memslot, 0, sizeof(memslot));
memslot.slot_group_id = MEMSLOT_GROUP_HOST;
memslot.virt_end = ~0;
@@ -308,11 +320,19 @@ void qemu_spice_create_host_memslot(SimpleSpiceDisplay *ssd)
void qemu_spice_create_host_primary(SimpleSpiceDisplay *ssd)
{
QXLDevSurfaceCreate surface;
+ uint64_t surface_size;
memset(&surface, 0, sizeof(surface));
- dprint(1, "%s: %dx%d\n", __FUNCTION__,
- surface_width(ssd->ds), surface_height(ssd->ds));
+ 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);
@@ -329,8 +349,6 @@ void qemu_spice_create_host_primary(SimpleSpiceDisplay *ssd)
void qemu_spice_destroy_host_primary(SimpleSpiceDisplay *ssd)
{
- dprint(1, "%s:\n", __FUNCTION__);
-
qemu_spice_destroy_primary_surface(ssd, 0, QXL_SYNC);
}
@@ -343,8 +361,6 @@ void qemu_spice_display_init_common(SimpleSpiceDisplay *ssd)
if (ssd->num_surfaces == 0) {
ssd->num_surfaces = 1024;
}
- ssd->bufsize = (16 * 1024 * 1024);
- ssd->buf = g_malloc(ssd->bufsize);
}
/* display listener callbacks */
@@ -354,7 +370,7 @@ void qemu_spice_display_update(SimpleSpiceDisplay *ssd,
{
QXLRect update_area;
- dprint(2, "%s: x %d y %d w %d h %d\n", __FUNCTION__, x, y, w, h);
+ 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;
@@ -370,8 +386,33 @@ void qemu_spice_display_switch(SimpleSpiceDisplay *ssd,
DisplaySurface *surface)
{
SimpleSpiceUpdate *update;
+ bool need_destroy;
+
+ if (surface && 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;
+ }
- dprint(1, "%s:\n", __FUNCTION__);
+ /* full mode switch */
+ trace_qemu_spice_display_surface(ssd->qxl.id,
+ surface ? surface_width(surface) : 0,
+ surface ? surface_height(surface) : 0,
+ false);
memset(&ssd->dirty, 0, sizeof(ssd->dirty));
if (ssd->surface) {
@@ -382,52 +423,78 @@ void qemu_spice_display_switch(SimpleSpiceDisplay *ssd,
}
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);
- qemu_spice_destroy_host_primary(ssd);
- qemu_spice_create_host_primary(ssd);
+ 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_unlocked(SimpleSpiceDisplay *ssd)
+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);
- dpy_cursor_define(ssd->dcl.con, ssd->cursor);
- cursor_put(ssd->cursor);
- ssd->cursor = NULL;
+ 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);
- dpy_mouse_set(ssd->dcl.con, ssd->mouse_x, ssd->mouse_y, 1);
+ 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)
{
- dprint(3, "%s:\n", __func__);
graphic_hw_update(ssd->dcl.con);
- qemu_mutex_lock(&ssd->lock);
- if (QTAILQ_EMPTY(&ssd->updates) && ssd->ds) {
- qemu_spice_create_update(ssd);
- ssd->notify++;
+ WITH_QEMU_LOCK_GUARD(&ssd->lock) {
+ if (QTAILQ_EMPTY(&ssd->updates) && ssd->ds) {
+ qemu_spice_create_update(ssd);
+ ssd->notify++;
+ }
}
- qemu_spice_cursor_refresh_unlocked(ssd);
- qemu_mutex_unlock(&ssd->lock);
+ trace_qemu_spice_display_refresh(ssd->qxl.id, ssd->notify);
if (ssd->notify) {
ssd->notify = 0;
qemu_spice_wakeup(ssd);
- dprint(2, "%s: notify\n", __FUNCTION__);
}
}
@@ -435,23 +502,20 @@ void qemu_spice_display_refresh(SimpleSpiceDisplay *ssd)
static void interface_attach_worker(QXLInstance *sin, QXLWorker *qxl_worker)
{
- SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl);
-
- dprint(1, "%s:\n", __FUNCTION__);
- ssd->worker = qxl_worker;
+ /* nothing to do */
}
static void interface_set_compression_level(QXLInstance *sin, int level)
{
- dprint(1, "%s:\n", __FUNCTION__);
/* nothing to do */
}
+#if SPICE_NEEDS_SET_MM_TIME
static void interface_set_mm_time(QXLInstance *sin, uint32_t mm_time)
{
- dprint(3, "%s:\n", __FUNCTION__);
/* nothing to do */
}
+#endif
static void interface_get_init_info(QXLInstance *sin, QXLDevInitInfo *info)
{
@@ -462,18 +526,16 @@ static void interface_get_init_info(QXLInstance *sin, QXLDevInitInfo *info)
info->num_memslots = NUM_MEMSLOTS;
info->num_memslots_groups = NUM_MEMSLOTS_GROUPS;
info->internal_groupslot_id = 0;
- info->qxl_ram_size = ssd->bufsize;
+ info->qxl_ram_size = 16 * 1024 * 1024;
info->n_surfaces = ssd->num_surfaces;
}
-static int interface_get_command(QXLInstance *sin, struct QXLCommandExt *ext)
+static int interface_get_command(QXLInstance *sin, QXLCommandExt *ext)
{
SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl);
SimpleSpiceUpdate *update;
int ret = false;
- dprint(3, "%s:\n", __FUNCTION__);
-
qemu_mutex_lock(&ssd->lock);
update = QTAILQ_FIRST(&ssd->updates);
if (update != NULL) {
@@ -488,42 +550,66 @@ static int interface_get_command(QXLInstance *sin, struct QXLCommandExt *ext)
static int interface_req_cmd_notification(QXLInstance *sin)
{
- dprint(1, "%s:\n", __FUNCTION__);
return 1;
}
static void interface_release_resource(QXLInstance *sin,
- struct QXLReleaseInfoExt ext)
+ QXLReleaseInfoExt rext)
{
SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl);
- uintptr_t id;
+ SimpleSpiceUpdate *update;
+ SimpleSpiceCursor *cursor;
+ QXLCommandExt *ext;
- dprint(2, "%s:\n", __FUNCTION__);
- id = ext.info->id;
- qemu_spice_destroy_update(ssd, (void*)id);
+ 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, struct QXLCommandExt *ext)
+static int interface_get_cursor_command(QXLInstance *sin, QXLCommandExt *ext)
{
- dprint(3, "%s:\n", __FUNCTION__);
- return false;
+ 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)
{
- dprint(1, "%s:\n", __FUNCTION__);
return 1;
}
static void interface_notify_update(QXLInstance *sin, uint32_t update_id)
{
- fprintf(stderr, "%s: abort()\n", __FUNCTION__);
+ fprintf(stderr, "%s: abort()\n", __func__);
abort();
}
static int interface_flush_resources(QXLInstance *sin)
{
- fprintf(stderr, "%s: abort()\n", __FUNCTION__);
+ fprintf(stderr, "%s: abort()\n", __func__);
abort();
return 0;
}
@@ -540,23 +626,70 @@ static void interface_update_area_complete(QXLInstance *sin,
/* called from spice server thread context only */
static void interface_async_complete(QXLInstance *sin, uint64_t cookie_token)
{
- /* should never be called, used in qxl native mode only */
- fprintf(stderr, "%s: abort()\n", __func__);
- abort();
+ 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])
{
- dprint(3, "%s:\n", __func__);
+ /* nothing to do */
}
static int interface_client_monitors_config(QXLInstance *sin,
- VDAgentMonitorsConfig *monitors_config)
+ VDAgentMonitorsConfig *mc)
{
- dprint(3, "%s:\n", __func__);
- return 0; /* == not supported by guest */
+ 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 = {
@@ -567,7 +700,9 @@ static const QXLInterface dpy_interface = {
.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 */
@@ -592,7 +727,7 @@ static void display_update(DisplayChangeListener *dcl,
}
static void display_switch(DisplayChangeListener *dcl,
- struct DisplaySurface *surface)
+ DisplaySurface *surface)
{
SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl);
qemu_spice_display_switch(ssd, surface);
@@ -604,28 +739,453 @@ static void display_refresh(DisplayChangeListener *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_refresh = display_refresh,
+ .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,
};
-void qemu_spice_display_init(DisplayState *ds)
+#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);
+}
+
+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);
+ 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);
+ 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_ctx_get_current = qemu_egl_get_current_context,
+
+ .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_interface(&ssd->qxl.base);
- assert(ssd->worker);
+ 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);
- ssd->dcl.ops = &display_listener_ops;
- ssd->dcl.con = qemu_console_lookup_by_index(0);
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;
+ }
- qemu_spice_create_host_primary(ssd);
+ 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);
+ }
}
diff --git a/ui/spice-input.c b/ui/spice-input.c
index 3beb8dead..bbd502564 100644
--- a/ui/spice-input.c
+++ b/ui/spice-input.c
@@ -15,28 +15,27 @@
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
-#include <stdlib.h>
-#include <stdio.h>
-#include <stdbool.h>
-#include <string.h>
+#include "qemu/osdep.h"
#include <spice.h>
#include <spice/enums.h>
-#include "qemu-common.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 void kbd_leds(void *opaque, int l);
static const SpiceKbdInterface kbd_interface = {
.base.type = SPICE_INTERFACE_KEYBOARD,
@@ -47,9 +46,37 @@ static const SpiceKbdInterface kbd_interface = {
.get_leds = kbd_get_leds,
};
-static void kbd_push_key(SpiceKbdInstance *sin, uint8_t frag)
+static void kbd_push_key(SpiceKbdInstance *sin, uint8_t scancode)
{
- kbd_put_keycode(frag);
+ 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)
@@ -72,7 +99,7 @@ static void kbd_leds(void *opaque, int ledstate)
if (ledstate & QEMU_CAPS_LOCK_LED) {
kbd->ledstate |= SPICE_KEYBOARD_MODIFIER_FLAGS_CAPS_LOCK;
}
- spice_server_kbd_leds(&kbd->sin, ledstate);
+ spice_server_kbd_leds(&kbd->sin, kbd->ledstate);
}
/* mouse bits */
@@ -80,41 +107,54 @@ static void kbd_leds(void *opaque, int ledstate)
typedef struct QemuSpicePointer {
SpiceMouseInstance mouse;
SpiceTabletInstance tablet;
- int width, height, x, y;
+ int width, height;
+ uint32_t last_bmask;
Notifier mouse_mode;
bool absolute;
} QemuSpicePointer;
-static int map_buttons(int spice_buttons)
+static void spice_update_buttons(QemuSpicePointer *pointer,
+ int wheel, uint32_t button_mask)
{
- int qemu_buttons = 0;
-
- /*
- * Note: SPICE_MOUSE_BUTTON_* specifies the wire protocol but this
- * isn't what we get passed in via interface callbacks for the
- * middle and right button ...
- */
- if (spice_buttons & SPICE_MOUSE_BUTTON_MASK_LEFT) {
- qemu_buttons |= MOUSE_EVENT_LBUTTON;
+ 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 (spice_buttons & 0x04 /* SPICE_MOUSE_BUTTON_MASK_MIDDLE */) {
- qemu_buttons |= MOUSE_EVENT_MBUTTON;
+ if (wheel > 0) {
+ button_mask |= 0x20;
}
- if (spice_buttons & 0x02 /* SPICE_MOUSE_BUTTON_MASK_RIGHT */) {
- qemu_buttons |= MOUSE_EVENT_RBUTTON;
+
+ if (pointer->last_bmask == button_mask) {
+ return;
}
- return qemu_buttons;
+ 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)
{
- kbd_mouse_event(dx, dy, dz, map_buttons(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)
{
- kbd_mouse_event(0, 0, 0, map_buttons(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 = {
@@ -145,9 +185,10 @@ static void tablet_position(SpiceTabletInstance* sin, int x, int y,
{
QemuSpicePointer *pointer = container_of(sin, QemuSpicePointer, tablet);
- pointer->x = x * 0x7FFF / (pointer->width - 1);
- pointer->y = y * 0x7FFF / (pointer->height - 1);
- kbd_mouse_event(pointer->x, pointer->y, 0, map_buttons(buttons_state));
+ 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();
}
@@ -156,7 +197,8 @@ static void tablet_wheel(SpiceTabletInstance* sin, int wheel,
{
QemuSpicePointer *pointer = container_of(sin, QemuSpicePointer, tablet);
- kbd_mouse_event(pointer->x, pointer->y, wheel, map_buttons(buttons_state));
+ spice_update_buttons(pointer, wheel, buttons_state);
+ qemu_input_event_sync();
}
static void tablet_buttons(SpiceTabletInstance *sin,
@@ -164,7 +206,8 @@ static void tablet_buttons(SpiceTabletInstance *sin,
{
QemuSpicePointer *pointer = container_of(sin, QemuSpicePointer, tablet);
- kbd_mouse_event(pointer->x, pointer->y, 0, map_buttons(buttons_state));
+ spice_update_buttons(pointer, 0, buttons_state);
+ qemu_input_event_sync();
}
static const SpiceTabletInterface tablet_interface = {
@@ -181,14 +224,14 @@ static const SpiceTabletInterface tablet_interface = {
static void mouse_mode_notifier(Notifier *notifier, void *data)
{
QemuSpicePointer *pointer = container_of(notifier, QemuSpicePointer, mouse_mode);
- bool is_absolute = kbd_mouse_is_absolute();
+ bool is_absolute = qemu_input_is_absolute();
if (pointer->absolute == is_absolute) {
return;
}
if (is_absolute) {
- qemu_spice_add_interface(&pointer->tablet.base);
+ qemu_spice.add_interface(&pointer->tablet.base);
} else {
spice_server_remove_interface(&pointer->tablet.base);
}
@@ -202,13 +245,13 @@ void qemu_spice_input_init(void)
kbd = g_malloc0(sizeof(*kbd));
kbd->sin.base.sif = &kbd_interface.base;
- qemu_spice_add_interface(&kbd->sin.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);
+ qemu_spice.add_interface(&pointer->mouse.base);
pointer->absolute = false;
pointer->mouse_mode.notify = mouse_mode_notifier;
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..0ffcdb440
--- /dev/null
+++ b/ui/trace-events
@@ -0,0 +1,110 @@
+# See docs/devel/tracing.txt 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_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_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'"
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/vnc-auth-sasl.c b/ui/vnc-auth-sasl.c
index f3ad75d52..f67111a36 100644
--- a/ui/vnc-auth-sasl.c
+++ b/ui/vnc-auth-sasl.c
@@ -22,7 +22,11 @@
* THE SOFTWARE.
*/
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "authz/base.h"
#include "vnc.h"
+#include "trace.h"
/* Max amount of data we send/recv for SASL steps to prevent DOS */
#define SASL_DATA_MAX_LEN (1024 * 1024)
@@ -45,9 +49,9 @@ void vnc_sasl_client_cleanup(VncState *vs)
}
-long vnc_client_write_sasl(VncState *vs)
+size_t vnc_client_write_sasl(VncState *vs)
{
- long ret;
+ size_t ret;
VNC_DEBUG("Write SASL: Pending output %p size %zd offset %zd "
"Encoded: %p size %d offset %d\n",
@@ -62,8 +66,9 @@ long vnc_client_write_sasl(VncState *vs)
(const char **)&vs->sasl.encoded,
&vs->sasl.encodedLength);
if (err != SASL_OK)
- return vnc_client_io_error(vs, -1, EIO);
+ return vnc_client_io_error(vs, -1, NULL);
+ vs->sasl.encodedRawLength = vs->output.offset;
vs->sasl.encodedOffset = 0;
}
@@ -75,7 +80,23 @@ long vnc_client_write_sasl(VncState *vs)
vs->sasl.encodedOffset += ret;
if (vs->sasl.encodedOffset == vs->sasl.encodedLength) {
- vs->output.offset = 0;
+ 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;
}
@@ -86,16 +107,21 @@ long vnc_client_write_sasl(VncState *vs)
* SASL encoded output
*/
if (vs->output.offset == 0) {
- qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, 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);
}
return ret;
}
-long vnc_client_read_sasl(VncState *vs)
+size_t vnc_client_read_sasl(VncState *vs)
{
- long ret;
+ size_t ret;
uint8_t encoded[4096];
const char *decoded;
unsigned int decodedLen;
@@ -110,7 +136,7 @@ long vnc_client_read_sasl(VncState *vs)
&decoded, &decodedLen);
if (err != SASL_OK)
- return vnc_client_io_error(vs, -1, -EIO);
+ 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);
@@ -122,32 +148,39 @@ long vnc_client_read_sasl(VncState *vs)
static int vnc_auth_sasl_check_access(VncState *vs)
{
const void *val;
- int err;
- int allow;
-
- err = sasl_getprop(vs->sasl.conn, SASL_USERNAME, &val);
- if (err != SASL_OK) {
- VNC_DEBUG("cannot query SASL username on connection %d (%s), denying access\n",
- err, sasl_errstring(err, NULL, NULL));
+ 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) {
- VNC_DEBUG("no client username was found, denying access\n");
+ trace_vnc_auth_fail(vs, vs->auth, "No SASL username set", "");
return -1;
}
- VNC_DEBUG("SASL client username %s\n", (const char *)val);
vs->sasl.username = g_strdup((const char*)val);
+ trace_vnc_auth_sasl_username(vs, vs->sasl.username);
- if (vs->vd->sasl.acl == NULL) {
- VNC_DEBUG("no ACL activated, allowing access\n");
+ if (vs->vd->sasl.authzid == NULL) {
+ trace_vnc_auth_sasl_acl(vs, 1);
return 0;
}
- allow = qemu_acl_party_is_allowed(vs->vd->sasl.acl, vs->sasl.username);
+ 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;
+ }
- VNC_DEBUG("SASL client %s %s by ACL\n", vs->sasl.username,
- allow ? "allowed" : "denied");
+ trace_vnc_auth_sasl_acl(vs, allow);
return allow ? 0 : -1;
}
@@ -164,7 +197,9 @@ static int vnc_auth_sasl_check_ssf(VncState *vs)
return 0;
ssf = *(const int *)val;
- VNC_DEBUG("negotiated an SSF of %d\n", ssf);
+
+ trace_vnc_auth_sasl_ssf(vs, ssf);
+
if (ssf < 56)
return 0; /* 56 is good for Kerberos */
@@ -212,33 +247,28 @@ static int protocol_client_auth_sasl_step(VncState *vs, uint8_t *data, size_t le
datalen--; /* Don't count NULL byte when passing to _start() */
}
- VNC_DEBUG("Step using SASL Data %p (%d bytes)\n",
- clientdata, datalen);
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) {
- VNC_DEBUG("sasl step failed %d (%s)\n",
- err, sasl_errdetail(vs->sasl.conn));
+ 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) {
- VNC_DEBUG("sasl step reply data too long %d\n",
- serveroutlen);
+ trace_vnc_auth_fail(vs, vs->auth, "SASL data too long", "");
sasl_dispose(&vs->sasl.conn);
vs->sasl.conn = NULL;
goto authabort;
}
- VNC_DEBUG("SASL return data %d bytes, nil; %d\n",
- serveroutlen, serverout ? 0 : 1);
-
if (serveroutlen) {
vnc_write_u32(vs, serveroutlen + 1);
vnc_write(vs, serverout, serveroutlen + 1);
@@ -250,22 +280,20 @@ static int protocol_client_auth_sasl_step(VncState *vs, uint8_t *data, size_t le
vnc_write_u8(vs, err == SASL_CONTINUE ? 0 : 1);
if (err == SASL_CONTINUE) {
- VNC_DEBUG("%s", "Authentication must continue\n");
/* Wait for step length */
vnc_read_when(vs, protocol_client_auth_sasl_step_len, 4);
} else {
if (!vnc_auth_sasl_check_ssf(vs)) {
- VNC_DEBUG("Authentication rejected for weak SSF %d\n", vs->csock);
+ trace_vnc_auth_fail(vs, vs->auth, "SASL SSF too weak", "");
goto authreject;
}
/* Check username whitelist ACL */
if (vnc_auth_sasl_check_access(vs) < 0) {
- VNC_DEBUG("Authentication rejected for ACL %d\n", vs->csock);
goto authreject;
}
- VNC_DEBUG("Authentication successful %d\n", vs->csock);
+ trace_vnc_auth_pass(vs, vs->auth);
vnc_write_u32(vs, 0); /* Accept auth */
/*
* Delay writing in SSF encoded mode until pending output
@@ -294,9 +322,9 @@ static int protocol_client_auth_sasl_step(VncState *vs, uint8_t *data, size_t le
static int protocol_client_auth_sasl_step_len(VncState *vs, uint8_t *data, size_t len)
{
uint32_t steplen = read_u32(data, 0);
- VNC_DEBUG("Got client step len %d\n", steplen);
+
if (steplen > SASL_DATA_MAX_LEN) {
- VNC_DEBUG("Too much SASL data %d\n", steplen);
+ trace_vnc_auth_fail(vs, vs->auth, "SASL step len too large", "");
vnc_client_error(vs);
return -1;
}
@@ -340,33 +368,28 @@ static int protocol_client_auth_sasl_start(VncState *vs, uint8_t *data, size_t l
datalen--; /* Don't count NULL byte when passing to _start() */
}
- VNC_DEBUG("Start SASL auth with mechanism %s. Data %p (%d bytes)\n",
- vs->sasl.mechlist, clientdata, datalen);
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) {
- VNC_DEBUG("sasl start failed %d (%s)\n",
- err, sasl_errdetail(vs->sasl.conn));
+ 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) {
- VNC_DEBUG("sasl start reply data too long %d\n",
- serveroutlen);
+ trace_vnc_auth_fail(vs, vs->auth, "SASL data too long", "");
sasl_dispose(&vs->sasl.conn);
vs->sasl.conn = NULL;
goto authabort;
}
- VNC_DEBUG("SASL return data %d bytes, nil; %d\n",
- serveroutlen, serverout ? 0 : 1);
-
if (serveroutlen) {
vnc_write_u32(vs, serveroutlen + 1);
vnc_write(vs, serverout, serveroutlen + 1);
@@ -378,22 +401,20 @@ static int protocol_client_auth_sasl_start(VncState *vs, uint8_t *data, size_t l
vnc_write_u8(vs, err == SASL_CONTINUE ? 0 : 1);
if (err == SASL_CONTINUE) {
- VNC_DEBUG("%s", "Authentication must continue\n");
/* Wait for step length */
vnc_read_when(vs, protocol_client_auth_sasl_step_len, 4);
} else {
if (!vnc_auth_sasl_check_ssf(vs)) {
- VNC_DEBUG("Authentication rejected for weak SSF %d\n", vs->csock);
+ trace_vnc_auth_fail(vs, vs->auth, "SASL SSF too weak", "");
goto authreject;
}
/* Check username whitelist ACL */
if (vnc_auth_sasl_check_access(vs) < 0) {
- VNC_DEBUG("Authentication rejected for ACL %d\n", vs->csock);
goto authreject;
}
- VNC_DEBUG("Authentication successful %d\n", vs->csock);
+ trace_vnc_auth_pass(vs, vs->auth);
vnc_write_u32(vs, 0); /* Accept auth */
start_client_init(vs);
}
@@ -416,9 +437,9 @@ static int protocol_client_auth_sasl_start(VncState *vs, uint8_t *data, size_t l
static int protocol_client_auth_sasl_start_len(VncState *vs, uint8_t *data, size_t len)
{
uint32_t startlen = read_u32(data, 0);
- VNC_DEBUG("Got client start len %d\n", startlen);
+
if (startlen > SASL_DATA_MAX_LEN) {
- VNC_DEBUG("Too much SASL data %d\n", startlen);
+ trace_vnc_auth_fail(vs, vs->auth, "SASL start len too large", "");
vnc_client_error(vs);
return -1;
}
@@ -433,22 +454,18 @@ static int protocol_client_auth_sasl_start_len(VncState *vs, uint8_t *data, size
static int protocol_client_auth_sasl_mechname(VncState *vs, uint8_t *data, size_t len)
{
char *mechname = g_strndup((const char *) data, len);
- VNC_DEBUG("Got client mechname '%s' check against '%s'\n",
- mechname, vs->sasl.mechlist);
+ 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] != ',') {
- VNC_DEBUG("One %d", vs->sasl.mechlist[len]);
goto fail;
}
} else {
char *offset = strstr(vs->sasl.mechlist, mechname);
- VNC_DEBUG("Two %p\n", offset);
if (!offset) {
goto fail;
}
- VNC_DEBUG("Two '%s'\n", offset);
if (offset[-1] != ',' ||
(offset[len] != '\0'&&
offset[len] != ',')) {
@@ -459,11 +476,11 @@ static int protocol_client_auth_sasl_mechname(VncState *vs, uint8_t *data, size_
g_free(vs->sasl.mechlist);
vs->sasl.mechlist = mechname;
- VNC_DEBUG("Validated mechname '%s'\n", 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;
@@ -472,14 +489,14 @@ static int protocol_client_auth_sasl_mechname(VncState *vs, uint8_t *data, size_
static int protocol_client_auth_sasl_mechname_len(VncState *vs, uint8_t *data, size_t len)
{
uint32_t mechlen = read_u32(data, 0);
- VNC_DEBUG("Got client mechname len %d\n", mechlen);
+
if (mechlen > 100) {
- VNC_DEBUG("Too long SASL mechname data %d\n", mechlen);
+ trace_vnc_auth_fail(vs, vs->auth, "SASL mechname too long", "");
vnc_client_error(vs);
return -1;
}
if (mechlen < 1) {
- VNC_DEBUG("Too short SASL mechname %d\n", mechlen);
+ trace_vnc_auth_fail(vs, vs->auth, "SASL mechname too short", "");
vnc_client_error(vs);
return -1;
}
@@ -487,21 +504,54 @@ static int protocol_client_auth_sasl_mechname_len(VncState *vs, uint8_t *data, s
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;
- VNC_DEBUG("Initialize SASL auth %d\n", vs->csock);
-
/* Get local & remote client addresses in form IPADDR;PORT */
- if (!(localAddr = vnc_socket_local_addr("%s;%s", vs->csock)))
+ 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;
+ }
- if (!(remoteAddr = vnc_socket_remote_addr("%s;%s", vs->csock))) {
+ 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;
}
@@ -519,50 +569,50 @@ void start_auth_sasl(VncState *vs)
localAddr = remoteAddr = NULL;
if (err != SASL_OK) {
- VNC_DEBUG("sasl context setup failed %d (%s)",
- err, sasl_errstring(err, NULL, NULL));
+ trace_vnc_auth_fail(vs, vs->auth, "SASL context setup failed",
+ sasl_errstring(err, NULL, NULL));
vs->sasl.conn = NULL;
goto authabort;
}
-#ifdef CONFIG_VNC_TLS
/* 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) {
- gnutls_cipher_algorithm_t cipher;
+ int keysize;
sasl_ssf_t ssf;
- cipher = gnutls_cipher_get(vs->tls.session);
- if (!(ssf = (sasl_ssf_t)gnutls_cipher_get_key_size(cipher))) {
- VNC_DEBUG("%s", "cannot TLS get cipher size\n");
+ 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 *= 8; /* tls key size is bytes, sasl wants bits */
+ 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) {
- VNC_DEBUG("cannot set SASL external SSF %d (%s)\n",
- err, sasl_errstring(err, NULL, NULL));
+ 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
-#endif /* CONFIG_VNC_TLS */
+ } else {
vs->sasl.wantSSF = 1;
+ }
memset (&secprops, 0, sizeof secprops);
- /* Inform SASL that we've got an external SSF layer from TLS */
- if (strncmp(vs->vd->display, "unix:", 5) == 0
-#ifdef CONFIG_VNC_TLS
- /* Disable SSF, if using TLS+x509+SASL only. TLS without x509
- is not sufficiently strong */
- || (vs->auth == VNC_AUTH_VENCRYPT &&
- vs->subauth == VNC_AUTH_VENCRYPT_X509SASL)
-#endif /* CONFIG_VNC_TLS */
- ) {
+ /* 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;
@@ -580,8 +630,8 @@ void start_auth_sasl(VncState *vs)
err = sasl_setprop(vs->sasl.conn, SASL_SEC_PROPS, &secprops);
if (err != SASL_OK) {
- VNC_DEBUG("cannot set SASL security props %d (%s)\n",
- err, sasl_errstring(err, NULL, NULL));
+ 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;
@@ -596,13 +646,13 @@ void start_auth_sasl(VncState *vs)
NULL,
NULL);
if (err != SASL_OK) {
- VNC_DEBUG("cannot list SASL mechanisms %d (%s)\n",
- err, sasl_errdetail(vs->sasl.conn));
+ 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;
}
- VNC_DEBUG("Available mechanisms for client: '%s'\n", mechlist);
+ trace_vnc_auth_sasl_mech_list(vs, mechlist);
vs->sasl.mechlist = g_strdup(mechlist);
mechlistlen = strlen(mechlist);
@@ -610,12 +660,12 @@ void start_auth_sasl(VncState *vs)
vnc_write(vs, mechlist, mechlistlen);
vnc_flush(vs);
- VNC_DEBUG("Wait for client mechname length\n");
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
index 8091d689c..1bfb86c6f 100644
--- a/ui/vnc-auth-sasl.h
+++ b/ui/vnc-auth-sasl.h
@@ -22,17 +22,15 @@
* THE SOFTWARE.
*/
-
-#ifndef __QEMU_VNC_AUTH_SASL_H__
-#define __QEMU_VNC_AUTH_SASL_H__
-
+#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 "qemu/acl.h"
+#include "authz/base.h"
struct VncStateSASL {
sasl_conn_t *conn;
@@ -54,21 +52,22 @@ struct VncStateSASL {
*/
const uint8_t *encoded;
unsigned int encodedLength;
+ unsigned int encodedRawLength;
unsigned int encodedOffset;
char *username;
char *mechlist;
};
struct VncDisplaySASL {
- qemu_acl *acl;
+ QAuthZ *authz;
+ char *authzid;
};
void vnc_sasl_client_cleanup(VncState *vs);
-long vnc_client_read_sasl(VncState *vs);
-long vnc_client_write_sasl(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__ */
-
+#endif /* QEMU_VNC_AUTH_SASL_H */
diff --git a/ui/vnc-auth-vencrypt.c b/ui/vnc-auth-vencrypt.c
index c59b18860..d9c212ff3 100644
--- a/ui/vnc-auth-vencrypt.c
+++ b/ui/vnc-auth-vencrypt.c
@@ -24,35 +24,35 @@
* 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_DEBUG("Accept TLS auth none\n");
vnc_write_u32(vs, 0); /* Accept auth completion */
start_client_init(vs);
break;
case VNC_AUTH_VENCRYPT_TLSVNC:
case VNC_AUTH_VENCRYPT_X509VNC:
- VNC_DEBUG("Start TLS auth VNC\n");
start_auth_vnc(vs);
break;
#ifdef CONFIG_VNC_SASL
case VNC_AUTH_VENCRYPT_TLSSASL:
case VNC_AUTH_VENCRYPT_X509SASL:
- VNC_DEBUG("Start TLS auth SASL\n");
start_auth_sasl(vs);
break;
#endif /* CONFIG_VNC_SASL */
default: /* Should not be possible, but just in case */
- VNC_DEBUG("Reject subauth %d server bug\n", vs->auth);
+ 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";
@@ -63,98 +63,88 @@ static void start_auth_vencrypt_subauth(VncState *vs)
}
}
-static void vnc_tls_handshake_io(void *opaque);
-
-static int vnc_start_vencrypt_handshake(struct VncState *vs) {
- int ret;
-
- if ((ret = gnutls_handshake(vs->tls.session)) < 0) {
- if (!gnutls_error_is_fatal(ret)) {
- VNC_DEBUG("Handshake interrupted (blocking)\n");
- if (!gnutls_record_get_direction(vs->tls.session))
- qemu_set_fd_handler(vs->csock, vnc_tls_handshake_io, NULL, vs);
- else
- qemu_set_fd_handler(vs->csock, NULL, vnc_tls_handshake_io, vs);
- return 0;
- }
- VNC_DEBUG("Handshake failed %s\n", gnutls_strerror(ret));
- vnc_client_error(vs);
- return -1;
- }
+static void vnc_tls_handshake_done(QIOTask *task,
+ gpointer user_data)
+{
+ VncState *vs = user_data;
+ Error *err = NULL;
- if (vs->vd->tls.x509verify) {
- if (vnc_tls_validate_certificate(vs) < 0) {
- VNC_DEBUG("Client verification failed\n");
- vnc_client_error(vs);
- return -1;
- } else {
- VNC_DEBUG("Client verification passed\n");
+ 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);
}
-
- VNC_DEBUG("Handshake done, switching to TLS data mode\n");
- vs->tls.wiremode = VNC_WIREMODE_TLS;
- qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, vnc_client_write, vs);
-
- start_auth_vencrypt_subauth(vs);
-
- return 0;
-}
-
-static void vnc_tls_handshake_io(void *opaque) {
- struct VncState *vs = (struct VncState *)opaque;
-
- VNC_DEBUG("Handshake IO continue\n");
- vnc_start_vencrypt_handshake(vs);
}
-
-#define NEED_X509_AUTH(vs) \
- ((vs)->subauth == VNC_AUTH_VENCRYPT_X509NONE || \
- (vs)->subauth == VNC_AUTH_VENCRYPT_X509VNC || \
- (vs)->subauth == VNC_AUTH_VENCRYPT_X509PLAIN || \
- (vs)->subauth == VNC_AUTH_VENCRYPT_X509SASL)
-
-
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) {
- VNC_DEBUG("Rejecting auth %d\n", auth);
+ 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 {
- VNC_DEBUG("Accepting auth %d, setting up TLS for handshake\n", auth);
+ Error *err = NULL;
+ QIOChannelTLS *tls;
vnc_write_u8(vs, 1); /* Accept auth */
vnc_flush(vs);
- if (vnc_tls_client_setup(vs, NEED_X509_AUTH(vs)) < 0) {
- VNC_DEBUG("Failed to setup TLS\n");
- return 0;
+ if (vs->ioc_tag) {
+ g_source_remove(vs->ioc_tag);
+ vs->ioc_tag = 0;
}
- VNC_DEBUG("Start TLS VeNCrypt handshake process\n");
- if (vnc_start_vencrypt_handshake(vs) < 0) {
- VNC_DEBUG("Failed to start TLS handshake\n");
+ 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) {
- VNC_DEBUG("Unsupported VeNCrypt protocol %d.%d\n", (int)data[0], (int)data[1]);
+ 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_DEBUG("Sending allowed auth %d\n", vs->subauth);
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 */
diff --git a/ui/vnc-auth-vencrypt.h b/ui/vnc-auth-vencrypt.h
index 9f674c517..1e3540666 100644
--- a/ui/vnc-auth-vencrypt.h
+++ b/ui/vnc-auth-vencrypt.h
@@ -24,10 +24,9 @@
* THE SOFTWARE.
*/
-
-#ifndef __QEMU_VNC_AUTH_VENCRYPT_H__
-#define __QEMU_VNC_AUTH_VENCRYPT_H__
+#ifndef QEMU_VNC_AUTH_VENCRYPT_H
+#define QEMU_VNC_AUTH_VENCRYPT_H
void start_auth_vencrypt(VncState *vs);
-#endif /* __QEMU_VNC_AUTH_VENCRYPT_H__ */
+#endif /* QEMU_VNC_AUTH_VENCRYPT_H */
diff --git a/ui/vnc-enc-hextile-template.h b/ui/vnc-enc-hextile-template.h
index d868d7572..0c56262af 100644
--- a/ui/vnc-enc-hextile-template.h
+++ b/ui/vnc-enc-hextile-template.h
@@ -30,127 +30,127 @@ static void CONCAT(send_hextile_tile_, NAME)(VncState *vs,
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);
+ 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;
+ pixel_t tmp = fg;
+ fg = bg;
+ bg = tmp;
}
if (!*has_bg || *last_bg != bg) {
- flags |= 0x02;
- *has_bg = 1;
- *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;
+ flags |= 0x04;
+ *has_fg = 1;
+ *last_fg = fg;
}
switch (n_colors) {
case 1:
- n_data = 0;
- break;
+ 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;
+ 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;
+ 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));
+ 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) {
+ 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;
@@ -158,50 +158,50 @@ static void CONCAT(send_hextile_tile_, NAME)(VncState *vs,
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. */
- }
+ 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;
+ break;
}
if (n_colors > 3) {
- flags = 0x01;
- *has_fg = 0;
- *has_bg = 0;
- n_colors = 4;
+ 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);
- }
+ 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);
- }
+ for (j = 0; j < h; j++) {
+ vs->write_pixels(vs, row, w * 4);
+ row += vnc_server_fb_stride(vd);
+ }
}
}
diff --git a/ui/vnc-enc-hextile.c b/ui/vnc-enc-hextile.c
index 2e768fd89..4215bd7da 100644
--- a/ui/vnc-enc-hextile.c
+++ b/ui/vnc-enc-hextile.c
@@ -24,6 +24,7 @@
* 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)
diff --git a/ui/vnc-enc-tight.c b/ui/vnc-enc-tight.c
index e6966aebc..cebd35841 100644
--- a/ui/vnc-enc-tight.c
+++ b/ui/vnc-enc-tight.c
@@ -26,12 +26,11 @@
* THE SOFTWARE.
*/
-#include "config-host.h"
+#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). */
-#include "qemu-common.h"
#ifdef CONFIG_VNC_PNG
/* The following define is needed by pngconf.h. Otherwise it won't compile,
@@ -40,12 +39,10 @@
#include <png.h>
#endif
#ifdef CONFIG_VNC_JPEG
-#include <stdio.h>
#include <jpeglib.h>
#endif
#include "qemu/bswap.h"
-#include "qapi/qmp/qint.h"
#include "vnc.h"
#include "vnc-enc-tight.h"
#include "vnc-palette.h"
@@ -119,7 +116,7 @@ static int send_png_rect(VncState *vs, int x, int y, int w, int h,
static bool tight_can_send_png_rect(VncState *vs, int w, int h)
{
- if (vs->tight.type != VNC_ENCODING_TIGHT_PNG) {
+ if (vs->tight->type != VNC_ENCODING_TIGHT_PNG) {
return false;
}
@@ -147,7 +144,7 @@ tight_detect_smooth_image24(VncState *vs, int w, int h)
int pixels = 0;
int pix, left[3];
unsigned int errors;
- unsigned char *buf = vs->tight.tight.buffer;
+ unsigned char *buf = vs->tight->tight.buffer;
/*
* If client is big-endian, color samples begin from the second
@@ -181,6 +178,10 @@ tight_detect_smooth_image24(VncState *vs, int w, int h)
}
}
+ if (pixels == 0) {
+ return 0;
+ }
+
/* 95% smooth or more ... */
if (stats[0] * 33 / pixels >= 95) {
return 0;
@@ -214,10 +215,9 @@ tight_detect_smooth_image24(VncState *vs, int w, int h)
int pixels = 0; \
int sample, sum, left[3]; \
unsigned int errors; \
- unsigned char *buf = vs->tight.tight.buffer; \
+ unsigned char *buf = vs->tight->tight.buffer; \
\
- endian = 0; /* FIXME: ((vs->clientds.flags & QEMU_BIG_ENDIAN_FLAG) != \
- (vs->ds->surface->flags & QEMU_BIG_ENDIAN_FLAG)); */ \
+ endian = 0; /* FIXME */ \
\
\
max[0] = vs->client_pf.rmax; \
@@ -267,7 +267,9 @@ tight_detect_smooth_image24(VncState *vs, int w, int h)
y += w; \
} \
} \
- \
+ if (pixels == 0) { \
+ return 0; \
+ } \
if ((stats[0] + stats[1]) * 100 / pixels >= 90) { \
return 0; \
} \
@@ -294,8 +296,8 @@ 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;
+ int compression = vs->tight->compression;
+ int quality = vs->tight->quality;
if (!vs->vd->lossy) {
return 0;
@@ -307,7 +309,7 @@ tight_detect_smooth_image(VncState *vs, int w, int h)
return 0;
}
- if (vs->tight.quality != (uint8_t)-1) {
+ if (vs->tight->quality != (uint8_t)-1) {
if (w * h < VNC_TIGHT_JPEG_MIN_RECT_SIZE) {
return 0;
}
@@ -318,9 +320,9 @@ tight_detect_smooth_image(VncState *vs, int w, int h)
}
if (vs->client_pf.bytes_per_pixel == 4) {
- if (vs->tight.pixel24) {
+ if (vs->tight->pixel24) {
errors = tight_detect_smooth_image24(vs, w, h);
- if (vs->tight.quality != (uint8_t)-1) {
+ if (vs->tight->quality != (uint8_t)-1) {
return (errors < tight_conf[quality].jpeg_threshold24);
}
return (errors < tight_conf[compression].gradient_threshold24);
@@ -330,7 +332,7 @@ tight_detect_smooth_image(VncState *vs, int w, int h)
} else {
errors = tight_detect_smooth_image16(vs, w, h);
}
- if (quality != -1) {
+ if (quality != (uint8_t)-1) {
return (errors < tight_conf[quality].jpeg_threshold);
}
return (errors < tight_conf[compression].gradient_threshold);
@@ -345,12 +347,12 @@ tight_detect_smooth_image(VncState *vs, int w, int h)
tight_fill_palette##bpp(VncState *vs, int x, int y, \
int max, size_t count, \
uint32_t *bg, uint32_t *fg, \
- VncPalette **palette) { \
+ VncPalette *palette) { \
uint##bpp##_t *data; \
uint##bpp##_t c0, c1, ci; \
int i, n0, n1; \
\
- data = (uint##bpp##_t *)vs->tight.tight.buffer; \
+ data = (uint##bpp##_t *)vs->tight->tight.buffer; \
\
c0 = data[0]; \
i = 1; \
@@ -392,23 +394,23 @@ tight_detect_smooth_image(VncState *vs, int w, int h)
return 0; \
} \
\
- *palette = palette_new(max, bpp); \
- palette_put(*palette, c0); \
- palette_put(*palette, c1); \
- palette_put(*palette, ci); \
+ 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)) { \
+ if (!palette_put(palette, (uint32_t)ci)) { \
return 0; \
} \
} \
} \
\
- return palette_size(*palette); \
+ return palette_size(palette); \
}
DEFINE_FILL_PALETTE_FUNCTION(8)
@@ -417,13 +419,13 @@ 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)
+ VncPalette *palette)
{
int max;
- max = count / tight_conf[vs->tight.compression].idx_max_colors_divisor;
+ max = count / tight_conf[vs->tight->compression].idx_max_colors_divisor;
if (max < 2 &&
- count >= tight_conf[vs->tight.compression].mono_min_rect_size) {
+ count >= tight_conf[vs->tight->compression].mono_min_rect_size) {
max = 2;
}
if (max >= 256) {
@@ -457,9 +459,10 @@ static int tight_fill_palette(VncState *vs, int x, int y,
\
src = (uint##bpp##_t *) buf; \
\
- for (i = 0; i < count; i++) { \
+ for (i = 0; i < count; ) { \
\
rgb = *src++; \
+ i++; \
rep = 0; \
while (i < count && *src == rgb) { \
rep++, src++, i++; \
@@ -555,10 +558,9 @@ tight_filter_gradient24(VncState *vs, uint8_t *buf, int w, int h)
int x, y, c;
buf32 = (uint32_t *)buf;
- memset(vs->tight.gradient.buffer, 0, w * 3 * sizeof(int));
+ memset(vs->tight->gradient.buffer, 0, w * 3 * sizeof(int));
- if (1 /* FIXME: (vs->clientds.flags & QEMU_BIG_ENDIAN_FLAG) ==
- (vs->ds->surface->flags & QEMU_BIG_ENDIAN_FLAG) */) {
+ if (1 /* FIXME */) {
shift[0] = vs->client_pf.rshift;
shift[1] = vs->client_pf.gshift;
shift[2] = vs->client_pf.bshift;
@@ -573,7 +575,7 @@ tight_filter_gradient24(VncState *vs, uint8_t *buf, int w, int h)
upper[c] = 0;
here[c] = 0;
}
- prev = (int *)vs->tight.gradient.buffer;
+ prev = (int *)vs->tight->gradient.buffer;
for (x = 0; x < w; x++) {
pix32 = *buf32++;
for (c = 0; c < 3; c++) {
@@ -613,10 +615,9 @@ tight_filter_gradient24(VncState *vs, uint8_t *buf, int w, int h)
int prediction; \
int x, y, c; \
\
- memset (vs->tight.gradient.buffer, 0, w * 3 * sizeof(int)); \
+ memset(vs->tight->gradient.buffer, 0, w * 3 * sizeof(int)); \
\
- endian = 0; /* FIXME: ((vs->clientds.flags & QEMU_BIG_ENDIAN_FLAG) != \
- (vs->ds->surface->flags & QEMU_BIG_ENDIAN_FLAG)); */ \
+ endian = 0; /* FIXME */ \
\
max[0] = vs->client_pf.rmax; \
max[1] = vs->client_pf.gmax; \
@@ -630,7 +631,7 @@ tight_filter_gradient24(VncState *vs, uint8_t *buf, int w, int h)
upper[c] = 0; \
here[c] = 0; \
} \
- prev = (int *)vs->tight.gradient.buffer; \
+ prev = (int *)vs->tight->gradient.buffer; \
for (x = 0; x < w; x++) { \
pix = *buf; \
if (endian) { \
@@ -704,10 +705,8 @@ check_solid_tile32(VncState *vs, int x, int y, int w, int h,
static bool check_solid_tile(VncState *vs, int x, int y, int w, int h,
uint32_t* color, bool samecolor)
{
- switch (VNC_SERVER_FB_BYTES) {
- case 4:
- return check_solid_tile32(vs, x, y, w, h, color, 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,
@@ -786,7 +785,7 @@ static void extend_solid_area(VncState *vs, int x, int y, int w, int h,
static int tight_init_stream(VncState *vs, int stream_id,
int level, int strategy)
{
- z_streamp zstream = &vs->tight.stream[stream_id];
+ z_streamp zstream = &vs->tight->stream[stream_id];
if (zstream->opaque == NULL) {
int err;
@@ -804,15 +803,15 @@ static int tight_init_stream(VncState *vs, int stream_id,
return -1;
}
- vs->tight.levels[stream_id] = level;
+ vs->tight->levels[stream_id] = level;
zstream->opaque = vs;
}
- if (vs->tight.levels[stream_id] != level) {
+ if (vs->tight->levels[stream_id] != level) {
if (deflateParams(zstream, level, strategy) != Z_OK) {
return -1;
}
- vs->tight.levels[stream_id] = level;
+ vs->tight->levels[stream_id] = level;
}
return 0;
}
@@ -840,11 +839,11 @@ static void tight_send_compact_size(VncState *vs, size_t len)
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];
+ 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);
+ vnc_write(vs, vs->tight->tight.buffer, vs->tight->tight.offset);
return bytes;
}
@@ -853,13 +852,13 @@ static int tight_compress_data(VncState *vs, int stream_id, size_t bytes,
}
/* reserve memory in output buffer */
- buffer_reserve(&vs->tight.zlib, bytes + 64);
+ 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;
+ 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;
@@ -869,14 +868,14 @@ static int tight_compress_data(VncState *vs, int stream_id, size_t bytes,
return -1;
}
- vs->tight.zlib.offset = vs->tight.zlib.capacity - zstream->avail_out;
+ 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);
+ vnc_write(vs, vs->tight->zlib.buffer, bytes);
- buffer_reset(&vs->tight.zlib);
+ buffer_reset(&vs->tight->zlib);
return bytes;
}
@@ -886,14 +885,13 @@ static int tight_compress_data(VncState *vs, int stream_id, size_t bytes,
*/
static void tight_pack24(VncState *vs, uint8_t *buf, size_t count, size_t *ret)
{
- uint32_t *buf32;
+ uint8_t *buf8;
uint32_t pix;
int rshift, gshift, bshift;
- buf32 = (uint32_t *)buf;
+ buf8 = buf;
- if (1 /* FIXME: (vs->clientds.flags & QEMU_BIG_ENDIAN_FLAG) ==
- (vs->ds->surface->flags & QEMU_BIG_ENDIAN_FLAG) */) {
+ if (1 /* FIXME */) {
rshift = vs->client_pf.rshift;
gshift = vs->client_pf.gshift;
bshift = vs->client_pf.bshift;
@@ -908,10 +906,11 @@ static void tight_pack24(VncState *vs, uint8_t *buf, size_t count, size_t *ret)
}
while (count--) {
- pix = *buf32++;
+ pix = ldl_he_p(buf8);
*buf++ = (char)(pix >> rshift);
*buf++ = (char)(pix >> gshift);
*buf++ = (char)(pix >> bshift);
+ buf8 += 4;
}
}
@@ -928,16 +927,17 @@ static int send_full_color_rect(VncState *vs, int x, int y, int w, int h)
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);
+ 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);
+ tight_conf[vs->tight->compression].raw_zlib_level,
+ Z_DEFAULT_STRATEGY);
return (bytes >= 0);
}
@@ -948,14 +948,14 @@ static int send_solid_rect(VncState *vs)
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);
+ 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);
+ vnc_write(vs, vs->tight->tight.buffer, bytes);
return 1;
}
@@ -964,7 +964,7 @@ static int send_mono_rect(VncState *vs, int x, int y,
{
ssize_t bytes;
int stream = 1;
- int level = tight_conf[vs->tight.compression].mono_zlib_level;
+ int level = tight_conf[vs->tight->compression].mono_zlib_level;
#ifdef CONFIG_VNC_PNG
if (tight_can_send_png_rect(vs, w, h)) {
@@ -980,7 +980,7 @@ static int send_mono_rect(VncState *vs, int x, int y,
}
#endif
- bytes = ((w + 7) / 8) * h;
+ 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);
@@ -992,26 +992,26 @@ static int send_mono_rect(VncState *vs, int x, int y,
uint32_t buf[2] = {bg, fg};
size_t ret = sizeof (buf);
- if (vs->tight.pixel24) {
+ 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);
+ 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);
+ 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);
+ tight_encode_mono_rect8(vs->tight->tight.buffer, w, h, bg, fg);
break;
}
- vs->tight.tight.offset = bytes;
+ vs->tight->tight.offset = bytes;
bytes = tight_compress_data(vs, stream, bytes, level, Z_DEFAULT_STRATEGY);
return (bytes >= 0);
@@ -1041,7 +1041,7 @@ static void write_palette(int idx, uint32_t color, void *opaque)
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;
+ int level = tight_conf[vs->tight->compression].gradient_zlib_level;
ssize_t bytes;
if (vs->client_pf.bytes_per_pixel == 1) {
@@ -1051,23 +1051,23 @@ static bool send_gradient_rect(VncState *vs, int x, int y, int w, int 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));
+ buffer_reserve(&vs->tight->gradient, w * 3 * sizeof(int));
- if (vs->tight.pixel24) {
- tight_filter_gradient24(vs, vs->tight.tight.buffer, w, h);
+ 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);
+ 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);
+ tight_filter_gradient16(vs, (uint16_t *)vs->tight->tight.buffer, w, h);
bytes = 2;
}
- buffer_reset(&vs->tight.gradient);
+ buffer_reset(&vs->tight->gradient);
bytes = w * h * bytes;
- vs->tight.tight.offset = bytes;
+ vs->tight->tight.offset = bytes;
bytes = tight_compress_data(vs, stream, bytes,
level, Z_FILTERED);
@@ -1078,7 +1078,7 @@ 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 level = tight_conf[vs->tight->compression].idx_zlib_level;
int colors;
ssize_t bytes;
@@ -1105,12 +1105,12 @@ static int send_palette_rect(VncState *vs, int x, int y,
palette_iter(palette, write_palette, &priv);
vnc_write(vs, header, sizeof(header));
- if (vs->tight.pixel24) {
+ 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);
+ tight_encode_indexed_rect32(vs->tight->tight.buffer, w * h, palette);
break;
}
case 2:
@@ -1120,15 +1120,14 @@ static int send_palette_rect(VncState *vs, int x, int y,
palette_iter(palette, write_palette, &priv);
vnc_write(vs, header, sizeof(header));
- tight_encode_indexed_rect16(vs->tight.tight.buffer, w * h, palette);
+ tight_encode_indexed_rect16(vs->tight->tight.buffer, w * h, palette);
break;
}
default:
return -1; /* No palette for 8bits colors */
- break;
}
bytes = w * h;
- vs->tight.tight.offset = bytes;
+ vs->tight->tight.offset = bytes;
bytes = tight_compress_data(vs, stream, bytes,
level, Z_DEFAULT_STRATEGY);
@@ -1147,7 +1146,7 @@ static int send_palette_rect(VncState *vs, int x, int y,
static void jpeg_init_destination(j_compress_ptr cinfo)
{
VncState *vs = cinfo->client_data;
- Buffer *buffer = &vs->tight.jpeg;
+ 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);
@@ -1157,7 +1156,7 @@ static void jpeg_init_destination(j_compress_ptr cinfo)
static boolean jpeg_empty_output_buffer(j_compress_ptr cinfo)
{
VncState *vs = cinfo->client_data;
- Buffer *buffer = &vs->tight.jpeg;
+ Buffer *buffer = &vs->tight->jpeg;
buffer->offset = buffer->capacity;
buffer_reserve(buffer, 2048);
@@ -1169,7 +1168,7 @@ static boolean jpeg_empty_output_buffer(j_compress_ptr cinfo)
static void jpeg_term_destination(j_compress_ptr cinfo)
{
VncState *vs = cinfo->client_data;
- Buffer *buffer = &vs->tight.jpeg;
+ Buffer *buffer = &vs->tight->jpeg;
buffer->offset = buffer->capacity - cinfo->dest->free_in_buffer;
}
@@ -1188,7 +1187,7 @@ static int send_jpeg_rect(VncState *vs, int x, int y, int w, int h, int quality)
return send_full_color_rect(vs, x, y, w, h);
}
- buffer_reserve(&vs->tight.jpeg, 2048);
+ buffer_reserve(&vs->tight->jpeg, 2048);
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
@@ -1223,9 +1222,9 @@ static int send_jpeg_rect(VncState *vs, int x, int y, int w, int h, int quality)
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);
+ 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;
}
@@ -1241,7 +1240,7 @@ static void write_png_palette(int idx, uint32_t pix, void *opaque)
VncState *vs = priv->vs;
png_colorp color = &priv->png_palette[idx];
- if (vs->tight.pixel24)
+ 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;
@@ -1268,10 +1267,10 @@ static void png_write_data(png_structp png_ptr, png_bytep data,
{
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);
+ 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;
+ vs->tight->png.offset += length;
}
static void png_flush_data(png_structp png_ptr)
@@ -1296,8 +1295,8 @@ static int send_png_rect(VncState *vs, int x, int y, int w, int h,
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;
+ 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;
@@ -1341,21 +1340,23 @@ static int send_png_rect(VncState *vs, int x, int y, int w, int h,
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);
+ tight_encode_indexed_rect32(vs->tight->tight.buffer, w * h,
+ palette);
} else {
- tight_encode_indexed_rect16(vs->tight.tight.buffer, w * h, palette);
+ tight_encode_indexed_rect16(vs->tight->tight.buffer, w * h,
+ palette);
}
}
png_write_info(png_ptr, info_ptr);
- buffer_reserve(&vs->tight.png, 2048);
+ 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);
+ memcpy(buf, vs->tight->tight.buffer + (dy * w), w);
} else {
qemu_pixman_linebuf_fill(linebuf, vs->vd->server, w, x, y + dy);
}
@@ -1373,27 +1374,27 @@ static int send_png_rect(VncState *vs, int x, int y, int w, int h,
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);
+ 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);
+ 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;
+ 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;
+ 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,
@@ -1427,9 +1428,9 @@ static int send_sub_rect_jpeg(VncState *vs, int x, int y, int w, int h,
int ret;
if (colors == 0) {
- if (force || (tight_jpeg_conf[vs->tight.quality].jpeg_full &&
+ 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;
+ int quality = tight_conf[vs->tight->quality].jpeg_quality;
ret = send_jpeg_rect(vs, x, y, w, h, quality);
} else {
@@ -1441,9 +1442,9 @@ static int send_sub_rect_jpeg(VncState *vs, int x, int y, int w, int h,
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_jpeg_conf[vs->tight->quality].jpeg_idx &&
tight_detect_smooth_image(vs, w, h))) {
- int quality = tight_conf[vs->tight.quality].jpeg_quality;
+ int quality = tight_conf[vs->tight->quality].jpeg_quality;
ret = send_jpeg_rect(vs, x, y, w, h, quality);
} else {
@@ -1456,9 +1457,17 @@ static int send_sub_rect_jpeg(VncState *vs, int x, int y, int w, int h,
}
#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)
{
- VncPalette *palette = NULL;
uint32_t bg = 0, fg = 0;
int colors;
int ret = 0;
@@ -1467,46 +1476,53 @@ static int send_sub_rect(VncState *vs, int x, int y, int w, int h)
bool allow_jpeg = true;
#endif
- vnc_framebuffer_update(vs, x, y, w, h, vs->tight.type);
+ 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) {
+ 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) {
+ 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) {
+ 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, &fg, &bg, &palette);
+ 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, palette,
- force_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, palette);
+ 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, palette);
+ ret = send_sub_rect_nojpeg(vs, x, y, w, h, bg, fg, colors,
+ color_count_palette);
#endif
- palette_destroy(palette);
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_framebuffer_update(vs, x, y, w, h, vs->tight->type);
vnc_tight_start(vs);
vnc_raw_send_framebuffer_update(vs, x, y, w, h);
@@ -1524,8 +1540,8 @@ static int send_rect_simple(VncState *vs, int x, int y, int w, int h,
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;
+ 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;
@@ -1634,16 +1650,16 @@ static int tight_send_framebuffer_update(VncState *vs, int x, int y,
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;
+ vs->tight->pixel24 = true;
} else {
- vs->tight.pixel24 = false;
+ vs->tight->pixel24 = false;
}
#ifdef CONFIG_VNC_JPEG
- if (vs->tight.quality != (uint8_t)-1) {
+ 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) {
+ if (freq > tight_jpeg_conf[vs->tight->quality].jpeg_freq_threshold) {
return send_rect_simple(vs, x, y, w, h, false);
}
}
@@ -1655,8 +1671,8 @@ static int tight_send_framebuffer_update(VncState *vs, int x, int y,
/* 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);
+ 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);
}
@@ -1664,33 +1680,33 @@ static int tight_send_framebuffer_update(VncState *vs, int x, int y,
int vnc_tight_send_framebuffer_update(VncState *vs, int x, int y,
int w, int h)
{
- vs->tight.type = VNC_ENCODING_TIGHT;
+ 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;
+ 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]);
+ 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);
+ buffer_free(&vs->tight->tight);
+ buffer_free(&vs->tight->zlib);
+ buffer_free(&vs->tight->gradient);
#ifdef CONFIG_VNC_JPEG
- buffer_free(&vs->tight.jpeg);
+ buffer_free(&vs->tight->jpeg);
#endif
#ifdef CONFIG_VNC_PNG
- buffer_free(&vs->tight.png);
+ buffer_free(&vs->tight->png);
#endif
}
diff --git a/ui/vnc-enc-tight.h b/ui/vnc-enc-tight.h
index a3add788e..95c3dac02 100644
--- a/ui/vnc-enc-tight.h
+++ b/ui/vnc-enc-tight.h
@@ -27,8 +27,8 @@
* THE SOFTWARE.
*/
-#ifndef VNC_ENCODING_TIGHT_H
-#define VNC_ENCODING_TIGHT_H
+#ifndef VNC_ENC_TIGHT_H
+#define VNC_ENC_TIGHT_H
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* Tight Encoding.
@@ -180,4 +180,4 @@
#define VNC_TIGHT_DETECT_MIN_WIDTH 8
#define VNC_TIGHT_DETECT_MIN_HEIGHT 8
-#endif /* VNC_ENCODING_TIGHT_H */
+#endif /* VNC_ENC_TIGHT_H */
diff --git a/ui/vnc-enc-zlib.c b/ui/vnc-enc-zlib.c
index d1b97f251..900ae5b30 100644
--- a/ui/vnc-enc-zlib.c
+++ b/ui/vnc-enc-zlib.c
@@ -24,6 +24,7 @@
* THE SOFTWARE.
*/
+#include "qemu/osdep.h"
#include "vnc.h"
#define ZALLOC_ALIGNMENT 16
@@ -75,7 +76,8 @@ static int vnc_zlib_stop(VncState *vs)
zstream->zalloc = vnc_zlib_zalloc;
zstream->zfree = vnc_zlib_zfree;
- err = deflateInit2(zstream, vs->tight.compression, Z_DEFLATED, MAX_WBITS,
+ err = deflateInit2(zstream, vs->tight->compression, Z_DEFLATED,
+ MAX_WBITS,
MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY);
if (err != Z_OK) {
@@ -83,16 +85,16 @@ static int vnc_zlib_stop(VncState *vs)
return -1;
}
- vs->zlib.level = vs->tight.compression;
+ vs->zlib.level = vs->tight->compression;
zstream->opaque = vs;
}
- if (vs->tight.compression != vs->zlib.level) {
- if (deflateParams(zstream, vs->tight.compression,
+ 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;
+ vs->zlib.level = vs->tight->compression;
}
// reserve memory in output buffer
diff --git a/ui/vnc-enc-zrle.c b/ui/vnc-enc-zrle.c
index ed3b48465..bd33b8906 100644
--- a/ui/vnc-enc-zrle.c
+++ b/ui/vnc-enc-zrle.c
@@ -26,6 +26,7 @@
* THE SOFTWARE.
*/
+#include "qemu/osdep.h"
#include "vnc.h"
#include "vnc-enc-zrle.h"
@@ -36,18 +37,18 @@ static const int bits_per_packed_pixel[] = {
static void vnc_zrle_start(VncState *vs)
{
- buffer_reset(&vs->zrle.zrle);
+ 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;
+ 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;
+ 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,
@@ -55,24 +56,24 @@ static void *zrle_convert_fb(VncState *vs, int x, int y, int w, int h,
{
Buffer tmp;
- buffer_reset(&vs->zrle.fb);
- buffer_reserve(&vs->zrle.fb, w * h * bpp + bpp);
+ buffer_reset(&vs->zrle->fb);
+ buffer_reserve(&vs->zrle->fb, w * h * bpp + bpp);
tmp = vs->output;
- vs->output = vs->zrle.fb;
+ vs->output = vs->zrle->fb;
vnc_raw_send_framebuffer_update(vs, x, y, w, h);
- vs->zrle.fb = vs->output;
+ vs->zrle->fb = vs->output;
vs->output = tmp;
- return vs->zrle.fb.buffer;
+ return vs->zrle->fb.buffer;
}
static int zrle_compress_data(VncState *vs, int level)
{
- z_streamp zstream = &vs->zrle.stream;
+ z_streamp zstream = &vs->zrle->stream;
- buffer_reset(&vs->zrle.zlib);
+ buffer_reset(&vs->zrle->zlib);
if (zstream->opaque != vs) {
int err;
@@ -92,13 +93,13 @@ static int zrle_compress_data(VncState *vs, int level)
}
/* reserve memory in output buffer */
- buffer_reserve(&vs->zrle.zlib, vs->zrle.zrle.offset + 64);
+ 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 + vs->zrle.zlib.offset;
- zstream->avail_out = vs->zrle.zlib.capacity - vs->zrle.zlib.offset;
+ 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 */
@@ -107,8 +108,8 @@ static int zrle_compress_data(VncState *vs, int level)
return -1;
}
- vs->zrle.zlib.offset = vs->zrle.zlib.capacity - zstream->avail_out;
- return vs->zrle.zlib.offset;
+ 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
@@ -162,7 +163,6 @@ static void zrle_choose_palette_rle(VncState *vs, int w, int h,
if (packed_bytes < estimated_bytes) {
*use_rle = false;
*use_palette = true;
- estimated_bytes = packed_bytes;
}
}
}
@@ -199,56 +199,56 @@ static void zrle_write_u8(VncState *vs, uint8_t value)
#define ZRLE_BPP 8
#define ZYWRLE_ENDIAN ENDIAN_NO
-#include "vnc-enc-zrle-template.c"
+#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-template.c"
+#include "vnc-enc-zrle.c.inc"
#undef ZYWRLE_ENDIAN
#define ZYWRLE_ENDIAN ENDIAN_BIG
-#include "vnc-enc-zrle-template.c"
+#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-template.c"
+#include "vnc-enc-zrle.c.inc"
#undef ZYWRLE_ENDIAN
#define ZYWRLE_ENDIAN ENDIAN_BIG
-#include "vnc-enc-zrle-template.c"
+#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-template.c"
+#include "vnc-enc-zrle.c.inc"
#undef ZYWRLE_ENDIAN
#define ZYWRLE_ENDIAN ENDIAN_BIG
-#include "vnc-enc-zrle-template.c"
+#include "vnc-enc-zrle.c.inc"
#define ZRLE_COMPACT_PIXEL 24a
#undef ZYWRLE_ENDIAN
#define ZYWRLE_ENDIAN ENDIAN_LITTLE
-#include "vnc-enc-zrle-template.c"
+#include "vnc-enc-zrle.c.inc"
#undef ZYWRLE_ENDIAN
#define ZYWRLE_ENDIAN ENDIAN_BIG
-#include "vnc-enc-zrle-template.c"
+#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-template.c"
+#include "vnc-enc-zrle.c.inc"
#undef ZYWRLE_ENDIAN
#define ZYWRLE_ENDIAN ENDIAN_BIG
-#include "vnc-enc-zrle-template.c"
+#include "vnc-enc-zrle.c.inc"
#undef ZRLE_COMPACT_PIXEL
#undef ZRLE_BPP
@@ -259,14 +259,14 @@ static int zrle_send_framebuffer_update(VncState *vs, int x, int y,
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) {
+ 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) {
+ vs->zrle->type = VNC_ENCODING_ZRLE;
+ } else if (vs->tight->quality < 3) {
zywrle_level = 3;
- } else if (vs->tight.quality < 6) {
+ } else if (vs->tight->quality < 6) {
zywrle_level = 2;
} else {
zywrle_level = 1;
@@ -337,30 +337,30 @@ static int zrle_send_framebuffer_update(VncState *vs, int x, int y,
vnc_zrle_stop(vs);
bytes = zrle_compress_data(vs, Z_DEFAULT_COMPRESSION);
- vnc_framebuffer_update(vs, x, y, w, h, vs->zrle.type);
+ 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);
+ 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;
+ 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;
+ 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);
+ if (vs->zrle->stream.opaque) {
+ deflateEnd(&vs->zrle->stream);
}
- buffer_free(&vs->zrle.zrle);
- buffer_free(&vs->zrle.fb);
- buffer_free(&vs->zrle.zlib);
+ buffer_free(&vs->zrle->zrle);
+ buffer_free(&vs->zrle->fb);
+ buffer_free(&vs->zrle->zlib);
}
diff --git a/ui/vnc-enc-zrle-template.c b/ui/vnc-enc-zrle.c.inc
index 70ae624ee..c107d8aff 100644
--- a/ui/vnc-enc-zrle-template.c
+++ b/ui/vnc-enc-zrle.c.inc
@@ -22,7 +22,7 @@
*/
-#include <assert.h>
+#include "qemu/osdep.h"
#undef ZRLE_ENDIAN_SUFFIX
@@ -96,7 +96,7 @@ static void ZRLE_ENCODE(VncState *vs, int x, int y, int w, int h,
static void ZRLE_ENCODE_TILE(VncState *vs, ZRLE_PIXEL *data, int w, int h,
int zywrle_level)
{
- VncPalette *palette = &vs->zrle.palette;
+ VncPalette *palette = &vs->zrle->palette;
int runs = 0;
int single_pixels = 0;
diff --git a/ui/vnc-enc-zrle.h b/ui/vnc-enc-zrle.h
index 6b182132a..6535cb2aa 100644
--- a/ui/vnc-enc-zrle.h
+++ b/ui/vnc-enc-zrle.h
@@ -26,8 +26,8 @@
* THE SOFTWARE.
*/
-#ifndef VNC_ENCODING_ZRLE_H
-#define VNC_ENCODING_ZRLE_H
+#ifndef VNC_ENC_ZRLE_H
+#define VNC_ENC_ZRLE_H
/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* ZRLE - encoding combining Zlib compression, tiling, palettisation and
diff --git a/ui/vnc-enc-zywrle-template.c b/ui/vnc-enc-zywrle-template.c
index 561f7bfab..e9be55966 100644
--- a/ui/vnc-enc-zywrle-template.c
+++ b/ui/vnc-enc-zywrle-template.c
@@ -44,8 +44,8 @@ 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)
+ (Thanks Johannes Schindelin, author of LibVNC
+ Server/Client)
V0.01 : 2007/02/06 : Initial release
*/
@@ -100,6 +100,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endif
#define ZYWRLE_QUANTIZE
+#include "qemu/osdep.h"
#include "vnc-enc-zywrle.h"
#ifndef ZRLE_COMPACT_PIXEL
diff --git a/ui/vnc-enc-zywrle.h b/ui/vnc-enc-zywrle.h
index 1ff40b1f4..9b7f69897 100644
--- a/ui/vnc-enc-zywrle.h
+++ b/ui/vnc-enc-zywrle.h
@@ -41,169 +41,169 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
********************************************************************/
-#ifndef VNC_ENCODING_ZYWRLE_H
-#define VNC_ENCODING_ZYWRLE_H
+#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},
+ {0x0000F000, 0x00000000, 0x00000000},
+ {0x0000C000, 0x00F0F0F0, 0x00000000},
+ {0x0000C000, 0x00C0C0C0, 0x00F0F0F0},
/* {0x0000FF00, 0x00000000, 0x00000000},
- {0x0000FF00, 0x00FFFFFF, 0x00000000},
- {0x0000FF00, 0x00FFFFFF, 0x00FFFFFF}, */
+ {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,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 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,
+ 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,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 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,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+ 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[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[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[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]}},
};
@@ -214,53 +214,53 @@ static const int8_t *zywrle_param[3][3][3]={
#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); \
+ 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; \
+ 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); \
+ 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); \
+ 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; \
+ 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); \
+ 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]; \
+ 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; \
+ ((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)
@@ -305,7 +305,7 @@ static inline void harr(int8_t *px0, int8_t *px1)
|L1H0H1H0|L1H0H1H0|L1H0H1H0|L1H0H1H0| : level 1
In this method, H/L and X0/X1 is always same position.
- This lead us to more speed and less memory.
+ 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.
*/
@@ -443,27 +443,27 @@ static inline void filter_wavelet_square(int *buf, int width, int height,
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);
- }
+ 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);
+ }
}
@@ -471,16 +471,16 @@ static inline void wavelet(int *buf, int width, int height, int level)
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]; \
+ 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; \
+ ((int8_t*)dst)[2] = (int8_t)r; \
+ ((int8_t*)dst)[1] = (int8_t)g; \
+ ((int8_t*)dst)[0] = (int8_t)b; \
} while (0)
/*
@@ -502,22 +502,22 @@ static inline void wavelet(int *buf, int width, int height, int level)
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 = (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) { \
+ if (u == -128) { \
u += (0xFFFFFFFF - uvmask + 1); \
} \
- if (v == -128) { \
+ if (v == -128) { \
v += (0xFFFFFFFF - uvmask + 1); \
} \
} while (0)
diff --git a/ui/vnc-jobs.c b/ui/vnc-jobs.c
index 2d3fce815..dbbfbefe5 100644
--- a/ui/vnc-jobs.c
+++ b/ui/vnc-jobs.c
@@ -26,9 +26,12 @@
*/
+#include "qemu/osdep.h"
#include "vnc.h"
#include "vnc-jobs.h"
#include "qemu/sockets.h"
+#include "qemu/main-loop.h"
+#include "block/aio.h"
/*
* Locking:
@@ -53,7 +56,6 @@ struct VncJobQueue {
QemuCond cond;
QemuMutex mutex;
QemuThread thread;
- Buffer buffer;
bool exit;
QTAILQ_HEAD(, VncJob) jobs;
};
@@ -78,8 +80,9 @@ static void vnc_unlock_queue(VncJobQueue *queue)
VncJob *vnc_job_new(VncState *vs)
{
- VncJob *job = g_malloc0(sizeof(VncJob));
+ VncJob *job = g_new0(VncJob, 1);
+ assert(vs->magic == VNC_MAGIC);
job->vs = vs;
vnc_lock_queue(queue);
QLIST_INIT(&job->rectangles);
@@ -89,7 +92,7 @@ VncJob *vnc_job_new(VncState *vs)
int vnc_job_add_rect(VncJob *job, int x, int y, int w, int h)
{
- VncRectEntry *entry = g_malloc0(sizeof(VncRectEntry));
+ VncRectEntry *entry = g_new0(VncRectEntry, 1);
entry->rect.x = x;
entry->rect.y = y;
@@ -126,29 +129,6 @@ static bool vnc_has_job_locked(VncState *vs)
return false;
}
-bool vnc_has_job(VncState *vs)
-{
- bool ret;
-
- vnc_lock_queue(queue);
- ret = vnc_has_job_locked(vs);
- vnc_unlock_queue(queue);
- return ret;
-}
-
-void vnc_jobs_clear(VncState *vs)
-{
- VncJob *job, *tmp;
-
- vnc_lock_queue(queue);
- QTAILQ_FOREACH_SAFE(job, &queue->jobs, next, tmp) {
- if (job->vs == vs || !vs) {
- QTAILQ_REMOVE(&queue->jobs, job, next);
- }
- }
- vnc_unlock_queue(queue);
-}
-
void vnc_jobs_join(VncState *vs)
{
vnc_lock_queue(queue);
@@ -165,10 +145,24 @@ void vnc_jobs_consume_buffer(VncState *vs)
vnc_lock_output(vs);
if (vs->jobs_buffer.offset) {
- vnc_write(vs, vs->jobs_buffer.buffer, vs->jobs_buffer.offset);
- buffer_reset(&vs->jobs_buffer);
+ 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->csock != -1 && vs->abort != true;
+ flush = vs->ioc != NULL && vs->abort != true;
vnc_unlock_output(vs);
if (flush) {
@@ -181,6 +175,10 @@ void vnc_jobs_consume_buffer(VncState *vs)
*/
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;
@@ -192,28 +190,23 @@ static void vnc_async_encoding_start(VncState *orig, VncState *local)
local->zlib = orig->zlib;
local->hextile = orig->hextile;
local->zrle = orig->zrle;
- local->output = queue->buffer;
- local->csock = -1; /* Don't do any network work on this thread */
-
- buffer_reset(&local->output);
}
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;
-
- queue->buffer = local->output;
}
static int vnc_worker_thread_loop(VncJobQueue *queue)
{
VncJob *job;
VncRectEntry *entry, *tmp;
- VncState vs;
+ VncState vs = {};
int n_rectangles;
int saved_offset;
@@ -224,20 +217,30 @@ static int vnc_worker_thread_loop(VncJobQueue *queue)
/* 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->csock == -1 || job->vs->abort == true) {
+ 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;
@@ -250,8 +253,10 @@ static int vnc_worker_thread_loop(VncJobQueue *queue)
QLIST_FOREACH_SAFE(entry, &job->rectangles, next, tmp) {
int n;
- if (job->vs->csock == -1) {
+ if (job->vs->ioc == NULL) {
vnc_unlock_display(job->vs->vd);
+ /* Copy persistent encoding data */
+ vnc_async_encoding_end(job->vs, &vs);
goto disconnected;
}
@@ -270,14 +275,16 @@ static int vnc_worker_thread_loop(VncJobQueue *queue)
vs.output.buffer[saved_offset + 1] = n_rectangles & 0xFF;
vnc_lock_output(job->vs);
- if (job->vs->csock != -1) {
- buffer_reserve(&job->vs->jobs_buffer, vs.output.offset);
- buffer_append(&job->vs->jobs_buffer, vs.output.buffer,
- vs.output.offset);
+ 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);
+ 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);
@@ -287,12 +294,13 @@ disconnected:
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_malloc0(sizeof(VncJobQueue));
+ VncJobQueue *queue = g_new0(VncJobQueue, 1);
qemu_cond_init(&queue->cond);
qemu_mutex_init(&queue->mutex);
@@ -304,7 +312,6 @@ static void vnc_queue_clear(VncJobQueue *q)
{
qemu_cond_destroy(&queue->cond);
qemu_mutex_destroy(&queue->mutex);
- buffer_free(&queue->buffer);
g_free(q);
queue = NULL; /* Unset global queue */
}
@@ -333,19 +340,7 @@ void vnc_start_worker_thread(void)
return ;
q = vnc_queue_init();
- qemu_thread_create(&q->thread, vnc_worker_thread, q, QEMU_THREAD_DETACHED);
+ qemu_thread_create(&q->thread, "vnc_worker", vnc_worker_thread, q,
+ QEMU_THREAD_DETACHED);
queue = q; /* Set global queue */
}
-
-void vnc_stop_worker_thread(void)
-{
- if (!vnc_worker_thread_running())
- return ;
-
- /* Remove all jobs and wake up the thread */
- vnc_lock_queue(queue);
- queue->exit = true;
- vnc_unlock_queue(queue);
- vnc_jobs_clear(NULL);
- qemu_cond_broadcast(&queue->cond);
-}
diff --git a/ui/vnc-jobs.h b/ui/vnc-jobs.h
index 31da103fa..59f66bcc3 100644
--- a/ui/vnc-jobs.h
+++ b/ui/vnc-jobs.h
@@ -34,13 +34,10 @@
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);
-bool vnc_has_job(VncState *vs);
-void vnc_jobs_clear(VncState *vs);
void vnc_jobs_join(VncState *vs);
void vnc_jobs_consume_buffer(VncState *vs);
void vnc_start_worker_thread(void);
-void vnc_stop_worker_thread(void);
/* Locks */
static inline int vnc_trylock_display(VncDisplay *vd)
diff --git a/ui/vnc-palette.c b/ui/vnc-palette.c
index c130deee9..dc7c0ba99 100644
--- a/ui/vnc-palette.c
+++ b/ui/vnc-palette.c
@@ -26,9 +26,8 @@
* THE SOFTWARE.
*/
+#include "qemu/osdep.h"
#include "vnc-palette.h"
-#include <glib.h>
-#include <string.h>
static VncPaletteEntry *palette_find(const VncPalette *palette,
uint32_t color, unsigned int hash)
diff --git a/ui/vnc-palette.h b/ui/vnc-palette.h
index d02f0236c..e9f0eaf73 100644
--- a/ui/vnc-palette.h
+++ b/ui/vnc-palette.h
@@ -29,10 +29,7 @@
#ifndef VNC_PALETTE_H
#define VNC_PALETTE_H
-#include "qapi/qmp/qlist.h"
#include "qemu/queue.h"
-#include <stdint.h>
-#include <stdbool.h>
#define VNC_PALETTE_HASH_SIZE 256
#define VNC_PALETTE_MAX_SIZE 256
diff --git a/ui/vnc-stubs.c b/ui/vnc-stubs.c
new file mode 100644
index 000000000..c6b737dce
--- /dev/null
+++ b/ui/vnc-stubs.c
@@ -0,0 +1,25 @@
+#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;
+};
+QemuOpts *vnc_parse(const char *str, Error **errp)
+{
+ if (strcmp(str, "none") == 0) {
+ return NULL;
+ }
+ error_setg(errp, "VNC support is disabled");
+ return NULL;
+}
+int vnc_init_func(void *opaque, QemuOpts *opts, Error **errp)
+{
+ error_setg(errp, "VNC support is disabled");
+ return -1;
+}
diff --git a/ui/vnc-tls.c b/ui/vnc-tls.c
deleted file mode 100644
index 50275de64..000000000
--- a/ui/vnc-tls.c
+++ /dev/null
@@ -1,492 +0,0 @@
-/*
- * QEMU VNC display driver: TLS helpers
- *
- * 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-x509.h"
-#include "vnc.h"
-#include "qemu/sockets.h"
-
-#if defined(_VNC_DEBUG) && _VNC_DEBUG >= 2
-/* Very verbose, so only enabled for _VNC_DEBUG >= 2 */
-static void vnc_debug_gnutls_log(int level, const char* str) {
- VNC_DEBUG("%d %s", level, str);
-}
-#endif /* defined(_VNC_DEBUG) && _VNC_DEBUG >= 2 */
-
-
-#define DH_BITS 1024
-static gnutls_dh_params_t dh_params;
-
-static int vnc_tls_initialize(void)
-{
- static int tlsinitialized = 0;
-
- if (tlsinitialized)
- return 1;
-
- if (gnutls_global_init () < 0)
- return 0;
-
- /* XXX ought to re-generate diffie-hellman params periodically */
- if (gnutls_dh_params_init (&dh_params) < 0)
- return 0;
- if (gnutls_dh_params_generate2 (dh_params, DH_BITS) < 0)
- return 0;
-
-#if defined(_VNC_DEBUG) && _VNC_DEBUG >= 2
- gnutls_global_set_log_level(10);
- gnutls_global_set_log_function(vnc_debug_gnutls_log);
-#endif
-
- tlsinitialized = 1;
-
- return 1;
-}
-
-static ssize_t vnc_tls_push(gnutls_transport_ptr_t transport,
- const void *data,
- size_t len) {
- struct VncState *vs = (struct VncState *)transport;
- int ret;
-
- retry:
- ret = send(vs->csock, data, len, 0);
- if (ret < 0) {
- if (errno == EINTR)
- goto retry;
- return -1;
- }
- return ret;
-}
-
-
-static ssize_t vnc_tls_pull(gnutls_transport_ptr_t transport,
- void *data,
- size_t len) {
- struct VncState *vs = (struct VncState *)transport;
- int ret;
-
- retry:
- ret = qemu_recv(vs->csock, data, len, 0);
- if (ret < 0) {
- if (errno == EINTR)
- goto retry;
- return -1;
- }
- return ret;
-}
-
-
-static gnutls_anon_server_credentials_t vnc_tls_initialize_anon_cred(void)
-{
- gnutls_anon_server_credentials_t anon_cred;
- int ret;
-
- if ((ret = gnutls_anon_allocate_server_credentials(&anon_cred)) < 0) {
- VNC_DEBUG("Cannot allocate credentials %s\n", gnutls_strerror(ret));
- return NULL;
- }
-
- gnutls_anon_set_server_dh_params(anon_cred, dh_params);
-
- return anon_cred;
-}
-
-
-static gnutls_certificate_credentials_t vnc_tls_initialize_x509_cred(VncDisplay *vd)
-{
- gnutls_certificate_credentials_t x509_cred;
- int ret;
-
- if (!vd->tls.x509cacert) {
- VNC_DEBUG("No CA x509 certificate specified\n");
- return NULL;
- }
- if (!vd->tls.x509cert) {
- VNC_DEBUG("No server x509 certificate specified\n");
- return NULL;
- }
- if (!vd->tls.x509key) {
- VNC_DEBUG("No server private key specified\n");
- return NULL;
- }
-
- if ((ret = gnutls_certificate_allocate_credentials(&x509_cred)) < 0) {
- VNC_DEBUG("Cannot allocate credentials %s\n", gnutls_strerror(ret));
- return NULL;
- }
- if ((ret = gnutls_certificate_set_x509_trust_file(x509_cred,
- vd->tls.x509cacert,
- GNUTLS_X509_FMT_PEM)) < 0) {
- VNC_DEBUG("Cannot load CA certificate %s\n", gnutls_strerror(ret));
- gnutls_certificate_free_credentials(x509_cred);
- return NULL;
- }
-
- if ((ret = gnutls_certificate_set_x509_key_file (x509_cred,
- vd->tls.x509cert,
- vd->tls.x509key,
- GNUTLS_X509_FMT_PEM)) < 0) {
- VNC_DEBUG("Cannot load certificate & key %s\n", gnutls_strerror(ret));
- gnutls_certificate_free_credentials(x509_cred);
- return NULL;
- }
-
- if (vd->tls.x509cacrl) {
- if ((ret = gnutls_certificate_set_x509_crl_file(x509_cred,
- vd->tls.x509cacrl,
- GNUTLS_X509_FMT_PEM)) < 0) {
- VNC_DEBUG("Cannot load CRL %s\n", gnutls_strerror(ret));
- gnutls_certificate_free_credentials(x509_cred);
- return NULL;
- }
- }
-
- gnutls_certificate_set_dh_params (x509_cred, dh_params);
-
- return x509_cred;
-}
-
-
-int vnc_tls_validate_certificate(struct VncState *vs)
-{
- int ret;
- unsigned int status;
- const gnutls_datum_t *certs;
- unsigned int nCerts, i;
- time_t now;
-
- VNC_DEBUG("Validating client certificate\n");
- if ((ret = gnutls_certificate_verify_peers2 (vs->tls.session, &status)) < 0) {
- VNC_DEBUG("Verify failed %s\n", gnutls_strerror(ret));
- return -1;
- }
-
- if ((now = time(NULL)) == ((time_t)-1)) {
- return -1;
- }
-
- if (status != 0) {
- if (status & GNUTLS_CERT_INVALID)
- VNC_DEBUG("The certificate is not trusted.\n");
-
- if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
- VNC_DEBUG("The certificate hasn't got a known issuer.\n");
-
- if (status & GNUTLS_CERT_REVOKED)
- VNC_DEBUG("The certificate has been revoked.\n");
-
- if (status & GNUTLS_CERT_INSECURE_ALGORITHM)
- VNC_DEBUG("The certificate uses an insecure algorithm\n");
-
- return -1;
- } else {
- VNC_DEBUG("Certificate is valid!\n");
- }
-
- /* Only support x509 for now */
- if (gnutls_certificate_type_get(vs->tls.session) != GNUTLS_CRT_X509)
- return -1;
-
- if (!(certs = gnutls_certificate_get_peers(vs->tls.session, &nCerts)))
- return -1;
-
- for (i = 0 ; i < nCerts ; i++) {
- gnutls_x509_crt_t cert;
- VNC_DEBUG ("Checking certificate chain %d\n", i);
- if (gnutls_x509_crt_init (&cert) < 0)
- return -1;
-
- if (gnutls_x509_crt_import(cert, &certs[i], GNUTLS_X509_FMT_DER) < 0) {
- gnutls_x509_crt_deinit (cert);
- return -1;
- }
-
- if (gnutls_x509_crt_get_expiration_time (cert) < now) {
- VNC_DEBUG("The certificate has expired\n");
- gnutls_x509_crt_deinit (cert);
- return -1;
- }
-
- if (gnutls_x509_crt_get_activation_time (cert) > now) {
- VNC_DEBUG("The certificate is not yet activated\n");
- gnutls_x509_crt_deinit (cert);
- return -1;
- }
-
- if (gnutls_x509_crt_get_activation_time (cert) > now) {
- VNC_DEBUG("The certificate is not yet activated\n");
- gnutls_x509_crt_deinit (cert);
- return -1;
- }
-
- if (i == 0) {
- size_t dnameSize = 1024;
- vs->tls.dname = g_malloc(dnameSize);
- requery:
- if ((ret = gnutls_x509_crt_get_dn (cert, vs->tls.dname, &dnameSize)) != 0) {
- if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER) {
- vs->tls.dname = g_realloc(vs->tls.dname, dnameSize);
- goto requery;
- }
- gnutls_x509_crt_deinit (cert);
- VNC_DEBUG("Cannot get client distinguished name: %s",
- gnutls_strerror (ret));
- return -1;
- }
-
- if (vs->vd->tls.x509verify) {
- int allow;
- if (!vs->vd->tls.acl) {
- VNC_DEBUG("no ACL activated, allowing access");
- gnutls_x509_crt_deinit (cert);
- continue;
- }
-
- allow = qemu_acl_party_is_allowed(vs->vd->tls.acl,
- vs->tls.dname);
-
- VNC_DEBUG("TLS x509 ACL check for %s is %s\n",
- vs->tls.dname, allow ? "allowed" : "denied");
- if (!allow) {
- gnutls_x509_crt_deinit (cert);
- return -1;
- }
- }
- }
-
- gnutls_x509_crt_deinit (cert);
- }
-
- return 0;
-}
-
-#if defined(GNUTLS_VERSION_NUMBER) && \
- GNUTLS_VERSION_NUMBER >= 0x020200 /* 2.2.0 */
-
-static int vnc_set_gnutls_priority(gnutls_session_t s, int x509)
-{
- const char *priority = x509 ? "NORMAL" : "NORMAL:+ANON-DH";
- int rc;
-
- rc = gnutls_priority_set_direct(s, priority, NULL);
- if (rc != GNUTLS_E_SUCCESS) {
- return -1;
- }
- return 0;
-}
-
-#else
-
-static int vnc_set_gnutls_priority(gnutls_session_t s, int x509)
-{
- static const int cert_types[] = { GNUTLS_CRT_X509, 0 };
- static const int protocols[] = {
- GNUTLS_TLS1_1, GNUTLS_TLS1_0, GNUTLS_SSL3, 0
- };
- static const int kx_anon[] = { GNUTLS_KX_ANON_DH, 0 };
- static const int kx_x509[] = {
- GNUTLS_KX_DHE_DSS, GNUTLS_KX_RSA,
- GNUTLS_KX_DHE_RSA, GNUTLS_KX_SRP, 0
- };
- int rc;
-
- rc = gnutls_kx_set_priority(s, x509 ? kx_x509 : kx_anon);
- if (rc != GNUTLS_E_SUCCESS) {
- return -1;
- }
-
- rc = gnutls_certificate_type_set_priority(s, cert_types);
- if (rc != GNUTLS_E_SUCCESS) {
- return -1;
- }
-
- rc = gnutls_protocol_set_priority(s, protocols);
- if (rc != GNUTLS_E_SUCCESS) {
- return -1;
- }
- return 0;
-}
-
-#endif
-
-int vnc_tls_client_setup(struct VncState *vs,
- int needX509Creds) {
- VncStateTLS *tls;
-
- VNC_DEBUG("Do TLS setup\n");
-#ifdef CONFIG_VNC_WS
- if (vs->websocket) {
- tls = &vs->ws_tls;
- } else
-#endif /* CONFIG_VNC_WS */
- {
- tls = &vs->tls;
- }
- if (vnc_tls_initialize() < 0) {
- VNC_DEBUG("Failed to init TLS\n");
- vnc_client_error(vs);
- return -1;
- }
- if (tls->session == NULL) {
- if (gnutls_init(&tls->session, GNUTLS_SERVER) < 0) {
- vnc_client_error(vs);
- return -1;
- }
-
- if (gnutls_set_default_priority(tls->session) < 0) {
- gnutls_deinit(tls->session);
- tls->session = NULL;
- vnc_client_error(vs);
- return -1;
- }
-
- if (vnc_set_gnutls_priority(tls->session, needX509Creds) < 0) {
- gnutls_deinit(tls->session);
- tls->session = NULL;
- vnc_client_error(vs);
- return -1;
- }
-
- if (needX509Creds) {
- gnutls_certificate_server_credentials x509_cred = vnc_tls_initialize_x509_cred(vs->vd);
- if (!x509_cred) {
- gnutls_deinit(tls->session);
- tls->session = NULL;
- vnc_client_error(vs);
- return -1;
- }
- if (gnutls_credentials_set(tls->session, GNUTLS_CRD_CERTIFICATE, x509_cred) < 0) {
- gnutls_deinit(tls->session);
- tls->session = NULL;
- gnutls_certificate_free_credentials(x509_cred);
- vnc_client_error(vs);
- return -1;
- }
- if (vs->vd->tls.x509verify) {
- VNC_DEBUG("Requesting a client certificate\n");
- gnutls_certificate_server_set_request (tls->session, GNUTLS_CERT_REQUEST);
- }
-
- } else {
- gnutls_anon_server_credentials_t anon_cred = vnc_tls_initialize_anon_cred();
- if (!anon_cred) {
- gnutls_deinit(tls->session);
- tls->session = NULL;
- vnc_client_error(vs);
- return -1;
- }
- if (gnutls_credentials_set(tls->session, GNUTLS_CRD_ANON, anon_cred) < 0) {
- gnutls_deinit(tls->session);
- tls->session = NULL;
- gnutls_anon_free_server_credentials(anon_cred);
- vnc_client_error(vs);
- return -1;
- }
- }
-
- gnutls_transport_set_ptr(tls->session, (gnutls_transport_ptr_t)vs);
- gnutls_transport_set_push_function(tls->session, vnc_tls_push);
- gnutls_transport_set_pull_function(tls->session, vnc_tls_pull);
- }
- return 0;
-}
-
-
-void vnc_tls_client_cleanup(struct VncState *vs)
-{
- if (vs->tls.session) {
- gnutls_deinit(vs->tls.session);
- vs->tls.session = NULL;
- }
- vs->tls.wiremode = VNC_WIREMODE_CLEAR;
- g_free(vs->tls.dname);
-#ifdef CONFIG_VNC_WS
- if (vs->ws_tls.session) {
- gnutls_deinit(vs->ws_tls.session);
- vs->ws_tls.session = NULL;
- }
- vs->ws_tls.wiremode = VNC_WIREMODE_CLEAR;
- g_free(vs->ws_tls.dname);
-#endif /* CONFIG_VNC_WS */
-}
-
-
-
-static int vnc_set_x509_credential(VncDisplay *vd,
- const char *certdir,
- const char *filename,
- char **cred,
- int ignoreMissing)
-{
- struct stat sb;
-
- if (*cred) {
- g_free(*cred);
- *cred = NULL;
- }
-
- *cred = g_malloc(strlen(certdir) + strlen(filename) + 2);
-
- strcpy(*cred, certdir);
- strcat(*cred, "/");
- strcat(*cred, filename);
-
- VNC_DEBUG("Check %s\n", *cred);
- if (stat(*cred, &sb) < 0) {
- g_free(*cred);
- *cred = NULL;
- if (ignoreMissing && errno == ENOENT)
- return 0;
- return -1;
- }
-
- return 0;
-}
-
-
-int vnc_tls_set_x509_creds_dir(VncDisplay *vd,
- const char *certdir)
-{
- if (vnc_set_x509_credential(vd, certdir, X509_CA_CERT_FILE, &vd->tls.x509cacert, 0) < 0)
- goto cleanup;
- if (vnc_set_x509_credential(vd, certdir, X509_CA_CRL_FILE, &vd->tls.x509cacrl, 1) < 0)
- goto cleanup;
- if (vnc_set_x509_credential(vd, certdir, X509_SERVER_CERT_FILE, &vd->tls.x509cert, 0) < 0)
- goto cleanup;
- if (vnc_set_x509_credential(vd, certdir, X509_SERVER_KEY_FILE, &vd->tls.x509key, 0) < 0)
- goto cleanup;
-
- return 0;
-
- cleanup:
- g_free(vd->tls.x509cacert);
- g_free(vd->tls.x509cacrl);
- g_free(vd->tls.x509cert);
- g_free(vd->tls.x509key);
- vd->tls.x509cacert = vd->tls.x509cacrl = vd->tls.x509cert = vd->tls.x509key = NULL;
- return -1;
-}
-
diff --git a/ui/vnc-tls.h b/ui/vnc-tls.h
deleted file mode 100644
index 36a2227fe..000000000
--- a/ui/vnc-tls.h
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * QEMU VNC display driver. TLS helpers
- *
- * 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_TLS_H__
-#define __QEMU_VNC_TLS_H__
-
-#include <gnutls/gnutls.h>
-#include <gnutls/x509.h>
-
-#include "qemu/acl.h"
-
-enum {
- VNC_WIREMODE_CLEAR,
- VNC_WIREMODE_TLS,
-};
-
-typedef struct VncDisplayTLS VncDisplayTLS;
-typedef struct VncStateTLS VncStateTLS;
-
-/* Server state */
-struct VncDisplayTLS {
- int x509verify; /* Non-zero if server requests & validates client cert */
- qemu_acl *acl;
-
- /* Paths to x509 certs/keys */
- char *x509cacert;
- char *x509cacrl;
- char *x509cert;
- char *x509key;
-};
-
-/* Per client state */
-struct VncStateTLS {
- /* Whether data is being TLS encrypted yet */
- int wiremode;
- gnutls_session_t session;
-
- /* Client's Distinguished Name from the x509 cert */
- char *dname;
-};
-
-int vnc_tls_client_setup(VncState *vs, int x509Creds);
-void vnc_tls_client_cleanup(VncState *vs);
-
-int vnc_tls_validate_certificate(VncState *vs);
-
-int vnc_tls_set_x509_creds_dir(VncDisplay *vd,
- const char *path);
-
-
-#endif /* __QEMU_VNC_TLS_H__ */
-
diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c
index df8931573..6d79f3e5a 100644
--- a/ui/vnc-ws.c
+++ b/ui/vnc-ws.c
@@ -18,331 +18,133 @@
* 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"
-#ifdef CONFIG_VNC_TLS
-#include "qemu/sockets.h"
-
-static void vncws_tls_handshake_io(void *opaque);
-
-static int vncws_start_tls_handshake(struct VncState *vs)
+static void vncws_tls_handshake_done(QIOTask *task,
+ gpointer user_data)
{
- int ret = gnutls_handshake(vs->ws_tls.session);
+ VncState *vs = user_data;
+ Error *err = NULL;
- if (ret < 0) {
- if (!gnutls_error_is_fatal(ret)) {
- VNC_DEBUG("Handshake interrupted (blocking)\n");
- if (!gnutls_record_get_direction(vs->ws_tls.session)) {
- qemu_set_fd_handler(vs->csock, vncws_tls_handshake_io,
- NULL, vs);
- } else {
- qemu_set_fd_handler(vs->csock, NULL, vncws_tls_handshake_io,
- vs);
- }
- return 0;
- }
- VNC_DEBUG("Handshake failed %s\n", gnutls_strerror(ret));
+ if (qio_task_propagate_error(task, &err)) {
+ VNC_DEBUG("Handshake failed %s\n", error_get_pretty(err));
vnc_client_error(vs);
- return -1;
+ 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);
}
-
- VNC_DEBUG("Handshake done, switching to TLS data mode\n");
- vs->ws_tls.wiremode = VNC_WIREMODE_TLS;
- qemu_set_fd_handler2(vs->csock, NULL, vncws_handshake_read, NULL, vs);
-
- return 0;
}
-static void vncws_tls_handshake_io(void *opaque)
-{
- struct VncState *vs = (struct VncState *)opaque;
-
- VNC_DEBUG("Handshake IO continue\n");
- vncws_start_tls_handshake(vs);
-}
-void vncws_tls_handshake_peek(void *opaque)
+gboolean vncws_tls_handshake_io(QIOChannel *ioc G_GNUC_UNUSED,
+ GIOCondition condition,
+ void *opaque)
{
VncState *vs = opaque;
- long ret;
+ QIOChannelTLS *tls;
+ Error *err = NULL;
- if (!vs->ws_tls.session) {
- char peek[4];
- ret = qemu_recv(vs->csock, peek, sizeof(peek), MSG_PEEK);
- if (ret && (strncmp(peek, "\x16", 1) == 0
- || strncmp(peek, "\x80", 1) == 0)) {
- VNC_DEBUG("TLS Websocket connection recognized");
- vnc_tls_client_setup(vs, 1);
- vncws_start_tls_handshake(vs);
- } else {
- vncws_handshake_read(vs);
- }
- } else {
- qemu_set_fd_handler2(vs->csock, NULL, vncws_handshake_read, NULL, vs);
+ if (vs->ioc_tag) {
+ g_source_remove(vs->ioc_tag);
+ vs->ioc_tag = 0;
}
-}
-#endif /* CONFIG_VNC_TLS */
-void vncws_handshake_read(void *opaque)
-{
- VncState *vs = opaque;
- uint8_t *handshake_end;
- long ret;
- buffer_reserve(&vs->ws_input, 4096);
- ret = vnc_client_read_buf(vs, buffer_end(&vs->ws_input), 4096);
-
- if (!ret) {
- if (vs->csock == -1) {
- vnc_disconnect_finish(vs);
- }
- return;
- }
- vs->ws_input.offset += ret;
-
- handshake_end = (uint8_t *)g_strstr_len((char *)vs->ws_input.buffer,
- vs->ws_input.offset, WS_HANDSHAKE_END);
- if (handshake_end) {
- qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs);
- vncws_process_handshake(vs, vs->ws_input.buffer, vs->ws_input.offset);
- buffer_advance(&vs->ws_input, handshake_end - vs->ws_input.buffer +
- strlen(WS_HANDSHAKE_END));
+ if (condition & (G_IO_HUP | G_IO_ERR)) {
+ vnc_client_error(vs);
+ return TRUE;
}
-}
-
-long vnc_client_read_ws(VncState *vs)
-{
- int ret, err;
- uint8_t *payload;
- size_t payload_size, frame_size;
- VNC_DEBUG("Read websocket %p size %zd offset %zd\n", vs->ws_input.buffer,
- vs->ws_input.capacity, vs->ws_input.offset);
- buffer_reserve(&vs->ws_input, 4096);
- ret = vnc_client_read_buf(vs, buffer_end(&vs->ws_input), 4096);
- if (!ret) {
- return 0;
+ 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;
}
- vs->ws_input.offset += ret;
- /* make sure that nothing is left in the ws_input buffer */
- do {
- err = vncws_decode_frame(&vs->ws_input, &payload,
- &payload_size, &frame_size);
- if (err <= 0) {
- return err;
- }
+ qio_channel_set_name(QIO_CHANNEL(tls), "vnc-ws-server-tls");
- buffer_reserve(&vs->input, payload_size);
- buffer_append(&vs->input, payload, payload_size);
+ 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);
- buffer_advance(&vs->ws_input, frame_size);
- } while (vs->ws_input.offset > 0);
+ qio_channel_tls_handshake(tls,
+ vncws_tls_handshake_done,
+ vs,
+ NULL,
+ NULL);
- return ret;
+ return TRUE;
}
-long vnc_client_write_ws(VncState *vs)
-{
- long ret;
- VNC_DEBUG("Write WS: Pending output %p size %zd offset %zd\n",
- vs->output.buffer, vs->output.capacity, vs->output.offset);
- vncws_encode_frame(&vs->ws_output, vs->output.buffer, vs->output.offset);
- buffer_reset(&vs->output);
- ret = vnc_client_write_buf(vs, vs->ws_output.buffer, vs->ws_output.offset);
- if (!ret) {
- return 0;
- }
-
- buffer_advance(&vs->ws_output, ret);
-
- if (vs->ws_output.offset == 0) {
- qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs);
- }
-
- return ret;
-}
-
-static char *vncws_extract_handshake_entry(const char *handshake,
- size_t handshake_len, const char *name)
-{
- char *begin, *end, *ret = NULL;
- char *line = g_strdup_printf("%s%s: ", WS_HANDSHAKE_DELIM, name);
- begin = g_strstr_len(handshake, handshake_len, line);
- if (begin != NULL) {
- begin += strlen(line);
- end = g_strstr_len(begin, handshake_len - (begin - handshake),
- WS_HANDSHAKE_DELIM);
- if (end != NULL) {
- ret = g_strndup(begin, end - begin);
- }
- }
- g_free(line);
- return ret;
-}
-static void vncws_send_handshake_response(VncState *vs, const char* key)
+static void vncws_handshake_done(QIOTask *task,
+ gpointer user_data)
{
- char combined_key[WS_CLIENT_KEY_LEN + WS_GUID_LEN + 1];
- unsigned char hash[SHA1_DIGEST_LEN];
- size_t hash_size = sizeof(hash);
- char *accept = NULL, *response = NULL;
- gnutls_datum_t in;
- int ret;
-
- g_strlcpy(combined_key, key, WS_CLIENT_KEY_LEN + 1);
- g_strlcat(combined_key, WS_GUID, WS_CLIENT_KEY_LEN + WS_GUID_LEN + 1);
+ VncState *vs = user_data;
+ Error *err = NULL;
- /* hash and encode it */
- in.data = (void *)combined_key;
- in.size = WS_CLIENT_KEY_LEN + WS_GUID_LEN;
- ret = gnutls_fingerprint(GNUTLS_DIG_SHA1, &in, hash, &hash_size);
- if (ret == GNUTLS_E_SUCCESS && hash_size <= SHA1_DIGEST_LEN) {
- accept = g_base64_encode(hash, hash_size);
- }
- if (accept == NULL) {
- VNC_DEBUG("Hashing Websocket combined key failed\n");
+ if (qio_task_propagate_error(task, &err)) {
+ VNC_DEBUG("Websock handshake failed %s\n", error_get_pretty(err));
vnc_client_error(vs);
- return;
- }
-
- response = g_strdup_printf(WS_HANDSHAKE, accept);
- vnc_write(vs, response, strlen(response));
- vnc_flush(vs);
-
- g_free(accept);
- g_free(response);
-
- vs->encode_ws = 1;
- vnc_init_state(vs);
-}
-
-void vncws_process_handshake(VncState *vs, uint8_t *line, size_t size)
-{
- char *protocols = vncws_extract_handshake_entry((const char *)line, size,
- "Sec-WebSocket-Protocol");
- char *version = vncws_extract_handshake_entry((const char *)line, size,
- "Sec-WebSocket-Version");
- char *key = vncws_extract_handshake_entry((const char *)line, size,
- "Sec-WebSocket-Key");
-
- if (protocols && version && key
- && g_strrstr(protocols, "binary")
- && !strcmp(version, WS_SUPPORTED_VERSION)
- && strlen(key) == WS_CLIENT_KEY_LEN) {
- vncws_send_handshake_response(vs, key);
+ error_free(err);
} else {
- VNC_DEBUG("Defective Websockets header or unsupported protocol\n");
- vnc_client_error(vs);
+ 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);
}
-
- g_free(protocols);
- g_free(version);
- g_free(key);
}
-void vncws_encode_frame(Buffer *output, const void *payload,
- const size_t payload_size)
-{
- size_t header_size = 0;
- unsigned char opcode = WS_OPCODE_BINARY_FRAME;
- union {
- char buf[WS_HEAD_MAX_LEN];
- WsHeader ws;
- } header;
-
- if (!payload_size) {
- return;
- }
- header.ws.b0 = 0x80 | (opcode & 0x0f);
- if (payload_size <= 125) {
- header.ws.b1 = (uint8_t)payload_size;
- header_size = 2;
- } else if (payload_size < 65536) {
- header.ws.b1 = 0x7e;
- header.ws.u.s16.l16 = cpu_to_be16((uint16_t)payload_size);
- header_size = 4;
- } else {
- header.ws.b1 = 0x7f;
- header.ws.u.s64.l64 = cpu_to_be64(payload_size);
- header_size = 10;
- }
-
- buffer_reserve(output, header_size + payload_size);
- buffer_append(output, header.buf, header_size);
- buffer_append(output, payload, payload_size);
-}
-
-int vncws_decode_frame(Buffer *input, uint8_t **payload,
- size_t *payload_size, size_t *frame_size)
+gboolean vncws_handshake_io(QIOChannel *ioc G_GNUC_UNUSED,
+ GIOCondition condition,
+ void *opaque)
{
- unsigned char opcode = 0, fin = 0, has_mask = 0;
- size_t header_size = 0;
- uint32_t *payload32;
- WsHeader *header = (WsHeader *)input->buffer;
- WsMask mask;
- int i;
-
- if (input->offset < WS_HEAD_MIN_LEN + 4) {
- /* header not complete */
- return 0;
- }
-
- fin = (header->b0 & 0x80) >> 7;
- opcode = header->b0 & 0x0f;
- has_mask = (header->b1 & 0x80) >> 7;
- *payload_size = header->b1 & 0x7f;
-
- if (opcode == WS_OPCODE_CLOSE) {
- /* disconnect */
- return -1;
- }
+ VncState *vs = opaque;
+ QIOChannelWebsock *wioc;
- /* Websocket frame sanity check:
- * * Websocket fragmentation is not supported.
- * * All websockets frames sent by a client have to be masked.
- * * Only binary encoding is supported.
- */
- if (!fin || !has_mask || opcode != WS_OPCODE_BINARY_FRAME) {
- VNC_DEBUG("Received faulty/unsupported Websocket frame\n");
- return -2;
+ if (vs->ioc_tag) {
+ g_source_remove(vs->ioc_tag);
+ vs->ioc_tag = 0;
}
- if (*payload_size < 126) {
- header_size = 6;
- mask = header->u.m;
- } else if (*payload_size == 126 && input->offset >= 8) {
- *payload_size = be16_to_cpu(header->u.s16.l16);
- header_size = 8;
- mask = header->u.s16.m16;
- } else if (*payload_size == 127 && input->offset >= 14) {
- *payload_size = be64_to_cpu(header->u.s64.l64);
- header_size = 14;
- mask = header->u.s64.m64;
- } else {
- /* header not complete */
- return 0;
+ if (condition & (G_IO_HUP | G_IO_ERR)) {
+ vnc_client_error(vs);
+ return TRUE;
}
- *frame_size = header_size + *payload_size;
+ wioc = qio_channel_websock_new_server(vs->ioc);
+ qio_channel_set_name(QIO_CHANNEL(wioc), "vnc-ws-server-websock");
- if (input->offset < *frame_size) {
- /* frame not complete */
- return 0;
- }
-
- *payload = input->buffer + header_size;
+ object_unref(OBJECT(vs->ioc));
+ vs->ioc = QIO_CHANNEL(wioc);
+ trace_vnc_client_io_wrap(vs, vs->ioc, "websock");
- /* unmask frame */
- /* process 1 frame (32 bit op) */
- payload32 = (uint32_t *)(*payload);
- for (i = 0; i < *payload_size / 4; i++) {
- payload32[i] ^= mask.u;
- }
- /* process the remaining bytes (if any) */
- for (i *= 4; i < *payload_size; i++) {
- (*payload)[i] ^= mask.c[i % 4];
- }
+ qio_channel_websock_handshake(wioc,
+ vncws_handshake_done,
+ vs,
+ NULL);
- return 1;
+ return TRUE;
}
diff --git a/ui/vnc-ws.h b/ui/vnc-ws.h
index 95c1b0aea..396cacfcb 100644
--- a/ui/vnc-ws.h
+++ b/ui/vnc-ws.h
@@ -18,72 +18,14 @@
* along with this software; if not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef __QEMU_UI_VNC_WS_H
-#define __QEMU_UI_VNC_WS_H
+#ifndef QEMU_UI_VNC_WS_H
+#define QEMU_UI_VNC_WS_H
-#include <gnutls/gnutls.h>
+gboolean vncws_tls_handshake_io(QIOChannel *ioc,
+ GIOCondition condition,
+ void *opaque);
+gboolean vncws_handshake_io(QIOChannel *ioc,
+ GIOCondition condition,
+ void *opaque);
-#define B64LEN(__x) (((__x + 2) / 3) * 12 / 3)
-#define SHA1_DIGEST_LEN 20
-
-#define WS_ACCEPT_LEN (B64LEN(SHA1_DIGEST_LEN) + 1)
-#define WS_CLIENT_KEY_LEN 24
-#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
-#define WS_GUID_LEN strlen(WS_GUID)
-
-#define WS_HANDSHAKE "HTTP/1.1 101 Switching Protocols\r\n\
-Upgrade: websocket\r\n\
-Connection: Upgrade\r\n\
-Sec-WebSocket-Accept: %s\r\n\
-Sec-WebSocket-Protocol: binary\r\n\
-\r\n"
-#define WS_HANDSHAKE_DELIM "\r\n"
-#define WS_HANDSHAKE_END "\r\n\r\n"
-#define WS_SUPPORTED_VERSION "13"
-
-#define WS_HEAD_MIN_LEN sizeof(uint16_t)
-#define WS_HEAD_MAX_LEN (WS_HEAD_MIN_LEN + sizeof(uint64_t) + sizeof(uint32_t))
-
-typedef union WsMask {
- char c[4];
- uint32_t u;
-} WsMask;
-
-typedef struct QEMU_PACKED WsHeader {
- unsigned char b0;
- unsigned char b1;
- union {
- struct QEMU_PACKED {
- uint16_t l16;
- WsMask m16;
- } s16;
- struct QEMU_PACKED {
- uint64_t l64;
- WsMask m64;
- } s64;
- WsMask m;
- } u;
-} WsHeader;
-
-enum {
- WS_OPCODE_CONTINUATION = 0x0,
- WS_OPCODE_TEXT_FRAME = 0x1,
- WS_OPCODE_BINARY_FRAME = 0x2,
- WS_OPCODE_CLOSE = 0x8,
- WS_OPCODE_PING = 0x9,
- WS_OPCODE_PONG = 0xA
-};
-
-#ifdef CONFIG_VNC_TLS
-void vncws_tls_handshake_peek(void *opaque);
-#endif /* CONFIG_VNC_TLS */
-void vncws_handshake_read(void *opaque);
-long vnc_client_write_ws(VncState *vs);
-long vnc_client_read_ws(VncState *vs);
-void vncws_process_handshake(VncState *vs, uint8_t *line, size_t size);
-void vncws_encode_frame(Buffer *output, const void *payload,
- const size_t payload_size);
-int vncws_decode_frame(Buffer *input, uint8_t **payload,
- size_t *payload_size, size_t *frame_size);
-
-#endif /* __QEMU_UI_VNC_WS_H */
+#endif /* QEMU_UI_VNC_WS_H */
diff --git a/ui/vnc.c b/ui/vnc.c
index 5601cc34e..ca3fc376a 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -24,15 +24,32 @@
* THE SOFTWARE.
*/
+#include "qemu/osdep.h"
#include "vnc.h"
#include "vnc-jobs.h"
+#include "trace.h"
+#include "hw/qdev-core.h"
#include "sysemu/sysemu.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 "qemu/acl.h"
-#include "qapi/qmp/types.h"
-#include "qmp-commands.h"
-#include "qemu/osdep.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/tlscredsanon.h"
+#include "crypto/tlscredsx509.h"
+#include "crypto/random.h"
+#include "qom/object_interfaces.h"
+#include "qemu/cutils.h"
+#include "io/dns-resolver.h"
#define VNC_REFRESH_INTERVAL_BASE GUI_REFRESH_INTERVAL_DEFAULT
#define VNC_REFRESH_INTERVAL_INC 50
@@ -41,12 +58,13 @@ static const struct timeval VNC_REFRESH_STATS = { 0, 500000 };
static const struct timeval VNC_REFRESH_LOSSY = { 2, 0 };
#include "vnc_keysym.h"
-#include "d3des.h"
+#include "crypto/cipher.h"
-static VncDisplay *vnc_display; /* needed for info vnc */
+static QTAILQ_HEAD(, VncDisplay) vnc_displays =
+ QTAILQ_HEAD_INITIALIZER(vnc_displays);
static int vnc_cursor_define(VncState *vs);
-static void vnc_release_modifiers(VncState *vs);
+static void vnc_update_throttle_offset(VncState *vs);
static void vnc_set_share_mode(VncState *vs, VncShareMode mode)
{
@@ -58,117 +76,108 @@ static void vnc_set_share_mode(VncState *vs, VncShareMode mode)
[VNC_SHARE_MODE_EXCLUSIVE] = "exclusive",
[VNC_SHARE_MODE_DISCONNECTED] = "disconnected",
};
- fprintf(stderr, "%s/%d: %s -> %s\n", __func__,
- vs->csock, mn[vs->share_mode], mn[mode]);
+ fprintf(stderr, "%s/%p: %s -> %s\n", __func__,
+ vs->ioc, mn[vs->share_mode], mn[mode]);
#endif
- if (vs->share_mode == VNC_SHARE_MODE_EXCLUSIVE) {
+ 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;
- if (vs->share_mode == VNC_SHARE_MODE_EXCLUSIVE) {
- vs->vd->num_exclusive++;
- }
-}
-static char *addr_to_string(const char *format,
- struct sockaddr_storage *sa,
- socklen_t salen) {
- char *addr;
- char host[NI_MAXHOST];
- char serv[NI_MAXSERV];
- int err;
- size_t addrlen;
+ vs->share_mode = mode;
- if ((err = getnameinfo((struct sockaddr *)sa, salen,
- host, sizeof(host),
- serv, sizeof(serv),
- NI_NUMERICHOST | NI_NUMERICSERV)) != 0) {
- VNC_DEBUG("Cannot resolve address %d: %s\n",
- err, gai_strerror(err));
- return NULL;
+ 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;
}
-
- /* Enough for the existing format + the 2 vars we're
- * substituting in. */
- addrlen = strlen(format) + strlen(host) + strlen(serv);
- addr = g_malloc(addrlen + 1);
- snprintf(addr, addrlen, format, host, serv);
- addr[addrlen] = '\0';
-
- return addr;
}
-char *vnc_socket_local_addr(const char *format, int fd) {
- struct sockaddr_storage sa;
- socklen_t salen;
-
- salen = sizeof(sa);
- if (getsockname(fd, (struct sockaddr*)&sa, &salen) < 0)
- return NULL;
-
- return addr_to_string(format, &sa, salen);
-}
+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;
-char *vnc_socket_remote_addr(const char *format, int fd) {
- struct sockaddr_storage sa;
- socklen_t salen;
+ 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;
- salen = sizeof(sa);
- if (getpeername(fd, (struct sockaddr*)&sa, &salen) < 0)
- return NULL;
+ 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 addr_to_string(format, &sa, salen);
+ return;
}
-static int put_addr_qdict(QDict *qdict, struct sockaddr_storage *sa,
- socklen_t salen)
+static void vnc_init_basic_info_from_server_addr(QIOChannelSocket *ioc,
+ VncBasicInfo *info,
+ Error **errp)
{
- char host[NI_MAXHOST];
- char serv[NI_MAXSERV];
- int err;
+ SocketAddress *addr = NULL;
- if ((err = getnameinfo((struct sockaddr *)sa, salen,
- host, sizeof(host),
- serv, sizeof(serv),
- NI_NUMERICHOST | NI_NUMERICSERV)) != 0) {
- VNC_DEBUG("Cannot resolve address %d: %s\n",
- err, gai_strerror(err));
- return -1;
+ if (!ioc) {
+ error_setg(errp, "No listener socket available");
+ return;
}
- qdict_put(qdict, "host", qstring_from_str(host));
- qdict_put(qdict, "service", qstring_from_str(serv));
- qdict_put(qdict, "family",qstring_from_str(inet_strfamily(sa->ss_family)));
-
- return 0;
-}
-
-static int vnc_server_addr_put(QDict *qdict, int fd)
-{
- struct sockaddr_storage sa;
- socklen_t salen;
-
- salen = sizeof(sa);
- if (getsockname(fd, (struct sockaddr*)&sa, &salen) < 0) {
- return -1;
+ addr = qio_channel_socket_get_local_address(ioc, errp);
+ if (!addr) {
+ return;
}
- return put_addr_qdict(qdict, &sa, salen);
+ vnc_init_basic_info(addr, info, errp);
+ qapi_free_SocketAddress(addr);
}
-static int vnc_qdict_remote_addr(QDict *qdict, int fd)
+static void vnc_init_basic_info_from_remote_addr(QIOChannelSocket *ioc,
+ VncBasicInfo *info,
+ Error **errp)
{
- struct sockaddr_storage sa;
- socklen_t salen;
+ SocketAddress *addr = NULL;
- salen = sizeof(sa);
- if (getpeername(fd, (struct sockaddr*)&sa, &salen) < 0) {
- return -1;
+ addr = qio_channel_socket_get_remote_address(ioc, errp);
+ if (!addr) {
+ return;
}
- return put_addr_qdict(qdict, &sa, salen);
+ vnc_init_basic_info(addr, info, errp);
+ qapi_free_SocketAddress(addr);
}
static const char *vnc_auth_name(VncDisplay *vd) {
@@ -190,7 +199,6 @@ static const char *vnc_auth_name(VncDisplay *vd) {
case VNC_AUTH_TLS:
return "tls";
case VNC_AUTH_VENCRYPT:
-#ifdef CONFIG_VNC_TLS
switch (vd->subauth) {
case VNC_AUTH_VENCRYPT_PLAIN:
return "vencrypt+plain";
@@ -213,122 +221,123 @@ static const char *vnc_auth_name(VncDisplay *vd) {
default:
return "vencrypt";
}
-#else
- return "vencrypt";
-#endif
case VNC_AUTH_SASL:
return "sasl";
}
return "unknown";
}
-static int vnc_server_info_put(QDict *qdict)
+static VncServerInfo *vnc_server_info_get(VncDisplay *vd)
{
- if (vnc_server_addr_put(qdict, vnc_display->lsock) < 0) {
- return -1;
+ VncServerInfo *info;
+ Error *err = NULL;
+
+ if (!vd->listener || !vd->listener->nsioc) {
+ return NULL;
}
- qdict_put(qdict, "auth", qstring_from_str(vnc_auth_name(vnc_display)));
- return 0;
+ 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 defined(CONFIG_VNC_TLS) || defined(CONFIG_VNC_SASL)
- QDict *qdict;
-#endif
-
if (!client->info) {
return;
}
-#if defined(CONFIG_VNC_TLS) || defined(CONFIG_VNC_SASL)
- qdict = qobject_to_qdict(client->info);
-#endif
-
-#ifdef CONFIG_VNC_TLS
- if (client->tls.session &&
- client->tls.dname) {
- qdict_put(qdict, "x509_dname", qstring_from_str(client->tls.dname));
+ 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;
}
-#endif
#ifdef CONFIG_VNC_SASL
if (client->sasl.conn &&
client->sasl.username) {
- qdict_put(qdict, "sasl_username",
- qstring_from_str(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)
{
- QDict *qdict;
-
- qdict = qdict_new();
- if (vnc_qdict_remote_addr(qdict, client->csock) < 0) {
- QDECREF(qdict);
- /* XXX: how to report the error? */
- return;
+ 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);
}
-
- client->info = QOBJECT(qdict);
}
-static void vnc_qmp_event(VncState *vs, MonitorEvent event)
+static void vnc_qmp_event(VncState *vs, QAPIEvent event)
{
- QDict *server;
- QObject *data;
+ VncServerInfo *si;
if (!vs->info) {
return;
}
- server = qdict_new();
- if (vnc_server_info_put(server) < 0) {
- QDECREF(server);
+ si = vnc_server_info_get(vs->vd);
+ if (!si) {
return;
}
- data = qobject_from_jsonf("{ 'client': %p, 'server': %p }",
- vs->info, QOBJECT(server));
-
- monitor_protocol_event(event, data);
+ 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;
+ }
- qobject_incref(vs->info);
- qobject_decref(data);
+ qapi_free_VncServerInfo(si);
}
static VncClientInfo *qmp_query_vnc_client(const VncState *client)
{
- struct sockaddr_storage sa;
- socklen_t salen = sizeof(sa);
- char host[NI_MAXHOST];
- char serv[NI_MAXSERV];
VncClientInfo *info;
+ Error *err = NULL;
- if (getpeername(client->csock, (struct sockaddr *)&sa, &salen) < 0) {
- return NULL;
- }
+ info = g_malloc0(sizeof(*info));
- if (getnameinfo((struct sockaddr *)&sa, salen,
- host, sizeof(host),
- serv, sizeof(serv),
- NI_NUMERICHOST | NI_NUMERICSERV) < 0) {
+ 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 = g_malloc0(sizeof(*info));
- info->host = g_strdup(host);
- info->service = g_strdup(serv);
- info->family = g_strdup(inet_strfamily(sa.ss_family));
+ info->websocket = client->websocket;
-#ifdef CONFIG_VNC_TLS
- if (client->tls.session && client->tls.dname) {
- info->has_x509_dname = true;
- info->x509_dname = g_strdup(client->tls.dname);
+ if (client->tls) {
+ info->x509_dname = qcrypto_tls_session_get_peer_name(client->tls);
+ info->has_x509_dname = info->x509_dname != NULL;
}
-#endif
#ifdef CONFIG_VNC_SASL
if (client->sasl.conn && client->sasl.username) {
info->has_sasl_username = true;
@@ -339,76 +348,249 @@ static VncClientInfo *qmp_query_vnc_client(const VncState *client)
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 *cinfo, *prev = NULL;
+ VncState *client;
+
+ QTAILQ_FOREACH(client, &vd->clients, next) {
+ cinfo = g_new0(VncClientInfoList, 1);
+ cinfo->value = qmp_query_vnc_client(client);
+ cinfo->next = prev;
+ prev = cinfo;
+ }
+ return prev;
+}
+
VncInfo *qmp_query_vnc(Error **errp)
{
VncInfo *info = g_malloc0(sizeof(*info));
+ VncDisplay *vd = vnc_display_find(NULL);
+ SocketAddress *addr = NULL;
- if (vnc_display == NULL || vnc_display->display == NULL) {
+ if (vd == NULL || !vd->listener || !vd->listener->nsioc) {
info->enabled = false;
} else {
- VncClientInfoList *cur_item = NULL;
- struct sockaddr_storage sa;
- socklen_t salen = sizeof(sa);
- char host[NI_MAXHOST];
- char serv[NI_MAXSERV];
- VncState *client;
-
info->enabled = true;
/* for compatibility with the original command */
info->has_clients = true;
+ info->clients = qmp_query_client_list(vd);
- QTAILQ_FOREACH(client, &vnc_display->clients, next) {
- VncClientInfoList *cinfo = g_malloc0(sizeof(*info));
- cinfo->value = qmp_query_vnc_client(client);
+ addr = qio_channel_socket_get_local_address(vd->listener->sioc[0],
+ errp);
+ if (!addr) {
+ goto out_error;
+ }
- /* XXX: waiting for the qapi to support GSList */
- if (!cur_item) {
- info->clients = cur_item = cinfo;
+ 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 {
- cur_item->next = cinfo;
- cur_item = cinfo;
+ info->family = NETWORK_ADDRESS_FAMILY_IPV4;
}
- }
-
- if (vnc_display->lsock == -1) {
- return info;
- }
+ break;
- if (getsockname(vnc_display->lsock, (struct sockaddr *)&sa,
- &salen) == -1) {
- error_set(errp, QERR_UNDEFINED_ERROR);
- goto out_error;
- }
+ 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;
- if (getnameinfo((struct sockaddr *)&sa, salen,
- host, sizeof(host),
- serv, sizeof(serv),
- NI_NUMERICHOST | NI_NUMERICSERV) < 0) {
- error_set(errp, QERR_UNDEFINED_ERROR);
+ 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->host = g_strdup(host);
-
info->has_service = true;
- info->service = g_strdup(serv);
-
info->has_family = true;
- info->family = g_strdup(inet_strfamily(sa.ss_family));
info->has_auth = true;
- info->auth = g_strdup(vnc_auth_name(vnc_display));
+ 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)
+{
+ VncServerInfo2List *list;
+ 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);
+
+ list = g_new0(VncServerInfo2List, 1);
+ list->value = info;
+ list->next = prev;
+ return list;
+}
+
+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 *item, *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);
+ }
+
+ item = g_new0(VncInfo2List, 1);
+ item->value = info;
+ item->next = prev;
+ prev = item;
+ }
+ return prev;
+}
+
/* TODO
1) Get the queue working for IO.
2) there is some weirdness when using the -S option (the screen is grey
@@ -417,7 +599,6 @@ out_error:
*/
static int vnc_update_client(VncState *vs, int has_dirty);
-static int vnc_update_client_sync(VncState *vs, int has_dirty);
static void vnc_disconnect_start(VncState *vs);
static void vnc_colordepth(VncState *vs);
@@ -427,32 +608,48 @@ static void framebuffer_update_request(VncState *vs, int incremental,
static void vnc_refresh(DisplayChangeListener *dcl);
static int vnc_refresh_server_surface(VncDisplay *vd);
-static void vnc_dpy_update(DisplayChangeListener *dcl,
- int x, int y, int w, int h)
+static int vnc_width(VncDisplay *vd)
{
- int i;
- VncDisplay *vd = container_of(dcl, VncDisplay, dcl);
- struct VncSurface *s = &vd->guest;
- int width = surface_width(vd->ds);
- int height = surface_height(vd->ds);
+ return MIN(VNC_MAX_WIDTH, ROUND_UP(surface_width(vd->ds),
+ VNC_DIRTY_PIXELS_PER_BIT));
+}
- h += y;
+static int vnc_height(VncDisplay *vd)
+{
+ return MIN(VNC_MAX_HEIGHT, surface_height(vd->ds));
+}
- /* round x down to ensure the loop only spans one 16-pixel block per,
- iteration. otherwise, if (x % 16) != 0, the last iteration may span
- two 16-pixel blocks but we only mark the first as dirty
- */
- w += (x % 16);
- x -= (x % 16);
+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(h, height);
+ h = MIN(y + h, height);
- for (; y < h; y++)
- for (i = 0; i < w; i += 16)
- set_bit((x + i) / 16, s->dirty[y]);
+ 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,
@@ -466,67 +663,23 @@ void vnc_framebuffer_update(VncState *vs, int x, int y, int w, int h,
vnc_write_s32(vs, encoding);
}
-void buffer_reserve(Buffer *buffer, size_t len)
-{
- if ((buffer->capacity - buffer->offset) < len) {
- buffer->capacity += (len + 1024);
- buffer->buffer = g_realloc(buffer->buffer, buffer->capacity);
- if (buffer->buffer == NULL) {
- fprintf(stderr, "vnc: out of memory\n");
- exit(1);
- }
- }
-}
-
-static int buffer_empty(Buffer *buffer)
-{
- return buffer->offset == 0;
-}
-
-uint8_t *buffer_end(Buffer *buffer)
-{
- return buffer->buffer + buffer->offset;
-}
-
-void buffer_reset(Buffer *buffer)
-{
- buffer->offset = 0;
-}
-
-void buffer_free(Buffer *buffer)
-{
- g_free(buffer->buffer);
- buffer->offset = 0;
- buffer->capacity = 0;
- buffer->buffer = NULL;
-}
-
-void buffer_append(Buffer *buffer, const void *data, size_t len)
-{
- memcpy(buffer->buffer + buffer->offset, data, len);
- buffer->offset += len;
-}
-
-void buffer_advance(Buffer *buf, size_t len)
-{
- memmove(buf->buffer, buf->buffer + len,
- (buf->offset - len));
- buf->offset -= len;
-}
static void vnc_desktop_resize(VncState *vs)
{
- DisplaySurface *ds = vs->vd->ds;
-
- if (vs->csock == -1 || !vnc_has_feature(vs, VNC_FEATURE_RESIZE)) {
+ if (vs->ioc == NULL || !vnc_has_feature(vs, VNC_FEATURE_RESIZE)) {
return;
}
- if (vs->client_width == surface_width(ds) &&
- vs->client_height == surface_height(ds)) {
+ if (vs->client_width == pixman_image_get_width(vs->vd->server) &&
+ vs->client_height == pixman_image_get_height(vs->vd->server)) {
return;
}
- vs->client_width = surface_width(ds);
- vs->client_height = surface_height(ds);
+
+ 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_lock_output(vs);
vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE);
vnc_write_u8(vs, 0);
@@ -551,6 +704,12 @@ static void vnc_abort_display_jobs(VncDisplay *vd)
}
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);
}
@@ -571,31 +730,73 @@ void *vnc_server_fb_ptr(VncDisplay *vd, int x, int y)
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->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)
{
+ static const char placeholder_msg[] =
+ "Display output is not active.";
+ static DisplaySurface *placeholder;
VncDisplay *vd = container_of(dcl, VncDisplay, dcl);
+ bool pageflip = vnc_check_pageflip(vd->ds, surface);
VncState *vs;
- vnc_abort_display_jobs(vd);
+ if (surface == NULL) {
+ if (placeholder == NULL) {
+ placeholder = qemu_create_message_surface(640, 480, placeholder_msg);
+ }
+ surface = placeholder;
+ }
- /* server surface */
- qemu_pixman_image_unref(vd->server);
+ vnc_abort_display_jobs(vd);
vd->ds = surface;
- vd->server = pixman_image_create_bits(VNC_SERVER_FB_FORMAT,
- surface_width(vd->ds),
- surface_height(vd->ds),
- NULL, 0);
/* guest surface */
-#if 0 /* FIXME */
- if (ds_get_bytes_per_pixel(ds) != vd->guest.ds->pf.bytes_per_pixel)
- console_color_init(ds);
-#endif
qemu_pixman_image_unref(vd->guest.fb);
vd->guest.fb = pixman_image_ref(surface->image);
vd->guest.format = surface->format;
- memset(vd->guest.dirty, 0xFF, sizeof(vd->guest.dirty));
+
+ if (pageflip) {
+ vnc_set_area_dirty(vd->guest.dirty, vd, 0, 0,
+ surface_width(surface),
+ surface_height(surface));
+ return;
+ }
+
+ /* server surface */
+ vnc_update_server_surface(vd);
QTAILQ_FOREACH(vs, &vd->clients, next) {
vnc_colordepth(vs);
@@ -603,7 +804,11 @@ static void vnc_dpy_switch(DisplayChangeListener *dcl,
if (vs->vd->cursor) {
vnc_cursor_define(vs);
}
- memset(vs->dirty, 0xFF, sizeof(vs->dirty));
+ 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);
}
}
@@ -721,93 +926,6 @@ int vnc_send_framebuffer_update(VncState *vs, int x, int y, int w, int h)
return n;
}
-static void vnc_copy(VncState *vs, int src_x, int src_y, int dst_x, int dst_y, int w, int h)
-{
- /* send bitblit op to the vnc 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, dst_x, dst_y, w, h, VNC_ENCODING_COPYRECT);
- vnc_write_u16(vs, src_x);
- vnc_write_u16(vs, src_y);
- vnc_unlock_output(vs);
- vnc_flush(vs);
-}
-
-static void vnc_dpy_copy(DisplayChangeListener *dcl,
- int src_x, int src_y,
- int dst_x, int dst_y, int w, int h)
-{
- VncDisplay *vd = container_of(dcl, VncDisplay, dcl);
- VncState *vs, *vn;
- uint8_t *src_row;
- uint8_t *dst_row;
- int i, x, y, pitch, inc, w_lim, s;
- int cmp_bytes;
-
- vnc_refresh_server_surface(vd);
- QTAILQ_FOREACH_SAFE(vs, &vd->clients, next, vn) {
- if (vnc_has_feature(vs, VNC_FEATURE_COPYRECT)) {
- vs->force_update = 1;
- vnc_update_client_sync(vs, 1);
- /* vs might be free()ed here */
- }
- }
-
- /* do bitblit op on the local surface too */
- pitch = vnc_server_fb_stride(vd);
- src_row = vnc_server_fb_ptr(vd, src_x, src_y);
- dst_row = vnc_server_fb_ptr(vd, dst_x, dst_y);
- y = dst_y;
- inc = 1;
- if (dst_y > src_y) {
- /* copy backwards */
- src_row += pitch * (h-1);
- dst_row += pitch * (h-1);
- pitch = -pitch;
- y = dst_y + h - 1;
- inc = -1;
- }
- w_lim = w - (16 - (dst_x % 16));
- if (w_lim < 0)
- w_lim = w;
- else
- w_lim = w - (w_lim % 16);
- for (i = 0; i < h; i++) {
- for (x = 0; x <= w_lim;
- x += s, src_row += cmp_bytes, dst_row += cmp_bytes) {
- if (x == w_lim) {
- if ((s = w - w_lim) == 0)
- break;
- } else if (!x) {
- s = (16 - (dst_x % 16));
- s = MIN(s, w_lim);
- } else {
- s = 16;
- }
- cmp_bytes = s * VNC_SERVER_FB_BYTES;
- if (memcmp(src_row, dst_row, cmp_bytes) == 0)
- continue;
- memmove(dst_row, src_row, cmp_bytes);
- QTAILQ_FOREACH(vs, &vd->clients, next) {
- if (!vnc_has_feature(vs, VNC_FEATURE_COPYRECT)) {
- set_bit(((x + dst_x) / 16), vs->dirty[y]);
- }
- }
- }
- src_row += pitch - w * VNC_SERVER_FB_BYTES;
- dst_row += pitch - w * VNC_SERVER_FB_BYTES;
- y += inc;
- }
-
- QTAILQ_FOREACH(vs, &vd->clients, next) {
- if (vnc_has_feature(vs, VNC_FEATURE_COPYRECT)) {
- vnc_copy(vs, src_x, src_y, dst_x, dst_y, w, h);
- }
- }
-}
-
static void vnc_mouse_set(DisplayChangeListener *dcl,
int x, int y, int visible)
{
@@ -838,7 +956,7 @@ static int vnc_cursor_define(VncState *vs)
static void vnc_dpy_cursor_define(DisplayChangeListener *dcl,
QEMUCursor *c)
{
- VncDisplay *vd = vnc_display;
+ VncDisplay *vd = container_of(dcl, VncDisplay, dcl);
VncState *vs;
cursor_put(vd->cursor);
@@ -855,94 +973,176 @@ static void vnc_dpy_cursor_define(DisplayChangeListener *dcl,
}
}
-static int find_and_clear_dirty_height(struct VncState *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++) {
- int tmp_x;
if (!test_bit(last_x, vs->dirty[y + h])) {
break;
}
- for (tmp_x = last_x; tmp_x < x; tmp_x++) {
- clear_bit(tmp_x, vs->dirty[y + h]);
- }
+ bitmap_clear(vs->dirty[y + h], last_x, x - last_x);
}
return h;
}
-static int vnc_update_client_sync(VncState *vs, int has_dirty)
+/*
+ * 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)
{
- int ret = vnc_update_client(vs, has_dirty);
- vnc_jobs_join(vs);
- return ret;
+ 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)
{
- if (vs->need_update && vs->csock != -1) {
- VncDisplay *vd = vs->vd;
- VncJob *job;
- int y;
- int width, height;
- int n = 0;
+ VncDisplay *vd = vs->vd;
+ VncJob *job;
+ int y;
+ int height, width;
+ int n = 0;
+ if (vs->disconnecting) {
+ vnc_disconnect_finish(vs);
+ return 0;
+ }
- if (vs->output.offset && !vs->audio_cap && !vs->force_update)
- /* kernel send buffers are full -> drop frames to throttle */
- return 0;
+ vs->has_dirty += has_dirty;
+ if (!vnc_should_update(vs)) {
+ return 0;
+ }
- if (!has_dirty && !vs->audio_cap && !vs->force_update)
- 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);
-
- width = MIN(pixman_image_get_width(vd->server), vs->client_width);
- height = MIN(pixman_image_get_height(vd->server), vs->client_height);
-
- for (y = 0; y < height; y++) {
- int x;
- int last_x = -1;
- for (x = 0; x < width / 16; x++) {
- if (test_and_clear_bit(x, vs->dirty[y])) {
- if (last_x == -1) {
- last_x = x;
- }
- } else {
- if (last_x != -1) {
- int h = find_and_clear_dirty_height(vs, y, last_x, x,
- height);
-
- n += vnc_job_add_rect(job, last_x * 16, y,
- (x - last_x) * 16, h);
- }
- last_x = -1;
- }
- }
- if (last_x != -1) {
- int h = find_and_clear_dirty_height(vs, y, last_x, x, height);
- n += vnc_job_add_rect(job, last_x * 16, y,
- (x - last_x) * 16, h);
+ /*
+ * 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;
}
}
-
- vnc_job_push(job);
- vs->force_update = 0;
- return n;
}
- if (vs->csock == -1)
- vnc_disconnect_finish(vs);
-
- return 0;
+ vs->job_update = vs->update;
+ vs->update = VNC_STATE_UPDATE_NONE;
+ vnc_job_push(job);
+ vs->has_dirty = 0;
+ return n;
}
/* audio */
@@ -950,6 +1150,7 @@ static void audio_capture_notify(void *opaque, audcnotification_e cmd)
{
VncState *vs = opaque;
+ assert(vs->magic == VNC_MAGIC);
switch (cmd) {
case AUD_CNOTIFY_DISABLE:
vnc_lock_output(vs);
@@ -975,16 +1176,21 @@ static void audio_capture_destroy(void *opaque)
{
}
-static void audio_capture(void *opaque, void *buf, int size)
+static void audio_capture(void *opaque, const void *buf, int size)
{
VncState *vs = opaque;
+ assert(vs->magic == VNC_MAGIC);
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_DATA);
- vnc_write_u32(vs, size);
- vnc_write(vs, buf, size);
+ 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);
}
@@ -994,7 +1200,7 @@ static void audio_add(VncState *vs)
struct audio_capture_ops ops;
if (vs->audio_cap) {
- monitor_printf(default_mon, "audio already running\n");
+ error_report("audio already running");
return;
}
@@ -1002,9 +1208,9 @@ static void audio_add(VncState *vs)
ops.destroy = audio_capture_destroy;
ops.capture = audio_capture;
- vs->audio_cap = AUD_add_capture(&vs->as, &ops, vs);
+ vs->audio_cap = AUD_add_capture(vs->vd->audio_state, &vs->as, &ops, vs);
if (!vs->audio_cap) {
- monitor_printf(default_mon, "Failed to add audio capture\n");
+ error_report("Failed to add audio capture");
}
}
@@ -1018,52 +1224,54 @@ static void audio_del(VncState *vs)
static void vnc_disconnect_start(VncState *vs)
{
- if (vs->csock == -1)
+ if (vs->disconnecting) {
return;
+ }
+ trace_vnc_client_disconnect_start(vs, vs->ioc);
vnc_set_share_mode(vs, VNC_SHARE_MODE_DISCONNECTED);
- qemu_set_fd_handler2(vs->csock, NULL, NULL, NULL, NULL);
- closesocket(vs->csock);
- vs->csock = -1;
+ 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, QEVENT_VNC_DISCONNECTED);
+ vnc_qmp_event(vs, QAPI_EVENT_VNC_DISCONNECTED);
buffer_free(&vs->input);
buffer_free(&vs->output);
-#ifdef CONFIG_VNC_WS
- buffer_free(&vs->ws_input);
- buffer_free(&vs->ws_output);
-#endif /* CONFIG_VNC_WS */
- qobject_decref(vs->info);
+ qapi_free_VncClientInfo(vs->info);
vnc_zlib_clear(vs);
vnc_tight_clear(vs);
vnc_zrle_clear(vs);
-#ifdef CONFIG_VNC_TLS
- vnc_tls_client_cleanup(vs);
-#endif /* CONFIG_VNC_TLS */
#ifdef CONFIG_VNC_SASL
vnc_sasl_client_cleanup(vs);
#endif /* CONFIG_VNC_SASL */
audio_del(vs);
- vnc_release_modifiers(vs);
+ qkbd_state_lift_all_keys(vs->vd->kbd);
- if (vs->initialized) {
- QTAILQ_REMOVE(&vs->vd->clients, vs, next);
+ 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->vd->lock_key_sync)
- qemu_remove_led_event_handler(vs->led);
vnc_unlock_output(vs);
qemu_mutex_destroy(&vs->output_mutex);
@@ -1076,29 +1284,30 @@ void vnc_disconnect_finish(VncState *vs)
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);
}
-int vnc_client_io_error(VncState *vs, int ret, int last_errno)
+size_t vnc_client_io_error(VncState *vs, ssize_t ret, Error *err)
{
- if (ret == 0 || ret == -1) {
- if (ret == -1) {
- switch (last_errno) {
- case EINTR:
- case EAGAIN:
-#ifdef _WIN32
- case WSAEWOULDBLOCK:
-#endif
- return 0;
- default:
- break;
- }
+ 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);
}
- VNC_DEBUG("Closing down client sock: ret %d, errno %d\n",
- ret, ret < 0 ? last_errno : 0);
- vnc_disconnect_start(vs);
-
+ error_free(err);
return 0;
}
return ret;
@@ -1111,23 +1320,6 @@ void vnc_client_error(VncState *vs)
vnc_disconnect_start(vs);
}
-#ifdef CONFIG_VNC_TLS
-static long vnc_client_write_tls(gnutls_session_t *session,
- const uint8_t *data,
- size_t datalen)
-{
- long ret = gnutls_write(*session, data, datalen);
- if (ret < 0) {
- if (ret == GNUTLS_E_AGAIN) {
- errno = EAGAIN;
- } else {
- errno = EIO;
- }
- ret = -1;
- }
- return ret;
-}
-#endif /* CONFIG_VNC_TLS */
/*
* Called to write a chunk of data to the client socket. The data may
@@ -1142,29 +1334,15 @@ static long vnc_client_write_tls(gnutls_session_t *session,
*
* Returns the number of bytes written, which may be less than
* the requested 'datalen' if the socket would block. Returns
- * -1 on error, and disconnects the client socket.
+ * 0 on I/O error, and disconnects the client socket.
*/
-long vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen)
+size_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen)
{
- long ret;
-#ifdef CONFIG_VNC_TLS
- if (vs->tls.session) {
- ret = vnc_client_write_tls(&vs->tls.session, data, datalen);
- } else {
-#ifdef CONFIG_VNC_WS
- if (vs->ws_tls.session) {
- ret = vnc_client_write_tls(&vs->ws_tls.session, data, datalen);
- } else
-#endif /* CONFIG_VNC_WS */
-#endif /* CONFIG_VNC_TLS */
- {
- ret = send(vs->csock, (const void *)data, datalen, 0);
- }
-#ifdef CONFIG_VNC_TLS
- }
-#endif /* CONFIG_VNC_TLS */
+ 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, socket_error());
+ return vnc_client_io_error(vs, ret, err);
}
@@ -1175,12 +1353,13 @@ long vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen)
* 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
- * -1 on error, and disconnects the client socket.
+ * the buffered output data if the socket would block. Returns
+ * 0 on I/O error, and disconnects the client socket.
*/
-static long vnc_client_write_plain(VncState *vs)
+static size_t vnc_client_write_plain(VncState *vs)
{
- long ret;
+ 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",
@@ -1199,10 +1378,28 @@ static long vnc_client_write_plain(VncState *vs)
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) {
- qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, 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);
}
return ret;
@@ -1214,10 +1411,8 @@ static long vnc_client_write_plain(VncState *vs)
* 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(void *opaque)
+static void vnc_client_write_locked(VncState *vs)
{
- VncState *vs = opaque;
-
#ifdef CONFIG_VNC_SASL
if (vs->sasl.conn &&
vs->sasl.runSSF &&
@@ -1226,30 +1421,23 @@ static void vnc_client_write_locked(void *opaque)
} else
#endif /* CONFIG_VNC_SASL */
{
-#ifdef CONFIG_VNC_WS
- if (vs->encode_ws) {
- vnc_client_write_ws(vs);
- } else
-#endif /* CONFIG_VNC_WS */
- {
- vnc_client_write_plain(vs);
- }
+ vnc_client_write_plain(vs);
}
}
-void vnc_client_write(void *opaque)
+static void vnc_client_write(VncState *vs)
{
- VncState *vs = opaque;
-
+ assert(vs->magic == VNC_MAGIC);
vnc_lock_output(vs);
- if (vs->output.offset
-#ifdef CONFIG_VNC_WS
- || vs->ws_output.offset
-#endif
- ) {
- vnc_client_write_locked(opaque);
- } else if (vs->csock != -1) {
- qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, 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);
}
@@ -1260,22 +1448,6 @@ void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting)
vs->read_handler_expect = expecting;
}
-#ifdef CONFIG_VNC_TLS
-static long vnc_client_read_tls(gnutls_session_t *session, uint8_t *data,
- size_t datalen)
-{
- long ret = gnutls_read(*session, data, datalen);
- if (ret < 0) {
- if (ret == GNUTLS_E_AGAIN) {
- errno = EAGAIN;
- } else {
- errno = EIO;
- }
- ret = -1;
- }
- return ret;
-}
-#endif /* CONFIG_VNC_TLS */
/*
* Called to read a chunk of data from the client socket. The data may
@@ -1290,29 +1462,15 @@ static long vnc_client_read_tls(gnutls_session_t *session, uint8_t *data,
*
* Returns the number of bytes read, which may be less than
* the requested 'datalen' if the socket would block. Returns
- * -1 on error, and disconnects the client socket.
+ * 0 on I/O error or EOF, and disconnects the client socket.
*/
-long vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen)
+size_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen)
{
- long ret;
-#ifdef CONFIG_VNC_TLS
- if (vs->tls.session) {
- ret = vnc_client_read_tls(&vs->tls.session, data, datalen);
- } else {
-#ifdef CONFIG_VNC_WS
- if (vs->ws_tls.session) {
- ret = vnc_client_read_tls(&vs->ws_tls.session, data, datalen);
- } else
-#endif /* CONFIG_VNC_WS */
-#endif /* CONFIG_VNC_TLS */
- {
- ret = qemu_recv(vs->csock, data, datalen, 0);
- }
-#ifdef CONFIG_VNC_TLS
- }
-#endif /* CONFIG_VNC_TLS */
+ 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, socket_error());
+ return vnc_client_io_error(vs, ret, err);
}
@@ -1321,12 +1479,13 @@ long vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen)
* when not using any SASL SSF encryption layers. Will read as much
* data as possible without blocking.
*
- * Returns the number of bytes read. Returns -1 on error, and
- * disconnects the client socket.
+ * 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 long vnc_client_read_plain(VncState *vs)
+static size_t vnc_client_read_plain(VncState *vs)
{
- int ret;
+ 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);
@@ -1341,6 +1500,7 @@ static void vnc_jobs_bh(void *opaque)
{
VncState *vs = opaque;
+ assert(vs->magic == VNC_MAGIC);
vnc_jobs_consume_buffer(vs);
}
@@ -1348,36 +1508,24 @@ static void vnc_jobs_bh(void *opaque)
* 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
*/
-void vnc_client_read(void *opaque)
+static int vnc_client_read(VncState *vs)
{
- VncState *vs = opaque;
- long ret;
+ 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 */
-#ifdef CONFIG_VNC_WS
- if (vs->encode_ws) {
- ret = vnc_client_read_ws(vs);
- if (ret == -1) {
- vnc_disconnect_start(vs);
- return;
- } else if (ret == -2) {
- vnc_client_error(vs);
- return;
- }
- } else
-#endif /* CONFIG_VNC_WS */
- {
ret = vnc_client_read_plain(vs);
- }
if (!ret) {
- if (vs->csock == -1)
+ if (vs->disconnecting) {
vnc_disconnect_finish(vs);
- return;
+ return -1;
+ }
+ return 0;
}
while (vs->read_handler && vs->input.offset >= vs->read_handler_expect) {
@@ -1385,9 +1533,9 @@ void vnc_client_read(void *opaque)
int ret;
ret = vs->read_handler(vs, vs->input.buffer, len);
- if (vs->csock == -1) {
+ if (vs->disconnecting) {
vnc_disconnect_finish(vs);
- return;
+ return -1;
}
if (!ret) {
@@ -1396,14 +1544,84 @@ void vnc_client_read(void *opaque)
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->csock != -1 && buffer_empty(&vs->output)) {
- qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, vnc_client_write, vs);
+ 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);
@@ -1444,13 +1662,15 @@ void vnc_write_u8(VncState *vs, uint8_t value)
void vnc_flush(VncState *vs)
{
vnc_lock_output(vs);
- if (vs->csock != -1 && (vs->output.offset
-#ifdef CONFIG_VNC_WS
- || vs->ws_output.offset
-#endif
- )) {
+ 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);
}
@@ -1483,7 +1703,7 @@ static void client_cut_text(VncState *vs, size_t len, uint8_t *text)
static void check_pointer_type_change(Notifier *notifier, void *data)
{
VncState *vs = container_of(notifier, VncState, mouse_mode_notifier);
- int absolute = kbd_mouse_is_absolute();
+ int absolute = qemu_input_is_absolute();
if (vnc_has_feature(vs, VNC_FEATURE_POINTER_TYPE_CHANGE) && vs->absolute != absolute) {
vnc_lock_output(vs);
@@ -1491,8 +1711,8 @@ static void check_pointer_type_change(Notifier *notifier, void *data)
vnc_write_u8(vs, 0);
vnc_write_u16(vs, 1);
vnc_framebuffer_update(vs, absolute, 0,
- surface_width(vs->vd->ds),
- surface_height(vs->vd->ds),
+ 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);
@@ -1502,154 +1722,97 @@ static void check_pointer_type_change(Notifier *notifier, void *data)
static void pointer_event(VncState *vs, int button_mask, int x, int y)
{
- int buttons = 0;
- int dz = 0;
- int width = surface_width(vs->vd->ds);
- int height = surface_height(vs->vd->ds);
-
- if (button_mask & 0x01)
- buttons |= MOUSE_EVENT_LBUTTON;
- if (button_mask & 0x02)
- buttons |= MOUSE_EVENT_MBUTTON;
- if (button_mask & 0x04)
- buttons |= MOUSE_EVENT_RBUTTON;
- if (button_mask & 0x08)
- dz = -1;
- if (button_mask & 0x10)
- dz = 1;
+ 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) {
- kbd_mouse_event(width > 1 ? x * 0x7FFF / (width - 1) : 0x4000,
- height > 1 ? y * 0x7FFF / (height - 1) : 0x4000,
- dz, buttons);
+ 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)) {
- x -= 0x7FFF;
- y -= 0x7FFF;
-
- kbd_mouse_event(x, y, dz, buttons);
+ 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)
- kbd_mouse_event(x - vs->last_x,
- y - vs->last_y,
- dz, buttons);
+ 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 reset_keys(VncState *vs)
+static void press_key(VncState *vs, QKeyCode qcode)
{
- int i;
- for(i = 0; i < 256; i++) {
- if (vs->modifiers_state[i]) {
- if (i & SCANCODE_GREY)
- kbd_put_keycode(SCANCODE_EMUL0);
- kbd_put_keycode(i | SCANCODE_UP);
- vs->modifiers_state[i] = 0;
- }
- }
-}
-
-static void press_key(VncState *vs, int keysym)
-{
- int keycode = keysym2scancode(vs->vd->kbd_layout, keysym) & SCANCODE_KEYMASK;
- if (keycode & SCANCODE_GREY)
- kbd_put_keycode(SCANCODE_EMUL0);
- kbd_put_keycode(keycode & SCANCODE_KEYCODEMASK);
- if (keycode & SCANCODE_GREY)
- kbd_put_keycode(SCANCODE_EMUL0);
- kbd_put_keycode(keycode | SCANCODE_UP);
-}
-
-static int current_led_state(VncState *vs)
-{
- int ledstate = 0;
-
- if (vs->modifiers_state[0x46]) {
- ledstate |= QEMU_SCROLL_LOCK_LED;
- }
- if (vs->modifiers_state[0x45]) {
- ledstate |= QEMU_NUM_LOCK_LED;
- }
- if (vs->modifiers_state[0x3a]) {
- ledstate |= QEMU_CAPS_LOCK_LED;
- }
-
- return ledstate;
+ 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)
{
- int ledstate = 0;
-
if (!vnc_has_feature(vs, VNC_FEATURE_LED_STATE)) {
return;
}
- ledstate = current_led_state(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, 1, 1, VNC_ENCODING_LED_STATE);
- vnc_write_u8(vs, ledstate);
+ vnc_write_u8(vs, vs->vd->ledstate);
vnc_unlock_output(vs);
vnc_flush(vs);
}
static void kbd_leds(void *opaque, int ledstate)
{
- VncState *vs = opaque;
- int caps, num, scr;
- bool has_changed = (ledstate != current_led_state(vs));
+ VncDisplay *vd = opaque;
+ VncState *client;
- caps = ledstate & QEMU_CAPS_LOCK_LED ? 1 : 0;
- num = ledstate & QEMU_NUM_LOCK_LED ? 1 : 0;
- scr = ledstate & QEMU_SCROLL_LOCK_LED ? 1 : 0;
+ trace_vnc_key_guest_leds((ledstate & QEMU_CAPS_LOCK_LED),
+ (ledstate & QEMU_NUM_LOCK_LED),
+ (ledstate & QEMU_SCROLL_LOCK_LED));
- if (vs->modifiers_state[0x3a] != caps) {
- vs->modifiers_state[0x3a] = caps;
- }
- if (vs->modifiers_state[0x45] != num) {
- vs->modifiers_state[0x45] = num;
- }
- if (vs->modifiers_state[0x46] != scr) {
- vs->modifiers_state[0x46] = scr;
+ if (ledstate == vd->ledstate) {
+ return;
}
- /* Sending the current led state message to the client */
- if (has_changed) {
- vnc_led_state_change(vs);
+ 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(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 */
- if (down)
- vs->modifiers_state[keycode] = 1;
- else
- vs->modifiers_state[keycode] = 0;
- break;
- case 0x02 ... 0x0a: /* '1' to '9' keys */
- if (down && vs->modifiers_state[0x1d] && vs->modifiers_state[0x38]) {
+ 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 */
- reset_keys(vs);
- console_select(keycode - 0x02);
+ qkbd_state_lift_all_keys(vs->vd->kbd);
+ console_select(qcode - Q_KEY_CODE_1);
return;
}
- break;
- case 0x3a: /* CapsLock */
- case 0x45: /* NumLock */
- if (down)
- vs->modifiers_state[keycode] ^= 1;
+ default:
break;
}
@@ -1664,14 +1827,14 @@ static void do_key_event(VncState *vs, int down, int keycode, int sym)
toggles numlock away from the VNC window.
*/
if (keysym_is_numlock(vs->vd->kbd_layout, sym & 0xFFFF)) {
- if (!vs->modifiers_state[0x45]) {
- vs->modifiers_state[0x45] = 1;
- press_key(vs, 0xff7f);
+ 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 (vs->modifiers_state[0x45]) {
- vs->modifiers_state[0x45] = 0;
- press_key(vs, 0xff7f);
+ 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);
}
}
}
@@ -1684,32 +1847,25 @@ static void do_key_event(VncState *vs, int down, int keycode, int sym)
toggles capslock away from the VNC window.
*/
int uppercase = !!(sym >= 'A' && sym <= 'Z');
- int shift = !!(vs->modifiers_state[0x2a] | vs->modifiers_state[0x36]);
- int capslock = !!(vs->modifiers_state[0x3a]);
+ 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) {
- vs->modifiers_state[0x3a] = 0;
- press_key(vs, 0xffe5);
+ trace_vnc_key_sync_capslock(false);
+ press_key(vs, Q_KEY_CODE_CAPS_LOCK);
}
} else {
if (uppercase != shift) {
- vs->modifiers_state[0x3a] = 1;
- press_key(vs, 0xffe5);
+ trace_vnc_key_sync_capslock(true);
+ press_key(vs, Q_KEY_CODE_CAPS_LOCK);
}
}
}
- if (qemu_console_is_graphic(NULL)) {
- if (keycode & SCANCODE_GREY)
- kbd_put_keycode(SCANCODE_EMUL0);
- if (down)
- kbd_put_keycode(keycode & SCANCODE_KEYCODEMASK);
- else
- kbd_put_keycode(keycode | SCANCODE_UP);
- } else {
- bool numlock = vs->modifiers_state[0x45];
- bool control = (vs->modifiers_state[0x1d] ||
- vs->modifiers_state[0x9d]);
+ 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) {
@@ -1810,27 +1966,9 @@ static void do_key_event(VncState *vs, int down, int keycode, int sym)
}
}
-static void vnc_release_modifiers(VncState *vs)
+static const char *code2name(int keycode)
{
- static const int keycodes[] = {
- /* shift, control, alt keys, both left & right */
- 0x2a, 0x36, 0x1d, 0x9d, 0x38, 0xb8,
- };
- int i, keycode;
-
- if (!qemu_console_is_graphic(NULL)) {
- return;
- }
- for (i = 0; i < ARRAY_SIZE(keycodes); i++) {
- keycode = keycodes[i];
- if (!vs->modifiers_state[keycode]) {
- continue;
- }
- if (keycode & SCANCODE_GREY) {
- kbd_put_keycode(SCANCODE_EMUL0);
- }
- kbd_put_keycode(keycode | SCANCODE_UP);
- }
+ return QKeyCode_str(qemu_input_key_number_to_qcode(keycode));
}
static void key_event(VncState *vs, int down, uint32_t sym)
@@ -1842,7 +1980,9 @@ static void key_event(VncState *vs, int down, uint32_t sym)
lsym = lsym - 'A' + 'a';
}
- keycode = keysym2scancode(vs->vd->kbd_layout, lsym & 0xFFFF) & SCANCODE_KEYMASK;
+ 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);
}
@@ -1850,35 +1990,24 @@ 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)
+ if (keyboard_layout) {
key_event(vs, down, sym);
- else
+ } 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_position, int y_position,
- int w, int h)
+ int x, int y, int w, int h)
{
- int i;
- const size_t width = surface_width(vs->vd->ds) / 16;
- const size_t height = surface_height(vs->vd->ds);
-
- if (y_position > height) {
- y_position = height;
- }
- if (y_position + h >= height) {
- h = height - y_position;
- }
-
- vs->need_update = 1;
- if (!incremental) {
- vs->force_update = 1;
- for (i = 0; i < h; i++) {
- bitmap_set(vs->dirty[y_position + i], 0, width);
- bitmap_clear(vs->dirty[y_position + i], width,
- VNC_DIRTY_BITS - width);
+ 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);
}
}
@@ -1889,8 +2018,8 @@ static void send_ext_key_event_ack(VncState *vs)
vnc_write_u8(vs, 0);
vnc_write_u16(vs, 1);
vnc_framebuffer_update(vs, 0, 0,
- surface_width(vs->vd->ds),
- surface_height(vs->vd->ds),
+ 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);
@@ -1903,8 +2032,8 @@ static void send_ext_audio_ack(VncState *vs)
vnc_write_u8(vs, 0);
vnc_write_u16(vs, 1);
vnc_framebuffer_update(vs, 0, 0,
- surface_width(vs->vd->ds),
- surface_height(vs->vd->ds),
+ pixman_image_get_width(vs->vd->server),
+ pixman_image_get_height(vs->vd->server),
VNC_ENCODING_AUDIO);
vnc_unlock_output(vs);
vnc_flush(vs);
@@ -1917,8 +2046,8 @@ static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings)
vs->features = 0;
vs->vnc_encoding = 0;
- vs->tight.compression = 9;
- vs->tight.quality = -1; /* Lossless by default */
+ vs->tight->compression = 9;
+ vs->tight->quality = -1; /* Lossless by default */
vs->absolute = -1;
/*
@@ -1950,8 +2079,15 @@ static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings)
break;
#endif
case VNC_ENCODING_ZLIB:
- vs->features |= VNC_FEATURE_ZLIB_MASK;
- vs->vnc_encoding = enc;
+ /*
+ * 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;
@@ -1969,6 +2105,9 @@ static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings)
break;
case VNC_ENCODING_RICH_CURSOR:
vs->features |= VNC_FEATURE_RICH_CURSOR_MASK;
+ if (vs->vd->cursor) {
+ vnc_cursor_define(vs);
+ }
break;
case VNC_ENCODING_EXT_KEY_EVENT:
send_ext_key_event_ack(vs);
@@ -1983,11 +2122,11 @@ static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings)
vs->features |= VNC_FEATURE_LED_STATE_MASK;
break;
case VNC_ENCODING_COMPRESSLEVEL0 ... VNC_ENCODING_COMPRESSLEVEL0 + 9:
- vs->tight.compression = (enc & 0x0F);
+ vs->tight->compression = (enc & 0x0F);
break;
case VNC_ENCODING_QUALITYLEVEL0 ... VNC_ENCODING_QUALITYLEVEL0 + 9:
if (vs->vd->lossy) {
- vs->tight.quality = (enc & 0x0F);
+ vs->tight->quality = (enc & 0x0F);
}
break;
default:
@@ -2013,27 +2152,62 @@ static void set_pixel_conversion(VncState *vs)
}
}
-static void set_pixel_format(VncState *vs,
- int bits_per_pixel, int depth,
+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;
- vs->client_pf.rbits = hweight_long(red_max);
+ 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;
- vs->client_pf.gbits = hweight_long(green_max);
+ 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;
- vs->client_pf.bbits = hweight_long(blue_max);
+ 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;
@@ -2041,10 +2215,14 @@ static void set_pixel_format(VncState *vs,
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(NULL);
- graphic_hw_update(NULL);
+ graphic_hw_invalidate(vs->vd->dcl.con);
+ graphic_hw_update(vs->vd->dcl.con);
}
static void pixel_format_message (VncState *vs) {
@@ -2082,8 +2260,8 @@ static void vnc_colordepth(VncState *vs)
vnc_write_u8(vs, 0);
vnc_write_u16(vs, 1); /* number of rects */
vnc_framebuffer_update(vs, 0, 0,
- surface_width(vs->vd->ds),
- surface_height(vs->vd->ds),
+ pixman_image_get_width(vs->vd->server),
+ pixman_image_get_height(vs->vd->server),
VNC_ENCODING_WMVi);
pixel_format_message(vs);
vnc_unlock_output(vs);
@@ -2097,6 +2275,7 @@ 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) {
@@ -2108,7 +2287,7 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len)
if (len == 1)
return 20;
- set_pixel_format(vs, read_u8(data, 4), read_u8(data, 5),
+ 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),
@@ -2153,13 +2332,20 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len)
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)
+ if (len == 1) {
return 8;
-
+ }
if (len == 8) {
uint32_t dlen = read_u32(data, 4);
- if (dlen > 0)
+ 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;
+ }
}
client_cut_text(vs, read_u32(data, 4), data + 8);
@@ -2191,45 +2377,56 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len)
if (len == 4)
return 10;
switch (read_u8(data, 4)) {
- case 0: vs->as.fmt = AUD_FMT_U8; break;
- case 1: vs->as.fmt = AUD_FMT_S8; break;
- case 2: vs->as.fmt = AUD_FMT_U16; break;
- case 3: vs->as.fmt = AUD_FMT_S16; break;
- case 4: vs->as.fmt = AUD_FMT_U32; break;
- case 5: vs->as.fmt = AUD_FMT_S32; break;
+ 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:
- printf("Invalid audio format %d\n", read_u8(data, 4));
+ 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) {
- printf("Invalid audio channel coount %d\n",
- read_u8(data, 5));
+ VNC_DEBUG("Invalid audio channel count %d\n",
+ read_u8(data, 5));
vnc_client_error(vs);
break;
}
- vs->as.freq = read_u32(data, 6);
+ 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;
break;
default:
- printf ("Invalid audio message %d\n", read_u8(data, 4));
+ VNC_DEBUG("Invalid audio message %d\n", read_u8(data, 4));
vnc_client_error(vs);
break;
}
break;
default:
- printf("Msg: %d\n", read_u16(data, 0));
+ VNC_DEBUG("Msg: %d\n", read_u16(data, 0));
vnc_client_error(vs);
break;
}
break;
default:
- printf("Msg: %d\n", data[0]);
+ 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;
}
@@ -2298,24 +2495,37 @@ static int protocol_client_init(VncState *vs, uint8_t *data, size_t len)
}
vnc_set_share_mode(vs, mode);
- vs->client_width = surface_width(vs->vd->ds);
- vs->client_height = surface_height(vs->vd->ds);
+ 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)
+ if (qemu_name) {
size = snprintf(buf, sizeof(buf), "QEMU (%s)", qemu_name);
- else
+ 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, QEVENT_VNC_INITIALIZED);
+ vnc_qmp_event(vs, QAPI_EVENT_VNC_INITIALIZED);
vnc_read_when(vs, protocol_client_msg, 1);
@@ -2327,29 +2537,33 @@ void start_client_init(VncState *vs)
vnc_read_when(vs, protocol_client_init, 1);
}
-static void make_challenge(VncState *vs)
+static void authentication_failed(VncState *vs)
{
- int i;
-
- srand(time(NULL)+getpid()+getpid()*987654+rand());
-
- for (i = 0 ; i < sizeof(vs->challenge) ; i++)
- vs->challenge[i] = (int) (256.0*rand()/(RAND_MAX+1.0));
+ 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 int protocol_client_auth_vnc(VncState *vs, uint8_t *data, size_t len)
{
unsigned char response[VNC_AUTH_CHALLENGE_SIZE];
- int i, j, pwlen;
+ size_t i, pwlen;
unsigned char key[8];
time_t now = time(NULL);
+ QCryptoCipher *cipher = NULL;
+ Error *err = NULL;
if (!vs->vd->password) {
- VNC_DEBUG("No password configured on server");
+ trace_vnc_auth_fail(vs, vs->auth, "password is not set", "");
goto reject;
}
if (vs->vd->expires < now) {
- VNC_DEBUG("Password is expired");
+ trace_vnc_auth_fail(vs, vs->auth, "password is expired", "");
goto reject;
}
@@ -2359,38 +2573,63 @@ static int protocol_client_auth_vnc(VncState *vs, uint8_t *data, size_t len)
pwlen = strlen(vs->vd->password);
for (i=0; i<sizeof(key); i++)
key[i] = i<pwlen ? vs->vd->password[i] : 0;
- deskey(key, EN0);
- for (j = 0; j < VNC_AUTH_CHALLENGE_SIZE; j += 8)
- des(response+j, response+j);
+
+ cipher = qcrypto_cipher_new(
+ QCRYPTO_CIPHER_ALG_DES_RFB,
+ 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) {
- VNC_DEBUG("Client challenge response did not match\n");
+ trace_vnc_auth_fail(vs, vs->auth, "mis-matched challenge response", "");
goto reject;
} else {
- VNC_DEBUG("Accepting VNC challenge response\n");
+ 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:
- 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);
+ authentication_failed(vs);
+ qcrypto_cipher_free(cipher);
return 0;
}
void start_auth_vnc(VncState *vs)
{
- make_challenge(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);
@@ -2404,54 +2643,37 @@ 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 */
- VNC_DEBUG("Reject auth %d because it didn't match advertized\n", (int)data[0]);
- vnc_write_u32(vs, 1);
- if (vs->minor >= 8) {
- static const char err[] = "Authentication failed";
- vnc_write_u32(vs, sizeof(err));
- vnc_write(vs, err, sizeof(err));
- }
- vnc_client_error(vs);
+ trace_vnc_auth_reject(vs, vs->auth, (int)data[0]);
+ authentication_failed(vs);
} else { /* Accept requested auth */
- VNC_DEBUG("Client requested auth %d\n", (int)data[0]);
+ trace_vnc_auth_start(vs, vs->auth);
switch (vs->auth) {
case VNC_AUTH_NONE:
- VNC_DEBUG("Accept auth none\n");
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:
- VNC_DEBUG("Start VNC auth\n");
start_auth_vnc(vs);
break;
-#ifdef CONFIG_VNC_TLS
case VNC_AUTH_VENCRYPT:
- VNC_DEBUG("Accept VeNCrypt auth\n");
start_auth_vencrypt(vs);
break;
-#endif /* CONFIG_VNC_TLS */
#ifdef CONFIG_VNC_SASL
case VNC_AUTH_SASL:
- VNC_DEBUG("Accept SASL auth\n");
start_auth_sasl(vs);
break;
#endif /* CONFIG_VNC_SASL */
default: /* Should not be possible, but just in case */
- VNC_DEBUG("Reject auth %d server code bug\n", vs->auth);
- vnc_write_u8(vs, 1);
- if (vs->minor >= 8) {
- static const char err[] = "Authentication failed";
- vnc_write_u32(vs, sizeof(err));
- vnc_write(vs, err, sizeof(err));
- }
- vnc_client_error(vs);
+ trace_vnc_auth_fail(vs, vs->auth, "Unhandled auth method", "");
+ authentication_failed(vs);
}
}
return 0;
@@ -2489,10 +2711,11 @@ static int protocol_version(VncState *vs, uint8_t *version, size_t len)
vs->minor = 3;
if (vs->minor == 3) {
+ trace_vnc_auth_start(vs, vs->auth);
if (vs->auth == VNC_AUTH_NONE) {
- VNC_DEBUG("Tell client auth none\n");
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");
@@ -2500,13 +2723,13 @@ static int protocol_version(VncState *vs, uint8_t *version, size_t len)
vnc_flush(vs);
start_auth_vnc(vs);
} else {
- VNC_DEBUG("Unsupported auth %d for protocol 3.3\n", vs->auth);
+ 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_DEBUG("Telling client we support auth %d\n", vs->auth);
vnc_write_u8(vs, 1); /* num auth */
vnc_write_u8(vs, vs->auth);
vnc_read_when(vs, protocol_client_auth, 1);
@@ -2546,8 +2769,8 @@ static int vnc_refresh_lossy_rect(VncDisplay *vd, int x, int y)
int stx = x / VNC_STAT_RECT;
int has_dirty = 0;
- y = y / VNC_STAT_RECT * VNC_STAT_RECT;
- x = x / VNC_STAT_RECT * VNC_STAT_RECT;
+ y = QEMU_ALIGN_DOWN(y, VNC_STAT_RECT);
+ x = QEMU_ALIGN_DOWN(x, VNC_STAT_RECT);
QTAILQ_FOREACH(vs, &vd->clients, next) {
int j;
@@ -2563,7 +2786,9 @@ static int vnc_refresh_lossy_rect(VncDisplay *vd, int x, int y)
vs->lossy_rect[sty][stx] = 0;
for (j = 0; j < VNC_STAT_RECT; ++j) {
- bitmap_set(vs->dirty[y + j], x / 16, VNC_STAT_RECT / 16);
+ bitmap_set(vs->dirty[y + j],
+ x / VNC_DIRTY_PIXELS_PER_BIT,
+ VNC_STAT_RECT / VNC_DIRTY_PIXELS_PER_BIT);
}
has_dirty++;
}
@@ -2573,8 +2798,10 @@ static int vnc_refresh_lossy_rect(VncDisplay *vd, int x, int y)
static int vnc_update_stats(VncDisplay *vd, struct timeval * tv)
{
- int width = pixman_image_get_width(vd->guest.fb);
- int height = pixman_image_get_height(vd->guest.fb);
+ 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;
@@ -2632,8 +2859,8 @@ double vnc_update_freq(VncState *vs, int x, int y, int w, int h)
double total = 0;
int num = 0;
- x = (x / VNC_STAT_RECT) * VNC_STAT_RECT;
- y = (y / VNC_STAT_RECT) * VNC_STAT_RECT;
+ 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) {
@@ -2664,12 +2891,12 @@ static void vnc_rect_updated(VncDisplay *vd, int x, int y, struct timeval * tv)
static int vnc_refresh_server_surface(VncDisplay *vd)
{
- int width = pixman_image_get_width(vd->guest.fb);
- int height = pixman_image_get_height(vd->guest.fb);
- int y;
- uint8_t *guest_row;
- uint8_t *server_row;
- int cmp_bytes;
+ 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;
@@ -2686,47 +2913,72 @@ static int vnc_refresh_server_surface(VncDisplay *vd)
* Check and copy modified bits from guest to server surface.
* Update server dirty map.
*/
- cmp_bytes = 64;
- if (cmp_bytes > vnc_server_fb_stride(vd)) {
- cmp_bytes = vnc_server_fb_stride(vd);
- }
+ 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);
- }
- guest_row = (uint8_t *)pixman_image_get_data(vd->guest.fb);
- server_row = (uint8_t *)pixman_image_get_data(vd->server);
- for (y = 0; y < height; y++) {
- if (!bitmap_empty(vd->guest.dirty[y], VNC_DIRTY_BITS)) {
- int x;
- uint8_t *guest_ptr;
- uint8_t *server_ptr;
-
- 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_row;
- }
- server_ptr = server_row;
+ } 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);
- for (x = 0; x + 15 < width;
- x += 16, guest_ptr += cmp_bytes, server_ptr += cmp_bytes) {
- if (!test_and_clear_bit((x / 16), vd->guest.dirty[y]))
- continue;
- 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, y, &tv);
- QTAILQ_FOREACH(vs, &vd->clients, next) {
- set_bit((x / 16), vs->dirty[y]);
- }
- has_dirty++;
+ 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++;
}
- guest_row += pixman_image_get_stride(vd->guest.fb);
- server_row += pixman_image_get_stride(vd->server);
+
+ y++;
}
qemu_pixman_image_unref(tmpbuf);
return has_dirty;
@@ -2738,7 +2990,12 @@ static void vnc_refresh(DisplayChangeListener *dcl)
VncState *vs, *vn;
int has_dirty, rects = 0;
- graphic_hw_update(NULL);
+ 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);
@@ -2753,11 +3010,6 @@ static void vnc_refresh(DisplayChangeListener *dcl)
/* vs might be free()ed here */
}
- if (QTAILQ_EMPTY(&vd->clients)) {
- update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_MAX);
- return;
- }
-
if (has_dirty && rects) {
vd->dcl.update_interval /= 2;
if (vd->dcl.update_interval < VNC_REFRESH_INTERVAL_BASE) {
@@ -2771,580 +3023,1111 @@ static void vnc_refresh(DisplayChangeListener *dcl)
}
}
-static void vnc_connect(VncDisplay *vd, int csock,
+static void vnc_connect(VncDisplay *vd, QIOChannelSocket *sioc,
bool skipauth, bool websocket)
{
- VncState *vs = g_malloc0(sizeof(VncState));
+ VncState *vs = g_new0(VncState, 1);
+ bool first_client = QTAILQ_EMPTY(&vd->clients);
int i;
- vs->csock = csock;
+ 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;
- if (skipauth) {
- vs->auth = VNC_AUTH_NONE;
-#ifdef CONFIG_VNC_TLS
- vs->subauth = VNC_AUTH_INVALID;
+ 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
- } else {
- vs->auth = vd->auth;
-#ifdef CONFIG_VNC_TLS
- vs->subauth = vd->subauth;
+#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_malloc0(VNC_STAT_COLS * sizeof (uint8_t));
+ vs->lossy_rect[i] = g_new0(uint8_t, VNC_STAT_COLS);
}
- VNC_DEBUG("New client on socket %d\n", csock);
+ VNC_DEBUG("New client on socket %p\n", vs->sioc);
update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_BASE);
- qemu_set_nonblock(vs->csock);
-#ifdef CONFIG_VNC_WS
+ qio_channel_set_blocking(vs->ioc, false, NULL);
+ if (vs->ioc_tag) {
+ g_source_remove(vs->ioc_tag);
+ }
if (websocket) {
vs->websocket = 1;
-#ifdef CONFIG_VNC_TLS
- if (vd->tls.x509cert) {
- qemu_set_fd_handler2(vs->csock, NULL, vncws_tls_handshake_peek,
- NULL, vs);
- } else
-#endif /* CONFIG_VNC_TLS */
- {
- qemu_set_fd_handler2(vs->csock, NULL, vncws_handshake_read,
- NULL, vs);
+ 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
-#endif /* CONFIG_VNC_WS */
- {
- qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs);
+ } 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, QEVENT_VNC_CONNECTED);
+ vnc_qmp_event(vs, QAPI_EVENT_VNC_CONNECTED);
vnc_set_share_mode(vs, VNC_SHARE_MODE_CONNECTING);
- vs->vd = vd;
-
-#ifdef CONFIG_VNC_WS
- if (!vs->websocket)
-#endif
- {
- vnc_init_state(vs);
- }
-}
-
-void vnc_init_state(VncState *vs)
-{
- vs->initialized = true;
- VncDisplay *vd = vs->vd;
-
vs->last_x = -1;
vs->last_y = -1;
vs->as.freq = 44100;
vs->as.nchannels = 2;
- vs->as.fmt = AUD_FMT_S16;
+ 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_HEAD(&vd->clients, vs, next);
+ QTAILQ_INSERT_TAIL(&vd->clients, vs, next);
+ if (first_client) {
+ vnc_update_server_surface(vd);
+ }
- graphic_hw_update(NULL);
+ 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);
- reset_keys(vs);
- if (vs->vd->lock_key_sync)
- vs->led = qemu_add_led_event_handler(kbd_leds, vs);
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;
- /* vs might be free()ed here */
+ 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 void vnc_listen_read(void *opaque, bool websocket)
+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 *vs = opaque;
- struct sockaddr_in addr;
- socklen_t addrlen = sizeof(addr);
- int csock;
+ VncDisplay *vd;
- /* Catch-up */
- graphic_hw_update(NULL);
-#ifdef CONFIG_VNC_WS
- if (websocket) {
- csock = qemu_accept(vs->lwebsock, (struct sockaddr *)&addr, &addrlen);
- } else
-#endif /* CONFIG_VNC_WS */
- {
- csock = qemu_accept(vs->lsock, (struct sockaddr *)&addr, &addrlen);
+ 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 (csock != -1) {
- vnc_connect(vs, csock, false, websocket);
+ 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_listen_regular_read(void *opaque)
+
+static void vnc_display_close(VncDisplay *vd)
{
- vnc_listen_read(opaque, false);
+ 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_unparent(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
}
-#ifdef CONFIG_VNC_WS
-static void vnc_listen_websocket_read(void *opaque)
+int vnc_display_password(const char *id, const char *password)
{
- vnc_listen_read(opaque, true);
-}
-#endif /* CONFIG_VNC_WS */
+ VncDisplay *vd = vnc_display_find(id);
-static const DisplayChangeListenerOps dcl_ops = {
- .dpy_name = "vnc",
- .dpy_refresh = vnc_refresh,
- .dpy_gfx_copy = vnc_dpy_copy,
- .dpy_gfx_update = vnc_dpy_update,
- .dpy_gfx_switch = vnc_dpy_switch,
- .dpy_mouse_set = vnc_mouse_set,
- .dpy_cursor_define = vnc_dpy_cursor_define,
-};
+ 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;
+ }
-void vnc_display_init(DisplayState *ds)
-{
- VncDisplay *vs = g_malloc0(sizeof(*vs));
+ g_free(vd->password);
+ vd->password = g_strdup(password);
- vnc_display = vs;
+ return 0;
+}
- vs->lsock = -1;
-#ifdef CONFIG_VNC_WS
- vs->lwebsock = -1;
-#endif
+int vnc_display_pw_expire(const char *id, time_t expires)
+{
+ VncDisplay *vd = vnc_display_find(id);
- QTAILQ_INIT(&vs->clients);
- vs->expires = TIME_MAX;
+ if (!vd) {
+ return -EINVAL;
+ }
- if (keyboard_layout)
- vs->kbd_layout = init_keyboard_layout(name2keysym, keyboard_layout);
- else
- vs->kbd_layout = init_keyboard_layout(name2keysym, "en-us");
+ vd->expires = expires;
+ return 0;
+}
- if (!vs->kbd_layout)
- exit(1);
+static void vnc_display_print_local_addr(VncDisplay *vd)
+{
+ SocketAddress *addr;
- qemu_mutex_init(&vs->mutex);
- vnc_start_worker_thread();
+ if (!vd->listener || !vd->listener->nsioc) {
+ return;
+ }
- vs->dcl.ops = &dcl_ops;
- register_displaychangelistener(&vs->dcl);
+ 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 = "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 = "acl",
+ .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,
+ },
+ { /* end of list */ }
+ },
+};
+
-static void vnc_display_close(DisplayState *ds)
+static int
+vnc_display_setup_auth(int *auth,
+ int *subauth,
+ QCryptoTLSCreds *tlscreds,
+ bool password,
+ bool sasl,
+ bool websocket,
+ Error **errp)
{
- VncDisplay *vs = vnc_display;
+ /*
+ * 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;
+ }
- if (!vs)
- return;
- if (vs->display) {
- g_free(vs->display);
- vs->display = NULL;
- }
- if (vs->lsock != -1) {
- qemu_set_fd_handler2(vs->lsock, NULL, NULL, NULL, NULL);
- close(vs->lsock);
- vs->lsock = -1;
- }
-#ifdef CONFIG_VNC_WS
- g_free(vs->ws_display);
- vs->ws_display = NULL;
- if (vs->lwebsock != -1) {
- qemu_set_fd_handler2(vs->lwebsock, NULL, NULL, NULL, NULL);
- close(vs->lwebsock);
- vs->lwebsock = -1;
- }
-#endif /* CONFIG_VNC_WS */
- vs->auth = VNC_AUTH_INVALID;
-#ifdef CONFIG_VNC_TLS
- vs->subauth = VNC_AUTH_INVALID;
- vs->tls.x509verify = 0;
-#endif
+ } 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_disable_login(DisplayState *ds)
+
+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)
{
- VncDisplay *vs = vnc_display;
+ int ret = -1;
+ SocketAddress *addr = NULL;
- if (!vs) {
- return -1;
+ 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;
}
- if (vs->password) {
- g_free(vs->password);
+ *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;
- vs->password = NULL;
- if (vs->auth == VNC_AUTH_NONE) {
- vs->auth = VNC_AUTH_VNC;
+ for (i = 0; i < *retnsaddr; i++) {
+ qapi_free_SocketAddress((*retsaddr)[i]);
}
+ g_free(*retsaddr);
- return 0;
+ *retsaddr = NULL;
+ *retnsaddr = 0;
}
-int vnc_display_password(DisplayState *ds, const char *password)
+static int vnc_display_get_addresses(QemuOpts *opts,
+ bool reverse,
+ SocketAddress ***retsaddr,
+ size_t *retnsaddr,
+ SocketAddress ***retwsaddr,
+ size_t *retnwsaddr,
+ Error **errp)
{
- VncDisplay *vs = vnc_display;
-
- if (!vs) {
- return -EINVAL;
+ 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 (!password) {
- /* This is not the intention of this interface but err on the side
- of being safe */
- return vnc_display_disable_login(ds);
+ /* If we had multiple primary displays, we don't do defaults
+ * for websocket, and require explicit config instead. */
+ if (*retnsaddr > 1) {
+ displaynum = -1;
}
- if (vs->password) {
- g_free(vs->password);
- vs->password = NULL;
+ 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;
}
- vs->password = g_strdup(password);
- if (vs->auth == VNC_AUTH_NONE) {
- vs->auth = VNC_AUTH_VNC;
+
+ 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) {
+ return -1;
+ }
+ vnc_connect(vd, sioc, false, false);
+ object_unref(OBJECT(sioc));
return 0;
}
-int vnc_display_pw_expire(DisplayState *ds, time_t expires)
+
+static int vnc_display_listen(VncDisplay *vd,
+ SocketAddress **saddr,
+ size_t nsaddr,
+ SocketAddress **wsaddr,
+ size_t nwsaddr,
+ Error **errp)
{
- VncDisplay *vs = vnc_display;
+ 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;
+ }
+ }
- if (!vs) {
- return -EINVAL;
+ 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);
}
- vs->expires = expires;
return 0;
}
-char *vnc_display_local_addr(DisplayState *ds)
-{
- VncDisplay *vs = vnc_display;
-
- return vnc_socket_local_addr("%s:%s", vs->lsock);
-}
-void vnc_display_open(DisplayState *ds, const char *display, Error **errp)
+void vnc_display_open(const char *id, Error **errp)
{
- VncDisplay *vs = vnc_display;
- const char *options;
- int password = 0;
- int reverse = 0;
-#ifdef CONFIG_VNC_TLS
- int tls = 0, x509 = 0;
-#endif
-#ifdef CONFIG_VNC_SASL
- int sasl = 0;
- int saslErr;
-#endif
-#if defined(CONFIG_VNC_TLS) || defined(CONFIG_VNC_SASL)
+ 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;
int acl = 0;
-#endif
+ const char *tlsauthz;
+ const char *saslauthz;
int lock_key_sync = 1;
+ int key_delay_ms;
+ const char *audiodev;
- if (!vnc_display) {
+ if (!vd) {
error_setg(errp, "VNC display not active");
return;
}
- vnc_display_close(ds);
- if (strcmp(display, "none") == 0)
+ vnc_display_close(vd);
+
+ if (!opts) {
return;
+ }
- vs->display = g_strdup(display);
- vs->share_policy = VNC_SHARE_POLICY_ALLOW_EXCLUSIVE;
-
- options = display;
- while ((options = strchr(options, ','))) {
- options++;
- if (strncmp(options, "password", 8) == 0) {
- 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;
- }
- password = 1; /* Require password auth */
- } else if (strncmp(options, "reverse", 7) == 0) {
- reverse = 1;
- } else if (strncmp(options, "no-lock-key-sync", 16) == 0) {
- lock_key_sync = 0;
-#ifdef CONFIG_VNC_SASL
- } else if (strncmp(options, "sasl", 4) == 0) {
- sasl = 1; /* Require SASL auth */
-#endif
-#ifdef CONFIG_VNC_WS
- } else if (strncmp(options, "websocket", 9) == 0) {
- char *start, *end;
- vs->websocket = 1;
-
- /* Check for 'websocket=<port>' */
- start = strchr(options, '=');
- end = strchr(options, ',');
- if (start && (!end || (start < end))) {
- int len = end ? end-(start+1) : strlen(start+1);
- if (len < 6) {
- /* extract the host specification from display */
- char *host = NULL, *port = NULL, *host_end = NULL;
- port = g_strndup(start + 1, len);
-
- /* ipv6 hosts have colons */
- end = strchr(display, ',');
- host_end = g_strrstr_len(display, end - display, ":");
-
- if (host_end) {
- host = g_strndup(display, host_end - display + 1);
- } else {
- host = g_strndup(":", 1);
- }
- vs->ws_display = g_strconcat(host, port, NULL);
- g_free(host);
- g_free(port);
- }
- }
-#endif /* CONFIG_VNC_WS */
-#ifdef CONFIG_VNC_TLS
- } else if (strncmp(options, "tls", 3) == 0) {
- tls = 1; /* Require TLS */
- } else if (strncmp(options, "x509", 4) == 0) {
- char *start, *end;
- x509 = 1; /* Require x509 certificates */
- if (strncmp(options, "x509verify", 10) == 0)
- vs->tls.x509verify = 1; /* ...and verify client certs */
-
- /* Now check for 'x509=/some/path' postfix
- * and use that to setup x509 certificate/key paths */
- start = strchr(options, '=');
- end = strchr(options, ',');
- if (start && (!end || (start < end))) {
- int len = end ? end-(start+1) : strlen(start+1);
- char *path = g_strndup(start + 1, len);
-
- VNC_DEBUG("Trying certificate path '%s'\n", path);
- if (vnc_tls_set_x509_creds_dir(vs, path) < 0) {
- error_setg(errp, "Failed to find x509 certificates/keys in %s", path);
- g_free(path);
- goto fail;
- }
- g_free(path);
- } else {
- error_setg(errp, "No certificate path provided");
- goto fail;
- }
-#endif
-#if defined(CONFIG_VNC_TLS) || defined(CONFIG_VNC_SASL)
- } else if (strncmp(options, "acl", 3) == 0) {
- acl = 1;
-#endif
- } else if (strncmp(options, "lossy", 5) == 0) {
- vs->lossy = true;
- } else if (strncmp(options, "non-adaptive", 12) == 0) {
- vs->non_adaptive = true;
- } else if (strncmp(options, "share=", 6) == 0) {
- if (strncmp(options+6, "ignore", 6) == 0) {
- vs->share_policy = VNC_SHARE_POLICY_IGNORE;
- } else if (strncmp(options+6, "allow-exclusive", 15) == 0) {
- vs->share_policy = VNC_SHARE_POLICY_ALLOW_EXCLUSIVE;
- } else if (strncmp(options+6, "force-shared", 12) == 0) {
- vs->share_policy = VNC_SHARE_POLICY_FORCE_SHARED;
- } else {
- error_setg(errp, "unknown vnc share= option");
- goto fail;
- }
- }
+ reverse = qemu_opt_get_bool(opts, "reverse", false);
+ if (vnc_display_get_addresses(opts, reverse, &saddr, &nsaddr,
+ &wsaddr, &nwsaddr, errp) < 0) {
+ goto fail;
}
-#ifdef CONFIG_VNC_TLS
- if (acl && x509 && vs->tls.x509verify) {
- if (!(vs->tls.acl = qemu_acl_init("vnc.x509dname"))) {
- fprintf(stderr, "Failed to create x509 dname ACL\n");
- exit(1);
+ 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_RFB, QCRYPTO_CIPHER_MODE_ECB)) {
+ error_setg(errp,
+ "Cipher backend does not support DES RFB algorithm");
+ goto fail;
}
}
-#endif
-#ifdef CONFIG_VNC_SASL
- if (acl && sasl) {
- if (!(vs->sasl.acl = qemu_acl_init("vnc.username"))) {
- fprintf(stderr, "Failed to create username ACL\n");
- exit(1);
+
+ 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 (vd->tlscreds->endpoint != QCRYPTO_TLS_CREDS_ENDPOINT_SERVER) {
+ error_setg(errp,
+ "Expecting TLS credentials with a server endpoint");
+ goto fail;
}
}
-#endif
+ if (qemu_opt_get(opts, "acl")) {
+ error_report("The 'acl' option to -vnc is deprecated. "
+ "Please use the 'tls-authz' and 'sasl-authz' "
+ "options instead");
+ }
+ acl = qemu_opt_get_bool(opts, "acl", false);
+ tlsauthz = qemu_opt_get(opts, "tls-authz");
+ if (acl && tlsauthz) {
+ error_setg(errp, "'acl' option is mutually exclusive with the "
+ "'tls-authz' option");
+ goto fail;
+ }
+ if (tlsauthz && !vd->tlscreds) {
+ error_setg(errp, "'tls-authz' provided but TLS is not enabled");
+ goto fail;
+ }
- /*
- * Combinations we support here:
- *
- * - no-auth (clear text, no auth)
- * - password (clear text, weak auth)
- * - sasl (encrypt, good auth *IF* using Kerberos via GSSAPI)
- * - tls (encrypt, weak anonymous creds, no auth)
- * - tls + password (encrypt, weak anonymous creds, weak auth)
- * - tls + sasl (encrypt, weak anonymous creds, good auth)
- * - tls + x509 (encrypt, good x509 creds, no auth)
- * - tls + x509 + password (encrypt, good x509 creds, weak auth)
- * - tls + x509 + sasl (encrypt, good x509 creds, good auth)
- *
- * NB1. TLS is a stackable auth scheme.
- * NB2. the x509 schemes have option to validate a client cert dname
- */
- if (password) {
-#ifdef CONFIG_VNC_TLS
- if (tls) {
- vs->auth = VNC_AUTH_VENCRYPT;
- if (x509) {
- VNC_DEBUG("Initializing VNC server with x509 password auth\n");
- vs->subauth = VNC_AUTH_VENCRYPT_X509VNC;
- } else {
- VNC_DEBUG("Initializing VNC server with TLS password auth\n");
- vs->subauth = VNC_AUTH_VENCRYPT_TLSVNC;
- }
+ saslauthz = qemu_opt_get(opts, "sasl-authz");
+ if (acl && saslauthz) {
+ error_setg(errp, "'acl' option is mutually exclusive with the "
+ "'sasl-authz' option");
+ goto fail;
+ }
+ 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 {
-#endif /* CONFIG_VNC_TLS */
- VNC_DEBUG("Initializing VNC server with password auth\n");
- vs->auth = VNC_AUTH_VNC;
-#ifdef CONFIG_VNC_TLS
- vs->subauth = VNC_AUTH_INVALID;
+ error_setg(errp, "unknown vnc share= option");
+ goto fail;
}
-#endif /* CONFIG_VNC_TLS */
-#ifdef CONFIG_VNC_SASL
- } else if (sasl) {
-#ifdef CONFIG_VNC_TLS
- if (tls) {
- vs->auth = VNC_AUTH_VENCRYPT;
- if (x509) {
- VNC_DEBUG("Initializing VNC server with x509 SASL auth\n");
- vs->subauth = VNC_AUTH_VENCRYPT_X509SASL;
- } else {
- VNC_DEBUG("Initializing VNC server with TLS SASL auth\n");
- vs->subauth = VNC_AUTH_VENCRYPT_TLSSASL;
- }
+ } 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;
+ }
+
+ if (tlsauthz) {
+ vd->tlsauthzid = g_strdup(tlsauthz);
+ } else if (acl) {
+ if (strcmp(vd->id, "default") == 0) {
+ vd->tlsauthzid = g_strdup("vnc.x509dname");
} else {
-#endif /* CONFIG_VNC_TLS */
- VNC_DEBUG("Initializing VNC server with SASL auth\n");
- vs->auth = VNC_AUTH_SASL;
-#ifdef CONFIG_VNC_TLS
- vs->subauth = VNC_AUTH_INVALID;
+ vd->tlsauthzid = g_strdup_printf("vnc.%s.x509dname", vd->id);
}
-#endif /* CONFIG_VNC_TLS */
-#endif /* CONFIG_VNC_SASL */
- } else {
-#ifdef CONFIG_VNC_TLS
- if (tls) {
- vs->auth = VNC_AUTH_VENCRYPT;
- if (x509) {
- VNC_DEBUG("Initializing VNC server with x509 no auth\n");
- vs->subauth = VNC_AUTH_VENCRYPT_X509NONE;
+ vd->tlsauthz = QAUTHZ(qauthz_list_new(vd->tlsauthzid,
+ QAUTHZ_LIST_POLICY_DENY,
+ &error_abort));
+ }
+#ifdef CONFIG_VNC_SASL
+ if (sasl) {
+ if (saslauthz) {
+ vd->sasl.authzid = g_strdup(saslauthz);
+ } else if (acl) {
+ if (strcmp(vd->id, "default") == 0) {
+ vd->sasl.authzid = g_strdup("vnc.username");
} else {
- VNC_DEBUG("Initializing VNC server with TLS no auth\n");
- vs->subauth = VNC_AUTH_VENCRYPT_TLSNONE;
+ vd->sasl.authzid = g_strdup_printf("vnc.%s.username", vd->id);
}
- } else {
-#endif
- VNC_DEBUG("Initializing VNC server with no auth\n");
- vs->auth = VNC_AUTH_NONE;
-#ifdef CONFIG_VNC_TLS
- vs->subauth = VNC_AUTH_INVALID;
+ vd->sasl.authz = QAUTHZ(qauthz_list_new(vd->sasl.authzid,
+ QAUTHZ_LIST_POLICY_DENY,
+ &error_abort));
}
+ }
#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);
-#ifdef CONFIG_VNC_SASL
- if ((saslErr = sasl_server_init(NULL, "qemu")) != SASL_OK) {
- error_setg(errp, "Failed to initialize SASL auth: %s",
- sasl_errstring(saslErr, NULL, NULL));
+ if (vnc_display_setup_auth(&vd->ws_auth, &vd->ws_subauth,
+ vd->tlscreds, password,
+ sasl, true, errp) < 0) {
goto fail;
}
-#endif
- vs->lock_key_sync = lock_key_sync;
+ trace_vnc_auth_init(vd, 1, vd->ws_auth, vd->ws_subauth);
- if (reverse) {
- /* connect to viewer */
- int csock;
- vs->lsock = -1;
-#ifdef CONFIG_VNC_WS
- vs->lwebsock = -1;
+#ifdef CONFIG_VNC_SASL
+ if (sasl) {
+ 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));
+ goto fail;
+ }
+ }
#endif
- if (strncmp(display, "unix:", 5) == 0) {
- csock = unix_connect(display+5, errp);
- } else {
- csock = inet_connect(display, errp);
+ 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;
}
- if (csock < 0) {
+ }
+
+ 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;
}
- vnc_connect(vs, csock, false, false);
} else {
- /* listen for connects */
- char *dpy;
- dpy = g_malloc(256);
- if (strncmp(display, "unix:", 5) == 0) {
- pstrcpy(dpy, 256, "unix:");
- vs->lsock = unix_listen(display+5, dpy+5, 256-5, errp);
- } else {
- vs->lsock = inet_listen(display, dpy, 256,
- SOCK_STREAM, 5900, errp);
- if (vs->lsock < 0) {
- g_free(dpy);
- goto fail;
- }
-#ifdef CONFIG_VNC_WS
- if (vs->websocket) {
- if (vs->ws_display) {
- vs->lwebsock = inet_listen(vs->ws_display, NULL, 256,
- SOCK_STREAM, 0, errp);
- } else {
- vs->lwebsock = inet_listen(vs->display, NULL, 256,
- SOCK_STREAM, 5700, errp);
- }
+ con = NULL;
+ }
- if (vs->lwebsock < 0) {
- if (vs->lsock) {
- close(vs->lsock);
- vs->lsock = -1;
- }
- g_free(dpy);
- goto fail;
- }
- }
-#endif /* CONFIG_VNC_WS */
+ 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;
}
- g_free(vs->display);
- vs->display = dpy;
- qemu_set_fd_handler2(vs->lsock, NULL,
- vnc_listen_regular_read, NULL, vs);
-#ifdef CONFIG_VNC_WS
- if (vs->websocket) {
- qemu_set_fd_handler2(vs->lwebsock, NULL,
- vnc_listen_websocket_read, NULL, vs);
+ } else {
+ if (vnc_display_listen(vd, saddr, nsaddr, wsaddr, nwsaddr, errp) < 0) {
+ goto fail;
}
-#endif /* CONFIG_VNC_WS */
}
+
+ 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:
- g_free(vs->display);
- vs->display = NULL;
-#ifdef CONFIG_VNC_WS
- g_free(vs->ws_display);
- vs->ws_display = NULL;
-#endif /* CONFIG_VNC_WS */
+ vnc_display_close(vd);
+ goto cleanup;
}
-void vnc_display_add_client(DisplayState *ds, int csock, bool skipauth)
+void vnc_display_add_client(const char *id, int csock, bool skipauth)
{
- VncDisplay *vs = vnc_display;
+ VncDisplay *vd = vnc_display_find(id);
+ QIOChannelSocket *sioc;
- vnc_connect(vs, csock, skipauth, false);
+ 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);
+}
+
+QemuOpts *vnc_parse(const char *str, Error **errp)
+{
+ QemuOptsList *olist = qemu_find_opts("vnc");
+ QemuOpts *opts = qemu_opts_parse(olist, str, true, errp);
+ const char *id;
+
+ if (!opts) {
+ return NULL;
+ }
+
+ id = qemu_opts_id(opts);
+ if (!id) {
+ /* auto-assign id if not present */
+ vnc_auto_assign_id(olist, opts);
+ }
+ return 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
index 6e9921387..4e2637ce6 100644
--- a/ui/vnc.h
+++ b/ui/vnc.h
@@ -24,22 +24,26 @@
* THE SOFTWARE.
*/
-#ifndef __QEMU_VNC_H
-#define __QEMU_VNC_H
+#ifndef QEMU_VNC_H
+#define QEMU_VNC_H
-#include "qemu-common.h"
#include "qemu/queue.h"
#include "qemu/thread.h"
#include "ui/console.h"
-#include "monitor/monitor.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 <stdbool.h>
#include "keymaps.h"
#include "vnc-palette.h"
#include "vnc-enc-zrle.h"
+#include "ui/kbd-state.h"
// #define _VNC_DEBUG 1
@@ -55,13 +59,6 @@
*
*****************************************************************************/
-typedef struct Buffer
-{
- size_t capacity;
- size_t offset;
- uint8_t *buffer;
-} Buffer;
-
typedef struct VncState VncState;
typedef struct VncJob VncJob;
typedef struct VncRect VncRect;
@@ -77,12 +74,21 @@ typedef void VncSendHextileTile(VncState *vs,
void *last_fg,
int *has_bg, int *has_fg);
-/* VNC_MAX_WIDTH must be a multiple of 16. */
-#define VNC_MAX_WIDTH 2560
+/* 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 / 16)
+#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)
@@ -92,16 +98,11 @@ typedef void VncSendHextileTile(VncState *vs,
typedef struct VncDisplay VncDisplay;
-#ifdef CONFIG_VNC_TLS
-#include "vnc-tls.h"
#include "vnc-auth-vencrypt.h"
-#endif
#ifdef CONFIG_VNC_SASL
#include "vnc-auth-sasl.h"
#endif
-#ifdef CONFIG_VNC_WS
#include "vnc-ws.h"
-#endif
struct VncRectStat
{
@@ -118,7 +119,8 @@ typedef struct VncRectStat VncRectStat;
struct VncSurface
{
struct timeval last_freq_check;
- DECLARE_BITMAP(dirty[VNC_MAX_HEIGHT], VNC_MAX_WIDTH / 16);
+ 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;
@@ -140,18 +142,20 @@ typedef enum VncSharePolicy {
struct VncDisplay
{
QTAILQ_HEAD(, VncState) clients;
+ int num_connecting;
+ int num_shared;
int num_exclusive;
+ int connections_limit;
VncSharePolicy share_policy;
- int lsock;
-#ifdef CONFIG_VNC_WS
- int lwebsock;
- bool websocket;
- char *ws_display;
-#endif
+ 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;
@@ -161,19 +165,25 @@ struct VncDisplay
struct VncSurface guest; /* guest visible surface (aka ds->surface) */
pixman_image_t *server; /* vnc server surface */
- char *display;
+ 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;
-#ifdef CONFIG_VNC_TLS
- int subauth; /* Used by VeNCrypt */
- VncDisplayTLS tls;
-#endif
+ QCryptoTLSCreds *tlscreds;
+ QAuthZ *tlsauthz;
+ char *tlsauthzid;
#ifdef CONFIG_VNC_SASL
VncDisplaySASL sasl;
#endif
+
+ AudioState *audio_state;
};
typedef struct VncTight {
@@ -242,23 +252,37 @@ struct VncJob
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
{
- int csock;
+ 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;
- int need_update;
- int force_update;
+ 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;
- int client_width;
- int client_height;
+ 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;
@@ -267,30 +291,33 @@ struct VncState
int minor;
int auth;
- char challenge[VNC_AUTH_CHALLENGE_SIZE];
-#ifdef CONFIG_VNC_TLS
int subauth; /* Used by VeNCrypt */
- VncStateTLS tls;
-#endif
+ char challenge[VNC_AUTH_CHALLENGE_SIZE];
+ QCryptoTLSSession *tls; /* Borrowed pointer from channel, don't free */
#ifdef CONFIG_VNC_SASL
VncStateSASL sasl;
#endif
-#ifdef CONFIG_VNC_WS
-#ifdef CONFIG_VNC_TLS
- VncStateTLS ws_tls;
-#endif /* CONFIG_VNC_TLS */
bool encode_ws;
bool websocket;
-#endif /* CONFIG_VNC_WS */
- QObject *info;
+#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;
-#ifdef CONFIG_VNC_WS
- Buffer ws_input;
- Buffer ws_output;
-#endif
/* current output mode information */
VncWritePixels *write_pixels;
PixelFormat client_pf;
@@ -302,12 +329,8 @@ struct VncState
VncReadEvent *read_handler;
size_t read_handler_expect;
- /* input */
- uint8_t modifiers_state[256];
- QEMUPutLEDEntry *led;
bool abort;
- bool initialized;
QemuMutex output_mutex;
QEMUBH *bh;
Buffer jobs_buffer;
@@ -315,10 +338,10 @@ struct VncState
/* Encoding specific, if you add something here, don't forget to
* update vnc_async_encoding_start()
*/
- VncTight tight;
+ VncTight *tight;
VncZlib zlib;
VncHextile hextile;
- VncZrle zrle;
+ VncZrle *zrle;
VncZywrle zywrle;
Notifier mouse_mode_notifier;
@@ -500,11 +523,12 @@ enum {
*****************************************************************************/
/* Event loop functions */
-void vnc_client_read(void *opaque);
-void vnc_client_write(void *opaque);
+gboolean vnc_client_io(QIOChannel *ioc,
+ GIOCondition condition,
+ void *opaque);
-long vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen);
-long vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen);
+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);
@@ -515,7 +539,7 @@ 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_init_state(VncState *vs);
+void vnc_start_protocol(VncState *vs);
/* Buffer I/O functions */
@@ -523,25 +547,14 @@ uint32_t read_u32(uint8_t *data, size_t offset);
/* Protocol stage functions */
void vnc_client_error(VncState *vs);
-int vnc_client_io_error(VncState *vs, int ret, int last_errno);
+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);
-/* Buffer management */
-void buffer_reserve(Buffer *buffer, size_t len);
-void buffer_reset(Buffer *buffer);
-void buffer_free(Buffer *buffer);
-void buffer_append(Buffer *buffer, const void *data, size_t len);
-void buffer_advance(Buffer *buf, size_t len);
-uint8_t *buffer_end(Buffer *buffer);
-
/* Misc helpers */
-char *vnc_socket_local_addr(const char *format, int fd);
-char *vnc_socket_remote_addr(const char *format, int fd);
-
static inline uint32_t vnc_has_feature(VncState *vs, int feature) {
return (vs->features & (1 << feature));
}
@@ -585,4 +598,4 @@ 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);
-#endif /* __QEMU_VNC_H */
+#endif /* QEMU_VNC_H */
diff --git a/ui/vnc_keysym.h b/ui/vnc_keysym.h
index 6250bec69..e8a2ec73c 100644
--- a/ui/vnc_keysym.h
+++ b/ui/vnc_keysym.h
@@ -224,6 +224,14 @@ static const name2keysym_t name2keysym[]={
{ "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 */
@@ -246,7 +254,9 @@ static const name2keysym_t name2keysym[]={
{"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 */
@@ -342,5 +352,371 @@ static const name2keysym_t name2keysym[]={
{"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
index b9b094418..555086fb6 100644
--- a/ui/x_keymap.c
+++ b/ui/x_keymap.c
@@ -1,168 +1,114 @@
/*
- * QEMU SDL display driver
+ * QEMU X11 keymaps
*
- * Copyright (c) 2003 Fabrice Bellard
+ * Copyright (C) 2009-2010 Daniel P. Berrange <dan@berrange.com>
+ * 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.
+ * 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-common.h"
+
+#include "qemu/osdep.h"
+
#include "x_keymap.h"
+#include "trace.h"
+#include "qemu/notify.h"
+#include "ui/input.h"
-static const uint8_t x_keycode_to_pc_keycode[115] = {
- 0xc7, /* 97 Home */
- 0xc8, /* 98 Up */
- 0xc9, /* 99 PgUp */
- 0xcb, /* 100 Left */
- 0x4c, /* 101 KP-5 */
- 0xcd, /* 102 Right */
- 0xcf, /* 103 End */
- 0xd0, /* 104 Down */
- 0xd1, /* 105 PgDn */
- 0xd2, /* 106 Ins */
- 0xd3, /* 107 Del */
- 0x9c, /* 108 Enter */
- 0x9d, /* 109 Ctrl-R */
- 0x0, /* 110 Pause */
- 0xb7, /* 111 Print */
- 0xb5, /* 112 Divide */
- 0xb8, /* 113 Alt-R */
- 0xc6, /* 114 Break */
- 0x0, /* 115 */
- 0x0, /* 116 */
- 0x0, /* 117 */
- 0x0, /* 118 */
- 0x0, /* 119 */
- 0x0, /* 120 */
- 0x0, /* 121 */
- 0x0, /* 122 */
- 0x0, /* 123 */
- 0x0, /* 124 */
- 0x0, /* 125 */
- 0x0, /* 126 */
- 0x0, /* 127 */
- 0x0, /* 128 */
- 0x79, /* 129 Henkan */
- 0x0, /* 130 */
- 0x7b, /* 131 Muhenkan */
- 0x0, /* 132 */
- 0x7d, /* 133 Yen */
- 0x0, /* 134 */
- 0x0, /* 135 */
- 0x47, /* 136 KP_7 */
- 0x48, /* 137 KP_8 */
- 0x49, /* 138 KP_9 */
- 0x4b, /* 139 KP_4 */
- 0x4c, /* 140 KP_5 */
- 0x4d, /* 141 KP_6 */
- 0x4f, /* 142 KP_1 */
- 0x50, /* 143 KP_2 */
- 0x51, /* 144 KP_3 */
- 0x52, /* 145 KP_0 */
- 0x53, /* 146 KP_. */
- 0x47, /* 147 KP_HOME */
- 0x48, /* 148 KP_UP */
- 0x49, /* 149 KP_PgUp */
- 0x4b, /* 150 KP_Left */
- 0x4c, /* 151 KP_ */
- 0x4d, /* 152 KP_Right */
- 0x4f, /* 153 KP_End */
- 0x50, /* 154 KP_Down */
- 0x51, /* 155 KP_PgDn */
- 0x52, /* 156 KP_Ins */
- 0x53, /* 157 KP_Del */
-};
+#include <X11/XKBlib.h>
+#include <X11/Xutil.h>
-/* This table is generated based off the xfree86 -> scancode mapping above
- * and the keycode mappings in /usr/share/X11/xkb/keycodes/evdev
- * and /usr/share/X11/xkb/keycodes/xfree86
- */
+static gboolean check_for_xwin(Display *dpy)
+{
+ const char *vendor = ServerVendor(dpy);
+
+ trace_xkeymap_vendor(vendor);
-static const uint8_t evdev_keycode_to_pc_keycode[61] = {
- 0, /* 97 EVDEV - RO ("Internet" Keyboards) */
- 0, /* 98 EVDEV - KATA (Katakana) */
- 0, /* 99 EVDEV - HIRA (Hiragana) */
- 0x79, /* 100 EVDEV - HENK (Henkan) */
- 0x70, /* 101 EVDEV - HKTG (Hiragana/Katakana toggle) */
- 0x7b, /* 102 EVDEV - MUHE (Muhenkan) */
- 0, /* 103 EVDEV - JPCM (KPJPComma) */
- 0x9c, /* 104 KPEN */
- 0x9d, /* 105 RCTL */
- 0xb5, /* 106 KPDV */
- 0xb7, /* 107 PRSC */
- 0xb8, /* 108 RALT */
- 0, /* 109 EVDEV - LNFD ("Internet" Keyboards) */
- 0xc7, /* 110 HOME */
- 0xc8, /* 111 UP */
- 0xc9, /* 112 PGUP */
- 0xcb, /* 113 LEFT */
- 0xcd, /* 114 RGHT */
- 0xcf, /* 115 END */
- 0xd0, /* 116 DOWN */
- 0xd1, /* 117 PGDN */
- 0xd2, /* 118 INS */
- 0xd3, /* 119 DELE */
- 0, /* 120 EVDEV - I120 ("Internet" Keyboards) */
- 0, /* 121 EVDEV - MUTE */
- 0, /* 122 EVDEV - VOL- */
- 0, /* 123 EVDEV - VOL+ */
- 0, /* 124 EVDEV - POWR */
- 0, /* 125 EVDEV - KPEQ */
- 0, /* 126 EVDEV - I126 ("Internet" Keyboards) */
- 0, /* 127 EVDEV - PAUS */
- 0, /* 128 EVDEV - ???? */
- 0, /* 129 EVDEV - I129 ("Internet" Keyboards) */
- 0xf1, /* 130 EVDEV - HNGL (Korean Hangul Latin toggle) */
- 0xf2, /* 131 EVDEV - HJCV (Korean Hangul Hanja toggle) */
- 0x7d, /* 132 AE13 (Yen)*/
- 0xdb, /* 133 EVDEV - LWIN */
- 0xdc, /* 134 EVDEV - RWIN */
- 0xdd, /* 135 EVDEV - MENU */
- 0, /* 136 EVDEV - STOP */
- 0, /* 137 EVDEV - AGAI */
- 0, /* 138 EVDEV - PROP */
- 0, /* 139 EVDEV - UNDO */
- 0, /* 140 EVDEV - FRNT */
- 0, /* 141 EVDEV - COPY */
- 0, /* 142 EVDEV - OPEN */
- 0, /* 143 EVDEV - PAST */
- 0, /* 144 EVDEV - FIND */
- 0, /* 145 EVDEV - CUT */
- 0, /* 146 EVDEV - HELP */
- 0, /* 147 EVDEV - I147 */
- 0, /* 148 EVDEV - I148 */
- 0, /* 149 EVDEV - I149 */
- 0, /* 150 EVDEV - I150 */
- 0, /* 151 EVDEV - I151 */
- 0, /* 152 EVDEV - I152 */
- 0, /* 153 EVDEV - I153 */
- 0, /* 154 EVDEV - I154 */
- 0, /* 155 EVDEV - I156 */
- 0, /* 156 EVDEV - I157 */
- 0, /* 157 EVDEV - I158 */
-};
+ if (strstr(vendor, "Cygwin/X")) {
+ return TRUE;
+ }
-uint8_t translate_xfree86_keycode(const int key)
+ return FALSE;
+}
+
+static gboolean check_for_xquartz(Display *dpy)
{
- return x_keycode_to_pc_keycode[key];
+ 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;
}
-uint8_t translate_evdev_keycode(const int key)
+const guint16 *qemu_xkeymap_mapping_table(Display *dpy, size_t *maplen)
{
- return evdev_keycode_to_pc_keycode[key];
+ XkbDescPtr desc;
+ const gchar *keycodes = NULL;
+
+ /* 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;
+ return qemu_input_map_xorgxwin_to_qcode;
+ } else if (check_for_xquartz(dpy)) {
+ trace_xkeymap_keymap("xquartz");
+ *maplen = qemu_input_map_xorgxquartz_to_qcode_len;
+ return 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;
+ return 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;
+ return 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>");
+ return NULL;
+ }
}
diff --git a/ui/x_keymap.h b/ui/x_keymap.h
index afde2e94b..0395e335f 100644
--- a/ui/x_keymap.h
+++ b/ui/x_keymap.h
@@ -1,7 +1,7 @@
/*
- * QEMU SDL display driver
+ * QEMU X11 keymaps
*
- * Copyright (c) 2003 Fabrice Bellard
+ * 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
@@ -25,8 +25,8 @@
#ifndef QEMU_X_KEYMAP_H
#define QEMU_X_KEYMAP_H
-uint8_t translate_xfree86_keycode(const int key);
+#include <X11/Xlib.h>
-uint8_t translate_evdev_keycode(const int key);
+const guint16 *qemu_xkeymap_mapping_table(Display *dpy, size_t *maplen);
#endif