diff options
author | Corentin Chary <corentincj@iksaif.net> | 2010-07-07 20:57:52 +0200 |
---|---|---|
committer | Anthony Liguori <aliguori@us.ibm.com> | 2010-07-26 17:35:54 -0500 |
commit | 3e230dd23b750f19a2fb95cf08250c74432ed164 (patch) | |
tree | 9397dccd2f430da4f644fbf83ea11e92ea046641 /ui | |
parent | 6f9c78c1499bd2fb3c31bf87d9b7c1b10c188dfb (diff) |
ui: move all ui components in ui/
Move sdl, vnc, curses and cocoa UI into ui/ to cleanup
the root directory. Also remove some unnecessary explicit
targets from Makefile.
aliguori: fix build when srcdir != objdir
Signed-off-by: Corentin Chary <corentincj@iksaif.net>
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
Diffstat (limited to 'ui')
-rw-r--r-- | ui/cocoa.m | 1014 | ||||
-rw-r--r-- | ui/curses.c | 365 | ||||
-rw-r--r-- | ui/curses_keys.h | 508 | ||||
-rw-r--r-- | ui/d3des.c | 424 | ||||
-rw-r--r-- | ui/d3des.h | 51 | ||||
-rw-r--r-- | ui/keymaps.c | 210 | ||||
-rw-r--r-- | ui/keymaps.h | 77 | ||||
-rw-r--r-- | ui/sdl.c | 879 | ||||
-rw-r--r-- | ui/sdl_keysym.h | 277 | ||||
-rw-r--r-- | ui/sdl_zoom.c | 95 | ||||
-rw-r--r-- | ui/sdl_zoom.h | 25 | ||||
-rw-r--r-- | ui/sdl_zoom_template.h | 225 | ||||
-rw-r--r-- | ui/vnc-auth-sasl.c | 637 | ||||
-rw-r--r-- | ui/vnc-auth-sasl.h | 74 | ||||
-rw-r--r-- | ui/vnc-auth-vencrypt.c | 175 | ||||
-rw-r--r-- | ui/vnc-auth-vencrypt.h | 33 | ||||
-rw-r--r-- | ui/vnc-encoding-hextile.c | 116 | ||||
-rw-r--r-- | ui/vnc-encoding-tight.c | 1520 | ||||
-rw-r--r-- | ui/vnc-encoding-tight.h | 181 | ||||
-rw-r--r-- | ui/vnc-encoding-zlib.c | 152 | ||||
-rw-r--r-- | ui/vnc-tls.c | 445 | ||||
-rw-r--r-- | ui/vnc-tls.h | 76 | ||||
-rw-r--r-- | ui/vnc.c | 2631 | ||||
-rw-r--r-- | ui/vnc.h | 430 | ||||
-rw-r--r-- | ui/vnc_keysym.h | 324 | ||||
-rw-r--r-- | ui/vnchextile.h | 211 | ||||
-rw-r--r-- | ui/x_keymap.c | 168 | ||||
-rw-r--r-- | ui/x_keymap.h | 32 |
28 files changed, 11355 insertions, 0 deletions
diff --git a/ui/cocoa.m b/ui/cocoa.m new file mode 100644 index 0000000000..56c789a98c --- /dev/null +++ b/ui/cocoa.m @@ -0,0 +1,1014 @@ +/* + * 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 "qemu-common.h" +#include "console.h" +#include "sysemu.h" + +#ifndef MAC_OS_X_VERSION_10_4 +#define MAC_OS_X_VERSION_10_4 1040 +#endif +#ifndef MAC_OS_X_VERSION_10_5 +#define MAC_OS_X_VERSION_10_5 1050 +#endif + + +//#define DEBUG + +#ifdef DEBUG +#define COCOA_DEBUG(...) { (void) fprintf (stdout, __VA_ARGS__); } +#else +#define COCOA_DEBUG(...) ((void) 0) +#endif + +#define cgrect(nsrect) (*(CGRect *)&(nsrect)) +#define COCOA_MOUSE_EVENT \ + if (isTabletEnabled) { \ + kbd_mouse_event((int)(p.x * 0x7FFF / (screen.width - 1)), (int)((screen.height - p.y) * 0x7FFF / (screen.height - 1)), 0, buttons); \ + } else if (isMouseGrabed) { \ + kbd_mouse_event((int)[event deltaX], (int)[event deltaY], 0, buttons); \ + } else { \ + [NSApp sendEvent:event]; \ + } + +typedef struct { + int width; + int height; + int bitsPerComponent; + int bitsPerPixel; +} QEMUScreen; + +int qemu_main(int argc, char **argv); // main defined in qemu/vl.c +NSWindow *normalWindow; +id cocoaView; +static DisplayChangeListener *dcl; + +int gArgc; +char **gArgv; + +// 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 + 0, // 54 0x36 QZ_RMETA + 0, // 55 0x37 QZ_LMETA + 42, // 56 0x38 0x2a L SHFT QZ_LSHIFT + 58, // 57 0x39 0x3a CAPS QZ_CAPSLOCK + 56, // 58 0x3A 0x38 L ALT QZ_LALT + 29, // 59 0x3B 0x1d L CTRL QZ_LCTRL + 54, // 60 0x3C 0x36 R SHFT QZ_RSHIFT + 184,// 61 0x3D 0xb8 E0,38 R ALT QZ_RALT + 157,// 62 0x3E 0x9d E0,1D R CTRL QZ_RCTRL + 0, // 63 0x3F Undefined + 0, // 64 0x40 Undefined + 0, // 65 0x41 Undefined + 0, // 66 0x42 Undefined + 55, // 67 0x43 0x37 KP * QZ_KP_MULTIPLY + 0, // 68 0x44 Undefined + 78, // 69 0x45 0x4e KP + QZ_KP_PLUS + 0, // 70 0x46 Undefined + 69, // 71 0x47 0x45 NUM QZ_NUMLOCK + 0, // 72 0x48 Undefined + 0, // 73 0x49 Undefined + 0, // 74 0x4A Undefined + 181,// 75 0x4B 0xb5 E0,35 KP / QZ_KP_DIVIDE + 152,// 76 0x4C 0x9c E0,1C KP EN QZ_KP_ENTER + 0, // 77 0x4D undefined + 74, // 78 0x4E 0x4a KP - QZ_KP_MINUS + 0, // 79 0x4F Undefined + 0, // 80 0x50 Undefined + 0, // 81 0x51 QZ_KP_EQUALS + 82, // 82 0x52 0x52 KP 0 QZ_KP0 + 79, // 83 0x53 0x4f KP 1 QZ_KP1 + 80, // 84 0x54 0x50 KP 2 QZ_KP2 + 81, // 85 0x55 0x51 KP 3 QZ_KP3 + 75, // 86 0x56 0x4b KP 4 QZ_KP4 + 76, // 87 0x57 0x4c KP 5 QZ_KP5 + 77, // 88 0x58 0x4d KP 6 QZ_KP6 + 71, // 89 0x59 0x47 KP 7 QZ_KP7 + 0, // 90 0x5A Undefined + 72, // 91 0x5B 0x48 KP 8 QZ_KP8 + 73, // 92 0x5C 0x49 KP 9 QZ_KP9 + 0, // 93 0x5D Undefined + 0, // 94 0x5E Undefined + 0, // 95 0x5F Undefined + 63, // 96 0x60 0x3f F5 QZ_F5 + 64, // 97 0x61 0x40 F6 QZ_F6 + 65, // 98 0x62 0x41 F7 QZ_F7 + 61, // 99 0x63 0x3d F3 QZ_F3 + 66, // 100 0x64 0x42 F8 QZ_F8 + 67, // 101 0x65 0x43 F9 QZ_F9 + 0, // 102 0x66 Undefined + 87, // 103 0x67 0x57 F11 QZ_F11 + 0, // 104 0x68 Undefined + 183,// 105 0x69 0xb7 QZ_PRINT + 0, // 106 0x6A Undefined + 70, // 107 0x6B 0x46 SCROLL QZ_SCROLLOCK + 0, // 108 0x6C Undefined + 68, // 109 0x6D 0x44 F10 QZ_F10 + 0, // 110 0x6E Undefined + 88, // 111 0x6F 0x58 F12 QZ_F12 + 0, // 112 0x70 Undefined + 110,// 113 0x71 0x0 QZ_PAUSE + 210,// 114 0x72 0xd2 E0,52 INSERT QZ_INSERT + 199,// 115 0x73 0xc7 E0,47 HOME QZ_HOME + 201,// 116 0x74 0xc9 E0,49 PG UP QZ_PAGEUP + 211,// 117 0x75 0xd3 E0,53 DELETE QZ_DELETE + 62, // 118 0x76 0x3e F4 QZ_F4 + 207,// 119 0x77 0xcf E0,4f END QZ_END + 60, // 120 0x78 0x3c F2 QZ_F2 + 209,// 121 0x79 0xd1 E0,51 PG DN QZ_PAGEDOWN + 59, // 122 0x7A 0x3b F1 QZ_F1 + 203,// 123 0x7B 0xcb e0,4B L ARROW QZ_LEFT + 205,// 124 0x7C 0xcd e0,4D R ARROW QZ_RIGHT + 208,// 125 0x7D 0xd0 E0,50 D ARROW QZ_DOWN + 200,// 126 0x7E 0xc8 E0,48 U ARROW QZ_UP +/* completed according to http://www.libsdl.org/cgi/cvsweb.cgi/SDL12/src/video/quartz/SDL_QuartzKeys.h?rev=1.6&content-type=text/x-cvsweb-markup */ + +/* Aditional 104 Key XP-Keyboard Scancodes from http://www.computer-engineering.org/ps2keyboard/scancodes1.html */ +/* + 219 // 0xdb e0,5b L GUI + 220 // 0xdc e0,5c R GUI + 221 // 0xdd e0,5d APPS + // E0,2A,E0,37 PRNT SCRN + // E1,1D,45,E1,9D,C5 PAUSE + 83 // 0x53 0x53 KP . +// ACPI Scan Codes + 222 // 0xde E0, 5E Power + 223 // 0xdf E0, 5F Sleep + 227 // 0xe3 E0, 63 Wake +// Windows Multimedia Scan Codes + 153 // 0x99 E0, 19 Next Track + 144 // 0x90 E0, 10 Previous Track + 164 // 0xa4 E0, 24 Stop + 162 // 0xa2 E0, 22 Play/Pause + 160 // 0xa0 E0, 20 Mute + 176 // 0xb0 E0, 30 Volume Up + 174 // 0xae E0, 2E Volume Down + 237 // 0xed E0, 6D Media Select + 236 // 0xec E0, 6C E-Mail + 161 // 0xa1 E0, 21 Calculator + 235 // 0xeb E0, 6B My Computer + 229 // 0xe5 E0, 65 WWW Search + 178 // 0xb2 E0, 32 WWW Home + 234 // 0xea E0, 6A WWW Back + 233 // 0xe9 E0, 69 WWW Forward + 232 // 0xe8 E0, 68 WWW Stop + 231 // 0xe7 E0, 67 WWW Refresh + 230 // 0xe6 E0, 66 WWW Favorites +*/ +}; + +static int cocoa_keycode_to_qemu(int keycode) +{ + if((sizeof(keymap)/sizeof(int)) <= keycode) + { + printf("(cocoa) warning unknow keycode 0x%x\n", keycode); + return 0; + } + return keymap[keycode]; +} + + + +/* + ------------------------------------------------------ + QemuCocoaView + ------------------------------------------------------ +*/ +@interface QemuCocoaView : NSView +{ + QEMUScreen screen; + NSWindow *fullScreenWindow; + float cx,cy,cw,ch,cdx,cdy; + CGDataProviderRef dataProviderRef; + int modifiers_state[256]; + BOOL isMouseGrabed; + BOOL isFullscreen; + BOOL isAbsoluteEnabled; + BOOL isTabletEnabled; +} +- (void) resizeContentToWidth:(int)w height:(int)h displayState:(DisplayState *)ds; +- (void) grabMouse; +- (void) ungrabMouse; +- (void) toggleFullScreen:(id)sender; +- (void) handleEvent:(NSEvent *)event; +- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled; +- (BOOL) isMouseGrabed; +- (BOOL) isAbsoluteEnabled; +- (float) cdx; +- (float) cdy; +- (QEMUScreen) gscreen; +@end + +@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; +} + +- (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) { + 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 + ); +// test if host supports "CGImageCreateWithImageInRect" at compile time +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4) + if (CGImageCreateWithImageInRect == NULL) { // test if "CGImageCreateWithImageInRect" is supported on host at runtime +#endif + // compatibility drawing code (draws everything) (OS X < 10.4) + CGContextDrawImage (viewContextRef, CGRectMake(0, 0, [self bounds].size.width, [self bounds].size.height), imageRef); +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4) + } else { + // selective drawing code (draws only dirty rectangles) (OS X >= 10.4) + const NSRect *rectList; +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + NSInteger rectCount; +#else + int rectCount; +#endif + int i; + CGImageRef clipImageRef; + CGRect clipRect; + + [self getRectsBeingDrawn:&rectList count:&rectCount]; + for (i = 0; i < rectCount; i++) { + clipRect.origin.x = rectList[i].origin.x / cdx; + clipRect.origin.y = (float)screen.height - (rectList[i].origin.y + rectList[i].size.height) / cdy; + clipRect.size.width = rectList[i].size.width / cdx; + clipRect.size.height = rectList[i].size.height / cdy; + clipImageRef = CGImageCreateWithImageInRect( + imageRef, + clipRect + ); + CGContextDrawImage (viewContextRef, cgrect(rectList[i]), clipImageRef); + CGImageRelease (clipImageRef); + } + } +#endif + 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; + 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) resizeContentToWidth:(int)w height:(int)h displayState:(DisplayState *)ds +{ + COCOA_DEBUG("QemuCocoaView: resizeContent\n"); + + // update screenBuffer + if (dataProviderRef) + CGDataProviderRelease(dataProviderRef); + + //sync host window color space with guests + screen.bitsPerPixel = ds_get_bits_per_pixel(ds); + screen.bitsPerComponent = ds_get_bytes_per_pixel(ds) * 2; + + dataProviderRef = CGDataProviderCreateWithData(NULL, ds_get_data(ds), w * 4 * h, NULL); + + // update windows + if (isFullscreen) { + [[fullScreenWindow contentView] setFrame:[[NSScreen mainScreen] frame]]; + [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + screen.height, w, h + [normalWindow frame].size.height - screen.height) display:NO animate:NO]; + } else { + if (qemu_name) + [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]]; + [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + screen.height, w, h + [normalWindow frame].size.height - screen.height) display:YES animate:NO]; + } + screen.width = w; + screen.height = h; + [normalWindow center]; + [self setContentDimensions]; + [self setFrame:NSMakeRect(cx, cy, cw, ch)]; +} + +- (void) toggleFullScreen:(id)sender +{ + COCOA_DEBUG("QemuCocoaView: toggleFullScreen\n"); + + if (isFullscreen) { // switch from fullscreen to desktop + isFullscreen = FALSE; + [self ungrabMouse]; + [self setContentDimensions]; +// test if host supports "exitFullScreenModeWithOptions" at compile time +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + if ([NSView respondsToSelector:@selector(exitFullScreenModeWithOptions:)]) { // test if "exitFullScreenModeWithOptions" is supported on host at runtime + [self exitFullScreenModeWithOptions:nil]; + } else { +#endif + [fullScreenWindow close]; + [normalWindow setContentView: self]; + [normalWindow makeKeyAndOrderFront: self]; + [NSMenu setMenuBarVisible:YES]; +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + } +#endif + } else { // switch from desktop to fullscreen + isFullscreen = TRUE; + [self grabMouse]; + [self setContentDimensions]; +// test if host supports "enterFullScreenMode:withOptions" at compile time +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + if ([NSView respondsToSelector:@selector(enterFullScreenMode:withOptions:)]) { // test if "enterFullScreenMode:withOptions" is supported on host at runtime + [self enterFullScreenMode:[NSScreen mainScreen] withOptions:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithBool:NO], NSFullScreenModeAllScreens, + [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO], kCGDisplayModeIsStretched, nil], NSFullScreenModeSetting, + nil]]; + } else { +#endif + [NSMenu setMenuBarVisible:NO]; + fullScreenWindow = [[NSWindow alloc] initWithContentRect:[[NSScreen mainScreen] frame] + styleMask:NSBorderlessWindowMask + backing:NSBackingStoreBuffered + defer:NO]; + [fullScreenWindow setHasShadow:NO]; + [fullScreenWindow setContentView:self]; + [fullScreenWindow makeKeyAndOrderFront:self]; +#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) + } +#endif + } +} + +- (void) handleEvent:(NSEvent *)event +{ + COCOA_DEBUG("QemuCocoaView: handleEvent\n"); + + int buttons = 0; + int keycode; + NSPoint p = [event locationInWindow]; + + switch ([event type]) { + case NSFlagsChanged: + keycode = cocoa_keycode_to_qemu([event keyCode]); + if (keycode) { + if (keycode == 58 || keycode == 69) { // emulate caps lock and num lock keydown and keyup + kbd_put_keycode(keycode); + kbd_put_keycode(keycode | 0x80); + } else if (is_graphic_console()) { + if (keycode & 0x80) + kbd_put_keycode(0xe0); + if (modifiers_state[keycode] == 0) { // keydown + kbd_put_keycode(keycode & 0x7f); + modifiers_state[keycode] = 1; + } else { // keyup + kbd_put_keycode(keycode | 0x80); + modifiers_state[keycode] = 0; + } + } + } + + // release Mouse grab when pressing ctrl+alt + if (!isFullscreen && ([event modifierFlags] & NSControlKeyMask) && ([event modifierFlags] & NSAlternateKeyMask)) { + [self ungrabMouse]; + } + break; + case NSKeyDown: + + // forward command Key Combos + if ([event modifierFlags] & NSCommandKeyMask) { + [NSApp sendEvent:event]; + return; + } + + // default + keycode = cocoa_keycode_to_qemu([event keyCode]); + + // handle control + alt Key Combos (ctrl+alt is reserved for QEMU) + if (([event modifierFlags] & NSControlKeyMask) && ([event modifierFlags] & NSAlternateKeyMask)) { + switch (keycode) { + + // enable graphic console + case 0x02 ... 0x0a: // '1' to '9' keys + console_select(keycode - 0x02); + break; + } + + // handle keys for graphic console + } else if (is_graphic_console()) { + if (keycode & 0x80) //check bit for e0 in front + kbd_put_keycode(0xe0); + kbd_put_keycode(keycode & 0x7f); //remove e0 bit in front + + // 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]); + if (is_graphic_console()) { + if (keycode & 0x80) + kbd_put_keycode(0xe0); + kbd_put_keycode(keycode | 0x80); //add 128 to signal release of key + } + break; + case NSMouseMoved: + if (isAbsoluteEnabled) { + if (p.x < 0 || p.x > screen.width || p.y < 0 || p.y > screen.height || ![[self window] isKeyWindow]) { + if (isTabletEnabled) { // if we leave the window, deactivate the tablet + [NSCursor unhide]; + isTabletEnabled = FALSE; + } + } else { + if (!isTabletEnabled) { // if we enter the window, activate the tablet + [NSCursor hide]; + isTabletEnabled = TRUE; + } + } + } + COCOA_MOUSE_EVENT + break; + case NSLeftMouseDown: + if ([event modifierFlags] & NSCommandKeyMask) { + buttons |= MOUSE_EVENT_RBUTTON; + } else { + buttons |= MOUSE_EVENT_LBUTTON; + } + COCOA_MOUSE_EVENT + break; + case NSRightMouseDown: + buttons |= MOUSE_EVENT_RBUTTON; + COCOA_MOUSE_EVENT + break; + case NSOtherMouseDown: + buttons |= MOUSE_EVENT_MBUTTON; + COCOA_MOUSE_EVENT + break; + case NSLeftMouseDragged: + if ([event modifierFlags] & NSCommandKeyMask) { + buttons |= MOUSE_EVENT_RBUTTON; + } else { + buttons |= MOUSE_EVENT_LBUTTON; + } + COCOA_MOUSE_EVENT + break; + case NSRightMouseDragged: + buttons |= MOUSE_EVENT_RBUTTON; + COCOA_MOUSE_EVENT + break; + case NSOtherMouseDragged: + buttons |= MOUSE_EVENT_MBUTTON; + COCOA_MOUSE_EVENT + break; + case NSLeftMouseUp: + if (isTabletEnabled) { + COCOA_MOUSE_EVENT + } else if (!isMouseGrabed) { + if (p.x > -1 && p.x < screen.width && p.y > -1 && p.y < screen.height) { + [self grabMouse]; + } else { + [NSApp sendEvent:event]; + } + } else { + COCOA_MOUSE_EVENT + } + break; + case NSRightMouseUp: + COCOA_MOUSE_EVENT + break; + case NSOtherMouseUp: + COCOA_MOUSE_EVENT + break; + case NSScrollWheel: + if (isTabletEnabled || isMouseGrabed) { + kbd_mouse_event(0, 0, -[event deltaY], 0); + } else { + [NSApp sendEvent:event]; + } + break; + default: + [NSApp sendEvent:event]; + } +} + +- (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)"]; + } + [NSCursor hide]; + CGAssociateMouseAndMouseCursorPosition(FALSE); + isMouseGrabed = TRUE; // while isMouseGrabed = 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"]; + } + [NSCursor unhide]; + CGAssociateMouseAndMouseCursorPosition(TRUE); + isMouseGrabed = FALSE; +} + +- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled {isAbsoluteEnabled = tIsAbsoluteEnabled;} +- (BOOL) isMouseGrabed {return isMouseGrabed;} +- (BOOL) isAbsoluteEnabled {return isAbsoluteEnabled;} +- (float) cdx {return cdx;} +- (float) cdy {return cdy;} +- (QEMUScreen) gscreen {return screen;} +@end + + + +/* + ------------------------------------------------------ + QemuCocoaAppController + ------------------------------------------------------ +*/ +@interface QemuCocoaAppController : NSObject +{ +} +- (void)startEmulationWithArgc:(int)argc argv:(char**)argv; +- (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo; +- (void)toggleFullScreen:(id)sender; +- (void)showQEMUDoc:(id)sender; +- (void)showQEMUTec:(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]; + [normalWindow useOptimizedDrawing:YES]; + [normalWindow makeKeyAndOrderFront:self]; + [normalWindow center]; + + } + 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 argument were passed or + // if qemu was launched from the finder ( the Finder passes "-psn" ) + if( gArgc <= 1 || strncmp ((char *)gArgv[1], "-psn", 4) == 0) { + NSOpenPanel *op = [[NSOpenPanel alloc] init]; + [op setPrompt:@"Boot image"]; + [op setMessage:@"Select the disk image you want to boot.\n\nHit the \"Cancel\" button to quit"]; + [op beginSheetForDirectory:nil file:nil types:[NSArray arrayWithObjects:@"img",@"iso",@"dmg",@"qcow",@"cow",@"cloop",@"vmdk",nil] + modalForWindow:normalWindow modalDelegate:self + didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:) contextInfo:NULL]; + } 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); + exit(status); +} + +- (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo +{ + COCOA_DEBUG("QemuCocoaAppController: openPanelDidEnd\n"); + + if(returnCode == NSCancelButton) { + exit(0); + } else if(returnCode == NSOKButton) { + const char *bin = "qemu"; + char *img = (char*)[ [ sheet filename ] cStringUsingEncoding:NSASCIIStringEncoding]; + + char **argv = (char**)malloc( sizeof(char*)*3 ); + + asprintf(&argv[0], "%s", bin); + asprintf(&argv[1], "-hda"); + asprintf(&argv[2], "%s", img); + + printf("Using argc %d argv %s -hda %s\n", 3, bin, img); + + [self startEmulationWithArgc:3 argv:(char**)argv]; + } +} +- (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"]; +} +@end + + + +// Dock Connection +typedef struct CPSProcessSerNum +{ + UInt32 lo; + UInt32 hi; +} CPSProcessSerNum; + +extern OSErr CPSGetCurrentProcess( CPSProcessSerNum *psn); +extern OSErr CPSEnableForegroundOperation( CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5); +extern OSErr CPSSetFrontProcess( CPSProcessSerNum *psn); + +int main (int argc, const char * argv[]) { + + gArgc = argc; + gArgv = (char **)argv; + CPSProcessSerNum PSN; + int i; + + /* In case we don't need to display a window, let's not do that */ + for (i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-vnc") || + !strcmp(argv[i], "-nographic") || + !strcmp(argv[i], "-curses")) { + return qemu_main(gArgc, gArgv); + } + } + + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + [NSApplication sharedApplication]; + + if (!CPSGetCurrentProcess(&PSN)) + if (!CPSEnableForegroundOperation(&PSN,0x03,0x3C,0x2C,0x1103)) + if (!CPSSetFrontProcess(&PSN)) + [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+) + + // View menu + menu = [[NSMenu alloc] initWithTitle:@"View"]; + [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(toggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen + 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(DisplayState *ds, int x, int y, int w, int h) +{ + 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]; +} + +static void cocoa_resize(DisplayState *ds) +{ + COCOA_DEBUG("qemu_cocoa: cocoa_resize\n"); + + [cocoaView resizeContentToWidth:(int)(ds_get_width(ds)) height:(int)(ds_get_height(ds)) displayState:ds]; +} + +static void cocoa_refresh(DisplayState *ds) +{ + COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n"); + + if (kbd_mouse_is_absolute()) { + if (![cocoaView isAbsoluteEnabled]) { + if ([cocoaView isMouseGrabed]) { + [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); + vga_hw_update(); +} + +static void cocoa_cleanup(void) +{ + COCOA_DEBUG("qemu_cocoa: cocoa_cleanup\n"); + qemu_free(dcl); +} + +void cocoa_display_init(DisplayState *ds, int full_screen) +{ + COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n"); + + dcl = qemu_mallocz(sizeof(DisplayChangeListener)); + + // register vga output callbacks + dcl->dpy_update = cocoa_update; + dcl->dpy_resize = cocoa_resize; + dcl->dpy_refresh = cocoa_refresh; + + register_displaychangelistener(ds, dcl); + + // register cleanup function + atexit(cocoa_cleanup); +} diff --git a/ui/curses.c b/ui/curses.c new file mode 100644 index 0000000000..ed3165e45e --- /dev/null +++ b/ui/curses.c @@ -0,0 +1,365 @@ +/* + * 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 <signal.h> +#include <sys/ioctl.h> +#include <termios.h> +#endif + +#ifdef __OpenBSD__ +#define resize_term resizeterm +#endif + +#include "qemu-common.h" +#include "console.h" +#include "sysemu.h" + +#define FONT_HEIGHT 16 +#define FONT_WIDTH 8 + +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(DisplayState *ds, 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 (is_fixedsize_console()) { + 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(DisplayState *ds) +{ + if (ds_get_width(ds) == gwidth && ds_get_height(ds) == gheight) + return; + + gwidth = ds_get_width(ds); + gheight = ds_get_height(ds); + + curses_calc_pad(); + ds->surface->width = width * FONT_WIDTH; + ds->surface->height = height * FONT_HEIGHT; +} + +#ifndef _WIN32 +#if defined(SIGWINCH) && defined(KEY_RESIZE) +static void curses_winch_handler(int signum) +{ + struct winsize { + unsigned short ws_row; + unsigned short ws_col; + unsigned short ws_xpixel; /* unused */ + unsigned short ws_ypixel; /* unused */ + } ws; + + /* terminal size changed */ + if (ioctl(1, TIOCGWINSZ, &ws) == -1) + return; + + resize_term(ws.ws_row, ws.ws_col); + curses_calc_pad(); + invalidate = 1; + + /* some systems require this */ + signal(SIGWINCH, curses_winch_handler); +} +#endif +#endif + +static void curses_cursor_position(DisplayState *ds, 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 (!is_graphic_console()) + 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(DisplayState *ds) +{ + int chr, nextchr, keysym, keycode, keycode_alt; + + if (invalidate) { + clear(); + refresh(); + curses_calc_pad(); + ds->surface->width = FONT_WIDTH * width; + ds->surface->height = FONT_HEIGHT * height; + vga_hw_invalidate(); + invalidate = 0; + } + + vga_hw_text_update(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(ds, 0, 0, width, height); + ds->surface->width = FONT_WIDTH * width; + ds->surface->height = FONT_HEIGHT * 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 + '@' - 'A' + 'a') | 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 (is_graphic_console()) { + /* since terminals don't know about key press and release + * events, we need to emit both for each key received */ + if (keycode & SHIFT) + kbd_put_keycode(SHIFT_CODE); + if (keycode & CNTRL) + kbd_put_keycode(CNTRL_CODE); + if (keycode & ALT) + kbd_put_keycode(ALT_CODE); + if (keycode & ALTGR) { + kbd_put_keycode(SCANCODE_EMUL0); + kbd_put_keycode(ALT_CODE); + } + if (keycode & GREY) + kbd_put_keycode(GREY_CODE); + kbd_put_keycode(keycode & KEY_MASK); + if (keycode & GREY) + kbd_put_keycode(GREY_CODE); + kbd_put_keycode((keycode & KEY_MASK) | KEY_RELEASE); + if (keycode & ALTGR) { + kbd_put_keycode(SCANCODE_EMUL0); + kbd_put_keycode(ALT_CODE | KEY_RELEASE); + } + if (keycode & ALT) + kbd_put_keycode(ALT_CODE | KEY_RELEASE); + if (keycode & CNTRL) + kbd_put_keycode(CNTRL_CODE | KEY_RELEASE); + if (keycode & SHIFT) + kbd_put_keycode(SHIFT_CODE | KEY_RELEASE); + } else { + keysym = curses2qemu[chr]; + 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); + } +} + +void curses_display_init(DisplayState *ds, int full_screen) +{ + DisplayChangeListener *dcl; +#ifndef _WIN32 + if (!isatty(1)) { + fprintf(stderr, "We need a terminal output\n"); + exit(1); + } +#endif + + curses_setup(); + curses_keyboard_setup(); + atexit(curses_atexit); + +#ifndef _WIN32 +#if defined(SIGWINCH) && defined(KEY_RESIZE) + /* some curses implementations provide a handler, but we + * want to be sure this is handled regardless of the library */ + signal(SIGWINCH, curses_winch_handler); +#endif +#endif + + dcl = (DisplayChangeListener *) qemu_mallocz(sizeof(DisplayChangeListener)); + dcl->dpy_update = curses_update; + dcl->dpy_resize = curses_resize; + dcl->dpy_refresh = curses_refresh; + dcl->dpy_text_cursor = curses_cursor_position; + register_displaychangelistener(ds, dcl); + qemu_free_displaysurface(ds); + ds->surface = qemu_create_displaysurface_from(640, 400, 0, 0, (uint8_t*) screen); + + invalidate = 1; +} diff --git a/ui/curses_keys.h b/ui/curses_keys.h new file mode 100644 index 0000000000..1decd1119d --- /dev/null +++ b/ui/curses_keys.h @@ -0,0 +1,508 @@ +/* + * 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. + */ + +#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, + [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 }, +}; diff --git a/ui/d3des.c b/ui/d3des.c new file mode 100644 index 0000000000..60c840ed53 --- /dev/null +++ b/ui/d3des.c @@ -0,0 +1,424 @@ +/* + * This is D3DES (V5.09) by Richard Outerbridge with the double and + * triple-length support removed for use in VNC. Also the bytebit[] array + * has been reversed so that the most significant bit in each byte of the + * key is ignored, not the least significant. + * + * These changes are: + * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +/* D3DES (V5.09) - + * + * A portable, public domain, version of the Data Encryption Standard. + * + * Written with Symantec's THINK (Lightspeed) C by Richard Outerbridge. + * Thanks to: Dan Hoey for his excellent Initial and Inverse permutation + * code; Jim Gillogly & Phil Karn for the DES key schedule code; Dennis + * Ferguson, Eric Young and Dana How for comparing notes; and Ray Lau, + * for humouring me on. + * + * Copyright (c) 1988,1989,1990,1991,1992 by Richard Outerbridge. + * (GEnie : OUTER; CIS : [71755,204]) Graven Imagery, 1992. + */ + +#include "d3des.h" + +static void scrunch(unsigned char *, unsigned long *); +static void unscrun(unsigned long *, unsigned char *); +static void desfunc(unsigned long *, unsigned long *); +static void cookey(unsigned long *); + +static unsigned long KnL[32] = { 0L }; + +static const unsigned short bytebit[8] = { + 01, 02, 04, 010, 020, 040, 0100, 0200 }; + +static const unsigned long bigbyte[24] = { + 0x800000L, 0x400000L, 0x200000L, 0x100000L, + 0x80000L, 0x40000L, 0x20000L, 0x10000L, + 0x8000L, 0x4000L, 0x2000L, 0x1000L, + 0x800L, 0x400L, 0x200L, 0x100L, + 0x80L, 0x40L, 0x20L, 0x10L, + 0x8L, 0x4L, 0x2L, 0x1L }; + +/* Use the key schedule specified in the Standard (ANSI X3.92-1981). */ + +static const unsigned char pc1[56] = { + 56, 48, 40, 32, 24, 16, 8, 0, 57, 49, 41, 33, 25, 17, + 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, + 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, + 13, 5, 60, 52, 44, 36, 28, 20, 12, 4, 27, 19, 11, 3 }; + +static const unsigned char totrot[16] = { + 1,2,4,6,8,10,12,14,15,17,19,21,23,25,27,28 }; + +static const unsigned char pc2[48] = { + 13, 16, 10, 23, 0, 4, 2, 27, 14, 5, 20, 9, + 22, 18, 11, 3, 25, 7, 15, 6, 26, 19, 12, 1, + 40, 51, 30, 36, 46, 54, 29, 39, 50, 44, 32, 47, + 43, 48, 38, 55, 33, 52, 45, 41, 49, 35, 28, 31 }; + +/* Thanks to James Gillogly & Phil Karn! */ +void deskey(unsigned char *key, int edf) +{ + register int i, j, l, m, n; + unsigned char pc1m[56], pcr[56]; + unsigned long kn[32]; + + for ( j = 0; j < 56; j++ ) { + l = pc1[j]; + m = l & 07; + pc1m[j] = (key[l >> 3] & bytebit[m]) ? 1 : 0; + } + for( i = 0; i < 16; i++ ) { + if( edf == DE1 ) m = (15 - i) << 1; + else m = i << 1; + n = m + 1; + kn[m] = kn[n] = 0L; + for( j = 0; j < 28; j++ ) { + l = j + totrot[i]; + if( l < 28 ) pcr[j] = pc1m[l]; + else pcr[j] = pc1m[l - 28]; + } + for( j = 28; j < 56; j++ ) { + l = j + totrot[i]; + if( l < 56 ) pcr[j] = pc1m[l]; + else pcr[j] = pc1m[l - 28]; + } + for( j = 0; j < 24; j++ ) { + if( pcr[pc2[j]] ) kn[m] |= bigbyte[j]; + if( pcr[pc2[j+24]] ) kn[n] |= bigbyte[j]; + } + } + cookey(kn); + return; + } + +static void cookey(register unsigned long *raw1) +{ + register unsigned long *cook, *raw0; + unsigned long dough[32]; + register int i; + + cook = dough; + for( i = 0; i < 16; i++, raw1++ ) { + raw0 = raw1++; + *cook = (*raw0 & 0x00fc0000L) << 6; + *cook |= (*raw0 & 0x00000fc0L) << 10; + *cook |= (*raw1 & 0x00fc0000L) >> 10; + *cook++ |= (*raw1 & 0x00000fc0L) >> 6; + *cook = (*raw0 & 0x0003f000L) << 12; + *cook |= (*raw0 & 0x0000003fL) << 16; + *cook |= (*raw1 & 0x0003f000L) >> 4; + *cook++ |= (*raw1 & 0x0000003fL); + } + usekey(dough); + return; + } + +void cpkey(register unsigned long *into) +{ + register unsigned long *from, *endp; + + from = KnL, endp = &KnL[32]; + while( from < endp ) *into++ = *from++; + return; + } + +void usekey(register unsigned long *from) +{ + register unsigned long *to, *endp; + + to = KnL, endp = &KnL[32]; + while( to < endp ) *to++ = *from++; + return; + } + +void des(unsigned char *inblock, unsigned char *outblock) +{ + unsigned long work[2]; + + scrunch(inblock, work); + desfunc(work, KnL); + unscrun(work, outblock); + return; + } + +static void scrunch(register unsigned char *outof, register unsigned long *into) +{ + *into = (*outof++ & 0xffL) << 24; + *into |= (*outof++ & 0xffL) << 16; + *into |= (*outof++ & 0xffL) << 8; + *into++ |= (*outof++ & 0xffL); + *into = (*outof++ & 0xffL) << 24; + *into |= (*outof++ & 0xffL) << 16; + *into |= (*outof++ & 0xffL) << 8; + *into |= (*outof & 0xffL); + return; + } + +static void unscrun(register unsigned long *outof, register unsigned char *into) +{ + *into++ = (unsigned char)((*outof >> 24) & 0xffL); + *into++ = (unsigned char)((*outof >> 16) & 0xffL); + *into++ = (unsigned char)((*outof >> 8) & 0xffL); + *into++ = (unsigned char)(*outof++ & 0xffL); + *into++ = (unsigned char)((*outof >> 24) & 0xffL); + *into++ = (unsigned char)((*outof >> 16) & 0xffL); + *into++ = (unsigned char)((*outof >> 8) & 0xffL); + *into = (unsigned char)(*outof & 0xffL); + return; + } + +static const unsigned long SP1[64] = { + 0x01010400L, 0x00000000L, 0x00010000L, 0x01010404L, + 0x01010004L, 0x00010404L, 0x00000004L, 0x00010000L, + 0x00000400L, 0x01010400L, 0x01010404L, 0x00000400L, + 0x01000404L, 0x01010004L, 0x01000000L, 0x00000004L, + 0x00000404L, 0x01000400L, 0x01000400L, 0x00010400L, + 0x00010400L, 0x01010000L, 0x01010000L, 0x01000404L, + 0x00010004L, 0x01000004L, 0x01000004L, 0x00010004L, + 0x00000000L, 0x00000404L, 0x00010404L, 0x01000000L, + 0x00010000L, 0x01010404L, 0x00000004L, 0x01010000L, + 0x01010400L, 0x01000000L, 0x01000000L, 0x00000400L, + 0x01010004L, 0x00010000L, 0x00010400L, 0x01000004L, + 0x00000400L, 0x00000004L, 0x01000404L, 0x00010404L, + 0x01010404L, 0x00010004L, 0x01010000L, 0x01000404L, + 0x01000004L, 0x00000404L, 0x00010404L, 0x01010400L, + 0x00000404L, 0x01000400L, 0x01000400L, 0x00000000L, + 0x00010004L, 0x00010400L, 0x00000000L, 0x01010004L }; + +static const unsigned long SP2[64] = { + 0x80108020L, 0x80008000L, 0x00008000L, 0x00108020L, + 0x00100000L, 0x00000020L, 0x80100020L, 0x80008020L, + 0x80000020L, 0x80108020L, 0x80108000L, 0x80000000L, + 0x80008000L, 0x00100000L, 0x00000020L, 0x80100020L, + 0x00108000L, 0x00100020L, 0x80008020L, 0x00000000L, + 0x80000000L, 0x00008000L, 0x00108020L, 0x80100000L, + 0x00100020L, 0x80000020L, 0x00000000L, 0x00108000L, + 0x00008020L, 0x80108000L, 0x80100000L, 0x00008020L, + 0x00000000L, 0x00108020L, 0x80100020L, 0x00100000L, + 0x80008020L, 0x80100000L, 0x80108000L, 0x00008000L, + 0x80100000L, 0x80008000L, 0x00000020L, 0x80108020L, + 0x00108020L, 0x00000020L, 0x00008000L, 0x80000000L, + 0x00008020L, 0x80108000L, 0x00100000L, 0x80000020L, + 0x00100020L, 0x80008020L, 0x80000020L, 0x00100020L, + 0x00108000L, 0x00000000L, 0x80008000L, 0x00008020L, + 0x80000000L, 0x80100020L, 0x80108020L, 0x00108000L }; + +static const unsigned long SP3[64] = { + 0x00000208L, 0x08020200L, 0x00000000L, 0x08020008L, + 0x08000200L, 0x00000000L, 0x00020208L, 0x08000200L, + 0x00020008L, 0x08000008L, 0x08000008L, 0x00020000L, + 0x08020208L, 0x00020008L, 0x08020000L, 0x00000208L, + 0x08000000L, 0x00000008L, 0x08020200L, 0x00000200L, + 0x00020200L, 0x08020000L, 0x08020008L, 0x00020208L, + 0x08000208L, 0x00020200L, 0x00020000L, 0x08000208L, + 0x00000008L, 0x08020208L, 0x00000200L, 0x08000000L, + 0x08020200L, 0x08000000L, 0x00020008L, 0x00000208L, + 0x00020000L, 0x08020200L, 0x08000200L, 0x00000000L, + 0x00000200L, 0x00020008L, 0x08020208L, 0x08000200L, + 0x08000008L, 0x00000200L, 0x00000000L, 0x08020008L, + 0x08000208L, 0x00020000L, 0x08000000L, 0x08020208L, + 0x00000008L, 0x00020208L, 0x00020200L, 0x08000008L, + 0x08020000L, 0x08000208L, 0x00000208L, 0x08020000L, + 0x00020208L, 0x00000008L, 0x08020008L, 0x00020200L }; + +static const unsigned long SP4[64] = { + 0x00802001L, 0x00002081L, 0x00002081L, 0x00000080L, + 0x00802080L, 0x00800081L, 0x00800001L, 0x00002001L, + 0x00000000L, 0x00802000L, 0x00802000L, 0x00802081L, + 0x00000081L, 0x00000000L, 0x00800080L, 0x00800001L, + 0x00000001L, 0x00002000L, 0x00800000L, 0x00802001L, + 0x00000080L, 0x00800000L, 0x00002001L, 0x00002080L, + 0x00800081L, 0x00000001L, 0x00002080L, 0x00800080L, + 0x00002000L, 0x00802080L, 0x00802081L, 0x00000081L, + 0x00800080L, 0x00800001L, 0x00802000L, 0x00802081L, + 0x00000081L, 0x00000000L, 0x00000000L, 0x00802000L, + 0x00002080L, 0x00800080L, 0x00800081L, 0x00000001L, + 0x00802001L, 0x00002081L, 0x00002081L, 0x00000080L, + 0x00802081L, 0x00000081L, 0x00000001L, 0x00002000L, + 0x00800001L, 0x00002001L, 0x00802080L, 0x00800081L, + 0x00002001L, 0x00002080L, 0x00800000L, 0x00802001L, + 0x00000080L, 0x00800000L, 0x00002000L, 0x00802080L }; + +static const unsigned long SP5[64] = { + 0x00000100L, 0x02080100L, 0x02080000L, 0x42000100L, + 0x00080000L, 0x00000100L, 0x40000000L, 0x02080000L, + 0x40080100L, 0x00080000L, 0x02000100L, 0x40080100L, + 0x42000100L, 0x42080000L, 0x00080100L, 0x40000000L, + 0x02000000L, 0x40080000L, 0x40080000L, 0x00000000L, + 0x40000100L, 0x42080100L, 0x42080100L, 0x02000100L, + 0x42080000L, 0x40000100L, 0x00000000L, 0x42000000L, + 0x02080100L, 0x02000000L, 0x42000000L, 0x00080100L, + 0x00080000L, 0x42000100L, 0x00000100L, 0x02000000L, + 0x40000000L, 0x02080000L, 0x42000100L, 0x40080100L, + 0x02000100L, 0x40000000L, 0x42080000L, 0x02080100L, + 0x40080100L, 0x00000100L, 0x02000000L, 0x42080000L, + 0x42080100L, 0x00080100L, 0x42000000L, 0x42080100L, + 0x02080000L, 0x00000000L, 0x40080000L, 0x42000000L, + 0x00080100L, 0x02000100L, 0x40000100L, 0x00080000L, + 0x00000000L, 0x40080000L, 0x02080100L, 0x40000100L }; + +static const unsigned long SP6[64] = { + 0x20000010L, 0x20400000L, 0x00004000L, 0x20404010L, + 0x20400000L, 0x00000010L, 0x20404010L, 0x00400000L, + 0x20004000L, 0x00404010L, 0x00400000L, 0x20000010L, + 0x00400010L, 0x20004000L, 0x20000000L, 0x00004010L, + 0x00000000L, 0x00400010L, 0x20004010L, 0x00004000L, + 0x00404000L, 0x20004010L, 0x00000010L, 0x20400010L, + 0x20400010L, 0x00000000L, 0x00404010L, 0x20404000L, + 0x00004010L, 0x00404000L, 0x20404000L, 0x20000000L, + 0x20004000L, 0x00000010L, 0x20400010L, 0x00404000L, + 0x20404010L, 0x00400000L, 0x00004010L, 0x20000010L, + 0x00400000L, 0x20004000L, 0x20000000L, 0x00004010L, + 0x20000010L, 0x20404010L, 0x00404000L, 0x20400000L, + 0x00404010L, 0x20404000L, 0x00000000L, 0x20400010L, + 0x00000010L, 0x00004000L, 0x20400000L, 0x00404010L, + 0x00004000L, 0x00400010L, 0x20004010L, 0x00000000L, + 0x20404000L, 0x20000000L, 0x00400010L, 0x20004010L }; + +static const unsigned long SP7[64] = { + 0x00200000L, 0x04200002L, 0x04000802L, 0x00000000L, + 0x00000800L, 0x04000802L, 0x00200802L, 0x04200800L, + 0x04200802L, 0x00200000L, 0x00000000L, 0x04000002L, + 0x00000002L, 0x04000000L, 0x04200002L, 0x00000802L, + 0x04000800L, 0x00200802L, 0x00200002L, 0x04000800L, + 0x04000002L, 0x04200000L, 0x04200800L, 0x00200002L, + 0x04200000L, 0x00000800L, 0x00000802L, 0x04200802L, + 0x00200800L, 0x00000002L, 0x04000000L, 0x00200800L, + 0x04000000L, 0x00200800L, 0x00200000L, 0x04000802L, + 0x04000802L, 0x04200002L, 0x04200002L, 0x00000002L, + 0x00200002L, 0x04000000L, 0x04000800L, 0x00200000L, + 0x04200800L, 0x00000802L, 0x00200802L, 0x04200800L, + 0x00000802L, 0x04000002L, 0x04200802L, 0x04200000L, + 0x00200800L, 0x00000000L, 0x00000002L, 0x04200802L, + 0x00000000L, 0x00200802L, 0x04200000L, 0x00000800L, + 0x04000002L, 0x04000800L, 0x00000800L, 0x00200002L }; + +static const unsigned long SP8[64] = { + 0x10001040L, 0x00001000L, 0x00040000L, 0x10041040L, + 0x10000000L, 0x10001040L, 0x00000040L, 0x10000000L, + 0x00040040L, 0x10040000L, 0x10041040L, 0x00041000L, + 0x10041000L, 0x00041040L, 0x00001000L, 0x00000040L, + 0x10040000L, 0x10000040L, 0x10001000L, 0x00001040L, + 0x00041000L, 0x00040040L, 0x10040040L, 0x10041000L, + 0x00001040L, 0x00000000L, 0x00000000L, 0x10040040L, + 0x10000040L, 0x10001000L, 0x00041040L, 0x00040000L, + 0x00041040L, 0x00040000L, 0x10041000L, 0x00001000L, + 0x00000040L, 0x10040040L, 0x00001000L, 0x00041040L, + 0x10001000L, 0x00000040L, 0x10000040L, 0x10040000L, + 0x10040040L, 0x10000000L, 0x00040000L, 0x10001040L, + 0x00000000L, 0x10041040L, 0x00040040L, 0x10000040L, + 0x10040000L, 0x10001000L, 0x10001040L, 0x00000000L, + 0x10041040L, 0x00041000L, 0x00041000L, 0x00001040L, + 0x00001040L, 0x00040040L, 0x10000000L, 0x10041000L }; + +static void desfunc(register unsigned long *block, register unsigned long *keys) +{ + register unsigned long fval, work, right, leftt; + register int round; + + leftt = block[0]; + right = block[1]; + work = ((leftt >> 4) ^ right) & 0x0f0f0f0fL; + right ^= work; + leftt ^= (work << 4); + work = ((leftt >> 16) ^ right) & 0x0000ffffL; + right ^= work; + leftt ^= (work << 16); + work = ((right >> 2) ^ leftt) & 0x33333333L; + leftt ^= work; + right ^= (work << 2); + work = ((right >> 8) ^ leftt) & 0x00ff00ffL; + leftt ^= work; + right ^= (work << 8); + right = ((right << 1) | ((right >> 31) & 1L)) & 0xffffffffL; + work = (leftt ^ right) & 0xaaaaaaaaL; + leftt ^= work; + right ^= work; + leftt = ((leftt << 1) | ((leftt >> 31) & 1L)) & 0xffffffffL; + + for( round = 0; round < 8; round++ ) { + work = (right << 28) | (right >> 4); + work ^= *keys++; + fval = SP7[ work & 0x3fL]; + fval |= SP5[(work >> 8) & 0x3fL]; + fval |= SP3[(work >> 16) & 0x3fL]; + fval |= SP1[(work >> 24) & 0x3fL]; + work = right ^ *keys++; + fval |= SP8[ work & 0x3fL]; + fval |= SP6[(work >> 8) & 0x3fL]; + fval |= SP4[(work >> 16) & 0x3fL]; + fval |= SP2[(work >> 24) & 0x3fL]; + leftt ^= fval; + work = (leftt << 28) | (leftt >> 4); + work ^= *keys++; + fval = SP7[ work & 0x3fL]; + fval |= SP5[(work >> 8) & 0x3fL]; + fval |= SP3[(work >> 16) & 0x3fL]; + fval |= SP1[(work >> 24) & 0x3fL]; + work = leftt ^ *keys++; + fval |= SP8[ work & 0x3fL]; + fval |= SP6[(work >> 8) & 0x3fL]; + fval |= SP4[(work >> 16) & 0x3fL]; + fval |= SP2[(work >> 24) & 0x3fL]; + right ^= fval; + } + + right = (right << 31) | (right >> 1); + work = (leftt ^ right) & 0xaaaaaaaaL; + leftt ^= work; + right ^= work; + leftt = (leftt << 31) | (leftt >> 1); + work = ((leftt >> 8) ^ right) & 0x00ff00ffL; + right ^= work; + leftt ^= (work << 8); + work = ((leftt >> 2) ^ right) & 0x33333333L; + right ^= work; + leftt ^= (work << 2); + work = ((right >> 16) ^ leftt) & 0x0000ffffL; + leftt ^= work; + right ^= (work << 16); + work = ((right >> 4) ^ leftt) & 0x0f0f0f0fL; + leftt ^= work; + right ^= (work << 4); + *block++ = right; + *block = leftt; + return; + } + +/* Validation sets: + * + * Single-length key, single-length plaintext - + * Key : 0123 4567 89ab cdef + * Plain : 0123 4567 89ab cde7 + * Cipher : c957 4425 6a5e d31d + * + * Double-length key, single-length plaintext - + * Key : 0123 4567 89ab cdef fedc ba98 7654 3210 + * Plain : 0123 4567 89ab cde7 + * Cipher : 7f1d 0a77 826b 8aff + * + * Double-length key, double-length plaintext - + * Key : 0123 4567 89ab cdef fedc ba98 7654 3210 + * Plain : 0123 4567 89ab cdef 0123 4567 89ab cdff + * Cipher : 27a0 8440 406a df60 278f 47cf 42d6 15d7 + * + * Triple-length key, single-length plaintext - + * Key : 0123 4567 89ab cdef fedc ba98 7654 3210 89ab cdef 0123 4567 + * Plain : 0123 4567 89ab cde7 + * Cipher : de0b 7c06 ae5e 0ed5 + * + * Triple-length key, double-length plaintext - + * Key : 0123 4567 89ab cdef fedc ba98 7654 3210 89ab cdef 0123 4567 + * Plain : 0123 4567 89ab cdef 0123 4567 89ab cdff + * Cipher : ad0d 1b30 ac17 cf07 0ed1 1c63 81e4 4de5 + * + * d3des V5.0a rwo 9208.07 18:44 Graven Imagery + **********************************************************************/ diff --git a/ui/d3des.h b/ui/d3des.h new file mode 100644 index 0000000000..ea3da44ce9 --- /dev/null +++ b/ui/d3des.h @@ -0,0 +1,51 @@ +/* + * This is D3DES (V5.09) by Richard Outerbridge with the double and + * triple-length support removed for use in VNC. + * + * These changes are: + * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + */ + +/* d3des.h - + * + * Headers and defines for d3des.c + * Graven Imagery, 1992. + * + * Copyright (c) 1988,1989,1990,1991,1992 by Richard Outerbridge + * (GEnie : OUTER; CIS : [71755,204]) + */ + +#define EN0 0 /* MODE == encrypt */ +#define DE1 1 /* MODE == decrypt */ + +extern void deskey(unsigned char *, int); +/* hexkey[8] MODE + * Sets the internal key register according to the hexadecimal + * key contained in the 8 bytes of hexkey, according to the DES, + * for encryption or decryption according to MODE. + */ + +extern void usekey(unsigned long *); +/* cookedkey[32] + * Loads the internal key register with the data in cookedkey. + */ + +extern void cpkey(unsigned long *); +/* cookedkey[32] + * Copies the contents of the internal key register into the storage + * located at &cookedkey[0]. + */ + +extern void des(unsigned char *, unsigned char *); +/* from[8] to[8] + * Encrypts/Decrypts (according to the key currently loaded in the + * internal key register) one block of eight bytes at address 'from' + * into the block at address 'to'. They can be the same. + */ + +/* d3des.h V5.09 rwo 9208.04 15:06 Graven Imagery + ********************************************************************/ diff --git a/ui/keymaps.c b/ui/keymaps.c new file mode 100644 index 0000000000..78c7ea375c --- /dev/null +++ b/ui/keymaps.c @@ -0,0 +1,210 @@ +/* + * 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.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; + } + 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 = qemu_mallocz(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); + + if (!k) + k = qemu_mallocz(sizeof(kbd_layout_t)); + if (!(filename && (f = fopen(filename, "r")))) { + fprintf(stderr, + "Could not read keymap file: '%s'\n", language); + return NULL; + } + qemu_free(filename); + 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; + char *rest2; + int keycode = strtol(rest, &rest2, 0); + + if (rest && 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 (rest && strstr(rest, "shift")) + keycode |= SCANCODE_SHIFT; + if (rest && strstr(rest, "altgr")) + keycode |= SCANCODE_ALTGR; + if (rest && strstr(rest, "ctrl")) + keycode |= SCANCODE_CTRL; + + add_keysym(line, keysym, keycode, k); + + if (rest && strstr(rest, "addupper")) { + char *c; + for (c = line; *c; c++) + *c = 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 0000000000..a7600d5751 --- /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/sdl.c b/ui/sdl.c new file mode 100644 index 0000000000..0072680f4c --- /dev/null +++ b/ui/sdl.c @@ -0,0 +1,879 @@ +/* + * 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 <SDL.h> +#include <SDL_syswm.h> + +#ifndef _WIN32 +#include <signal.h> +#endif + +#include "qemu-common.h" +#include "console.h" +#include "sysemu.h" +#include "x_keymap.h" +#include "sdl_zoom.h" + +static DisplayChangeListener *dcl; +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 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_fullscreen_initial_grab; +static int gui_grab_code = KMOD_LALT | KMOD_LCTRL; +static uint8_t modifiers_state[256]; +static int width, height; +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 uint8_t allocator; +static SDL_PixelFormat host_format; +static int scaling_active = 0; +static Notifier mouse_mode_notifier; + +static void sdl_update(DisplayState *ds, int x, int y, int w, int h) +{ + // printf("updating x=%d y=%d w=%d h=%d\n", x, y, w, h); + SDL_Rect rec; + rec.x = x; + rec.y = y; + rec.w = w; + rec.h = h; + + if (guest_screen) { + if (!scaling_active) { + SDL_BlitSurface(guest_screen, &rec, real_screen, &rec); + } else { + if (sdl_zoom_blit(guest_screen, real_screen, SMOOTHING_ON, &rec) < 0) { + fprintf(stderr, "Zoom blit failed\n"); + exit(1); + } + } + } + SDL_UpdateRect(real_screen, rec.x, rec.y, rec.w, rec.h); +} + +static void sdl_setdata(DisplayState *ds) +{ + SDL_Rect rec; + rec.x = 0; + rec.y = 0; + rec.w = real_screen->w; + rec.h = real_screen->h; + + if (guest_screen != NULL) SDL_FreeSurface(guest_screen); + + guest_screen = SDL_CreateRGBSurfaceFrom(ds_get_data(ds), ds_get_width(ds), ds_get_height(ds), + ds_get_bits_per_pixel(ds), ds_get_linesize(ds), + ds->surface->pf.rmask, ds->surface->pf.gmask, + ds->surface->pf.bmask, ds->surface->pf.amask); +} + +static void do_sdl_resize(int new_width, int new_height, int bpp) +{ + int flags; + + // printf("resizing to %d %d\n", w, h); + + flags = SDL_HWSURFACE|SDL_ASYNCBLIT|SDL_HWACCEL|SDL_RESIZABLE; + if (gui_fullscreen) + flags |= SDL_FULLSCREEN; + if (gui_noframe) + flags |= SDL_NOFRAME; + + width = new_width; + height = new_height; + real_screen = SDL_SetVideoMode(width, height, bpp, flags); + if (!real_screen) { + fprintf(stderr, "Could not open SDL display (%dx%dx%d): %s\n", width, + height, bpp, SDL_GetError()); + exit(1); + } +} + +static void sdl_resize(DisplayState *ds) +{ + if (!allocator) { + if (!scaling_active) + do_sdl_resize(ds_get_width(ds), ds_get_height(ds), 0); + else if (real_screen->format->BitsPerPixel != ds_get_bits_per_pixel(ds)) + do_sdl_resize(real_screen->w, real_screen->h, ds_get_bits_per_pixel(ds)); + sdl_setdata(ds); + } else { + if (guest_screen != NULL) { + SDL_FreeSurface(guest_screen); + guest_screen = NULL; + } + } +} + +static PixelFormat sdl_to_qemu_pixelformat(SDL_PixelFormat *sdl_pf) +{ + PixelFormat qemu_pf; + + memset(&qemu_pf, 0x00, sizeof(PixelFormat)); + + qemu_pf.bits_per_pixel = sdl_pf->BitsPerPixel; + qemu_pf.bytes_per_pixel = sdl_pf->BytesPerPixel; + qemu_pf.depth = (qemu_pf.bits_per_pixel) == 32 ? 24 : (qemu_pf.bits_per_pixel); + + qemu_pf.rmask = sdl_pf->Rmask; + qemu_pf.gmask = sdl_pf->Gmask; + qemu_pf.bmask = sdl_pf->Bmask; + qemu_pf.amask = sdl_pf->Amask; + + qemu_pf.rshift = sdl_pf->Rshift; + qemu_pf.gshift = sdl_pf->Gshift; + qemu_pf.bshift = sdl_pf->Bshift; + qemu_pf.ashift = sdl_pf->Ashift; + + qemu_pf.rbits = 8 - sdl_pf->Rloss; + qemu_pf.gbits = 8 - sdl_pf->Gloss; + qemu_pf.bbits = 8 - sdl_pf->Bloss; + qemu_pf.abits = 8 - sdl_pf->Aloss; + + qemu_pf.rmax = ((1 << qemu_pf.rbits) - 1); + qemu_pf.gmax = ((1 << qemu_pf.gbits) - 1); + qemu_pf.bmax = ((1 << qemu_pf.bbits) - 1); + qemu_pf.amax = ((1 << qemu_pf.abits) - 1); + + return qemu_pf; +} + +static DisplaySurface* sdl_create_displaysurface(int width, int height) +{ + DisplaySurface *surface = (DisplaySurface*) qemu_mallocz(sizeof(DisplaySurface)); + if (surface == NULL) { + fprintf(stderr, "sdl_create_displaysurface: malloc failed\n"); + exit(1); + } + + surface->width = width; + surface->height = height; + + if (scaling_active) { + if (host_format.BytesPerPixel != 2 && host_format.BytesPerPixel != 4) { + surface->linesize = width * 4; + surface->pf = qemu_default_pixelformat(32); + } else { + surface->linesize = width * host_format.BytesPerPixel; + surface->pf = sdl_to_qemu_pixelformat(&host_format); + } +#ifdef HOST_WORDS_BIGENDIAN + surface->flags = QEMU_ALLOCATED_FLAG | QEMU_BIG_ENDIAN_FLAG; +#else + surface->flags = QEMU_ALLOCATED_FLAG; +#endif + surface->data = (uint8_t*) qemu_mallocz(surface->linesize * surface->height); + + return surface; + } + + if (host_format.BitsPerPixel == 16) + do_sdl_resize(width, height, 16); + else + do_sdl_resize(width, height, 32); + + surface->pf = sdl_to_qemu_pixelformat(real_screen->format); + surface->linesize = real_screen->pitch; + surface->data = real_screen->pixels; + +#ifdef HOST_WORDS_BIGENDIAN + surface->flags = QEMU_REALPIXELS_FLAG | QEMU_BIG_ENDIAN_FLAG; +#else + surface->flags = QEMU_REALPIXELS_FLAG; +#endif + allocator = 1; + + return surface; +} + +static void sdl_free_displaysurface(DisplaySurface *surface) +{ + allocator = 0; + if (surface == NULL) + return; + + if (surface->flags & QEMU_ALLOCATED_FLAG) + qemu_free(surface->data); + qemu_free(surface); +} + +static DisplaySurface* sdl_resize_displaysurface(DisplaySurface *surface, int width, int height) +{ + sdl_free_displaysurface(surface); + return sdl_create_displaysurface(width, height); +} + +/* generic keyboard conversion */ + +#include "sdl_keysym.h" + +static kbd_layout_t *kbd_layout = NULL; + +static uint8_t sdl_keyevent_to_keycode_generic(const SDL_KeyboardEvent *ev) +{ + int keysym; + /* workaround for X11+SDL bug with AltGR */ + keysym = ev->keysym.sym; + if (keysym == 0 && ev->keysym.scancode == 113) + keysym = SDLK_MODE; + /* For Japanese key '\' and '|' */ + if (keysym == 92 && ev->keysym.scancode == 133) { + keysym = 0xa5; + } + return keysym2scancode(kbd_layout, keysym) & SCANCODE_KEYMASK; +} + +/* specific keyboard conversions from scan codes */ + +#if defined(_WIN32) + +static uint8_t sdl_keyevent_to_keycode(const SDL_KeyboardEvent *ev) +{ + return ev->keysym.scancode; +} + +#else + +#if defined(SDL_VIDEO_DRIVER_X11) +#include <X11/XKBlib.h> + +static int check_for_evdev(void) +{ + SDL_SysWMinfo info; + XkbDescPtr desc = NULL; + int has_evdev = 0; + char *keycodes = NULL; + + SDL_VERSION(&info.version); + if (!SDL_GetWMInfo(&info)) { + return 0; + } + desc = XkbGetKeyboard(info.info.x11.display, + XkbGBN_AllComponentsMask, + XkbUseCoreKbd); + if (desc && desc->names) { + keycodes = XGetAtomName(info.info.x11.display, desc->names->keycodes); + if (keycodes == NULL) { + fprintf(stderr, "could not lookup keycode name\n"); + } else if (strstart(keycodes, "evdev", NULL)) { + has_evdev = 1; + } else if (!strstart(keycodes, "xfree86", NULL)) { + fprintf(stderr, "unknown keycodes `%s', please report to " + "qemu-devel@nongnu.org\n", keycodes); + } + } + + if (desc) { + XkbFreeKeyboard(desc, XkbGBN_AllComponentsMask, True); + } + if (keycodes) { + XFree(keycodes); + } + return has_evdev; +} +#else +static int check_for_evdev(void) +{ + return 0; +} +#endif + +static uint8_t sdl_keyevent_to_keycode(const SDL_KeyboardEvent *ev) +{ + int keycode; + static int has_evdev = -1; + + if (has_evdev == -1) + has_evdev = check_for_evdev(); + + keycode = ev->keysym.scancode; + + if (keycode < 9) { + keycode = 0; + } else if (keycode < 97) { + keycode -= 8; /* just an offset */ + } else if (keycode < 158) { + /* use conversion table */ + if (has_evdev) + keycode = translate_evdev_keycode(keycode - 97); + else + keycode = translate_xfree86_keycode(keycode - 97); + } else if (keycode == 208) { /* Hiragana_Katakana */ + keycode = 0x70; + } else if (keycode == 211) { /* backslash */ + keycode = 0x73; + } else { + keycode = 0; + } + return keycode; +} + +#endif + +static void reset_keys(void) +{ + int i; + for(i = 0; i < 256; i++) { + if (modifiers_state[i]) { + if (i & SCANCODE_GREY) + kbd_put_keycode(SCANCODE_EMUL0); + kbd_put_keycode(i | SCANCODE_UP); + modifiers_state[i] = 0; + } + } +} + +static void sdl_process_key(SDL_KeyboardEvent *ev) +{ + int keycode, v; + + if (ev->keysym.sym == SDLK_PAUSE) { + /* specific case */ + v = 0; + if (ev->type == SDL_KEYUP) + v |= SCANCODE_UP; + kbd_put_keycode(0xe1); + kbd_put_keycode(0x1d | v); + kbd_put_keycode(0x45 | v); + return; + } + + if (kbd_layout) { + keycode = sdl_keyevent_to_keycode_generic(ev); + } else { + keycode = sdl_keyevent_to_keycode(ev); + } + + switch(keycode) { + case 0x00: + /* sent when leaving window: reset the modifiers state */ + reset_keys(); + return; + case 0x2a: /* Left Shift */ + case 0x36: /* Right Shift */ + case 0x1d: /* Left CTRL */ + case 0x9d: /* Right CTRL */ + case 0x38: /* Left ALT */ + case 0xb8: /* Right ALT */ + if (ev->type == SDL_KEYUP) + modifiers_state[keycode] = 0; + else + modifiers_state[keycode] = 1; + break; + case 0x45: /* num lock */ + case 0x3a: /* caps lock */ + /* SDL does not send the key up event, so we generate it */ + kbd_put_keycode(keycode); + kbd_put_keycode(keycode | SCANCODE_UP); + return; + } + + /* now send the key code */ + if (keycode & SCANCODE_GREY) + kbd_put_keycode(SCANCODE_EMUL0); + if (ev->type == SDL_KEYUP) + kbd_put_keycode(keycode | SCANCODE_UP); + else + kbd_put_keycode(keycode & SCANCODE_KEYCODEMASK); +} + +static void sdl_update_caption(void) +{ + char win_title[1024]; + char icon_title[1024]; + const char *status = ""; + + if (!vm_running) + status = " [Stopped]"; + else if (gui_grab) { + if (alt_grab) + status = " - Press Ctrl-Alt-Shift to exit mouse grab"; + else if (ctrl_grab) + status = " - Press Right-Ctrl to exit mouse grab"; + else + status = " - Press Ctrl-Alt to exit mouse grab"; + } + + if (qemu_name) { + snprintf(win_title, sizeof(win_title), "QEMU (%s)%s", qemu_name, status); + snprintf(icon_title, sizeof(icon_title), "QEMU (%s)", qemu_name); + } else { + snprintf(win_title, sizeof(win_title), "QEMU%s", status); + snprintf(icon_title, sizeof(icon_title), "QEMU"); + } + + SDL_WM_SetCaption(win_title, icon_title); +} + +static void sdl_hide_cursor(void) +{ + if (!cursor_hide) + return; + + if (kbd_mouse_is_absolute()) { + SDL_ShowCursor(1); + SDL_SetCursor(sdl_cursor_hidden); + } else { + SDL_ShowCursor(0); + } +} + +static void sdl_show_cursor(void) +{ + if (!cursor_hide) + return; + + if (!kbd_mouse_is_absolute()) { + SDL_ShowCursor(1); + if (guest_cursor && + (gui_grab || kbd_mouse_is_absolute() || absolute_enabled)) + SDL_SetCursor(guest_sprite); + else + SDL_SetCursor(sdl_cursor_normal); + } +} + +static void sdl_grab_start(void) +{ + if (guest_cursor) { + SDL_SetCursor(guest_sprite); + if (!kbd_mouse_is_absolute() && !absolute_enabled) + SDL_WarpMouse(guest_x, guest_y); + } else + sdl_hide_cursor(); + + if (SDL_WM_GrabInput(SDL_GRAB_ON) == SDL_GRAB_ON) { + gui_grab = 1; + sdl_update_caption(); + } else + sdl_show_cursor(); +} + +static void sdl_grab_end(void) +{ + SDL_WM_GrabInput(SDL_GRAB_OFF); + gui_grab = 0; + sdl_show_cursor(); + sdl_update_caption(); +} + +static void sdl_mouse_mode_change(Notifier *notify) +{ + if (kbd_mouse_is_absolute()) { + if (!absolute_enabled) { + sdl_hide_cursor(); + if (gui_grab) { + sdl_grab_end(); + } + absolute_enabled = 1; + } + } else if (absolute_enabled) { + sdl_show_cursor(); + absolute_enabled = 0; + } +} + +static void sdl_send_mouse_event(int dx, int dy, int dz, int x, int y, int state) +{ + int buttons; + buttons = 0; + if (state & SDL_BUTTON(SDL_BUTTON_LEFT)) + buttons |= MOUSE_EVENT_LBUTTON; + if (state & SDL_BUTTON(SDL_BUTTON_RIGHT)) + buttons |= MOUSE_EVENT_RBUTTON; + if (state & SDL_BUTTON(SDL_BUTTON_MIDDLE)) + buttons |= MOUSE_EVENT_MBUTTON; + + if (kbd_mouse_is_absolute()) { + dx = x * 0x7FFF / (width - 1); + dy = y * 0x7FFF / (height - 1); + } else if (guest_cursor) { + x -= guest_x; + y -= guest_y; + guest_x += x; + guest_y += y; + dx = x; + dy = y; + } + + kbd_mouse_event(dx, dy, dz, buttons); +} + +static void toggle_full_screen(DisplayState *ds) +{ + gui_fullscreen = !gui_fullscreen; + do_sdl_resize(real_screen->w, real_screen->h, real_screen->format->BitsPerPixel); + if (gui_fullscreen) { + scaling_active = 0; + gui_saved_grab = gui_grab; + sdl_grab_start(); + } else { + if (!gui_saved_grab) + sdl_grab_end(); + } + vga_hw_invalidate(); + vga_hw_update(); +} + +static void sdl_refresh(DisplayState *ds) +{ + SDL_Event ev1, *ev = &ev1; + int mod_state; + int buttonstate = SDL_GetMouseState(NULL, NULL); + + if (last_vm_running != vm_running) { + last_vm_running = vm_running; + sdl_update_caption(); + } + + vga_hw_update(); + SDL_EnableUNICODE(!is_graphic_console()); + + while (SDL_PollEvent(ev)) { + switch (ev->type) { + case SDL_VIDEOEXPOSE: + sdl_update(ds, 0, 0, real_screen->w, real_screen->h); + break; + case SDL_KEYDOWN: + case SDL_KEYUP: + if (ev->type == SDL_KEYDOWN) { + 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) { + int keycode; + keycode = sdl_keyevent_to_keycode(&ev->key); + switch(keycode) { + case 0x21: /* 'f' key on US keyboard */ + toggle_full_screen(ds); + gui_keysym = 1; + break; + case 0x16: /* 'u' key on US keyboard */ + scaling_active = 0; + sdl_resize(ds); + vga_hw_invalidate(); + vga_hw_update(); + break; + case 0x02 ... 0x0a: /* '1' to '9' keys */ + /* Reset the modifiers sent to the current console */ + reset_keys(); + console_select(keycode - 0x02); + if (!is_graphic_console()) { + /* display grab if going to a text console */ + if (gui_grab) + sdl_grab_end(); + } + gui_keysym = 1; + break; + default: + break; + } + } else if (!is_graphic_console()) { + int keysym; + 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); + } + } + } else if (ev->type == SDL_KEYUP) { + 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) { + if (gui_key_modifier_pressed) { + gui_key_modifier_pressed = 0; + if (gui_keysym == 0) { + /* exit/enter grab if pressing Ctrl-Alt */ + if (!gui_grab) { + /* if the application is not active, + do not try to enter grab state. It + prevents + 'SDL_WM_GrabInput(SDL_GRAB_ON)' + from blocking all the application + (SDL bug). */ + if (SDL_GetAppState() & SDL_APPACTIVE) + sdl_grab_start(); + } else { + sdl_grab_end(); + } + /* SDL does not send back all the + modifiers key, so we must correct it */ + reset_keys(); + break; + } + gui_keysym = 0; + } + } + } + if (is_graphic_console() && !gui_keysym) + sdl_process_key(&ev->key); + break; + case SDL_QUIT: + if (!no_quit) + qemu_system_shutdown_request(); + break; + case SDL_MOUSEMOTION: + if (gui_grab || kbd_mouse_is_absolute() || + absolute_enabled) { + sdl_send_mouse_event(ev->motion.xrel, ev->motion.yrel, 0, + ev->motion.x, ev->motion.y, ev->motion.state); + } + break; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + { + SDL_MouseButtonEvent *bev = &ev->button; + if (!gui_grab && !kbd_mouse_is_absolute()) { + if (ev->type == SDL_MOUSEBUTTONDOWN && + (bev->button == SDL_BUTTON_LEFT)) { + /* start grabbing all events */ + sdl_grab_start(); + } + } else { + int dz; + dz = 0; + if (ev->type == SDL_MOUSEBUTTONDOWN) { + buttonstate |= SDL_BUTTON(bev->button); + } else { + buttonstate &= ~SDL_BUTTON(bev->button); + } +#ifdef SDL_BUTTON_WHEELUP + if (bev->button == SDL_BUTTON_WHEELUP && ev->type == SDL_MOUSEBUTTONDOWN) { + dz = -1; + } else if (bev->button == SDL_BUTTON_WHEELDOWN && ev->type == SDL_MOUSEBUTTONDOWN) { + dz = 1; + } +#endif + sdl_send_mouse_event(0, 0, dz, bev->x, bev->y, buttonstate); + } + } + break; + case SDL_ACTIVEEVENT: + if (gui_grab && ev->active.state == SDL_APPINPUTFOCUS && + !ev->active.gain && !gui_fullscreen_initial_grab) { + sdl_grab_end(); + } + if (ev->active.state & SDL_APPACTIVE) { + if (ev->active.gain) { + /* Back to default interval */ + dcl->gui_timer_interval = 0; + dcl->idle = 0; + } else { + /* Sleeping interval */ + dcl->gui_timer_interval = 500; + dcl->idle = 1; + } + } + break; + case SDL_VIDEORESIZE: + { + SDL_ResizeEvent *rev = &ev->resize; + int bpp = real_screen->format->BitsPerPixel; + if (bpp != 16 && bpp != 32) + bpp = 32; + do_sdl_resize(rev->w, rev->h, bpp); + scaling_active = 1; + if (!is_buffer_shared(ds->surface)) { + ds->surface = qemu_resize_displaysurface(ds, ds_get_width(ds), ds_get_height(ds)); + dpy_resize(ds); + } + vga_hw_invalidate(); + vga_hw_update(); + break; + } + default: + break; + } + } +} + +static void sdl_fill(DisplayState *ds, int x, int y, int w, int h, uint32_t c) +{ + SDL_Rect dst = { x, y, w, h }; + SDL_FillRect(real_screen, &dst, c); +} + +static void sdl_mouse_warp(int x, int y, int on) +{ + if (on) { + if (!guest_cursor) + sdl_show_cursor(); + if (gui_grab || kbd_mouse_is_absolute() || absolute_enabled) { + SDL_SetCursor(guest_sprite); + if (!kbd_mouse_is_absolute() && !absolute_enabled) + SDL_WarpMouse(x, y); + } + } else if (gui_grab) + sdl_hide_cursor(); + guest_cursor = on; + guest_x = x, guest_y = y; +} + +static void sdl_mouse_define(QEMUCursor *c) +{ + uint8_t *image, *mask; + int bpl; + + if (guest_sprite) + SDL_FreeCursor(guest_sprite); + + bpl = cursor_get_mono_bpl(c); + image = qemu_mallocz(bpl * c->height); + mask = qemu_mallocz(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); + qemu_free(image); + qemu_free(mask); + + if (guest_cursor && + (gui_grab || kbd_mouse_is_absolute() || absolute_enabled)) + SDL_SetCursor(guest_sprite); +} + +static void sdl_cleanup(void) +{ + if (guest_sprite) + SDL_FreeCursor(guest_sprite); + SDL_QuitSubSystem(SDL_INIT_VIDEO); +} + +void sdl_display_init(DisplayState *ds, int full_screen, int no_frame) +{ + int flags; + uint8_t data = 0; + DisplayAllocator *da; + const SDL_VideoInfo *vi; + +#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); + } + + 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); + + dcl = qemu_mallocz(sizeof(DisplayChangeListener)); + dcl->dpy_update = sdl_update; + dcl->dpy_resize = sdl_resize; + dcl->dpy_refresh = sdl_refresh; + dcl->dpy_setdata = sdl_setdata; + dcl->dpy_fill = sdl_fill; + ds->mouse_set = sdl_mouse_warp; + ds->cursor_define = sdl_mouse_define; + register_displaychangelistener(ds, dcl); + + da = qemu_mallocz(sizeof(DisplayAllocator)); + da->create_displaysurface = sdl_create_displaysurface; + da->resize_displaysurface = sdl_resize_displaysurface; + da->free_displaysurface = sdl_free_displaysurface; + if (register_displayallocator(ds, da) == da) { + dpy_resize(ds); + } + + 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); + if (full_screen) { + gui_fullscreen = 1; + gui_fullscreen_initial_grab = 1; + sdl_grab_start(); + } +} diff --git a/ui/sdl_keysym.h b/ui/sdl_keysym.h new file mode 100644 index 0000000000..ee904805da --- /dev/null +++ b/ui/sdl_keysym.h @@ -0,0 +1,277 @@ + +#include "keymaps.h" + +static const name2keysym_t name2keysym[]={ +/* ascii */ + { "space", 0x020}, + { "exclam", 0x021}, + { "quotedbl", 0x022}, + { "numbersign", 0x023}, + { "dollar", 0x024}, + { "percent", 0x025}, + { "ampersand", 0x026}, + { "apostrophe", 0x027}, + { "parenleft", 0x028}, + { "parenright", 0x029}, + { "asterisk", 0x02a}, + { "plus", 0x02b}, + { "comma", 0x02c}, + { "minus", 0x02d}, + { "period", 0x02e}, + { "slash", 0x02f}, + { "0", 0x030}, + { "1", 0x031}, + { "2", 0x032}, + { "3", 0x033}, + { "4", 0x034}, + { "5", 0x035}, + { "6", 0x036}, + { "7", 0x037}, + { "8", 0x038}, + { "9", 0x039}, + { "colon", 0x03a}, + { "semicolon", 0x03b}, + { "less", 0x03c}, + { "equal", 0x03d}, + { "greater", 0x03e}, + { "question", 0x03f}, + { "at", 0x040}, + { "A", 0x041}, + { "B", 0x042}, + { "C", 0x043}, + { "D", 0x044}, + { "E", 0x045}, + { "F", 0x046}, + { "G", 0x047}, + { "H", 0x048}, + { "I", 0x049}, + { "J", 0x04a}, + { "K", 0x04b}, + { "L", 0x04c}, + { "M", 0x04d}, + { "N", 0x04e}, + { "O", 0x04f}, + { "P", 0x050}, + { "Q", 0x051}, + { "R", 0x052}, + { "S", 0x053}, + { "T", 0x054}, + { "U", 0x055}, + { "V", 0x056}, + { "W", 0x057}, + { "X", 0x058}, + { "Y", 0x059}, + { "Z", 0x05a}, + { "bracketleft", 0x05b}, + { "backslash", 0x05c}, + { "bracketright", 0x05d}, + { "asciicircum", 0x05e}, + { "underscore", 0x05f}, + { "grave", 0x060}, + { "a", 0x061}, + { "b", 0x062}, + { "c", 0x063}, + { "d", 0x064}, + { "e", 0x065}, + { "f", 0x066}, + { "g", 0x067}, + { "h", 0x068}, + { "i", 0x069}, + { "j", 0x06a}, + { "k", 0x06b}, + { "l", 0x06c}, + { "m", 0x06d}, + { "n", 0x06e}, + { "o", 0x06f}, + { "p", 0x070}, + { "q", 0x071}, + { "r", 0x072}, + { "s", 0x073}, + { "t", 0x074}, + { "u", 0x075}, + { "v", 0x076}, + { "w", 0x077}, + { "x", 0x078}, + { "y", 0x079}, + { "z", 0x07a}, + { "braceleft", 0x07b}, + { "bar", 0x07c}, + { "braceright", 0x07d}, + { "asciitilde", 0x07e}, + +/* latin 1 extensions */ +{ "nobreakspace", 0x0a0}, +{ "exclamdown", 0x0a1}, +{ "cent", 0x0a2}, +{ "sterling", 0x0a3}, +{ "currency", 0x0a4}, +{ "yen", 0x0a5}, +{ "brokenbar", 0x0a6}, +{ "section", 0x0a7}, +{ "diaeresis", 0x0a8}, +{ "copyright", 0x0a9}, +{ "ordfeminine", 0x0aa}, +{ "guillemotleft", 0x0ab}, +{ "notsign", 0x0ac}, +{ "hyphen", 0x0ad}, +{ "registered", 0x0ae}, +{ "macron", 0x0af}, +{ "degree", 0x0b0}, +{ "plusminus", 0x0b1}, +{ "twosuperior", 0x0b2}, +{ "threesuperior", 0x0b3}, +{ "acute", 0x0b4}, +{ "mu", 0x0b5}, +{ "paragraph", 0x0b6}, +{ "periodcentered", 0x0b7}, +{ "cedilla", 0x0b8}, +{ "onesuperior", 0x0b9}, +{ "masculine", 0x0ba}, +{ "guillemotright", 0x0bb}, +{ "onequarter", 0x0bc}, +{ "onehalf", 0x0bd}, +{ "threequarters", 0x0be}, +{ "questiondown", 0x0bf}, +{ "Agrave", 0x0c0}, +{ "Aacute", 0x0c1}, +{ "Acircumflex", 0x0c2}, +{ "Atilde", 0x0c3}, +{ "Adiaeresis", 0x0c4}, +{ "Aring", 0x0c5}, +{ "AE", 0x0c6}, +{ "Ccedilla", 0x0c7}, +{ "Egrave", 0x0c8}, +{ "Eacute", 0x0c9}, +{ "Ecircumflex", 0x0ca}, +{ "Ediaeresis", 0x0cb}, +{ "Igrave", 0x0cc}, +{ "Iacute", 0x0cd}, +{ "Icircumflex", 0x0ce}, +{ "Idiaeresis", 0x0cf}, +{ "ETH", 0x0d0}, +{ "Eth", 0x0d0}, +{ "Ntilde", 0x0d1}, +{ "Ograve", 0x0d2}, +{ "Oacute", 0x0d3}, +{ "Ocircumflex", 0x0d4}, +{ "Otilde", 0x0d5}, +{ "Odiaeresis", 0x0d6}, +{ "multiply", 0x0d7}, +{ "Ooblique", 0x0d8}, +{ "Oslash", 0x0d8}, +{ "Ugrave", 0x0d9}, +{ "Uacute", 0x0da}, +{ "Ucircumflex", 0x0db}, +{ "Udiaeresis", 0x0dc}, +{ "Yacute", 0x0dd}, +{ "THORN", 0x0de}, +{ "Thorn", 0x0de}, +{ "ssharp", 0x0df}, +{ "agrave", 0x0e0}, +{ "aacute", 0x0e1}, +{ "acircumflex", 0x0e2}, +{ "atilde", 0x0e3}, +{ "adiaeresis", 0x0e4}, +{ "aring", 0x0e5}, +{ "ae", 0x0e6}, +{ "ccedilla", 0x0e7}, +{ "egrave", 0x0e8}, +{ "eacute", 0x0e9}, +{ "ecircumflex", 0x0ea}, +{ "ediaeresis", 0x0eb}, +{ "igrave", 0x0ec}, +{ "iacute", 0x0ed}, +{ "icircumflex", 0x0ee}, +{ "idiaeresis", 0x0ef}, +{ "eth", 0x0f0}, +{ "ntilde", 0x0f1}, +{ "ograve", 0x0f2}, +{ "oacute", 0x0f3}, +{ "ocircumflex", 0x0f4}, +{ "otilde", 0x0f5}, +{ "odiaeresis", 0x0f6}, +{ "division", 0x0f7}, +{ "oslash", 0x0f8}, +{ "ooblique", 0x0f8}, +{ "ugrave", 0x0f9}, +{ "uacute", 0x0fa}, +{ "ucircumflex", 0x0fb}, +{ "udiaeresis", 0x0fc}, +{ "yacute", 0x0fd}, +{ "thorn", 0x0fe}, +{ "ydiaeresis", 0x0ff}, +{"EuroSign", SDLK_EURO}, + + /* modifiers */ +{"Control_L", SDLK_LCTRL}, +{"Control_R", SDLK_RCTRL}, +{"Alt_L", SDLK_LALT}, +{"Alt_R", SDLK_RALT}, +{"Caps_Lock", SDLK_CAPSLOCK}, +{"Meta_L", SDLK_LMETA}, +{"Meta_R", SDLK_RMETA}, +{"Shift_L", SDLK_LSHIFT}, +{"Shift_R", SDLK_RSHIFT}, +{"Super_L", SDLK_LSUPER}, +{"Super_R", SDLK_RSUPER}, + + /* special keys */ +{"BackSpace", SDLK_BACKSPACE}, +{"Tab", SDLK_TAB}, +{"Return", SDLK_RETURN}, +{"Right", SDLK_RIGHT}, +{"Left", SDLK_LEFT}, +{"Up", SDLK_UP}, +{"Down", SDLK_DOWN}, +{"Page_Down", SDLK_PAGEDOWN}, +{"Page_Up", SDLK_PAGEUP}, +{"Insert", SDLK_INSERT}, +{"Delete", SDLK_DELETE}, +{"Home", SDLK_HOME}, +{"End", SDLK_END}, +{"Scroll_Lock", SDLK_SCROLLOCK}, +{"F1", SDLK_F1}, +{"F2", SDLK_F2}, +{"F3", SDLK_F3}, +{"F4", SDLK_F4}, +{"F5", SDLK_F5}, +{"F6", SDLK_F6}, +{"F7", SDLK_F7}, +{"F8", SDLK_F8}, +{"F9", SDLK_F9}, +{"F10", SDLK_F10}, +{"F11", SDLK_F11}, +{"F12", SDLK_F12}, +{"F13", SDLK_F13}, +{"F14", SDLK_F14}, +{"F15", SDLK_F15}, +{"Sys_Req", SDLK_SYSREQ}, +{"KP_0", SDLK_KP0}, +{"KP_1", SDLK_KP1}, +{"KP_2", SDLK_KP2}, +{"KP_3", SDLK_KP3}, +{"KP_4", SDLK_KP4}, +{"KP_5", SDLK_KP5}, +{"KP_6", SDLK_KP6}, +{"KP_7", SDLK_KP7}, +{"KP_8", SDLK_KP8}, +{"KP_9", SDLK_KP9}, +{"KP_Add", SDLK_KP_PLUS}, +{"KP_Decimal", SDLK_KP_PERIOD}, +{"KP_Divide", SDLK_KP_DIVIDE}, +{"KP_Enter", SDLK_KP_ENTER}, +{"KP_Equal", SDLK_KP_EQUALS}, +{"KP_Multiply", SDLK_KP_MULTIPLY}, +{"KP_Subtract", SDLK_KP_MINUS}, +{"help", SDLK_HELP}, +{"Menu", SDLK_MENU}, +{"Power", SDLK_POWER}, +{"Print", SDLK_PRINT}, +{"Mode_switch", SDLK_MODE}, +{"Multi_Key", SDLK_COMPOSE}, +{"Num_Lock", SDLK_NUMLOCK}, +{"Pause", SDLK_PAUSE}, +{"Escape", SDLK_ESCAPE}, + +{NULL, 0}, +}; diff --git a/ui/sdl_zoom.c b/ui/sdl_zoom.c new file mode 100644 index 0000000000..a986c7c14c --- /dev/null +++ b/ui/sdl_zoom.c @@ -0,0 +1,95 @@ +/* + * 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 "osdep.h" +#include <stdint.h> +#include <stdio.h> + +static int sdl_zoom_rgb16(SDL_Surface *src, SDL_Surface *dst, int smooth, + SDL_Rect *dst_rect); +static int 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 0000000000..74955bc944 --- /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 0000000000..64bbca849b --- /dev/null +++ b/ui/sdl_zoom_template.h @@ -0,0 +1,225 @@ +/* + * 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 int 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); + } + + if ((sax = (int *) malloc((dst->w + 1) * sizeof(Uint32))) == NULL) { + return (-1); + } + if ((say = (int *) malloc((dst->h + 1) * sizeof(Uint32))) == NULL) { + free(sax); + return (-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); + } + } + + free(sax); + free(say); + return (0); +} + +#undef SDL_TYPE + diff --git a/ui/vnc-auth-sasl.c b/ui/vnc-auth-sasl.c new file mode 100644 index 0000000000..a51ddc8a18 --- /dev/null +++ b/ui/vnc-auth-sasl.c @@ -0,0 +1,637 @@ +/* + * 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 = vs->sasl.waitWriteSSF = vs->sasl.wantSSF = 0; + vs->sasl.encodedLength = vs->sasl.encodedOffset = 0; + vs->sasl.encoded = NULL; + free(vs->sasl.username); + 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_handler2(vs->csock, NULL, 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 = qemu_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 = malloc(len + 1); + if (!mechname) { + VNC_DEBUG("Out of memory reading mechname\n"); + vnc_client_error(vs); + } + strncpy(mechname, (char*)data, len); + mechname[len] = '\0'; + 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]); + vnc_client_error(vs); + return -1; + } + } else { + char *offset = strstr(vs->sasl.mechlist, mechname); + VNC_DEBUG("Two %p\n", offset); + if (!offset) { + vnc_client_error(vs); + return -1; + } + VNC_DEBUG("Two '%s'\n", offset); + if (offset[-1] != ',' || + (offset[len] != '\0'&& + offset[len] != ',')) { + vnc_client_error(vs); + return -1; + } + } + + 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; +} + +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; +} + +#define USES_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) + + +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))) { + 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); + free(localAddr); + 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->vd->auth == VNC_AUTH_VENCRYPT && + vs->vd->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 (strncmp(vs->vd->display, "unix:", 5) == 0 +#ifdef CONFIG_VNC_TLS + /* Disable SSF, if using TLS+x509+SASL only. TLS without x509 + is not sufficiently strong */ + || (vs->vd->auth == VNC_AUTH_VENCRYPT && + vs->vd->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); + + if (!(vs->sasl.mechlist = strdup(mechlist))) { + VNC_DEBUG("Out of memory"); + sasl_dispose(&vs->sasl.conn); + vs->sasl.conn = NULL; + goto authabort; + } + 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); + return; +} + + diff --git a/ui/vnc-auth-sasl.h b/ui/vnc-auth-sasl.h new file mode 100644 index 0000000000..fd9b18a8fe --- /dev/null +++ b/ui/vnc-auth-sasl.h @@ -0,0 +1,74 @@ +/* + * QEMU VNC display driver: SASL auth protocol + * + * Copyright (C) 2009 Red Hat, Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + + +#ifndef __QEMU_VNC_AUTH_SASL_H__ +#define __QEMU_VNC_AUTH_SASL_H__ + + +#include <sasl/sasl.h> + +typedef struct VncStateSASL VncStateSASL; +typedef struct VncDisplaySASL VncDisplaySASL; + +#include "acl.h" + +struct VncStateSASL { + sasl_conn_t *conn; + /* If we want to negotiate an SSF layer with client */ + int wantSSF :1; + /* If we are now running the SSF layer */ + int runSSF :1; + /* + * 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 0000000000..07c169186a --- /dev/null +++ b/ui/vnc-auth-vencrypt.c @@ -0,0 +1,175 @@ +/* + * 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" + + +static void start_auth_vencrypt_subauth(VncState *vs) +{ + switch (vs->vd->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"); + return start_auth_sasl(vs); +#endif /* CONFIG_VNC_SASL */ + + default: /* Should not be possible, but just in case */ + VNC_DEBUG("Reject subauth %d server bug\n", vs->vd->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(struct VncState *vs) { + int ret; + + if ((ret = gnutls_handshake(vs->tls.session)) < 0) { + if (!gnutls_error_is_fatal(ret)) { + VNC_DEBUG("Handshake interrupted (blocking)\n"); + if (!gnutls_record_get_direction(vs->tls.session)) + qemu_set_fd_handler(vs->csock, vnc_tls_handshake_io, NULL, vs); + else + qemu_set_fd_handler(vs->csock, NULL, vnc_tls_handshake_io, vs); + return 0; + } + VNC_DEBUG("Handshake failed %s\n", gnutls_strerror(ret)); + vnc_client_error(vs); + return -1; + } + + 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"); + vs->tls.wiremode = VNC_WIREMODE_TLS; + qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, vnc_client_write, vs); + + start_auth_vencrypt_subauth(vs); + + return 0; +} + +static void vnc_tls_handshake_io(void *opaque) { + struct VncState *vs = (struct VncState *)opaque; + + VNC_DEBUG("Handshake IO continue\n"); + vnc_start_vencrypt_handshake(vs); +} + + + +#define NEED_X509_AUTH(vs) \ + ((vs)->vd->subauth == VNC_AUTH_VENCRYPT_X509NONE || \ + (vs)->vd->subauth == VNC_AUTH_VENCRYPT_X509VNC || \ + (vs)->vd->subauth == VNC_AUTH_VENCRYPT_X509PLAIN || \ + (vs)->vd->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->vd->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->vd->subauth); + vnc_write_u8(vs, 0); /* Accept version */ + vnc_write_u8(vs, 1); /* Number of sub-auths */ + vnc_write_u32(vs, vs->vd->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 0000000000..9f674c5173 --- /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-encoding-hextile.c b/ui/vnc-encoding-hextile.c new file mode 100644 index 0000000000..728f25e5d8 --- /dev/null +++ b/ui/vnc-encoding-hextile.c @@ -0,0 +1,116 @@ +/* + * 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 8 +#include "vnchextile.h" +#undef BPP + +#define BPP 16 +#include "vnchextile.h" +#undef BPP + +#define BPP 32 +#include "vnchextile.h" +#undef BPP + +#define GENERIC +#define BPP 8 +#include "vnchextile.h" +#undef BPP +#undef GENERIC + +#define GENERIC +#define BPP 16 +#include "vnchextile.h" +#undef BPP +#undef GENERIC + +#define GENERIC +#define BPP 32 +#include "vnchextile.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; + VncDisplay *vd = vs->vd; + + last_fg = (uint8_t *) qemu_malloc(vd->server->pf.bytes_per_pixel); + last_bg = (uint8_t *) qemu_malloc(vd->server->pf.bytes_per_pixel); + has_fg = has_bg = 0; + for (j = y; j < (y + h); j += 16) { + for (i = x; i < (x + w); i += 16) { + vs->send_hextile_tile(vs, i, j, + MIN(16, x + w - i), MIN(16, y + h - j), + last_bg, last_fg, &has_bg, &has_fg); + } + } + free(last_fg); + free(last_bg); + + return 1; +} + +void vnc_hextile_set_pixel_conversion(VncState *vs, int generic) +{ + if (!generic) { + switch (vs->ds->surface->pf.bits_per_pixel) { + case 8: + vs->send_hextile_tile = send_hextile_tile_8; + break; + case 16: + vs->send_hextile_tile = send_hextile_tile_16; + break; + case 32: + vs->send_hextile_tile = send_hextile_tile_32; + break; + } + } else { + switch (vs->ds->surface->pf.bits_per_pixel) { + case 8: + vs->send_hextile_tile = send_hextile_tile_generic_8; + break; + case 16: + vs->send_hextile_tile = send_hextile_tile_generic_16; + break; + case 32: + vs->send_hextile_tile = send_hextile_tile_generic_32; + break; + } + } +} diff --git a/ui/vnc-encoding-tight.c b/ui/vnc-encoding-tight.c new file mode 100644 index 0000000000..c1a292bdd2 --- /dev/null +++ b/ui/vnc-encoding-tight.c @@ -0,0 +1,1520 @@ +/* + * QEMU VNC display driver: tight encoding + * + * From libvncserver/libvncserver/tight.c + * Copyright (C) 2000, 2001 Const Kaplinsky. All Rights Reserved. + * Copyright (C) 1999 AT&T Laboratories Cambridge. All Rights Reserved. + * + * Copyright (C) 2010 Corentin Chary <corentin.chary@gmail.com> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "qemu-common.h" + +#ifdef CONFIG_VNC_JPEG +#include <stdio.h> +#include <jpeglib.h> +#endif + +#include "bswap.h" +#include "qdict.h" +#include "qint.h" +#include "vnc.h" +#include "vnc-encoding-tight.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 } +}; + +/* + * Code to guess if given rectangle is suitable for smooth image + * compression (by applying "gradient" filter or JPEG coder). + */ + +static uint +tight_detect_smooth_image24(VncState *vs, int w, int h) +{ + int off; + int x, y, d, dx; + uint c; + uint stats[256]; + int pixels = 0; + int pix, left[3]; + uint errors; + unsigned char *buf = vs->tight.buffer; + + /* + * If client is big-endian, color samples begin from the second + * byte (offset 1) of a 32-bit pixel value. + */ + off = !!(vs->clientds.flags & QEMU_BIG_ENDIAN_FLAG); + + 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; + } + } + + /* 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 uint \ + 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; \ + uint c; \ + uint stats[256]; \ + int pixels = 0; \ + int sample, sum, left[3]; \ + uint errors; \ + unsigned char *buf = vs->tight.buffer; \ + \ + endian = ((vs->clientds.flags & QEMU_BIG_ENDIAN_FLAG) != \ + (vs->ds->surface->flags & QEMU_BIG_ENDIAN_FLAG)); \ + \ + \ + max[0] = vs->clientds.pf.rmax; \ + max[1] = vs->clientds.pf.gmax; \ + max[2] = vs->clientds.pf.bmax; \ + shift[0] = vs->clientds.pf.rshift; \ + shift[1] = vs->clientds.pf.gshift; \ + shift[2] = vs->clientds.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 ((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) +{ + uint errors; + int compression = vs->tight_compression; + int quality = vs->tight_quality; + + if (!vs->vd->lossy) { + return 0; + } + + if (ds_get_bytes_per_pixel(vs->ds) == 1 || + vs->clientds.pf.bytes_per_pixel == 1 || + w < VNC_TIGHT_DETECT_MIN_WIDTH || h < VNC_TIGHT_DETECT_MIN_HEIGHT) { + return 0; + } + + if (vs->tight_quality != -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->clientds.pf.bytes_per_pixel == 4) { + if (vs->tight_pixel24) { + errors = tight_detect_smooth_image24(vs, w, h); + if (vs->tight_quality != -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 != -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. + */ + +static void tight_palette_rgb2buf(uint32_t rgb, int bpp, uint8_t buf[6]) +{ + memset(buf, 0, 6); + + if (bpp == 32) { + buf[0] = ((rgb >> 24) & 0xFF); + buf[1] = ((rgb >> 16) & 0xFF); + buf[2] = ((rgb >> 8) & 0xFF); + buf[3] = ((rgb >> 0) & 0xFF); + buf[4] = ((buf[0] & 1) == 0) << 3 | ((buf[1] & 1) == 0) << 2; + buf[4]|= ((buf[2] & 1) == 0) << 1 | ((buf[3] & 1) == 0) << 0; + buf[0] |= 1; + buf[1] |= 1; + buf[2] |= 1; + buf[3] |= 1; + } + if (bpp == 16) { + buf[0] = ((rgb >> 8) & 0xFF); + buf[1] = ((rgb >> 0) & 0xFF); + buf[2] = ((buf[0] & 1) == 0) << 1 | ((buf[1] & 1) == 0) << 0; + buf[0] |= 1; + buf[1] |= 1; + } +} + +static uint32_t tight_palette_buf2rgb(int bpp, const uint8_t *buf) +{ + uint32_t rgb = 0; + + if (bpp == 32) { + rgb |= ((buf[0] & ~1) | !((buf[4] >> 3) & 1)) << 24; + rgb |= ((buf[1] & ~1) | !((buf[4] >> 2) & 1)) << 16; + rgb |= ((buf[2] & ~1) | !((buf[4] >> 1) & 1)) << 8; + rgb |= ((buf[3] & ~1) | !((buf[4] >> 0) & 1)) << 0; + } + if (bpp == 16) { + rgb |= ((buf[0] & ~1) | !((buf[2] >> 1) & 1)) << 8; + rgb |= ((buf[1] & ~1) | !((buf[2] >> 0) & 1)) << 0; + } + return rgb; +} + + +static int tight_palette_insert(QDict *palette, uint32_t rgb, int bpp, int max) +{ + uint8_t key[6]; + int idx = qdict_size(palette); + bool present; + + tight_palette_rgb2buf(rgb, bpp, key); + present = qdict_haskey(palette, (char *)key); + if (idx >= max && !present) { + return 0; + } + if (!present) { + qdict_put(palette, (char *)key, qint_from_int(idx)); + } + return qdict_size(palette); +} + +#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, \ + struct QDict **palette) { \ + uint##bpp##_t *data; \ + uint##bpp##_t c0, c1, ci; \ + int i, n0, n1; \ + \ + data = (uint##bpp##_t *)vs->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 = qdict_new(); \ + tight_palette_insert(*palette, c0, bpp, max); \ + tight_palette_insert(*palette, c1, bpp, max); \ + tight_palette_insert(*palette, ci, bpp, max); \ + \ + for (i++; i < count; i++) { \ + if (data[i] == ci) { \ + continue; \ + } else { \ + if (!tight_palette_insert(*palette, (uint32_t)ci, \ + bpp, max)) { \ + return 0; \ + } \ + ci = data[i]; \ + } \ + } \ + \ + return qdict_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, + struct QDict **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->clientds.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; +} + +/* Callback to dump a palette with qdict_iter +static void print_palette(const char *key, QObject *obj, void *opaque) +{ + uint8_t idx = qint_get_int(qobject_to_qint(obj)); + uint32_t rgb = tight_palette_buf2rgb(32, (uint8_t *)key); + + fprintf(stderr, "%.2x ", (unsigned char)*key); + while (*key++) + fprintf(stderr, "%.2x ", (unsigned char)*key); + + fprintf(stderr, ": idx: %x rgb: %x\n", idx, rgb); +} +*/ + +/* + * Converting truecolor samples into palette indices. + */ +#define DEFINE_IDX_ENCODE_FUNCTION(bpp) \ + \ + static void \ + tight_encode_indexed_rect##bpp(uint8_t *buf, int count, \ + struct QDict *palette) { \ + uint##bpp##_t *src; \ + uint##bpp##_t rgb; \ + uint8_t key[6]; \ + 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++; \ + } \ + tight_palette_rgb2buf(rgb, bpp, key); \ + if (!qdict_haskey(palette, (char *)key)) { \ + /* \ + * Should never happen, but don't break everything \ + * if it does, use the first color instead \ + */ \ + idx = 0; \ + } else { \ + idx = qdict_get_int(palette, (char *)key); \ + } \ + 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 ((vs->clientds.flags & QEMU_BIG_ENDIAN_FLAG) == + (vs->ds->surface->flags & QEMU_BIG_ENDIAN_FLAG)) { + shift[0] = vs->clientds.pf.rshift; + shift[1] = vs->clientds.pf.gshift; + shift[2] = vs->clientds.pf.bshift; + } else { + shift[0] = 24 - vs->clientds.pf.rshift; + shift[1] = 24 - vs->clientds.pf.gshift; + shift[2] = 24 - vs->clientds.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 = ((vs->clientds.flags & QEMU_BIG_ENDIAN_FLAG) != \ + (vs->ds->surface->flags & QEMU_BIG_ENDIAN_FLAG)); \ + \ + max[0] = vs->clientds.pf.rmax; \ + max[1] = vs->clientds.pf.gmax; \ + max[2] = vs->clientds.pf.bmax; \ + shift[0] = vs->clientds.pf.rshift; \ + shift[1] = vs->clientds.pf.gshift; \ + shift[2] = vs->clientds.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 successfull, and in + * that case new color will be stored in *colorPtr. + */ + +#define DEFINE_CHECK_SOLID_FUNCTION(bpp) \ + \ + static bool \ + check_solid_tile##bpp(VncState *vs, int x, int y, int w, int h, \ + uint32_t* color, bool samecolor) \ + { \ + VncDisplay *vd = vs->vd; \ + uint##bpp##_t *fbptr; \ + uint##bpp##_t c; \ + int dx, dy; \ + \ + fbptr = (uint##bpp##_t *) \ + (vd->server->data + y * ds_get_linesize(vs->ds) + \ + x * ds_get_bytes_per_pixel(vs->ds)); \ + \ + 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 = (uint##bpp##_t *) \ + ((uint8_t *)fbptr + ds_get_linesize(vs->ds)); \ + } \ + \ + *color = (uint32_t)c; \ + return true; \ + } + +DEFINE_CHECK_SOLID_FUNCTION(32) +DEFINE_CHECK_SOLID_FUNCTION(16) +DEFINE_CHECK_SOLID_FUNCTION(8) + +static bool check_solid_tile(VncState *vs, int x, int y, int w, int h, + uint32_t* color, bool samecolor) +{ + VncDisplay *vd = vs->vd; + + switch(vd->server->pf.bytes_per_pixel) { + case 4: + return check_solid_tile32(vs, x, y, w, h, color, samecolor); + case 2: + return check_solid_tile16(vs, x, y, w, h, color, samecolor); + default: + return check_solid_tile8(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.buffer, vs->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.buffer; + zstream->avail_in = vs->tight.offset; + zstream->next_out = vs->tight_zlib.buffer + vs->tight_zlib.offset; + zstream->avail_out = vs->tight_zlib.capacity - vs->tight_zlib.offset; + zstream->data_type = Z_BINARY; + previous_out = zstream->total_out; + + /* 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; + bytes = zstream->total_out - previous_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 ((vs->clientds.flags & QEMU_BIG_ENDIAN_FLAG) == + (vs->ds->surface->flags & QEMU_BIG_ENDIAN_FLAG)) { + rshift = vs->clientds.pf.rshift; + gshift = vs->clientds.pf.gshift; + bshift = vs->clientds.pf.bshift; + } else { + rshift = 24 - vs->clientds.pf.rshift; + gshift = 24 - vs->clientds.pf.gshift; + bshift = 24 - vs->clientds.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 w, int h) +{ + int stream = 0; + size_t bytes; + + vnc_write_u8(vs, stream << 4); /* no flushing, no filter */ + + if (vs->tight_pixel24) { + tight_pack24(vs, vs->tight.buffer, w * h, &vs->tight.offset); + bytes = 3; + } else { + bytes = vs->clientds.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.buffer, 1, &vs->tight.offset); + bytes = 3; + } else { + bytes = vs->clientds.pf.bytes_per_pixel; + } + + vnc_write(vs, vs->tight.buffer, bytes); + return 1; +} + +static int send_mono_rect(VncState *vs, int w, int h, uint32_t bg, uint32_t fg) +{ + size_t bytes; + int stream = 1; + int level = tight_conf[vs->tight_compression].mono_zlib_level; + + 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->clientds.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.buffer, w, h, bg, fg); + break; + } + case 2: + vnc_write(vs, &bg, 2); + vnc_write(vs, &fg, 2); + tight_encode_mono_rect16(vs->tight.buffer, w, h, bg, fg); + break; + default: + vnc_write_u8(vs, bg); + vnc_write_u8(vs, fg); + tight_encode_mono_rect8(vs->tight.buffer, w, h, bg, fg); + break; + } + vs->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; +}; + +static void write_palette(const char *key, QObject *obj, void *opaque) +{ + struct palette_cb_priv *priv = opaque; + VncState *vs = priv->vs; + uint32_t bytes = vs->clientds.pf.bytes_per_pixel; + uint8_t idx = qint_get_int(qobject_to_qint(obj)); + + if (bytes == 4) { + uint32_t color = tight_palette_buf2rgb(32, (uint8_t *)key); + + ((uint32_t*)priv->header)[idx] = color; + } else { + uint16_t color = tight_palette_buf2rgb(16, (uint8_t *)key); + + ((uint16_t*)priv->header)[idx] = color; + } +} + +static bool send_gradient_rect(VncState *vs, int w, int h) +{ + int stream = 3; + int level = tight_conf[vs->tight_compression].gradient_zlib_level; + size_t bytes; + + if (vs->clientds.pf.bytes_per_pixel == 1) + return send_full_color_rect(vs, 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.buffer, w, h); + bytes = 3; + } else if (vs->clientds.pf.bytes_per_pixel == 4) { + tight_filter_gradient32(vs, (uint32_t *)vs->tight.buffer, w, h); + bytes = 4; + } else { + tight_filter_gradient16(vs, (uint16_t *)vs->tight.buffer, w, h); + bytes = 2; + } + + buffer_reset(&vs->tight_gradient); + + bytes = w * h * bytes; + vs->tight.offset = bytes; + + bytes = tight_compress_data(vs, stream, bytes, + level, Z_FILTERED); + return (bytes >= 0); +} + +static int send_palette_rect(VncState *vs, int w, int h, struct QDict *palette) +{ + int stream = 2; + int level = tight_conf[vs->tight_compression].idx_zlib_level; + int colors; + size_t bytes; + + colors = qdict_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->clientds.pf.bytes_per_pixel) { + case 4: + { + size_t old_offset, offset; + uint32_t header[qdict_size(palette)]; + struct palette_cb_priv priv = { vs, (uint8_t *)header }; + + old_offset = vs->output.offset; + qdict_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.buffer, w * h, palette); + break; + } + case 2: + { + uint16_t header[qdict_size(palette)]; + struct palette_cb_priv priv = { vs, (uint8_t *)header }; + + qdict_iter(palette, write_palette, &priv); + vnc_write(vs, header, sizeof(header)); + tight_encode_indexed_rect16(vs->tight.buffer, w * h, palette); + break; + } + default: + return -1; /* No palette for 8bits colors */ + break; + } + bytes = w * h; + vs->tight.offset = bytes; + + bytes = tight_compress_data(vs, stream, bytes, + level, Z_DEFAULT_STRATEGY); + return (bytes >= 0); +} + +/* + * JPEG compression stuff. + */ +#ifdef CONFIG_VNC_JPEG +static void jpeg_prepare_row24(VncState *vs, uint8_t *dst, int x, int y, + int count) +{ + VncDisplay *vd = vs->vd; + uint32_t *fbptr; + uint32_t pix; + + fbptr = (uint32_t *)(vd->server->data + y * ds_get_linesize(vs->ds) + + x * ds_get_bytes_per_pixel(vs->ds)); + + while (count--) { + pix = *fbptr++; + *dst++ = (uint8_t)(pix >> vs->ds->surface->pf.rshift); + *dst++ = (uint8_t)(pix >> vs->ds->surface->pf.gshift); + *dst++ = (uint8_t)(pix >> vs->ds->surface->pf.bshift); + } +} + +#define DEFINE_JPEG_GET_ROW_FUNCTION(bpp) \ + \ + static void \ + jpeg_prepare_row##bpp(VncState *vs, uint8_t *dst, \ + int x, int y, int count) \ + { \ + VncDisplay *vd = vs->vd; \ + uint##bpp##_t *fbptr; \ + uint##bpp##_t pix; \ + int r, g, b; \ + \ + fbptr = (uint##bpp##_t *) \ + (vd->server->data + y * ds_get_linesize(vs->ds) + \ + x * ds_get_bytes_per_pixel(vs->ds)); \ + \ + while (count--) { \ + pix = *fbptr++; \ + \ + r = (int)((pix >> vs->ds->surface->pf.rshift) \ + & vs->ds->surface->pf.rmax); \ + g = (int)((pix >> vs->ds->surface->pf.gshift) \ + & vs->ds->surface->pf.gmax); \ + b = (int)((pix >> vs->ds->surface->pf.bshift) \ + & vs->ds->surface->pf.bmax); \ + \ + *dst++ = (uint8_t)((r * 255 + vs->ds->surface->pf.rmax / 2) \ + / vs->ds->surface->pf.rmax); \ + *dst++ = (uint8_t)((g * 255 + vs->ds->surface->pf.gmax / 2) \ + / vs->ds->surface->pf.gmax); \ + *dst++ = (uint8_t)((b * 255 + vs->ds->surface->pf.bmax / 2) \ + / vs->ds->surface->pf.bmax); \ + } \ + } + +DEFINE_JPEG_GET_ROW_FUNCTION(16) +DEFINE_JPEG_GET_ROW_FUNCTION(32) + +static void jpeg_prepare_row(VncState *vs, uint8_t *dst, int x, int y, + int count) +{ + if (vs->tight_pixel24) + jpeg_prepare_row24(vs, dst, x, y, count); + else if (ds_get_bytes_per_pixel(vs->ds) == 4) + jpeg_prepare_row32(vs, dst, x, y, count); + else + jpeg_prepare_row16(vs, dst, x, y, count); +} + +/* + * 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; + JSAMPROW row[1]; + uint8_t *buf; + int dy; + + if (ds_get_bytes_per_pixel(vs->ds) == 1) + return send_full_color_rect(vs, w, h); + + buf = qemu_malloc(w * 3); + row[0] = buf; + 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); + + for (dy = 0; dy < h; dy++) { + jpeg_prepare_row(vs, buf, x, y + dy, w); + jpeg_write_scanlines(&cinfo, row, 1); + } + + 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 */ + +static void vnc_tight_start(VncState *vs) +{ + buffer_reset(&vs->tight); + + // make the output buffer be the zlib buffer, so we can compress it later + vs->tight_tmp = vs->output; + vs->output = vs->tight; +} + +static void vnc_tight_stop(VncState *vs) +{ + // switch back to normal output/zlib buffers + vs->tight = vs->output; + vs->output = vs->tight_tmp; +} + +static int send_sub_rect(VncState *vs, int x, int y, int w, int h) +{ + struct QDict *palette = NULL; + uint32_t bg = 0, fg = 0; + int colors; + int ret = 0; + + vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_TIGHT); + + vnc_tight_start(vs); + vnc_raw_send_framebuffer_update(vs, x, y, w, h); + vnc_tight_stop(vs); + + colors = tight_fill_palette(vs, x, y, w * h, &fg, &bg, &palette); + + if (colors == 0) { + if (tight_detect_smooth_image(vs, w, h)) { + if (vs->tight_quality == -1) { + ret = send_gradient_rect(vs, w, h); + } else { +#ifdef CONFIG_VNC_JPEG + 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, w, h); +#endif + } + } else { + ret = send_full_color_rect(vs, w, h); + } + } else if (colors == 1) { + ret = send_solid_rect(vs); + } else if (colors == 2) { + ret = send_mono_rect(vs, w, h, bg, fg); + } else if (colors <= 256) { +#ifdef CONFIG_VNC_JPEG + if (colors > 96 && vs->tight_quality != -1 && vs->tight_quality <= 3 && + 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, w, h, palette); + } +#else + ret = send_palette_rect(vs, w, h, palette); +#endif + } + QDECREF(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, VNC_ENCODING_TIGHT); + + 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) +{ + 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 (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); + 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); + } + if (x_best != x) { + n += vnc_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 += vnc_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 += vnc_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); +} + +int vnc_tight_send_framebuffer_update(VncState *vs, int x, int y, + int w, int h) +{ + int max_rows; + + if (vs->clientds.pf.bytes_per_pixel == 4 && vs->clientds.pf.rmax == 0xFF && + vs->clientds.pf.bmax == 0xFF && vs->clientds.pf.gmax == 0xFF) { + vs->tight_pixel24 = true; + } else { + vs->tight_pixel24 = false; + } + + if (w * h < VNC_TIGHT_MIN_SPLIT_RECT_SIZE) + return send_rect_simple(vs, x, y, w, h); + + /* 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); +} + +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); + buffer_free(&vs->tight_zlib); + buffer_free(&vs->tight_gradient); +#ifdef CONFIG_VNC_JPEG + buffer_free(&vs->tight_jpeg); +#endif +} diff --git a/ui/vnc-encoding-tight.h b/ui/vnc-encoding-tight.h new file mode 100644 index 0000000000..9b0910c79a --- /dev/null +++ b/ui/vnc-encoding-tight.h @@ -0,0 +1,181 @@ +/* + * 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 0xxx, then the compression type is "basic", + * values greater than 1001 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" 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", the following data stream looks like + * this: + * + * 1..3 bytes: data size (N) in compact representation; + * N bytes: JPEG 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" or "jpeg". + * + *-- 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_MAX_SUBENCODING 0x09 + +/* 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-encoding-zlib.c b/ui/vnc-encoding-zlib.c new file mode 100644 index 0000000000..a99bc387dc --- /dev/null +++ b/ui/vnc-encoding-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 = qemu_mallocz(size); + + return (p); +} + +void vnc_zlib_zfree(void *x, void *addr) +{ + qemu_free(addr); +} + +static void vnc_zlib_start(VncState *vs) +{ + buffer_reset(&vs->zlib); + + // make the output buffer be the zlib buffer, so we can compress it later + vs->zlib_tmp = vs->output; + vs->output = vs->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 = 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.offset + 64); + + // set pointers + zstream->next_in = vs->zlib.buffer; + zstream->avail_in = vs->zlib.offset; + zstream->next_out = vs->output.buffer + vs->output.offset; + zstream->avail_out = vs->output.capacity - vs->output.offset; + zstream->data_type = Z_BINARY; + previous_out = zstream->total_out; + + // 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 zstream->total_out - previous_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); +} diff --git a/ui/vnc-tls.c b/ui/vnc-tls.c new file mode 100644 index 0000000000..dec626c539 --- /dev/null +++ b/ui/vnc-tls.c @@ -0,0 +1,445 @@ +/* + * 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_socket.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-hellmen params periodically */ + if (gnutls_dh_params_init (&dh_params) < 0) + return 0; + if (gnutls_dh_params_generate2 (dh_params, DH_BITS) < 0) + return 0; + +#if defined(_VNC_DEBUG) && _VNC_DEBUG >= 2 + gnutls_global_set_log_level(10); + gnutls_global_set_log_function(vnc_debug_gnutls_log); +#endif + + tlsinitialized = 1; + + return 1; +} + +static ssize_t vnc_tls_push(gnutls_transport_ptr_t transport, + const void *data, + size_t len) { + struct VncState *vs = (struct VncState *)transport; + int ret; + + retry: + ret = send(vs->csock, data, len, 0); + if (ret < 0) { + if (errno == EINTR) + goto retry; + return -1; + } + return ret; +} + + +static ssize_t vnc_tls_pull(gnutls_transport_ptr_t transport, + void *data, + size_t len) { + struct VncState *vs = (struct VncState *)transport; + int ret; + + retry: + ret = recv(vs->csock, data, len, 0); + if (ret < 0) { + if (errno == EINTR) + goto retry; + return -1; + } + return ret; +} + + +static gnutls_anon_server_credentials vnc_tls_initialize_anon_cred(void) +{ + gnutls_anon_server_credentials anon_cred; + int ret; + + if ((ret = gnutls_anon_allocate_server_credentials(&anon_cred)) < 0) { + VNC_DEBUG("Cannot allocate credentials %s\n", gnutls_strerror(ret)); + return NULL; + } + + gnutls_anon_set_server_dh_params(anon_cred, dh_params); + + return anon_cred; +} + + +static gnutls_certificate_credentials_t vnc_tls_initialize_x509_cred(VncDisplay *vd) +{ + gnutls_certificate_credentials_t x509_cred; + int ret; + + if (!vd->tls.x509cacert) { + VNC_DEBUG("No CA x509 certificate specified\n"); + return NULL; + } + if (!vd->tls.x509cert) { + VNC_DEBUG("No server x509 certificate specified\n"); + return NULL; + } + if (!vd->tls.x509key) { + VNC_DEBUG("No server private key specified\n"); + return NULL; + } + + if ((ret = gnutls_certificate_allocate_credentials(&x509_cred)) < 0) { + VNC_DEBUG("Cannot allocate credentials %s\n", gnutls_strerror(ret)); + return NULL; + } + if ((ret = gnutls_certificate_set_x509_trust_file(x509_cred, + vd->tls.x509cacert, + GNUTLS_X509_FMT_PEM)) < 0) { + VNC_DEBUG("Cannot load CA certificate %s\n", gnutls_strerror(ret)); + gnutls_certificate_free_credentials(x509_cred); + return NULL; + } + + if ((ret = gnutls_certificate_set_x509_key_file (x509_cred, + vd->tls.x509cert, + vd->tls.x509key, + GNUTLS_X509_FMT_PEM)) < 0) { + VNC_DEBUG("Cannot load certificate & key %s\n", gnutls_strerror(ret)); + gnutls_certificate_free_credentials(x509_cred); + return NULL; + } + + if (vd->tls.x509cacrl) { + if ((ret = gnutls_certificate_set_x509_crl_file(x509_cred, + vd->tls.x509cacrl, + GNUTLS_X509_FMT_PEM)) < 0) { + VNC_DEBUG("Cannot load CRL %s\n", gnutls_strerror(ret)); + gnutls_certificate_free_credentials(x509_cred); + return NULL; + } + } + + gnutls_certificate_set_dh_params (x509_cred, dh_params); + + return x509_cred; +} + + +int vnc_tls_validate_certificate(struct VncState *vs) +{ + int ret; + unsigned int status; + const gnutls_datum_t *certs; + unsigned int nCerts, i; + time_t now; + + VNC_DEBUG("Validating client certificate\n"); + if ((ret = gnutls_certificate_verify_peers2 (vs->tls.session, &status)) < 0) { + VNC_DEBUG("Verify failed %s\n", gnutls_strerror(ret)); + return -1; + } + + if ((now = time(NULL)) == ((time_t)-1)) { + return -1; + } + + if (status != 0) { + if (status & GNUTLS_CERT_INVALID) + VNC_DEBUG("The certificate is not trusted.\n"); + + if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) + VNC_DEBUG("The certificate hasn't got a known issuer.\n"); + + if (status & GNUTLS_CERT_REVOKED) + VNC_DEBUG("The certificate has been revoked.\n"); + + if (status & GNUTLS_CERT_INSECURE_ALGORITHM) + VNC_DEBUG("The certificate uses an insecure algorithm\n"); + + return -1; + } else { + VNC_DEBUG("Certificate is valid!\n"); + } + + /* Only support x509 for now */ + if (gnutls_certificate_type_get(vs->tls.session) != GNUTLS_CRT_X509) + return -1; + + if (!(certs = gnutls_certificate_get_peers(vs->tls.session, &nCerts))) + return -1; + + for (i = 0 ; i < nCerts ; i++) { + gnutls_x509_crt_t cert; + VNC_DEBUG ("Checking certificate chain %d\n", i); + if (gnutls_x509_crt_init (&cert) < 0) + return -1; + + if (gnutls_x509_crt_import(cert, &certs[i], GNUTLS_X509_FMT_DER) < 0) { + gnutls_x509_crt_deinit (cert); + return -1; + } + + if (gnutls_x509_crt_get_expiration_time (cert) < now) { + VNC_DEBUG("The certificate has expired\n"); + gnutls_x509_crt_deinit (cert); + return -1; + } + + if (gnutls_x509_crt_get_activation_time (cert) > now) { + VNC_DEBUG("The certificate is not yet activated\n"); + gnutls_x509_crt_deinit (cert); + return -1; + } + + if (gnutls_x509_crt_get_activation_time (cert) > now) { + VNC_DEBUG("The certificate is not yet activated\n"); + gnutls_x509_crt_deinit (cert); + return -1; + } + + if (i == 0) { + size_t dnameSize = 1024; + vs->tls.dname = qemu_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 = qemu_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; +} + + +int vnc_tls_client_setup(struct VncState *vs, + int needX509Creds) { + static const int cert_type_priority[] = { GNUTLS_CRT_X509, 0 }; + static const int protocol_priority[]= { 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}; + + 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 (gnutls_kx_set_priority(vs->tls.session, needX509Creds ? kx_x509 : kx_anon) < 0) { + gnutls_deinit(vs->tls.session); + vs->tls.session = NULL; + vnc_client_error(vs); + return -1; + } + + if (gnutls_certificate_type_set_priority(vs->tls.session, cert_type_priority) < 0) { + gnutls_deinit(vs->tls.session); + vs->tls.session = NULL; + vnc_client_error(vs); + return -1; + } + + if (gnutls_protocol_set_priority(vs->tls.session, protocol_priority) < 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 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(struct VncState *vs) +{ + if (vs->tls.session) { + gnutls_deinit(vs->tls.session); + vs->tls.session = NULL; + } + vs->tls.wiremode = VNC_WIREMODE_CLEAR; + 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; + + if (*cred) { + qemu_free(*cred); + *cred = NULL; + } + + *cred = qemu_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) { + qemu_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: + qemu_free(vd->tls.x509cacert); + qemu_free(vd->tls.x509cacrl); + qemu_free(vd->tls.x509cert); + qemu_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 0000000000..2b93633896 --- /dev/null +++ b/ui/vnc-tls.h @@ -0,0 +1,76 @@ +/* + * 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 "acl.h" + +enum { + VNC_WIREMODE_CLEAR, + VNC_WIREMODE_TLS, +}; + +typedef struct VncDisplayTLS VncDisplayTLS; +typedef struct VncStateTLS VncStateTLS; + +/* Server state */ +struct VncDisplayTLS { + int x509verify; /* Non-zero if server requests & validates client cert */ + qemu_acl *acl; + + /* Paths to x509 certs/keys */ + char *x509cacert; + char *x509cacrl; + char *x509cert; + char *x509key; +}; + +/* Per client state */ +struct VncStateTLS { + /* Whether data is being TLS encrypted yet */ + int wiremode; + gnutls_session_t session; + + /* Client's Distinguished Name from the x509 cert */ + char *dname; +}; + +int vnc_tls_client_setup(VncState *vs, int x509Creds); +void vnc_tls_client_cleanup(VncState *vs); + +int vnc_tls_validate_certificate(VncState *vs); + +int vnc_tls_set_x509_creds_dir(VncDisplay *vd, + const char *path); + + +#endif /* __QEMU_VNC_TLS_H__ */ + diff --git a/ui/vnc.c b/ui/vnc.c new file mode 100644 index 0000000000..ccd7aad86b --- /dev/null +++ b/ui/vnc.c @@ -0,0 +1,2631 @@ +/* + * 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 "sysemu.h" +#include "qemu_socket.h" +#include "qemu-timer.h" +#include "acl.h" +#include "qemu-objects.h" + +#define VNC_REFRESH_INTERVAL_BASE 30 +#define VNC_REFRESH_INTERVAL_INC 50 +#define VNC_REFRESH_INTERVAL_MAX 2000 + +#include "vnc_keysym.h" +#include "d3des.h" + +#define count_bits(c, v) { \ + for (c = 0; v; v >>= 1) \ + { \ + c += v & 1; \ + } \ +} + + +static VncDisplay *vnc_display; /* needed for info vnc */ +static DisplayChangeListener *dcl; + +static int vnc_cursor_define(VncState *vs); + +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 = qemu_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 int put_addr_qdict(QDict *qdict, struct sockaddr_storage *sa, + socklen_t salen) +{ + 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 -1; + } + + qdict_put(qdict, "host", qstring_from_str(host)); + qdict_put(qdict, "service", qstring_from_str(serv)); + qdict_put(qdict, "family",qstring_from_str(inet_strfamily(sa->ss_family))); + + return 0; +} + +static int vnc_server_addr_put(QDict *qdict, int fd) +{ + struct sockaddr_storage sa; + socklen_t salen; + + salen = sizeof(sa); + if (getsockname(fd, (struct sockaddr*)&sa, &salen) < 0) { + return -1; + } + + return put_addr_qdict(qdict, &sa, salen); +} + +static int vnc_qdict_remote_addr(QDict *qdict, int fd) +{ + struct sockaddr_storage sa; + socklen_t salen; + + salen = sizeof(sa); + if (getpeername(fd, (struct sockaddr*)&sa, &salen) < 0) { + return -1; + } + + return put_addr_qdict(qdict, &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 int vnc_server_info_put(QDict *qdict) +{ + if (vnc_server_addr_put(qdict, vnc_display->lsock) < 0) { + return -1; + } + + qdict_put(qdict, "auth", qstring_from_str(vnc_auth_name(vnc_display))); + return 0; +} + +static void vnc_client_cache_auth(VncState *client) +{ + QDict *qdict; + + if (!client->info) { + return; + } + + qdict = qobject_to_qdict(client->info); + +#ifdef CONFIG_VNC_TLS + if (client->tls.session && + client->tls.dname) { + qdict_put(qdict, "x509_dname", qstring_from_str(client->tls.dname)); + } +#endif +#ifdef CONFIG_VNC_SASL + if (client->sasl.conn && + client->sasl.username) { + qdict_put(qdict, "sasl_username", + qstring_from_str(client->sasl.username)); + } +#endif +} + +static void vnc_client_cache_addr(VncState *client) +{ + QDict *qdict; + + qdict = qdict_new(); + if (vnc_qdict_remote_addr(qdict, client->csock) < 0) { + QDECREF(qdict); + /* XXX: how to report the error? */ + return; + } + + client->info = QOBJECT(qdict); +} + +static void vnc_qmp_event(VncState *vs, MonitorEvent event) +{ + QDict *server; + QObject *data; + + if (!vs->info) { + return; + } + + server = qdict_new(); + if (vnc_server_info_put(server) < 0) { + QDECREF(server); + return; + } + + data = qobject_from_jsonf("{ 'client': %p, 'server': %p }", + vs->info, QOBJECT(server)); + + monitor_protocol_event(event, data); + + qobject_incref(vs->info); + qobject_decref(data); +} + +static void info_vnc_iter(QObject *obj, void *opaque) +{ + QDict *client; + Monitor *mon = opaque; + + client = qobject_to_qdict(obj); + monitor_printf(mon, "Client:\n"); + monitor_printf(mon, " address: %s:%s\n", + qdict_get_str(client, "host"), + qdict_get_str(client, "service")); + +#ifdef CONFIG_VNC_TLS + monitor_printf(mon, " x509_dname: %s\n", + qdict_haskey(client, "x509_dname") ? + qdict_get_str(client, "x509_dname") : "none"); +#endif +#ifdef CONFIG_VNC_SASL + monitor_printf(mon, " username: %s\n", + qdict_haskey(client, "sasl_username") ? + qdict_get_str(client, "sasl_username") : "none"); +#endif +} + +void do_info_vnc_print(Monitor *mon, const QObject *data) +{ + QDict *server; + QList *clients; + + server = qobject_to_qdict(data); + if (qdict_get_bool(server, "enabled") == 0) { + monitor_printf(mon, "Server: disabled\n"); + return; + } + + monitor_printf(mon, "Server:\n"); + monitor_printf(mon, " address: %s:%s\n", + qdict_get_str(server, "host"), + qdict_get_str(server, "service")); + monitor_printf(mon, " auth: %s\n", qdict_get_str(server, "auth")); + + clients = qdict_get_qlist(server, "clients"); + if (qlist_empty(clients)) { + monitor_printf(mon, "Client: none\n"); + } else { + qlist_iter(clients, info_vnc_iter, mon); + } +} + +void do_info_vnc(Monitor *mon, QObject **ret_data) +{ + if (vnc_display == NULL || vnc_display->display == NULL) { + *ret_data = qobject_from_jsonf("{ 'enabled': false }"); + } else { + QList *clist; + VncState *client; + + clist = qlist_new(); + QTAILQ_FOREACH(client, &vnc_display->clients, next) { + if (client->info) { + /* incref so that it's not freed by upper layers */ + qobject_incref(client->info); + qlist_append_obj(clist, client->info); + } + } + + *ret_data = qobject_from_jsonf("{ 'enabled': true, 'clients': %p }", + QOBJECT(clist)); + assert(*ret_data != NULL); + + if (vnc_server_info_put(qobject_to_qdict(*ret_data)) < 0) { + qobject_decref(*ret_data); + *ret_data = NULL; + } + } +} + +static inline uint32_t vnc_has_feature(VncState *vs, int feature) { + return (vs->features & (1 << feature)); +} + +/* TODO + 1) Get the queue working for IO. + 2) there is some weirdness when using the -S option (the screen is grey + and not totally invalidated + 3) resolutions > 1024 +*/ + +static int vnc_update_client(VncState *vs, int has_dirty); +static void vnc_disconnect_start(VncState *vs); +static void vnc_disconnect_finish(VncState *vs); +static void vnc_init_timer(VncDisplay *vd); +static void vnc_remove_timer(VncDisplay *vd); + +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(void *opaque); +static int vnc_refresh_server_surface(VncDisplay *vd); + +static inline void vnc_set_bit(uint32_t *d, int k) +{ + d[k >> 5] |= 1 << (k & 0x1f); +} + +static inline void vnc_clear_bit(uint32_t *d, int k) +{ + d[k >> 5] &= ~(1 << (k & 0x1f)); +} + +static inline void vnc_set_bits(uint32_t *d, int n, int nb_words) +{ + int j; + + j = 0; + while (n >= 32) { + d[j++] = -1; + n -= 32; + } + if (n > 0) + d[j++] = (1 << n) - 1; + while (j < nb_words) + d[j++] = 0; +} + +static inline int vnc_get_bit(const uint32_t *d, int k) +{ + return (d[k >> 5] >> (k & 0x1f)) & 1; +} + +static inline int vnc_and_bits(const uint32_t *d1, const uint32_t *d2, + int nb_words) +{ + int i; + for(i = 0; i < nb_words; i++) { + if ((d1[i] & d2[i]) != 0) + return 1; + } + return 0; +} + +static void vnc_dpy_update(DisplayState *ds, int x, int y, int w, int h) +{ + int i; + VncDisplay *vd = ds->opaque; + struct VncSurface *s = &vd->guest; + + h += y; + + /* round x down to ensure the loop only spans one 16-pixel block per, + iteration. otherwise, if (x % 16) != 0, the last iteration may span + two 16-pixel blocks but we only mark the first as dirty + */ + w += (x % 16); + x -= (x % 16); + + x = MIN(x, s->ds->width); + y = MIN(y, s->ds->height); + w = MIN(x + w, s->ds->width) - x; + h = MIN(h, s->ds->height); + + for (; y < h; y++) + for (i = 0; i < w; i += 16) + vnc_set_bit(s->dirty[y], (x + i) / 16); +} + +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 = qemu_realloc(buffer->buffer, buffer->capacity); + if (buffer->buffer == NULL) { + fprintf(stderr, "vnc: out of memory\n"); + exit(1); + } + } +} + +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) +{ + qemu_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; +} + +static void vnc_desktop_resize(VncState *vs) +{ + DisplayState *ds = vs->ds; + + if (vs->csock == -1 || !vnc_has_feature(vs, VNC_FEATURE_RESIZE)) { + return; + } + if (vs->client_width == ds_get_width(ds) && + vs->client_height == ds_get_height(ds)) { + return; + } + vs->client_width = ds_get_width(ds); + vs->client_height = ds_get_height(ds); + 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_flush(vs); +} + +static void vnc_dpy_resize(DisplayState *ds) +{ + VncDisplay *vd = ds->opaque; + VncState *vs; + + /* server surface */ + if (!vd->server) + vd->server = qemu_mallocz(sizeof(*vd->server)); + if (vd->server->data) + qemu_free(vd->server->data); + *(vd->server) = *(ds->surface); + vd->server->data = qemu_mallocz(vd->server->linesize * + vd->server->height); + + /* guest surface */ + if (!vd->guest.ds) + vd->guest.ds = qemu_mallocz(sizeof(*vd->guest.ds)); + if (ds_get_bytes_per_pixel(ds) != vd->guest.ds->pf.bytes_per_pixel) + console_color_init(ds); + *(vd->guest.ds) = *(ds->surface); + memset(vd->guest.dirty, 0xFF, sizeof(vd->guest.dirty)); + + QTAILQ_FOREACH(vs, &vd->clients, next) { + vnc_colordepth(vs); + vnc_desktop_resize(vs); + if (vs->vd->cursor) { + vnc_cursor_define(vs); + } + memset(vs->dirty, 0xFF, sizeof(vs->dirty)); + } +} + +/* fastest code */ +static void vnc_write_pixels_copy(VncState *vs, struct PixelFormat *pf, + 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; + VncDisplay *vd = vs->vd; + + r = ((((v & vd->server->pf.rmask) >> vd->server->pf.rshift) << vs->clientds.pf.rbits) >> + vd->server->pf.rbits); + g = ((((v & vd->server->pf.gmask) >> vd->server->pf.gshift) << vs->clientds.pf.gbits) >> + vd->server->pf.gbits); + b = ((((v & vd->server->pf.bmask) >> vd->server->pf.bshift) << vs->clientds.pf.bbits) >> + vd->server->pf.bbits); + v = (r << vs->clientds.pf.rshift) | + (g << vs->clientds.pf.gshift) | + (b << vs->clientds.pf.bshift); + switch(vs->clientds.pf.bytes_per_pixel) { + case 1: + buf[0] = v; + break; + case 2: + if (vs->clientds.flags & QEMU_BIG_ENDIAN_FLAG) { + buf[0] = v >> 8; + buf[1] = v; + } else { + buf[1] = v >> 8; + buf[0] = v; + } + break; + default: + case 4: + if (vs->clientds.flags & QEMU_BIG_ENDIAN_FLAG) { + 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, struct PixelFormat *pf, + void *pixels1, int size) +{ + uint8_t buf[4]; + + if (pf->bytes_per_pixel == 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->clientds.pf.bytes_per_pixel); + } + } else if (pf->bytes_per_pixel == 2) { + uint16_t *pixels = pixels1; + int n, i; + n = size >> 1; + for(i = 0; i < n; i++) { + vnc_convert_pixel(vs, buf, pixels[i]); + vnc_write(vs, buf, vs->clientds.pf.bytes_per_pixel); + } + } else if (pf->bytes_per_pixel == 1) { + uint8_t *pixels = pixels1; + int n, i; + n = size; + for(i = 0; i < n; i++) { + vnc_convert_pixel(vs, buf, pixels[i]); + vnc_write(vs, buf, vs->clientds.pf.bytes_per_pixel); + } + } else { + fprintf(stderr, "vnc_write_pixels_generic: VncState color depth not supported\n"); + } +} + +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 = vd->server->data + y * ds_get_linesize(vs->ds) + x * ds_get_bytes_per_pixel(vs->ds); + for (i = 0; i < h; i++) { + vs->write_pixels(vs, &vd->server->pf, row, w * ds_get_bytes_per_pixel(vs->ds)); + row += ds_get_linesize(vs->ds); + } + return 1; +} + +static int 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; + 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_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_flush(vs); +} + +static void vnc_dpy_copy(DisplayState *ds, int src_x, int src_y, int dst_x, int dst_y, int w, int h) +{ + VncDisplay *vd = ds->opaque; + VncState *vs, *vn; + uint8_t *src_row; + uint8_t *dst_row; + int i,x,y,pitch,depth,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); + /* vs might be free()ed here */ + } + } + + /* do bitblit op on the local surface too */ + pitch = ds_get_linesize(vd->ds); + depth = ds_get_bytes_per_pixel(vd->ds); + src_row = vd->server->data + pitch * src_y + depth * src_x; + dst_row = vd->server->data + pitch * dst_y + depth * dst_x; + y = dst_y; + inc = 1; + if (dst_y > src_y) { + /* copy backwards */ + src_row += pitch * (h-1); + dst_row += pitch * (h-1); + pitch = -pitch; + y = dst_y + h - 1; + inc = -1; + } + w_lim = w - (16 - (dst_x % 16)); + if (w_lim < 0) + w_lim = w; + else + w_lim = w - (w_lim % 16); + for (i = 0; i < h; i++) { + for (x = 0; x <= w_lim; + x += s, src_row += cmp_bytes, dst_row += cmp_bytes) { + if (x == w_lim) { + if ((s = w - w_lim) == 0) + break; + } else if (!x) { + s = (16 - (dst_x % 16)); + s = MIN(s, w_lim); + } else { + s = 16; + } + cmp_bytes = s * depth; + 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)) { + vnc_set_bit(vs->dirty[y], ((x + dst_x) / 16)); + } + } + } + src_row += pitch - w * depth; + dst_row += pitch - w * depth; + 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(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; + PixelFormat pf = qemu_default_pixelformat(32); + int isize; + + if (vnc_has_feature(vs, VNC_FEATURE_RICH_CURSOR)) { + 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->clientds.pf.bytes_per_pixel; + vnc_write_pixels_generic(vs, &pf, c->data, isize); + vnc_write(vs, vs->vd->cursor_mask, vs->vd->cursor_msize); + return 0; + } + return -1; +} + +static void vnc_dpy_cursor_define(QEMUCursor *c) +{ + VncDisplay *vd = vnc_display; + VncState *vs; + + cursor_put(vd->cursor); + qemu_free(vd->cursor_mask); + + vd->cursor = c; + cursor_get(vd->cursor); + vd->cursor_msize = cursor_get_mono_bpl(c) * c->height; + vd->cursor_mask = qemu_mallocz(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(struct VncState *vs, + int y, int last_x, int x) +{ + int h; + VncDisplay *vd = vs->vd; + + for (h = 1; h < (vd->server->height - y); h++) { + int tmp_x; + if (!vnc_get_bit(vs->dirty[y + h], last_x)) + break; + for (tmp_x = last_x; tmp_x < x; tmp_x++) + vnc_clear_bit(vs->dirty[y + h], tmp_x); + } + + return h; +} + +static int vnc_update_client(VncState *vs, int has_dirty) +{ + if (vs->need_update && vs->csock != -1) { + VncDisplay *vd = vs->vd; + int y; + int n_rectangles; + int saved_offset; + int width, height; + int n; + + if (vs->output.offset && !vs->audio_cap && !vs->force_update) + /* kernel send buffers are full -> drop frames to throttle */ + return 0; + + if (!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. + */ + 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); + + width = MIN(vd->server->width, vs->client_width); + height = MIN(vd->server->height, vs->client_height); + + for (y = 0; y < height; y++) { + int x; + int last_x = -1; + for (x = 0; x < width / 16; x++) { + if (vnc_get_bit(vs->dirty[y], x)) { + if (last_x == -1) { + last_x = x; + } + vnc_clear_bit(vs->dirty[y], x); + } else { + if (last_x != -1) { + int h = find_and_clear_dirty_height(vs, y, last_x, x); + n = send_framebuffer_update(vs, last_x * 16, y, + (x - last_x) * 16, h); + n_rectangles += n; + } + last_x = -1; + } + } + if (last_x != -1) { + int h = find_and_clear_dirty_height(vs, y, last_x, x); + n = send_framebuffer_update(vs, last_x * 16, y, + (x - last_x) * 16, h); + n_rectangles += n; + } + } + vs->output.buffer[saved_offset] = (n_rectangles >> 8) & 0xFF; + vs->output.buffer[saved_offset + 1] = n_rectangles & 0xFF; + vnc_flush(vs); + vs->force_update = 0; + return n_rectangles; + } + + if (vs->csock == -1) + vnc_disconnect_finish(vs); + + return 0; +} + +/* audio */ +static void audio_capture_notify(void *opaque, audcnotification_e cmd) +{ + VncState *vs = opaque; + + switch (cmd) { + case AUD_CNOTIFY_DISABLE: + 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_flush(vs); + break; + + case AUD_CNOTIFY_ENABLE: + 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_flush(vs); + break; + } +} + +static void audio_capture_destroy(void *opaque) +{ +} + +static void audio_capture(void *opaque, void *buf, int size) +{ + VncState *vs = opaque; + + 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_flush(vs); +} + +static void audio_add(VncState *vs) +{ + struct audio_capture_ops ops; + + if (vs->audio_cap) { + monitor_printf(default_mon, "audio already running\n"); + 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) { + monitor_printf(default_mon, "Failed to add audio capture\n"); + } +} + +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; + qemu_set_fd_handler2(vs->csock, NULL, NULL, NULL, NULL); + closesocket(vs->csock); + vs->csock = -1; +} + +static void vnc_disconnect_finish(VncState *vs) +{ + vnc_qmp_event(vs, QEVENT_VNC_DISCONNECTED); + + buffer_free(&vs->input); + buffer_free(&vs->output); + + qobject_decref(vs->info); + + vnc_zlib_clear(vs); + vnc_tight_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); + + QTAILQ_REMOVE(&vs->vd->clients, vs, next); + + if (QTAILQ_EMPTY(&vs->vd->clients)) { + dcl->idle = 1; + } + + qemu_remove_mouse_mode_change_notifier(&vs->mouse_mode_notifier); + vnc_remove_timer(vs->vd); + if (vs->vd->lock_key_sync) + qemu_remove_led_event_handler(vs->led); + qemu_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); +} + + +/* + * 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 = gnutls_write(vs->tls.session, data, datalen); + if (ret < 0) { + if (ret == GNUTLS_E_AGAIN) + errno = EAGAIN; + else + errno = EIO; + ret = -1; + } + } else +#endif /* CONFIG_VNC_TLS */ + ret = send(vs->csock, (const void *)data, datalen, 0); + 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; + + memmove(vs->output.buffer, vs->output.buffer + ret, (vs->output.offset - ret)); + vs->output.offset -= ret; + + if (vs->output.offset == 0) { + qemu_set_fd_handler2(vs->csock, NULL, 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) + */ +void vnc_client_write(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 */ + vnc_client_write_plain(vs); +} + +void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting) +{ + vs->read_handler = func; + vs->read_handler_expect = expecting; +} + + +/* + * Called to read a chunk of data from the client socket. The data may + * be the raw data, or may need to be further decoded by SASL. + * The data will be read either straight from to the socket, or + * read via the GNUTLS wrappers, if TLS/SSL encryption is enabled + * + * NB, it is theoretically possible to have 2 layers of encryption, + * both SASL, and this TLS layer. It is highly unlikely in practice + * though, since SASL encryption will typically be a no-op if TLS + * is active + * + * Returns the number of bytes read, which may be less than + * the requested 'datalen' if the socket would block. Returns + * -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 = gnutls_read(vs->tls.session, data, datalen); + if (ret < 0) { + if (ret == GNUTLS_E_AGAIN) + errno = EAGAIN; + else + errno = EIO; + ret = -1; + } + } else +#endif /* CONFIG_VNC_TLS */ + ret = recv(vs->csock, (void *)data, datalen, 0); + 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; +} + + +/* + * 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 */ + 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) { + memmove(vs->input.buffer, vs->input.buffer + len, (vs->input.offset - len)); + vs->input.offset -= 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_handler2(vs->csock, NULL, 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) +{ + if (vs->csock != -1 && vs->output.offset) + vnc_client_write(vs); +} + +uint8_t read_u8(uint8_t *data, size_t offset) +{ + return data[offset]; +} + +uint16_t read_u16(uint8_t *data, size_t offset) +{ + return ((data[offset] & 0xFF) << 8) | (data[offset + 1] & 0xFF); +} + +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) +{ + VncState *vs = container_of(notifier, VncState, mouse_mode_notifier); + int absolute = kbd_mouse_is_absolute(); + + if (vnc_has_feature(vs, VNC_FEATURE_POINTER_TYPE_CHANGE) && vs->absolute != absolute) { + 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, + ds_get_width(vs->ds), ds_get_height(vs->ds), + VNC_ENCODING_POINTER_TYPE_CHANGE); + vnc_flush(vs); + } + vs->absolute = absolute; +} + +static void pointer_event(VncState *vs, int button_mask, int x, int y) +{ + int buttons = 0; + int dz = 0; + + if (button_mask & 0x01) + buttons |= MOUSE_EVENT_LBUTTON; + if (button_mask & 0x02) + buttons |= MOUSE_EVENT_MBUTTON; + if (button_mask & 0x04) + buttons |= MOUSE_EVENT_RBUTTON; + if (button_mask & 0x08) + dz = -1; + if (button_mask & 0x10) + dz = 1; + + if (vs->absolute) { + kbd_mouse_event(ds_get_width(vs->ds) > 1 ? + x * 0x7FFF / (ds_get_width(vs->ds) - 1) : 0x4000, + ds_get_height(vs->ds) > 1 ? + y * 0x7FFF / (ds_get_height(vs->ds) - 1) : 0x4000, + dz, buttons); + } else if (vnc_has_feature(vs, VNC_FEATURE_POINTER_TYPE_CHANGE)) { + x -= 0x7FFF; + y -= 0x7FFF; + + kbd_mouse_event(x, y, dz, buttons); + } else { + if (vs->last_x != -1) + kbd_mouse_event(x - vs->last_x, + y - vs->last_y, + dz, buttons); + vs->last_x = x; + vs->last_y = y; + } +} + +static void reset_keys(VncState *vs) +{ + int i; + for(i = 0; i < 256; i++) { + if (vs->modifiers_state[i]) { + if (i & SCANCODE_GREY) + kbd_put_keycode(SCANCODE_EMUL0); + kbd_put_keycode(i | SCANCODE_UP); + vs->modifiers_state[i] = 0; + } + } +} + +static void press_key(VncState *vs, int keysym) +{ + int keycode = keysym2scancode(vs->vd->kbd_layout, keysym) & SCANCODE_KEYMASK; + if (keycode & SCANCODE_GREY) + kbd_put_keycode(SCANCODE_EMUL0); + kbd_put_keycode(keycode & SCANCODE_KEYCODEMASK); + if (keycode & SCANCODE_GREY) + kbd_put_keycode(SCANCODE_EMUL0); + kbd_put_keycode(keycode | SCANCODE_UP); +} + +static void kbd_leds(void *opaque, int ledstate) +{ + VncState *vs = opaque; + int caps, num; + + caps = ledstate & QEMU_CAPS_LOCK_LED ? 1 : 0; + num = ledstate & QEMU_NUM_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; + } +} + +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 (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; + } + + if (vs->vd->lock_key_sync && + 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]) { + vs->modifiers_state[0x45] = 1; + press_key(vs, 0xff7f); + } + } else { + if (vs->modifiers_state[0x45]) { + vs->modifiers_state[0x45] = 0; + press_key(vs, 0xff7f); + } + } + } + + if (vs->vd->lock_key_sync && + ((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) { + vs->modifiers_state[0x3a] = 0; + press_key(vs, 0xffe5); + } + } else { + if (uppercase != shift) { + vs->modifiers_state[0x3a] = 1; + press_key(vs, 0xffe5); + } + } + } + + if (is_graphic_console()) { + if (keycode & SCANCODE_GREY) + kbd_put_keycode(SCANCODE_EMUL0); + if (down) + kbd_put_keycode(keycode & SCANCODE_KEYCODEMASK); + else + kbd_put_keycode(keycode | SCANCODE_UP); + } else { + /* QEMU console emulation */ + if (down) { + int numlock = vs->modifiers_state[0x45]; + 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: + kbd_put_keysym(sym); + break; + } + } + } +} + +static void key_event(VncState *vs, int down, uint32_t sym) +{ + int keycode; + int lsym = sym; + + if (lsym >= 'A' && lsym <= 'Z' && is_graphic_console()) { + lsym = lsym - 'A' + 'a'; + } + + keycode = keysym2scancode(vs->vd->kbd_layout, lsym & 0xFFFF) & SCANCODE_KEYMASK; + 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 + do_key_event(vs, down, keycode, sym); +} + +static void framebuffer_update_request(VncState *vs, int incremental, + int x_position, int y_position, + int w, int h) +{ + if (y_position > ds_get_height(vs->ds)) + y_position = ds_get_height(vs->ds); + if (y_position + h >= ds_get_height(vs->ds)) + h = ds_get_height(vs->ds) - y_position; + + int i; + vs->need_update = 1; + if (!incremental) { + vs->force_update = 1; + for (i = 0; i < h; i++) { + vnc_set_bits(vs->dirty[y_position + i], + (ds_get_width(vs->ds) / 16), VNC_DIRTY_WORDS); + } + } +} + +static void send_ext_key_event_ack(VncState *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, ds_get_width(vs->ds), ds_get_height(vs->ds), + VNC_ENCODING_EXT_KEY_EVENT); + vnc_flush(vs); +} + +static void send_ext_audio_ack(VncState *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, ds_get_width(vs->ds), ds_get_height(vs->ds), + VNC_ENCODING_AUDIO); + 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 prefered 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; + case VNC_ENCODING_ZLIB: + vs->features |= VNC_FEATURE_ZLIB_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_COMPRESSLEVEL0 ... VNC_ENCODING_COMPRESSLEVEL0 + 9: + vs->tight_compression = (enc & 0x0F); + break; + case VNC_ENCODING_QUALITYLEVEL0 ... VNC_ENCODING_QUALITYLEVEL0 + 9: + 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); +} + +static void set_pixel_conversion(VncState *vs) +{ + if ((vs->clientds.flags & QEMU_BIG_ENDIAN_FLAG) == + (vs->ds->surface->flags & QEMU_BIG_ENDIAN_FLAG) && + !memcmp(&(vs->clientds.pf), &(vs->ds->surface->pf), sizeof(PixelFormat))) { + 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; + } + + vs->clientds = *(vs->vd->guest.ds); + vs->clientds.pf.rmax = red_max; + count_bits(vs->clientds.pf.rbits, red_max); + vs->clientds.pf.rshift = red_shift; + vs->clientds.pf.rmask = red_max << red_shift; + vs->clientds.pf.gmax = green_max; + count_bits(vs->clientds.pf.gbits, green_max); + vs->clientds.pf.gshift = green_shift; + vs->clientds.pf.gmask = green_max << green_shift; + vs->clientds.pf.bmax = blue_max; + count_bits(vs->clientds.pf.bbits, blue_max); + vs->clientds.pf.bshift = blue_shift; + vs->clientds.pf.bmask = blue_max << blue_shift; + vs->clientds.pf.bits_per_pixel = bits_per_pixel; + vs->clientds.pf.bytes_per_pixel = bits_per_pixel / 8; + vs->clientds.pf.depth = bits_per_pixel == 32 ? 24 : bits_per_pixel; + vs->clientds.flags = big_endian_flag ? QEMU_BIG_ENDIAN_FLAG : 0x00; + + set_pixel_conversion(vs); + + vga_hw_invalidate(); + vga_hw_update(); +} + +static void pixel_format_message (VncState *vs) { + char pad[3] = { 0, 0, 0 }; + + vnc_write_u8(vs, vs->ds->surface->pf.bits_per_pixel); /* bits-per-pixel */ + vnc_write_u8(vs, vs->ds->surface->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->ds->surface->pf.rmax); /* red-max */ + vnc_write_u16(vs, vs->ds->surface->pf.gmax); /* green-max */ + vnc_write_u16(vs, vs->ds->surface->pf.bmax); /* blue-max */ + vnc_write_u8(vs, vs->ds->surface->pf.rshift); /* red-shift */ + vnc_write_u8(vs, vs->ds->surface->pf.gshift); /* green-shift */ + vnc_write_u8(vs, vs->ds->surface->pf.bshift); /* blue-shift */ + + vnc_hextile_set_pixel_conversion(vs, 0); + + vs->clientds = *(vs->ds->surface); + vs->clientds.flags &= ~QEMU_ALLOCATED_FLAG; + vs->write_pixels = vnc_write_pixels_copy; + + vnc_write(vs, pad, 3); /* padding */ +} + +static void vnc_dpy_setdata(DisplayState *ds) +{ + /* We don't have to do anything */ +} + +static void vnc_colordepth(VncState *vs) +{ + if (vnc_has_feature(vs, VNC_FEATURE_WMVI)) { + /* Sending a WMVi message to notify the client*/ + 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, ds_get_width(vs->ds), + ds_get_height(vs->ds), VNC_ENCODING_WMVi); + pixel_format_message(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) { + vd->timer_interval = VNC_REFRESH_INTERVAL_BASE; + if (!qemu_timer_expired(vd->timer, qemu_get_clock(rt_clock) + vd->timer_interval)) + qemu_mod_timer(vd->timer, qemu_get_clock(rt_clock) + vd->timer_interval); + } + + 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 > 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: + printf("Invalid audio format %d\n", read_u8(data, 4)); + vnc_client_error(vs); + break; + } + vs->as.nchannels = read_u8(data, 5); + if (vs->as.nchannels != 1 && vs->as.nchannels != 2) { + printf("Invalid audio channel coount %d\n", + read_u8(data, 5)); + vnc_client_error(vs); + break; + } + vs->as.freq = read_u32(data, 6); + break; + default: + printf ("Invalid audio message %d\n", read_u8(data, 4)); + vnc_client_error(vs); + break; + } + break; + + default: + printf("Msg: %d\n", read_u16(data, 0)); + vnc_client_error(vs); + break; + } + break; + default: + printf("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]; + int size; + + vs->client_width = ds_get_width(vs->ds); + vs->client_height = ds_get_height(vs->ds); + 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, QEVENT_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]; + int i, j, pwlen; + unsigned char key[8]; + + if (!vs->vd->password || !vs->vd->password[0]) { + VNC_DEBUG("No password configured on server"); + 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); + return 0; + } + + 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; + deskey(key, EN0); + for (j = 0; j < VNC_AUTH_CHALLENGE_SIZE; j += 8) + des(response+j, response+j); + + /* Compare expected vs actual challenge response */ + if (memcmp(response, data, VNC_AUTH_CHALLENGE_SIZE) != 0) { + VNC_DEBUG("Client challenge reponse did not match\n"); + 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); + } else { + VNC_DEBUG("Accepting VNC challenge response\n"); + vnc_write_u32(vs, 0); /* Accept auth */ + vnc_flush(vs); + + start_client_init(vs); + } + 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->vd->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->vd->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->vd->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->vd->auth == VNC_AUTH_NONE) { + VNC_DEBUG("Tell client auth none\n"); + vnc_write_u32(vs, vs->vd->auth); + vnc_flush(vs); + start_client_init(vs); + } else if (vs->vd->auth == VNC_AUTH_VNC) { + VNC_DEBUG("Tell client VNC auth\n"); + vnc_write_u32(vs, vs->vd->auth); + vnc_flush(vs); + start_auth_vnc(vs); + } else { + VNC_DEBUG("Unsupported auth %d for protocol 3.3\n", vs->vd->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->vd->auth); + vnc_write_u8(vs, 1); /* num auth */ + vnc_write_u8(vs, vs->vd->auth); + vnc_read_when(vs, protocol_client_auth, 1); + vnc_flush(vs); + } + + return 0; +} + +static int vnc_refresh_server_surface(VncDisplay *vd) +{ + int y; + uint8_t *guest_row; + uint8_t *server_row; + int cmp_bytes; + uint32_t width_mask[VNC_DIRTY_WORDS]; + VncState *vs; + int has_dirty = 0; + + /* + * Walk through the guest dirty map. + * Check and copy modified bits from guest to server surface. + * Update server dirty map. + */ + vnc_set_bits(width_mask, (ds_get_width(vd->ds) / 16), VNC_DIRTY_WORDS); + cmp_bytes = 16 * ds_get_bytes_per_pixel(vd->ds); + guest_row = vd->guest.ds->data; + server_row = vd->server->data; + for (y = 0; y < vd->guest.ds->height; y++) { + if (vnc_and_bits(vd->guest.dirty[y], width_mask, VNC_DIRTY_WORDS)) { + int x; + uint8_t *guest_ptr; + uint8_t *server_ptr; + + guest_ptr = guest_row; + server_ptr = server_row; + + for (x = 0; x < vd->guest.ds->width; + x += 16, guest_ptr += cmp_bytes, server_ptr += cmp_bytes) { + if (!vnc_get_bit(vd->guest.dirty[y], (x / 16))) + continue; + vnc_clear_bit(vd->guest.dirty[y], (x / 16)); + if (memcmp(server_ptr, guest_ptr, cmp_bytes) == 0) + continue; + memcpy(server_ptr, guest_ptr, cmp_bytes); + QTAILQ_FOREACH(vs, &vd->clients, next) { + vnc_set_bit(vs->dirty[y], (x / 16)); + } + has_dirty++; + } + } + guest_row += ds_get_linesize(vd->ds); + server_row += ds_get_linesize(vd->ds); + } + return has_dirty; +} + +static void vnc_refresh(void *opaque) +{ + VncDisplay *vd = opaque; + VncState *vs, *vn; + int has_dirty, rects = 0; + + vga_hw_update(); + + has_dirty = vnc_refresh_server_surface(vd); + + QTAILQ_FOREACH_SAFE(vs, &vd->clients, next, vn) { + rects += vnc_update_client(vs, has_dirty); + /* vs might be free()ed here */ + } + /* vd->timer could be NULL now if the last client disconnected, + * in this case don't update the timer */ + if (vd->timer == NULL) + return; + + if (has_dirty && rects) { + vd->timer_interval /= 2; + if (vd->timer_interval < VNC_REFRESH_INTERVAL_BASE) + vd->timer_interval = VNC_REFRESH_INTERVAL_BASE; + } else { + vd->timer_interval += VNC_REFRESH_INTERVAL_INC; + if (vd->timer_interval > VNC_REFRESH_INTERVAL_MAX) + vd->timer_interval = VNC_REFRESH_INTERVAL_MAX; + } + qemu_mod_timer(vd->timer, qemu_get_clock(rt_clock) + vd->timer_interval); +} + +static void vnc_init_timer(VncDisplay *vd) +{ + vd->timer_interval = VNC_REFRESH_INTERVAL_BASE; + if (vd->timer == NULL && !QTAILQ_EMPTY(&vd->clients)) { + vd->timer = qemu_new_timer(rt_clock, vnc_refresh, vd); + vnc_refresh(vd); + } +} + +static void vnc_remove_timer(VncDisplay *vd) +{ + if (vd->timer != NULL && QTAILQ_EMPTY(&vd->clients)) { + qemu_del_timer(vd->timer); + qemu_free_timer(vd->timer); + vd->timer = NULL; + } +} + +static void vnc_connect(VncDisplay *vd, int csock) +{ + VncState *vs = qemu_mallocz(sizeof(VncState)); + vs->csock = csock; + + VNC_DEBUG("New client on socket %d\n", csock); + dcl->idle = 0; + socket_set_nonblock(vs->csock); + qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs); + + vnc_client_cache_addr(vs); + vnc_qmp_event(vs, QEVENT_VNC_CONNECTED); + + vs->vd = vd; + vs->ds = vd->ds; + 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; + + QTAILQ_INSERT_HEAD(&vd->clients, vs, next); + + vga_hw_update(); + + 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); + + vnc_init_timer(vd); + + /* vs might be free()ed here */ +} + +static void vnc_listen_read(void *opaque) +{ + VncDisplay *vs = opaque; + struct sockaddr_in addr; + socklen_t addrlen = sizeof(addr); + + /* Catch-up */ + vga_hw_update(); + + int csock = qemu_accept(vs->lsock, (struct sockaddr *)&addr, &addrlen); + if (csock != -1) { + vnc_connect(vs, csock); + } +} + +void vnc_display_init(DisplayState *ds) +{ + VncDisplay *vs = qemu_mallocz(sizeof(*vs)); + + dcl = qemu_mallocz(sizeof(DisplayChangeListener)); + + ds->opaque = vs; + dcl->idle = 1; + vnc_display = vs; + + vs->lsock = -1; + + vs->ds = ds; + QTAILQ_INIT(&vs->clients); + + if (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); + + dcl->dpy_copy = vnc_dpy_copy; + dcl->dpy_update = vnc_dpy_update; + dcl->dpy_resize = vnc_dpy_resize; + dcl->dpy_setdata = vnc_dpy_setdata; + register_displaychangelistener(ds, dcl); + ds->mouse_set = vnc_mouse_set; + ds->cursor_define = vnc_dpy_cursor_define; +} + + +void vnc_display_close(DisplayState *ds) +{ + VncDisplay *vs = ds ? (VncDisplay *)ds->opaque : vnc_display; + + if (!vs) + return; + if (vs->display) { + qemu_free(vs->display); + vs->display = NULL; + } + if (vs->lsock != -1) { + qemu_set_fd_handler2(vs->lsock, NULL, NULL, NULL, NULL); + close(vs->lsock); + vs->lsock = -1; + } + vs->auth = VNC_AUTH_INVALID; +#ifdef CONFIG_VNC_TLS + vs->subauth = VNC_AUTH_INVALID; + vs->tls.x509verify = 0; +#endif +} + +int vnc_display_password(DisplayState *ds, const char *password) +{ + VncDisplay *vs = ds ? (VncDisplay *)ds->opaque : vnc_display; + + if (!vs) { + return -1; + } + + if (vs->password) { + qemu_free(vs->password); + vs->password = NULL; + } + if (password && password[0]) { + if (!(vs->password = qemu_strdup(password))) + return -1; + if (vs->auth == VNC_AUTH_NONE) { + vs->auth = VNC_AUTH_VNC; + } + } else { + vs->auth = VNC_AUTH_NONE; + } + + return 0; +} + +char *vnc_display_local_addr(DisplayState *ds) +{ + VncDisplay *vs = ds ? (VncDisplay *)ds->opaque : vnc_display; + + return vnc_socket_local_addr("%s:%s", vs->lsock); +} + +int vnc_display_open(DisplayState *ds, const char *display) +{ + VncDisplay *vs = ds ? (VncDisplay *)ds->opaque : vnc_display; + const char *options; + int password = 0; + int reverse = 0; +#ifdef CONFIG_VNC_TLS + int tls = 0, x509 = 0; +#endif +#ifdef CONFIG_VNC_SASL + int sasl = 0; + int saslErr; +#endif + int acl = 0; + int lock_key_sync = 1; + + if (!vnc_display) + return -1; + vnc_display_close(ds); + if (strcmp(display, "none") == 0) + return 0; + + if (!(vs->display = strdup(display))) + return -1; + + options = display; + while ((options = strchr(options, ','))) { + options++; + if (strncmp(options, "password", 8) == 0) { + password = 1; /* Require password auth */ + } else if (strncmp(options, "reverse", 7) == 0) { + reverse = 1; + } else if (strncmp(options, "no-lock-key-sync", 9) == 0) { + lock_key_sync = 0; +#ifdef CONFIG_VNC_SASL + } else if (strncmp(options, "sasl", 4) == 0) { + sasl = 1; /* Require SASL auth */ +#endif +#ifdef CONFIG_VNC_TLS + } else if (strncmp(options, "tls", 3) == 0) { + tls = 1; /* Require TLS */ + } else if (strncmp(options, "x509", 4) == 0) { + char *start, *end; + x509 = 1; /* Require x509 certificates */ + if (strncmp(options, "x509verify", 10) == 0) + vs->tls.x509verify = 1; /* ...and verify client certs */ + + /* Now check for 'x509=/some/path' postfix + * and use that to setup x509 certificate/key paths */ + start = strchr(options, '='); + end = strchr(options, ','); + if (start && (!end || (start < end))) { + int len = end ? end-(start+1) : strlen(start+1); + char *path = qemu_strndup(start + 1, len); + + VNC_DEBUG("Trying certificate path '%s'\n", path); + if (vnc_tls_set_x509_creds_dir(vs, path) < 0) { + fprintf(stderr, "Failed to find x509 certificates/keys in %s\n", path); + qemu_free(path); + qemu_free(vs->display); + vs->display = NULL; + return -1; + } + qemu_free(path); + } else { + fprintf(stderr, "No certificate path provided\n"); + qemu_free(vs->display); + vs->display = NULL; + return -1; + } +#endif + } else if (strncmp(options, "acl", 3) == 0) { + acl = 1; + } else if (strncmp(options, "lossy", 5) == 0) { + vs->lossy = true; + } + } + +#ifdef CONFIG_VNC_TLS + if (acl && x509 && vs->tls.x509verify) { + if (!(vs->tls.acl = qemu_acl_init("vnc.x509dname"))) { + fprintf(stderr, "Failed to create x509 dname ACL\n"); + exit(1); + } + } +#endif +#ifdef CONFIG_VNC_SASL + if (acl && sasl) { + if (!(vs->sasl.acl = qemu_acl_init("vnc.username"))) { + fprintf(stderr, "Failed to create username ACL\n"); + exit(1); + } + } +#endif + + /* + * Combinations we support here: + * + * - no-auth (clear text, no auth) + * - password (clear text, weak auth) + * - sasl (encrypt, good auth *IF* using Kerberos via GSSAPI) + * - tls (encrypt, weak anonymous creds, no auth) + * - tls + password (encrypt, weak anonymous creds, weak auth) + * - tls + sasl (encrypt, weak anonymous creds, good auth) + * - tls + x509 (encrypt, good x509 creds, no auth) + * - tls + x509 + password (encrypt, good x509 creds, weak auth) + * - tls + x509 + sasl (encrypt, good x509 creds, good auth) + * + * NB1. TLS is a stackable auth scheme. + * NB2. the x509 schemes have option to validate a client cert dname + */ + if (password) { +#ifdef CONFIG_VNC_TLS + if (tls) { + vs->auth = VNC_AUTH_VENCRYPT; + if (x509) { + VNC_DEBUG("Initializing VNC server with x509 password auth\n"); + vs->subauth = VNC_AUTH_VENCRYPT_X509VNC; + } else { + VNC_DEBUG("Initializing VNC server with TLS password auth\n"); + vs->subauth = VNC_AUTH_VENCRYPT_TLSVNC; + } + } else { +#endif /* CONFIG_VNC_TLS */ + VNC_DEBUG("Initializing VNC server with password auth\n"); + vs->auth = VNC_AUTH_VNC; +#ifdef CONFIG_VNC_TLS + vs->subauth = VNC_AUTH_INVALID; + } +#endif /* CONFIG_VNC_TLS */ +#ifdef CONFIG_VNC_SASL + } else if (sasl) { +#ifdef CONFIG_VNC_TLS + if (tls) { + vs->auth = VNC_AUTH_VENCRYPT; + if (x509) { + VNC_DEBUG("Initializing VNC server with x509 SASL auth\n"); + vs->subauth = VNC_AUTH_VENCRYPT_X509SASL; + } else { + VNC_DEBUG("Initializing VNC server with TLS SASL auth\n"); + vs->subauth = VNC_AUTH_VENCRYPT_TLSSASL; + } + } else { +#endif /* CONFIG_VNC_TLS */ + VNC_DEBUG("Initializing VNC server with SASL auth\n"); + vs->auth = VNC_AUTH_SASL; +#ifdef CONFIG_VNC_TLS + vs->subauth = VNC_AUTH_INVALID; + } +#endif /* CONFIG_VNC_TLS */ +#endif /* CONFIG_VNC_SASL */ + } else { +#ifdef CONFIG_VNC_TLS + if (tls) { + vs->auth = VNC_AUTH_VENCRYPT; + if (x509) { + VNC_DEBUG("Initializing VNC server with x509 no auth\n"); + vs->subauth = VNC_AUTH_VENCRYPT_X509NONE; + } else { + VNC_DEBUG("Initializing VNC server with TLS no auth\n"); + vs->subauth = VNC_AUTH_VENCRYPT_TLSNONE; + } + } else { +#endif + VNC_DEBUG("Initializing VNC server with no auth\n"); + vs->auth = VNC_AUTH_NONE; +#ifdef CONFIG_VNC_TLS + vs->subauth = VNC_AUTH_INVALID; + } +#endif + } + +#ifdef CONFIG_VNC_SASL + if ((saslErr = sasl_server_init(NULL, "qemu")) != SASL_OK) { + fprintf(stderr, "Failed to initialize SASL auth %s", + sasl_errstring(saslErr, NULL, NULL)); + free(vs->display); + vs->display = NULL; + return -1; + } +#endif + vs->lock_key_sync = lock_key_sync; + + if (reverse) { + /* connect to viewer */ + if (strncmp(display, "unix:", 5) == 0) + vs->lsock = unix_connect(display+5); + else + vs->lsock = inet_connect(display, SOCK_STREAM); + if (-1 == vs->lsock) { + free(vs->display); + vs->display = NULL; + return -1; + } else { + int csock = vs->lsock; + vs->lsock = -1; + vnc_connect(vs, csock); + } + return 0; + + } else { + /* listen for connects */ + char *dpy; + dpy = qemu_malloc(256); + if (strncmp(display, "unix:", 5) == 0) { + pstrcpy(dpy, 256, "unix:"); + vs->lsock = unix_listen(display+5, dpy+5, 256-5); + } else { + vs->lsock = inet_listen(display, dpy, 256, SOCK_STREAM, 5900); + } + if (-1 == vs->lsock) { + free(dpy); + return -1; + } else { + free(vs->display); + vs->display = dpy; + } + } + return qemu_set_fd_handler2(vs->lsock, NULL, vnc_listen_read, NULL, vs); +} diff --git a/ui/vnc.h b/ui/vnc.h new file mode 100644 index 0000000000..ec90cd389c --- /dev/null +++ b/ui/vnc.h @@ -0,0 +1,430 @@ +/* + * 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 "console.h" +#include "monitor.h" +#include "audio/audio.h" +#include <zlib.h> +#include <stdbool.h> + +#include "keymaps.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 int VncReadEvent(VncState *vs, uint8_t *data, size_t len); + +typedef void VncWritePixels(VncState *vs, struct PixelFormat *pf, 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); + +#define VNC_MAX_WIDTH 2560 +#define VNC_MAX_HEIGHT 2048 +#define VNC_DIRTY_WORDS (VNC_MAX_WIDTH / (16 * 32)) + +#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 + +struct VncSurface +{ + uint32_t dirty[VNC_MAX_HEIGHT][VNC_DIRTY_WORDS]; + DisplaySurface *ds; +}; + +struct VncDisplay +{ + QTAILQ_HEAD(, VncState) clients; + QEMUTimer *timer; + int timer_interval; + int lsock; + DisplayState *ds; + kbd_layout_t *kbd_layout; + int lock_key_sync; + + QEMUCursor *cursor; + int cursor_msize; + uint8_t *cursor_mask; + + struct VncSurface guest; /* guest visible surface (aka ds->surface) */ + DisplaySurface *server; /* vnc server surface */ + + char *display; + char *password; + int auth; + bool lossy; +#ifdef CONFIG_VNC_TLS + int subauth; /* Used by VeNCrypt */ + VncDisplayTLS tls; +#endif +#ifdef CONFIG_VNC_SASL + VncDisplaySASL sasl; +#endif +}; + +struct VncState +{ + int csock; + + DisplayState *ds; + uint32_t dirty[VNC_MAX_HEIGHT][VNC_DIRTY_WORDS]; + + VncDisplay *vd; + int need_update; + int force_update; + uint32_t features; + int absolute; + int last_x; + int last_y; + int client_width; + int client_height; + + uint32_t vnc_encoding; + + int major; + int minor; + + char challenge[VNC_AUTH_CHALLENGE_SIZE]; +#ifdef CONFIG_VNC_TLS + VncStateTLS tls; +#endif +#ifdef CONFIG_VNC_SASL + VncStateSASL sasl; +#endif + + QObject *info; + + Buffer output; + Buffer input; + /* current output mode information */ + VncWritePixels *write_pixels; + DisplaySurface clientds; + + CaptureVoiceOut *audio_cap; + struct audsettings as; + + VncReadEvent *read_handler; + size_t read_handler_expect; + /* input */ + uint8_t modifiers_state[256]; + QEMUPutLEDEntry *led; + + /* Encoding specific */ + + /* Tight */ + uint8_t tight_quality; + uint8_t tight_compression; + uint8_t tight_pixel24; + Buffer tight; + Buffer tight_tmp; + Buffer tight_zlib; + Buffer tight_gradient; +#ifdef CONFIG_VNC_JPEG + Buffer tight_jpeg; +#endif + int tight_levels[4]; + z_stream tight_stream[4]; + + /* Hextile */ + VncSendHextileTile *send_hextile_tile; + + /* Zlib */ + Buffer zlib; + Buffer zlib_tmp; + z_stream zlib_stream; + int zlib_level; + + 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_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_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_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) + + +/* 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); + + +/* Buffer I/O functions */ +uint8_t read_u8(uint8_t *data, size_t offset); +uint16_t read_u16(uint8_t *data, size_t offset); +int32_t read_s32(uint8_t *data, size_t offset); +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); +int buffer_empty(Buffer *buffer); +uint8_t *buffer_end(Buffer *buffer); +void buffer_reset(Buffer *buffer); +void buffer_free(Buffer *buffer); +void buffer_append(Buffer *buffer, const void *data, size_t len); + + +/* Misc helpers */ + +char *vnc_socket_local_addr(const char *format, int fd); +char *vnc_socket_remote_addr(const char *format, int fd); + +/* Framebuffer */ +void vnc_framebuffer_update(VncState *vs, int x, int y, int w, int h, + int32_t encoding); + +void vnc_convert_pixel(VncState *vs, uint8_t *buf, uint32_t v); + +/* Encodings */ +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); +void vnc_tight_clear(VncState *vs); + +#endif /* __QEMU_VNC_H */ diff --git a/ui/vnc_keysym.h b/ui/vnc_keysym.h new file mode 100644 index 0000000000..55cb87edec --- /dev/null +++ b/ui/vnc_keysym.h @@ -0,0 +1,324 @@ + +#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 */ + + /* 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}, + +{NULL,0}, +}; diff --git a/ui/vnchextile.h b/ui/vnchextile.h new file mode 100644 index 0000000000..b9f9f5ef89 --- /dev/null +++ b/ui/vnchextile.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 = vd->server->data + y * ds_get_linesize(vs->ds) + x * ds_get_bytes_per_pixel(vs->ds); + 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->clientds.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 += ds_get_linesize(vs->ds) / 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 += ds_get_linesize(vs->ds) / 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->clientds.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->clientds.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 += ds_get_linesize(vs->ds) / 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. */ + } + 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, &vd->server->pf, last_bg, sizeof(pixel_t)); + if (flags & 0x04) + vs->write_pixels(vs, &vd->server->pf, 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, &vd->server->pf, row, + w * ds_get_bytes_per_pixel(vs->ds)); + row += ds_get_linesize(vs->ds); + } + } +} + +#undef NAME +#undef pixel_t +#undef CONCAT_I +#undef CONCAT diff --git a/ui/x_keymap.c b/ui/x_keymap.c new file mode 100644 index 0000000000..b9b0944180 --- /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] = { + 0, /* 97 EVDEV - RO ("Internet" Keyboards) */ + 0, /* 98 EVDEV - KATA (Katakana) */ + 0, /* 99 EVDEV - HIRA (Hiragana) */ + 0x79, /* 100 EVDEV - HENK (Henkan) */ + 0x70, /* 101 EVDEV - HKTG (Hiragana/Katakana toggle) */ + 0x7b, /* 102 EVDEV - MUHE (Muhenkan) */ + 0, /* 103 EVDEV - JPCM (KPJPComma) */ + 0x9c, /* 104 KPEN */ + 0x9d, /* 105 RCTL */ + 0xb5, /* 106 KPDV */ + 0xb7, /* 107 PRSC */ + 0xb8, /* 108 RALT */ + 0, /* 109 EVDEV - LNFD ("Internet" Keyboards) */ + 0xc7, /* 110 HOME */ + 0xc8, /* 111 UP */ + 0xc9, /* 112 PGUP */ + 0xcb, /* 113 LEFT */ + 0xcd, /* 114 RGHT */ + 0xcf, /* 115 END */ + 0xd0, /* 116 DOWN */ + 0xd1, /* 117 PGDN */ + 0xd2, /* 118 INS */ + 0xd3, /* 119 DELE */ + 0, /* 120 EVDEV - I120 ("Internet" Keyboards) */ + 0, /* 121 EVDEV - MUTE */ + 0, /* 122 EVDEV - VOL- */ + 0, /* 123 EVDEV - VOL+ */ + 0, /* 124 EVDEV - POWR */ + 0, /* 125 EVDEV - KPEQ */ + 0, /* 126 EVDEV - I126 ("Internet" Keyboards) */ + 0, /* 127 EVDEV - PAUS */ + 0, /* 128 EVDEV - ???? */ + 0, /* 129 EVDEV - I129 ("Internet" Keyboards) */ + 0xf1, /* 130 EVDEV - HNGL (Korean Hangul Latin toggle) */ + 0xf2, /* 131 EVDEV - HJCV (Korean Hangul Hanja toggle) */ + 0x7d, /* 132 AE13 (Yen)*/ + 0xdb, /* 133 EVDEV - LWIN */ + 0xdc, /* 134 EVDEV - RWIN */ + 0xdd, /* 135 EVDEV - MENU */ + 0, /* 136 EVDEV - STOP */ + 0, /* 137 EVDEV - AGAI */ + 0, /* 138 EVDEV - PROP */ + 0, /* 139 EVDEV - UNDO */ + 0, /* 140 EVDEV - FRNT */ + 0, /* 141 EVDEV - COPY */ + 0, /* 142 EVDEV - OPEN */ + 0, /* 143 EVDEV - PAST */ + 0, /* 144 EVDEV - FIND */ + 0, /* 145 EVDEV - CUT */ + 0, /* 146 EVDEV - HELP */ + 0, /* 147 EVDEV - I147 */ + 0, /* 148 EVDEV - I148 */ + 0, /* 149 EVDEV - I149 */ + 0, /* 150 EVDEV - I150 */ + 0, /* 151 EVDEV - I151 */ + 0, /* 152 EVDEV - I152 */ + 0, /* 153 EVDEV - I153 */ + 0, /* 154 EVDEV - I154 */ + 0, /* 155 EVDEV - I156 */ + 0, /* 156 EVDEV - I157 */ + 0, /* 157 EVDEV - I158 */ +}; + +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 0000000000..2042ce0ed2 --- /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 + +extern uint8_t translate_xfree86_keycode(const int key); + +extern uint8_t translate_evdev_keycode(const int key); + +#endif |