1/* 2 * QEMU Cocoa CG display driver 3 * 4 * Copyright (c) 2008 Mike Kronenberg 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining a copy 7 * of this software and associated documentation files (the "Software"), to deal 8 * in the Software without restriction, including without limitation the rights 9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 * copies of the Software, and to permit persons to whom the Software is 11 * furnished to do so, subject to the following conditions: 12 * 13 * The above copyright notice and this permission notice shall be included in 14 * all copies or substantial portions of the Software. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 * THE SOFTWARE. 23 */ 24 25#import <Cocoa/Cocoa.h> 26#include <crt_externs.h> 27 28#include "qemu-common.h" 29#include "ui/console.h" 30#include "ui/input.h" 31#include "sysemu/sysemu.h" 32 33#ifndef MAC_OS_X_VERSION_10_4 34#define MAC_OS_X_VERSION_10_4 1040 35#endif 36#ifndef MAC_OS_X_VERSION_10_5 37#define MAC_OS_X_VERSION_10_5 1050 38#endif 39#ifndef MAC_OS_X_VERSION_10_6 40#define MAC_OS_X_VERSION_10_6 1060 41#endif 42 43 44//#define DEBUG 45 46#ifdef DEBUG 47#define COCOA_DEBUG(...) { (void) fprintf (stdout, __VA_ARGS__); } 48#else 49#define COCOA_DEBUG(...) ((void) 0) 50#endif 51 52#define cgrect(nsrect) (*(CGRect *)&(nsrect)) 53 54typedef struct { 55 int width; 56 int height; 57 int bitsPerComponent; 58 int bitsPerPixel; 59} QEMUScreen; 60 61NSWindow *normalWindow; 62static DisplayChangeListener *dcl; 63static int last_buttons; 64 65int gArgc; 66char **gArgv; 67 68// keymap conversion 69int keymap[] = 70{ 71// SdlI macI macH SdlH 104xtH 104xtC sdl 72 30, // 0 0x00 0x1e A QZ_a 73 31, // 1 0x01 0x1f S QZ_s 74 32, // 2 0x02 0x20 D QZ_d 75 33, // 3 0x03 0x21 F QZ_f 76 35, // 4 0x04 0x23 H QZ_h 77 34, // 5 0x05 0x22 G QZ_g 78 44, // 6 0x06 0x2c Z QZ_z 79 45, // 7 0x07 0x2d X QZ_x 80 46, // 8 0x08 0x2e C QZ_c 81 47, // 9 0x09 0x2f V QZ_v 82 0, // 10 0x0A Undefined 83 48, // 11 0x0B 0x30 B QZ_b 84 16, // 12 0x0C 0x10 Q QZ_q 85 17, // 13 0x0D 0x11 W QZ_w 86 18, // 14 0x0E 0x12 E QZ_e 87 19, // 15 0x0F 0x13 R QZ_r 88 21, // 16 0x10 0x15 Y QZ_y 89 20, // 17 0x11 0x14 T QZ_t 90 2, // 18 0x12 0x02 1 QZ_1 91 3, // 19 0x13 0x03 2 QZ_2 92 4, // 20 0x14 0x04 3 QZ_3 93 5, // 21 0x15 0x05 4 QZ_4 94 7, // 22 0x16 0x07 6 QZ_6 95 6, // 23 0x17 0x06 5 QZ_5 96 13, // 24 0x18 0x0d = QZ_EQUALS 97 10, // 25 0x19 0x0a 9 QZ_9 98 8, // 26 0x1A 0x08 7 QZ_7 99 12, // 27 0x1B 0x0c - QZ_MINUS 100 9, // 28 0x1C 0x09 8 QZ_8 101 11, // 29 0x1D 0x0b 0 QZ_0 102 27, // 30 0x1E 0x1b ] QZ_RIGHTBRACKET 103 24, // 31 0x1F 0x18 O QZ_o 104 22, // 32 0x20 0x16 U QZ_u 105 26, // 33 0x21 0x1a [ QZ_LEFTBRACKET 106 23, // 34 0x22 0x17 I QZ_i 107 25, // 35 0x23 0x19 P QZ_p 108 28, // 36 0x24 0x1c ENTER QZ_RETURN 109 38, // 37 0x25 0x26 L QZ_l 110 36, // 38 0x26 0x24 J QZ_j 111 40, // 39 0x27 0x28 ' QZ_QUOTE 112 37, // 40 0x28 0x25 K QZ_k 113 39, // 41 0x29 0x27 ; QZ_SEMICOLON 114 43, // 42 0x2A 0x2b \ QZ_BACKSLASH 115 51, // 43 0x2B 0x33 , QZ_COMMA 116 53, // 44 0x2C 0x35 / QZ_SLASH 117 49, // 45 0x2D 0x31 N QZ_n 118 50, // 46 0x2E 0x32 M QZ_m 119 52, // 47 0x2F 0x34 . QZ_PERIOD 120 15, // 48 0x30 0x0f TAB QZ_TAB 121 57, // 49 0x31 0x39 SPACE QZ_SPACE 122 41, // 50 0x32 0x29 ` QZ_BACKQUOTE 123 14, // 51 0x33 0x0e BKSP QZ_BACKSPACE 124 0, // 52 0x34 Undefined 125 1, // 53 0x35 0x01 ESC QZ_ESCAPE 126 220, // 54 0x36 0xdc E0,5C R GUI QZ_RMETA 127 219, // 55 0x37 0xdb E0,5B L GUI QZ_LMETA 128 42, // 56 0x38 0x2a L SHFT QZ_LSHIFT 129 58, // 57 0x39 0x3a CAPS QZ_CAPSLOCK 130 56, // 58 0x3A 0x38 L ALT QZ_LALT 131 29, // 59 0x3B 0x1d L CTRL QZ_LCTRL 132 54, // 60 0x3C 0x36 R SHFT QZ_RSHIFT 133 184,// 61 0x3D 0xb8 E0,38 R ALT QZ_RALT 134 157,// 62 0x3E 0x9d E0,1D R CTRL QZ_RCTRL 135 0, // 63 0x3F Undefined 136 0, // 64 0x40 Undefined 137 0, // 65 0x41 Undefined 138 0, // 66 0x42 Undefined 139 55, // 67 0x43 0x37 KP * QZ_KP_MULTIPLY 140 0, // 68 0x44 Undefined 141 78, // 69 0x45 0x4e KP + QZ_KP_PLUS 142 0, // 70 0x46 Undefined 143 69, // 71 0x47 0x45 NUM QZ_NUMLOCK 144 0, // 72 0x48 Undefined 145 0, // 73 0x49 Undefined 146 0, // 74 0x4A Undefined 147 181,// 75 0x4B 0xb5 E0,35 KP / QZ_KP_DIVIDE 148 152,// 76 0x4C 0x9c E0,1C KP EN QZ_KP_ENTER 149 0, // 77 0x4D undefined 150 74, // 78 0x4E 0x4a KP - QZ_KP_MINUS 151 0, // 79 0x4F Undefined 152 0, // 80 0x50 Undefined 153 0, // 81 0x51 QZ_KP_EQUALS 154 82, // 82 0x52 0x52 KP 0 QZ_KP0 155 79, // 83 0x53 0x4f KP 1 QZ_KP1 156 80, // 84 0x54 0x50 KP 2 QZ_KP2 157 81, // 85 0x55 0x51 KP 3 QZ_KP3 158 75, // 86 0x56 0x4b KP 4 QZ_KP4 159 76, // 87 0x57 0x4c KP 5 QZ_KP5 160 77, // 88 0x58 0x4d KP 6 QZ_KP6 161 71, // 89 0x59 0x47 KP 7 QZ_KP7 162 0, // 90 0x5A Undefined 163 72, // 91 0x5B 0x48 KP 8 QZ_KP8 164 73, // 92 0x5C 0x49 KP 9 QZ_KP9 165 0, // 93 0x5D Undefined 166 0, // 94 0x5E Undefined 167 0, // 95 0x5F Undefined 168 63, // 96 0x60 0x3f F5 QZ_F5 169 64, // 97 0x61 0x40 F6 QZ_F6 170 65, // 98 0x62 0x41 F7 QZ_F7 171 61, // 99 0x63 0x3d F3 QZ_F3 172 66, // 100 0x64 0x42 F8 QZ_F8 173 67, // 101 0x65 0x43 F9 QZ_F9 174 0, // 102 0x66 Undefined 175 87, // 103 0x67 0x57 F11 QZ_F11 176 0, // 104 0x68 Undefined 177 183,// 105 0x69 0xb7 QZ_PRINT 178 0, // 106 0x6A Undefined 179 70, // 107 0x6B 0x46 SCROLL QZ_SCROLLOCK 180 0, // 108 0x6C Undefined 181 68, // 109 0x6D 0x44 F10 QZ_F10 182 0, // 110 0x6E Undefined 183 88, // 111 0x6F 0x58 F12 QZ_F12 184 0, // 112 0x70 Undefined 185 110,// 113 0x71 0x0 QZ_PAUSE 186 210,// 114 0x72 0xd2 E0,52 INSERT QZ_INSERT 187 199,// 115 0x73 0xc7 E0,47 HOME QZ_HOME 188 201,// 116 0x74 0xc9 E0,49 PG UP QZ_PAGEUP 189 211,// 117 0x75 0xd3 E0,53 DELETE QZ_DELETE 190 62, // 118 0x76 0x3e F4 QZ_F4 191 207,// 119 0x77 0xcf E0,4f END QZ_END 192 60, // 120 0x78 0x3c F2 QZ_F2 193 209,// 121 0x79 0xd1 E0,51 PG DN QZ_PAGEDOWN 194 59, // 122 0x7A 0x3b F1 QZ_F1 195 203,// 123 0x7B 0xcb e0,4B L ARROW QZ_LEFT 196 205,// 124 0x7C 0xcd e0,4D R ARROW QZ_RIGHT 197 208,// 125 0x7D 0xd0 E0,50 D ARROW QZ_DOWN 198 200,// 126 0x7E 0xc8 E0,48 U ARROW QZ_UP 199/* 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 */ 200 201/* Additional 104 Key XP-Keyboard Scancodes from http://www.computer-engineering.org/ps2keyboard/scancodes1.html */ 202/* 203 221 // 0xdd e0,5d APPS 204 // E0,2A,E0,37 PRNT SCRN 205 // E1,1D,45,E1,9D,C5 PAUSE 206 83 // 0x53 0x53 KP . 207// ACPI Scan Codes 208 222 // 0xde E0, 5E Power 209 223 // 0xdf E0, 5F Sleep 210 227 // 0xe3 E0, 63 Wake 211// Windows Multimedia Scan Codes 212 153 // 0x99 E0, 19 Next Track 213 144 // 0x90 E0, 10 Previous Track 214 164 // 0xa4 E0, 24 Stop 215 162 // 0xa2 E0, 22 Play/Pause 216 160 // 0xa0 E0, 20 Mute 217 176 // 0xb0 E0, 30 Volume Up 218 174 // 0xae E0, 2E Volume Down 219 237 // 0xed E0, 6D Media Select 220 236 // 0xec E0, 6C E-Mail 221 161 // 0xa1 E0, 21 Calculator 222 235 // 0xeb E0, 6B My Computer 223 229 // 0xe5 E0, 65 WWW Search 224 178 // 0xb2 E0, 32 WWW Home 225 234 // 0xea E0, 6A WWW Back 226 233 // 0xe9 E0, 69 WWW Forward 227 232 // 0xe8 E0, 68 WWW Stop 228 231 // 0xe7 E0, 67 WWW Refresh 229 230 // 0xe6 E0, 66 WWW Favorites 230*/ 231}; 232 233static int cocoa_keycode_to_qemu(int keycode) 234{ 235 if (ARRAY_SIZE(keymap) <= keycode) { 236 fprintf(stderr, "(cocoa) warning unknown keycode 0x%x\n", keycode); 237 return 0; 238 } 239 return keymap[keycode]; 240} 241 242 243 244/* 245 ------------------------------------------------------ 246 QemuCocoaView 247 ------------------------------------------------------ 248*/ 249@interface QemuCocoaView : NSView 250{ 251 QEMUScreen screen; 252 NSWindow *fullScreenWindow; 253 float cx,cy,cw,ch,cdx,cdy; 254 CGDataProviderRef dataProviderRef; 255 int modifiers_state[256]; 256 BOOL isMouseGrabbed; 257 BOOL isFullscreen; 258 BOOL isAbsoluteEnabled; 259 BOOL isTabletEnabled; 260} 261- (void) switchSurface:(DisplaySurface *)surface; 262- (void) grabMouse; 263- (void) ungrabMouse; 264- (void) toggleFullScreen:(id)sender; 265- (void) handleEvent:(NSEvent *)event; 266- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled; 267- (BOOL) isMouseGrabbed; 268- (BOOL) isAbsoluteEnabled; 269- (float) cdx; 270- (float) cdy; 271- (QEMUScreen) gscreen; 272@end 273 274QemuCocoaView *cocoaView; 275 276@implementation QemuCocoaView 277- (id)initWithFrame:(NSRect)frameRect 278{ 279 COCOA_DEBUG("QemuCocoaView: initWithFrame\n"); 280 281 self = [super initWithFrame:frameRect]; 282 if (self) { 283 284 screen.bitsPerComponent = 8; 285 screen.bitsPerPixel = 32; 286 screen.width = frameRect.size.width; 287 screen.height = frameRect.size.height; 288 289 } 290 return self; 291} 292 293- (void) dealloc 294{ 295 COCOA_DEBUG("QemuCocoaView: dealloc\n"); 296 297 if (dataProviderRef) 298 CGDataProviderRelease(dataProviderRef); 299 300 [super dealloc]; 301} 302 303- (BOOL) isOpaque 304{ 305 return YES; 306} 307 308- (void) drawRect:(NSRect) rect 309{ 310 COCOA_DEBUG("QemuCocoaView: drawRect\n"); 311 312 // get CoreGraphic context 313 CGContextRef viewContextRef = [[NSGraphicsContext currentContext] graphicsPort]; 314 CGContextSetInterpolationQuality (viewContextRef, kCGInterpolationNone); 315 CGContextSetShouldAntialias (viewContextRef, NO); 316 317 // draw screen bitmap directly to Core Graphics context 318 if (!dataProviderRef) { 319 // Draw request before any guest device has set up a framebuffer: 320 // just draw an opaque black rectangle 321 CGContextSetRGBFillColor(viewContextRef, 0, 0, 0, 1.0); 322 CGContextFillRect(viewContextRef, NSRectToCGRect(rect)); 323 } else { 324 CGImageRef imageRef = CGImageCreate( 325 screen.width, //width 326 screen.height, //height 327 screen.bitsPerComponent, //bitsPerComponent 328 screen.bitsPerPixel, //bitsPerPixel 329 (screen.width * (screen.bitsPerComponent/2)), //bytesPerRow 330#ifdef __LITTLE_ENDIAN__ 331 CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB), //colorspace for OS X >= 10.4 332 kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, 333#else 334 CGColorSpaceCreateDeviceRGB(), //colorspace for OS X < 10.4 (actually ppc) 335 kCGImageAlphaNoneSkipFirst, //bitmapInfo 336#endif 337 dataProviderRef, //provider 338 NULL, //decode 339 0, //interpolate 340 kCGRenderingIntentDefault //intent 341 ); 342// test if host supports "CGImageCreateWithImageInRect" at compile time 343#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4) 344 if (CGImageCreateWithImageInRect == NULL) { // test if "CGImageCreateWithImageInRect" is supported on host at runtime 345#endif 346 // compatibility drawing code (draws everything) (OS X < 10.4) 347 CGContextDrawImage (viewContextRef, CGRectMake(0, 0, [self bounds].size.width, [self bounds].size.height), imageRef); 348#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4) 349 } else { 350 // selective drawing code (draws only dirty rectangles) (OS X >= 10.4) 351 const NSRect *rectList; 352#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) 353 NSInteger rectCount; 354#else 355 int rectCount; 356#endif 357 int i; 358 CGImageRef clipImageRef; 359 CGRect clipRect; 360 361 [self getRectsBeingDrawn:&rectList count:&rectCount]; 362 for (i = 0; i < rectCount; i++) { 363 clipRect.origin.x = rectList[i].origin.x / cdx; 364 clipRect.origin.y = (float)screen.height - (rectList[i].origin.y + rectList[i].size.height) / cdy; 365 clipRect.size.width = rectList[i].size.width / cdx; 366 clipRect.size.height = rectList[i].size.height / cdy; 367 clipImageRef = CGImageCreateWithImageInRect( 368 imageRef, 369 clipRect 370 ); 371 CGContextDrawImage (viewContextRef, cgrect(rectList[i]), clipImageRef); 372 CGImageRelease (clipImageRef); 373 } 374 } 375#endif 376 CGImageRelease (imageRef); 377 } 378} 379 380- (void) setContentDimensions 381{ 382 COCOA_DEBUG("QemuCocoaView: setContentDimensions\n"); 383 384 if (isFullscreen) { 385 cdx = [[NSScreen mainScreen] frame].size.width / (float)screen.width; 386 cdy = [[NSScreen mainScreen] frame].size.height / (float)screen.height; 387 cw = screen.width * cdx; 388 ch = screen.height * cdy; 389 cx = ([[NSScreen mainScreen] frame].size.width - cw) / 2.0; 390 cy = ([[NSScreen mainScreen] frame].size.height - ch) / 2.0; 391 } else { 392 cx = 0; 393 cy = 0; 394 cw = screen.width; 395 ch = screen.height; 396 cdx = 1.0; 397 cdy = 1.0; 398 } 399} 400 401- (void) switchSurface:(DisplaySurface *)surface 402{ 403 COCOA_DEBUG("QemuCocoaView: switchSurface\n"); 404 405 int w = surface_width(surface); 406 int h = surface_height(surface); 407 bool isResize = (w != screen.width || h != screen.height); 408 409 int oldh = screen.height; 410 if (isResize) { 411 // Resize before we trigger the redraw, or we'll redraw at the wrong size 412 COCOA_DEBUG("switchSurface: new size %d x %d\n", w, h); 413 screen.width = w; 414 screen.height = h; 415 [self setContentDimensions]; 416 [self setFrame:NSMakeRect(cx, cy, cw, ch)]; 417 } 418 419 // update screenBuffer 420 if (dataProviderRef) 421 CGDataProviderRelease(dataProviderRef); 422 423 //sync host window color space with guests 424 screen.bitsPerPixel = surface_bits_per_pixel(surface); 425 screen.bitsPerComponent = surface_bytes_per_pixel(surface) * 2; 426 427 dataProviderRef = CGDataProviderCreateWithData(NULL, surface_data(surface), w * 4 * h, NULL); 428 429 // update windows 430 if (isFullscreen) { 431 [[fullScreenWindow contentView] setFrame:[[NSScreen mainScreen] frame]]; 432 [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + oldh, w, h + [normalWindow frame].size.height - oldh) display:NO animate:NO]; 433 } else { 434 if (qemu_name) 435 [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]]; 436 [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + oldh, w, h + [normalWindow frame].size.height - oldh) display:YES animate:NO]; 437 } 438 439 if (isResize) { 440 [normalWindow center]; 441 } 442} 443 444- (void) toggleFullScreen:(id)sender 445{ 446 COCOA_DEBUG("QemuCocoaView: toggleFullScreen\n"); 447 448 if (isFullscreen) { // switch from fullscreen to desktop 449 isFullscreen = FALSE; 450 [self ungrabMouse]; 451 [self setContentDimensions]; 452// test if host supports "exitFullScreenModeWithOptions" at compile time 453#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) 454 if ([NSView respondsToSelector:@selector(exitFullScreenModeWithOptions:)]) { // test if "exitFullScreenModeWithOptions" is supported on host at runtime 455 [self exitFullScreenModeWithOptions:nil]; 456 } else { 457#endif 458 [fullScreenWindow close]; 459 [normalWindow setContentView: self]; 460 [normalWindow makeKeyAndOrderFront: self]; 461 [NSMenu setMenuBarVisible:YES]; 462#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) 463 } 464#endif 465 } else { // switch from desktop to fullscreen 466 isFullscreen = TRUE; 467 [self grabMouse]; 468 [self setContentDimensions]; 469// test if host supports "enterFullScreenMode:withOptions" at compile time 470#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) 471 if ([NSView respondsToSelector:@selector(enterFullScreenMode:withOptions:)]) { // test if "enterFullScreenMode:withOptions" is supported on host at runtime 472 [self enterFullScreenMode:[NSScreen mainScreen] withOptions:[NSDictionary dictionaryWithObjectsAndKeys: 473 [NSNumber numberWithBool:NO], NSFullScreenModeAllScreens, 474 [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO], kCGDisplayModeIsStretched, nil], NSFullScreenModeSetting, 475 nil]]; 476 } else { 477#endif 478 [NSMenu setMenuBarVisible:NO]; 479 fullScreenWindow = [[NSWindow alloc] initWithContentRect:[[NSScreen mainScreen] frame] 480 styleMask:NSBorderlessWindowMask 481 backing:NSBackingStoreBuffered 482 defer:NO]; 483 [fullScreenWindow setHasShadow:NO]; 484 [fullScreenWindow setContentView:self]; 485 [fullScreenWindow makeKeyAndOrderFront:self]; 486#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) 487 } 488#endif 489 } 490} 491 492- (void) handleEvent:(NSEvent *)event 493{ 494 COCOA_DEBUG("QemuCocoaView: handleEvent\n"); 495 496 int buttons = 0; 497 int keycode; 498 bool mouse_event = false; 499 NSPoint p = [event locationInWindow]; 500 501 switch ([event type]) { 502 case NSFlagsChanged: 503 keycode = cocoa_keycode_to_qemu([event keyCode]); 504 505 if ((keycode == 219 || keycode == 220) && !isMouseGrabbed) { 506 /* Don't pass command key changes to guest unless mouse is grabbed */ 507 keycode = 0; 508 } 509 510 if (keycode) { 511 if (keycode == 58 || keycode == 69) { // emulate caps lock and num lock keydown and keyup 512 qemu_input_event_send_key_number(dcl->con, keycode, true); 513 qemu_input_event_send_key_number(dcl->con, keycode, false); 514 } else if (qemu_console_is_graphic(NULL)) { 515 if (modifiers_state[keycode] == 0) { // keydown 516 qemu_input_event_send_key_number(dcl->con, keycode, true); 517 modifiers_state[keycode] = 1; 518 } else { // keyup 519 qemu_input_event_send_key_number(dcl->con, keycode, false); 520 modifiers_state[keycode] = 0; 521 } 522 } 523 } 524 525 // release Mouse grab when pressing ctrl+alt 526 if (!isFullscreen && ([event modifierFlags] & NSControlKeyMask) && ([event modifierFlags] & NSAlternateKeyMask)) { 527 [self ungrabMouse]; 528 } 529 break; 530 case NSKeyDown: 531 keycode = cocoa_keycode_to_qemu([event keyCode]); 532 533 // forward command key combos to the host UI unless the mouse is grabbed 534 if (!isMouseGrabbed && ([event modifierFlags] & NSCommandKeyMask)) { 535 [NSApp sendEvent:event]; 536 return; 537 } 538 539 // default 540 541 // handle control + alt Key Combos (ctrl+alt is reserved for QEMU) 542 if (([event modifierFlags] & NSControlKeyMask) && ([event modifierFlags] & NSAlternateKeyMask)) { 543 switch (keycode) { 544 545 // enable graphic console 546 case 0x02 ... 0x0a: // '1' to '9' keys 547 console_select(keycode - 0x02); 548 break; 549 } 550 551 // handle keys for graphic console 552 } else if (qemu_console_is_graphic(NULL)) { 553 qemu_input_event_send_key_number(dcl->con, keycode, true); 554 555 // handlekeys for Monitor 556 } else { 557 int keysym = 0; 558 switch([event keyCode]) { 559 case 115: 560 keysym = QEMU_KEY_HOME; 561 break; 562 case 117: 563 keysym = QEMU_KEY_DELETE; 564 break; 565 case 119: 566 keysym = QEMU_KEY_END; 567 break; 568 case 123: 569 keysym = QEMU_KEY_LEFT; 570 break; 571 case 124: 572 keysym = QEMU_KEY_RIGHT; 573 break; 574 case 125: 575 keysym = QEMU_KEY_DOWN; 576 break; 577 case 126: 578 keysym = QEMU_KEY_UP; 579 break; 580 default: 581 { 582 NSString *ks = [event characters]; 583 if ([ks length] > 0) 584 keysym = [ks characterAtIndex:0]; 585 } 586 } 587 if (keysym) 588 kbd_put_keysym(keysym); 589 } 590 break; 591 case NSKeyUp: 592 keycode = cocoa_keycode_to_qemu([event keyCode]); 593 594 // don't pass the guest a spurious key-up if we treated this 595 // command-key combo as a host UI action 596 if (!isMouseGrabbed && ([event modifierFlags] & NSCommandKeyMask)) { 597 return; 598 } 599 600 if (qemu_console_is_graphic(NULL)) { 601 qemu_input_event_send_key_number(dcl->con, keycode, false); 602 } 603 break; 604 case NSMouseMoved: 605 if (isAbsoluteEnabled) { 606 if (p.x < 0 || p.x > screen.width || p.y < 0 || p.y > screen.height || ![[self window] isKeyWindow]) { 607 if (isTabletEnabled) { // if we leave the window, deactivate the tablet 608 [NSCursor unhide]; 609 isTabletEnabled = FALSE; 610 } 611 } else { 612 if (!isTabletEnabled) { // if we enter the window, activate the tablet 613 [NSCursor hide]; 614 isTabletEnabled = TRUE; 615 } 616 } 617 } 618 mouse_event = true; 619 break; 620 case NSLeftMouseDown: 621 if ([event modifierFlags] & NSCommandKeyMask) { 622 buttons |= MOUSE_EVENT_RBUTTON; 623 } else { 624 buttons |= MOUSE_EVENT_LBUTTON; 625 } 626 mouse_event = true; 627 break; 628 case NSRightMouseDown: 629 buttons |= MOUSE_EVENT_RBUTTON; 630 mouse_event = true; 631 break; 632 case NSOtherMouseDown: 633 buttons |= MOUSE_EVENT_MBUTTON; 634 mouse_event = true; 635 break; 636 case NSLeftMouseDragged: 637 if ([event modifierFlags] & NSCommandKeyMask) { 638 buttons |= MOUSE_EVENT_RBUTTON; 639 } else { 640 buttons |= MOUSE_EVENT_LBUTTON; 641 } 642 mouse_event = true; 643 break; 644 case NSRightMouseDragged: 645 buttons |= MOUSE_EVENT_RBUTTON; 646 mouse_event = true; 647 break; 648 case NSOtherMouseDragged: 649 buttons |= MOUSE_EVENT_MBUTTON; 650 mouse_event = true; 651 break; 652 case NSLeftMouseUp: 653 if (isTabletEnabled) { 654 mouse_event = true; 655 } else if (!isMouseGrabbed) { 656 if (p.x > -1 && p.x < screen.width && p.y > -1 && p.y < screen.height) { 657 [self grabMouse]; 658 } else { 659 [NSApp sendEvent:event]; 660 } 661 } else { 662 mouse_event = true; 663 } 664 break; 665 case NSRightMouseUp: 666 mouse_event = true; 667 break; 668 case NSOtherMouseUp: 669 mouse_event = true; 670 break; 671 case NSScrollWheel: 672 if (isTabletEnabled || isMouseGrabbed) { 673 buttons |= ([event deltaY] < 0) ? 674 MOUSE_EVENT_WHEELUP : MOUSE_EVENT_WHEELDN; 675 mouse_event = true; 676 } else { 677 [NSApp sendEvent:event]; 678 } 679 break; 680 default: 681 [NSApp sendEvent:event]; 682 } 683 684 if (mouse_event) { 685 if (last_buttons != buttons) { 686 static uint32_t bmap[INPUT_BUTTON_MAX] = { 687 [INPUT_BUTTON_LEFT] = MOUSE_EVENT_LBUTTON, 688 [INPUT_BUTTON_MIDDLE] = MOUSE_EVENT_MBUTTON, 689 [INPUT_BUTTON_RIGHT] = MOUSE_EVENT_RBUTTON, 690 [INPUT_BUTTON_WHEEL_UP] = MOUSE_EVENT_WHEELUP, 691 [INPUT_BUTTON_WHEEL_DOWN] = MOUSE_EVENT_WHEELDN, 692 }; 693 qemu_input_update_buttons(dcl->con, bmap, last_buttons, buttons); 694 last_buttons = buttons; 695 } 696 if (isTabletEnabled) { 697 qemu_input_queue_abs(dcl->con, INPUT_AXIS_X, p.x, screen.width); 698 qemu_input_queue_abs(dcl->con, INPUT_AXIS_Y, p.y, screen.height); 699 } else if (isMouseGrabbed) { 700 qemu_input_queue_rel(dcl->con, INPUT_AXIS_X, (int)[event deltaX]); 701 qemu_input_queue_rel(dcl->con, INPUT_AXIS_Y, (int)[event deltaY]); 702 } else { 703 [NSApp sendEvent:event]; 704 } 705 qemu_input_event_sync(); 706 } 707} 708 709- (void) grabMouse 710{ 711 COCOA_DEBUG("QemuCocoaView: grabMouse\n"); 712 713 if (!isFullscreen) { 714 if (qemu_name) 715 [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s - (Press ctrl + alt to release Mouse)", qemu_name]]; 716 else 717 [normalWindow setTitle:@"QEMU - (Press ctrl + alt to release Mouse)"]; 718 } 719 [NSCursor hide]; 720 CGAssociateMouseAndMouseCursorPosition(FALSE); 721 isMouseGrabbed = TRUE; // while isMouseGrabbed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:] 722} 723 724- (void) ungrabMouse 725{ 726 COCOA_DEBUG("QemuCocoaView: ungrabMouse\n"); 727 728 if (!isFullscreen) { 729 if (qemu_name) 730 [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]]; 731 else 732 [normalWindow setTitle:@"QEMU"]; 733 } 734 [NSCursor unhide]; 735 CGAssociateMouseAndMouseCursorPosition(TRUE); 736 isMouseGrabbed = FALSE; 737} 738 739- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled {isAbsoluteEnabled = tIsAbsoluteEnabled;} 740- (BOOL) isMouseGrabbed {return isMouseGrabbed;} 741- (BOOL) isAbsoluteEnabled {return isAbsoluteEnabled;} 742- (float) cdx {return cdx;} 743- (float) cdy {return cdy;} 744- (QEMUScreen) gscreen {return screen;} 745@end 746 747 748 749/* 750 ------------------------------------------------------ 751 QemuCocoaAppController 752 ------------------------------------------------------ 753*/ 754@interface QemuCocoaAppController : NSObject 755{ 756} 757- (void)startEmulationWithArgc:(int)argc argv:(char**)argv; 758- (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo; 759- (void)toggleFullScreen:(id)sender; 760- (void)showQEMUDoc:(id)sender; 761- (void)showQEMUTec:(id)sender; 762@end 763 764@implementation QemuCocoaAppController 765- (id) init 766{ 767 COCOA_DEBUG("QemuCocoaAppController: init\n"); 768 769 self = [super init]; 770 if (self) { 771 772 // create a view and add it to the window 773 cocoaView = [[QemuCocoaView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 640.0, 480.0)]; 774 if(!cocoaView) { 775 fprintf(stderr, "(cocoa) can't create a view\n"); 776 exit(1); 777 } 778 779 // create a window 780 normalWindow = [[NSWindow alloc] initWithContentRect:[cocoaView frame] 781 styleMask:NSTitledWindowMask|NSMiniaturizableWindowMask|NSClosableWindowMask 782 backing:NSBackingStoreBuffered defer:NO]; 783 if(!normalWindow) { 784 fprintf(stderr, "(cocoa) can't create window\n"); 785 exit(1); 786 } 787 [normalWindow setAcceptsMouseMovedEvents:YES]; 788 [normalWindow setTitle:[NSString stringWithFormat:@"QEMU"]]; 789 [normalWindow setContentView:cocoaView]; 790 [normalWindow useOptimizedDrawing:YES]; 791 [normalWindow makeKeyAndOrderFront:self]; 792 [normalWindow center]; 793 794 } 795 return self; 796} 797 798- (void) dealloc 799{ 800 COCOA_DEBUG("QemuCocoaAppController: dealloc\n"); 801 802 if (cocoaView) 803 [cocoaView release]; 804 [super dealloc]; 805} 806 807- (void)applicationDidFinishLaunching: (NSNotification *) note 808{ 809 COCOA_DEBUG("QemuCocoaAppController: applicationDidFinishLaunching\n"); 810 811 // Display an open dialog box if no arguments were passed or 812 // if qemu was launched from the finder ( the Finder passes "-psn" ) 813 if( gArgc <= 1 || strncmp ((char *)gArgv[1], "-psn", 4) == 0) { 814 NSOpenPanel *op = [[NSOpenPanel alloc] init]; 815 [op setPrompt:@"Boot image"]; 816 [op setMessage:@"Select the disk image you want to boot.\n\nHit the \"Cancel\" button to quit"]; 817 NSArray *filetypes = [NSArray arrayWithObjects:@"img", @"iso", @"dmg", 818 @"qcow", @"qcow2", @"cow", @"cloop", @"vmdk", nil]; 819#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6) 820 [op setAllowedFileTypes:filetypes]; 821 [op beginSheetModalForWindow:normalWindow 822 completionHandler:^(NSInteger returnCode) 823 { [self openPanelDidEnd:op 824 returnCode:returnCode contextInfo:NULL ]; } ]; 825#else 826 // Compatibility code for pre-10.6, using deprecated method 827 [op beginSheetForDirectory:nil file:nil types:filetypes 828 modalForWindow:normalWindow modalDelegate:self 829 didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:) contextInfo:NULL]; 830#endif 831 } else { 832 // or launch QEMU, with the global args 833 [self startEmulationWithArgc:gArgc argv:(char **)gArgv]; 834 } 835} 836 837- (void)applicationWillTerminate:(NSNotification *)aNotification 838{ 839 COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n"); 840 841 qemu_system_shutdown_request(); 842 exit(0); 843} 844 845- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication 846{ 847 return YES; 848} 849 850- (void)startEmulationWithArgc:(int)argc argv:(char**)argv 851{ 852 COCOA_DEBUG("QemuCocoaAppController: startEmulationWithArgc\n"); 853 854 int status; 855 status = qemu_main(argc, argv, *_NSGetEnviron()); 856 exit(status); 857} 858 859- (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo 860{ 861 COCOA_DEBUG("QemuCocoaAppController: openPanelDidEnd\n"); 862 863 if(returnCode == NSCancelButton) { 864 exit(0); 865 } else if(returnCode == NSOKButton) { 866 char *img = (char*)[ [ [ sheet URL ] path ] cStringUsingEncoding:NSASCIIStringEncoding]; 867 868 char **argv = g_new(char *, 4); 869 870 [sheet close]; 871 872 argv[0] = g_strdup(gArgv[0]); 873 argv[1] = g_strdup("-hda"); 874 argv[2] = g_strdup(img); 875 argv[3] = NULL; 876 877 // printf("Using argc %d argv %s -hda %s\n", 3, gArgv[0], img); 878 879 [self startEmulationWithArgc:3 argv:(char**)argv]; 880 } 881} 882- (void)toggleFullScreen:(id)sender 883{ 884 COCOA_DEBUG("QemuCocoaAppController: toggleFullScreen\n"); 885 886 [cocoaView toggleFullScreen:sender]; 887} 888 889- (void)showQEMUDoc:(id)sender 890{ 891 COCOA_DEBUG("QemuCocoaAppController: showQEMUDoc\n"); 892 893 [[NSWorkspace sharedWorkspace] openFile:[NSString stringWithFormat:@"%@/../doc/qemu/qemu-doc.html", 894 [[NSBundle mainBundle] resourcePath]] withApplication:@"Help Viewer"]; 895} 896 897- (void)showQEMUTec:(id)sender 898{ 899 COCOA_DEBUG("QemuCocoaAppController: showQEMUTec\n"); 900 901 [[NSWorkspace sharedWorkspace] openFile:[NSString stringWithFormat:@"%@/../doc/qemu/qemu-tech.html", 902 [[NSBundle mainBundle] resourcePath]] withApplication:@"Help Viewer"]; 903} 904@end 905 906 907 908int main (int argc, const char * argv[]) { 909 910 gArgc = argc; 911 gArgv = (char **)argv; 912 int i; 913 914 /* In case we don't need to display a window, let's not do that */ 915 for (i = 1; i < argc; i++) { 916 const char *opt = argv[i]; 917 918 if (opt[0] == '-') { 919 /* Treat --foo the same as -foo. */ 920 if (opt[1] == '-') { 921 opt++; 922 } 923 if (!strcmp(opt, "-h") || !strcmp(opt, "-help") || 924 !strcmp(opt, "-vnc") || 925 !strcmp(opt, "-nographic") || 926 !strcmp(opt, "-version") || 927 !strcmp(opt, "-curses") || 928 !strcmp(opt, "-qtest")) { 929 return qemu_main(gArgc, gArgv, *_NSGetEnviron()); 930 } 931 } 932 } 933 934 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 935 936 // Pull this console process up to being a fully-fledged graphical 937 // app with a menubar and Dock icon 938 ProcessSerialNumber psn = { 0, kCurrentProcess }; 939 TransformProcessType(&psn, kProcessTransformToForegroundApplication); 940 941 [NSApplication sharedApplication]; 942 943 // Add menus 944 NSMenu *menu; 945 NSMenuItem *menuItem; 946 947 [NSApp setMainMenu:[[NSMenu alloc] init]]; 948 949 // Application menu 950 menu = [[NSMenu alloc] initWithTitle:@""]; 951 [menu addItemWithTitle:@"About QEMU" action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; // About QEMU 952 [menu addItem:[NSMenuItem separatorItem]]; //Separator 953 [menu addItemWithTitle:@"Hide QEMU" action:@selector(hide:) keyEquivalent:@"h"]; //Hide QEMU 954 menuItem = (NSMenuItem *)[menu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; // Hide Others 955 [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; 956 [menu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; // Show All 957 [menu addItem:[NSMenuItem separatorItem]]; //Separator 958 [menu addItemWithTitle:@"Quit QEMU" action:@selector(terminate:) keyEquivalent:@"q"]; 959 menuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" action:nil keyEquivalent:@""]; 960 [menuItem setSubmenu:menu]; 961 [[NSApp mainMenu] addItem:menuItem]; 962 [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; // Workaround (this method is private since 10.4+) 963 964 // View menu 965 menu = [[NSMenu alloc] initWithTitle:@"View"]; 966 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(toggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen 967 menuItem = [[[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""] autorelease]; 968 [menuItem setSubmenu:menu]; 969 [[NSApp mainMenu] addItem:menuItem]; 970 971 // Window menu 972 menu = [[NSMenu alloc] initWithTitle:@"Window"]; 973 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease]]; // Miniaturize 974 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease]; 975 [menuItem setSubmenu:menu]; 976 [[NSApp mainMenu] addItem:menuItem]; 977 [NSApp setWindowsMenu:menu]; 978 979 // Help menu 980 menu = [[NSMenu alloc] initWithTitle:@"Help"]; 981 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Documentation" action:@selector(showQEMUDoc:) keyEquivalent:@"?"] autorelease]]; // QEMU Help 982 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Technology" action:@selector(showQEMUTec:) keyEquivalent:@""] autorelease]]; // QEMU Help 983 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease]; 984 [menuItem setSubmenu:menu]; 985 [[NSApp mainMenu] addItem:menuItem]; 986 987 // Create an Application controller 988 QemuCocoaAppController *appController = [[QemuCocoaAppController alloc] init]; 989 [NSApp setDelegate:appController]; 990 991 // Start the main event loop 992 [NSApp run]; 993 994 [appController release]; 995 [pool release]; 996 997 return 0; 998} 999 1000 1001 1002#pragma mark qemu 1003static void cocoa_update(DisplayChangeListener *dcl, 1004 int x, int y, int w, int h) 1005{ 1006 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 1007 1008 COCOA_DEBUG("qemu_cocoa: cocoa_update\n"); 1009 1010 NSRect rect; 1011 if ([cocoaView cdx] == 1.0) { 1012 rect = NSMakeRect(x, [cocoaView gscreen].height - y - h, w, h); 1013 } else { 1014 rect = NSMakeRect( 1015 x * [cocoaView cdx], 1016 ([cocoaView gscreen].height - y - h) * [cocoaView cdy], 1017 w * [cocoaView cdx], 1018 h * [cocoaView cdy]); 1019 } 1020 [cocoaView setNeedsDisplayInRect:rect]; 1021 1022 [pool release]; 1023} 1024 1025static void cocoa_switch(DisplayChangeListener *dcl, 1026 DisplaySurface *surface) 1027{ 1028 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 1029 1030 COCOA_DEBUG("qemu_cocoa: cocoa_switch\n"); 1031 [cocoaView switchSurface:surface]; 1032 [pool release]; 1033} 1034 1035static void cocoa_refresh(DisplayChangeListener *dcl) 1036{ 1037 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 1038 1039 COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n"); 1040 1041 if (qemu_input_is_absolute()) { 1042 if (![cocoaView isAbsoluteEnabled]) { 1043 if ([cocoaView isMouseGrabbed]) { 1044 [cocoaView ungrabMouse]; 1045 } 1046 } 1047 [cocoaView setAbsoluteEnabled:YES]; 1048 } 1049 1050 NSDate *distantPast; 1051 NSEvent *event; 1052 distantPast = [NSDate distantPast]; 1053 do { 1054 event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:distantPast 1055 inMode: NSDefaultRunLoopMode dequeue:YES]; 1056 if (event != nil) { 1057 [cocoaView handleEvent:event]; 1058 } 1059 } while(event != nil); 1060 graphic_hw_update(NULL); 1061 [pool release]; 1062} 1063 1064static void cocoa_cleanup(void) 1065{ 1066 COCOA_DEBUG("qemu_cocoa: cocoa_cleanup\n"); 1067 g_free(dcl); 1068} 1069 1070static const DisplayChangeListenerOps dcl_ops = { 1071 .dpy_name = "cocoa", 1072 .dpy_gfx_update = cocoa_update, 1073 .dpy_gfx_switch = cocoa_switch, 1074 .dpy_refresh = cocoa_refresh, 1075}; 1076 1077void cocoa_display_init(DisplayState *ds, int full_screen) 1078{ 1079 COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n"); 1080 1081 dcl = g_malloc0(sizeof(DisplayChangeListener)); 1082 1083 // register vga output callbacks 1084 dcl->ops = &dcl_ops; 1085 register_displaychangelistener(dcl); 1086 1087 // register cleanup function 1088 atexit(cocoa_cleanup); 1089} 1090