diff options
| author | fishsoupisgood <github@madingley.org> | 2019-04-29 01:17:54 +0100 | 
|---|---|---|
| committer | fishsoupisgood <github@madingley.org> | 2019-05-27 03:43:43 +0100 | 
| commit | 3f2546b2ef55b661fd8dd69682b38992225e86f6 (patch) | |
| tree | 65ca85f13617aee1dce474596800950f266a456c /ui | |
| download | qemu-master.tar.gz qemu-master.tar.bz2 qemu-master.zip  | |
Diffstat (limited to 'ui')
63 files changed, 30307 insertions, 0 deletions
diff --git a/ui/Makefile.objs b/ui/Makefile.objs new file mode 100644 index 00000000..c62d4d97 --- /dev/null +++ b/ui/Makefile.objs @@ -0,0 +1,46 @@ +vnc-obj-y += vnc.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-y += vnc-ws.o +vnc-obj-y += vnc-jobs.o + +common-obj-y += keymaps.o console.o cursor.o qemu-pixman.o +common-obj-y += input.o input-keymap.o input-legacy.o +common-obj-$(CONFIG_SPICE) += spice-core.o spice-input.o spice-display.o +common-obj-$(CONFIG_SDL) += sdl.mo 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 + +ifeq ($(CONFIG_SDLABI),1.2) +sdl.mo-objs := sdl.o sdl_zoom.o +endif +ifeq ($(CONFIG_SDLABI),2.0) +sdl.mo-objs := sdl2.o sdl2-input.o sdl2-2d.o +ifeq ($(CONFIG_OPENGL),y) +sdl.mo-objs += sdl2-gl.o +endif +endif +sdl.mo-cflags := $(SDL_CFLAGS) + +ifeq ($(CONFIG_OPENGL),y) +common-obj-y += shader.o +common-obj-y += console-gl.o +common-obj-y += egl-helpers.o +common-obj-$(CONFIG_GTK) += gtk-egl.o +endif + +gtk.o-cflags := $(GTK_CFLAGS) $(VTE_CFLAGS) +gtk-egl.o-cflags := $(GTK_CFLAGS) $(VTE_CFLAGS) $(OPENGL_CFLAGS) +shader.o-cflags += $(OPENGL_CFLAGS) +console-gl.o-cflags += $(OPENGL_CFLAGS) +egl-helpers.o-cflags += $(OPENGL_CFLAGS) + +gtk-egl.o-libs += $(OPENGL_LIBS) +shader.o-libs += $(OPENGL_LIBS) +console-gl.o-libs += $(OPENGL_LIBS) +egl-helpers.o-libs += $(OPENGL_LIBS) diff --git a/ui/cocoa.m b/ui/cocoa.m new file mode 100644 index 00000000..334e6f66 --- /dev/null +++ b/ui/cocoa.m @@ -0,0 +1,1436 @@ +/* + * QEMU Cocoa CG display driver + * + * Copyright (c) 2008 Mike Kronenberg + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#import <Cocoa/Cocoa.h> +#include <crt_externs.h> + +#include "qemu-common.h" +#include "ui/console.h" +#include "ui/input.h" +#include "sysemu/sysemu.h" +#include "qmp-commands.h" +#include "sysemu/blockdev.h" + +#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 +#endif +#ifndef MAC_OS_X_VERSION_10_10 +#define MAC_OS_X_VERSION_10_10 101000 +#endif + + +//#define DEBUG + +#ifdef DEBUG +#define COCOA_DEBUG(...)  { (void) fprintf (stdout, __VA_ARGS__); } +#else +#define COCOA_DEBUG(...)  ((void) 0) +#endif + +#define cgrect(nsrect) (*(CGRect *)&(nsrect)) + +typedef struct { +    int width; +    int height; +    int bitsPerComponent; +    int bitsPerPixel; +} QEMUScreen; + +NSWindow *normalWindow; +static DisplayChangeListener *dcl; +static int last_buttons; + +int gArgc; +char **gArgv; +bool stretch_video; +NSTextField *pauseLabel; +NSArray * supportedImageFileTypes; + +// keymap conversion +int keymap[] = +{ +//  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 +    220, // 54      0x36    0xdc    E0,5C   R GUI   QZ_RMETA +    219, // 55      0x37    0xdb    E0,5B   L GUI   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 */ + +/* Additional 104 Key XP-Keyboard Scancodes from http://www.computer-engineering.org/ps2keyboard/scancodes1.html */ +/* +    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 +*/ +}; + +static int cocoa_keycode_to_qemu(int keycode) +{ +    if (ARRAY_SIZE(keymap) <= keycode) { +        fprintf(stderr, "(cocoa) warning unknown keycode 0x%x\n", keycode); +        return 0; +    } +    return keymap[keycode]; +} + +/* Displays an alert dialog box with the specified message */ +static void QEMU_Alert(NSString *message) +{ +    NSAlert *alert; +    alert = [NSAlert new]; +    [alert setMessageText: message]; +    [alert runModal]; +} + +/* Handles any errors that happen with a device transaction */ +static void handleAnyDeviceErrors(Error * err) +{ +    if (err) { +        QEMU_Alert([NSString stringWithCString: error_get_pretty(err) +                                      encoding: NSASCIIStringEncoding]); +        error_free(err); +    } +} + +/* + ------------------------------------------------------ +    QemuCocoaView + ------------------------------------------------------ +*/ +@interface QemuCocoaView : NSView +{ +    QEMUScreen screen; +    NSWindow *fullScreenWindow; +    float cx,cy,cw,ch,cdx,cdy; +    CGDataProviderRef dataProviderRef; +    int modifiers_state[256]; +    BOOL isMouseGrabbed; +    BOOL isFullscreen; +    BOOL isAbsoluteEnabled; +    BOOL isMouseDeassociated; +} +- (void) switchSurface:(DisplaySurface *)surface; +- (void) grabMouse; +- (void) ungrabMouse; +- (void) toggleFullScreen:(id)sender; +- (void) handleEvent:(NSEvent *)event; +- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled; +/* The state surrounding mouse grabbing is potentially confusing. + * isAbsoluteEnabled tracks qemu_input_is_absolute() [ie "is the emulated + *   pointing device an absolute-position one?"], but is only updated on + *   next refresh. + * isMouseGrabbed tracks whether GUI events are directed to the guest; + *   it controls whether special keys like Cmd get sent to the guest, + *   and whether we capture the mouse when in non-absolute mode. + * 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; +@end + +QemuCocoaView *cocoaView; + +@implementation QemuCocoaView +- (id)initWithFrame:(NSRect)frameRect +{ +    COCOA_DEBUG("QemuCocoaView: initWithFrame\n"); + +    self = [super initWithFrame:frameRect]; +    if (self) { + +        screen.bitsPerComponent = 8; +        screen.bitsPerPixel = 32; +        screen.width = frameRect.size.width; +        screen.height = frameRect.size.height; + +    } +    return self; +} + +- (void) dealloc +{ +    COCOA_DEBUG("QemuCocoaView: dealloc\n"); + +    if (dataProviderRef) +        CGDataProviderRelease(dataProviderRef); + +    [super dealloc]; +} + +- (BOOL) isOpaque +{ +    return YES; +} + +- (BOOL) screenContainsPoint:(NSPoint) p +{ +    return (p.x > -1 && p.x < screen.width && p.y > -1 && p.y < screen.height); +} + +- (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]; +    CGContextSetInterpolationQuality (viewContextRef, kCGInterpolationNone); +    CGContextSetShouldAntialias (viewContextRef, NO); + +    // draw screen bitmap directly to Core Graphics context +    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 +            screen.bitsPerComponent, //bitsPerComponent +            screen.bitsPerPixel, //bitsPerPixel +            (screen.width * (screen.bitsPerComponent/2)), //bytesPerRow +#ifdef __LITTLE_ENDIAN__ +            CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB), //colorspace for OS X >= 10.4 +            kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, +#else +            CGColorSpaceCreateDeviceRGB(), //colorspace for OS X < 10.4 (actually ppc) +            kCGImageAlphaNoneSkipFirst, //bitmapInfo +#endif +            dataProviderRef, //provider +            NULL, //decode +            0, //interpolate +            kCGRenderingIntentDefault //intent +        ); +        // selective drawing code (draws only dirty rectangles) (OS X >= 10.4) +        const NSRect *rectList; +        NSInteger rectCount; +        int i; +        CGImageRef clipImageRef; +        CGRect clipRect; + +        [self getRectsBeingDrawn:&rectList count:&rectCount]; +        for (i = 0; i < rectCount; i++) { +            clipRect.origin.x = rectList[i].origin.x / cdx; +            clipRect.origin.y = (float)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); +        } +        CGImageRelease (imageRef); +    } +} + +- (void) setContentDimensions +{ +    COCOA_DEBUG("QemuCocoaView: setContentDimensions\n"); + +    if (isFullscreen) { +        cdx = [[NSScreen mainScreen] frame].size.width / (float)screen.width; +        cdy = [[NSScreen mainScreen] frame].size.height / (float)screen.height; + +        /* stretches video, but keeps same aspect ratio */ +        if (stretch_video == true) { +            /* use smallest stretch value - prevents clipping on sides */ +            if (MIN(cdx, cdy) == cdx) { +                cdy = cdx; +            } else { +                cdx = cdy; +            } +        } else {  /* No stretching */ +            cdx = cdy = 1; +        } +        cw = screen.width * cdx; +        ch = screen.height * cdy; +        cx = ([[NSScreen mainScreen] frame].size.width - cw) / 2.0; +        cy = ([[NSScreen mainScreen] frame].size.height - ch) / 2.0; +    } else { +        cx = 0; +        cy = 0; +        cw = screen.width; +        ch = screen.height; +        cdx = 1.0; +        cdy = 1.0; +    } +} + +- (void) switchSurface:(DisplaySurface *)surface +{ +    COCOA_DEBUG("QemuCocoaView: switchSurface\n"); + +    int w = surface_width(surface); +    int h = surface_height(surface); +    /* 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) +        CGDataProviderRelease(dataProviderRef); + +    //sync host window color space with guests +    screen.bitsPerPixel = surface_bits_per_pixel(surface); +    screen.bitsPerComponent = surface_bytes_per_pixel(surface) * 2; + +    dataProviderRef = CGDataProviderCreateWithData(NULL, surface_data(surface), 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 + oldh, w, h + [normalWindow frame].size.height - oldh) display:NO animate:NO]; +    } else { +        if (qemu_name) +            [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]]; +        [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + oldh, w, h + [normalWindow frame].size.height - oldh) display:YES animate:NO]; +    } + +    if (isResize) { +        [normalWindow center]; +    } +} + +- (void) toggleFullScreen:(id)sender +{ +    COCOA_DEBUG("QemuCocoaView: toggleFullScreen\n"); + +    if (isFullscreen) { // switch from fullscreen to desktop +        isFullscreen = FALSE; +        [self ungrabMouse]; +        [self setContentDimensions]; +        if ([NSView respondsToSelector:@selector(exitFullScreenModeWithOptions:)]) { // test if "exitFullScreenModeWithOptions" is supported on host at runtime +            [self exitFullScreenModeWithOptions:nil]; +        } else { +            [fullScreenWindow close]; +            [normalWindow setContentView: self]; +            [normalWindow makeKeyAndOrderFront: self]; +            [NSMenu setMenuBarVisible:YES]; +        } +    } else { // switch from desktop to fullscreen +        isFullscreen = TRUE; +        [normalWindow orderOut: nil]; /* Hide the window */ +        [self grabMouse]; +        [self setContentDimensions]; +        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 { +            [NSMenu setMenuBarVisible:NO]; +            fullScreenWindow = [[NSWindow alloc] initWithContentRect:[[NSScreen mainScreen] frame] +                styleMask:NSBorderlessWindowMask +                backing:NSBackingStoreBuffered +                defer:NO]; +            [fullScreenWindow setAcceptsMouseMovedEvents: YES]; +            [fullScreenWindow setHasShadow:NO]; +            [fullScreenWindow setBackgroundColor: [NSColor blackColor]]; +            [self setFrame:NSMakeRect(cx, cy, cw, ch)]; +            [[fullScreenWindow contentView] addSubview: self]; +            [fullScreenWindow makeKeyAndOrderFront:self]; +        } +    } +} + +- (void) handleEvent:(NSEvent *)event +{ +    COCOA_DEBUG("QemuCocoaView: handleEvent\n"); + +    int buttons = 0; +    int keycode; +    bool mouse_event = false; +    NSPoint p = [event locationInWindow]; + +    switch ([event type]) { +        case NSFlagsChanged: +            keycode = cocoa_keycode_to_qemu([event keyCode]); + +            if ((keycode == 219 || keycode == 220) && !isMouseGrabbed) { +              /* Don't pass command key changes to guest unless mouse is grabbed */ +              keycode = 0; +            } + +            if (keycode) { +                if (keycode == 58 || keycode == 69) { // emulate caps lock and num lock keydown and keyup +                    qemu_input_event_send_key_number(dcl->con, keycode, true); +                    qemu_input_event_send_key_number(dcl->con, keycode, false); +                } else if (qemu_console_is_graphic(NULL)) { +                    if (modifiers_state[keycode] == 0) { // keydown +                        qemu_input_event_send_key_number(dcl->con, keycode, true); +                        modifiers_state[keycode] = 1; +                    } else { // keyup +                        qemu_input_event_send_key_number(dcl->con, keycode, false); +                        modifiers_state[keycode] = 0; +                    } +                } +            } + +            // release Mouse grab when pressing ctrl+alt +            if (([event modifierFlags] & NSControlKeyMask) && ([event modifierFlags] & NSAlternateKeyMask)) { +                [self ungrabMouse]; +            } +            break; +        case NSKeyDown: +            keycode = cocoa_keycode_to_qemu([event keyCode]); + +            // forward command key combos to the host UI unless the mouse is grabbed +            if (!isMouseGrabbed && ([event modifierFlags] & NSCommandKeyMask)) { +                [NSApp sendEvent:event]; +                return; +            } + +            // default + +            // 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; +                } + +            // handle keys for graphic console +            } else if (qemu_console_is_graphic(NULL)) { +                qemu_input_event_send_key_number(dcl->con, keycode, true); + +            // 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]; +                    } +                } +                if (keysym) +                    kbd_put_keysym(keysym); +            } +            break; +        case NSKeyUp: +            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] & NSCommandKeyMask)) { +                return; +            } + +            if (qemu_console_is_graphic(NULL)) { +                qemu_input_event_send_key_number(dcl->con, keycode, false); +            } +            break; +        case NSMouseMoved: +            if (isAbsoluteEnabled) { +                if (![self screenContainsPoint:p] || ![[self window] isKeyWindow]) { +                    if (isMouseGrabbed) { +                        [self ungrabMouse]; +                    } +                } else { +                    if (!isMouseGrabbed) { +                        [self grabMouse]; +                    } +                } +            } +            mouse_event = true; +            break; +        case NSLeftMouseDown: +            if ([event modifierFlags] & NSCommandKeyMask) { +                buttons |= MOUSE_EVENT_RBUTTON; +            } else { +                buttons |= MOUSE_EVENT_LBUTTON; +            } +            mouse_event = true; +            break; +        case NSRightMouseDown: +            buttons |= MOUSE_EVENT_RBUTTON; +            mouse_event = true; +            break; +        case NSOtherMouseDown: +            buttons |= MOUSE_EVENT_MBUTTON; +            mouse_event = true; +            break; +        case NSLeftMouseDragged: +            if ([event modifierFlags] & NSCommandKeyMask) { +                buttons |= MOUSE_EVENT_RBUTTON; +            } else { +                buttons |= MOUSE_EVENT_LBUTTON; +            } +            mouse_event = true; +            break; +        case NSRightMouseDragged: +            buttons |= MOUSE_EVENT_RBUTTON; +            mouse_event = true; +            break; +        case NSOtherMouseDragged: +            buttons |= MOUSE_EVENT_MBUTTON; +            mouse_event = true; +            break; +        case NSLeftMouseUp: +            mouse_event = true; +            if (!isMouseGrabbed && [self screenContainsPoint:p]) { +                [self grabMouse]; +            } +            break; +        case NSRightMouseUp: +            mouse_event = true; +            break; +        case NSOtherMouseUp: +            mouse_event = true; +            break; +        case NSScrollWheel: +            if (isMouseGrabbed) { +                buttons |= ([event deltaY] < 0) ? +                    MOUSE_EVENT_WHEELUP : MOUSE_EVENT_WHEELDN; +            } +            mouse_event = true; +            break; +        default: +            [NSApp sendEvent:event]; +    } + +    if (mouse_event) { +        if (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, +                [INPUT_BUTTON_WHEEL_UP]   = MOUSE_EVENT_WHEELUP, +                [INPUT_BUTTON_WHEEL_DOWN] = MOUSE_EVENT_WHEELDN, +            }; +            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, screen.width); +                    qemu_input_queue_abs(dcl->con, INPUT_AXIS_Y, screen.height - p.y, 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 { +            [NSApp sendEvent:event]; +        } +        qemu_input_event_sync(); +    } +} + +- (void) grabMouse +{ +    COCOA_DEBUG("QemuCocoaView: grabMouse\n"); + +    if (!isFullscreen) { +        if (qemu_name) +            [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s - (Press ctrl + alt to release Mouse)", qemu_name]]; +        else +            [normalWindow setTitle:@"QEMU - (Press ctrl + alt to release Mouse)"]; +    } +    [self hideCursor]; +    if (!isAbsoluteEnabled) { +        isMouseDeassociated = TRUE; +        CGAssociateMouseAndMouseCursorPosition(FALSE); +    } +    isMouseGrabbed = TRUE; // while isMouseGrabbed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:] +} + +- (void) ungrabMouse +{ +    COCOA_DEBUG("QemuCocoaView: ungrabMouse\n"); + +    if (!isFullscreen) { +        if (qemu_name) +            [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]]; +        else +            [normalWindow setTitle:@"QEMU"]; +    } +    [self unhideCursor]; +    if (isMouseDeassociated) { +        CGAssociateMouseAndMouseCursorPosition(TRUE); +        isMouseDeassociated = FALSE; +    } +    isMouseGrabbed = FALSE; +} + +- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled {isAbsoluteEnabled = tIsAbsoluteEnabled;} +- (BOOL) isMouseGrabbed {return isMouseGrabbed;} +- (BOOL) isAbsoluteEnabled {return isAbsoluteEnabled;} +- (BOOL) isMouseDeassociated {return isMouseDeassociated;} +- (float) cdx {return cdx;} +- (float) cdy {return cdy;} +- (QEMUScreen) gscreen {return screen;} +@end + + + +/* + ------------------------------------------------------ +    QemuCocoaAppController + ------------------------------------------------------ +*/ +@interface QemuCocoaAppController : NSObject +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6) +                                             <NSApplicationDelegate> +#endif +{ +} +- (void)startEmulationWithArgc:(int)argc argv:(char**)argv; +- (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(NSInteger)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; +@end + +@implementation QemuCocoaAppController +- (id) init +{ +    COCOA_DEBUG("QemuCocoaAppController: init\n"); + +    self = [super init]; +    if (self) { + +        // create a view and add it to the window +        cocoaView = [[QemuCocoaView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 640.0, 480.0)]; +        if(!cocoaView) { +            fprintf(stderr, "(cocoa) can't create a view\n"); +            exit(1); +        } + +        // create a window +        normalWindow = [[NSWindow alloc] initWithContentRect:[cocoaView frame] +            styleMask:NSTitledWindowMask|NSMiniaturizableWindowMask|NSClosableWindowMask +            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 setContentView:cocoaView]; +#if (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10) +        [normalWindow useOptimizedDrawing:YES]; +#endif +        [normalWindow makeKeyAndOrderFront:self]; +        [normalWindow center]; +        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", nil]; +    } +    return self; +} + +- (void) dealloc +{ +    COCOA_DEBUG("QemuCocoaAppController: dealloc\n"); + +    if (cocoaView) +        [cocoaView release]; +    [super dealloc]; +} + +- (void)applicationDidFinishLaunching: (NSNotification *) note +{ +    COCOA_DEBUG("QemuCocoaAppController: applicationDidFinishLaunching\n"); + +    // Display an open dialog box if no arguments 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"]; +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6) +        [op setAllowedFileTypes:supportedImageFileTypes]; +        [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]; +    } +} + +- (void)applicationWillTerminate:(NSNotification *)aNotification +{ +    COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n"); + +    qemu_system_shutdown_request(); +    exit(0); +} + +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication +{ +    return YES; +} + +- (void)startEmulationWithArgc:(int)argc argv:(char**)argv +{ +    COCOA_DEBUG("QemuCocoaAppController: startEmulationWithArgc\n"); + +    int status; +    status = qemu_main(argc, argv, *_NSGetEnviron()); +    exit(status); +} + +- (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo +{ +    COCOA_DEBUG("QemuCocoaAppController: openPanelDidEnd\n"); + +    /* The NSFileHandlingPanelOKButton/NSFileHandlingPanelCancelButton values for +     * returnCode strictly only apply for the 10.6-and-up beginSheetModalForWindow +     * API. For the legacy pre-10.6 beginSheetForDirectory API they are NSOKButton +     * and NSCancelButton. However conveniently the values are the same. +     * We use the non-legacy names because the others are deprecated in OSX 10.10. +     */ +    if (returnCode == NSFileHandlingPanelCancelButton) { +        exit(0); +    } else if (returnCode == NSFileHandlingPanelOKButton) { +        char *img = (char*)[ [ [ sheet URL ] path ] cStringUsingEncoding:NSASCIIStringEncoding]; + +        char **argv = g_new(char *, 4); + +        [sheet close]; + +        argv[0] = g_strdup(gArgv[0]); +        argv[1] = g_strdup("-hda"); +        argv[2] = g_strdup(img); +        argv[3] = NULL; + +        // printf("Using argc %d argv %s -hda %s\n", 3, gArgv[0], img); + +        [self startEmulationWithArgc:3 argv:(char**)argv]; +    } +} + +/* We abstract the method called by the Enter Fullscreen menu item + * because Mac OS 10.7 and higher disables it. This is because of the + * menu item's old selector's name toggleFullScreen: + */ +- (void) doToggleFullScreen:(id)sender +{ +    [self toggleFullScreen:(id)sender]; +} + +- (void)toggleFullScreen:(id)sender +{ +    COCOA_DEBUG("QemuCocoaAppController: toggleFullScreen\n"); + +    [cocoaView toggleFullScreen:sender]; +} + +- (void)showQEMUDoc:(id)sender +{ +    COCOA_DEBUG("QemuCocoaAppController: showQEMUDoc\n"); + +    [[NSWorkspace sharedWorkspace] openFile:[NSString stringWithFormat:@"%@/../doc/qemu/qemu-doc.html", +        [[NSBundle mainBundle] resourcePath]] withApplication:@"Help Viewer"]; +} + +- (void)showQEMUTec:(id)sender +{ +    COCOA_DEBUG("QemuCocoaAppController: showQEMUTec\n"); + +    [[NSWorkspace sharedWorkspace] openFile:[NSString stringWithFormat:@"%@/../doc/qemu/qemu-tech.html", +        [[NSBundle mainBundle] resourcePath]] withApplication:@"Help Viewer"]; +} + +/* Stretches video to fit host monitor size */ +- (void)zoomToFit:(id) sender +{ +    stretch_video = !stretch_video; +    if (stretch_video == true) { +        [sender setState: NSOnState]; +    } else { +        [sender setState: NSOffState]; +    } +} + +/* Displays the console on the screen */ +- (void)displayConsole:(id)sender +{ +    console_select([sender tag]); +} + +/* Pause the guest */ +- (void)pauseQEMU:(id)sender +{ +    qmp_stop(NULL); +    [sender setEnabled: NO]; +    [[[sender menu] itemWithTitle: @"Resume"] setEnabled: YES]; +    [self displayPause]; +} + +/* Resume running the guest operating system */ +- (void)resumeQEMU:(id) sender +{ +    qmp_cont(NULL); +    [sender setEnabled: NO]; +    [[[sender menu] itemWithTitle: @"Pause"] setEnabled: YES]; +    [self removePause]; +} + +/* Displays the word pause on the screen */ +- (void)displayPause +{ +    /* Coordinates have to be calculated each time because the window can change its size */ +    int xCoord, yCoord, width, height; +    xCoord = ([normalWindow frame].size.width - [pauseLabel frame].size.width)/2; +    yCoord = [normalWindow frame].size.height - [pauseLabel frame].size.height - ([pauseLabel frame].size.height * .5); +    width = [pauseLabel frame].size.width; +    height = [pauseLabel frame].size.height; +    [pauseLabel setFrame: NSMakeRect(xCoord, yCoord, width, height)]; +    [cocoaView addSubview: pauseLabel]; +} + +/* Removes the word pause from the screen */ +- (void)removePause +{ +    [pauseLabel removeFromSuperview]; +} + +/* Restarts QEMU */ +- (void)restartQEMU:(id)sender +{ +    qmp_system_reset(NULL); +} + +/* Powers down QEMU */ +- (void)powerDownQEMU:(id)sender +{ +    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; +    } + +    Error *err = NULL; +    qmp_eject([drive cStringUsingEncoding: NSASCIIStringEncoding], false, false, &err); +    handleAnyDeviceErrors(err); +} + +/* Displays a dialog box asking the user to select an image file to load. + * Uses sender's represented object value to figure out which drive to use. + */ +- (void)changeDeviceMedia:(id)sender +{ +    /* Find the drive name */ +    NSString * drive; +    drive = [sender representedObject]; +    if(drive == nil) { +        NSBeep(); +        QEMU_Alert(@"Could not find drive!"); +        return; +    } + +    /* Display the file open dialog */ +    NSOpenPanel * openPanel; +    openPanel = [NSOpenPanel openPanel]; +    [openPanel setCanChooseFiles: YES]; +    [openPanel setAllowsMultipleSelection: NO]; +    [openPanel setAllowedFileTypes: supportedImageFileTypes]; +    if([openPanel runModal] == NSFileHandlingPanelOKButton) { +        NSString * file = [[[openPanel URLs] objectAtIndex: 0] path]; +        if(file == nil) { +            NSBeep(); +            QEMU_Alert(@"Failed to convert URL to file path!"); +            return; +        } + +        Error *err = NULL; +        qmp_change_blockdev([drive cStringUsingEncoding: NSASCIIStringEncoding], +                            [file cStringUsingEncoding: NSASCIIStringEncoding], +                            "raw", +                            &err); +        handleAnyDeviceErrors(err); +    } +} + +@end + + +int main (int argc, const char * argv[]) { + +    gArgc = argc; +    gArgv = (char **)argv; +    int i; + +    /* 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]; + +        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()); +            } +        } +    } + +    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); + +    [NSApplication sharedApplication]; + +    // Add menus +    NSMenu      *menu; +    NSMenuItem  *menuItem; + +    [NSApp setMainMenu:[[NSMenu alloc] init]]; + +    // Application menu +    menu = [[NSMenu alloc] initWithTitle:@""]; +    [menu addItemWithTitle:@"About QEMU" action:@selector(orderFrontStandardAboutPanel:) 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)]; +    [menu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; // Show All +    [menu addItem:[NSMenuItem separatorItem]]; //Separator +    [menu addItemWithTitle:@"Quit QEMU" action:@selector(terminate:) keyEquivalent:@"q"]; +    menuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" action:nil keyEquivalent:@""]; +    [menuItem setSubmenu:menu]; +    [[NSApp mainMenu] addItem:menuItem]; +    [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; // Workaround (this method is private since 10.4+) + +    // Machine menu +    menu = [[NSMenu alloc] initWithTitle: @"Machine"]; +    [menu setAutoenablesItems: NO]; +    [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Pause" action: @selector(pauseQEMU:) keyEquivalent: @""] autorelease]]; +    menuItem = [[[NSMenuItem alloc] initWithTitle: @"Resume" action: @selector(resumeQEMU:) keyEquivalent: @""] autorelease]; +    [menu addItem: menuItem]; +    [menuItem setEnabled: NO]; +    [menu addItem: [NSMenuItem separatorItem]]; +    [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Reset" action: @selector(restartQEMU:) keyEquivalent: @""] autorelease]]; +    [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Power Down" action: @selector(powerDownQEMU:) keyEquivalent: @""] autorelease]]; +    menuItem = [[[NSMenuItem alloc] initWithTitle: @"Machine" action:nil keyEquivalent:@""] autorelease]; +    [menuItem setSubmenu:menu]; +    [[NSApp mainMenu] addItem:menuItem]; + +    // View menu +    menu = [[NSMenu alloc] initWithTitle:@"View"]; +    [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(doToggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen +    [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Zoom To Fit" action:@selector(zoomToFit:) keyEquivalent:@""] autorelease]]; +    menuItem = [[[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""] autorelease]; +    [menuItem setSubmenu:menu]; +    [[NSApp mainMenu] addItem:menuItem]; + +    // Window menu +    menu = [[NSMenu alloc] initWithTitle:@"Window"]; +    [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease]]; // Miniaturize +    menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease]; +    [menuItem setSubmenu:menu]; +    [[NSApp mainMenu] addItem:menuItem]; +    [NSApp setWindowsMenu:menu]; + +    // Help menu +    menu = [[NSMenu alloc] initWithTitle:@"Help"]; +    [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Documentation" action:@selector(showQEMUDoc:) keyEquivalent:@"?"] autorelease]]; // QEMU Help +    [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]; + +    // Create an Application controller +    QemuCocoaAppController *appController = [[QemuCocoaAppController alloc] init]; +    [NSApp setDelegate:appController]; + +    // Start the main event loop +    [NSApp run]; + +    [appController release]; +    [pool release]; + +    return 0; +} + + + +#pragma mark qemu +static void cocoa_update(DisplayChangeListener *dcl, +                         int x, int y, int w, int h) +{ +    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + +    COCOA_DEBUG("qemu_cocoa: cocoa_update\n"); + +    NSRect rect; +    if ([cocoaView cdx] == 1.0) { +        rect = NSMakeRect(x, [cocoaView gscreen].height - y - h, w, h); +    } else { +        rect = NSMakeRect( +            x * [cocoaView cdx], +            ([cocoaView gscreen].height - y - h) * [cocoaView cdy], +            w * [cocoaView cdx], +            h * [cocoaView cdy]); +    } +    [cocoaView setNeedsDisplayInRect:rect]; + +    [pool release]; +} + +static void cocoa_switch(DisplayChangeListener *dcl, +                         DisplaySurface *surface) +{ +    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + +    COCOA_DEBUG("qemu_cocoa: cocoa_switch\n"); +    [cocoaView switchSurface:surface]; +    [pool release]; +} + +static void cocoa_refresh(DisplayChangeListener *dcl) +{ +    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + +    COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n"); + +    if (qemu_input_is_absolute()) { +        if (![cocoaView isAbsoluteEnabled]) { +            if ([cocoaView isMouseGrabbed]) { +                [cocoaView ungrabMouse]; +            } +        } +        [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]; +} + +static void cocoa_cleanup(void) +{ +    COCOA_DEBUG("qemu_cocoa: cocoa_cleanup\n"); +    g_free(dcl); +} + +static const DisplayChangeListenerOps dcl_ops = { +    .dpy_name          = "cocoa", +    .dpy_gfx_update = cocoa_update, +    .dpy_gfx_switch = cocoa_switch, +    .dpy_refresh = cocoa_refresh, +}; + +/* 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() +{ +    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 thru 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); +} + +void cocoa_display_init(DisplayState *ds, int full_screen) +{ +    COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n"); + +    /* if fullscreen mode is to be used */ +    if (full_screen == true) { +        [NSApp activateIgnoringOtherApps: YES]; +        [(QemuCocoaAppController *)[[NSApplication sharedApplication] delegate] toggleFullScreen: nil]; +    } + +    dcl = g_malloc0(sizeof(DisplayChangeListener)); + +    // register vga output callbacks +    dcl->ops = &dcl_ops; +    register_displaychangelistener(dcl); + +    // register cleanup function +    atexit(cocoa_cleanup); + +    /* At this point QEMU has created all the consoles, so we can add View +     * menu entries for them. +     */ +    add_console_menu_entries(); + +    /* Give all removable devices a menu item. +     * Has to be called after QEMU has started to +     * find out what removable devices it has. +     */ +    addRemovableDevicesMenuItems(); +} diff --git a/ui/console-gl.c b/ui/console-gl.c new file mode 100644 index 00000000..cb45cf8a --- /dev/null +++ b/ui/console-gl.c @@ -0,0 +1,168 @@ +/* + * 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-common.h" +#include "ui/console.h" +#include "ui/shader.h" + +#include "shader/texture-blit-vert.h" +#include "shader/texture-blit-frag.h" + +struct ConsoleGLState { +    GLint texture_blit_prog; +}; + +/* ---------------------------------------------------------------------- */ + +ConsoleGLState *console_gl_init_context(void) +{ +    ConsoleGLState *gls = g_new0(ConsoleGLState, 1); + +    gls->texture_blit_prog = qemu_gl_create_compile_link_program +        (texture_blit_vert_src, texture_blit_frag_src); +    if (!gls->texture_blit_prog) { +        exit(1); +    } + +    return gls; +} + +void console_gl_fini_context(ConsoleGLState *gls) +{ +    if (!gls) { +        return; +    } +    g_free(gls); +} + +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(ConsoleGLState *gls, +                               DisplaySurface *surface) +{ +    assert(gls); +    assert(surface_stride(surface) % surface_bytes_per_pixel(surface) == 0); + +    switch (surface->format) { +    case PIXMAN_BE_b8g8r8x8: +    case PIXMAN_BE_b8g8r8a8: +        surface->glformat = GL_BGRA_EXT; +        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(ConsoleGLState *gls, +                               DisplaySurface *surface, +                               int x, int y, int w, int h) +{ +    uint8_t *data = (void *)surface_data(surface); + +    assert(gls); + +    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(ConsoleGLState *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->texture_blit_prog); +} + +void surface_gl_destroy_texture(ConsoleGLState *gls, +                                DisplaySurface *surface) +{ +    if (!surface || !surface->texture) { +        return; +    } +    glDeleteTextures(1, &surface->texture); +    surface->texture = 0; +} + +void surface_gl_setup_viewport(ConsoleGLState *gls, +                               DisplaySurface *surface, +                               int ww, int wh) +{ +    int gw, gh, stripe; +    float sw, sh; + +    assert(gls); + +    gw = surface_width(surface); +    gh = surface_height(surface); + +    sw = (float)ww/gw; +    sh = (float)wh/gh; +    if (sw < sh) { +        stripe = wh - wh*sw/sh; +        glViewport(0, stripe / 2, ww, wh - stripe); +    } else { +        stripe = ww - ww*sh/sw; +        glViewport(stripe / 2, 0, ww - stripe, wh); +    } +} diff --git a/ui/console.c b/ui/console.c new file mode 100644 index 00000000..75fc492f --- /dev/null +++ b/ui/console.c @@ -0,0 +1,2036 @@ +/* + * QEMU graphical console + * + * Copyright (c) 2004 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "qemu-common.h" +#include "ui/console.h" +#include "hw/qdev-core.h" +#include "qemu/timer.h" +#include "qmp-commands.h" +#include "sysemu/char.h" +#include "trace.h" +#include "exec/memory.h" + +#define DEFAULT_BACKSCROLL 512 +#define CONSOLE_CURSOR_PERIOD 500 + +typedef struct TextAttributes { +    uint8_t fgcol:4; +    uint8_t bgcol:4; +    uint8_t bold:1; +    uint8_t uline:1; +    uint8_t blink:1; +    uint8_t invers:1; +    uint8_t unvisible:1; +} TextAttributes; + +typedef struct TextCell { +    uint8_t ch; +    TextAttributes t_attrib; +} TextCell; + +#define MAX_ESC_PARAMS 3 + +enum TTYState { +    TTY_STATE_NORM, +    TTY_STATE_ESC, +    TTY_STATE_CSI, +}; + +typedef struct QEMUFIFO { +    uint8_t *buf; +    int buf_size; +    int count, wptr, rptr; +} QEMUFIFO; + +static int qemu_fifo_write(QEMUFIFO *f, const uint8_t *buf, int len1) +{ +    int l, len; + +    l = f->buf_size - f->count; +    if (len1 > l) +        len1 = l; +    len = len1; +    while (len > 0) { +        l = f->buf_size - f->wptr; +        if (l > len) +            l = len; +        memcpy(f->buf + f->wptr, buf, l); +        f->wptr += l; +        if (f->wptr >= f->buf_size) +            f->wptr = 0; +        buf += l; +        len -= l; +    } +    f->count += len1; +    return len1; +} + +static int qemu_fifo_read(QEMUFIFO *f, uint8_t *buf, int len1) +{ +    int l, len; + +    if (len1 > f->count) +        len1 = f->count; +    len = len1; +    while (len > 0) { +        l = f->buf_size - f->rptr; +        if (l > len) +            l = len; +        memcpy(buf, f->buf + f->rptr, l); +        f->rptr += l; +        if (f->rptr >= f->buf_size) +            f->rptr = 0; +        buf += l; +        len -= l; +    } +    f->count -= len1; +    return len1; +} + +typedef enum { +    GRAPHIC_CONSOLE, +    TEXT_CONSOLE, +    TEXT_CONSOLE_FIXED_SIZE +} console_type_t; + +struct QemuConsole { +    Object parent; + +    int index; +    console_type_t console_type; +    DisplayState *ds; +    DisplaySurface *surface; +    int dcls; + +    /* Graphic console state.  */ +    Object *device; +    uint32_t head; +    QemuUIInfo ui_info; +    QEMUTimer *ui_timer; +    const GraphicHwOps *hw_ops; +    void *hw; + +    /* Text console state */ +    int width; +    int height; +    int total_height; +    int backscroll_height; +    int x, y; +    int x_saved, y_saved; +    int y_displayed; +    int y_base; +    TextAttributes t_attrib_default; /* default text attributes */ +    TextAttributes t_attrib; /* currently active text attributes */ +    TextCell *cells; +    int text_x[2], text_y[2], cursor_invalidate; +    int echo; + +    int update_x0; +    int update_y0; +    int update_x1; +    int update_y1; + +    enum TTYState state; +    int esc_params[MAX_ESC_PARAMS]; +    int nb_esc_params; + +    CharDriverState *chr; +    /* fifo for key pressed */ +    QEMUFIFO out_fifo; +    uint8_t out_fifo_buf[16]; +    QEMUTimer *kbd_timer; +}; + +struct DisplayState { +    QEMUTimer *gui_timer; +    uint64_t last_update; +    uint64_t update_interval; +    bool refreshing; +    bool have_gfx; +    bool have_text; + +    QLIST_HEAD(, DisplayChangeListener) listeners; +}; + +static DisplayState *display_state; +static QemuConsole *active_console; +static QemuConsole **consoles; +static int nb_consoles = 0; +static bool cursor_visible_phase; +static QEMUTimer *cursor_timer; + +static void text_console_do_init(CharDriverState *chr, DisplayState *ds); +static void dpy_refresh(DisplayState *s); +static DisplayState *get_alloc_displaystate(void); +static void text_console_update_cursor_timer(void); +static void text_console_update_cursor(void *opaque); + +static void gui_update(void *opaque) +{ +    uint64_t interval = GUI_REFRESH_INTERVAL_IDLE; +    uint64_t dcl_interval; +    DisplayState *ds = opaque; +    DisplayChangeListener *dcl; +    int i; + +    ds->refreshing = true; +    dpy_refresh(ds); +    ds->refreshing = false; + +    QLIST_FOREACH(dcl, &ds->listeners, next) { +        dcl_interval = dcl->update_interval ? +            dcl->update_interval : GUI_REFRESH_INTERVAL_DEFAULT; +        if (interval > dcl_interval) { +            interval = dcl_interval; +        } +    } +    if (ds->update_interval != interval) { +        ds->update_interval = interval; +        for (i = 0; i < nb_consoles; i++) { +            if (consoles[i]->hw_ops->update_interval) { +                consoles[i]->hw_ops->update_interval(consoles[i]->hw, interval); +            } +        } +        trace_console_refresh(interval); +    } +    ds->last_update = qemu_clock_get_ms(QEMU_CLOCK_REALTIME); +    timer_mod(ds->gui_timer, ds->last_update + interval); +} + +static void gui_setup_refresh(DisplayState *ds) +{ +    DisplayChangeListener *dcl; +    bool need_timer = false; +    bool have_gfx = false; +    bool have_text = false; + +    QLIST_FOREACH(dcl, &ds->listeners, next) { +        if (dcl->ops->dpy_refresh != NULL) { +            need_timer = true; +        } +        if (dcl->ops->dpy_gfx_update != NULL) { +            have_gfx = true; +        } +        if (dcl->ops->dpy_text_update != NULL) { +            have_text = true; +        } +    } + +    if (need_timer && ds->gui_timer == NULL) { +        ds->gui_timer = timer_new_ms(QEMU_CLOCK_REALTIME, gui_update, ds); +        timer_mod(ds->gui_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME)); +    } +    if (!need_timer && ds->gui_timer != NULL) { +        timer_del(ds->gui_timer); +        timer_free(ds->gui_timer); +        ds->gui_timer = NULL; +    } + +    ds->have_gfx = have_gfx; +    ds->have_text = have_text; +} + +void graphic_hw_update(QemuConsole *con) +{ +    if (!con) { +        con = active_console; +    } +    if (con && con->hw_ops->gfx_update) { +        con->hw_ops->gfx_update(con->hw); +    } +} + +void graphic_hw_invalidate(QemuConsole *con) +{ +    if (!con) { +        con = active_console; +    } +    if (con && con->hw_ops->invalidate) { +        con->hw_ops->invalidate(con->hw); +    } +} + +static void ppm_save(const char *filename, DisplaySurface *ds, +                     Error **errp) +{ +    int width = pixman_image_get_width(ds->image); +    int height = pixman_image_get_height(ds->image); +    int fd; +    FILE *f; +    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; +    } +    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; +        } +    } + +out: +    qemu_pixman_image_unref(linebuf); +    fclose(f); +    return; + +write_err: +    error_setg(errp, "failed to write to file '%s': %s", filename, +               strerror(errno)); +    unlink(filename); +    goto out; +} + +void qmp_screendump(const char *filename, Error **errp) +{ +    QemuConsole *con = qemu_console_lookup_by_index(0); +    DisplaySurface *surface; + +    if (con == NULL) { +        error_setg(errp, "There is no QemuConsole I can screendump from."); +        return; +    } + +    graphic_hw_update(con); +    surface = qemu_console_surface(con); +    ppm_save(filename, surface, errp); +} + +void graphic_hw_text_update(QemuConsole *con, console_ch_t *chardata) +{ +    if (!con) { +        con = active_console; +    } +    if (con && con->hw_ops->text_update) { +        con->hw_ops->text_update(con->hw, chardata); +    } +} + +static void vga_fill_rect(QemuConsole *con, +                          int posx, int posy, int width, int height, +                          pixman_color_t color) +{ +    DisplaySurface *surface = qemu_console_surface(con); +    pixman_rectangle16_t rect = { +        .x = posx, .y = posy, .width = width, .height = height +    }; + +    pixman_image_fill_rectangles(PIXMAN_OP_SRC, surface->image, +                                 &color, 1, &rect); +} + +/* copy from (xs, ys) to (xd, yd) a rectangle of size (w, h) */ +static void vga_bitblt(QemuConsole *con, +                       int xs, int ys, int xd, int yd, int w, int h) +{ +    DisplaySurface *surface = qemu_console_surface(con); + +    pixman_image_composite(PIXMAN_OP_SRC, +                           surface->image, NULL, surface->image, +                           xs, ys, 0, 0, xd, yd, w, h); +} + +/***********************************************************/ +/* basic char display */ + +#define FONT_HEIGHT 16 +#define FONT_WIDTH 8 + +#include "vgafont.h" + +#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 */ +    }, +    {   /* 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 */ +    } +}; + +static void vga_putcharxy(QemuConsole *s, int x, int y, int ch, +                          TextAttributes *t_attrib) +{ +    static pixman_image_t *glyphs[256]; +    DisplaySurface *surface = qemu_console_surface(s); +    pixman_color_t fgcol, bgcol; + +    if (t_attrib->invers) { +        bgcol = color_table_rgb[t_attrib->bold][t_attrib->fgcol]; +        fgcol = color_table_rgb[t_attrib->bold][t_attrib->bgcol]; +    } else { +        fgcol = color_table_rgb[t_attrib->bold][t_attrib->fgcol]; +        bgcol = color_table_rgb[t_attrib->bold][t_attrib->bgcol]; +    } + +    if (!glyphs[ch]) { +        glyphs[ch] = qemu_pixman_glyph_from_vgafont(FONT_HEIGHT, vgafont16, ch); +    } +    qemu_pixman_glyph_render(glyphs[ch], surface->image, +                             &fgcol, &bgcol, x, y, FONT_WIDTH, FONT_HEIGHT); +} + +static void text_console_resize(QemuConsole *s) +{ +    TextCell *cells, *c, *c1; +    int w1, x, y, last_width; + +    last_width = s->width; +    s->width = surface_width(s->surface) / FONT_WIDTH; +    s->height = surface_height(s->surface) / FONT_HEIGHT; + +    w1 = last_width; +    if (s->width < w1) +        w1 = s->width; + +    cells = g_malloc(s->width * s->total_height * sizeof(TextCell)); +    for(y = 0; y < s->total_height; y++) { +        c = &cells[y * s->width]; +        if (w1 > 0) { +            c1 = &s->cells[y * last_width]; +            for(x = 0; x < w1; x++) { +                *c++ = *c1++; +            } +        } +        for(x = w1; x < s->width; x++) { +            c->ch = ' '; +            c->t_attrib = s->t_attrib_default; +            c++; +        } +    } +    g_free(s->cells); +    s->cells = cells; +} + +static inline void text_update_xy(QemuConsole *s, int x, int y) +{ +    s->text_x[0] = MIN(s->text_x[0], x); +    s->text_x[1] = MAX(s->text_x[1], x); +    s->text_y[0] = MIN(s->text_y[0], y); +    s->text_y[1] = MAX(s->text_y[1], y); +} + +static void invalidate_xy(QemuConsole *s, int x, int y) +{ +    if (!qemu_console_is_visible(s)) { +        return; +    } +    if (s->update_x0 > x * FONT_WIDTH) +        s->update_x0 = x * FONT_WIDTH; +    if (s->update_y0 > y * FONT_HEIGHT) +        s->update_y0 = y * FONT_HEIGHT; +    if (s->update_x1 < (x + 1) * FONT_WIDTH) +        s->update_x1 = (x + 1) * FONT_WIDTH; +    if (s->update_y1 < (y + 1) * FONT_HEIGHT) +        s->update_y1 = (y + 1) * FONT_HEIGHT; +} + +static void update_xy(QemuConsole *s, int x, int y) +{ +    TextCell *c; +    int y1, y2; + +    if (s->ds->have_text) { +        text_update_xy(s, x, y); +    } + +    y1 = (s->y_base + y) % s->total_height; +    y2 = y1 - s->y_displayed; +    if (y2 < 0) { +        y2 += s->total_height; +    } +    if (y2 < s->height) { +        c = &s->cells[y1 * s->width + x]; +        vga_putcharxy(s, x, y2, c->ch, +                      &(c->t_attrib)); +        invalidate_xy(s, x, y2); +    } +} + +static void console_show_cursor(QemuConsole *s, int show) +{ +    TextCell *c; +    int y, y1; +    int x = s->x; + +    if (s->ds->have_text) { +        s->cursor_invalidate = 1; +    } + +    if (x >= s->width) { +        x = s->width - 1; +    } +    y1 = (s->y_base + s->y) % s->total_height; +    y = y1 - s->y_displayed; +    if (y < 0) { +        y += s->total_height; +    } +    if (y < s->height) { +        c = &s->cells[y1 * s->width + x]; +        if (show && cursor_visible_phase) { +            TextAttributes t_attrib = s->t_attrib_default; +            t_attrib.invers = !(t_attrib.invers); /* invert fg and bg */ +            vga_putcharxy(s, x, y, c->ch, &t_attrib); +        } else { +            vga_putcharxy(s, x, y, c->ch, &(c->t_attrib)); +        } +        invalidate_xy(s, x, y); +    } +} + +static void console_refresh(QemuConsole *s) +{ +    DisplaySurface *surface = qemu_console_surface(s); +    TextCell *c; +    int x, y, y1; + +    if (s->ds->have_text) { +        s->text_x[0] = 0; +        s->text_y[0] = 0; +        s->text_x[1] = s->width - 1; +        s->text_y[1] = s->height - 1; +        s->cursor_invalidate = 1; +    } + +    vga_fill_rect(s, 0, 0, surface_width(surface), surface_height(surface), +                  color_table_rgb[0][COLOR_BLACK]); +    y1 = s->y_displayed; +    for (y = 0; y < s->height; y++) { +        c = s->cells + y1 * s->width; +        for (x = 0; x < s->width; x++) { +            vga_putcharxy(s, x, y, c->ch, +                          &(c->t_attrib)); +            c++; +        } +        if (++y1 == s->total_height) { +            y1 = 0; +        } +    } +    console_show_cursor(s, 1); +    dpy_gfx_update(s, 0, 0, +                   surface_width(surface), surface_height(surface)); +} + +static void console_scroll(QemuConsole *s, int ydelta) +{ +    int i, y1; + +    if (ydelta > 0) { +        for(i = 0; i < ydelta; i++) { +            if (s->y_displayed == s->y_base) +                break; +            if (++s->y_displayed == s->total_height) +                s->y_displayed = 0; +        } +    } else { +        ydelta = -ydelta; +        i = s->backscroll_height; +        if (i > s->total_height - s->height) +            i = s->total_height - s->height; +        y1 = s->y_base - i; +        if (y1 < 0) +            y1 += s->total_height; +        for(i = 0; i < ydelta; i++) { +            if (s->y_displayed == y1) +                break; +            if (--s->y_displayed < 0) +                s->y_displayed = s->total_height - 1; +        } +    } +    console_refresh(s); +} + +static void console_put_lf(QemuConsole *s) +{ +    TextCell *c; +    int x, y1; + +    s->y++; +    if (s->y >= s->height) { +        s->y = s->height - 1; + +        if (s->y_displayed == s->y_base) { +            if (++s->y_displayed == s->total_height) +                s->y_displayed = 0; +        } +        if (++s->y_base == s->total_height) +            s->y_base = 0; +        if (s->backscroll_height < s->total_height) +            s->backscroll_height++; +        y1 = (s->y_base + s->height - 1) % s->total_height; +        c = &s->cells[y1 * s->width]; +        for(x = 0; x < s->width; x++) { +            c->ch = ' '; +            c->t_attrib = s->t_attrib_default; +            c++; +        } +        if (s->y_displayed == s->y_base) { +            if (s->ds->have_text) { +                s->text_x[0] = 0; +                s->text_y[0] = 0; +                s->text_x[1] = s->width - 1; +                s->text_y[1] = s->height - 1; +            } + +            vga_bitblt(s, 0, FONT_HEIGHT, 0, 0, +                       s->width * FONT_WIDTH, +                       (s->height - 1) * FONT_HEIGHT); +            vga_fill_rect(s, 0, (s->height - 1) * FONT_HEIGHT, +                          s->width * FONT_WIDTH, FONT_HEIGHT, +                          color_table_rgb[0][s->t_attrib_default.bgcol]); +            s->update_x0 = 0; +            s->update_y0 = 0; +            s->update_x1 = s->width * FONT_WIDTH; +            s->update_y1 = s->height * FONT_HEIGHT; +        } +    } +} + +/* Set console attributes depending on the current escape codes. + * NOTE: I know this code is not very efficient (checking every color for it + * self) but it is more readable and better maintainable. + */ +static void console_handle_escape(QemuConsole *s) +{ +    int i; + +    for (i=0; i<s->nb_esc_params; i++) { +        switch (s->esc_params[i]) { +            case 0: /* reset all console attributes to default */ +                s->t_attrib = s->t_attrib_default; +                break; +            case 1: +                s->t_attrib.bold = 1; +                break; +            case 4: +                s->t_attrib.uline = 1; +                break; +            case 5: +                s->t_attrib.blink = 1; +                break; +            case 7: +                s->t_attrib.invers = 1; +                break; +            case 8: +                s->t_attrib.unvisible = 1; +                break; +            case 22: +                s->t_attrib.bold = 0; +                break; +            case 24: +                s->t_attrib.uline = 0; +                break; +            case 25: +                s->t_attrib.blink = 0; +                break; +            case 27: +                s->t_attrib.invers = 0; +                break; +            case 28: +                s->t_attrib.unvisible = 0; +                break; +            /* set foreground color */ +            case 30: +                s->t_attrib.fgcol=COLOR_BLACK; +                break; +            case 31: +                s->t_attrib.fgcol=COLOR_RED; +                break; +            case 32: +                s->t_attrib.fgcol=COLOR_GREEN; +                break; +            case 33: +                s->t_attrib.fgcol=COLOR_YELLOW; +                break; +            case 34: +                s->t_attrib.fgcol=COLOR_BLUE; +                break; +            case 35: +                s->t_attrib.fgcol=COLOR_MAGENTA; +                break; +            case 36: +                s->t_attrib.fgcol=COLOR_CYAN; +                break; +            case 37: +                s->t_attrib.fgcol=COLOR_WHITE; +                break; +            /* set background color */ +            case 40: +                s->t_attrib.bgcol=COLOR_BLACK; +                break; +            case 41: +                s->t_attrib.bgcol=COLOR_RED; +                break; +            case 42: +                s->t_attrib.bgcol=COLOR_GREEN; +                break; +            case 43: +                s->t_attrib.bgcol=COLOR_YELLOW; +                break; +            case 44: +                s->t_attrib.bgcol=COLOR_BLUE; +                break; +            case 45: +                s->t_attrib.bgcol=COLOR_MAGENTA; +                break; +            case 46: +                s->t_attrib.bgcol=COLOR_CYAN; +                break; +            case 47: +                s->t_attrib.bgcol=COLOR_WHITE; +                break; +        } +    } +} + +static void console_clear_xy(QemuConsole *s, int x, int y) +{ +    int y1 = (s->y_base + y) % s->total_height; +    TextCell *c = &s->cells[y1 * s->width + x]; +    c->ch = ' '; +    c->t_attrib = s->t_attrib_default; +    update_xy(s, x, y); +} + +/* set cursor, checking bounds */ +static void set_cursor(QemuConsole *s, int x, int y) +{ +    if (x < 0) { +        x = 0; +    } +    if (y < 0) { +        y = 0; +    } +    if (y >= s->height) { +        y = s->height - 1; +    } +    if (x >= s->width) { +        x = s->width - 1; +    } + +    s->x = x; +    s->y = y; +} + +static void console_putchar(QemuConsole *s, int ch) +{ +    TextCell *c; +    int y1, i; +    int x, y; + +    switch(s->state) { +    case TTY_STATE_NORM: +        switch(ch) { +        case '\r':  /* carriage return */ +            s->x = 0; +            break; +        case '\n':  /* newline */ +            console_put_lf(s); +            break; +        case '\b':  /* backspace */ +            if (s->x > 0) +                s->x--; +            break; +        case '\t':  /* tabspace */ +            if (s->x + (8 - (s->x % 8)) > s->width) { +                s->x = 0; +                console_put_lf(s); +            } else { +                s->x = s->x + (8 - (s->x % 8)); +            } +            break; +        case '\a':  /* alert aka. bell */ +            /* TODO: has to be implemented */ +            break; +        case 14: +            /* SI (shift in), character set 0 (ignored) */ +            break; +        case 15: +            /* SO (shift out), character set 1 (ignored) */ +            break; +        case 27:    /* esc (introducing an escape sequence) */ +            s->state = TTY_STATE_ESC; +            break; +        default: +            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++; +            break; +        } +        break; +    case TTY_STATE_ESC: /* check if it is a terminal escape sequence */ +        if (ch == '[') { +            for(i=0;i<MAX_ESC_PARAMS;i++) +                s->esc_params[i] = 0; +            s->nb_esc_params = 0; +            s->state = TTY_STATE_CSI; +        } else { +            s->state = TTY_STATE_NORM; +        } +        break; +    case TTY_STATE_CSI: /* handle escape sequence parameters */ +        if (ch >= '0' && ch <= '9') { +            if (s->nb_esc_params < MAX_ESC_PARAMS) { +                int *param = &s->esc_params[s->nb_esc_params]; +                int digit = (ch - '0'); + +                *param = (*param <= (INT_MAX - digit) / 10) ? +                         *param * 10 + digit : INT_MAX; +            } +        } else { +            if (s->nb_esc_params < MAX_ESC_PARAMS) +                s->nb_esc_params++; +            if (ch == ';') +                break; +            trace_console_putchar_csi(s->esc_params[0], s->esc_params[1], +                                      ch, s->nb_esc_params); +            s->state = TTY_STATE_NORM; +            switch(ch) { +            case 'A': +                /* move cursor up */ +                if (s->esc_params[0] == 0) { +                    s->esc_params[0] = 1; +                } +                set_cursor(s, s->x, s->y - s->esc_params[0]); +                break; +            case 'B': +                /* move cursor down */ +                if (s->esc_params[0] == 0) { +                    s->esc_params[0] = 1; +                } +                set_cursor(s, s->x, s->y + s->esc_params[0]); +                break; +            case 'C': +                /* move cursor right */ +                if (s->esc_params[0] == 0) { +                    s->esc_params[0] = 1; +                } +                set_cursor(s, s->x + s->esc_params[0], s->y); +                break; +            case 'D': +                /* move cursor left */ +                if (s->esc_params[0] == 0) { +                    s->esc_params[0] = 1; +                } +                set_cursor(s, s->x - s->esc_params[0], s->y); +                break; +            case 'G': +                /* move cursor to column */ +                set_cursor(s, s->esc_params[0] - 1, s->y); +                break; +            case 'f': +            case 'H': +                /* move cursor to row, column */ +                set_cursor(s, s->esc_params[1] - 1, s->esc_params[0] - 1); +                break; +            case 'J': +                switch (s->esc_params[0]) { +                case 0: +                    /* clear to end of screen */ +                    for (y = s->y; y < s->height; y++) { +                        for (x = 0; x < s->width; x++) { +                            if (y == s->y && x < s->x) { +                                continue; +                            } +                            console_clear_xy(s, x, y); +                        } +                    } +                    break; +                case 1: +                    /* clear from beginning of screen */ +                    for (y = 0; y <= s->y; y++) { +                        for (x = 0; x < s->width; x++) { +                            if (y == s->y && x > s->x) { +                                break; +                            } +                            console_clear_xy(s, x, y); +                        } +                    } +                    break; +                case 2: +                    /* clear entire screen */ +                    for (y = 0; y <= s->height; y++) { +                        for (x = 0; x < s->width; x++) { +                            console_clear_xy(s, x, y); +                        } +                    } +                    break; +                } +                break; +            case 'K': +                switch (s->esc_params[0]) { +                case 0: +                    /* clear to eol */ +                    for(x = s->x; x < s->width; x++) { +                        console_clear_xy(s, x, s->y); +                    } +                    break; +                case 1: +                    /* clear from beginning of line */ +                    for (x = 0; x <= s->x; x++) { +                        console_clear_xy(s, x, s->y); +                    } +                    break; +                case 2: +                    /* clear entire line */ +                    for(x = 0; x < s->width; x++) { +                        console_clear_xy(s, x, s->y); +                    } +                    break; +                } +                break; +            case 'm': +                console_handle_escape(s); +                break; +            case 'n': +                /* report cursor position */ +                /* TODO: send ESC[row;colR */ +                break; +            case 's': +                /* save cursor position */ +                s->x_saved = s->x; +                s->y_saved = s->y; +                break; +            case 'u': +                /* restore cursor position */ +                s->x = s->x_saved; +                s->y = s->y_saved; +                break; +            default: +                trace_console_putchar_unhandled(ch); +                break; +            } +            break; +        } +    } +} + +void console_select(unsigned int index) +{ +    DisplayChangeListener *dcl; +    QemuConsole *s; + +    trace_console_select(index); +    s = qemu_console_lookup_by_index(index); +    if (s) { +        DisplayState *ds = s->ds; + +        active_console = s; +        if (ds->have_gfx) { +            QLIST_FOREACH(dcl, &ds->listeners, next) { +                if (dcl->con != NULL) { +                    continue; +                } +                if (dcl->ops->dpy_gfx_switch) { +                    dcl->ops->dpy_gfx_switch(dcl, s->surface); +                } +            } +            dpy_gfx_update(s, 0, 0, surface_width(s->surface), +                           surface_height(s->surface)); +        } +        if (ds->have_text) { +            dpy_text_resize(s, s->width, s->height); +        } +        text_console_update_cursor(NULL); +    } +} + +static int console_puts(CharDriverState *chr, const uint8_t *buf, int len) +{ +    QemuConsole *s = chr->opaque; +    int i; + +    s->update_x0 = s->width * FONT_WIDTH; +    s->update_y0 = s->height * FONT_HEIGHT; +    s->update_x1 = 0; +    s->update_y1 = 0; +    console_show_cursor(s, 0); +    for(i = 0; i < len; i++) { +        console_putchar(s, buf[i]); +    } +    console_show_cursor(s, 1); +    if (s->ds->have_gfx && s->update_x0 < s->update_x1) { +        dpy_gfx_update(s, s->update_x0, s->update_y0, +                       s->update_x1 - s->update_x0, +                       s->update_y1 - s->update_y0); +    } +    return len; +} + +static void kbd_send_chars(void *opaque) +{ +    QemuConsole *s = opaque; +    int len; +    uint8_t buf[16]; + +    len = qemu_chr_be_can_write(s->chr); +    if (len > s->out_fifo.count) +        len = s->out_fifo.count; +    if (len > 0) { +        if (len > sizeof(buf)) +            len = sizeof(buf); +        qemu_fifo_read(&s->out_fifo, buf, len); +        qemu_chr_be_write(s->chr, buf, len); +    } +    /* characters are pending: we send them a bit later (XXX: +       horrible, should change char device API) */ +    if (s->out_fifo.count > 0) { +        timer_mod(s->kbd_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1); +    } +} + +/* called when an ascii key is pressed */ +void kbd_put_keysym_console(QemuConsole *s, int keysym) +{ +    uint8_t buf[16], *q; +    int c; + +    if (!s || (s->console_type == GRAPHIC_CONSOLE)) +        return; + +    switch(keysym) { +    case QEMU_KEY_CTRL_UP: +        console_scroll(s, -1); +        break; +    case QEMU_KEY_CTRL_DOWN: +        console_scroll(s, 1); +        break; +    case QEMU_KEY_CTRL_PAGEUP: +        console_scroll(s, -10); +        break; +    case QEMU_KEY_CTRL_PAGEDOWN: +        console_scroll(s, 10); +        break; +    default: +        /* convert the QEMU keysym to VT100 key string */ +        q = buf; +        if (keysym >= 0xe100 && keysym <= 0xe11f) { +            *q++ = '\033'; +            *q++ = '['; +            c = keysym - 0xe100; +            if (c >= 10) +                *q++ = '0' + (c / 10); +            *q++ = '0' + (c % 10); +            *q++ = '~'; +        } else if (keysym >= 0xe120 && keysym <= 0xe17f) { +            *q++ = '\033'; +            *q++ = '['; +            *q++ = keysym & 0xff; +        } else if (s->echo && (keysym == '\r' || keysym == '\n')) { +            console_puts(s->chr, (const uint8_t *) "\r", 1); +            *q++ = '\n'; +        } else { +            *q++ = keysym; +        } +        if (s->echo) { +            console_puts(s->chr, buf, q - buf); +        } +        if (s->chr->chr_read) { +            qemu_fifo_write(&s->out_fifo, buf, q - buf); +            kbd_send_chars(s); +        } +        break; +    } +} + +static const int qcode_to_keysym[Q_KEY_CODE_MAX] = { +    [Q_KEY_CODE_UP]     = QEMU_KEY_UP, +    [Q_KEY_CODE_DOWN]   = QEMU_KEY_DOWN, +    [Q_KEY_CODE_RIGHT]  = QEMU_KEY_RIGHT, +    [Q_KEY_CODE_LEFT]   = QEMU_KEY_LEFT, +    [Q_KEY_CODE_HOME]   = QEMU_KEY_HOME, +    [Q_KEY_CODE_END]    = QEMU_KEY_END, +    [Q_KEY_CODE_PGUP]   = QEMU_KEY_PAGEUP, +    [Q_KEY_CODE_PGDN]   = QEMU_KEY_PAGEDOWN, +    [Q_KEY_CODE_DELETE] = QEMU_KEY_DELETE, +}; + +bool kbd_put_qcode_console(QemuConsole *s, int qcode) +{ +    int keysym; + +    keysym = qcode_to_keysym[qcode]; +    if (keysym == 0) { +        return false; +    } +    kbd_put_keysym_console(s, keysym); +    return true; +} + +void kbd_put_string_console(QemuConsole *s, const char *str, int len) +{ +    int i; + +    for (i = 0; i < len && str[i]; i++) { +        kbd_put_keysym_console(s, str[i]); +    } +} + +void kbd_put_keysym(int keysym) +{ +    kbd_put_keysym_console(active_console, keysym); +} + +static void text_console_invalidate(void *opaque) +{ +    QemuConsole *s = (QemuConsole *) opaque; + +    if (s->ds->have_text && s->console_type == TEXT_CONSOLE) { +        text_console_resize(s); +    } +    console_refresh(s); +} + +static void text_console_update(void *opaque, console_ch_t *chardata) +{ +    QemuConsole *s = (QemuConsole *) opaque; +    int i, j, src; + +    if (s->text_x[0] <= s->text_x[1]) { +        src = (s->y_base + s->text_y[0]) * s->width; +        chardata += s->text_y[0] * s->width; +        for (i = s->text_y[0]; i <= s->text_y[1]; i ++) +            for (j = 0; j < s->width; j ++, src ++) +                console_write_ch(chardata ++, 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)); +        dpy_text_update(s, s->text_x[0], s->text_y[0], +                        s->text_x[1] - s->text_x[0], i - s->text_y[0]); +        s->text_x[0] = s->width; +        s->text_y[0] = s->height; +        s->text_x[1] = 0; +        s->text_y[1] = 0; +    } +    if (s->cursor_invalidate) { +        dpy_text_cursor(s, s->x, s->y); +        s->cursor_invalidate = 0; +    } +} + +static QemuConsole *new_console(DisplayState *ds, console_type_t console_type, +                                uint32_t head) +{ +    Object *obj; +    QemuConsole *s; +    int i; + +    obj = object_new(TYPE_QEMU_CONSOLE); +    s = QEMU_CONSOLE(obj); +    s->head = head; +    object_property_add_link(obj, "device", TYPE_DEVICE, +                             (Object **)&s->device, +                             object_property_allow_set_link, +                             OBJ_PROP_LINK_UNREF_ON_RELEASE, +                             &error_abort); +    object_property_add_uint32_ptr(obj, "head", +                                   &s->head, &error_abort); + +    if (!active_console || ((active_console->console_type != GRAPHIC_CONSOLE) && +        (console_type == GRAPHIC_CONSOLE))) { +        active_console = s; +    } +    s->ds = ds; +    s->console_type = console_type; + +    consoles = g_realloc(consoles, sizeof(*consoles) * (nb_consoles+1)); +    if (console_type != GRAPHIC_CONSOLE) { +        s->index = nb_consoles; +        consoles[nb_consoles++] = s; +    } 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; +        } +        s->index = i; +        consoles[i] = s; +        nb_consoles++; +    } +    return s; +} + +static void qemu_alloc_display(DisplaySurface *surface, int width, int height) +{ +    qemu_pixman_image_unref(surface->image); +    surface->image = NULL; + +    surface->format = PIXMAN_x8r8g8b8; +    surface->image = pixman_image_create_bits(surface->format, +                                              width, height, +                                              NULL, width * 4); +    assert(surface->image != NULL); + +    surface->flags = QEMU_ALLOCATED_FLAG; +} + +DisplaySurface *qemu_create_displaysurface(int width, int height) +{ +    DisplaySurface *surface = g_new0(DisplaySurface, 1); + +    trace_displaysurface_create(surface, width, height); +    qemu_alloc_display(surface, width, height); +    return surface; +} + +DisplaySurface *qemu_create_displaysurface_from(int width, int height, +                                                pixman_format_code_t format, +                                                int linesize, uint8_t *data) +{ +    DisplaySurface *surface = g_new0(DisplaySurface, 1); + +    trace_displaysurface_create_from(surface, width, height, format); +    surface->format = format; +    surface->image = pixman_image_create_bits(surface->format, +                                              width, height, +                                              (void *)data, linesize); +    assert(surface->image != NULL); + +    return surface; +} + +static void qemu_unmap_displaysurface_guestmem(pixman_image_t *image, +                                               void *unused) +{ +    void *data = pixman_image_get_data(image); +    uint32_t size = pixman_image_get_stride(image) * +        pixman_image_get_height(image); +    cpu_physical_memory_unmap(data, size, 0, 0); +} + +DisplaySurface *qemu_create_displaysurface_guestmem(int width, int height, +                                                    pixman_format_code_t format, +                                                    int linesize, uint64_t addr) +{ +    DisplaySurface *surface; +    hwaddr size; +    void *data; + +    if (linesize == 0) { +        linesize = width * PIXMAN_FORMAT_BPP(format) / 8; +    } + +    size = (hwaddr)linesize * height; +    data = cpu_physical_memory_map(addr, &size, 0); +    if (size != (hwaddr)linesize * height) { +        cpu_physical_memory_unmap(data, size, 0, 0); +        return NULL; +    } + +    surface = qemu_create_displaysurface_from +        (width, height, format, linesize, data); +    pixman_image_set_destroy_function +        (surface->image, qemu_unmap_displaysurface_guestmem, NULL); + +    return surface; +} + +static DisplaySurface *qemu_create_message_surface(int w, int h, +                                                   const char *msg) +{ +    DisplaySurface *surface = qemu_create_displaysurface(w, h); +    pixman_color_t bg = color_table_rgb[0][COLOR_BLACK]; +    pixman_color_t fg = color_table_rgb[0][COLOR_WHITE]; +    pixman_image_t *glyph; +    int len, x, y, i; + +    len = strlen(msg); +    x = (w / FONT_WIDTH  - len) / 2; +    y = (h / FONT_HEIGHT - 1)   / 2; +    for (i = 0; i < len; i++) { +        glyph = qemu_pixman_glyph_from_vgafont(FONT_HEIGHT, vgafont16, msg[i]); +        qemu_pixman_glyph_render(glyph, surface->image, &fg, &bg, +                                 x+i, y, FONT_WIDTH, FONT_HEIGHT); +        qemu_pixman_image_unref(glyph); +    } +    return surface; +} + +void qemu_free_displaysurface(DisplaySurface *surface) +{ +    if (surface == NULL) { +        return; +    } +    trace_displaysurface_free(surface); +    qemu_pixman_image_unref(surface->image); +    g_free(surface); +} + +void register_displaychangelistener(DisplayChangeListener *dcl) +{ +    static const char nodev[] = +        "This VM has no graphic display device."; +    static DisplaySurface *dummy; +    QemuConsole *con; + +    trace_displaychangelistener_register(dcl, dcl->ops->dpy_name); +    dcl->ds = get_alloc_displaystate(); +    QLIST_INSERT_HEAD(&dcl->ds->listeners, dcl, next); +    gui_setup_refresh(dcl->ds); +    if (dcl->con) { +        dcl->con->dcls++; +        con = dcl->con; +    } else { +        con = active_console; +    } +    if (dcl->ops->dpy_gfx_switch) { +        if (con) { +            dcl->ops->dpy_gfx_switch(dcl, con->surface); +        } else { +            if (!dummy) { +                dummy = qemu_create_message_surface(640, 480, nodev); +            } +            dcl->ops->dpy_gfx_switch(dcl, dummy); +        } +    } +    text_console_update_cursor(NULL); +} + +void update_displaychangelistener(DisplayChangeListener *dcl, +                                  uint64_t interval) +{ +    DisplayState *ds = dcl->ds; + +    dcl->update_interval = interval; +    if (!ds->refreshing && ds->update_interval > interval) { +        timer_mod(ds->gui_timer, ds->last_update + interval); +    } +} + +void unregister_displaychangelistener(DisplayChangeListener *dcl) +{ +    DisplayState *ds = dcl->ds; +    trace_displaychangelistener_unregister(dcl, dcl->ops->dpy_name); +    if (dcl->con) { +        dcl->con->dcls--; +    } +    QLIST_REMOVE(dcl, next); +    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; +} + +int dpy_set_ui_info(QemuConsole *con, QemuUIInfo *info) +{ +    assert(con != NULL); +    con->ui_info = *info; +    if (!dpy_ui_info_supported(con)) { +        return -1; +    } + +    /* +     * 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. +     */ +    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); + +    x = MAX(x, 0); +    y = MAX(y, 0); +    x = MIN(x, width); +    y = MIN(y, height); +    w = MIN(w, width - x); +    h = MIN(h, height - y); + +    if (!qemu_console_is_visible(con)) { +        return; +    } +    QLIST_FOREACH(dcl, &s->listeners, next) { +        if (con != (dcl->con ? dcl->con : active_console)) { +            continue; +        } +        if (dcl->ops->dpy_gfx_update) { +            dcl->ops->dpy_gfx_update(dcl, x, y, w, h); +        } +    } +} + +void dpy_gfx_replace_surface(QemuConsole *con, +                             DisplaySurface *surface) +{ +    DisplayState *s = con->ds; +    DisplaySurface *old_surface = con->surface; +    DisplayChangeListener *dcl; + +    con->surface = surface; +    QLIST_FOREACH(dcl, &s->listeners, next) { +        if (con != (dcl->con ? dcl->con : active_console)) { +            continue; +        } +        if (dcl->ops->dpy_gfx_switch) { +            dcl->ops->dpy_gfx_switch(dcl, surface); +        } +    } +    qemu_free_displaysurface(old_surface); +} + +bool dpy_gfx_check_format(QemuConsole *con, +                          pixman_format_code_t format) +{ +    DisplayChangeListener *dcl; +    DisplayState *s = con->ds; + +    QLIST_FOREACH(dcl, &s->listeners, next) { +        if (dcl->con && dcl->con != con) { +            /* dcl bound to another console -> skip */ +            continue; +        } +        if (dcl->ops->dpy_gfx_check_format) { +            if (!dcl->ops->dpy_gfx_check_format(dcl, format)) { +                return false; +            } +        } else { +            /* default is to whitelist native 32 bpp only */ +            if (format != qemu_default_pixman_format(32, true)) { +                return false; +            } +        } +    } +    return true; +} + +static void dpy_refresh(DisplayState *s) +{ +    DisplayChangeListener *dcl; + +    QLIST_FOREACH(dcl, &s->listeners, next) { +        if (dcl->ops->dpy_refresh) { +            dcl->ops->dpy_refresh(dcl); +        } +    } +} + +void dpy_gfx_copy(QemuConsole *con, int src_x, int src_y, +                  int dst_x, int dst_y, int w, int h) +{ +    DisplayState *s = con->ds; +    DisplayChangeListener *dcl; + +    if (!qemu_console_is_visible(con)) { +        return; +    } +    QLIST_FOREACH(dcl, &s->listeners, next) { +        if (con != (dcl->con ? dcl->con : active_console)) { +            continue; +        } +        if (dcl->ops->dpy_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); +        } +    } +} + +void dpy_text_cursor(QemuConsole *con, int x, int y) +{ +    DisplayState *s = con->ds; +    DisplayChangeListener *dcl; + +    if (!qemu_console_is_visible(con)) { +        return; +    } +    QLIST_FOREACH(dcl, &s->listeners, next) { +        if (con != (dcl->con ? dcl->con : active_console)) { +            continue; +        } +        if (dcl->ops->dpy_text_cursor) { +            dcl->ops->dpy_text_cursor(dcl, x, y); +        } +    } +} + +void dpy_text_update(QemuConsole *con, int x, int y, int w, int h) +{ +    DisplayState *s = con->ds; +    DisplayChangeListener *dcl; + +    if (!qemu_console_is_visible(con)) { +        return; +    } +    QLIST_FOREACH(dcl, &s->listeners, next) { +        if (con != (dcl->con ? dcl->con : active_console)) { +            continue; +        } +        if (dcl->ops->dpy_text_update) { +            dcl->ops->dpy_text_update(dcl, x, y, w, h); +        } +    } +} + +void dpy_text_resize(QemuConsole *con, int w, int h) +{ +    DisplayState *s = con->ds; +    DisplayChangeListener *dcl; + +    if (!qemu_console_is_visible(con)) { +        return; +    } +    QLIST_FOREACH(dcl, &s->listeners, next) { +        if (con != (dcl->con ? dcl->con : active_console)) { +            continue; +        } +        if (dcl->ops->dpy_text_resize) { +            dcl->ops->dpy_text_resize(dcl, w, h); +        } +    } +} + +void dpy_mouse_set(QemuConsole *con, int x, int y, int on) +{ +    DisplayState *s = con->ds; +    DisplayChangeListener *dcl; + +    if (!qemu_console_is_visible(con)) { +        return; +    } +    QLIST_FOREACH(dcl, &s->listeners, next) { +        if (con != (dcl->con ? dcl->con : active_console)) { +            continue; +        } +        if (dcl->ops->dpy_mouse_set) { +            dcl->ops->dpy_mouse_set(dcl, x, y, on); +        } +    } +} + +void dpy_cursor_define(QemuConsole *con, QEMUCursor *cursor) +{ +    DisplayState *s = con->ds; +    DisplayChangeListener *dcl; + +    if (!qemu_console_is_visible(con)) { +        return; +    } +    QLIST_FOREACH(dcl, &s->listeners, next) { +        if (con != (dcl->con ? dcl->con : active_console)) { +            continue; +        } +        if (dcl->ops->dpy_cursor_define) { +            dcl->ops->dpy_cursor_define(dcl, cursor); +        } +    } +} + +bool dpy_cursor_define_supported(QemuConsole *con) +{ +    DisplayState *s = con->ds; +    DisplayChangeListener *dcl; + +    QLIST_FOREACH(dcl, &s->listeners, next) { +        if (dcl->ops->dpy_cursor_define) { +            return true; +        } +    } +    return false; +} + +/***********************************************************/ +/* register display */ + +/* console.c internal use only */ +static DisplayState *get_alloc_displaystate(void) +{ +    if (!display_state) { +        display_state = g_new0(DisplayState, 1); +        cursor_timer = timer_new_ms(QEMU_CLOCK_REALTIME, +                                    text_console_update_cursor, NULL); +    } +    return display_state; +} + +/* + * Called by main(), after creating QemuConsoles + * and before initializing ui (sdl/vnc/...). + */ +DisplayState *init_displaystate(void) +{ +    gchar *name; +    int i; + +    get_alloc_displaystate(); +    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); +        } + +        /* 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); +        object_property_add_child(container_get(object_get_root(), "/backend"), +                                  name, OBJECT(consoles[i]), &error_abort); +        g_free(name); +    } + +    return display_state; +} + +void graphic_console_set_hwops(QemuConsole *con, +                               const GraphicHwOps *hw_ops, +                               void *opaque) +{ +    con->hw_ops = hw_ops; +    con->hw = opaque; +} + +QemuConsole *graphic_console_init(DeviceState *dev, uint32_t head, +                                  const GraphicHwOps *hw_ops, +                                  void *opaque) +{ +    static const char noinit[] = +        "Guest has not initialized the display (yet)."; +    int width = 640; +    int height = 480; +    QemuConsole *s; +    DisplayState *ds; + +    ds = get_alloc_displaystate(); +    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", +                                 &error_abort); +    } + +    s->surface = qemu_create_message_surface(width, height, noinit); +    return s; +} + +QemuConsole *qemu_console_lookup_by_index(unsigned int index) +{ +    if (index >= nb_consoles) { +        return NULL; +    } +    return consoles[index]; +} + +QemuConsole *qemu_console_lookup_by_device(DeviceState *dev, uint32_t head) +{ +    Object *obj; +    uint32_t h; +    int i; + +    for (i = 0; i < nb_consoles; i++) { +        if (!consoles[i]) { +            continue; +        } +        obj = object_property_get_link(OBJECT(consoles[i]), +                                       "device", &error_abort); +        if (DEVICE(obj) != dev) { +            continue; +        } +        h = object_property_get_int(OBJECT(consoles[i]), +                                    "head", &error_abort); +        if (h != head) { +            continue; +        } +        return consoles[i]; +    } +    return NULL; +} + +bool qemu_console_is_visible(QemuConsole *con) +{ +    return (con == active_console) || (con->dcls > 0); +} + +bool qemu_console_is_graphic(QemuConsole *con) +{ +    if (con == NULL) { +        con = active_console; +    } +    return con && (con->console_type == GRAPHIC_CONSOLE); +} + +bool qemu_console_is_fixedsize(QemuConsole *con) +{ +    if (con == NULL) { +        con = active_console; +    } +    return con && (con->console_type != TEXT_CONSOLE); +} + +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 text_console_set_echo(CharDriverState *chr, bool echo) +{ +    QemuConsole *s = chr->opaque; + +    s->echo = echo; +} + +static void text_console_update_cursor_timer(void) +{ +    timer_mod(cursor_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) +              + CONSOLE_CURSOR_PERIOD / 2); +} + +static void text_console_update_cursor(void *opaque) +{ +    QemuConsole *s; +    int i, count = 0; + +    cursor_visible_phase = !cursor_visible_phase; + +    for (i = 0; i < nb_consoles; i++) { +        s = consoles[i]; +        if (qemu_console_is_graphic(s) || +            !qemu_console_is_visible(s)) { +            continue; +        } +        count++; +        graphic_hw_invalidate(s); +    } + +    if (count) { +        text_console_update_cursor_timer(); +    } +} + +static const GraphicHwOps text_console_ops = { +    .invalidate  = text_console_invalidate, +    .text_update = text_console_update, +}; + +static void text_console_do_init(CharDriverState *chr, DisplayState *ds) +{ +    QemuConsole *s; +    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 = timer_new_ms(QEMU_CLOCK_REALTIME, kbd_send_chars, s); +    s->ds = ds; + +    s->y_displayed = 0; +    s->y_base = 0; +    s->total_height = DEFAULT_BACKSCROLL; +    s->x = 0; +    s->y = 0; +    if (!s->surface) { +        if (active_console && active_console->surface) { +            g_width = surface_width(active_console->surface); +            g_height = surface_height(active_console->surface); +        } +        s->surface = qemu_create_displaysurface(g_width, g_height); +    } + +    s->hw_ops = &text_console_ops; +    s->hw = s; + +    /* Set text attribute defaults */ +    s->t_attrib_default.bold = 0; +    s->t_attrib_default.uline = 0; +    s->t_attrib_default.blink = 0; +    s->t_attrib_default.invers = 0; +    s->t_attrib_default.unvisible = 0; +    s->t_attrib_default.fgcol = COLOR_WHITE; +    s->t_attrib_default.bgcol = 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; + +        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 = s->t_attrib_default; +    } + +    qemu_chr_be_generic_open(chr); +    if (chr->init) +        chr->init(chr); +} + +static CharDriverState *text_console_init(ChardevVC *vc) +{ +    CharDriverState *chr; +    QemuConsole *s; +    unsigned width = 0; +    unsigned height = 0; + +    chr = qemu_chr_alloc(); + +    if (vc->has_width) { +        width = vc->width; +    } else if (vc->has_cols) { +        width = vc->cols * FONT_WIDTH; +    } + +    if (vc->has_height) { +        height = vc->height; +    } else if (vc->has_rows) { +        height = vc->rows * FONT_HEIGHT; +    } + +    trace_console_txt_new(width, height); +    if (width == 0 || height == 0) { +        s = new_console(NULL, TEXT_CONSOLE, 0); +    } else { +        s = new_console(NULL, TEXT_CONSOLE_FIXED_SIZE, 0); +        s->surface = qemu_create_displaysurface(width, height); +    } + +    if (!s) { +        g_free(chr); +        return NULL; +    } + +    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; + +    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; +} + +void qemu_console_resize(QemuConsole *s, int width, int height) +{ +    DisplaySurface *surface; + +    assert(s->console_type == GRAPHIC_CONSOLE); +    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; +} + +PixelFormat qemu_default_pixelformat(int bpp) +{ +    pixman_format_code_t fmt = qemu_default_pixman_format(bpp, true); +    PixelFormat pf = qemu_pixelformat_from_pixman(fmt); +    return pf; +} + +static void qemu_chr_parse_vc(QemuOpts *opts, ChardevBackend *backend, +                              Error **errp) +{ +    int val; + +    backend->vc = g_new0(ChardevVC, 1); + +    val = qemu_opt_get_number(opts, "width", 0); +    if (val != 0) { +        backend->vc->has_width = true; +        backend->vc->width = val; +    } + +    val = qemu_opt_get_number(opts, "height", 0); +    if (val != 0) { +        backend->vc->has_height = true; +        backend->vc->height = val; +    } + +    val = qemu_opt_get_number(opts, "cols", 0); +    if (val != 0) { +        backend->vc->has_cols = true; +        backend->vc->cols = val; +    } + +    val = qemu_opt_get_number(opts, "rows", 0); +    if (val != 0) { +        backend->vc->has_rows = true; +        backend->vc->rows = val; +    } +} + +static const TypeInfo qemu_console_info = { +    .name = TYPE_QEMU_CONSOLE, +    .parent = TYPE_OBJECT, +    .instance_size = sizeof(QemuConsole), +    .class_size = sizeof(QemuConsoleClass), +}; + + +static void register_types(void) +{ +    type_register_static(&qemu_console_info); +    register_char_driver("vc", CHARDEV_BACKEND_KIND_VC, qemu_chr_parse_vc); +} + +type_init(register_types); diff --git a/ui/curses.c b/ui/curses.c new file mode 100644 index 00000000..8edb038b --- /dev/null +++ b/ui/curses.c @@ -0,0 +1,390 @@ +/* + * QEMU curses/ncurses display driver + *  + * Copyright (c) 2005 Andrzej Zaborowski  <balrog@zabor.org> + *  + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include <curses.h> + +#ifndef _WIN32 +#include <sys/ioctl.h> +#include <termios.h> +#endif + +#include "qemu-common.h" +#include "ui/console.h" +#include "ui/input.h" +#include "sysemu/sysemu.h" + +#define FONT_HEIGHT 16 +#define FONT_WIDTH 8 + +static DisplayChangeListener *dcl; +static console_ch_t screen[160 * 100]; +static WINDOW *screenpad = NULL; +static int width, height, gwidth, gheight, invalidate; +static int px, py, sminx, sminy, smaxx, smaxy; + +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); + +    pnoutrefresh(screenpad, py, px, sminy, sminx, smaxy - 1, smaxx - 1); +    refresh(); +} + +static void curses_calc_pad(void) +{ +    if (qemu_console_is_fixedsize(NULL)) { +        width = gwidth; +        height = gheight; +    } else { +        width = COLS; +        height = LINES; +    } + +    if (screenpad) +        delwin(screenpad); + +    clear(); +    refresh(); + +    screenpad = newpad(height, width); + +    if (width > COLS) { +        px = (width - COLS) / 2; +        sminx = 0; +        smaxx = COLS; +    } else { +        px = 0; +        sminx = (COLS - width) / 2; +        smaxx = sminx + width; +    } + +    if (height > LINES) { +        py = (height - LINES) / 2; +        sminy = 0; +        smaxy = LINES; +    } else { +        py = 0; +        sminy = (LINES - height) / 2; +        smaxy = sminy + height; +    } +} + +static void curses_resize(DisplayChangeListener *dcl, +                          int width, int height) +{ +    if (width == gwidth && height == gheight) { +        return; +    } + +    gwidth = width; +    gheight = height; + +    curses_calc_pad(); +} + +#if !defined(_WIN32) && defined(SIGWINCH) && defined(KEY_RESIZE) +static volatile sig_atomic_t got_sigwinch; +static void curses_winch_check(void) +{ +    struct winsize { +        unsigned short ws_row; +        unsigned short ws_col; +        unsigned short ws_xpixel;   /* unused */ +        unsigned short ws_ypixel;   /* unused */ +    } ws; + +    if (!got_sigwinch) { +        return; +    } +    got_sigwinch = false; + +    if (ioctl(1, TIOCGWINSZ, &ws) == -1) { +        return; +    } + +    resize_term(ws.ws_row, ws.ws_col); +    invalidate = 1; +} + +static void curses_winch_handler(int signum) +{ +    got_sigwinch = true; +} + +static void curses_winch_init(void) +{ +    struct sigaction old, winch = { +        .sa_handler  = curses_winch_handler, +    }; +    sigaction(SIGWINCH, &winch, &old); +} +#else +static void curses_winch_check(void) {} +static void curses_winch_init(void) {} +#endif + +static void curses_cursor_position(DisplayChangeListener *dcl, +                                   int x, int y) +{ +    if (x >= 0) { +        x = sminx + x - px; +        y = sminy + y - py; + +        if (x >= 0 && y >= 0 && x < COLS && y < LINES) { +            move(y, x); +            curs_set(1); +            /* it seems that curs_set(1) must always be called before +             * curs_set(2) for the latter to have effect */ +            if (!qemu_console_is_graphic(NULL)) { +                curs_set(2); +            } +            return; +        } +    } + +    curs_set(0); +} + +/* generic keyboard conversion */ + +#include "curses_keys.h" + +static kbd_layout_t *kbd_layout = NULL; + +static void curses_refresh(DisplayChangeListener *dcl) +{ +    int chr, nextchr, keysym, keycode, keycode_alt; + +    curses_winch_check(); + +    if (invalidate) { +        clear(); +        refresh(); +        curses_calc_pad(); +        graphic_hw_invalidate(NULL); +        invalidate = 0; +    } + +    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; +        } + +        if (chr == ERR) +            break; + +#ifdef KEY_RESIZE +        /* this shouldn't occur when we use a custom SIGWINCH handler */ +        if (chr == KEY_RESIZE) { +            clear(); +            refresh(); +            curses_calc_pad(); +            curses_update(dcl, 0, 0, width, height); +            continue; +        } +#endif + +        keycode = curses2keycode[chr]; +        keycode_alt = 0; + +        /* alt key */ +        if (keycode == 1) { +            nextchr = getch(); + +            if (nextchr != ERR) { +                chr = nextchr; +                keycode_alt = ALT; +                keycode = curses2keycode[nextchr]; +                nextchr = ERR; + +                if (keycode != -1) { +                    keycode |= ALT; + +                    /* process keys reserved for qemu */ +                    if (keycode >= QEMU_KEY_CONSOLE0 && +                            keycode < QEMU_KEY_CONSOLE0 + 9) { +                        erase(); +                        wnoutrefresh(stdscr); +                        console_select(keycode - QEMU_KEY_CONSOLE0); + +                        invalidate = 1; +                        continue; +                    } +                } +            } +        } + +        if (kbd_layout) { +            keysym = -1; +            if (chr < CURSES_KEYS) +                keysym = curses2keysym[chr]; + +            if (keysym == -1) { +                if (chr < ' ') { +                    keysym = chr + '@'; +                    if (keysym >= 'A' && keysym <= 'Z') +                        keysym += 'a' - 'A'; +                    keysym |= KEYSYM_CNTRL; +                } else +                    keysym = chr; +            } + +            keycode = keysym2scancode(kbd_layout, keysym & KEYSYM_MASK); +            if (keycode == 0) +                continue; + +            keycode |= (keysym & ~KEYSYM_MASK) >> 16; +            keycode |= keycode_alt; +        } + +        if (keycode == -1) +            continue; + +        if (qemu_console_is_graphic(NULL)) { +            /* since terminals don't know about key press and release +             * events, we need to emit both for each key received */ +            if (keycode & SHIFT) { +                qemu_input_event_send_key_number(NULL, SHIFT_CODE, true); +                qemu_input_event_send_key_delay(0); +            } +            if (keycode & CNTRL) { +                qemu_input_event_send_key_number(NULL, CNTRL_CODE, true); +                qemu_input_event_send_key_delay(0); +            } +            if (keycode & ALT) { +                qemu_input_event_send_key_number(NULL, ALT_CODE, true); +                qemu_input_event_send_key_delay(0); +            } +            if (keycode & ALTGR) { +                qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, true); +                qemu_input_event_send_key_delay(0); +            } + +            qemu_input_event_send_key_number(NULL, keycode & KEY_MASK, true); +            qemu_input_event_send_key_delay(0); +            qemu_input_event_send_key_number(NULL, keycode & KEY_MASK, false); +            qemu_input_event_send_key_delay(0); + +            if (keycode & ALTGR) { +                qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, false); +                qemu_input_event_send_key_delay(0); +            } +            if (keycode & ALT) { +                qemu_input_event_send_key_number(NULL, ALT_CODE, false); +                qemu_input_event_send_key_delay(0); +            } +            if (keycode & CNTRL) { +                qemu_input_event_send_key_number(NULL, CNTRL_CODE, false); +                qemu_input_event_send_key_delay(0); +            } +            if (keycode & SHIFT) { +                qemu_input_event_send_key_number(NULL, SHIFT_CODE, false); +                qemu_input_event_send_key_delay(0); +            } +        } else { +            keysym = curses2qemu[chr]; +            if (keysym == -1) +                keysym = chr; + +            kbd_put_keysym(keysym); +        } +    } +} + +static void curses_atexit(void) +{ +    endwin(); +} + +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, +    }; + +    /* input as raw as possible, let everything be interpreted +     * by the guest system */ +    initscr(); noecho(); intrflush(stdscr, FALSE); +    nodelay(stdscr, TRUE); nonl(); keypad(stdscr, TRUE); +    start_color(); raw(); scrollok(stdscr, FALSE); + +    for (i = 0; i < 64; i ++) +        init_pair(i, colour_default[i & 7], colour_default[i >> 3]); +} + +static void curses_keyboard_setup(void) +{ +#if defined(__APPLE__) +    /* always use generic keymaps */ +    if (!keyboard_layout) +        keyboard_layout = "en-us"; +#endif +    if(keyboard_layout) { +        kbd_layout = init_keyboard_layout(name2keysym, keyboard_layout); +        if (!kbd_layout) +            exit(1); +    } +} + +static const DisplayChangeListenerOps dcl_ops = { +    .dpy_name        = "curses", +    .dpy_text_update = curses_update, +    .dpy_text_resize = curses_resize, +    .dpy_refresh     = curses_refresh, +    .dpy_text_cursor = curses_cursor_position, +}; + +void curses_display_init(DisplayState *ds, int full_screen) +{ +#ifndef _WIN32 +    if (!isatty(1)) { +        fprintf(stderr, "We need a terminal output\n"); +        exit(1); +    } +#endif + +    curses_setup(); +    curses_keyboard_setup(); +    atexit(curses_atexit); + +    curses_winch_init(); + +    dcl = (DisplayChangeListener *) g_malloc0(sizeof(DisplayChangeListener)); +    dcl->ops = &dcl_ops; +    register_displaychangelistener(dcl); + +    invalidate = 1; +} diff --git a/ui/curses_keys.h b/ui/curses_keys.h new file mode 100644 index 00000000..18ce6dce --- /dev/null +++ b/ui/curses_keys.h @@ -0,0 +1,514 @@ +/* + * Keycode and keysyms conversion tables for curses + *  + * Copyright (c) 2005 Andrzej Zaborowski  <balrog@zabor.org> + *  + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef QEMU_CURSES_KEYS_H +#define QEMU_CURSES_KEYS_H 1 + +#include <curses.h> +#include "keymaps.h" + + +#define KEY_RELEASE         0x80 +#define KEY_MASK            0x7f +#define GREY_CODE           0xe0 +#define GREY                SCANCODE_GREY +#define SHIFT_CODE          0x2a +#define SHIFT               SCANCODE_SHIFT +#define CNTRL_CODE          0x1d +#define CNTRL               SCANCODE_CTRL +#define ALT_CODE            0x38 +#define ALT                 SCANCODE_ALT +#define ALTGR               SCANCODE_ALTGR + +#define KEYSYM_MASK         0x0ffffff +#define KEYSYM_SHIFT        (SCANCODE_SHIFT << 16) +#define KEYSYM_CNTRL        (SCANCODE_CTRL  << 16) +#define KEYSYM_ALT          (SCANCODE_ALT   << 16) +#define KEYSYM_ALTGR        (SCANCODE_ALTGR << 16) + +/* curses won't detect a Control + Alt + 1, so use Alt + 1 */ +#define QEMU_KEY_CONSOLE0   (2 | ALT)   /* (curses2keycode['1'] | ALT) */ + +#define CURSES_KEYS         KEY_MAX     /* KEY_MAX defined in <curses.h> */ + +static const int curses2keysym[CURSES_KEYS] = { +    [0 ... (CURSES_KEYS - 1)] = -1, + +    [0x7f] = KEY_BACKSPACE, +    ['\r'] = KEY_ENTER, +    ['\n'] = KEY_ENTER, +    [27] = 27, +    [KEY_BTAB] = '\t' | KEYSYM_SHIFT, +}; + +static const int curses2keycode[CURSES_KEYS] = { +    [0 ... (CURSES_KEYS - 1)] = -1, + +    [0x01b] = 1, /* Escape */ +    ['1'] = 2, +    ['2'] = 3, +    ['3'] = 4, +    ['4'] = 5, +    ['5'] = 6, +    ['6'] = 7, +    ['7'] = 8, +    ['8'] = 9, +    ['9'] = 10, +    ['0'] = 11, +    ['-'] = 12, +    ['='] = 13, +    [0x07f] = 14, /* Backspace */ +    [KEY_BACKSPACE] = 14, /* Backspace */ + +    ['\t'] = 15, /* Tab */ +    ['q'] = 16, +    ['w'] = 17, +    ['e'] = 18, +    ['r'] = 19, +    ['t'] = 20, +    ['y'] = 21, +    ['u'] = 22, +    ['i'] = 23, +    ['o'] = 24, +    ['p'] = 25, +    ['['] = 26, +    [']'] = 27, +    ['\n'] = 28, /* Return */ +    ['\r'] = 28, /* Return */ +    [KEY_ENTER] = 28, /* Return */ + +    ['a'] = 30, +    ['s'] = 31, +    ['d'] = 32, +    ['f'] = 33, +    ['g'] = 34, +    ['h'] = 35, +    ['j'] = 36, +    ['k'] = 37, +    ['l'] = 38, +    [';'] = 39, +    ['\''] = 40, /* Single quote */ +    ['`'] = 41, +    ['\\'] = 43, /* Backslash */ + +    ['z'] = 44, +    ['x'] = 45, +    ['c'] = 46, +    ['v'] = 47, +    ['b'] = 48, +    ['n'] = 49, +    ['m'] = 50, +    [','] = 51, +    ['.'] = 52, +    ['/'] = 53, + +    [' '] = 57, + +    [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, +    ['$'] = 5 | SHIFT, +    ['%'] = 6 | SHIFT, +    ['^'] = 7 | SHIFT, +    ['&'] = 8 | SHIFT, +    ['*'] = 9 | SHIFT, +    ['('] = 10 | SHIFT, +    [')'] = 11 | SHIFT, +    ['_'] = 12 | SHIFT, +    ['+'] = 13 | SHIFT, + +    [KEY_BTAB] = 15 | SHIFT, /* Shift + Tab */ +    ['Q'] = 16 | SHIFT, +    ['W'] = 17 | SHIFT, +    ['E'] = 18 | SHIFT, +    ['R'] = 19 | SHIFT, +    ['T'] = 20 | SHIFT, +    ['Y'] = 21 | SHIFT, +    ['U'] = 22 | SHIFT, +    ['I'] = 23 | SHIFT, +    ['O'] = 24 | SHIFT, +    ['P'] = 25 | SHIFT, +    ['{'] = 26 | SHIFT, +    ['}'] = 27 | SHIFT, + +    ['A'] = 30 | SHIFT, +    ['S'] = 31 | SHIFT, +    ['D'] = 32 | SHIFT, +    ['F'] = 33 | SHIFT, +    ['G'] = 34 | SHIFT, +    ['H'] = 35 | SHIFT, +    ['J'] = 36 | SHIFT, +    ['K'] = 37 | SHIFT, +    ['L'] = 38 | SHIFT, +    [':'] = 39 | SHIFT, +    ['"'] = 40 | SHIFT, +    ['~'] = 41 | SHIFT, +    ['|'] = 43 | SHIFT, + +    ['Z'] = 44 | SHIFT, +    ['X'] = 45 | SHIFT, +    ['C'] = 46 | SHIFT, +    ['V'] = 47 | SHIFT, +    ['B'] = 48 | SHIFT, +    ['N'] = 49 | SHIFT, +    ['M'] = 50 | SHIFT, +    ['<'] = 51 | SHIFT, +    ['>'] = 52 | SHIFT, +    ['?'] = 53 | SHIFT, + +    [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 */ +    ['R' - '@'] = 19 | CNTRL, /* Control + r */ +    ['T' - '@'] = 20 | CNTRL, /* Control + t */ +    ['Y' - '@'] = 21 | CNTRL, /* Control + y */ +    ['U' - '@'] = 22 | CNTRL, /* Control + u */ +    /* Control + i collides with Tab */ +    ['O' - '@'] = 24 | CNTRL, /* Control + o */ +    ['P' - '@'] = 25 | CNTRL, /* Control + p */ + +    ['A' - '@'] = 30 | CNTRL, /* Control + a */ +    ['S' - '@'] = 31 | CNTRL, /* Control + s */ +    ['D' - '@'] = 32 | CNTRL, /* Control + d */ +    ['F' - '@'] = 33 | CNTRL, /* Control + f */ +    ['G' - '@'] = 34 | CNTRL, /* Control + g */ +    ['H' - '@'] = 35 | CNTRL, /* Control + h */ +    /* Control + j collides with Return */ +    ['K' - '@'] = 37 | CNTRL, /* Control + k */ +    ['L' - '@'] = 38 | CNTRL, /* Control + l */ + +    ['Z' - '@'] = 44 | CNTRL, /* Control + z */ +    ['X' - '@'] = 45 | CNTRL, /* Control + x */ +    ['C' - '@'] = 46 | CNTRL, /* Control + c */ +    ['V' - '@'] = 47 | CNTRL, /* Control + v */ +    ['B' - '@'] = 48 | CNTRL, /* Control + b */ +    ['N' - '@'] = 49 | CNTRL, /* Control + n */ +    /* Control + m collides with the keycode for Enter */ + +}; + +static const int curses2qemu[CURSES_KEYS] = { +    [0 ... (CURSES_KEYS - 1)] = -1, + +    ['\n'] = '\n', +    ['\r'] = '\n', + +    [0x07f] = QEMU_KEY_BACKSPACE, + +    [KEY_DOWN] = QEMU_KEY_DOWN, +    [KEY_UP] = QEMU_KEY_UP, +    [KEY_LEFT] = QEMU_KEY_LEFT, +    [KEY_RIGHT] = QEMU_KEY_RIGHT, +    [KEY_HOME] = QEMU_KEY_HOME, +    [KEY_BACKSPACE] = QEMU_KEY_BACKSPACE, + +    [KEY_DC] = QEMU_KEY_DELETE, +    [KEY_NPAGE] = QEMU_KEY_PAGEDOWN, +    [KEY_PPAGE] = QEMU_KEY_PAGEUP, +    [KEY_ENTER] = '\n', +    [KEY_END] = QEMU_KEY_END, + +}; + +static const name2keysym_t name2keysym[] = { +    /* Plain ASCII */ +    { "space", 0x020 }, +    { "exclam", 0x021 }, +    { "quotedbl", 0x022 }, +    { "numbersign", 0x023 }, +    { "dollar", 0x024 }, +    { "percent", 0x025 }, +    { "ampersand", 0x026 }, +    { "apostrophe", 0x027 }, +    { "parenleft", 0x028 }, +    { "parenright", 0x029 }, +    { "asterisk", 0x02a }, +    { "plus", 0x02b }, +    { "comma", 0x02c }, +    { "minus", 0x02d }, +    { "period", 0x02e }, +    { "slash", 0x02f }, +    { "0", 0x030 }, +    { "1", 0x031 }, +    { "2", 0x032 }, +    { "3", 0x033 }, +    { "4", 0x034 }, +    { "5", 0x035 }, +    { "6", 0x036 }, +    { "7", 0x037 }, +    { "8", 0x038 }, +    { "9", 0x039 }, +    { "colon", 0x03a }, +    { "semicolon", 0x03b }, +    { "less", 0x03c }, +    { "equal", 0x03d }, +    { "greater", 0x03e }, +    { "question", 0x03f }, +    { "at", 0x040 }, +    { "A", 0x041 }, +    { "B", 0x042 }, +    { "C", 0x043 }, +    { "D", 0x044 }, +    { "E", 0x045 }, +    { "F", 0x046 }, +    { "G", 0x047 }, +    { "H", 0x048 }, +    { "I", 0x049 }, +    { "J", 0x04a }, +    { "K", 0x04b }, +    { "L", 0x04c }, +    { "M", 0x04d }, +    { "N", 0x04e }, +    { "O", 0x04f }, +    { "P", 0x050 }, +    { "Q", 0x051 }, +    { "R", 0x052 }, +    { "S", 0x053 }, +    { "T", 0x054 }, +    { "U", 0x055 }, +    { "V", 0x056 }, +    { "W", 0x057 }, +    { "X", 0x058 }, +    { "Y", 0x059 }, +    { "Z", 0x05a }, +    { "bracketleft", 0x05b }, +    { "backslash", 0x05c }, +    { "bracketright", 0x05d }, +    { "asciicircum", 0x05e }, +    { "underscore", 0x05f }, +    { "grave", 0x060 }, +    { "a", 0x061 }, +    { "b", 0x062 }, +    { "c", 0x063 }, +    { "d", 0x064 }, +    { "e", 0x065 }, +    { "f", 0x066 }, +    { "g", 0x067 }, +    { "h", 0x068 }, +    { "i", 0x069 }, +    { "j", 0x06a }, +    { "k", 0x06b }, +    { "l", 0x06c }, +    { "m", 0x06d }, +    { "n", 0x06e }, +    { "o", 0x06f }, +    { "p", 0x070 }, +    { "q", 0x071 }, +    { "r", 0x072 }, +    { "s", 0x073 }, +    { "t", 0x074 }, +    { "u", 0x075 }, +    { "v", 0x076 }, +    { "w", 0x077 }, +    { "x", 0x078 }, +    { "y", 0x079 }, +    { "z", 0x07a }, +    { "braceleft", 0x07b }, +    { "bar", 0x07c }, +    { "braceright", 0x07d }, +    { "asciitilde", 0x07e }, + +    /* Latin-1 extensions */ +    { "nobreakspace", 0x0a0 }, +    { "exclamdown", 0x0a1 }, +    { "cent", 0x0a2 }, +    { "sterling", 0x0a3 }, +    { "currency", 0x0a4 }, +    { "yen", 0x0a5 }, +    { "brokenbar", 0x0a6 }, +    { "section", 0x0a7 }, +    { "diaeresis", 0x0a8 }, +    { "copyright", 0x0a9 }, +    { "ordfeminine", 0x0aa }, +    { "guillemotleft", 0x0ab }, +    { "notsign", 0x0ac }, +    { "hyphen", 0x0ad }, +    { "registered", 0x0ae }, +    { "macron", 0x0af }, +    { "degree", 0x0b0 }, +    { "plusminus", 0x0b1 }, +    { "twosuperior", 0x0b2 }, +    { "threesuperior", 0x0b3 }, +    { "acute", 0x0b4 }, +    { "mu", 0x0b5 }, +    { "paragraph", 0x0b6 }, +    { "periodcentered", 0x0b7 }, +    { "cedilla", 0x0b8 }, +    { "onesuperior", 0x0b9 }, +    { "masculine", 0x0ba }, +    { "guillemotright", 0x0bb }, +    { "onequarter", 0x0bc }, +    { "onehalf", 0x0bd }, +    { "threequarters", 0x0be }, +    { "questiondown", 0x0bf }, +    { "Agrave", 0x0c0 }, +    { "Aacute", 0x0c1 }, +    { "Acircumflex", 0x0c2 }, +    { "Atilde", 0x0c3 }, +    { "Adiaeresis", 0x0c4 }, +    { "Aring", 0x0c5 }, +    { "AE", 0x0c6 }, +    { "Ccedilla", 0x0c7 }, +    { "Egrave", 0x0c8 }, +    { "Eacute", 0x0c9 }, +    { "Ecircumflex", 0x0ca }, +    { "Ediaeresis", 0x0cb }, +    { "Igrave", 0x0cc }, +    { "Iacute", 0x0cd }, +    { "Icircumflex", 0x0ce }, +    { "Idiaeresis", 0x0cf }, +    { "ETH", 0x0d0 }, +    { "Eth", 0x0d0 }, +    { "Ntilde", 0x0d1 }, +    { "Ograve", 0x0d2 }, +    { "Oacute", 0x0d3 }, +    { "Ocircumflex", 0x0d4 }, +    { "Otilde", 0x0d5 }, +    { "Odiaeresis", 0x0d6 }, +    { "multiply", 0x0d7 }, +    { "Ooblique", 0x0d8 }, +    { "Oslash", 0x0d8 }, +    { "Ugrave", 0x0d9 }, +    { "Uacute", 0x0da }, +    { "Ucircumflex", 0x0db }, +    { "Udiaeresis", 0x0dc }, +    { "Yacute", 0x0dd }, +    { "THORN", 0x0de }, +    { "Thorn", 0x0de }, +    { "ssharp", 0x0df }, +    { "agrave", 0x0e0 }, +    { "aacute", 0x0e1 }, +    { "acircumflex", 0x0e2 }, +    { "atilde", 0x0e3 }, +    { "adiaeresis", 0x0e4 }, +    { "aring", 0x0e5 }, +    { "ae", 0x0e6 }, +    { "ccedilla", 0x0e7 }, +    { "egrave", 0x0e8 }, +    { "eacute", 0x0e9 }, +    { "ecircumflex", 0x0ea }, +    { "ediaeresis", 0x0eb }, +    { "igrave", 0x0ec }, +    { "iacute", 0x0ed }, +    { "icircumflex", 0x0ee }, +    { "idiaeresis", 0x0ef }, +    { "eth", 0x0f0 }, +    { "ntilde", 0x0f1 }, +    { "ograve", 0x0f2 }, +    { "oacute", 0x0f3 }, +    { "ocircumflex", 0x0f4 }, +    { "otilde", 0x0f5 }, +    { "odiaeresis", 0x0f6 }, +    { "division", 0x0f7 }, +    { "oslash", 0x0f8 }, +    { "ooblique", 0x0f8 }, +    { "ugrave", 0x0f9 }, +    { "uacute", 0x0fa }, +    { "ucircumflex", 0x0fb }, +    { "udiaeresis", 0x0fc }, +    { "yacute", 0x0fd }, +    { "thorn", 0x0fe }, +    { "ydiaeresis", 0x0ff }, + +    /* Special keys */ +    { "BackSpace", KEY_BACKSPACE }, +    { "Tab", '\t' }, +    { "Return", KEY_ENTER }, +    { "Right", KEY_RIGHT }, +    { "Left", KEY_LEFT }, +    { "Up", KEY_UP }, +    { "Down", KEY_DOWN }, +    { "Page_Down", KEY_NPAGE }, +    { "Page_Up", KEY_PPAGE }, +    { "Insert", KEY_IC }, +    { "Delete", KEY_DC }, +    { "Home", KEY_HOME }, +    { "End", KEY_END }, +    { "F1", KEY_F(1) }, +    { "F2", KEY_F(2) }, +    { "F3", KEY_F(3) }, +    { "F4", KEY_F(4) }, +    { "F5", KEY_F(5) }, +    { "F6", KEY_F(6) }, +    { "F7", KEY_F(7) }, +    { "F8", KEY_F(8) }, +    { "F9", KEY_F(9) }, +    { "F10", KEY_F(10) }, +    { "F11", KEY_F(11) }, +    { "F12", KEY_F(12) }, +    { "F13", KEY_F(13) }, +    { "F14", KEY_F(14) }, +    { "F15", KEY_F(15) }, +    { "F16", KEY_F(16) }, +    { "F17", KEY_F(17) }, +    { "F18", KEY_F(18) }, +    { "F19", KEY_F(19) }, +    { "F20", KEY_F(20) }, +    { "F21", KEY_F(21) }, +    { "F22", KEY_F(22) }, +    { "F23", KEY_F(23) }, +    { "F24", KEY_F(24) }, +    { "Escape", 27 }, + +    { NULL, 0 }, +}; + +#endif diff --git a/ui/cursor.c b/ui/cursor.c new file mode 100644 index 00000000..2b8dd3fa --- /dev/null +++ b/ui/cursor.c @@ -0,0 +1,211 @@ +#include "qemu-common.h" +#include "ui/console.h" + +#include "cursor_hidden.xpm" +#include "cursor_left_ptr.xpm" + +/* for creating built-in cursors */ +static QEMUCursor *cursor_parse_xpm(const char *xpm[]) +{ +    QEMUCursor *c; +    uint32_t ctab[128]; +    unsigned int width, height, colors, chars; +    unsigned int line = 0, i, r, g, b, x, y, pixel; +    char name[16]; +    uint8_t idx; + +    /* parse header line: width, height, #colors, #chars */ +    if (sscanf(xpm[line], "%u %u %u %u", +               &width, &height, &colors, &chars) != 4) { +        fprintf(stderr, "%s: header parse error: \"%s\"\n", +                __FUNCTION__, xpm[line]); +        return NULL; +    } +    if (chars != 1) { +        fprintf(stderr, "%s: chars != 1 not supported\n", __FUNCTION__); +        return NULL; +    } +    line++; + +    /* parse color table */ +    for (i = 0; i < colors; i++, line++) { +        if (sscanf(xpm[line], "%c c %15s", &idx, name) == 2) { +            if (sscanf(name, "#%02x%02x%02x", &r, &g, &b) == 3) { +                ctab[idx] = (0xff << 24) | (b << 16) | (g << 8) | r; +                continue; +            } +            if (strcmp(name, "None") == 0) { +                ctab[idx] = 0x00000000; +                continue; +            } +        } +        fprintf(stderr, "%s: color parse error: \"%s\"\n", +                __FUNCTION__, xpm[line]); +        return NULL; +    } + +    /* parse pixel data */ +    c = cursor_alloc(width, height); +    for (pixel = 0, y = 0; y < height; y++, line++) { +        for (x = 0; x < height; x++, pixel++) { +            idx = xpm[line][x]; +            c->data[pixel] = ctab[idx]; +        } +    } +    return c; +} + +/* nice for debugging */ +void cursor_print_ascii_art(QEMUCursor *c, const char *prefix) +{ +    uint32_t *data = c->data; +    int x,y; + +    for (y = 0; y < c->height; y++) { +        fprintf(stderr, "%s: %2d: |", prefix, y); +        for (x = 0; x < c->width; x++, data++) { +            if ((*data & 0xff000000) != 0xff000000) { +                fprintf(stderr, " "); /* transparent */ +            } else if ((*data & 0x00ffffff) == 0x00ffffff) { +                fprintf(stderr, "."); /* white */ +            } else if ((*data & 0x00ffffff) == 0x00000000) { +                fprintf(stderr, "X"); /* black */ +            } else { +                fprintf(stderr, "o"); /* other */ +            } +        } +        fprintf(stderr, "|\n"); +    } +} + +QEMUCursor *cursor_builtin_hidden(void) +{ +    QEMUCursor *c; + +    c = cursor_parse_xpm(cursor_hidden_xpm); +    return c; +} + +QEMUCursor *cursor_builtin_left_ptr(void) +{ +    QEMUCursor *c; + +    c = cursor_parse_xpm(cursor_left_ptr_xpm); +    return c; +} + +QEMUCursor *cursor_alloc(int width, int height) +{ +    QEMUCursor *c; +    int datasize = width * height * sizeof(uint32_t); + +    c = g_malloc0(sizeof(QEMUCursor) + datasize); +    c->width  = width; +    c->height = height; +    c->refcount = 1; +    return c; +} + +void cursor_get(QEMUCursor *c) +{ +    c->refcount++; +} + +void cursor_put(QEMUCursor *c) +{ +    if (c == NULL) +        return; +    c->refcount--; +    if (c->refcount) +        return; +    g_free(c); +} + +int cursor_get_mono_bpl(QEMUCursor *c) +{ +    return (c->width + 7) / 8; +} + +void cursor_set_mono(QEMUCursor *c, +                     uint32_t foreground, uint32_t background, uint8_t *image, +                     int transparent, uint8_t *mask) +{ +    uint32_t *data = c->data; +    uint8_t bit; +    int x,y,bpl; + +    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; +            } else if (!transparent && !(mask[x/8] & bit)) { +                *data = 0x00000000; +            } else if (image[x/8] & bit) { +                *data = 0xff000000 | foreground; +            } else { +                *data = 0xff000000 | background; +            } +            bit >>= 1; +            if (bit == 0) { +                bit = 0x80; +            } +        } +        mask  += bpl; +        image += bpl; +    } +} + +void cursor_get_mono_image(QEMUCursor *c, int foreground, uint8_t *image) +{ +    uint32_t *data = c->data; +    uint8_t bit; +    int x,y,bpl; + +    bpl = cursor_get_mono_bpl(c); +    memset(image, 0, bpl * c->height); +    for (y = 0; y < c->height; y++) { +        bit = 0x80; +        for (x = 0; x < c->width; x++, data++) { +            if (((*data & 0xff000000) == 0xff000000) && +                ((*data & 0x00ffffff) == foreground)) { +                image[x/8] |= bit; +            } +            bit >>= 1; +            if (bit == 0) { +                bit = 0x80; +            } +        } +        image += bpl; +    } +} + +void cursor_get_mono_mask(QEMUCursor *c, int transparent, uint8_t *mask) +{ +    uint32_t *data = c->data; +    uint8_t bit; +    int x,y,bpl; + +    bpl = cursor_get_mono_bpl(c); +    memset(mask, 0, bpl * c->height); +    for (y = 0; y < c->height; y++) { +        bit = 0x80; +        for (x = 0; x < c->width; x++, data++) { +            if ((*data & 0xff000000) != 0xff000000) { +                if (transparent != 0) { +                    mask[x/8] |= bit; +                } +            } else { +                if (transparent == 0) { +                    mask[x/8] |= bit; +                } +            } +            bit >>= 1; +            if (bit == 0) { +                bit = 0x80; +            } +        } +        mask += bpl; +    } +} diff --git a/ui/cursor_hidden.xpm b/ui/cursor_hidden.xpm new file mode 100644 index 00000000..354e7a93 --- /dev/null +++ b/ui/cursor_hidden.xpm @@ -0,0 +1,37 @@ +/* XPM */ +static const char *cursor_hidden_xpm[] = { +    "32 32 1 1", +    "  c None", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +}; diff --git a/ui/cursor_left_ptr.xpm b/ui/cursor_left_ptr.xpm new file mode 100644 index 00000000..6c9ada90 --- /dev/null +++ b/ui/cursor_left_ptr.xpm @@ -0,0 +1,39 @@ +/* XPM */ +static const char *cursor_left_ptr_xpm[] = { +    "32 32 3 1", +    "X c #000000", +    ". c #ffffff", +    "  c None", +    "X                               ", +    "XX                              ", +    "X.X                             ", +    "X..X                            ", +    "X...X                           ", +    "X....X                          ", +    "X.....X                         ", +    "X......X                        ", +    "X.......X                       ", +    "X........X                      ", +    "X.....XXXXX                     ", +    "X..X..X                         ", +    "X.X X..X                        ", +    "XX  X..X                        ", +    "X    X..X                       ", +    "     X..X                       ", +    "      X..X                      ", +    "      X..X                      ", +    "       XX                       ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +    "                                ", +}; diff --git a/ui/egl-helpers.c b/ui/egl-helpers.c new file mode 100644 index 00000000..87d77afa --- /dev/null +++ b/ui/egl-helpers.c @@ -0,0 +1,148 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <glob.h> + +#include "ui/egl-helpers.h" + +EGLDisplay *qemu_egl_display; +EGLConfig qemu_egl_config; + +/* ---------------------------------------------------------------------- */ + +static bool egl_gles; +static int egl_debug; + +#define egl_dbg(_x ...)                          \ +    do {                                         \ +        if (egl_debug) {                         \ +            fprintf(stderr, "egl: " _x);         \ +        }                                        \ +    } while (0); + +/* ---------------------------------------------------------------------- */ + +EGLSurface qemu_egl_init_surface_x11(EGLContext ectx, Window win) +{ +    EGLSurface esurface; +    EGLBoolean b; + +    egl_dbg("eglCreateWindowSurface (x11 win id 0x%lx) ...\n", +            (unsigned long) win); +    esurface = eglCreateWindowSurface(qemu_egl_display, +                                      qemu_egl_config, +                                      (EGLNativeWindowType)win, NULL); +    if (esurface == EGL_NO_SURFACE) { +        fprintf(stderr, "egl: eglCreateWindowSurface failed\n"); +        return NULL; +    } + +    b = eglMakeCurrent(qemu_egl_display, esurface, esurface, ectx); +    if (b == EGL_FALSE) { +        fprintf(stderr, "egl: eglMakeCurrent failed\n"); +        return NULL; +    } + +    return esurface; +} + +/* ---------------------------------------------------------------------- */ + +int qemu_egl_init_dpy(EGLNativeDisplayType dpy, bool gles, bool debug) +{ +    static const EGLint conf_att_gl[] = { +        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; + +    if (debug) { +        egl_debug = 1; +        setenv("EGL_LOG_LEVEL", "debug", true); +        setenv("LIBGL_DEBUG", "verbose", true); +    } + +    egl_dbg("eglGetDisplay (dpy %p) ...\n", dpy); +    qemu_egl_display = eglGetDisplay(dpy); +    if (qemu_egl_display == EGL_NO_DISPLAY) { +        fprintf(stderr, "egl: eglGetDisplay failed\n"); +        return -1; +    } + +    egl_dbg("eglInitialize ...\n"); +    b = eglInitialize(qemu_egl_display, &major, &minor); +    if (b == EGL_FALSE) { +        fprintf(stderr, "egl: eglInitialize failed\n"); +        return -1; +    } + +    egl_dbg("eglBindAPI ...\n"); +    b = eglBindAPI(gles ? EGL_OPENGL_ES_API : EGL_OPENGL_API); +    if (b == EGL_FALSE) { +        fprintf(stderr, "egl: eglBindAPI failed\n"); +        return -1; +    } + +    egl_dbg("eglChooseConfig ...\n"); +    b = eglChooseConfig(qemu_egl_display, +                        gles ? conf_att_gles : conf_att_gl, +                        &qemu_egl_config, 1, &n); +    if (b == EGL_FALSE || n != 1) { +        fprintf(stderr, "egl: eglChooseConfig failed\n"); +        return -1; +    } + +    egl_gles = gles; +    return 0; +} + +EGLContext qemu_egl_init_ctx(void) +{ +    static const EGLint ctx_att_gl[] = { +        EGL_NONE +    }; +    static const EGLint ctx_att_gles[] = { +        EGL_CONTEXT_CLIENT_VERSION, 2, +        EGL_NONE +    }; + +    EGLContext ectx; +    EGLBoolean b; + +    egl_dbg("eglCreateContext ...\n"); +    ectx = eglCreateContext(qemu_egl_display, qemu_egl_config, EGL_NO_CONTEXT, +                            egl_gles ? ctx_att_gles : ctx_att_gl); +    if (ectx == EGL_NO_CONTEXT) { +        fprintf(stderr, "egl: eglCreateContext failed\n"); +        return NULL; +    } + +    b = eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, ectx); +    if (b == EGL_FALSE) { +        fprintf(stderr, "egl: eglMakeCurrent failed\n"); +        return NULL; +    } + +    return ectx; +} diff --git a/ui/gtk-egl.c b/ui/gtk-egl.c new file mode 100644 index 00000000..15b41f2b --- /dev/null +++ b/ui/gtk-egl.c @@ -0,0 +1,141 @@ +/* + * 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-common.h" + +#include "trace.h" + +#include "ui/console.h" +#include "ui/gtk.h" +#include "ui/egl-helpers.h" + +#include "sysemu/sysemu.h" + +/** 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; +    } + +#if GTK_CHECK_VERSION(3, 0, 0) +    Window x11_window = gdk_x11_window_get_xid(gdk_window); +#else +    Window x11_window = gdk_x11_drawable_get_xid(gdk_window); +#endif +    if (!x11_window) { +        return; +    } + +    vc->gfx.ectx = qemu_egl_init_ctx(); +    vc->gfx.esurface = qemu_egl_init_surface_x11(vc->gfx.ectx, x11_window); + +    assert(vc->gfx.esurface); +} + +void gd_egl_draw(VirtualConsole *vc) +{ +    GdkWindow *window; +    int ww, wh; + +    if (!vc->gfx.gls || !vc->gfx.ds) { +        return; +    } + +    eglMakeCurrent(qemu_egl_display, vc->gfx.esurface, +                   vc->gfx.esurface, vc->gfx.ectx); + +    window = gtk_widget_get_window(vc->gfx.drawing_area); +    gdk_drawable_get_size(window, &ww, &wh); +    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); +} + +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 = console_gl_init_context(); +        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; +        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); +    } +} + +void gtk_egl_init(void) +{ +    GdkDisplay *gdk_display = gdk_display_get_default(); +    Display *x11_display = gdk_x11_display_get_xdisplay(gdk_display); + +    if (qemu_egl_init_dpy(x11_display, false, false) < 0) { +        return; +    } + +    display_opengl = 1; +} diff --git a/ui/gtk.c b/ui/gtk.c new file mode 100644 index 00000000..11ea2cf3 --- /dev/null +++ b/ui/gtk.c @@ -0,0 +1,2055 @@ +/* + * GTK UI + * + * Copyright IBM, Corp. 2012 + * + * 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. + * + * Portions from gtk-vnc: + * + * 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 "ui/console.h" +#include "ui/gtk.h" + +#include <glib/gi18n.h> +#include <locale.h> +#if defined(CONFIG_VTE) +#include <vte/vte.h> +#endif +#include <math.h> + +#include "trace.h" +#include "ui/input.h" +#include "sysemu/sysemu.h" +#include "qmp-commands.h" +#include "x_keymap.h" +#include "keymaps.h" +#include "sysemu/char.h" +#include "qom/object.h" + +#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 + +#if !defined(CONFIG_VTE) +# define VTE_CHECK_VERSION(a, b, c) 0 +#endif + +#if defined(CONFIG_VTE) && !GTK_CHECK_VERSION(3, 0, 0) +/* + * The gtk2 vte terminal widget seriously messes up the window resize + * for some reason.  You basically can't make the qemu window smaller + * any more because the toplevel window geoemtry hints are overridden. + * + * Workaround that by hiding all vte widgets, except the one in the + * current tab. + * + * Luckily everything works smooth in gtk3. + */ +# define VTE_RESIZE_HACK 1 +#endif + +#if !GTK_CHECK_VERSION(2, 20, 0) +#define gtk_widget_get_realized(widget) GTK_WIDGET_REALIZED(widget) +#endif + +#ifndef GDK_IS_X11_DISPLAY +#define GDK_IS_X11_DISPLAY(dpy) (dpy == dpy) +#endif +#ifndef GDK_IS_WIN32_DISPLAY +#define GDK_IS_WIN32_DISPLAY(dpy) (dpy == dpy) +#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_q GDK_q +#define GDK_KEY_plus GDK_plus +#define GDK_KEY_minus GDK_minus +#define GDK_KEY_Pause GDK_Pause +#endif + +#define HOTKEY_MODIFIERS        (GDK_CONTROL_MASK | GDK_MOD1_MASK) + +static const int modifier_keycode[] = { +    /* shift, control, alt keys, meta keys, both left & right */ +    0x2a, 0x36, 0x1d, 0x9d, 0x38, 0xb8, 0xdb, 0xdd, +}; + +struct GtkDisplayState { +    GtkWidget *window; + +    GtkWidget *menu_bar; + +    GtkAccelGroup *accel_group; + +    GtkWidget *machine_menu_item; +    GtkWidget *machine_menu; +    GtkWidget *pause_item; +    GtkWidget *reset_item; +    GtkWidget *powerdown_item; +    GtkWidget *quit_item; + +    GtkWidget *view_menu_item; +    GtkWidget *view_menu; +    GtkWidget *full_screen_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; + +    int nb_vcs; +    VirtualConsole vc[MAX_VCS]; + +    GtkWidget *show_tabs_item; +    GtkWidget *untabify_item; + +    GtkWidget *vbox; +    GtkWidget *notebook; +    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; + +    gboolean full_screen; + +    GdkCursor *null_cursor; +    Notifier mouse_mode_notifier; +    gboolean free_scale; + +    bool external_pause_update; + +    bool modifier_pressed[ARRAY_SIZE(modifier_keycode)]; +    bool has_evdev; +    bool ignore_keys; +}; + +static void gd_grab_pointer(VirtualConsole *vc); +static void gd_ungrab_pointer(GtkDisplayState *s); + +/** Utility Functions **/ + +static VirtualConsole *gd_vc_find_by_menu(GtkDisplayState *s) +{ +    VirtualConsole *vc; +    gint i; + +    for (i = 0; i < s->nb_vcs; i++) { +        vc = &s->vc[i]; +        if (gtk_check_menu_item_get_active +            (GTK_CHECK_MENU_ITEM(vc->menu_item))) { +            return vc; +        } +    } +    return NULL; +} + +static VirtualConsole *gd_vc_find_by_page(GtkDisplayState *s, gint page) +{ +    VirtualConsole *vc; +    gint i, p; + +    for (i = 0; i < s->nb_vcs; i++) { +        vc = &s->vc[i]; +        p = gtk_notebook_page_num(GTK_NOTEBOOK(s->notebook), vc->tab_item); +        if (p == page) { +            return vc; +        } +    } +    return NULL; +} + +static VirtualConsole *gd_vc_find_current(GtkDisplayState *s) +{ +    gint page; + +    page = gtk_notebook_get_current_page(GTK_NOTEBOOK(s->notebook)); +    return gd_vc_find_by_page(s, page); +} + +static bool gd_is_grab_active(GtkDisplayState *s) +{ +    return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->grab_item)); +} + +static bool gd_grab_on_hover(GtkDisplayState *s) +{ +    return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->grab_on_hover_item)); +} + +static void gd_update_cursor(VirtualConsole *vc) +{ +    GtkDisplayState *s = vc->s; +    GdkWindow *window; + +    if (vc->type != GD_VC_GFX || +        !qemu_console_is_graphic(vc->gfx.dcl.con)) { +        return; +    } + +    if (!gtk_widget_get_realized(vc->gfx.drawing_area)) { +        return; +    } + +    window = gtk_widget_get_window(GTK_WIDGET(vc->gfx.drawing_area)); +    if (s->full_screen || qemu_input_is_absolute() || s->ptr_owner == vc) { +        gdk_window_set_cursor(window, s->null_cursor); +    } else { +        gdk_window_set_cursor(window, NULL); +    } +} + +static void gd_update_caption(GtkDisplayState *s) +{ +    const char *status = ""; +    gchar *prefix; +    gchar *title; +    const char *grab = ""; +    bool is_paused = !runstate_is_running(); +    int i; + +    if (qemu_name) { +        prefix = g_strdup_printf("QEMU (%s)", qemu_name); +    } else { +        prefix = g_strdup_printf("QEMU"); +    } + +    if (s->ptr_owner != NULL && +        s->ptr_owner->window == NULL) { +        grab = _(" - Press Ctrl+Alt+G to release grab"); +    } + +    if (is_paused) { +        status = _(" [Paused]"); +    } +    s->external_pause_update = true; +    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->pause_item), +                                   is_paused); +    s->external_pause_update = false; + +    title = g_strdup_printf("%s%s%s", prefix, status, grab); +    gtk_window_set_title(GTK_WINDOW(s->window), title); +    g_free(title); + +    for (i = 0; i < s->nb_vcs; i++) { +        VirtualConsole *vc = &s->vc[i]; + +        if (!vc->window) { +            continue; +        } +        title = g_strdup_printf("%s: %s%s%s", prefix, vc->label, +                                vc == s->kbd_owner ? " +kbd" : "", +                                vc == s->ptr_owner ? " +ptr" : ""); +        gtk_window_set_title(GTK_WINDOW(vc->window), title); +        g_free(title); +    } + +    g_free(prefix); +} + +static void gd_update_geometry_hints(VirtualConsole *vc) +{ +    GtkDisplayState *s = vc->s; +    GdkWindowHints mask = 0; +    GdkGeometry geo = {}; +    GtkWidget *geo_widget = NULL; +    GtkWindow *geo_window; + +    if (vc->type == GD_VC_GFX) { +        if (!vc->gfx.ds) { +            return; +        } +        if (s->free_scale) { +            geo.min_width  = surface_width(vc->gfx.ds) * VC_SCALE_MIN; +            geo.min_height = surface_height(vc->gfx.ds) * VC_SCALE_MIN; +            mask |= GDK_HINT_MIN_SIZE; +        } else { +            geo.min_width  = surface_width(vc->gfx.ds) * vc->gfx.scale_x; +            geo.min_height = surface_height(vc->gfx.ds) * vc->gfx.scale_y; +            mask |= GDK_HINT_MIN_SIZE; +        } +        geo_widget = vc->gfx.drawing_area; +        gtk_widget_set_size_request(geo_widget, geo.min_width, geo.min_height); + +#if defined(CONFIG_VTE) +    } else if (vc->type == GD_VC_VTE) { +        VteTerminal *term = VTE_TERMINAL(vc->vte.terminal); +        GtkBorder *ib; + +        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; +        gtk_widget_style_get(vc->vte.terminal, "inner-border", &ib, NULL); +        geo.base_width  += ib->left + ib->right; +        geo.base_height += ib->top + ib->bottom; +        geo.min_width   += ib->left + ib->right; +        geo.min_height  += ib->top + ib->bottom; +        geo_widget = vc->vte.terminal; +#endif +    } + +    geo_window = GTK_WINDOW(vc->window ? vc->window : s->window); +    gtk_window_set_geometry_hints(geo_window, geo_widget, &geo, mask); +} + +void gd_update_windowsize(VirtualConsole *vc) +{ +    GtkDisplayState *s = vc->s; + +    gd_update_geometry_hints(vc); + +    if (vc->type == GD_VC_GFX && !s->full_screen && !s->free_scale) { +        gtk_window_resize(GTK_WINDOW(vc->window ? vc->window : s->window), +                          VC_WINDOW_X_MIN, VC_WINDOW_Y_MIN); +    } +} + +static void gd_update_full_redraw(VirtualConsole *vc) +{ +    GtkWidget *area = vc->gfx.drawing_area; +    int ww, wh; +    gdk_drawable_get_size(gtk_widget_get_window(area), &ww, &wh); +    gtk_widget_queue_draw_area(area, 0, 0, ww, wh); +} + +static void gtk_release_modifiers(GtkDisplayState *s) +{ +    VirtualConsole *vc = gd_vc_find_current(s); +    int i, keycode; + +    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; +        } +        qemu_input_event_send_key_number(vc->gfx.dcl.con, keycode, false); +        s->modifier_pressed[i] = false; +    } +} + +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)); +} + +/** DisplayState Callbacks **/ + +static void gd_update(DisplayChangeListener *dcl, +                      int x, int y, int w, int h) +{ +    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); +    GdkWindow *win; +    int x1, x2, y1, y2; +    int mx, my; +    int fbw, fbh; +    int ww, wh; + +    trace_gd_update(vc->label, x, y, w, h); + +    if (!gtk_widget_get_realized(vc->gfx.drawing_area)) { +        return; +    } + +    if (vc->gfx.convert) { +        pixman_image_composite(PIXMAN_OP_SRC, vc->gfx.ds->image, +                               NULL, vc->gfx.convert, +                               x, y, 0, 0, x, y, w, h); +    } + +    x1 = floor(x * vc->gfx.scale_x); +    y1 = floor(y * vc->gfx.scale_y); + +    x2 = ceil(x * vc->gfx.scale_x + w * vc->gfx.scale_x); +    y2 = ceil(y * vc->gfx.scale_y + h * vc->gfx.scale_y); + +    fbw = surface_width(vc->gfx.ds) * vc->gfx.scale_x; +    fbh = surface_height(vc->gfx.ds) * vc->gfx.scale_y; + +    win = gtk_widget_get_window(vc->gfx.drawing_area); +    if (!win) { +        return; +    } +    gdk_drawable_get_size(win, &ww, &wh); + +    mx = my = 0; +    if (ww > fbw) { +        mx = (ww - fbw) / 2; +    } +    if (wh > fbh) { +        my = (wh - fbh) / 2; +    } + +    gtk_widget_queue_draw_area(vc->gfx.drawing_area, +                               mx + x1, my + y1, (x2 - x1), (y2 - y1)); +} + +static void gd_refresh(DisplayChangeListener *dcl) +{ +    graphic_hw_update(dcl->con); +} + +#if GTK_CHECK_VERSION(3, 0, 0) +static void gd_mouse_set(DisplayChangeListener *dcl, +                         int x, int y, int visible) +{ +    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); +    GdkDisplay *dpy; +    GdkDeviceManager *mgr; +    gint x_root, y_root; + +    if (qemu_input_is_absolute()) { +        return; +    } + +    dpy = gtk_widget_get_display(vc->gfx.drawing_area); +    mgr = gdk_display_get_device_manager(dpy); +    gdk_window_get_root_coords(gtk_widget_get_window(vc->gfx.drawing_area), +                               x, y, &x_root, &y_root); +    gdk_device_warp(gdk_device_manager_get_client_pointer(mgr), +                    gtk_widget_get_screen(vc->gfx.drawing_area), +                    x_root, y_root); +    vc->s->last_x = x; +    vc->s->last_y = y; +} +#else +static void gd_mouse_set(DisplayChangeListener *dcl, +                         int x, int y, int visible) +{ +    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); +    gint x_root, y_root; + +    if (qemu_input_is_absolute()) { +        return; +    } + +    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(vc->gfx.drawing_area), +                             gtk_widget_get_screen(vc->gfx.drawing_area), +                             x_root, y_root); +} +#endif + +static void gd_cursor_define(DisplayChangeListener *dcl, +                             QEMUCursor *c) +{ +    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); +    GdkPixbuf *pixbuf; +    GdkCursor *cursor; + +    if (!gtk_widget_get_realized(vc->gfx.drawing_area)) { +        return; +    } + +    pixbuf = gdk_pixbuf_new_from_data((guchar *)(c->data), +                                      GDK_COLORSPACE_RGB, true, 8, +                                      c->width, c->height, c->width * 4, +                                      NULL, NULL); +    cursor = gdk_cursor_new_from_pixbuf +        (gtk_widget_get_display(vc->gfx.drawing_area), +         pixbuf, c->hot_x, c->hot_y); +    gdk_window_set_cursor(gtk_widget_get_window(vc->gfx.drawing_area), cursor); +    g_object_unref(pixbuf); +#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) +{ +    VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); +    bool resized = true; + +    trace_gd_switch(vc->label, +                    surface ? surface_width(surface)  : 0, +                    surface ? surface_height(surface) : 0); + +    if (vc->gfx.surface) { +        cairo_surface_destroy(vc->gfx.surface); +        vc->gfx.surface = NULL; +    } +    if (vc->gfx.convert) { +        pixman_image_unref(vc->gfx.convert); +        vc->gfx.convert = NULL; +    } + +    if (vc->gfx.ds && surface && +        surface_width(vc->gfx.ds) == surface_width(surface) && +        surface_height(vc->gfx.ds) == surface_height(surface)) { +        resized = false; +    } +    vc->gfx.ds = surface; + +    if (!surface) { +        return; +    } + +    if (surface->format == PIXMAN_x8r8g8b8) { +        /* +         * PIXMAN_x8r8g8b8 == CAIRO_FORMAT_RGB24 +         * +         * No need to convert, use surface directly.  Should be the +         * common case as this is qemu_default_pixelformat(32) too. +         */ +        vc->gfx.surface = cairo_image_surface_create_for_data +            (surface_data(surface), +             CAIRO_FORMAT_RGB24, +             surface_width(surface), +             surface_height(surface), +             surface_stride(surface)); +    } else { +        /* Must convert surface, use pixman to do it. */ +        vc->gfx.convert = pixman_image_create_bits(PIXMAN_x8r8g8b8, +                                                   surface_width(surface), +                                                   surface_height(surface), +                                                   NULL, 0); +        vc->gfx.surface = cairo_image_surface_create_for_data +            ((void *)pixman_image_get_data(vc->gfx.convert), +             CAIRO_FORMAT_RGB24, +             pixman_image_get_width(vc->gfx.convert), +             pixman_image_get_height(vc->gfx.convert), +             pixman_image_get_stride(vc->gfx.convert)); +        pixman_image_composite(PIXMAN_OP_SRC, vc->gfx.ds->image, +                               NULL, vc->gfx.convert, +                               0, 0, 0, 0, 0, 0, +                               pixman_image_get_width(vc->gfx.convert), +                               pixman_image_get_height(vc->gfx.convert)); +    } + +    if (resized) { +        gd_update_windowsize(vc); +    } else { +        gd_update_full_redraw(vc); +    } +} + +static const DisplayChangeListenerOps dcl_ops = { +    .dpy_name             = "gtk", +    .dpy_gfx_update       = gd_update, +    .dpy_gfx_switch       = gd_switch, +    .dpy_gfx_check_format = qemu_pixman_check_format, +    .dpy_refresh          = gd_refresh, +    .dpy_mouse_set        = gd_mouse_set, +    .dpy_cursor_define    = gd_cursor_define, +}; + + +#if defined(CONFIG_OPENGL) + +/** DisplayState Callbacks (opengl version) **/ + +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, +}; + +#endif + +/** QEMU Events **/ + +static void gd_change_runstate(void *opaque, int running, RunState state) +{ +    GtkDisplayState *s = opaque; + +    gd_update_caption(s); +} + +static void gd_mouse_mode_change(Notifier *notify, void *data) +{ +    GtkDisplayState *s; +    int i; + +    s = container_of(notify, GtkDisplayState, mouse_mode_notifier); +    /* release the grab at switching to absolute mode */ +    if (qemu_input_is_absolute() && gd_is_grab_active(s)) { +        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item), +                                       FALSE); +    } +    for (i = 0; i < s->nb_vcs; i++) { +        VirtualConsole *vc = &s->vc[i]; +        gd_update_cursor(vc); +    } +} + +/** GTK Events **/ + +static gboolean gd_window_close(GtkWidget *widget, GdkEvent *event, +                                void *opaque) +{ +    GtkDisplayState *s = opaque; +    int i; + +    if (!no_quit) { +        for (i = 0; i < s->nb_vcs; i++) { +            if (s->vc[i].type != GD_VC_GFX) { +                continue; +            } +            unregister_displaychangelistener(&s->vc[i].gfx.dcl); +        } +        qmp_quit(NULL); +        return FALSE; +    } + +    return TRUE; +} + +static gboolean gd_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque) +{ +    VirtualConsole *vc = opaque; +    GtkDisplayState *s = vc->s; +    int mx, my; +    int ww, wh; +    int fbw, fbh; + +#if defined(CONFIG_OPENGL) +    if (vc->gfx.gls) { +        gd_egl_draw(vc); +        return TRUE; +    } +#endif + +    if (!gtk_widget_get_realized(widget)) { +        return FALSE; +    } +    if (!vc->gfx.ds) { +        return FALSE; +    } + +    fbw = surface_width(vc->gfx.ds); +    fbh = surface_height(vc->gfx.ds); + +    gdk_drawable_get_size(gtk_widget_get_window(widget), &ww, &wh); + +    if (s->full_screen) { +        vc->gfx.scale_x = (double)ww / fbw; +        vc->gfx.scale_y = (double)wh / fbh; +    } else if (s->free_scale) { +        double sx, sy; + +        sx = (double)ww / fbw; +        sy = (double)wh / fbh; + +        vc->gfx.scale_x = vc->gfx.scale_y = MIN(sx, sy); +    } + +    fbw *= vc->gfx.scale_x; +    fbh *= vc->gfx.scale_y; + +    mx = my = 0; +    if (ww > fbw) { +        mx = (ww - fbw) / 2; +    } +    if (wh > fbh) { +        my = (wh - fbh) / 2; +    } + +    cairo_rectangle(cr, 0, 0, ww, wh); + +    /* Optionally cut out the inner area where the pixmap +       will be drawn. This avoids 'flashing' since we're +       not double-buffering. Note we're using the undocumented +       behaviour of drawing the rectangle from right to left +       to cut out the whole */ +    cairo_rectangle(cr, mx + fbw, my, +                    -1 * fbw, fbh); +    cairo_fill(cr); + +    cairo_scale(cr, vc->gfx.scale_x, vc->gfx.scale_y); +    cairo_set_source_surface(cr, vc->gfx.surface, +                             mx / vc->gfx.scale_x, my / vc->gfx.scale_y); +    cairo_paint(cr); + +    return TRUE; +} + +#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) +{ +    VirtualConsole *vc = opaque; +    GtkDisplayState *s = vc->s; +    int x, y; +    int mx, my; +    int fbh, fbw; +    int ww, wh; + +    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(vc->gfx.drawing_area), +                          &ww, &wh); + +    mx = my = 0; +    if (ww > fbw) { +        mx = (ww - fbw) / 2; +    } +    if (wh > fbh) { +        my = (wh - fbh) / 2; +    } + +    x = (motion->x - mx) / vc->gfx.scale_x; +    y = (motion->y - my) / vc->gfx.scale_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, +                             surface_width(vc->gfx.ds)); +        qemu_input_queue_abs(vc->gfx.dcl.con, INPUT_AXIS_Y, y, +                             surface_height(vc->gfx.ds)); +        qemu_input_event_sync(); +    } else if (s->last_set && s->ptr_owner == vc) { +        qemu_input_queue_rel(vc->gfx.dcl.con, INPUT_AXIS_X, x - s->last_x); +        qemu_input_queue_rel(vc->gfx.dcl.con, INPUT_AXIS_Y, y - s->last_y); +        qemu_input_event_sync(); +    } +    s->last_x = x; +    s->last_y = y; +    s->last_set = TRUE; + +    if (!qemu_input_is_absolute() && s->ptr_owner == vc) { +        GdkScreen *screen = gtk_widget_get_screen(vc->gfx.drawing_area); +        int x = (int)motion->x_root; +        int y = (int)motion->y_root; + +        /* 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 +         * in the server doesn't correspond 1-for-1, and so +         * may still be only half way across the screen. Without +         * this warp, the server pointer would thus appear to hit +         * an invisible wall */ +        if (x == 0) { +            x += 200; +        } +        if (y == 0) { +            y += 200; +        } +        if (x == (gdk_screen_get_width(screen) - 1)) { +            x -= 200; +        } +        if (y == (gdk_screen_get_height(screen) - 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_set = FALSE; +            return FALSE; +        } +    } +    return TRUE; +} + +static gboolean gd_button_event(GtkWidget *widget, GdkEventButton *button, +                                void *opaque) +{ +    VirtualConsole *vc = opaque; +    GtkDisplayState *s = vc->s; +    InputButton btn; + +    /* implicitly grab the input at the first click in the relative mode */ +    if (button->button == 1 && button->type == GDK_BUTTON_PRESS && +        !qemu_input_is_absolute() && s->ptr_owner != vc) { +        gd_ungrab_pointer(s); +        if (!vc->window) { +            gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item), +                                           TRUE); +        } else { +            gd_grab_pointer(vc); +            gd_update_caption(s); +        } +        return TRUE; +    } + +    if (button->button == 1) { +        btn = INPUT_BUTTON_LEFT; +    } else if (button->button == 2) { +        btn = INPUT_BUTTON_MIDDLE; +    } else if (button->button == 3) { +        btn = INPUT_BUTTON_RIGHT; +    } else { +        return TRUE; +    } + +    qemu_input_queue_btn(vc->gfx.dcl.con, btn, +                         button->type == GDK_BUTTON_PRESS); +    qemu_input_event_sync(); +    return TRUE; +} + +static gboolean gd_scroll_event(GtkWidget *widget, GdkEventScroll *scroll, +                                void *opaque) +{ +    VirtualConsole *vc = opaque; +    InputButton btn; + +    if (scroll->direction == GDK_SCROLL_UP) { +        btn = INPUT_BUTTON_WHEEL_UP; +    } else if (scroll->direction == GDK_SCROLL_DOWN) { +        btn = INPUT_BUTTON_WHEEL_DOWN; +    } else { +        return TRUE; +    } + +    qemu_input_queue_btn(vc->gfx.dcl.con, btn, true); +    qemu_input_event_sync(); +    qemu_input_queue_btn(vc->gfx.dcl.con, btn, false); +    qemu_input_event_sync(); +    return TRUE; +} + +static int gd_map_keycode(GtkDisplayState *s, GdkDisplay *dpy, int gdk_keycode) +{ +    int qemu_keycode; + +#ifdef GDK_WINDOWING_WIN32 +    if (GDK_IS_WIN32_DISPLAY(dpy)) { +        qemu_keycode = MapVirtualKey(gdk_keycode, MAPVK_VK_TO_VSC); +        switch (qemu_keycode) { +        case 103:   /* alt gr */ +            qemu_keycode = 56 | SCANCODE_GREY; +            break; +        } +        return qemu_keycode; +    } +#endif + +    if (gdk_keycode < 9) { +        qemu_keycode = 0; +    } else if (gdk_keycode < 97) { +        qemu_keycode = gdk_keycode - 8; +#ifdef GDK_WINDOWING_X11 +    } else if (GDK_IS_X11_DISPLAY(dpy) && gdk_keycode < 158) { +        if (s->has_evdev) { +            qemu_keycode = translate_evdev_keycode(gdk_keycode - 97); +        } else { +            qemu_keycode = translate_xfree86_keycode(gdk_keycode - 97); +        } +#endif +    } else if (gdk_keycode == 208) { /* Hiragana_Katakana */ +        qemu_keycode = 0x70; +    } else if (gdk_keycode == 211) { /* backslash */ +        qemu_keycode = 0x73; +    } else { +        qemu_keycode = 0; +    } + +    return qemu_keycode; +} + +static gboolean gd_text_key_down(GtkWidget *widget, +                                 GdkEventKey *key, void *opaque) +{ +    VirtualConsole *vc = opaque; +    QemuConsole *con = vc->gfx.dcl.con; + +    if (key->length) { +        kbd_put_string_console(con, key->string, key->length); +    } else { +        int num = gd_map_keycode(vc->s, gtk_widget_get_display(widget), +                                 key->hardware_keycode); +        int qcode = qemu_input_key_number_to_qcode(num); +        kbd_put_qcode_console(con, qcode); +    } +    return TRUE; +} + +static gboolean gd_key_event(GtkWidget *widget, GdkEventKey *key, void *opaque) +{ +    VirtualConsole *vc = opaque; +    GtkDisplayState *s = vc->s; +    int gdk_keycode = key->hardware_keycode; +    int qemu_keycode; +    int i; + +    if (s->ignore_keys) { +        s->ignore_keys = (key->type == GDK_KEY_PRESS); +        return TRUE; +    } + +    if (key->keyval == GDK_KEY_Pause) { +        qemu_input_event_send_key_qcode(vc->gfx.dcl.con, Q_KEY_CODE_PAUSE, +                                        key->type == GDK_KEY_PRESS); +        return TRUE; +    } + +    qemu_keycode = gd_map_keycode(s, gtk_widget_get_display(widget), +                                  gdk_keycode); + +    trace_gd_key_event(vc->label, gdk_keycode, qemu_keycode, +                       (key->type == GDK_KEY_PRESS) ? "down" : "up"); + +    for (i = 0; i < ARRAY_SIZE(modifier_keycode); i++) { +        if (qemu_keycode == modifier_keycode[i]) { +            s->modifier_pressed[i] = (key->type == GDK_KEY_PRESS); +        } +    } + +    qemu_input_event_send_key_number(vc->gfx.dcl.con, qemu_keycode, +                                     key->type == GDK_KEY_PRESS); + +    return TRUE; +} + +static gboolean gd_event(GtkWidget *widget, GdkEvent *event, void *opaque) +{ +    if (event->type == GDK_MOTION_NOTIFY) { +        return gd_motion_event(widget, &event->motion, opaque); +    } +    return FALSE; +} + +/** Window Menu Actions **/ + +static void gd_menu_pause(GtkMenuItem *item, void *opaque) +{ +    GtkDisplayState *s = opaque; + +    if (s->external_pause_update) { +        return; +    } +    if (runstate_is_running()) { +        qmp_stop(NULL); +    } else { +        qmp_cont(NULL); +    } +} + +static void gd_menu_reset(GtkMenuItem *item, void *opaque) +{ +    qmp_system_reset(NULL); +} + +static void gd_menu_powerdown(GtkMenuItem *item, void *opaque) +{ +    qmp_system_powerdown(NULL); +} + +static void gd_menu_quit(GtkMenuItem *item, void *opaque) +{ +    qmp_quit(NULL); +} + +static void gd_menu_switch_vc(GtkMenuItem *item, void *opaque) +{ +    GtkDisplayState *s = opaque; +    VirtualConsole *vc = gd_vc_find_by_menu(s); +    GtkNotebook *nb = GTK_NOTEBOOK(s->notebook); +    gint page; + +    gtk_release_modifiers(s); +    if (vc) { +        page = gtk_notebook_page_num(nb, vc->tab_item); +        gtk_notebook_set_current_page(nb, page); +        gtk_widget_grab_focus(vc->focus); +    } +    s->ignore_keys = false; +} + +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); +#if !GTK_CHECK_VERSION(3, 0, 0) +    /* GTK2 sends the accel key to the target console - ignore this until */ +    vc->s->ignore_keys = true; +#endif +} + +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); +    } +    gd_update_caption(vc->s); +    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_full_screen(GtkMenuItem *item, void *opaque) +{ +    GtkDisplayState *s = opaque; +    VirtualConsole *vc = gd_vc_find_current(s); + +    if (!s->full_screen) { +        gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE); +        gtk_widget_hide(s->menu_bar); +        if (vc->type == GD_VC_GFX) { +            gtk_widget_set_size_request(vc->gfx.drawing_area, -1, -1); +            if (qemu_console_is_graphic(vc->gfx.dcl.con)) { +                gtk_check_menu_item_set_active +                    (GTK_CHECK_MENU_ITEM(s->grab_item), TRUE); +            } +        } +        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_show(s->menu_bar); +        s->full_screen = FALSE; +        if (vc->type == GD_VC_GFX) { +            vc->gfx.scale_x = 1.0; +            vc->gfx.scale_y = 1.0; +            gd_update_windowsize(vc); +            gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item), +                                           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); + +    vc->gfx.scale_x += VC_SCALE_STEP; +    vc->gfx.scale_y += VC_SCALE_STEP; + +    gd_update_windowsize(vc); +} + +static void gd_menu_zoom_out(GtkMenuItem *item, void *opaque) +{ +    GtkDisplayState *s = opaque; +    VirtualConsole *vc = gd_vc_find_current(s); + +    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item), +                                   FALSE); + +    vc->gfx.scale_x -= VC_SCALE_STEP; +    vc->gfx.scale_y -= VC_SCALE_STEP; + +    vc->gfx.scale_x = MAX(vc->gfx.scale_x, VC_SCALE_MIN); +    vc->gfx.scale_y = MAX(vc->gfx.scale_y, VC_SCALE_MIN); + +    gd_update_windowsize(vc); +} + +static void gd_menu_zoom_fixed(GtkMenuItem *item, void *opaque) +{ +    GtkDisplayState *s = opaque; +    VirtualConsole *vc = gd_vc_find_current(s); + +    vc->gfx.scale_x = 1.0; +    vc->gfx.scale_y = 1.0; + +    gd_update_windowsize(vc); +} + +static void gd_menu_zoom_fit(GtkMenuItem *item, void *opaque) +{ +    GtkDisplayState *s = opaque; +    VirtualConsole *vc = gd_vc_find_current(s); + +    if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(s->zoom_fit_item))) { +        s->free_scale = TRUE; +    } else { +        s->free_scale = FALSE; +        vc->gfx.scale_x = 1.0; +        vc->gfx.scale_y = 1.0; +    } + +    gd_update_windowsize(vc); +    gd_update_full_redraw(vc); +} + +#if GTK_CHECK_VERSION(3, 0, 0) +static void gd_grab_devices(VirtualConsole *vc, bool grab, +                            GdkInputSource source, GdkEventMask mask, +                            GdkCursor *cursor) +{ +    GdkDisplay *display = gtk_widget_get_display(vc->gfx.drawing_area); +    GdkDeviceManager *mgr = gdk_display_get_device_manager(display); +    GList *devs = gdk_device_manager_list_devices(mgr, GDK_DEVICE_TYPE_MASTER); +    GList *tmp = devs; + +    for (tmp = devs; tmp; tmp = tmp->next) { +        GdkDevice *dev = tmp->data; +        if (gdk_device_get_source(dev) != source) { +            continue; +        } +        if (grab) { +            GdkWindow *win = gtk_widget_get_window(vc->gfx.drawing_area); +            gdk_device_grab(dev, win, GDK_OWNERSHIP_NONE, FALSE, +                            mask, cursor, GDK_CURRENT_TIME); +        } else { +            gdk_device_ungrab(dev, GDK_CURRENT_TIME); +        } +    } +    g_list_free(devs); +} +#endif + +static void gd_grab_keyboard(VirtualConsole *vc) +{ +#if GTK_CHECK_VERSION(3, 0, 0) +    gd_grab_devices(vc, true, GDK_SOURCE_KEYBOARD, +                   GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK, +                   NULL); +#else +    gdk_keyboard_grab(gtk_widget_get_window(vc->gfx.drawing_area), +                      FALSE, +                      GDK_CURRENT_TIME); +#endif +    vc->s->kbd_owner = vc; +    trace_gd_grab(vc->label, "kbd", true); +} + +static void gd_ungrab_keyboard(GtkDisplayState *s) +{ +    VirtualConsole *vc = s->kbd_owner; + +    if (vc == NULL) { +        return; +    } +    s->kbd_owner = NULL; + +#if GTK_CHECK_VERSION(3, 0, 0) +    gd_grab_devices(vc, false, GDK_SOURCE_KEYBOARD, 0, NULL); +#else +    gdk_keyboard_ungrab(GDK_CURRENT_TIME); +#endif +    trace_gd_grab(vc->label, "kbd", false); +} + +static void gd_grab_pointer(VirtualConsole *vc) +{ +    GdkDisplay *display = gtk_widget_get_display(vc->gfx.drawing_area); +#if GTK_CHECK_VERSION(3, 0, 0) +    GdkDeviceManager *mgr = gdk_display_get_device_manager(display); +    gd_grab_devices(vc, true, GDK_SOURCE_MOUSE, +                    GDK_POINTER_MOTION_MASK | +                    GDK_BUTTON_PRESS_MASK | +                    GDK_BUTTON_RELEASE_MASK | +                    GDK_BUTTON_MOTION_MASK | +                    GDK_SCROLL_MASK, +                    vc->s->null_cursor); +    gdk_device_get_position(gdk_device_manager_get_client_pointer(mgr), +                            NULL, &vc->s->grab_x_root, &vc->s->grab_y_root); +#else +    gdk_pointer_grab(gtk_widget_get_window(vc->gfx.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 */ +                     vc->s->null_cursor, +                     GDK_CURRENT_TIME); +    gdk_display_get_pointer(display, NULL, +                            &vc->s->grab_x_root, &vc->s->grab_y_root, NULL); +#endif +    vc->s->ptr_owner = vc; +    trace_gd_grab(vc->label, "ptr", true); +} + +static void gd_ungrab_pointer(GtkDisplayState *s) +{ +    VirtualConsole *vc = s->ptr_owner; + +    if (vc == NULL) { +        return; +    } +    s->ptr_owner = NULL; + +    GdkDisplay *display = gtk_widget_get_display(vc->gfx.drawing_area); +#if GTK_CHECK_VERSION(3, 0, 0) +    GdkDeviceManager *mgr = gdk_display_get_device_manager(display); +    gd_grab_devices(vc, false, GDK_SOURCE_MOUSE, 0, NULL); +    gdk_device_warp(gdk_device_manager_get_client_pointer(mgr), +                    gtk_widget_get_screen(vc->gfx.drawing_area), +                    vc->s->grab_x_root, vc->s->grab_y_root); +#else +    gdk_pointer_ungrab(GDK_CURRENT_TIME); +    gdk_display_warp_pointer(display, +                             gtk_widget_get_screen(vc->gfx.drawing_area), +                             vc->s->grab_x_root, vc->s->grab_y_root); +#endif +    trace_gd_grab(vc->label, "ptr", false); +} + +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)) { +        if (!gd_grab_on_hover(s)) { +            gd_grab_keyboard(vc); +        } +        gd_grab_pointer(vc); +    } else { +        gd_ungrab_keyboard(s); +        gd_ungrab_pointer(s); +    } + +    gd_update_caption(s); +    gd_update_cursor(vc); +} + +static void gd_change_page(GtkNotebook *nb, gpointer arg1, guint arg2, +                           gpointer data) +{ +    GtkDisplayState *s = data; +    VirtualConsole *vc; +    gboolean on_vga; + +    if (!gtk_widget_get_realized(s->notebook)) { +        return; +    } + +#ifdef VTE_RESIZE_HACK +    vc = gd_vc_find_current(s); +    if (vc && vc->type == GD_VC_VTE) { +        gtk_widget_hide(vc->vte.terminal); +    } +#endif +    vc = gd_vc_find_by_page(s, arg2); +    if (!vc) { +        return; +    } +#ifdef VTE_RESIZE_HACK +    if (vc->type == GD_VC_VTE) { +        gtk_widget_show(vc->vte.terminal); +    } +#endif +    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(vc->menu_item), +                                   TRUE); +    on_vga = (vc->type == GD_VC_GFX && +              qemu_console_is_graphic(vc->gfx.dcl.con)); +    if (!on_vga) { +        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item), +                                       FALSE); +    } else if (s->full_screen) { +        gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item), +                                       TRUE); +    } +    gtk_widget_set_sensitive(s->grab_item, on_vga); + +    gd_update_windowsize(vc); +    gd_update_cursor(vc); +} + +static gboolean gd_enter_event(GtkWidget *widget, GdkEventCrossing *crossing, +                               gpointer opaque) +{ +    VirtualConsole *vc = opaque; +    GtkDisplayState *s = vc->s; + +    if (gd_grab_on_hover(s)) { +        gd_ungrab_keyboard(s); +        gd_grab_keyboard(vc); +        gd_update_caption(s); +    } +    return TRUE; +} + +static gboolean gd_leave_event(GtkWidget *widget, GdkEventCrossing *crossing, +                               gpointer opaque) +{ +    VirtualConsole *vc = opaque; +    GtkDisplayState *s = vc->s; + +    if (gd_grab_on_hover(s)) { +        gd_ungrab_keyboard(s); +        gd_update_caption(s); +    } +    return TRUE; +} + +static gboolean gd_focus_out_event(GtkWidget *widget, +                                   GdkEventCrossing *crossing, gpointer opaque) +{ +    VirtualConsole *vc = opaque; +    GtkDisplayState *s = vc->s; + +    gtk_release_modifiers(s); +    return TRUE; +} + +static gboolean gd_configure(GtkWidget *widget, +                             GdkEventConfigure *cfg, gpointer opaque) +{ +    VirtualConsole *vc = opaque; +    QemuUIInfo info; + +    memset(&info, 0, sizeof(info)); +    info.width = cfg->width; +    info.height = cfg->height; +    dpy_set_ui_info(vc->gfx.dcl.con, &info); +    return FALSE; +} + +/** Virtual Console Callbacks **/ + +static GSList *gd_vc_menu_init(GtkDisplayState *s, VirtualConsole *vc, +                               int idx, GSList *group, GtkWidget *view_menu) +{ +    vc->menu_item = gtk_radio_menu_item_new_with_mnemonic(group, vc->label); +    gtk_accel_group_connect(s->accel_group, GDK_KEY_1 + idx, +            HOTKEY_MODIFIERS, 0, +            g_cclosure_new_swap(G_CALLBACK(gd_accel_switch_vc), vc, NULL)); +#if GTK_CHECK_VERSION(3, 8, 0) +    gtk_accel_label_set_accel( +            GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(vc->menu_item))), +            GDK_KEY_1 + idx, HOTKEY_MODIFIERS); +#endif + +    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); + +    group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(vc->menu_item)); +    return group; +} + +#if defined(CONFIG_VTE) +static void gd_vc_adjustment_changed(GtkAdjustment *adjustment, void *opaque) +{ +    VirtualConsole *vc = opaque; + +    if (gtk_adjustment_get_upper(adjustment) > +        gtk_adjustment_get_page_size(adjustment)) { +        gtk_widget_show(vc->vte.scrollbar); +    } else { +        gtk_widget_hide(vc->vte.scrollbar); +    } +} + +static int gd_vc_chr_write(CharDriverState *chr, const uint8_t *buf, int len) +{ +    VirtualConsole *vc = chr->opaque; + +    vte_terminal_feed(VTE_TERMINAL(vc->vte.terminal), (const char *)buf, len); +    return len; +} + +static int nb_vcs; +static CharDriverState *vcs[MAX_VCS]; + +static CharDriverState *gd_vc_handler(ChardevVC *unused) +{ +    CharDriverState *chr; + +    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; + +    vcs[nb_vcs++] = chr; + +    return chr; +} + +static gboolean gd_vc_in(VteTerminal *terminal, gchar *text, guint size, +                         gpointer user_data) +{ +    VirtualConsole *vc = user_data; + +    qemu_chr_be_write(vc->vte.chr, (uint8_t  *)text, (unsigned int)size); +    return TRUE; +} + +static GSList *gd_vc_vte_init(GtkDisplayState *s, VirtualConsole *vc, +                              CharDriverState *chr, int idx, +                              GSList *group, GtkWidget *view_menu) +{ +    char buffer[32]; +    GtkWidget *box; +    GtkWidget *scrollbar; +    GtkAdjustment *vadjustment; + +    vc->s = s; +    vc->vte.chr = chr; + +    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); + +    vte_terminal_set_scrollback_lines(VTE_TERMINAL(vc->vte.terminal), -1); +    vte_terminal_set_size(VTE_TERMINAL(vc->vte.terminal), +                          VC_TERM_X_MIN, VC_TERM_Y_MIN); + +#if VTE_CHECK_VERSION(0, 28, 0) && GTK_CHECK_VERSION(3, 0, 0) +    vadjustment = gtk_scrollable_get_vadjustment +        (GTK_SCROLLABLE(vc->vte.terminal)); +#else +    vadjustment = vte_terminal_get_adjustment(VTE_TERMINAL(vc->vte.terminal)); +#endif + +#if GTK_CHECK_VERSION(3, 0, 0) +    box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 2); +    scrollbar = gtk_scrollbar_new(GTK_ORIENTATION_VERTICAL, vadjustment); +#else +    box = gtk_hbox_new(false, 2); +    scrollbar = gtk_vscrollbar_new(vadjustment); +#endif + +    gtk_box_pack_start(GTK_BOX(box), vc->vte.terminal, TRUE, TRUE, 0); +    gtk_box_pack_start(GTK_BOX(box), scrollbar, FALSE, FALSE, 0); + +    vc->vte.chr->opaque = vc; +    vc->vte.box = box; +    vc->vte.scrollbar = scrollbar; + +    g_signal_connect(vadjustment, "changed", +                     G_CALLBACK(gd_vc_adjustment_changed), vc); + +    vc->type = GD_VC_VTE; +    vc->tab_item = box; +    vc->focus = vc->vte.terminal; +    gtk_notebook_append_page(GTK_NOTEBOOK(s->notebook), vc->tab_item, +                             gtk_label_new(vc->label)); + +    qemu_chr_be_generic_open(vc->vte.chr); +    if (vc->vte.chr->init) { +        vc->vte.chr->init(vc->vte.chr); +    } + +    return group; +} + +static void gd_vcs_init(GtkDisplayState *s, GSList *group, +                        GtkWidget *view_menu) +{ +    int i; + +    for (i = 0; i < nb_vcs; i++) { +        VirtualConsole *vc = &s->vc[s->nb_vcs]; +        group = gd_vc_vte_init(s, vc, vcs[i], s->nb_vcs, group, view_menu); +        s->nb_vcs++; +    } +} +#endif /* CONFIG_VTE */ + +/** Window Creation **/ + +static void gd_connect_vc_gfx_signals(VirtualConsole *vc) +{ +#if GTK_CHECK_VERSION(3, 0, 0) +    g_signal_connect(vc->gfx.drawing_area, "draw", +                     G_CALLBACK(gd_draw_event), vc); +#else +    g_signal_connect(vc->gfx.drawing_area, "expose-event", +                     G_CALLBACK(gd_expose_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-out-event", +                         G_CALLBACK(gd_focus_out_event), vc); +        g_signal_connect(vc->gfx.drawing_area, "configure-event", +                         G_CALLBACK(gd_configure), 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->window, "delete-event", +                     G_CALLBACK(gd_window_close), s); + +    g_signal_connect(s->pause_item, "activate", +                     G_CALLBACK(gd_menu_pause), s); +    g_signal_connect(s->reset_item, "activate", +                     G_CALLBACK(gd_menu_reset), s); +    g_signal_connect(s->powerdown_item, "activate", +                     G_CALLBACK(gd_menu_powerdown), s); +    g_signal_connect(s->quit_item, "activate", +                     G_CALLBACK(gd_menu_quit), s); +    g_signal_connect(s->full_screen_item, "activate", +                     G_CALLBACK(gd_menu_full_screen), s); +    g_signal_connect(s->zoom_in_item, "activate", +                     G_CALLBACK(gd_menu_zoom_in), s); +    g_signal_connect(s->zoom_out_item, "activate", +                     G_CALLBACK(gd_menu_zoom_out), s); +    g_signal_connect(s->zoom_fixed_item, "activate", +                     G_CALLBACK(gd_menu_zoom_fixed), s); +    g_signal_connect(s->zoom_fit_item, "activate", +                     G_CALLBACK(gd_menu_zoom_fit), s); +    g_signal_connect(s->grab_item, "activate", +                     G_CALLBACK(gd_menu_grab_input), s); +    g_signal_connect(s->notebook, "switch-page", +                     G_CALLBACK(gd_change_page), s); +} + +static GtkWidget *gd_create_menu_machine(GtkDisplayState *s) +{ +    GtkWidget *machine_menu; +    GtkWidget *separator; + +    machine_menu = gtk_menu_new(); +    gtk_menu_set_accel_group(GTK_MENU(machine_menu), s->accel_group); + +    s->pause_item = gtk_check_menu_item_new_with_mnemonic(_("_Pause")); +    gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->pause_item); + +    separator = gtk_separator_menu_item_new(); +    gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), separator); + +    s->reset_item = gtk_menu_item_new_with_mnemonic(_("_Reset")); +    gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->reset_item); + +    s->powerdown_item = gtk_menu_item_new_with_mnemonic(_("Power _Down")); +    gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->powerdown_item); + +    separator = gtk_separator_menu_item_new(); +    gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), separator); + +    s->quit_item = gtk_menu_item_new_with_mnemonic(_("_Quit")); +    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->quit_item), +                                 "<QEMU>/Machine/Quit"); +    gtk_accel_map_add_entry("<QEMU>/Machine/Quit", +                            GDK_KEY_q, HOTKEY_MODIFIERS); +    gtk_menu_shell_append(GTK_MENU_SHELL(machine_menu), s->quit_item); + +    return machine_menu; +} + +static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc, +                              QemuConsole *con, int idx, +                              GSList *group, GtkWidget *view_menu) +{ +    vc->label = qemu_console_get_label(con); +    vc->s = s; +    vc->gfx.scale_x = 1.0; +    vc->gfx.scale_y = 1.0; + +    vc->gfx.drawing_area = gtk_drawing_area_new(); +    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_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)); + +#if defined(CONFIG_OPENGL) +    if (display_opengl) { +        /* +         * 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. +         */ +#ifdef CONFIG_PRAGMA_DIAGNOSTIC_AVAILABLE +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif +        gtk_widget_set_double_buffered(vc->gfx.drawing_area, FALSE); +#ifdef CONFIG_PRAGMA_DIAGNOSTIC_AVAILABLE +#pragma GCC diagnostic pop +#endif +        vc->gfx.dcl.ops = &dcl_egl_ops; +    } else +#endif +    { +        vc->gfx.dcl.ops = &dcl_ops; +    } + +    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)) { +        gtk_menu_item_activate(GTK_MENU_ITEM(s->zoom_fit_item)); +    } + +    return group; +} + +static GtkWidget *gd_create_menu_view(GtkDisplayState *s) +{ +    GSList *group = NULL; +    GtkWidget *view_menu; +    GtkWidget *separator; +    QemuConsole *con; +    int vc; + +    view_menu = gtk_menu_new(); +    gtk_menu_set_accel_group(GTK_MENU(view_menu), s->accel_group); + +    s->full_screen_item = gtk_menu_item_new_with_mnemonic(_("_Fullscreen")); + +    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)); +#if GTK_CHECK_VERSION(3, 8, 0) +    gtk_accel_label_set_accel( +            GTK_ACCEL_LABEL(gtk_bin_get_child(GTK_BIN(s->full_screen_item))), +            GDK_KEY_f, HOTKEY_MODIFIERS); +#endif +    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->full_screen_item); + +    separator = gtk_separator_menu_item_new(); +    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator); + +    s->zoom_in_item = gtk_menu_item_new_with_mnemonic(_("Zoom _In")); +    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_in_item), +                                 "<QEMU>/View/Zoom In"); +    gtk_accel_map_add_entry("<QEMU>/View/Zoom In", GDK_KEY_plus, +                            HOTKEY_MODIFIERS); +    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_in_item); + +    s->zoom_out_item = gtk_menu_item_new_with_mnemonic(_("Zoom _Out")); +    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_out_item), +                                 "<QEMU>/View/Zoom Out"); +    gtk_accel_map_add_entry("<QEMU>/View/Zoom Out", GDK_KEY_minus, +                            HOTKEY_MODIFIERS); +    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_out_item); + +    s->zoom_fixed_item = gtk_menu_item_new_with_mnemonic(_("Best _Fit")); +    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->zoom_fixed_item), +                                 "<QEMU>/View/Zoom Fixed"); +    gtk_accel_map_add_entry("<QEMU>/View/Zoom Fixed", GDK_KEY_0, +                            HOTKEY_MODIFIERS); +    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_fixed_item); + +    s->zoom_fit_item = gtk_check_menu_item_new_with_mnemonic(_("Zoom To _Fit")); +    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->zoom_fit_item); + +    separator = gtk_separator_menu_item_new(); +    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator); + +    s->grab_on_hover_item = gtk_check_menu_item_new_with_mnemonic(_("Grab On _Hover")); +    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->grab_on_hover_item); + +    s->grab_item = gtk_check_menu_item_new_with_mnemonic(_("_Grab Input")); +    gtk_menu_item_set_accel_path(GTK_MENU_ITEM(s->grab_item), +                                 "<QEMU>/View/Grab Input"); +    gtk_accel_map_add_entry("<QEMU>/View/Grab Input", GDK_KEY_g, +                            HOTKEY_MODIFIERS); +    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->grab_item); + +    separator = gtk_separator_menu_item_new(); +    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator); + +    /* gfx */ +    for (vc = 0;; vc++) { +        con = qemu_console_lookup_by_index(vc); +        if (!con) { +            break; +        } +        group = gd_vc_gfx_init(s, &s->vc[vc], con, +                               vc, group, view_menu); +        s->nb_vcs++; +    } + +#if defined(CONFIG_VTE) +    /* vte */ +    gd_vcs_init(s, group, view_menu); +#endif + +    separator = gtk_separator_menu_item_new(); +    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), separator); + +    s->show_tabs_item = gtk_check_menu_item_new_with_mnemonic(_("Show _Tabs")); +    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->show_tabs_item); + +    s->untabify_item = gtk_menu_item_new_with_mnemonic(_("Detach Tab")); +    gtk_menu_shell_append(GTK_MENU_SHELL(view_menu), s->untabify_item); + +    return view_menu; +} + +static void gd_create_menus(GtkDisplayState *s) +{ +    s->accel_group = gtk_accel_group_new(); +    s->machine_menu = gd_create_menu_machine(s); +    s->view_menu = gd_create_menu_view(s); + +    s->machine_menu_item = gtk_menu_item_new_with_mnemonic(_("_Machine")); +    gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->machine_menu_item), +                              s->machine_menu); +    gtk_menu_shell_append(GTK_MENU_SHELL(s->menu_bar), s->machine_menu_item); + +    s->view_menu_item = gtk_menu_item_new_with_mnemonic(_("_View")); +    gtk_menu_item_set_submenu(GTK_MENU_ITEM(s->view_menu_item), s->view_menu); +    gtk_menu_shell_append(GTK_MENU_SHELL(s->menu_bar), s->view_menu_item); + +    g_object_set_data(G_OBJECT(s->window), "accel_group", s->accel_group); +    gtk_window_add_accel_group(GTK_WINDOW(s->window), s->accel_group); +} + +static void gd_set_keycode_type(GtkDisplayState *s) +{ +#ifdef GDK_WINDOWING_X11 +    GdkDisplay *display = gtk_widget_get_display(s->window); +    if (GDK_IS_X11_DISPLAY(display)) { +        Display *x11_display = gdk_x11_display_get_xdisplay(display); +        XkbDescPtr desc = XkbGetKeyboard(x11_display, XkbGBN_AllComponentsMask, +                                         XkbUseCoreKbd); +        char *keycodes = NULL; + +        if (desc && desc->names) { +            keycodes = XGetAtomName(x11_display, desc->names->keycodes); +        } +        if (keycodes == NULL) { +            fprintf(stderr, "could not lookup keycode name\n"); +        } else if (strstart(keycodes, "evdev", NULL)) { +            s->has_evdev = true; +        } 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); +        } +    } +#endif +} + +static gboolean gtkinit; + +void gtk_display_init(DisplayState *ds, bool full_screen, bool grab_on_hover) +{ +    GtkDisplayState *s = g_malloc0(sizeof(*s)); +    char *filename; +    GdkDisplay *window_display; + +    if (!gtkinit) { +        fprintf(stderr, "gtk initialization failed\n"); +        exit(1); +    } + +    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->menu_bar = gtk_menu_bar_new(); + +    s->free_scale = FALSE; + +    /* LC_MESSAGES only. See early_gtk_display_init() for details */ +    setlocale(LC_MESSAGES, ""); +    bindtextdomain("qemu", CONFIG_QEMU_LOCALEDIR); +    textdomain("qemu"); + +    window_display = gtk_widget_get_display(s->window); +    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); + +    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); +    } + +    gd_create_menus(s); + +    gd_connect_signals(s); + +    gtk_notebook_set_show_tabs(GTK_NOTEBOOK(s->notebook), FALSE); +    gtk_notebook_set_show_border(GTK_NOTEBOOK(s->notebook), FALSE); + +    gd_update_caption(s); + +    gtk_box_pack_start(GTK_BOX(s->vbox), s->menu_bar, FALSE, TRUE, 0); +    gtk_box_pack_start(GTK_BOX(s->vbox), s->notebook, TRUE, TRUE, 0); + +    gtk_container_add(GTK_CONTAINER(s->window), s->vbox); + +    gtk_widget_show_all(s->window); + +#ifdef VTE_RESIZE_HACK +    { +        VirtualConsole *cur = gd_vc_find_current(s); +        if (cur) { +            int i; + +            for (i = 0; i < s->nb_vcs; i++) { +                VirtualConsole *vc = &s->vc[i]; +                if (vc && vc->type == GD_VC_VTE && vc != cur) { +                    gtk_widget_hide(vc->vte.terminal); +                } +            } +            gd_update_windowsize(cur); +        } +    } +#endif + +    if (full_screen) { +        gtk_menu_item_activate(GTK_MENU_ITEM(s->full_screen_item)); +    } +    if (grab_on_hover) { +        gtk_menu_item_activate(GTK_MENU_ITEM(s->grab_on_hover_item)); +    } + +    gd_set_keycode_type(s); +} + +void early_gtk_display_init(int opengl) +{ +    /* 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; +    } + +    switch (opengl) { +    case -1: /* default */ +    case 0:  /* off */ +        break; +    case 1: /* on */ +#if defined(CONFIG_OPENGL) +        gtk_egl_init(); +#endif +        break; +    default: +        g_assert_not_reached(); +        break; +    } + +#if defined(CONFIG_VTE) +    register_vc_handler(gd_vc_handler); +#endif +} diff --git a/ui/input-keymap.c b/ui/input-keymap.c new file mode 100644 index 00000000..7635cb0d --- /dev/null +++ b/ui/input-keymap.c @@ -0,0 +1,202 @@ +#include "sysemu/sysemu.h" +#include "ui/keymaps.h" +#include "ui/input.h" + +static const int qcode_to_number[] = { +    [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_META_L] = 0xdb, +    [Q_KEY_CODE_META_R] = 0xdc, +    [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, + +    [Q_KEY_CODE_RO] = 0x73, +    [Q_KEY_CODE_KP_COMMA] = 0x7e, + +    [Q_KEY_CODE_MAX] = 0, +}; + +static int number_to_qcode[0x100]; + +int qemu_input_key_value_to_number(const KeyValue *value) +{ +    if (value->kind == KEY_VALUE_KIND_QCODE) { +        return qcode_to_number[value->qcode]; +    } else { +        assert(value->kind == KEY_VALUE_KIND_NUMBER); +        return value->number; +    } +} + +int qemu_input_key_number_to_qcode(uint8_t nr) +{ +    static int first = true; + +    if (first) { +        int qcode, number; +        first = false; +        for (qcode = 0; qcode < Q_KEY_CODE_MAX; qcode++) { +            number = qcode_to_number[qcode]; +            assert(number < ARRAY_SIZE(number_to_qcode)); +            number_to_qcode[number] = qcode; +        } +    } + +    return number_to_qcode[nr]; +} + +int qemu_input_key_value_to_qcode(const KeyValue *value) +{ +    if (value->kind == KEY_VALUE_KIND_QCODE) { +        return value->qcode; +    } else { +        assert(value->kind == KEY_VALUE_KIND_NUMBER); +        return qemu_input_key_number_to_qcode(value->number); +    } +} + +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->kind == KEY_VALUE_KIND_QCODE && +        value->qcode == 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 00000000..e50f2968 --- /dev/null +++ b/ui/input-legacy.c @@ -0,0 +1,265 @@ +/* + * 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 "sysemu/sysemu.h" +#include "ui/console.h" +#include "qapi/error.h" +#include "qmp-commands.h" +#include "qapi-types.h" +#include "ui/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) +{ +    int i; + +    for (i = 0; QKeyCode_lookup[i] != NULL; i++) { +        if (!strcmp(key, QKeyCode_lookup[i])) { +            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)); +    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; + +    if (!entry || !entry->put_kbd) { +        return; +    } +    count = qemu_input_key_value_to_scancode(evt->key->key, +                                             evt->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; + +    switch (evt->kind) { +    case INPUT_EVENT_KIND_BTN: +        if (evt->btn->down) { +            s->buttons |= bmap[evt->btn->button]; +        } else { +            s->buttons &= ~bmap[evt->btn->button]; +        } +        if (evt->btn->down && evt->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 (evt->btn->down && evt->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: +        s->axis[evt->abs->axis] = evt->abs->value; +        break; +    case INPUT_EVENT_KIND_REL: +        s->axis[evt->rel->axis] += evt->rel->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_malloc0(sizeof(QEMUPutMouseEntry)); + +    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_malloc0(sizeof(QEMUPutLEDEntry)); + +    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.c b/ui/input.c new file mode 100644 index 00000000..1a552d1d --- /dev/null +++ b/ui/input.c @@ -0,0 +1,556 @@ +#include "hw/qdev.h" +#include "sysemu/sysemu.h" +#include "qapi-types.h" +#include "qemu/error-report.h" +#include "qmp-commands.h" +#include "trace.h" +#include "ui/input.h" +#include "ui/console.h" + +struct QemuInputHandlerState { +    DeviceState       *dev; +    QemuInputHandler  *handler; +    int               id; +    int               events; +    QemuConsole       *con; +    QTAILQ_ENTRY(QemuInputHandlerState) node; +}; + +typedef struct QemuInputEventQueue QemuInputEventQueue; +struct QemuInputEventQueue { +    enum { +        QEMU_INPUT_QUEUE_DELAY = 1, +        QEMU_INPUT_QUEUE_EVENT, +        QEMU_INPUT_QUEUE_SYNC, +    } type; +    QEMUTimer *timer; +    uint32_t delay_ms; +    QemuConsole *src; +    InputEvent *evt; +    QTAILQ_ENTRY(QemuInputEventQueue) node; +}; + +static QTAILQ_HEAD(, QemuInputHandlerState) handlers = +    QTAILQ_HEAD_INITIALIZER(handlers); +static NotifierList mouse_mode_notifiers = +    NOTIFIER_LIST_INITIALIZER(mouse_mode_notifiers); + +static QTAILQ_HEAD(QemuInputEventQueueHead, QemuInputEventQueue) kbd_queue = +    QTAILQ_HEAD_INITIALIZER(kbd_queue); +static QEMUTimer *kbd_timer; +static uint32_t kbd_default_delay_ms = 10; + +QemuInputHandlerState *qemu_input_handler_register(DeviceState *dev, +                                                   QemuInputHandler *handler) +{ +    QemuInputHandlerState *s = g_new0(QemuInputHandlerState, 1); +    static int id = 1; + +    s->dev = dev; +    s->handler = handler; +    s->id = id++; +    QTAILQ_INSERT_TAIL(&handlers, s, node); + +    qemu_input_check_mode_change(); +    return s; +} + +void qemu_input_handler_activate(QemuInputHandlerState *s) +{ +    QTAILQ_REMOVE(&handlers, s, node); +    QTAILQ_INSERT_HEAD(&handlers, s, node); +    qemu_input_check_mode_change(); +} + +void qemu_input_handler_deactivate(QemuInputHandlerState *s) +{ +    QTAILQ_REMOVE(&handlers, s, node); +    QTAILQ_INSERT_TAIL(&handlers, s, node); +    qemu_input_check_mode_change(); +} + +void qemu_input_handler_unregister(QemuInputHandlerState *s) +{ +    QTAILQ_REMOVE(&handlers, s, node); +    g_free(s); +    qemu_input_check_mode_change(); +} + +void qemu_input_handler_bind(QemuInputHandlerState *s, +                             const char *device_id, int head, +                             Error **errp) +{ +    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; +    } + +    con = qemu_console_lookup_by_device(dev, head); +    if (con == NULL) { +        error_setg(errp, "Device %s is not bound to a QemuConsole", device_id); +        return; +    } + +    s->con = con; +} + +static QemuInputHandlerState* +qemu_input_find_handler(uint32_t mask, QemuConsole *con) +{ +    QemuInputHandlerState *s; + +    QTAILQ_FOREACH(s, &handlers, node) { +        if (s->con == NULL || s->con != con) { +            continue; +        } +        if (mask & s->handler->mask) { +            return s; +        } +    } + +    QTAILQ_FOREACH(s, &handlers, node) { +        if (s->con != NULL) { +            continue; +        } +        if (mask & s->handler->mask) { +            return s; +        } +    } +    return NULL; +} + +void qmp_x_input_send_event(bool has_console, int64_t console, +                            InputEventList *events, Error **errp) +{ +    InputEventList *e; +    QemuConsole *con; + +    con = NULL; +    if (has_console) { +        con = qemu_console_lookup_by_index(console); +        if (!con) { +            error_setg(errp, "console %" PRId64 " not found", console); +            return; +        } +    } + +    if (!runstate_is_running() && !runstate_check(RUN_STATE_SUSPENDED)) { +        error_setg(errp, "VM not running"); +        return; +    } + +    for (e = events; e != NULL; e = e->next) { +        InputEvent *event = e->value; + +        if (!qemu_input_find_handler(1 << event->kind, con)) { +            error_setg(errp, "Input handler not found for " +                             "event type %s", +                            InputEventKind_lookup[event->kind]); +            return; +        } +    } + +    for (e = events; e != NULL; e = e->next) { +        InputEvent *event = e->value; + +        qemu_input_event_send(con, event); +    } + +    qemu_input_event_sync(); +} + +static void qemu_input_transform_abs_rotate(InputEvent *evt) +{ +    switch (graphic_rotate) { +    case 90: +        if (evt->abs->axis == INPUT_AXIS_X) { +            evt->abs->axis = INPUT_AXIS_Y; +        } else if (evt->abs->axis == INPUT_AXIS_Y) { +            evt->abs->axis = INPUT_AXIS_X; +            evt->abs->value = INPUT_EVENT_ABS_SIZE - 1 - evt->abs->value; +        } +        break; +    case 180: +        evt->abs->value = INPUT_EVENT_ABS_SIZE - 1 - evt->abs->value; +        break; +    case 270: +        if (evt->abs->axis == INPUT_AXIS_X) { +            evt->abs->axis = INPUT_AXIS_Y; +            evt->abs->value = INPUT_EVENT_ABS_SIZE - 1 - evt->abs->value; +        } else if (evt->abs->axis == INPUT_AXIS_Y) { +            evt->abs->axis = INPUT_AXIS_X; +        } +        break; +    } +} + +static void qemu_input_event_trace(QemuConsole *src, InputEvent *evt) +{ +    const char *name; +    int qcode, idx = -1; + +    if (src) { +        idx = qemu_console_get_index(src); +    } +    switch (evt->kind) { +    case INPUT_EVENT_KIND_KEY: +        switch (evt->key->key->kind) { +        case KEY_VALUE_KIND_NUMBER: +            qcode = qemu_input_key_number_to_qcode(evt->key->key->number); +            name = QKeyCode_lookup[qcode]; +            trace_input_event_key_number(idx, evt->key->key->number, +                                         name, evt->key->down); +            break; +        case KEY_VALUE_KIND_QCODE: +            name = QKeyCode_lookup[evt->key->key->qcode]; +            trace_input_event_key_qcode(idx, name, evt->key->down); +            break; +        case KEY_VALUE_KIND_MAX: +            /* keep gcc happy */ +            break; +        } +        break; +    case INPUT_EVENT_KIND_BTN: +        name = InputButton_lookup[evt->btn->button]; +        trace_input_event_btn(idx, name, evt->btn->down); +        break; +    case INPUT_EVENT_KIND_REL: +        name = InputAxis_lookup[evt->rel->axis]; +        trace_input_event_rel(idx, name, evt->rel->value); +        break; +    case INPUT_EVENT_KIND_ABS: +        name = InputAxis_lookup[evt->abs->axis]; +        trace_input_event_abs(idx, name, evt->abs->value); +        break; +    case INPUT_EVENT_KIND_MAX: +        /* keep gcc happy */ +        break; +    } +} + +static void qemu_input_queue_process(void *opaque) +{ +    struct 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); +    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); +        g_free(item); +    } +} + +static void qemu_input_queue_delay(struct 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); + +    if (start_timer) { +        timer_mod(item->timer, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) +                  + item->delay_ms); +    } +} + +static void qemu_input_queue_event(struct 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); +} + +static void qemu_input_queue_sync(struct QemuInputEventQueueHead *queue) +{ +    QemuInputEventQueue *item = g_new0(QemuInputEventQueue, 1); + +    item->type = QEMU_INPUT_QUEUE_SYNC; +    QTAILQ_INSERT_TAIL(queue, item, node); +} + +void qemu_input_event_send(QemuConsole *src, InputEvent *evt) +{ +    QemuInputHandlerState *s; + +    if (!runstate_is_running() && !runstate_check(RUN_STATE_SUSPENDED)) { +        return; +    } + +    qemu_input_event_trace(src, evt); + +    /* pre processing */ +    if (graphic_rotate && (evt->kind == INPUT_EVENT_KIND_ABS)) { +            qemu_input_transform_abs_rotate(evt); +    } + +    /* send event */ +    s = qemu_input_find_handler(1 << evt->kind, src); +    if (!s) { +        return; +    } +    s->handler->event(s->dev, src, evt); +    s->events++; +} + +void qemu_input_event_sync(void) +{ +    QemuInputHandlerState *s; + +    if (!runstate_is_running() && !runstate_check(RUN_STATE_SUSPENDED)) { +        return; +    } + +    trace_input_event_sync(); + +    QTAILQ_FOREACH(s, &handlers, node) { +        if (!s->events) { +            continue; +        } +        if (s->handler->sync) { +            s->handler->sync(s->dev); +        } +        s->events = 0; +    } +} + +InputEvent *qemu_input_event_new_key(KeyValue *key, bool down) +{ +    InputEvent *evt = g_new0(InputEvent, 1); +    evt->key = g_new0(InputKeyEvent, 1); +    evt->kind = INPUT_EVENT_KIND_KEY; +    evt->key->key = key; +    evt->key->down = down; +    return evt; +} + +void qemu_input_event_send_key(QemuConsole *src, KeyValue *key, bool down) +{ +    InputEvent *evt; +    evt = qemu_input_event_new_key(key, down); +    if (QTAILQ_EMPTY(&kbd_queue)) { +        qemu_input_event_send(src, evt); +        qemu_input_event_sync(); +        qapi_free_InputEvent(evt); +    } else { +        qemu_input_queue_event(&kbd_queue, src, evt); +        qemu_input_queue_sync(&kbd_queue); +    } +} + +void qemu_input_event_send_key_number(QemuConsole *src, int num, bool down) +{ +    KeyValue *key = g_new0(KeyValue, 1); +    key->kind = KEY_VALUE_KIND_NUMBER; +    key->number = num; +    qemu_input_event_send_key(src, key, down); +} + +void qemu_input_event_send_key_qcode(QemuConsole *src, QKeyCode q, bool down) +{ +    KeyValue *key = g_new0(KeyValue, 1); +    key->kind = KEY_VALUE_KIND_QCODE; +    key->qcode = q; +    qemu_input_event_send_key(src, key, down); +} + +void qemu_input_event_send_key_delay(uint32_t delay_ms) +{ +    if (!kbd_timer) { +        kbd_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, qemu_input_queue_process, +                                 &kbd_queue); +    } +    qemu_input_queue_delay(&kbd_queue, kbd_timer, +                           delay_ms ? delay_ms : kbd_default_delay_ms); +} + +InputEvent *qemu_input_event_new_btn(InputButton btn, bool down) +{ +    InputEvent *evt = g_new0(InputEvent, 1); +    evt->btn = g_new0(InputBtnEvent, 1); +    evt->kind = INPUT_EVENT_KIND_BTN; +    evt->btn->button = btn; +    evt->btn->down = down; +    return evt; +} + +void qemu_input_queue_btn(QemuConsole *src, InputButton btn, bool down) +{ +    InputEvent *evt; +    evt = qemu_input_event_new_btn(btn, down); +    qemu_input_event_send(src, evt); +    qapi_free_InputEvent(evt); +} + +void qemu_input_update_buttons(QemuConsole *src, uint32_t *button_map, +                               uint32_t button_old, uint32_t button_new) +{ +    InputButton btn; +    uint32_t mask; + +    for (btn = 0; btn < INPUT_BUTTON_MAX; btn++) { +        mask = button_map[btn]; +        if ((button_old & mask) == (button_new & mask)) { +            continue; +        } +        qemu_input_queue_btn(src, btn, button_new & mask); +    } +} + +bool qemu_input_is_absolute(void) +{ +    QemuInputHandlerState *s; + +    s = qemu_input_find_handler(INPUT_EVENT_MASK_REL | INPUT_EVENT_MASK_ABS, +                                NULL); +    return (s != NULL) && (s->handler->mask & INPUT_EVENT_MASK_ABS); +} + +int qemu_input_scale_axis(int value, int size_in, int size_out) +{ +    if (size_in < 2) { +        return size_out / 2; +    } +    return (int64_t)value * (size_out - 1) / (size_in - 1); +} + +InputEvent *qemu_input_event_new_move(InputEventKind kind, +                                      InputAxis axis, int value) +{ +    InputEvent *evt = g_new0(InputEvent, 1); +    InputMoveEvent *move = g_new0(InputMoveEvent, 1); + +    evt->kind = kind; +    evt->data = move; +    move->axis = axis; +    move->value = value; +    return evt; +} + +void qemu_input_queue_rel(QemuConsole *src, InputAxis axis, int value) +{ +    InputEvent *evt; +    evt = qemu_input_event_new_move(INPUT_EVENT_KIND_REL, axis, value); +    qemu_input_event_send(src, evt); +    qapi_free_InputEvent(evt); +} + +void qemu_input_queue_abs(QemuConsole *src, InputAxis axis, int value, int size) +{ +    InputEvent *evt; +    int scaled = qemu_input_scale_axis(value, size, INPUT_EVENT_ABS_SIZE); +    evt = qemu_input_event_new_move(INPUT_EVENT_KIND_ABS, axis, scaled); +    qemu_input_event_send(src, evt); +    qapi_free_InputEvent(evt); +} + +void qemu_input_check_mode_change(void) +{ +    static int current_is_absolute; +    int is_absolute; + +    is_absolute = qemu_input_is_absolute(); + +    if (is_absolute != current_is_absolute) { +        trace_input_mouse_mode(is_absolute); +        notifier_list_notify(&mouse_mode_notifiers, NULL); +    } + +    current_is_absolute = is_absolute; +} + +void qemu_add_mouse_mode_change_notifier(Notifier *notify) +{ +    notifier_list_add(&mouse_mode_notifiers, notify); +} + +void qemu_remove_mouse_mode_change_notifier(Notifier *notify) +{ +    notifier_remove(notify); +} + +MouseInfoList *qmp_query_mice(Error **errp) +{ +    MouseInfoList *mice_list = NULL; +    MouseInfoList *info; +    QemuInputHandlerState *s; +    bool current = true; + +    QTAILQ_FOREACH(s, &handlers, node) { +        if (!(s->handler->mask & +              (INPUT_EVENT_MASK_REL | INPUT_EVENT_MASK_ABS))) { +            continue; +        } + +        info = g_new0(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; +    } + +    return mice_list; +} + +void hmp_mouse_set(Monitor *mon, const QDict *qdict) +{ +    QemuInputHandlerState *s; +    int index = qdict_get_int(qdict, "index"); +    int found = 0; + +    QTAILQ_FOREACH(s, &handlers, node) { +        if (s->id != index) { +            continue; +        } +        if (!(s->handler->mask & (INPUT_EVENT_MASK_REL | +                                  INPUT_EVENT_MASK_ABS))) { +            error_report("Input device '%s' is not a mouse", s->handler->name); +            return; +        } +        found = 1; +        qemu_input_handler_activate(s); +        break; +    } + +    if (!found) { +        error_report("Mouse at index '%d' not found", index); +    } + +    qemu_input_check_mode_change(); +} diff --git a/ui/keymaps.c b/ui/keymaps.c new file mode 100644 index 00000000..49410ae9 --- /dev/null +++ b/ui/keymaps.c @@ -0,0 +1,240 @@ +/* + * QEMU keysym to keycode conversion using rdesktop keymaps + * + * Copyright (c) 2004 Johannes Schindelin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "keymaps.h" +#include "sysemu/sysemu.h" + +static int get_keysym(const name2keysym_t *table, +                      const char *name) +{ +    const name2keysym_t *p; +    for(p = table; p->name != NULL; p++) { +        if (!strcmp(p->name, name)) { +            return p->keysym; +        } +    } +    if (name[0] == 'U' && strlen(name) == 5) { /* try unicode Uxxxx */ +        char *end; +        int ret = (int)strtoul(name + 1, &end, 16); +        if (*end == '\0' && ret > 0) { +            return ret; +        } +    } +    return 0; +} + + +static void add_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) { +    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++; +        } +    } +} + +static kbd_layout_t *parse_keyboard_layout(const name2keysym_t *table, +                                           const char *language, +                                           kbd_layout_t *k) +{ +    FILE *f; +    char * filename; +    char line[1024]; +    int len; + +    filename = qemu_find_file(QEMU_FILE_TYPE_KEYMAP, language); +    f = filename ? fopen(filename, "r") : NULL; +    g_free(filename); +    if (!f) { +        fprintf(stderr, "Could not read keymap file: '%s'\n", language); +        return NULL; +    } + +    if (!k) { +        k = g_malloc0(sizeof(kbd_layout_t)); +    } + +    for(;;) { +        if (fgets(line, 1024, f) == NULL) { +            break; +        } +        len = strlen(line); +        if (len > 0 && line[len - 1] == '\n') { +            line[len - 1] = '\0'; +        } +        if (line[0] == '#') { +            continue; +        } +        if (!strncmp(line, "map ", 4)) { +            continue; +        } +        if (!strncmp(line, "include ", 8)) { +            parse_keyboard_layout(table, line + 8, k); +        } 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 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; +                    } +                    if (strstr(rest, "altgr")) { +                        keycode |= SCANCODE_ALTGR; +                    } +                    if (strstr(rest, "ctrl")) { +                        keycode |= SCANCODE_CTRL; +                    } + +                    add_keysym(line, keysym, keycode, k); + +                    if (strstr(rest, "addupper")) { +                        char *c; +                        for (c = line; *c; c++) { +                            *c = qemu_toupper(*c); +                        } +                        keysym = get_keysym(table, line); +                        if (keysym) { +                            add_keysym(line, keysym, +                                       keycode | SCANCODE_SHIFT, k); +                        } +                    } +                } +            } +        } +    } +    fclose(f); +    return k; +} + + +void *init_keyboard_layout(const name2keysym_t *table, const char *language) +{ +    return parse_keyboard_layout(table, language, NULL); +} + + +int keysym2scancode(void *kbd_layout, int keysym) +{ +    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; +#ifdef XK_ISO_Left_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; +            } +        } +    } +    return 0; +} + +int keycode_is_keypad(void *kbd_layout, int keycode) +{ +    kbd_layout_t *k = kbd_layout; +    struct key_range *kr; + +    for (kr = k->keypad_range; kr; kr = kr->next) { +        if (keycode >= kr->start && keycode <= kr->end) { +            return 1; +        } +    } +    return 0; +} + +int keysym_is_numlock(void *kbd_layout, int keysym) +{ +    kbd_layout_t *k = kbd_layout; +    struct key_range *kr; + +    for (kr = k->numlock_range; kr; kr = kr->next) { +        if (keysym >= kr->start && keysym <= kr->end) { +            return 1; +        } +    } +    return 0; +} diff --git a/ui/keymaps.h b/ui/keymaps.h new file mode 100644 index 00000000..a7600d57 --- /dev/null +++ b/ui/keymaps.h @@ -0,0 +1,77 @@ +/* + * QEMU keysym to keycode conversion using rdesktop keymaps + * + * Copyright (c) 2004 Johannes Schindelin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef __QEMU_KEYMAPS_H__ +#define __QEMU_KEYMAPS_H__ + +#include "qemu-common.h" + +typedef struct { +	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 */ +#define SCANCODE_KEYCODEMASK 0x7f + +/* "grey" keys will usually need a 0xe0 prefix */ +#define SCANCODE_GREY   0x80 +#define SCANCODE_EMUL0  0xE0 +/* "up" flag */ +#define SCANCODE_UP     0x80 + +/* Additional modifiers to use if not catched another way. */ +#define SCANCODE_SHIFT  0x100 +#define SCANCODE_CTRL   0x200 +#define SCANCODE_ALT    0x400 +#define SCANCODE_ALTGR  0x800 + + +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); + +#endif /* __QEMU_KEYMAPS_H__ */ diff --git a/ui/qemu-pixman.c b/ui/qemu-pixman.c new file mode 100644 index 00000000..4116e150 --- /dev/null +++ b/ui/qemu-pixman.c @@ -0,0 +1,252 @@ +/* + * 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-common.h" +#include "ui/console.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; +} + +int qemu_pixman_get_type(int rshift, int gshift, int bshift) +{ +    int type = PIXMAN_TYPE_OTHER; + +    if (rshift > gshift && gshift > bshift) { +        if (bshift == 0) { +            type = PIXMAN_TYPE_ARGB; +        } else { +#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; +} + +pixman_format_code_t qemu_pixman_get_format(PixelFormat *pf) +{ +    pixman_format_code_t format; +    int type; + +    type = qemu_pixman_get_type(pf->rshift, pf->gshift, pf->bshift); +    format = PIXMAN_FORMAT(pf->bits_per_pixel, type, +                           pf->abits, pf->rbits, pf->gbits, pf->bbits); +    if (!pixman_format_supported_source(format)) { +        return 0; +    } +    return format; +} + +/* + * Return true for known-good pixman conversions. + * + * UIs using pixman for format conversion can hook this into + * DisplayChangeListenerOps->dpy_gfx_check_format + */ +bool qemu_pixman_check_format(DisplayChangeListener *dcl, +                              pixman_format_code_t format) +{ +    switch (format) { +    /* 32 bpp */ +    case PIXMAN_x8r8g8b8: +    case PIXMAN_a8r8g8b8: +    case PIXMAN_b8g8r8x8: +    case PIXMAN_b8g8r8a8: +    /* 24 bpp */ +    case PIXMAN_r8g8b8: +    case PIXMAN_b8g8r8: +    /* 16 bpp */ +    case PIXMAN_x1r5g5b5: +    case PIXMAN_r5g6b5: +        return true; +    default: +        return false; +    } +} + +pixman_image_t *qemu_pixman_linebuf_create(pixman_format_code_t format, +                                           int width) +{ +    pixman_image_t *image = pixman_image_create_bits(format, width, 1, NULL, 0); +    assert(image != NULL); +    return image; +} + +/* fill linebuf from framebuffer */ +void qemu_pixman_linebuf_fill(pixman_image_t *linebuf, pixman_image_t *fb, +                              int width, int x, int y) +{ +    pixman_image_composite(PIXMAN_OP_SRC, fb, NULL, linebuf, +                           x, y, 0, 0, 0, 0, width, 1); +} + +/* copy linebuf to framebuffer */ +void qemu_pixman_linebuf_copy(pixman_image_t *fb, int width, int x, int y, +                              pixman_image_t *linebuf) +{ +    pixman_image_composite(PIXMAN_OP_SRC, linebuf, NULL, fb, +                           0, 0, 0, 0, x, y, width, 1); +} + +pixman_image_t *qemu_pixman_mirror_create(pixman_format_code_t format, +                                          pixman_image_t *image) +{ +    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; +} + +void qemu_pixman_image_unref(pixman_image_t *image) +{ +    if (image == NULL) { +        return; +    } +    pixman_image_unref(image); +} + +pixman_color_t qemu_pixman_color(PixelFormat *pf, uint32_t color) +{ +    pixman_color_t c; + +    c.red   = ((color & pf->rmask) >> pf->rshift) << (16 - pf->rbits); +    c.green = ((color & pf->gmask) >> pf->gshift) << (16 - pf->gbits); +    c.blue  = ((color & pf->bmask) >> pf->bshift) << (16 - pf->bbits); +    c.alpha = ((color & pf->amask) >> pf->ashift) << (16 - pf->abits); +    return c; +} + +pixman_image_t *qemu_pixman_glyph_from_vgafont(int height, const uint8_t *font, +                                               unsigned int ch) +{ +    pixman_image_t *glyph; +    uint8_t *data; +    bool bit; +    int x, y; + +    glyph = pixman_image_create_bits(PIXMAN_a8, 8, height, +                                     NULL, 0); +    data = (uint8_t *)pixman_image_get_data(glyph); + +    font += height * ch; +    for (y = 0; y < height; y++, font++) { +        for (x = 0; x < 8; x++, data++) { +            bit = (*font) & (1 << (7-x)); +            *data = bit ? 0xff : 0x00; +        } +    } +    return glyph; +} + +void qemu_pixman_glyph_render(pixman_image_t *glyph, +                              pixman_image_t *surface, +                              pixman_color_t *fgcol, +                              pixman_color_t *bgcol, +                              int x, int y, int cw, int ch) +{ +    pixman_image_t *ifg = pixman_image_create_solid_fill(fgcol); +    pixman_image_t *ibg = pixman_image_create_solid_fill(bgcol); + +    pixman_image_composite(PIXMAN_OP_SRC, ibg, NULL, surface, +                           0, 0, 0, 0, +                           cw * x, ch * y, +                           cw, ch); +    pixman_image_composite(PIXMAN_OP_OVER, ifg, glyph, surface, +                           0, 0, 0, 0, +                           cw * x, ch * y, +                           cw, ch); +    pixman_image_unref(ifg); +    pixman_image_unref(ibg); +} diff --git a/ui/qemu-x509.h b/ui/qemu-x509.h new file mode 100644 index 00000000..095aec16 --- /dev/null +++ b/ui/qemu-x509.h @@ -0,0 +1,9 @@ +#ifndef QEMU_X509_H +#define QEMU_X509_H + +#define X509_CA_CERT_FILE "ca-cert.pem" +#define X509_CA_CRL_FILE "ca-crl.pem" +#define X509_SERVER_KEY_FILE "server-key.pem" +#define X509_SERVER_CERT_FILE "server-cert.pem" + +#endif /* QEMU_X509_H */ diff --git a/ui/sdl.c b/ui/sdl.c new file mode 100644 index 00000000..3be29101 --- /dev/null +++ b/ui/sdl.c @@ -0,0 +1,1003 @@ +/* + * 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 "ui/input.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; + +#if 0 +#define DEBUG_SDL +#endif + +static void sdl_update(DisplayChangeListener *dcl, +                       int x, int y, int w, int h) +{ +    SDL_Rect rec; +    rec.x = x; +    rec.y = y; +    rec.w = w; +    rec.h = h; + +#ifdef DEBUG_SDL +    printf("SDL: Updating x=%d y=%d w=%d h=%d (scaling: %d)\n", +           x, y, w, h, scaling_active); +#endif + +    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; +    SDL_Surface *tmp_screen; + +#ifdef DEBUG_SDL +    printf("SDL: Resizing to %dx%d bpp %d\n", width, height, bpp); +#endif + +    flags = SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_HWACCEL; +    if (gui_fullscreen) { +        flags |= SDL_FULLSCREEN; +    } else { +        flags |= SDL_RESIZABLE; +    } +    if (gui_noframe) +        flags |= SDL_NOFRAME; + +    tmp_screen = SDL_SetVideoMode(width, height, bpp, flags); +    if (!real_screen) { +        if (!tmp_screen) { +            fprintf(stderr, "Could not open SDL display (%dx%dx%d): %s\n", +                    width, height, bpp, SDL_GetError()); +            exit(1); +        } +    } else { +        /* +         * Revert to the previous video mode if the change of resizing or +         * resolution failed. +         */ +        if (!tmp_screen) { +            fprintf(stderr, "Failed to set SDL display (%dx%dx%d): %s\n", +                    width, height, bpp, SDL_GetError()); +            return; +        } +    } + +    real_screen = tmp_screen; +} + +static void sdl_switch(DisplayChangeListener *dcl, +                       DisplaySurface *new_surface) +{ +    PixelFormat pf; + +    /* temporary hack: allows to call sdl_switch to handle scaling changes */ +    if (new_surface) { +        surface = new_surface; +    } +    pf = qemu_pixelformat_from_pixman(surface->format); + +    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); +    } + +#ifdef DEBUG_SDL +    printf("SDL: Creating surface with masks: %08x %08x %08x %08x\n", +           pf.rmask, pf.gmask, pf.bmask, pf.amask); +#endif + +    guest_screen = SDL_CreateRGBSurfaceFrom +        (surface_data(surface), +         surface_width(surface), surface_height(surface), +         surface_bits_per_pixel(surface), surface_stride(surface), +         pf.rmask, pf.gmask, +         pf.bmask, pf.amask); +} + +static bool sdl_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_b8g8r8x8 || +            format == PIXMAN_x1r5g5b5 || +            format == PIXMAN_r5g6b5); +} + +/* 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]) { +            qemu_input_event_send_key_number(dcl->con, i, false); +            modifiers_state[i] = 0; +        } +    } +} + +static void sdl_process_key(SDL_KeyboardEvent *ev) +{ +    int keycode; + +    if (ev->keysym.sym == SDLK_PAUSE) { +        /* specific case */ +        qemu_input_event_send_key_qcode(dcl->con, Q_KEY_CODE_PAUSE, +                                        ev->type == SDL_KEYDOWN); +        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 */ +        qemu_input_event_send_key_number(dcl->con, keycode, true); +        qemu_input_event_send_key_number(dcl->con, keycode, false); +        return; +#endif +    } + +    /* now send the key code */ +    qemu_input_event_send_key_number(dcl->con, keycode, +                                     ev->type == SDL_KEYDOWN); +} + +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 (qemu_input_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 (!qemu_input_is_absolute() || !qemu_console_is_graphic(NULL)) { +        SDL_ShowCursor(1); +        if (guest_cursor && +                (gui_grab || qemu_input_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 (!qemu_input_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 (qemu_input_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 x, int y, int state) +{ +    static uint32_t bmap[INPUT_BUTTON_MAX] = { +        [INPUT_BUTTON_LEFT]       = SDL_BUTTON(SDL_BUTTON_LEFT), +        [INPUT_BUTTON_MIDDLE]     = SDL_BUTTON(SDL_BUTTON_MIDDLE), +        [INPUT_BUTTON_RIGHT]      = SDL_BUTTON(SDL_BUTTON_RIGHT), +        [INPUT_BUTTON_WHEEL_UP]   = SDL_BUTTON(SDL_BUTTON_WHEELUP), +        [INPUT_BUTTON_WHEEL_DOWN] = SDL_BUTTON(SDL_BUTTON_WHEELDOWN), +    }; +    static uint32_t prev_state; + +    if (prev_state != state) { +        qemu_input_update_buttons(dcl->con, bmap, prev_state, state); +        prev_state = state; +    } + +    if (qemu_input_is_absolute()) { +        qemu_input_queue_abs(dcl->con, INPUT_AXIS_X, x, +                             real_screen->w); +        qemu_input_queue_abs(dcl->con, INPUT_AXIS_Y, y, +                             real_screen->h); +    } else { +        if (guest_cursor) { +            x -= guest_x; +            y -= guest_y; +            guest_x += x; +            guest_y += y; +            dx = x; +            dy = y; +        } +        qemu_input_queue_rel(dcl->con, INPUT_AXIS_X, dx); +        qemu_input_queue_rel(dcl->con, INPUT_AXIS_Y, dy); +    } +    qemu_input_event_sync(); +} + +static void sdl_scale(int width, int height) +{ +    int bpp = real_screen->format->BitsPerPixel; + +#ifdef DEBUG_SDL +    printf("SDL: Scaling to %dx%d bpp %d\n", width, height, bpp); +#endif + +    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) && +        (qemu_input_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 || qemu_input_is_absolute() || absolute_enabled) { +        sdl_send_mouse_event(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; + +    if (!qemu_console_is_graphic(NULL)) { +        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(); +        } +    } else { +        if (ev->type == SDL_MOUSEBUTTONDOWN) { +            buttonstate |= SDL_BUTTON(bev->button); +        } else { +            buttonstate &= ~SDL_BUTTON(bev->button); +        } +        sdl_send_mouse_event(0, 0, 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) && +        (qemu_input_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 || qemu_input_is_absolute() || absolute_enabled) { +            SDL_SetCursor(guest_sprite); +            if (!qemu_input_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 || 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_ops = { +    .dpy_name             = "sdl", +    .dpy_gfx_update       = sdl_update, +    .dpy_gfx_switch       = sdl_switch, +    .dpy_gfx_check_format = sdl_check_format, +    .dpy_refresh          = sdl_refresh, +    .dpy_mouse_set        = sdl_mouse_warp, +    .dpy_cursor_define    = sdl_mouse_define, +}; + +void sdl_display_early_init(int opengl) +{ +    if (opengl == 1 /* on */) { +        fprintf(stderr, +                "SDL1 display code has no opengl support.\n" +                "Please recompile qemu with SDL2, using\n" +                "./configure --enable-sdl --with-sdlabi=2.0\n"); +    } +} + +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 00000000..d0b340f9 --- /dev/null +++ b/ui/sdl2-2d.c @@ -0,0 +1,147 @@ +/* + * 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-common.h" +#include "ui/console.h" +#include "ui/input.h" +#include "ui/sdl2.h" +#include "sysemu/sysemu.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; + +    assert(!scon->opengl); + +    if (!surf) { +        return; +    } +    if (!scon->texture) { +        return; +    } + +    rect.x = x; +    rect.y = y; +    rect.w = w; +    rect.h = h; + +    SDL_UpdateTexture(scon->texture, NULL, surface_data(surf), +                      surface_stride(surf)); +    SDL_RenderCopy(scon->real_renderer, scon->texture, &rect, &rect); +    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_x8r8g8b8: +        format = SDL_PIXELFORMAT_ARGB8888; +        break; +    case PIXMAN_r8g8b8x8: +        format = SDL_PIXELFORMAT_RGBA8888; +        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_b8g8r8x8 || +            format == PIXMAN_x1r5g5b5 || +            format == PIXMAN_r5g6b5); +} diff --git a/ui/sdl2-gl.c b/ui/sdl2-gl.c new file mode 100644 index 00000000..b604c067 --- /dev/null +++ b/ui/sdl2-gl.c @@ -0,0 +1,112 @@ +/* + * 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-common.h" +#include "ui/console.h" +#include "ui/input.h" +#include "ui/sdl2.h" +#include "sysemu/sysemu.h" + +static void sdl2_gl_render_surface(struct sdl2_console *scon) +{ +    int ww, wh; + +    SDL_GL_MakeCurrent(scon->real_window, scon->winctx); + +    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) { +        console_gl_fini_context(scon->gls); +        scon->gls = NULL; +        sdl2_window_destroy(scon); +        return; +    } + +    if (!scon->real_window) { +        sdl2_window_create(scon); +        scon->gls = console_gl_init_context(); +    } 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->surface) { +        sdl2_gl_render_surface(scon); +    } +} diff --git a/ui/sdl2-input.c b/ui/sdl2-input.c new file mode 100644 index 00000000..ac5dc947 --- /dev/null +++ b/ui/sdl2-input.c @@ -0,0 +1,100 @@ +/* + * 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-common.h" +#include "ui/console.h" +#include "ui/input.h" +#include "ui/sdl2.h" +#include "sysemu/sysemu.h" + +#include "sdl2-keymap.h" + +static uint8_t modifiers_state[SDL_NUM_SCANCODES]; + +void sdl2_reset_keys(struct sdl2_console *scon) +{ +    QemuConsole *con = scon ? scon->dcl.con : NULL; +    int i; + +    for (i = 0; i < SDL_NUM_SCANCODES; i++) { +        if (modifiers_state[i]) { +            int qcode = sdl2_scancode_to_qcode[i]; +            qemu_input_event_send_key_qcode(con, qcode, false); +            modifiers_state[i] = 0; +        } +    } +} + +void sdl2_process_key(struct sdl2_console *scon, +                      SDL_KeyboardEvent *ev) +{ +    int qcode = sdl2_scancode_to_qcode[ev->keysym.scancode]; +    QemuConsole *con = scon ? scon->dcl.con : NULL; + +    if (!qemu_console_is_graphic(con)) { +        if (ev->type == SDL_KEYDOWN) { +            switch (ev->keysym.scancode) { +            case SDL_SCANCODE_RETURN: +                kbd_put_keysym_console(con, '\n'); +                break; +            case SDL_SCANCODE_BACKSPACE: +                kbd_put_keysym_console(con, QEMU_KEY_BACKSPACE); +                break; +            default: +                kbd_put_qcode_console(con, qcode); +                break; +            } +        } +        return; +    } + +    switch (ev->keysym.scancode) { +#if 0 +    case SDL_SCANCODE_NUMLOCKCLEAR: +    case SDL_SCANCODE_CAPSLOCK: +        /* SDL does not send the key up event, so we generate it */ +        qemu_input_event_send_key_qcode(con, qcode, true); +        qemu_input_event_send_key_qcode(con, qcode, false); +        return; +#endif +    case SDL_SCANCODE_LCTRL: +    case SDL_SCANCODE_LSHIFT: +    case SDL_SCANCODE_LALT: +    case SDL_SCANCODE_LGUI: +    case SDL_SCANCODE_RCTRL: +    case SDL_SCANCODE_RSHIFT: +    case SDL_SCANCODE_RALT: +    case SDL_SCANCODE_RGUI: +        if (ev->type == SDL_KEYUP) { +            modifiers_state[ev->keysym.scancode] = 0; +        } else { +            modifiers_state[ev->keysym.scancode] = 1; +        } +        /* fall though */ +    default: +        qemu_input_event_send_key_qcode(con, qcode, +                                        ev->type == SDL_KEYDOWN); +    } +} diff --git a/ui/sdl2-keymap.h b/ui/sdl2-keymap.h new file mode 100644 index 00000000..cbedaa47 --- /dev/null +++ b/ui/sdl2-keymap.h @@ -0,0 +1,267 @@ + +/* map SDL2 scancodes to QKeyCode */ + +static const int sdl2_scancode_to_qcode[SDL_NUM_SCANCODES] = { +    [SDL_SCANCODE_A]                 = Q_KEY_CODE_A, +    [SDL_SCANCODE_B]                 = Q_KEY_CODE_B, +    [SDL_SCANCODE_C]                 = Q_KEY_CODE_C, +    [SDL_SCANCODE_D]                 = Q_KEY_CODE_D, +    [SDL_SCANCODE_E]                 = Q_KEY_CODE_E, +    [SDL_SCANCODE_F]                 = Q_KEY_CODE_F, +    [SDL_SCANCODE_G]                 = Q_KEY_CODE_G, +    [SDL_SCANCODE_H]                 = Q_KEY_CODE_H, +    [SDL_SCANCODE_I]                 = Q_KEY_CODE_I, +    [SDL_SCANCODE_J]                 = Q_KEY_CODE_J, +    [SDL_SCANCODE_K]                 = Q_KEY_CODE_K, +    [SDL_SCANCODE_L]                 = Q_KEY_CODE_L, +    [SDL_SCANCODE_M]                 = Q_KEY_CODE_M, +    [SDL_SCANCODE_N]                 = Q_KEY_CODE_N, +    [SDL_SCANCODE_O]                 = Q_KEY_CODE_O, +    [SDL_SCANCODE_P]                 = Q_KEY_CODE_P, +    [SDL_SCANCODE_Q]                 = Q_KEY_CODE_Q, +    [SDL_SCANCODE_R]                 = Q_KEY_CODE_R, +    [SDL_SCANCODE_S]                 = Q_KEY_CODE_S, +    [SDL_SCANCODE_T]                 = Q_KEY_CODE_T, +    [SDL_SCANCODE_U]                 = Q_KEY_CODE_U, +    [SDL_SCANCODE_V]                 = Q_KEY_CODE_V, +    [SDL_SCANCODE_W]                 = Q_KEY_CODE_W, +    [SDL_SCANCODE_X]                 = Q_KEY_CODE_X, +    [SDL_SCANCODE_Y]                 = Q_KEY_CODE_Y, +    [SDL_SCANCODE_Z]                 = Q_KEY_CODE_Z, + +    [SDL_SCANCODE_1]                 = Q_KEY_CODE_1, +    [SDL_SCANCODE_2]                 = Q_KEY_CODE_2, +    [SDL_SCANCODE_3]                 = Q_KEY_CODE_3, +    [SDL_SCANCODE_4]                 = Q_KEY_CODE_4, +    [SDL_SCANCODE_5]                 = Q_KEY_CODE_5, +    [SDL_SCANCODE_6]                 = Q_KEY_CODE_6, +    [SDL_SCANCODE_7]                 = Q_KEY_CODE_7, +    [SDL_SCANCODE_8]                 = Q_KEY_CODE_8, +    [SDL_SCANCODE_9]                 = Q_KEY_CODE_9, +    [SDL_SCANCODE_0]                 = Q_KEY_CODE_0, + +    [SDL_SCANCODE_RETURN]            = Q_KEY_CODE_RET, +    [SDL_SCANCODE_ESCAPE]            = Q_KEY_CODE_ESC, +    [SDL_SCANCODE_BACKSPACE]         = Q_KEY_CODE_BACKSPACE, +    [SDL_SCANCODE_TAB]               = Q_KEY_CODE_TAB, +    [SDL_SCANCODE_SPACE]             = Q_KEY_CODE_SPC, +    [SDL_SCANCODE_MINUS]             = Q_KEY_CODE_MINUS, +    [SDL_SCANCODE_EQUALS]            = Q_KEY_CODE_EQUAL, +    [SDL_SCANCODE_LEFTBRACKET]       = Q_KEY_CODE_BRACKET_LEFT, +    [SDL_SCANCODE_RIGHTBRACKET]      = Q_KEY_CODE_BRACKET_RIGHT, +    [SDL_SCANCODE_BACKSLASH]         = Q_KEY_CODE_BACKSLASH, +#if 0 +    [SDL_SCANCODE_NONUSHASH]         = Q_KEY_CODE_NONUSHASH, +#endif +    [SDL_SCANCODE_SEMICOLON]         = Q_KEY_CODE_SEMICOLON, +    [SDL_SCANCODE_APOSTROPHE]        = Q_KEY_CODE_APOSTROPHE, +    [SDL_SCANCODE_GRAVE]             = Q_KEY_CODE_GRAVE_ACCENT, +    [SDL_SCANCODE_COMMA]             = Q_KEY_CODE_COMMA, +    [SDL_SCANCODE_PERIOD]            = Q_KEY_CODE_DOT, +    [SDL_SCANCODE_SLASH]             = Q_KEY_CODE_SLASH, +    [SDL_SCANCODE_CAPSLOCK]          = Q_KEY_CODE_CAPS_LOCK, + +    [SDL_SCANCODE_F1]                = Q_KEY_CODE_F1, +    [SDL_SCANCODE_F2]                = Q_KEY_CODE_F2, +    [SDL_SCANCODE_F3]                = Q_KEY_CODE_F3, +    [SDL_SCANCODE_F4]                = Q_KEY_CODE_F4, +    [SDL_SCANCODE_F5]                = Q_KEY_CODE_F5, +    [SDL_SCANCODE_F6]                = Q_KEY_CODE_F6, +    [SDL_SCANCODE_F7]                = Q_KEY_CODE_F7, +    [SDL_SCANCODE_F8]                = Q_KEY_CODE_F8, +    [SDL_SCANCODE_F9]                = Q_KEY_CODE_F9, +    [SDL_SCANCODE_F10]               = Q_KEY_CODE_F10, +    [SDL_SCANCODE_F11]               = Q_KEY_CODE_F11, +    [SDL_SCANCODE_F12]               = Q_KEY_CODE_F12, + +    [SDL_SCANCODE_PRINTSCREEN]       = Q_KEY_CODE_PRINT, +    [SDL_SCANCODE_SCROLLLOCK]        = Q_KEY_CODE_SCROLL_LOCK, +    [SDL_SCANCODE_PAUSE]             = Q_KEY_CODE_PAUSE, +    [SDL_SCANCODE_INSERT]            = Q_KEY_CODE_INSERT, +    [SDL_SCANCODE_HOME]              = Q_KEY_CODE_HOME, +    [SDL_SCANCODE_PAGEUP]            = Q_KEY_CODE_PGUP, +    [SDL_SCANCODE_DELETE]            = Q_KEY_CODE_DELETE, +    [SDL_SCANCODE_END]               = Q_KEY_CODE_END, +    [SDL_SCANCODE_PAGEDOWN]          = Q_KEY_CODE_PGDN, +    [SDL_SCANCODE_RIGHT]             = Q_KEY_CODE_RIGHT, +    [SDL_SCANCODE_LEFT]              = Q_KEY_CODE_LEFT, +    [SDL_SCANCODE_DOWN]              = Q_KEY_CODE_DOWN, +    [SDL_SCANCODE_UP]                = Q_KEY_CODE_UP, +    [SDL_SCANCODE_NUMLOCKCLEAR]      = Q_KEY_CODE_NUM_LOCK, + +    [SDL_SCANCODE_KP_DIVIDE]         = Q_KEY_CODE_KP_DIVIDE, +    [SDL_SCANCODE_KP_MULTIPLY]       = Q_KEY_CODE_KP_MULTIPLY, +    [SDL_SCANCODE_KP_MINUS]          = Q_KEY_CODE_KP_SUBTRACT, +    [SDL_SCANCODE_KP_PLUS]           = Q_KEY_CODE_KP_ADD, +    [SDL_SCANCODE_KP_ENTER]          = Q_KEY_CODE_KP_ENTER, +    [SDL_SCANCODE_KP_1]              = Q_KEY_CODE_KP_1, +    [SDL_SCANCODE_KP_2]              = Q_KEY_CODE_KP_2, +    [SDL_SCANCODE_KP_3]              = Q_KEY_CODE_KP_3, +    [SDL_SCANCODE_KP_4]              = Q_KEY_CODE_KP_4, +    [SDL_SCANCODE_KP_5]              = Q_KEY_CODE_KP_5, +    [SDL_SCANCODE_KP_6]              = Q_KEY_CODE_KP_6, +    [SDL_SCANCODE_KP_7]              = Q_KEY_CODE_KP_7, +    [SDL_SCANCODE_KP_8]              = Q_KEY_CODE_KP_8, +    [SDL_SCANCODE_KP_9]              = Q_KEY_CODE_KP_9, +    [SDL_SCANCODE_KP_0]              = Q_KEY_CODE_KP_0, +    [SDL_SCANCODE_KP_PERIOD]         = Q_KEY_CODE_KP_DECIMAL, + +    [SDL_SCANCODE_NONUSBACKSLASH]    = Q_KEY_CODE_LESS, +    [SDL_SCANCODE_APPLICATION]       = Q_KEY_CODE_MENU, +#if 0 +    [SDL_SCANCODE_POWER]             = Q_KEY_CODE_POWER, +    [SDL_SCANCODE_KP_EQUALS]         = Q_KEY_CODE_KP_EQUALS, + +    [SDL_SCANCODE_F13]               = Q_KEY_CODE_F13, +    [SDL_SCANCODE_F14]               = Q_KEY_CODE_F14, +    [SDL_SCANCODE_F15]               = Q_KEY_CODE_F15, +    [SDL_SCANCODE_F16]               = Q_KEY_CODE_F16, +    [SDL_SCANCODE_F17]               = Q_KEY_CODE_F17, +    [SDL_SCANCODE_F18]               = Q_KEY_CODE_F18, +    [SDL_SCANCODE_F19]               = Q_KEY_CODE_F19, +    [SDL_SCANCODE_F20]               = Q_KEY_CODE_F20, +    [SDL_SCANCODE_F21]               = Q_KEY_CODE_F21, +    [SDL_SCANCODE_F22]               = Q_KEY_CODE_F22, +    [SDL_SCANCODE_F23]               = Q_KEY_CODE_F23, +    [SDL_SCANCODE_F24]               = Q_KEY_CODE_F24, + +    [SDL_SCANCODE_EXECUTE]           = Q_KEY_CODE_EXECUTE, +#endif +    [SDL_SCANCODE_HELP]              = Q_KEY_CODE_HELP, +    [SDL_SCANCODE_MENU]              = Q_KEY_CODE_MENU, +#if 0 +    [SDL_SCANCODE_SELECT]            = Q_KEY_CODE_SELECT, +#endif +    [SDL_SCANCODE_STOP]              = Q_KEY_CODE_STOP, +    [SDL_SCANCODE_AGAIN]             = Q_KEY_CODE_AGAIN, +    [SDL_SCANCODE_UNDO]              = Q_KEY_CODE_UNDO, +    [SDL_SCANCODE_CUT]               = Q_KEY_CODE_CUT, +    [SDL_SCANCODE_COPY]              = Q_KEY_CODE_COPY, +    [SDL_SCANCODE_PASTE]             = Q_KEY_CODE_PASTE, +    [SDL_SCANCODE_FIND]              = Q_KEY_CODE_FIND, +#if 0 +    [SDL_SCANCODE_MUTE]              = Q_KEY_CODE_MUTE, +    [SDL_SCANCODE_VOLUMEUP]          = Q_KEY_CODE_VOLUMEUP, +    [SDL_SCANCODE_VOLUMEDOWN]        = Q_KEY_CODE_VOLUMEDOWN, + +    [SDL_SCANCODE_KP_COMMA]          = Q_KEY_CODE_KP_COMMA, +    [SDL_SCANCODE_KP_EQUALSAS400]    = Q_KEY_CODE_KP_EQUALSAS400, + +    [SDL_SCANCODE_INTERNATIONAL1]    = Q_KEY_CODE_INTERNATIONAL1, +    [SDL_SCANCODE_INTERNATIONAL2]    = Q_KEY_CODE_INTERNATIONAL2, +    [SDL_SCANCODE_INTERNATIONAL3]    = Q_KEY_CODE_INTERNATIONAL3, +    [SDL_SCANCODE_INTERNATIONAL4]    = Q_KEY_CODE_INTERNATIONAL4, +    [SDL_SCANCODE_INTERNATIONAL5]    = Q_KEY_CODE_INTERNATIONAL5, +    [SDL_SCANCODE_INTERNATIONAL6]    = Q_KEY_CODE_INTERNATIONAL6, +    [SDL_SCANCODE_INTERNATIONAL7]    = Q_KEY_CODE_INTERNATIONAL7, +    [SDL_SCANCODE_INTERNATIONAL8]    = Q_KEY_CODE_INTERNATIONAL8, +    [SDL_SCANCODE_INTERNATIONAL9]    = Q_KEY_CODE_INTERNATIONAL9, +    [SDL_SCANCODE_LANG1]             = Q_KEY_CODE_LANG1, +    [SDL_SCANCODE_LANG2]             = Q_KEY_CODE_LANG2, +    [SDL_SCANCODE_LANG3]             = Q_KEY_CODE_LANG3, +    [SDL_SCANCODE_LANG4]             = Q_KEY_CODE_LANG4, +    [SDL_SCANCODE_LANG5]             = Q_KEY_CODE_LANG5, +    [SDL_SCANCODE_LANG6]             = Q_KEY_CODE_LANG6, +    [SDL_SCANCODE_LANG7]             = Q_KEY_CODE_LANG7, +    [SDL_SCANCODE_LANG8]             = Q_KEY_CODE_LANG8, +    [SDL_SCANCODE_LANG9]             = Q_KEY_CODE_LANG9, +    [SDL_SCANCODE_ALTERASE]          = Q_KEY_CODE_ALTERASE, +#endif +    [SDL_SCANCODE_SYSREQ]            = Q_KEY_CODE_SYSRQ, +#if 0 +    [SDL_SCANCODE_CANCEL]            = Q_KEY_CODE_CANCEL, +    [SDL_SCANCODE_CLEAR]             = Q_KEY_CODE_CLEAR, +    [SDL_SCANCODE_PRIOR]             = Q_KEY_CODE_PRIOR, +    [SDL_SCANCODE_RETURN2]           = Q_KEY_CODE_RETURN2, +    [SDL_SCANCODE_SEPARATOR]         = Q_KEY_CODE_SEPARATOR, +    [SDL_SCANCODE_OUT]               = Q_KEY_CODE_OUT, +    [SDL_SCANCODE_OPER]              = Q_KEY_CODE_OPER, +    [SDL_SCANCODE_CLEARAGAIN]        = Q_KEY_CODE_CLEARAGAIN, +    [SDL_SCANCODE_CRSEL]             = Q_KEY_CODE_CRSEL, +    [SDL_SCANCODE_EXSEL]             = Q_KEY_CODE_EXSEL, +    [SDL_SCANCODE_KP_00]             = Q_KEY_CODE_KP_00, +    [SDL_SCANCODE_KP_000]            = Q_KEY_CODE_KP_000, +    [SDL_SCANCODE_THOUSANDSSEPARATOR] = Q_KEY_CODE_THOUSANDSSEPARATOR, +    [SDL_SCANCODE_DECIMALSEPARATOR]  = Q_KEY_CODE_DECIMALSEPARATOR, +    [SDL_SCANCODE_CURRENCYUNIT]      = Q_KEY_CODE_CURRENCYUNIT, +    [SDL_SCANCODE_CURRENCYSUBUNIT]   = Q_KEY_CODE_CURRENCYSUBUNIT, +    [SDL_SCANCODE_KP_LEFTPAREN]      = Q_KEY_CODE_KP_LEFTPAREN, +    [SDL_SCANCODE_KP_RIGHTPAREN]     = Q_KEY_CODE_KP_RIGHTPAREN, +    [SDL_SCANCODE_KP_LEFTBRACE]      = Q_KEY_CODE_KP_LEFTBRACE, +    [SDL_SCANCODE_KP_RIGHTBRACE]     = Q_KEY_CODE_KP_RIGHTBRACE, +    [SDL_SCANCODE_KP_TAB]            = Q_KEY_CODE_KP_TAB, +    [SDL_SCANCODE_KP_BACKSPACE]      = Q_KEY_CODE_KP_BACKSPACE, +    [SDL_SCANCODE_KP_A]              = Q_KEY_CODE_KP_A, +    [SDL_SCANCODE_KP_B]              = Q_KEY_CODE_KP_B, +    [SDL_SCANCODE_KP_C]              = Q_KEY_CODE_KP_C, +    [SDL_SCANCODE_KP_D]              = Q_KEY_CODE_KP_D, +    [SDL_SCANCODE_KP_E]              = Q_KEY_CODE_KP_E, +    [SDL_SCANCODE_KP_F]              = Q_KEY_CODE_KP_F, +    [SDL_SCANCODE_KP_XOR]            = Q_KEY_CODE_KP_XOR, +    [SDL_SCANCODE_KP_POWER]          = Q_KEY_CODE_KP_POWER, +    [SDL_SCANCODE_KP_PERCENT]        = Q_KEY_CODE_KP_PERCENT, +    [SDL_SCANCODE_KP_LESS]           = Q_KEY_CODE_KP_LESS, +    [SDL_SCANCODE_KP_GREATER]        = Q_KEY_CODE_KP_GREATER, +    [SDL_SCANCODE_KP_AMPERSAND]      = Q_KEY_CODE_KP_AMPERSAND, +    [SDL_SCANCODE_KP_DBLAMPERSAND]   = Q_KEY_CODE_KP_DBLAMPERSAND, +    [SDL_SCANCODE_KP_VERTICALBAR]    = Q_KEY_CODE_KP_VERTICALBAR, +    [SDL_SCANCODE_KP_DBLVERTICALBAR] = Q_KEY_CODE_KP_DBLVERTICALBAR, +    [SDL_SCANCODE_KP_COLON]          = Q_KEY_CODE_KP_COLON, +    [SDL_SCANCODE_KP_HASH]           = Q_KEY_CODE_KP_HASH, +    [SDL_SCANCODE_KP_SPACE]          = Q_KEY_CODE_KP_SPACE, +    [SDL_SCANCODE_KP_AT]             = Q_KEY_CODE_KP_AT, +    [SDL_SCANCODE_KP_EXCLAM]         = Q_KEY_CODE_KP_EXCLAM, +    [SDL_SCANCODE_KP_MEMSTORE]       = Q_KEY_CODE_KP_MEMSTORE, +    [SDL_SCANCODE_KP_MEMRECALL]      = Q_KEY_CODE_KP_MEMRECALL, +    [SDL_SCANCODE_KP_MEMCLEAR]       = Q_KEY_CODE_KP_MEMCLEAR, +    [SDL_SCANCODE_KP_MEMADD]         = Q_KEY_CODE_KP_MEMADD, +    [SDL_SCANCODE_KP_MEMSUBTRACT]    = Q_KEY_CODE_KP_MEMSUBTRACT, +    [SDL_SCANCODE_KP_MEMMULTIPLY]    = Q_KEY_CODE_KP_MEMMULTIPLY, +    [SDL_SCANCODE_KP_MEMDIVIDE]      = Q_KEY_CODE_KP_MEMDIVIDE, +    [SDL_SCANCODE_KP_PLUSMINUS]      = Q_KEY_CODE_KP_PLUSMINUS, +    [SDL_SCANCODE_KP_CLEAR]          = Q_KEY_CODE_KP_CLEAR, +    [SDL_SCANCODE_KP_CLEARENTRY]     = Q_KEY_CODE_KP_CLEARENTRY, +    [SDL_SCANCODE_KP_BINARY]         = Q_KEY_CODE_KP_BINARY, +    [SDL_SCANCODE_KP_OCTAL]          = Q_KEY_CODE_KP_OCTAL, +    [SDL_SCANCODE_KP_DECIMAL]        = Q_KEY_CODE_KP_DECIMAL, +    [SDL_SCANCODE_KP_HEXADECIMAL]    = Q_KEY_CODE_KP_HEXADECIMAL, +#endif +    [SDL_SCANCODE_LCTRL]             = Q_KEY_CODE_CTRL, +    [SDL_SCANCODE_LSHIFT]            = Q_KEY_CODE_SHIFT, +    [SDL_SCANCODE_LALT]              = Q_KEY_CODE_ALT, +    [SDL_SCANCODE_LGUI]              = Q_KEY_CODE_META_L, +    [SDL_SCANCODE_RCTRL]             = Q_KEY_CODE_CTRL_R, +    [SDL_SCANCODE_RSHIFT]            = Q_KEY_CODE_SHIFT_R, +    [SDL_SCANCODE_RALT]              = Q_KEY_CODE_ALT_R, +    [SDL_SCANCODE_RGUI]              = Q_KEY_CODE_META_R, +#if 0 +    [SDL_SCANCODE_MODE]              = Q_KEY_CODE_MODE, +    [SDL_SCANCODE_AUDIONEXT]         = Q_KEY_CODE_AUDIONEXT, +    [SDL_SCANCODE_AUDIOPREV]         = Q_KEY_CODE_AUDIOPREV, +    [SDL_SCANCODE_AUDIOSTOP]         = Q_KEY_CODE_AUDIOSTOP, +    [SDL_SCANCODE_AUDIOPLAY]         = Q_KEY_CODE_AUDIOPLAY, +    [SDL_SCANCODE_AUDIOMUTE]         = Q_KEY_CODE_AUDIOMUTE, +    [SDL_SCANCODE_MEDIASELECT]       = Q_KEY_CODE_MEDIASELECT, +    [SDL_SCANCODE_WWW]               = Q_KEY_CODE_WWW, +    [SDL_SCANCODE_MAIL]              = Q_KEY_CODE_MAIL, +    [SDL_SCANCODE_CALCULATOR]        = Q_KEY_CODE_CALCULATOR, +    [SDL_SCANCODE_COMPUTER]          = Q_KEY_CODE_COMPUTER, +    [SDL_SCANCODE_AC_SEARCH]         = Q_KEY_CODE_AC_SEARCH, +    [SDL_SCANCODE_AC_HOME]           = Q_KEY_CODE_AC_HOME, +    [SDL_SCANCODE_AC_BACK]           = Q_KEY_CODE_AC_BACK, +    [SDL_SCANCODE_AC_FORWARD]        = Q_KEY_CODE_AC_FORWARD, +    [SDL_SCANCODE_AC_STOP]           = Q_KEY_CODE_AC_STOP, +    [SDL_SCANCODE_AC_REFRESH]        = Q_KEY_CODE_AC_REFRESH, +    [SDL_SCANCODE_AC_BOOKMARKS]      = Q_KEY_CODE_AC_BOOKMARKS, +    [SDL_SCANCODE_BRIGHTNESSDOWN]    = Q_KEY_CODE_BRIGHTNESSDOWN, +    [SDL_SCANCODE_BRIGHTNESSUP]      = Q_KEY_CODE_BRIGHTNESSUP, +    [SDL_SCANCODE_DISPLAYSWITCH]     = Q_KEY_CODE_DISPLAYSWITCH, +    [SDL_SCANCODE_KBDILLUMTOGGLE]    = Q_KEY_CODE_KBDILLUMTOGGLE, +    [SDL_SCANCODE_KBDILLUMDOWN]      = Q_KEY_CODE_KBDILLUMDOWN, +    [SDL_SCANCODE_KBDILLUMUP]        = Q_KEY_CODE_KBDILLUMUP, +    [SDL_SCANCODE_EJECT]             = Q_KEY_CODE_EJECT, +    [SDL_SCANCODE_SLEEP]             = Q_KEY_CODE_SLEEP, +    [SDL_SCANCODE_APP1]              = Q_KEY_CODE_APP1, +    [SDL_SCANCODE_APP2]              = Q_KEY_CODE_APP2, +#endif +}; diff --git a/ui/sdl2.c b/ui/sdl2.c new file mode 100644 index 00000000..5cb75aa3 --- /dev/null +++ b/ui/sdl2.c @@ -0,0 +1,806 @@ +/* + * 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-common.h" +#include "ui/console.h" +#include "ui/input.h" +#include "ui/sdl2.h" +#include "sysemu/sysemu.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_noframe; +static int gui_key_modifier_pressed; +static int gui_keysym; +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; + +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; +    } + +    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 to exit grab"; +        } else if (ctrl_grab) { +            status = " - Press Right-Ctrl to exit grab"; +        } else { +            status = " - Press Ctrl-Alt 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(void) +{ +    if (!cursor_hide) { +        return; +    } + +    if (qemu_input_is_absolute()) { +        SDL_ShowCursor(1); +        SDL_SetCursor(sdl_cursor_hidden); +    } else { +        SDL_SetRelativeMouseMode(SDL_TRUE); +    } +} + +static void sdl_show_cursor(void) +{ +    if (!cursor_hide) { +        return; +    } + +    if (!qemu_input_is_absolute()) { +        SDL_SetRelativeMouseMode(SDL_FALSE); +        SDL_ShowCursor(1); +        if (guest_cursor && +            (gui_grab || qemu_input_is_absolute() || absolute_enabled)) { +            SDL_SetCursor(guest_sprite); +        } else { +            SDL_SetCursor(sdl_cursor_normal); +        } +    } +} + +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(); +    } +    SDL_SetWindowGrab(scon->real_window, SDL_TRUE); +    gui_grab = 1; +    sdl_update_caption(scon); +} + +static void sdl_grab_end(struct sdl2_console *scon) +{ +    SDL_SetWindowGrab(scon->real_window, SDL_FALSE); +    gui_grab = 0; +    sdl_show_cursor(); +    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; +            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()) { +        int scr_w, scr_h; +        int max_w = 0, max_h = 0; +        int off_x = 0, off_y = 0; +        int cur_off_x = 0, cur_off_y = 0; +        int i; + +        for (i = 0; i < sdl2_num_outputs; i++) { +            struct sdl2_console *thiscon = &sdl2_console[i]; +            if (thiscon->real_window && thiscon->surface) { +                SDL_GetWindowSize(thiscon->real_window, &scr_w, &scr_h); +                cur_off_x = thiscon->x; +                cur_off_y = thiscon->y; +                if (scr_w + cur_off_x > max_w) { +                    max_w = scr_w + cur_off_x; +                } +                if (scr_h + cur_off_y > max_h) { +                    max_h = scr_h + cur_off_y; +                } +                if (i == scon->idx) { +                    off_x = cur_off_x; +                    off_y = cur_off_y; +                } +            } +        } +        qemu_input_queue_abs(scon->dcl.con, INPUT_AXIS_X, off_x + x, max_w); +        qemu_input_queue_abs(scon->dcl.con, INPUT_AXIS_Y, off_y + y, max_h); +    } 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 void handle_keydown(SDL_Event *ev) +{ +    int mod_state, win; +    struct sdl2_console *scon = get_scon_from_window(ev->key.windowID); + +    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) { +        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: +            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_U: +            sdl2_window_destroy(scon); +            sdl2_window_create(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) +{ +    int mod_state; +    struct sdl2_console *scon = get_scon_from_window(ev->key.windowID); + +    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) { +                sdl_grab_start(scon); +            } else if (!gui_fullscreen) { +                sdl_grab_end(scon); +            } +            /* SDL does not send back all the modifiers key, so we must +             * correct it. */ +            sdl2_reset_keys(scon); +            return; +        } +        gui_keysym = 0; +    } +    if (!gui_keysym) { +        sdl2_process_key(scon, &ev->key); +    } +} + +static void handle_textinput(SDL_Event *ev) +{ +    struct sdl2_console *scon = get_scon_from_window(ev->key.windowID); +    QemuConsole *con = scon ? scon->dcl.con : NULL; + +    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->key.windowID); + +    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 && (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->key.windowID); + +    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->key.windowID); +    SDL_MouseWheelEvent *wev = &ev->wheel; +    InputButton btn; + +    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); + +    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: +    case SDL_WINDOWEVENT_ENTER: +        if (!gui_grab && (qemu_input_is_absolute() || absolute_enabled)) { +            absolute_mouse_grab(scon); +        } +        break; +    case SDL_WINDOWEVENT_FOCUS_LOST: +        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 (!no_quit) { +            no_shutdown = 0; +            qemu_system_shutdown_request(); +        } +        break; +    case SDL_WINDOWEVENT_SHOWN: +        if (scon->hidden) { +            SDL_HideWindow(scon->real_window); +        } +        break; +    case SDL_WINDOWEVENT_HIDDEN: +        if (!scon->hidden) { +            SDL_ShowWindow(scon->real_window); +        } +        break; +    } +} + +void sdl2_poll_events(struct sdl2_console *scon) +{ +    SDL_Event ev1, *ev = &ev1; + +    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: +            handle_keydown(ev); +            break; +        case SDL_KEYUP: +            handle_keyup(ev); +            break; +        case SDL_TEXTINPUT: +            handle_textinput(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_MOUSEWHEEL: +            handle_mousewheel(ev); +            break; +        case SDL_WINDOWEVENT: +            handle_windowevent(ev); +            break; +        default: +            break; +        } +    } +} + +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 (on) { +        if (!guest_cursor) { +            sdl_show_cursor(); +        } +        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(); +    } +    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, +}; +#endif + +void sdl_display_early_init(int opengl) +{ +    switch (opengl) { +    case -1: /* default */ +    case 0:  /* off */ +        break; +    case 1: /* on */ +#ifdef CONFIG_OPENGL +        display_opengl = 1; +#endif +        break; +    default: +        g_assert_not_reached(); +        break; +    } +} + +void sdl_display_init(DisplayState *ds, int full_screen, int no_frame) +{ +    int flags; +    uint8_t data = 0; +    char *filename; +    int i; + +    if (no_frame) { +        gui_noframe = 1; +    } + +#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 + +    flags = SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE; +    if (SDL_Init(flags)) { +        fprintf(stderr, "Could not initialize SDL(%s) - exiting\n", +                SDL_GetError()); +        exit(1); +    } +    SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "1"); + +    for (i = 0;; i++) { +        QemuConsole *con = qemu_console_lookup_by_index(i); +        if (!con) { +            break; +        } +    } +    sdl2_num_outputs = i; +    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); +        if (!qemu_console_is_graphic(con)) { +            sdl2_console[i].hidden = true; +        } +        sdl2_console[i].idx = i; +#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; +        register_displaychangelistener(&sdl2_console[i].dcl); +    } + +    /* 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_TRUE, colorkey); +            SDL_SetWindowIcon(sdl2_console[0].real_window, image); +        } +        g_free(filename); +    } + +    if (full_screen) { +        gui_fullscreen = 1; +        sdl_grab_start(0); +    } + +    mouse_mode_notifier.notify = sdl_mouse_mode_change; +    qemu_add_mouse_mode_change_notifier(&mouse_mode_notifier); + +    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/sdl_keysym.h b/ui/sdl_keysym.h new file mode 100644 index 00000000..599d9fc6 --- /dev/null +++ b/ui/sdl_keysym.h @@ -0,0 +1,278 @@ + +#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}, +#if SDL_MAJOR_VERSION == 1 +{"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}, +#endif +{NULL, 0}, +}; diff --git a/ui/sdl_zoom.c b/ui/sdl_zoom.c new file mode 100644 index 00000000..2625c455 --- /dev/null +++ b/ui/sdl_zoom.c @@ -0,0 +1,96 @@ +/* + * 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 new file mode 100644 index 00000000..74955bc9 --- /dev/null +++ b/ui/sdl_zoom.h @@ -0,0 +1,25 @@ +/* + * 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 new file mode 100644 index 00000000..3bb508b5 --- /dev/null +++ b/ui/sdl_zoom_template.h @@ -0,0 +1,219 @@ +/* + * 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 00000000..52a46329 --- /dev/null +++ b/ui/shader.c @@ -0,0 +1,114 @@ +/* + * 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-common.h" +#include "ui/shader.h" + +/* ---------------------------------------------------------------------- */ + +void qemu_gl_run_texture_blit(GLint texture_blit_prog) +{ +    GLfloat in_position[] = { +        -1, -1, +        1,  -1, +        -1,  1, +        1,   1, +    }; +    GLint l_position; + +    glUseProgram(texture_blit_prog); +    l_position = glGetAttribLocation(texture_blit_prog, "in_position"); +    glVertexAttribPointer(l_position, 2, GL_FLOAT, GL_FALSE, 0, in_position); +    glEnableVertexAttribArray(l_position); +    glDrawArrays(GL_TRIANGLE_STRIP, l_position, 4); +} + +/* ---------------------------------------------------------------------- */ + +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 = malloc(length); +        glGetShaderInfoLog(shader, length, &length, errmsg); +        fprintf(stderr, "%s: compile %s error\n%s\n", __func__, +                (type == GL_VERTEX_SHADER) ? "vertex" : "fragment", +                errmsg); +        free(errmsg); +        return 0; +    } +    return shader; +} + +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 = malloc(length); +        glGetProgramInfoLog(program, length, &length, errmsg); +        fprintf(stderr, "%s: link program: %s\n", __func__, errmsg); +        free(errmsg); +        return 0; +    } +    return program; +} + +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; +} diff --git a/ui/shader/texture-blit.frag b/ui/shader/texture-blit.frag new file mode 100644 index 00000000..bfa202c2 --- /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 00000000..6fe2744d --- /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-core.c b/ui/spice-core.c new file mode 100644 index 00000000..bf4fd074 --- /dev/null +++ b/ui/spice-core.c @@ -0,0 +1,930 @@ +/* + * 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 <spice.h> + +#include <netdb.h> +#include "sysemu/sysemu.h" + +#include "qemu-common.h" +#include "ui/qemu-spice.h" +#include "qemu/error-report.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 "qemu/notify.h" +#include "migration/migration.h" +#include "hw/hw.h" +#include "ui/spice-display.h" +#include "qapi-event.h" + +/* core bits */ + +static SpiceServer *spice_server; +static Notifier migration_state; +static const char *auth = "spice"; +static char *auth_passwd; +static time_t auth_expires = TIME_MAX; +static int spice_migration_completed; +static int spice_display_is_running; +static int spice_have_target_host; +int using_spice = 0; + +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 = timer_new_ms(QEMU_CLOCK_REALTIME, func, opaque); +    QTAILQ_INSERT_TAIL(&timers, timer, next); +    return timer; +} + +static void timer_start(SpiceTimer *timer, uint32_t ms) +{ +    timer_mod(timer->timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + ms); +} + +static void timer_cancel(SpiceTimer *timer) +{ +    timer_del(timer->timer); +} + +static void timer_remove(SpiceTimer *timer) +{ +    timer_del(timer->timer); +    timer_free(timer->timer); +    QTAILQ_REMOVE(&timers, timer, next); +    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) +{ +    SpiceWatch *watch = opaque; +    watch->func(watch->fd, SPICE_WATCH_EVENT_READ, watch->opaque); +} + +static void watch_write(void *opaque) +{ +    SpiceWatch *watch = opaque; +    watch->func(watch->fd, SPICE_WATCH_EVENT_WRITE, watch->opaque); +} + +static void watch_update_mask(SpiceWatch *watch, int event_mask) +{ +    IOHandler *on_read = NULL; +    IOHandler *on_write = NULL; + +    watch->event_mask = event_mask; +    if (watch->event_mask & SPICE_WATCH_EVENT_READ) { +        on_read = watch_read; +    } +    if (watch->event_mask & SPICE_WATCH_EVENT_WRITE) { +        on_write = watch_write; +    } +    qemu_set_fd_handler(watch->fd, on_read, on_write, watch); +} + +static SpiceWatch *watch_add(int fd, int event_mask, SpiceWatchFunc func, void *opaque) +{ +    SpiceWatch *watch; + +    watch = g_malloc0(sizeof(*watch)); +    watch->fd     = fd; +    watch->func   = func; +    watch->opaque = opaque; +    QTAILQ_INSERT_TAIL(&watches, watch, next); + +    watch_update_mask(watch, event_mask); +    return watch; +} + +static void watch_remove(SpiceWatch *watch) +{ +    qemu_set_fd_handler(watch->fd, NULL, NULL, NULL); +    QTAILQ_REMOVE(&watches, watch, next); +    g_free(watch); +} + +typedef struct ChannelList ChannelList; +struct ChannelList { +    SpiceChannelEventInfo *info; +    QTAILQ_ENTRY(ChannelList) link; +}; +static QTAILQ_HEAD(, ChannelList) channel_list = QTAILQ_HEAD_INITIALIZER(channel_list); + +static void channel_list_add(SpiceChannelEventInfo *info) +{ +    ChannelList *item; + +    item = g_malloc0(sizeof(*item)); +    item->info = info; +    QTAILQ_INSERT_TAIL(&channel_list, item, link); +} + +static void channel_list_del(SpiceChannelEventInfo *info) +{ +    ChannelList *item; + +    QTAILQ_FOREACH(item, &channel_list, link) { +        if (item->info != info) { +            continue; +        } +        QTAILQ_REMOVE(&channel_list, item, link); +        g_free(item); +        return; +    } +} + +static void add_addr_info(SpiceBasicInfo *info, struct sockaddr *addr, int len) +{ +    char host[NI_MAXHOST], port[NI_MAXSERV]; + +    getnameinfo(addr, len, host, sizeof(host), port, sizeof(port), +                NI_NUMERICHOST | NI_NUMERICSERV); + +    info->host = g_strdup(host); +    info->port = g_strdup(port); +    info->family = inet_netfamily(addr->sa_family); +} + +static void add_channel_info(SpiceChannel *sc, SpiceChannelEventInfo *info) +{ +    int tls = info->flags & SPICE_CHANNEL_EVENT_FLAG_TLS; + +    sc->connection_id = info->connection_id; +    sc->channel_type = info->type; +    sc->channel_id = info->id; +    sc->tls = !!tls; +} + +static void channel_event(int event, SpiceChannelEventInfo *info) +{ +    SpiceServerInfo *server = g_malloc0(sizeof(*server)); +    SpiceChannel *client = g_malloc0(sizeof(*client)); +    server->base = g_malloc0(sizeof(*server->base)); +    client->base = g_malloc0(sizeof(*client->base)); + +    /* +     * Spice server might have called us from spice worker thread +     * context (happens on display channel disconnects).  Spice should +     * not do that.  It isn't that easy to fix it in spice and even +     * when it is fixed we still should cover the already released +     * spice versions.  So detect that we've been called from another +     * thread and grab the iothread lock if so before calling qemu +     * functions. +     */ +    bool need_lock = !qemu_thread_is_self(&me); +    if (need_lock) { +        qemu_mutex_lock_iothread(); +    } + +    if (info->flags & SPICE_CHANNEL_EVENT_FLAG_ADDR_EXT) { +        add_addr_info(client->base, (struct sockaddr *)&info->paddr_ext, +                      info->plen_ext); +        add_addr_info(server->base, (struct sockaddr *)&info->laddr_ext, +                      info->llen_ext); +    } else { +        error_report("spice: %s, extended address is expected", +                     __func__); +    } + +    switch (event) { +    case SPICE_CHANNEL_EVENT_CONNECTED: +        qapi_event_send_spice_connected(server->base, client->base, &error_abort); +        break; +    case SPICE_CHANNEL_EVENT_INITIALIZED: +        if (auth) { +            server->has_auth = true; +            server->auth = g_strdup(auth); +        } +        add_channel_info(client, info); +        channel_list_add(info); +        qapi_event_send_spice_initialized(server, client, &error_abort); +        break; +    case SPICE_CHANNEL_EVENT_DISCONNECTED: +        channel_list_del(info); +        qapi_event_send_spice_disconnected(server->base, client->base, &error_abort); +        break; +    default: +        break; +    } + +    if (need_lock) { +        qemu_mutex_unlock_iothread(); +    } + +    qapi_free_SpiceServerInfo(server); +    qapi_free_SpiceChannel(client); +} + +static SpiceCoreInterface core_interface = { +    .base.type          = SPICE_INTERFACE_CORE, +    .base.description   = "qemu core services", +    .base.major_version = SPICE_INTERFACE_CORE_MAJOR, +    .base.minor_version = SPICE_INTERFACE_CORE_MINOR, + +    .timer_add          = timer_add, +    .timer_start        = timer_start, +    .timer_cancel       = timer_cancel, +    .timer_remove       = timer_remove, + +    .watch_add          = watch_add, +    .watch_update_mask  = watch_update_mask, +    .watch_remove       = watch_remove, + +    .channel_event      = channel_event, +}; + +static void migrate_connect_complete_cb(SpiceMigrateInstance *sin); +static void migrate_end_complete_cb(SpiceMigrateInstance *sin); + +static const SpiceMigrateInterface migrate_interface = { +    .base.type = SPICE_INTERFACE_MIGRATION, +    .base.description = "migration", +    .base.major_version = SPICE_INTERFACE_MIGRATION_MAJOR, +    .base.minor_version = SPICE_INTERFACE_MIGRATION_MINOR, +    .migrate_connect_complete = migrate_connect_complete_cb, +    .migrate_end_complete = migrate_end_complete_cb, +}; + +static SpiceMigrateInstance spice_migrate; + +static void migrate_connect_complete_cb(SpiceMigrateInstance *sin) +{ +    /* nothing, but libspice-server expects this cb being present. */ +} + +static void migrate_end_complete_cb(SpiceMigrateInstance *sin) +{ +    qapi_event_send_spice_migrate_completed(&error_abort); +    spice_migration_completed = true; +} + +/* config string parsing */ + +static int name2enum(const char *string, const char *table[], int entries) +{ +    int i; + +    if (string) { +        for (i = 0; i < entries; i++) { +            if (!table[i]) { +                continue; +            } +            if (strcmp(string, table[i]) != 0) { +                continue; +            } +            return i; +        } +    } +    return -1; +} + +static int parse_name(const char *string, const char *optname, +                      const char *table[], int entries) +{ +    int value = name2enum(string, table, entries); + +    if (value != -1) { +        return value; +    } +    error_report("spice: invalid %s: %s", optname, string); +    exit(1); +} + +static const char *stream_video_names[] = { +    [ SPICE_STREAM_VIDEO_OFF ]    = "off", +    [ SPICE_STREAM_VIDEO_ALL ]    = "all", +    [ SPICE_STREAM_VIDEO_FILTER ] = "filter", +}; +#define parse_stream_video(_name) \ +    parse_name(_name, "stream video control", \ +               stream_video_names, ARRAY_SIZE(stream_video_names)) + +static const char *compression_names[] = { +    [ SPICE_IMAGE_COMPRESS_OFF ]      = "off", +    [ SPICE_IMAGE_COMPRESS_AUTO_GLZ ] = "auto_glz", +    [ SPICE_IMAGE_COMPRESS_AUTO_LZ ]  = "auto_lz", +    [ SPICE_IMAGE_COMPRESS_QUIC ]     = "quic", +    [ SPICE_IMAGE_COMPRESS_GLZ ]      = "glz", +    [ SPICE_IMAGE_COMPRESS_LZ ]       = "lz", +}; +#define parse_compression(_name)                                        \ +    parse_name(_name, "image compression",                              \ +               compression_names, ARRAY_SIZE(compression_names)) + +static const char *wan_compression_names[] = { +    [ SPICE_WAN_COMPRESSION_AUTO   ] = "auto", +    [ SPICE_WAN_COMPRESSION_NEVER  ] = "never", +    [ SPICE_WAN_COMPRESSION_ALWAYS ] = "always", +}; +#define parse_wan_compression(_name)                                    \ +    parse_name(_name, "wan compression",                                \ +               wan_compression_names, ARRAY_SIZE(wan_compression_names)) + +/* functions for the rest of qemu */ + +static SpiceChannelList *qmp_query_spice_channels(void) +{ +    SpiceChannelList *cur_item = NULL, *head = NULL; +    ChannelList *item; + +    QTAILQ_FOREACH(item, &channel_list, link) { +        SpiceChannelList *chan; +        char host[NI_MAXHOST], port[NI_MAXSERV]; +        struct sockaddr *paddr; +        socklen_t plen; + +        assert(item->info->flags & SPICE_CHANNEL_EVENT_FLAG_ADDR_EXT); + +        chan = g_malloc0(sizeof(*chan)); +        chan->value = g_malloc0(sizeof(*chan->value)); +        chan->value->base = g_malloc0(sizeof(*chan->value->base)); + +        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->base->host = g_strdup(host); +        chan->value->base->port = g_strdup(port); +        chan->value->base->family = inet_netfamily(paddr->sa_family); + +        chan->value->connection_id = item->info->connection_id; +        chan->value->channel_type = item->info->type; +        chan->value->channel_id = item->info->id; +        chan->value->tls = item->info->flags & SPICE_CHANNEL_EVENT_FLAG_TLS; + +       /* XXX: waiting for the qapi to support GSList */ +        if (!cur_item) { +            head = cur_item = chan; +        } else { +            cur_item->next = chan; +            cur_item = chan; +        } +    } + +    return head; +} + +static QemuOptsList qemu_spice_opts = { +    .name = "spice", +    .head = QTAILQ_HEAD_INITIALIZER(qemu_spice_opts.head), +    .desc = { +        { +            .name = "port", +            .type = QEMU_OPT_NUMBER, +        },{ +            .name = "tls-port", +            .type = QEMU_OPT_NUMBER, +        },{ +            .name = "addr", +            .type = QEMU_OPT_STRING, +        },{ +            .name = "ipv4", +            .type = QEMU_OPT_BOOL, +        },{ +            .name = "ipv6", +            .type = QEMU_OPT_BOOL, +#ifdef SPICE_ADDR_FLAG_UNIX_ONLY +        },{ +            .name = "unix", +            .type = QEMU_OPT_BOOL, +#endif +        },{ +            .name = "password", +            .type = QEMU_OPT_STRING, +        },{ +            .name = "disable-ticketing", +            .type = QEMU_OPT_BOOL, +        },{ +            .name = "disable-copy-paste", +            .type = QEMU_OPT_BOOL, +        },{ +            .name = "disable-agent-file-xfer", +            .type = QEMU_OPT_BOOL, +        },{ +            .name = "sasl", +            .type = QEMU_OPT_BOOL, +        },{ +            .name = "x509-dir", +            .type = QEMU_OPT_STRING, +        },{ +            .name = "x509-key-file", +            .type = QEMU_OPT_STRING, +        },{ +            .name = "x509-key-password", +            .type = QEMU_OPT_STRING, +        },{ +            .name = "x509-cert-file", +            .type = QEMU_OPT_STRING, +        },{ +            .name = "x509-cacert-file", +            .type = QEMU_OPT_STRING, +        },{ +            .name = "x509-dh-key-file", +            .type = QEMU_OPT_STRING, +        },{ +            .name = "tls-ciphers", +            .type = QEMU_OPT_STRING, +        },{ +            .name = "tls-channel", +            .type = QEMU_OPT_STRING, +        },{ +            .name = "plaintext-channel", +            .type = QEMU_OPT_STRING, +        },{ +            .name = "image-compression", +            .type = QEMU_OPT_STRING, +        },{ +            .name = "jpeg-wan-compression", +            .type = QEMU_OPT_STRING, +        },{ +            .name = "zlib-glz-wan-compression", +            .type = QEMU_OPT_STRING, +        },{ +            .name = "streaming-video", +            .type = QEMU_OPT_STRING, +        },{ +            .name = "agent-mouse", +            .type = QEMU_OPT_BOOL, +        },{ +            .name = "playback-compression", +            .type = QEMU_OPT_BOOL, +        }, { +            .name = "seamless-migration", +            .type = QEMU_OPT_BOOL, +        }, +        { /* end of list */ } +    }, +}; + +SpiceInfo *qmp_query_spice(Error **errp) +{ +    QemuOpts *opts = QTAILQ_FIRST(&qemu_spice_opts.head); +    int port, tls_port; +    const char *addr; +    SpiceInfo *info; +    unsigned int major; +    unsigned int minor; +    unsigned int micro; + +    info = g_malloc0(sizeof(*info)); + +    if (!spice_server || !opts) { +        info->enabled = false; +        return info; +    } + +    info->enabled = true; +    info->migrated = spice_migration_completed; + +    addr = qemu_opt_get(opts, "addr"); +    port = qemu_opt_get_number(opts, "port", 0); +    tls_port = qemu_opt_get_number(opts, "tls-port", 0); + +    info->has_auth = true; +    info->auth = g_strdup(auth); + +    info->has_host = true; +    info->host = g_strdup(addr ? addr : "*"); + +    info->has_compiled_version = true; +    major = (SPICE_SERVER_VERSION & 0xff0000) >> 16; +    minor = (SPICE_SERVER_VERSION & 0xff00) >> 8; +    micro = SPICE_SERVER_VERSION & 0xff; +    info->compiled_version = g_strdup_printf("%d.%d.%d", major, minor, micro); + +    if (port) { +        info->has_port = true; +        info->port = port; +    } +    if (tls_port) { +        info->has_tls_port = true; +        info->tls_port = tls_port; +    } + +    info->mouse_mode = spice_server_is_server_mouse(spice_server) ? +                       SPICE_QUERY_MOUSE_MODE_SERVER : +                       SPICE_QUERY_MOUSE_MODE_CLIENT; + +    /* for compatibility with the original command */ +    info->has_channels = true; +    info->channels = qmp_query_spice_channels(); + +    return info; +} + +static void migration_state_notifier(Notifier *notifier, void *data) +{ +    MigrationState *s = data; + +    if (!spice_have_target_host) { +        return; +    } + +    if (migration_in_setup(s)) { +        spice_server_migrate_start(spice_server); +    } else if (migration_has_finished(s)) { +        spice_server_migrate_end(spice_server, true); +        spice_have_target_host = false; +    } else if (migration_has_failed(s)) { +        spice_server_migrate_end(spice_server, false); +        spice_have_target_host = false; +    } +} + +int qemu_spice_migrate_info(const char *hostname, int port, int tls_port, +                            const char *subject) +{ +    int ret; + +    ret = spice_server_migrate_connect(spice_server, hostname, +                                       port, tls_port, subject); +    spice_have_target_host = true; +    return ret; +} + +static int add_channel(void *opaque, const char *name, const char *value, +                       Error **errp) +{ +    int security = 0; +    int rc; + +    if (strcmp(name, "tls-channel") == 0) { +        int *tls_port = opaque; +        if (!*tls_port) { +            error_report("spice: tried to setup tls-channel" +                         " without specifying a TLS port"); +            exit(1); +        } +        security = SPICE_CHANNEL_SECURITY_SSL; +    } +    if (strcmp(name, "plaintext-channel") == 0) { +        security = SPICE_CHANNEL_SECURITY_NONE; +    } +    if (security == 0) { +        return 0; +    } +    if (strcmp(value, "default") == 0) { +        rc = spice_server_set_channel_security(spice_server, NULL, security); +    } else { +        rc = spice_server_set_channel_security(spice_server, value, security); +    } +    if (rc != 0) { +        error_report("spice: failed to set channel security for %s", value); +        exit(1); +    } +    return 0; +} + +static void vm_change_state_handler(void *opaque, int running, +                                    RunState state) +{ +    if (running) { +        qemu_spice_display_start(); +    } else { +        qemu_spice_display_stop(); +    } +} + +void qemu_spice_init(void) +{ +    QemuOpts *opts = QTAILQ_FIRST(&qemu_spice_opts.head); +    const char *password, *str, *x509_dir, *addr, +        *x509_key_password = NULL, +        *x509_dh_file = NULL, +        *tls_ciphers = NULL; +    char *x509_key_file = NULL, +        *x509_cert_file = NULL, +        *x509_cacert_file = NULL; +    int port, tls_port, addr_flags; +    spice_image_compression_t compression; +    spice_wan_compression_t wan_compr; +    bool seamless_migration; + +    qemu_thread_get_self(&me); + +    if (!opts) { +        return; +    } +    port = qemu_opt_get_number(opts, "port", 0); +    tls_port = qemu_opt_get_number(opts, "tls-port", 0); +    if (port < 0 || port > 65535) { +        error_report("spice port is out of range"); +        exit(1); +    } +    if (tls_port < 0 || tls_port > 65535) { +        error_report("spice tls-port is out of range"); +        exit(1); +    } +    password = qemu_opt_get(opts, "password"); + +    if (tls_port) { +        x509_dir = qemu_opt_get(opts, "x509-dir"); +        if (!x509_dir) { +            x509_dir = "."; +        } + +        str = qemu_opt_get(opts, "x509-key-file"); +        if (str) { +            x509_key_file = g_strdup(str); +        } else { +            x509_key_file = g_strdup_printf("%s/%s", x509_dir, +                                            X509_SERVER_KEY_FILE); +        } + +        str = qemu_opt_get(opts, "x509-cert-file"); +        if (str) { +            x509_cert_file = g_strdup(str); +        } else { +            x509_cert_file = g_strdup_printf("%s/%s", x509_dir, +                                             X509_SERVER_CERT_FILE); +        } + +        str = qemu_opt_get(opts, "x509-cacert-file"); +        if (str) { +            x509_cacert_file = g_strdup(str); +        } else { +            x509_cacert_file = g_strdup_printf("%s/%s", x509_dir, +                                               X509_CA_CERT_FILE); +        } + +        x509_key_password = qemu_opt_get(opts, "x509-key-password"); +        x509_dh_file = qemu_opt_get(opts, "x509-dh-key-file"); +        tls_ciphers = qemu_opt_get(opts, "tls-ciphers"); +    } + +    addr = qemu_opt_get(opts, "addr"); +    addr_flags = 0; +    if (qemu_opt_get_bool(opts, "ipv4", 0)) { +        addr_flags |= SPICE_ADDR_FLAG_IPV4_ONLY; +    } else if (qemu_opt_get_bool(opts, "ipv6", 0)) { +        addr_flags |= SPICE_ADDR_FLAG_IPV6_ONLY; +#ifdef SPICE_ADDR_FLAG_UNIX_ONLY +    } else if (qemu_opt_get_bool(opts, "unix", 0)) { +        addr_flags |= SPICE_ADDR_FLAG_UNIX_ONLY; +#endif +    } + +    spice_server = spice_server_new(); +    spice_server_set_addr(spice_server, addr ? addr : "", addr_flags); +    if (port) { +        spice_server_set_port(spice_server, port); +    } +    if (tls_port) { +        spice_server_set_tls(spice_server, tls_port, +                             x509_cacert_file, +                             x509_cert_file, +                             x509_key_file, +                             x509_key_password, +                             x509_dh_file, +                             tls_ciphers); +    } +    if (password) { +        qemu_spice_set_passwd(password, false, false); +    } +    if (qemu_opt_get_bool(opts, "sasl", 0)) { +        if (spice_server_set_sasl_appname(spice_server, "qemu") == -1 || +            spice_server_set_sasl(spice_server, 1) == -1) { +            error_report("spice: failed to enable sasl"); +            exit(1); +        } +        auth = "sasl"; +    } +    if (qemu_opt_get_bool(opts, "disable-ticketing", 0)) { +        auth = "none"; +        spice_server_set_noauth(spice_server); +    } + +    if (qemu_opt_get_bool(opts, "disable-copy-paste", 0)) { +        spice_server_set_agent_copypaste(spice_server, false); +    } + +    if (qemu_opt_get_bool(opts, "disable-agent-file-xfer", 0)) { +#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; +    str = qemu_opt_get(opts, "image-compression"); +    if (str) { +        compression = parse_compression(str); +    } +    spice_server_set_image_compression(spice_server, compression); + +    wan_compr = SPICE_WAN_COMPRESSION_AUTO; +    str = qemu_opt_get(opts, "jpeg-wan-compression"); +    if (str) { +        wan_compr = parse_wan_compression(str); +    } +    spice_server_set_jpeg_compression(spice_server, wan_compr); + +    wan_compr = SPICE_WAN_COMPRESSION_AUTO; +    str = qemu_opt_get(opts, "zlib-glz-wan-compression"); +    if (str) { +        wan_compr = parse_wan_compression(str); +    } +    spice_server_set_zlib_glz_compression(spice_server, wan_compr); + +    str = qemu_opt_get(opts, "streaming-video"); +    if (str) { +        int streaming_video = parse_stream_video(str); +        spice_server_set_streaming_video(spice_server, streaming_video); +    } else { +        spice_server_set_streaming_video(spice_server, SPICE_STREAM_VIDEO_OFF); +    } + +    spice_server_set_agent_mouse +        (spice_server, qemu_opt_get_bool(opts, "agent-mouse", 1)); +    spice_server_set_playback_compression +        (spice_server, qemu_opt_get_bool(opts, "playback-compression", 1)); + +    qemu_opt_foreach(opts, add_channel, &tls_port, NULL); + +    spice_server_set_name(spice_server, qemu_name); +    spice_server_set_uuid(spice_server, qemu_uuid); + +    seamless_migration = qemu_opt_get_bool(opts, "seamless-migration", 0); +    spice_server_set_seamless_migration(spice_server, seamless_migration); +    if (spice_server_init(spice_server, &core_interface) != 0) { +        error_report("failed to initialize spice server"); +        exit(1); +    }; +    using_spice = 1; + +    migration_state.notify = migration_state_notifier; +    add_migration_state_change_notifier(&migration_state); +    spice_migrate.base.sif = &migrate_interface.base; +    qemu_spice_add_interface(&spice_migrate.base); + +    qemu_spice_input_init(); +    qemu_spice_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(); +#endif +} + +int qemu_spice_add_interface(SpiceBaseInstance *sin) +{ +    if (!spice_server) { +        if (QTAILQ_FIRST(&qemu_spice_opts.head) != NULL) { +            error_report("Oops: spice configured but not active"); +            exit(1); +        } +        /* +         * Create a spice server instance. +         * It does *not* listen on the network. +         * It handles QXL local rendering only. +         * +         * With a command line like '-vnc :0 -vga qxl' you'll end up here. +         */ +        spice_server = spice_server_new(); +        spice_server_set_sasl_appname(spice_server, "qemu"); +        spice_server_init(spice_server, &core_interface); +        qemu_add_vm_change_state_handler(vm_change_state_handler, NULL); +    } + +    return spice_server_add_interface(spice_server, sin); +} + +static GSList *spice_consoles; + +bool qemu_spice_have_display_interface(QemuConsole *con) +{ +    if (g_slist_find(spice_consoles, con)) { +        return true; +    } +    return false; +} + +int qemu_spice_add_display_interface(QXLInstance *qxlin, QemuConsole *con) +{ +    if (g_slist_find(spice_consoles, con)) { +        return -1; +    } +    qxlin->id = qemu_console_get_index(con); +    spice_consoles = g_slist_append(spice_consoles, con); +    return qemu_spice_add_interface(&qxlin->base); +} + +static int qemu_spice_set_ticket(bool fail_if_conn, bool disconnect_if_conn) +{ +    time_t lifetime, now = time(NULL); +    char *passwd; + +    if (now < auth_expires) { +        passwd = auth_passwd; +        lifetime = (auth_expires - now); +        if (lifetime > INT_MAX) { +            lifetime = INT_MAX; +        } +    } else { +        passwd = NULL; +        lifetime = 1; +    } +    return spice_server_set_ticket(spice_server, passwd, lifetime, +                                   fail_if_conn, disconnect_if_conn); +} + +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) +{ +    auth_expires = expires; +    return qemu_spice_set_ticket(false, false); +} + +int qemu_spice_display_add_client(int csock, int skipauth, int tls) +{ +    if (tls) { +        return spice_server_add_ssl_client(spice_server, csock, skipauth); +    } else { +        return spice_server_add_client(spice_server, csock, skipauth); +    } +} + +void qemu_spice_display_start(void) +{ +    spice_display_is_running = true; +    spice_server_vm_start(spice_server); +} + +void qemu_spice_display_stop(void) +{ +    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 void spice_register_config(void) +{ +    qemu_add_opts(&qemu_spice_opts); +} +machine_init(spice_register_config); diff --git a/ui/spice-display.c b/ui/spice-display.c new file mode 100644 index 00000000..0360abfd --- /dev/null +++ b/ui/spice-display.c @@ -0,0 +1,808 @@ +/* + * 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-common.h" +#include "ui/qemu-spice.h" +#include "qemu/timer.h" +#include "qemu/queue.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); +    } +} + +int qemu_spice_rect_is_empty(const QXLRect* r) +{ +    return r->top == r->bottom || r->left == r->right; +} + +void qemu_spice_rect_union(QXLRect *dest, const QXLRect *r) +{ +    if (qemu_spice_rect_is_empty(r)) { +        return; +    } + +    if (qemu_spice_rect_is_empty(dest)) { +        *dest = *r; +        return; +    } + +    dest->top = MIN(dest->top, r->top); +    dest->left = MIN(dest->left, r->left); +    dest->bottom = MAX(dest->bottom, r->bottom); +    dest->right = MAX(dest->right, r->right); +} + +QXLCookie *qxl_cookie_new(int type, uint64_t io) +{ +    QXLCookie *cookie; + +    cookie = g_malloc0(sizeof(*cookie)); +    cookie->type = type; +    cookie->io = io; +    return cookie; +} + +void qemu_spice_add_memslot(SimpleSpiceDisplay *ssd, QXLDevMemSlot *memslot, +                            qxl_async_io async) +{ +    trace_qemu_spice_add_memslot(ssd->qxl.id, memslot->slot_id, +                                memslot->virt_start, memslot->virt_end, +                                async); + +    if (async != QXL_SYNC) { +        spice_qxl_add_memslot_async(&ssd->qxl, memslot, +                (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO, +                                          QXL_IO_MEMSLOT_ADD_ASYNC)); +    } else { +        spice_qxl_add_memslot(&ssd->qxl, memslot); +    } +} + +void qemu_spice_del_memslot(SimpleSpiceDisplay *ssd, uint32_t gid, uint32_t sid) +{ +    trace_qemu_spice_del_memslot(ssd->qxl.id, gid, sid); +    spice_qxl_del_memslot(&ssd->qxl, gid, sid); +} + +void qemu_spice_create_primary_surface(SimpleSpiceDisplay *ssd, uint32_t id, +                                       QXLDevSurfaceCreate *surface, +                                       qxl_async_io async) +{ +    trace_qemu_spice_create_primary_surface(ssd->qxl.id, id, surface, async); +    if (async != QXL_SYNC) { +        spice_qxl_create_primary_surface_async(&ssd->qxl, id, surface, +                (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO, +                                          QXL_IO_CREATE_PRIMARY_ASYNC)); +    } else { +        spice_qxl_create_primary_surface(&ssd->qxl, id, surface); +    } +} + +void qemu_spice_destroy_primary_surface(SimpleSpiceDisplay *ssd, +                                        uint32_t id, qxl_async_io async) +{ +    trace_qemu_spice_destroy_primary_surface(ssd->qxl.id, id, async); +    if (async != QXL_SYNC) { +        spice_qxl_destroy_primary_surface_async(&ssd->qxl, id, +                (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO, +                                          QXL_IO_DESTROY_PRIMARY_ASYNC)); +    } else { +        spice_qxl_destroy_primary_surface(&ssd->qxl, id); +    } +} + +void qemu_spice_wakeup(SimpleSpiceDisplay *ssd) +{ +    trace_qemu_spice_wakeup(ssd->qxl.id); +    spice_qxl_wakeup(&ssd->qxl); +} + +static void qemu_spice_create_one_update(SimpleSpiceDisplay *ssd, +                                         QXLRect *rect) +{ +    SimpleSpiceUpdate *update; +    QXLDrawable *drawable; +    QXLImage *image; +    QXLCommand *cmd; +    int bw, bh; +    struct timespec time_space; +    pixman_image_t *dest; + +    trace_qemu_spice_create_update( +           rect->left, rect->right, +           rect->top, rect->bottom); + +    update   = g_malloc0(sizeof(*update)); +    drawable = &update->drawable; +    image    = &update->image; +    cmd      = &update->ext.cmd; + +    bw       = rect->right - rect->left; +    bh       = rect->bottom - rect->top; +    update->bitmap = g_malloc(bw * bh * 4); + +    drawable->bbox            = *rect; +    drawable->clip.type       = SPICE_CLIP_TYPE_NONE; +    drawable->effect          = QXL_EFFECT_OPAQUE; +    drawable->release_info.id = (uintptr_t)(&update->ext); +    drawable->type            = QXL_DRAW_COPY; +    drawable->surfaces_dest[0] = -1; +    drawable->surfaces_dest[1] = -1; +    drawable->surfaces_dest[2] = -1; +    clock_gettime(CLOCK_MONOTONIC, &time_space); +    /* time in milliseconds from epoch. */ +    drawable->mm_time = time_space.tv_sec * 1000 +                      + time_space.tv_nsec / 1000 / 1000; + +    drawable->u.copy.rop_descriptor  = SPICE_ROPD_OP_PUT; +    drawable->u.copy.src_bitmap      = (uintptr_t)image; +    drawable->u.copy.src_area.right  = bw; +    drawable->u.copy.src_area.bottom = bh; + +    QXL_SET_IMAGE_ID(image, QXL_IMAGE_GROUP_DEVICE, ssd->unique++); +    image->descriptor.type   = SPICE_IMAGE_TYPE_BITMAP; +    image->bitmap.flags      = QXL_BITMAP_DIRECT | QXL_BITMAP_TOP_DOWN; +    image->bitmap.stride     = bw * 4; +    image->descriptor.width  = image->bitmap.x = bw; +    image->descriptor.height = image->bitmap.y = bh; +    image->bitmap.data = (uintptr_t)(update->bitmap); +    image->bitmap.palette = 0; +    image->bitmap.format = SPICE_BITMAP_FMT_32BIT; + +    dest = pixman_image_create_bits(PIXMAN_LE_x8r8g8b8, bw, bh, +                                    (void *)update->bitmap, bw * 4); +    pixman_image_composite(PIXMAN_OP_SRC, ssd->surface, NULL, ssd->mirror, +                           rect->left, rect->top, 0, 0, +                           rect->left, rect->top, bw, bh); +    pixman_image_composite(PIXMAN_OP_SRC, ssd->mirror, NULL, dest, +                           rect->left, rect->top, 0, 0, +                           0, 0, bw, bh); +    pixman_image_unref(dest); + +    cmd->type = QXL_CMD_DRAW; +    cmd->data = (uintptr_t)drawable; + +    QTAILQ_INSERT_TAIL(&ssd->updates, update, next); +} + +static void qemu_spice_create_update(SimpleSpiceDisplay *ssd) +{ +    static const int blksize = 32; +    int blocks = (surface_width(ssd->ds) + blksize - 1) / blksize; +    int dirty_top[blocks]; +    int y, yoff1, yoff2, x, xoff, blk, bw; +    int bpp = surface_bytes_per_pixel(ssd->ds); +    uint8_t *guest, *mirror; + +    if (qemu_spice_rect_is_empty(&ssd->dirty)) { +        return; +    }; + +    for (blk = 0; blk < blocks; blk++) { +        dirty_top[blk] = -1; +    } + +    guest = surface_data(ssd->ds); +    mirror = (void *)pixman_image_get_data(ssd->mirror); +    for (y = ssd->dirty.top; y < ssd->dirty.bottom; y++) { +        yoff1 = y * surface_stride(ssd->ds); +        yoff2 = y * pixman_image_get_stride(ssd->mirror); +        for (x = ssd->dirty.left; x < ssd->dirty.right; x += blksize) { +            xoff = x * bpp; +            blk = x / blksize; +            bw = MIN(blksize, ssd->dirty.right - x); +            if (memcmp(guest + yoff1 + xoff, +                       mirror + yoff2 + xoff, +                       bw * bpp) == 0) { +                if (dirty_top[blk] != -1) { +                    QXLRect update = { +                        .top    = dirty_top[blk], +                        .bottom = y, +                        .left   = x, +                        .right  = x + bw, +                    }; +                    qemu_spice_create_one_update(ssd, &update); +                    dirty_top[blk] = -1; +                } +            } else { +                if (dirty_top[blk] == -1) { +                    dirty_top[blk] = y; +                } +            } +        } +    } + +    for (x = ssd->dirty.left; x < ssd->dirty.right; x += blksize) { +        blk = x / blksize; +        bw = MIN(blksize, ssd->dirty.right - x); +        if (dirty_top[blk] != -1) { +            QXLRect update = { +                .top    = dirty_top[blk], +                .bottom = ssd->dirty.bottom, +                .left   = x, +                .right  = x + bw, +            }; +            qemu_spice_create_one_update(ssd, &update); +            dirty_top[blk] = -1; +        } +    } + +    memset(&ssd->dirty, 0, sizeof(ssd->dirty)); +} + +static SimpleSpiceCursor* +qemu_spice_create_cursor_update(SimpleSpiceDisplay *ssd, +                                QEMUCursor *c, +                                int on) +{ +    size_t size = c ? c->width * c->height * 4 : 0; +    SimpleSpiceCursor *update; +    QXLCursorCmd *ccmd; +    QXLCursor *cursor; +    QXLCommand *cmd; + +    update   = g_malloc0(sizeof(*update) + size); +    ccmd     = &update->cmd; +    cursor   = &update->cursor; +    cmd      = &update->ext.cmd; + +    if (c) { +        ccmd->type = QXL_CURSOR_SET; +        ccmd->u.set.position.x = ssd->ptr_x + ssd->hot_x; +        ccmd->u.set.position.y = ssd->ptr_y + ssd->hot_y; +        ccmd->u.set.visible    = true; +        ccmd->u.set.shape      = (uintptr_t)cursor; +        cursor->header.unique     = ssd->unique++; +        cursor->header.type       = SPICE_CURSOR_TYPE_ALPHA; +        cursor->header.width      = c->width; +        cursor->header.height     = c->height; +        cursor->header.hot_spot_x = c->hot_x; +        cursor->header.hot_spot_y = c->hot_y; +        cursor->data_size         = size; +        cursor->chunk.data_size   = size; +        memcpy(cursor->chunk.data, c->data, size); +    } else if (!on) { +        ccmd->type = QXL_CURSOR_HIDE; +    } else { +        ccmd->type = QXL_CURSOR_MOVE; +        ccmd->u.position.x = ssd->ptr_x + ssd->hot_x; +        ccmd->u.position.y = ssd->ptr_y + ssd->hot_y; +    } +    ccmd->release_info.id = (uintptr_t)(&update->ext); + +    cmd->type = QXL_CMD_CURSOR; +    cmd->data = (uintptr_t)ccmd; + +    return update; +} + +/* + * Called from spice server thread context (via interface_release_resource) + * We do *not* hold the global qemu mutex here, so extra care is needed + * when calling qemu functions.  QEMU interfaces used: + *    - g_free (underlying glibc free is re-entrant). + */ +void qemu_spice_destroy_update(SimpleSpiceDisplay *sdpy, SimpleSpiceUpdate *update) +{ +    g_free(update->bitmap); +    g_free(update); +} + +void qemu_spice_create_host_memslot(SimpleSpiceDisplay *ssd) +{ +    QXLDevMemSlot memslot; + +    dprint(1, "%s/%d:\n", __func__, ssd->qxl.id); + +    memset(&memslot, 0, sizeof(memslot)); +    memslot.slot_group_id = MEMSLOT_GROUP_HOST; +    memslot.virt_end = ~0; +    qemu_spice_add_memslot(ssd, &memslot, QXL_SYNC); +} + +void qemu_spice_create_host_primary(SimpleSpiceDisplay *ssd) +{ +    QXLDevSurfaceCreate surface; +    uint64_t surface_size; + +    memset(&surface, 0, sizeof(surface)); + +    surface_size = (uint64_t) surface_width(ssd->ds) * +        surface_height(ssd->ds) * 4; +    assert(surface_size > 0); +    assert(surface_size < INT_MAX); +    if (ssd->bufsize < surface_size) { +        ssd->bufsize = surface_size; +        g_free(ssd->buf); +        ssd->buf = g_malloc(ssd->bufsize); +    } + +    dprint(1, "%s/%d: %ux%u (size %" PRIu64 "/%d)\n", __func__, ssd->qxl.id, +           surface_width(ssd->ds), surface_height(ssd->ds), +           surface_size, ssd->bufsize); + +    surface.format     = SPICE_SURFACE_FMT_32_xRGB; +    surface.width      = surface_width(ssd->ds); +    surface.height     = surface_height(ssd->ds); +    surface.stride     = -surface.width * 4; +    surface.mouse_mode = true; +    surface.flags      = 0; +    surface.type       = 0; +    surface.mem        = (uintptr_t)ssd->buf; +    surface.group_id   = MEMSLOT_GROUP_HOST; + +    qemu_spice_create_primary_surface(ssd, 0, &surface, QXL_SYNC); +} + +void qemu_spice_destroy_host_primary(SimpleSpiceDisplay *ssd) +{ +    dprint(1, "%s/%d:\n", __func__, ssd->qxl.id); + +    qemu_spice_destroy_primary_surface(ssd, 0, QXL_SYNC); +} + +void qemu_spice_display_init_common(SimpleSpiceDisplay *ssd) +{ +    qemu_mutex_init(&ssd->lock); +    QTAILQ_INIT(&ssd->updates); +    ssd->mouse_x = -1; +    ssd->mouse_y = -1; +    if (ssd->num_surfaces == 0) { +        ssd->num_surfaces = 1024; +    } +} + +/* display listener callbacks */ + +void qemu_spice_display_update(SimpleSpiceDisplay *ssd, +                               int x, int y, int w, int h) +{ +    QXLRect update_area; + +    dprint(2, "%s/%d: x %d y %d w %d h %d\n", __func__, +           ssd->qxl.id, x, y, w, h); +    update_area.left = x, +    update_area.right = x + w; +    update_area.top = y; +    update_area.bottom = y + h; + +    if (qemu_spice_rect_is_empty(&ssd->dirty)) { +        ssd->notify++; +    } +    qemu_spice_rect_union(&ssd->dirty, &update_area); +} + +void qemu_spice_display_switch(SimpleSpiceDisplay *ssd, +                               DisplaySurface *surface) +{ +    SimpleSpiceUpdate *update; +    bool need_destroy; + +    if (surface && ssd->surface && +        surface_width(surface) == pixman_image_get_width(ssd->surface) && +        surface_height(surface) == pixman_image_get_height(ssd->surface)) { +        /* no-resize fast path: just swap backing store */ +        dprint(1, "%s/%d: fast (%dx%d)\n", __func__, ssd->qxl.id, +               surface_width(surface), surface_height(surface)); +        qemu_mutex_lock(&ssd->lock); +        ssd->ds = surface; +        pixman_image_unref(ssd->surface); +        ssd->surface = pixman_image_ref(ssd->ds->image); +        qemu_mutex_unlock(&ssd->lock); +        qemu_spice_display_update(ssd, 0, 0, +                                  surface_width(surface), +                                  surface_height(surface)); +        return; +    } + +    /* full mode switch */ +    dprint(1, "%s/%d: full (%dx%d -> %dx%d)\n", __func__, ssd->qxl.id, +           ssd->surface ? pixman_image_get_width(ssd->surface)  : 0, +           ssd->surface ? pixman_image_get_height(ssd->surface) : 0, +           surface ? surface_width(surface)  : 0, +           surface ? surface_height(surface) : 0); + +    memset(&ssd->dirty, 0, sizeof(ssd->dirty)); +    if (ssd->surface) { +        pixman_image_unref(ssd->surface); +        ssd->surface = NULL; +        pixman_image_unref(ssd->mirror); +        ssd->mirror = NULL; +    } + +    qemu_mutex_lock(&ssd->lock); +    need_destroy = (ssd->ds != NULL); +    ssd->ds = surface; +    while ((update = QTAILQ_FIRST(&ssd->updates)) != NULL) { +        QTAILQ_REMOVE(&ssd->updates, update, next); +        qemu_spice_destroy_update(ssd, update); +    } +    qemu_mutex_unlock(&ssd->lock); +    if (need_destroy) { +        qemu_spice_destroy_host_primary(ssd); +    } +    if (ssd->ds) { +        ssd->surface = pixman_image_ref(ssd->ds->image); +        ssd->mirror  = qemu_pixman_mirror_create(ssd->ds->format, +                                                 ssd->ds->image); +        qemu_spice_create_host_primary(ssd); +    } + +    memset(&ssd->dirty, 0, sizeof(ssd->dirty)); +    ssd->notify++; +} + +static void qemu_spice_cursor_refresh_unlocked(SimpleSpiceDisplay *ssd) +{ +    if (ssd->cursor) { +        assert(ssd->dcl.con); +        dpy_cursor_define(ssd->dcl.con, ssd->cursor); +        cursor_put(ssd->cursor); +        ssd->cursor = NULL; +    } +    if (ssd->mouse_x != -1 && ssd->mouse_y != -1) { +        assert(ssd->dcl.con); +        dpy_mouse_set(ssd->dcl.con, ssd->mouse_x, ssd->mouse_y, 1); +        ssd->mouse_x = -1; +        ssd->mouse_y = -1; +    } +} + +void qemu_spice_cursor_refresh_bh(void *opaque) +{ +    SimpleSpiceDisplay *ssd = opaque; + +    qemu_mutex_lock(&ssd->lock); +    qemu_spice_cursor_refresh_unlocked(ssd); +    qemu_mutex_unlock(&ssd->lock); +} + +void qemu_spice_display_refresh(SimpleSpiceDisplay *ssd) +{ +    dprint(3, "%s/%d:\n", __func__, ssd->qxl.id); +    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++; +    } +    qemu_mutex_unlock(&ssd->lock); + +    if (ssd->notify) { +        ssd->notify = 0; +        qemu_spice_wakeup(ssd); +        dprint(2, "%s/%d: notify\n", __func__, ssd->qxl.id); +    } +} + +/* spice display interface callbacks */ + +static void interface_attach_worker(QXLInstance *sin, QXLWorker *qxl_worker) +{ +    SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl); + +    dprint(1, "%s/%d:\n", __func__, ssd->qxl.id); +    ssd->worker = qxl_worker; +} + +static void interface_set_compression_level(QXLInstance *sin, int level) +{ +    dprint(1, "%s/%d:\n", __func__, sin->id); +    /* nothing to do */ +} + +static void interface_set_mm_time(QXLInstance *sin, uint32_t mm_time) +{ +    dprint(3, "%s/%d:\n", __func__, sin->id); +    /* nothing to do */ +} + +static void interface_get_init_info(QXLInstance *sin, QXLDevInitInfo *info) +{ +    SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl); + +    info->memslot_gen_bits = MEMSLOT_GENERATION_BITS; +    info->memslot_id_bits  = MEMSLOT_SLOT_BITS; +    info->num_memslots = NUM_MEMSLOTS; +    info->num_memslots_groups = NUM_MEMSLOTS_GROUPS; +    info->internal_groupslot_id = 0; +    info->qxl_ram_size = 16 * 1024 * 1024; +    info->n_surfaces = ssd->num_surfaces; +} + +static int interface_get_command(QXLInstance *sin, QXLCommandExt *ext) +{ +    SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl); +    SimpleSpiceUpdate *update; +    int ret = false; + +    dprint(3, "%s/%d:\n", __func__, ssd->qxl.id); + +    qemu_mutex_lock(&ssd->lock); +    update = QTAILQ_FIRST(&ssd->updates); +    if (update != NULL) { +        QTAILQ_REMOVE(&ssd->updates, update, next); +        *ext = update->ext; +        ret = true; +    } +    qemu_mutex_unlock(&ssd->lock); + +    return ret; +} + +static int interface_req_cmd_notification(QXLInstance *sin) +{ +    dprint(1, "%s/%d:\n", __func__, sin->id); +    return 1; +} + +static void interface_release_resource(QXLInstance *sin, +                                       QXLReleaseInfoExt rext) +{ +    SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl); +    SimpleSpiceUpdate *update; +    SimpleSpiceCursor *cursor; +    QXLCommandExt *ext; + +    dprint(2, "%s/%d:\n", __func__, ssd->qxl.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, QXLCommandExt *ext) +{ +    SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl); +    int ret; + +    dprint(3, "%s/%d:\n", __func__, ssd->qxl.id); + +    qemu_mutex_lock(&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; +    } +    qemu_mutex_unlock(&ssd->lock); +    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__); +    abort(); +} + +static int interface_flush_resources(QXLInstance *sin) +{ +    fprintf(stderr, "%s: abort()\n", __FUNCTION__); +    abort(); +    return 0; +} + +static void interface_update_area_complete(QXLInstance *sin, +        uint32_t surface_id, +        QXLRect *dirty, uint32_t num_updated_rects) +{ +    /* should never be called, used in qxl native mode only */ +    fprintf(stderr, "%s: abort()\n", __func__); +    abort(); +} + +/* called from spice server thread context only */ +static void interface_async_complete(QXLInstance *sin, uint64_t cookie_token) +{ +    /* should never be called, used in qxl native mode only */ +    fprintf(stderr, "%s: abort()\n", __func__); +    abort(); +} + +static void interface_set_client_capabilities(QXLInstance *sin, +                                              uint8_t client_present, +                                              uint8_t caps[58]) +{ +    dprint(3, "%s:\n", __func__); +} + +static int interface_client_monitors_config(QXLInstance *sin, +                                            VDAgentMonitorsConfig *mc) +{ +    SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl); +    QemuUIInfo info; + +    if (!dpy_ui_info_supported(ssd->dcl.con)) { +        return 0; /* == not supported by guest */ +    } + +    if (!mc) { +        return 1; +    } + +    /* +     * FIXME: multihead is tricky due to the way +     * spice has multihead implemented. +     */ +    memset(&info, 0, sizeof(info)); +    if (mc->num_of_monitors > 0) { +        info.width  = mc->monitors[0].width; +        info.height = mc->monitors[0].height; +    } +    dpy_set_ui_info(ssd->dcl.con, &info); +    dprint(1, "%s/%d: size %dx%d\n", __func__, ssd->qxl.id, +           info.width, info.height); +    return 1; +} + +static const QXLInterface dpy_interface = { +    .base.type               = SPICE_INTERFACE_QXL, +    .base.description        = "qemu simple display", +    .base.major_version      = SPICE_INTERFACE_QXL_MAJOR, +    .base.minor_version      = SPICE_INTERFACE_QXL_MINOR, + +    .attache_worker          = interface_attach_worker, +    .set_compression_level   = interface_set_compression_level, +    .set_mm_time             = interface_set_mm_time, +    .get_init_info           = interface_get_init_info, + +    /* the callbacks below are called from spice server thread context */ +    .get_command             = interface_get_command, +    .req_cmd_notification    = interface_req_cmd_notification, +    .release_resource        = interface_release_resource, +    .get_cursor_command      = interface_get_cursor_command, +    .req_cursor_notification = interface_req_cursor_notification, +    .notify_update           = interface_notify_update, +    .flush_resources         = interface_flush_resources, +    .async_complete          = interface_async_complete, +    .update_area_complete    = interface_update_area_complete, +    .set_client_capabilities = interface_set_client_capabilities, +    .client_monitors_config  = interface_client_monitors_config, +}; + +static void display_update(DisplayChangeListener *dcl, +                           int x, int y, int w, int h) +{ +    SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl); +    qemu_spice_display_update(ssd, x, y, w, h); +} + +static void display_switch(DisplayChangeListener *dcl, +                           DisplaySurface *surface) +{ +    SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl); +    qemu_spice_display_switch(ssd, surface); +} + +static void display_refresh(DisplayChangeListener *dcl) +{ +    SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl); +    qemu_spice_display_refresh(ssd); +} + +static void display_mouse_set(DisplayChangeListener *dcl, +                              int x, int y, int on) +{ +    SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl); + +    qemu_mutex_lock(&ssd->lock); +    ssd->ptr_x = x; +    ssd->ptr_y = y; +    if (ssd->ptr_move) { +        g_free(ssd->ptr_move); +    } +    ssd->ptr_move = qemu_spice_create_cursor_update(ssd, NULL, on); +    qemu_mutex_unlock(&ssd->lock); +} + +static void display_mouse_define(DisplayChangeListener *dcl, +                                 QEMUCursor *c) +{ +    SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl); + +    qemu_mutex_lock(&ssd->lock); +    ssd->hot_x = c->hot_x; +    ssd->hot_y = c->hot_y; +    if (ssd->ptr_move) { +        g_free(ssd->ptr_move); +        ssd->ptr_move = NULL; +    } +    if (ssd->ptr_define) { +        g_free(ssd->ptr_define); +    } +    ssd->ptr_define = qemu_spice_create_cursor_update(ssd, c, 0); +    qemu_mutex_unlock(&ssd->lock); +} + +static const DisplayChangeListenerOps display_listener_ops = { +    .dpy_name             = "spice", +    .dpy_gfx_update       = display_update, +    .dpy_gfx_switch       = display_switch, +    .dpy_gfx_check_format = qemu_pixman_check_format, +    .dpy_refresh          = display_refresh, +    .dpy_mouse_set        = display_mouse_set, +    .dpy_cursor_define    = display_mouse_define, +}; + +static void qemu_spice_display_init_one(QemuConsole *con) +{ +    SimpleSpiceDisplay *ssd = g_new0(SimpleSpiceDisplay, 1); + +    qemu_spice_display_init_common(ssd); + +    ssd->qxl.base.sif = &dpy_interface.base; +    qemu_spice_add_display_interface(&ssd->qxl, con); +    assert(ssd->worker); + +    qemu_spice_create_host_memslot(ssd); + +    ssd->dcl.ops = &display_listener_ops; +    ssd->dcl.con = con; +    register_displaychangelistener(&ssd->dcl); +} + +void qemu_spice_display_init(void) +{ +    QemuConsole *con; +    int i; + +    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; +        } +        qemu_spice_display_init_one(con); +    } +} diff --git a/ui/spice-input.c b/ui/spice-input.c new file mode 100644 index 00000000..c342e0dc --- /dev/null +++ b/ui/spice-input.c @@ -0,0 +1,249 @@ +/* + * 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 <stdlib.h> +#include <stdio.h> +#include <stdbool.h> +#include <string.h> + +#include <spice.h> +#include <spice/enums.h> + +#include "qemu-common.h" +#include "ui/qemu-spice.h" +#include "ui/console.h" +#include "ui/keymaps.h" +#include "ui/input.h" + +/* keyboard bits */ + +typedef struct QemuSpiceKbd { +    SpiceKbdInstance sin; +    int ledstate; +    bool emul0; +} 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, +    .base.description   = "qemu keyboard", +    .base.major_version = SPICE_INTERFACE_KEYBOARD_MAJOR, +    .base.minor_version = SPICE_INTERFACE_KEYBOARD_MINOR, +    .push_scan_freg     = kbd_push_key, +    .get_leds           = kbd_get_leds, +}; + +static void kbd_push_key(SpiceKbdInstance *sin, uint8_t scancode) +{ +    QemuSpiceKbd *kbd = container_of(sin, QemuSpiceKbd, sin); +    int keycode; +    bool up; + +    if (scancode == SCANCODE_EMUL0) { +        kbd->emul0 = true; +        return; +    } +    keycode = scancode & ~SCANCODE_UP; +    up = scancode & SCANCODE_UP; +    if (kbd->emul0) { +        kbd->emul0 = false; +        keycode |= SCANCODE_GREY; +    } + +    qemu_input_event_send_key_number(NULL, keycode, !up); +} + +static uint8_t kbd_get_leds(SpiceKbdInstance *sin) +{ +    QemuSpiceKbd *kbd = container_of(sin, QemuSpiceKbd, sin); +    return kbd->ledstate; +} + +static void kbd_leds(void *opaque, int ledstate) +{ +    QemuSpiceKbd *kbd = opaque; + +    kbd->ledstate = 0; +    if (ledstate & QEMU_SCROLL_LOCK_LED) { +        kbd->ledstate |= SPICE_KEYBOARD_MODIFIER_FLAGS_SCROLL_LOCK; +    } +    if (ledstate & QEMU_NUM_LOCK_LED) { +        kbd->ledstate |= SPICE_KEYBOARD_MODIFIER_FLAGS_NUM_LOCK; +    } +    if (ledstate & QEMU_CAPS_LOCK_LED) { +        kbd->ledstate |= SPICE_KEYBOARD_MODIFIER_FLAGS_CAPS_LOCK; +    } +    spice_server_kbd_leds(&kbd->sin, ledstate); +} + +/* mouse bits */ + +typedef struct QemuSpicePointer { +    SpiceMouseInstance  mouse; +    SpiceTabletInstance tablet; +    int width, height; +    uint32_t last_bmask; +    Notifier mouse_mode; +    bool absolute; +} QemuSpicePointer; + +static void spice_update_buttons(QemuSpicePointer *pointer, +                                 int wheel, uint32_t button_mask) +{ +    static uint32_t bmap[INPUT_BUTTON_MAX] = { +        [INPUT_BUTTON_LEFT]        = 0x01, +        [INPUT_BUTTON_MIDDLE]      = 0x04, +        [INPUT_BUTTON_RIGHT]       = 0x02, +        [INPUT_BUTTON_WHEEL_UP]    = 0x10, +        [INPUT_BUTTON_WHEEL_DOWN]  = 0x20, +    }; + +    if (wheel < 0) { +        button_mask |= 0x10; +    } +    if (wheel > 0) { +        button_mask |= 0x20; +    } + +    if (pointer->last_bmask == button_mask) { +        return; +    } +    qemu_input_update_buttons(NULL, bmap, pointer->last_bmask, button_mask); +    pointer->last_bmask = button_mask; +} + +static void mouse_motion(SpiceMouseInstance *sin, int dx, int dy, int dz, +                         uint32_t buttons_state) +{ +    QemuSpicePointer *pointer = container_of(sin, QemuSpicePointer, mouse); +    spice_update_buttons(pointer, dz, buttons_state); +    qemu_input_queue_rel(NULL, INPUT_AXIS_X, dx); +    qemu_input_queue_rel(NULL, INPUT_AXIS_Y, dy); +    qemu_input_event_sync(); +} + +static void mouse_buttons(SpiceMouseInstance *sin, uint32_t buttons_state) +{ +    QemuSpicePointer *pointer = container_of(sin, QemuSpicePointer, mouse); +    spice_update_buttons(pointer, 0, buttons_state); +    qemu_input_event_sync(); +} + +static const SpiceMouseInterface mouse_interface = { +    .base.type          = SPICE_INTERFACE_MOUSE, +    .base.description   = "mouse", +    .base.major_version = SPICE_INTERFACE_MOUSE_MAJOR, +    .base.minor_version = SPICE_INTERFACE_MOUSE_MINOR, +    .motion             = mouse_motion, +    .buttons            = mouse_buttons, +}; + +static void tablet_set_logical_size(SpiceTabletInstance* sin, int width, int height) +{ +    QemuSpicePointer *pointer = container_of(sin, QemuSpicePointer, tablet); + +    if (height < 16) { +        height = 16; +    } +    if (width < 16) { +        width = 16; +    } +    pointer->width  = width; +    pointer->height = height; +} + +static void tablet_position(SpiceTabletInstance* sin, int x, int y, +                            uint32_t buttons_state) +{ +    QemuSpicePointer *pointer = container_of(sin, QemuSpicePointer, tablet); + +    spice_update_buttons(pointer, 0, buttons_state); +    qemu_input_queue_abs(NULL, INPUT_AXIS_X, x, pointer->width); +    qemu_input_queue_abs(NULL, INPUT_AXIS_Y, y, pointer->height); +    qemu_input_event_sync(); +} + + +static void tablet_wheel(SpiceTabletInstance* sin, int wheel, +                         uint32_t buttons_state) +{ +    QemuSpicePointer *pointer = container_of(sin, QemuSpicePointer, tablet); + +    spice_update_buttons(pointer, wheel, buttons_state); +    qemu_input_event_sync(); +} + +static void tablet_buttons(SpiceTabletInstance *sin, +                           uint32_t buttons_state) +{ +    QemuSpicePointer *pointer = container_of(sin, QemuSpicePointer, tablet); + +    spice_update_buttons(pointer, 0, buttons_state); +    qemu_input_event_sync(); +} + +static const SpiceTabletInterface tablet_interface = { +    .base.type          = SPICE_INTERFACE_TABLET, +    .base.description   = "tablet", +    .base.major_version = SPICE_INTERFACE_TABLET_MAJOR, +    .base.minor_version = SPICE_INTERFACE_TABLET_MINOR, +    .set_logical_size   = tablet_set_logical_size, +    .position           = tablet_position, +    .wheel              = tablet_wheel, +    .buttons            = tablet_buttons, +}; + +static void mouse_mode_notifier(Notifier *notifier, void *data) +{ +    QemuSpicePointer *pointer = container_of(notifier, QemuSpicePointer, mouse_mode); +    bool is_absolute  = qemu_input_is_absolute(); + +    if (pointer->absolute == is_absolute) { +        return; +    } + +    if (is_absolute) { +        qemu_spice_add_interface(&pointer->tablet.base); +    } else { +        spice_server_remove_interface(&pointer->tablet.base); +    } +    pointer->absolute = is_absolute; +} + +void qemu_spice_input_init(void) +{ +    QemuSpiceKbd *kbd; +    QemuSpicePointer *pointer; + +    kbd = g_malloc0(sizeof(*kbd)); +    kbd->sin.base.sif = &kbd_interface.base; +    qemu_spice_add_interface(&kbd->sin.base); +    qemu_add_led_event_handler(kbd_leds, kbd); + +    pointer = g_malloc0(sizeof(*pointer)); +    pointer->mouse.base.sif  = &mouse_interface.base; +    pointer->tablet.base.sif = &tablet_interface.base; +    qemu_spice_add_interface(&pointer->mouse.base); + +    pointer->absolute = false; +    pointer->mouse_mode.notify = mouse_mode_notifier; +    qemu_add_mouse_mode_change_notifier(&pointer->mouse_mode); +    mouse_mode_notifier(&pointer->mouse_mode, NULL); +} diff --git a/ui/vgafont.h b/ui/vgafont.h new file mode 100644 index 00000000..3606dd73 --- /dev/null +++ b/ui/vgafont.h @@ -0,0 +1,4611 @@ +static const uint8_t vgafont16[256 * 16] = { + +	/* 0 0x00 '^@' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 1 0x01 '^A' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7e, /* 01111110 */ +	0x81, /* 10000001 */ +	0xa5, /* 10100101 */ +	0x81, /* 10000001 */ +	0x81, /* 10000001 */ +	0xbd, /* 10111101 */ +	0x99, /* 10011001 */ +	0x81, /* 10000001 */ +	0x81, /* 10000001 */ +	0x7e, /* 01111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 2 0x02 '^B' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7e, /* 01111110 */ +	0xff, /* 11111111 */ +	0xdb, /* 11011011 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xc3, /* 11000011 */ +	0xe7, /* 11100111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0x7e, /* 01111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 3 0x03 '^C' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x6c, /* 01101100 */ +	0xfe, /* 11111110 */ +	0xfe, /* 11111110 */ +	0xfe, /* 11111110 */ +	0xfe, /* 11111110 */ +	0x7c, /* 01111100 */ +	0x38, /* 00111000 */ +	0x10, /* 00010000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 4 0x04 '^D' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x10, /* 00010000 */ +	0x38, /* 00111000 */ +	0x7c, /* 01111100 */ +	0xfe, /* 11111110 */ +	0x7c, /* 01111100 */ +	0x38, /* 00111000 */ +	0x10, /* 00010000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 5 0x05 '^E' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x3c, /* 00111100 */ +	0x3c, /* 00111100 */ +	0xe7, /* 11100111 */ +	0xe7, /* 11100111 */ +	0xe7, /* 11100111 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x3c, /* 00111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 6 0x06 '^F' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x3c, /* 00111100 */ +	0x7e, /* 01111110 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0x7e, /* 01111110 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x3c, /* 00111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 7 0x07 '^G' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x3c, /* 00111100 */ +	0x3c, /* 00111100 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 8 0x08 '^H' */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xe7, /* 11100111 */ +	0xc3, /* 11000011 */ +	0xc3, /* 11000011 */ +	0xe7, /* 11100111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ + +	/* 9 0x09 '^I' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x3c, /* 00111100 */ +	0x66, /* 01100110 */ +	0x42, /* 01000010 */ +	0x42, /* 01000010 */ +	0x66, /* 01100110 */ +	0x3c, /* 00111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 10 0x0a '^J' */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xc3, /* 11000011 */ +	0x99, /* 10011001 */ +	0xbd, /* 10111101 */ +	0xbd, /* 10111101 */ +	0x99, /* 10011001 */ +	0xc3, /* 11000011 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ + +	/* 11 0x0b '^K' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x1e, /* 00011110 */ +	0x0e, /* 00001110 */ +	0x1a, /* 00011010 */ +	0x32, /* 00110010 */ +	0x78, /* 01111000 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0x78, /* 01111000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 12 0x0c '^L' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x3c, /* 00111100 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x3c, /* 00111100 */ +	0x18, /* 00011000 */ +	0x7e, /* 01111110 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 13 0x0d '^M' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x3f, /* 00111111 */ +	0x33, /* 00110011 */ +	0x3f, /* 00111111 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x70, /* 01110000 */ +	0xf0, /* 11110000 */ +	0xe0, /* 11100000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 14 0x0e '^N' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7f, /* 01111111 */ +	0x63, /* 01100011 */ +	0x7f, /* 01111111 */ +	0x63, /* 01100011 */ +	0x63, /* 01100011 */ +	0x63, /* 01100011 */ +	0x63, /* 01100011 */ +	0x67, /* 01100111 */ +	0xe7, /* 11100111 */ +	0xe6, /* 11100110 */ +	0xc0, /* 11000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 15 0x0f '^O' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0xdb, /* 11011011 */ +	0x3c, /* 00111100 */ +	0xe7, /* 11100111 */ +	0x3c, /* 00111100 */ +	0xdb, /* 11011011 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 16 0x10 '^P' */ +	0x00, /* 00000000 */ +	0x80, /* 10000000 */ +	0xc0, /* 11000000 */ +	0xe0, /* 11100000 */ +	0xf0, /* 11110000 */ +	0xf8, /* 11111000 */ +	0xfe, /* 11111110 */ +	0xf8, /* 11111000 */ +	0xf0, /* 11110000 */ +	0xe0, /* 11100000 */ +	0xc0, /* 11000000 */ +	0x80, /* 10000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 17 0x11 '^Q' */ +	0x00, /* 00000000 */ +	0x02, /* 00000010 */ +	0x06, /* 00000110 */ +	0x0e, /* 00001110 */ +	0x1e, /* 00011110 */ +	0x3e, /* 00111110 */ +	0xfe, /* 11111110 */ +	0x3e, /* 00111110 */ +	0x1e, /* 00011110 */ +	0x0e, /* 00001110 */ +	0x06, /* 00000110 */ +	0x02, /* 00000010 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 18 0x12 '^R' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x3c, /* 00111100 */ +	0x7e, /* 01111110 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x7e, /* 01111110 */ +	0x3c, /* 00111100 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 19 0x13 '^S' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x00, /* 00000000 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 20 0x14 '^T' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7f, /* 01111111 */ +	0xdb, /* 11011011 */ +	0xdb, /* 11011011 */ +	0xdb, /* 11011011 */ +	0x7b, /* 01111011 */ +	0x1b, /* 00011011 */ +	0x1b, /* 00011011 */ +	0x1b, /* 00011011 */ +	0x1b, /* 00011011 */ +	0x1b, /* 00011011 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 21 0x15 '^U' */ +	0x00, /* 00000000 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0x60, /* 01100000 */ +	0x38, /* 00111000 */ +	0x6c, /* 01101100 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x6c, /* 01101100 */ +	0x38, /* 00111000 */ +	0x0c, /* 00001100 */ +	0xc6, /* 11000110 */ +	0x7c, /* 01111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 22 0x16 '^V' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xfe, /* 11111110 */ +	0xfe, /* 11111110 */ +	0xfe, /* 11111110 */ +	0xfe, /* 11111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 23 0x17 '^W' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x3c, /* 00111100 */ +	0x7e, /* 01111110 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x7e, /* 01111110 */ +	0x3c, /* 00111100 */ +	0x18, /* 00011000 */ +	0x7e, /* 01111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 24 0x18 '^X' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x3c, /* 00111100 */ +	0x7e, /* 01111110 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 25 0x19 '^Y' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x7e, /* 01111110 */ +	0x3c, /* 00111100 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 26 0x1a '^Z' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x0c, /* 00001100 */ +	0xfe, /* 11111110 */ +	0x0c, /* 00001100 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 27 0x1b '^[' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x30, /* 00110000 */ +	0x60, /* 01100000 */ +	0xfe, /* 11111110 */ +	0x60, /* 01100000 */ +	0x30, /* 00110000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 28 0x1c '^\' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xfe, /* 11111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 29 0x1d '^]' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x28, /* 00101000 */ +	0x6c, /* 01101100 */ +	0xfe, /* 11111110 */ +	0x6c, /* 01101100 */ +	0x28, /* 00101000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 30 0x1e '^^' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x10, /* 00010000 */ +	0x38, /* 00111000 */ +	0x38, /* 00111000 */ +	0x7c, /* 01111100 */ +	0x7c, /* 01111100 */ +	0xfe, /* 11111110 */ +	0xfe, /* 11111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 31 0x1f '^_' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xfe, /* 11111110 */ +	0xfe, /* 11111110 */ +	0x7c, /* 01111100 */ +	0x7c, /* 01111100 */ +	0x38, /* 00111000 */ +	0x38, /* 00111000 */ +	0x10, /* 00010000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 32 0x20 ' ' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 33 0x21 '!' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x3c, /* 00111100 */ +	0x3c, /* 00111100 */ +	0x3c, /* 00111100 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 34 0x22 '"' */ +	0x00, /* 00000000 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x24, /* 00100100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 35 0x23 '#' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x6c, /* 01101100 */ +	0x6c, /* 01101100 */ +	0xfe, /* 11111110 */ +	0x6c, /* 01101100 */ +	0x6c, /* 01101100 */ +	0x6c, /* 01101100 */ +	0xfe, /* 11111110 */ +	0x6c, /* 01101100 */ +	0x6c, /* 01101100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 36 0x24 '$' */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0xc2, /* 11000010 */ +	0xc0, /* 11000000 */ +	0x7c, /* 01111100 */ +	0x06, /* 00000110 */ +	0x06, /* 00000110 */ +	0x86, /* 10000110 */ +	0xc6, /* 11000110 */ +	0x7c, /* 01111100 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 37 0x25 '%' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xc2, /* 11000010 */ +	0xc6, /* 11000110 */ +	0x0c, /* 00001100 */ +	0x18, /* 00011000 */ +	0x30, /* 00110000 */ +	0x60, /* 01100000 */ +	0xc6, /* 11000110 */ +	0x86, /* 10000110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 38 0x26 '&' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x38, /* 00111000 */ +	0x6c, /* 01101100 */ +	0x6c, /* 01101100 */ +	0x38, /* 00111000 */ +	0x76, /* 01110110 */ +	0xdc, /* 11011100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0x76, /* 01110110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 39 0x27 ''' */ +	0x00, /* 00000000 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x60, /* 01100000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 40 0x28 '(' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x0c, /* 00001100 */ +	0x18, /* 00011000 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x18, /* 00011000 */ +	0x0c, /* 00001100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 41 0x29 ')' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x30, /* 00110000 */ +	0x18, /* 00011000 */ +	0x0c, /* 00001100 */ +	0x0c, /* 00001100 */ +	0x0c, /* 00001100 */ +	0x0c, /* 00001100 */ +	0x0c, /* 00001100 */ +	0x0c, /* 00001100 */ +	0x18, /* 00011000 */ +	0x30, /* 00110000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 42 0x2a '*' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x66, /* 01100110 */ +	0x3c, /* 00111100 */ +	0xff, /* 11111111 */ +	0x3c, /* 00111100 */ +	0x66, /* 01100110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 43 0x2b '+' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x7e, /* 01111110 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 44 0x2c ',' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x30, /* 00110000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 45 0x2d '-' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xfe, /* 11111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 46 0x2e '.' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 47 0x2f '/' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x02, /* 00000010 */ +	0x06, /* 00000110 */ +	0x0c, /* 00001100 */ +	0x18, /* 00011000 */ +	0x30, /* 00110000 */ +	0x60, /* 01100000 */ +	0xc0, /* 11000000 */ +	0x80, /* 10000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 48 0x30 '0' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x38, /* 00111000 */ +	0x6c, /* 01101100 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xd6, /* 11010110 */ +	0xd6, /* 11010110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x6c, /* 01101100 */ +	0x38, /* 00111000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 49 0x31 '1' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x38, /* 00111000 */ +	0x78, /* 01111000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x7e, /* 01111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 50 0x32 '2' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0x06, /* 00000110 */ +	0x0c, /* 00001100 */ +	0x18, /* 00011000 */ +	0x30, /* 00110000 */ +	0x60, /* 01100000 */ +	0xc0, /* 11000000 */ +	0xc6, /* 11000110 */ +	0xfe, /* 11111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 51 0x33 '3' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0x06, /* 00000110 */ +	0x06, /* 00000110 */ +	0x3c, /* 00111100 */ +	0x06, /* 00000110 */ +	0x06, /* 00000110 */ +	0x06, /* 00000110 */ +	0xc6, /* 11000110 */ +	0x7c, /* 01111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 52 0x34 '4' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x0c, /* 00001100 */ +	0x1c, /* 00011100 */ +	0x3c, /* 00111100 */ +	0x6c, /* 01101100 */ +	0xcc, /* 11001100 */ +	0xfe, /* 11111110 */ +	0x0c, /* 00001100 */ +	0x0c, /* 00001100 */ +	0x0c, /* 00001100 */ +	0x1e, /* 00011110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 53 0x35 '5' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xfe, /* 11111110 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xfc, /* 11111100 */ +	0x06, /* 00000110 */ +	0x06, /* 00000110 */ +	0x06, /* 00000110 */ +	0xc6, /* 11000110 */ +	0x7c, /* 01111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 54 0x36 '6' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x38, /* 00111000 */ +	0x60, /* 01100000 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xfc, /* 11111100 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x7c, /* 01111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 55 0x37 '7' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xfe, /* 11111110 */ +	0xc6, /* 11000110 */ +	0x06, /* 00000110 */ +	0x06, /* 00000110 */ +	0x0c, /* 00001100 */ +	0x18, /* 00011000 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 56 0x38 '8' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x7c, /* 01111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 57 0x39 '9' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x7e, /* 01111110 */ +	0x06, /* 00000110 */ +	0x06, /* 00000110 */ +	0x06, /* 00000110 */ +	0x0c, /* 00001100 */ +	0x78, /* 01111000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 58 0x3a ':' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 59 0x3b ';' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x30, /* 00110000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 60 0x3c '<' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x06, /* 00000110 */ +	0x0c, /* 00001100 */ +	0x18, /* 00011000 */ +	0x30, /* 00110000 */ +	0x60, /* 01100000 */ +	0x30, /* 00110000 */ +	0x18, /* 00011000 */ +	0x0c, /* 00001100 */ +	0x06, /* 00000110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 61 0x3d '=' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7e, /* 01111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7e, /* 01111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 62 0x3e '>' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x60, /* 01100000 */ +	0x30, /* 00110000 */ +	0x18, /* 00011000 */ +	0x0c, /* 00001100 */ +	0x06, /* 00000110 */ +	0x0c, /* 00001100 */ +	0x18, /* 00011000 */ +	0x30, /* 00110000 */ +	0x60, /* 01100000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 63 0x3f '?' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x0c, /* 00001100 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 64 0x40 '@' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xde, /* 11011110 */ +	0xde, /* 11011110 */ +	0xde, /* 11011110 */ +	0xdc, /* 11011100 */ +	0xc0, /* 11000000 */ +	0x7c, /* 01111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 65 0x41 'A' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x10, /* 00010000 */ +	0x38, /* 00111000 */ +	0x6c, /* 01101100 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xfe, /* 11111110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 66 0x42 'B' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xfc, /* 11111100 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x7c, /* 01111100 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0xfc, /* 11111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 67 0x43 'C' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x3c, /* 00111100 */ +	0x66, /* 01100110 */ +	0xc2, /* 11000010 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xc2, /* 11000010 */ +	0x66, /* 01100110 */ +	0x3c, /* 00111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 68 0x44 'D' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xf8, /* 11111000 */ +	0x6c, /* 01101100 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x6c, /* 01101100 */ +	0xf8, /* 11111000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 69 0x45 'E' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xfe, /* 11111110 */ +	0x66, /* 01100110 */ +	0x62, /* 01100010 */ +	0x68, /* 01101000 */ +	0x78, /* 01111000 */ +	0x68, /* 01101000 */ +	0x60, /* 01100000 */ +	0x62, /* 01100010 */ +	0x66, /* 01100110 */ +	0xfe, /* 11111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 70 0x46 'F' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xfe, /* 11111110 */ +	0x66, /* 01100110 */ +	0x62, /* 01100010 */ +	0x68, /* 01101000 */ +	0x78, /* 01111000 */ +	0x68, /* 01101000 */ +	0x60, /* 01100000 */ +	0x60, /* 01100000 */ +	0x60, /* 01100000 */ +	0xf0, /* 11110000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 71 0x47 'G' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x3c, /* 00111100 */ +	0x66, /* 01100110 */ +	0xc2, /* 11000010 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xde, /* 11011110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x66, /* 01100110 */ +	0x3a, /* 00111010 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 72 0x48 'H' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xfe, /* 11111110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 73 0x49 'I' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x3c, /* 00111100 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x3c, /* 00111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 74 0x4a 'J' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x1e, /* 00011110 */ +	0x0c, /* 00001100 */ +	0x0c, /* 00001100 */ +	0x0c, /* 00001100 */ +	0x0c, /* 00001100 */ +	0x0c, /* 00001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0x78, /* 01111000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 75 0x4b 'K' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xe6, /* 11100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x6c, /* 01101100 */ +	0x78, /* 01111000 */ +	0x78, /* 01111000 */ +	0x6c, /* 01101100 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0xe6, /* 11100110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 76 0x4c 'L' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xf0, /* 11110000 */ +	0x60, /* 01100000 */ +	0x60, /* 01100000 */ +	0x60, /* 01100000 */ +	0x60, /* 01100000 */ +	0x60, /* 01100000 */ +	0x60, /* 01100000 */ +	0x62, /* 01100010 */ +	0x66, /* 01100110 */ +	0xfe, /* 11111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 77 0x4d 'M' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xc6, /* 11000110 */ +	0xee, /* 11101110 */ +	0xfe, /* 11111110 */ +	0xfe, /* 11111110 */ +	0xd6, /* 11010110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 78 0x4e 'N' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xc6, /* 11000110 */ +	0xe6, /* 11100110 */ +	0xf6, /* 11110110 */ +	0xfe, /* 11111110 */ +	0xde, /* 11011110 */ +	0xce, /* 11001110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 79 0x4f 'O' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x7c, /* 01111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 80 0x50 'P' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xfc, /* 11111100 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x7c, /* 01111100 */ +	0x60, /* 01100000 */ +	0x60, /* 01100000 */ +	0x60, /* 01100000 */ +	0x60, /* 01100000 */ +	0xf0, /* 11110000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 81 0x51 'Q' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xd6, /* 11010110 */ +	0xde, /* 11011110 */ +	0x7c, /* 01111100 */ +	0x0c, /* 00001100 */ +	0x0e, /* 00001110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 82 0x52 'R' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xfc, /* 11111100 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x7c, /* 01111100 */ +	0x6c, /* 01101100 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0xe6, /* 11100110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 83 0x53 'S' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x60, /* 01100000 */ +	0x38, /* 00111000 */ +	0x0c, /* 00001100 */ +	0x06, /* 00000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x7c, /* 01111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 84 0x54 'T' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7e, /* 01111110 */ +	0x7e, /* 01111110 */ +	0x5a, /* 01011010 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x3c, /* 00111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 85 0x55 'U' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x7c, /* 01111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 86 0x56 'V' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x6c, /* 01101100 */ +	0x38, /* 00111000 */ +	0x10, /* 00010000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 87 0x57 'W' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xd6, /* 11010110 */ +	0xd6, /* 11010110 */ +	0xd6, /* 11010110 */ +	0xfe, /* 11111110 */ +	0xee, /* 11101110 */ +	0x6c, /* 01101100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 88 0x58 'X' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x6c, /* 01101100 */ +	0x7c, /* 01111100 */ +	0x38, /* 00111000 */ +	0x38, /* 00111000 */ +	0x7c, /* 01111100 */ +	0x6c, /* 01101100 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 89 0x59 'Y' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x3c, /* 00111100 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x3c, /* 00111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 90 0x5a 'Z' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xfe, /* 11111110 */ +	0xc6, /* 11000110 */ +	0x86, /* 10000110 */ +	0x0c, /* 00001100 */ +	0x18, /* 00011000 */ +	0x30, /* 00110000 */ +	0x60, /* 01100000 */ +	0xc2, /* 11000010 */ +	0xc6, /* 11000110 */ +	0xfe, /* 11111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 91 0x5b '[' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x3c, /* 00111100 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x3c, /* 00111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 92 0x5c '\' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x80, /* 10000000 */ +	0xc0, /* 11000000 */ +	0xe0, /* 11100000 */ +	0x70, /* 01110000 */ +	0x38, /* 00111000 */ +	0x1c, /* 00011100 */ +	0x0e, /* 00001110 */ +	0x06, /* 00000110 */ +	0x02, /* 00000010 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 93 0x5d ']' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x3c, /* 00111100 */ +	0x0c, /* 00001100 */ +	0x0c, /* 00001100 */ +	0x0c, /* 00001100 */ +	0x0c, /* 00001100 */ +	0x0c, /* 00001100 */ +	0x0c, /* 00001100 */ +	0x0c, /* 00001100 */ +	0x0c, /* 00001100 */ +	0x3c, /* 00111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 94 0x5e '^' */ +	0x10, /* 00010000 */ +	0x38, /* 00111000 */ +	0x6c, /* 01101100 */ +	0xc6, /* 11000110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 95 0x5f '_' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xff, /* 11111111 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 96 0x60 '`' */ +	0x00, /* 00000000 */ +	0x30, /* 00110000 */ +	0x18, /* 00011000 */ +	0x0c, /* 00001100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 97 0x61 'a' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x78, /* 01111000 */ +	0x0c, /* 00001100 */ +	0x7c, /* 01111100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0x76, /* 01110110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 98 0x62 'b' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xe0, /* 11100000 */ +	0x60, /* 01100000 */ +	0x60, /* 01100000 */ +	0x78, /* 01111000 */ +	0x6c, /* 01101100 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x7c, /* 01111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 99 0x63 'c' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xc6, /* 11000110 */ +	0x7c, /* 01111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 100 0x64 'd' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x1c, /* 00011100 */ +	0x0c, /* 00001100 */ +	0x0c, /* 00001100 */ +	0x3c, /* 00111100 */ +	0x6c, /* 01101100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0x76, /* 01110110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 101 0x65 'e' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0xfe, /* 11111110 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xc6, /* 11000110 */ +	0x7c, /* 01111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 102 0x66 'f' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x1c, /* 00011100 */ +	0x36, /* 00110110 */ +	0x32, /* 00110010 */ +	0x30, /* 00110000 */ +	0x78, /* 01111000 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x78, /* 01111000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 103 0x67 'g' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x76, /* 01110110 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0x7c, /* 01111100 */ +	0x0c, /* 00001100 */ +	0xcc, /* 11001100 */ +	0x78, /* 01111000 */ +	0x00, /* 00000000 */ + +	/* 104 0x68 'h' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xe0, /* 11100000 */ +	0x60, /* 01100000 */ +	0x60, /* 01100000 */ +	0x6c, /* 01101100 */ +	0x76, /* 01110110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0xe6, /* 11100110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 105 0x69 'i' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x38, /* 00111000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x3c, /* 00111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 106 0x6a 'j' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x06, /* 00000110 */ +	0x06, /* 00000110 */ +	0x00, /* 00000000 */ +	0x0e, /* 00001110 */ +	0x06, /* 00000110 */ +	0x06, /* 00000110 */ +	0x06, /* 00000110 */ +	0x06, /* 00000110 */ +	0x06, /* 00000110 */ +	0x06, /* 00000110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x3c, /* 00111100 */ +	0x00, /* 00000000 */ + +	/* 107 0x6b 'k' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xe0, /* 11100000 */ +	0x60, /* 01100000 */ +	0x60, /* 01100000 */ +	0x66, /* 01100110 */ +	0x6c, /* 01101100 */ +	0x78, /* 01111000 */ +	0x78, /* 01111000 */ +	0x6c, /* 01101100 */ +	0x66, /* 01100110 */ +	0xe6, /* 11100110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 108 0x6c 'l' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x38, /* 00111000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x3c, /* 00111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 109 0x6d 'm' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xec, /* 11101100 */ +	0xfe, /* 11111110 */ +	0xd6, /* 11010110 */ +	0xd6, /* 11010110 */ +	0xd6, /* 11010110 */ +	0xd6, /* 11010110 */ +	0xc6, /* 11000110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 110 0x6e 'n' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xdc, /* 11011100 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 111 0x6f 'o' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x7c, /* 01111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 112 0x70 'p' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xdc, /* 11011100 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x7c, /* 01111100 */ +	0x60, /* 01100000 */ +	0x60, /* 01100000 */ +	0xf0, /* 11110000 */ +	0x00, /* 00000000 */ + +	/* 113 0x71 'q' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x76, /* 01110110 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0x7c, /* 01111100 */ +	0x0c, /* 00001100 */ +	0x0c, /* 00001100 */ +	0x1e, /* 00011110 */ +	0x00, /* 00000000 */ + +	/* 114 0x72 'r' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xdc, /* 11011100 */ +	0x76, /* 01110110 */ +	0x66, /* 01100110 */ +	0x60, /* 01100000 */ +	0x60, /* 01100000 */ +	0x60, /* 01100000 */ +	0xf0, /* 11110000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 115 0x73 's' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0x60, /* 01100000 */ +	0x38, /* 00111000 */ +	0x0c, /* 00001100 */ +	0xc6, /* 11000110 */ +	0x7c, /* 01111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 116 0x74 't' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x10, /* 00010000 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0xfc, /* 11111100 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x36, /* 00110110 */ +	0x1c, /* 00011100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 117 0x75 'u' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0x76, /* 01110110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 118 0x76 'v' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x6c, /* 01101100 */ +	0x38, /* 00111000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 119 0x77 'w' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xd6, /* 11010110 */ +	0xd6, /* 11010110 */ +	0xd6, /* 11010110 */ +	0xfe, /* 11111110 */ +	0x6c, /* 01101100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 120 0x78 'x' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xc6, /* 11000110 */ +	0x6c, /* 01101100 */ +	0x38, /* 00111000 */ +	0x38, /* 00111000 */ +	0x38, /* 00111000 */ +	0x6c, /* 01101100 */ +	0xc6, /* 11000110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 121 0x79 'y' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x7e, /* 01111110 */ +	0x06, /* 00000110 */ +	0x0c, /* 00001100 */ +	0xf8, /* 11111000 */ +	0x00, /* 00000000 */ + +	/* 122 0x7a 'z' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xfe, /* 11111110 */ +	0xcc, /* 11001100 */ +	0x18, /* 00011000 */ +	0x30, /* 00110000 */ +	0x60, /* 01100000 */ +	0xc6, /* 11000110 */ +	0xfe, /* 11111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 123 0x7b '{' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x0e, /* 00001110 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x70, /* 01110000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x0e, /* 00001110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 124 0x7c '|' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 125 0x7d '}' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x70, /* 01110000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x0e, /* 00001110 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x70, /* 01110000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 126 0x7e '~' */ +	0x00, /* 00000000 */ +	0x76, /* 01110110 */ +	0xdc, /* 11011100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 127 0x7f '' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x10, /* 00010000 */ +	0x38, /* 00111000 */ +	0x6c, /* 01101100 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xfe, /* 11111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 128 0x80 '' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x3c, /* 00111100 */ +	0x66, /* 01100110 */ +	0xc2, /* 11000010 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xc2, /* 11000010 */ +	0x66, /* 01100110 */ +	0x3c, /* 00111100 */ +	0x18, /* 00011000 */ +	0x70, /* 01110000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 129 0x81 '' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xcc, /* 11001100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0x76, /* 01110110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 130 0x82 '' */ +	0x00, /* 00000000 */ +	0x0c, /* 00001100 */ +	0x18, /* 00011000 */ +	0x30, /* 00110000 */ +	0x00, /* 00000000 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0xfe, /* 11111110 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xc6, /* 11000110 */ +	0x7c, /* 01111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 131 0x83 '' */ +	0x00, /* 00000000 */ +	0x10, /* 00010000 */ +	0x38, /* 00111000 */ +	0x6c, /* 01101100 */ +	0x00, /* 00000000 */ +	0x78, /* 01111000 */ +	0x0c, /* 00001100 */ +	0x7c, /* 01111100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0x76, /* 01110110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 132 0x84 '' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xcc, /* 11001100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x78, /* 01111000 */ +	0x0c, /* 00001100 */ +	0x7c, /* 01111100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0x76, /* 01110110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 133 0x85 '
' */ +	0x00, /* 00000000 */ +	0x60, /* 01100000 */ +	0x30, /* 00110000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x78, /* 01111000 */ +	0x0c, /* 00001100 */ +	0x7c, /* 01111100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0x76, /* 01110110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 134 0x86 '' */ +	0x00, /* 00000000 */ +	0x38, /* 00111000 */ +	0x6c, /* 01101100 */ +	0x38, /* 00111000 */ +	0x00, /* 00000000 */ +	0x78, /* 01111000 */ +	0x0c, /* 00001100 */ +	0x7c, /* 01111100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0x76, /* 01110110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 135 0x87 '' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xc6, /* 11000110 */ +	0x7c, /* 01111100 */ +	0x18, /* 00011000 */ +	0x70, /* 01110000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 136 0x88 '' */ +	0x00, /* 00000000 */ +	0x10, /* 00010000 */ +	0x38, /* 00111000 */ +	0x6c, /* 01101100 */ +	0x00, /* 00000000 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0xfe, /* 11111110 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xc6, /* 11000110 */ +	0x7c, /* 01111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 137 0x89 '' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xc6, /* 11000110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0xfe, /* 11111110 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xc6, /* 11000110 */ +	0x7c, /* 01111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 138 0x8a '' */ +	0x00, /* 00000000 */ +	0x60, /* 01100000 */ +	0x30, /* 00110000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0xfe, /* 11111110 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xc6, /* 11000110 */ +	0x7c, /* 01111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 139 0x8b '' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x66, /* 01100110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x38, /* 00111000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x3c, /* 00111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 140 0x8c '' */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x3c, /* 00111100 */ +	0x66, /* 01100110 */ +	0x00, /* 00000000 */ +	0x38, /* 00111000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x3c, /* 00111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 141 0x8d '' */ +	0x00, /* 00000000 */ +	0x60, /* 01100000 */ +	0x30, /* 00110000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x38, /* 00111000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x3c, /* 00111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 142 0x8e '' */ +	0x00, /* 00000000 */ +	0xc6, /* 11000110 */ +	0x00, /* 00000000 */ +	0x10, /* 00010000 */ +	0x38, /* 00111000 */ +	0x6c, /* 01101100 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xfe, /* 11111110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 143 0x8f '' */ +	0x38, /* 00111000 */ +	0x6c, /* 01101100 */ +	0x38, /* 00111000 */ +	0x10, /* 00010000 */ +	0x38, /* 00111000 */ +	0x6c, /* 01101100 */ +	0xc6, /* 11000110 */ +	0xfe, /* 11111110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 144 0x90 '' */ +	0x0c, /* 00001100 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0xfe, /* 11111110 */ +	0x66, /* 01100110 */ +	0x62, /* 01100010 */ +	0x68, /* 01101000 */ +	0x78, /* 01111000 */ +	0x68, /* 01101000 */ +	0x62, /* 01100010 */ +	0x66, /* 01100110 */ +	0xfe, /* 11111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 145 0x91 '' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xec, /* 11101100 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x7e, /* 01111110 */ +	0xd8, /* 11011000 */ +	0xd8, /* 11011000 */ +	0x6e, /* 01101110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 146 0x92 '' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x3e, /* 00111110 */ +	0x6c, /* 01101100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xfe, /* 11111110 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xce, /* 11001110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 147 0x93 '' */ +	0x00, /* 00000000 */ +	0x10, /* 00010000 */ +	0x38, /* 00111000 */ +	0x6c, /* 01101100 */ +	0x00, /* 00000000 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x7c, /* 01111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 148 0x94 '' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xc6, /* 11000110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x7c, /* 01111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 149 0x95 '' */ +	0x00, /* 00000000 */ +	0x60, /* 01100000 */ +	0x30, /* 00110000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x7c, /* 01111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 150 0x96 '' */ +	0x00, /* 00000000 */ +	0x30, /* 00110000 */ +	0x78, /* 01111000 */ +	0xcc, /* 11001100 */ +	0x00, /* 00000000 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0x76, /* 01110110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 151 0x97 '' */ +	0x00, /* 00000000 */ +	0x60, /* 01100000 */ +	0x30, /* 00110000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0x76, /* 01110110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 152 0x98 '' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xc6, /* 11000110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x7e, /* 01111110 */ +	0x06, /* 00000110 */ +	0x0c, /* 00001100 */ +	0x78, /* 01111000 */ +	0x00, /* 00000000 */ + +	/* 153 0x99 '' */ +	0x00, /* 00000000 */ +	0xc6, /* 11000110 */ +	0x00, /* 00000000 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x7c, /* 01111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 154 0x9a '' */ +	0x00, /* 00000000 */ +	0xc6, /* 11000110 */ +	0x00, /* 00000000 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x7c, /* 01111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 155 0x9b '' */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xc6, /* 11000110 */ +	0x7c, /* 01111100 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 156 0x9c '' */ +	0x00, /* 00000000 */ +	0x38, /* 00111000 */ +	0x6c, /* 01101100 */ +	0x64, /* 01100100 */ +	0x60, /* 01100000 */ +	0xf0, /* 11110000 */ +	0x60, /* 01100000 */ +	0x60, /* 01100000 */ +	0x60, /* 01100000 */ +	0x60, /* 01100000 */ +	0xe6, /* 11100110 */ +	0xfc, /* 11111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 157 0x9d '' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x3c, /* 00111100 */ +	0x18, /* 00011000 */ +	0x7e, /* 01111110 */ +	0x18, /* 00011000 */ +	0x7e, /* 01111110 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 158 0x9e '' */ +	0x00, /* 00000000 */ +	0xf8, /* 11111000 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xf8, /* 11111000 */ +	0xc4, /* 11000100 */ +	0xcc, /* 11001100 */ +	0xde, /* 11011110 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xc6, /* 11000110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 159 0x9f '' */ +	0x00, /* 00000000 */ +	0x0e, /* 00001110 */ +	0x1b, /* 00011011 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x7e, /* 01111110 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0xd8, /* 11011000 */ +	0x70, /* 01110000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 160 0xa0 ' ' */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x30, /* 00110000 */ +	0x60, /* 01100000 */ +	0x00, /* 00000000 */ +	0x78, /* 01111000 */ +	0x0c, /* 00001100 */ +	0x7c, /* 01111100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0x76, /* 01110110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 161 0xa1 '¡' */ +	0x00, /* 00000000 */ +	0x0c, /* 00001100 */ +	0x18, /* 00011000 */ +	0x30, /* 00110000 */ +	0x00, /* 00000000 */ +	0x38, /* 00111000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x3c, /* 00111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 162 0xa2 '¢' */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x30, /* 00110000 */ +	0x60, /* 01100000 */ +	0x00, /* 00000000 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x7c, /* 01111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 163 0xa3 '£' */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x30, /* 00110000 */ +	0x60, /* 01100000 */ +	0x00, /* 00000000 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0x76, /* 01110110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 164 0xa4 '¤' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x76, /* 01110110 */ +	0xdc, /* 11011100 */ +	0x00, /* 00000000 */ +	0xdc, /* 11011100 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 165 0xa5 '¥' */ +	0x76, /* 01110110 */ +	0xdc, /* 11011100 */ +	0x00, /* 00000000 */ +	0xc6, /* 11000110 */ +	0xe6, /* 11100110 */ +	0xf6, /* 11110110 */ +	0xfe, /* 11111110 */ +	0xde, /* 11011110 */ +	0xce, /* 11001110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 166 0xa6 '¦' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x3c, /* 00111100 */ +	0x6c, /* 01101100 */ +	0x6c, /* 01101100 */ +	0x3e, /* 00111110 */ +	0x00, /* 00000000 */ +	0x7e, /* 01111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 167 0xa7 '§' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x38, /* 00111000 */ +	0x6c, /* 01101100 */ +	0x6c, /* 01101100 */ +	0x38, /* 00111000 */ +	0x00, /* 00000000 */ +	0x7c, /* 01111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 168 0xa8 '¨' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x00, /* 00000000 */ +	0x30, /* 00110000 */ +	0x30, /* 00110000 */ +	0x60, /* 01100000 */ +	0xc0, /* 11000000 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x7c, /* 01111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 169 0xa9 '©' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xfe, /* 11111110 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 170 0xaa 'ª' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xfe, /* 11111110 */ +	0x06, /* 00000110 */ +	0x06, /* 00000110 */ +	0x06, /* 00000110 */ +	0x06, /* 00000110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 171 0xab '«' */ +	0x00, /* 00000000 */ +	0x60, /* 01100000 */ +	0xe0, /* 11100000 */ +	0x62, /* 01100010 */ +	0x66, /* 01100110 */ +	0x6c, /* 01101100 */ +	0x18, /* 00011000 */ +	0x30, /* 00110000 */ +	0x60, /* 01100000 */ +	0xdc, /* 11011100 */ +	0x86, /* 10000110 */ +	0x0c, /* 00001100 */ +	0x18, /* 00011000 */ +	0x3e, /* 00111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 172 0xac '¬' */ +	0x00, /* 00000000 */ +	0x60, /* 01100000 */ +	0xe0, /* 11100000 */ +	0x62, /* 01100010 */ +	0x66, /* 01100110 */ +	0x6c, /* 01101100 */ +	0x18, /* 00011000 */ +	0x30, /* 00110000 */ +	0x66, /* 01100110 */ +	0xce, /* 11001110 */ +	0x9a, /* 10011010 */ +	0x3f, /* 00111111 */ +	0x06, /* 00000110 */ +	0x06, /* 00000110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 173 0xad '' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x3c, /* 00111100 */ +	0x3c, /* 00111100 */ +	0x3c, /* 00111100 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 174 0xae '®' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x36, /* 00110110 */ +	0x6c, /* 01101100 */ +	0xd8, /* 11011000 */ +	0x6c, /* 01101100 */ +	0x36, /* 00110110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 175 0xaf '¯' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xd8, /* 11011000 */ +	0x6c, /* 01101100 */ +	0x36, /* 00110110 */ +	0x6c, /* 01101100 */ +	0xd8, /* 11011000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 176 0xb0 '°' */ +	0x11, /* 00010001 */ +	0x44, /* 01000100 */ +	0x11, /* 00010001 */ +	0x44, /* 01000100 */ +	0x11, /* 00010001 */ +	0x44, /* 01000100 */ +	0x11, /* 00010001 */ +	0x44, /* 01000100 */ +	0x11, /* 00010001 */ +	0x44, /* 01000100 */ +	0x11, /* 00010001 */ +	0x44, /* 01000100 */ +	0x11, /* 00010001 */ +	0x44, /* 01000100 */ +	0x11, /* 00010001 */ +	0x44, /* 01000100 */ + +	/* 177 0xb1 '±' */ +	0x55, /* 01010101 */ +	0xaa, /* 10101010 */ +	0x55, /* 01010101 */ +	0xaa, /* 10101010 */ +	0x55, /* 01010101 */ +	0xaa, /* 10101010 */ +	0x55, /* 01010101 */ +	0xaa, /* 10101010 */ +	0x55, /* 01010101 */ +	0xaa, /* 10101010 */ +	0x55, /* 01010101 */ +	0xaa, /* 10101010 */ +	0x55, /* 01010101 */ +	0xaa, /* 10101010 */ +	0x55, /* 01010101 */ +	0xaa, /* 10101010 */ + +	/* 178 0xb2 '²' */ +	0xdd, /* 11011101 */ +	0x77, /* 01110111 */ +	0xdd, /* 11011101 */ +	0x77, /* 01110111 */ +	0xdd, /* 11011101 */ +	0x77, /* 01110111 */ +	0xdd, /* 11011101 */ +	0x77, /* 01110111 */ +	0xdd, /* 11011101 */ +	0x77, /* 01110111 */ +	0xdd, /* 11011101 */ +	0x77, /* 01110111 */ +	0xdd, /* 11011101 */ +	0x77, /* 01110111 */ +	0xdd, /* 11011101 */ +	0x77, /* 01110111 */ + +	/* 179 0xb3 '³' */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ + +	/* 180 0xb4 '´' */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0xf8, /* 11111000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ + +	/* 181 0xb5 'µ' */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0xf8, /* 11111000 */ +	0x18, /* 00011000 */ +	0xf8, /* 11111000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ + +	/* 182 0xb6 '¶' */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0xf6, /* 11110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ + +	/* 183 0xb7 '·' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xfe, /* 11111110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ + +	/* 184 0xb8 '¸' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xf8, /* 11111000 */ +	0x18, /* 00011000 */ +	0xf8, /* 11111000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ + +	/* 185 0xb9 '¹' */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0xf6, /* 11110110 */ +	0x06, /* 00000110 */ +	0xf6, /* 11110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ + +	/* 186 0xba 'º' */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ + +	/* 187 0xbb '»' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xfe, /* 11111110 */ +	0x06, /* 00000110 */ +	0xf6, /* 11110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ + +	/* 188 0xbc '¼' */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0xf6, /* 11110110 */ +	0x06, /* 00000110 */ +	0xfe, /* 11111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 189 0xbd '½' */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0xfe, /* 11111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 190 0xbe '¾' */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0xf8, /* 11111000 */ +	0x18, /* 00011000 */ +	0xf8, /* 11111000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 191 0xbf '¿' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xf8, /* 11111000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ + +	/* 192 0xc0 'À' */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x1f, /* 00011111 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 193 0xc1 'Á' */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0xff, /* 11111111 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 194 0xc2 'Â' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xff, /* 11111111 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ + +	/* 195 0xc3 'Ã' */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x1f, /* 00011111 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ + +	/* 196 0xc4 'Ä' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xff, /* 11111111 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 197 0xc5 'Å' */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0xff, /* 11111111 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ + +	/* 198 0xc6 'Æ' */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x1f, /* 00011111 */ +	0x18, /* 00011000 */ +	0x1f, /* 00011111 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ + +	/* 199 0xc7 'Ç' */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x37, /* 00110111 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ + +	/* 200 0xc8 'È' */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x37, /* 00110111 */ +	0x30, /* 00110000 */ +	0x3f, /* 00111111 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 201 0xc9 'É' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x3f, /* 00111111 */ +	0x30, /* 00110000 */ +	0x37, /* 00110111 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ + +	/* 202 0xca 'Ê' */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0xf7, /* 11110111 */ +	0x00, /* 00000000 */ +	0xff, /* 11111111 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 203 0xcb 'Ë' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xff, /* 11111111 */ +	0x00, /* 00000000 */ +	0xf7, /* 11110111 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ + +	/* 204 0xcc 'Ì' */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x37, /* 00110111 */ +	0x30, /* 00110000 */ +	0x37, /* 00110111 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ + +	/* 205 0xcd 'Í' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xff, /* 11111111 */ +	0x00, /* 00000000 */ +	0xff, /* 11111111 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 206 0xce 'Î' */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0xf7, /* 11110111 */ +	0x00, /* 00000000 */ +	0xf7, /* 11110111 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ + +	/* 207 0xcf 'Ï' */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0xff, /* 11111111 */ +	0x00, /* 00000000 */ +	0xff, /* 11111111 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 208 0xd0 'Ð' */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0xff, /* 11111111 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 209 0xd1 'Ñ' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xff, /* 11111111 */ +	0x00, /* 00000000 */ +	0xff, /* 11111111 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ + +	/* 210 0xd2 'Ò' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xff, /* 11111111 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ + +	/* 211 0xd3 'Ó' */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x3f, /* 00111111 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 212 0xd4 'Ô' */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x1f, /* 00011111 */ +	0x18, /* 00011000 */ +	0x1f, /* 00011111 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 213 0xd5 'Õ' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x1f, /* 00011111 */ +	0x18, /* 00011000 */ +	0x1f, /* 00011111 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ + +	/* 214 0xd6 'Ö' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x3f, /* 00111111 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ + +	/* 215 0xd7 '×' */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0xff, /* 11111111 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ + +	/* 216 0xd8 'Ø' */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0xff, /* 11111111 */ +	0x18, /* 00011000 */ +	0xff, /* 11111111 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ + +	/* 217 0xd9 'Ù' */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0xf8, /* 11111000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 218 0xda 'Ú' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x1f, /* 00011111 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ + +	/* 219 0xdb 'Û' */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ + +	/* 220 0xdc 'Ü' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ + +	/* 221 0xdd 'Ý' */ +	0xf0, /* 11110000 */ +	0xf0, /* 11110000 */ +	0xf0, /* 11110000 */ +	0xf0, /* 11110000 */ +	0xf0, /* 11110000 */ +	0xf0, /* 11110000 */ +	0xf0, /* 11110000 */ +	0xf0, /* 11110000 */ +	0xf0, /* 11110000 */ +	0xf0, /* 11110000 */ +	0xf0, /* 11110000 */ +	0xf0, /* 11110000 */ +	0xf0, /* 11110000 */ +	0xf0, /* 11110000 */ +	0xf0, /* 11110000 */ +	0xf0, /* 11110000 */ + +	/* 222 0xde 'Þ' */ +	0x0f, /* 00001111 */ +	0x0f, /* 00001111 */ +	0x0f, /* 00001111 */ +	0x0f, /* 00001111 */ +	0x0f, /* 00001111 */ +	0x0f, /* 00001111 */ +	0x0f, /* 00001111 */ +	0x0f, /* 00001111 */ +	0x0f, /* 00001111 */ +	0x0f, /* 00001111 */ +	0x0f, /* 00001111 */ +	0x0f, /* 00001111 */ +	0x0f, /* 00001111 */ +	0x0f, /* 00001111 */ +	0x0f, /* 00001111 */ +	0x0f, /* 00001111 */ + +	/* 223 0xdf 'ß' */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0xff, /* 11111111 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 224 0xe0 'à' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x76, /* 01110110 */ +	0xdc, /* 11011100 */ +	0xd8, /* 11011000 */ +	0xd8, /* 11011000 */ +	0xd8, /* 11011000 */ +	0xdc, /* 11011100 */ +	0x76, /* 01110110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 225 0xe1 'á' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x78, /* 01111000 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xcc, /* 11001100 */ +	0xd8, /* 11011000 */ +	0xcc, /* 11001100 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xcc, /* 11001100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 226 0xe2 'â' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xfe, /* 11111110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0xc0, /* 11000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 227 0xe3 'ã' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xfe, /* 11111110 */ +	0x6c, /* 01101100 */ +	0x6c, /* 01101100 */ +	0x6c, /* 01101100 */ +	0x6c, /* 01101100 */ +	0x6c, /* 01101100 */ +	0x6c, /* 01101100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 228 0xe4 'ä' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xfe, /* 11111110 */ +	0xc6, /* 11000110 */ +	0x60, /* 01100000 */ +	0x30, /* 00110000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x30, /* 00110000 */ +	0x60, /* 01100000 */ +	0xc6, /* 11000110 */ +	0xfe, /* 11111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 229 0xe5 'å' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7e, /* 01111110 */ +	0xd8, /* 11011000 */ +	0xd8, /* 11011000 */ +	0xd8, /* 11011000 */ +	0xd8, /* 11011000 */ +	0xd8, /* 11011000 */ +	0x70, /* 01110000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 230 0xe6 'æ' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x7c, /* 01111100 */ +	0x60, /* 01100000 */ +	0x60, /* 01100000 */ +	0xc0, /* 11000000 */ +	0x00, /* 00000000 */ + +	/* 231 0xe7 'ç' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x76, /* 01110110 */ +	0xdc, /* 11011100 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 232 0xe8 'è' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7e, /* 01111110 */ +	0x18, /* 00011000 */ +	0x3c, /* 00111100 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x3c, /* 00111100 */ +	0x18, /* 00011000 */ +	0x7e, /* 01111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 233 0xe9 'é' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x38, /* 00111000 */ +	0x6c, /* 01101100 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xfe, /* 11111110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x6c, /* 01101100 */ +	0x38, /* 00111000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 234 0xea 'ê' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x38, /* 00111000 */ +	0x6c, /* 01101100 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x6c, /* 01101100 */ +	0x6c, /* 01101100 */ +	0x6c, /* 01101100 */ +	0x6c, /* 01101100 */ +	0xee, /* 11101110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 235 0xeb 'ë' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x1e, /* 00011110 */ +	0x30, /* 00110000 */ +	0x18, /* 00011000 */ +	0x0c, /* 00001100 */ +	0x3e, /* 00111110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x66, /* 01100110 */ +	0x3c, /* 00111100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 236 0xec 'ì' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7e, /* 01111110 */ +	0xdb, /* 11011011 */ +	0xdb, /* 11011011 */ +	0xdb, /* 11011011 */ +	0x7e, /* 01111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 237 0xed 'í' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x03, /* 00000011 */ +	0x06, /* 00000110 */ +	0x7e, /* 01111110 */ +	0xdb, /* 11011011 */ +	0xdb, /* 11011011 */ +	0xf3, /* 11110011 */ +	0x7e, /* 01111110 */ +	0x60, /* 01100000 */ +	0xc0, /* 11000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 238 0xee 'î' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x1c, /* 00011100 */ +	0x30, /* 00110000 */ +	0x60, /* 01100000 */ +	0x60, /* 01100000 */ +	0x7c, /* 01111100 */ +	0x60, /* 01100000 */ +	0x60, /* 01100000 */ +	0x60, /* 01100000 */ +	0x30, /* 00110000 */ +	0x1c, /* 00011100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 239 0xef 'ï' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7c, /* 01111100 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0xc6, /* 11000110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 240 0xf0 'ð' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xfe, /* 11111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xfe, /* 11111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0xfe, /* 11111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 241 0xf1 'ñ' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x7e, /* 01111110 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7e, /* 01111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 242 0xf2 'ò' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x30, /* 00110000 */ +	0x18, /* 00011000 */ +	0x0c, /* 00001100 */ +	0x06, /* 00000110 */ +	0x0c, /* 00001100 */ +	0x18, /* 00011000 */ +	0x30, /* 00110000 */ +	0x00, /* 00000000 */ +	0x7e, /* 01111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 243 0xf3 'ó' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x0c, /* 00001100 */ +	0x18, /* 00011000 */ +	0x30, /* 00110000 */ +	0x60, /* 01100000 */ +	0x30, /* 00110000 */ +	0x18, /* 00011000 */ +	0x0c, /* 00001100 */ +	0x00, /* 00000000 */ +	0x7e, /* 01111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 244 0xf4 'ô' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x0e, /* 00001110 */ +	0x1b, /* 00011011 */ +	0x1b, /* 00011011 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ + +	/* 245 0xf5 'õ' */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0xd8, /* 11011000 */ +	0xd8, /* 11011000 */ +	0xd8, /* 11011000 */ +	0x70, /* 01110000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 246 0xf6 'ö' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x7e, /* 01111110 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 247 0xf7 '÷' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x76, /* 01110110 */ +	0xdc, /* 11011100 */ +	0x00, /* 00000000 */ +	0x76, /* 01110110 */ +	0xdc, /* 11011100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 248 0xf8 'ø' */ +	0x00, /* 00000000 */ +	0x38, /* 00111000 */ +	0x6c, /* 01101100 */ +	0x6c, /* 01101100 */ +	0x38, /* 00111000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 249 0xf9 'ù' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 250 0xfa 'ú' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x18, /* 00011000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 251 0xfb 'û' */ +	0x00, /* 00000000 */ +	0x0f, /* 00001111 */ +	0x0c, /* 00001100 */ +	0x0c, /* 00001100 */ +	0x0c, /* 00001100 */ +	0x0c, /* 00001100 */ +	0x0c, /* 00001100 */ +	0xec, /* 11101100 */ +	0x6c, /* 01101100 */ +	0x6c, /* 01101100 */ +	0x3c, /* 00111100 */ +	0x1c, /* 00011100 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 252 0xfc 'ü' */ +	0x00, /* 00000000 */ +	0x6c, /* 01101100 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x36, /* 00110110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 253 0xfd 'ý' */ +	0x00, /* 00000000 */ +	0x3c, /* 00111100 */ +	0x66, /* 01100110 */ +	0x0c, /* 00001100 */ +	0x18, /* 00011000 */ +	0x32, /* 00110010 */ +	0x7e, /* 01111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 254 0xfe 'þ' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x7e, /* 01111110 */ +	0x7e, /* 01111110 */ +	0x7e, /* 01111110 */ +	0x7e, /* 01111110 */ +	0x7e, /* 01111110 */ +	0x7e, /* 01111110 */ +	0x7e, /* 01111110 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +	/* 255 0xff 'ÿ' */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ +	0x00, /* 00000000 */ + +}; diff --git a/ui/vnc-auth-sasl.c b/ui/vnc-auth-sasl.c new file mode 100644 index 00000000..62a5fc4b --- /dev/null +++ b/ui/vnc-auth-sasl.c @@ -0,0 +1,622 @@ +/* + * QEMU VNC display driver: SASL auth protocol + * + * Copyright (C) 2009 Red Hat, Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "vnc.h" + +/* Max amount of data we send/recv for SASL steps to prevent DOS */ +#define SASL_DATA_MAX_LEN (1024 * 1024) + + +void vnc_sasl_client_cleanup(VncState *vs) +{ +    if (vs->sasl.conn) { +        vs->sasl.runSSF = false; +        vs->sasl.wantSSF = false; +        vs->sasl.waitWriteSSF = 0; +        vs->sasl.encodedLength = vs->sasl.encodedOffset = 0; +        vs->sasl.encoded = NULL; +        g_free(vs->sasl.username); +        g_free(vs->sasl.mechlist); +        vs->sasl.username = vs->sasl.mechlist = NULL; +        sasl_dispose(&vs->sasl.conn); +        vs->sasl.conn = NULL; +    } +} + + +long vnc_client_write_sasl(VncState *vs) +{ +    long ret; + +    VNC_DEBUG("Write SASL: Pending output %p size %zd offset %zd " +              "Encoded: %p size %d offset %d\n", +              vs->output.buffer, vs->output.capacity, vs->output.offset, +              vs->sasl.encoded, vs->sasl.encodedLength, vs->sasl.encodedOffset); + +    if (!vs->sasl.encoded) { +        int err; +        err = sasl_encode(vs->sasl.conn, +                          (char *)vs->output.buffer, +                          vs->output.offset, +                          (const char **)&vs->sasl.encoded, +                          &vs->sasl.encodedLength); +        if (err != SASL_OK) +            return vnc_client_io_error(vs, -1, EIO); + +        vs->sasl.encodedOffset = 0; +    } + +    ret = vnc_client_write_buf(vs, +                               vs->sasl.encoded + vs->sasl.encodedOffset, +                               vs->sasl.encodedLength - vs->sasl.encodedOffset); +    if (!ret) +        return 0; + +    vs->sasl.encodedOffset += ret; +    if (vs->sasl.encodedOffset == vs->sasl.encodedLength) { +        vs->output.offset = 0; +        vs->sasl.encoded = NULL; +        vs->sasl.encodedOffset = vs->sasl.encodedLength = 0; +    } + +    /* Can't merge this block with one above, because +     * someone might have written more unencrypted +     * data in vs->output while we were processing +     * SASL encoded output +     */ +    if (vs->output.offset == 0) { +        qemu_set_fd_handler(vs->csock, vnc_client_read, NULL, vs); +    } + +    return ret; +} + + +long vnc_client_read_sasl(VncState *vs) +{ +    long ret; +    uint8_t encoded[4096]; +    const char *decoded; +    unsigned int decodedLen; +    int err; + +    ret = vnc_client_read_buf(vs, encoded, sizeof(encoded)); +    if (!ret) +        return 0; + +    err = sasl_decode(vs->sasl.conn, +                      (char *)encoded, ret, +                      &decoded, &decodedLen); + +    if (err != SASL_OK) +        return vnc_client_io_error(vs, -1, -EIO); +    VNC_DEBUG("Read SASL Encoded %p size %ld Decoded %p size %d\n", +              encoded, ret, decoded, decodedLen); +    buffer_reserve(&vs->input, decodedLen); +    buffer_append(&vs->input, decoded, decodedLen); +    return decodedLen; +} + + +static int vnc_auth_sasl_check_access(VncState *vs) +{ +    const void *val; +    int 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)); +        return -1; +    } +    if (val == NULL) { +        VNC_DEBUG("no client username was found, denying access\n"); +        return -1; +    } +    VNC_DEBUG("SASL client username %s\n", (const char *)val); + +    vs->sasl.username = g_strdup((const char*)val); + +    if (vs->vd->sasl.acl == NULL) { +        VNC_DEBUG("no ACL activated, allowing access\n"); +        return 0; +    } + +    allow = qemu_acl_party_is_allowed(vs->vd->sasl.acl, vs->sasl.username); + +    VNC_DEBUG("SASL client %s %s by ACL\n", vs->sasl.username, +              allow ? "allowed" : "denied"); +    return allow ? 0 : -1; +} + +static int vnc_auth_sasl_check_ssf(VncState *vs) +{ +    const void *val; +    int err, ssf; + +    if (!vs->sasl.wantSSF) +        return 1; + +    err = sasl_getprop(vs->sasl.conn, SASL_SSF, &val); +    if (err != SASL_OK) +        return 0; + +    ssf = *(const int *)val; +    VNC_DEBUG("negotiated an SSF of %d\n", ssf); +    if (ssf < 56) +        return 0; /* 56 is good for Kerberos */ + +    /* Only setup for read initially, because we're about to send an RPC +     * reply which must be in plain text. When the next incoming RPC +     * arrives, we'll switch on writes too +     * +     * cf qemudClientReadSASL  in qemud.c +     */ +    vs->sasl.runSSF = 1; + +    /* We have a SSF that's good enough */ +    return 1; +} + +/* + * Step Msg + * + * Input from client: + * + * u32 clientin-length + * u8-array clientin-string + * + * Output to client: + * + * u32 serverout-length + * u8-array serverout-strin + * u8 continue + */ + +static int protocol_client_auth_sasl_step_len(VncState *vs, uint8_t *data, size_t len); + +static int protocol_client_auth_sasl_step(VncState *vs, uint8_t *data, size_t len) +{ +    uint32_t datalen = len; +    const char *serverout; +    unsigned int serveroutlen; +    int err; +    char *clientdata = NULL; + +    /* NB, distinction of NULL vs "" is *critical* in SASL */ +    if (datalen) { +        clientdata = (char*)data; +        clientdata[datalen-1] = '\0'; /* Wire includes '\0', but make sure */ +        datalen--; /* Don't count NULL byte when passing to _start() */ +    } + +    VNC_DEBUG("Step using SASL Data %p (%d bytes)\n", +              clientdata, datalen); +    err = sasl_server_step(vs->sasl.conn, +                           clientdata, +                           datalen, +                           &serverout, +                           &serveroutlen); +    if (err != SASL_OK && +        err != SASL_CONTINUE) { +        VNC_DEBUG("sasl step failed %d (%s)\n", +                  err, 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); +        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); +    } else { +        vnc_write_u32(vs, 0); +    } + +    /* Whether auth is complete */ +    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); +            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); +        vnc_write_u32(vs, 0); /* Accept auth */ +        /* +         * Delay writing in SSF encoded mode until pending output +         * buffer is written +         */ +        if (vs->sasl.runSSF) +            vs->sasl.waitWriteSSF = vs->output.offset; +        start_client_init(vs); +    } + +    return 0; + + authreject: +    vnc_write_u32(vs, 1); /* Reject auth */ +    vnc_write_u32(vs, sizeof("Authentication failed")); +    vnc_write(vs, "Authentication failed", sizeof("Authentication failed")); +    vnc_flush(vs); +    vnc_client_error(vs); +    return -1; + + authabort: +    vnc_client_error(vs); +    return -1; +} + +static int protocol_client_auth_sasl_step_len(VncState *vs, uint8_t *data, size_t len) +{ +    uint32_t steplen = read_u32(data, 0); +    VNC_DEBUG("Got client step len %d\n", steplen); +    if (steplen > SASL_DATA_MAX_LEN) { +        VNC_DEBUG("Too much SASL data %d\n", steplen); +        vnc_client_error(vs); +        return -1; +    } + +    if (steplen == 0) +        return protocol_client_auth_sasl_step(vs, NULL, 0); +    else +        vnc_read_when(vs, protocol_client_auth_sasl_step, steplen); +    return 0; +} + +/* + * Start Msg + * + * Input from client: + * + * u32 clientin-length + * u8-array clientin-string + * + * Output to client: + * + * u32 serverout-length + * u8-array serverout-strin + * u8 continue + */ + +#define SASL_DATA_MAX_LEN (1024 * 1024) + +static int protocol_client_auth_sasl_start(VncState *vs, uint8_t *data, size_t len) +{ +    uint32_t datalen = len; +    const char *serverout; +    unsigned int serveroutlen; +    int err; +    char *clientdata = NULL; + +    /* NB, distinction of NULL vs "" is *critical* in SASL */ +    if (datalen) { +        clientdata = (char*)data; +        clientdata[datalen-1] = '\0'; /* Should be on wire, but make sure */ +        datalen--; /* Don't count NULL byte when passing to _start() */ +    } + +    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); +    if (err != SASL_OK && +        err != SASL_CONTINUE) { +        VNC_DEBUG("sasl start failed %d (%s)\n", +                  err, 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); +        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); +    } else { +        vnc_write_u32(vs, 0); +    } + +    /* Whether auth is complete */ +    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); +            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); +        vnc_write_u32(vs, 0); /* Accept auth */ +        start_client_init(vs); +    } + +    return 0; + + authreject: +    vnc_write_u32(vs, 1); /* Reject auth */ +    vnc_write_u32(vs, sizeof("Authentication failed")); +    vnc_write(vs, "Authentication failed", sizeof("Authentication failed")); +    vnc_flush(vs); +    vnc_client_error(vs); +    return -1; + + authabort: +    vnc_client_error(vs); +    return -1; +} + +static int protocol_client_auth_sasl_start_len(VncState *vs, uint8_t *data, size_t len) +{ +    uint32_t startlen = read_u32(data, 0); +    VNC_DEBUG("Got client start len %d\n", startlen); +    if (startlen > SASL_DATA_MAX_LEN) { +        VNC_DEBUG("Too much SASL data %d\n", startlen); +        vnc_client_error(vs); +        return -1; +    } + +    if (startlen == 0) +        return protocol_client_auth_sasl_start(vs, NULL, 0); + +    vnc_read_when(vs, protocol_client_auth_sasl_start, startlen); +    return 0; +} + +static int protocol_client_auth_sasl_mechname(VncState *vs, uint8_t *data, size_t len) +{ +    char *mechname = g_strndup((const char *) data, len); +    VNC_DEBUG("Got client mechname '%s' check against '%s'\n", +              mechname, vs->sasl.mechlist); + +    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] != ',')) { +            goto fail; +        } +    } + +    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: +    vnc_client_error(vs); +    g_free(mechname); +    return -1; +} + +static int protocol_client_auth_sasl_mechname_len(VncState *vs, uint8_t *data, size_t len) +{ +    uint32_t mechlen = read_u32(data, 0); +    VNC_DEBUG("Got client mechname len %d\n", mechlen); +    if (mechlen > 100) { +        VNC_DEBUG("Too long SASL mechname data %d\n", mechlen); +        vnc_client_error(vs); +        return -1; +    } +    if (mechlen < 1) { +        VNC_DEBUG("Too short SASL mechname %d\n", mechlen); +        vnc_client_error(vs); +        return -1; +    } +    vnc_read_when(vs, protocol_client_auth_sasl_mechname,mechlen); +    return 0; +} + +void start_auth_sasl(VncState *vs) +{ +    const char *mechlist = NULL; +    sasl_security_properties_t secprops; +    int err; +    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))) +        goto authabort; + +    if (!(remoteAddr = vnc_socket_remote_addr("%s;%s", vs->csock))) { +        g_free(localAddr); +        goto authabort; +    } + +    err = sasl_server_new("vnc", +                          NULL, /* FQDN - just delegates to gethostname */ +                          NULL, /* User realm */ +                          localAddr, +                          remoteAddr, +                          NULL, /* Callbacks, not needed */ +                          SASL_SUCCESS_DATA, +                          &vs->sasl.conn); +    g_free(localAddr); +    g_free(remoteAddr); +    localAddr = remoteAddr = NULL; + +    if (err != SASL_OK) { +        VNC_DEBUG("sasl context setup failed %d (%s)", +                  err, 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; +        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"); +            sasl_dispose(&vs->sasl.conn); +            vs->sasl.conn = NULL; +            goto authabort; +        } +        ssf *= 8; /* 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)); +            sasl_dispose(&vs->sasl.conn); +            vs->sasl.conn = NULL; +            goto authabort; +        } +    } else +#endif /* CONFIG_VNC_TLS */ +        vs->sasl.wantSSF = 1; + +    memset (&secprops, 0, sizeof secprops); +    /* Inform SASL that we've got an external SSF layer from TLS */ +    if (vs->vd->is_unix +#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 */ +        ) { +        /* If we've got TLS or UNIX domain sock, we don't care about SSF */ +        secprops.min_ssf = 0; +        secprops.max_ssf = 0; +        secprops.maxbufsize = 8192; +        secprops.security_flags = 0; +    } else { +        /* Plain TCP, better get an SSF layer */ +        secprops.min_ssf = 56; /* Good enough to require kerberos */ +        secprops.max_ssf = 100000; /* Arbitrary big number */ +        secprops.maxbufsize = 8192; +        /* Forbid any anonymous or trivially crackable auth */ +        secprops.security_flags = +            SASL_SEC_NOANONYMOUS | SASL_SEC_NOPLAINTEXT; +    } + +    err = sasl_setprop(vs->sasl.conn, SASL_SEC_PROPS, &secprops); +    if (err != SASL_OK) { +        VNC_DEBUG("cannot set SASL security props %d (%s)\n", +                  err, sasl_errstring(err, NULL, NULL)); +        sasl_dispose(&vs->sasl.conn); +        vs->sasl.conn = NULL; +        goto authabort; +    } + +    err = sasl_listmech(vs->sasl.conn, +                        NULL, /* Don't need to set user */ +                        "", /* Prefix */ +                        ",", /* Separator */ +                        "", /* Suffix */ +                        &mechlist, +                        NULL, +                        NULL); +    if (err != SASL_OK) { +        VNC_DEBUG("cannot list SASL mechanisms %d (%s)\n", +                  err, 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); + +    vs->sasl.mechlist = g_strdup(mechlist); +    mechlistlen = strlen(mechlist); +    vnc_write_u32(vs, mechlistlen); +    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: +    vnc_client_error(vs); +} + + diff --git a/ui/vnc-auth-sasl.h b/ui/vnc-auth-sasl.h new file mode 100644 index 00000000..3f59da67 --- /dev/null +++ b/ui/vnc-auth-sasl.h @@ -0,0 +1,75 @@ +/* + * QEMU VNC display driver: SASL auth protocol + * + * Copyright (C) 2009 Red Hat, Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#ifndef __QEMU_VNC_AUTH_SASL_H__ +#define __QEMU_VNC_AUTH_SASL_H__ + + +#include <sasl/sasl.h> + +typedef struct VncStateSASL VncStateSASL; +typedef struct VncDisplaySASL VncDisplaySASL; + +#include "qemu/acl.h" +#include "qemu/main-loop.h" + +struct VncStateSASL { +    sasl_conn_t *conn; +    /* If we want to negotiate an SSF layer with client */ +    bool wantSSF; +    /* If we are now running the SSF layer */ +    bool runSSF; +    /* +     * If this is non-zero, then wait for that many bytes +     * to be written plain, before switching to SSF encoding +     * This allows the VNC auth result to finish being +     * written in plain. +     */ +    unsigned int waitWriteSSF; + +    /* +     * Buffering encoded data to allow more clear data +     * to be stuffed onto the output buffer +     */ +    const uint8_t *encoded; +    unsigned int encodedLength; +    unsigned int encodedOffset; +    char *username; +    char *mechlist; +}; + +struct VncDisplaySASL { +    qemu_acl *acl; +}; + +void vnc_sasl_client_cleanup(VncState *vs); + +long vnc_client_read_sasl(VncState *vs); +long vnc_client_write_sasl(VncState *vs); + +void start_auth_sasl(VncState *vs); + +#endif /* __QEMU_VNC_AUTH_SASL_H__ */ + diff --git a/ui/vnc-auth-vencrypt.c b/ui/vnc-auth-vencrypt.c new file mode 100644 index 00000000..8fc965b4 --- /dev/null +++ b/ui/vnc-auth-vencrypt.c @@ -0,0 +1,177 @@ +/* + * QEMU VNC display driver: VeNCrypt authentication setup + * + * Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws> + * Copyright (C) 2006 Fabrice Bellard + * Copyright (C) 2009 Red Hat, Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "vnc.h" +#include "qemu/main-loop.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); +       vnc_write_u8(vs, 1); +       if (vs->minor >= 8) { +           static const char err[] = "Unsupported authentication type"; +           vnc_write_u32(vs, sizeof(err)); +           vnc_write(vs, err, sizeof(err)); +       } +       vnc_client_error(vs); +    } +} + +static void vnc_tls_handshake_io(void *opaque); + +static int vnc_start_vencrypt_handshake(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; +    } + +    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"); +        } +    } + +    VNC_DEBUG("Handshake done, switching to TLS data mode\n"); +    qemu_set_fd_handler(vs->csock, vnc_client_read, vnc_client_write, vs); + +    start_auth_vencrypt_subauth(vs); + +    return 0; +} + +static void vnc_tls_handshake_io(void *opaque) +{ +    VncState *vs = (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); + +    if (auth != vs->subauth) { +        VNC_DEBUG("Rejecting auth %d\n", auth); +        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); +        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; +        } + +        VNC_DEBUG("Start TLS VeNCrypt handshake process\n"); +        if (vnc_start_vencrypt_handshake(vs) < 0) { +            VNC_DEBUG("Failed to start TLS handshake\n"); +            return 0; +        } +    } +    return 0; +} + +static int protocol_client_vencrypt_init(VncState *vs, uint8_t *data, size_t len) +{ +    if (data[0] != 0 || +        data[1] != 2) { +        VNC_DEBUG("Unsupported VeNCrypt protocol %d.%d\n", (int)data[0], (int)data[1]); +        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 */ +        vnc_flush(vs); +        vnc_read_when(vs, protocol_client_vencrypt_auth, 4); +    } +    return 0; +} + + +void start_auth_vencrypt(VncState *vs) +{ +    /* Send VeNCrypt version 0.2 */ +    vnc_write_u8(vs, 0); +    vnc_write_u8(vs, 2); + +    vnc_read_when(vs, protocol_client_vencrypt_init, 2); +} + diff --git a/ui/vnc-auth-vencrypt.h b/ui/vnc-auth-vencrypt.h new file mode 100644 index 00000000..9f674c51 --- /dev/null +++ b/ui/vnc-auth-vencrypt.h @@ -0,0 +1,33 @@ +/* + * QEMU VNC display driver + * + * Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws> + * Copyright (C) 2006 Fabrice Bellard + * Copyright (C) 2009 Red Hat, Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#ifndef __QEMU_VNC_AUTH_VENCRYPT_H__ +#define __QEMU_VNC_AUTH_VENCRYPT_H__ + +void start_auth_vencrypt(VncState *vs); + +#endif /* __QEMU_VNC_AUTH_VENCRYPT_H__ */ diff --git a/ui/vnc-enc-hextile-template.h b/ui/vnc-enc-hextile-template.h new file mode 100644 index 00000000..d868d757 --- /dev/null +++ b/ui/vnc-enc-hextile-template.h @@ -0,0 +1,211 @@ +#define CONCAT_I(a, b) a ## b +#define CONCAT(a, b) CONCAT_I(a, b) +#define pixel_t CONCAT(uint, CONCAT(BPP, _t)) +#ifdef GENERIC +#define NAME CONCAT(generic_, BPP) +#else +#define NAME BPP +#endif + +static void CONCAT(send_hextile_tile_, NAME)(VncState *vs, +                                             int x, int y, int w, int h, +                                             void *last_bg_, +                                             void *last_fg_, +                                             int *has_bg, int *has_fg) +{ +    VncDisplay *vd = vs->vd; +    uint8_t *row = vnc_server_fb_ptr(vd, x, y); +    pixel_t *irow = (pixel_t *)row; +    int j, i; +    pixel_t *last_bg = (pixel_t *)last_bg_; +    pixel_t *last_fg = (pixel_t *)last_fg_; +    pixel_t bg = 0; +    pixel_t fg = 0; +    int n_colors = 0; +    int bg_count = 0; +    int fg_count = 0; +    int flags = 0; +    uint8_t data[(vs->client_pf.bytes_per_pixel + 2) * 16 * 16]; +    int n_data = 0; +    int n_subtiles = 0; + +    for (j = 0; j < h; j++) { +	for (i = 0; i < w; i++) { +	    switch (n_colors) { +	    case 0: +		bg = irow[i]; +		n_colors = 1; +		break; +	    case 1: +		if (irow[i] != bg) { +		    fg = irow[i]; +		    n_colors = 2; +		} +		break; +	    case 2: +		if (irow[i] != bg && irow[i] != fg) { +		    n_colors = 3; +		} else { +		    if (irow[i] == bg) +			bg_count++; +		    else if (irow[i] == fg) +			fg_count++; +		} +		break; +	    default: +		break; +	    } +	} +	if (n_colors > 2) +	    break; +	irow += vnc_server_fb_stride(vd) / sizeof(pixel_t); +    } + +    if (n_colors > 1 && fg_count > bg_count) { +	pixel_t tmp = fg; +	fg = bg; +	bg = tmp; +    } + +    if (!*has_bg || *last_bg != bg) { +	flags |= 0x02; +	*has_bg = 1; +	*last_bg = bg; +    } + +    if (n_colors < 3 && (!*has_fg || *last_fg != fg)) { +	flags |= 0x04; +	*has_fg = 1; +	*last_fg = fg; +    } + +    switch (n_colors) { +    case 1: +	n_data = 0; +	break; +    case 2: +	flags |= 0x08; + +	irow = (pixel_t *)row; + +	for (j = 0; j < h; j++) { +	    int min_x = -1; +	    for (i = 0; i < w; i++) { +		if (irow[i] == fg) { +		    if (min_x == -1) +			min_x = i; +		} else if (min_x != -1) { +		    hextile_enc_cord(data + n_data, min_x, j, i - min_x, 1); +		    n_data += 2; +		    n_subtiles++; +		    min_x = -1; +		} +	    } +	    if (min_x != -1) { +		hextile_enc_cord(data + n_data, min_x, j, i - min_x, 1); +		n_data += 2; +		n_subtiles++; +	    } +	    irow += vnc_server_fb_stride(vd) / sizeof(pixel_t); +	} +	break; +    case 3: +	flags |= 0x18; + +	irow = (pixel_t *)row; + +	if (!*has_bg || *last_bg != bg) +	    flags |= 0x02; + +	for (j = 0; j < h; j++) { +	    int has_color = 0; +	    int min_x = -1; +	    pixel_t color = 0; /* shut up gcc */ + +	    for (i = 0; i < w; i++) { +		if (!has_color) { +		    if (irow[i] == bg) +			continue; +		    color = irow[i]; +		    min_x = i; +		    has_color = 1; +		} else if (irow[i] != color) { +		    has_color = 0; +#ifdef GENERIC +                    vnc_convert_pixel(vs, data + n_data, color); +                    n_data += vs->client_pf.bytes_per_pixel; +#else +		    memcpy(data + n_data, &color, sizeof(color)); +                    n_data += sizeof(pixel_t); +#endif +		    hextile_enc_cord(data + n_data, min_x, j, i - min_x, 1); +		    n_data += 2; +		    n_subtiles++; + +		    min_x = -1; +		    if (irow[i] != bg) { +			color = irow[i]; +			min_x = i; +			has_color = 1; +		    } +		} +	    } +	    if (has_color) { +#ifdef GENERIC +                vnc_convert_pixel(vs, data + n_data, color); +                n_data += vs->client_pf.bytes_per_pixel; +#else +                memcpy(data + n_data, &color, sizeof(color)); +                n_data += sizeof(pixel_t); +#endif +		hextile_enc_cord(data + n_data, min_x, j, i - min_x, 1); +		n_data += 2; +		n_subtiles++; +	    } +	    irow += vnc_server_fb_stride(vd) / sizeof(pixel_t); +	} + +	/* A SubrectsColoured subtile invalidates the foreground color */ +	*has_fg = 0; +	if (n_data > (w * h * sizeof(pixel_t))) { +	    n_colors = 4; +	    flags = 0x01; +	    *has_bg = 0; + +	    /* we really don't have to invalidate either the bg or fg +	       but we've lost the old values.  oh well. */ +	} +        break; +    default: +	break; +    } + +    if (n_colors > 3) { +	flags = 0x01; +	*has_fg = 0; +	*has_bg = 0; +	n_colors = 4; +    } + +    vnc_write_u8(vs, flags); +    if (n_colors < 4) { +	if (flags & 0x02) +	    vs->write_pixels(vs, last_bg, sizeof(pixel_t)); +	if (flags & 0x04) +	    vs->write_pixels(vs, last_fg, sizeof(pixel_t)); +	if (n_subtiles) { +	    vnc_write_u8(vs, n_subtiles); +	    vnc_write(vs, data, n_data); +	} +    } else { +	for (j = 0; j < h; j++) { +	    vs->write_pixels(vs, row, w * 4); +	    row += vnc_server_fb_stride(vd); +	} +    } +} + +#undef NAME +#undef pixel_t +#undef CONCAT_I +#undef CONCAT diff --git a/ui/vnc-enc-hextile.c b/ui/vnc-enc-hextile.c new file mode 100644 index 00000000..2e768fd8 --- /dev/null +++ b/ui/vnc-enc-hextile.c @@ -0,0 +1,83 @@ +/* + * QEMU VNC display driver: hextile encoding + * + * Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws> + * Copyright (C) 2006 Fabrice Bellard + * Copyright (C) 2009 Red Hat, Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "vnc.h" + +static void hextile_enc_cord(uint8_t *ptr, int x, int y, int w, int h) +{ +    ptr[0] = ((x & 0x0F) << 4) | (y & 0x0F); +    ptr[1] = (((w - 1) & 0x0F) << 4) | ((h - 1) & 0x0F); +} + +#define BPP 32 +#include "vnc-enc-hextile-template.h" +#undef BPP + +#define GENERIC +#define BPP 32 +#include "vnc-enc-hextile-template.h" +#undef BPP +#undef GENERIC + +int vnc_hextile_send_framebuffer_update(VncState *vs, int x, +                                        int y, int w, int h) +{ +    int i, j; +    int has_fg, has_bg; +    uint8_t *last_fg, *last_bg; + +    last_fg = (uint8_t *) g_malloc(VNC_SERVER_FB_BYTES); +    last_bg = (uint8_t *) g_malloc(VNC_SERVER_FB_BYTES); +    has_fg = has_bg = 0; +    for (j = y; j < (y + h); j += 16) { +        for (i = x; i < (x + w); i += 16) { +            vs->hextile.send_tile(vs, i, j, +                                  MIN(16, x + w - i), MIN(16, y + h - j), +                                  last_bg, last_fg, &has_bg, &has_fg); +        } +    } +    g_free(last_fg); +    g_free(last_bg); + +    return 1; +} + +void vnc_hextile_set_pixel_conversion(VncState *vs, int generic) +{ +    if (!generic) { +        switch (VNC_SERVER_FB_BITS) { +        case 32: +            vs->hextile.send_tile = send_hextile_tile_32; +            break; +        } +    } else { +        switch (VNC_SERVER_FB_BITS) { +        case 32: +            vs->hextile.send_tile = send_hextile_tile_generic_32; +            break; +        } +    } +} diff --git a/ui/vnc-enc-tight.c b/ui/vnc-enc-tight.c new file mode 100644 index 00000000..9a9ddf2e --- /dev/null +++ b/ui/vnc-enc-tight.c @@ -0,0 +1,1698 @@ +/* + * QEMU VNC display driver: tight encoding + * + * From libvncserver/libvncserver/tight.c + * Copyright (C) 2000, 2001 Const Kaplinsky.  All Rights Reserved. + * Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved. + * + * Copyright (C) 2010 Corentin Chary <corentin.chary@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "config-host.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, +   because setjmp.h was already included by qemu-common.h. */ +#define PNG_SKIP_SETJMP_CHECK +#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" + +/* Compression level stuff. The following array contains various +   encoder parameters for each of 10 compression levels (0..9). +   Last three parameters correspond to JPEG quality levels (0..9). */ + +static const struct { +    int max_rect_size, max_rect_width; +    int mono_min_rect_size, gradient_min_rect_size; +    int idx_zlib_level, mono_zlib_level, raw_zlib_level, gradient_zlib_level; +    int gradient_threshold, gradient_threshold24; +    int idx_max_colors_divisor; +    int jpeg_quality, jpeg_threshold, jpeg_threshold24; +} tight_conf[] = { +    {   512,   32,   6, 65536, 0, 0, 0, 0,   0,   0,   4,  5, 10000, 23000 }, +    {  2048,  128,   6, 65536, 1, 1, 1, 0,   0,   0,   8, 10,  8000, 18000 }, +    {  6144,  256,   8, 65536, 3, 3, 2, 0,   0,   0,  24, 15,  6500, 15000 }, +    { 10240, 1024,  12, 65536, 5, 5, 3, 0,   0,   0,  32, 25,  5000, 12000 }, +    { 16384, 2048,  12, 65536, 6, 6, 4, 0,   0,   0,  32, 37,  4000, 10000 }, +    { 32768, 2048,  12,  4096, 7, 7, 5, 4, 150, 380,  32, 50,  3000,  8000 }, +    { 65536, 2048,  16,  4096, 7, 7, 6, 4, 170, 420,  48, 60,  2000,  5000 }, +    { 65536, 2048,  16,  4096, 8, 8, 7, 5, 180, 450,  64, 70,  1000,  2500 }, +    { 65536, 2048,  32,  8192, 9, 9, 8, 6, 190, 475,  64, 75,   500,  1200 }, +    { 65536, 2048,  32,  8192, 9, 9, 9, 6, 200, 500,  96, 80,   200,   500 } +}; + + +static int tight_send_framebuffer_update(VncState *vs, int x, int y, +                                         int w, int h); + +#ifdef CONFIG_VNC_JPEG +static const struct { +    double jpeg_freq_min;       /* Don't send JPEG if the freq is bellow */ +    double jpeg_freq_threshold; /* Always send JPEG if the freq is above */ +    int jpeg_idx;               /* Allow indexed JPEG */ +    int jpeg_full;              /* Allow full color JPEG */ +} tight_jpeg_conf[] = { +    { 0,   8,  1, 1 }, +    { 0,   8,  1, 1 }, +    { 0,   8,  1, 1 }, +    { 0,   8,  1, 1 }, +    { 0,   10, 1, 1 }, +    { 0.1, 10, 1, 1 }, +    { 0.2, 10, 1, 1 }, +    { 0.3, 12, 0, 0 }, +    { 0.4, 14, 0, 0 }, +    { 0.5, 16, 0, 0 }, +}; +#endif + +#ifdef CONFIG_VNC_PNG +static const struct { +    int png_zlib_level, png_filters; +} tight_png_conf[] = { +    { 0, PNG_NO_FILTERS }, +    { 1, PNG_NO_FILTERS }, +    { 2, PNG_NO_FILTERS }, +    { 3, PNG_NO_FILTERS }, +    { 4, PNG_NO_FILTERS }, +    { 5, PNG_ALL_FILTERS }, +    { 6, PNG_ALL_FILTERS }, +    { 7, PNG_ALL_FILTERS }, +    { 8, PNG_ALL_FILTERS }, +    { 9, PNG_ALL_FILTERS }, +}; + +static int send_png_rect(VncState *vs, int x, int y, int w, int h, +                         VncPalette *palette); + +static bool tight_can_send_png_rect(VncState *vs, int w, int h) +{ +    if (vs->tight.type != VNC_ENCODING_TIGHT_PNG) { +        return false; +    } + +    if (surface_bytes_per_pixel(vs->vd->ds) == 1 || +        vs->client_pf.bytes_per_pixel == 1) { +        return false; +    } + +    return true; +} +#endif + +/* + * Code to guess if given rectangle is suitable for smooth image + * compression (by applying "gradient" filter or JPEG coder). + */ + +static unsigned int +tight_detect_smooth_image24(VncState *vs, int w, int h) +{ +    int off; +    int x, y, d, dx; +    unsigned int c; +    unsigned int stats[256]; +    int pixels = 0; +    int pix, left[3]; +    unsigned int errors; +    unsigned char *buf = vs->tight.tight.buffer; + +    /* +     * If client is big-endian, color samples begin from the second +     * byte (offset 1) of a 32-bit pixel value. +     */ +    off = vs->client_be; + +    memset(stats, 0, sizeof (stats)); + +    for (y = 0, x = 0; y < h && x < w;) { +        for (d = 0; d < h - y && d < w - x - VNC_TIGHT_DETECT_SUBROW_WIDTH; +             d++) { +            for (c = 0; c < 3; c++) { +                left[c] = buf[((y+d)*w+x+d)*4+off+c] & 0xFF; +            } +            for (dx = 1; dx <= VNC_TIGHT_DETECT_SUBROW_WIDTH; dx++) { +                for (c = 0; c < 3; c++) { +                    pix = buf[((y+d)*w+x+d+dx)*4+off+c] & 0xFF; +                    stats[abs(pix - left[c])]++; +                    left[c] = pix; +                } +                pixels++; +            } +        } +        if (w > h) { +            x += h; +            y = 0; +        } else { +            x = 0; +            y += w; +        } +    } + +    if (pixels == 0) { +        return 0; +    } + +    /* 95% smooth or more ... */ +    if (stats[0] * 33 / pixels >= 95) { +        return 0; +    } + +    errors = 0; +    for (c = 1; c < 8; c++) { +        errors += stats[c] * (c * c); +        if (stats[c] == 0 || stats[c] > stats[c-1] * 2) { +            return 0; +        } +    } +    for (; c < 256; c++) { +        errors += stats[c] * (c * c); +    } +    errors /= (pixels * 3 - stats[0]); + +    return errors; +} + +#define DEFINE_DETECT_FUNCTION(bpp)                                     \ +                                                                        \ +    static unsigned int                                                 \ +    tight_detect_smooth_image##bpp(VncState *vs, int w, int h) {        \ +        bool endian;                                                    \ +        uint##bpp##_t pix;                                              \ +        int max[3], shift[3];                                           \ +        int x, y, d, dx;                                                \ +        unsigned int c;                                                 \ +        unsigned int stats[256];                                        \ +        int pixels = 0;                                                 \ +        int sample, sum, left[3];                                       \ +        unsigned int errors;                                            \ +        unsigned char *buf = vs->tight.tight.buffer;                    \ +                                                                        \ +        endian = 0; /* FIXME */                                         \ +                                                                        \ +                                                                        \ +        max[0] = vs->client_pf.rmax;                                  \ +        max[1] = vs->client_pf.gmax;                                  \ +        max[2] = vs->client_pf.bmax;                                  \ +        shift[0] = vs->client_pf.rshift;                              \ +        shift[1] = vs->client_pf.gshift;                              \ +        shift[2] = vs->client_pf.bshift;                              \ +                                                                        \ +        memset(stats, 0, sizeof(stats));                                \ +                                                                        \ +        y = 0, x = 0;                                                   \ +        while (y < h && x < w) {                                        \ +            for (d = 0; d < h - y &&                                    \ +                     d < w - x - VNC_TIGHT_DETECT_SUBROW_WIDTH; d++) {  \ +                pix = ((uint##bpp##_t *)buf)[(y+d)*w+x+d];              \ +                if (endian) {                                           \ +                    pix = bswap##bpp(pix);                              \ +                }                                                       \ +                for (c = 0; c < 3; c++) {                               \ +                    left[c] = (int)(pix >> shift[c] & max[c]);          \ +                }                                                       \ +                for (dx = 1; dx <= VNC_TIGHT_DETECT_SUBROW_WIDTH;       \ +                     dx++) {                                            \ +                    pix = ((uint##bpp##_t *)buf)[(y+d)*w+x+d+dx];       \ +                    if (endian) {                                       \ +                        pix = bswap##bpp(pix);                          \ +                    }                                                   \ +                    sum = 0;                                            \ +                    for (c = 0; c < 3; c++) {                           \ +                        sample = (int)(pix >> shift[c] & max[c]);       \ +                        sum += abs(sample - left[c]);                   \ +                        left[c] = sample;                               \ +                    }                                                   \ +                    if (sum > 255) {                                    \ +                        sum = 255;                                      \ +                    }                                                   \ +                    stats[sum]++;                                       \ +                    pixels++;                                           \ +                }                                                       \ +            }                                                           \ +            if (w > h) {                                                \ +                x += h;                                                 \ +                y = 0;                                                  \ +            } else {                                                    \ +                x = 0;                                                  \ +                y += w;                                                 \ +            }                                                           \ +        }                                                               \ +        if (pixels == 0) {                                              \ +            return 0;                                                   \ +        }                                                               \ +        if ((stats[0] + stats[1]) * 100 / pixels >= 90) {               \ +            return 0;                                                   \ +        }                                                               \ +                                                                        \ +        errors = 0;                                                     \ +        for (c = 1; c < 8; c++) {                                       \ +            errors += stats[c] * (c * c);                               \ +            if (stats[c] == 0 || stats[c] > stats[c-1] * 2) {           \ +                return 0;                                               \ +            }                                                           \ +        }                                                               \ +        for (; c < 256; c++) {                                          \ +            errors += stats[c] * (c * c);                               \ +        }                                                               \ +        errors /= (pixels - stats[0]);                                  \ +                                                                        \ +        return errors;                                                  \ +    } + +DEFINE_DETECT_FUNCTION(16) +DEFINE_DETECT_FUNCTION(32) + +static int +tight_detect_smooth_image(VncState *vs, int w, int h) +{ +    unsigned int errors; +    int compression = vs->tight.compression; +    int quality = vs->tight.quality; + +    if (!vs->vd->lossy) { +        return 0; +    } + +    if (surface_bytes_per_pixel(vs->vd->ds) == 1 || +        vs->client_pf.bytes_per_pixel == 1 || +        w < VNC_TIGHT_DETECT_MIN_WIDTH || h < VNC_TIGHT_DETECT_MIN_HEIGHT) { +        return 0; +    } + +    if (vs->tight.quality != (uint8_t)-1) { +        if (w * h < VNC_TIGHT_JPEG_MIN_RECT_SIZE) { +            return 0; +        } +    } else { +        if (w * h < tight_conf[compression].gradient_min_rect_size) { +            return 0; +        } +    } + +    if (vs->client_pf.bytes_per_pixel == 4) { +        if (vs->tight.pixel24) { +            errors = tight_detect_smooth_image24(vs, w, h); +            if (vs->tight.quality != (uint8_t)-1) { +                return (errors < tight_conf[quality].jpeg_threshold24); +            } +            return (errors < tight_conf[compression].gradient_threshold24); +        } else { +            errors = tight_detect_smooth_image32(vs, w, h); +        } +    } else { +        errors = tight_detect_smooth_image16(vs, w, h); +    } +    if (quality != (uint8_t)-1) { +        return (errors < tight_conf[quality].jpeg_threshold); +    } +    return (errors < tight_conf[compression].gradient_threshold); +} + +/* + * Code to determine how many different colors used in rectangle. + */ +#define DEFINE_FILL_PALETTE_FUNCTION(bpp)                               \ +                                                                        \ +    static int                                                          \ +    tight_fill_palette##bpp(VncState *vs, int x, int y,                 \ +                            int max, size_t count,                      \ +                            uint32_t *bg, uint32_t *fg,                 \ +                            VncPalette **palette) {                     \ +        uint##bpp##_t *data;                                            \ +        uint##bpp##_t c0, c1, ci;                                       \ +        int i, n0, n1;                                                  \ +                                                                        \ +        data = (uint##bpp##_t *)vs->tight.tight.buffer;                 \ +                                                                        \ +        c0 = data[0];                                                   \ +        i = 1;                                                          \ +        while (i < count && data[i] == c0)                              \ +            i++;                                                        \ +        if (i >= count) {                                               \ +            *bg = *fg = c0;                                             \ +            return 1;                                                   \ +        }                                                               \ +                                                                        \ +        if (max < 2) {                                                  \ +            return 0;                                                   \ +        }                                                               \ +                                                                        \ +        n0 = i;                                                         \ +        c1 = data[i];                                                   \ +        n1 = 0;                                                         \ +        for (i++; i < count; i++) {                                     \ +            ci = data[i];                                               \ +            if (ci == c0) {                                             \ +                n0++;                                                   \ +            } else if (ci == c1) {                                      \ +                n1++;                                                   \ +            } else                                                      \ +                break;                                                  \ +        }                                                               \ +        if (i >= count) {                                               \ +            if (n0 > n1) {                                              \ +                *bg = (uint32_t)c0;                                     \ +                *fg = (uint32_t)c1;                                     \ +            } else {                                                    \ +                *bg = (uint32_t)c1;                                     \ +                *fg = (uint32_t)c0;                                     \ +            }                                                           \ +            return 2;                                                   \ +        }                                                               \ +                                                                        \ +        if (max == 2) {                                                 \ +            return 0;                                                   \ +        }                                                               \ +                                                                        \ +        *palette = palette_new(max, bpp);                               \ +        palette_put(*palette, c0);                                      \ +        palette_put(*palette, c1);                                      \ +        palette_put(*palette, ci);                                      \ +                                                                        \ +        for (i++; i < count; i++) {                                     \ +            if (data[i] == ci) {                                        \ +                continue;                                               \ +            } else {                                                    \ +                ci = data[i];                                           \ +                if (!palette_put(*palette, (uint32_t)ci)) {             \ +                    return 0;                                           \ +                }                                                       \ +            }                                                           \ +        }                                                               \ +                                                                        \ +        return palette_size(*palette);                                  \ +    } + +DEFINE_FILL_PALETTE_FUNCTION(8) +DEFINE_FILL_PALETTE_FUNCTION(16) +DEFINE_FILL_PALETTE_FUNCTION(32) + +static int tight_fill_palette(VncState *vs, int x, int y, +                              size_t count, uint32_t *bg, uint32_t *fg, +                              VncPalette **palette) +{ +    int max; + +    max = count / tight_conf[vs->tight.compression].idx_max_colors_divisor; +    if (max < 2 && +        count >= tight_conf[vs->tight.compression].mono_min_rect_size) { +        max = 2; +    } +    if (max >= 256) { +        max = 256; +    } + +    switch (vs->client_pf.bytes_per_pixel) { +    case 4: +        return tight_fill_palette32(vs, x, y, max, count, bg, fg, palette); +    case 2: +        return tight_fill_palette16(vs, x, y, max, count, bg, fg, palette); +    default: +        max = 2; +        return tight_fill_palette8(vs, x, y, max, count, bg, fg, palette); +    } +    return 0; +} + +/* + * Converting truecolor samples into palette indices. + */ +#define DEFINE_IDX_ENCODE_FUNCTION(bpp)                                 \ +                                                                        \ +    static void                                                         \ +    tight_encode_indexed_rect##bpp(uint8_t *buf, int count,             \ +                                   VncPalette *palette) {               \ +        uint##bpp##_t *src;                                             \ +        uint##bpp##_t rgb;                                              \ +        int i, rep;                                                     \ +        uint8_t idx;                                                    \ +                                                                        \ +        src = (uint##bpp##_t *) buf;                                    \ +                                                                        \ +        for (i = 0; i < count; i++) {                                   \ +                                                                        \ +            rgb = *src++;                                               \ +            rep = 0;                                                    \ +            while (i < count && *src == rgb) {                          \ +                rep++, src++, i++;                                      \ +            }                                                           \ +            idx = palette_idx(palette, rgb);                            \ +            /*                                                          \ +             * Should never happen, but don't break everything          \ +             * if it does, use the first color instead                  \ +             */                                                         \ +            if (idx == (uint8_t)-1) {                                   \ +                idx = 0;                                                \ +            }                                                           \ +            while (rep >= 0) {                                          \ +                *buf++ = idx;                                           \ +                rep--;                                                  \ +            }                                                           \ +        }                                                               \ +    } + +DEFINE_IDX_ENCODE_FUNCTION(16) +DEFINE_IDX_ENCODE_FUNCTION(32) + +#define DEFINE_MONO_ENCODE_FUNCTION(bpp)                                \ +                                                                        \ +    static void                                                         \ +    tight_encode_mono_rect##bpp(uint8_t *buf, int w, int h,             \ +                                uint##bpp##_t bg, uint##bpp##_t fg) {   \ +        uint##bpp##_t *ptr;                                             \ +        unsigned int value, mask;                                       \ +        int aligned_width;                                              \ +        int x, y, bg_bits;                                              \ +                                                                        \ +        ptr = (uint##bpp##_t *) buf;                                    \ +        aligned_width = w - w % 8;                                      \ +                                                                        \ +        for (y = 0; y < h; y++) {                                       \ +            for (x = 0; x < aligned_width; x += 8) {                    \ +                for (bg_bits = 0; bg_bits < 8; bg_bits++) {             \ +                    if (*ptr++ != bg) {                                 \ +                        break;                                          \ +                    }                                                   \ +                }                                                       \ +                if (bg_bits == 8) {                                     \ +                    *buf++ = 0;                                         \ +                    continue;                                           \ +                }                                                       \ +                mask = 0x80 >> bg_bits;                                 \ +                value = mask;                                           \ +                for (bg_bits++; bg_bits < 8; bg_bits++) {               \ +                    mask >>= 1;                                         \ +                    if (*ptr++ != bg) {                                 \ +                        value |= mask;                                  \ +                    }                                                   \ +                }                                                       \ +                *buf++ = (uint8_t)value;                                \ +            }                                                           \ +                                                                        \ +            mask = 0x80;                                                \ +            value = 0;                                                  \ +            if (x >= w) {                                               \ +                continue;                                               \ +            }                                                           \ +                                                                        \ +            for (; x < w; x++) {                                        \ +                if (*ptr++ != bg) {                                     \ +                    value |= mask;                                      \ +                }                                                       \ +                mask >>= 1;                                             \ +            }                                                           \ +            *buf++ = (uint8_t)value;                                    \ +        }                                                               \ +    } + +DEFINE_MONO_ENCODE_FUNCTION(8) +DEFINE_MONO_ENCODE_FUNCTION(16) +DEFINE_MONO_ENCODE_FUNCTION(32) + +/* + * ``Gradient'' filter for 24-bit color samples. + * Should be called only when redMax, greenMax and blueMax are 255. + * Color components assumed to be byte-aligned. + */ + +static void +tight_filter_gradient24(VncState *vs, uint8_t *buf, int w, int h) +{ +    uint32_t *buf32; +    uint32_t pix32; +    int shift[3]; +    int *prev; +    int here[3], upper[3], left[3], upperleft[3]; +    int prediction; +    int x, y, c; + +    buf32 = (uint32_t *)buf; +    memset(vs->tight.gradient.buffer, 0, w * 3 * sizeof(int)); + +    if (1 /* FIXME */) { +        shift[0] = vs->client_pf.rshift; +        shift[1] = vs->client_pf.gshift; +        shift[2] = vs->client_pf.bshift; +    } else { +        shift[0] = 24 - vs->client_pf.rshift; +        shift[1] = 24 - vs->client_pf.gshift; +        shift[2] = 24 - vs->client_pf.bshift; +    } + +    for (y = 0; y < h; y++) { +        for (c = 0; c < 3; c++) { +            upper[c] = 0; +            here[c] = 0; +        } +        prev = (int *)vs->tight.gradient.buffer; +        for (x = 0; x < w; x++) { +            pix32 = *buf32++; +            for (c = 0; c < 3; c++) { +                upperleft[c] = upper[c]; +                left[c] = here[c]; +                upper[c] = *prev; +                here[c] = (int)(pix32 >> shift[c] & 0xFF); +                *prev++ = here[c]; + +                prediction = left[c] + upper[c] - upperleft[c]; +                if (prediction < 0) { +                    prediction = 0; +                } else if (prediction > 0xFF) { +                    prediction = 0xFF; +                } +                *buf++ = (char)(here[c] - prediction); +            } +        } +    } +} + + +/* + * ``Gradient'' filter for other color depths. + */ + +#define DEFINE_GRADIENT_FILTER_FUNCTION(bpp)                            \ +                                                                        \ +    static void                                                         \ +    tight_filter_gradient##bpp(VncState *vs, uint##bpp##_t *buf,        \ +                               int w, int h) {                          \ +        uint##bpp##_t pix, diff;                                        \ +        bool endian;                                                    \ +        int *prev;                                                      \ +        int max[3], shift[3];                                           \ +        int here[3], upper[3], left[3], upperleft[3];                   \ +        int prediction;                                                 \ +        int x, y, c;                                                    \ +                                                                        \ +        memset (vs->tight.gradient.buffer, 0, w * 3 * sizeof(int));     \ +                                                                        \ +        endian = 0; /* FIXME */                                         \ +                                                                        \ +        max[0] = vs->client_pf.rmax;                                    \ +        max[1] = vs->client_pf.gmax;                                    \ +        max[2] = vs->client_pf.bmax;                                    \ +        shift[0] = vs->client_pf.rshift;                                \ +        shift[1] = vs->client_pf.gshift;                                \ +        shift[2] = vs->client_pf.bshift;                                \ +                                                                        \ +        for (y = 0; y < h; y++) {                                       \ +            for (c = 0; c < 3; c++) {                                   \ +                upper[c] = 0;                                           \ +                here[c] = 0;                                            \ +            }                                                           \ +            prev = (int *)vs->tight.gradient.buffer;                    \ +            for (x = 0; x < w; x++) {                                   \ +                pix = *buf;                                             \ +                if (endian) {                                           \ +                    pix = bswap##bpp(pix);                              \ +                }                                                       \ +                diff = 0;                                               \ +                for (c = 0; c < 3; c++) {                               \ +                    upperleft[c] = upper[c];                            \ +                    left[c] = here[c];                                  \ +                    upper[c] = *prev;                                   \ +                    here[c] = (int)(pix >> shift[c] & max[c]);          \ +                    *prev++ = here[c];                                  \ +                                                                        \ +                    prediction = left[c] + upper[c] - upperleft[c];     \ +                    if (prediction < 0) {                               \ +                        prediction = 0;                                 \ +                    } else if (prediction > max[c]) {                   \ +                        prediction = max[c];                            \ +                    }                                                   \ +                    diff |= ((here[c] - prediction) & max[c])           \ +                        << shift[c];                                    \ +                }                                                       \ +                if (endian) {                                           \ +                    diff = bswap##bpp(diff);                            \ +                }                                                       \ +                *buf++ = diff;                                          \ +            }                                                           \ +        }                                                               \ +    } + +DEFINE_GRADIENT_FILTER_FUNCTION(16) +DEFINE_GRADIENT_FILTER_FUNCTION(32) + +/* + * Check if a rectangle is all of the same color. If needSameColor is + * set to non-zero, then also check that its color equals to the + * *colorPtr value. The result is 1 if the test is successful, and in + * that case new color will be stored in *colorPtr. + */ + +static bool +check_solid_tile32(VncState *vs, int x, int y, int w, int h, +                   uint32_t *color, bool samecolor) +{ +    VncDisplay *vd = vs->vd; +    uint32_t *fbptr; +    uint32_t c; +    int dx, dy; + +    fbptr = vnc_server_fb_ptr(vd, x, y); + +    c = *fbptr; +    if (samecolor && (uint32_t)c != *color) { +        return false; +    } + +    for (dy = 0; dy < h; dy++) { +        for (dx = 0; dx < w; dx++) { +            if (c != fbptr[dx]) { +                return false; +            } +        } +        fbptr = (uint32_t *) +            ((uint8_t *)fbptr + vnc_server_fb_stride(vd)); +    } + +    *color = (uint32_t)c; +    return true; +} + +static bool check_solid_tile(VncState *vs, int x, int y, int w, int h, +                             uint32_t* color, bool samecolor) +{ +    switch (VNC_SERVER_FB_BYTES) { +    case 4: +        return check_solid_tile32(vs, x, y, w, h, color, samecolor); +    } +} + +static void find_best_solid_area(VncState *vs, int x, int y, int w, int h, +                                 uint32_t color, int *w_ptr, int *h_ptr) +{ +    int dx, dy, dw, dh; +    int w_prev; +    int w_best = 0, h_best = 0; + +    w_prev = w; + +    for (dy = y; dy < y + h; dy += VNC_TIGHT_MAX_SPLIT_TILE_SIZE) { + +        dh = MIN(VNC_TIGHT_MAX_SPLIT_TILE_SIZE, y + h - dy); +        dw = MIN(VNC_TIGHT_MAX_SPLIT_TILE_SIZE, w_prev); + +        if (!check_solid_tile(vs, x, dy, dw, dh, &color, true)) { +            break; +        } + +        for (dx = x + dw; dx < x + w_prev;) { +            dw = MIN(VNC_TIGHT_MAX_SPLIT_TILE_SIZE, x + w_prev - dx); + +            if (!check_solid_tile(vs, dx, dy, dw, dh, &color, true)) { +                break; +            } +            dx += dw; +        } + +        w_prev = dx - x; +        if (w_prev * (dy + dh - y) > w_best * h_best) { +            w_best = w_prev; +            h_best = dy + dh - y; +        } +    } + +    *w_ptr = w_best; +    *h_ptr = h_best; +} + +static void extend_solid_area(VncState *vs, int x, int y, int w, int h, +                              uint32_t color, int *x_ptr, int *y_ptr, +                              int *w_ptr, int *h_ptr) +{ +    int cx, cy; + +    /* Try to extend the area upwards. */ +    for ( cy = *y_ptr - 1; +          cy >= y && check_solid_tile(vs, *x_ptr, cy, *w_ptr, 1, &color, true); +          cy-- ); +    *h_ptr += *y_ptr - (cy + 1); +    *y_ptr = cy + 1; + +    /* ... downwards. */ +    for ( cy = *y_ptr + *h_ptr; +          cy < y + h && +              check_solid_tile(vs, *x_ptr, cy, *w_ptr, 1, &color, true); +          cy++ ); +    *h_ptr += cy - (*y_ptr + *h_ptr); + +    /* ... to the left. */ +    for ( cx = *x_ptr - 1; +          cx >= x && check_solid_tile(vs, cx, *y_ptr, 1, *h_ptr, &color, true); +          cx-- ); +    *w_ptr += *x_ptr - (cx + 1); +    *x_ptr = cx + 1; + +    /* ... to the right. */ +    for ( cx = *x_ptr + *w_ptr; +          cx < x + w && +              check_solid_tile(vs, cx, *y_ptr, 1, *h_ptr, &color, true); +          cx++ ); +    *w_ptr += cx - (*x_ptr + *w_ptr); +} + +static int tight_init_stream(VncState *vs, int stream_id, +                             int level, int strategy) +{ +    z_streamp zstream = &vs->tight.stream[stream_id]; + +    if (zstream->opaque == NULL) { +        int err; + +        VNC_DEBUG("VNC: TIGHT: initializing zlib stream %d\n", stream_id); +        VNC_DEBUG("VNC: TIGHT: opaque = %p | vs = %p\n", zstream->opaque, vs); +        zstream->zalloc = vnc_zlib_zalloc; +        zstream->zfree = vnc_zlib_zfree; + +        err = deflateInit2(zstream, level, Z_DEFLATED, MAX_WBITS, +                           MAX_MEM_LEVEL, strategy); + +        if (err != Z_OK) { +            fprintf(stderr, "VNC: error initializing zlib\n"); +            return -1; +        } + +        vs->tight.levels[stream_id] = level; +        zstream->opaque = vs; +    } + +    if (vs->tight.levels[stream_id] != level) { +        if (deflateParams(zstream, level, strategy) != Z_OK) { +            return -1; +        } +        vs->tight.levels[stream_id] = level; +    } +    return 0; +} + +static void tight_send_compact_size(VncState *vs, size_t len) +{ +    int lpc = 0; +    int bytes = 0; +    char buf[3] = {0, 0, 0}; + +    buf[bytes++] = len & 0x7F; +    if (len > 0x7F) { +        buf[bytes-1] |= 0x80; +        buf[bytes++] = (len >> 7) & 0x7F; +        if (len > 0x3FFF) { +            buf[bytes-1] |= 0x80; +            buf[bytes++] = (len >> 14) & 0xFF; +        } +    } +    for (lpc = 0; lpc < bytes; lpc++) { +        vnc_write_u8(vs, buf[lpc]); +    } +} + +static int tight_compress_data(VncState *vs, int stream_id, size_t bytes, +                               int level, int strategy) +{ +    z_streamp zstream = &vs->tight.stream[stream_id]; +    int previous_out; + +    if (bytes < VNC_TIGHT_MIN_TO_COMPRESS) { +        vnc_write(vs, vs->tight.tight.buffer, vs->tight.tight.offset); +        return bytes; +    } + +    if (tight_init_stream(vs, stream_id, level, strategy)) { +        return -1; +    } + +    /* reserve memory in output buffer */ +    buffer_reserve(&vs->tight.zlib, bytes + 64); + +    /* set pointers */ +    zstream->next_in = vs->tight.tight.buffer; +    zstream->avail_in = vs->tight.tight.offset; +    zstream->next_out = vs->tight.zlib.buffer + vs->tight.zlib.offset; +    zstream->avail_out = vs->tight.zlib.capacity - vs->tight.zlib.offset; +    previous_out = zstream->avail_out; +    zstream->data_type = Z_BINARY; + +    /* start encoding */ +    if (deflate(zstream, Z_SYNC_FLUSH) != Z_OK) { +        fprintf(stderr, "VNC: error during tight compression\n"); +        return -1; +    } + +    vs->tight.zlib.offset = vs->tight.zlib.capacity - zstream->avail_out; +    /* ...how much data has actually been produced by deflate() */ +    bytes = previous_out - zstream->avail_out; + +    tight_send_compact_size(vs, bytes); +    vnc_write(vs, vs->tight.zlib.buffer, bytes); + +    buffer_reset(&vs->tight.zlib); + +    return bytes; +} + +/* + * Subencoding implementations. + */ +static void tight_pack24(VncState *vs, uint8_t *buf, size_t count, size_t *ret) +{ +    uint32_t *buf32; +    uint32_t pix; +    int rshift, gshift, bshift; + +    buf32 = (uint32_t *)buf; + +    if (1 /* FIXME */) { +        rshift = vs->client_pf.rshift; +        gshift = vs->client_pf.gshift; +        bshift = vs->client_pf.bshift; +    } else { +        rshift = 24 - vs->client_pf.rshift; +        gshift = 24 - vs->client_pf.gshift; +        bshift = 24 - vs->client_pf.bshift; +    } + +    if (ret) { +        *ret = count * 3; +    } + +    while (count--) { +        pix = *buf32++; +        *buf++ = (char)(pix >> rshift); +        *buf++ = (char)(pix >> gshift); +        *buf++ = (char)(pix >> bshift); +    } +} + +static int send_full_color_rect(VncState *vs, int x, int y, int w, int h) +{ +    int stream = 0; +    ssize_t bytes; + +#ifdef CONFIG_VNC_PNG +    if (tight_can_send_png_rect(vs, w, h)) { +        return send_png_rect(vs, x, y, w, h, NULL); +    } +#endif + +    vnc_write_u8(vs, stream << 4); /* no flushing, no filter */ + +    if (vs->tight.pixel24) { +        tight_pack24(vs, vs->tight.tight.buffer, w * h, &vs->tight.tight.offset); +        bytes = 3; +    } else { +        bytes = vs->client_pf.bytes_per_pixel; +    } + +    bytes = tight_compress_data(vs, stream, w * h * bytes, +                                tight_conf[vs->tight.compression].raw_zlib_level, +                                Z_DEFAULT_STRATEGY); + +    return (bytes >= 0); +} + +static int send_solid_rect(VncState *vs) +{ +    size_t bytes; + +    vnc_write_u8(vs, VNC_TIGHT_FILL << 4); /* no flushing, no filter */ + +    if (vs->tight.pixel24) { +        tight_pack24(vs, vs->tight.tight.buffer, 1, &vs->tight.tight.offset); +        bytes = 3; +    } else { +        bytes = vs->client_pf.bytes_per_pixel; +    } + +    vnc_write(vs, vs->tight.tight.buffer, bytes); +    return 1; +} + +static int send_mono_rect(VncState *vs, int x, int y, +                          int w, int h, uint32_t bg, uint32_t fg) +{ +    ssize_t bytes; +    int stream = 1; +    int level = tight_conf[vs->tight.compression].mono_zlib_level; + +#ifdef CONFIG_VNC_PNG +    if (tight_can_send_png_rect(vs, w, h)) { +        int ret; +        int bpp = vs->client_pf.bytes_per_pixel * 8; +        VncPalette *palette = palette_new(2, bpp); + +        palette_put(palette, bg); +        palette_put(palette, fg); +        ret = send_png_rect(vs, x, y, w, h, palette); +        palette_destroy(palette); +        return ret; +    } +#endif + +    bytes = ((w + 7) / 8) * h; + +    vnc_write_u8(vs, (stream | VNC_TIGHT_EXPLICIT_FILTER) << 4); +    vnc_write_u8(vs, VNC_TIGHT_FILTER_PALETTE); +    vnc_write_u8(vs, 1); + +    switch (vs->client_pf.bytes_per_pixel) { +    case 4: +    { +        uint32_t buf[2] = {bg, fg}; +        size_t ret = sizeof (buf); + +        if (vs->tight.pixel24) { +            tight_pack24(vs, (unsigned char*)buf, 2, &ret); +        } +        vnc_write(vs, buf, ret); + +        tight_encode_mono_rect32(vs->tight.tight.buffer, w, h, bg, fg); +        break; +    } +    case 2: +        vnc_write(vs, &bg, 2); +        vnc_write(vs, &fg, 2); +        tight_encode_mono_rect16(vs->tight.tight.buffer, w, h, bg, fg); +        break; +    default: +        vnc_write_u8(vs, bg); +        vnc_write_u8(vs, fg); +        tight_encode_mono_rect8(vs->tight.tight.buffer, w, h, bg, fg); +        break; +    } +    vs->tight.tight.offset = bytes; + +    bytes = tight_compress_data(vs, stream, bytes, level, Z_DEFAULT_STRATEGY); +    return (bytes >= 0); +} + +struct palette_cb_priv { +    VncState *vs; +    uint8_t *header; +#ifdef CONFIG_VNC_PNG +    png_colorp png_palette; +#endif +}; + +static void write_palette(int idx, uint32_t color, void *opaque) +{ +    struct palette_cb_priv *priv = opaque; +    VncState *vs = priv->vs; +    uint32_t bytes = vs->client_pf.bytes_per_pixel; + +    if (bytes == 4) { +        ((uint32_t*)priv->header)[idx] = color; +    } else { +        ((uint16_t*)priv->header)[idx] = color; +    } +} + +static bool send_gradient_rect(VncState *vs, int x, int y, int w, int h) +{ +    int stream = 3; +    int level = tight_conf[vs->tight.compression].gradient_zlib_level; +    ssize_t bytes; + +    if (vs->client_pf.bytes_per_pixel == 1) { +        return send_full_color_rect(vs, x, y, w, h); +    } + +    vnc_write_u8(vs, (stream | VNC_TIGHT_EXPLICIT_FILTER) << 4); +    vnc_write_u8(vs, VNC_TIGHT_FILTER_GRADIENT); + +    buffer_reserve(&vs->tight.gradient, w * 3 * sizeof (int)); + +    if (vs->tight.pixel24) { +        tight_filter_gradient24(vs, vs->tight.tight.buffer, w, h); +        bytes = 3; +    } else if (vs->client_pf.bytes_per_pixel == 4) { +        tight_filter_gradient32(vs, (uint32_t *)vs->tight.tight.buffer, w, h); +        bytes = 4; +    } else { +        tight_filter_gradient16(vs, (uint16_t *)vs->tight.tight.buffer, w, h); +        bytes = 2; +    } + +    buffer_reset(&vs->tight.gradient); + +    bytes = w * h * bytes; +    vs->tight.tight.offset = bytes; + +    bytes = tight_compress_data(vs, stream, bytes, +                                level, Z_FILTERED); +    return (bytes >= 0); +} + +static int send_palette_rect(VncState *vs, int x, int y, +                             int w, int h, VncPalette *palette) +{ +    int stream = 2; +    int level = tight_conf[vs->tight.compression].idx_zlib_level; +    int colors; +    ssize_t bytes; + +#ifdef CONFIG_VNC_PNG +    if (tight_can_send_png_rect(vs, w, h)) { +        return send_png_rect(vs, x, y, w, h, palette); +    } +#endif + +    colors = palette_size(palette); + +    vnc_write_u8(vs, (stream | VNC_TIGHT_EXPLICIT_FILTER) << 4); +    vnc_write_u8(vs, VNC_TIGHT_FILTER_PALETTE); +    vnc_write_u8(vs, colors - 1); + +    switch (vs->client_pf.bytes_per_pixel) { +    case 4: +    { +        size_t old_offset, offset; +        uint32_t header[palette_size(palette)]; +        struct palette_cb_priv priv = { vs, (uint8_t *)header }; + +        old_offset = vs->output.offset; +        palette_iter(palette, write_palette, &priv); +        vnc_write(vs, header, sizeof(header)); + +        if (vs->tight.pixel24) { +            tight_pack24(vs, vs->output.buffer + old_offset, colors, &offset); +            vs->output.offset = old_offset + offset; +        } + +        tight_encode_indexed_rect32(vs->tight.tight.buffer, w * h, palette); +        break; +    } +    case 2: +    { +        uint16_t header[palette_size(palette)]; +        struct palette_cb_priv priv = { vs, (uint8_t *)header }; + +        palette_iter(palette, write_palette, &priv); +        vnc_write(vs, header, sizeof(header)); +        tight_encode_indexed_rect16(vs->tight.tight.buffer, w * h, palette); +        break; +    } +    default: +        return -1; /* No palette for 8bits colors */ +        break; +    } +    bytes = w * h; +    vs->tight.tight.offset = bytes; + +    bytes = tight_compress_data(vs, stream, bytes, +                                level, Z_DEFAULT_STRATEGY); +    return (bytes >= 0); +} + +/* + * JPEG compression stuff. + */ +#ifdef CONFIG_VNC_JPEG +/* + * Destination manager implementation for JPEG library. + */ + +/* This is called once per encoding */ +static void jpeg_init_destination(j_compress_ptr cinfo) +{ +    VncState *vs = cinfo->client_data; +    Buffer *buffer = &vs->tight.jpeg; + +    cinfo->dest->next_output_byte = (JOCTET *)buffer->buffer + buffer->offset; +    cinfo->dest->free_in_buffer = (size_t)(buffer->capacity - buffer->offset); +} + +/* This is called when we ran out of buffer (shouldn't happen!) */ +static boolean jpeg_empty_output_buffer(j_compress_ptr cinfo) +{ +    VncState *vs = cinfo->client_data; +    Buffer *buffer = &vs->tight.jpeg; + +    buffer->offset = buffer->capacity; +    buffer_reserve(buffer, 2048); +    jpeg_init_destination(cinfo); +    return TRUE; +} + +/* This is called when we are done processing data */ +static void jpeg_term_destination(j_compress_ptr cinfo) +{ +    VncState *vs = cinfo->client_data; +    Buffer *buffer = &vs->tight.jpeg; + +    buffer->offset = buffer->capacity - cinfo->dest->free_in_buffer; +} + +static int send_jpeg_rect(VncState *vs, int x, int y, int w, int h, int quality) +{ +    struct jpeg_compress_struct cinfo; +    struct jpeg_error_mgr jerr; +    struct jpeg_destination_mgr manager; +    pixman_image_t *linebuf; +    JSAMPROW row[1]; +    uint8_t *buf; +    int dy; + +    if (surface_bytes_per_pixel(vs->vd->ds) == 1) { +        return send_full_color_rect(vs, x, y, w, h); +    } + +    buffer_reserve(&vs->tight.jpeg, 2048); + +    cinfo.err = jpeg_std_error(&jerr); +    jpeg_create_compress(&cinfo); + +    cinfo.client_data = vs; +    cinfo.image_width = w; +    cinfo.image_height = h; +    cinfo.input_components = 3; +    cinfo.in_color_space = JCS_RGB; + +    jpeg_set_defaults(&cinfo); +    jpeg_set_quality(&cinfo, quality, true); + +    manager.init_destination = jpeg_init_destination; +    manager.empty_output_buffer = jpeg_empty_output_buffer; +    manager.term_destination = jpeg_term_destination; +    cinfo.dest = &manager; + +    jpeg_start_compress(&cinfo, true); + +    linebuf = qemu_pixman_linebuf_create(PIXMAN_BE_r8g8b8, w); +    buf = (uint8_t *)pixman_image_get_data(linebuf); +    row[0] = buf; +    for (dy = 0; dy < h; dy++) { +        qemu_pixman_linebuf_fill(linebuf, vs->vd->server, w, x, y + dy); +        jpeg_write_scanlines(&cinfo, row, 1); +    } +    qemu_pixman_image_unref(linebuf); + +    jpeg_finish_compress(&cinfo); +    jpeg_destroy_compress(&cinfo); + +    vnc_write_u8(vs, VNC_TIGHT_JPEG << 4); + +    tight_send_compact_size(vs, vs->tight.jpeg.offset); +    vnc_write(vs, vs->tight.jpeg.buffer, vs->tight.jpeg.offset); +    buffer_reset(&vs->tight.jpeg); + +    return 1; +} +#endif /* CONFIG_VNC_JPEG */ + +/* + * PNG compression stuff. + */ +#ifdef CONFIG_VNC_PNG +static void write_png_palette(int idx, uint32_t pix, void *opaque) +{ +    struct palette_cb_priv *priv = opaque; +    VncState *vs = priv->vs; +    png_colorp color = &priv->png_palette[idx]; + +    if (vs->tight.pixel24) +    { +        color->red = (pix >> vs->client_pf.rshift) & vs->client_pf.rmax; +        color->green = (pix >> vs->client_pf.gshift) & vs->client_pf.gmax; +        color->blue = (pix >> vs->client_pf.bshift) & vs->client_pf.bmax; +    } +    else +    { +        int red, green, blue; + +        red = (pix >> vs->client_pf.rshift) & vs->client_pf.rmax; +        green = (pix >> vs->client_pf.gshift) & vs->client_pf.gmax; +        blue = (pix >> vs->client_pf.bshift) & vs->client_pf.bmax; +        color->red = ((red * 255 + vs->client_pf.rmax / 2) / +                      vs->client_pf.rmax); +        color->green = ((green * 255 + vs->client_pf.gmax / 2) / +                        vs->client_pf.gmax); +        color->blue = ((blue * 255 + vs->client_pf.bmax / 2) / +                       vs->client_pf.bmax); +    } +} + +static void png_write_data(png_structp png_ptr, png_bytep data, +                           png_size_t length) +{ +    VncState *vs = png_get_io_ptr(png_ptr); + +    buffer_reserve(&vs->tight.png, vs->tight.png.offset + length); +    memcpy(vs->tight.png.buffer + vs->tight.png.offset, data, length); + +    vs->tight.png.offset += length; +} + +static void png_flush_data(png_structp png_ptr) +{ +} + +static void *vnc_png_malloc(png_structp png_ptr, png_size_t size) +{ +    return g_malloc(size); +} + +static void vnc_png_free(png_structp png_ptr, png_voidp ptr) +{ +    g_free(ptr); +} + +static int send_png_rect(VncState *vs, int x, int y, int w, int h, +                         VncPalette *palette) +{ +    png_byte color_type; +    png_structp png_ptr; +    png_infop info_ptr; +    png_colorp png_palette = NULL; +    pixman_image_t *linebuf; +    int level = tight_png_conf[vs->tight.compression].png_zlib_level; +    int filters = tight_png_conf[vs->tight.compression].png_filters; +    uint8_t *buf; +    int dy; + +    png_ptr = png_create_write_struct_2(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL, +                                        NULL, vnc_png_malloc, vnc_png_free); + +    if (png_ptr == NULL) +        return -1; + +    info_ptr = png_create_info_struct(png_ptr); + +    if (info_ptr == NULL) { +        png_destroy_write_struct(&png_ptr, NULL); +        return -1; +    } + +    png_set_write_fn(png_ptr, (void *) vs, png_write_data, png_flush_data); +    png_set_compression_level(png_ptr, level); +    png_set_filter(png_ptr, PNG_FILTER_TYPE_DEFAULT, filters); + +    if (palette) { +        color_type = PNG_COLOR_TYPE_PALETTE; +    } else { +        color_type = PNG_COLOR_TYPE_RGB; +    } + +    png_set_IHDR(png_ptr, info_ptr, w, h, +                 8, color_type, PNG_INTERLACE_NONE, +                 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + +    if (color_type == PNG_COLOR_TYPE_PALETTE) { +        struct palette_cb_priv priv; + +        png_palette = png_malloc(png_ptr, sizeof(*png_palette) * +                                 palette_size(palette)); + +        priv.vs = vs; +        priv.png_palette = png_palette; +        palette_iter(palette, write_png_palette, &priv); + +        png_set_PLTE(png_ptr, info_ptr, png_palette, palette_size(palette)); + +        if (vs->client_pf.bytes_per_pixel == 4) { +            tight_encode_indexed_rect32(vs->tight.tight.buffer, w * h, palette); +        } else { +            tight_encode_indexed_rect16(vs->tight.tight.buffer, w * h, palette); +        } +    } + +    png_write_info(png_ptr, info_ptr); + +    buffer_reserve(&vs->tight.png, 2048); +    linebuf = qemu_pixman_linebuf_create(PIXMAN_BE_r8g8b8, w); +    buf = (uint8_t *)pixman_image_get_data(linebuf); +    for (dy = 0; dy < h; dy++) +    { +        if (color_type == PNG_COLOR_TYPE_PALETTE) { +            memcpy(buf, vs->tight.tight.buffer + (dy * w), w); +        } else { +            qemu_pixman_linebuf_fill(linebuf, vs->vd->server, w, x, y + dy); +        } +        png_write_row(png_ptr, buf); +    } +    qemu_pixman_image_unref(linebuf); + +    png_write_end(png_ptr, NULL); + +    if (color_type == PNG_COLOR_TYPE_PALETTE) { +        png_free(png_ptr, png_palette); +    } + +    png_destroy_write_struct(&png_ptr, &info_ptr); + +    vnc_write_u8(vs, VNC_TIGHT_PNG << 4); + +    tight_send_compact_size(vs, vs->tight.png.offset); +    vnc_write(vs, vs->tight.png.buffer, vs->tight.png.offset); +    buffer_reset(&vs->tight.png); +    return 1; +} +#endif /* CONFIG_VNC_PNG */ + +static void vnc_tight_start(VncState *vs) +{ +    buffer_reset(&vs->tight.tight); + +    // make the output buffer be the zlib buffer, so we can compress it later +    vs->tight.tmp = vs->output; +    vs->output = vs->tight.tight; +} + +static void vnc_tight_stop(VncState *vs) +{ +    // switch back to normal output/zlib buffers +    vs->tight.tight = vs->output; +    vs->output = vs->tight.tmp; +} + +static int send_sub_rect_nojpeg(VncState *vs, int x, int y, int w, int h, +                                int bg, int fg, int colors, VncPalette *palette) +{ +    int ret; + +    if (colors == 0) { +        if (tight_detect_smooth_image(vs, w, h)) { +            ret = send_gradient_rect(vs, x, y, w, h); +        } else { +            ret = send_full_color_rect(vs, x, y, w, h); +        } +    } else if (colors == 1) { +        ret = send_solid_rect(vs); +    } else if (colors == 2) { +        ret = send_mono_rect(vs, x, y, w, h, bg, fg); +    } else if (colors <= 256) { +        ret = send_palette_rect(vs, x, y, w, h, palette); +    } else { +        ret = 0; +    } +    return ret; +} + +#ifdef CONFIG_VNC_JPEG +static int send_sub_rect_jpeg(VncState *vs, int x, int y, int w, int h, +                              int bg, int fg, int colors, +                              VncPalette *palette, bool force) +{ +    int ret; + +    if (colors == 0) { +        if (force || (tight_jpeg_conf[vs->tight.quality].jpeg_full && +                      tight_detect_smooth_image(vs, w, h))) { +            int quality = tight_conf[vs->tight.quality].jpeg_quality; + +            ret = send_jpeg_rect(vs, x, y, w, h, quality); +        } else { +            ret = send_full_color_rect(vs, x, y, w, h); +        } +    } else if (colors == 1) { +        ret = send_solid_rect(vs); +    } else if (colors == 2) { +        ret = send_mono_rect(vs, x, y, w, h, bg, fg); +    } else if (colors <= 256) { +        if (force || (colors > 96 && +                      tight_jpeg_conf[vs->tight.quality].jpeg_idx && +                      tight_detect_smooth_image(vs, w, h))) { +            int quality = tight_conf[vs->tight.quality].jpeg_quality; + +            ret = send_jpeg_rect(vs, x, y, w, h, quality); +        } else { +            ret = send_palette_rect(vs, x, y, w, h, palette); +        } +    } else { +        ret = 0; +    } +    return ret; +} +#endif + +static 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; +#ifdef CONFIG_VNC_JPEG +    bool force_jpeg = false; +    bool allow_jpeg = true; +#endif + +    vnc_framebuffer_update(vs, x, y, w, h, vs->tight.type); + +    vnc_tight_start(vs); +    vnc_raw_send_framebuffer_update(vs, x, y, w, h); +    vnc_tight_stop(vs); + +#ifdef CONFIG_VNC_JPEG +    if (!vs->vd->non_adaptive && vs->tight.quality != (uint8_t)-1) { +        double freq = vnc_update_freq(vs, x, y, w, h); + +        if (freq < tight_jpeg_conf[vs->tight.quality].jpeg_freq_min) { +            allow_jpeg = false; +        } +        if (freq >= tight_jpeg_conf[vs->tight.quality].jpeg_freq_threshold) { +            force_jpeg = true; +            vnc_sent_lossy_rect(vs, x, y, w, h); +        } +    } +#endif + +    colors = tight_fill_palette(vs, x, y, w * h, &bg, &fg, &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); +    } else { +        ret = send_sub_rect_nojpeg(vs, x, y, w, h, bg, fg, colors, palette); +    } +#else +    ret = send_sub_rect_nojpeg(vs, x, y, w, h, bg, fg, colors, 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_tight_start(vs); +    vnc_raw_send_framebuffer_update(vs, x, y, w, h); +    vnc_tight_stop(vs); + +    return send_solid_rect(vs); +} + +static int send_rect_simple(VncState *vs, int x, int y, int w, int h, +                            bool split) +{ +    int max_size, max_width; +    int max_sub_width, max_sub_height; +    int dx, dy; +    int rw, rh; +    int n = 0; + +    max_size = tight_conf[vs->tight.compression].max_rect_size; +    max_width = tight_conf[vs->tight.compression].max_rect_width; + +    if (split && (w > max_width || w * h > max_size)) { +        max_sub_width = (w > max_width) ? max_width : w; +        max_sub_height = max_size / max_sub_width; + +        for (dy = 0; dy < h; dy += max_sub_height) { +            for (dx = 0; dx < w; dx += max_width) { +                rw = MIN(max_sub_width, w - dx); +                rh = MIN(max_sub_height, h - dy); +                n += send_sub_rect(vs, x+dx, y+dy, rw, rh); +            } +        } +    } else { +        n += send_sub_rect(vs, x, y, w, h); +    } + +    return n; +} + +static int find_large_solid_color_rect(VncState *vs, int x, int y, +                                       int w, int h, int max_rows) +{ +    int dx, dy, dw, dh; +    int n = 0; + +    /* Try to find large solid-color areas and send them separately. */ + +    for (dy = y; dy < y + h; dy += VNC_TIGHT_MAX_SPLIT_TILE_SIZE) { + +        /* If a rectangle becomes too large, send its upper part now. */ + +        if (dy - y >= max_rows) { +            n += send_rect_simple(vs, x, y, w, max_rows, true); +            y += max_rows; +            h -= max_rows; +        } + +        dh = MIN(VNC_TIGHT_MAX_SPLIT_TILE_SIZE, (y + h - dy)); + +        for (dx = x; dx < x + w; dx += VNC_TIGHT_MAX_SPLIT_TILE_SIZE) { +            uint32_t color_value; +            int x_best, y_best, w_best, h_best; + +            dw = MIN(VNC_TIGHT_MAX_SPLIT_TILE_SIZE, (x + w - dx)); + +            if (!check_solid_tile(vs, dx, dy, dw, dh, &color_value, false)) { +                continue ; +            } + +            /* Get dimensions of solid-color area. */ + +            find_best_solid_area(vs, dx, dy, w - (dx - x), h - (dy - y), +                                 color_value, &w_best, &h_best); + +            /* Make sure a solid rectangle is large enough +               (or the whole rectangle is of the same color). */ + +            if (w_best * h_best != w * h && +                w_best * h_best < VNC_TIGHT_MIN_SOLID_SUBRECT_SIZE) { +                continue; +            } + +            /* Try to extend solid rectangle to maximum size. */ + +            x_best = dx; y_best = dy; +            extend_solid_area(vs, x, y, w, h, color_value, +                              &x_best, &y_best, &w_best, &h_best); + +            /* Send rectangles at top and left to solid-color area. */ + +            if (y_best != y) { +                n += send_rect_simple(vs, x, y, w, y_best-y, true); +            } +            if (x_best != x) { +                n += tight_send_framebuffer_update(vs, x, y_best, +                                                   x_best-x, h_best); +            } + +            /* Send solid-color rectangle. */ +            n += send_sub_rect_solid(vs, x_best, y_best, w_best, h_best); + +            /* Send remaining rectangles (at right and bottom). */ + +            if (x_best + w_best != x + w) { +                n += tight_send_framebuffer_update(vs, x_best+w_best, +                                                   y_best, +                                                   w-(x_best-x)-w_best, +                                                   h_best); +            } +            if (y_best + h_best != y + h) { +                n += tight_send_framebuffer_update(vs, x, y_best+h_best, +                                                   w, h-(y_best-y)-h_best); +            } + +            /* Return after all recursive calls are done. */ +            return n; +        } +    } +    return n + send_rect_simple(vs, x, y, w, h, true); +} + +static int tight_send_framebuffer_update(VncState *vs, int x, int y, +                                         int w, int h) +{ +    int max_rows; + +    if (vs->client_pf.bytes_per_pixel == 4 && vs->client_pf.rmax == 0xFF && +        vs->client_pf.bmax == 0xFF && vs->client_pf.gmax == 0xFF) { +        vs->tight.pixel24 = true; +    } else { +        vs->tight.pixel24 = false; +    } + +#ifdef CONFIG_VNC_JPEG +    if (vs->tight.quality != (uint8_t)-1) { +        double freq = vnc_update_freq(vs, x, y, w, h); + +        if (freq > tight_jpeg_conf[vs->tight.quality].jpeg_freq_threshold) { +            return send_rect_simple(vs, x, y, w, h, false); +        } +    } +#endif + +    if (w * h < VNC_TIGHT_MIN_SPLIT_RECT_SIZE) { +        return send_rect_simple(vs, x, y, w, h, true); +    } + +    /* Calculate maximum number of rows in one non-solid rectangle. */ + +    max_rows = tight_conf[vs->tight.compression].max_rect_size; +    max_rows /= MIN(tight_conf[vs->tight.compression].max_rect_width, w); + +    return find_large_solid_color_rect(vs, x, y, w, h, max_rows); +} + +int vnc_tight_send_framebuffer_update(VncState *vs, int x, int y, +                                      int w, int h) +{ +    vs->tight.type = VNC_ENCODING_TIGHT; +    return tight_send_framebuffer_update(vs, x, y, w, h); +} + +int vnc_tight_png_send_framebuffer_update(VncState *vs, int x, int y, +                                          int w, int h) +{ +    vs->tight.type = VNC_ENCODING_TIGHT_PNG; +    return tight_send_framebuffer_update(vs, x, y, w, h); +} + +void vnc_tight_clear(VncState *vs) +{ +    int i; +    for (i=0; i<ARRAY_SIZE(vs->tight.stream); i++) { +        if (vs->tight.stream[i].opaque) { +            deflateEnd(&vs->tight.stream[i]); +        } +    } + +    buffer_free(&vs->tight.tight); +    buffer_free(&vs->tight.zlib); +    buffer_free(&vs->tight.gradient); +#ifdef CONFIG_VNC_JPEG +    buffer_free(&vs->tight.jpeg); +#endif +#ifdef CONFIG_VNC_PNG +    buffer_free(&vs->tight.png); +#endif +} diff --git a/ui/vnc-enc-tight.h b/ui/vnc-enc-tight.h new file mode 100644 index 00000000..a3add788 --- /dev/null +++ b/ui/vnc-enc-tight.h @@ -0,0 +1,183 @@ +/* + * QEMU VNC display driver: tight encoding + * + * From libvncserver/rfb/rfbproto.h + * Copyright (C) 2005 Rohit Kumar, Johannes E. Schindelin + * Copyright (C) 2000-2002 Constantin Kaplinsky.  All Rights Reserved. + * Copyright (C) 2000 Tridia Corporation.  All Rights Reserved. + * Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved. + * + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef VNC_ENCODING_TIGHT_H +#define VNC_ENCODING_TIGHT_H + +/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * Tight Encoding. + * + *-- The first byte of each Tight-encoded rectangle is a "compression control + *   byte". Its format is as follows (bit 0 is the least significant one): + * + *   bit 0:    if 1, then compression stream 0 should be reset; + *   bit 1:    if 1, then compression stream 1 should be reset; + *   bit 2:    if 1, then compression stream 2 should be reset; + *   bit 3:    if 1, then compression stream 3 should be reset; + *   bits 7-4: if 1000 (0x08), then the compression type is "fill", + *             if 1001 (0x09), then the compression type is "jpeg", + *             if 1010 (0x0A), then the compression type is "png", + *             if 0xxx, then the compression type is "basic", + *             values greater than 1010 are not valid. + * + * If the compression type is "basic", then bits 6..4 of the + * compression control byte (those xxx in 0xxx) specify the following: + * + *   bits 5-4:  decimal representation is the index of a particular zlib + *              stream which should be used for decompressing the data; + *   bit 6:     if 1, then a "filter id" byte is following this byte. + * + *-- The data that follows after the compression control byte described + * above depends on the compression type ("fill", "jpeg", "png" or "basic"). + * + *-- If the compression type is "fill", then the only pixel value follows, in + * client pixel format (see NOTE 1). This value applies to all pixels of the + * rectangle. + * + *-- If the compression type is "jpeg" or "png", the following data stream + * looks like this: + * + *   1..3 bytes:  data size (N) in compact representation; + *   N bytes:     JPEG or PNG image. + * + * Data size is compactly represented in one, two or three bytes, according + * to the following scheme: + * + *  0xxxxxxx                    (for values 0..127) + *  1xxxxxxx 0yyyyyyy           (for values 128..16383) + *  1xxxxxxx 1yyyyyyy zzzzzzzz  (for values 16384..4194303) + * + * Here each character denotes one bit, xxxxxxx are the least significant 7 + * bits of the value (bits 0-6), yyyyyyy are bits 7-13, and zzzzzzzz are the + * most significant 8 bits (bits 14-21). For example, decimal value 10000 + * should be represented as two bytes: binary 10010000 01001110, or + * hexadecimal 90 4E. + * + *-- If the compression type is "basic" and bit 6 of the compression control + * byte was set to 1, then the next (second) byte specifies "filter id" which + * tells the decoder what filter type was used by the encoder to pre-process + * pixel data before the compression. The "filter id" byte can be one of the + * following: + * + *   0:  no filter ("copy" filter); + *   1:  "palette" filter; + *   2:  "gradient" filter. + * + *-- If bit 6 of the compression control byte is set to 0 (no "filter id" + * byte), or if the filter id is 0, then raw pixel values in the client + * format (see NOTE 1) will be compressed. See below details on the + * compression. + * + *-- The "gradient" filter pre-processes pixel data with a simple algorithm + * which converts each color component to a difference between a "predicted" + * intensity and the actual intensity. Such a technique does not affect + * uncompressed data size, but helps to compress photo-like images better. + * Pseudo-code for converting intensities to differences is the following: + * + *   P[i,j] := V[i-1,j] + V[i,j-1] - V[i-1,j-1]; + *   if (P[i,j] < 0) then P[i,j] := 0; + *   if (P[i,j] > MAX) then P[i,j] := MAX; + *   D[i,j] := V[i,j] - P[i,j]; + * + * Here V[i,j] is the intensity of a color component for a pixel at + * coordinates (i,j). MAX is the maximum value of intensity for a color + * component. + * + *-- The "palette" filter converts true-color pixel data to indexed colors + * and a palette which can consist of 2..256 colors. If the number of colors + * is 2, then each pixel is encoded in 1 bit, otherwise 8 bits is used to + * encode one pixel. 1-bit encoding is performed such way that the most + * significant bits correspond to the leftmost pixels, and each raw of pixels + * is aligned to the byte boundary. When "palette" filter is used, the + * palette is sent before the pixel data. The palette begins with an unsigned + * byte which value is the number of colors in the palette minus 1 (i.e. 1 + * means 2 colors, 255 means 256 colors in the palette). Then follows the + * palette itself which consist of pixel values in client pixel format (see + * NOTE 1). + * + *-- The pixel data is compressed using the zlib library. But if the data + * size after applying the filter but before the compression is less then 12, + * then the data is sent as is, uncompressed. Four separate zlib streams + * (0..3) can be used and the decoder should read the actual stream id from + * the compression control byte (see NOTE 2). + * + * If the compression is not used, then the pixel data is sent as is, + * otherwise the data stream looks like this: + * + *   1..3 bytes:  data size (N) in compact representation; + *   N bytes:     zlib-compressed data. + * + * Data size is compactly represented in one, two or three bytes, just like + * in the "jpeg" compression method (see above). + * + *-- NOTE 1. If the color depth is 24, and all three color components are + * 8-bit wide, then one pixel in Tight encoding is always represented by + * three bytes, where the first byte is red component, the second byte is + * green component, and the third byte is blue component of the pixel color + * value. This applies to colors in palettes as well. + * + *-- NOTE 2. The decoder must reset compression streams' states before + * decoding the rectangle, if some of bits 0,1,2,3 in the compression control + * byte are set to 1. Note that the decoder must reset zlib streams even if + * the compression type is "fill", "jpeg" or "png". + * + *-- NOTE 3. The "gradient" filter and "jpeg" compression may be used only + * when bits-per-pixel value is either 16 or 32, not 8. + * + *-- NOTE 4. The width of any Tight-encoded rectangle cannot exceed 2048 + * pixels. If a rectangle is wider, it must be split into several rectangles + * and each one should be encoded separately. + * + */ + +#define VNC_TIGHT_EXPLICIT_FILTER       0x04 +#define VNC_TIGHT_FILL                  0x08 +#define VNC_TIGHT_JPEG                  0x09 +#define VNC_TIGHT_PNG                   0x0A +#define VNC_TIGHT_MAX_SUBENCODING       0x0A + +/* Filters to improve compression efficiency */ +#define VNC_TIGHT_FILTER_COPY             0x00 +#define VNC_TIGHT_FILTER_PALETTE          0x01 +#define VNC_TIGHT_FILTER_GRADIENT         0x02 + +/* Note: The following constant should not be changed. */ +#define VNC_TIGHT_MIN_TO_COMPRESS 12 + +/* The parameters below may be adjusted. */ +#define VNC_TIGHT_MIN_SPLIT_RECT_SIZE     4096 +#define VNC_TIGHT_MIN_SOLID_SUBRECT_SIZE  2048 +#define VNC_TIGHT_MAX_SPLIT_TILE_SIZE       16 + +#define VNC_TIGHT_JPEG_MIN_RECT_SIZE      4096 +#define VNC_TIGHT_DETECT_SUBROW_WIDTH        7 +#define VNC_TIGHT_DETECT_MIN_WIDTH           8 +#define VNC_TIGHT_DETECT_MIN_HEIGHT          8 + +#endif /* VNC_ENCODING_TIGHT_H */ diff --git a/ui/vnc-enc-zlib.c b/ui/vnc-enc-zlib.c new file mode 100644 index 00000000..d1b97f25 --- /dev/null +++ b/ui/vnc-enc-zlib.c @@ -0,0 +1,152 @@ +/* + * QEMU VNC display driver: zlib encoding + * + * Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws> + * Copyright (C) 2006 Fabrice Bellard + * Copyright (C) 2009 Red Hat, Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "vnc.h" + +#define ZALLOC_ALIGNMENT 16 + +void *vnc_zlib_zalloc(void *x, unsigned items, unsigned size) +{ +    void *p; + +    size *= items; +    size = (size + ZALLOC_ALIGNMENT - 1) & ~(ZALLOC_ALIGNMENT - 1); + +    p = g_malloc0(size); + +    return (p); +} + +void vnc_zlib_zfree(void *x, void *addr) +{ +    g_free(addr); +} + +static void vnc_zlib_start(VncState *vs) +{ +    buffer_reset(&vs->zlib.zlib); + +    // make the output buffer be the zlib buffer, so we can compress it later +    vs->zlib.tmp = vs->output; +    vs->output = vs->zlib.zlib; +} + +static int vnc_zlib_stop(VncState *vs) +{ +    z_streamp zstream = &vs->zlib.stream; +    int previous_out; + +    // switch back to normal output/zlib buffers +    vs->zlib.zlib = vs->output; +    vs->output = vs->zlib.tmp; + +    // compress the zlib buffer + +    // initialize the stream +    // XXX need one stream per session +    if (zstream->opaque != vs) { +        int err; + +        VNC_DEBUG("VNC: initializing zlib stream\n"); +        VNC_DEBUG("VNC: opaque = %p | vs = %p\n", zstream->opaque, vs); +        zstream->zalloc = vnc_zlib_zalloc; +        zstream->zfree = vnc_zlib_zfree; + +        err = deflateInit2(zstream, vs->tight.compression, Z_DEFLATED, MAX_WBITS, +                           MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); + +        if (err != Z_OK) { +            fprintf(stderr, "VNC: error initializing zlib\n"); +            return -1; +        } + +        vs->zlib.level = vs->tight.compression; +        zstream->opaque = vs; +    } + +    if (vs->tight.compression != vs->zlib.level) { +        if (deflateParams(zstream, vs->tight.compression, +                          Z_DEFAULT_STRATEGY) != Z_OK) { +            return -1; +        } +        vs->zlib.level = vs->tight.compression; +    } + +    // reserve memory in output buffer +    buffer_reserve(&vs->output, vs->zlib.zlib.offset + 64); + +    // set pointers +    zstream->next_in = vs->zlib.zlib.buffer; +    zstream->avail_in = vs->zlib.zlib.offset; +    zstream->next_out = vs->output.buffer + vs->output.offset; +    zstream->avail_out = vs->output.capacity - vs->output.offset; +    previous_out = zstream->avail_out; +    zstream->data_type = Z_BINARY; + +    // start encoding +    if (deflate(zstream, Z_SYNC_FLUSH) != Z_OK) { +        fprintf(stderr, "VNC: error during zlib compression\n"); +        return -1; +    } + +    vs->output.offset = vs->output.capacity - zstream->avail_out; +    return previous_out - zstream->avail_out; +} + +int vnc_zlib_send_framebuffer_update(VncState *vs, int x, int y, int w, int h) +{ +    int old_offset, new_offset, bytes_written; + +    vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_ZLIB); + +    // remember where we put in the follow-up size +    old_offset = vs->output.offset; +    vnc_write_s32(vs, 0); + +    // compress the stream +    vnc_zlib_start(vs); +    vnc_raw_send_framebuffer_update(vs, x, y, w, h); +    bytes_written = vnc_zlib_stop(vs); + +    if (bytes_written == -1) +        return 0; + +    // hack in the size +    new_offset = vs->output.offset; +    vs->output.offset = old_offset; +    vnc_write_u32(vs, bytes_written); +    vs->output.offset = new_offset; + +    return 1; +} + +void vnc_zlib_clear(VncState *vs) +{ +    if (vs->zlib.stream.opaque) { +        deflateEnd(&vs->zlib.stream); +    } +    buffer_free(&vs->zlib.zlib); +} diff --git a/ui/vnc-enc-zrle-template.c b/ui/vnc-enc-zrle-template.c new file mode 100644 index 00000000..70ae624e --- /dev/null +++ b/ui/vnc-enc-zrle-template.c @@ -0,0 +1,263 @@ +/* + * QEMU VNC display driver: Zlib Run-length Encoding (ZRLE) + * + * From libvncserver/libvncserver/zrleencodetemplate.c + * Copyright (C) 2002 RealVNC Ltd.  All Rights Reserved. + * Copyright (C) 2003 Sun Microsystems, Inc. + * + * Copyright (C) 2010 Corentin Chary <corentin.chary@gmail.com> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +/* + * Before including this file, you must define a number of CPP macros. + * + * ZRLE_BPP should be 8, 16 or 32 depending on the bits per pixel. + * + * Note that the buf argument to ZRLE_ENCODE needs to be at least one pixel + * bigger than the largest tile of pixel data, since the ZRLE encoding + * algorithm writes to the position one past the end of the pixel data. + */ + + +#include <assert.h> + +#undef ZRLE_ENDIAN_SUFFIX + +#if ZYWRLE_ENDIAN == ENDIAN_LITTLE +#define ZRLE_ENDIAN_SUFFIX le +#elif ZYWRLE_ENDIAN == ENDIAN_BIG +#define ZRLE_ENDIAN_SUFFIX be +#else +#define ZRLE_ENDIAN_SUFFIX ne +#endif + +#ifndef ZRLE_CONCAT +#define ZRLE_CONCAT_I(a, b)    a##b +#define ZRLE_CONCAT2(a, b)     ZRLE_CONCAT_I(a, b) +#define ZRLE_CONCAT3(a, b, c)  ZRLE_CONCAT2(a, ZRLE_CONCAT2(b, c)) +#endif + +#ifdef ZRLE_COMPACT_PIXEL +#define ZRLE_ENCODE_SUFFIX   ZRLE_CONCAT2(ZRLE_COMPACT_PIXEL,ZRLE_ENDIAN_SUFFIX) +#define ZRLE_WRITE_SUFFIX    ZRLE_COMPACT_PIXEL +#define ZRLE_PIXEL           ZRLE_CONCAT3(uint,ZRLE_BPP,_t) +#define ZRLE_BPP_OUT         24 +#elif ZRLE_BPP == 15 +#define ZRLE_ENCODE_SUFFIX   ZRLE_CONCAT2(ZRLE_BPP,ZRLE_ENDIAN_SUFFIX) +#define ZRLE_WRITE_SUFFIX    16 +#define ZRLE_PIXEL           uint16_t +#define ZRLE_BPP_OUT         16 +#else +#define ZRLE_ENCODE_SUFFIX   ZRLE_CONCAT2(ZRLE_BPP,ZRLE_ENDIAN_SUFFIX) +#define ZRLE_WRITE_SUFFIX    ZRLE_BPP +#define ZRLE_BPP_OUT         ZRLE_BPP +#define ZRLE_PIXEL           ZRLE_CONCAT3(uint,ZRLE_BPP,_t) +#endif + +#define ZRLE_WRITE_PIXEL     ZRLE_CONCAT2(zrle_write_u,       ZRLE_WRITE_SUFFIX) +#define ZRLE_ENCODE          ZRLE_CONCAT2(zrle_encode_,      ZRLE_ENCODE_SUFFIX) +#define ZRLE_ENCODE_TILE     ZRLE_CONCAT2(zrle_encode_tile,  ZRLE_ENCODE_SUFFIX) +#define ZRLE_WRITE_PALETTE   ZRLE_CONCAT2(zrle_write_palette,ZRLE_ENCODE_SUFFIX) + +static void ZRLE_ENCODE_TILE(VncState *vs, ZRLE_PIXEL *data, int w, int h, +                             int zywrle_level); + +#if ZRLE_BPP != 8 +#include "vnc-enc-zywrle-template.c" +#endif + + +static void ZRLE_ENCODE(VncState *vs, int x, int y, int w, int h, +                        int zywrle_level) +{ +    int ty; + +    for (ty = y; ty < y + h; ty += VNC_ZRLE_TILE_HEIGHT) { + +        int tx, th; + +        th = MIN(VNC_ZRLE_TILE_HEIGHT, y + h - ty); + +        for (tx = x; tx < x + w; tx += VNC_ZRLE_TILE_WIDTH) { +            int tw; +            ZRLE_PIXEL *buf; + +            tw = MIN(VNC_ZRLE_TILE_WIDTH, x + w - tx); + +            buf = zrle_convert_fb(vs, tx, ty, tw, th, ZRLE_BPP); +            ZRLE_ENCODE_TILE(vs, buf, tw, th, zywrle_level); +        } +    } +} + +static void ZRLE_ENCODE_TILE(VncState *vs, ZRLE_PIXEL *data, int w, int h, +                             int zywrle_level) +{ +    VncPalette *palette = &vs->zrle.palette; + +    int runs = 0; +    int single_pixels = 0; + +    bool use_rle; +    bool use_palette; + +    int i; + +    ZRLE_PIXEL *ptr = data; +    ZRLE_PIXEL *end = ptr + h * w; +    *end = ~*(end-1); /* one past the end is different so the while loop ends */ + +    /* Real limit is 127 but we wan't a way to know if there is more than 127 */ +    palette_init(palette, 256, ZRLE_BPP); + +    while (ptr < end) { +        ZRLE_PIXEL pix = *ptr; +        if (*++ptr != pix) { /* FIXME */ +            single_pixels++; +        } else { +            while (*++ptr == pix) ; +            runs++; +        } +        palette_put(palette, pix); +    } + +    /* Solid tile is a special case */ + +    if (palette_size(palette) == 1) { +        bool found; + +        vnc_write_u8(vs, 1); +        ZRLE_WRITE_PIXEL(vs, palette_color(palette, 0, &found)); +        return; +    } + +    zrle_choose_palette_rle(vs, w, h, palette, ZRLE_BPP_OUT, +                            runs, single_pixels, zywrle_level, +                            &use_rle, &use_palette); + +    if (!use_palette) { +        vnc_write_u8(vs, (use_rle ? 128 : 0)); +    } else { +        uint32_t colors[VNC_PALETTE_MAX_SIZE]; +        size_t size = palette_size(palette); + +        vnc_write_u8(vs, (use_rle ? 128 : 0) | size); +        palette_fill(palette, colors); + +        for (i = 0; i < size; i++) { +            ZRLE_WRITE_PIXEL(vs, colors[i]); +        } +    } + +    if (use_rle) { +        ZRLE_PIXEL *ptr = data; +        ZRLE_PIXEL *end = ptr + w * h; +        ZRLE_PIXEL *run_start; +        ZRLE_PIXEL pix; + +        while (ptr < end) { +            int len; +            int index = 0; + +            run_start = ptr; +            pix = *ptr++; + +            while (*ptr == pix && ptr < end) { +                ptr++; +            } + +            len = ptr - run_start; + +            if (use_palette) +                index = palette_idx(palette, pix); + +            if (len <= 2 && use_palette) { +                if (len == 2) { +                    vnc_write_u8(vs, index); +                } +                vnc_write_u8(vs, index); +                continue; +            } +            if (use_palette) { +                vnc_write_u8(vs, index | 128); +            } else { +                ZRLE_WRITE_PIXEL(vs, pix); +            } + +            len -= 1; + +            while (len >= 255) { +                vnc_write_u8(vs, 255); +                len -= 255; +            } + +            vnc_write_u8(vs, len); +        } +    } else if (use_palette) { /* no RLE */ +        int bppp; +        ZRLE_PIXEL *ptr = data; + +        /* packed pixels */ + +        assert (palette_size(palette) < 17); + +        bppp = bits_per_packed_pixel[palette_size(palette)-1]; + +        for (i = 0; i < h; i++) { +            uint8_t nbits = 0; +            uint8_t byte = 0; + +            ZRLE_PIXEL *eol = ptr + w; + +            while (ptr < eol) { +                ZRLE_PIXEL pix = *ptr++; +                uint8_t index = palette_idx(palette, pix); + +                byte = (byte << bppp) | index; +                nbits += bppp; +                if (nbits >= 8) { +                    vnc_write_u8(vs, byte); +                    nbits = 0; +                } +            } +            if (nbits > 0) { +                byte <<= 8 - nbits; +                vnc_write_u8(vs, byte); +            } +        } +    } else { + +        /* raw */ + +#if ZRLE_BPP != 8 +        if (zywrle_level > 0 && !(zywrle_level & 0x80)) { +            ZYWRLE_ANALYZE(data, data, w, h, w, zywrle_level, vs->zywrle.buf); +            ZRLE_ENCODE_TILE(vs, data, w, h, zywrle_level | 0x80); +        } +        else +#endif +        { +#ifdef ZRLE_COMPACT_PIXEL +            ZRLE_PIXEL *ptr; + +            for (ptr = data; ptr < data + w * h; ptr++) { +                ZRLE_WRITE_PIXEL(vs, *ptr); +            } +#else +            vnc_write(vs, data, w * h * (ZRLE_BPP / 8)); +#endif +        } +    } +} + +#undef ZRLE_PIXEL +#undef ZRLE_WRITE_PIXEL +#undef ZRLE_ENCODE +#undef ZRLE_ENCODE_TILE +#undef ZYWRLE_ENCODE_TILE +#undef ZRLE_BPP_OUT +#undef ZRLE_WRITE_SUFFIX +#undef ZRLE_ENCODE_SUFFIX diff --git a/ui/vnc-enc-zrle.c b/ui/vnc-enc-zrle.c new file mode 100644 index 00000000..ed3b4846 --- /dev/null +++ b/ui/vnc-enc-zrle.c @@ -0,0 +1,366 @@ +/* + * QEMU VNC display driver: Zlib Run-length Encoding (ZRLE) + * + * From libvncserver/libvncserver/zrle.c + * Copyright (C) 2002 RealVNC Ltd.  All Rights Reserved. + * Copyright (C) 2003 Sun Microsystems, Inc. + * + * Copyright (C) 2010 Corentin Chary <corentin.chary@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "vnc.h" +#include "vnc-enc-zrle.h" + +static const int bits_per_packed_pixel[] = { +  0, 1, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 +}; + + +static void vnc_zrle_start(VncState *vs) +{ +    buffer_reset(&vs->zrle.zrle); + +    /* make the output buffer be the zlib buffer, so we can compress it later */ +    vs->zrle.tmp = vs->output; +    vs->output = vs->zrle.zrle; +} + +static void vnc_zrle_stop(VncState *vs) +{ +    /* switch back to normal output/zlib buffers */ +    vs->zrle.zrle = vs->output; +    vs->output = vs->zrle.tmp; +} + +static void *zrle_convert_fb(VncState *vs, int x, int y, int w, int h, +                             int bpp) +{ +    Buffer tmp; + +    buffer_reset(&vs->zrle.fb); +    buffer_reserve(&vs->zrle.fb, w * h * bpp + bpp); + +    tmp = vs->output; +    vs->output = vs->zrle.fb; + +    vnc_raw_send_framebuffer_update(vs, x, y, w, h); + +    vs->zrle.fb = vs->output; +    vs->output = tmp; +    return vs->zrle.fb.buffer; +} + +static int zrle_compress_data(VncState *vs, int level) +{ +    z_streamp zstream = &vs->zrle.stream; + +    buffer_reset(&vs->zrle.zlib); + +    if (zstream->opaque != vs) { +        int err; + +        zstream->zalloc = vnc_zlib_zalloc; +        zstream->zfree = vnc_zlib_zfree; + +        err = deflateInit2(zstream, level, Z_DEFLATED, MAX_WBITS, +                           MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); + +        if (err != Z_OK) { +            fprintf(stderr, "VNC: error initializing zlib\n"); +            return -1; +        } + +        zstream->opaque = vs; +    } + +    /* reserve memory in output buffer */ +    buffer_reserve(&vs->zrle.zlib, vs->zrle.zrle.offset + 64); + +    /* set pointers */ +    zstream->next_in = vs->zrle.zrle.buffer; +    zstream->avail_in = vs->zrle.zrle.offset; +    zstream->next_out = vs->zrle.zlib.buffer + vs->zrle.zlib.offset; +    zstream->avail_out = vs->zrle.zlib.capacity - vs->zrle.zlib.offset; +    zstream->data_type = Z_BINARY; + +    /* start encoding */ +    if (deflate(zstream, Z_SYNC_FLUSH) != Z_OK) { +        fprintf(stderr, "VNC: error during zrle compression\n"); +        return -1; +    } + +    vs->zrle.zlib.offset = vs->zrle.zlib.capacity - zstream->avail_out; +    return vs->zrle.zlib.offset; +} + +/* Try to work out whether to use RLE and/or a palette.  We do this by + * estimating the number of bytes which will be generated and picking the + * method which results in the fewest bytes.  Of course this may not result + * in the fewest bytes after compression... */ +static void zrle_choose_palette_rle(VncState *vs, int w, int h, +                                    VncPalette *palette, int bpp_out, +                                    int runs, int single_pixels, +                                    int zywrle_level, +                                    bool *use_rle, bool *use_palette) +{ +    size_t estimated_bytes; +    size_t plain_rle_bytes; + +    *use_palette = *use_rle = false; + +    estimated_bytes = w * h * (bpp_out / 8); /* start assuming raw */ + +    if (bpp_out != 8) { +        if (zywrle_level > 0 && !(zywrle_level & 0x80)) +            estimated_bytes >>= zywrle_level; +    } + +    plain_rle_bytes = ((bpp_out / 8) + 1) * (runs + single_pixels); + +    if (plain_rle_bytes < estimated_bytes) { +        *use_rle = true; +        estimated_bytes = plain_rle_bytes; +    } + +    if (palette_size(palette) < 128) { +        int palette_rle_bytes; + +        palette_rle_bytes = (bpp_out / 8) * palette_size(palette); +        palette_rle_bytes += 2 * runs + single_pixels; + +        if (palette_rle_bytes < estimated_bytes) { +            *use_rle = true; +            *use_palette = true; +            estimated_bytes = palette_rle_bytes; +        } + +        if (palette_size(palette) < 17) { +            int packed_bytes; + +            packed_bytes = (bpp_out / 8) * palette_size(palette); +            packed_bytes += w * h * +                bits_per_packed_pixel[palette_size(palette)-1] / 8; + +            if (packed_bytes < estimated_bytes) { +                *use_rle = false; +                *use_palette = true; +                estimated_bytes = packed_bytes; +            } +        } +    } +} + +static void zrle_write_u32(VncState *vs, uint32_t value) +{ +    vnc_write(vs, (uint8_t *)&value, 4); +} + +static void zrle_write_u24a(VncState *vs, uint32_t value) +{ +    vnc_write(vs, (uint8_t *)&value, 3); +} + +static void zrle_write_u24b(VncState *vs, uint32_t value) +{ +    vnc_write(vs, ((uint8_t *)&value) + 1, 3); +} + +static void zrle_write_u16(VncState *vs, uint16_t value) +{ +    vnc_write(vs, (uint8_t *)&value, 2); +} + +static void zrle_write_u8(VncState *vs, uint8_t value) +{ +    vnc_write_u8(vs, value); +} + +#define ENDIAN_LITTLE 0 +#define ENDIAN_BIG    1 +#define ENDIAN_NO     2 + +#define ZRLE_BPP 8 +#define ZYWRLE_ENDIAN ENDIAN_NO +#include "vnc-enc-zrle-template.c" +#undef ZRLE_BPP + +#define ZRLE_BPP 15 +#undef ZYWRLE_ENDIAN +#define ZYWRLE_ENDIAN ENDIAN_LITTLE +#include "vnc-enc-zrle-template.c" + +#undef ZYWRLE_ENDIAN +#define ZYWRLE_ENDIAN ENDIAN_BIG +#include "vnc-enc-zrle-template.c" + +#undef ZRLE_BPP +#define ZRLE_BPP 16 +#undef ZYWRLE_ENDIAN +#define ZYWRLE_ENDIAN ENDIAN_LITTLE +#include "vnc-enc-zrle-template.c" + +#undef ZYWRLE_ENDIAN +#define ZYWRLE_ENDIAN ENDIAN_BIG +#include "vnc-enc-zrle-template.c" + +#undef ZRLE_BPP +#define ZRLE_BPP 32 +#undef ZYWRLE_ENDIAN +#define ZYWRLE_ENDIAN ENDIAN_LITTLE +#include "vnc-enc-zrle-template.c" + +#undef ZYWRLE_ENDIAN +#define ZYWRLE_ENDIAN ENDIAN_BIG +#include "vnc-enc-zrle-template.c" + +#define ZRLE_COMPACT_PIXEL 24a +#undef ZYWRLE_ENDIAN +#define ZYWRLE_ENDIAN ENDIAN_LITTLE +#include "vnc-enc-zrle-template.c" + +#undef ZYWRLE_ENDIAN +#define ZYWRLE_ENDIAN ENDIAN_BIG +#include "vnc-enc-zrle-template.c" + +#undef ZRLE_COMPACT_PIXEL +#define ZRLE_COMPACT_PIXEL 24b +#undef ZYWRLE_ENDIAN +#define ZYWRLE_ENDIAN ENDIAN_LITTLE +#include "vnc-enc-zrle-template.c" + +#undef ZYWRLE_ENDIAN +#define ZYWRLE_ENDIAN ENDIAN_BIG +#include "vnc-enc-zrle-template.c" +#undef ZRLE_COMPACT_PIXEL +#undef ZRLE_BPP + +static int zrle_send_framebuffer_update(VncState *vs, int x, int y, +                                        int w, int h) +{ +    bool be = vs->client_be; +    size_t bytes; +    int zywrle_level; + +    if (vs->zrle.type == VNC_ENCODING_ZYWRLE) { +        if (!vs->vd->lossy || vs->tight.quality == (uint8_t)-1 +            || vs->tight.quality == 9) { +            zywrle_level = 0; +            vs->zrle.type = VNC_ENCODING_ZRLE; +        } else if (vs->tight.quality < 3) { +            zywrle_level = 3; +        } else if (vs->tight.quality < 6) { +            zywrle_level = 2; +        } else { +            zywrle_level = 1; +        } +    } else { +        zywrle_level = 0; +    } + +    vnc_zrle_start(vs); + +    switch (vs->client_pf.bytes_per_pixel) { +    case 1: +        zrle_encode_8ne(vs, x, y, w, h, zywrle_level); +        break; + +    case 2: +        if (vs->client_pf.gmax > 0x1F) { +            if (be) { +                zrle_encode_16be(vs, x, y, w, h, zywrle_level); +            } else { +                zrle_encode_16le(vs, x, y, w, h, zywrle_level); +            } +        } else { +            if (be) { +                zrle_encode_15be(vs, x, y, w, h, zywrle_level); +            } else { +                zrle_encode_15le(vs, x, y, w, h, zywrle_level); +            } +        } +        break; + +    case 4: +    { +        bool fits_in_ls3bytes; +        bool fits_in_ms3bytes; + +        fits_in_ls3bytes = +            ((vs->client_pf.rmax << vs->client_pf.rshift) < (1 << 24) && +             (vs->client_pf.gmax << vs->client_pf.gshift) < (1 << 24) && +             (vs->client_pf.bmax << vs->client_pf.bshift) < (1 << 24)); + +        fits_in_ms3bytes = (vs->client_pf.rshift > 7 && +                            vs->client_pf.gshift > 7 && +                            vs->client_pf.bshift > 7); + +        if ((fits_in_ls3bytes && !be) || (fits_in_ms3bytes && be)) { +            if (be) { +                zrle_encode_24abe(vs, x, y, w, h, zywrle_level); +            } else { +                zrle_encode_24ale(vs, x, y, w, h, zywrle_level); +          } +        } else if ((fits_in_ls3bytes && be) || (fits_in_ms3bytes && !be)) { +            if (be) { +                zrle_encode_24bbe(vs, x, y, w, h, zywrle_level); +            } else { +                zrle_encode_24ble(vs, x, y, w, h, zywrle_level); +            } +        } else { +            if (be) { +                zrle_encode_32be(vs, x, y, w, h, zywrle_level); +            } else { +                zrle_encode_32le(vs, x, y, w, h, zywrle_level); +            } +        } +    } +    break; +    } + +    vnc_zrle_stop(vs); +    bytes = zrle_compress_data(vs, Z_DEFAULT_COMPRESSION); +    vnc_framebuffer_update(vs, x, y, w, h, vs->zrle.type); +    vnc_write_u32(vs, bytes); +    vnc_write(vs, vs->zrle.zlib.buffer, vs->zrle.zlib.offset); +    return 1; +} + +int vnc_zrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h) +{ +    vs->zrle.type = VNC_ENCODING_ZRLE; +    return zrle_send_framebuffer_update(vs, x, y, w, h); +} + +int vnc_zywrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h) +{ +    vs->zrle.type = VNC_ENCODING_ZYWRLE; +    return zrle_send_framebuffer_update(vs, x, y, w, h); +} + +void vnc_zrle_clear(VncState *vs) +{ +    if (vs->zrle.stream.opaque) { +        deflateEnd(&vs->zrle.stream); +    } +    buffer_free(&vs->zrle.zrle); +    buffer_free(&vs->zrle.fb); +    buffer_free(&vs->zrle.zlib); +} diff --git a/ui/vnc-enc-zrle.h b/ui/vnc-enc-zrle.h new file mode 100644 index 00000000..6b182132 --- /dev/null +++ b/ui/vnc-enc-zrle.h @@ -0,0 +1,40 @@ +/* + * QEMU VNC display driver: Zlib Run-length Encoding (ZRLE) + * + * From libvncserver/libvncserver/zrle.c + * Copyright (C) 2002 RealVNC Ltd.  All Rights Reserved. + * Copyright (C) 2003 Sun Microsystems, Inc. + * + * Copyright (C) 2010 Corentin Chary <corentin.chary@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef VNC_ENCODING_ZRLE_H +#define VNC_ENCODING_ZRLE_H + +/*- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + * ZRLE - encoding combining Zlib compression, tiling, palettisation and + * run-length encoding. + */ + +#define VNC_ZRLE_TILE_WIDTH  64 +#define VNC_ZRLE_TILE_HEIGHT 64 + +#endif diff --git a/ui/vnc-enc-zywrle-template.c b/ui/vnc-enc-zywrle-template.c new file mode 100644 index 00000000..561f7bfa --- /dev/null +++ b/ui/vnc-enc-zywrle-template.c @@ -0,0 +1,170 @@ + +/******************************************************************** + *                                                                  * + * THIS FILE IS PART OF THE 'ZYWRLE' VNC CODEC SOURCE CODE.         * + *                                                                  * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS     * + * GOVERNED BY A FOLLOWING BSD-STYLE SOURCE LICENSE.                * + * PLEASE READ THESE TERMS BEFORE DISTRIBUTING.                     * + *                                                                  * + * THE 'ZYWRLE' VNC CODEC SOURCE CODE IS (C) COPYRIGHT 2006         * + * BY Hitachi Systems & Services, Ltd.                              * + * (Noriaki Yamazaki, Research & Development Center)               * + *                                                                  * + *                                                                  * + ******************************************************************** +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +- Neither the name of the Hitachi Systems & Services, Ltd. nor +the names of its contributors may be used to endorse or promote +products derived from this software without specific prior written +permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ********************************************************************/ + +/* Change Log: +     V0.02 : 2008/02/04 : Fix mis encode/decode when width != scanline +	                     (Thanks Johannes Schindelin, author of LibVNC +						  Server/Client) +     V0.01 : 2007/02/06 : Initial release +*/ + +/* +[References] + PLHarr: +   Senecal, J. G., P. Lindstrom, M. A. Duchaineau, and K. I. Joy, +   "An Improved N-Bit to N-Bit Reversible Haar-Like Transform," +   Pacific Graphics 2004, October 2004, pp. 371-380. + EZW: +   Shapiro, JM: Embedded Image Coding Using Zerotrees of Wavelet Coefficients, +   IEEE Trans. Signal. Process., Vol.41, pp.3445-3462 (1993). +*/ + + +/* Template Macro stuffs. */ +#undef ZYWRLE_ANALYZE +#undef ZYWRLE_SYNTHESIZE + +#define ZYWRLE_SUFFIX     ZRLE_CONCAT2(ZRLE_BPP,ZRLE_ENDIAN_SUFFIX) + +#define ZYWRLE_ANALYZE    ZRLE_CONCAT2(zywrle_analyze_,   ZYWRLE_SUFFIX) +#define ZYWRLE_SYNTHESIZE ZRLE_CONCAT2(zywrle_synthesize_,ZYWRLE_SUFFIX) + +#define ZYWRLE_RGBYUV     ZRLE_CONCAT2(zywrle_rgbyuv_,    ZYWRLE_SUFFIX) +#define ZYWRLE_YUVRGB     ZRLE_CONCAT2(zywrle_yuvrgb_,    ZYWRLE_SUFFIX) +#define ZYWRLE_YMASK      ZRLE_CONCAT2(ZYWRLE_YMASK,      ZRLE_BPP) +#define ZYWRLE_UVMASK     ZRLE_CONCAT2(ZYWRLE_UVMASK,     ZRLE_BPP) +#define ZYWRLE_LOAD_PIXEL ZRLE_CONCAT2(ZYWRLE_LOAD_PIXEL, ZRLE_BPP) +#define ZYWRLE_SAVE_PIXEL ZRLE_CONCAT2(ZYWRLE_SAVE_PIXEL, ZRLE_BPP) + +/* Packing/Unpacking pixel stuffs. +   Endian conversion stuffs. */ +#undef S_0 +#undef S_1 +#undef L_0 +#undef L_1 +#undef L_2 + +#if ZYWRLE_ENDIAN == ENDIAN_BIG +#  define S_0	1 +#  define S_1	0 +#  define L_0	3 +#  define L_1	2 +#  define L_2	1 +#else +#  define S_0	0 +#  define S_1	1 +#  define L_0	0 +#  define L_1	1 +#  define L_2	2 +#endif + +#define ZYWRLE_QUANTIZE +#include "vnc-enc-zywrle.h" + +#ifndef ZRLE_COMPACT_PIXEL +static inline void ZYWRLE_RGBYUV(int *buf, ZRLE_PIXEL *data, +                                 int width, int height, int scanline) +{ +    int r, g, b; +    int y, u, v; +    int *line; +    int *end; + +    end = buf + height * width; +    while (buf < end) { +        line = buf + width; +        while (buf < line) { +            ZYWRLE_LOAD_PIXEL(data, r, g, b); +            ZYWRLE_RGBYUV_(r, g, b, y, u, v, ZYWRLE_YMASK, ZYWRLE_UVMASK); +            ZYWRLE_SAVE_COEFF(buf, v, y, u); +            buf++; +            data++; +        } +        data += scanline - width; +    } +} + +static ZRLE_PIXEL *ZYWRLE_ANALYZE(ZRLE_PIXEL *dst, ZRLE_PIXEL *src, +                                  int w, int h, int scanline, int level, +                                  int *buf) { +    int l; +    int uw = w; +    int uh = h; +    int *top; +    int *end; +    int *line; +    ZRLE_PIXEL *p; +    int r, g, b; +    int s; +    int *ph; + +    zywrle_calc_size(&w, &h, level); + +    if (w == 0 || h == 0) { +        return NULL; +    } +    uw -= w; +    uh -= h; + +    p = dst; +    ZYWRLE_LOAD_UNALIGN(src,*(ZRLE_PIXEL*)top = *p;); +    ZYWRLE_RGBYUV(buf, src, w, h, scanline); +    wavelet(buf, w, h, level); +    for (l = 0; l < level; l++) { +        ZYWRLE_PACK_COEFF(buf, dst, 3, w, h, scanline, l); +        ZYWRLE_PACK_COEFF(buf, dst, 2, w, h, scanline, l); +        ZYWRLE_PACK_COEFF(buf, dst, 1, w, h, scanline, l); +        if (l == level - 1) { +            ZYWRLE_PACK_COEFF(buf, dst, 0, w, h, scanline, l); +        } +    } +    ZYWRLE_SAVE_UNALIGN(dst,*dst = *(ZRLE_PIXEL*)top;); +    return dst; +} +#endif  /* ZRLE_COMPACT_PIXEL */ + +#undef ZYWRLE_RGBYUV +#undef ZYWRLE_YUVRGB +#undef ZYWRLE_LOAD_PIXEL +#undef ZYWRLE_SAVE_PIXEL diff --git a/ui/vnc-enc-zywrle.h b/ui/vnc-enc-zywrle.h new file mode 100644 index 00000000..d436d588 --- /dev/null +++ b/ui/vnc-enc-zywrle.h @@ -0,0 +1,659 @@ +/******************************************************************** + *                                                                  * + * THIS FILE IS PART OF THE 'ZYWRLE' VNC CODEC SOURCE CODE.         * + *                                                                  * + * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS     * + * GOVERNED BY A FOLLOWING BSD-STYLE SOURCE LICENSE.                * + * PLEASE READ THESE TERMS BEFORE DISTRIBUTING.                     * + *                                                                  * + * THE 'ZYWRLE' VNC CODEC SOURCE CODE IS (C) COPYRIGHT 2006         * + * BY Hitachi Systems & Services, Ltd.                              * + * (Noriaki Yamazaki, Research & Development Center)               * + *                                                                  * + *                                                                  * + ******************************************************************** +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +- Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +- Neither the name of the Hitachi Systems & Services, Ltd. nor +the names of its contributors may be used to endorse or promote +products derived from this software without specific prior written +permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ********************************************************************/ + +#ifndef VNC_ENCODING_ZYWRLE_H +#define VNC_ENCODING_ZYWRLE_H + +/* Tables for Coefficients filtering. */ +#ifndef ZYWRLE_QUANTIZE +/* Type A:lower bit omitting of EZW style. */ +static const unsigned int zywrle_param[3][3]={ +	{0x0000F000, 0x00000000, 0x00000000}, +	{0x0000C000, 0x00F0F0F0, 0x00000000}, +	{0x0000C000, 0x00C0C0C0, 0x00F0F0F0}, +/*	{0x0000FF00, 0x00000000, 0x00000000}, +	{0x0000FF00, 0x00FFFFFF, 0x00000000}, +	{0x0000FF00, 0x00FFFFFF, 0x00FFFFFF}, */ +}; +#else +/* Type B:Non liner quantization filter. */ +static const int8_t zywrle_conv[4][256]={ +{	/* bi=5, bo=5 r=0.0:PSNR=24.849 */ +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +}, +{	/* bi=5, bo=5 r=2.0:PSNR=74.031 */ +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 32, +	32, 32, 32, 32, 32, 32, 32, 32, +	32, 32, 32, 32, 32, 32, 32, 32, +	48, 48, 48, 48, 48, 48, 48, 48, +	48, 48, 48, 56, 56, 56, 56, 56, +	56, 56, 56, 56, 64, 64, 64, 64, +	64, 64, 64, 64, 72, 72, 72, 72, +	72, 72, 72, 72, 80, 80, 80, 80, +	80, 80, 88, 88, 88, 88, 88, 88, +	88, 88, 88, 88, 88, 88, 96, 96, +	96, 96, 96, 104, 104, 104, 104, 104, +	104, 104, 104, 104, 104, 112, 112, 112, +	112, 112, 112, 112, 112, 112, 120, 120, +	120, 120, 120, 120, 120, 120, 120, 120, +	0, -120, -120, -120, -120, -120, -120, -120, +	-120, -120, -120, -112, -112, -112, -112, -112, +	-112, -112, -112, -112, -104, -104, -104, -104, +	-104, -104, -104, -104, -104, -104, -96, -96, +	-96, -96, -96, -88, -88, -88, -88, -88, +	-88, -88, -88, -88, -88, -88, -88, -80, +	-80, -80, -80, -80, -80, -72, -72, -72, +	-72, -72, -72, -72, -72, -64, -64, -64, +	-64, -64, -64, -64, -64, -56, -56, -56, +	-56, -56, -56, -56, -56, -56, -48, -48, +	-48, -48, -48, -48, -48, -48, -48, -48, +	-48, -32, -32, -32, -32, -32, -32, -32, +	-32, -32, -32, -32, -32, -32, -32, -32, +	-32, -32, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +}, +{	/* bi=5, bo=4 r=2.0:PSNR=64.441 */ +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	48, 48, 48, 48, 48, 48, 48, 48, +	48, 48, 48, 48, 48, 48, 48, 48, +	48, 48, 48, 48, 48, 48, 48, 48, +	64, 64, 64, 64, 64, 64, 64, 64, +	64, 64, 64, 64, 64, 64, 64, 64, +	80, 80, 80, 80, 80, 80, 80, 80, +	80, 80, 80, 80, 80, 88, 88, 88, +	88, 88, 88, 88, 88, 88, 88, 88, +	104, 104, 104, 104, 104, 104, 104, 104, +	104, 104, 104, 112, 112, 112, 112, 112, +	112, 112, 112, 112, 120, 120, 120, 120, +	120, 120, 120, 120, 120, 120, 120, 120, +	0, -120, -120, -120, -120, -120, -120, -120, +	-120, -120, -120, -120, -120, -112, -112, -112, +	-112, -112, -112, -112, -112, -112, -104, -104, +	-104, -104, -104, -104, -104, -104, -104, -104, +	-104, -88, -88, -88, -88, -88, -88, -88, +	-88, -88, -88, -88, -80, -80, -80, -80, +	-80, -80, -80, -80, -80, -80, -80, -80, +	-80, -64, -64, -64, -64, -64, -64, -64, +	-64, -64, -64, -64, -64, -64, -64, -64, +	-64, -48, -48, -48, -48, -48, -48, -48, +	-48, -48, -48, -48, -48, -48, -48, -48, +	-48, -48, -48, -48, -48, -48, -48, -48, +	-48, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +}, +{	/* bi=5, bo=2 r=2.0:PSNR=43.175 */ +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	88, 88, 88, 88, 88, 88, 88, 88, +	88, 88, 88, 88, 88, 88, 88, 88, +	88, 88, 88, 88, 88, 88, 88, 88, +	88, 88, 88, 88, 88, 88, 88, 88, +	88, 88, 88, 88, 88, 88, 88, 88, +	88, 88, 88, 88, 88, 88, 88, 88, +	88, 88, 88, 88, 88, 88, 88, 88, +	88, 88, 88, 88, 88, 88, 88, 88, +	0, -88, -88, -88, -88, -88, -88, -88, +	-88, -88, -88, -88, -88, -88, -88, -88, +	-88, -88, -88, -88, -88, -88, -88, -88, +	-88, -88, -88, -88, -88, -88, -88, -88, +	-88, -88, -88, -88, -88, -88, -88, -88, +	-88, -88, -88, -88, -88, -88, -88, -88, +	-88, -88, -88, -88, -88, -88, -88, -88, +	-88, -88, -88, -88, -88, -88, -88, -88, +	-88, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +	0, 0, 0, 0, 0, 0, 0, 0, +} +}; + +static const int8_t *zywrle_param[3][3][3]={ +	{{zywrle_conv[0], zywrle_conv[2], zywrle_conv[0]}, +         {zywrle_conv[0], zywrle_conv[0], zywrle_conv[0]}, +         {zywrle_conv[0], zywrle_conv[0], zywrle_conv[0]}}, +	{{zywrle_conv[0], zywrle_conv[3], zywrle_conv[0]}, +         {zywrle_conv[1], zywrle_conv[1], zywrle_conv[1]}, +         {zywrle_conv[0], zywrle_conv[0], zywrle_conv[0]}}, +	{{zywrle_conv[0], zywrle_conv[3], zywrle_conv[0]}, +         {zywrle_conv[2], zywrle_conv[2], zywrle_conv[2]}, +         {zywrle_conv[1], zywrle_conv[1], zywrle_conv[1]}}, +}; +#endif + +/*   Load/Save pixel stuffs. */ +#define ZYWRLE_YMASK15  0xFFFFFFF8 +#define ZYWRLE_UVMASK15 0xFFFFFFF8 +#define ZYWRLE_LOAD_PIXEL15(src, r, g, b)                               \ +    do {                                                                \ +	r = (((uint8_t*)src)[S_1]<< 1)& 0xF8;                           \ +	g = (((uint8_t*)src)[S_1]<< 6) | (((uint8_t*)src)[S_0]>> 2);    \ +        g &= 0xF8;                                                      \ +	b =  (((uint8_t*)src)[S_0]<< 3)& 0xF8;                          \ +    } while (0) + +#define ZYWRLE_SAVE_PIXEL15(dst, r, g, b)                               \ +    do {                                                                \ +	r &= 0xF8;                                                      \ +	g &= 0xF8;                                                      \ +	b &= 0xF8;                                                      \ +	((uint8_t*)dst)[S_1] = (uint8_t)((r >> 1)|(g >> 6));            \ +	((uint8_t*)dst)[S_0] = (uint8_t)(((b >> 3)|(g << 2))& 0xFF);    \ +    } while (0) + +#define ZYWRLE_YMASK16  0xFFFFFFFC +#define ZYWRLE_UVMASK16 0xFFFFFFF8 +#define ZYWRLE_LOAD_PIXEL16(src, r, g, b)                               \ +    do {                                                                \ +	r = ((uint8_t*)src)[S_1] & 0xF8;                                \ +	g = (((uint8_t*)src)[S_1]<< 5) | (((uint8_t*)src)[S_0] >> 3);   \ +        g &= 0xFC;                                                      \ +	b = (((uint8_t*)src)[S_0]<< 3) & 0xF8;                          \ +    } while (0) + +#define ZYWRLE_SAVE_PIXEL16(dst, r, g,b)                                \ +    do {                                                                \ +	r &= 0xF8;                                                      \ +	g &= 0xFC;                                                      \ +	b &= 0xF8;                                                      \ +	((uint8_t*)dst)[S_1] = (uint8_t)(r | (g >> 5));                 \ +	((uint8_t*)dst)[S_0] = (uint8_t)(((b >> 3)|(g << 3)) & 0xFF);   \ +    } while (0) + +#define ZYWRLE_YMASK32  0xFFFFFFFF +#define ZYWRLE_UVMASK32 0xFFFFFFFF +#define ZYWRLE_LOAD_PIXEL32(src, r, g, b)     \ +    do {                                      \ +	r = ((uint8_t*)src)[L_2];             \ +	g = ((uint8_t*)src)[L_1];             \ +	b = ((uint8_t*)src)[L_0];             \ +    } while (0) +#define ZYWRLE_SAVE_PIXEL32(dst, r, g, b)             \ +    do {                                              \ +	((uint8_t*)dst)[L_2] = (uint8_t)r;            \ +	((uint8_t*)dst)[L_1] = (uint8_t)g;            \ +	((uint8_t*)dst)[L_0] = (uint8_t)b;            \ +    } while (0) + +static inline void harr(int8_t *px0, int8_t *px1) +{ +    /* Piecewise-Linear Harr(PLHarr) */ +    int x0 = (int)*px0, x1 = (int)*px1; +    int orgx0 = x0, orgx1 = x1; + +    if ((x0 ^ x1) & 0x80) { +        /* differ sign */ +        x1 += x0; +        if (((x1 ^ orgx1) & 0x80) == 0) { +            /* |x1| > |x0| */ +            x0 -= x1;	/* H = -B */ +        } +    } else { +        /* same sign */ +        x0 -= x1; +        if (((x0 ^ orgx0) & 0x80) == 0) { +            /* |x0| > |x1| */ +            x1 += x0;	/* L = A */ +        } +    } +    *px0 = (int8_t)x1; +    *px1 = (int8_t)x0; +} + +/* + 1D-Wavelet transform. + + In coefficients array, the famous 'pyramid' decomposition is well used. + + 1D Model: +   |L0L0L0L0|L0L0L0L0|H0H0H0H0|H0H0H0H0| : level 0 +   |L1L1L1L1|H1H1H1H1|H0H0H0H0|H0H0H0H0| : level 1 + + But this method needs line buffer because H/L is different position from X0/X1. + So, I used 'interleave' decomposition instead of it. + + 1D Model: +   |L0H0L0H0|L0H0L0H0|L0H0L0H0|L0H0L0H0| : level 0 +   |L1H0H1H0|L1H0H1H0|L1H0H1H0|L1H0H1H0| : level 1 + + In this method, H/L and X0/X1 is always same position. + This leads us to more speed and less memory. + Of cause, the result of both method is quite same + because it's only difference that coefficient position. +*/ +static inline void wavelet_level(int *data, int size, int l, int skip_pixel) +{ +    int s, ofs; +    int8_t *px0; +    int8_t *end; + +    px0 = (int8_t*)data; +    s = (8 << l) * skip_pixel; +    end = px0 + (size >> (l + 1)) * s; +    s -= 2; +    ofs = (4 << l) * skip_pixel; + +    while (px0 < end) { +        harr(px0, px0 + ofs); +        px0++; +        harr(px0, px0 + ofs); +        px0++; +        harr(px0, px0 + ofs); +        px0 += s; +    } +} + +#ifndef ZYWRLE_QUANTIZE +/* Type A:lower bit omitting of EZW style. */ +static inline void filter_wavelet_square(int *buf, int width, int height, +                                         int level, int l) +{ +    int r, s; +    int x, y; +    int *h; +    const unsigned int *m; + +    m = &(zywrle_param[level - 1][l]); +    s = 2 << l; + +    for (r = 1; r < 4; r++) { +        h = buf; +        if (r & 0x01) { +            h += s >> 1; +        } +        if (r & 0x02) { +            h += (s >> 1) * width; +        } +        for (y = 0; y < height / s; y++) { +            for (x = 0; x < width / s; x++) { +                /* +                  these are same following code. +                  h[x] = h[x] / (~m[x]+1) * (~m[x]+1); +                  ( round h[x] with m[x] bit ) +                  '&' operator isn't 'round' but is 'floor'. +                  So, we must offset when h[x] is negative. +                */ +                if (((int8_t*)h)[0] & 0x80) { +                    ((int8_t*)h)[0] += ~((int8_t*)m)[0]; +                } +                if (((int8_t*)h)[1] & 0x80) { +                    ((int8_t*)h)[1] += ~((int8_t*)m)[1]; +                } +                if (((int8_t*)h)[2] & 0x80) { +                    ((int8_t*)h)[2] += ~((int8_t*)m)[2]; +                } +                *h &= *m; +                h += s; +            } +            h += (s-1)*width; +        } +    } +} +#else +/* + Type B:Non liner quantization filter. + + Coefficients have Gaussian curve and smaller value which is + large part of coefficients isn't more important than larger value. + So, I use filter of Non liner quantize/dequantize table. + In general, Non liner quantize formula is explained as following. + +    y=f(x)   = sign(x)*round( ((abs(x)/(2^7))^ r   )* 2^(bo-1) )*2^(8-bo) +    x=f-1(y) = sign(y)*round( ((abs(y)/(2^7))^(1/r))* 2^(bi-1) )*2^(8-bi) + ( r:power coefficient  bi:effective MSB in input  bo:effective MSB in output ) + +   r < 1.0 : Smaller value is more important than larger value. +   r > 1.0 : Larger value is more important than smaller value. +   r = 1.0 : Liner quantization which is same with EZW style. + + r = 0.75 is famous non liner quantization used in MP3 audio codec. + In contrast to audio data, larger value is important in wavelet coefficients. + So, I select r = 2.0 table( quantize is x^2, dequantize sqrt(x) ). + + As compared with EZW style liner quantization, this filter tended to be + more sharp edge and be more compression rate but be more blocking noise and be + less quality. Especially, the surface of graphic objects has distinguishable + noise in middle quality mode. + + We need only quantized-dequantized(filtered) value rather than quantized value + itself because all values are packed or palette-lized in later ZRLE section. + This lead us not to need to modify client decoder when we change + the filtering procedure in future. + Client only decodes coefficients given by encoder. +*/ +static inline void filter_wavelet_square(int *buf, int width, int height, +                                         int level, int l) +{ +    int r, s; +    int x, y; +    int *h; +    const int8_t **m; + +    m = zywrle_param[level - 1][l]; +    s = 2 << l; + +    for (r = 1; r < 4; r++) { +        h = buf; +        if (r & 0x01) { +            h += s >> 1; +        } +        if (r & 0x02) { +            h += (s >> 1) * width; +        } +        for (y = 0; y < height / s; y++) { +            for (x = 0; x < width / s; x++) { +                ((int8_t*)h)[0] = m[0][((uint8_t*)h)[0]]; +                ((int8_t*)h)[1] = m[1][((uint8_t*)h)[1]]; +                ((int8_t*)h)[2] = m[2][((uint8_t*)h)[2]]; +                h += s; +            } +            h += (s - 1) * width; +        } +    } +} +#endif + +static inline void wavelet(int *buf, int width, int height, int level) +{ +	int l, s; +	int *top; +	int *end; + +	for (l = 0; l < level; l++) { +		top = buf; +		end = buf + height * width; +		s = width << l; +		while (top < end) { +			wavelet_level(top, width, l, 1); +			top += s; +		} +		top = buf; +		end = buf + width; +		s = 1<<l; +		while (top < end) { +			wavelet_level(top, height, l, width); +			top += s; +		} +		filter_wavelet_square(buf, width, height, level, l); +	} +} + + +/* Load/Save coefficients stuffs. + Coefficients manages as 24 bits little-endian pixel. */ +#define ZYWRLE_LOAD_COEFF(src, r, g, b)         \ +    do {                                        \ +	r = ((int8_t*)src)[2];                  \ +	g = ((int8_t*)src)[1];                  \ +	b = ((int8_t*)src)[0];                  \ +    } while (0) + +#define ZYWRLE_SAVE_COEFF(dst, r, g, b)       \ +    do {                                      \ +	((int8_t*)dst)[2] = (int8_t)r;        \ +	((int8_t*)dst)[1] = (int8_t)g;        \ +	((int8_t*)dst)[0] = (int8_t)b;        \ +    } while (0) + +/* +  RGB <=> YUV conversion stuffs. +  YUV coversion is explained as following formula in strict meaning: +  Y =  0.299R + 0.587G + 0.114B (   0<=Y<=255) +  U = -0.169R - 0.331G + 0.500B (-128<=U<=127) +  V =  0.500R - 0.419G - 0.081B (-128<=V<=127) + +  I use simple conversion RCT(reversible color transform) which is described +  in JPEG-2000 specification. +  Y = (R + 2G + B)/4 (   0<=Y<=255) +  U = B-G (-256<=U<=255) +  V = R-G (-256<=V<=255) +*/ + +/* RCT is N-bit RGB to N-bit Y and N+1-bit UV. +   For make Same N-bit, UV is lossy. +   More exact PLHarr, we reduce to odd range(-127<=x<=127). */ +#define ZYWRLE_RGBYUV_(r, g, b, y, u, v, ymask, uvmask)          \ +    do {                                                         \ +	y = (r + (g << 1) + b) >> 2;                             \ +	u =  b - g;                                              \ +	v =  r - g;                                              \ +	y -= 128;                                                \ +	u >>= 1;                                                 \ +	v >>= 1;                                                 \ +	y &= ymask;                                              \ +	u &= uvmask;                                             \ +	v &= uvmask;                                             \ +	if (y == -128) {                                         \ +            y += (0xFFFFFFFF - ymask + 1);                       \ +        }                                                        \ +	if (u == -128) {                                         \ +            u += (0xFFFFFFFF - uvmask + 1);                      \ +        }                                                        \ +	if (v == -128) {                                         \ +            v += (0xFFFFFFFF - uvmask + 1);                      \ +        }                                                        \ +    } while (0) + + +/* + coefficient packing/unpacking stuffs. + Wavelet transform makes 4 sub coefficient image from 1 original image. + + model with pyramid decomposition: +   +------+------+ +   |      |      | +   |  L   |  Hx  | +   |      |      | +   +------+------+ +   |      |      | +   |  H   |  Hxy | +   |      |      | +   +------+------+ + + So, we must transfer each sub images individually in strict meaning. + But at least ZRLE meaning, following one decompositon image is same as + avobe individual sub image. I use this format. + (Strictly saying, transfer order is reverse(Hxy->Hy->Hx->L) +  for simplified procedure for any wavelet level.) + +   +------+------+ +   |      L      | +   +------+------+ +   |      Hx     | +   +------+------+ +   |      Hy     | +   +------+------+ +   |      Hxy    | +   +------+------+ +*/ +#define ZYWRLE_INC_PTR(data)                         \ +    do {                                             \ +        data++;                                      \ +        if( data - p >= (w + uw) ) {                 \ +            data += scanline-(w + uw);               \ +            p = data;                                \ +        }                                            \ +    } while (0) + +#define ZYWRLE_TRANSFER_COEFF(buf, data, t, w, h, scanline, level, TRANS) \ +    do {                                                                \ +        ph = buf;                                                       \ +        s = 2 << level;                                                 \ +        if (t & 0x01) {                                                 \ +            ph += s >> 1;                                               \ +        }                                                               \ +        if (t & 0x02) {                                                 \ +            ph += (s >> 1) * w;                                         \ +        }                                                               \ +        end = ph + h * w;                                               \ +        while (ph < end) {                                              \ +            line = ph + w;                                              \ +            while (ph < line) {                                         \ +                TRANS                                                   \ +                    ZYWRLE_INC_PTR(data);                               \ +                ph += s;                                                \ +            }                                                           \ +            ph += (s - 1) * w;                                          \ +        }                                                               \ +    } while (0) + +#define ZYWRLE_PACK_COEFF(buf, data, t, width, height, scanline, level)	\ +    ZYWRLE_TRANSFER_COEFF(buf, data, t, width, height, scanline, level, \ +                          ZYWRLE_LOAD_COEFF(ph, r, g, b);               \ +                          ZYWRLE_SAVE_PIXEL(data, r, g, b);) + +#define ZYWRLE_UNPACK_COEFF(buf, data, t, width, height, scanline, level) \ +    ZYWRLE_TRANSFER_COEFF(buf, data, t, width, height, scanline, level, \ +                          ZYWRLE_LOAD_PIXEL(data, r, g, b);             \ +                          ZYWRLE_SAVE_COEFF(ph, r, g, b);) + +#define ZYWRLE_SAVE_UNALIGN(data, TRANS)                     \ +    do {                                                     \ +        top = buf + w * h;                                   \ +        end = buf + (w + uw) * (h + uh);                     \ +        while (top < end) {                                  \ +            TRANS                                            \ +                ZYWRLE_INC_PTR(data);                        \ +                top++;                                       \ +        }                                                    \ +    } while (0) + +#define ZYWRLE_LOAD_UNALIGN(data,TRANS)                                 \ +    do {                                                                \ +        top = buf + w * h;                                              \ +        if (uw) {                                                       \ +            p = data + w;                                               \ +            end = (int*)(p + h * scanline);                             \ +            while (p < (ZRLE_PIXEL*)end) {                              \ +                line = (int*)(p + uw);                                  \ +                while (p < (ZRLE_PIXEL*)line) {                         \ +                    TRANS                                               \ +                        p++;                                            \ +                    top++;                                              \ +                }                                                       \ +                p += scanline - uw;                                     \ +            }                                                           \ +        }                                                               \ +        if (uh) {                                                       \ +            p = data + h * scanline;                                    \ +            end = (int*)(p + uh * scanline);                            \ +            while (p < (ZRLE_PIXEL*)end) {                              \ +                line = (int*)(p + w);                                   \ +                while (p < (ZRLE_PIXEL*)line) {                         \ +                    TRANS                                               \ +                        p++;                                            \ +                    top++;                                              \ +                }                                                       \ +                p += scanline - w;                                      \ +            }                                                           \ +        }                                                               \ +        if (uw && uh) {                                                 \ +            p= data + w + h * scanline;                                 \ +            end = (int*)(p + uh * scanline);                            \ +            while (p < (ZRLE_PIXEL*)end) {                              \ +                line = (int*)(p + uw);                                  \ +                while (p < (ZRLE_PIXEL*)line) {                         \ +                    TRANS                                               \ +                        p++;                                            \ +                    top++;                                              \ +                }                                                       \ +                p += scanline-uw;                                       \ +            }                                                           \ +        }                                                               \ +    } while (0) + +static inline void zywrle_calc_size(int *w, int *h, int level) +{ +    *w &= ~((1 << level) - 1); +    *h &= ~((1 << level) - 1); +} + +#endif diff --git a/ui/vnc-jobs.c b/ui/vnc-jobs.c new file mode 100644 index 00000000..22c9abce --- /dev/null +++ b/ui/vnc-jobs.c @@ -0,0 +1,345 @@ +/* + * QEMU VNC display driver + * + * Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws> + * Copyright (C) 2006 Fabrice Bellard + * Copyright (C) 2009 Red Hat, Inc + * Copyright (C) 2010 Corentin Chary <corentin.chary@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#include "vnc.h" +#include "vnc-jobs.h" +#include "qemu/sockets.h" +#include "block/aio.h" + +/* + * Locking: + * + * There are three levels of locking: + * - jobs queue lock: for each operation on the queue (push, pop, isEmpty?) + * - VncDisplay global lock: mainly used for framebuffer updates to avoid + *                      screen corruption if the framebuffer is updated + *                      while the worker is doing something. + * - VncState::output lock: used to make sure the output buffer is not corrupted + *                          if two threads try to write on it at the same time + * + * While the VNC worker thread is working, the VncDisplay global lock is held + * to avoid screen corruption (this does not block vnc_refresh() because it + * uses trylock()) but the output lock is not held because the thread works on + * its own output buffer. + * When the encoding job is done, the worker thread will hold the output lock + * and copy its output buffer in vs->output. + */ + +struct VncJobQueue { +    QemuCond cond; +    QemuMutex mutex; +    QemuThread thread; +    Buffer buffer; +    bool exit; +    QTAILQ_HEAD(, VncJob) jobs; +}; + +typedef struct VncJobQueue VncJobQueue; + +/* + * We use a single global queue, but most of the functions are + * already reentrant, so we can easily add more than one encoding thread + */ +static VncJobQueue *queue; + +static void vnc_lock_queue(VncJobQueue *queue) +{ +    qemu_mutex_lock(&queue->mutex); +} + +static void vnc_unlock_queue(VncJobQueue *queue) +{ +    qemu_mutex_unlock(&queue->mutex); +} + +VncJob *vnc_job_new(VncState *vs) +{ +    VncJob *job = g_malloc0(sizeof(VncJob)); + +    job->vs = vs; +    vnc_lock_queue(queue); +    QLIST_INIT(&job->rectangles); +    vnc_unlock_queue(queue); +    return job; +} + +int vnc_job_add_rect(VncJob *job, int x, int y, int w, int h) +{ +    VncRectEntry *entry = g_malloc0(sizeof(VncRectEntry)); + +    entry->rect.x = x; +    entry->rect.y = y; +    entry->rect.w = w; +    entry->rect.h = h; + +    vnc_lock_queue(queue); +    QLIST_INSERT_HEAD(&job->rectangles, entry, next); +    vnc_unlock_queue(queue); +    return 1; +} + +void vnc_job_push(VncJob *job) +{ +    vnc_lock_queue(queue); +    if (queue->exit || QLIST_EMPTY(&job->rectangles)) { +        g_free(job); +    } else { +        QTAILQ_INSERT_TAIL(&queue->jobs, job, next); +        qemu_cond_broadcast(&queue->cond); +    } +    vnc_unlock_queue(queue); +} + +static bool vnc_has_job_locked(VncState *vs) +{ +    VncJob *job; + +    QTAILQ_FOREACH(job, &queue->jobs, next) { +        if (job->vs == vs || !vs) { +            return true; +        } +    } +    return false; +} + +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); +    while (vnc_has_job_locked(vs)) { +        qemu_cond_wait(&queue->cond, &queue->mutex); +    } +    vnc_unlock_queue(queue); +    vnc_jobs_consume_buffer(vs); +} + +void vnc_jobs_consume_buffer(VncState *vs) +{ +    bool flush; + +    vnc_lock_output(vs); +    if (vs->jobs_buffer.offset) { +        vnc_write(vs, vs->jobs_buffer.buffer, vs->jobs_buffer.offset); +        buffer_reset(&vs->jobs_buffer); +    } +    flush = vs->csock != -1 && vs->abort != true; +    vnc_unlock_output(vs); + +    if (flush) { +      vnc_flush(vs); +    } +} + +/* + * Copy data for local use + */ +static void vnc_async_encoding_start(VncState *orig, VncState *local) +{ +    local->vnc_encoding = orig->vnc_encoding; +    local->features = orig->features; +    local->vd = orig->vd; +    local->lossy_rect = orig->lossy_rect; +    local->write_pixels = orig->write_pixels; +    local->client_pf = orig->client_pf; +    local->client_be = orig->client_be; +    local->tight = orig->tight; +    local->zlib = orig->zlib; +    local->hextile = orig->hextile; +    local->zrle = orig->zrle; +    local->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) +{ +    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; +    int n_rectangles; +    int saved_offset; + +    vnc_lock_queue(queue); +    while (QTAILQ_EMPTY(&queue->jobs) && !queue->exit) { +        qemu_cond_wait(&queue->cond, &queue->mutex); +    } +    /* Here job can only be NULL if queue->exit is true */ +    job = QTAILQ_FIRST(&queue->jobs); +    vnc_unlock_queue(queue); + +    if (queue->exit) { +        return -1; +    } + +    vnc_lock_output(job->vs); +    if (job->vs->csock == -1 || job->vs->abort == true) { +        vnc_unlock_output(job->vs); +        goto disconnected; +    } +    vnc_unlock_output(job->vs); + +    /* Make a local copy of vs and switch output buffers */ +    vnc_async_encoding_start(job->vs, &vs); + +    /* Start sending rectangles */ +    n_rectangles = 0; +    vnc_write_u8(&vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE); +    vnc_write_u8(&vs, 0); +    saved_offset = vs.output.offset; +    vnc_write_u16(&vs, 0); + +    vnc_lock_display(job->vs->vd); +    QLIST_FOREACH_SAFE(entry, &job->rectangles, next, tmp) { +        int n; + +        if (job->vs->csock == -1) { +            vnc_unlock_display(job->vs->vd); +            /* Copy persistent encoding data */ +            vnc_async_encoding_end(job->vs, &vs); +            goto disconnected; +        } + +        n = vnc_send_framebuffer_update(&vs, entry->rect.x, entry->rect.y, +                                        entry->rect.w, entry->rect.h); + +        if (n >= 0) { +            n_rectangles += n; +        } +        g_free(entry); +    } +    vnc_unlock_display(job->vs->vd); + +    /* Put n_rectangles at the beginning of the message */ +    vs.output.buffer[saved_offset] = (n_rectangles >> 8) & 0xFF; +    vs.output.buffer[saved_offset + 1] = n_rectangles & 0xFF; + +    vnc_lock_output(job->vs); +    if (job->vs->csock != -1) { +        buffer_reserve(&job->vs->jobs_buffer, vs.output.offset); +        buffer_append(&job->vs->jobs_buffer, vs.output.buffer, +                      vs.output.offset); +        /* Copy persistent encoding data */ +        vnc_async_encoding_end(job->vs, &vs); + +	qemu_bh_schedule(job->vs->bh); +    }  else { +        /* Copy persistent encoding data */ +        vnc_async_encoding_end(job->vs, &vs); +    } +    vnc_unlock_output(job->vs); + +disconnected: +    vnc_lock_queue(queue); +    QTAILQ_REMOVE(&queue->jobs, job, next); +    vnc_unlock_queue(queue); +    qemu_cond_broadcast(&queue->cond); +    g_free(job); +    return 0; +} + +static VncJobQueue *vnc_queue_init(void) +{ +    VncJobQueue *queue = g_malloc0(sizeof(VncJobQueue)); + +    qemu_cond_init(&queue->cond); +    qemu_mutex_init(&queue->mutex); +    QTAILQ_INIT(&queue->jobs); +    return queue; +} + +static void vnc_queue_clear(VncJobQueue *q) +{ +    qemu_cond_destroy(&queue->cond); +    qemu_mutex_destroy(&queue->mutex); +    buffer_free(&queue->buffer); +    g_free(q); +    queue = NULL; /* Unset global queue */ +} + +static void *vnc_worker_thread(void *arg) +{ +    VncJobQueue *queue = arg; + +    qemu_thread_get_self(&queue->thread); + +    while (!vnc_worker_thread_loop(queue)) ; +    vnc_queue_clear(queue); +    return NULL; +} + +static bool vnc_worker_thread_running(void) +{ +    return queue; /* Check global queue */ +} + +void vnc_start_worker_thread(void) +{ +    VncJobQueue *q; + +    if (vnc_worker_thread_running()) +        return ; + +    q = vnc_queue_init(); +    qemu_thread_create(&q->thread, "vnc_worker", vnc_worker_thread, q, +                       QEMU_THREAD_DETACHED); +    queue = q; /* Set global queue */ +} diff --git a/ui/vnc-jobs.h b/ui/vnc-jobs.h new file mode 100644 index 00000000..044bf9fb --- /dev/null +++ b/ui/vnc-jobs.h @@ -0,0 +1,70 @@ +/* + * QEMU VNC display driver + * + * From libvncserver/rfb/rfbproto.h + * Copyright (C) 2005 Rohit Kumar, Johannes E. Schindelin + * Copyright (C) 2000-2002 Constantin Kaplinsky.  All Rights Reserved. + * Copyright (C) 2000 Tridia Corporation.  All Rights Reserved. + * Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved. + * + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef VNC_JOBS_H +#define VNC_JOBS_H + +/* Jobs */ +VncJob *vnc_job_new(VncState *vs); +int vnc_job_add_rect(VncJob *job, int x, int y, int w, int h); +void vnc_job_push(VncJob *job); +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); + +/* Locks */ +static inline int vnc_trylock_display(VncDisplay *vd) +{ +    return qemu_mutex_trylock(&vd->mutex); +} + +static inline void vnc_lock_display(VncDisplay *vd) +{ +    qemu_mutex_lock(&vd->mutex); +} + +static inline void vnc_unlock_display(VncDisplay *vd) +{ +    qemu_mutex_unlock(&vd->mutex); +} + +static inline void vnc_lock_output(VncState *vs) +{ +    qemu_mutex_lock(&vs->output_mutex); +} + +static inline void vnc_unlock_output(VncState *vs) +{ +    qemu_mutex_unlock(&vs->output_mutex); +} + +#endif /* VNC_JOBS_H */ diff --git a/ui/vnc-palette.c b/ui/vnc-palette.c new file mode 100644 index 00000000..c130deee --- /dev/null +++ b/ui/vnc-palette.c @@ -0,0 +1,160 @@ +/* + * QEMU VNC display driver: palette hash table + * + * From libvncserver/libvncserver/tight.c + * Copyright (C) 2000, 2001 Const Kaplinsky.  All Rights Reserved. + * Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved. + * + * Copyright (C) 2010 Corentin Chary <corentin.chary@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "vnc-palette.h" +#include <glib.h> +#include <string.h> + +static VncPaletteEntry *palette_find(const VncPalette *palette, +                                     uint32_t color, unsigned int hash) +{ +    VncPaletteEntry *entry; + +    QLIST_FOREACH(entry, &palette->table[hash], next) { +        if (entry->color == color) { +            return entry; +        } +    } + +    return NULL; +} + +static unsigned int palette_hash(uint32_t rgb, int bpp) +{ +    if (bpp == 16) { +        return ((unsigned int)(((rgb >> 8) + rgb) & 0xFF)); +    } else { +        return ((unsigned int)(((rgb >> 16) + (rgb >> 8)) & 0xFF)); +    } +} + +VncPalette *palette_new(size_t max, int bpp) +{ +    VncPalette *palette; + +    palette = g_malloc0(sizeof(*palette)); +    palette_init(palette, max, bpp); +    return palette; +} + +void palette_init(VncPalette *palette, size_t max, int bpp) +{ +    memset(palette, 0, sizeof (*palette)); +    palette->max = max; +    palette->bpp = bpp; +} + +void palette_destroy(VncPalette *palette) +{ +    g_free(palette); +} + +int palette_put(VncPalette *palette, uint32_t color) +{ +    unsigned int hash; +    unsigned int idx = palette->size; +    VncPaletteEntry *entry; + +    hash = palette_hash(color, palette->bpp) % VNC_PALETTE_HASH_SIZE; +    entry = palette_find(palette, color, hash); + +    if (!entry && palette->size >= palette->max) { +        return 0; +    } +    if (!entry) { +        VncPaletteEntry *entry; + +        entry = &palette->pool[palette->size]; +        entry->color = color; +        entry->idx = idx; +        QLIST_INSERT_HEAD(&palette->table[hash], entry, next); +        palette->size++; +    } +    return palette->size; +} + +int palette_idx(const VncPalette *palette, uint32_t color) +{ +    VncPaletteEntry *entry; +    unsigned int hash; + +    hash = palette_hash(color, palette->bpp) % VNC_PALETTE_HASH_SIZE; +    entry = palette_find(palette, color, hash); +    return (entry == NULL ? -1 : entry->idx); +} + +size_t palette_size(const VncPalette *palette) +{ +    return palette->size; +} + +void palette_iter(const VncPalette *palette, +                  void (*iter)(int idx, uint32_t color, void *opaque), +                  void *opaque) +{ +    int i; +    VncPaletteEntry *entry; + +    for (i = 0; i < VNC_PALETTE_HASH_SIZE; i++) { +        QLIST_FOREACH(entry, &palette->table[i], next) { +            iter(entry->idx, entry->color, opaque); +        } +    } +} + +uint32_t palette_color(const VncPalette *palette, int idx, bool *found) +{ +    int i; +    VncPaletteEntry *entry; + +    for (i = 0; i < VNC_PALETTE_HASH_SIZE; i++) { +        QLIST_FOREACH(entry, &palette->table[i], next) { +            if (entry->idx == idx) { +                *found = true; +                return entry->color; +            } +        } +    } + +    *found = false; +    return -1; +} + +static void palette_fill_cb(int idx, uint32_t color, void *opaque) +{ +    uint32_t *colors = opaque; + +    colors[idx] = color; +} + +size_t palette_fill(const VncPalette *palette, +                    uint32_t colors[VNC_PALETTE_MAX_SIZE]) +{ +    palette_iter(palette, palette_fill_cb, colors); +    return palette_size(palette); +} diff --git a/ui/vnc-palette.h b/ui/vnc-palette.h new file mode 100644 index 00000000..d02f0236 --- /dev/null +++ b/ui/vnc-palette.h @@ -0,0 +1,69 @@ +/* + * QEMU VNC display driver: palette hash table + * + * From libvncserver/libvncserver/tight.c + * Copyright (C) 2000, 2001 Const Kaplinsky.  All Rights Reserved. + * Copyright (C) 1999 AT&T Laboratories Cambridge.  All Rights Reserved. + * + * Copyright (C) 2010 Corentin Chary <corentin.chary@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef VNC_PALETTE_H +#define VNC_PALETTE_H + +#include "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 + +typedef struct VncPaletteEntry { +    int idx; +    uint32_t color; +    QLIST_ENTRY(VncPaletteEntry) next; +} VncPaletteEntry; + +typedef struct VncPalette { +    VncPaletteEntry pool[VNC_PALETTE_MAX_SIZE]; +    size_t size; +    size_t max; +    int bpp; +    QLIST_HEAD(,VncPaletteEntry) table[VNC_PALETTE_HASH_SIZE]; +} VncPalette; + +VncPalette *palette_new(size_t max, int bpp); +void palette_init(VncPalette *palette, size_t max, int bpp); +void palette_destroy(VncPalette *palette); + +int palette_put(VncPalette *palette, uint32_t color); +int palette_idx(const VncPalette *palette, uint32_t color); +size_t palette_size(const VncPalette *palette); + +void palette_iter(const VncPalette *palette, +                  void (*iter)(int idx, uint32_t color, void *opaque), +                  void *opaque); +uint32_t palette_color(const VncPalette *palette, int idx, bool *found); +size_t palette_fill(const VncPalette *palette, +                    uint32_t colors[VNC_PALETTE_MAX_SIZE]); + +#endif /* VNC_PALETTE_H */ diff --git a/ui/vnc-tls.c b/ui/vnc-tls.c new file mode 100644 index 00000000..028fc4db --- /dev/null +++ b/ui/vnc-tls.c @@ -0,0 +1,474 @@ +/* + * 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) { +    VncState *vs = (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) { +    VncState *vs = (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(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(VncState *vs, +                         int needX509Creds) { +    VNC_DEBUG("Do TLS setup\n"); +    if (vnc_tls_initialize() < 0) { +        VNC_DEBUG("Failed to init TLS\n"); +        vnc_client_error(vs); +        return -1; +    } +    if (vs->tls.session == NULL) { +        if (gnutls_init(&vs->tls.session, GNUTLS_SERVER) < 0) { +            vnc_client_error(vs); +            return -1; +        } + +        if (gnutls_set_default_priority(vs->tls.session) < 0) { +            gnutls_deinit(vs->tls.session); +            vs->tls.session = NULL; +            vnc_client_error(vs); +            return -1; +        } + +        if (vnc_set_gnutls_priority(vs->tls.session, needX509Creds) < 0) { +            gnutls_deinit(vs->tls.session); +            vs->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(vs->tls.session); +                vs->tls.session = NULL; +                vnc_client_error(vs); +                return -1; +            } +            if (gnutls_credentials_set(vs->tls.session, +                                       GNUTLS_CRD_CERTIFICATE, x509_cred) < 0) { +                gnutls_deinit(vs->tls.session); +                vs->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(vs->tls.session, +                                                      GNUTLS_CERT_REQUEST); +            } + +        } else { +            gnutls_anon_server_credentials_t anon_cred = +                vnc_tls_initialize_anon_cred(); +            if (!anon_cred) { +                gnutls_deinit(vs->tls.session); +                vs->tls.session = NULL; +                vnc_client_error(vs); +                return -1; +            } +            if (gnutls_credentials_set(vs->tls.session, +                                       GNUTLS_CRD_ANON, anon_cred) < 0) { +                gnutls_deinit(vs->tls.session); +                vs->tls.session = NULL; +                gnutls_anon_free_server_credentials(anon_cred); +                vnc_client_error(vs); +                return -1; +            } +        } + +        gnutls_transport_set_ptr(vs->tls.session, (gnutls_transport_ptr_t)vs); +        gnutls_transport_set_push_function(vs->tls.session, vnc_tls_push); +        gnutls_transport_set_pull_function(vs->tls.session, vnc_tls_pull); +    } +    return 0; +} + + +void vnc_tls_client_cleanup(VncState *vs) +{ +    if (vs->tls.session) { +        gnutls_deinit(vs->tls.session); +        vs->tls.session = NULL; +    } +    g_free(vs->tls.dname); +} + + + +static int vnc_set_x509_credential(VncDisplay *vd, +                                   const char *certdir, +                                   const char *filename, +                                   char **cred, +                                   int ignoreMissing) +{ +    struct stat sb; + +    g_free(*cred); +    *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 new file mode 100644 index 00000000..f9829c78 --- /dev/null +++ b/ui/vnc-tls.h @@ -0,0 +1,69 @@ +/* + * 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" + +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 { +    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 new file mode 100644 index 00000000..b4cb6bde --- /dev/null +++ b/ui/vnc-ws.c @@ -0,0 +1,381 @@ +/* + * QEMU VNC display driver: Websockets support + * + * Copyright (C) 2010 Joel Martin + * Copyright (C) 2012 Tim Hardeck + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "vnc.h" +#include "qemu/main-loop.h" +#include "crypto/hash.h" + +#ifdef CONFIG_VNC_TLS +#include "qemu/sockets.h" + +static int vncws_start_tls_handshake(VncState *vs) +{ +    int ret = gnutls_handshake(vs->tls.session); + +    if (ret < 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, 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)); +        vnc_client_error(vs); +        return -1; +    } + +    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"); +        } +    } + +    VNC_DEBUG("Handshake done, switching to TLS data mode\n"); +    qemu_set_fd_handler(vs->csock, vncws_handshake_read, NULL, vs); + +    return 0; +} + +void vncws_tls_handshake_io(void *opaque) +{ +    VncState *vs = (VncState *)opaque; + +    if (!vs->tls.session) { +        VNC_DEBUG("TLS Websocket setup\n"); +        if (vnc_tls_client_setup(vs, vs->vd->tls.x509cert != NULL) < 0) { +            return; +        } +    } +    VNC_DEBUG("Handshake IO continue\n"); +    vncws_start_tls_handshake(vs); +} +#endif /* CONFIG_VNC_TLS */ + +void vncws_handshake_read(void *opaque) +{ +    VncState *vs = opaque; +    uint8_t *handshake_end; +    long ret; +    /* Typical HTTP headers from novnc are 512 bytes, so limiting +     * total header size to 4096 is easily enough. */ +    size_t want = 4096 - vs->ws_input.offset; +    buffer_reserve(&vs->ws_input, want); +    ret = vnc_client_read_buf(vs, buffer_end(&vs->ws_input), want); + +    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_handler(vs->csock, 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)); +    } else if (vs->ws_input.offset >= 4096) { +        VNC_DEBUG("End of headers not found in first 4096 bytes\n"); +        vnc_client_error(vs); +    } +} + + +long vnc_client_read_ws(VncState *vs) +{ +    int ret, err; +    uint8_t *payload; +    size_t payload_size, header_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; +    } +    vs->ws_input.offset += ret; + +    ret = 0; +    /* consume as much of ws_input buffer as possible */ +    do { +        if (vs->ws_payload_remain == 0) { +            err = vncws_decode_frame_header(&vs->ws_input, +                                            &header_size, +                                            &vs->ws_payload_remain, +                                            &vs->ws_payload_mask); +            if (err <= 0) { +                return err; +            } + +            buffer_advance(&vs->ws_input, header_size); +        } +        if (vs->ws_payload_remain != 0) { +            err = vncws_decode_frame_payload(&vs->ws_input, +                                             &vs->ws_payload_remain, +                                             &vs->ws_payload_mask, +                                             &payload, +                                             &payload_size); +            if (err < 0) { +                return err; +            } +            if (err == 0) { +                return ret; +            } +            ret += err; + +            buffer_reserve(&vs->input, payload_size); +            buffer_append(&vs->input, payload, payload_size); + +            buffer_advance(&vs->ws_input, payload_size); +        } +    } while (vs->ws_input.offset > 0); + +    return ret; +} + +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_handler(vs->csock, 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) +{ +    char combined_key[WS_CLIENT_KEY_LEN + WS_GUID_LEN + 1]; +    char *accept = NULL, *response = NULL; +    Error *err = NULL; + +    g_strlcpy(combined_key, key, WS_CLIENT_KEY_LEN + 1); +    g_strlcat(combined_key, WS_GUID, WS_CLIENT_KEY_LEN + WS_GUID_LEN + 1); + +    /* hash and encode it */ +    if (qcrypto_hash_base64(QCRYPTO_HASH_ALG_SHA1, +                            combined_key, +                            WS_CLIENT_KEY_LEN + WS_GUID_LEN, +                            &accept, +                            &err) < 0) { +        VNC_DEBUG("Hashing Websocket combined key failed %s\n", +                  error_get_pretty(err)); +        error_free(err); +        vnc_client_error(vs); +        return; +    } + +    response = g_strdup_printf(WS_HANDSHAKE, accept); +    vnc_client_write_buf(vs, (const uint8_t *)response, strlen(response)); + +    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); +    } else { +        VNC_DEBUG("Defective Websockets header or unsupported protocol\n"); +        vnc_client_error(vs); +    } + +    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_header(Buffer *input, +                              size_t *header_size, +                              size_t *payload_remain, +                              WsMask *payload_mask) +{ +    unsigned char opcode = 0, fin = 0, has_mask = 0; +    size_t payload_len; +    WsHeader *header = (WsHeader *)input->buffer; + +    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_len = header->b1 & 0x7f; + +    if (opcode == WS_OPCODE_CLOSE) { +        /* disconnect */ +        return -1; +    } + +    /* 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 (payload_len < 126) { +        *payload_remain = payload_len; +        *header_size = 6; +        *payload_mask = header->u.m; +    } else if (payload_len == 126 && input->offset >= 8) { +        *payload_remain = be16_to_cpu(header->u.s16.l16); +        *header_size = 8; +        *payload_mask = header->u.s16.m16; +    } else if (payload_len == 127 && input->offset >= 14) { +        *payload_remain = be64_to_cpu(header->u.s64.l64); +        *header_size = 14; +        *payload_mask = header->u.s64.m64; +    } else { +        /* header not complete */ +        return 0; +    } + +    return 1; +} + +int vncws_decode_frame_payload(Buffer *input, +                               size_t *payload_remain, WsMask *payload_mask, +                               uint8_t **payload, size_t *payload_size) +{ +    size_t i; +    uint32_t *payload32; + +    *payload = input->buffer; +    /* If we aren't at the end of the payload, then drop +     * off the last bytes, so we're always multiple of 4 +     * for purpose of unmasking, except at end of payload +     */ +    if (input->offset < *payload_remain) { +        *payload_size = input->offset - (input->offset % 4); +    } else { +        *payload_size = *payload_remain; +    } +    if (*payload_size == 0) { +        return 0; +    } +    *payload_remain -= *payload_size; + +    /* unmask frame */ +    /* process 1 frame (32 bit op) */ +    payload32 = (uint32_t *)(*payload); +    for (i = 0; i < *payload_size / 4; i++) { +        payload32[i] ^= payload_mask->u; +    } +    /* process the remaining bytes (if any) */ +    for (i *= 4; i < *payload_size; i++) { +        (*payload)[i] ^= payload_mask->c[i % 4]; +    } + +    return 1; +} diff --git a/ui/vnc-ws.h b/ui/vnc-ws.h new file mode 100644 index 00000000..94942258 --- /dev/null +++ b/ui/vnc-ws.h @@ -0,0 +1,92 @@ +/* + * QEMU VNC display driver: Websockets support + * + * Copyright (C) 2010 Joel Martin + * Copyright (C) 2012 Tim Hardeck + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __QEMU_UI_VNC_WS_H +#define __QEMU_UI_VNC_WS_H + +#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_io(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_header(Buffer *input, +                              size_t *header_size, +                              size_t *payload_remain, +                              WsMask *payload_mask); +int vncws_decode_frame_payload(Buffer *input, +                               size_t *payload_remain, WsMask *payload_mask, +                               uint8_t **payload, size_t *payload_size); + +#endif /* __QEMU_UI_VNC_WS_H */ diff --git a/ui/vnc.c b/ui/vnc.c new file mode 100644 index 00000000..52c68095 --- /dev/null +++ b/ui/vnc.c @@ -0,0 +1,3790 @@ +/* + * QEMU VNC display driver + * + * Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws> + * Copyright (C) 2006 Fabrice Bellard + * Copyright (C) 2009 Red Hat, Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "vnc.h" +#include "vnc-jobs.h" +#include "trace.h" +#include "hw/qdev.h" +#include "sysemu/sysemu.h" +#include "qemu/error-report.h" +#include "qemu/sockets.h" +#include "qemu/timer.h" +#include "qemu/acl.h" +#include "qemu/config-file.h" +#include "qapi/qmp/qerror.h" +#include "qapi/qmp/types.h" +#include "qmp-commands.h" +#include "qemu/osdep.h" +#include "ui/input.h" +#include "qapi-event.h" +#include "crypto/hash.h" + +#define VNC_REFRESH_INTERVAL_BASE GUI_REFRESH_INTERVAL_DEFAULT +#define VNC_REFRESH_INTERVAL_INC  50 +#define VNC_REFRESH_INTERVAL_MAX  GUI_REFRESH_INTERVAL_IDLE +static const struct timeval VNC_REFRESH_STATS = { 0, 500000 }; +static const struct timeval VNC_REFRESH_LOSSY = { 2, 0 }; + +#include "vnc_keysym.h" +#include "crypto/cipher.h" + +static QTAILQ_HEAD(, VncDisplay) vnc_displays = +    QTAILQ_HEAD_INITIALIZER(vnc_displays); + +static int vnc_cursor_define(VncState *vs); +static void vnc_release_modifiers(VncState *vs); + +static void vnc_set_share_mode(VncState *vs, VncShareMode mode) +{ +#ifdef _VNC_DEBUG +    static const char *mn[] = { +        [0]                           = "undefined", +        [VNC_SHARE_MODE_CONNECTING]   = "connecting", +        [VNC_SHARE_MODE_SHARED]       = "shared", +        [VNC_SHARE_MODE_EXCLUSIVE]    = "exclusive", +        [VNC_SHARE_MODE_DISCONNECTED] = "disconnected", +    }; +    fprintf(stderr, "%s/%d: %s -> %s\n", __func__, +            vs->csock, mn[vs->share_mode], mn[mode]); +#endif + +    switch (vs->share_mode) { +    case VNC_SHARE_MODE_CONNECTING: +        vs->vd->num_connecting--; +        break; +    case VNC_SHARE_MODE_SHARED: +        vs->vd->num_shared--; +        break; +    case VNC_SHARE_MODE_EXCLUSIVE: +        vs->vd->num_exclusive--; +        break; +    default: +        break; +    } + +    vs->share_mode = mode; + +    switch (vs->share_mode) { +    case VNC_SHARE_MODE_CONNECTING: +        vs->vd->num_connecting++; +        break; +    case VNC_SHARE_MODE_SHARED: +        vs->vd->num_shared++; +        break; +    case VNC_SHARE_MODE_EXCLUSIVE: +        vs->vd->num_exclusive++; +        break; +    default: +        break; +    } +} + +static 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; + +    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; +    } + +    /* 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); +} + +char *vnc_socket_remote_addr(const char *format, int fd) { +    struct sockaddr_storage sa; +    socklen_t salen; + +    salen = sizeof(sa); +    if (getpeername(fd, (struct sockaddr*)&sa, &salen) < 0) +        return NULL; + +    return addr_to_string(format, &sa, salen); +} + +static VncBasicInfo *vnc_basic_info_get(struct sockaddr_storage *sa, +                                        socklen_t salen) +{ +    VncBasicInfo *info; +    char host[NI_MAXHOST]; +    char serv[NI_MAXSERV]; +    int err; + +    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; +    } + +    info = g_malloc0(sizeof(VncBasicInfo)); +    info->host = g_strdup(host); +    info->service = g_strdup(serv); +    info->family = inet_netfamily(sa->ss_family); +    return info; +} + +static VncBasicInfo *vnc_basic_info_get_from_server_addr(int fd) +{ +    struct sockaddr_storage sa; +    socklen_t salen; + +    salen = sizeof(sa); +    if (getsockname(fd, (struct sockaddr*)&sa, &salen) < 0) { +        return NULL; +    } + +    return vnc_basic_info_get(&sa, salen); +} + +static VncBasicInfo *vnc_basic_info_get_from_remote_addr(int fd) +{ +    struct sockaddr_storage sa; +    socklen_t salen; + +    salen = sizeof(sa); +    if (getpeername(fd, (struct sockaddr*)&sa, &salen) < 0) { +        return NULL; +    } + +    return vnc_basic_info_get(&sa, salen); +} + +static const char *vnc_auth_name(VncDisplay *vd) { +    switch (vd->auth) { +    case VNC_AUTH_INVALID: +        return "invalid"; +    case VNC_AUTH_NONE: +        return "none"; +    case VNC_AUTH_VNC: +        return "vnc"; +    case VNC_AUTH_RA2: +        return "ra2"; +    case VNC_AUTH_RA2NE: +        return "ra2ne"; +    case VNC_AUTH_TIGHT: +        return "tight"; +    case VNC_AUTH_ULTRA: +        return "ultra"; +    case VNC_AUTH_TLS: +        return "tls"; +    case VNC_AUTH_VENCRYPT: +#ifdef CONFIG_VNC_TLS +        switch (vd->subauth) { +        case VNC_AUTH_VENCRYPT_PLAIN: +            return "vencrypt+plain"; +        case VNC_AUTH_VENCRYPT_TLSNONE: +            return "vencrypt+tls+none"; +        case VNC_AUTH_VENCRYPT_TLSVNC: +            return "vencrypt+tls+vnc"; +        case VNC_AUTH_VENCRYPT_TLSPLAIN: +            return "vencrypt+tls+plain"; +        case VNC_AUTH_VENCRYPT_X509NONE: +            return "vencrypt+x509+none"; +        case VNC_AUTH_VENCRYPT_X509VNC: +            return "vencrypt+x509+vnc"; +        case VNC_AUTH_VENCRYPT_X509PLAIN: +            return "vencrypt+x509+plain"; +        case VNC_AUTH_VENCRYPT_TLSSASL: +            return "vencrypt+tls+sasl"; +        case VNC_AUTH_VENCRYPT_X509SASL: +            return "vencrypt+x509+sasl"; +        default: +            return "vencrypt"; +        } +#else +        return "vencrypt"; +#endif +    case VNC_AUTH_SASL: +        return "sasl"; +    } +    return "unknown"; +} + +static VncServerInfo *vnc_server_info_get(VncDisplay *vd) +{ +    VncServerInfo *info; +    VncBasicInfo *bi = vnc_basic_info_get_from_server_addr(vd->lsock); +    if (!bi) { +        return NULL; +    } + +    info = g_malloc(sizeof(*info)); +    info->base = bi; +    info->has_auth = true; +    info->auth = g_strdup(vnc_auth_name(vd)); +    return info; +} + +static void vnc_client_cache_auth(VncState *client) +{ +    if (!client->info) { +        return; +    } + +#ifdef CONFIG_VNC_TLS +    if (client->tls.session && +        client->tls.dname) { +        client->info->has_x509_dname = true; +        client->info->x509_dname = g_strdup(client->tls.dname); +    } +#endif +#ifdef CONFIG_VNC_SASL +    if (client->sasl.conn && +        client->sasl.username) { +        client->info->has_sasl_username = true; +        client->info->sasl_username = g_strdup(client->sasl.username); +    } +#endif +} + +static void vnc_client_cache_addr(VncState *client) +{ +    VncBasicInfo *bi = vnc_basic_info_get_from_remote_addr(client->csock); + +    if (bi) { +        client->info = g_malloc0(sizeof(*client->info)); +        client->info->base = bi; +    } +} + +static void vnc_qmp_event(VncState *vs, QAPIEvent event) +{ +    VncServerInfo *si; + +    if (!vs->info) { +        return; +    } +    g_assert(vs->info->base); + +    si = vnc_server_info_get(vs->vd); +    if (!si) { +        return; +    } + +    switch (event) { +    case QAPI_EVENT_VNC_CONNECTED: +        qapi_event_send_vnc_connected(si, vs->info->base, &error_abort); +        break; +    case QAPI_EVENT_VNC_INITIALIZED: +        qapi_event_send_vnc_initialized(si, vs->info, &error_abort); +        break; +    case QAPI_EVENT_VNC_DISCONNECTED: +        qapi_event_send_vnc_disconnected(si, vs->info, &error_abort); +        break; +    default: +        break; +    } + +    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; + +    if (getpeername(client->csock, (struct sockaddr *)&sa, &salen) < 0) { +        return NULL; +    } + +    if (getnameinfo((struct sockaddr *)&sa, salen, +                    host, sizeof(host), +                    serv, sizeof(serv), +                    NI_NUMERICHOST | NI_NUMERICSERV) < 0) { +        return NULL; +    } + +    info = g_malloc0(sizeof(*info)); +    info->base = g_malloc0(sizeof(*info->base)); +    info->base->host = g_strdup(host); +    info->base->service = g_strdup(serv); +    info->base->family = inet_netfamily(sa.ss_family); +    info->base->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); +    } +#endif +#ifdef CONFIG_VNC_SASL +    if (client->sasl.conn && client->sasl.username) { +        info->has_sasl_username = true; +        info->sasl_username = g_strdup(client->sasl.username); +    } +#endif + +    return info; +} + +static VncDisplay *vnc_display_find(const char *id) +{ +    VncDisplay *vd; + +    if (id == NULL) { +        return QTAILQ_FIRST(&vnc_displays); +    } +    QTAILQ_FOREACH(vd, &vnc_displays, next) { +        if (strcmp(id, vd->id) == 0) { +            return vd; +        } +    } +    return NULL; +} + +static VncClientInfoList *qmp_query_client_list(VncDisplay *vd) +{ +    VncClientInfoList *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); + +    if (vd == NULL || !vd->enabled) { +        info->enabled = false; +    } else { +        struct sockaddr_storage sa; +        socklen_t salen = sizeof(sa); +        char host[NI_MAXHOST]; +        char serv[NI_MAXSERV]; + +        info->enabled = true; + +        /* for compatibility with the original command */ +        info->has_clients = true; +        info->clients = qmp_query_client_list(vd); + +        if (vd->lsock == -1) { +            return info; +        } + +        if (getsockname(vd->lsock, (struct sockaddr *)&sa, +                        &salen) == -1) { +            error_setg(errp, QERR_UNDEFINED_ERROR); +            goto out_error; +        } + +        if (getnameinfo((struct sockaddr *)&sa, salen, +                        host, sizeof(host), +                        serv, sizeof(serv), +                        NI_NUMERICHOST | NI_NUMERICSERV) < 0) { +            error_setg(errp, QERR_UNDEFINED_ERROR); +            goto out_error; +        } + +        info->has_host = true; +        info->host = g_strdup(host); + +        info->has_service = true; +        info->service = g_strdup(serv); + +        info->has_family = true; +        info->family = inet_netfamily(sa.ss_family); + +        info->has_auth = true; +        info->auth = g_strdup(vnc_auth_name(vd)); +    } + +    return info; + +out_error: +    qapi_free_VncInfo(info); +    return NULL; +} + +static VncBasicInfoList *qmp_query_server_entry(int socket, +                                                bool websocket, +                                                VncBasicInfoList *prev) +{ +    VncBasicInfoList *list; +    VncBasicInfo *info; +    struct sockaddr_storage sa; +    socklen_t salen = sizeof(sa); +    char host[NI_MAXHOST]; +    char serv[NI_MAXSERV]; + +    if (getsockname(socket, (struct sockaddr *)&sa, &salen) < 0 || +        getnameinfo((struct sockaddr *)&sa, salen, +                    host, sizeof(host), serv, sizeof(serv), +                    NI_NUMERICHOST | NI_NUMERICSERV) < 0) { +        return prev; +    } + +    info = g_new0(VncBasicInfo, 1); +    info->host = g_strdup(host); +    info->service = g_strdup(serv); +    info->family = inet_netfamily(sa.ss_family); +    info->websocket = websocket; + +    list = g_new0(VncBasicInfoList, 1); +    list->value = info; +    list->next = prev; +    return list; +} + +static void qmp_query_auth(VncDisplay *vd, VncInfo2 *info) +{ +    switch (vd->auth) { +    case VNC_AUTH_VNC: +        info->auth = VNC_PRIMARY_AUTH_VNC; +        break; +    case VNC_AUTH_RA2: +        info->auth = VNC_PRIMARY_AUTH_RA2; +        break; +    case VNC_AUTH_RA2NE: +        info->auth = VNC_PRIMARY_AUTH_RA2NE; +        break; +    case VNC_AUTH_TIGHT: +        info->auth = VNC_PRIMARY_AUTH_TIGHT; +        break; +    case VNC_AUTH_ULTRA: +        info->auth = VNC_PRIMARY_AUTH_ULTRA; +        break; +    case VNC_AUTH_TLS: +        info->auth = VNC_PRIMARY_AUTH_TLS; +        break; +    case VNC_AUTH_VENCRYPT: +        info->auth = VNC_PRIMARY_AUTH_VENCRYPT; +#ifdef CONFIG_VNC_TLS +        info->has_vencrypt = true; +        switch (vd->subauth) { +        case VNC_AUTH_VENCRYPT_PLAIN: +            info->vencrypt = VNC_VENCRYPT_SUB_AUTH_PLAIN; +            break; +        case VNC_AUTH_VENCRYPT_TLSNONE: +            info->vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_NONE; +            break; +        case VNC_AUTH_VENCRYPT_TLSVNC: +            info->vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_VNC; +            break; +        case VNC_AUTH_VENCRYPT_TLSPLAIN: +            info->vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_PLAIN; +            break; +        case VNC_AUTH_VENCRYPT_X509NONE: +            info->vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_NONE; +            break; +        case VNC_AUTH_VENCRYPT_X509VNC: +            info->vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_VNC; +            break; +        case VNC_AUTH_VENCRYPT_X509PLAIN: +            info->vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_PLAIN; +            break; +        case VNC_AUTH_VENCRYPT_TLSSASL: +            info->vencrypt = VNC_VENCRYPT_SUB_AUTH_TLS_SASL; +            break; +        case VNC_AUTH_VENCRYPT_X509SASL: +            info->vencrypt = VNC_VENCRYPT_SUB_AUTH_X509_SASL; +            break; +        default: +            info->has_vencrypt = false; +            break; +        } +#endif +        break; +    case VNC_AUTH_SASL: +        info->auth = VNC_PRIMARY_AUTH_SASL; +        break; +    case VNC_AUTH_NONE: +    default: +        info->auth = VNC_PRIMARY_AUTH_NONE; +        break; +    } +} + +VncInfo2List *qmp_query_vnc_servers(Error **errp) +{ +    VncInfo2List *item, *prev = NULL; +    VncInfo2 *info; +    VncDisplay *vd; +    DeviceState *dev; + +    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, info); +        if (vd->dcl.con) { +            dev = DEVICE(object_property_get_link(OBJECT(vd->dcl.con), +                                                  "device", NULL)); +            info->has_display = true; +            info->display = g_strdup(dev->id); +        } +        if (vd->lsock != -1) { +            info->server = qmp_query_server_entry(vd->lsock, false, +                                                  info->server); +        } +        if (vd->lwebsock != -1) { +            info->server = qmp_query_server_entry(vd->lwebsock, true, +                                                  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 +      and not totally invalidated +   3) resolutions > 1024 +*/ + +static int vnc_update_client(VncState *vs, int has_dirty, bool sync); +static void vnc_disconnect_start(VncState *vs); + +static void vnc_colordepth(VncState *vs); +static void framebuffer_update_request(VncState *vs, int incremental, +                                       int x_position, int y_position, +                                       int w, int h); +static void vnc_refresh(DisplayChangeListener *dcl); +static int vnc_refresh_server_surface(VncDisplay *vd); + +static void vnc_set_area_dirty(DECLARE_BITMAP(dirty[VNC_MAX_HEIGHT], +                               VNC_MAX_WIDTH / VNC_DIRTY_PIXELS_PER_BIT), +                               int width, int height, +                               int x, int y, int w, int h) { +    /* this is needed this to ensure we updated all affected +     * blocks if x % VNC_DIRTY_PIXELS_PER_BIT != 0 */ +    w += (x % VNC_DIRTY_PIXELS_PER_BIT); +    x -= (x % VNC_DIRTY_PIXELS_PER_BIT); + +    x = MIN(x, width); +    y = MIN(y, height); +    w = MIN(x + w, width) - x; +    h = MIN(y + h, height); + +    for (; y < h; y++) { +        bitmap_set(dirty[y], x / VNC_DIRTY_PIXELS_PER_BIT, +                   DIV_ROUND_UP(w, VNC_DIRTY_PIXELS_PER_BIT)); +    } +} + +static void vnc_dpy_update(DisplayChangeListener *dcl, +                           int x, int y, int w, int h) +{ +    VncDisplay *vd = container_of(dcl, VncDisplay, dcl); +    struct VncSurface *s = &vd->guest; +    int width = pixman_image_get_width(vd->server); +    int height = pixman_image_get_height(vd->server); + +    vnc_set_area_dirty(s->dirty, width, height, x, y, w, h); +} + +void vnc_framebuffer_update(VncState *vs, int x, int y, int w, int h, +                            int32_t encoding) +{ +    vnc_write_u16(vs, x); +    vnc_write_u16(vs, y); +    vnc_write_u16(vs, w); +    vnc_write_u16(vs, h); + +    vnc_write_s32(vs, encoding); +} + +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); +    } +} + +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) +{ +    if (vs->csock == -1 || !vnc_has_feature(vs, VNC_FEATURE_RESIZE)) { +        return; +    } +    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 = 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); +    vnc_write_u16(vs, 1); /* number of rects */ +    vnc_framebuffer_update(vs, 0, 0, vs->client_width, vs->client_height, +                           VNC_ENCODING_DESKTOPRESIZE); +    vnc_unlock_output(vs); +    vnc_flush(vs); +} + +static void vnc_abort_display_jobs(VncDisplay *vd) +{ +    VncState *vs; + +    QTAILQ_FOREACH(vs, &vd->clients, next) { +        vnc_lock_output(vs); +        vs->abort = true; +        vnc_unlock_output(vs); +    } +    QTAILQ_FOREACH(vs, &vd->clients, next) { +        vnc_jobs_join(vs); +    } +    QTAILQ_FOREACH(vs, &vd->clients, next) { +        vnc_lock_output(vs); +        vs->abort = false; +        vnc_unlock_output(vs); +    } +} + +int vnc_server_fb_stride(VncDisplay *vd) +{ +    return pixman_image_get_stride(vd->server); +} + +void *vnc_server_fb_ptr(VncDisplay *vd, int x, int y) +{ +    uint8_t *ptr; + +    ptr  = (uint8_t *)pixman_image_get_data(vd->server); +    ptr += y * vnc_server_fb_stride(vd); +    ptr += x * VNC_SERVER_FB_BYTES; +    return ptr; +} + +static void vnc_dpy_switch(DisplayChangeListener *dcl, +                           DisplaySurface *surface) +{ +    VncDisplay *vd = container_of(dcl, VncDisplay, dcl); +    VncState *vs; +    int width, height; + +    vnc_abort_display_jobs(vd); + +    /* server surface */ +    qemu_pixman_image_unref(vd->server); +    vd->ds = surface; +    width = MIN(VNC_MAX_WIDTH, ROUND_UP(surface_width(vd->ds), +                                        VNC_DIRTY_PIXELS_PER_BIT)); +    height = MIN(VNC_MAX_HEIGHT, surface_height(vd->ds)); +    vd->server = pixman_image_create_bits(VNC_SERVER_FB_FORMAT, +                                          width, height, 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, 0x00, sizeof(vd->guest.dirty)); +    vnc_set_area_dirty(vd->guest.dirty, width, height, 0, 0, +                       width, height); + +    QTAILQ_FOREACH(vs, &vd->clients, next) { +        vnc_colordepth(vs); +        vnc_desktop_resize(vs); +        if (vs->vd->cursor) { +            vnc_cursor_define(vs); +        } +        memset(vs->dirty, 0x00, sizeof(vs->dirty)); +        vnc_set_area_dirty(vs->dirty, width, height, 0, 0, +                           width, height); +    } +} + +/* fastest code */ +static void vnc_write_pixels_copy(VncState *vs, +                                  void *pixels, int size) +{ +    vnc_write(vs, pixels, size); +} + +/* slowest but generic code. */ +void vnc_convert_pixel(VncState *vs, uint8_t *buf, uint32_t v) +{ +    uint8_t r, g, b; + +#if VNC_SERVER_FB_FORMAT == PIXMAN_FORMAT(32, PIXMAN_TYPE_ARGB, 0, 8, 8, 8) +    r = (((v & 0x00ff0000) >> 16) << vs->client_pf.rbits) >> 8; +    g = (((v & 0x0000ff00) >>  8) << vs->client_pf.gbits) >> 8; +    b = (((v & 0x000000ff) >>  0) << vs->client_pf.bbits) >> 8; +#else +# error need some bits here if you change VNC_SERVER_FB_FORMAT +#endif +    v = (r << vs->client_pf.rshift) | +        (g << vs->client_pf.gshift) | +        (b << vs->client_pf.bshift); +    switch (vs->client_pf.bytes_per_pixel) { +    case 1: +        buf[0] = v; +        break; +    case 2: +        if (vs->client_be) { +            buf[0] = v >> 8; +            buf[1] = v; +        } else { +            buf[1] = v >> 8; +            buf[0] = v; +        } +        break; +    default: +    case 4: +        if (vs->client_be) { +            buf[0] = v >> 24; +            buf[1] = v >> 16; +            buf[2] = v >> 8; +            buf[3] = v; +        } else { +            buf[3] = v >> 24; +            buf[2] = v >> 16; +            buf[1] = v >> 8; +            buf[0] = v; +        } +        break; +    } +} + +static void vnc_write_pixels_generic(VncState *vs, +                                     void *pixels1, int size) +{ +    uint8_t buf[4]; + +    if (VNC_SERVER_FB_BYTES == 4) { +        uint32_t *pixels = pixels1; +        int n, i; +        n = size >> 2; +        for (i = 0; i < n; i++) { +            vnc_convert_pixel(vs, buf, pixels[i]); +            vnc_write(vs, buf, vs->client_pf.bytes_per_pixel); +        } +    } +} + +int vnc_raw_send_framebuffer_update(VncState *vs, int x, int y, int w, int h) +{ +    int i; +    uint8_t *row; +    VncDisplay *vd = vs->vd; + +    row = vnc_server_fb_ptr(vd, x, y); +    for (i = 0; i < h; i++) { +        vs->write_pixels(vs, row, w * VNC_SERVER_FB_BYTES); +        row += vnc_server_fb_stride(vd); +    } +    return 1; +} + +int vnc_send_framebuffer_update(VncState *vs, int x, int y, int w, int h) +{ +    int n = 0; + +    switch(vs->vnc_encoding) { +        case VNC_ENCODING_ZLIB: +            n = vnc_zlib_send_framebuffer_update(vs, x, y, w, h); +            break; +        case VNC_ENCODING_HEXTILE: +            vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_HEXTILE); +            n = vnc_hextile_send_framebuffer_update(vs, x, y, w, h); +            break; +        case VNC_ENCODING_TIGHT: +            n = vnc_tight_send_framebuffer_update(vs, x, y, w, h); +            break; +        case VNC_ENCODING_TIGHT_PNG: +            n = vnc_tight_png_send_framebuffer_update(vs, x, y, w, h); +            break; +        case VNC_ENCODING_ZRLE: +            n = vnc_zrle_send_framebuffer_update(vs, x, y, w, h); +            break; +        case VNC_ENCODING_ZYWRLE: +            n = vnc_zywrle_send_framebuffer_update(vs, x, y, w, h); +            break; +        default: +            vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_RAW); +            n = vnc_raw_send_framebuffer_update(vs, x, y, w, h); +            break; +    } +    return n; +} + +static void vnc_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(vs, 1, true); +            /* 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 - (VNC_DIRTY_PIXELS_PER_BIT - (dst_x % VNC_DIRTY_PIXELS_PER_BIT)); +    if (w_lim < 0) { +        w_lim = w; +    } else { +        w_lim = w - (w_lim % VNC_DIRTY_PIXELS_PER_BIT); +    } +    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 = (VNC_DIRTY_PIXELS_PER_BIT - +                    (dst_x % VNC_DIRTY_PIXELS_PER_BIT)); +                s = MIN(s, w_lim); +            } else { +                s = VNC_DIRTY_PIXELS_PER_BIT; +            } +            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) / VNC_DIRTY_PIXELS_PER_BIT), +                            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) +{ +    /* can we ask the client(s) to move the pointer ??? */ +} + +static int vnc_cursor_define(VncState *vs) +{ +    QEMUCursor *c = vs->vd->cursor; +    int isize; + +    if (vnc_has_feature(vs, VNC_FEATURE_RICH_CURSOR)) { +        vnc_lock_output(vs); +        vnc_write_u8(vs,  VNC_MSG_SERVER_FRAMEBUFFER_UPDATE); +        vnc_write_u8(vs,  0);  /*  padding     */ +        vnc_write_u16(vs, 1);  /*  # of rects  */ +        vnc_framebuffer_update(vs, c->hot_x, c->hot_y, c->width, c->height, +                               VNC_ENCODING_RICH_CURSOR); +        isize = c->width * c->height * vs->client_pf.bytes_per_pixel; +        vnc_write_pixels_generic(vs, c->data, isize); +        vnc_write(vs, vs->vd->cursor_mask, vs->vd->cursor_msize); +        vnc_unlock_output(vs); +        return 0; +    } +    return -1; +} + +static void vnc_dpy_cursor_define(DisplayChangeListener *dcl, +                                  QEMUCursor *c) +{ +    VncDisplay *vd = container_of(dcl, VncDisplay, dcl); +    VncState *vs; + +    cursor_put(vd->cursor); +    g_free(vd->cursor_mask); + +    vd->cursor = c; +    cursor_get(vd->cursor); +    vd->cursor_msize = cursor_get_mono_bpl(c) * c->height; +    vd->cursor_mask = g_malloc0(vd->cursor_msize); +    cursor_get_mono_mask(c, 0, vd->cursor_mask); + +    QTAILQ_FOREACH(vs, &vd->clients, next) { +        vnc_cursor_define(vs); +    } +} + +static int find_and_clear_dirty_height(VncState *vs, +                                       int y, int last_x, int x, int height) +{ +    int h; + +    for (h = 1; h < (height - y); h++) { +        if (!test_bit(last_x, vs->dirty[y + h])) { +            break; +        } +        bitmap_clear(vs->dirty[y + h], last_x, x - last_x); +    } + +    return h; +} + +static int vnc_update_client(VncState *vs, int has_dirty, bool sync) +{ +    vs->has_dirty += has_dirty; +    if (vs->need_update && vs->csock != -1) { +        VncDisplay *vd = vs->vd; +        VncJob *job; +        int y; +        int height, width; +        int n = 0; + +        if (vs->output.offset && !vs->audio_cap && !vs->force_update) +            /* kernel send buffers are full -> drop frames to throttle */ +            return 0; + +        if (!vs->has_dirty && !vs->audio_cap && !vs->force_update) +            return 0; + +        /* +         * Send screen updates to the vnc client using the server +         * surface and server dirty map.  guest surface updates +         * happening in parallel don't disturb us, the next pass will +         * send them to the client. +         */ +        job = vnc_job_new(vs); + +        height = pixman_image_get_height(vd->server); +        width = pixman_image_get_width(vd->server); + +        y = 0; +        for (;;) { +            int x, h; +            unsigned long x2; +            unsigned long offset = find_next_bit((unsigned long *) &vs->dirty, +                                                 height * VNC_DIRTY_BPL(vs), +                                                 y * VNC_DIRTY_BPL(vs)); +            if (offset == height * VNC_DIRTY_BPL(vs)) { +                /* no more dirty bits */ +                break; +            } +            y = offset / VNC_DIRTY_BPL(vs); +            x = offset % VNC_DIRTY_BPL(vs); +            x2 = find_next_zero_bit((unsigned long *) &vs->dirty[y], +                                    VNC_DIRTY_BPL(vs), x); +            bitmap_clear(vs->dirty[y], x, x2 - x); +            h = find_and_clear_dirty_height(vs, y, x, x2, height); +            x2 = MIN(x2, width / VNC_DIRTY_PIXELS_PER_BIT); +            if (x2 > x) { +                n += vnc_job_add_rect(job, x * VNC_DIRTY_PIXELS_PER_BIT, y, +                                      (x2 - x) * VNC_DIRTY_PIXELS_PER_BIT, h); +            } +            if (!x && x2 == width / VNC_DIRTY_PIXELS_PER_BIT) { +                y += h; +                if (y == height) { +                    break; +                } +            } +        } + +        vnc_job_push(job); +        if (sync) { +            vnc_jobs_join(vs); +        } +        vs->force_update = 0; +        vs->has_dirty = 0; +        return n; +    } + +    if (vs->csock == -1) { +        vnc_disconnect_finish(vs); +    } else if (sync) { +        vnc_jobs_join(vs); +    } + +    return 0; +} + +/* audio */ +static void audio_capture_notify(void *opaque, audcnotification_e cmd) +{ +    VncState *vs = opaque; + +    switch (cmd) { +    case AUD_CNOTIFY_DISABLE: +        vnc_lock_output(vs); +        vnc_write_u8(vs, VNC_MSG_SERVER_QEMU); +        vnc_write_u8(vs, VNC_MSG_SERVER_QEMU_AUDIO); +        vnc_write_u16(vs, VNC_MSG_SERVER_QEMU_AUDIO_END); +        vnc_unlock_output(vs); +        vnc_flush(vs); +        break; + +    case AUD_CNOTIFY_ENABLE: +        vnc_lock_output(vs); +        vnc_write_u8(vs, VNC_MSG_SERVER_QEMU); +        vnc_write_u8(vs, VNC_MSG_SERVER_QEMU_AUDIO); +        vnc_write_u16(vs, VNC_MSG_SERVER_QEMU_AUDIO_BEGIN); +        vnc_unlock_output(vs); +        vnc_flush(vs); +        break; +    } +} + +static void audio_capture_destroy(void *opaque) +{ +} + +static void audio_capture(void *opaque, void *buf, int size) +{ +    VncState *vs = opaque; + +    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); +    vnc_unlock_output(vs); +    vnc_flush(vs); +} + +static void audio_add(VncState *vs) +{ +    struct audio_capture_ops ops; + +    if (vs->audio_cap) { +        error_report("audio already running"); +        return; +    } + +    ops.notify = audio_capture_notify; +    ops.destroy = audio_capture_destroy; +    ops.capture = audio_capture; + +    vs->audio_cap = AUD_add_capture(&vs->as, &ops, vs); +    if (!vs->audio_cap) { +        error_report("Failed to add audio capture"); +    } +} + +static void audio_del(VncState *vs) +{ +    if (vs->audio_cap) { +        AUD_del_capture(vs->audio_cap, vs); +        vs->audio_cap = NULL; +    } +} + +static void vnc_disconnect_start(VncState *vs) +{ +    if (vs->csock == -1) +        return; +    vnc_set_share_mode(vs, VNC_SHARE_MODE_DISCONNECTED); +    qemu_set_fd_handler(vs->csock, NULL, NULL, NULL); +    closesocket(vs->csock); +    vs->csock = -1; +} + +void vnc_disconnect_finish(VncState *vs) +{ +    int i; + +    vnc_jobs_join(vs); /* Wait encoding jobs */ + +    vnc_lock_output(vs); +    vnc_qmp_event(vs, QAPI_EVENT_VNC_DISCONNECTED); + +    buffer_free(&vs->input); +    buffer_free(&vs->output); +    buffer_free(&vs->ws_input); +    buffer_free(&vs->ws_output); + +    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); + +    if (vs->initialized) { +        QTAILQ_REMOVE(&vs->vd->clients, vs, next); +        qemu_remove_mouse_mode_change_notifier(&vs->mouse_mode_notifier); +    } + +    if (vs->vd->lock_key_sync) +        qemu_remove_led_event_handler(vs->led); +    vnc_unlock_output(vs); + +    qemu_mutex_destroy(&vs->output_mutex); +    if (vs->bh != NULL) { +        qemu_bh_delete(vs->bh); +    } +    buffer_free(&vs->jobs_buffer); + +    for (i = 0; i < VNC_STAT_ROWS; ++i) { +        g_free(vs->lossy_rect[i]); +    } +    g_free(vs->lossy_rect); +    g_free(vs); +} + +int vnc_client_io_error(VncState *vs, int ret, int last_errno) +{ +    if (ret == 0 || ret == -1) { +        if (ret == -1) { +            switch (last_errno) { +                case EINTR: +                case EAGAIN: +#ifdef _WIN32 +                case WSAEWOULDBLOCK: +#endif +                    return 0; +                default: +                    break; +            } +        } + +        VNC_DEBUG("Closing down client sock: ret %d, errno %d\n", +                  ret, ret < 0 ? last_errno : 0); +        vnc_disconnect_start(vs); + +        return 0; +    } +    return ret; +} + + +void vnc_client_error(VncState *vs) +{ +    VNC_DEBUG("Closing down client sock: protocol error\n"); +    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 + * be the raw data, or may have already been encoded by SASL. + * The data will be written either straight onto the socket, or + * written via the GNUTLS wrappers, if TLS/SSL encryption is enabled + * + * NB, it is theoretically possible to have 2 layers of encryption, + * both SASL, and this TLS layer. It is highly unlikely in practice + * though, since SASL encryption will typically be a no-op if TLS + * is active + * + * Returns the number of bytes written, which may be less than + * the requested 'datalen' if the socket would block. Returns + * -1 on error, and disconnects the client socket. + */ +long 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 { +#endif /* CONFIG_VNC_TLS */ +        ret = send(vs->csock, (const void *)data, datalen, 0); +#ifdef CONFIG_VNC_TLS +    } +#endif /* CONFIG_VNC_TLS */ +    VNC_DEBUG("Wrote wire %p %zd -> %ld\n", data, datalen, ret); +    return vnc_client_io_error(vs, ret, socket_error()); +} + + +/* + * Called to write buffered data to the client socket, when not + * using any SASL SSF encryption layers. Will write as much data + * as possible without blocking. If all buffered data is written, + * will switch the FD poll() handler back to read monitoring. + * + * Returns the number of bytes written, which may be less than + * the buffered output data if the socket would block. Returns + * -1 on error, and disconnects the client socket. + */ +static long vnc_client_write_plain(VncState *vs) +{ +    long ret; + +#ifdef CONFIG_VNC_SASL +    VNC_DEBUG("Write Plain: Pending output %p size %zd offset %zd. Wait SSF %d\n", +              vs->output.buffer, vs->output.capacity, vs->output.offset, +              vs->sasl.waitWriteSSF); + +    if (vs->sasl.conn && +        vs->sasl.runSSF && +        vs->sasl.waitWriteSSF) { +        ret = vnc_client_write_buf(vs, vs->output.buffer, vs->sasl.waitWriteSSF); +        if (ret) +            vs->sasl.waitWriteSSF -= ret; +    } else +#endif /* CONFIG_VNC_SASL */ +        ret = vnc_client_write_buf(vs, vs->output.buffer, vs->output.offset); +    if (!ret) +        return 0; + +    buffer_advance(&vs->output, ret); + +    if (vs->output.offset == 0) { +        qemu_set_fd_handler(vs->csock, vnc_client_read, NULL, vs); +    } + +    return ret; +} + + +/* + * First function called whenever there is data to be written to + * the client socket. Will delegate actual work according to whether + * SASL SSF layers are enabled (thus requiring encryption calls) + */ +static void vnc_client_write_locked(void *opaque) +{ +    VncState *vs = opaque; + +#ifdef CONFIG_VNC_SASL +    if (vs->sasl.conn && +        vs->sasl.runSSF && +        !vs->sasl.waitWriteSSF) { +        vnc_client_write_sasl(vs); +    } else +#endif /* CONFIG_VNC_SASL */ +    { +        if (vs->encode_ws) { +            vnc_client_write_ws(vs); +        } else { +            vnc_client_write_plain(vs); +        } +    } +} + +void vnc_client_write(void *opaque) +{ +    VncState *vs = opaque; + +    vnc_lock_output(vs); +    if (vs->output.offset || vs->ws_output.offset) { +        vnc_client_write_locked(opaque); +    } else if (vs->csock != -1) { +        qemu_set_fd_handler(vs->csock, vnc_client_read, NULL, vs); +    } +    vnc_unlock_output(vs); +} + +void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting) +{ +    vs->read_handler = func; +    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 + * be the raw data, or may need to be further decoded by SASL. + * The data will be read either straight from to the socket, or + * read via the GNUTLS wrappers, if TLS/SSL encryption is enabled + * + * NB, it is theoretically possible to have 2 layers of encryption, + * both SASL, and this TLS layer. It is highly unlikely in practice + * though, since SASL encryption will typically be a no-op if TLS + * is active + * + * Returns the number of bytes read, which may be less than + * the requested 'datalen' if the socket would block. Returns + * -1 on error, and disconnects the client socket. + */ +long 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 { +#endif /* CONFIG_VNC_TLS */ +        ret = qemu_recv(vs->csock, data, datalen, 0); +#ifdef CONFIG_VNC_TLS +    } +#endif /* CONFIG_VNC_TLS */ +    VNC_DEBUG("Read wire %p %zd -> %ld\n", data, datalen, ret); +    return vnc_client_io_error(vs, ret, socket_error()); +} + + +/* + * Called to read data from the client socket to the input buffer, + * when not using any SASL SSF encryption layers. Will read as much + * data as possible without blocking. + * + * Returns the number of bytes read. Returns -1 on error, and + * disconnects the client socket. + */ +static long vnc_client_read_plain(VncState *vs) +{ +    int ret; +    VNC_DEBUG("Read plain %p size %zd offset %zd\n", +              vs->input.buffer, vs->input.capacity, vs->input.offset); +    buffer_reserve(&vs->input, 4096); +    ret = vnc_client_read_buf(vs, buffer_end(&vs->input), 4096); +    if (!ret) +        return 0; +    vs->input.offset += ret; +    return ret; +} + +static void vnc_jobs_bh(void *opaque) +{ +    VncState *vs = opaque; + +    vnc_jobs_consume_buffer(vs); +} + +/* + * First function called whenever there is more data to be read from + * the client socket. Will delegate actual work according to whether + * SASL SSF layers are enabled (thus requiring decryption calls) + */ +void vnc_client_read(void *opaque) +{ +    VncState *vs = opaque; +    long ret; + +#ifdef CONFIG_VNC_SASL +    if (vs->sasl.conn && vs->sasl.runSSF) +        ret = vnc_client_read_sasl(vs); +    else +#endif /* CONFIG_VNC_SASL */ +        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 { +            ret = vnc_client_read_plain(vs); +        } +    if (!ret) { +        if (vs->csock == -1) +            vnc_disconnect_finish(vs); +        return; +    } + +    while (vs->read_handler && vs->input.offset >= vs->read_handler_expect) { +        size_t len = vs->read_handler_expect; +        int ret; + +        ret = vs->read_handler(vs, vs->input.buffer, len); +        if (vs->csock == -1) { +            vnc_disconnect_finish(vs); +            return; +        } + +        if (!ret) { +            buffer_advance(&vs->input, len); +        } else { +            vs->read_handler_expect = ret; +        } +    } +} + +void vnc_write(VncState *vs, const void *data, size_t len) +{ +    buffer_reserve(&vs->output, len); + +    if (vs->csock != -1 && buffer_empty(&vs->output)) { +        qemu_set_fd_handler(vs->csock, vnc_client_read, vnc_client_write, vs); +    } + +    buffer_append(&vs->output, data, len); +} + +void vnc_write_s32(VncState *vs, int32_t value) +{ +    vnc_write_u32(vs, *(uint32_t *)&value); +} + +void vnc_write_u32(VncState *vs, uint32_t value) +{ +    uint8_t buf[4]; + +    buf[0] = (value >> 24) & 0xFF; +    buf[1] = (value >> 16) & 0xFF; +    buf[2] = (value >>  8) & 0xFF; +    buf[3] = value & 0xFF; + +    vnc_write(vs, buf, 4); +} + +void vnc_write_u16(VncState *vs, uint16_t value) +{ +    uint8_t buf[2]; + +    buf[0] = (value >> 8) & 0xFF; +    buf[1] = value & 0xFF; + +    vnc_write(vs, buf, 2); +} + +void vnc_write_u8(VncState *vs, uint8_t value) +{ +    vnc_write(vs, (char *)&value, 1); +} + +void vnc_flush(VncState *vs) +{ +    vnc_lock_output(vs); +    if (vs->csock != -1 && (vs->output.offset || +                            vs->ws_output.offset)) { +        vnc_client_write_locked(vs); +    } +    vnc_unlock_output(vs); +} + +static uint8_t read_u8(uint8_t *data, size_t offset) +{ +    return data[offset]; +} + +static uint16_t read_u16(uint8_t *data, size_t offset) +{ +    return ((data[offset] & 0xFF) << 8) | (data[offset + 1] & 0xFF); +} + +static int32_t read_s32(uint8_t *data, size_t offset) +{ +    return (int32_t)((data[offset] << 24) | (data[offset + 1] << 16) | +                     (data[offset + 2] << 8) | data[offset + 3]); +} + +uint32_t read_u32(uint8_t *data, size_t offset) +{ +    return ((data[offset] << 24) | (data[offset + 1] << 16) | +            (data[offset + 2] << 8) | data[offset + 3]); +} + +static void 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 = qemu_input_is_absolute(); + +    if (vnc_has_feature(vs, VNC_FEATURE_POINTER_TYPE_CHANGE) && vs->absolute != absolute) { +        vnc_lock_output(vs); +        vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE); +        vnc_write_u8(vs, 0); +        vnc_write_u16(vs, 1); +        vnc_framebuffer_update(vs, absolute, 0, +                               pixman_image_get_width(vs->vd->server), +                               pixman_image_get_height(vs->vd->server), +                               VNC_ENCODING_POINTER_TYPE_CHANGE); +        vnc_unlock_output(vs); +        vnc_flush(vs); +    } +    vs->absolute = absolute; +} + +static void pointer_event(VncState *vs, int button_mask, int x, int y) +{ +    static uint32_t bmap[INPUT_BUTTON_MAX] = { +        [INPUT_BUTTON_LEFT]       = 0x01, +        [INPUT_BUTTON_MIDDLE]     = 0x02, +        [INPUT_BUTTON_RIGHT]      = 0x04, +        [INPUT_BUTTON_WHEEL_UP]   = 0x08, +        [INPUT_BUTTON_WHEEL_DOWN] = 0x10, +    }; +    QemuConsole *con = vs->vd->dcl.con; +    int width = pixman_image_get_width(vs->vd->server); +    int height = pixman_image_get_height(vs->vd->server); + +    if (vs->last_bmask != button_mask) { +        qemu_input_update_buttons(con, bmap, vs->last_bmask, button_mask); +        vs->last_bmask = button_mask; +    } + +    if (vs->absolute) { +        qemu_input_queue_abs(con, INPUT_AXIS_X, x, width); +        qemu_input_queue_abs(con, INPUT_AXIS_Y, y, height); +    } else if (vnc_has_feature(vs, VNC_FEATURE_POINTER_TYPE_CHANGE)) { +        qemu_input_queue_rel(con, INPUT_AXIS_X, x - 0x7FFF); +        qemu_input_queue_rel(con, INPUT_AXIS_Y, y - 0x7FFF); +    } else { +        if (vs->last_x != -1) { +            qemu_input_queue_rel(con, INPUT_AXIS_X, x - vs->last_x); +            qemu_input_queue_rel(con, INPUT_AXIS_Y, y - vs->last_y); +        } +        vs->last_x = x; +        vs->last_y = y; +    } +    qemu_input_event_sync(); +} + +static void reset_keys(VncState *vs) +{ +    int i; +    for(i = 0; i < 256; i++) { +        if (vs->modifiers_state[i]) { +            qemu_input_event_send_key_number(vs->vd->dcl.con, i, false); +            vs->modifiers_state[i] = 0; +        } +    } +} + +static void press_key(VncState *vs, int keysym) +{ +    int keycode = keysym2scancode(vs->vd->kbd_layout, keysym) & SCANCODE_KEYMASK; +    qemu_input_event_send_key_number(vs->vd->dcl.con, keycode, true); +    qemu_input_event_send_key_delay(0); +    qemu_input_event_send_key_number(vs->vd->dcl.con, keycode, false); +    qemu_input_event_send_key_delay(0); +} + +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; +} + +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_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)); + +    trace_vnc_key_guest_leds((ledstate & QEMU_CAPS_LOCK_LED), +                             (ledstate & QEMU_NUM_LOCK_LED), +                             (ledstate & QEMU_SCROLL_LOCK_LED)); + +    caps = ledstate & QEMU_CAPS_LOCK_LED ? 1 : 0; +    num  = ledstate & QEMU_NUM_LOCK_LED  ? 1 : 0; +    scr  = ledstate & QEMU_SCROLL_LOCK_LED ? 1 : 0; + +    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; +    } + +    /* Sending the current led state message to the client */ +    if (has_changed) { +        vnc_led_state_change(vs); +    } +} + +static void do_key_event(VncState *vs, int down, int keycode, int sym) +{ +    /* 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 (vs->vd->dcl.con == NULL && +            down && vs->modifiers_state[0x1d] && vs->modifiers_state[0x38]) { +            /* Reset the modifiers sent to the current console */ +            reset_keys(vs); +            console_select(keycode - 0x02); +            return; +        } +        break; +    case 0x3a:                        /* CapsLock */ +    case 0x45:                        /* NumLock */ +        if (down) +            vs->modifiers_state[keycode] ^= 1; +        break; +    } + +    /* Turn off the lock state sync logic if the client support the led +       state extension. +    */ +    if (down && vs->vd->lock_key_sync && +        !vnc_has_feature(vs, VNC_FEATURE_LED_STATE) && +        keycode_is_keypad(vs->vd->kbd_layout, keycode)) { +        /* If the numlock state needs to change then simulate an additional +           keypress before sending this one.  This will happen if the user +           toggles numlock away from the VNC window. +        */ +        if (keysym_is_numlock(vs->vd->kbd_layout, sym & 0xFFFF)) { +            if (!vs->modifiers_state[0x45]) { +                trace_vnc_key_sync_numlock(true); +                vs->modifiers_state[0x45] = 1; +                press_key(vs, 0xff7f); +            } +        } else { +            if (vs->modifiers_state[0x45]) { +                trace_vnc_key_sync_numlock(false); +                vs->modifiers_state[0x45] = 0; +                press_key(vs, 0xff7f); +            } +        } +    } + +    if (down && vs->vd->lock_key_sync && +        !vnc_has_feature(vs, VNC_FEATURE_LED_STATE) && +        ((sym >= 'A' && sym <= 'Z') || (sym >= 'a' && sym <= 'z'))) { +        /* If the capslock state needs to change then simulate an additional +           keypress before sending this one.  This will happen if the user +           toggles capslock away from the VNC window. +        */ +        int uppercase = !!(sym >= 'A' && sym <= 'Z'); +        int shift = !!(vs->modifiers_state[0x2a] | vs->modifiers_state[0x36]); +        int capslock = !!(vs->modifiers_state[0x3a]); +        if (capslock) { +            if (uppercase == shift) { +                trace_vnc_key_sync_capslock(false); +                vs->modifiers_state[0x3a] = 0; +                press_key(vs, 0xffe5); +            } +        } else { +            if (uppercase != shift) { +                trace_vnc_key_sync_capslock(true); +                vs->modifiers_state[0x3a] = 1; +                press_key(vs, 0xffe5); +            } +        } +    } + +    if (qemu_console_is_graphic(NULL)) { +        qemu_input_event_send_key_number(vs->vd->dcl.con, keycode, down); +    } else { +        bool numlock = vs->modifiers_state[0x45]; +        bool control = (vs->modifiers_state[0x1d] || +                        vs->modifiers_state[0x9d]); +        /* QEMU console emulation */ +        if (down) { +            switch (keycode) { +            case 0x2a:                          /* Left Shift */ +            case 0x36:                          /* Right Shift */ +            case 0x1d:                          /* Left CTRL */ +            case 0x9d:                          /* Right CTRL */ +            case 0x38:                          /* Left ALT */ +            case 0xb8:                          /* Right ALT */ +                break; +            case 0xc8: +                kbd_put_keysym(QEMU_KEY_UP); +                break; +            case 0xd0: +                kbd_put_keysym(QEMU_KEY_DOWN); +                break; +            case 0xcb: +                kbd_put_keysym(QEMU_KEY_LEFT); +                break; +            case 0xcd: +                kbd_put_keysym(QEMU_KEY_RIGHT); +                break; +            case 0xd3: +                kbd_put_keysym(QEMU_KEY_DELETE); +                break; +            case 0xc7: +                kbd_put_keysym(QEMU_KEY_HOME); +                break; +            case 0xcf: +                kbd_put_keysym(QEMU_KEY_END); +                break; +            case 0xc9: +                kbd_put_keysym(QEMU_KEY_PAGEUP); +                break; +            case 0xd1: +                kbd_put_keysym(QEMU_KEY_PAGEDOWN); +                break; + +            case 0x47: +                kbd_put_keysym(numlock ? '7' : QEMU_KEY_HOME); +                break; +            case 0x48: +                kbd_put_keysym(numlock ? '8' : QEMU_KEY_UP); +                break; +            case 0x49: +                kbd_put_keysym(numlock ? '9' : QEMU_KEY_PAGEUP); +                break; +            case 0x4b: +                kbd_put_keysym(numlock ? '4' : QEMU_KEY_LEFT); +                break; +            case 0x4c: +                kbd_put_keysym('5'); +                break; +            case 0x4d: +                kbd_put_keysym(numlock ? '6' : QEMU_KEY_RIGHT); +                break; +            case 0x4f: +                kbd_put_keysym(numlock ? '1' : QEMU_KEY_END); +                break; +            case 0x50: +                kbd_put_keysym(numlock ? '2' : QEMU_KEY_DOWN); +                break; +            case 0x51: +                kbd_put_keysym(numlock ? '3' : QEMU_KEY_PAGEDOWN); +                break; +            case 0x52: +                kbd_put_keysym('0'); +                break; +            case 0x53: +                kbd_put_keysym(numlock ? '.' : QEMU_KEY_DELETE); +                break; + +            case 0xb5: +                kbd_put_keysym('/'); +                break; +            case 0x37: +                kbd_put_keysym('*'); +                break; +            case 0x4a: +                kbd_put_keysym('-'); +                break; +            case 0x4e: +                kbd_put_keysym('+'); +                break; +            case 0x9c: +                kbd_put_keysym('\n'); +                break; + +            default: +                if (control) { +                    kbd_put_keysym(sym & 0x1f); +                } else { +                    kbd_put_keysym(sym); +                } +                break; +            } +        } +    } +} + +static void vnc_release_modifiers(VncState *vs) +{ +    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; +        } +        qemu_input_event_send_key_number(vs->vd->dcl.con, keycode, false); +    } +} + +static const char *code2name(int keycode) +{ +    return QKeyCode_lookup[qemu_input_key_number_to_qcode(keycode)]; +} + +static void key_event(VncState *vs, int down, uint32_t sym) +{ +    int keycode; +    int lsym = sym; + +    if (lsym >= 'A' && lsym <= 'Z' && qemu_console_is_graphic(NULL)) { +        lsym = lsym - 'A' + 'a'; +    } + +    keycode = keysym2scancode(vs->vd->kbd_layout, lsym & 0xFFFF) & SCANCODE_KEYMASK; +    trace_vnc_key_event_map(down, sym, keycode, code2name(keycode)); +    do_key_event(vs, down, keycode, sym); +} + +static void ext_key_event(VncState *vs, int down, +                          uint32_t sym, uint16_t keycode) +{ +    /* if the user specifies a keyboard layout, always use it */ +    if (keyboard_layout) { +        key_event(vs, down, sym); +    } else { +        trace_vnc_key_event_ext(down, sym, keycode, code2name(keycode)); +        do_key_event(vs, down, keycode, sym); +    } +} + +static void framebuffer_update_request(VncState *vs, int incremental, +                                       int x, int y, int w, int h) +{ +    int width = pixman_image_get_width(vs->vd->server); +    int height = pixman_image_get_height(vs->vd->server); + +    vs->need_update = 1; + +    if (incremental) { +        return; +    } + +    vs->force_update = 1; +    vnc_set_area_dirty(vs->dirty, width, height, x, y, w, h); +} + +static void send_ext_key_event_ack(VncState *vs) +{ +    vnc_lock_output(vs); +    vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE); +    vnc_write_u8(vs, 0); +    vnc_write_u16(vs, 1); +    vnc_framebuffer_update(vs, 0, 0, +                           pixman_image_get_width(vs->vd->server), +                           pixman_image_get_height(vs->vd->server), +                           VNC_ENCODING_EXT_KEY_EVENT); +    vnc_unlock_output(vs); +    vnc_flush(vs); +} + +static void send_ext_audio_ack(VncState *vs) +{ +    vnc_lock_output(vs); +    vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE); +    vnc_write_u8(vs, 0); +    vnc_write_u16(vs, 1); +    vnc_framebuffer_update(vs, 0, 0, +                           pixman_image_get_width(vs->vd->server), +                           pixman_image_get_height(vs->vd->server), +                           VNC_ENCODING_AUDIO); +    vnc_unlock_output(vs); +    vnc_flush(vs); +} + +static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings) +{ +    int i; +    unsigned int enc = 0; + +    vs->features = 0; +    vs->vnc_encoding = 0; +    vs->tight.compression = 9; +    vs->tight.quality = -1; /* Lossless by default */ +    vs->absolute = -1; + +    /* +     * Start from the end because the encodings are sent in order of preference. +     * This way the preferred encoding (first encoding defined in the array) +     * will be set at the end of the loop. +     */ +    for (i = n_encodings - 1; i >= 0; i--) { +        enc = encodings[i]; +        switch (enc) { +        case VNC_ENCODING_RAW: +            vs->vnc_encoding = enc; +            break; +        case VNC_ENCODING_COPYRECT: +            vs->features |= VNC_FEATURE_COPYRECT_MASK; +            break; +        case VNC_ENCODING_HEXTILE: +            vs->features |= VNC_FEATURE_HEXTILE_MASK; +            vs->vnc_encoding = enc; +            break; +        case VNC_ENCODING_TIGHT: +            vs->features |= VNC_FEATURE_TIGHT_MASK; +            vs->vnc_encoding = enc; +            break; +#ifdef CONFIG_VNC_PNG +        case VNC_ENCODING_TIGHT_PNG: +            vs->features |= VNC_FEATURE_TIGHT_PNG_MASK; +            vs->vnc_encoding = enc; +            break; +#endif +        case VNC_ENCODING_ZLIB: +            vs->features |= VNC_FEATURE_ZLIB_MASK; +            vs->vnc_encoding = enc; +            break; +        case VNC_ENCODING_ZRLE: +            vs->features |= VNC_FEATURE_ZRLE_MASK; +            vs->vnc_encoding = enc; +            break; +        case VNC_ENCODING_ZYWRLE: +            vs->features |= VNC_FEATURE_ZYWRLE_MASK; +            vs->vnc_encoding = enc; +            break; +        case VNC_ENCODING_DESKTOPRESIZE: +            vs->features |= VNC_FEATURE_RESIZE_MASK; +            break; +        case VNC_ENCODING_POINTER_TYPE_CHANGE: +            vs->features |= VNC_FEATURE_POINTER_TYPE_CHANGE_MASK; +            break; +        case VNC_ENCODING_RICH_CURSOR: +            vs->features |= VNC_FEATURE_RICH_CURSOR_MASK; +            break; +        case VNC_ENCODING_EXT_KEY_EVENT: +            send_ext_key_event_ack(vs); +            break; +        case VNC_ENCODING_AUDIO: +            send_ext_audio_ack(vs); +            break; +        case VNC_ENCODING_WMVi: +            vs->features |= VNC_FEATURE_WMVI_MASK; +            break; +        case VNC_ENCODING_LED_STATE: +            vs->features |= VNC_FEATURE_LED_STATE_MASK; +            break; +        case VNC_ENCODING_COMPRESSLEVEL0 ... VNC_ENCODING_COMPRESSLEVEL0 + 9: +            vs->tight.compression = (enc & 0x0F); +            break; +        case VNC_ENCODING_QUALITYLEVEL0 ... VNC_ENCODING_QUALITYLEVEL0 + 9: +            if (vs->vd->lossy) { +                vs->tight.quality = (enc & 0x0F); +            } +            break; +        default: +            VNC_DEBUG("Unknown encoding: %d (0x%.8x): %d\n", i, enc, enc); +            break; +        } +    } +    vnc_desktop_resize(vs); +    check_pointer_type_change(&vs->mouse_mode_notifier, NULL); +    vnc_led_state_change(vs); +} + +static void set_pixel_conversion(VncState *vs) +{ +    pixman_format_code_t fmt = qemu_pixman_get_format(&vs->client_pf); + +    if (fmt == VNC_SERVER_FB_FORMAT) { +        vs->write_pixels = vnc_write_pixels_copy; +        vnc_hextile_set_pixel_conversion(vs, 0); +    } else { +        vs->write_pixels = vnc_write_pixels_generic; +        vnc_hextile_set_pixel_conversion(vs, 1); +    } +} + +static void set_pixel_format(VncState *vs, +                             int bits_per_pixel, int depth, +                             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) { +        vnc_client_error(vs); +        return; +    } + +    switch (bits_per_pixel) { +    case 8: +    case 16: +    case 32: +        break; +    default: +        vnc_client_error(vs); +        return; +    } + +    vs->client_pf.rmax = red_max ? red_max : 0xFF; +    vs->client_pf.rbits = hweight_long(red_max); +    vs->client_pf.rshift = red_shift; +    vs->client_pf.rmask = red_max << red_shift; +    vs->client_pf.gmax = green_max ? green_max : 0xFF; +    vs->client_pf.gbits = hweight_long(green_max); +    vs->client_pf.gshift = green_shift; +    vs->client_pf.gmask = green_max << green_shift; +    vs->client_pf.bmax = blue_max ? blue_max : 0xFF; +    vs->client_pf.bbits = hweight_long(blue_max); +    vs->client_pf.bshift = blue_shift; +    vs->client_pf.bmask = blue_max << blue_shift; +    vs->client_pf.bits_per_pixel = bits_per_pixel; +    vs->client_pf.bytes_per_pixel = bits_per_pixel / 8; +    vs->client_pf.depth = bits_per_pixel == 32 ? 24 : bits_per_pixel; +    vs->client_be = big_endian_flag; + +    set_pixel_conversion(vs); + +    graphic_hw_invalidate(vs->vd->dcl.con); +    graphic_hw_update(vs->vd->dcl.con); +} + +static void pixel_format_message (VncState *vs) { +    char pad[3] = { 0, 0, 0 }; + +    vs->client_pf = qemu_default_pixelformat(32); + +    vnc_write_u8(vs, vs->client_pf.bits_per_pixel); /* bits-per-pixel */ +    vnc_write_u8(vs, vs->client_pf.depth); /* depth */ + +#ifdef HOST_WORDS_BIGENDIAN +    vnc_write_u8(vs, 1);             /* big-endian-flag */ +#else +    vnc_write_u8(vs, 0);             /* big-endian-flag */ +#endif +    vnc_write_u8(vs, 1);             /* true-color-flag */ +    vnc_write_u16(vs, vs->client_pf.rmax);     /* red-max */ +    vnc_write_u16(vs, vs->client_pf.gmax);     /* green-max */ +    vnc_write_u16(vs, vs->client_pf.bmax);     /* blue-max */ +    vnc_write_u8(vs, vs->client_pf.rshift);    /* red-shift */ +    vnc_write_u8(vs, vs->client_pf.gshift);    /* green-shift */ +    vnc_write_u8(vs, vs->client_pf.bshift);    /* blue-shift */ +    vnc_write(vs, pad, 3);           /* padding */ + +    vnc_hextile_set_pixel_conversion(vs, 0); +    vs->write_pixels = vnc_write_pixels_copy; +} + +static void vnc_colordepth(VncState *vs) +{ +    if (vnc_has_feature(vs, VNC_FEATURE_WMVI)) { +        /* Sending a WMVi message to notify the client*/ +        vnc_lock_output(vs); +        vnc_write_u8(vs, VNC_MSG_SERVER_FRAMEBUFFER_UPDATE); +        vnc_write_u8(vs, 0); +        vnc_write_u16(vs, 1); /* number of rects */ +        vnc_framebuffer_update(vs, 0, 0, +                               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); +        vnc_flush(vs); +    } else { +        set_pixel_conversion(vs); +    } +} + +static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len) +{ +    int i; +    uint16_t limit; +    VncDisplay *vd = vs->vd; + +    if (data[0] > 3) { +        update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_BASE); +    } + +    switch (data[0]) { +    case VNC_MSG_CLIENT_SET_PIXEL_FORMAT: +        if (len == 1) +            return 20; + +        set_pixel_format(vs, read_u8(data, 4), read_u8(data, 5), +                         read_u8(data, 6), read_u8(data, 7), +                         read_u16(data, 8), read_u16(data, 10), +                         read_u16(data, 12), read_u8(data, 14), +                         read_u8(data, 15), read_u8(data, 16)); +        break; +    case VNC_MSG_CLIENT_SET_ENCODINGS: +        if (len == 1) +            return 4; + +        if (len == 4) { +            limit = read_u16(data, 2); +            if (limit > 0) +                return 4 + (limit * 4); +        } else +            limit = read_u16(data, 2); + +        for (i = 0; i < limit; i++) { +            int32_t val = read_s32(data, 4 + (i * 4)); +            memcpy(data + 4 + (i * 4), &val, sizeof(val)); +        } + +        set_encodings(vs, (int32_t *)(data + 4), limit); +        break; +    case VNC_MSG_CLIENT_FRAMEBUFFER_UPDATE_REQUEST: +        if (len == 1) +            return 10; + +        framebuffer_update_request(vs, +                                   read_u8(data, 1), read_u16(data, 2), read_u16(data, 4), +                                   read_u16(data, 6), read_u16(data, 8)); +        break; +    case VNC_MSG_CLIENT_KEY_EVENT: +        if (len == 1) +            return 8; + +        key_event(vs, read_u8(data, 1), read_u32(data, 4)); +        break; +    case VNC_MSG_CLIENT_POINTER_EVENT: +        if (len == 1) +            return 6; + +        pointer_event(vs, read_u8(data, 1), read_u16(data, 2), read_u16(data, 4)); +        break; +    case VNC_MSG_CLIENT_CUT_TEXT: +        if (len == 1) { +            return 8; +        } +        if (len == 8) { +            uint32_t dlen = read_u32(data, 4); +            if (dlen > (1 << 20)) { +                error_report("vnc: client_cut_text msg payload has %u bytes" +                             " which exceeds our limit of 1MB.", dlen); +                vnc_client_error(vs); +                break; +            } +            if (dlen > 0) { +                return 8 + dlen; +            } +        } + +        client_cut_text(vs, read_u32(data, 4), data + 8); +        break; +    case VNC_MSG_CLIENT_QEMU: +        if (len == 1) +            return 2; + +        switch (read_u8(data, 1)) { +        case VNC_MSG_CLIENT_QEMU_EXT_KEY_EVENT: +            if (len == 2) +                return 12; + +            ext_key_event(vs, read_u16(data, 2), +                          read_u32(data, 4), read_u32(data, 8)); +            break; +        case VNC_MSG_CLIENT_QEMU_AUDIO: +            if (len == 2) +                return 4; + +            switch (read_u16 (data, 2)) { +            case VNC_MSG_CLIENT_QEMU_AUDIO_ENABLE: +                audio_add(vs); +                break; +            case VNC_MSG_CLIENT_QEMU_AUDIO_DISABLE: +                audio_del(vs); +                break; +            case VNC_MSG_CLIENT_QEMU_AUDIO_SET_FORMAT: +                if (len == 4) +                    return 10; +                switch (read_u8(data, 4)) { +                case 0: vs->as.fmt = 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; +                default: +                    VNC_DEBUG("Invalid audio format %d\n", read_u8(data, 4)); +                    vnc_client_error(vs); +                    break; +                } +                vs->as.nchannels = read_u8(data, 5); +                if (vs->as.nchannels != 1 && vs->as.nchannels != 2) { +                    VNC_DEBUG("Invalid audio channel coount %d\n", +                              read_u8(data, 5)); +                    vnc_client_error(vs); +                    break; +                } +                vs->as.freq = read_u32(data, 6); +                break; +            default: +                VNC_DEBUG("Invalid audio message %d\n", read_u8(data, 4)); +                vnc_client_error(vs); +                break; +            } +            break; + +        default: +            VNC_DEBUG("Msg: %d\n", read_u16(data, 0)); +            vnc_client_error(vs); +            break; +        } +        break; +    default: +        VNC_DEBUG("Msg: %d\n", data[0]); +        vnc_client_error(vs); +        break; +    } + +    vnc_read_when(vs, protocol_client_msg, 1); +    return 0; +} + +static int protocol_client_init(VncState *vs, uint8_t *data, size_t len) +{ +    char buf[1024]; +    VncShareMode mode; +    int size; + +    mode = data[0] ? VNC_SHARE_MODE_SHARED : VNC_SHARE_MODE_EXCLUSIVE; +    switch (vs->vd->share_policy) { +    case VNC_SHARE_POLICY_IGNORE: +        /* +         * Ignore the shared flag.  Nothing to do here. +         * +         * Doesn't conform to the rfb spec but is traditional qemu +         * behavior, thus left here as option for compatibility +         * reasons. +         */ +        break; +    case VNC_SHARE_POLICY_ALLOW_EXCLUSIVE: +        /* +         * Policy: Allow clients ask for exclusive access. +         * +         * Implementation: When a client asks for exclusive access, +         * disconnect all others. Shared connects are allowed as long +         * as no exclusive connection exists. +         * +         * This is how the rfb spec suggests to handle the shared flag. +         */ +        if (mode == VNC_SHARE_MODE_EXCLUSIVE) { +            VncState *client; +            QTAILQ_FOREACH(client, &vs->vd->clients, next) { +                if (vs == client) { +                    continue; +                } +                if (client->share_mode != VNC_SHARE_MODE_EXCLUSIVE && +                    client->share_mode != VNC_SHARE_MODE_SHARED) { +                    continue; +                } +                vnc_disconnect_start(client); +            } +        } +        if (mode == VNC_SHARE_MODE_SHARED) { +            if (vs->vd->num_exclusive > 0) { +                vnc_disconnect_start(vs); +                return 0; +            } +        } +        break; +    case VNC_SHARE_POLICY_FORCE_SHARED: +        /* +         * Policy: Shared connects only. +         * Implementation: Disallow clients asking for exclusive access. +         * +         * Useful for shared desktop sessions where you don't want +         * someone forgetting to say -shared when running the vnc +         * client disconnect everybody else. +         */ +        if (mode == VNC_SHARE_MODE_EXCLUSIVE) { +            vnc_disconnect_start(vs); +            return 0; +        } +        break; +    } +    vnc_set_share_mode(vs, mode); + +    if (vs->vd->num_shared > vs->vd->connections_limit) { +        vnc_disconnect_start(vs); +        return 0; +    } + +    vs->client_width = pixman_image_get_width(vs->vd->server); +    vs->client_height = pixman_image_get_height(vs->vd->server); +    vnc_write_u16(vs, vs->client_width); +    vnc_write_u16(vs, vs->client_height); + +    pixel_format_message(vs); + +    if (qemu_name) +        size = snprintf(buf, sizeof(buf), "QEMU (%s)", qemu_name); +    else +        size = snprintf(buf, sizeof(buf), "QEMU"); + +    vnc_write_u32(vs, size); +    vnc_write(vs, buf, size); +    vnc_flush(vs); + +    vnc_client_cache_auth(vs); +    vnc_qmp_event(vs, QAPI_EVENT_VNC_INITIALIZED); + +    vnc_read_when(vs, protocol_client_msg, 1); + +    return 0; +} + +void start_client_init(VncState *vs) +{ +    vnc_read_when(vs, protocol_client_init, 1); +} + +static void make_challenge(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)); +} + +static int protocol_client_auth_vnc(VncState *vs, uint8_t *data, size_t len) +{ +    unsigned char response[VNC_AUTH_CHALLENGE_SIZE]; +    size_t i, pwlen; +    unsigned char key[8]; +    time_t now = time(NULL); +    QCryptoCipher *cipher = NULL; +    Error *err = NULL; + +    if (!vs->vd->password) { +        VNC_DEBUG("No password configured on server"); +        goto reject; +    } +    if (vs->vd->expires < now) { +        VNC_DEBUG("Password is expired"); +        goto reject; +    } + +    memcpy(response, vs->challenge, VNC_AUTH_CHALLENGE_SIZE); + +    /* Calculate the expected challenge response */ +    pwlen = strlen(vs->vd->password); +    for (i=0; i<sizeof(key); i++) +        key[i] = i<pwlen ? vs->vd->password[i] : 0; + +    cipher = qcrypto_cipher_new( +        QCRYPTO_CIPHER_ALG_DES_RFB, +        QCRYPTO_CIPHER_MODE_ECB, +        key, G_N_ELEMENTS(key), +        &err); +    if (!cipher) { +        VNC_DEBUG("Cannot initialize cipher %s", +                  error_get_pretty(err)); +        error_free(err); +        goto reject; +    } + +    if (qcrypto_cipher_encrypt(cipher, +                               vs->challenge, +                               response, +                               VNC_AUTH_CHALLENGE_SIZE, +                               &err) < 0) { +        VNC_DEBUG("Cannot encrypt challenge %s", +                  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"); +        goto reject; +    } else { +        VNC_DEBUG("Accepting VNC challenge response\n"); +        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); +    qcrypto_cipher_free(cipher); +    return 0; +} + +void start_auth_vnc(VncState *vs) +{ +    make_challenge(vs); +    /* Send client a 'random' challenge */ +    vnc_write(vs, vs->challenge, sizeof(vs->challenge)); +    vnc_flush(vs); + +    vnc_read_when(vs, protocol_client_auth_vnc, sizeof(vs->challenge)); +} + + +static int protocol_client_auth(VncState *vs, uint8_t *data, size_t len) +{ +    /* We only advertise 1 auth scheme at a time, so client +     * must pick the one we sent. Verify this */ +    if (data[0] != vs->auth) { /* Reject auth */ +       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); +    } else { /* Accept requested auth */ +       VNC_DEBUG("Client requested auth %d\n", (int)data[0]); +       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); +           } +           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); +       } +    } +    return 0; +} + +static int protocol_version(VncState *vs, uint8_t *version, size_t len) +{ +    char local[13]; + +    memcpy(local, version, 12); +    local[12] = 0; + +    if (sscanf(local, "RFB %03d.%03d\n", &vs->major, &vs->minor) != 2) { +        VNC_DEBUG("Malformed protocol version %s\n", local); +        vnc_client_error(vs); +        return 0; +    } +    VNC_DEBUG("Client request protocol version %d.%d\n", vs->major, vs->minor); +    if (vs->major != 3 || +        (vs->minor != 3 && +         vs->minor != 4 && +         vs->minor != 5 && +         vs->minor != 7 && +         vs->minor != 8)) { +        VNC_DEBUG("Unsupported client version\n"); +        vnc_write_u32(vs, VNC_AUTH_INVALID); +        vnc_flush(vs); +        vnc_client_error(vs); +        return 0; +    } +    /* Some broken clients report v3.4 or v3.5, which spec requires to be treated +     * as equivalent to v3.3 by servers +     */ +    if (vs->minor == 4 || vs->minor == 5) +        vs->minor = 3; + +    if (vs->minor == 3) { +        if (vs->auth == VNC_AUTH_NONE) { +            VNC_DEBUG("Tell client auth none\n"); +            vnc_write_u32(vs, vs->auth); +            vnc_flush(vs); +            start_client_init(vs); +       } else if (vs->auth == VNC_AUTH_VNC) { +            VNC_DEBUG("Tell client VNC auth\n"); +            vnc_write_u32(vs, vs->auth); +            vnc_flush(vs); +            start_auth_vnc(vs); +       } else { +            VNC_DEBUG("Unsupported auth %d for protocol 3.3\n", vs->auth); +            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); +        vnc_flush(vs); +    } + +    return 0; +} + +static VncRectStat *vnc_stat_rect(VncDisplay *vd, int x, int y) +{ +    struct VncSurface *vs = &vd->guest; + +    return &vs->stats[y / VNC_STAT_RECT][x / VNC_STAT_RECT]; +} + +void vnc_sent_lossy_rect(VncState *vs, int x, int y, int w, int h) +{ +    int i, j; + +    w = (x + w) / VNC_STAT_RECT; +    h = (y + h) / VNC_STAT_RECT; +    x /= VNC_STAT_RECT; +    y /= VNC_STAT_RECT; + +    for (j = y; j <= h; j++) { +        for (i = x; i <= w; i++) { +            vs->lossy_rect[j][i] = 1; +        } +    } +} + +static int vnc_refresh_lossy_rect(VncDisplay *vd, int x, int y) +{ +    VncState *vs; +    int sty = y / VNC_STAT_RECT; +    int stx = x / VNC_STAT_RECT; +    int has_dirty = 0; + +    y = y / VNC_STAT_RECT * VNC_STAT_RECT; +    x = x / VNC_STAT_RECT * VNC_STAT_RECT; + +    QTAILQ_FOREACH(vs, &vd->clients, next) { +        int j; + +        /* kernel send buffers are full -> refresh later */ +        if (vs->output.offset) { +            continue; +        } + +        if (!vs->lossy_rect[sty][stx]) { +            continue; +        } + +        vs->lossy_rect[sty][stx] = 0; +        for (j = 0; j < VNC_STAT_RECT; ++j) { +            bitmap_set(vs->dirty[y + j], +                       x / VNC_DIRTY_PIXELS_PER_BIT, +                       VNC_STAT_RECT / VNC_DIRTY_PIXELS_PER_BIT); +        } +        has_dirty++; +    } + +    return has_dirty; +} + +static int vnc_update_stats(VncDisplay *vd,  struct timeval * tv) +{ +    int width = pixman_image_get_width(vd->guest.fb); +    int height = pixman_image_get_height(vd->guest.fb); +    int x, y; +    struct timeval res; +    int has_dirty = 0; + +    for (y = 0; y < height; y += VNC_STAT_RECT) { +        for (x = 0; x < width; x += VNC_STAT_RECT) { +            VncRectStat *rect = vnc_stat_rect(vd, x, y); + +            rect->updated = false; +        } +    } + +    qemu_timersub(tv, &VNC_REFRESH_STATS, &res); + +    if (timercmp(&vd->guest.last_freq_check, &res, >)) { +        return has_dirty; +    } +    vd->guest.last_freq_check = *tv; + +    for (y = 0; y < height; y += VNC_STAT_RECT) { +        for (x = 0; x < width; x += VNC_STAT_RECT) { +            VncRectStat *rect= vnc_stat_rect(vd, x, y); +            int count = ARRAY_SIZE(rect->times); +            struct timeval min, max; + +            if (!timerisset(&rect->times[count - 1])) { +                continue ; +            } + +            max = rect->times[(rect->idx + count - 1) % count]; +            qemu_timersub(tv, &max, &res); + +            if (timercmp(&res, &VNC_REFRESH_LOSSY, >)) { +                rect->freq = 0; +                has_dirty += vnc_refresh_lossy_rect(vd, x, y); +                memset(rect->times, 0, sizeof (rect->times)); +                continue ; +            } + +            min = rect->times[rect->idx]; +            max = rect->times[(rect->idx + count - 1) % count]; +            qemu_timersub(&max, &min, &res); + +            rect->freq = res.tv_sec + res.tv_usec / 1000000.; +            rect->freq /= count; +            rect->freq = 1. / rect->freq; +        } +    } +    return has_dirty; +} + +double vnc_update_freq(VncState *vs, int x, int y, int w, int h) +{ +    int i, j; +    double total = 0; +    int num = 0; + +    x =  (x / VNC_STAT_RECT) * VNC_STAT_RECT; +    y =  (y / VNC_STAT_RECT) * VNC_STAT_RECT; + +    for (j = y; j <= y + h; j += VNC_STAT_RECT) { +        for (i = x; i <= x + w; i += VNC_STAT_RECT) { +            total += vnc_stat_rect(vs->vd, i, j)->freq; +            num++; +        } +    } + +    if (num) { +        return total / num; +    } else { +        return 0; +    } +} + +static void vnc_rect_updated(VncDisplay *vd, int x, int y, struct timeval * tv) +{ +    VncRectStat *rect; + +    rect = vnc_stat_rect(vd, x, y); +    if (rect->updated) { +        return ; +    } +    rect->times[rect->idx] = *tv; +    rect->idx = (rect->idx + 1) % ARRAY_SIZE(rect->times); +    rect->updated = true; +} + +static int vnc_refresh_server_surface(VncDisplay *vd) +{ +    int width = MIN(pixman_image_get_width(vd->guest.fb), +                    pixman_image_get_width(vd->server)); +    int height = MIN(pixman_image_get_height(vd->guest.fb), +                     pixman_image_get_height(vd->server)); +    int cmp_bytes, server_stride, line_bytes, guest_ll, guest_stride, y = 0; +    uint8_t *guest_row0 = NULL, *server_row0; +    VncState *vs; +    int has_dirty = 0; +    pixman_image_t *tmpbuf = NULL; + +    struct timeval tv = { 0, 0 }; + +    if (!vd->non_adaptive) { +        gettimeofday(&tv, NULL); +        has_dirty = vnc_update_stats(vd, &tv); +    } + +    /* +     * Walk through the guest dirty map. +     * Check and copy modified bits from guest to server surface. +     * Update server dirty map. +     */ +    server_row0 = (uint8_t *)pixman_image_get_data(vd->server); +    server_stride = guest_stride = guest_ll = +        pixman_image_get_stride(vd->server); +    cmp_bytes = MIN(VNC_DIRTY_PIXELS_PER_BIT * VNC_SERVER_FB_BYTES, +                    server_stride); +    if (vd->guest.format != VNC_SERVER_FB_FORMAT) { +        int width = pixman_image_get_width(vd->server); +        tmpbuf = qemu_pixman_linebuf_create(VNC_SERVER_FB_FORMAT, width); +    } else { +        int guest_bpp = +            PIXMAN_FORMAT_BPP(pixman_image_get_format(vd->guest.fb)); +        guest_row0 = (uint8_t *)pixman_image_get_data(vd->guest.fb); +        guest_stride = pixman_image_get_stride(vd->guest.fb); +        guest_ll = pixman_image_get_width(vd->guest.fb) * ((guest_bpp + 7) / 8); +    } +    line_bytes = MIN(server_stride, guest_ll); + +    for (;;) { +        int x; +        uint8_t *guest_ptr, *server_ptr; +        unsigned long offset = find_next_bit((unsigned long *) &vd->guest.dirty, +                                             height * VNC_DIRTY_BPL(&vd->guest), +                                             y * VNC_DIRTY_BPL(&vd->guest)); +        if (offset == height * VNC_DIRTY_BPL(&vd->guest)) { +            /* no more dirty bits */ +            break; +        } +        y = offset / VNC_DIRTY_BPL(&vd->guest); +        x = offset % VNC_DIRTY_BPL(&vd->guest); + +        server_ptr = server_row0 + y * server_stride + x * cmp_bytes; + +        if (vd->guest.format != VNC_SERVER_FB_FORMAT) { +            qemu_pixman_linebuf_fill(tmpbuf, vd->guest.fb, width, 0, y); +            guest_ptr = (uint8_t *)pixman_image_get_data(tmpbuf); +        } else { +            guest_ptr = guest_row0 + y * guest_stride; +        } +        guest_ptr += x * cmp_bytes; + +        for (; x < DIV_ROUND_UP(width, VNC_DIRTY_PIXELS_PER_BIT); +             x++, guest_ptr += cmp_bytes, server_ptr += cmp_bytes) { +            int _cmp_bytes = cmp_bytes; +            if (!test_and_clear_bit(x, vd->guest.dirty[y])) { +                continue; +            } +            if ((x + 1) * cmp_bytes > line_bytes) { +                _cmp_bytes = line_bytes - x * cmp_bytes; +            } +            assert(_cmp_bytes >= 0); +            if (memcmp(server_ptr, guest_ptr, _cmp_bytes) == 0) { +                continue; +            } +            memcpy(server_ptr, guest_ptr, _cmp_bytes); +            if (!vd->non_adaptive) { +                vnc_rect_updated(vd, x * VNC_DIRTY_PIXELS_PER_BIT, +                                 y, &tv); +            } +            QTAILQ_FOREACH(vs, &vd->clients, next) { +                set_bit(x, vs->dirty[y]); +            } +            has_dirty++; +        } + +        y++; +    } +    qemu_pixman_image_unref(tmpbuf); +    return has_dirty; +} + +static void vnc_refresh(DisplayChangeListener *dcl) +{ +    VncDisplay *vd = container_of(dcl, VncDisplay, dcl); +    VncState *vs, *vn; +    int has_dirty, rects = 0; + +    if (QTAILQ_EMPTY(&vd->clients)) { +        update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_MAX); +        return; +    } + +    graphic_hw_update(vd->dcl.con); + +    if (vnc_trylock_display(vd)) { +        update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_BASE); +        return; +    } + +    has_dirty = vnc_refresh_server_surface(vd); +    vnc_unlock_display(vd); + +    QTAILQ_FOREACH_SAFE(vs, &vd->clients, next, vn) { +        rects += vnc_update_client(vs, has_dirty, false); +        /* vs might be free()ed here */ +    } + +    if (has_dirty && rects) { +        vd->dcl.update_interval /= 2; +        if (vd->dcl.update_interval < VNC_REFRESH_INTERVAL_BASE) { +            vd->dcl.update_interval = VNC_REFRESH_INTERVAL_BASE; +        } +    } else { +        vd->dcl.update_interval += VNC_REFRESH_INTERVAL_INC; +        if (vd->dcl.update_interval > VNC_REFRESH_INTERVAL_MAX) { +            vd->dcl.update_interval = VNC_REFRESH_INTERVAL_MAX; +        } +    } +} + +static void vnc_connect(VncDisplay *vd, int csock, +                        bool skipauth, bool websocket) +{ +    VncState *vs = g_malloc0(sizeof(VncState)); +    int i; + +    vs->csock = csock; +    vs->vd = vd; + +    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 sock=%d ws=%d auth=%d subauth=%d\n", +              csock, 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)); +    } + +    VNC_DEBUG("New client on socket %d\n", csock); +    update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_BASE); +    qemu_set_nonblock(vs->csock); +    if (websocket) { +        vs->websocket = 1; +#ifdef CONFIG_VNC_TLS +        if (vd->ws_tls) { +            qemu_set_fd_handler(vs->csock, vncws_tls_handshake_io, NULL, vs); +        } else +#endif /* CONFIG_VNC_TLS */ +        { +            qemu_set_fd_handler(vs->csock, vncws_handshake_read, NULL, vs); +        } +    } else +    { +        qemu_set_fd_handler(vs->csock, vnc_client_read, NULL, vs); +    } + +    vnc_client_cache_addr(vs); +    vnc_qmp_event(vs, QAPI_EVENT_VNC_CONNECTED); +    vnc_set_share_mode(vs, VNC_SHARE_MODE_CONNECTING); + +    if (!vs->websocket) { +        vnc_init_state(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_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.endianness = 0; + +    qemu_mutex_init(&vs->output_mutex); +    vs->bh = qemu_bh_new(vnc_jobs_bh, vs); + +    QTAILQ_INSERT_TAIL(&vd->clients, vs, next); + +    graphic_hw_update(vd->dcl.con); + +    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); + +    /* vs might be free()ed here */ +} + +static void vnc_listen_read(void *opaque, bool websocket) +{ +    VncDisplay *vs = opaque; +    struct sockaddr_in addr; +    socklen_t addrlen = sizeof(addr); +    int csock; + +    /* Catch-up */ +    graphic_hw_update(vs->dcl.con); +    if (websocket) { +        csock = qemu_accept(vs->lwebsock, (struct sockaddr *)&addr, &addrlen); +    } else { +        csock = qemu_accept(vs->lsock, (struct sockaddr *)&addr, &addrlen); +    } + +    if (csock != -1) { +        socket_set_nodelay(csock); +        vnc_connect(vs, csock, false, websocket); +    } +} + +static void vnc_listen_regular_read(void *opaque) +{ +    vnc_listen_read(opaque, false); +} + +static void vnc_listen_websocket_read(void *opaque) +{ +    vnc_listen_read(opaque, true); +} + +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_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) +{ +    VncDisplay *vs; + +    if (vnc_display_find(id) != NULL) { +        return; +    } +    vs = g_malloc0(sizeof(*vs)); + +    vs->id = strdup(id); +    QTAILQ_INSERT_TAIL(&vnc_displays, vs, next); + +    vs->lsock = -1; +    vs->lwebsock = -1; + +    QTAILQ_INIT(&vs->clients); +    vs->expires = TIME_MAX; + +    if (keyboard_layout) { +        trace_vnc_key_map_init(keyboard_layout); +        vs->kbd_layout = init_keyboard_layout(name2keysym, keyboard_layout); +    } else { +        vs->kbd_layout = init_keyboard_layout(name2keysym, "en-us"); +    } + +    if (!vs->kbd_layout) +        exit(1); + +    qemu_mutex_init(&vs->mutex); +    vnc_start_worker_thread(); + +    vs->dcl.ops = &dcl_ops; +    register_displaychangelistener(&vs->dcl); +} + + +static void vnc_display_close(VncDisplay *vs) +{ +    if (!vs) +        return; +    vs->enabled = false; +    vs->is_unix = false; +    if (vs->lsock != -1) { +        qemu_set_fd_handler(vs->lsock, NULL, NULL, NULL); +        close(vs->lsock); +        vs->lsock = -1; +    } +    vs->ws_enabled = false; +    if (vs->lwebsock != -1) { +        qemu_set_fd_handler(vs->lwebsock, NULL, NULL, NULL); +        close(vs->lwebsock); +        vs->lwebsock = -1; +    } +    vs->auth = VNC_AUTH_INVALID; +    vs->subauth = VNC_AUTH_INVALID; +#ifdef CONFIG_VNC_TLS +    vs->tls.x509verify = 0; +#endif +} + +int vnc_display_password(const char *id, const char *password) +{ +    VncDisplay *vs = vnc_display_find(id); + +    if (!vs) { +        return -EINVAL; +    } +    if (vs->auth == VNC_AUTH_NONE) { +        error_printf_unless_qmp("If you want use passwords please enable " +                                "password auth using '-vnc ${dpy},password'."); +        return -EINVAL; +    } + +    g_free(vs->password); +    vs->password = g_strdup(password); + +    return 0; +} + +int vnc_display_pw_expire(const char *id, time_t expires) +{ +    VncDisplay *vs = vnc_display_find(id); + +    if (!vs) { +        return -EINVAL; +    } + +    vs->expires = expires; +    return 0; +} + +char *vnc_display_local_addr(const char *id) +{ +    VncDisplay *vs = vnc_display_find(id); + +    assert(vs); +    return vnc_socket_local_addr("%s:%s", vs->lsock); +} + +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 = "x509", +            .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 = "sasl", +            .type = QEMU_OPT_BOOL, +        },{ +            .name = "tls", +            .type = QEMU_OPT_BOOL, +        },{ +            .name = "x509verify", +            .type = QEMU_OPT_STRING, +        },{ +            .name = "acl", +            .type = QEMU_OPT_BOOL, +        },{ +            .name = "lossy", +            .type = QEMU_OPT_BOOL, +        },{ +            .name = "non-adaptive", +            .type = QEMU_OPT_BOOL, +        }, +        { /* end of list */ } +    }, +}; + + +static void +vnc_display_setup_auth(VncDisplay *vs, +                       bool password, +                       bool sasl, +                       bool tls, +                       bool x509, +                       bool websocket) +{ +    /* +     * 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 (password) { +        if (tls) { +            vs->auth = VNC_AUTH_VENCRYPT; +            if (websocket) { +                vs->ws_tls = true; +            } +            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; +            } +        } else { +            VNC_DEBUG("Initializing VNC server with password auth\n"); +            vs->auth = VNC_AUTH_VNC; +            vs->subauth = VNC_AUTH_INVALID; +        } +        if (websocket) { +            vs->ws_auth = VNC_AUTH_VNC; +        } else { +            vs->ws_auth = VNC_AUTH_INVALID; +        } +    } else if (sasl) { +        if (tls) { +            vs->auth = VNC_AUTH_VENCRYPT; +            if (websocket) { +                vs->ws_tls = true; +            } +            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 { +            VNC_DEBUG("Initializing VNC server with SASL auth\n"); +            vs->auth = VNC_AUTH_SASL; +            vs->subauth = VNC_AUTH_INVALID; +        } +        if (websocket) { +            vs->ws_auth = VNC_AUTH_SASL; +        } else { +            vs->ws_auth = VNC_AUTH_INVALID; +        } +    } else { +        if (tls) { +            vs->auth = VNC_AUTH_VENCRYPT; +            if (websocket) { +                vs->ws_tls = true; +            } +            if (x509) { +                VNC_DEBUG("Initializing VNC server with x509 no auth\n"); +                vs->subauth = VNC_AUTH_VENCRYPT_X509NONE; +            } else { +                VNC_DEBUG("Initializing VNC server with TLS no auth\n"); +                vs->subauth = VNC_AUTH_VENCRYPT_TLSNONE; +            } +        } else { +            VNC_DEBUG("Initializing VNC server with no auth\n"); +            vs->auth = VNC_AUTH_NONE; +            vs->subauth = VNC_AUTH_INVALID; +        } +        if (websocket) { +            vs->ws_auth = VNC_AUTH_NONE; +        } else { +            vs->ws_auth = VNC_AUTH_INVALID; +        } +    } +} + +void vnc_display_open(const char *id, Error **errp) +{ +    VncDisplay *vs = vnc_display_find(id); +    QemuOpts *opts = qemu_opts_find(&qemu_vnc_opts, id); +    QemuOpts *sopts, *wsopts; +    const char *share, *device_id; +    QemuConsole *con; +    bool password = false; +    bool reverse = false; +    const char *vnc; +    const char *has_to; +    char *h; +    bool has_ipv4 = false; +    bool has_ipv6 = false; +    const char *websocket; +    bool tls = false, x509 = false; +#ifdef CONFIG_VNC_TLS +    const char *path; +#endif +    bool sasl = false; +#ifdef CONFIG_VNC_SASL +    int saslErr; +#endif +#if defined(CONFIG_VNC_TLS) || defined(CONFIG_VNC_SASL) +    int acl = 0; +#endif +    int lock_key_sync = 1; + +    if (!vs) { +        error_setg(errp, "VNC display not active"); +        return; +    } +    vnc_display_close(vs); + +    if (!opts) { +        return; +    } +    vnc = qemu_opt_get(opts, "vnc"); +    if (!vnc || strcmp(vnc, "none") == 0) { +        return; +    } + +    sopts = qemu_opts_create(&socket_optslist, NULL, 0, &error_abort); +    wsopts = qemu_opts_create(&socket_optslist, NULL, 0, &error_abort); + +    h = strrchr(vnc, ':'); +    if (h) { +        char *host; +        size_t hlen = h - vnc; + +        if (vnc[0] == '[' && vnc[hlen - 1] == ']') { +            host = g_strndup(vnc + 1, hlen - 2); +        } else { +            host = g_strndup(vnc, hlen); +        } +        qemu_opt_set(sopts, "host", host, &error_abort); +        qemu_opt_set(wsopts, "host", host, &error_abort); +        qemu_opt_set(sopts, "port", h+1, &error_abort); +        g_free(host); +    } else { +        error_setg(errp, "no vnc port specified"); +        goto fail; +    } + +    has_to = qemu_opt_get(opts, "to"); +    has_ipv4 = qemu_opt_get_bool(opts, "ipv4", false); +    has_ipv6 = qemu_opt_get_bool(opts, "ipv6", false); +    if (has_to) { +        qemu_opt_set(sopts, "to", has_to, &error_abort); +        qemu_opt_set(wsopts, "to", has_to, &error_abort); +    } +    if (has_ipv4) { +        qemu_opt_set(sopts, "ipv4", "on", &error_abort); +        qemu_opt_set(wsopts, "ipv4", "on", &error_abort); +    } +    if (has_ipv6) { +        qemu_opt_set(sopts, "ipv6", "on", &error_abort); +        qemu_opt_set(wsopts, "ipv6", "on", &error_abort); +    } + +    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)) { +            error_setg(errp, +                       "Cipher backend does not support DES RFB algorithm"); +            goto fail; +        } +    } + +    reverse = qemu_opt_get_bool(opts, "reverse", false); +    lock_key_sync = qemu_opt_get_bool(opts, "lock-key-sync", true); +    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 */ +    tls  = qemu_opt_get_bool(opts, "tls", false); +#ifdef CONFIG_VNC_TLS +    path = qemu_opt_get(opts, "x509"); +    if (!path) { +        path = qemu_opt_get(opts, "x509verify"); +        if (path) { +            vs->tls.x509verify = true; +        } +    } +    if (path) { +        x509 = true; +        if (vnc_tls_set_x509_creds_dir(vs, path) < 0) { +            error_setg(errp, "Failed to find x509 certificates/keys in %s", +                       path); +            goto fail; +        } +    } +#else /* ! CONFIG_VNC_TLS */ +    if (tls) { +        error_setg(errp, "VNC TLS auth requires gnutls support"); +        goto fail; +    } +#endif /* ! CONFIG_VNC_TLS */ +#if defined(CONFIG_VNC_TLS) || defined(CONFIG_VNC_SASL) +    acl = qemu_opt_get_bool(opts, "acl", false); +#endif + +    share = qemu_opt_get(opts, "share"); +    if (share) { +        if (strcmp(share, "ignore") == 0) { +            vs->share_policy = VNC_SHARE_POLICY_IGNORE; +        } else if (strcmp(share, "allow-exclusive") == 0) { +            vs->share_policy = VNC_SHARE_POLICY_ALLOW_EXCLUSIVE; +        } else if (strcmp(share, "force-shared") == 0) { +            vs->share_policy = VNC_SHARE_POLICY_FORCE_SHARED; +        } else { +            error_setg(errp, "unknown vnc share= option"); +            goto fail; +        } +    } else { +        vs->share_policy = VNC_SHARE_POLICY_ALLOW_EXCLUSIVE; +    } +    vs->connections_limit = qemu_opt_get_number(opts, "connections", 32); + +    websocket = qemu_opt_get(opts, "websocket"); +    if (websocket) { +        vs->ws_enabled = true; +        qemu_opt_set(wsopts, "port", websocket, &error_abort); +        if (!qcrypto_hash_supports(QCRYPTO_HASH_ALG_SHA1)) { +            error_setg(errp, "SHA1 hash support is required for websockets"); +            goto fail; +        } +    } + +#ifdef CONFIG_VNC_JPEG +    vs->lossy = qemu_opt_get_bool(opts, "lossy", false); +#endif +    vs->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 (!vs->lossy) { +        vs->non_adaptive = true; +    } + +#ifdef CONFIG_VNC_TLS +    if (acl && x509 && vs->tls.x509verify) { +        char *aclname; + +        if (strcmp(vs->id, "default") == 0) { +            aclname = g_strdup("vnc.x509dname"); +        } else { +            aclname = g_strdup_printf("vnc.%s.x509dname", vs->id); +        } +        vs->tls.acl = qemu_acl_init(aclname); +        g_free(aclname); +    } +#endif +#ifdef CONFIG_VNC_SASL +    if (acl && sasl) { +        char *aclname; + +        if (strcmp(vs->id, "default") == 0) { +            aclname = g_strdup("vnc.username"); +        } else { +            aclname = g_strdup_printf("vnc.%s.username", vs->id); +        } +        vs->sasl.acl = qemu_acl_init(aclname); +        g_free(aclname); +    } +#endif + +    vnc_display_setup_auth(vs, password, sasl, tls, x509, websocket); + +#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)); +        goto fail; +    } +#endif +    vs->lock_key_sync = lock_key_sync; + +    device_id = qemu_opt_get(opts, "display"); +    if (device_id) { +        DeviceState *dev; +        int head = qemu_opt_get_number(opts, "head", 0); + +        dev = qdev_find_recursive(sysbus_get_default(), device_id); +        if (dev == NULL) { +            error_setg(errp, "Device '%s' not found", device_id); +            goto fail; +        } + +        con = qemu_console_lookup_by_device(dev, head); +        if (con == NULL) { +            error_setg(errp, "Device %s is not bound to a QemuConsole", +                       device_id); +            goto fail; +        } +    } else { +        con = NULL; +    } + +    if (con != vs->dcl.con) { +        unregister_displaychangelistener(&vs->dcl); +        vs->dcl.con = con; +        register_displaychangelistener(&vs->dcl); +    } + +    if (reverse) { +        /* connect to viewer */ +        int csock; +        vs->lsock = -1; +        vs->lwebsock = -1; +        if (strncmp(vnc, "unix:", 5) == 0) { +            csock = unix_connect(vnc+5, errp); +        } else { +            csock = inet_connect(vnc, errp); +        } +        if (csock < 0) { +            goto fail; +        } +        vnc_connect(vs, csock, false, false); +    } else { +        /* listen for connects */ +        if (strncmp(vnc, "unix:", 5) == 0) { +            vs->lsock = unix_listen(vnc+5, NULL, 0, errp); +            if (vs->lsock < 0) { +                goto fail; +            } +            vs->is_unix = true; +        } else { +            vs->lsock = inet_listen_opts(sopts, 5900, errp); +            if (vs->lsock < 0) { +                goto fail; +            } +            if (vs->ws_enabled) { +                vs->lwebsock = inet_listen_opts(wsopts, 0, errp); +                if (vs->lwebsock < 0) { +                    if (vs->lsock != -1) { +                        close(vs->lsock); +                        vs->lsock = -1; +                    } +                    goto fail; +                } +            } +        } +        vs->enabled = true; +        qemu_set_fd_handler(vs->lsock, vnc_listen_regular_read, NULL, vs); +        if (vs->ws_enabled) { +            qemu_set_fd_handler(vs->lwebsock, vnc_listen_websocket_read, +                                NULL, vs); +        } +    } +    qemu_opts_del(sopts); +    qemu_opts_del(wsopts); +    return; + +fail: +    qemu_opts_del(sopts); +    qemu_opts_del(wsopts); +    vs->enabled = false; +    vs->ws_enabled = false; +} + +void vnc_display_add_client(const char *id, int csock, bool skipauth) +{ +    VncDisplay *vs = vnc_display_find(id); + +    if (!vs) { +        return; +    } +    vnc_connect(vs, csock, skipauth, false); +} + +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); +    vnc_display_open(id, &local_err); +    if (local_err != NULL) { +        error_report("Failed to start VNC server: %s", +                     error_get_pretty(local_err)); +        error_free(local_err); +        exit(1); +    } +    return 0; +} + +static void vnc_register_config(void) +{ +    qemu_add_opts(&qemu_vnc_opts); +} +machine_init(vnc_register_config); diff --git a/ui/vnc.h b/ui/vnc.h new file mode 100644 index 00000000..814d720d --- /dev/null +++ b/ui/vnc.h @@ -0,0 +1,598 @@ +/* + * QEMU VNC display driver + * + * Copyright (C) 2006 Anthony Liguori <anthony@codemonkey.ws> + * Copyright (C) 2006 Fabrice Bellard + * Copyright (C) 2009 Red Hat, Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifndef __QEMU_VNC_H +#define __QEMU_VNC_H + +#include "qemu-common.h" +#include "qemu/queue.h" +#include "qemu/thread.h" +#include "ui/console.h" +#include "audio/audio.h" +#include "qemu/bitmap.h" +#include <zlib.h> +#include <stdbool.h> + +#include "keymaps.h" +#include "vnc-palette.h" +#include "vnc-enc-zrle.h" +#include "qapi-types.h" + +// #define _VNC_DEBUG 1 + +#ifdef _VNC_DEBUG +#define VNC_DEBUG(fmt, ...) do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0) +#else +#define VNC_DEBUG(fmt, ...) do { } while (0) +#endif + +/***************************************************************************** + * + * Core data structures + * + *****************************************************************************/ + +typedef struct Buffer +{ +    size_t capacity; +    size_t offset; +    uint8_t *buffer; +} Buffer; + +typedef struct VncState VncState; +typedef struct VncJob VncJob; +typedef struct VncRect VncRect; +typedef struct VncRectEntry VncRectEntry; + +typedef int VncReadEvent(VncState *vs, uint8_t *data, size_t len); + +typedef void VncWritePixels(VncState *vs, void *data, int size); + +typedef void VncSendHextileTile(VncState *vs, +                                int x, int y, int w, int h, +                                void *last_bg, +                                void *last_fg, +                                int *has_bg, int *has_fg); + +/* VNC_DIRTY_PIXELS_PER_BIT is the number of dirty pixels represented + * by one bit in the dirty bitmap, should be a power of 2 */ +#define VNC_DIRTY_PIXELS_PER_BIT 16 + +/* VNC_MAX_WIDTH must be a multiple of VNC_DIRTY_PIXELS_PER_BIT. */ + +#define VNC_MAX_WIDTH ROUND_UP(2560, VNC_DIRTY_PIXELS_PER_BIT) +#define VNC_MAX_HEIGHT 2048 + +/* VNC_DIRTY_BITS is the number of bits in the dirty bitmap. */ +#define VNC_DIRTY_BITS (VNC_MAX_WIDTH / VNC_DIRTY_PIXELS_PER_BIT) + +/* VNC_DIRTY_BPL (BPL = bits per line) might be greater than + * VNC_DIRTY_BITS due to alignment */ +#define VNC_DIRTY_BPL(x) (sizeof((x)->dirty) / VNC_MAX_HEIGHT * BITS_PER_BYTE) + +#define VNC_STAT_RECT  64 +#define VNC_STAT_COLS (VNC_MAX_WIDTH / VNC_STAT_RECT) +#define VNC_STAT_ROWS (VNC_MAX_HEIGHT / VNC_STAT_RECT) + +#define VNC_AUTH_CHALLENGE_SIZE 16 + +typedef struct VncDisplay VncDisplay; + +#ifdef CONFIG_VNC_TLS +#include "vnc-tls.h" +#include "vnc-auth-vencrypt.h" +#endif +#ifdef CONFIG_VNC_SASL +#include "vnc-auth-sasl.h" +#endif +#include "vnc-ws.h" + +struct VncRectStat +{ +    /* time of last 10 updates, to find update frequency */ +    struct timeval times[10]; +    int idx; + +    double freq;        /* Update frequency (in Hz) */ +    bool updated;       /* Already updated during this refresh */ +}; + +typedef struct VncRectStat VncRectStat; + +struct VncSurface +{ +    struct timeval last_freq_check; +    DECLARE_BITMAP(dirty[VNC_MAX_HEIGHT], +                   VNC_MAX_WIDTH / VNC_DIRTY_PIXELS_PER_BIT); +    VncRectStat stats[VNC_STAT_ROWS][VNC_STAT_COLS]; +    pixman_image_t *fb; +    pixman_format_code_t format; +}; + +typedef enum VncShareMode { +    VNC_SHARE_MODE_CONNECTING = 1, +    VNC_SHARE_MODE_SHARED, +    VNC_SHARE_MODE_EXCLUSIVE, +    VNC_SHARE_MODE_DISCONNECTED, +} VncShareMode; + +typedef enum VncSharePolicy { +    VNC_SHARE_POLICY_IGNORE = 1, +    VNC_SHARE_POLICY_ALLOW_EXCLUSIVE, +    VNC_SHARE_POLICY_FORCE_SHARED, +} VncSharePolicy; + +struct VncDisplay +{ +    QTAILQ_HEAD(, VncState) clients; +    int num_connecting; +    int num_shared; +    int num_exclusive; +    int connections_limit; +    VncSharePolicy share_policy; +    int lsock; +    int lwebsock; +    bool ws_enabled; +    DisplaySurface *ds; +    DisplayChangeListener dcl; +    kbd_layout_t *kbd_layout; +    int lock_key_sync; +    QemuMutex mutex; + +    QEMUCursor *cursor; +    int cursor_msize; +    uint8_t *cursor_mask; + +    struct VncSurface guest;   /* guest visible surface (aka ds->surface) */ +    pixman_image_t *server;    /* vnc server surface */ + +    const char *id; +    QTAILQ_ENTRY(VncDisplay) next; +    bool enabled; +    bool is_unix; +    char *password; +    time_t expires; +    int auth; +    int subauth; /* Used by VeNCrypt */ +    int ws_auth; /* Used by websockets */ +    bool ws_tls; /* Used by websockets */ +    bool lossy; +    bool non_adaptive; +#ifdef CONFIG_VNC_TLS +    VncDisplayTLS tls; +#endif +#ifdef CONFIG_VNC_SASL +    VncDisplaySASL sasl; +#endif +}; + +typedef struct VncTight { +    int type; +    uint8_t quality; +    uint8_t compression; +    uint8_t pixel24; +    Buffer tight; +    Buffer tmp; +    Buffer zlib; +    Buffer gradient; +#ifdef CONFIG_VNC_JPEG +    Buffer jpeg; +#endif +#ifdef CONFIG_VNC_PNG +    Buffer png; +#endif +    int levels[4]; +    z_stream stream[4]; +} VncTight; + +typedef struct VncHextile { +    VncSendHextileTile *send_tile; +} VncHextile; + +typedef struct VncZlib { +    Buffer zlib; +    Buffer tmp; +    z_stream stream; +    int level; +} VncZlib; + +typedef struct VncZrle { +    int type; +    Buffer fb; +    Buffer zrle; +    Buffer tmp; +    Buffer zlib; +    z_stream stream; +    VncPalette palette; +} VncZrle; + +typedef struct VncZywrle { +    int buf[VNC_ZRLE_TILE_WIDTH * VNC_ZRLE_TILE_HEIGHT]; +} VncZywrle; + +struct VncRect +{ +    int x; +    int y; +    int w; +    int h; +}; + +struct VncRectEntry +{ +    struct VncRect rect; +    QLIST_ENTRY(VncRectEntry) next; +}; + +struct VncJob +{ +    VncState *vs; + +    QLIST_HEAD(, VncRectEntry) rectangles; +    QTAILQ_ENTRY(VncJob) next; +}; + +struct VncState +{ +    int csock; + +    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; +    int has_dirty; +    uint32_t features; +    int absolute; +    int last_x; +    int last_y; +    uint32_t last_bmask; +    int client_width; +    int client_height; +    VncShareMode share_mode; + +    uint32_t vnc_encoding; + +    int major; +    int minor; + +    int auth; +    int subauth; /* Used by VeNCrypt */ +    char challenge[VNC_AUTH_CHALLENGE_SIZE]; +#ifdef CONFIG_VNC_TLS +    VncStateTLS tls; +#endif +#ifdef CONFIG_VNC_SASL +    VncStateSASL sasl; +#endif +    bool encode_ws; +    bool websocket; + +    VncClientInfo *info; + +    Buffer output; +    Buffer input; +    Buffer ws_input; +    Buffer ws_output; +    size_t ws_payload_remain; +    WsMask ws_payload_mask; +    /* current output mode information */ +    VncWritePixels *write_pixels; +    PixelFormat client_pf; +    pixman_format_code_t client_format; +    bool client_be; + +    CaptureVoiceOut *audio_cap; +    struct audsettings as; + +    VncReadEvent *read_handler; +    size_t read_handler_expect; +    /* input */ +    uint8_t modifiers_state[256]; +    QEMUPutLEDEntry *led; + +    bool abort; +    bool initialized; +    QemuMutex output_mutex; +    QEMUBH *bh; +    Buffer jobs_buffer; + +    /* Encoding specific, if you add something here, don't forget to +     *  update vnc_async_encoding_start() +     */ +    VncTight tight; +    VncZlib zlib; +    VncHextile hextile; +    VncZrle zrle; +    VncZywrle zywrle; + +    Notifier mouse_mode_notifier; + +    QTAILQ_ENTRY(VncState) next; +}; + + +/***************************************************************************** + * + * Authentication modes + * + *****************************************************************************/ + +enum { +    VNC_AUTH_INVALID = 0, +    VNC_AUTH_NONE = 1, +    VNC_AUTH_VNC = 2, +    VNC_AUTH_RA2 = 5, +    VNC_AUTH_RA2NE = 6, +    VNC_AUTH_TIGHT = 16, +    VNC_AUTH_ULTRA = 17, +    VNC_AUTH_TLS = 18,      /* Supported in GTK-VNC & VINO */ +    VNC_AUTH_VENCRYPT = 19, /* Supported in GTK-VNC & VeNCrypt */ +    VNC_AUTH_SASL = 20,     /* Supported in GTK-VNC & VINO */ +}; + +enum { +    VNC_AUTH_VENCRYPT_PLAIN = 256, +    VNC_AUTH_VENCRYPT_TLSNONE = 257, +    VNC_AUTH_VENCRYPT_TLSVNC = 258, +    VNC_AUTH_VENCRYPT_TLSPLAIN = 259, +    VNC_AUTH_VENCRYPT_X509NONE = 260, +    VNC_AUTH_VENCRYPT_X509VNC = 261, +    VNC_AUTH_VENCRYPT_X509PLAIN = 262, +    VNC_AUTH_VENCRYPT_X509SASL = 263, +    VNC_AUTH_VENCRYPT_TLSSASL = 264, +}; + + +/***************************************************************************** + * + * Encoding types + * + *****************************************************************************/ + +#define VNC_ENCODING_RAW                  0x00000000 +#define VNC_ENCODING_COPYRECT             0x00000001 +#define VNC_ENCODING_RRE                  0x00000002 +#define VNC_ENCODING_CORRE                0x00000004 +#define VNC_ENCODING_HEXTILE              0x00000005 +#define VNC_ENCODING_ZLIB                 0x00000006 +#define VNC_ENCODING_TIGHT                0x00000007 +#define VNC_ENCODING_ZLIBHEX              0x00000008 +#define VNC_ENCODING_TRLE                 0x0000000f +#define VNC_ENCODING_ZRLE                 0x00000010 +#define VNC_ENCODING_ZYWRLE               0x00000011 +#define VNC_ENCODING_COMPRESSLEVEL0       0xFFFFFF00 /* -256 */ +#define VNC_ENCODING_QUALITYLEVEL0        0xFFFFFFE0 /* -32  */ +#define VNC_ENCODING_XCURSOR              0xFFFFFF10 /* -240 */ +#define VNC_ENCODING_RICH_CURSOR          0xFFFFFF11 /* -239 */ +#define VNC_ENCODING_POINTER_POS          0xFFFFFF18 /* -232 */ +#define VNC_ENCODING_LASTRECT             0xFFFFFF20 /* -224 */ +#define VNC_ENCODING_DESKTOPRESIZE        0xFFFFFF21 /* -223 */ +#define VNC_ENCODING_POINTER_TYPE_CHANGE  0XFFFFFEFF /* -257 */ +#define VNC_ENCODING_EXT_KEY_EVENT        0XFFFFFEFE /* -258 */ +#define VNC_ENCODING_AUDIO                0XFFFFFEFD /* -259 */ +#define VNC_ENCODING_TIGHT_PNG            0xFFFFFEFC /* -260 */ +#define VNC_ENCODING_LED_STATE            0XFFFFFEFB /* -261 */ +#define VNC_ENCODING_WMVi                 0x574D5669 + +/***************************************************************************** + * + * Other tight constants + * + *****************************************************************************/ + +/* + * Vendors known by TightVNC: standard VNC/RealVNC, TridiaVNC, and TightVNC. + */ + +#define VNC_TIGHT_CCB_RESET_MASK   (0x0f) +#define VNC_TIGHT_CCB_TYPE_MASK    (0x0f << 4) +#define VNC_TIGHT_CCB_TYPE_FILL    (0x08 << 4) +#define VNC_TIGHT_CCB_TYPE_JPEG    (0x09 << 4) +#define VNC_TIGHT_CCB_TYPE_PNG     (0x0A << 4) +#define VNC_TIGHT_CCB_BASIC_MAX    (0x07 << 4) +#define VNC_TIGHT_CCB_BASIC_ZLIB   (0x03 << 4) +#define VNC_TIGHT_CCB_BASIC_FILTER (0x04 << 4) + +/***************************************************************************** + * + * Features + * + *****************************************************************************/ + +#define VNC_FEATURE_RESIZE                   0 +#define VNC_FEATURE_HEXTILE                  1 +#define VNC_FEATURE_POINTER_TYPE_CHANGE      2 +#define VNC_FEATURE_WMVI                     3 +#define VNC_FEATURE_TIGHT                    4 +#define VNC_FEATURE_ZLIB                     5 +#define VNC_FEATURE_COPYRECT                 6 +#define VNC_FEATURE_RICH_CURSOR              7 +#define VNC_FEATURE_TIGHT_PNG                8 +#define VNC_FEATURE_ZRLE                     9 +#define VNC_FEATURE_ZYWRLE                  10 +#define VNC_FEATURE_LED_STATE               11 + +#define VNC_FEATURE_RESIZE_MASK              (1 << VNC_FEATURE_RESIZE) +#define VNC_FEATURE_HEXTILE_MASK             (1 << VNC_FEATURE_HEXTILE) +#define VNC_FEATURE_POINTER_TYPE_CHANGE_MASK (1 << VNC_FEATURE_POINTER_TYPE_CHANGE) +#define VNC_FEATURE_WMVI_MASK                (1 << VNC_FEATURE_WMVI) +#define VNC_FEATURE_TIGHT_MASK               (1 << VNC_FEATURE_TIGHT) +#define VNC_FEATURE_ZLIB_MASK                (1 << VNC_FEATURE_ZLIB) +#define VNC_FEATURE_COPYRECT_MASK            (1 << VNC_FEATURE_COPYRECT) +#define VNC_FEATURE_RICH_CURSOR_MASK         (1 << VNC_FEATURE_RICH_CURSOR) +#define VNC_FEATURE_TIGHT_PNG_MASK           (1 << VNC_FEATURE_TIGHT_PNG) +#define VNC_FEATURE_ZRLE_MASK                (1 << VNC_FEATURE_ZRLE) +#define VNC_FEATURE_ZYWRLE_MASK              (1 << VNC_FEATURE_ZYWRLE) +#define VNC_FEATURE_LED_STATE_MASK           (1 << VNC_FEATURE_LED_STATE) + + +/* Client -> Server message IDs */ +#define VNC_MSG_CLIENT_SET_PIXEL_FORMAT           0 +#define VNC_MSG_CLIENT_SET_ENCODINGS              2 +#define VNC_MSG_CLIENT_FRAMEBUFFER_UPDATE_REQUEST 3 +#define VNC_MSG_CLIENT_KEY_EVENT                  4 +#define VNC_MSG_CLIENT_POINTER_EVENT              5 +#define VNC_MSG_CLIENT_CUT_TEXT                   6 +#define VNC_MSG_CLIENT_VMWARE_0                   127 +#define VNC_MSG_CLIENT_CALL_CONTROL               249 +#define VNC_MSG_CLIENT_XVP                        250 +#define VNC_MSG_CLIENT_SET_DESKTOP_SIZE           251 +#define VNC_MSG_CLIENT_TIGHT                      252 +#define VNC_MSG_CLIENT_GII                        253 +#define VNC_MSG_CLIENT_VMWARE_1                   254 +#define VNC_MSG_CLIENT_QEMU                       255 + +/* Server -> Client message IDs */ +#define VNC_MSG_SERVER_FRAMEBUFFER_UPDATE         0 +#define VNC_MSG_SERVER_SET_COLOUR_MAP_ENTRIES     1 +#define VNC_MSG_SERVER_BELL                       2 +#define VNC_MSG_SERVER_CUT_TEXT                   3 +#define VNC_MSG_SERVER_VMWARE_0                   127 +#define VNC_MSG_SERVER_CALL_CONTROL               249 +#define VNC_MSG_SERVER_XVP                        250 +#define VNC_MSG_SERVER_TIGHT                      252 +#define VNC_MSG_SERVER_GII                        253 +#define VNC_MSG_SERVER_VMWARE_1                   254 +#define VNC_MSG_SERVER_QEMU                       255 + + + +/* QEMU client -> server message IDs */ +#define VNC_MSG_CLIENT_QEMU_EXT_KEY_EVENT         0 +#define VNC_MSG_CLIENT_QEMU_AUDIO                 1 + +/* QEMU server -> client message IDs */ +#define VNC_MSG_SERVER_QEMU_AUDIO                 1 + + + +/* QEMU client -> server audio message IDs */ +#define VNC_MSG_CLIENT_QEMU_AUDIO_ENABLE          0 +#define VNC_MSG_CLIENT_QEMU_AUDIO_DISABLE         1 +#define VNC_MSG_CLIENT_QEMU_AUDIO_SET_FORMAT      2 + +/* QEMU server -> client audio message IDs */ +#define VNC_MSG_SERVER_QEMU_AUDIO_END             0 +#define VNC_MSG_SERVER_QEMU_AUDIO_BEGIN           1 +#define VNC_MSG_SERVER_QEMU_AUDIO_DATA            2 + + +/***************************************************************************** + * + * Internal APIs + * + *****************************************************************************/ + +/* Event loop functions */ +void vnc_client_read(void *opaque); +void vnc_client_write(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); + +/* Protocol I/O functions */ +void vnc_write(VncState *vs, const void *data, size_t len); +void vnc_write_u32(VncState *vs, uint32_t value); +void vnc_write_s32(VncState *vs, int32_t value); +void vnc_write_u16(VncState *vs, uint16_t value); +void vnc_write_u8(VncState *vs, uint8_t value); +void vnc_flush(VncState *vs); +void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting); +void vnc_disconnect_finish(VncState *vs); +void vnc_init_state(VncState *vs); + + +/* Buffer I/O functions */ +uint32_t read_u32(uint8_t *data, size_t offset); + +/* Protocol stage functions */ +void vnc_client_error(VncState *vs); +int vnc_client_io_error(VncState *vs, int ret, int last_errno); + +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)); +} + +/* Framebuffer */ +void vnc_framebuffer_update(VncState *vs, int x, int y, int w, int h, +                            int32_t encoding); + +/* server fb is in PIXMAN_x8r8g8b8 */ +#define VNC_SERVER_FB_FORMAT PIXMAN_FORMAT(32, PIXMAN_TYPE_ARGB, 0, 8, 8, 8) +#define VNC_SERVER_FB_BITS   (PIXMAN_FORMAT_BPP(VNC_SERVER_FB_FORMAT)) +#define VNC_SERVER_FB_BYTES  ((VNC_SERVER_FB_BITS+7)/8) + +void *vnc_server_fb_ptr(VncDisplay *vd, int x, int y); +int vnc_server_fb_stride(VncDisplay *vd); + +void vnc_convert_pixel(VncState *vs, uint8_t *buf, uint32_t v); +double vnc_update_freq(VncState *vs, int x, int y, int w, int h); +void vnc_sent_lossy_rect(VncState *vs, int x, int y, int w, int h); + +/* Encodings */ +int vnc_send_framebuffer_update(VncState *vs, int x, int y, int w, int h); + +int vnc_raw_send_framebuffer_update(VncState *vs, int x, int y, int w, int h); + +int vnc_hextile_send_framebuffer_update(VncState *vs, int x, +                                         int y, int w, int h); +void vnc_hextile_set_pixel_conversion(VncState *vs, int generic); + +void *vnc_zlib_zalloc(void *x, unsigned items, unsigned size); +void vnc_zlib_zfree(void *x, void *addr); +int vnc_zlib_send_framebuffer_update(VncState *vs, int x, int y, int w, int h); +void vnc_zlib_clear(VncState *vs); + +int vnc_tight_send_framebuffer_update(VncState *vs, int x, int y, int w, int h); +int vnc_tight_png_send_framebuffer_update(VncState *vs, int x, int y, +                                          int w, int h); +void vnc_tight_clear(VncState *vs); + +int vnc_zrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h); +int vnc_zywrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h); +void vnc_zrle_clear(VncState *vs); + +#endif /* __QEMU_VNC_H */ diff --git a/ui/vnc_keysym.h b/ui/vnc_keysym.h new file mode 100644 index 00000000..7fa2bc1f --- /dev/null +++ b/ui/vnc_keysym.h @@ -0,0 +1,720 @@ + +#include "keymaps.h" + +static const name2keysym_t name2keysym[]={ +/* ascii */ +    { "space",                0x020}, +    { "exclam",               0x021}, +    { "quotedbl",             0x022}, +    { "numbersign",           0x023}, +    { "dollar",               0x024}, +    { "percent",              0x025}, +    { "ampersand",            0x026}, +    { "apostrophe",           0x027}, +    { "parenleft",            0x028}, +    { "parenright",           0x029}, +    { "asterisk",             0x02a}, +    { "plus",                 0x02b}, +    { "comma",                0x02c}, +    { "minus",                0x02d}, +    { "period",               0x02e}, +    { "slash",                0x02f}, +    { "0",                    0x030}, +    { "1",                    0x031}, +    { "2",                    0x032}, +    { "3",                    0x033}, +    { "4",                    0x034}, +    { "5",                    0x035}, +    { "6",                    0x036}, +    { "7",                    0x037}, +    { "8",                    0x038}, +    { "9",                    0x039}, +    { "colon",                0x03a}, +    { "semicolon",            0x03b}, +    { "less",                 0x03c}, +    { "equal",                0x03d}, +    { "greater",              0x03e}, +    { "question",             0x03f}, +    { "at",                   0x040}, +    { "A",                    0x041}, +    { "B",                    0x042}, +    { "C",                    0x043}, +    { "D",                    0x044}, +    { "E",                    0x045}, +    { "F",                    0x046}, +    { "G",                    0x047}, +    { "H",                    0x048}, +    { "I",                    0x049}, +    { "J",                    0x04a}, +    { "K",                    0x04b}, +    { "L",                    0x04c}, +    { "M",                    0x04d}, +    { "N",                    0x04e}, +    { "O",                    0x04f}, +    { "P",                    0x050}, +    { "Q",                    0x051}, +    { "R",                    0x052}, +    { "S",                    0x053}, +    { "T",                    0x054}, +    { "U",                    0x055}, +    { "V",                    0x056}, +    { "W",                    0x057}, +    { "X",                    0x058}, +    { "Y",                    0x059}, +    { "Z",                    0x05a}, +    { "bracketleft",          0x05b}, +    { "backslash",            0x05c}, +    { "bracketright",         0x05d}, +    { "asciicircum",          0x05e}, +    { "underscore",           0x05f}, +    { "grave",                0x060}, +    { "a",                    0x061}, +    { "b",                    0x062}, +    { "c",                    0x063}, +    { "d",                    0x064}, +    { "e",                    0x065}, +    { "f",                    0x066}, +    { "g",                    0x067}, +    { "h",                    0x068}, +    { "i",                    0x069}, +    { "j",                    0x06a}, +    { "k",                    0x06b}, +    { "l",                    0x06c}, +    { "m",                    0x06d}, +    { "n",                    0x06e}, +    { "o",                    0x06f}, +    { "p",                    0x070}, +    { "q",                    0x071}, +    { "r",                    0x072}, +    { "s",                    0x073}, +    { "t",                    0x074}, +    { "u",                    0x075}, +    { "v",                    0x076}, +    { "w",                    0x077}, +    { "x",                    0x078}, +    { "y",                    0x079}, +    { "z",                    0x07a}, +    { "braceleft",            0x07b}, +    { "bar",                  0x07c}, +    { "braceright",           0x07d}, +    { "asciitilde",           0x07e}, + +/* latin 1 extensions */ +{ "nobreakspace",         0x0a0}, +{ "exclamdown",           0x0a1}, +{ "cent",         	  0x0a2}, +{ "sterling",             0x0a3}, +{ "currency",             0x0a4}, +{ "yen",                  0x0a5}, +{ "brokenbar",            0x0a6}, +{ "section",              0x0a7}, +{ "diaeresis",            0x0a8}, +{ "copyright",            0x0a9}, +{ "ordfeminine",          0x0aa}, +{ "guillemotleft",        0x0ab}, +{ "notsign",              0x0ac}, +{ "hyphen",               0x0ad}, +{ "registered",           0x0ae}, +{ "macron",               0x0af}, +{ "degree",               0x0b0}, +{ "plusminus",            0x0b1}, +{ "twosuperior",          0x0b2}, +{ "threesuperior",        0x0b3}, +{ "acute",                0x0b4}, +{ "mu",                   0x0b5}, +{ "paragraph",            0x0b6}, +{ "periodcentered",       0x0b7}, +{ "cedilla",              0x0b8}, +{ "onesuperior",          0x0b9}, +{ "masculine",            0x0ba}, +{ "guillemotright",       0x0bb}, +{ "onequarter",           0x0bc}, +{ "onehalf",              0x0bd}, +{ "threequarters",        0x0be}, +{ "questiondown",         0x0bf}, +{ "Agrave",               0x0c0}, +{ "Aacute",               0x0c1}, +{ "Acircumflex",          0x0c2}, +{ "Atilde",               0x0c3}, +{ "Adiaeresis",           0x0c4}, +{ "Aring",                0x0c5}, +{ "AE",                   0x0c6}, +{ "Ccedilla",             0x0c7}, +{ "Egrave",               0x0c8}, +{ "Eacute",               0x0c9}, +{ "Ecircumflex",          0x0ca}, +{ "Ediaeresis",           0x0cb}, +{ "Igrave",               0x0cc}, +{ "Iacute",               0x0cd}, +{ "Icircumflex",          0x0ce}, +{ "Idiaeresis",           0x0cf}, +{ "ETH",                  0x0d0}, +{ "Eth",                  0x0d0}, +{ "Ntilde",               0x0d1}, +{ "Ograve",               0x0d2}, +{ "Oacute",               0x0d3}, +{ "Ocircumflex",          0x0d4}, +{ "Otilde",               0x0d5}, +{ "Odiaeresis",           0x0d6}, +{ "multiply",             0x0d7}, +{ "Ooblique",             0x0d8}, +{ "Oslash",               0x0d8}, +{ "Ugrave",               0x0d9}, +{ "Uacute",               0x0da}, +{ "Ucircumflex",          0x0db}, +{ "Udiaeresis",           0x0dc}, +{ "Yacute",               0x0dd}, +{ "THORN",                0x0de}, +{ "Thorn",                0x0de}, +{ "ssharp",               0x0df}, +{ "agrave",               0x0e0}, +{ "aacute",               0x0e1}, +{ "acircumflex",          0x0e2}, +{ "atilde",               0x0e3}, +{ "adiaeresis",           0x0e4}, +{ "aring",                0x0e5}, +{ "ae",                   0x0e6}, +{ "ccedilla",             0x0e7}, +{ "egrave",               0x0e8}, +{ "eacute",               0x0e9}, +{ "ecircumflex",          0x0ea}, +{ "ediaeresis",           0x0eb}, +{ "igrave",               0x0ec}, +{ "iacute",               0x0ed}, +{ "icircumflex",          0x0ee}, +{ "idiaeresis",           0x0ef}, +{ "eth",                  0x0f0}, +{ "ntilde",               0x0f1}, +{ "ograve",               0x0f2}, +{ "oacute",               0x0f3}, +{ "ocircumflex",          0x0f4}, +{ "otilde",               0x0f5}, +{ "odiaeresis",           0x0f6}, +{ "division",             0x0f7}, +{ "oslash",               0x0f8}, +{ "ooblique",             0x0f8}, +{ "ugrave",               0x0f9}, +{ "uacute",               0x0fa}, +{ "ucircumflex",          0x0fb}, +{ "udiaeresis",           0x0fc}, +{ "yacute",               0x0fd}, +{ "thorn",                0x0fe}, +{ "ydiaeresis",           0x0ff}, +{"EuroSign", 0x20ac},  /* XK_EuroSign */ + +/* latin 2 - Polish national characters */ +{ "eogonek",              0x1ea}, +{ "Eogonek",              0x1ca}, +{ "aogonek",              0x1b1}, +{ "Aogonek",              0x1a1}, +{ "sacute",               0x1b6}, +{ "Sacute",               0x1a6}, +{ "lstroke",              0x1b3}, +{ "Lstroke",              0x1a3}, +{ "zabovedot",            0x1bf}, +{ "Zabovedot",            0x1af}, +{ "zacute",               0x1bc}, +{ "Zacute",               0x1ac}, +{ "Odoubleacute",         0x1d5}, +{ "Udoubleacute",         0x1db}, +{ "cacute",               0x1e6}, +{ "Cacute",               0x1c6}, +{ "nacute",               0x1f1}, +{ "Nacute",               0x1d1}, +{ "odoubleacute",         0x1f5}, +{ "udoubleacute",         0x1fb}, + +/* Czech national characters */ +{ "ecaron",               0x1ec}, +{ "scaron",               0x1b9}, +{ "ccaron",               0x1e8}, +{ "rcaron",               0x1f8}, +{ "zcaron",               0x1be}, +{ "uring",                0x1f9}, + +    /* modifiers */ +{"ISO_Level3_Shift", 0xfe03}, /* XK_ISO_Level3_Shift */ +{"Control_L", 0xffe3}, /* XK_Control_L */ +{"Control_R", 0xffe4}, /* XK_Control_R */ +{"Alt_L", 0xffe9},     /* XK_Alt_L */ +{"Alt_R", 0xffea},     /* XK_Alt_R */ +{"Caps_Lock", 0xffe5}, /* XK_Caps_Lock */ +{"Meta_L", 0xffe7},    /* XK_Meta_L */ +{"Meta_R", 0xffe8},    /* XK_Meta_R */ +{"Shift_L", 0xffe1},   /* XK_Shift_L */ +{"Shift_R", 0xffe2},   /* XK_Shift_R */ +{"Super_L", 0xffeb},   /* XK_Super_L */ +{"Super_R", 0xffec},   /* XK_Super_R */ + +    /* special keys */ +{"BackSpace", 0xff08}, /* XK_BackSpace */ +{"Tab", 0xff09},       /* XK_Tab */ +{"Return", 0xff0d},    /* XK_Return */ +{"Right", 0xff53},     /* XK_Right */ +{"Left", 0xff51},      /* XK_Left */ +{"Up", 0xff52},        /* XK_Up */ +{"Down", 0xff54},      /* XK_Down */ +{"Page_Down", 0xff56}, /* XK_Page_Down */ +{"Page_Up", 0xff55},   /* XK_Page_Up */ +{"Insert", 0xff63},    /* XK_Insert */ +{"Delete", 0xffff},    /* XK_Delete */ +{"Home", 0xff50},      /* XK_Home */ +{"End", 0xff57},       /* XK_End */ +{"Scroll_Lock", 0xff14}, /* XK_Scroll_Lock */ +{"KP_Home", 0xff95}, +{"KP_Left", 0xff96}, +{"KP_Up", 0xff97}, +{"KP_Right", 0xff98}, +{"KP_Down", 0xff99}, +{"KP_Prior", 0xff9a}, +{"KP_Page_Up", 0xff9a}, +{"KP_Next", 0xff9b}, +{"KP_Page_Down", 0xff9b}, +{"KP_End", 0xff9c}, +{"KP_Begin", 0xff9d}, +{"KP_Insert", 0xff9e}, +{"KP_Delete", 0xff9f}, +{"F1", 0xffbe},        /* XK_F1 */ +{"F2", 0xffbf},        /* XK_F2 */ +{"F3", 0xffc0},        /* XK_F3 */ +{"F4", 0xffc1},        /* XK_F4 */ +{"F5", 0xffc2},        /* XK_F5 */ +{"F6", 0xffc3},        /* XK_F6 */ +{"F7", 0xffc4},        /* XK_F7 */ +{"F8", 0xffc5},        /* XK_F8 */ +{"F9", 0xffc6},        /* XK_F9 */ +{"F10", 0xffc7},       /* XK_F10 */ +{"F11", 0xffc8},       /* XK_F11 */ +{"F12", 0xffc9},       /* XK_F12 */ +{"F13", 0xffca},       /* XK_F13 */ +{"F14", 0xffcb},       /* XK_F14 */ +{"F15", 0xffcc},       /* XK_F15 */ +{"Sys_Req", 0xff15},   /* XK_Sys_Req */ +{"KP_0", 0xffb0},      /* XK_KP_0 */ +{"KP_1", 0xffb1},      /* XK_KP_1 */ +{"KP_2", 0xffb2},      /* XK_KP_2 */ +{"KP_3", 0xffb3},      /* XK_KP_3 */ +{"KP_4", 0xffb4},      /* XK_KP_4 */ +{"KP_5", 0xffb5},      /* XK_KP_5 */ +{"KP_6", 0xffb6},      /* XK_KP_6 */ +{"KP_7", 0xffb7},      /* XK_KP_7 */ +{"KP_8", 0xffb8},      /* XK_KP_8 */ +{"KP_9", 0xffb9},      /* XK_KP_9 */ +{"KP_Add", 0xffab},    /* XK_KP_Add */ +{"KP_Separator", 0xffac},/* XK_KP_Separator */ +{"KP_Decimal", 0xffae},  /* XK_KP_Decimal */ +{"KP_Divide", 0xffaf},   /* XK_KP_Divide */ +{"KP_Enter", 0xff8d},    /* XK_KP_Enter */ +{"KP_Equal", 0xffbd},    /* XK_KP_Equal */ +{"KP_Multiply", 0xffaa}, /* XK_KP_Multiply */ +{"KP_Subtract", 0xffad}, /* XK_KP_Subtract */ +{"help", 0xff6a},        /* XK_Help */ +{"Menu", 0xff67},        /* XK_Menu */ +{"Print", 0xff61},       /* XK_Print */ +{"Mode_switch", 0xff7e}, /* XK_Mode_switch */ +{"Num_Lock", 0xff7f},    /* XK_Num_Lock */ +{"Pause", 0xff13},       /* XK_Pause */ +{"Escape", 0xff1b},      /* XK_Escape */ + +/* dead keys */ +{"dead_grave", 0xfe50}, /* XK_dead_grave */ +{"dead_acute", 0xfe51}, /* XK_dead_acute */ +{"dead_circumflex", 0xfe52}, /* XK_dead_circumflex */ +{"dead_tilde", 0xfe53}, /* XK_dead_tilde */ +{"dead_macron", 0xfe54}, /* XK_dead_macron */ +{"dead_breve", 0xfe55}, /* XK_dead_breve */ +{"dead_abovedot", 0xfe56}, /* XK_dead_abovedot */ +{"dead_diaeresis", 0xfe57}, /* XK_dead_diaeresis */ +{"dead_abovering", 0xfe58}, /* XK_dead_abovering */ +{"dead_doubleacute", 0xfe59}, /* XK_dead_doubleacute */ +{"dead_caron", 0xfe5a}, /* XK_dead_caron */ +{"dead_cedilla", 0xfe5b}, /* XK_dead_cedilla */ +{"dead_ogonek", 0xfe5c}, /* XK_dead_ogonek */ +{"dead_iota", 0xfe5d}, /* XK_dead_iota */ +{"dead_voiced_sound", 0xfe5e}, /* XK_dead_voiced_sound */ +{"dead_semivoiced_sound", 0xfe5f}, /* XK_dead_semivoiced_sound */ +{"dead_belowdot", 0xfe60}, /* XK_dead_belowdot */ +{"dead_hook", 0xfe61}, /* XK_dead_hook */ +{"dead_horn", 0xfe62}, /* XK_dead_horn */ + + +    /* localized keys */ +{"BackApostrophe", 0xff21}, +{"Muhenkan", 0xff22}, +{"Katakana", 0xff27}, +{"Hankaku", 0xff29}, +{"Zenkaku_Hankaku", 0xff2a}, +{"Henkan_Mode_Real", 0xff23}, +{"Henkan_Mode_Ultra", 0xff3e}, +{"backslash_ja", 0xffa5}, +{"Katakana_Real", 0xff25}, +{"Eisu_toggle", 0xff30}, + +{"abovedot",                      0x01ff},  /* U+02D9 DOT ABOVE */ +{"amacron",                       0x03e0},  /* U+0101 LATIN SMALL LETTER A WITH MACRON */ +{"Amacron",                       0x03c0},  /* U+0100 LATIN CAPITAL LETTER A WITH MACRON */ +{"Arabic_ain",                    0x05d9},  /* U+0639 ARABIC LETTER AIN */ +{"Arabic_alef",                   0x05c7},  /* U+0627 ARABIC LETTER ALEF */ +{"Arabic_alefmaksura",            0x05e9},  /* U+0649 ARABIC LETTER ALEF MAKSURA */ +{"Arabic_beh",                    0x05c8},  /* U+0628 ARABIC LETTER BEH */ +{"Arabic_comma",                  0x05ac},  /* U+060C ARABIC COMMA */ +{"Arabic_dad",                    0x05d6},  /* U+0636 ARABIC LETTER DAD */ +{"Arabic_dal",                    0x05cf},  /* U+062F ARABIC LETTER DAL */ +{"Arabic_damma",                  0x05ef},  /* U+064F ARABIC DAMMA */ +{"Arabic_dammatan",               0x05ec},  /* U+064C ARABIC DAMMATAN */ +{"Arabic_fatha",                  0x05ee},  /* U+064E ARABIC FATHA */ +{"Arabic_fathatan",               0x05eb},  /* U+064B ARABIC FATHATAN */ +{"Arabic_feh",                    0x05e1},  /* U+0641 ARABIC LETTER FEH */ +{"Arabic_ghain",                  0x05da},  /* U+063A ARABIC LETTER GHAIN */ +{"Arabic_ha",                     0x05e7},  /* U+0647 ARABIC LETTER HEH */ +{"Arabic_hah",                    0x05cd},  /* U+062D ARABIC LETTER HAH */ +{"Arabic_hamza",                  0x05c1},  /* U+0621 ARABIC LETTER HAMZA */ +{"Arabic_hamzaonalef",            0x05c3},  /* U+0623 ARABIC LETTER ALEF WITH HAMZA ABOVE */ +{"Arabic_hamzaonwaw",             0x05c4},  /* U+0624 ARABIC LETTER WAW WITH HAMZA ABOVE */ +{"Arabic_hamzaonyeh",             0x05c6},  /* U+0626 ARABIC LETTER YEH WITH HAMZA ABOVE */ +{"Arabic_hamzaunderalef",         0x05c5},  /* U+0625 ARABIC LETTER ALEF WITH HAMZA BELOW */ +{"Arabic_jeem",                   0x05cc},  /* U+062C ARABIC LETTER JEEM */ +{"Arabic_kaf",                    0x05e3},  /* U+0643 ARABIC LETTER KAF */ +{"Arabic_kasra",                  0x05f0},  /* U+0650 ARABIC KASRA */ +{"Arabic_kasratan",               0x05ed},  /* U+064D ARABIC KASRATAN */ +{"Arabic_khah",                   0x05ce},  /* U+062E ARABIC LETTER KHAH */ +{"Arabic_lam",                    0x05e4},  /* U+0644 ARABIC LETTER LAM */ +{"Arabic_maddaonalef",            0x05c2},  /* U+0622 ARABIC LETTER ALEF WITH MADDA ABOVE */ +{"Arabic_meem",                   0x05e5},  /* U+0645 ARABIC LETTER MEEM */ +{"Arabic_noon",                   0x05e6},  /* U+0646 ARABIC LETTER NOON */ +{"Arabic_qaf",                    0x05e2},  /* U+0642 ARABIC LETTER QAF */ +{"Arabic_question_mark",          0x05bf},  /* U+061F ARABIC QUESTION MARK */ +{"Arabic_ra",                     0x05d1},  /* U+0631 ARABIC LETTER REH */ +{"Arabic_sad",                    0x05d5},  /* U+0635 ARABIC LETTER SAD */ +{"Arabic_seen",                   0x05d3},  /* U+0633 ARABIC LETTER SEEN */ +{"Arabic_semicolon",              0x05bb},  /* U+061B ARABIC SEMICOLON */ +{"Arabic_shadda",                 0x05f1},  /* U+0651 ARABIC SHADDA */ +{"Arabic_sheen",                  0x05d4},  /* U+0634 ARABIC LETTER SHEEN */ +{"Arabic_sukun",                  0x05f2},  /* U+0652 ARABIC SUKUN */ +{"Arabic_tah",                    0x05d7},  /* U+0637 ARABIC LETTER TAH */ +{"Arabic_tatweel",                0x05e0},  /* U+0640 ARABIC TATWEEL */ +{"Arabic_teh",                    0x05ca},  /* U+062A ARABIC LETTER TEH */ +{"Arabic_tehmarbuta",             0x05c9},  /* U+0629 ARABIC LETTER TEH MARBUTA */ +{"Arabic_thal",                   0x05d0},  /* U+0630 ARABIC LETTER THAL */ +{"Arabic_theh",                   0x05cb},  /* U+062B ARABIC LETTER THEH */ +{"Arabic_waw",                    0x05e8},  /* U+0648 ARABIC LETTER WAW */ +{"Arabic_yeh",                    0x05ea},  /* U+064A ARABIC LETTER YEH */ +{"Arabic_zah",                    0x05d8},  /* U+0638 ARABIC LETTER ZAH */ +{"Arabic_zain",                   0x05d2},  /* U+0632 ARABIC LETTER ZAIN */ +{"breve",                         0x01a2},  /* U+02D8 BREVE */ +{"caron",                         0x01b7},  /* U+02C7 CARON */ +{"Ccaron",                        0x01c8},  /* U+010C LATIN CAPITAL LETTER C WITH CARON */ +{"numerosign",                    0x06b0},  /* U+2116 NUMERO SIGN */ +{"Cyrillic_a",                    0x06c1},  /* U+0430 CYRILLIC SMALL LETTER A */ +{"Cyrillic_A",                    0x06e1},  /* U+0410 CYRILLIC CAPITAL LETTER A */ +{"Cyrillic_be",                   0x06c2},  /* U+0431 CYRILLIC SMALL LETTER BE */ +{"Cyrillic_BE",                   0x06e2},  /* U+0411 CYRILLIC CAPITAL LETTER BE */ +{"Cyrillic_che",                  0x06de},  /* U+0447 CYRILLIC SMALL LETTER CHE */ +{"Cyrillic_CHE",                  0x06fe},  /* U+0427 CYRILLIC CAPITAL LETTER CHE */ +{"Cyrillic_de",                   0x06c4},  /* U+0434 CYRILLIC SMALL LETTER DE */ +{"Cyrillic_DE",                   0x06e4},  /* U+0414 CYRILLIC CAPITAL LETTER DE */ +{"Cyrillic_dzhe",                 0x06af},  /* U+045F CYRILLIC SMALL LETTER DZHE */ +{"Cyrillic_DZHE",                 0x06bf},  /* U+040F CYRILLIC CAPITAL LETTER DZHE */ +{"Cyrillic_e",                    0x06dc},  /* U+044D CYRILLIC SMALL LETTER E */ +{"Cyrillic_E",                    0x06fc},  /* U+042D CYRILLIC CAPITAL LETTER E */ +{"Cyrillic_ef",                   0x06c6},  /* U+0444 CYRILLIC SMALL LETTER EF */ +{"Cyrillic_EF",                   0x06e6},  /* U+0424 CYRILLIC CAPITAL LETTER EF */ +{"Cyrillic_el",                   0x06cc},  /* U+043B CYRILLIC SMALL LETTER EL */ +{"Cyrillic_EL",                   0x06ec},  /* U+041B CYRILLIC CAPITAL LETTER EL */ +{"Cyrillic_em",                   0x06cd},  /* U+043C CYRILLIC SMALL LETTER EM */ +{"Cyrillic_EM",                   0x06ed},  /* U+041C CYRILLIC CAPITAL LETTER EM */ +{"Cyrillic_en",                   0x06ce},  /* U+043D CYRILLIC SMALL LETTER EN */ +{"Cyrillic_EN",                   0x06ee},  /* U+041D CYRILLIC CAPITAL LETTER EN */ +{"Cyrillic_er",                   0x06d2},  /* U+0440 CYRILLIC SMALL LETTER ER */ +{"Cyrillic_ER",                   0x06f2},  /* U+0420 CYRILLIC CAPITAL LETTER ER */ +{"Cyrillic_es",                   0x06d3},  /* U+0441 CYRILLIC SMALL LETTER ES */ +{"Cyrillic_ES",                   0x06f3},  /* U+0421 CYRILLIC CAPITAL LETTER ES */ +{"Cyrillic_ghe",                  0x06c7},  /* U+0433 CYRILLIC SMALL LETTER GHE */ +{"Cyrillic_GHE",                  0x06e7},  /* U+0413 CYRILLIC CAPITAL LETTER GHE */ +{"Cyrillic_ha",                   0x06c8},  /* U+0445 CYRILLIC SMALL LETTER HA */ +{"Cyrillic_HA",                   0x06e8},  /* U+0425 CYRILLIC CAPITAL LETTER HA */ +{"Cyrillic_hardsign",             0x06df},  /* U+044A CYRILLIC SMALL LETTER HARD SIGN */ +{"Cyrillic_HARDSIGN",             0x06ff},  /* U+042A CYRILLIC CAPITAL LETTER HARD SIGN */ +{"Cyrillic_i",                    0x06c9},  /* U+0438 CYRILLIC SMALL LETTER I */ +{"Cyrillic_I",                    0x06e9},  /* U+0418 CYRILLIC CAPITAL LETTER I */ +{"Cyrillic_ie",                   0x06c5},  /* U+0435 CYRILLIC SMALL LETTER IE */ +{"Cyrillic_IE",                   0x06e5},  /* U+0415 CYRILLIC CAPITAL LETTER IE */ +{"Cyrillic_io",                   0x06a3},  /* U+0451 CYRILLIC SMALL LETTER IO */ +{"Cyrillic_IO",                   0x06b3},  /* U+0401 CYRILLIC CAPITAL LETTER IO */ +{"Cyrillic_je",                   0x06a8},  /* U+0458 CYRILLIC SMALL LETTER JE */ +{"Cyrillic_JE",                   0x06b8},  /* U+0408 CYRILLIC CAPITAL LETTER JE */ +{"Cyrillic_ka",                   0x06cb},  /* U+043A CYRILLIC SMALL LETTER KA */ +{"Cyrillic_KA",                   0x06eb},  /* U+041A CYRILLIC CAPITAL LETTER KA */ +{"Cyrillic_lje",                  0x06a9},  /* U+0459 CYRILLIC SMALL LETTER LJE */ +{"Cyrillic_LJE",                  0x06b9},  /* U+0409 CYRILLIC CAPITAL LETTER LJE */ +{"Cyrillic_nje",                  0x06aa},  /* U+045A CYRILLIC SMALL LETTER NJE */ +{"Cyrillic_NJE",                  0x06ba},  /* U+040A CYRILLIC CAPITAL LETTER NJE */ +{"Cyrillic_o",                    0x06cf},  /* U+043E CYRILLIC SMALL LETTER O */ +{"Cyrillic_O",                    0x06ef},  /* U+041E CYRILLIC CAPITAL LETTER O */ +{"Cyrillic_pe",                   0x06d0},  /* U+043F CYRILLIC SMALL LETTER PE */ +{"Cyrillic_PE",                   0x06f0},  /* U+041F CYRILLIC CAPITAL LETTER PE */ +{"Cyrillic_sha",                  0x06db},  /* U+0448 CYRILLIC SMALL LETTER SHA */ +{"Cyrillic_SHA",                  0x06fb},  /* U+0428 CYRILLIC CAPITAL LETTER SHA */ +{"Cyrillic_shcha",                0x06dd},  /* U+0449 CYRILLIC SMALL LETTER SHCHA */ +{"Cyrillic_SHCHA",                0x06fd},  /* U+0429 CYRILLIC CAPITAL LETTER SHCHA */ +{"Cyrillic_shorti",               0x06ca},  /* U+0439 CYRILLIC SMALL LETTER SHORT I */ +{"Cyrillic_SHORTI",               0x06ea},  /* U+0419 CYRILLIC CAPITAL LETTER SHORT I */ +{"Cyrillic_softsign",             0x06d8},  /* U+044C CYRILLIC SMALL LETTER SOFT SIGN */ +{"Cyrillic_SOFTSIGN",             0x06f8},  /* U+042C CYRILLIC CAPITAL LETTER SOFT SIGN */ +{"Cyrillic_te",                   0x06d4},  /* U+0442 CYRILLIC SMALL LETTER TE */ +{"Cyrillic_TE",                   0x06f4},  /* U+0422 CYRILLIC CAPITAL LETTER TE */ +{"Cyrillic_tse",                  0x06c3},  /* U+0446 CYRILLIC SMALL LETTER TSE */ +{"Cyrillic_TSE",                  0x06e3},  /* U+0426 CYRILLIC CAPITAL LETTER TSE */ +{"Cyrillic_u",                    0x06d5},  /* U+0443 CYRILLIC SMALL LETTER U */ +{"Cyrillic_U",                    0x06f5},  /* U+0423 CYRILLIC CAPITAL LETTER U */ +{"Cyrillic_ve",                   0x06d7},  /* U+0432 CYRILLIC SMALL LETTER VE */ +{"Cyrillic_VE",                   0x06f7},  /* U+0412 CYRILLIC CAPITAL LETTER VE */ +{"Cyrillic_ya",                   0x06d1},  /* U+044F CYRILLIC SMALL LETTER YA */ +{"Cyrillic_YA",                   0x06f1},  /* U+042F CYRILLIC CAPITAL LETTER YA */ +{"Cyrillic_yeru",                 0x06d9},  /* U+044B CYRILLIC SMALL LETTER YERU */ +{"Cyrillic_YERU",                 0x06f9},  /* U+042B CYRILLIC CAPITAL LETTER YERU */ +{"Cyrillic_yu",                   0x06c0},  /* U+044E CYRILLIC SMALL LETTER YU */ +{"Cyrillic_YU",                   0x06e0},  /* U+042E CYRILLIC CAPITAL LETTER YU */ +{"Cyrillic_ze",                   0x06da},  /* U+0437 CYRILLIC SMALL LETTER ZE */ +{"Cyrillic_ZE",                   0x06fa},  /* U+0417 CYRILLIC CAPITAL LETTER ZE */ +{"Cyrillic_zhe",                  0x06d6},  /* U+0436 CYRILLIC SMALL LETTER ZHE */ +{"Cyrillic_ZHE",                  0x06f6},  /* U+0416 CYRILLIC CAPITAL LETTER ZHE */ +{"doubleacute",                   0x01bd},  /* U+02DD DOUBLE ACUTE ACCENT */ +{"doublelowquotemark",            0x0afe},  /* U+201E DOUBLE LOW-9 QUOTATION MARK */ +{"downarrow",                     0x08fe},  /* U+2193 DOWNWARDS ARROW */ +{"dstroke",                       0x01f0},  /* U+0111 LATIN SMALL LETTER D WITH STROKE */ +{"Dstroke",                       0x01d0},  /* U+0110 LATIN CAPITAL LETTER D WITH STROKE */ +{"eabovedot",                     0x03ec},  /* U+0117 LATIN SMALL LETTER E WITH DOT ABOVE */ +{"Eabovedot",                     0x03cc},  /* U+0116 LATIN CAPITAL LETTER E WITH DOT ABOVE */ +{"emacron",                       0x03ba},  /* U+0113 LATIN SMALL LETTER E WITH MACRON */ +{"Emacron",                       0x03aa},  /* U+0112 LATIN CAPITAL LETTER E WITH MACRON */ +{"endash",                        0x0aaa},  /* U+2013 EN DASH */ +{"eng",                           0x03bf},  /* U+014B LATIN SMALL LETTER ENG */ +{"ENG",                           0x03bd},  /* U+014A LATIN CAPITAL LETTER ENG */ +{"Execute",                       0xff62},  /* Execute, run, do */ +{"F16",                           0xffcd}, +{"F17",                           0xffce}, +{"F18",                           0xffcf}, +{"F19",                           0xffd0}, +{"F20",                           0xffd1}, +{"F21",                           0xffd2}, +{"F22",                           0xffd3}, +{"F23",                           0xffd4}, +{"F24",                           0xffd5}, +{"F25",                           0xffd6}, +{"F26",                           0xffd7}, +{"F27",                           0xffd8}, +{"F28",                           0xffd9}, +{"F29",                           0xffda}, +{"F30",                           0xffdb}, +{"F31",                           0xffdc}, +{"F32",                           0xffdd}, +{"F33",                           0xffde}, +{"F34",                           0xffdf}, +{"F35",                           0xffe0}, +{"fiveeighths",                   0x0ac5},  /* U+215D VULGAR FRACTION FIVE EIGHTHS */ +{"gbreve",                        0x02bb},  /* U+011F LATIN SMALL LETTER G WITH BREVE */ +{"Gbreve",                        0x02ab},  /* U+011E LATIN CAPITAL LETTER G WITH BREVE */ +{"gcedilla",                      0x03bb},  /* U+0123 LATIN SMALL LETTER G WITH CEDILLA */ +{"Gcedilla",                      0x03ab},  /* U+0122 LATIN CAPITAL LETTER G WITH CEDILLA */ +{"Greek_OMEGA",                   0x07d9},  /* U+03A9 GREEK CAPITAL LETTER OMEGA */ +{"Henkan_Mode",                   0xff23},  /* Start/Stop Conversion */ +{"horizconnector",                0x08a3},  /*(U+2500 BOX DRAWINGS LIGHT HORIZONTAL)*/ +{"hstroke",                       0x02b1},  /* U+0127 LATIN SMALL LETTER H WITH STROKE */ +{"Hstroke",                       0x02a1},  /* U+0126 LATIN CAPITAL LETTER H WITH STROKE */ +{"Iabovedot",                     0x02a9},  /* U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE */ +{"idotless",                      0x02b9},  /* U+0131 LATIN SMALL LETTER DOTLESS I */ +{"imacron",                       0x03ef},  /* U+012B LATIN SMALL LETTER I WITH MACRON */ +{"Imacron",                       0x03cf},  /* U+012A LATIN CAPITAL LETTER I WITH MACRON */ +{"iogonek",                       0x03e7},  /* U+012F LATIN SMALL LETTER I WITH OGONEK */ +{"Iogonek",                       0x03c7},  /* U+012E LATIN CAPITAL LETTER I WITH OGONEK */ +{"ISO_First_Group",               0xfe0c}, +{"ISO_Last_Group",                0xfe0e}, +{"ISO_Next_Group",                0xfe08}, +{"kana_a",                        0x04a7},  /* U+30A1 KATAKANA LETTER SMALL A */ +{"kana_A",                        0x04b1},  /* U+30A2 KATAKANA LETTER A */ +{"kana_CHI",                      0x04c1},  /* U+30C1 KATAKANA LETTER TI */ +{"kana_closingbracket",           0x04a3},  /* U+300D RIGHT CORNER BRACKET */ +{"kana_comma",                    0x04a4},  /* U+3001 IDEOGRAPHIC COMMA */ +{"kana_conjunctive",              0x04a5},  /* U+30FB KATAKANA MIDDLE DOT */ +{"kana_e",                        0x04aa},  /* U+30A7 KATAKANA LETTER SMALL E */ +{"kana_E",                        0x04b4},  /* U+30A8 KATAKANA LETTER E */ +{"kana_FU",                       0x04cc},  /* U+30D5 KATAKANA LETTER HU */ +{"kana_fullstop",                 0x04a1},  /* U+3002 IDEOGRAPHIC FULL STOP */ +{"kana_HA",                       0x04ca},  /* U+30CF KATAKANA LETTER HA */ +{"kana_HE",                       0x04cd},  /* U+30D8 KATAKANA LETTER HE */ +{"kana_HI",                       0x04cb},  /* U+30D2 KATAKANA LETTER HI */ +{"kana_HO",                       0x04ce},  /* U+30DB KATAKANA LETTER HO */ +{"kana_i",                        0x04a8},  /* U+30A3 KATAKANA LETTER SMALL I */ +{"kana_I",                        0x04b2},  /* U+30A4 KATAKANA LETTER I */ +{"kana_KA",                       0x04b6},  /* U+30AB KATAKANA LETTER KA */ +{"kana_KE",                       0x04b9},  /* U+30B1 KATAKANA LETTER KE */ +{"kana_KI",                       0x04b7},  /* U+30AD KATAKANA LETTER KI */ +{"kana_KO",                       0x04ba},  /* U+30B3 KATAKANA LETTER KO */ +{"kana_KU",                       0x04b8},  /* U+30AF KATAKANA LETTER KU */ +{"kana_MA",                       0x04cf},  /* U+30DE KATAKANA LETTER MA */ +{"kana_ME",                       0x04d2},  /* U+30E1 KATAKANA LETTER ME */ +{"kana_MI",                       0x04d0},  /* U+30DF KATAKANA LETTER MI */ +{"kana_MO",                       0x04d3},  /* U+30E2 KATAKANA LETTER MO */ +{"kana_MU",                       0x04d1},  /* U+30E0 KATAKANA LETTER MU */ +{"kana_N",                        0x04dd},  /* U+30F3 KATAKANA LETTER N */ +{"kana_NA",                       0x04c5},  /* U+30CA KATAKANA LETTER NA */ +{"kana_NE",                       0x04c8},  /* U+30CD KATAKANA LETTER NE */ +{"kana_NI",                       0x04c6},  /* U+30CB KATAKANA LETTER NI */ +{"kana_NO",                       0x04c9},  /* U+30CE KATAKANA LETTER NO */ +{"kana_NU",                       0x04c7},  /* U+30CC KATAKANA LETTER NU */ +{"kana_o",                        0x04ab},  /* U+30A9 KATAKANA LETTER SMALL O */ +{"kana_O",                        0x04b5},  /* U+30AA KATAKANA LETTER O */ +{"kana_openingbracket",           0x04a2},  /* U+300C LEFT CORNER BRACKET */ +{"kana_RA",                       0x04d7},  /* U+30E9 KATAKANA LETTER RA */ +{"kana_RE",                       0x04da},  /* U+30EC KATAKANA LETTER RE */ +{"kana_RI",                       0x04d8},  /* U+30EA KATAKANA LETTER RI */ +{"kana_RU",                       0x04d9},  /* U+30EB KATAKANA LETTER RU */ +{"kana_SA",                       0x04bb},  /* U+30B5 KATAKANA LETTER SA */ +{"kana_SE",                       0x04be},  /* U+30BB KATAKANA LETTER SE */ +{"kana_SHI",                      0x04bc},  /* U+30B7 KATAKANA LETTER SI */ +{"kana_SO",                       0x04bf},  /* U+30BD KATAKANA LETTER SO */ +{"kana_SU",                       0x04bd},  /* U+30B9 KATAKANA LETTER SU */ +{"kana_TA",                       0x04c0},  /* U+30BF KATAKANA LETTER TA */ +{"kana_TE",                       0x04c3},  /* U+30C6 KATAKANA LETTER TE */ +{"kana_TO",                       0x04c4},  /* U+30C8 KATAKANA LETTER TO */ +{"kana_tsu",                      0x04af},  /* U+30C3 KATAKANA LETTER SMALL TU */ +{"kana_TSU",                      0x04c2},  /* U+30C4 KATAKANA LETTER TU */ +{"kana_u",                        0x04a9},  /* U+30A5 KATAKANA LETTER SMALL U */ +{"kana_U",                        0x04b3},  /* U+30A6 KATAKANA LETTER U */ +{"kana_WA",                       0x04dc},  /* U+30EF KATAKANA LETTER WA */ +{"kana_WO",                       0x04a6},  /* U+30F2 KATAKANA LETTER WO */ +{"kana_ya",                       0x04ac},  /* U+30E3 KATAKANA LETTER SMALL YA */ +{"kana_YA",                       0x04d4},  /* U+30E4 KATAKANA LETTER YA */ +{"kana_yo",                       0x04ae},  /* U+30E7 KATAKANA LETTER SMALL YO */ +{"kana_YO",                       0x04d6},  /* U+30E8 KATAKANA LETTER YO */ +{"kana_yu",                       0x04ad},  /* U+30E5 KATAKANA LETTER SMALL YU */ +{"kana_YU",                       0x04d5},  /* U+30E6 KATAKANA LETTER YU */ +{"Kanji",                         0xff21},  /* Kanji, Kanji convert */ +{"kcedilla",                      0x03f3},  /* U+0137 LATIN SMALL LETTER K WITH CEDILLA */ +{"Kcedilla",                      0x03d3},  /* U+0136 LATIN CAPITAL LETTER K WITH CEDILLA */ +{"kra",                           0x03a2},  /* U+0138 LATIN SMALL LETTER KRA */ +{"lcedilla",                      0x03b6},  /* U+013C LATIN SMALL LETTER L WITH CEDILLA */ +{"Lcedilla",                      0x03a6},  /* U+013B LATIN CAPITAL LETTER L WITH CEDILLA */ +{"leftarrow",                     0x08fb},  /* U+2190 LEFTWARDS ARROW */ +{"leftdoublequotemark",           0x0ad2},  /* U+201C LEFT DOUBLE QUOTATION MARK */ +{"Macedonia_dse",                 0x06a5},  /* U+0455 CYRILLIC SMALL LETTER DZE */ +{"Macedonia_DSE",                 0x06b5},  /* U+0405 CYRILLIC CAPITAL LETTER DZE */ +{"Macedonia_gje",                 0x06a2},  /* U+0453 CYRILLIC SMALL LETTER GJE */ +{"Macedonia_GJE",                 0x06b2},  /* U+0403 CYRILLIC CAPITAL LETTER GJE */ +{"Macedonia_kje",                 0x06ac},  /* U+045C CYRILLIC SMALL LETTER KJE */ +{"Macedonia_KJE",                 0x06bc},  /* U+040C CYRILLIC CAPITAL LETTER KJE */ +{"ncedilla",                      0x03f1},  /* U+0146 LATIN SMALL LETTER N WITH CEDILLA */ +{"Ncedilla",                      0x03d1},  /* U+0145 LATIN CAPITAL LETTER N WITH CEDILLA */ +{"oe",                            0x13bd},  /* U+0153 LATIN SMALL LIGATURE OE */ +{"OE",                            0x13bc},  /* U+0152 LATIN CAPITAL LIGATURE OE */ +{"ogonek",                        0x01b2},  /* U+02DB OGONEK */ +{"omacron",                       0x03f2},  /* U+014D LATIN SMALL LETTER O WITH MACRON */ +{"Omacron",                       0x03d2},  /* U+014C LATIN CAPITAL LETTER O WITH MACRON */ +{"oneeighth",                     0x0ac3},  /* U+215B VULGAR FRACTION ONE EIGHTH */ +{"rcedilla",                      0x03b3},  /* U+0157 LATIN SMALL LETTER R WITH CEDILLA */ +{"Rcedilla",                      0x03a3},  /* U+0156 LATIN CAPITAL LETTER R WITH CEDILLA */ +{"rightarrow",                    0x08fd},  /* U+2192 RIGHTWARDS ARROW */ +{"rightdoublequotemark",          0x0ad3},  /* U+201D RIGHT DOUBLE QUOTATION MARK */ +{"Scaron",                        0x01a9},  /* U+0160 LATIN CAPITAL LETTER S WITH CARON */ +{"scedilla",                      0x01ba},  /* U+015F LATIN SMALL LETTER S WITH CEDILLA */ +{"Scedilla",                      0x01aa},  /* U+015E LATIN CAPITAL LETTER S WITH CEDILLA */ +{"semivoicedsound",               0x04df},  /* U+309C KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */ +{"seveneighths",                  0x0ac6},  /* U+215E VULGAR FRACTION SEVEN EIGHTHS */ +{"Thai_baht",                     0x0ddf},  /* U+0E3F THAI CURRENCY SYMBOL BAHT */ +{"Thai_bobaimai",                 0x0dba},  /* U+0E1A THAI CHARACTER BO BAIMAI */ +{"Thai_chochan",                  0x0da8},  /* U+0E08 THAI CHARACTER CHO CHAN */ +{"Thai_chochang",                 0x0daa},  /* U+0E0A THAI CHARACTER CHO CHANG */ +{"Thai_choching",                 0x0da9},  /* U+0E09 THAI CHARACTER CHO CHING */ +{"Thai_chochoe",                  0x0dac},  /* U+0E0C THAI CHARACTER CHO CHOE */ +{"Thai_dochada",                  0x0dae},  /* U+0E0E THAI CHARACTER DO CHADA */ +{"Thai_dodek",                    0x0db4},  /* U+0E14 THAI CHARACTER DO DEK */ +{"Thai_fofa",                     0x0dbd},  /* U+0E1D THAI CHARACTER FO FA */ +{"Thai_fofan",                    0x0dbf},  /* U+0E1F THAI CHARACTER FO FAN */ +{"Thai_hohip",                    0x0dcb},  /* U+0E2B THAI CHARACTER HO HIP */ +{"Thai_honokhuk",                 0x0dce},  /* U+0E2E THAI CHARACTER HO NOKHUK */ +{"Thai_khokhai",                  0x0da2},  /* U+0E02 THAI CHARACTER KHO KHAI */ +{"Thai_khokhon",                  0x0da5},  /* U+0E05 THAI CHARACTER KHO KHON */ +{"Thai_khokhuat",                 0x0da3},  /* U+0E03 THAI CHARACTER KHO KHUAT */ +{"Thai_khokhwai",                 0x0da4},  /* U+0E04 THAI CHARACTER KHO KHWAI */ +{"Thai_khorakhang",               0x0da6},  /* U+0E06 THAI CHARACTER KHO RAKHANG */ +{"Thai_kokai",                    0x0da1},  /* U+0E01 THAI CHARACTER KO KAI */ +{"Thai_lakkhangyao",              0x0de5},  /* U+0E45 THAI CHARACTER LAKKHANGYAO */ +{"Thai_lekchet",                  0x0df7},  /* U+0E57 THAI DIGIT SEVEN */ +{"Thai_lekha",                    0x0df5},  /* U+0E55 THAI DIGIT FIVE */ +{"Thai_lekhok",                   0x0df6},  /* U+0E56 THAI DIGIT SIX */ +{"Thai_lekkao",                   0x0df9},  /* U+0E59 THAI DIGIT NINE */ +{"Thai_leknung",                  0x0df1},  /* U+0E51 THAI DIGIT ONE */ +{"Thai_lekpaet",                  0x0df8},  /* U+0E58 THAI DIGIT EIGHT */ +{"Thai_leksam",                   0x0df3},  /* U+0E53 THAI DIGIT THREE */ +{"Thai_leksi",                    0x0df4},  /* U+0E54 THAI DIGIT FOUR */ +{"Thai_leksong",                  0x0df2},  /* U+0E52 THAI DIGIT TWO */ +{"Thai_leksun",                   0x0df0},  /* U+0E50 THAI DIGIT ZERO */ +{"Thai_lochula",                  0x0dcc},  /* U+0E2C THAI CHARACTER LO CHULA */ +{"Thai_loling",                   0x0dc5},  /* U+0E25 THAI CHARACTER LO LING */ +{"Thai_lu",                       0x0dc6},  /* U+0E26 THAI CHARACTER LU */ +{"Thai_maichattawa",              0x0deb},  /* U+0E4B THAI CHARACTER MAI CHATTAWA */ +{"Thai_maiek",                    0x0de8},  /* U+0E48 THAI CHARACTER MAI EK */ +{"Thai_maihanakat",               0x0dd1},  /* U+0E31 THAI CHARACTER MAI HAN-AKAT */ +{"Thai_maitaikhu",                0x0de7},  /* U+0E47 THAI CHARACTER MAITAIKHU */ +{"Thai_maitho",                   0x0de9},  /* U+0E49 THAI CHARACTER MAI THO */ +{"Thai_maitri",                   0x0dea},  /* U+0E4A THAI CHARACTER MAI TRI */ +{"Thai_maiyamok",                 0x0de6},  /* U+0E46 THAI CHARACTER MAIYAMOK */ +{"Thai_moma",                     0x0dc1},  /* U+0E21 THAI CHARACTER MO MA */ +{"Thai_ngongu",                   0x0da7},  /* U+0E07 THAI CHARACTER NGO NGU */ +{"Thai_nikhahit",                 0x0ded},  /* U+0E4D THAI CHARACTER NIKHAHIT */ +{"Thai_nonen",                    0x0db3},  /* U+0E13 THAI CHARACTER NO NEN */ +{"Thai_nonu",                     0x0db9},  /* U+0E19 THAI CHARACTER NO NU */ +{"Thai_oang",                     0x0dcd},  /* U+0E2D THAI CHARACTER O ANG */ +{"Thai_paiyannoi",                0x0dcf},  /* U+0E2F THAI CHARACTER PAIYANNOI */ +{"Thai_phinthu",                  0x0dda},  /* U+0E3A THAI CHARACTER PHINTHU */ +{"Thai_phophan",                  0x0dbe},  /* U+0E1E THAI CHARACTER PHO PHAN */ +{"Thai_phophung",                 0x0dbc},  /* U+0E1C THAI CHARACTER PHO PHUNG */ +{"Thai_phosamphao",               0x0dc0},  /* U+0E20 THAI CHARACTER PHO SAMPHAO */ +{"Thai_popla",                    0x0dbb},  /* U+0E1B THAI CHARACTER PO PLA */ +{"Thai_rorua",                    0x0dc3},  /* U+0E23 THAI CHARACTER RO RUA */ +{"Thai_ru",                       0x0dc4},  /* U+0E24 THAI CHARACTER RU */ +{"Thai_saraa",                    0x0dd0},  /* U+0E30 THAI CHARACTER SARA A */ +{"Thai_saraaa",                   0x0dd2},  /* U+0E32 THAI CHARACTER SARA AA */ +{"Thai_saraae",                   0x0de1},  /* U+0E41 THAI CHARACTER SARA AE */ +{"Thai_saraaimaimalai",           0x0de4},  /* U+0E44 THAI CHARACTER SARA AI MAIMALAI */ +{"Thai_saraaimaimuan",            0x0de3},  /* U+0E43 THAI CHARACTER SARA AI MAIMUAN */ +{"Thai_saraam",                   0x0dd3},  /* U+0E33 THAI CHARACTER SARA AM */ +{"Thai_sarae",                    0x0de0},  /* U+0E40 THAI CHARACTER SARA E */ +{"Thai_sarai",                    0x0dd4},  /* U+0E34 THAI CHARACTER SARA I */ +{"Thai_saraii",                   0x0dd5},  /* U+0E35 THAI CHARACTER SARA II */ +{"Thai_sarao",                    0x0de2},  /* U+0E42 THAI CHARACTER SARA O */ +{"Thai_sarau",                    0x0dd8},  /* U+0E38 THAI CHARACTER SARA U */ +{"Thai_saraue",                   0x0dd6},  /* U+0E36 THAI CHARACTER SARA UE */ +{"Thai_sarauee",                  0x0dd7},  /* U+0E37 THAI CHARACTER SARA UEE */ +{"Thai_sarauu",                   0x0dd9},  /* U+0E39 THAI CHARACTER SARA UU */ +{"Thai_sorusi",                   0x0dc9},  /* U+0E29 THAI CHARACTER SO RUSI */ +{"Thai_sosala",                   0x0dc8},  /* U+0E28 THAI CHARACTER SO SALA */ +{"Thai_soso",                     0x0dab},  /* U+0E0B THAI CHARACTER SO SO */ +{"Thai_sosua",                    0x0dca},  /* U+0E2A THAI CHARACTER SO SUA */ +{"Thai_thanthakhat",              0x0dec},  /* U+0E4C THAI CHARACTER THANTHAKHAT */ +{"Thai_thonangmontho",            0x0db1},  /* U+0E11 THAI CHARACTER THO NANGMONTHO */ +{"Thai_thophuthao",               0x0db2},  /* U+0E12 THAI CHARACTER THO PHUTHAO */ +{"Thai_thothahan",                0x0db7},  /* U+0E17 THAI CHARACTER THO THAHAN */ +{"Thai_thothan",                  0x0db0},  /* U+0E10 THAI CHARACTER THO THAN */ +{"Thai_thothong",                 0x0db8},  /* U+0E18 THAI CHARACTER THO THONG */ +{"Thai_thothung",                 0x0db6},  /* U+0E16 THAI CHARACTER THO THUNG */ +{"Thai_topatak",                  0x0daf},  /* U+0E0F THAI CHARACTER TO PATAK */ +{"Thai_totao",                    0x0db5},  /* U+0E15 THAI CHARACTER TO TAO */ +{"Thai_wowaen",                   0x0dc7},  /* U+0E27 THAI CHARACTER WO WAEN */ +{"Thai_yoyak",                    0x0dc2},  /* U+0E22 THAI CHARACTER YO YAK */ +{"Thai_yoying",                   0x0dad},  /* U+0E0D THAI CHARACTER YO YING */ +{"threeeighths",                  0x0ac4},  /* U+215C VULGAR FRACTION THREE EIGHTHS */ +{"trademark",                     0x0ac9},  /* U+2122 TRADE MARK SIGN */ +{"tslash",                        0x03bc},  /* U+0167 LATIN SMALL LETTER T WITH STROKE */ +{"Tslash",                        0x03ac},  /* U+0166 LATIN CAPITAL LETTER T WITH STROKE */ +{"umacron",                       0x03fe},  /* U+016B LATIN SMALL LETTER U WITH MACRON */ +{"Umacron",                       0x03de},  /* U+016A LATIN CAPITAL LETTER U WITH MACRON */ +{"uogonek",                       0x03f9},  /* U+0173 LATIN SMALL LETTER U WITH OGONEK */ +{"Uogonek",                       0x03d9},  /* U+0172 LATIN CAPITAL LETTER U WITH OGONEK */ +{"uparrow",                       0x08fc},  /* U+2191 UPWARDS ARROW */ +{"voicedsound",                   0x04de},  /* U+309B KATAKANA-HIRAGANA VOICED SOUND MARK */ +{"Zcaron",                        0x01ae},  /* U+017D LATIN CAPITAL LETTER Z WITH CARON */ + +{NULL,0}, +}; diff --git a/ui/x_keymap.c b/ui/x_keymap.c new file mode 100644 index 00000000..1a773174 --- /dev/null +++ b/ui/x_keymap.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. + */ +#include "qemu-common.h" +#include "x_keymap.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 */ +}; + +/* 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 const uint8_t evdev_keycode_to_pc_keycode[61] = { +    0x73,      /*  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 - ???? */ +    0x7e,      /* 129 EVDEV - KP_COMMA (brazilian) */ +    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 */ +}; + +uint8_t translate_xfree86_keycode(const int key) +{ +    return x_keycode_to_pc_keycode[key]; +} + +uint8_t translate_evdev_keycode(const int key) +{ +    return evdev_keycode_to_pc_keycode[key]; +} diff --git a/ui/x_keymap.h b/ui/x_keymap.h new file mode 100644 index 00000000..afde2e94 --- /dev/null +++ b/ui/x_keymap.h @@ -0,0 +1,32 @@ +/* + * 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. + */ + +#ifndef QEMU_X_KEYMAP_H +#define QEMU_X_KEYMAP_H + +uint8_t translate_xfree86_keycode(const int key); + +uint8_t translate_evdev_keycode(const int key); + +#endif  | 
