diff options
author | Chanho Park <chanho61.park@samsung.com> | 2014-06-26 20:28:10 +0900 |
---|---|---|
committer | Chanho Park <chanho61.park@samsung.com> | 2014-07-07 16:25:44 +0900 |
commit | a15119db2ff5c2fdfdeb913b297bf8aa3399132e (patch) | |
tree | 7d6f779408bb772b11c029ab88000fc01856b599 /ui | |
parent | 340f06c9eaee097e626c251bf7a013350649c091 (diff) | |
download | qemu-a15119db2ff5c2fdfdeb913b297bf8aa3399132e.tar.gz qemu-a15119db2ff5c2fdfdeb913b297bf8aa3399132e.tar.bz2 qemu-a15119db2ff5c2fdfdeb913b297bf8aa3399132e.zip |
Imported Upstream version 2.0.0upstream/2.0.0
Change-Id: I081766c4314e7893f54fec80b920b1638d15021f
Diffstat (limited to 'ui')
-rw-r--r-- | ui/Makefile.objs | 8 | ||||
-rw-r--r-- | ui/cocoa.m | 182 | ||||
-rw-r--r-- | ui/console.c | 151 | ||||
-rw-r--r-- | ui/curses.c | 91 | ||||
-rw-r--r-- | ui/gtk.c | 228 | ||||
-rw-r--r-- | ui/input-legacy.c | 468 | ||||
-rw-r--r-- | ui/input.c | 697 | ||||
-rw-r--r-- | ui/keymaps.c | 6 | ||||
-rw-r--r-- | ui/sdl.c | 151 | ||||
-rw-r--r-- | ui/sdl2-keymap.h | 266 | ||||
-rw-r--r-- | ui/sdl2.c | 834 | ||||
-rw-r--r-- | ui/sdl_keysym.h | 3 | ||||
-rw-r--r-- | ui/spice-core.c | 100 | ||||
-rw-r--r-- | ui/spice-display.c | 116 | ||||
-rw-r--r-- | ui/spice-input.c | 84 | ||||
-rw-r--r-- | ui/vnc-auth-sasl.h | 1 | ||||
-rw-r--r-- | ui/vnc-auth-vencrypt.c | 2 | ||||
-rw-r--r-- | ui/vnc-enc-tight.c | 2 | ||||
-rw-r--r-- | ui/vnc-enc-zywrle.h | 2 | ||||
-rw-r--r-- | ui/vnc-jobs.c | 8 | ||||
-rw-r--r-- | ui/vnc-ws.c | 1 | ||||
-rw-r--r-- | ui/vnc.c | 300 | ||||
-rw-r--r-- | ui/vnc.h | 11 | ||||
-rw-r--r-- | ui/vnc_keysym.h | 373 |
24 files changed, 3052 insertions, 1033 deletions
diff --git a/ui/Makefile.objs b/ui/Makefile.objs index 6ddc0def6..6f2294efd 100644 --- a/ui/Makefile.objs +++ b/ui/Makefile.objs @@ -7,16 +7,14 @@ vnc-obj-$(CONFIG_VNC_SASL) += vnc-auth-sasl.o vnc-obj-$(CONFIG_VNC_WS) += vnc-ws.o vnc-obj-y += vnc-jobs.o -common-obj-y += keymaps.o console.o cursor.o input.o qemu-pixman.o +common-obj-y += keymaps.o console.o cursor.o input.o input-legacy.o qemu-pixman.o common-obj-$(CONFIG_SPICE) += spice-core.o spice-input.o spice-display.o -common-obj-$(CONFIG_SDL) += sdl.o sdl_zoom.o x_keymap.o +common-obj-$(CONFIG_SDL) += sdl.o sdl_zoom.o x_keymap.o sdl2.o common-obj-$(CONFIG_COCOA) += cocoa.o common-obj-$(CONFIG_CURSES) += curses.o common-obj-$(CONFIG_VNC) += $(vnc-obj-y) common-obj-$(CONFIG_GTK) += gtk.o x_keymap.o -$(obj)/sdl.o $(obj)/sdl_zoom.o: QEMU_CFLAGS += $(SDL_CFLAGS) - -$(obj)/cocoa.o: $(SRC_PATH)/$(obj)/cocoa.m +$(obj)/sdl.o $(obj)/sdl_zoom.o $(obj)/sdl2.o: QEMU_CFLAGS += $(SDL_CFLAGS) $(obj)/gtk.o: QEMU_CFLAGS += $(GTK_CFLAGS) $(VTE_CFLAGS) diff --git a/ui/cocoa.m b/ui/cocoa.m index be491794d..f20fd1ffa 100644 --- a/ui/cocoa.m +++ b/ui/cocoa.m @@ -27,6 +27,7 @@ #include "qemu-common.h" #include "ui/console.h" +#include "ui/input.h" #include "sysemu/sysemu.h" #ifndef MAC_OS_X_VERSION_10_4 @@ -49,14 +50,6 @@ #endif #define cgrect(nsrect) (*(CGRect *)&(nsrect)) -#define COCOA_MOUSE_EVENT \ - if (isTabletEnabled) { \ - kbd_mouse_event((int)(p.x * 0x7FFF / (screen.width - 1)), (int)((screen.height - p.y) * 0x7FFF / (screen.height - 1)), 0, buttons); \ - } else if (isMouseGrabed) { \ - kbd_mouse_event((int)[event deltaX], (int)[event deltaY], 0, buttons); \ - } else { \ - [NSApp sendEvent:event]; \ - } typedef struct { int width; @@ -67,6 +60,7 @@ typedef struct { NSWindow *normalWindow; static DisplayChangeListener *dcl; +static int last_buttons; int gArgc; char **gArgv; @@ -129,8 +123,8 @@ int keymap[] = 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 + 220, // 54 0x36 0xdc E0,5C R GUI QZ_RMETA + 219, // 55 0x37 0xdb E0,5B L GUI QZ_LMETA 42, // 56 0x38 0x2a L SHFT QZ_LSHIFT 58, // 57 0x39 0x3a CAPS QZ_CAPSLOCK 56, // 58 0x3A 0x38 L ALT QZ_LALT @@ -204,10 +198,8 @@ int keymap[] = 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 */ +/* Additional 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 @@ -240,9 +232,8 @@ int keymap[] = static int cocoa_keycode_to_qemu(int keycode) { - if((sizeof(keymap)/sizeof(int)) <= keycode) - { - printf("(cocoa) warning unknow keycode 0x%x\n", keycode); + if (ARRAY_SIZE(keymap) <= keycode) { + fprintf(stderr, "(cocoa) warning unknown keycode 0x%x\n", keycode); return 0; } return keymap[keycode]; @@ -262,7 +253,7 @@ static int cocoa_keycode_to_qemu(int keycode) float cx,cy,cw,ch,cdx,cdy; CGDataProviderRef dataProviderRef; int modifiers_state[256]; - BOOL isMouseGrabed; + BOOL isMouseGrabbed; BOOL isFullscreen; BOOL isAbsoluteEnabled; BOOL isTabletEnabled; @@ -273,7 +264,7 @@ static int cocoa_keycode_to_qemu(int keycode) - (void) toggleFullScreen:(id)sender; - (void) handleEvent:(NSEvent *)event; - (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled; -- (BOOL) isMouseGrabed; +- (BOOL) isMouseGrabbed; - (BOOL) isAbsoluteEnabled; - (float) cdx; - (float) cdy; @@ -324,7 +315,12 @@ QemuCocoaView *cocoaView; CGContextSetShouldAntialias (viewContextRef, NO); // draw screen bitmap directly to Core Graphics context - if (dataProviderRef) { + if (!dataProviderRef) { + // Draw request before any guest device has set up a framebuffer: + // just draw an opaque black rectangle + CGContextSetRGBFillColor(viewContextRef, 0, 0, 0, 1.0); + CGContextFillRect(viewContextRef, NSRectToCGRect(rect)); + } else { CGImageRef imageRef = CGImageCreate( screen.width, //width screen.height, //height @@ -408,31 +404,41 @@ QemuCocoaView *cocoaView; int w = surface_width(surface); int h = surface_height(surface); + bool isResize = (w != screen.width || h != screen.height); + + int oldh = screen.height; + if (isResize) { + // Resize before we trigger the redraw, or we'll redraw at the wrong size + COCOA_DEBUG("switchSurface: new size %d x %d\n", w, h); + screen.width = w; + screen.height = h; + [self setContentDimensions]; + [self setFrame:NSMakeRect(cx, cy, cw, ch)]; + } // update screenBuffer if (dataProviderRef) CGDataProviderRelease(dataProviderRef); //sync host window color space with guests - screen.bitsPerPixel = surface_bits_per_pixel(surface); - screen.bitsPerComponent = surface_bytes_per_pixel(surface) * 2; + screen.bitsPerPixel = surface_bits_per_pixel(surface); + screen.bitsPerComponent = surface_bytes_per_pixel(surface) * 2; dataProviderRef = CGDataProviderCreateWithData(NULL, surface_data(surface), w * 4 * h, NULL); // update windows if (isFullscreen) { [[fullScreenWindow contentView] setFrame:[[NSScreen mainScreen] frame]]; - [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + screen.height, w, h + [normalWindow frame].size.height - screen.height) display:NO animate:NO]; + [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + oldh, w, h + [normalWindow frame].size.height - oldh) display:NO animate:NO]; } else { if (qemu_name) [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]]; - [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + screen.height, w, h + [normalWindow frame].size.height - screen.height) display:YES animate:NO]; + [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + oldh, w, h + [normalWindow frame].size.height - oldh) display:YES animate:NO]; + } + + if (isResize) { + [normalWindow center]; } - screen.width = w; - screen.height = h; - [normalWindow center]; - [self setContentDimensions]; - [self setFrame:NSMakeRect(cx, cy, cw, ch)]; } - (void) toggleFullScreen:(id)sender @@ -489,23 +495,28 @@ QemuCocoaView *cocoaView; int buttons = 0; int keycode; + bool mouse_event = false; NSPoint p = [event locationInWindow]; switch ([event type]) { case NSFlagsChanged: keycode = cocoa_keycode_to_qemu([event keyCode]); + + if ((keycode == 219 || keycode == 220) && !isMouseGrabbed) { + /* Don't pass command key changes to guest unless mouse is grabbed */ + keycode = 0; + } + if (keycode) { if (keycode == 58 || keycode == 69) { // emulate caps lock and num lock keydown and keyup - kbd_put_keycode(keycode); - kbd_put_keycode(keycode | 0x80); + qemu_input_event_send_key_number(dcl->con, keycode, true); + qemu_input_event_send_key_number(dcl->con, keycode, false); } else if (qemu_console_is_graphic(NULL)) { - if (keycode & 0x80) - kbd_put_keycode(0xe0); if (modifiers_state[keycode] == 0) { // keydown - kbd_put_keycode(keycode & 0x7f); + qemu_input_event_send_key_number(dcl->con, keycode, true); modifiers_state[keycode] = 1; } else { // keyup - kbd_put_keycode(keycode | 0x80); + qemu_input_event_send_key_number(dcl->con, keycode, false); modifiers_state[keycode] = 0; } } @@ -517,15 +528,15 @@ QemuCocoaView *cocoaView; } break; case NSKeyDown: + keycode = cocoa_keycode_to_qemu([event keyCode]); - // forward command Key Combos - if ([event modifierFlags] & NSCommandKeyMask) { + // forward command key combos to the host UI unless the mouse is grabbed + if (!isMouseGrabbed && ([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)) { @@ -539,9 +550,7 @@ QemuCocoaView *cocoaView; // handle keys for graphic console } else if (qemu_console_is_graphic(NULL)) { - if (keycode & 0x80) //check bit for e0 in front - kbd_put_keycode(0xe0); - kbd_put_keycode(keycode & 0x7f); //remove e0 bit in front + qemu_input_event_send_key_number(dcl->con, keycode, true); // handlekeys for Monitor } else { @@ -581,10 +590,15 @@ QemuCocoaView *cocoaView; break; case NSKeyUp: keycode = cocoa_keycode_to_qemu([event keyCode]); + + // don't pass the guest a spurious key-up if we treated this + // command-key combo as a host UI action + if (!isMouseGrabbed && ([event modifierFlags] & NSCommandKeyMask)) { + return; + } + if (qemu_console_is_graphic(NULL)) { - if (keycode & 0x80) - kbd_put_keycode(0xe0); - kbd_put_keycode(keycode | 0x80); //add 128 to signal release of key + qemu_input_event_send_key_number(dcl->con, keycode, false); } break; case NSMouseMoved: @@ -601,7 +615,7 @@ QemuCocoaView *cocoaView; } } } - COCOA_MOUSE_EVENT + mouse_event = true; break; case NSLeftMouseDown: if ([event modifierFlags] & NSCommandKeyMask) { @@ -609,15 +623,15 @@ QemuCocoaView *cocoaView; } else { buttons |= MOUSE_EVENT_LBUTTON; } - COCOA_MOUSE_EVENT + mouse_event = true; break; case NSRightMouseDown: buttons |= MOUSE_EVENT_RBUTTON; - COCOA_MOUSE_EVENT + mouse_event = true; break; case NSOtherMouseDown: buttons |= MOUSE_EVENT_MBUTTON; - COCOA_MOUSE_EVENT + mouse_event = true; break; case NSLeftMouseDragged: if ([event modifierFlags] & NSCommandKeyMask) { @@ -625,38 +639,40 @@ QemuCocoaView *cocoaView; } else { buttons |= MOUSE_EVENT_LBUTTON; } - COCOA_MOUSE_EVENT + mouse_event = true; break; case NSRightMouseDragged: buttons |= MOUSE_EVENT_RBUTTON; - COCOA_MOUSE_EVENT + mouse_event = true; break; case NSOtherMouseDragged: buttons |= MOUSE_EVENT_MBUTTON; - COCOA_MOUSE_EVENT + mouse_event = true; break; case NSLeftMouseUp: if (isTabletEnabled) { - COCOA_MOUSE_EVENT - } else if (!isMouseGrabed) { + mouse_event = true; + } else if (!isMouseGrabbed) { 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 + mouse_event = true; } break; case NSRightMouseUp: - COCOA_MOUSE_EVENT + mouse_event = true; break; case NSOtherMouseUp: - COCOA_MOUSE_EVENT + mouse_event = true; break; case NSScrollWheel: - if (isTabletEnabled || isMouseGrabed) { - kbd_mouse_event(0, 0, -[event deltaY], 0); + if (isTabletEnabled || isMouseGrabbed) { + buttons |= ([event deltaY] < 0) ? + MOUSE_EVENT_WHEELUP : MOUSE_EVENT_WHEELDN; + mouse_event = true; } else { [NSApp sendEvent:event]; } @@ -664,6 +680,30 @@ QemuCocoaView *cocoaView; default: [NSApp sendEvent:event]; } + + if (mouse_event) { + if (last_buttons != buttons) { + static uint32_t bmap[INPUT_BUTTON_MAX] = { + [INPUT_BUTTON_LEFT] = MOUSE_EVENT_LBUTTON, + [INPUT_BUTTON_MIDDLE] = MOUSE_EVENT_MBUTTON, + [INPUT_BUTTON_RIGHT] = MOUSE_EVENT_RBUTTON, + [INPUT_BUTTON_WHEEL_UP] = MOUSE_EVENT_WHEELUP, + [INPUT_BUTTON_WHEEL_DOWN] = MOUSE_EVENT_WHEELDN, + }; + qemu_input_update_buttons(dcl->con, bmap, last_buttons, buttons); + last_buttons = buttons; + } + if (isTabletEnabled) { + qemu_input_queue_abs(dcl->con, INPUT_AXIS_X, p.x, screen.width); + qemu_input_queue_abs(dcl->con, INPUT_AXIS_Y, p.y, screen.height); + } else if (isMouseGrabbed) { + qemu_input_queue_rel(dcl->con, INPUT_AXIS_X, (int)[event deltaX]); + qemu_input_queue_rel(dcl->con, INPUT_AXIS_Y, (int)[event deltaY]); + } else { + [NSApp sendEvent:event]; + } + qemu_input_event_sync(); + } } - (void) grabMouse @@ -678,7 +718,7 @@ QemuCocoaView *cocoaView; } [NSCursor hide]; CGAssociateMouseAndMouseCursorPosition(FALSE); - isMouseGrabed = TRUE; // while isMouseGrabed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:] + isMouseGrabbed = TRUE; // while isMouseGrabbed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:] } - (void) ungrabMouse @@ -693,11 +733,11 @@ QemuCocoaView *cocoaView; } [NSCursor unhide]; CGAssociateMouseAndMouseCursorPosition(TRUE); - isMouseGrabed = FALSE; + isMouseGrabbed = FALSE; } - (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled {isAbsoluteEnabled = tIsAbsoluteEnabled;} -- (BOOL) isMouseGrabed {return isMouseGrabed;} +- (BOOL) isMouseGrabbed {return isMouseGrabbed;} - (BOOL) isAbsoluteEnabled {return isAbsoluteEnabled;} - (float) cdx {return cdx;} - (float) cdy {return cdy;} @@ -749,7 +789,7 @@ QemuCocoaView *cocoaView; [normalWindow setContentView:cocoaView]; [normalWindow useOptimizedDrawing:YES]; [normalWindow makeKeyAndOrderFront:self]; - [normalWindow center]; + [normalWindow center]; } return self; @@ -768,14 +808,14 @@ QemuCocoaView *cocoaView; { COCOA_DEBUG("QemuCocoaAppController: applicationDidFinishLaunching\n"); - // Display an open dialog box if no argument were passed or + // Display an open dialog box if no arguments were passed or // if qemu was launched from the finder ( the Finder passes "-psn" ) if( gArgc <= 1 || strncmp ((char *)gArgv[1], "-psn", 4) == 0) { NSOpenPanel *op = [[NSOpenPanel alloc] init]; [op setPrompt:@"Boot image"]; [op setMessage:@"Select the disk image you want to boot.\n\nHit the \"Cancel\" button to quit"]; NSArray *filetypes = [NSArray arrayWithObjects:@"img", @"iso", @"dmg", - @"qcow", @"cow", @"cloop", @"vmdk", nil]; + @"qcow", @"qcow2", @"cow", @"cloop", @"vmdk", nil]; #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6) [op setAllowedFileTypes:filetypes]; [op beginSheetModalForWindow:normalWindow @@ -823,18 +863,18 @@ QemuCocoaView *cocoaView; if(returnCode == NSCancelButton) { exit(0); } else if(returnCode == NSOKButton) { - const char *bin = "qemu"; char *img = (char*)[ [ [ sheet URL ] path ] cStringUsingEncoding:NSASCIIStringEncoding]; - char **argv = (char**)malloc( sizeof(char*)*3 ); + char **argv = g_new(char *, 4); [sheet close]; - argv[0] = g_strdup_printf("%s", bin); - argv[1] = g_strdup_printf("-hda"); - argv[2] = g_strdup_printf("%s", img); + argv[0] = g_strdup(gArgv[0]); + argv[1] = g_strdup("-hda"); + argv[2] = g_strdup(img); + argv[3] = NULL; - printf("Using argc %d argv %s -hda %s\n", 3, bin, img); + // printf("Using argc %d argv %s -hda %s\n", 3, gArgv[0], img); [self startEmulationWithArgc:3 argv:(char**)argv]; } @@ -998,9 +1038,9 @@ static void cocoa_refresh(DisplayChangeListener *dcl) COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n"); - if (kbd_mouse_is_absolute()) { + if (qemu_input_is_absolute()) { if (![cocoaView isAbsoluteEnabled]) { - if ([cocoaView isMouseGrabed]) { + if ([cocoaView isMouseGrabbed]) { [cocoaView ungrabMouse]; } } diff --git a/ui/console.c b/ui/console.c index e3e82979d..e057755c0 100644 --- a/ui/console.c +++ b/ui/console.c @@ -27,8 +27,8 @@ #include "qemu/timer.h" #include "qmp-commands.h" #include "sysemu/char.h" +#include "trace.h" -//#define DEBUG_CONSOLE #define DEFAULT_BACKSCROLL 512 #define MAX_CONSOLES 12 #define CONSOLE_CURSOR_PERIOD 500 @@ -124,6 +124,8 @@ struct QemuConsole { /* Graphic console state. */ Object *device; + uint32_t head; + QemuUIInfo ui_info; const GraphicHwOps *hw_ops; void *hw; @@ -161,7 +163,7 @@ struct QemuConsole { }; struct DisplayState { - struct QEMUTimer *gui_timer; + QEMUTimer *gui_timer; uint64_t last_update; uint64_t update_interval; bool refreshing; @@ -208,8 +210,8 @@ static void gui_update(void *opaque) } trace_console_refresh(interval); } - ds->last_update = qemu_get_clock_ms(rt_clock); - qemu_mod_timer(ds->gui_timer, ds->last_update + interval); + ds->last_update = qemu_clock_get_ms(QEMU_CLOCK_REALTIME); + timer_mod(ds->gui_timer, ds->last_update + interval); } static void gui_setup_refresh(DisplayState *ds) @@ -232,12 +234,12 @@ static void gui_setup_refresh(DisplayState *ds) } if (need_timer && ds->gui_timer == NULL) { - ds->gui_timer = qemu_new_timer_ms(rt_clock, gui_update, ds); - qemu_mod_timer(ds->gui_timer, qemu_get_clock_ms(rt_clock)); + ds->gui_timer = timer_new_ms(QEMU_CLOCK_REALTIME, gui_update, ds); + timer_mod(ds->gui_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME)); } if (!need_timer && ds->gui_timer != NULL) { - qemu_del_timer(ds->gui_timer); - qemu_free_timer(ds->gui_timer); + timer_del(ds->gui_timer); + timer_free(ds->gui_timer); ds->gui_timer = NULL; } @@ -409,39 +411,6 @@ static const pixman_color_t color_table_rgb[2][8] = { } }; -#ifdef DEBUG_CONSOLE -static void console_print_text_attributes(TextAttributes *t_attrib, char ch) -{ - if (t_attrib->bold) { - printf("b"); - } else { - printf(" "); - } - if (t_attrib->uline) { - printf("u"); - } else { - printf(" "); - } - if (t_attrib->blink) { - printf("l"); - } else { - printf(" "); - } - if (t_attrib->invers) { - printf("i"); - } else { - printf(" "); - } - if (t_attrib->unvisible) { - printf("n"); - } else { - printf(" "); - } - - printf(" fg: %d bg: %d ch:'%2X' '%c'\n", t_attrib->fgcol, t_attrib->bgcol, ch, ch); -} -#endif - static void vga_putcharxy(QemuConsole *s, int x, int y, int ch, TextAttributes *t_attrib) { @@ -899,10 +868,8 @@ static void console_putchar(QemuConsole *s, int ch) s->nb_esc_params++; if (ch == ';') break; -#ifdef DEBUG_CONSOLE - fprintf(stderr, "escape sequence CSI%d;%d%c, %d parameters\n", - s->esc_params[0], s->esc_params[1], ch, s->nb_esc_params); -#endif + trace_console_putchar_csi(s->esc_params[0], s->esc_params[1], + ch, s->nb_esc_params); s->state = TTY_STATE_NORM; switch(ch) { case 'A': @@ -1016,9 +983,7 @@ static void console_putchar(QemuConsole *s, int ch) s->y = s->y_saved; break; default: -#ifdef DEBUG_CONSOLE - fprintf(stderr, "unhandled escape character '%c'\n", ch); -#endif + trace_console_putchar_unhandled(ch); break; } break; @@ -1040,7 +1005,7 @@ void console_select(unsigned int index) DisplayState *ds = s->ds; if (active_console && active_console->cursor_timer) { - qemu_del_timer(active_console->cursor_timer); + timer_del(active_console->cursor_timer); } active_console = s; if (ds->have_gfx) { @@ -1059,8 +1024,8 @@ void console_select(unsigned int index) dpy_text_resize(s, s->width, s->height); } if (s->cursor_timer) { - qemu_mod_timer(s->cursor_timer, - qemu_get_clock_ms(rt_clock) + CONSOLE_CURSOR_PERIOD / 2); + timer_mod(s->cursor_timer, + qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + CONSOLE_CURSOR_PERIOD / 2); } } } @@ -1105,7 +1070,7 @@ static void kbd_send_chars(void *opaque) /* characters are pending: we send them a bit later (XXX: horrible, should change char device API) */ if (s->out_fifo.count > 0) { - qemu_mod_timer(s->kbd_timer, qemu_get_clock_ms(rt_clock) + 1); + timer_mod(s->kbd_timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 1); } } @@ -1215,7 +1180,12 @@ static QemuConsole *new_console(DisplayState *ds, console_type_t console_type) obj = object_new(TYPE_QEMU_CONSOLE); s = QEMU_CONSOLE(obj); object_property_add_link(obj, "device", TYPE_DEVICE, - (Object **)&s->device, &local_err); + (Object **)&s->device, + object_property_allow_set_link, + OBJ_PROP_LINK_UNREF_ON_RELEASE, + &local_err); + object_property_add_uint32_ptr(obj, "head", + &s->head, &local_err); if (!active_console || ((active_console->console_type != GRAPHIC_CONSOLE) && (console_type == GRAPHIC_CONSOLE))) { @@ -1366,7 +1336,7 @@ void update_displaychangelistener(DisplayChangeListener *dcl, dcl->update_interval = interval; if (!ds->refreshing && ds->update_interval > interval) { - qemu_mod_timer(ds->gui_timer, ds->last_update + interval); + timer_mod(ds->gui_timer, ds->last_update + interval); } } @@ -1381,6 +1351,16 @@ void unregister_displaychangelistener(DisplayChangeListener *dcl) gui_setup_refresh(ds); } +int dpy_set_ui_info(QemuConsole *con, QemuUIInfo *info) +{ + assert(con != NULL); + con->ui_info = *info; + if (con->hw_ops->ui_info) { + return con->hw_ops->ui_info(con->hw, con->head, info); + } + return -1; +} + void dpy_gfx_update(QemuConsole *con, int x, int y, int w, int h) { DisplayState *s = con->ds; @@ -1606,7 +1586,7 @@ DisplayState *init_displaystate(void) return display_state; } -QemuConsole *graphic_console_init(DeviceState *dev, +QemuConsole *graphic_console_init(DeviceState *dev, uint32_t head, const GraphicHwOps *hw_ops, void *opaque) { @@ -1624,6 +1604,8 @@ QemuConsole *graphic_console_init(DeviceState *dev, if (dev) { object_property_set_link(OBJECT(s), OBJECT(dev), "device", &local_err); + object_property_set_int(OBJECT(s), head, + "head", &local_err); } s->surface = qemu_create_displaysurface(width, height); @@ -1638,10 +1620,11 @@ QemuConsole *qemu_console_lookup_by_index(unsigned int index) return consoles[index]; } -QemuConsole *qemu_console_lookup_by_device(DeviceState *dev) +QemuConsole *qemu_console_lookup_by_device(DeviceState *dev, uint32_t head) { Error *local_err = NULL; Object *obj; + uint32_t h; int i; for (i = 0; i < nb_consoles; i++) { @@ -1650,9 +1633,15 @@ QemuConsole *qemu_console_lookup_by_device(DeviceState *dev) } obj = object_property_get_link(OBJECT(consoles[i]), "device", &local_err); - if (DEVICE(obj) == dev) { - return consoles[i]; + if (DEVICE(obj) != dev) { + continue; } + h = object_property_get_int(OBJECT(consoles[i]), + "head", &local_err); + if (h != head) { + continue; + } + return consoles[i]; } return NULL; } @@ -1678,6 +1667,44 @@ bool qemu_console_is_fixedsize(QemuConsole *con) return con && (con->console_type != TEXT_CONSOLE); } +int qemu_console_get_index(QemuConsole *con) +{ + if (con == NULL) { + con = active_console; + } + return con ? con->index : -1; +} + +uint32_t qemu_console_get_head(QemuConsole *con) +{ + if (con == NULL) { + con = active_console; + } + return con ? con->head : -1; +} + +QemuUIInfo *qemu_console_get_ui_info(QemuConsole *con) +{ + assert(con != NULL); + return &con->ui_info; +} + +int qemu_console_get_width(QemuConsole *con, int fallback) +{ + if (con == NULL) { + con = active_console; + } + return con ? surface_width(con->surface) : fallback; +} + +int qemu_console_get_height(QemuConsole *con, int fallback) +{ + if (con == NULL) { + con = active_console; + } + return con ? surface_height(con->surface) : fallback; +} + static void text_console_set_echo(CharDriverState *chr, bool echo) { QemuConsole *s = chr->opaque; @@ -1691,8 +1718,8 @@ static void text_console_update_cursor(void *opaque) s->cursor_visible_phase = !s->cursor_visible_phase; graphic_hw_invalidate(s); - qemu_mod_timer(s->cursor_timer, - qemu_get_clock_ms(rt_clock) + CONSOLE_CURSOR_PERIOD / 2); + timer_mod(s->cursor_timer, + qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + CONSOLE_CURSOR_PERIOD / 2); } static const GraphicHwOps text_console_ops = { @@ -1712,7 +1739,7 @@ static void text_console_do_init(CharDriverState *chr, DisplayState *ds) s->out_fifo.buf = s->out_fifo_buf; s->out_fifo.buf_size = sizeof(s->out_fifo_buf); - s->kbd_timer = qemu_new_timer_ms(rt_clock, kbd_send_chars, s); + s->kbd_timer = timer_new_ms(QEMU_CLOCK_REALTIME, kbd_send_chars, s); s->ds = ds; s->y_displayed = 0; @@ -1729,7 +1756,7 @@ static void text_console_do_init(CharDriverState *chr, DisplayState *ds) } s->cursor_timer = - qemu_new_timer_ms(rt_clock, text_console_update_cursor, s); + timer_new_ms(QEMU_CLOCK_REALTIME, text_console_update_cursor, s); s->hw_ops = &text_console_ops; s->hw = s; diff --git a/ui/curses.c b/ui/curses.c index 289a9558d..b044790e4 100644 --- a/ui/curses.c +++ b/ui/curses.c @@ -30,6 +30,7 @@ #include "qemu-common.h" #include "ui/console.h" +#include "ui/input.h" #include "sysemu/sysemu.h" #define FONT_HEIGHT 16 @@ -106,9 +107,9 @@ static void curses_resize(DisplayChangeListener *dcl, curses_calc_pad(); } -#ifndef _WIN32 -#if defined(SIGWINCH) && defined(KEY_RESIZE) -static void curses_winch_handler(int signum) +#if !defined(_WIN32) && defined(SIGWINCH) && defined(KEY_RESIZE) +static volatile sig_atomic_t got_sigwinch; +static void curses_winch_check(void) { struct winsize { unsigned short ws_row; @@ -117,18 +118,34 @@ static void curses_winch_handler(int signum) unsigned short ws_ypixel; /* unused */ } ws; - /* terminal size changed */ - if (ioctl(1, TIOCGWINSZ, &ws) == -1) + if (!got_sigwinch) { return; + } + got_sigwinch = false; + + if (ioctl(1, TIOCGWINSZ, &ws) == -1) { + return; + } resize_term(ws.ws_row, ws.ws_col); - curses_calc_pad(); invalidate = 1; +} - /* some systems require this */ - signal(SIGWINCH, curses_winch_handler); +static void curses_winch_handler(int signum) +{ + got_sigwinch = true; } -#endif + +static void curses_winch_init(void) +{ + struct sigaction old, winch = { + .sa_handler = curses_winch_handler, + }; + sigaction(SIGWINCH, &winch, &old); +} +#else +static void curses_winch_check(void) {} +static void curses_winch_init(void) {} #endif static void curses_cursor_position(DisplayChangeListener *dcl, @@ -163,6 +180,8 @@ static void curses_refresh(DisplayChangeListener *dcl) { int chr, nextchr, keysym, keycode, keycode_alt; + curses_winch_check(); + if (invalidate) { clear(); refresh(); @@ -256,32 +275,34 @@ static void curses_refresh(DisplayChangeListener *dcl) if (qemu_console_is_graphic(NULL)) { /* since terminals don't know about key press and release * events, we need to emit both for each key received */ - if (keycode & SHIFT) - kbd_put_keycode(SHIFT_CODE); - if (keycode & CNTRL) - kbd_put_keycode(CNTRL_CODE); - if (keycode & ALT) - kbd_put_keycode(ALT_CODE); + if (keycode & SHIFT) { + qemu_input_event_send_key_number(NULL, SHIFT_CODE, true); + } + if (keycode & CNTRL) { + qemu_input_event_send_key_number(NULL, CNTRL_CODE, true); + } + if (keycode & ALT) { + qemu_input_event_send_key_number(NULL, ALT_CODE, true); + } if (keycode & ALTGR) { - kbd_put_keycode(SCANCODE_EMUL0); - kbd_put_keycode(ALT_CODE); + qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, true); } - if (keycode & GREY) - kbd_put_keycode(GREY_CODE); - kbd_put_keycode(keycode & KEY_MASK); - if (keycode & GREY) - kbd_put_keycode(GREY_CODE); - kbd_put_keycode((keycode & KEY_MASK) | KEY_RELEASE); + + qemu_input_event_send_key_number(NULL, keycode, true); + qemu_input_event_send_key_number(NULL, keycode, false); + if (keycode & ALTGR) { - kbd_put_keycode(SCANCODE_EMUL0); - kbd_put_keycode(ALT_CODE | KEY_RELEASE); + qemu_input_event_send_key_number(NULL, GREY | ALT_CODE, false); + } + if (keycode & ALT) { + qemu_input_event_send_key_number(NULL, ALT_CODE, false); + } + if (keycode & CNTRL) { + qemu_input_event_send_key_number(NULL, CNTRL_CODE, false); + } + if (keycode & SHIFT) { + qemu_input_event_send_key_number(NULL, SHIFT_CODE, false); } - 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) @@ -349,13 +370,7 @@ void curses_display_init(DisplayState *ds, int full_screen) curses_keyboard_setup(); atexit(curses_atexit); -#ifndef _WIN32 -#if defined(SIGWINCH) && defined(KEY_RESIZE) - /* some curses implementations provide a handler, but we - * want to be sure this is handled regardless of the library */ - signal(SIGWINCH, curses_winch_handler); -#endif -#endif + curses_winch_init(); dcl = (DisplayChangeListener *) g_malloc0(sizeof(DisplayChangeListener)); dcl->ops = &dcl_ops; @@ -34,6 +34,10 @@ #define GETTEXT_PACKAGE "qemu" #define LOCALEDIR "po" +#ifdef _WIN32 +# define _WIN32_WINNT 0x0601 /* needed to get definition of MAPVK_VK_TO_VSC */ +#endif + #include "qemu-common.h" #ifdef CONFIG_PRAGMA_DIAGNOSTIC_AVAILABLE @@ -50,30 +54,25 @@ #include <gdk/gdkkeysyms.h> #include <glib/gi18n.h> #include <locale.h> +#if defined(CONFIG_VTE) #include <vte/vte.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <sys/un.h> -#include <sys/wait.h> +#endif #include <math.h> +#include "trace.h" #include "ui/console.h" +#include "ui/input.h" #include "sysemu/sysemu.h" #include "qmp-commands.h" #include "x_keymap.h" #include "keymaps.h" #include "sysemu/char.h" -//#define DEBUG_GTK - -#ifdef DEBUG_GTK -#define DPRINTF(fmt, ...) printf(fmt, ## __VA_ARGS__) -#else -#define DPRINTF(fmt, ...) do { } while (0) -#endif - #define MAX_VCS 10 +#if !defined(CONFIG_VTE) +# define VTE_CHECK_VERSION(a, b, c) 0 +#endif /* Compatibility define to let us build on both Gtk2 and Gtk3 */ #if GTK_CHECK_VERSION(3, 0, 0) @@ -111,8 +110,10 @@ typedef struct VirtualConsole { GtkWidget *menu_item; GtkWidget *terminal; +#if defined(CONFIG_VTE) GtkWidget *scrolled_window; CharDriverState *chr; +#endif int fd; } VirtualConsole; @@ -155,8 +156,11 @@ typedef struct GtkDisplayState DisplayChangeListener dcl; DisplaySurface *ds; int button_mask; + gboolean last_set; int last_x; int last_y; + int grab_x_root; + int grab_y_root; double scale_x; double scale_y; @@ -200,7 +204,7 @@ static void gd_update_cursor(GtkDisplayState *s, gboolean override) on_vga = gd_on_vga(s); if ((override || on_vga) && - (s->full_screen || kbd_mouse_is_absolute() || gd_is_grab_active(s))) { + (s->full_screen || qemu_input_is_absolute() || gd_is_grab_active(s))) { gdk_window_set_cursor(window, s->null_cursor); } else { gdk_window_set_cursor(window, NULL); @@ -287,10 +291,7 @@ static void gtk_release_modifiers(GtkDisplayState *s) if (!s->modifier_pressed[i]) { continue; } - if (keycode & SCANCODE_GREY) { - kbd_put_keycode(SCANCODE_EMUL0); - } - kbd_put_keycode(keycode | SCANCODE_UP); + qemu_input_event_send_key_number(s->dcl.con, keycode, false); s->modifier_pressed[i] = false; } } @@ -306,7 +307,7 @@ static void gd_update(DisplayChangeListener *dcl, int fbw, fbh; int ww, wh; - DPRINTF("update(x=%d, y=%d, w=%d, h=%d)\n", x, y, w, h); + trace_gd_update(x, y, w, h); if (s->convert) { pixman_image_composite(PIXMAN_OP_SRC, s->ds->image, NULL, s->convert, @@ -349,13 +350,17 @@ static void gd_mouse_set(DisplayChangeListener *dcl, GdkDeviceManager *mgr; gint x_root, y_root; + if (qemu_input_is_absolute()) { + return; + } + dpy = gtk_widget_get_display(s->drawing_area); mgr = gdk_display_get_device_manager(dpy); gdk_window_get_root_coords(gtk_widget_get_window(s->drawing_area), x, y, &x_root, &y_root); gdk_device_warp(gdk_device_manager_get_client_pointer(mgr), gtk_widget_get_screen(s->drawing_area), - x, y); + x_root, y_root); } #else static void gd_mouse_set(DisplayChangeListener *dcl, @@ -364,6 +369,10 @@ static void gd_mouse_set(DisplayChangeListener *dcl, GtkDisplayState *s = container_of(dcl, GtkDisplayState, dcl); gint x_root, y_root; + if (qemu_input_is_absolute()) { + return; + } + gdk_window_get_root_coords(gtk_widget_get_window(s->drawing_area), x, y, &x_root, &y_root); gdk_display_warp_pointer(gtk_widget_get_display(s->drawing_area), @@ -400,8 +409,7 @@ static void gd_switch(DisplayChangeListener *dcl, GtkDisplayState *s = container_of(dcl, GtkDisplayState, dcl); bool resized = true; - DPRINTF("resize(width=%d, height=%d)\n", - surface_width(surface), surface_height(surface)); + trace_gd_switch(surface_width(surface), surface_height(surface)); if (s->surface) { cairo_surface_destroy(s->surface); @@ -468,8 +476,15 @@ static void gd_change_runstate(void *opaque, int running, RunState state) static void gd_mouse_mode_change(Notifier *notify, void *data) { - gd_update_cursor(container_of(notify, GtkDisplayState, mouse_mode_notifier), - FALSE); + GtkDisplayState *s; + + s = container_of(notify, GtkDisplayState, mouse_mode_notifier); + /* release the grab at switching to absolute mode */ + if (qemu_input_is_absolute() && gd_is_grab_active(s)) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item), + FALSE); + } + gd_update_cursor(s, FALSE); } /** GTK Events **/ @@ -590,7 +605,6 @@ static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion, void *opaque) { GtkDisplayState *s = opaque; - int dx, dy; int x, y; int mx, my; int fbh, fbw; @@ -612,31 +626,27 @@ static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion, x = (motion->x - mx) / s->scale_x; y = (motion->y - my) / s->scale_y; - if (x < 0 || y < 0 || - x >= surface_width(s->ds) || - y >= surface_height(s->ds)) { - return TRUE; - } - - if (kbd_mouse_is_absolute()) { - dx = x * 0x7FFF / (surface_width(s->ds) - 1); - dy = y * 0x7FFF / (surface_height(s->ds) - 1); - } else if (s->last_x == -1 || s->last_y == -1) { - dx = 0; - dy = 0; - } else { - dx = x - s->last_x; - dy = y - s->last_y; + if (qemu_input_is_absolute()) { + if (x < 0 || y < 0 || + x >= surface_width(s->ds) || + y >= surface_height(s->ds)) { + return TRUE; + } + qemu_input_queue_abs(s->dcl.con, INPUT_AXIS_X, x, + surface_width(s->ds)); + qemu_input_queue_abs(s->dcl.con, INPUT_AXIS_Y, y, + surface_height(s->ds)); + qemu_input_event_sync(); + } else if (s->last_set && gd_is_grab_active(s)) { + qemu_input_queue_rel(s->dcl.con, INPUT_AXIS_X, x - s->last_x); + qemu_input_queue_rel(s->dcl.con, INPUT_AXIS_Y, y - s->last_y); + qemu_input_event_sync(); } - s->last_x = x; s->last_y = y; + s->last_set = TRUE; - if (kbd_mouse_is_absolute() || gd_is_grab_active(s)) { - kbd_mouse_event(dx, dy, 0, s->button_mask); - } - - if (!kbd_mouse_is_absolute() && gd_is_grab_active(s)) { + if (!qemu_input_is_absolute() && gd_is_grab_active(s)) { GdkScreen *screen = gtk_widget_get_screen(s->drawing_area); int x = (int)motion->x_root; int y = (int)motion->y_root; @@ -669,8 +679,7 @@ static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion, GdkDisplay *display = gtk_widget_get_display(widget); gdk_display_warp_pointer(display, screen, x, y); #endif - s->last_x = -1; - s->last_y = -1; + s->last_set = FALSE; return FALSE; } } @@ -681,46 +690,67 @@ static gboolean gd_button_event(GtkWidget *widget, GdkEventButton *button, void *opaque) { GtkDisplayState *s = opaque; - int dx, dy; - int n; + InputButton btn; + + /* implicitly grab the input at the first click in the relative mode */ + if (button->button == 1 && button->type == GDK_BUTTON_PRESS && + !qemu_input_is_absolute() && !gd_is_grab_active(s)) { + gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->grab_item), + TRUE); + return TRUE; + } if (button->button == 1) { - n = 0x01; + btn = INPUT_BUTTON_LEFT; } else if (button->button == 2) { - n = 0x04; + btn = INPUT_BUTTON_MIDDLE; } else if (button->button == 3) { - n = 0x02; + btn = INPUT_BUTTON_RIGHT; } else { - n = 0x00; + return TRUE; } - if (button->type == GDK_BUTTON_PRESS) { - s->button_mask |= n; - } else if (button->type == GDK_BUTTON_RELEASE) { - s->button_mask &= ~n; - } + qemu_input_queue_btn(s->dcl.con, btn, button->type == GDK_BUTTON_PRESS); + qemu_input_event_sync(); + return TRUE; +} - if (kbd_mouse_is_absolute()) { - dx = s->last_x * 0x7FFF / (surface_width(s->ds) - 1); - dy = s->last_y * 0x7FFF / (surface_height(s->ds) - 1); +static gboolean gd_scroll_event(GtkWidget *widget, GdkEventScroll *scroll, + void *opaque) +{ + GtkDisplayState *s = opaque; + InputButton btn; + + if (scroll->direction == GDK_SCROLL_UP) { + btn = INPUT_BUTTON_WHEEL_UP; + } else if (scroll->direction == GDK_SCROLL_DOWN) { + btn = INPUT_BUTTON_WHEEL_DOWN; } else { - dx = 0; - dy = 0; + return TRUE; } - kbd_mouse_event(dx, dy, 0, s->button_mask); - + qemu_input_queue_btn(s->dcl.con, btn, true); + qemu_input_event_sync(); + qemu_input_queue_btn(s->dcl.con, btn, false); + qemu_input_event_sync(); return TRUE; } static gboolean gd_key_event(GtkWidget *widget, GdkEventKey *key, void *opaque) { GtkDisplayState *s = opaque; - int gdk_keycode; - int qemu_keycode; + int gdk_keycode = key->hardware_keycode; int i; - gdk_keycode = key->hardware_keycode; +#ifdef _WIN32 + UINT qemu_keycode = MapVirtualKey(gdk_keycode, MAPVK_VK_TO_VSC); + switch (qemu_keycode) { + case 103: /* alt gr */ + qemu_keycode = 56 | SCANCODE_GREY; + break; + } +#else + int qemu_keycode; if (gdk_keycode < 9) { qemu_keycode = 0; @@ -735,10 +765,10 @@ static gboolean gd_key_event(GtkWidget *widget, GdkEventKey *key, void *opaque) } else { qemu_keycode = 0; } +#endif - DPRINTF("translated GDK keycode %d to QEMU keycode %d (%s)\n", - gdk_keycode, qemu_keycode, - (key->type == GDK_KEY_PRESS) ? "down" : "up"); + trace_gd_key_event(gdk_keycode, qemu_keycode, + (key->type == GDK_KEY_PRESS) ? "down" : "up"); for (i = 0; i < ARRAY_SIZE(modifier_keycode); i++) { if (qemu_keycode == modifier_keycode[i]) { @@ -746,21 +776,20 @@ static gboolean gd_key_event(GtkWidget *widget, GdkEventKey *key, void *opaque) } } - if (qemu_keycode & SCANCODE_GREY) { - kbd_put_keycode(SCANCODE_EMUL0); - } - - if (key->type == GDK_KEY_PRESS) { - kbd_put_keycode(qemu_keycode & SCANCODE_KEYCODEMASK); - } else if (key->type == GDK_KEY_RELEASE) { - kbd_put_keycode(qemu_keycode | SCANCODE_UP); - } else { - g_assert_not_reached(); - } + qemu_input_event_send_key_number(s->dcl.con, qemu_keycode, + key->type == GDK_KEY_PRESS); return TRUE; } +static gboolean gd_event(GtkWidget *widget, GdkEvent *event, void *opaque) +{ + if (event->type == GDK_MOTION_NOTIFY) { + return gd_motion_event(widget, &event->motion, opaque); + } + return FALSE; +} + /** Window Menu Actions **/ static void gd_menu_pause(GtkMenuItem *item, void *opaque) @@ -959,8 +988,8 @@ static void gd_ungrab_keyboard(GtkDisplayState *s) static void gd_grab_pointer(GtkDisplayState *s) { -#if GTK_CHECK_VERSION(3, 0, 0) GdkDisplay *display = gtk_widget_get_display(s->drawing_area); +#if GTK_CHECK_VERSION(3, 0, 0) GdkDeviceManager *mgr = gdk_display_get_device_manager(display); GList *devices = gdk_device_manager_list_devices(mgr, GDK_DEVICE_TYPE_MASTER); @@ -984,6 +1013,8 @@ static void gd_grab_pointer(GtkDisplayState *s) tmp = tmp->next; } g_list_free(devices); + gdk_device_get_position(gdk_device_manager_get_client_pointer(mgr), + NULL, &s->grab_x_root, &s->grab_y_root); #else gdk_pointer_grab(gtk_widget_get_window(s->drawing_area), FALSE, /* All events to come to our window directly */ @@ -995,13 +1026,15 @@ static void gd_grab_pointer(GtkDisplayState *s) NULL, /* Allow cursor to move over entire desktop */ s->null_cursor, GDK_CURRENT_TIME); + gdk_display_get_pointer(display, NULL, + &s->grab_x_root, &s->grab_y_root, NULL); #endif } static void gd_ungrab_pointer(GtkDisplayState *s) { -#if GTK_CHECK_VERSION(3, 0, 0) GdkDisplay *display = gtk_widget_get_display(s->drawing_area); +#if GTK_CHECK_VERSION(3, 0, 0) GdkDeviceManager *mgr = gdk_display_get_device_manager(display); GList *devices = gdk_device_manager_list_devices(mgr, GDK_DEVICE_TYPE_MASTER); @@ -1015,8 +1048,14 @@ static void gd_ungrab_pointer(GtkDisplayState *s) tmp = tmp->next; } g_list_free(devices); + gdk_device_warp(gdk_device_manager_get_client_pointer(mgr), + gtk_widget_get_screen(s->drawing_area), + s->grab_x_root, s->grab_y_root); #else gdk_pointer_ungrab(GDK_CURRENT_TIME); + gdk_display_warp_pointer(display, + gtk_widget_get_screen(s->drawing_area), + s->grab_x_root, s->grab_y_root); #endif } @@ -1066,6 +1105,7 @@ static void gd_change_page(GtkNotebook *nb, gpointer arg1, guint arg2, if (arg2 == 0) { gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(s->vga_item), TRUE); } else { +#if defined(CONFIG_VTE) VirtualConsole *vc = &s->vc[arg2 - 1]; VteTerminal *term = VTE_TERMINAL(vc->terminal); int width, height; @@ -1075,6 +1115,9 @@ static void gd_change_page(GtkNotebook *nb, gpointer arg1, guint arg2, gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(vc->menu_item), TRUE); gtk_widget_set_size_request(vc->terminal, width, height); +#else + g_assert_not_reached(); +#endif } gtk_widget_set_sensitive(s->grab_item, on_vga); @@ -1120,7 +1163,7 @@ static int gd_vc_chr_write(CharDriverState *chr, const uint8_t *buf, int len) { VirtualConsole *vc = chr->opaque; - return write(vc->fd, buf, len); + return vc ? write(vc->fd, buf, len) : len; } static int nb_vcs; @@ -1145,6 +1188,7 @@ void early_gtk_display_init(void) register_vc_handler(gd_vc_handler); } +#if defined(CONFIG_VTE) static gboolean gd_vc_in(GIOChannel *chan, GIOCondition cond, void *opaque) { VirtualConsole *vc = opaque; @@ -1160,10 +1204,12 @@ static gboolean gd_vc_in(GIOChannel *chan, GIOCondition cond, void *opaque) return TRUE; } +#endif static GSList *gd_vc_init(GtkDisplayState *s, VirtualConsole *vc, int index, GSList *group, GtkWidget *view_menu) { +#if defined(CONFIG_VTE) const char *label; char buffer[32]; char path[32]; @@ -1233,6 +1279,7 @@ static GSList *gd_vc_init(GtkDisplayState *s, VirtualConsole *vc, int index, GSL chan = g_io_channel_unix_new(vc->fd); g_io_add_watch(chan, G_IO_IN, gd_vc_in, vc); +#endif /* CONFIG_VTE */ return group; } @@ -1255,12 +1302,14 @@ static void gd_connect_signals(GtkDisplayState *s) g_signal_connect(s->drawing_area, "expose-event", G_CALLBACK(gd_expose_event), s); #endif - g_signal_connect(s->drawing_area, "motion-notify-event", - G_CALLBACK(gd_motion_event), s); + g_signal_connect(s->drawing_area, "event", + G_CALLBACK(gd_event), s); g_signal_connect(s->drawing_area, "button-press-event", G_CALLBACK(gd_button_event), s); g_signal_connect(s->drawing_area, "button-release-event", G_CALLBACK(gd_button_event), s); + g_signal_connect(s->drawing_area, "scroll-event", + G_CALLBACK(gd_scroll_event), s); g_signal_connect(s->drawing_area, "key-press-event", G_CALLBACK(gd_key_event), s); g_signal_connect(s->drawing_area, "key-release-event", @@ -1447,7 +1496,7 @@ static const DisplayChangeListenerOps dcl_ops = { .dpy_cursor_define = gd_cursor_define, }; -void gtk_display_init(DisplayState *ds, bool full_screen) +void gtk_display_init(DisplayState *ds, bool full_screen, bool grab_on_hover) { GtkDisplayState *s = g_malloc0(sizeof(*s)); char *filename; @@ -1526,6 +1575,9 @@ void gtk_display_init(DisplayState *ds, bool full_screen) if (full_screen) { gtk_menu_item_activate(GTK_MENU_ITEM(s->full_screen_item)); } + if (grab_on_hover) { + gtk_menu_item_activate(GTK_MENU_ITEM(s->grab_on_hover_item)); + } register_displaychangelistener(&s->dcl); diff --git a/ui/input-legacy.c b/ui/input-legacy.c new file mode 100644 index 000000000..1aa2605b7 --- /dev/null +++ b/ui/input-legacy.c @@ -0,0 +1,468 @@ +/* + * QEMU System Emulator + * + * Copyright (c) 2003-2008 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "sysemu/sysemu.h" +#include "monitor/monitor.h" +#include "ui/console.h" +#include "qapi/error.h" +#include "qmp-commands.h" +#include "qapi-types.h" +#include "ui/keymaps.h" +#include "ui/input.h" + +struct QEMUPutMouseEntry { + QEMUPutMouseEvent *qemu_put_mouse_event; + void *qemu_put_mouse_event_opaque; + int qemu_put_mouse_event_absolute; + + /* new input core */ + QemuInputHandler h; + QemuInputHandlerState *s; + int axis[INPUT_AXIS_MAX]; + int buttons; +}; + +struct QEMUPutKbdEntry { + QEMUPutKBDEvent *put_kbd; + void *opaque; + QemuInputHandlerState *s; +}; + +struct QEMUPutLEDEntry { + QEMUPutLEDEvent *put_led; + void *opaque; + QTAILQ_ENTRY(QEMUPutLEDEntry) next; +}; + +static QTAILQ_HEAD(, QEMUPutLEDEntry) led_handlers = + QTAILQ_HEAD_INITIALIZER(led_handlers); +static QTAILQ_HEAD(, QEMUPutMouseEntry) mouse_handlers = + QTAILQ_HEAD_INITIALIZER(mouse_handlers); + +static const int key_defs[] = { + [Q_KEY_CODE_SHIFT] = 0x2a, + [Q_KEY_CODE_SHIFT_R] = 0x36, + + [Q_KEY_CODE_ALT] = 0x38, + [Q_KEY_CODE_ALT_R] = 0xb8, + [Q_KEY_CODE_ALTGR] = 0x64, + [Q_KEY_CODE_ALTGR_R] = 0xe4, + [Q_KEY_CODE_CTRL] = 0x1d, + [Q_KEY_CODE_CTRL_R] = 0x9d, + + [Q_KEY_CODE_MENU] = 0xdd, + + [Q_KEY_CODE_ESC] = 0x01, + + [Q_KEY_CODE_1] = 0x02, + [Q_KEY_CODE_2] = 0x03, + [Q_KEY_CODE_3] = 0x04, + [Q_KEY_CODE_4] = 0x05, + [Q_KEY_CODE_5] = 0x06, + [Q_KEY_CODE_6] = 0x07, + [Q_KEY_CODE_7] = 0x08, + [Q_KEY_CODE_8] = 0x09, + [Q_KEY_CODE_9] = 0x0a, + [Q_KEY_CODE_0] = 0x0b, + [Q_KEY_CODE_MINUS] = 0x0c, + [Q_KEY_CODE_EQUAL] = 0x0d, + [Q_KEY_CODE_BACKSPACE] = 0x0e, + + [Q_KEY_CODE_TAB] = 0x0f, + [Q_KEY_CODE_Q] = 0x10, + [Q_KEY_CODE_W] = 0x11, + [Q_KEY_CODE_E] = 0x12, + [Q_KEY_CODE_R] = 0x13, + [Q_KEY_CODE_T] = 0x14, + [Q_KEY_CODE_Y] = 0x15, + [Q_KEY_CODE_U] = 0x16, + [Q_KEY_CODE_I] = 0x17, + [Q_KEY_CODE_O] = 0x18, + [Q_KEY_CODE_P] = 0x19, + [Q_KEY_CODE_BRACKET_LEFT] = 0x1a, + [Q_KEY_CODE_BRACKET_RIGHT] = 0x1b, + [Q_KEY_CODE_RET] = 0x1c, + + [Q_KEY_CODE_A] = 0x1e, + [Q_KEY_CODE_S] = 0x1f, + [Q_KEY_CODE_D] = 0x20, + [Q_KEY_CODE_F] = 0x21, + [Q_KEY_CODE_G] = 0x22, + [Q_KEY_CODE_H] = 0x23, + [Q_KEY_CODE_J] = 0x24, + [Q_KEY_CODE_K] = 0x25, + [Q_KEY_CODE_L] = 0x26, + [Q_KEY_CODE_SEMICOLON] = 0x27, + [Q_KEY_CODE_APOSTROPHE] = 0x28, + [Q_KEY_CODE_GRAVE_ACCENT] = 0x29, + + [Q_KEY_CODE_BACKSLASH] = 0x2b, + [Q_KEY_CODE_Z] = 0x2c, + [Q_KEY_CODE_X] = 0x2d, + [Q_KEY_CODE_C] = 0x2e, + [Q_KEY_CODE_V] = 0x2f, + [Q_KEY_CODE_B] = 0x30, + [Q_KEY_CODE_N] = 0x31, + [Q_KEY_CODE_M] = 0x32, + [Q_KEY_CODE_COMMA] = 0x33, + [Q_KEY_CODE_DOT] = 0x34, + [Q_KEY_CODE_SLASH] = 0x35, + + [Q_KEY_CODE_ASTERISK] = 0x37, + + [Q_KEY_CODE_SPC] = 0x39, + [Q_KEY_CODE_CAPS_LOCK] = 0x3a, + [Q_KEY_CODE_F1] = 0x3b, + [Q_KEY_CODE_F2] = 0x3c, + [Q_KEY_CODE_F3] = 0x3d, + [Q_KEY_CODE_F4] = 0x3e, + [Q_KEY_CODE_F5] = 0x3f, + [Q_KEY_CODE_F6] = 0x40, + [Q_KEY_CODE_F7] = 0x41, + [Q_KEY_CODE_F8] = 0x42, + [Q_KEY_CODE_F9] = 0x43, + [Q_KEY_CODE_F10] = 0x44, + [Q_KEY_CODE_NUM_LOCK] = 0x45, + [Q_KEY_CODE_SCROLL_LOCK] = 0x46, + + [Q_KEY_CODE_KP_DIVIDE] = 0xb5, + [Q_KEY_CODE_KP_MULTIPLY] = 0x37, + [Q_KEY_CODE_KP_SUBTRACT] = 0x4a, + [Q_KEY_CODE_KP_ADD] = 0x4e, + [Q_KEY_CODE_KP_ENTER] = 0x9c, + [Q_KEY_CODE_KP_DECIMAL] = 0x53, + [Q_KEY_CODE_SYSRQ] = 0x54, + + [Q_KEY_CODE_KP_0] = 0x52, + [Q_KEY_CODE_KP_1] = 0x4f, + [Q_KEY_CODE_KP_2] = 0x50, + [Q_KEY_CODE_KP_3] = 0x51, + [Q_KEY_CODE_KP_4] = 0x4b, + [Q_KEY_CODE_KP_5] = 0x4c, + [Q_KEY_CODE_KP_6] = 0x4d, + [Q_KEY_CODE_KP_7] = 0x47, + [Q_KEY_CODE_KP_8] = 0x48, + [Q_KEY_CODE_KP_9] = 0x49, + + [Q_KEY_CODE_LESS] = 0x56, + + [Q_KEY_CODE_F11] = 0x57, + [Q_KEY_CODE_F12] = 0x58, + + [Q_KEY_CODE_PRINT] = 0xb7, + + [Q_KEY_CODE_HOME] = 0xc7, + [Q_KEY_CODE_PGUP] = 0xc9, + [Q_KEY_CODE_PGDN] = 0xd1, + [Q_KEY_CODE_END] = 0xcf, + + [Q_KEY_CODE_LEFT] = 0xcb, + [Q_KEY_CODE_UP] = 0xc8, + [Q_KEY_CODE_DOWN] = 0xd0, + [Q_KEY_CODE_RIGHT] = 0xcd, + + [Q_KEY_CODE_INSERT] = 0xd2, + [Q_KEY_CODE_DELETE] = 0xd3, +#ifdef NEED_CPU_H +#if defined(TARGET_SPARC) && !defined(TARGET_SPARC64) + [Q_KEY_CODE_STOP] = 0xf0, + [Q_KEY_CODE_AGAIN] = 0xf1, + [Q_KEY_CODE_PROPS] = 0xf2, + [Q_KEY_CODE_UNDO] = 0xf3, + [Q_KEY_CODE_FRONT] = 0xf4, + [Q_KEY_CODE_COPY] = 0xf5, + [Q_KEY_CODE_OPEN] = 0xf6, + [Q_KEY_CODE_PASTE] = 0xf7, + [Q_KEY_CODE_FIND] = 0xf8, + [Q_KEY_CODE_CUT] = 0xf9, + [Q_KEY_CODE_LF] = 0xfa, + [Q_KEY_CODE_HELP] = 0xfb, + [Q_KEY_CODE_META_L] = 0xfc, + [Q_KEY_CODE_META_R] = 0xfd, + [Q_KEY_CODE_COMPOSE] = 0xfe, +#endif +#endif + [Q_KEY_CODE_MAX] = 0, +}; + +int index_from_key(const char *key) +{ + int i; + + for (i = 0; QKeyCode_lookup[i] != NULL; i++) { + if (!strcmp(key, QKeyCode_lookup[i])) { + break; + } + } + + /* Return Q_KEY_CODE_MAX if the key is invalid */ + return i; +} + +static int *keycodes; +static int keycodes_size; +static QEMUTimer *key_timer; + +static int keycode_from_keyvalue(const KeyValue *value) +{ + if (value->kind == KEY_VALUE_KIND_QCODE) { + return key_defs[value->qcode]; + } else { + assert(value->kind == KEY_VALUE_KIND_NUMBER); + return value->number; + } +} + +static void free_keycodes(void) +{ + g_free(keycodes); + keycodes = NULL; + keycodes_size = 0; +} + +static void release_keys(void *opaque) +{ + while (keycodes_size > 0) { + qemu_input_event_send_key_number(NULL, keycodes[--keycodes_size], + false); + } + + free_keycodes(); +} + +void qmp_send_key(KeyValueList *keys, bool has_hold_time, int64_t hold_time, + Error **errp) +{ + int keycode; + KeyValueList *p; + + if (!key_timer) { + key_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, release_keys, NULL); + } + + if (keycodes != NULL) { + timer_del(key_timer); + release_keys(NULL); + } + + if (!has_hold_time) { + hold_time = 100; + } + + for (p = keys; p != NULL; p = p->next) { + /* key down events */ + keycode = keycode_from_keyvalue(p->value); + if (keycode < 0x01 || keycode > 0xff) { + error_setg(errp, "invalid hex keycode 0x%x", keycode); + free_keycodes(); + return; + } + + qemu_input_event_send_key_number(NULL, keycode, true); + + keycodes = g_realloc(keycodes, sizeof(int) * (keycodes_size + 1)); + keycodes[keycodes_size++] = keycode; + } + + /* delayed key up events */ + timer_mod(key_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + muldiv64(get_ticks_per_sec(), hold_time, 1000)); +} + +static void legacy_kbd_event(DeviceState *dev, QemuConsole *src, + InputEvent *evt) +{ + QEMUPutKbdEntry *entry = (QEMUPutKbdEntry *)dev; + int keycode = keycode_from_keyvalue(evt->key->key); + + if (!entry || !entry->put_kbd) { + return; + } + if (evt->key->key->kind == KEY_VALUE_KIND_QCODE && + evt->key->key->qcode == Q_KEY_CODE_PAUSE) { + /* specific case */ + int v = evt->key->down ? 0 : 0x80; + entry->put_kbd(entry->opaque, 0xe1); + entry->put_kbd(entry->opaque, 0x1d | v); + entry->put_kbd(entry->opaque, 0x45 | v); + return; + } + if (keycode & SCANCODE_GREY) { + entry->put_kbd(entry->opaque, SCANCODE_EMUL0); + keycode &= ~SCANCODE_GREY; + } + if (!evt->key->down) { + keycode |= SCANCODE_UP; + } + entry->put_kbd(entry->opaque, keycode); +} + +static QemuInputHandler legacy_kbd_handler = { + .name = "legacy-kbd", + .mask = INPUT_EVENT_MASK_KEY, + .event = legacy_kbd_event, +}; + +QEMUPutKbdEntry *qemu_add_kbd_event_handler(QEMUPutKBDEvent *func, void *opaque) +{ + QEMUPutKbdEntry *entry; + + entry = g_new0(QEMUPutKbdEntry, 1); + entry->put_kbd = func; + entry->opaque = opaque; + entry->s = qemu_input_handler_register((DeviceState *)entry, + &legacy_kbd_handler); + qemu_input_handler_activate(entry->s); + return entry; +} + +void qemu_remove_kbd_event_handler(QEMUPutKbdEntry *entry) +{ + qemu_input_handler_unregister(entry->s); + g_free(entry); +} + +static void legacy_mouse_event(DeviceState *dev, QemuConsole *src, + InputEvent *evt) +{ + static const int bmap[INPUT_BUTTON_MAX] = { + [INPUT_BUTTON_LEFT] = MOUSE_EVENT_LBUTTON, + [INPUT_BUTTON_MIDDLE] = MOUSE_EVENT_MBUTTON, + [INPUT_BUTTON_RIGHT] = MOUSE_EVENT_RBUTTON, + }; + QEMUPutMouseEntry *s = (QEMUPutMouseEntry *)dev; + + switch (evt->kind) { + case INPUT_EVENT_KIND_BTN: + if (evt->btn->down) { + s->buttons |= bmap[evt->btn->button]; + } else { + s->buttons &= ~bmap[evt->btn->button]; + } + if (evt->btn->down && evt->btn->button == INPUT_BUTTON_WHEEL_UP) { + s->qemu_put_mouse_event(s->qemu_put_mouse_event_opaque, + s->axis[INPUT_AXIS_X], + s->axis[INPUT_AXIS_Y], + -1, + s->buttons); + } + if (evt->btn->down && evt->btn->button == INPUT_BUTTON_WHEEL_DOWN) { + s->qemu_put_mouse_event(s->qemu_put_mouse_event_opaque, + s->axis[INPUT_AXIS_X], + s->axis[INPUT_AXIS_Y], + 1, + s->buttons); + } + break; + case INPUT_EVENT_KIND_ABS: + s->axis[evt->abs->axis] = evt->abs->value; + break; + case INPUT_EVENT_KIND_REL: + s->axis[evt->rel->axis] += evt->rel->value; + break; + default: + break; + } +} + +static void legacy_mouse_sync(DeviceState *dev) +{ + QEMUPutMouseEntry *s = (QEMUPutMouseEntry *)dev; + + s->qemu_put_mouse_event(s->qemu_put_mouse_event_opaque, + s->axis[INPUT_AXIS_X], + s->axis[INPUT_AXIS_Y], + 0, + s->buttons); + + if (!s->qemu_put_mouse_event_absolute) { + s->axis[INPUT_AXIS_X] = 0; + s->axis[INPUT_AXIS_Y] = 0; + } +} + +QEMUPutMouseEntry *qemu_add_mouse_event_handler(QEMUPutMouseEvent *func, + void *opaque, int absolute, + const char *name) +{ + QEMUPutMouseEntry *s; + + s = g_malloc0(sizeof(QEMUPutMouseEntry)); + + s->qemu_put_mouse_event = func; + s->qemu_put_mouse_event_opaque = opaque; + s->qemu_put_mouse_event_absolute = absolute; + + s->h.name = name; + s->h.mask = INPUT_EVENT_MASK_BTN | + (absolute ? INPUT_EVENT_MASK_ABS : INPUT_EVENT_MASK_REL); + s->h.event = legacy_mouse_event; + s->h.sync = legacy_mouse_sync; + s->s = qemu_input_handler_register((DeviceState *)s, + &s->h); + + return s; +} + +void qemu_activate_mouse_event_handler(QEMUPutMouseEntry *entry) +{ + qemu_input_handler_activate(entry->s); +} + +void qemu_remove_mouse_event_handler(QEMUPutMouseEntry *entry) +{ + qemu_input_handler_unregister(entry->s); + + g_free(entry); +} + +QEMUPutLEDEntry *qemu_add_led_event_handler(QEMUPutLEDEvent *func, + void *opaque) +{ + QEMUPutLEDEntry *s; + + s = g_malloc0(sizeof(QEMUPutLEDEntry)); + + s->put_led = func; + s->opaque = opaque; + QTAILQ_INSERT_TAIL(&led_handlers, s, next); + return s; +} + +void qemu_remove_led_event_handler(QEMUPutLEDEntry *entry) +{ + if (entry == NULL) + return; + QTAILQ_REMOVE(&led_handlers, entry, next); + g_free(entry); +} + +void kbd_put_ledstate(int ledstate) +{ + QEMUPutLEDEntry *cursor; + + QTAILQ_FOREACH(cursor, &led_handlers, next) { + cursor->put_led(cursor->opaque, ledstate); + } +} diff --git a/ui/input.c b/ui/input.c index 92c44ca81..1ed0e783b 100644 --- a/ui/input.c +++ b/ui/input.c @@ -1,520 +1,336 @@ -/* - * QEMU System Emulator - * - * Copyright (c) 2003-2008 Fabrice Bellard - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - #include "sysemu/sysemu.h" -#include "monitor/monitor.h" -#include "ui/console.h" -#include "qapi/error.h" -#include "qmp-commands.h" #include "qapi-types.h" -#include "ui/keymaps.h" - -struct QEMUPutMouseEntry { - QEMUPutMouseEvent *qemu_put_mouse_event; - void *qemu_put_mouse_event_opaque; - int qemu_put_mouse_event_absolute; - char *qemu_put_mouse_event_name; - - int index; - - /* used internally by qemu for handling mice */ - QTAILQ_ENTRY(QEMUPutMouseEntry) node; -}; - -struct QEMUPutKbdEntry { - QEMUPutKBDEvent *put_kbd; - void *opaque; - QTAILQ_ENTRY(QEMUPutKbdEntry) next; -}; +#include "qmp-commands.h" +#include "trace.h" +#include "ui/input.h" +#include "ui/console.h" -struct QEMUPutLEDEntry { - QEMUPutLEDEvent *put_led; - void *opaque; - QTAILQ_ENTRY(QEMUPutLEDEntry) next; +struct QemuInputHandlerState { + DeviceState *dev; + QemuInputHandler *handler; + int id; + int events; + QTAILQ_ENTRY(QemuInputHandlerState) node; }; - -static QTAILQ_HEAD(, QEMUPutLEDEntry) led_handlers = - QTAILQ_HEAD_INITIALIZER(led_handlers); -static QTAILQ_HEAD(, QEMUPutKbdEntry) kbd_handlers = - QTAILQ_HEAD_INITIALIZER(kbd_handlers); -static QTAILQ_HEAD(, QEMUPutMouseEntry) mouse_handlers = - QTAILQ_HEAD_INITIALIZER(mouse_handlers); +static QTAILQ_HEAD(, QemuInputHandlerState) handlers = + QTAILQ_HEAD_INITIALIZER(handlers); static NotifierList mouse_mode_notifiers = NOTIFIER_LIST_INITIALIZER(mouse_mode_notifiers); -static const int key_defs[] = { - [Q_KEY_CODE_SHIFT] = 0x2a, - [Q_KEY_CODE_SHIFT_R] = 0x36, - - [Q_KEY_CODE_ALT] = 0x38, - [Q_KEY_CODE_ALT_R] = 0xb8, - [Q_KEY_CODE_ALTGR] = 0x64, - [Q_KEY_CODE_ALTGR_R] = 0xe4, - [Q_KEY_CODE_CTRL] = 0x1d, - [Q_KEY_CODE_CTRL_R] = 0x9d, - - [Q_KEY_CODE_MENU] = 0xdd, - - [Q_KEY_CODE_ESC] = 0x01, - - [Q_KEY_CODE_1] = 0x02, - [Q_KEY_CODE_2] = 0x03, - [Q_KEY_CODE_3] = 0x04, - [Q_KEY_CODE_4] = 0x05, - [Q_KEY_CODE_5] = 0x06, - [Q_KEY_CODE_6] = 0x07, - [Q_KEY_CODE_7] = 0x08, - [Q_KEY_CODE_8] = 0x09, - [Q_KEY_CODE_9] = 0x0a, - [Q_KEY_CODE_0] = 0x0b, - [Q_KEY_CODE_MINUS] = 0x0c, - [Q_KEY_CODE_EQUAL] = 0x0d, - [Q_KEY_CODE_BACKSPACE] = 0x0e, - - [Q_KEY_CODE_TAB] = 0x0f, - [Q_KEY_CODE_Q] = 0x10, - [Q_KEY_CODE_W] = 0x11, - [Q_KEY_CODE_E] = 0x12, - [Q_KEY_CODE_R] = 0x13, - [Q_KEY_CODE_T] = 0x14, - [Q_KEY_CODE_Y] = 0x15, - [Q_KEY_CODE_U] = 0x16, - [Q_KEY_CODE_I] = 0x17, - [Q_KEY_CODE_O] = 0x18, - [Q_KEY_CODE_P] = 0x19, - [Q_KEY_CODE_BRACKET_LEFT] = 0x1a, - [Q_KEY_CODE_BRACKET_RIGHT] = 0x1b, - [Q_KEY_CODE_RET] = 0x1c, - - [Q_KEY_CODE_A] = 0x1e, - [Q_KEY_CODE_S] = 0x1f, - [Q_KEY_CODE_D] = 0x20, - [Q_KEY_CODE_F] = 0x21, - [Q_KEY_CODE_G] = 0x22, - [Q_KEY_CODE_H] = 0x23, - [Q_KEY_CODE_J] = 0x24, - [Q_KEY_CODE_K] = 0x25, - [Q_KEY_CODE_L] = 0x26, - [Q_KEY_CODE_SEMICOLON] = 0x27, - [Q_KEY_CODE_APOSTROPHE] = 0x28, - [Q_KEY_CODE_GRAVE_ACCENT] = 0x29, - - [Q_KEY_CODE_BACKSLASH] = 0x2b, - [Q_KEY_CODE_Z] = 0x2c, - [Q_KEY_CODE_X] = 0x2d, - [Q_KEY_CODE_C] = 0x2e, - [Q_KEY_CODE_V] = 0x2f, - [Q_KEY_CODE_B] = 0x30, - [Q_KEY_CODE_N] = 0x31, - [Q_KEY_CODE_M] = 0x32, - [Q_KEY_CODE_COMMA] = 0x33, - [Q_KEY_CODE_DOT] = 0x34, - [Q_KEY_CODE_SLASH] = 0x35, - - [Q_KEY_CODE_ASTERISK] = 0x37, - - [Q_KEY_CODE_SPC] = 0x39, - [Q_KEY_CODE_CAPS_LOCK] = 0x3a, - [Q_KEY_CODE_F1] = 0x3b, - [Q_KEY_CODE_F2] = 0x3c, - [Q_KEY_CODE_F3] = 0x3d, - [Q_KEY_CODE_F4] = 0x3e, - [Q_KEY_CODE_F5] = 0x3f, - [Q_KEY_CODE_F6] = 0x40, - [Q_KEY_CODE_F7] = 0x41, - [Q_KEY_CODE_F8] = 0x42, - [Q_KEY_CODE_F9] = 0x43, - [Q_KEY_CODE_F10] = 0x44, - [Q_KEY_CODE_NUM_LOCK] = 0x45, - [Q_KEY_CODE_SCROLL_LOCK] = 0x46, - - [Q_KEY_CODE_KP_DIVIDE] = 0xb5, - [Q_KEY_CODE_KP_MULTIPLY] = 0x37, - [Q_KEY_CODE_KP_SUBTRACT] = 0x4a, - [Q_KEY_CODE_KP_ADD] = 0x4e, - [Q_KEY_CODE_KP_ENTER] = 0x9c, - [Q_KEY_CODE_KP_DECIMAL] = 0x53, - [Q_KEY_CODE_SYSRQ] = 0x54, - - [Q_KEY_CODE_KP_0] = 0x52, - [Q_KEY_CODE_KP_1] = 0x4f, - [Q_KEY_CODE_KP_2] = 0x50, - [Q_KEY_CODE_KP_3] = 0x51, - [Q_KEY_CODE_KP_4] = 0x4b, - [Q_KEY_CODE_KP_5] = 0x4c, - [Q_KEY_CODE_KP_6] = 0x4d, - [Q_KEY_CODE_KP_7] = 0x47, - [Q_KEY_CODE_KP_8] = 0x48, - [Q_KEY_CODE_KP_9] = 0x49, - - [Q_KEY_CODE_LESS] = 0x56, - - [Q_KEY_CODE_F11] = 0x57, - [Q_KEY_CODE_F12] = 0x58, - - [Q_KEY_CODE_PRINT] = 0xb7, - - [Q_KEY_CODE_HOME] = 0xc7, - [Q_KEY_CODE_PGUP] = 0xc9, - [Q_KEY_CODE_PGDN] = 0xd1, - [Q_KEY_CODE_END] = 0xcf, - - [Q_KEY_CODE_LEFT] = 0xcb, - [Q_KEY_CODE_UP] = 0xc8, - [Q_KEY_CODE_DOWN] = 0xd0, - [Q_KEY_CODE_RIGHT] = 0xcd, - - [Q_KEY_CODE_INSERT] = 0xd2, - [Q_KEY_CODE_DELETE] = 0xd3, -#ifdef NEED_CPU_H -#if defined(TARGET_SPARC) && !defined(TARGET_SPARC64) - [Q_KEY_CODE_STOP] = 0xf0, - [Q_KEY_CODE_AGAIN] = 0xf1, - [Q_KEY_CODE_PROPS] = 0xf2, - [Q_KEY_CODE_UNDO] = 0xf3, - [Q_KEY_CODE_FRONT] = 0xf4, - [Q_KEY_CODE_COPY] = 0xf5, - [Q_KEY_CODE_OPEN] = 0xf6, - [Q_KEY_CODE_PASTE] = 0xf7, - [Q_KEY_CODE_FIND] = 0xf8, - [Q_KEY_CODE_CUT] = 0xf9, - [Q_KEY_CODE_LF] = 0xfa, - [Q_KEY_CODE_HELP] = 0xfb, - [Q_KEY_CODE_META_L] = 0xfc, - [Q_KEY_CODE_META_R] = 0xfd, - [Q_KEY_CODE_COMPOSE] = 0xfe, -#endif -#endif - [Q_KEY_CODE_MAX] = 0, -}; - -int index_from_key(const char *key) +QemuInputHandlerState *qemu_input_handler_register(DeviceState *dev, + QemuInputHandler *handler) { - int i; + QemuInputHandlerState *s = g_new0(QemuInputHandlerState, 1); + static int id = 1; - for (i = 0; QKeyCode_lookup[i] != NULL; i++) { - if (!strcmp(key, QKeyCode_lookup[i])) { - break; - } - } + s->dev = dev; + s->handler = handler; + s->id = id++; + QTAILQ_INSERT_TAIL(&handlers, s, node); - /* Return Q_KEY_CODE_MAX if the key is invalid */ - return i; + qemu_input_check_mode_change(); + return s; } -int index_from_keycode(int code) +void qemu_input_handler_activate(QemuInputHandlerState *s) { - int i; - - for (i = 0; i < Q_KEY_CODE_MAX; i++) { - if (key_defs[i] == code) { - break; - } - } - - /* Return Q_KEY_CODE_MAX if the code is invalid */ - return i; + QTAILQ_REMOVE(&handlers, s, node); + QTAILQ_INSERT_HEAD(&handlers, s, node); + qemu_input_check_mode_change(); } -static int *keycodes; -static int keycodes_size; -static QEMUTimer *key_timer; - -static int keycode_from_keyvalue(const KeyValue *value) +void qemu_input_handler_unregister(QemuInputHandlerState *s) { - if (value->kind == KEY_VALUE_KIND_QCODE) { - return key_defs[value->qcode]; - } else { - assert(value->kind == KEY_VALUE_KIND_NUMBER); - return value->number; - } + QTAILQ_REMOVE(&handlers, s, node); + g_free(s); + qemu_input_check_mode_change(); } -static void free_keycodes(void) +static QemuInputHandlerState* +qemu_input_find_handler(uint32_t mask) { - g_free(keycodes); - keycodes = NULL; - keycodes_size = 0; -} + QemuInputHandlerState *s; -static void release_keys(void *opaque) -{ - while (keycodes_size > 0) { - if (keycodes[--keycodes_size] & SCANCODE_GREY) { - kbd_put_keycode(SCANCODE_EMUL0); + QTAILQ_FOREACH(s, &handlers, node) { + if (mask & s->handler->mask) { + return s; } - kbd_put_keycode(keycodes[keycodes_size] | SCANCODE_UP); } - - free_keycodes(); + return NULL; } -void qmp_send_key(KeyValueList *keys, bool has_hold_time, int64_t hold_time, - Error **errp) +static void qemu_input_transform_abs_rotate(InputEvent *evt) { - int keycode; - KeyValueList *p; - - if (!key_timer) { - key_timer = qemu_new_timer_ns(vm_clock, release_keys, NULL); - } - - if (keycodes != NULL) { - qemu_del_timer(key_timer); - release_keys(NULL); - } - - if (!has_hold_time) { - hold_time = 100; - } - - for (p = keys; p != NULL; p = p->next) { - /* key down events */ - keycode = keycode_from_keyvalue(p->value); - if (keycode < 0x01 || keycode > 0xff) { - error_setg(errp, "invalid hex keycode 0x%x", keycode); - free_keycodes(); - return; + switch (graphic_rotate) { + case 90: + if (evt->abs->axis == INPUT_AXIS_X) { + evt->abs->axis = INPUT_AXIS_Y; + } else if (evt->abs->axis == INPUT_AXIS_Y) { + evt->abs->axis = INPUT_AXIS_X; + evt->abs->value = INPUT_EVENT_ABS_SIZE - 1 - evt->abs->value; } - - if (keycode & SCANCODE_GREY) { - kbd_put_keycode(SCANCODE_EMUL0); + break; + case 180: + evt->abs->value = INPUT_EVENT_ABS_SIZE - 1 - evt->abs->value; + break; + case 270: + if (evt->abs->axis == INPUT_AXIS_X) { + evt->abs->axis = INPUT_AXIS_Y; + evt->abs->value = INPUT_EVENT_ABS_SIZE - 1 - evt->abs->value; + } else if (evt->abs->axis == INPUT_AXIS_Y) { + evt->abs->axis = INPUT_AXIS_X; } - kbd_put_keycode(keycode & SCANCODE_KEYCODEMASK); - - keycodes = g_realloc(keycodes, sizeof(int) * (keycodes_size + 1)); - keycodes[keycodes_size++] = keycode; + break; } - - /* delayed key up events */ - qemu_mod_timer(key_timer, qemu_get_clock_ns(vm_clock) + - muldiv64(get_ticks_per_sec(), hold_time, 1000)); } -QEMUPutKbdEntry *qemu_add_kbd_event_handler(QEMUPutKBDEvent *func, void *opaque) +static void qemu_input_event_trace(QemuConsole *src, InputEvent *evt) { - QEMUPutKbdEntry *entry; + const char *name; + int idx = -1; - entry = g_malloc0(sizeof(QEMUPutKbdEntry)); - entry->put_kbd = func; - entry->opaque = opaque; - QTAILQ_INSERT_HEAD(&kbd_handlers, entry, next); - return entry; + if (src) { + idx = qemu_console_get_index(src); + } + switch (evt->kind) { + case INPUT_EVENT_KIND_KEY: + switch (evt->key->key->kind) { + case KEY_VALUE_KIND_NUMBER: + trace_input_event_key_number(idx, evt->key->key->number, + evt->key->down); + break; + case KEY_VALUE_KIND_QCODE: + name = QKeyCode_lookup[evt->key->key->qcode]; + trace_input_event_key_qcode(idx, name, evt->key->down); + break; + case KEY_VALUE_KIND_MAX: + /* keep gcc happy */ + break; + } + break; + case INPUT_EVENT_KIND_BTN: + name = InputButton_lookup[evt->btn->button]; + trace_input_event_btn(idx, name, evt->btn->down); + break; + case INPUT_EVENT_KIND_REL: + name = InputAxis_lookup[evt->rel->axis]; + trace_input_event_rel(idx, name, evt->rel->value); + break; + case INPUT_EVENT_KIND_ABS: + name = InputAxis_lookup[evt->abs->axis]; + trace_input_event_abs(idx, name, evt->abs->value); + break; + case INPUT_EVENT_KIND_MAX: + /* keep gcc happy */ + break; + } } -void qemu_remove_kbd_event_handler(QEMUPutKbdEntry *entry) +void qemu_input_event_send(QemuConsole *src, InputEvent *evt) { - QTAILQ_REMOVE(&kbd_handlers, entry, next); -} + QemuInputHandlerState *s; -static void check_mode_change(void) -{ - static int current_is_absolute, current_has_absolute; - int is_absolute; - int has_absolute; + if (!runstate_is_running() && !runstate_check(RUN_STATE_SUSPENDED)) { + return; + } - is_absolute = kbd_mouse_is_absolute(); - has_absolute = kbd_mouse_has_absolute(); + qemu_input_event_trace(src, evt); - if (is_absolute != current_is_absolute || - has_absolute != current_has_absolute) { - notifier_list_notify(&mouse_mode_notifiers, NULL); + /* pre processing */ + if (graphic_rotate && (evt->kind == INPUT_EVENT_KIND_ABS)) { + qemu_input_transform_abs_rotate(evt); } - current_is_absolute = is_absolute; - current_has_absolute = has_absolute; + /* send event */ + s = qemu_input_find_handler(1 << evt->kind); + if (!s) { + return; + } + s->handler->event(s->dev, src, evt); + s->events++; } -QEMUPutMouseEntry *qemu_add_mouse_event_handler(QEMUPutMouseEvent *func, - void *opaque, int absolute, - const char *name) +void qemu_input_event_sync(void) { - QEMUPutMouseEntry *s; - static int mouse_index = 0; - - s = g_malloc0(sizeof(QEMUPutMouseEntry)); - - s->qemu_put_mouse_event = func; - s->qemu_put_mouse_event_opaque = opaque; - s->qemu_put_mouse_event_absolute = absolute; - s->qemu_put_mouse_event_name = g_strdup(name); - s->index = mouse_index++; + QemuInputHandlerState *s; - QTAILQ_INSERT_TAIL(&mouse_handlers, s, node); + if (!runstate_is_running() && !runstate_check(RUN_STATE_SUSPENDED)) { + return; + } - check_mode_change(); + trace_input_event_sync(); - return s; + QTAILQ_FOREACH(s, &handlers, node) { + if (!s->events) { + continue; + } + if (s->handler->sync) { + s->handler->sync(s->dev); + } + s->events = 0; + } } -void qemu_activate_mouse_event_handler(QEMUPutMouseEntry *entry) +InputEvent *qemu_input_event_new_key(KeyValue *key, bool down) { - QTAILQ_REMOVE(&mouse_handlers, entry, node); - QTAILQ_INSERT_HEAD(&mouse_handlers, entry, node); - - check_mode_change(); + InputEvent *evt = g_new0(InputEvent, 1); + evt->key = g_new0(InputKeyEvent, 1); + evt->kind = INPUT_EVENT_KIND_KEY; + evt->key->key = key; + evt->key->down = down; + return evt; } -void qemu_remove_mouse_event_handler(QEMUPutMouseEntry *entry) +void qemu_input_event_send_key(QemuConsole *src, KeyValue *key, bool down) { - QTAILQ_REMOVE(&mouse_handlers, entry, node); - - g_free(entry->qemu_put_mouse_event_name); - g_free(entry); - - check_mode_change(); + InputEvent *evt; + evt = qemu_input_event_new_key(key, down); + qemu_input_event_send(src, evt); + qemu_input_event_sync(); + qapi_free_InputEvent(evt); } -QEMUPutLEDEntry *qemu_add_led_event_handler(QEMUPutLEDEvent *func, - void *opaque) +void qemu_input_event_send_key_number(QemuConsole *src, int num, bool down) { - QEMUPutLEDEntry *s; + KeyValue *key = g_new0(KeyValue, 1); + key->kind = KEY_VALUE_KIND_NUMBER; + key->number = num; + qemu_input_event_send_key(src, key, down); +} - s = g_malloc0(sizeof(QEMUPutLEDEntry)); +void qemu_input_event_send_key_qcode(QemuConsole *src, QKeyCode q, bool down) +{ + KeyValue *key = g_new0(KeyValue, 1); + key->kind = KEY_VALUE_KIND_QCODE; + key->qcode = q; + qemu_input_event_send_key(src, key, down); +} - s->put_led = func; - s->opaque = opaque; - QTAILQ_INSERT_TAIL(&led_handlers, s, next); - return s; +InputEvent *qemu_input_event_new_btn(InputButton btn, bool down) +{ + InputEvent *evt = g_new0(InputEvent, 1); + evt->btn = g_new0(InputBtnEvent, 1); + evt->kind = INPUT_EVENT_KIND_BTN; + evt->btn->button = btn; + evt->btn->down = down; + return evt; } -void qemu_remove_led_event_handler(QEMUPutLEDEntry *entry) +void qemu_input_queue_btn(QemuConsole *src, InputButton btn, bool down) { - if (entry == NULL) - return; - QTAILQ_REMOVE(&led_handlers, entry, next); - g_free(entry); + InputEvent *evt; + evt = qemu_input_event_new_btn(btn, down); + qemu_input_event_send(src, evt); + qapi_free_InputEvent(evt); } -void kbd_put_keycode(int keycode) +void qemu_input_update_buttons(QemuConsole *src, uint32_t *button_map, + uint32_t button_old, uint32_t button_new) { - QEMUPutKbdEntry *entry = QTAILQ_FIRST(&kbd_handlers); + InputButton btn; + uint32_t mask; - if (!runstate_is_running() && !runstate_check(RUN_STATE_SUSPENDED)) { - return; - } - if (entry) { - entry->put_kbd(entry->opaque, keycode); + for (btn = 0; btn < INPUT_BUTTON_MAX; btn++) { + mask = button_map[btn]; + if ((button_old & mask) == (button_new & mask)) { + continue; + } + qemu_input_queue_btn(src, btn, button_new & mask); } } -void kbd_put_ledstate(int ledstate) +bool qemu_input_is_absolute(void) { - QEMUPutLEDEntry *cursor; + QemuInputHandlerState *s; - QTAILQ_FOREACH(cursor, &led_handlers, next) { - cursor->put_led(cursor->opaque, ledstate); - } + s = qemu_input_find_handler(INPUT_EVENT_MASK_REL | INPUT_EVENT_MASK_ABS); + return (s != NULL) && (s->handler->mask & INPUT_EVENT_MASK_ABS); } -void kbd_mouse_event(int dx, int dy, int dz, int buttons_state) +int qemu_input_scale_axis(int value, int size_in, int size_out) { - QEMUPutMouseEntry *entry; - QEMUPutMouseEvent *mouse_event; - void *mouse_event_opaque; - int width, height; - - if (!runstate_is_running() && !runstate_check(RUN_STATE_SUSPENDED)) { - return; + if (size_in < 2) { + return size_out / 2; } - if (QTAILQ_EMPTY(&mouse_handlers)) { - return; - } - - entry = QTAILQ_FIRST(&mouse_handlers); + return (int64_t)value * (size_out - 1) / (size_in - 1); +} - mouse_event = entry->qemu_put_mouse_event; - mouse_event_opaque = entry->qemu_put_mouse_event_opaque; +InputEvent *qemu_input_event_new_move(InputEventKind kind, + InputAxis axis, int value) +{ + InputEvent *evt = g_new0(InputEvent, 1); + InputMoveEvent *move = g_new0(InputMoveEvent, 1); + + evt->kind = kind; + evt->data = move; + move->axis = axis; + move->value = value; + return evt; +} - if (mouse_event) { - if (entry->qemu_put_mouse_event_absolute) { - width = 0x7fff; - height = 0x7fff; - } else { - width = graphic_width - 1; - height = graphic_height - 1; - } +void qemu_input_queue_rel(QemuConsole *src, InputAxis axis, int value) +{ + InputEvent *evt; + evt = qemu_input_event_new_move(INPUT_EVENT_KIND_REL, axis, value); + qemu_input_event_send(src, evt); + qapi_free_InputEvent(evt); +} - switch (graphic_rotate) { - case 0: - mouse_event(mouse_event_opaque, - dx, dy, dz, buttons_state); - break; - case 90: - mouse_event(mouse_event_opaque, - width - dy, dx, dz, buttons_state); - break; - case 180: - mouse_event(mouse_event_opaque, - width - dx, height - dy, dz, buttons_state); - break; - case 270: - mouse_event(mouse_event_opaque, - dy, height - dx, dz, buttons_state); - break; - } - } +void qemu_input_queue_abs(QemuConsole *src, InputAxis axis, int value, int size) +{ + InputEvent *evt; + int scaled = qemu_input_scale_axis(value, size, INPUT_EVENT_ABS_SIZE); + evt = qemu_input_event_new_move(INPUT_EVENT_KIND_ABS, axis, scaled); + qemu_input_event_send(src, evt); + qapi_free_InputEvent(evt); } -int kbd_mouse_is_absolute(void) +void qemu_input_check_mode_change(void) { - if (QTAILQ_EMPTY(&mouse_handlers)) { - return 0; + static int current_is_absolute; + int is_absolute; + + is_absolute = qemu_input_is_absolute(); + + if (is_absolute != current_is_absolute) { + trace_input_mouse_mode(is_absolute); + notifier_list_notify(&mouse_mode_notifiers, NULL); } - return QTAILQ_FIRST(&mouse_handlers)->qemu_put_mouse_event_absolute; + current_is_absolute = is_absolute; } -int kbd_mouse_has_absolute(void) +void qemu_add_mouse_mode_change_notifier(Notifier *notify) { - QEMUPutMouseEntry *entry; - - QTAILQ_FOREACH(entry, &mouse_handlers, node) { - if (entry->qemu_put_mouse_event_absolute) { - return 1; - } - } + notifier_list_add(&mouse_mode_notifiers, notify); +} - return 0; +void qemu_remove_mouse_mode_change_notifier(Notifier *notify) +{ + notifier_remove(notify); } MouseInfoList *qmp_query_mice(Error **errp) { MouseInfoList *mice_list = NULL; - QEMUPutMouseEntry *cursor; + MouseInfoList *info; + QemuInputHandlerState *s; bool current = true; - QTAILQ_FOREACH(cursor, &mouse_handlers, node) { - MouseInfoList *info = g_malloc0(sizeof(*info)); - info->value = g_malloc0(sizeof(*info->value)); - info->value->name = g_strdup(cursor->qemu_put_mouse_event_name); - info->value->index = cursor->index; - info->value->absolute = !!cursor->qemu_put_mouse_event_absolute; + QTAILQ_FOREACH(s, &handlers, node) { + if (!(s->handler->mask & + (INPUT_EVENT_MASK_REL | INPUT_EVENT_MASK_ABS))) { + continue; + } + + info = g_new0(MouseInfoList, 1); + info->value = g_new0(MouseInfo, 1); + info->value->index = s->id; + info->value->name = g_strdup(s->handler->name); + info->value->absolute = s->handler->mask & INPUT_EVENT_MASK_ABS; info->value->current = current; current = false; - info->next = mice_list; mice_list = info; } @@ -524,36 +340,27 @@ MouseInfoList *qmp_query_mice(Error **errp) void do_mouse_set(Monitor *mon, const QDict *qdict) { - QEMUPutMouseEntry *cursor; + QemuInputHandlerState *s; int index = qdict_get_int(qdict, "index"); int found = 0; - if (QTAILQ_EMPTY(&mouse_handlers)) { - monitor_printf(mon, "No mouse devices connected\n"); - return; - } - - QTAILQ_FOREACH(cursor, &mouse_handlers, node) { - if (cursor->index == index) { - found = 1; - qemu_activate_mouse_event_handler(cursor); - break; + QTAILQ_FOREACH(s, &handlers, node) { + if (s->id != index) { + continue; } + if (!(s->handler->mask & (INPUT_EVENT_MASK_REL | + INPUT_EVENT_MASK_ABS))) { + error_report("Input device '%s' is not a mouse", s->handler->name); + return; + } + found = 1; + qemu_input_handler_activate(s); + break; } if (!found) { - monitor_printf(mon, "Mouse at given index not found\n"); + error_report("Mouse at index '%d' not found", index); } - check_mode_change(); -} - -void qemu_add_mouse_mode_change_notifier(Notifier *notify) -{ - notifier_list_add(&mouse_mode_notifiers, notify); -} - -void qemu_remove_mouse_mode_change_notifier(Notifier *notify) -{ - notifier_remove(notify); + qemu_input_check_mode_change(); } diff --git a/ui/keymaps.c b/ui/keymaps.c index f373cc53d..80d658d90 100644 --- a/ui/keymaps.c +++ b/ui/keymaps.c @@ -33,6 +33,12 @@ static int get_keysym(const name2keysym_t *table, if (!strcmp(p->name, name)) return p->keysym; } + if (name[0] == 'U' && strlen(name) == 5) { /* try unicode Uxxxx */ + char *end; + int ret = (int)strtoul(name + 1, &end, 16); + if (*end == '\0' && ret > 0) + return ret; + } return 0; } @@ -26,10 +26,13 @@ #undef WIN32_LEAN_AND_MEAN #include <SDL.h> + +#if SDL_MAJOR_VERSION == 1 #include <SDL_syswm.h> #include "qemu-common.h" #include "ui/console.h" +#include "ui/input.h" #include "sysemu/sysemu.h" #include "x_keymap.h" #include "sdl_zoom.h" @@ -86,6 +89,7 @@ static void sdl_update(DisplayChangeListener *dcl, static void do_sdl_resize(int width, int height, int bpp) { int flags; + SDL_Surface *tmp_screen; // printf("resizing to %d %d\n", w, h); @@ -98,12 +102,26 @@ static void do_sdl_resize(int width, int height, int bpp) if (gui_noframe) flags |= SDL_NOFRAME; - real_screen = SDL_SetVideoMode(width, height, bpp, flags); + tmp_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); + if (!tmp_screen) { + fprintf(stderr, "Could not open SDL display (%dx%dx%d): %s\n", + width, height, bpp, SDL_GetError()); + exit(1); + } + } else { + /* + * Revert to the previous video mode if the change of resizing or + * resolution failed. + */ + if (!tmp_screen) { + fprintf(stderr, "Failed to set SDL display (%dx%dx%d): %s\n", + width, height, bpp, SDL_GetError()); + return; + } } + + real_screen = tmp_screen; } static void sdl_switch(DisplayChangeListener *dcl, @@ -246,9 +264,7 @@ 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); + qemu_input_event_send_key_number(dcl->con, i, false); modifiers_state[i] = 0; } } @@ -256,16 +272,12 @@ static void reset_keys(void) static void sdl_process_key(SDL_KeyboardEvent *ev) { - int keycode, v; + int keycode; 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); + qemu_input_event_send_key_qcode(dcl->con, Q_KEY_CODE_PAUSE, + ev->type == SDL_KEYDOWN); return; } @@ -297,19 +309,15 @@ static void sdl_process_key(SDL_KeyboardEvent *ev) 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); + qemu_input_event_send_key_number(dcl->con, keycode, true); + qemu_input_event_send_key_number(dcl->con, keycode, false); return; #endif } /* now send the key code */ - 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); + qemu_input_event_send_key_number(dcl->con, keycode, + ev->type == SDL_KEYDOWN); } static void sdl_update_caption(void) @@ -345,7 +353,7 @@ static void sdl_hide_cursor(void) if (!cursor_hide) return; - if (kbd_mouse_is_absolute()) { + if (qemu_input_is_absolute()) { SDL_ShowCursor(1); SDL_SetCursor(sdl_cursor_hidden); } else { @@ -358,10 +366,10 @@ static void sdl_show_cursor(void) if (!cursor_hide) return; - if (!kbd_mouse_is_absolute() || !qemu_console_is_graphic(NULL)) { + if (!qemu_input_is_absolute() || !qemu_console_is_graphic(NULL)) { SDL_ShowCursor(1); if (guest_cursor && - (gui_grab || kbd_mouse_is_absolute() || absolute_enabled)) + (gui_grab || qemu_input_is_absolute() || absolute_enabled)) SDL_SetCursor(guest_sprite); else SDL_SetCursor(sdl_cursor_normal); @@ -380,8 +388,9 @@ static void sdl_grab_start(void) } if (guest_cursor) { SDL_SetCursor(guest_sprite); - if (!kbd_mouse_is_absolute() && !absolute_enabled) + if (!qemu_input_is_absolute() && !absolute_enabled) { SDL_WarpMouse(guest_x, guest_y); + } } else sdl_hide_cursor(); SDL_WM_GrabInput(SDL_GRAB_ON); @@ -410,7 +419,7 @@ static void absolute_mouse_grab(void) static void sdl_mouse_mode_change(Notifier *notify, void *data) { - if (kbd_mouse_is_absolute()) { + if (qemu_input_is_absolute()) { if (!absolute_enabled) { absolute_enabled = 1; if (qemu_console_is_graphic(NULL)) { @@ -425,33 +434,40 @@ static void sdl_mouse_mode_change(Notifier *notify, void *data) } } -static void sdl_send_mouse_event(int dx, int dy, int dz, int x, int y, int state) +static void sdl_send_mouse_event(int dx, int dy, int x, int y, int state) { - int buttons = 0; - - if (state & SDL_BUTTON(SDL_BUTTON_LEFT)) { - buttons |= MOUSE_EVENT_LBUTTON; - } - if (state & SDL_BUTTON(SDL_BUTTON_RIGHT)) { - buttons |= MOUSE_EVENT_RBUTTON; - } - if (state & SDL_BUTTON(SDL_BUTTON_MIDDLE)) { - buttons |= MOUSE_EVENT_MBUTTON; - } - - if (kbd_mouse_is_absolute()) { - dx = x * 0x7FFF / (real_screen->w - 1); - dy = y * 0x7FFF / (real_screen->h - 1); - } else if (guest_cursor) { - x -= guest_x; - y -= guest_y; - guest_x += x; - guest_y += y; - dx = x; - dy = y; + static uint32_t bmap[INPUT_BUTTON_MAX] = { + [INPUT_BUTTON_LEFT] = SDL_BUTTON(SDL_BUTTON_LEFT), + [INPUT_BUTTON_MIDDLE] = SDL_BUTTON(SDL_BUTTON_MIDDLE), + [INPUT_BUTTON_RIGHT] = SDL_BUTTON(SDL_BUTTON_RIGHT), + [INPUT_BUTTON_WHEEL_UP] = SDL_BUTTON(SDL_BUTTON_WHEELUP), + [INPUT_BUTTON_WHEEL_DOWN] = SDL_BUTTON(SDL_BUTTON_WHEELDOWN), + }; + static uint32_t prev_state; + + if (prev_state != state) { + qemu_input_update_buttons(dcl->con, bmap, prev_state, state); + prev_state = state; + } + + if (qemu_input_is_absolute()) { + qemu_input_queue_abs(dcl->con, INPUT_AXIS_X, x, + real_screen->w); + qemu_input_queue_abs(dcl->con, INPUT_AXIS_Y, y, + real_screen->h); + } else { + if (guest_cursor) { + x -= guest_x; + y -= guest_y; + guest_x += x; + guest_y += y; + dx = x; + dy = y; + } + qemu_input_queue_rel(dcl->con, INPUT_AXIS_X, dx); + qemu_input_queue_rel(dcl->con, INPUT_AXIS_Y, dy); } - - kbd_mouse_event(dx, dy, dz, buttons); + qemu_input_event_sync(); } static void sdl_scale(int width, int height) @@ -679,7 +695,7 @@ static void handle_mousemotion(SDL_Event *ev) int max_x, max_y; if (qemu_console_is_graphic(NULL) && - (kbd_mouse_is_absolute() || absolute_enabled)) { + (qemu_input_is_absolute() || absolute_enabled)) { max_x = real_screen->w - 1; max_y = real_screen->h - 1; if (gui_grab && (ev->motion.x == 0 || ev->motion.y == 0 || @@ -692,8 +708,8 @@ static void handle_mousemotion(SDL_Event *ev) sdl_grab_start(); } } - if (gui_grab || kbd_mouse_is_absolute() || absolute_enabled) { - sdl_send_mouse_event(ev->motion.xrel, ev->motion.yrel, 0, + if (gui_grab || qemu_input_is_absolute() || absolute_enabled) { + sdl_send_mouse_event(ev->motion.xrel, ev->motion.yrel, ev->motion.x, ev->motion.y, ev->motion.state); } } @@ -702,35 +718,24 @@ static void handle_mousebutton(SDL_Event *ev) { int buttonstate = SDL_GetMouseState(NULL, NULL); SDL_MouseButtonEvent *bev; - int dz; if (!qemu_console_is_graphic(NULL)) { return; } bev = &ev->button; - if (!gui_grab && !kbd_mouse_is_absolute()) { + if (!gui_grab && !qemu_input_is_absolute()) { if (ev->type == SDL_MOUSEBUTTONUP && bev->button == SDL_BUTTON_LEFT) { /* start grabbing all events */ sdl_grab_start(); } } else { - dz = 0; if (ev->type == SDL_MOUSEBUTTONDOWN) { buttonstate |= SDL_BUTTON(bev->button); } else { buttonstate &= ~SDL_BUTTON(bev->button); } -#ifdef SDL_BUTTON_WHEELUP - if (bev->button == SDL_BUTTON_WHEELUP && - ev->type == SDL_MOUSEBUTTONDOWN) { - dz = -1; - } else if (bev->button == SDL_BUTTON_WHEELDOWN && - ev->type == SDL_MOUSEBUTTONDOWN) { - dz = 1; - } -#endif - sdl_send_mouse_event(0, 0, dz, bev->x, bev->y, buttonstate); + sdl_send_mouse_event(0, 0, bev->x, bev->y, buttonstate); } } @@ -745,7 +750,7 @@ static void handle_activation(SDL_Event *ev) } #endif if (!gui_grab && ev->active.gain && qemu_console_is_graphic(NULL) && - (kbd_mouse_is_absolute() || absolute_enabled)) { + (qemu_input_is_absolute() || absolute_enabled)) { absolute_mouse_grab(); } if (ev->active.state & SDL_APPACTIVE) { @@ -817,10 +822,11 @@ static void sdl_mouse_warp(DisplayChangeListener *dcl, if (on) { if (!guest_cursor) sdl_show_cursor(); - if (gui_grab || kbd_mouse_is_absolute() || absolute_enabled) { + if (gui_grab || qemu_input_is_absolute() || absolute_enabled) { SDL_SetCursor(guest_sprite); - if (!kbd_mouse_is_absolute() && !absolute_enabled) + if (!qemu_input_is_absolute() && !absolute_enabled) { SDL_WarpMouse(x, y); + } } } else if (gui_grab) sdl_hide_cursor(); @@ -848,7 +854,7 @@ static void sdl_mouse_define(DisplayChangeListener *dcl, g_free(mask); if (guest_cursor && - (gui_grab || kbd_mouse_is_absolute() || absolute_enabled)) + (gui_grab || qemu_input_is_absolute() || absolute_enabled)) SDL_SetCursor(guest_sprite); } @@ -951,3 +957,4 @@ void sdl_display_init(DisplayState *ds, int full_screen, int no_frame) atexit(sdl_cleanup); } +#endif diff --git a/ui/sdl2-keymap.h b/ui/sdl2-keymap.h new file mode 100644 index 000000000..5a12f4543 --- /dev/null +++ b/ui/sdl2-keymap.h @@ -0,0 +1,266 @@ + +/* map SDL2 scancodes to QKeyCode */ + +static const int sdl2_scancode_to_qcode[SDL_NUM_SCANCODES] = { + [SDL_SCANCODE_A] = Q_KEY_CODE_A, + [SDL_SCANCODE_B] = Q_KEY_CODE_B, + [SDL_SCANCODE_C] = Q_KEY_CODE_C, + [SDL_SCANCODE_D] = Q_KEY_CODE_D, + [SDL_SCANCODE_E] = Q_KEY_CODE_E, + [SDL_SCANCODE_F] = Q_KEY_CODE_F, + [SDL_SCANCODE_G] = Q_KEY_CODE_G, + [SDL_SCANCODE_H] = Q_KEY_CODE_H, + [SDL_SCANCODE_I] = Q_KEY_CODE_I, + [SDL_SCANCODE_J] = Q_KEY_CODE_J, + [SDL_SCANCODE_K] = Q_KEY_CODE_K, + [SDL_SCANCODE_L] = Q_KEY_CODE_L, + [SDL_SCANCODE_M] = Q_KEY_CODE_M, + [SDL_SCANCODE_N] = Q_KEY_CODE_N, + [SDL_SCANCODE_O] = Q_KEY_CODE_O, + [SDL_SCANCODE_P] = Q_KEY_CODE_P, + [SDL_SCANCODE_Q] = Q_KEY_CODE_Q, + [SDL_SCANCODE_R] = Q_KEY_CODE_R, + [SDL_SCANCODE_S] = Q_KEY_CODE_S, + [SDL_SCANCODE_T] = Q_KEY_CODE_T, + [SDL_SCANCODE_U] = Q_KEY_CODE_U, + [SDL_SCANCODE_V] = Q_KEY_CODE_V, + [SDL_SCANCODE_W] = Q_KEY_CODE_W, + [SDL_SCANCODE_X] = Q_KEY_CODE_X, + [SDL_SCANCODE_Y] = Q_KEY_CODE_Y, + [SDL_SCANCODE_Z] = Q_KEY_CODE_Z, + + [SDL_SCANCODE_1] = Q_KEY_CODE_1, + [SDL_SCANCODE_2] = Q_KEY_CODE_2, + [SDL_SCANCODE_3] = Q_KEY_CODE_3, + [SDL_SCANCODE_4] = Q_KEY_CODE_4, + [SDL_SCANCODE_5] = Q_KEY_CODE_5, + [SDL_SCANCODE_6] = Q_KEY_CODE_6, + [SDL_SCANCODE_7] = Q_KEY_CODE_7, + [SDL_SCANCODE_8] = Q_KEY_CODE_8, + [SDL_SCANCODE_9] = Q_KEY_CODE_9, + [SDL_SCANCODE_0] = Q_KEY_CODE_0, + + [SDL_SCANCODE_RETURN] = Q_KEY_CODE_RET, + [SDL_SCANCODE_ESCAPE] = Q_KEY_CODE_ESC, + [SDL_SCANCODE_BACKSPACE] = Q_KEY_CODE_BACKSPACE, + [SDL_SCANCODE_TAB] = Q_KEY_CODE_TAB, + [SDL_SCANCODE_SPACE] = Q_KEY_CODE_SPC, + [SDL_SCANCODE_MINUS] = Q_KEY_CODE_MINUS, + [SDL_SCANCODE_EQUALS] = Q_KEY_CODE_EQUAL, + [SDL_SCANCODE_LEFTBRACKET] = Q_KEY_CODE_BRACKET_LEFT, + [SDL_SCANCODE_RIGHTBRACKET] = Q_KEY_CODE_BRACKET_RIGHT, + [SDL_SCANCODE_BACKSLASH] = Q_KEY_CODE_BACKSLASH, +#if 0 + [SDL_SCANCODE_NONUSHASH] = Q_KEY_CODE_NONUSHASH, +#endif + [SDL_SCANCODE_SEMICOLON] = Q_KEY_CODE_SEMICOLON, + [SDL_SCANCODE_APOSTROPHE] = Q_KEY_CODE_APOSTROPHE, + [SDL_SCANCODE_GRAVE] = Q_KEY_CODE_GRAVE_ACCENT, + [SDL_SCANCODE_COMMA] = Q_KEY_CODE_COMMA, + [SDL_SCANCODE_PERIOD] = Q_KEY_CODE_DOT, + [SDL_SCANCODE_SLASH] = Q_KEY_CODE_SLASH, + [SDL_SCANCODE_CAPSLOCK] = Q_KEY_CODE_CAPS_LOCK, + + [SDL_SCANCODE_F1] = Q_KEY_CODE_F1, + [SDL_SCANCODE_F2] = Q_KEY_CODE_F2, + [SDL_SCANCODE_F3] = Q_KEY_CODE_F3, + [SDL_SCANCODE_F4] = Q_KEY_CODE_F4, + [SDL_SCANCODE_F5] = Q_KEY_CODE_F5, + [SDL_SCANCODE_F6] = Q_KEY_CODE_F6, + [SDL_SCANCODE_F7] = Q_KEY_CODE_F7, + [SDL_SCANCODE_F8] = Q_KEY_CODE_F8, + [SDL_SCANCODE_F9] = Q_KEY_CODE_F9, + [SDL_SCANCODE_F10] = Q_KEY_CODE_F10, + [SDL_SCANCODE_F11] = Q_KEY_CODE_F11, + [SDL_SCANCODE_F12] = Q_KEY_CODE_F12, + + [SDL_SCANCODE_PRINTSCREEN] = Q_KEY_CODE_PRINT, + [SDL_SCANCODE_SCROLLLOCK] = Q_KEY_CODE_SCROLL_LOCK, + [SDL_SCANCODE_PAUSE] = Q_KEY_CODE_PAUSE, + [SDL_SCANCODE_INSERT] = Q_KEY_CODE_INSERT, + [SDL_SCANCODE_HOME] = Q_KEY_CODE_HOME, + [SDL_SCANCODE_PAGEUP] = Q_KEY_CODE_PGUP, + [SDL_SCANCODE_DELETE] = Q_KEY_CODE_DELETE, + [SDL_SCANCODE_END] = Q_KEY_CODE_END, + [SDL_SCANCODE_PAGEDOWN] = Q_KEY_CODE_PGDN, + [SDL_SCANCODE_RIGHT] = Q_KEY_CODE_RIGHT, + [SDL_SCANCODE_LEFT] = Q_KEY_CODE_LEFT, + [SDL_SCANCODE_DOWN] = Q_KEY_CODE_DOWN, + [SDL_SCANCODE_UP] = Q_KEY_CODE_UP, + [SDL_SCANCODE_NUMLOCKCLEAR] = Q_KEY_CODE_NUM_LOCK, + + [SDL_SCANCODE_KP_DIVIDE] = Q_KEY_CODE_KP_DIVIDE, + [SDL_SCANCODE_KP_MULTIPLY] = Q_KEY_CODE_KP_MULTIPLY, + [SDL_SCANCODE_KP_MINUS] = Q_KEY_CODE_KP_SUBTRACT, + [SDL_SCANCODE_KP_PLUS] = Q_KEY_CODE_KP_ADD, + [SDL_SCANCODE_KP_ENTER] = Q_KEY_CODE_KP_ENTER, + [SDL_SCANCODE_KP_1] = Q_KEY_CODE_KP_1, + [SDL_SCANCODE_KP_2] = Q_KEY_CODE_KP_2, + [SDL_SCANCODE_KP_3] = Q_KEY_CODE_KP_3, + [SDL_SCANCODE_KP_4] = Q_KEY_CODE_KP_4, + [SDL_SCANCODE_KP_5] = Q_KEY_CODE_KP_5, + [SDL_SCANCODE_KP_6] = Q_KEY_CODE_KP_6, + [SDL_SCANCODE_KP_7] = Q_KEY_CODE_KP_7, + [SDL_SCANCODE_KP_8] = Q_KEY_CODE_KP_8, + [SDL_SCANCODE_KP_9] = Q_KEY_CODE_KP_9, + [SDL_SCANCODE_KP_0] = Q_KEY_CODE_KP_0, + [SDL_SCANCODE_KP_PERIOD] = Q_KEY_CODE_KP_DECIMAL, +#if 0 + [SDL_SCANCODE_NONUSBACKSLASH] = Q_KEY_CODE_NONUSBACKSLASH, + [SDL_SCANCODE_APPLICATION] = Q_KEY_CODE_APPLICATION, + [SDL_SCANCODE_POWER] = Q_KEY_CODE_POWER, + [SDL_SCANCODE_KP_EQUALS] = Q_KEY_CODE_KP_EQUALS, + + [SDL_SCANCODE_F13] = Q_KEY_CODE_F13, + [SDL_SCANCODE_F14] = Q_KEY_CODE_F14, + [SDL_SCANCODE_F15] = Q_KEY_CODE_F15, + [SDL_SCANCODE_F16] = Q_KEY_CODE_F16, + [SDL_SCANCODE_F17] = Q_KEY_CODE_F17, + [SDL_SCANCODE_F18] = Q_KEY_CODE_F18, + [SDL_SCANCODE_F19] = Q_KEY_CODE_F19, + [SDL_SCANCODE_F20] = Q_KEY_CODE_F20, + [SDL_SCANCODE_F21] = Q_KEY_CODE_F21, + [SDL_SCANCODE_F22] = Q_KEY_CODE_F22, + [SDL_SCANCODE_F23] = Q_KEY_CODE_F23, + [SDL_SCANCODE_F24] = Q_KEY_CODE_F24, + + [SDL_SCANCODE_EXECUTE] = Q_KEY_CODE_EXECUTE, +#endif + [SDL_SCANCODE_HELP] = Q_KEY_CODE_HELP, + [SDL_SCANCODE_MENU] = Q_KEY_CODE_MENU, +#if 0 + [SDL_SCANCODE_SELECT] = Q_KEY_CODE_SELECT, +#endif + [SDL_SCANCODE_STOP] = Q_KEY_CODE_STOP, + [SDL_SCANCODE_AGAIN] = Q_KEY_CODE_AGAIN, + [SDL_SCANCODE_UNDO] = Q_KEY_CODE_UNDO, + [SDL_SCANCODE_CUT] = Q_KEY_CODE_CUT, + [SDL_SCANCODE_COPY] = Q_KEY_CODE_COPY, + [SDL_SCANCODE_PASTE] = Q_KEY_CODE_PASTE, + [SDL_SCANCODE_FIND] = Q_KEY_CODE_FIND, +#if 0 + [SDL_SCANCODE_MUTE] = Q_KEY_CODE_MUTE, + [SDL_SCANCODE_VOLUMEUP] = Q_KEY_CODE_VOLUMEUP, + [SDL_SCANCODE_VOLUMEDOWN] = Q_KEY_CODE_VOLUMEDOWN, + + [SDL_SCANCODE_KP_COMMA] = Q_KEY_CODE_KP_COMMA, + [SDL_SCANCODE_KP_EQUALSAS400] = Q_KEY_CODE_KP_EQUALSAS400, + + [SDL_SCANCODE_INTERNATIONAL1] = Q_KEY_CODE_INTERNATIONAL1, + [SDL_SCANCODE_INTERNATIONAL2] = Q_KEY_CODE_INTERNATIONAL2, + [SDL_SCANCODE_INTERNATIONAL3] = Q_KEY_CODE_INTERNATIONAL3, + [SDL_SCANCODE_INTERNATIONAL4] = Q_KEY_CODE_INTERNATIONAL4, + [SDL_SCANCODE_INTERNATIONAL5] = Q_KEY_CODE_INTERNATIONAL5, + [SDL_SCANCODE_INTERNATIONAL6] = Q_KEY_CODE_INTERNATIONAL6, + [SDL_SCANCODE_INTERNATIONAL7] = Q_KEY_CODE_INTERNATIONAL7, + [SDL_SCANCODE_INTERNATIONAL8] = Q_KEY_CODE_INTERNATIONAL8, + [SDL_SCANCODE_INTERNATIONAL9] = Q_KEY_CODE_INTERNATIONAL9, + [SDL_SCANCODE_LANG1] = Q_KEY_CODE_LANG1, + [SDL_SCANCODE_LANG2] = Q_KEY_CODE_LANG2, + [SDL_SCANCODE_LANG3] = Q_KEY_CODE_LANG3, + [SDL_SCANCODE_LANG4] = Q_KEY_CODE_LANG4, + [SDL_SCANCODE_LANG5] = Q_KEY_CODE_LANG5, + [SDL_SCANCODE_LANG6] = Q_KEY_CODE_LANG6, + [SDL_SCANCODE_LANG7] = Q_KEY_CODE_LANG7, + [SDL_SCANCODE_LANG8] = Q_KEY_CODE_LANG8, + [SDL_SCANCODE_LANG9] = Q_KEY_CODE_LANG9, + [SDL_SCANCODE_ALTERASE] = Q_KEY_CODE_ALTERASE, +#endif + [SDL_SCANCODE_SYSREQ] = Q_KEY_CODE_SYSRQ, +#if 0 + [SDL_SCANCODE_CANCEL] = Q_KEY_CODE_CANCEL, + [SDL_SCANCODE_CLEAR] = Q_KEY_CODE_CLEAR, + [SDL_SCANCODE_PRIOR] = Q_KEY_CODE_PRIOR, + [SDL_SCANCODE_RETURN2] = Q_KEY_CODE_RETURN2, + [SDL_SCANCODE_SEPARATOR] = Q_KEY_CODE_SEPARATOR, + [SDL_SCANCODE_OUT] = Q_KEY_CODE_OUT, + [SDL_SCANCODE_OPER] = Q_KEY_CODE_OPER, + [SDL_SCANCODE_CLEARAGAIN] = Q_KEY_CODE_CLEARAGAIN, + [SDL_SCANCODE_CRSEL] = Q_KEY_CODE_CRSEL, + [SDL_SCANCODE_EXSEL] = Q_KEY_CODE_EXSEL, + [SDL_SCANCODE_KP_00] = Q_KEY_CODE_KP_00, + [SDL_SCANCODE_KP_000] = Q_KEY_CODE_KP_000, + [SDL_SCANCODE_THOUSANDSSEPARATOR] = Q_KEY_CODE_THOUSANDSSEPARATOR, + [SDL_SCANCODE_DECIMALSEPARATOR] = Q_KEY_CODE_DECIMALSEPARATOR, + [SDL_SCANCODE_CURRENCYUNIT] = Q_KEY_CODE_CURRENCYUNIT, + [SDL_SCANCODE_CURRENCYSUBUNIT] = Q_KEY_CODE_CURRENCYSUBUNIT, + [SDL_SCANCODE_KP_LEFTPAREN] = Q_KEY_CODE_KP_LEFTPAREN, + [SDL_SCANCODE_KP_RIGHTPAREN] = Q_KEY_CODE_KP_RIGHTPAREN, + [SDL_SCANCODE_KP_LEFTBRACE] = Q_KEY_CODE_KP_LEFTBRACE, + [SDL_SCANCODE_KP_RIGHTBRACE] = Q_KEY_CODE_KP_RIGHTBRACE, + [SDL_SCANCODE_KP_TAB] = Q_KEY_CODE_KP_TAB, + [SDL_SCANCODE_KP_BACKSPACE] = Q_KEY_CODE_KP_BACKSPACE, + [SDL_SCANCODE_KP_A] = Q_KEY_CODE_KP_A, + [SDL_SCANCODE_KP_B] = Q_KEY_CODE_KP_B, + [SDL_SCANCODE_KP_C] = Q_KEY_CODE_KP_C, + [SDL_SCANCODE_KP_D] = Q_KEY_CODE_KP_D, + [SDL_SCANCODE_KP_E] = Q_KEY_CODE_KP_E, + [SDL_SCANCODE_KP_F] = Q_KEY_CODE_KP_F, + [SDL_SCANCODE_KP_XOR] = Q_KEY_CODE_KP_XOR, + [SDL_SCANCODE_KP_POWER] = Q_KEY_CODE_KP_POWER, + [SDL_SCANCODE_KP_PERCENT] = Q_KEY_CODE_KP_PERCENT, + [SDL_SCANCODE_KP_LESS] = Q_KEY_CODE_KP_LESS, + [SDL_SCANCODE_KP_GREATER] = Q_KEY_CODE_KP_GREATER, + [SDL_SCANCODE_KP_AMPERSAND] = Q_KEY_CODE_KP_AMPERSAND, + [SDL_SCANCODE_KP_DBLAMPERSAND] = Q_KEY_CODE_KP_DBLAMPERSAND, + [SDL_SCANCODE_KP_VERTICALBAR] = Q_KEY_CODE_KP_VERTICALBAR, + [SDL_SCANCODE_KP_DBLVERTICALBAR] = Q_KEY_CODE_KP_DBLVERTICALBAR, + [SDL_SCANCODE_KP_COLON] = Q_KEY_CODE_KP_COLON, + [SDL_SCANCODE_KP_HASH] = Q_KEY_CODE_KP_HASH, + [SDL_SCANCODE_KP_SPACE] = Q_KEY_CODE_KP_SPACE, + [SDL_SCANCODE_KP_AT] = Q_KEY_CODE_KP_AT, + [SDL_SCANCODE_KP_EXCLAM] = Q_KEY_CODE_KP_EXCLAM, + [SDL_SCANCODE_KP_MEMSTORE] = Q_KEY_CODE_KP_MEMSTORE, + [SDL_SCANCODE_KP_MEMRECALL] = Q_KEY_CODE_KP_MEMRECALL, + [SDL_SCANCODE_KP_MEMCLEAR] = Q_KEY_CODE_KP_MEMCLEAR, + [SDL_SCANCODE_KP_MEMADD] = Q_KEY_CODE_KP_MEMADD, + [SDL_SCANCODE_KP_MEMSUBTRACT] = Q_KEY_CODE_KP_MEMSUBTRACT, + [SDL_SCANCODE_KP_MEMMULTIPLY] = Q_KEY_CODE_KP_MEMMULTIPLY, + [SDL_SCANCODE_KP_MEMDIVIDE] = Q_KEY_CODE_KP_MEMDIVIDE, + [SDL_SCANCODE_KP_PLUSMINUS] = Q_KEY_CODE_KP_PLUSMINUS, + [SDL_SCANCODE_KP_CLEAR] = Q_KEY_CODE_KP_CLEAR, + [SDL_SCANCODE_KP_CLEARENTRY] = Q_KEY_CODE_KP_CLEARENTRY, + [SDL_SCANCODE_KP_BINARY] = Q_KEY_CODE_KP_BINARY, + [SDL_SCANCODE_KP_OCTAL] = Q_KEY_CODE_KP_OCTAL, + [SDL_SCANCODE_KP_DECIMAL] = Q_KEY_CODE_KP_DECIMAL, + [SDL_SCANCODE_KP_HEXADECIMAL] = Q_KEY_CODE_KP_HEXADECIMAL, +#endif + [SDL_SCANCODE_LCTRL] = Q_KEY_CODE_CTRL, + [SDL_SCANCODE_LSHIFT] = Q_KEY_CODE_SHIFT, + [SDL_SCANCODE_LALT] = Q_KEY_CODE_ALT, + [SDL_SCANCODE_LGUI] = Q_KEY_CODE_META_L, + [SDL_SCANCODE_RCTRL] = Q_KEY_CODE_CTRL_R, + [SDL_SCANCODE_RSHIFT] = Q_KEY_CODE_SHIFT_R, + [SDL_SCANCODE_RALT] = Q_KEY_CODE_ALTGR, + [SDL_SCANCODE_RGUI] = Q_KEY_CODE_META_R, +#if 0 + [SDL_SCANCODE_MODE] = Q_KEY_CODE_MODE, + [SDL_SCANCODE_AUDIONEXT] = Q_KEY_CODE_AUDIONEXT, + [SDL_SCANCODE_AUDIOPREV] = Q_KEY_CODE_AUDIOPREV, + [SDL_SCANCODE_AUDIOSTOP] = Q_KEY_CODE_AUDIOSTOP, + [SDL_SCANCODE_AUDIOPLAY] = Q_KEY_CODE_AUDIOPLAY, + [SDL_SCANCODE_AUDIOMUTE] = Q_KEY_CODE_AUDIOMUTE, + [SDL_SCANCODE_MEDIASELECT] = Q_KEY_CODE_MEDIASELECT, + [SDL_SCANCODE_WWW] = Q_KEY_CODE_WWW, + [SDL_SCANCODE_MAIL] = Q_KEY_CODE_MAIL, + [SDL_SCANCODE_CALCULATOR] = Q_KEY_CODE_CALCULATOR, + [SDL_SCANCODE_COMPUTER] = Q_KEY_CODE_COMPUTER, + [SDL_SCANCODE_AC_SEARCH] = Q_KEY_CODE_AC_SEARCH, + [SDL_SCANCODE_AC_HOME] = Q_KEY_CODE_AC_HOME, + [SDL_SCANCODE_AC_BACK] = Q_KEY_CODE_AC_BACK, + [SDL_SCANCODE_AC_FORWARD] = Q_KEY_CODE_AC_FORWARD, + [SDL_SCANCODE_AC_STOP] = Q_KEY_CODE_AC_STOP, + [SDL_SCANCODE_AC_REFRESH] = Q_KEY_CODE_AC_REFRESH, + [SDL_SCANCODE_AC_BOOKMARKS] = Q_KEY_CODE_AC_BOOKMARKS, + [SDL_SCANCODE_BRIGHTNESSDOWN] = Q_KEY_CODE_BRIGHTNESSDOWN, + [SDL_SCANCODE_BRIGHTNESSUP] = Q_KEY_CODE_BRIGHTNESSUP, + [SDL_SCANCODE_DISPLAYSWITCH] = Q_KEY_CODE_DISPLAYSWITCH, + [SDL_SCANCODE_KBDILLUMTOGGLE] = Q_KEY_CODE_KBDILLUMTOGGLE, + [SDL_SCANCODE_KBDILLUMDOWN] = Q_KEY_CODE_KBDILLUMDOWN, + [SDL_SCANCODE_KBDILLUMUP] = Q_KEY_CODE_KBDILLUMUP, + [SDL_SCANCODE_EJECT] = Q_KEY_CODE_EJECT, + [SDL_SCANCODE_SLEEP] = Q_KEY_CODE_SLEEP, + [SDL_SCANCODE_APP1] = Q_KEY_CODE_APP1, + [SDL_SCANCODE_APP2] = Q_KEY_CODE_APP2, +#endif +}; diff --git a/ui/sdl2.c b/ui/sdl2.c new file mode 100644 index 000000000..7506e2e13 --- /dev/null +++ b/ui/sdl2.c @@ -0,0 +1,834 @@ +/* + * QEMU SDL display driver + * + * Copyright (c) 2003 Fabrice Bellard + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +/* Ported SDL 1.2 code to 2.0 by Dave Airlie. */ + +/* Avoid compiler warning because macro is redefined in SDL_syswm.h. */ +#undef WIN32_LEAN_AND_MEAN + +#include <SDL.h> + +#if SDL_MAJOR_VERSION == 2 +#include <SDL_syswm.h> + +#include "qemu-common.h" +#include "ui/console.h" +#include "ui/input.h" +#include "sysemu/sysemu.h" +#include "sdl_zoom.h" + +#include "sdl2-keymap.h" + +static int sdl2_num_outputs; +static struct sdl2_state { + DisplayChangeListener dcl; + DisplaySurface *surface; + SDL_Texture *texture; + SDL_Window *real_window; + SDL_Renderer *real_renderer; + int idx; + int last_vm_running; /* per console for caption reasons */ + int x, y; +} *sdl2_console; + +static SDL_Surface *guest_sprite_surface; +static int gui_grab; /* if true, all keyboard/mouse events are grabbed */ + +static bool gui_saved_scaling; +static int gui_saved_width; +static int gui_saved_height; +static int gui_saved_grab; +static int gui_fullscreen; +static int gui_noframe; +static int gui_key_modifier_pressed; +static int gui_keysym; +static int gui_grab_code = KMOD_LALT | KMOD_LCTRL; +static uint8_t modifiers_state[SDL_NUM_SCANCODES]; +static SDL_Cursor *sdl_cursor_normal; +static SDL_Cursor *sdl_cursor_hidden; +static int absolute_enabled; +static int guest_cursor; +static int guest_x, guest_y; +static SDL_Cursor *guest_sprite; +static int scaling_active; +static Notifier mouse_mode_notifier; + +static void sdl_update_caption(struct sdl2_state *scon); + +static struct sdl2_state *get_scon_from_window(uint32_t window_id) +{ + int i; + for (i = 0; i < sdl2_num_outputs; i++) { + if (sdl2_console[i].real_window == SDL_GetWindowFromID(window_id)) { + return &sdl2_console[i]; + } + } + return NULL; +} + +static void sdl_update(DisplayChangeListener *dcl, + int x, int y, int w, int h) +{ + struct sdl2_state *scon = container_of(dcl, struct sdl2_state, dcl); + SDL_Rect rect; + DisplaySurface *surf = qemu_console_surface(dcl->con); + + if (!surf) { + return; + } + if (!scon->texture) { + return; + } + + rect.x = x; + rect.y = y; + rect.w = w; + rect.h = h; + + SDL_UpdateTexture(scon->texture, NULL, surface_data(surf), + surface_stride(surf)); + SDL_RenderCopy(scon->real_renderer, scon->texture, &rect, &rect); + SDL_RenderPresent(scon->real_renderer); +} + +static void do_sdl_resize(struct sdl2_state *scon, int width, int height, + int bpp) +{ + int flags; + + if (scon->real_window && scon->real_renderer) { + if (width && height) { + SDL_RenderSetLogicalSize(scon->real_renderer, width, height); + SDL_SetWindowSize(scon->real_window, width, height); + } else { + SDL_DestroyRenderer(scon->real_renderer); + SDL_DestroyWindow(scon->real_window); + scon->real_renderer = NULL; + scon->real_window = NULL; + } + } else { + if (!width || !height) { + return; + } + flags = 0; + if (gui_fullscreen) { + flags |= SDL_WINDOW_FULLSCREEN; + } else { + flags |= SDL_WINDOW_RESIZABLE; + } + + scon->real_window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + width, height, flags); + scon->real_renderer = SDL_CreateRenderer(scon->real_window, -1, 0); + sdl_update_caption(scon); + } +} + +static void sdl_switch(DisplayChangeListener *dcl, + DisplaySurface *new_surface) +{ + struct sdl2_state *scon = container_of(dcl, struct sdl2_state, dcl); + int format = 0; + int idx = scon->idx; + DisplaySurface *old_surface = scon->surface; + + /* temporary hack: allows to call sdl_switch to handle scaling changes */ + if (new_surface) { + scon->surface = new_surface; + } + + if (!new_surface && idx > 0) { + scon->surface = NULL; + } + + if (new_surface == NULL) { + do_sdl_resize(scon, 0, 0, 0); + } else { + do_sdl_resize(scon, surface_width(scon->surface), + surface_height(scon->surface), 0); + } + + if (old_surface && scon->texture) { + SDL_DestroyTexture(scon->texture); + scon->texture = NULL; + } + + if (new_surface) { + if (!scon->texture) { + if (surface_bits_per_pixel(scon->surface) == 16) { + format = SDL_PIXELFORMAT_RGB565; + } else if (surface_bits_per_pixel(scon->surface) == 32) { + format = SDL_PIXELFORMAT_ARGB8888; + } + + scon->texture = SDL_CreateTexture(scon->real_renderer, format, + SDL_TEXTUREACCESS_STREAMING, + surface_width(new_surface), + surface_height(new_surface)); + } + } +} + +static void reset_keys(void) +{ + int i; + + for (i = 0; i < 256; i++) { + if (modifiers_state[i]) { + int qcode = sdl2_scancode_to_qcode[i]; + qemu_input_event_send_key_qcode(NULL, qcode, false); + modifiers_state[i] = 0; + } + } +} + +static void sdl_process_key(SDL_KeyboardEvent *ev) +{ + int qcode = sdl2_scancode_to_qcode[ev->keysym.scancode]; + + switch (ev->keysym.scancode) { +#if 0 + case SDL_SCANCODE_NUMLOCKCLEAR: + case SDL_SCANCODE_CAPSLOCK: + /* SDL does not send the key up event, so we generate it */ + qemu_input_event_send_key_qcode(NULL, qcode, true); + qemu_input_event_send_key_qcode(NULL, qcode, false); + return; +#endif + case SDL_SCANCODE_LCTRL: + case SDL_SCANCODE_LSHIFT: + case SDL_SCANCODE_LALT: + case SDL_SCANCODE_LGUI: + case SDL_SCANCODE_RCTRL: + case SDL_SCANCODE_RSHIFT: + case SDL_SCANCODE_RALT: + case SDL_SCANCODE_RGUI: + if (ev->type == SDL_KEYUP) { + modifiers_state[ev->keysym.scancode] = 0; + } else { + modifiers_state[ev->keysym.scancode] = 1; + } + /* fall though */ + default: + qemu_input_event_send_key_qcode(NULL, qcode, + ev->type == SDL_KEYDOWN); + } +} + +static void sdl_update_caption(struct sdl2_state *scon) +{ + char win_title[1024]; + char icon_title[1024]; + const char *status = ""; + + if (!runstate_is_running()) { + status = " [Stopped]"; + } else if (gui_grab) { + if (alt_grab) { + status = " - Press Ctrl-Alt-Shift to exit 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-%d)%s", qemu_name, + scon->idx, status); + snprintf(icon_title, sizeof(icon_title), "QEMU (%s)", qemu_name); + } else { + snprintf(win_title, sizeof(win_title), "QEMU%s", status); + snprintf(icon_title, sizeof(icon_title), "QEMU"); + } + + if (scon->real_window) { + SDL_SetWindowTitle(scon->real_window, win_title); + } +} + +static void sdl_hide_cursor(void) +{ + if (!cursor_hide) { + return; + } + + if (qemu_input_is_absolute()) { + SDL_ShowCursor(1); + SDL_SetCursor(sdl_cursor_hidden); + } else { + SDL_SetRelativeMouseMode(SDL_TRUE); + } +} + +static void sdl_show_cursor(void) +{ + if (!cursor_hide) { + return; + } + + if (!qemu_input_is_absolute()) { + SDL_SetRelativeMouseMode(SDL_FALSE); + SDL_ShowCursor(1); + if (guest_cursor && + (gui_grab || qemu_input_is_absolute() || absolute_enabled)) { + SDL_SetCursor(guest_sprite); + } else { + SDL_SetCursor(sdl_cursor_normal); + } + } +} + +static void sdl_grab_start(struct sdl2_state *scon) +{ + /* + * If the application is not active, do not try to enter grab state. This + * prevents 'SDL_WM_GrabInput(SDL_GRAB_ON)' from blocking all the + * application (SDL bug). + */ + if (!(SDL_GetWindowFlags(scon->real_window) & SDL_WINDOW_INPUT_FOCUS)) { + return; + } + if (guest_cursor) { + SDL_SetCursor(guest_sprite); + if (!qemu_input_is_absolute() && !absolute_enabled) { + SDL_WarpMouseInWindow(scon->real_window, guest_x, guest_y); + } + } else { + sdl_hide_cursor(); + } + SDL_SetWindowGrab(scon->real_window, SDL_TRUE); + gui_grab = 1; + sdl_update_caption(scon); +} + +static void sdl_grab_end(struct sdl2_state *scon) +{ + SDL_SetWindowGrab(scon->real_window, SDL_FALSE); + gui_grab = 0; + sdl_show_cursor(); + sdl_update_caption(scon); +} + +static void absolute_mouse_grab(struct sdl2_state *scon) +{ + int mouse_x, mouse_y; + int scr_w, scr_h; + SDL_GetMouseState(&mouse_x, &mouse_y); + SDL_GetWindowSize(scon->real_window, &scr_w, &scr_h); + if (mouse_x > 0 && mouse_x < scr_w - 1 && + mouse_y > 0 && mouse_y < scr_h - 1) { + sdl_grab_start(scon); + } +} + +static void sdl_mouse_mode_change(Notifier *notify, void *data) +{ + if (qemu_input_is_absolute()) { + if (!absolute_enabled) { + absolute_enabled = 1; + absolute_mouse_grab(&sdl2_console[0]); + } + } else if (absolute_enabled) { + if (!gui_fullscreen) { + sdl_grab_end(&sdl2_console[0]); + } + absolute_enabled = 0; + } +} + +static void sdl_send_mouse_event(struct sdl2_state *scon, int dx, int dy, + int dz, int x, int y, int state) +{ + static uint32_t bmap[INPUT_BUTTON_MAX] = { + [INPUT_BUTTON_LEFT] = SDL_BUTTON(SDL_BUTTON_LEFT), + [INPUT_BUTTON_MIDDLE] = SDL_BUTTON(SDL_BUTTON_MIDDLE), + [INPUT_BUTTON_RIGHT] = SDL_BUTTON(SDL_BUTTON_RIGHT), +#if 0 + [INPUT_BUTTON_WHEEL_UP] = SDL_BUTTON(SDL_BUTTON_WHEELUP), + [INPUT_BUTTON_WHEEL_DOWN] = SDL_BUTTON(SDL_BUTTON_WHEELDOWN), +#endif + }; + static uint32_t prev_state; + + if (prev_state != state) { + qemu_input_update_buttons(scon->dcl.con, bmap, prev_state, state); + prev_state = state; + } + + if (qemu_input_is_absolute()) { + int scr_w, scr_h; + int max_w = 0, max_h = 0; + int off_x = 0, off_y = 0; + int cur_off_x = 0, cur_off_y = 0; + int i; + + for (i = 0; i < sdl2_num_outputs; i++) { + struct sdl2_state *thiscon = &sdl2_console[i]; + if (thiscon->real_window && thiscon->surface) { + SDL_GetWindowSize(thiscon->real_window, &scr_w, &scr_h); + cur_off_x = thiscon->x; + cur_off_y = thiscon->y; + if (scr_w + cur_off_x > max_w) { + max_w = scr_w + cur_off_x; + } + if (scr_h + cur_off_y > max_h) { + max_h = scr_h + cur_off_y; + } + if (i == scon->idx) { + off_x = cur_off_x; + off_y = cur_off_y; + } + } + } + qemu_input_queue_abs(scon->dcl.con, INPUT_AXIS_X, off_x + x, max_w); + qemu_input_queue_abs(scon->dcl.con, INPUT_AXIS_Y, off_y + y, max_h); + } else { + if (guest_cursor) { + x -= guest_x; + y -= guest_y; + guest_x += x; + guest_y += y; + dx = x; + dy = y; + } + qemu_input_queue_rel(scon->dcl.con, INPUT_AXIS_X, dx); + qemu_input_queue_rel(scon->dcl.con, INPUT_AXIS_Y, dy); + } + qemu_input_event_sync(); +} + +static void sdl_scale(struct sdl2_state *scon, int width, int height) +{ + int bpp = 0; + do_sdl_resize(scon, width, height, bpp); + scaling_active = 1; +} + +static void toggle_full_screen(struct sdl2_state *scon) +{ + int width = surface_width(scon->surface); + int height = surface_height(scon->surface); + int bpp = surface_bits_per_pixel(scon->surface); + + gui_fullscreen = !gui_fullscreen; + if (gui_fullscreen) { + SDL_GetWindowSize(scon->real_window, + &gui_saved_width, &gui_saved_height); + gui_saved_scaling = scaling_active; + + do_sdl_resize(scon, width, height, bpp); + scaling_active = 0; + + gui_saved_grab = gui_grab; + sdl_grab_start(scon); + } else { + if (gui_saved_scaling) { + sdl_scale(scon, gui_saved_width, gui_saved_height); + } else { + do_sdl_resize(scon, width, height, 0); + } + if (!gui_saved_grab) { + sdl_grab_end(scon); + } + } + graphic_hw_invalidate(scon->dcl.con); + graphic_hw_update(scon->dcl.con); +} + +static void handle_keydown(SDL_Event *ev) +{ + int mod_state; + struct sdl2_state *scon = get_scon_from_window(ev->key.windowID); + + if (alt_grab) { + mod_state = (SDL_GetModState() & (gui_grab_code | KMOD_LSHIFT)) == + (gui_grab_code | KMOD_LSHIFT); + } else if (ctrl_grab) { + mod_state = (SDL_GetModState() & KMOD_RCTRL) == KMOD_RCTRL; + } else { + mod_state = (SDL_GetModState() & gui_grab_code) == gui_grab_code; + } + gui_key_modifier_pressed = mod_state; + + if (gui_key_modifier_pressed) { + switch (ev->key.keysym.scancode) { + case SDL_SCANCODE_F: + toggle_full_screen(scon); + gui_keysym = 1; + break; + case SDL_SCANCODE_U: + if (scaling_active) { + scaling_active = 0; + sdl_switch(&scon->dcl, NULL); + graphic_hw_invalidate(scon->dcl.con); + graphic_hw_update(scon->dcl.con); + } + gui_keysym = 1; + break; + case SDL_SCANCODE_KP_PLUS: + case SDL_SCANCODE_KP_MINUS: + if (!gui_fullscreen) { + int scr_w, scr_h; + int width, height; + SDL_GetWindowSize(scon->real_window, &scr_w, &scr_h); + + width = MAX(scr_w + (ev->key.keysym.scancode == + SDL_SCANCODE_KP_PLUS ? 50 : -50), + 160); + height = (surface_height(scon->surface) * width) / + surface_width(scon->surface); + + sdl_scale(scon, width, height); + graphic_hw_invalidate(NULL); + graphic_hw_update(NULL); + gui_keysym = 1; + } + default: + break; + } + } + if (!gui_keysym) { + sdl_process_key(&ev->key); + } +} + +static void handle_keyup(SDL_Event *ev) +{ + int mod_state; + struct sdl2_state *scon = get_scon_from_window(ev->key.windowID); + + if (!alt_grab) { + mod_state = (ev->key.keysym.mod & gui_grab_code); + } else { + mod_state = (ev->key.keysym.mod & (gui_grab_code | KMOD_LSHIFT)); + } + if (!mod_state && gui_key_modifier_pressed) { + gui_key_modifier_pressed = 0; + if (gui_keysym == 0) { + /* exit/enter grab if pressing Ctrl-Alt */ + if (!gui_grab) { + sdl_grab_start(scon); + } else if (!gui_fullscreen) { + sdl_grab_end(scon); + } + /* SDL does not send back all the modifiers key, so we must + * correct it. */ + reset_keys(); + return; + } + gui_keysym = 0; + } + if (!gui_keysym) { + sdl_process_key(&ev->key); + } +} + +static void handle_mousemotion(SDL_Event *ev) +{ + int max_x, max_y; + struct sdl2_state *scon = get_scon_from_window(ev->key.windowID); + + if (qemu_input_is_absolute() || absolute_enabled) { + int scr_w, scr_h; + SDL_GetWindowSize(scon->real_window, &scr_w, &scr_h); + max_x = scr_w - 1; + max_y = scr_h - 1; + if (gui_grab && (ev->motion.x == 0 || ev->motion.y == 0 || + ev->motion.x == max_x || ev->motion.y == max_y)) { + sdl_grab_end(scon); + } + if (!gui_grab && + (ev->motion.x > 0 && ev->motion.x < max_x && + ev->motion.y > 0 && ev->motion.y < max_y)) { + sdl_grab_start(scon); + } + } + if (gui_grab || qemu_input_is_absolute() || absolute_enabled) { + sdl_send_mouse_event(scon, ev->motion.xrel, ev->motion.yrel, 0, + ev->motion.x, ev->motion.y, ev->motion.state); + } +} + +static void handle_mousebutton(SDL_Event *ev) +{ + int buttonstate = SDL_GetMouseState(NULL, NULL); + SDL_MouseButtonEvent *bev; + struct sdl2_state *scon = get_scon_from_window(ev->key.windowID); + int dz; + + bev = &ev->button; + if (!gui_grab && !qemu_input_is_absolute()) { + if (ev->type == SDL_MOUSEBUTTONUP && bev->button == SDL_BUTTON_LEFT) { + /* start grabbing all events */ + sdl_grab_start(scon); + } + } else { + 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(scon, 0, 0, dz, bev->x, bev->y, buttonstate); + } +} + +static void handle_windowevent(DisplayChangeListener *dcl, SDL_Event *ev) +{ + int w, h; + struct sdl2_state *scon = get_scon_from_window(ev->key.windowID); + + switch (ev->window.event) { + case SDL_WINDOWEVENT_RESIZED: + sdl_scale(scon, ev->window.data1, ev->window.data2); + graphic_hw_invalidate(scon->dcl.con); + graphic_hw_update(scon->dcl.con); + break; + case SDL_WINDOWEVENT_EXPOSED: + SDL_GetWindowSize(SDL_GetWindowFromID(ev->window.windowID), &w, &h); + sdl_update(dcl, 0, 0, w, h); + break; + case SDL_WINDOWEVENT_FOCUS_GAINED: + case SDL_WINDOWEVENT_ENTER: + if (!gui_grab && (qemu_input_is_absolute() || absolute_enabled)) { + absolute_mouse_grab(scon); + } + break; + case SDL_WINDOWEVENT_FOCUS_LOST: + if (gui_grab && !gui_fullscreen) { + sdl_grab_end(scon); + } + break; + case SDL_WINDOWEVENT_RESTORED: + update_displaychangelistener(dcl, GUI_REFRESH_INTERVAL_DEFAULT); + break; + case SDL_WINDOWEVENT_MINIMIZED: + update_displaychangelistener(dcl, 500); + break; + case SDL_WINDOWEVENT_CLOSE: + if (!no_quit) { + no_shutdown = 0; + qemu_system_shutdown_request(); + } + break; + } +} + +static void sdl_refresh(DisplayChangeListener *dcl) +{ + struct sdl2_state *scon = container_of(dcl, struct sdl2_state, dcl); + SDL_Event ev1, *ev = &ev1; + + if (scon->last_vm_running != runstate_is_running()) { + scon->last_vm_running = runstate_is_running(); + sdl_update_caption(scon); + } + + graphic_hw_update(dcl->con); + + while (SDL_PollEvent(ev)) { + switch (ev->type) { + case SDL_KEYDOWN: + handle_keydown(ev); + break; + case SDL_KEYUP: + handle_keyup(ev); + break; + case SDL_QUIT: + if (!no_quit) { + no_shutdown = 0; + qemu_system_shutdown_request(); + } + break; + case SDL_MOUSEMOTION: + handle_mousemotion(ev); + break; + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + handle_mousebutton(ev); + break; + case SDL_WINDOWEVENT: + handle_windowevent(dcl, ev); + break; + default: + break; + } + } +} + +static void sdl_mouse_warp(DisplayChangeListener *dcl, + int x, int y, int on) +{ + struct sdl2_state *scon = container_of(dcl, struct sdl2_state, dcl); + if (on) { + if (!guest_cursor) { + sdl_show_cursor(); + } + if (gui_grab || qemu_input_is_absolute() || absolute_enabled) { + SDL_SetCursor(guest_sprite); + if (!qemu_input_is_absolute() && !absolute_enabled) { + SDL_WarpMouseInWindow(scon->real_window, x, y); + } + } + } else if (gui_grab) { + sdl_hide_cursor(); + } + guest_cursor = on; + guest_x = x, guest_y = y; +} + +static void sdl_mouse_define(DisplayChangeListener *dcl, + QEMUCursor *c) +{ + + if (guest_sprite) { + SDL_FreeCursor(guest_sprite); + } + + if (guest_sprite_surface) { + SDL_FreeSurface(guest_sprite_surface); + } + + guest_sprite_surface = + SDL_CreateRGBSurfaceFrom(c->data, c->width, c->height, 32, c->width * 4, + 0xff0000, 0x00ff00, 0xff, 0xff000000); + + if (!guest_sprite_surface) { + fprintf(stderr, "Failed to make rgb surface from %p\n", c); + return; + } + guest_sprite = SDL_CreateColorCursor(guest_sprite_surface, + c->hot_x, c->hot_y); + if (!guest_sprite) { + fprintf(stderr, "Failed to make color cursor from %p\n", c); + return; + } + if (guest_cursor && + (gui_grab || qemu_input_is_absolute() || absolute_enabled)) { + SDL_SetCursor(guest_sprite); + } +} + +static void sdl_cleanup(void) +{ + if (guest_sprite) { + SDL_FreeCursor(guest_sprite); + } + SDL_QuitSubSystem(SDL_INIT_VIDEO); +} + +static const DisplayChangeListenerOps dcl_ops = { + .dpy_name = "sdl", + .dpy_gfx_update = sdl_update, + .dpy_gfx_switch = sdl_switch, + .dpy_refresh = sdl_refresh, + .dpy_mouse_set = sdl_mouse_warp, + .dpy_cursor_define = sdl_mouse_define, +}; + +void sdl_display_init(DisplayState *ds, int full_screen, int no_frame) +{ + int flags; + uint8_t data = 0; + char *filename; + int i; + + if (no_frame) { + gui_noframe = 1; + } + +#ifdef __linux__ + /* on Linux, SDL may use fbcon|directfb|svgalib when run without + * accessible $DISPLAY to open X11 window. This is often the case + * when qemu is run using sudo. But in this case, and when actually + * run in X11 environment, SDL fights with X11 for the video card, + * making current display unavailable, often until reboot. + * So make x11 the default SDL video driver if this variable is unset. + * This is a bit hackish but saves us from bigger problem. + * Maybe it's a good idea to fix this in SDL instead. + */ + setenv("SDL_VIDEODRIVER", "x11", 0); +#endif + + flags = SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE; + if (SDL_Init(flags)) { + fprintf(stderr, "Could not initialize SDL(%s) - exiting\n", + SDL_GetError()); + exit(1); + } + + for (i = 0;; i++) { + QemuConsole *con = qemu_console_lookup_by_index(i); + if (!con || !qemu_console_is_graphic(con)) { + break; + } + } + sdl2_num_outputs = i; + sdl2_console = g_new0(struct sdl2_state, sdl2_num_outputs); + for (i = 0; i < sdl2_num_outputs; i++) { + QemuConsole *con = qemu_console_lookup_by_index(i); + sdl2_console[i].dcl.ops = &dcl_ops; + sdl2_console[i].dcl.con = con; + register_displaychangelistener(&sdl2_console[i].dcl); + sdl2_console[i].idx = i; + } + + /* Load a 32x32x4 image. White pixels are transparent. */ + filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, "qemu-icon.bmp"); + if (filename) { + SDL_Surface *image = SDL_LoadBMP(filename); + if (image) { + uint32_t colorkey = SDL_MapRGB(image->format, 255, 255, 255); + SDL_SetColorKey(image, SDL_TRUE, colorkey); + SDL_SetWindowIcon(sdl2_console[0].real_window, image); + } + g_free(filename); + } + + if (full_screen) { + gui_fullscreen = 1; + sdl_grab_start(0); + } + + mouse_mode_notifier.notify = sdl_mouse_mode_change; + qemu_add_mouse_mode_change_notifier(&mouse_mode_notifier); + + gui_grab = 0; + + sdl_cursor_hidden = SDL_CreateCursor(&data, &data, 8, 1, 0, 0); + sdl_cursor_normal = SDL_GetCursor(); + + atexit(sdl_cleanup); +} +#endif diff --git a/ui/sdl_keysym.h b/ui/sdl_keysym.h index ee904805d..599d9fc64 100644 --- a/ui/sdl_keysym.h +++ b/ui/sdl_keysym.h @@ -200,6 +200,7 @@ static const name2keysym_t name2keysym[]={ { "yacute", 0x0fd}, { "thorn", 0x0fe}, { "ydiaeresis", 0x0ff}, +#if SDL_MAJOR_VERSION == 1 {"EuroSign", SDLK_EURO}, /* modifiers */ @@ -272,6 +273,6 @@ static const name2keysym_t name2keysym[]={ {"Num_Lock", SDLK_NUMLOCK}, {"Pause", SDLK_PAUSE}, {"Escape", SDLK_ESCAPE}, - +#endif {NULL, 0}, }; diff --git a/ui/spice-core.c b/ui/spice-core.c index bd7a248f9..4cce3b38c 100644 --- a/ui/spice-core.c +++ b/ui/spice-core.c @@ -47,8 +47,8 @@ static const char *auth = "spice"; static char *auth_passwd; static time_t auth_expires = TIME_MAX; static int spice_migration_completed; +static int spice_display_is_running; int using_spice = 0; -int spice_displays; static QemuThread me; @@ -63,25 +63,25 @@ static SpiceTimer *timer_add(SpiceTimerFunc func, void *opaque) SpiceTimer *timer; timer = g_malloc0(sizeof(*timer)); - timer->timer = qemu_new_timer_ms(rt_clock, func, opaque); + timer->timer = timer_new_ms(QEMU_CLOCK_REALTIME, func, opaque); QTAILQ_INSERT_TAIL(&timers, timer, next); return timer; } static void timer_start(SpiceTimer *timer, uint32_t ms) { - qemu_mod_timer(timer->timer, qemu_get_clock_ms(rt_clock) + ms); + timer_mod(timer->timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + ms); } static void timer_cancel(SpiceTimer *timer) { - qemu_del_timer(timer->timer); + timer_del(timer->timer); } static void timer_remove(SpiceTimer *timer) { - qemu_del_timer(timer->timer); - qemu_free_timer(timer->timer); + timer_del(timer->timer); + timer_free(timer->timer); QTAILQ_REMOVE(&timers, timer, next); g_free(timer); } @@ -383,17 +383,16 @@ static SpiceChannelList *qmp_query_spice_channels(void) struct sockaddr *paddr; socklen_t plen; + if (!(item->info->flags & SPICE_CHANNEL_EVENT_FLAG_ADDR_EXT)) { + error_report("invalid channel event"); + return NULL; + } + chan = g_malloc0(sizeof(*chan)); chan->value = g_malloc0(sizeof(*chan->value)); - if (item->info->flags & SPICE_CHANNEL_EVENT_FLAG_ADDR_EXT) { - paddr = (struct sockaddr *)&item->info->paddr_ext; - plen = item->info->plen_ext; - } else { - paddr = &item->info->paddr; - plen = item->info->plen; - } - + paddr = (struct sockaddr *)&item->info->paddr_ext; + plen = item->info->plen_ext; getnameinfo(paddr, plen, host, sizeof(host), port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV); @@ -511,7 +510,9 @@ SpiceInfo *qmp_query_spice(Error **errp) int port, tls_port; const char *addr; SpiceInfo *info; - char version_string[20]; /* 12 = |255.255.255\0| is the max */ + unsigned int major; + unsigned int minor; + unsigned int micro; info = g_malloc0(sizeof(*info)); @@ -534,11 +535,10 @@ SpiceInfo *qmp_query_spice(Error **errp) info->host = g_strdup(addr ? addr : "0.0.0.0"); info->has_compiled_version = true; - snprintf(version_string, sizeof(version_string), "%d.%d.%d", - (SPICE_SERVER_VERSION & 0xff0000) >> 16, - (SPICE_SERVER_VERSION & 0xff00) >> 8, - SPICE_SERVER_VERSION & 0xff); - info->compiled_version = g_strdup(version_string); + major = (SPICE_SERVER_VERSION & 0xff0000) >> 16; + minor = (SPICE_SERVER_VERSION & 0xff00) >> 8; + micro = SPICE_SERVER_VERSION & 0xff; + info->compiled_version = g_strdup_printf("%d.%d.%d", major, minor, micro); if (port) { info->has_port = true; @@ -623,9 +623,7 @@ static void vm_change_state_handler(void *opaque, int running, { if (running) { qemu_spice_display_start(); - spice_server_vm_start(spice_server); } else { - spice_server_vm_stop(spice_server); qemu_spice_display_stop(); } } @@ -640,7 +638,7 @@ void qemu_spice_init(void) char *x509_key_file = NULL, *x509_cert_file = NULL, *x509_cacert_file = NULL; - int port, tls_port, len, addr_flags; + int port, tls_port, addr_flags; spice_image_compression_t compression; spice_wan_compression_t wan_compr; bool seamless_migration; @@ -671,30 +669,29 @@ void qemu_spice_init(void) if (NULL == x509_dir) { x509_dir = "."; } - len = strlen(x509_dir) + 32; str = qemu_opt_get(opts, "x509-key-file"); if (str) { x509_key_file = g_strdup(str); } else { - x509_key_file = g_malloc(len); - snprintf(x509_key_file, len, "%s/%s", x509_dir, X509_SERVER_KEY_FILE); + x509_key_file = g_strdup_printf("%s/%s", x509_dir, + X509_SERVER_KEY_FILE); } str = qemu_opt_get(opts, "x509-cert-file"); if (str) { x509_cert_file = g_strdup(str); } else { - x509_cert_file = g_malloc(len); - snprintf(x509_cert_file, len, "%s/%s", x509_dir, X509_SERVER_CERT_FILE); + x509_cert_file = g_strdup_printf("%s/%s", x509_dir, + X509_SERVER_CERT_FILE); } str = qemu_opt_get(opts, "x509-cacert-file"); if (str) { x509_cacert_file = g_strdup(str); } else { - x509_cacert_file = g_malloc(len); - snprintf(x509_cacert_file, len, "%s/%s", x509_dir, X509_CA_CERT_FILE); + x509_cacert_file = g_strdup_printf("%s/%s", x509_dir, + X509_CA_CERT_FILE); } x509_key_password = qemu_opt_get(opts, "x509-key-password"); @@ -778,6 +775,8 @@ void qemu_spice_init(void) if (str) { int streaming_video = parse_stream_video(str); spice_server_set_streaming_video(spice_server, streaming_video); + } else { + spice_server_set_streaming_video(spice_server, SPICE_STREAM_VIDEO_OFF); } spice_server_set_agent_mouse @@ -833,15 +832,33 @@ int qemu_spice_add_interface(SpiceBaseInstance *sin) * With a command line like '-vnc :0 -vga qxl' you'll end up here. */ spice_server = spice_server_new(); + spice_server_set_sasl_appname(spice_server, "qemu"); spice_server_init(spice_server, &core_interface); qemu_add_vm_change_state_handler(vm_change_state_handler, NULL); } - if (strcmp(sin->sif->type, SPICE_INTERFACE_QXL) == 0) { - spice_displays++; + return spice_server_add_interface(spice_server, sin); +} + +static GSList *spice_consoles; +static int display_id; + +bool qemu_spice_have_display_interface(QemuConsole *con) +{ + if (g_slist_find(spice_consoles, con)) { + return true; } + return false; +} - return spice_server_add_interface(spice_server, sin); +int qemu_spice_add_display_interface(QXLInstance *qxlin, QemuConsole *con) +{ + if (g_slist_find(spice_consoles, con)) { + return -1; + } + qxlin->id = display_id++; + spice_consoles = g_slist_append(spice_consoles, con); + return qemu_spice_add_interface(&qxlin->base); } static int qemu_spice_set_ticket(bool fail_if_conn, bool disconnect_if_conn) @@ -886,6 +903,23 @@ int qemu_spice_display_add_client(int csock, int skipauth, int tls) } } +void qemu_spice_display_start(void) +{ + spice_display_is_running = true; + spice_server_vm_start(spice_server); +} + +void qemu_spice_display_stop(void) +{ + spice_server_vm_stop(spice_server); + spice_display_is_running = false; +} + +int qemu_spice_display_is_running(SimpleSpiceDisplay *ssd) +{ + return spice_display_is_running; +} + static void spice_register_config(void) { qemu_add_opts(&qemu_spice_opts); diff --git a/ui/spice-display.c b/ui/spice-display.c index 82d8b9f9a..ce6b220f5 100644 --- a/ui/spice-display.c +++ b/ui/spice-display.c @@ -83,14 +83,14 @@ void qemu_spice_add_memslot(SimpleSpiceDisplay *ssd, QXLDevMemSlot *memslot, (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO, QXL_IO_MEMSLOT_ADD_ASYNC)); } else { - ssd->worker->add_memslot(ssd->worker, memslot); + spice_qxl_add_memslot(&ssd->qxl, memslot); } } void qemu_spice_del_memslot(SimpleSpiceDisplay *ssd, uint32_t gid, uint32_t sid) { trace_qemu_spice_del_memslot(ssd->qxl.id, gid, sid); - ssd->worker->del_memslot(ssd->worker, gid, sid); + spice_qxl_del_memslot(&ssd->qxl, gid, sid); } void qemu_spice_create_primary_surface(SimpleSpiceDisplay *ssd, uint32_t id, @@ -103,7 +103,7 @@ void qemu_spice_create_primary_surface(SimpleSpiceDisplay *ssd, uint32_t id, (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO, QXL_IO_CREATE_PRIMARY_ASYNC)); } else { - ssd->worker->create_primary_surface(ssd->worker, id, surface); + spice_qxl_create_primary_surface(&ssd->qxl, id, surface); } } @@ -116,31 +116,14 @@ void qemu_spice_destroy_primary_surface(SimpleSpiceDisplay *ssd, (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_IO, QXL_IO_DESTROY_PRIMARY_ASYNC)); } else { - ssd->worker->destroy_primary_surface(ssd->worker, id); + spice_qxl_destroy_primary_surface(&ssd->qxl, id); } } void qemu_spice_wakeup(SimpleSpiceDisplay *ssd) { trace_qemu_spice_wakeup(ssd->qxl.id); - ssd->worker->wakeup(ssd->worker); -} - -static int spice_display_is_running; - -void qemu_spice_display_start(void) -{ - spice_display_is_running = true; -} - -void qemu_spice_display_stop(void) -{ - spice_display_is_running = false; -} - -int qemu_spice_display_is_running(SimpleSpiceDisplay *ssd) -{ - return spice_display_is_running; + spice_qxl_wakeup(&ssd->qxl); } static void qemu_spice_create_one_update(SimpleSpiceDisplay *ssd, @@ -297,7 +280,7 @@ void qemu_spice_create_host_memslot(SimpleSpiceDisplay *ssd) { QXLDevMemSlot memslot; - dprint(1, "%s:\n", __FUNCTION__); + dprint(1, "%s/%d:\n", __func__, ssd->qxl.id); memset(&memslot, 0, sizeof(memslot)); memslot.slot_group_id = MEMSLOT_GROUP_HOST; @@ -311,7 +294,7 @@ void qemu_spice_create_host_primary(SimpleSpiceDisplay *ssd) memset(&surface, 0, sizeof(surface)); - dprint(1, "%s: %dx%d\n", __FUNCTION__, + dprint(1, "%s/%d: %dx%d\n", __func__, ssd->qxl.id, surface_width(ssd->ds), surface_height(ssd->ds)); surface.format = SPICE_SURFACE_FMT_32_xRGB; @@ -329,7 +312,7 @@ void qemu_spice_create_host_primary(SimpleSpiceDisplay *ssd) void qemu_spice_destroy_host_primary(SimpleSpiceDisplay *ssd) { - dprint(1, "%s:\n", __FUNCTION__); + dprint(1, "%s/%d:\n", __func__, ssd->qxl.id); qemu_spice_destroy_primary_surface(ssd, 0, QXL_SYNC); } @@ -354,7 +337,8 @@ void qemu_spice_display_update(SimpleSpiceDisplay *ssd, { QXLRect update_area; - dprint(2, "%s: x %d y %d w %d h %d\n", __FUNCTION__, x, y, w, h); + dprint(2, "%s/%d: x %d y %d w %d h %d\n", __func__, + ssd->qxl.id, x, y, w, h); update_area.left = x, update_area.right = x + w; update_area.top = y; @@ -370,8 +354,9 @@ void qemu_spice_display_switch(SimpleSpiceDisplay *ssd, DisplaySurface *surface) { SimpleSpiceUpdate *update; + bool need_destroy; - dprint(1, "%s:\n", __FUNCTION__); + dprint(1, "%s/%d:\n", __func__, ssd->qxl.id); memset(&ssd->dirty, 0, sizeof(ssd->dirty)); if (ssd->surface) { @@ -382,14 +367,19 @@ void qemu_spice_display_switch(SimpleSpiceDisplay *ssd, } qemu_mutex_lock(&ssd->lock); + need_destroy = (ssd->ds != NULL); ssd->ds = surface; while ((update = QTAILQ_FIRST(&ssd->updates)) != NULL) { QTAILQ_REMOVE(&ssd->updates, update, next); qemu_spice_destroy_update(ssd, update); } qemu_mutex_unlock(&ssd->lock); - qemu_spice_destroy_host_primary(ssd); - qemu_spice_create_host_primary(ssd); + if (need_destroy) { + qemu_spice_destroy_host_primary(ssd); + } + if (ssd->ds) { + qemu_spice_create_host_primary(ssd); + } memset(&ssd->dirty, 0, sizeof(ssd->dirty)); ssd->notify++; @@ -413,7 +403,7 @@ void qemu_spice_cursor_refresh_unlocked(SimpleSpiceDisplay *ssd) void qemu_spice_display_refresh(SimpleSpiceDisplay *ssd) { - dprint(3, "%s:\n", __func__); + dprint(3, "%s/%d:\n", __func__, ssd->qxl.id); graphic_hw_update(ssd->dcl.con); qemu_mutex_lock(&ssd->lock); @@ -427,7 +417,7 @@ void qemu_spice_display_refresh(SimpleSpiceDisplay *ssd) if (ssd->notify) { ssd->notify = 0; qemu_spice_wakeup(ssd); - dprint(2, "%s: notify\n", __FUNCTION__); + dprint(2, "%s/%d: notify\n", __func__, ssd->qxl.id); } } @@ -437,19 +427,19 @@ static void interface_attach_worker(QXLInstance *sin, QXLWorker *qxl_worker) { SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl); - dprint(1, "%s:\n", __FUNCTION__); + dprint(1, "%s/%d:\n", __func__, ssd->qxl.id); ssd->worker = qxl_worker; } static void interface_set_compression_level(QXLInstance *sin, int level) { - dprint(1, "%s:\n", __FUNCTION__); + dprint(1, "%s/%d:\n", __func__, sin->id); /* nothing to do */ } static void interface_set_mm_time(QXLInstance *sin, uint32_t mm_time) { - dprint(3, "%s:\n", __FUNCTION__); + dprint(3, "%s/%d:\n", __func__, sin->id); /* nothing to do */ } @@ -472,7 +462,7 @@ static int interface_get_command(QXLInstance *sin, struct QXLCommandExt *ext) SimpleSpiceUpdate *update; int ret = false; - dprint(3, "%s:\n", __FUNCTION__); + dprint(3, "%s/%d:\n", __func__, ssd->qxl.id); qemu_mutex_lock(&ssd->lock); update = QTAILQ_FIRST(&ssd->updates); @@ -488,7 +478,7 @@ static int interface_get_command(QXLInstance *sin, struct QXLCommandExt *ext) static int interface_req_cmd_notification(QXLInstance *sin) { - dprint(1, "%s:\n", __FUNCTION__); + dprint(1, "%s/%d:\n", __func__, sin->id); return 1; } @@ -498,7 +488,7 @@ static void interface_release_resource(QXLInstance *sin, SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl); uintptr_t id; - dprint(2, "%s:\n", __FUNCTION__); + dprint(2, "%s/%d:\n", __func__, ssd->qxl.id); id = ext.info->id; qemu_spice_destroy_update(ssd, (void*)id); } @@ -553,10 +543,33 @@ static void interface_set_client_capabilities(QXLInstance *sin, } static int interface_client_monitors_config(QXLInstance *sin, - VDAgentMonitorsConfig *monitors_config) + VDAgentMonitorsConfig *mc) { - dprint(3, "%s:\n", __func__); - return 0; /* == not supported by guest */ + SimpleSpiceDisplay *ssd = container_of(sin, SimpleSpiceDisplay, qxl); + QemuUIInfo info; + int rc; + + if (!mc) { + return 1; + } + + /* + * FIXME: multihead is tricky due to the way + * spice has multihead implemented. + */ + memset(&info, 0, sizeof(info)); + if (mc->num_of_monitors > 0) { + info.width = mc->monitors[0].width; + info.height = mc->monitors[0].height; + } + rc = dpy_set_ui_info(ssd->dcl.con, &info); + dprint(1, "%s/%d: size %dx%d, rc %d <--- ==========================\n", + __func__, ssd->qxl.id, info.width, info.height, rc); + if (rc != 0) { + return 0; /* == not supported by guest */ + } else { + return 1; + } } static const QXLInterface dpy_interface = { @@ -611,21 +624,36 @@ static const DisplayChangeListenerOps display_listener_ops = { .dpy_refresh = display_refresh, }; -void qemu_spice_display_init(DisplayState *ds) +static void qemu_spice_display_init_one(QemuConsole *con) { SimpleSpiceDisplay *ssd = g_new0(SimpleSpiceDisplay, 1); qemu_spice_display_init_common(ssd); ssd->qxl.base.sif = &dpy_interface.base; - qemu_spice_add_interface(&ssd->qxl.base); + qemu_spice_add_display_interface(&ssd->qxl, con); assert(ssd->worker); qemu_spice_create_host_memslot(ssd); ssd->dcl.ops = &display_listener_ops; - ssd->dcl.con = qemu_console_lookup_by_index(0); + ssd->dcl.con = con; register_displaychangelistener(&ssd->dcl); +} + +void qemu_spice_display_init(void) +{ + QemuConsole *con; + int i; - qemu_spice_create_host_primary(ssd); + for (i = 0;; i++) { + con = qemu_console_lookup_by_index(i); + if (!con || !qemu_console_is_graphic(con)) { + break; + } + if (qemu_spice_have_display_interface(con)) { + continue; + } + qemu_spice_display_init_one(con); + } } diff --git a/ui/spice-input.c b/ui/spice-input.c index 3beb8dead..c342e0dcf 100644 --- a/ui/spice-input.c +++ b/ui/spice-input.c @@ -26,12 +26,15 @@ #include "qemu-common.h" #include "ui/qemu-spice.h" #include "ui/console.h" +#include "ui/keymaps.h" +#include "ui/input.h" /* keyboard bits */ typedef struct QemuSpiceKbd { SpiceKbdInstance sin; int ledstate; + bool emul0; } QemuSpiceKbd; static void kbd_push_key(SpiceKbdInstance *sin, uint8_t frag); @@ -47,9 +50,24 @@ static const SpiceKbdInterface kbd_interface = { .get_leds = kbd_get_leds, }; -static void kbd_push_key(SpiceKbdInstance *sin, uint8_t frag) +static void kbd_push_key(SpiceKbdInstance *sin, uint8_t scancode) { - kbd_put_keycode(frag); + QemuSpiceKbd *kbd = container_of(sin, QemuSpiceKbd, sin); + int keycode; + bool up; + + if (scancode == SCANCODE_EMUL0) { + kbd->emul0 = true; + return; + } + keycode = scancode & ~SCANCODE_UP; + up = scancode & SCANCODE_UP; + if (kbd->emul0) { + kbd->emul0 = false; + keycode |= SCANCODE_GREY; + } + + qemu_input_event_send_key_number(NULL, keycode, !up); } static uint8_t kbd_get_leds(SpiceKbdInstance *sin) @@ -80,41 +98,52 @@ static void kbd_leds(void *opaque, int ledstate) typedef struct QemuSpicePointer { SpiceMouseInstance mouse; SpiceTabletInstance tablet; - int width, height, x, y; + int width, height; + uint32_t last_bmask; Notifier mouse_mode; bool absolute; } QemuSpicePointer; -static int map_buttons(int spice_buttons) +static void spice_update_buttons(QemuSpicePointer *pointer, + int wheel, uint32_t button_mask) { - int qemu_buttons = 0; - - /* - * Note: SPICE_MOUSE_BUTTON_* specifies the wire protocol but this - * isn't what we get passed in via interface callbacks for the - * middle and right button ... - */ - if (spice_buttons & SPICE_MOUSE_BUTTON_MASK_LEFT) { - qemu_buttons |= MOUSE_EVENT_LBUTTON; + static uint32_t bmap[INPUT_BUTTON_MAX] = { + [INPUT_BUTTON_LEFT] = 0x01, + [INPUT_BUTTON_MIDDLE] = 0x04, + [INPUT_BUTTON_RIGHT] = 0x02, + [INPUT_BUTTON_WHEEL_UP] = 0x10, + [INPUT_BUTTON_WHEEL_DOWN] = 0x20, + }; + + if (wheel < 0) { + button_mask |= 0x10; } - if (spice_buttons & 0x04 /* SPICE_MOUSE_BUTTON_MASK_MIDDLE */) { - qemu_buttons |= MOUSE_EVENT_MBUTTON; + if (wheel > 0) { + button_mask |= 0x20; } - if (spice_buttons & 0x02 /* SPICE_MOUSE_BUTTON_MASK_RIGHT */) { - qemu_buttons |= MOUSE_EVENT_RBUTTON; + + if (pointer->last_bmask == button_mask) { + return; } - return qemu_buttons; + qemu_input_update_buttons(NULL, bmap, pointer->last_bmask, button_mask); + pointer->last_bmask = button_mask; } static void mouse_motion(SpiceMouseInstance *sin, int dx, int dy, int dz, uint32_t buttons_state) { - kbd_mouse_event(dx, dy, dz, map_buttons(buttons_state)); + QemuSpicePointer *pointer = container_of(sin, QemuSpicePointer, mouse); + spice_update_buttons(pointer, dz, buttons_state); + qemu_input_queue_rel(NULL, INPUT_AXIS_X, dx); + qemu_input_queue_rel(NULL, INPUT_AXIS_Y, dy); + qemu_input_event_sync(); } static void mouse_buttons(SpiceMouseInstance *sin, uint32_t buttons_state) { - kbd_mouse_event(0, 0, 0, map_buttons(buttons_state)); + QemuSpicePointer *pointer = container_of(sin, QemuSpicePointer, mouse); + spice_update_buttons(pointer, 0, buttons_state); + qemu_input_event_sync(); } static const SpiceMouseInterface mouse_interface = { @@ -145,9 +174,10 @@ static void tablet_position(SpiceTabletInstance* sin, int x, int y, { QemuSpicePointer *pointer = container_of(sin, QemuSpicePointer, tablet); - pointer->x = x * 0x7FFF / (pointer->width - 1); - pointer->y = y * 0x7FFF / (pointer->height - 1); - kbd_mouse_event(pointer->x, pointer->y, 0, map_buttons(buttons_state)); + spice_update_buttons(pointer, 0, buttons_state); + qemu_input_queue_abs(NULL, INPUT_AXIS_X, x, pointer->width); + qemu_input_queue_abs(NULL, INPUT_AXIS_Y, y, pointer->height); + qemu_input_event_sync(); } @@ -156,7 +186,8 @@ static void tablet_wheel(SpiceTabletInstance* sin, int wheel, { QemuSpicePointer *pointer = container_of(sin, QemuSpicePointer, tablet); - kbd_mouse_event(pointer->x, pointer->y, wheel, map_buttons(buttons_state)); + spice_update_buttons(pointer, wheel, buttons_state); + qemu_input_event_sync(); } static void tablet_buttons(SpiceTabletInstance *sin, @@ -164,7 +195,8 @@ static void tablet_buttons(SpiceTabletInstance *sin, { QemuSpicePointer *pointer = container_of(sin, QemuSpicePointer, tablet); - kbd_mouse_event(pointer->x, pointer->y, 0, map_buttons(buttons_state)); + spice_update_buttons(pointer, 0, buttons_state); + qemu_input_event_sync(); } static const SpiceTabletInterface tablet_interface = { @@ -181,7 +213,7 @@ static const SpiceTabletInterface tablet_interface = { static void mouse_mode_notifier(Notifier *notifier, void *data) { QemuSpicePointer *pointer = container_of(notifier, QemuSpicePointer, mouse_mode); - bool is_absolute = kbd_mouse_is_absolute(); + bool is_absolute = qemu_input_is_absolute(); if (pointer->absolute == is_absolute) { return; diff --git a/ui/vnc-auth-sasl.h b/ui/vnc-auth-sasl.h index 8091d689c..3f59da67e 100644 --- a/ui/vnc-auth-sasl.h +++ b/ui/vnc-auth-sasl.h @@ -33,6 +33,7 @@ typedef struct VncStateSASL VncStateSASL; typedef struct VncDisplaySASL VncDisplaySASL; #include "qemu/acl.h" +#include "qemu/main-loop.h" struct VncStateSASL { sasl_conn_t *conn; diff --git a/ui/vnc-auth-vencrypt.c b/ui/vnc-auth-vencrypt.c index c59b18860..bc7032e69 100644 --- a/ui/vnc-auth-vencrypt.c +++ b/ui/vnc-auth-vencrypt.c @@ -25,7 +25,7 @@ */ #include "vnc.h" - +#include "qemu/main-loop.h" static void start_auth_vencrypt_subauth(VncState *vs) { diff --git a/ui/vnc-enc-tight.c b/ui/vnc-enc-tight.c index e6966aebc..59b59c0c7 100644 --- a/ui/vnc-enc-tight.c +++ b/ui/vnc-enc-tight.c @@ -330,7 +330,7 @@ tight_detect_smooth_image(VncState *vs, int w, int h) } else { errors = tight_detect_smooth_image16(vs, w, h); } - if (quality != -1) { + if (quality != (uint8_t)-1) { return (errors < tight_conf[quality].jpeg_threshold); } return (errors < tight_conf[compression].gradient_threshold); diff --git a/ui/vnc-enc-zywrle.h b/ui/vnc-enc-zywrle.h index 1ff40b1f4..d436d588f 100644 --- a/ui/vnc-enc-zywrle.h +++ b/ui/vnc-enc-zywrle.h @@ -305,7 +305,7 @@ static inline void harr(int8_t *px0, int8_t *px1) |L1H0H1H0|L1H0H1H0|L1H0H1H0|L1H0H1H0| : level 1 In this method, H/L and X0/X1 is always same position. - This lead us to more speed and less memory. + This leads us to more speed and less memory. Of cause, the result of both method is quite same because it's only difference that coefficient position. */ diff --git a/ui/vnc-jobs.c b/ui/vnc-jobs.c index 2d3fce815..68f3d773d 100644 --- a/ui/vnc-jobs.c +++ b/ui/vnc-jobs.c @@ -252,6 +252,8 @@ static int vnc_worker_thread_loop(VncJobQueue *queue) if (job->vs->csock == -1) { vnc_unlock_display(job->vs->vd); + /* Copy persistent encoding data */ + vnc_async_encoding_end(job->vs, &vs); goto disconnected; } @@ -278,6 +280,9 @@ static int vnc_worker_thread_loop(VncJobQueue *queue) vnc_async_encoding_end(job->vs, &vs); qemu_bh_schedule(job->vs->bh); + } else { + /* Copy persistent encoding data */ + vnc_async_encoding_end(job->vs, &vs); } vnc_unlock_output(job->vs); @@ -333,7 +338,8 @@ void vnc_start_worker_thread(void) return ; q = vnc_queue_init(); - qemu_thread_create(&q->thread, vnc_worker_thread, q, QEMU_THREAD_DETACHED); + qemu_thread_create(&q->thread, "vnc_worker", vnc_worker_thread, q, + QEMU_THREAD_DETACHED); queue = q; /* Set global queue */ } diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c index df8931573..e304bafeb 100644 --- a/ui/vnc-ws.c +++ b/ui/vnc-ws.c @@ -19,6 +19,7 @@ */ #include "vnc.h" +#include "qemu/main-loop.h" #ifdef CONFIG_VNC_TLS #include "qemu/sockets.h" @@ -33,6 +33,7 @@ #include "qapi/qmp/types.h" #include "qmp-commands.h" #include "qemu/osdep.h" +#include "ui/input.h" #define VNC_REFRESH_INTERVAL_BASE GUI_REFRESH_INTERVAL_DEFAULT #define VNC_REFRESH_INTERVAL_INC 50 @@ -416,8 +417,7 @@ out_error: 3) resolutions > 1024 */ -static int vnc_update_client(VncState *vs, int has_dirty); -static int vnc_update_client_sync(VncState *vs, int has_dirty); +static int vnc_update_client(VncState *vs, int has_dirty, bool sync); static void vnc_disconnect_start(VncState *vs); static void vnc_colordepth(VncState *vs); @@ -430,29 +430,25 @@ static int vnc_refresh_server_surface(VncDisplay *vd); static void vnc_dpy_update(DisplayChangeListener *dcl, int x, int y, int w, int h) { - int i; VncDisplay *vd = container_of(dcl, VncDisplay, dcl); struct VncSurface *s = &vd->guest; int width = surface_width(vd->ds); int height = surface_height(vd->ds); - 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); + /* this is needed this to ensure we updated all affected + * blocks if x % VNC_DIRTY_PIXELS_PER_BIT != 0 */ + w += (x % VNC_DIRTY_PIXELS_PER_BIT); + x -= (x % VNC_DIRTY_PIXELS_PER_BIT); x = MIN(x, width); y = MIN(y, height); w = MIN(x + w, width) - x; - h = MIN(h, height); + h = MIN(y + h, height); - for (; y < h; y++) - for (i = 0; i < w; i += 16) - set_bit((x + i) / 16, s->dirty[y]); + for (; y < h; y++) { + bitmap_set(s->dirty[y], x / VNC_DIRTY_PIXELS_PER_BIT, + DIV_ROUND_UP(w, VNC_DIRTY_PIXELS_PER_BIT)); + } } void vnc_framebuffer_update(VncState *vs, int x, int y, int w, int h, @@ -570,6 +566,15 @@ void *vnc_server_fb_ptr(VncDisplay *vd, int x, int y) ptr += x * VNC_SERVER_FB_BYTES; return ptr; } +/* this sets only the visible pixels of a dirty bitmap */ +#define VNC_SET_VISIBLE_PIXELS_DIRTY(bitmap, w, h) {\ + int y;\ + memset(bitmap, 0x00, sizeof(bitmap));\ + for (y = 0; y < h; y++) {\ + bitmap_set(bitmap[y], 0,\ + DIV_ROUND_UP(w, VNC_DIRTY_PIXELS_PER_BIT));\ + } \ + } static void vnc_dpy_switch(DisplayChangeListener *dcl, DisplaySurface *surface) @@ -595,7 +600,9 @@ static void vnc_dpy_switch(DisplayChangeListener *dcl, qemu_pixman_image_unref(vd->guest.fb); vd->guest.fb = pixman_image_ref(surface->image); vd->guest.format = surface->format; - memset(vd->guest.dirty, 0xFF, sizeof(vd->guest.dirty)); + VNC_SET_VISIBLE_PIXELS_DIRTY(vd->guest.dirty, + surface_width(vd->ds), + surface_height(vd->ds)); QTAILQ_FOREACH(vs, &vd->clients, next) { vnc_colordepth(vs); @@ -603,7 +610,9 @@ static void vnc_dpy_switch(DisplayChangeListener *dcl, if (vs->vd->cursor) { vnc_cursor_define(vs); } - memset(vs->dirty, 0xFF, sizeof(vs->dirty)); + VNC_SET_VISIBLE_PIXELS_DIRTY(vs->dirty, + surface_width(vd->ds), + surface_height(vd->ds)); } } @@ -750,7 +759,7 @@ static void vnc_dpy_copy(DisplayChangeListener *dcl, QTAILQ_FOREACH_SAFE(vs, &vd->clients, next, vn) { if (vnc_has_feature(vs, VNC_FEATURE_COPYRECT)) { vs->force_update = 1; - vnc_update_client_sync(vs, 1); + vnc_update_client(vs, 1, true); /* vs might be free()ed here */ } } @@ -769,11 +778,12 @@ static void vnc_dpy_copy(DisplayChangeListener *dcl, y = dst_y + h - 1; inc = -1; } - w_lim = w - (16 - (dst_x % 16)); - if (w_lim < 0) + w_lim = w - (VNC_DIRTY_PIXELS_PER_BIT - (dst_x % VNC_DIRTY_PIXELS_PER_BIT)); + if (w_lim < 0) { w_lim = w; - else - w_lim = w - (w_lim % 16); + } else { + w_lim = w - (w_lim % VNC_DIRTY_PIXELS_PER_BIT); + } for (i = 0; i < h; i++) { for (x = 0; x <= w_lim; x += s, src_row += cmp_bytes, dst_row += cmp_bytes) { @@ -781,10 +791,11 @@ static void vnc_dpy_copy(DisplayChangeListener *dcl, if ((s = w - w_lim) == 0) break; } else if (!x) { - s = (16 - (dst_x % 16)); + s = (VNC_DIRTY_PIXELS_PER_BIT - + (dst_x % VNC_DIRTY_PIXELS_PER_BIT)); s = MIN(s, w_lim); } else { - s = 16; + s = VNC_DIRTY_PIXELS_PER_BIT; } cmp_bytes = s * VNC_SERVER_FB_BYTES; if (memcmp(src_row, dst_row, cmp_bytes) == 0) @@ -792,7 +803,8 @@ static void vnc_dpy_copy(DisplayChangeListener *dcl, memmove(dst_row, src_row, cmp_bytes); QTAILQ_FOREACH(vs, &vd->clients, next) { if (!vnc_has_feature(vs, VNC_FEATURE_COPYRECT)) { - set_bit(((x + dst_x) / 16), vs->dirty[y]); + set_bit(((x + dst_x) / VNC_DIRTY_PIXELS_PER_BIT), + vs->dirty[y]); } } } @@ -861,35 +873,24 @@ static int find_and_clear_dirty_height(struct VncState *vs, int h; for (h = 1; h < (height - y); h++) { - int tmp_x; if (!test_bit(last_x, vs->dirty[y + h])) { break; } - for (tmp_x = last_x; tmp_x < x; tmp_x++) { - clear_bit(tmp_x, vs->dirty[y + h]); - } + bitmap_clear(vs->dirty[y + h], last_x, x - last_x); } return h; } -static int vnc_update_client_sync(VncState *vs, int has_dirty) -{ - int ret = vnc_update_client(vs, has_dirty); - vnc_jobs_join(vs); - return ret; -} - -static int vnc_update_client(VncState *vs, int has_dirty) +static int vnc_update_client(VncState *vs, int has_dirty, bool sync) { if (vs->need_update && vs->csock != -1) { VncDisplay *vd = vs->vd; VncJob *job; int y; - int width, height; + int height, width; int n = 0; - if (vs->output.offset && !vs->audio_cap && !vs->force_update) /* kernel send buffers are full -> drop frames to throttle */ return 0; @@ -905,32 +906,30 @@ static int vnc_update_client(VncState *vs, int has_dirty) */ job = vnc_job_new(vs); - width = MIN(pixman_image_get_width(vd->server), vs->client_width); height = MIN(pixman_image_get_height(vd->server), vs->client_height); + width = MIN(pixman_image_get_width(vd->server), vs->client_width); - for (y = 0; y < height; y++) { - int x; - int last_x = -1; - for (x = 0; x < width / 16; x++) { - if (test_and_clear_bit(x, vs->dirty[y])) { - if (last_x == -1) { - last_x = x; - } - } else { - if (last_x != -1) { - int h = find_and_clear_dirty_height(vs, y, last_x, x, - height); - - n += vnc_job_add_rect(job, last_x * 16, y, - (x - last_x) * 16, h); - } - last_x = -1; - } + y = 0; + for (;;) { + int x, h; + unsigned long x2; + unsigned long offset = find_next_bit((unsigned long *) &vs->dirty, + height * VNC_DIRTY_BPL(vs), + y * VNC_DIRTY_BPL(vs)); + if (offset == height * VNC_DIRTY_BPL(vs)) { + /* no more dirty bits */ + break; } - if (last_x != -1) { - int h = find_and_clear_dirty_height(vs, y, last_x, x, height); - n += vnc_job_add_rect(job, last_x * 16, y, - (x - last_x) * 16, h); + y = offset / VNC_DIRTY_BPL(vs); + x = offset % VNC_DIRTY_BPL(vs); + x2 = find_next_zero_bit((unsigned long *) &vs->dirty[y], + VNC_DIRTY_BPL(vs), x); + bitmap_clear(vs->dirty[y], x, x2 - x); + h = find_and_clear_dirty_height(vs, y, x, x2, height); + x2 = MIN(x2, width / VNC_DIRTY_PIXELS_PER_BIT); + if (x2 > x) { + n += vnc_job_add_rect(job, x * VNC_DIRTY_PIXELS_PER_BIT, y, + (x2 - x) * VNC_DIRTY_PIXELS_PER_BIT, h); } } @@ -939,8 +938,11 @@ static int vnc_update_client(VncState *vs, int has_dirty) return n; } - if (vs->csock == -1) + if (vs->csock == -1) { vnc_disconnect_finish(vs); + } else if (sync) { + vnc_jobs_join(vs); + } return 0; } @@ -1483,7 +1485,7 @@ static void client_cut_text(VncState *vs, size_t len, uint8_t *text) static void check_pointer_type_change(Notifier *notifier, void *data) { VncState *vs = container_of(notifier, VncState, mouse_mode_notifier); - int absolute = kbd_mouse_is_absolute(); + int absolute = qemu_input_is_absolute(); if (vnc_has_feature(vs, VNC_FEATURE_POINTER_TYPE_CHANGE) && vs->absolute != absolute) { vnc_lock_output(vs); @@ -1502,39 +1504,37 @@ static void check_pointer_type_change(Notifier *notifier, void *data) static void pointer_event(VncState *vs, int button_mask, int x, int y) { - int buttons = 0; - int dz = 0; + static uint32_t bmap[INPUT_BUTTON_MAX] = { + [INPUT_BUTTON_LEFT] = 0x01, + [INPUT_BUTTON_MIDDLE] = 0x02, + [INPUT_BUTTON_RIGHT] = 0x04, + [INPUT_BUTTON_WHEEL_UP] = 0x08, + [INPUT_BUTTON_WHEEL_DOWN] = 0x10, + }; + QemuConsole *con = vs->vd->dcl.con; int width = surface_width(vs->vd->ds); int height = surface_height(vs->vd->ds); - if (button_mask & 0x01) - buttons |= MOUSE_EVENT_LBUTTON; - if (button_mask & 0x02) - buttons |= MOUSE_EVENT_MBUTTON; - if (button_mask & 0x04) - buttons |= MOUSE_EVENT_RBUTTON; - if (button_mask & 0x08) - dz = -1; - if (button_mask & 0x10) - dz = 1; + if (vs->last_bmask != button_mask) { + qemu_input_update_buttons(con, bmap, vs->last_bmask, button_mask); + vs->last_bmask = button_mask; + } if (vs->absolute) { - kbd_mouse_event(width > 1 ? x * 0x7FFF / (width - 1) : 0x4000, - height > 1 ? y * 0x7FFF / (height - 1) : 0x4000, - dz, buttons); + qemu_input_queue_abs(con, INPUT_AXIS_X, x, width); + qemu_input_queue_abs(con, INPUT_AXIS_Y, y, height); } else if (vnc_has_feature(vs, VNC_FEATURE_POINTER_TYPE_CHANGE)) { - x -= 0x7FFF; - y -= 0x7FFF; - - kbd_mouse_event(x, y, dz, buttons); + qemu_input_queue_rel(con, INPUT_AXIS_X, x - 0x7FFF); + qemu_input_queue_rel(con, INPUT_AXIS_Y, y - 0x7FFF); } else { - if (vs->last_x != -1) - kbd_mouse_event(x - vs->last_x, - y - vs->last_y, - dz, buttons); + if (vs->last_x != -1) { + qemu_input_queue_rel(con, INPUT_AXIS_X, x - vs->last_x); + qemu_input_queue_rel(con, INPUT_AXIS_Y, y - vs->last_y); + } vs->last_x = x; vs->last_y = y; } + qemu_input_event_sync(); } static void reset_keys(VncState *vs) @@ -1542,9 +1542,7 @@ 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); + qemu_input_event_send_key_number(vs->vd->dcl.con, i, false); vs->modifiers_state[i] = 0; } } @@ -1553,12 +1551,8 @@ static void reset_keys(VncState *vs) 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); + qemu_input_event_send_key_number(vs->vd->dcl.con, keycode, true); + qemu_input_event_send_key_number(vs->vd->dcl.con, keycode, false); } static int current_led_state(VncState *vs) @@ -1700,12 +1694,7 @@ static void do_key_event(VncState *vs, int down, int keycode, int sym) } if (qemu_console_is_graphic(NULL)) { - if (keycode & SCANCODE_GREY) - kbd_put_keycode(SCANCODE_EMUL0); - if (down) - kbd_put_keycode(keycode & SCANCODE_KEYCODEMASK); - else - kbd_put_keycode(keycode | SCANCODE_UP); + qemu_input_event_send_key_number(vs->vd->dcl.con, keycode, down); } else { bool numlock = vs->modifiers_state[0x45]; bool control = (vs->modifiers_state[0x1d] || @@ -1826,10 +1815,7 @@ static void vnc_release_modifiers(VncState *vs) if (!vs->modifiers_state[keycode]) { continue; } - if (keycode & SCANCODE_GREY) { - kbd_put_keycode(SCANCODE_EMUL0); - } - kbd_put_keycode(keycode | SCANCODE_UP); + qemu_input_event_send_key_number(vs->vd->dcl.con, keycode, false); } } @@ -1861,7 +1847,7 @@ static void framebuffer_update_request(VncState *vs, int incremental, int w, int h) { int i; - const size_t width = surface_width(vs->vd->ds) / 16; + const size_t width = surface_width(vs->vd->ds) / VNC_DIRTY_PIXELS_PER_BIT; const size_t height = surface_height(vs->vd->ds); if (y_position > height) { @@ -2563,7 +2549,9 @@ static int vnc_refresh_lossy_rect(VncDisplay *vd, int x, int y) vs->lossy_rect[sty][stx] = 0; for (j = 0; j < VNC_STAT_RECT; ++j) { - bitmap_set(vs->dirty[y + j], x / 16, VNC_STAT_RECT / 16); + bitmap_set(vs->dirty[y + j], + x / VNC_DIRTY_PIXELS_PER_BIT, + VNC_STAT_RECT / VNC_DIRTY_PIXELS_PER_BIT); } has_dirty++; } @@ -2667,8 +2655,8 @@ static int vnc_refresh_server_surface(VncDisplay *vd) int width = pixman_image_get_width(vd->guest.fb); int height = pixman_image_get_height(vd->guest.fb); int y; - uint8_t *guest_row; - uint8_t *server_row; + uint8_t *guest_row0 = NULL, *server_row0; + int guest_stride = 0, server_stride; int cmp_bytes; VncState *vs; int has_dirty = 0; @@ -2686,47 +2674,64 @@ static int vnc_refresh_server_surface(VncDisplay *vd) * Check and copy modified bits from guest to server surface. * Update server dirty map. */ - cmp_bytes = 64; + cmp_bytes = VNC_DIRTY_PIXELS_PER_BIT * VNC_SERVER_FB_BYTES; if (cmp_bytes > vnc_server_fb_stride(vd)) { cmp_bytes = vnc_server_fb_stride(vd); } if (vd->guest.format != VNC_SERVER_FB_FORMAT) { int width = pixman_image_get_width(vd->server); tmpbuf = qemu_pixman_linebuf_create(VNC_SERVER_FB_FORMAT, width); - } - guest_row = (uint8_t *)pixman_image_get_data(vd->guest.fb); - server_row = (uint8_t *)pixman_image_get_data(vd->server); - for (y = 0; y < height; y++) { - if (!bitmap_empty(vd->guest.dirty[y], VNC_DIRTY_BITS)) { - int x; - uint8_t *guest_ptr; - uint8_t *server_ptr; - - if (vd->guest.format != VNC_SERVER_FB_FORMAT) { - qemu_pixman_linebuf_fill(tmpbuf, vd->guest.fb, width, 0, y); - guest_ptr = (uint8_t *)pixman_image_get_data(tmpbuf); - } else { - guest_ptr = guest_row; - } - server_ptr = server_row; + } else { + guest_row0 = (uint8_t *)pixman_image_get_data(vd->guest.fb); + guest_stride = pixman_image_get_stride(vd->guest.fb); + } + server_row0 = (uint8_t *)pixman_image_get_data(vd->server); + server_stride = pixman_image_get_stride(vd->server); + + y = 0; + for (;;) { + int x; + uint8_t *guest_ptr, *server_ptr; + unsigned long offset = find_next_bit((unsigned long *) &vd->guest.dirty, + height * VNC_DIRTY_BPL(&vd->guest), + y * VNC_DIRTY_BPL(&vd->guest)); + if (offset == height * VNC_DIRTY_BPL(&vd->guest)) { + /* no more dirty bits */ + break; + } + y = offset / VNC_DIRTY_BPL(&vd->guest); + x = offset % VNC_DIRTY_BPL(&vd->guest); - for (x = 0; x + 15 < width; - x += 16, guest_ptr += cmp_bytes, server_ptr += cmp_bytes) { - if (!test_and_clear_bit((x / 16), vd->guest.dirty[y])) - continue; - if (memcmp(server_ptr, guest_ptr, cmp_bytes) == 0) - continue; - memcpy(server_ptr, guest_ptr, cmp_bytes); - if (!vd->non_adaptive) - vnc_rect_updated(vd, x, y, &tv); - QTAILQ_FOREACH(vs, &vd->clients, next) { - set_bit((x / 16), vs->dirty[y]); - } - has_dirty++; + server_ptr = server_row0 + y * server_stride + x * cmp_bytes; + + if (vd->guest.format != VNC_SERVER_FB_FORMAT) { + qemu_pixman_linebuf_fill(tmpbuf, vd->guest.fb, width, 0, y); + guest_ptr = (uint8_t *)pixman_image_get_data(tmpbuf); + } else { + guest_ptr = guest_row0 + y * guest_stride; + } + guest_ptr += x * cmp_bytes; + + for (; x < DIV_ROUND_UP(width, VNC_DIRTY_PIXELS_PER_BIT); + x++, guest_ptr += cmp_bytes, server_ptr += cmp_bytes) { + if (!test_and_clear_bit(x, vd->guest.dirty[y])) { + continue; + } + if (memcmp(server_ptr, guest_ptr, cmp_bytes) == 0) { + continue; + } + memcpy(server_ptr, guest_ptr, cmp_bytes); + if (!vd->non_adaptive) { + vnc_rect_updated(vd, x * VNC_DIRTY_PIXELS_PER_BIT, + y, &tv); } + QTAILQ_FOREACH(vs, &vd->clients, next) { + set_bit(x, vs->dirty[y]); + } + has_dirty++; } - guest_row += pixman_image_get_stride(vd->guest.fb); - server_row += pixman_image_get_stride(vd->server); + + y++; } qemu_pixman_image_unref(tmpbuf); return has_dirty; @@ -2749,7 +2754,7 @@ static void vnc_refresh(DisplayChangeListener *dcl) vnc_unlock_display(vd); QTAILQ_FOREACH_SAFE(vs, &vd->clients, next, vn) { - rects += vnc_update_client(vs, has_dirty); + rects += vnc_update_client(vs, has_dirty, false); /* vs might be free()ed here */ } @@ -3149,7 +3154,9 @@ void vnc_display_open(DisplayState *ds, const char *display, Error **errp) acl = 1; #endif } else if (strncmp(options, "lossy", 5) == 0) { +#ifdef CONFIG_VNC_JPEG vs->lossy = true; +#endif } else if (strncmp(options, "non-adaptive", 12) == 0) { vs->non_adaptive = true; } else if (strncmp(options, "share=", 6) == 0) { @@ -3166,6 +3173,13 @@ void vnc_display_open(DisplayState *ds, const char *display, Error **errp) } } + /* adaptive updates are only used with tight encoding and + * if lossy updates are enabled so we can disable all the + * calculations otherwise */ + if (!vs->lossy) { + vs->non_adaptive = true; + } + #ifdef CONFIG_VNC_TLS if (acl && x509 && vs->tls.x509verify) { if (!(vs->tls.acl = qemu_acl_init("vnc.x509dname"))) { @@ -81,8 +81,16 @@ typedef void VncSendHextileTile(VncState *vs, #define VNC_MAX_WIDTH 2560 #define VNC_MAX_HEIGHT 2048 +/* VNC_DIRTY_PIXELS_PER_BIT is the number of dirty pixels represented + * by one bit in the dirty bitmap */ +#define VNC_DIRTY_PIXELS_PER_BIT 16 + /* VNC_DIRTY_BITS is the number of bits in the dirty bitmap. */ -#define VNC_DIRTY_BITS (VNC_MAX_WIDTH / 16) +#define VNC_DIRTY_BITS (VNC_MAX_WIDTH / VNC_DIRTY_PIXELS_PER_BIT) + +/* VNC_DIRTY_BPL (BPL = bits per line) might be greater than + * VNC_DIRTY_BITS due to alignment */ +#define VNC_DIRTY_BPL(x) (sizeof((x)->dirty) / VNC_MAX_HEIGHT * BITS_PER_BYTE) #define VNC_STAT_RECT 64 #define VNC_STAT_COLS (VNC_MAX_WIDTH / VNC_STAT_RECT) @@ -257,6 +265,7 @@ struct VncState int absolute; int last_x; int last_y; + uint32_t last_bmask; int client_width; int client_height; VncShareMode share_mode; diff --git a/ui/vnc_keysym.h b/ui/vnc_keysym.h index 6250bec69..1dc039f71 100644 --- a/ui/vnc_keysym.h +++ b/ui/vnc_keysym.h @@ -224,6 +224,14 @@ static const name2keysym_t name2keysym[]={ { "odoubleacute", 0x1f5}, { "udoubleacute", 0x1fb}, +/* Czech national characters */ +{ "ecaron", 0x1ec}, +{ "scaron", 0x1b9}, +{ "ccaron", 0x1e8}, +{ "rcaron", 0x1f8}, +{ "zcaron", 0x1be}, +{ "uring", 0x1f9}, + /* modifiers */ {"ISO_Level3_Shift", 0xfe03}, /* XK_ISO_Level3_Shift */ {"Control_L", 0xffe3}, /* XK_Control_L */ @@ -342,5 +350,370 @@ static const name2keysym_t name2keysym[]={ {"Katakana_Real", 0xff25}, {"Eisu_toggle", 0xff30}, +{"abovedot", 0x01ff}, /* U+02D9 DOT ABOVE */ +{"amacron", 0x03e0}, /* U+0101 LATIN SMALL LETTER A WITH MACRON */ +{"Amacron", 0x03c0}, /* U+0100 LATIN CAPITAL LETTER A WITH MACRON */ +{"Arabic_ain", 0x05d9}, /* U+0639 ARABIC LETTER AIN */ +{"Arabic_alef", 0x05c7}, /* U+0627 ARABIC LETTER ALEF */ +{"Arabic_alefmaksura", 0x05e9}, /* U+0649 ARABIC LETTER ALEF MAKSURA */ +{"Arabic_beh", 0x05c8}, /* U+0628 ARABIC LETTER BEH */ +{"Arabic_comma", 0x05ac}, /* U+060C ARABIC COMMA */ +{"Arabic_dad", 0x05d6}, /* U+0636 ARABIC LETTER DAD */ +{"Arabic_dal", 0x05cf}, /* U+062F ARABIC LETTER DAL */ +{"Arabic_damma", 0x05ef}, /* U+064F ARABIC DAMMA */ +{"Arabic_dammatan", 0x05ec}, /* U+064C ARABIC DAMMATAN */ +{"Arabic_fatha", 0x05ee}, /* U+064E ARABIC FATHA */ +{"Arabic_fathatan", 0x05eb}, /* U+064B ARABIC FATHATAN */ +{"Arabic_feh", 0x05e1}, /* U+0641 ARABIC LETTER FEH */ +{"Arabic_ghain", 0x05da}, /* U+063A ARABIC LETTER GHAIN */ +{"Arabic_ha", 0x05e7}, /* U+0647 ARABIC LETTER HEH */ +{"Arabic_hah", 0x05cd}, /* U+062D ARABIC LETTER HAH */ +{"Arabic_hamza", 0x05c1}, /* U+0621 ARABIC LETTER HAMZA */ +{"Arabic_hamzaonalef", 0x05c3}, /* U+0623 ARABIC LETTER ALEF WITH HAMZA ABOVE */ +{"Arabic_hamzaonwaw", 0x05c4}, /* U+0624 ARABIC LETTER WAW WITH HAMZA ABOVE */ +{"Arabic_hamzaonyeh", 0x05c6}, /* U+0626 ARABIC LETTER YEH WITH HAMZA ABOVE */ +{"Arabic_hamzaunderalef", 0x05c5}, /* U+0625 ARABIC LETTER ALEF WITH HAMZA BELOW */ +{"Arabic_jeem", 0x05cc}, /* U+062C ARABIC LETTER JEEM */ +{"Arabic_kaf", 0x05e3}, /* U+0643 ARABIC LETTER KAF */ +{"Arabic_kasra", 0x05f0}, /* U+0650 ARABIC KASRA */ +{"Arabic_kasratan", 0x05ed}, /* U+064D ARABIC KASRATAN */ +{"Arabic_khah", 0x05ce}, /* U+062E ARABIC LETTER KHAH */ +{"Arabic_lam", 0x05e4}, /* U+0644 ARABIC LETTER LAM */ +{"Arabic_maddaonalef", 0x05c2}, /* U+0622 ARABIC LETTER ALEF WITH MADDA ABOVE */ +{"Arabic_meem", 0x05e5}, /* U+0645 ARABIC LETTER MEEM */ +{"Arabic_noon", 0x05e6}, /* U+0646 ARABIC LETTER NOON */ +{"Arabic_qaf", 0x05e2}, /* U+0642 ARABIC LETTER QAF */ +{"Arabic_question_mark", 0x05bf}, /* U+061F ARABIC QUESTION MARK */ +{"Arabic_ra", 0x05d1}, /* U+0631 ARABIC LETTER REH */ +{"Arabic_sad", 0x05d5}, /* U+0635 ARABIC LETTER SAD */ +{"Arabic_seen", 0x05d3}, /* U+0633 ARABIC LETTER SEEN */ +{"Arabic_semicolon", 0x05bb}, /* U+061B ARABIC SEMICOLON */ +{"Arabic_shadda", 0x05f1}, /* U+0651 ARABIC SHADDA */ +{"Arabic_sheen", 0x05d4}, /* U+0634 ARABIC LETTER SHEEN */ +{"Arabic_sukun", 0x05f2}, /* U+0652 ARABIC SUKUN */ +{"Arabic_tah", 0x05d7}, /* U+0637 ARABIC LETTER TAH */ +{"Arabic_tatweel", 0x05e0}, /* U+0640 ARABIC TATWEEL */ +{"Arabic_teh", 0x05ca}, /* U+062A ARABIC LETTER TEH */ +{"Arabic_tehmarbuta", 0x05c9}, /* U+0629 ARABIC LETTER TEH MARBUTA */ +{"Arabic_thal", 0x05d0}, /* U+0630 ARABIC LETTER THAL */ +{"Arabic_theh", 0x05cb}, /* U+062B ARABIC LETTER THEH */ +{"Arabic_waw", 0x05e8}, /* U+0648 ARABIC LETTER WAW */ +{"Arabic_yeh", 0x05ea}, /* U+064A ARABIC LETTER YEH */ +{"Arabic_zah", 0x05d8}, /* U+0638 ARABIC LETTER ZAH */ +{"Arabic_zain", 0x05d2}, /* U+0632 ARABIC LETTER ZAIN */ +{"breve", 0x01a2}, /* U+02D8 BREVE */ +{"caron", 0x01b7}, /* U+02C7 CARON */ +{"Ccaron", 0x01c8}, /* U+010C LATIN CAPITAL LETTER C WITH CARON */ +{"Cyrillic_a", 0x06c1}, /* U+0430 CYRILLIC SMALL LETTER A */ +{"Cyrillic_A", 0x06e1}, /* U+0410 CYRILLIC CAPITAL LETTER A */ +{"Cyrillic_be", 0x06c2}, /* U+0431 CYRILLIC SMALL LETTER BE */ +{"Cyrillic_BE", 0x06e2}, /* U+0411 CYRILLIC CAPITAL LETTER BE */ +{"Cyrillic_che", 0x06de}, /* U+0447 CYRILLIC SMALL LETTER CHE */ +{"Cyrillic_CHE", 0x06fe}, /* U+0427 CYRILLIC CAPITAL LETTER CHE */ +{"Cyrillic_de", 0x06c4}, /* U+0434 CYRILLIC SMALL LETTER DE */ +{"Cyrillic_DE", 0x06e4}, /* U+0414 CYRILLIC CAPITAL LETTER DE */ +{"Cyrillic_dzhe", 0x06af}, /* U+045F CYRILLIC SMALL LETTER DZHE */ +{"Cyrillic_DZHE", 0x06bf}, /* U+040F CYRILLIC CAPITAL LETTER DZHE */ +{"Cyrillic_e", 0x06dc}, /* U+044D CYRILLIC SMALL LETTER E */ +{"Cyrillic_E", 0x06fc}, /* U+042D CYRILLIC CAPITAL LETTER E */ +{"Cyrillic_ef", 0x06c6}, /* U+0444 CYRILLIC SMALL LETTER EF */ +{"Cyrillic_EF", 0x06e6}, /* U+0424 CYRILLIC CAPITAL LETTER EF */ +{"Cyrillic_el", 0x06cc}, /* U+043B CYRILLIC SMALL LETTER EL */ +{"Cyrillic_EL", 0x06ec}, /* U+041B CYRILLIC CAPITAL LETTER EL */ +{"Cyrillic_em", 0x06cd}, /* U+043C CYRILLIC SMALL LETTER EM */ +{"Cyrillic_EM", 0x06ed}, /* U+041C CYRILLIC CAPITAL LETTER EM */ +{"Cyrillic_en", 0x06ce}, /* U+043D CYRILLIC SMALL LETTER EN */ +{"Cyrillic_EN", 0x06ee}, /* U+041D CYRILLIC CAPITAL LETTER EN */ +{"Cyrillic_er", 0x06d2}, /* U+0440 CYRILLIC SMALL LETTER ER */ +{"Cyrillic_ER", 0x06f2}, /* U+0420 CYRILLIC CAPITAL LETTER ER */ +{"Cyrillic_es", 0x06d3}, /* U+0441 CYRILLIC SMALL LETTER ES */ +{"Cyrillic_ES", 0x06f3}, /* U+0421 CYRILLIC CAPITAL LETTER ES */ +{"Cyrillic_ghe", 0x06c7}, /* U+0433 CYRILLIC SMALL LETTER GHE */ +{"Cyrillic_GHE", 0x06e7}, /* U+0413 CYRILLIC CAPITAL LETTER GHE */ +{"Cyrillic_ha", 0x06c8}, /* U+0445 CYRILLIC SMALL LETTER HA */ +{"Cyrillic_HA", 0x06e8}, /* U+0425 CYRILLIC CAPITAL LETTER HA */ +{"Cyrillic_hardsign", 0x06df}, /* U+044A CYRILLIC SMALL LETTER HARD SIGN */ +{"Cyrillic_HARDSIGN", 0x06ff}, /* U+042A CYRILLIC CAPITAL LETTER HARD SIGN */ +{"Cyrillic_i", 0x06c9}, /* U+0438 CYRILLIC SMALL LETTER I */ +{"Cyrillic_I", 0x06e9}, /* U+0418 CYRILLIC CAPITAL LETTER I */ +{"Cyrillic_ie", 0x06c5}, /* U+0435 CYRILLIC SMALL LETTER IE */ +{"Cyrillic_IE", 0x06e5}, /* U+0415 CYRILLIC CAPITAL LETTER IE */ +{"Cyrillic_io", 0x06a3}, /* U+0451 CYRILLIC SMALL LETTER IO */ +{"Cyrillic_IO", 0x06b3}, /* U+0401 CYRILLIC CAPITAL LETTER IO */ +{"Cyrillic_je", 0x06a8}, /* U+0458 CYRILLIC SMALL LETTER JE */ +{"Cyrillic_JE", 0x06b8}, /* U+0408 CYRILLIC CAPITAL LETTER JE */ +{"Cyrillic_ka", 0x06cb}, /* U+043A CYRILLIC SMALL LETTER KA */ +{"Cyrillic_KA", 0x06eb}, /* U+041A CYRILLIC CAPITAL LETTER KA */ +{"Cyrillic_lje", 0x06a9}, /* U+0459 CYRILLIC SMALL LETTER LJE */ +{"Cyrillic_LJE", 0x06b9}, /* U+0409 CYRILLIC CAPITAL LETTER LJE */ +{"Cyrillic_nje", 0x06aa}, /* U+045A CYRILLIC SMALL LETTER NJE */ +{"Cyrillic_NJE", 0x06ba}, /* U+040A CYRILLIC CAPITAL LETTER NJE */ +{"Cyrillic_o", 0x06cf}, /* U+043E CYRILLIC SMALL LETTER O */ +{"Cyrillic_O", 0x06ef}, /* U+041E CYRILLIC CAPITAL LETTER O */ +{"Cyrillic_pe", 0x06d0}, /* U+043F CYRILLIC SMALL LETTER PE */ +{"Cyrillic_PE", 0x06f0}, /* U+041F CYRILLIC CAPITAL LETTER PE */ +{"Cyrillic_sha", 0x06db}, /* U+0448 CYRILLIC SMALL LETTER SHA */ +{"Cyrillic_SHA", 0x06fb}, /* U+0428 CYRILLIC CAPITAL LETTER SHA */ +{"Cyrillic_shcha", 0x06dd}, /* U+0449 CYRILLIC SMALL LETTER SHCHA */ +{"Cyrillic_SHCHA", 0x06fd}, /* U+0429 CYRILLIC CAPITAL LETTER SHCHA */ +{"Cyrillic_shorti", 0x06ca}, /* U+0439 CYRILLIC SMALL LETTER SHORT I */ +{"Cyrillic_SHORTI", 0x06ea}, /* U+0419 CYRILLIC CAPITAL LETTER SHORT I */ +{"Cyrillic_softsign", 0x06d8}, /* U+044C CYRILLIC SMALL LETTER SOFT SIGN */ +{"Cyrillic_SOFTSIGN", 0x06f8}, /* U+042C CYRILLIC CAPITAL LETTER SOFT SIGN */ +{"Cyrillic_te", 0x06d4}, /* U+0442 CYRILLIC SMALL LETTER TE */ +{"Cyrillic_TE", 0x06f4}, /* U+0422 CYRILLIC CAPITAL LETTER TE */ +{"Cyrillic_tse", 0x06c3}, /* U+0446 CYRILLIC SMALL LETTER TSE */ +{"Cyrillic_TSE", 0x06e3}, /* U+0426 CYRILLIC CAPITAL LETTER TSE */ +{"Cyrillic_u", 0x06d5}, /* U+0443 CYRILLIC SMALL LETTER U */ +{"Cyrillic_U", 0x06f5}, /* U+0423 CYRILLIC CAPITAL LETTER U */ +{"Cyrillic_ve", 0x06d7}, /* U+0432 CYRILLIC SMALL LETTER VE */ +{"Cyrillic_VE", 0x06f7}, /* U+0412 CYRILLIC CAPITAL LETTER VE */ +{"Cyrillic_ya", 0x06d1}, /* U+044F CYRILLIC SMALL LETTER YA */ +{"Cyrillic_YA", 0x06f1}, /* U+042F CYRILLIC CAPITAL LETTER YA */ +{"Cyrillic_yeru", 0x06d9}, /* U+044B CYRILLIC SMALL LETTER YERU */ +{"Cyrillic_YERU", 0x06f9}, /* U+042B CYRILLIC CAPITAL LETTER YERU */ +{"Cyrillic_yu", 0x06c0}, /* U+044E CYRILLIC SMALL LETTER YU */ +{"Cyrillic_YU", 0x06e0}, /* U+042E CYRILLIC CAPITAL LETTER YU */ +{"Cyrillic_ze", 0x06da}, /* U+0437 CYRILLIC SMALL LETTER ZE */ +{"Cyrillic_ZE", 0x06fa}, /* U+0417 CYRILLIC CAPITAL LETTER ZE */ +{"Cyrillic_zhe", 0x06d6}, /* U+0436 CYRILLIC SMALL LETTER ZHE */ +{"Cyrillic_ZHE", 0x06f6}, /* U+0416 CYRILLIC CAPITAL LETTER ZHE */ +{"doubleacute", 0x01bd}, /* U+02DD DOUBLE ACUTE ACCENT */ +{"doublelowquotemark", 0x0afe}, /* U+201E DOUBLE LOW-9 QUOTATION MARK */ +{"downarrow", 0x08fe}, /* U+2193 DOWNWARDS ARROW */ +{"dstroke", 0x01f0}, /* U+0111 LATIN SMALL LETTER D WITH STROKE */ +{"Dstroke", 0x01d0}, /* U+0110 LATIN CAPITAL LETTER D WITH STROKE */ +{"eabovedot", 0x03ec}, /* U+0117 LATIN SMALL LETTER E WITH DOT ABOVE */ +{"Eabovedot", 0x03cc}, /* U+0116 LATIN CAPITAL LETTER E WITH DOT ABOVE */ +{"emacron", 0x03ba}, /* U+0113 LATIN SMALL LETTER E WITH MACRON */ +{"Emacron", 0x03aa}, /* U+0112 LATIN CAPITAL LETTER E WITH MACRON */ +{"endash", 0x0aaa}, /* U+2013 EN DASH */ +{"eng", 0x03bf}, /* U+014B LATIN SMALL LETTER ENG */ +{"ENG", 0x03bd}, /* U+014A LATIN CAPITAL LETTER ENG */ +{"Execute", 0xff62}, /* Execute, run, do */ +{"F16", 0xffcd}, +{"F17", 0xffce}, +{"F18", 0xffcf}, +{"F19", 0xffd0}, +{"F20", 0xffd1}, +{"F21", 0xffd2}, +{"F22", 0xffd3}, +{"F23", 0xffd4}, +{"F24", 0xffd5}, +{"F25", 0xffd6}, +{"F26", 0xffd7}, +{"F27", 0xffd8}, +{"F28", 0xffd9}, +{"F29", 0xffda}, +{"F30", 0xffdb}, +{"F31", 0xffdc}, +{"F32", 0xffdd}, +{"F33", 0xffde}, +{"F34", 0xffdf}, +{"F35", 0xffe0}, +{"fiveeighths", 0x0ac5}, /* U+215D VULGAR FRACTION FIVE EIGHTHS */ +{"gbreve", 0x02bb}, /* U+011F LATIN SMALL LETTER G WITH BREVE */ +{"Gbreve", 0x02ab}, /* U+011E LATIN CAPITAL LETTER G WITH BREVE */ +{"gcedilla", 0x03bb}, /* U+0123 LATIN SMALL LETTER G WITH CEDILLA */ +{"Gcedilla", 0x03ab}, /* U+0122 LATIN CAPITAL LETTER G WITH CEDILLA */ +{"Greek_OMEGA", 0x07d9}, /* U+03A9 GREEK CAPITAL LETTER OMEGA */ +{"Henkan_Mode", 0xff23}, /* Start/Stop Conversion */ +{"horizconnector", 0x08a3}, /*(U+2500 BOX DRAWINGS LIGHT HORIZONTAL)*/ +{"hstroke", 0x02b1}, /* U+0127 LATIN SMALL LETTER H WITH STROKE */ +{"Hstroke", 0x02a1}, /* U+0126 LATIN CAPITAL LETTER H WITH STROKE */ +{"Iabovedot", 0x02a9}, /* U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE */ +{"idotless", 0x02b9}, /* U+0131 LATIN SMALL LETTER DOTLESS I */ +{"imacron", 0x03ef}, /* U+012B LATIN SMALL LETTER I WITH MACRON */ +{"Imacron", 0x03cf}, /* U+012A LATIN CAPITAL LETTER I WITH MACRON */ +{"iogonek", 0x03e7}, /* U+012F LATIN SMALL LETTER I WITH OGONEK */ +{"Iogonek", 0x03c7}, /* U+012E LATIN CAPITAL LETTER I WITH OGONEK */ +{"ISO_First_Group", 0xfe0c}, +{"ISO_Last_Group", 0xfe0e}, +{"ISO_Next_Group", 0xfe08}, +{"kana_a", 0x04a7}, /* U+30A1 KATAKANA LETTER SMALL A */ +{"kana_A", 0x04b1}, /* U+30A2 KATAKANA LETTER A */ +{"kana_CHI", 0x04c1}, /* U+30C1 KATAKANA LETTER TI */ +{"kana_closingbracket", 0x04a3}, /* U+300D RIGHT CORNER BRACKET */ +{"kana_comma", 0x04a4}, /* U+3001 IDEOGRAPHIC COMMA */ +{"kana_conjunctive", 0x04a5}, /* U+30FB KATAKANA MIDDLE DOT */ +{"kana_e", 0x04aa}, /* U+30A7 KATAKANA LETTER SMALL E */ +{"kana_E", 0x04b4}, /* U+30A8 KATAKANA LETTER E */ +{"kana_FU", 0x04cc}, /* U+30D5 KATAKANA LETTER HU */ +{"kana_fullstop", 0x04a1}, /* U+3002 IDEOGRAPHIC FULL STOP */ +{"kana_HA", 0x04ca}, /* U+30CF KATAKANA LETTER HA */ +{"kana_HE", 0x04cd}, /* U+30D8 KATAKANA LETTER HE */ +{"kana_HI", 0x04cb}, /* U+30D2 KATAKANA LETTER HI */ +{"kana_HO", 0x04ce}, /* U+30DB KATAKANA LETTER HO */ +{"kana_i", 0x04a8}, /* U+30A3 KATAKANA LETTER SMALL I */ +{"kana_I", 0x04b2}, /* U+30A4 KATAKANA LETTER I */ +{"kana_KA", 0x04b6}, /* U+30AB KATAKANA LETTER KA */ +{"kana_KE", 0x04b9}, /* U+30B1 KATAKANA LETTER KE */ +{"kana_KI", 0x04b7}, /* U+30AD KATAKANA LETTER KI */ +{"kana_KO", 0x04ba}, /* U+30B3 KATAKANA LETTER KO */ +{"kana_KU", 0x04b8}, /* U+30AF KATAKANA LETTER KU */ +{"kana_MA", 0x04cf}, /* U+30DE KATAKANA LETTER MA */ +{"kana_ME", 0x04d2}, /* U+30E1 KATAKANA LETTER ME */ +{"kana_MI", 0x04d0}, /* U+30DF KATAKANA LETTER MI */ +{"kana_MO", 0x04d3}, /* U+30E2 KATAKANA LETTER MO */ +{"kana_MU", 0x04d1}, /* U+30E0 KATAKANA LETTER MU */ +{"kana_N", 0x04dd}, /* U+30F3 KATAKANA LETTER N */ +{"kana_NA", 0x04c5}, /* U+30CA KATAKANA LETTER NA */ +{"kana_NE", 0x04c8}, /* U+30CD KATAKANA LETTER NE */ +{"kana_NI", 0x04c6}, /* U+30CB KATAKANA LETTER NI */ +{"kana_NO", 0x04c9}, /* U+30CE KATAKANA LETTER NO */ +{"kana_NU", 0x04c7}, /* U+30CC KATAKANA LETTER NU */ +{"kana_o", 0x04ab}, /* U+30A9 KATAKANA LETTER SMALL O */ +{"kana_O", 0x04b5}, /* U+30AA KATAKANA LETTER O */ +{"kana_openingbracket", 0x04a2}, /* U+300C LEFT CORNER BRACKET */ +{"kana_RA", 0x04d7}, /* U+30E9 KATAKANA LETTER RA */ +{"kana_RE", 0x04da}, /* U+30EC KATAKANA LETTER RE */ +{"kana_RI", 0x04d8}, /* U+30EA KATAKANA LETTER RI */ +{"kana_RU", 0x04d9}, /* U+30EB KATAKANA LETTER RU */ +{"kana_SA", 0x04bb}, /* U+30B5 KATAKANA LETTER SA */ +{"kana_SE", 0x04be}, /* U+30BB KATAKANA LETTER SE */ +{"kana_SHI", 0x04bc}, /* U+30B7 KATAKANA LETTER SI */ +{"kana_SO", 0x04bf}, /* U+30BD KATAKANA LETTER SO */ +{"kana_SU", 0x04bd}, /* U+30B9 KATAKANA LETTER SU */ +{"kana_TA", 0x04c0}, /* U+30BF KATAKANA LETTER TA */ +{"kana_TE", 0x04c3}, /* U+30C6 KATAKANA LETTER TE */ +{"kana_TO", 0x04c4}, /* U+30C8 KATAKANA LETTER TO */ +{"kana_tsu", 0x04af}, /* U+30C3 KATAKANA LETTER SMALL TU */ +{"kana_TSU", 0x04c2}, /* U+30C4 KATAKANA LETTER TU */ +{"kana_u", 0x04a9}, /* U+30A5 KATAKANA LETTER SMALL U */ +{"kana_U", 0x04b3}, /* U+30A6 KATAKANA LETTER U */ +{"kana_WA", 0x04dc}, /* U+30EF KATAKANA LETTER WA */ +{"kana_WO", 0x04a6}, /* U+30F2 KATAKANA LETTER WO */ +{"kana_ya", 0x04ac}, /* U+30E3 KATAKANA LETTER SMALL YA */ +{"kana_YA", 0x04d4}, /* U+30E4 KATAKANA LETTER YA */ +{"kana_yo", 0x04ae}, /* U+30E7 KATAKANA LETTER SMALL YO */ +{"kana_YO", 0x04d6}, /* U+30E8 KATAKANA LETTER YO */ +{"kana_yu", 0x04ad}, /* U+30E5 KATAKANA LETTER SMALL YU */ +{"kana_YU", 0x04d5}, /* U+30E6 KATAKANA LETTER YU */ +{"Kanji", 0xff21}, /* Kanji, Kanji convert */ +{"kcedilla", 0x03f3}, /* U+0137 LATIN SMALL LETTER K WITH CEDILLA */ +{"Kcedilla", 0x03d3}, /* U+0136 LATIN CAPITAL LETTER K WITH CEDILLA */ +{"kra", 0x03a2}, /* U+0138 LATIN SMALL LETTER KRA */ +{"lcedilla", 0x03b6}, /* U+013C LATIN SMALL LETTER L WITH CEDILLA */ +{"Lcedilla", 0x03a6}, /* U+013B LATIN CAPITAL LETTER L WITH CEDILLA */ +{"leftarrow", 0x08fb}, /* U+2190 LEFTWARDS ARROW */ +{"leftdoublequotemark", 0x0ad2}, /* U+201C LEFT DOUBLE QUOTATION MARK */ +{"Macedonia_dse", 0x06a5}, /* U+0455 CYRILLIC SMALL LETTER DZE */ +{"Macedonia_DSE", 0x06b5}, /* U+0405 CYRILLIC CAPITAL LETTER DZE */ +{"Macedonia_gje", 0x06a2}, /* U+0453 CYRILLIC SMALL LETTER GJE */ +{"Macedonia_GJE", 0x06b2}, /* U+0403 CYRILLIC CAPITAL LETTER GJE */ +{"Macedonia_kje", 0x06ac}, /* U+045C CYRILLIC SMALL LETTER KJE */ +{"Macedonia_KJE", 0x06bc}, /* U+040C CYRILLIC CAPITAL LETTER KJE */ +{"ncedilla", 0x03f1}, /* U+0146 LATIN SMALL LETTER N WITH CEDILLA */ +{"Ncedilla", 0x03d1}, /* U+0145 LATIN CAPITAL LETTER N WITH CEDILLA */ +{"oe", 0x13bd}, /* U+0153 LATIN SMALL LIGATURE OE */ +{"OE", 0x13bc}, /* U+0152 LATIN CAPITAL LIGATURE OE */ +{"ogonek", 0x01b2}, /* U+02DB OGONEK */ +{"omacron", 0x03f2}, /* U+014D LATIN SMALL LETTER O WITH MACRON */ +{"Omacron", 0x03d2}, /* U+014C LATIN CAPITAL LETTER O WITH MACRON */ +{"oneeighth", 0x0ac3}, /* U+215B VULGAR FRACTION ONE EIGHTH */ +{"rcedilla", 0x03b3}, /* U+0157 LATIN SMALL LETTER R WITH CEDILLA */ +{"Rcedilla", 0x03a3}, /* U+0156 LATIN CAPITAL LETTER R WITH CEDILLA */ +{"rightarrow", 0x08fd}, /* U+2192 RIGHTWARDS ARROW */ +{"rightdoublequotemark", 0x0ad3}, /* U+201D RIGHT DOUBLE QUOTATION MARK */ +{"Scaron", 0x01a9}, /* U+0160 LATIN CAPITAL LETTER S WITH CARON */ +{"scedilla", 0x01ba}, /* U+015F LATIN SMALL LETTER S WITH CEDILLA */ +{"Scedilla", 0x01aa}, /* U+015E LATIN CAPITAL LETTER S WITH CEDILLA */ +{"semivoicedsound", 0x04df}, /* U+309C KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK */ +{"seveneighths", 0x0ac6}, /* U+215E VULGAR FRACTION SEVEN EIGHTHS */ +{"Thai_baht", 0x0ddf}, /* U+0E3F THAI CURRENCY SYMBOL BAHT */ +{"Thai_bobaimai", 0x0dba}, /* U+0E1A THAI CHARACTER BO BAIMAI */ +{"Thai_chochan", 0x0da8}, /* U+0E08 THAI CHARACTER CHO CHAN */ +{"Thai_chochang", 0x0daa}, /* U+0E0A THAI CHARACTER CHO CHANG */ +{"Thai_choching", 0x0da9}, /* U+0E09 THAI CHARACTER CHO CHING */ +{"Thai_chochoe", 0x0dac}, /* U+0E0C THAI CHARACTER CHO CHOE */ +{"Thai_dochada", 0x0dae}, /* U+0E0E THAI CHARACTER DO CHADA */ +{"Thai_dodek", 0x0db4}, /* U+0E14 THAI CHARACTER DO DEK */ +{"Thai_fofa", 0x0dbd}, /* U+0E1D THAI CHARACTER FO FA */ +{"Thai_fofan", 0x0dbf}, /* U+0E1F THAI CHARACTER FO FAN */ +{"Thai_hohip", 0x0dcb}, /* U+0E2B THAI CHARACTER HO HIP */ +{"Thai_honokhuk", 0x0dce}, /* U+0E2E THAI CHARACTER HO NOKHUK */ +{"Thai_khokhai", 0x0da2}, /* U+0E02 THAI CHARACTER KHO KHAI */ +{"Thai_khokhon", 0x0da5}, /* U+0E05 THAI CHARACTER KHO KHON */ +{"Thai_khokhuat", 0x0da3}, /* U+0E03 THAI CHARACTER KHO KHUAT */ +{"Thai_khokhwai", 0x0da4}, /* U+0E04 THAI CHARACTER KHO KHWAI */ +{"Thai_khorakhang", 0x0da6}, /* U+0E06 THAI CHARACTER KHO RAKHANG */ +{"Thai_kokai", 0x0da1}, /* U+0E01 THAI CHARACTER KO KAI */ +{"Thai_lakkhangyao", 0x0de5}, /* U+0E45 THAI CHARACTER LAKKHANGYAO */ +{"Thai_lekchet", 0x0df7}, /* U+0E57 THAI DIGIT SEVEN */ +{"Thai_lekha", 0x0df5}, /* U+0E55 THAI DIGIT FIVE */ +{"Thai_lekhok", 0x0df6}, /* U+0E56 THAI DIGIT SIX */ +{"Thai_lekkao", 0x0df9}, /* U+0E59 THAI DIGIT NINE */ +{"Thai_leknung", 0x0df1}, /* U+0E51 THAI DIGIT ONE */ +{"Thai_lekpaet", 0x0df8}, /* U+0E58 THAI DIGIT EIGHT */ +{"Thai_leksam", 0x0df3}, /* U+0E53 THAI DIGIT THREE */ +{"Thai_leksi", 0x0df4}, /* U+0E54 THAI DIGIT FOUR */ +{"Thai_leksong", 0x0df2}, /* U+0E52 THAI DIGIT TWO */ +{"Thai_leksun", 0x0df0}, /* U+0E50 THAI DIGIT ZERO */ +{"Thai_lochula", 0x0dcc}, /* U+0E2C THAI CHARACTER LO CHULA */ +{"Thai_loling", 0x0dc5}, /* U+0E25 THAI CHARACTER LO LING */ +{"Thai_lu", 0x0dc6}, /* U+0E26 THAI CHARACTER LU */ +{"Thai_maichattawa", 0x0deb}, /* U+0E4B THAI CHARACTER MAI CHATTAWA */ +{"Thai_maiek", 0x0de8}, /* U+0E48 THAI CHARACTER MAI EK */ +{"Thai_maihanakat", 0x0dd1}, /* U+0E31 THAI CHARACTER MAI HAN-AKAT */ +{"Thai_maitaikhu", 0x0de7}, /* U+0E47 THAI CHARACTER MAITAIKHU */ +{"Thai_maitho", 0x0de9}, /* U+0E49 THAI CHARACTER MAI THO */ +{"Thai_maitri", 0x0dea}, /* U+0E4A THAI CHARACTER MAI TRI */ +{"Thai_maiyamok", 0x0de6}, /* U+0E46 THAI CHARACTER MAIYAMOK */ +{"Thai_moma", 0x0dc1}, /* U+0E21 THAI CHARACTER MO MA */ +{"Thai_ngongu", 0x0da7}, /* U+0E07 THAI CHARACTER NGO NGU */ +{"Thai_nikhahit", 0x0ded}, /* U+0E4D THAI CHARACTER NIKHAHIT */ +{"Thai_nonen", 0x0db3}, /* U+0E13 THAI CHARACTER NO NEN */ +{"Thai_nonu", 0x0db9}, /* U+0E19 THAI CHARACTER NO NU */ +{"Thai_oang", 0x0dcd}, /* U+0E2D THAI CHARACTER O ANG */ +{"Thai_paiyannoi", 0x0dcf}, /* U+0E2F THAI CHARACTER PAIYANNOI */ +{"Thai_phinthu", 0x0dda}, /* U+0E3A THAI CHARACTER PHINTHU */ +{"Thai_phophan", 0x0dbe}, /* U+0E1E THAI CHARACTER PHO PHAN */ +{"Thai_phophung", 0x0dbc}, /* U+0E1C THAI CHARACTER PHO PHUNG */ +{"Thai_phosamphao", 0x0dc0}, /* U+0E20 THAI CHARACTER PHO SAMPHAO */ +{"Thai_popla", 0x0dbb}, /* U+0E1B THAI CHARACTER PO PLA */ +{"Thai_rorua", 0x0dc3}, /* U+0E23 THAI CHARACTER RO RUA */ +{"Thai_ru", 0x0dc4}, /* U+0E24 THAI CHARACTER RU */ +{"Thai_saraa", 0x0dd0}, /* U+0E30 THAI CHARACTER SARA A */ +{"Thai_saraaa", 0x0dd2}, /* U+0E32 THAI CHARACTER SARA AA */ +{"Thai_saraae", 0x0de1}, /* U+0E41 THAI CHARACTER SARA AE */ +{"Thai_saraaimaimalai", 0x0de4}, /* U+0E44 THAI CHARACTER SARA AI MAIMALAI */ +{"Thai_saraaimaimuan", 0x0de3}, /* U+0E43 THAI CHARACTER SARA AI MAIMUAN */ +{"Thai_saraam", 0x0dd3}, /* U+0E33 THAI CHARACTER SARA AM */ +{"Thai_sarae", 0x0de0}, /* U+0E40 THAI CHARACTER SARA E */ +{"Thai_sarai", 0x0dd4}, /* U+0E34 THAI CHARACTER SARA I */ +{"Thai_saraii", 0x0dd5}, /* U+0E35 THAI CHARACTER SARA II */ +{"Thai_sarao", 0x0de2}, /* U+0E42 THAI CHARACTER SARA O */ +{"Thai_sarau", 0x0dd8}, /* U+0E38 THAI CHARACTER SARA U */ +{"Thai_saraue", 0x0dd6}, /* U+0E36 THAI CHARACTER SARA UE */ +{"Thai_sarauee", 0x0dd7}, /* U+0E37 THAI CHARACTER SARA UEE */ +{"Thai_sarauu", 0x0dd9}, /* U+0E39 THAI CHARACTER SARA UU */ +{"Thai_sorusi", 0x0dc9}, /* U+0E29 THAI CHARACTER SO RUSI */ +{"Thai_sosala", 0x0dc8}, /* U+0E28 THAI CHARACTER SO SALA */ +{"Thai_soso", 0x0dab}, /* U+0E0B THAI CHARACTER SO SO */ +{"Thai_sosua", 0x0dca}, /* U+0E2A THAI CHARACTER SO SUA */ +{"Thai_thanthakhat", 0x0dec}, /* U+0E4C THAI CHARACTER THANTHAKHAT */ +{"Thai_thonangmontho", 0x0db1}, /* U+0E11 THAI CHARACTER THO NANGMONTHO */ +{"Thai_thophuthao", 0x0db2}, /* U+0E12 THAI CHARACTER THO PHUTHAO */ +{"Thai_thothahan", 0x0db7}, /* U+0E17 THAI CHARACTER THO THAHAN */ +{"Thai_thothan", 0x0db0}, /* U+0E10 THAI CHARACTER THO THAN */ +{"Thai_thothong", 0x0db8}, /* U+0E18 THAI CHARACTER THO THONG */ +{"Thai_thothung", 0x0db6}, /* U+0E16 THAI CHARACTER THO THUNG */ +{"Thai_topatak", 0x0daf}, /* U+0E0F THAI CHARACTER TO PATAK */ +{"Thai_totao", 0x0db5}, /* U+0E15 THAI CHARACTER TO TAO */ +{"Thai_wowaen", 0x0dc7}, /* U+0E27 THAI CHARACTER WO WAEN */ +{"Thai_yoyak", 0x0dc2}, /* U+0E22 THAI CHARACTER YO YAK */ +{"Thai_yoying", 0x0dad}, /* U+0E0D THAI CHARACTER YO YING */ +{"threeeighths", 0x0ac4}, /* U+215C VULGAR FRACTION THREE EIGHTHS */ +{"trademark", 0x0ac9}, /* U+2122 TRADE MARK SIGN */ +{"tslash", 0x03bc}, /* U+0167 LATIN SMALL LETTER T WITH STROKE */ +{"Tslash", 0x03ac}, /* U+0166 LATIN CAPITAL LETTER T WITH STROKE */ +{"umacron", 0x03fe}, /* U+016B LATIN SMALL LETTER U WITH MACRON */ +{"Umacron", 0x03de}, /* U+016A LATIN CAPITAL LETTER U WITH MACRON */ +{"uogonek", 0x03f9}, /* U+0173 LATIN SMALL LETTER U WITH OGONEK */ +{"Uogonek", 0x03d9}, /* U+0172 LATIN CAPITAL LETTER U WITH OGONEK */ +{"uparrow", 0x08fc}, /* U+2191 UPWARDS ARROW */ +{"voicedsound", 0x04de}, /* U+309B KATAKANA-HIRAGANA VOICED SOUND MARK */ +{"Zcaron", 0x01ae}, /* U+017D LATIN CAPITAL LETTER Z WITH CARON */ + {NULL,0}, }; |