diff options
author | biao716.wang <biao716.wang@samsung.com> | 2023-08-28 14:40:25 +0900 |
---|---|---|
committer | biao716.wang <biao716.wang@samsung.com> | 2023-08-28 14:40:25 +0900 |
commit | d0ba767e7dc0fded6daae31c11d13b538ed8d53f (patch) | |
tree | eaaffe8ef043ee1803a7bbff50a9946e1613f58f /ui | |
parent | 0889ee8339e51dfdf78c39a8f15b6e5fe5ab49d5 (diff) | |
download | qemu-arm-static-d0ba767e7dc0fded6daae31c11d13b538ed8d53f.tar.gz qemu-arm-static-d0ba767e7dc0fded6daae31c11d13b538ed8d53f.tar.bz2 qemu-arm-static-d0ba767e7dc0fded6daae31c11d13b538ed8d53f.zip |
Upgrade version to 5.2upstream/5.2.0sandbox/wangbiao/qemu_5.2
Change-Id: I79199e4f7e9f27b498cd41113833a6158d4f07a2
Signed-off-by: biao716.wang <biao716.wang@samsung.com>
Diffstat (limited to 'ui')
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; +} @@ -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 Binary files differnew file mode 100644 index 000000000..96831807b --- /dev/null +++ b/ui/icons/qemu_128x128.png diff --git a/ui/icons/qemu_16x16.png b/ui/icons/qemu_16x16.png Binary files differnew file mode 100644 index 000000000..ff4f04602 --- /dev/null +++ b/ui/icons/qemu_16x16.png diff --git a/ui/icons/qemu_24x24.png b/ui/icons/qemu_24x24.png Binary files differnew file mode 100644 index 000000000..f039c6e25 --- /dev/null +++ b/ui/icons/qemu_24x24.png diff --git a/ui/icons/qemu_256x256.png b/ui/icons/qemu_256x256.png Binary files differnew file mode 100644 index 000000000..a39c0e307 --- /dev/null +++ b/ui/icons/qemu_256x256.png diff --git a/ui/icons/qemu_32x32.bmp b/ui/icons/qemu_32x32.bmp Binary files differnew file mode 100644 index 000000000..c0daa54ab --- /dev/null +++ b/ui/icons/qemu_32x32.bmp diff --git a/ui/icons/qemu_32x32.png b/ui/icons/qemu_32x32.png Binary files differnew file mode 100644 index 000000000..b746096cf --- /dev/null +++ b/ui/icons/qemu_32x32.png diff --git a/ui/icons/qemu_48x48.png b/ui/icons/qemu_48x48.png Binary files differnew file mode 100644 index 000000000..067281225 --- /dev/null +++ b/ui/icons/qemu_48x48.png diff --git a/ui/icons/qemu_512x512.png b/ui/icons/qemu_512x512.png Binary files differnew file mode 100644 index 000000000..86aaa6395 --- /dev/null +++ b/ui/icons/qemu_512x512.png diff --git a/ui/icons/qemu_64x64.png b/ui/icons/qemu_64x64.png Binary files differnew file mode 100644 index 000000000..e00c8b4c9 --- /dev/null +++ b/ui/icons/qemu_64x64.png 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 */ @@ -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); @@ -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 |