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 27#include "qemu-common.h" 28#include "console.h" 29#include "sysemu.h" 30 31#ifndef MAC_OS_X_VERSION_10_4 32#define MAC_OS_X_VERSION_10_4 1040 33#endif 34#ifndef MAC_OS_X_VERSION_10_5 35#define MAC_OS_X_VERSION_10_5 1050 36#endif 37 38 39//#define DEBUG 40 41#ifdef DEBUG 42#define COCOA_DEBUG(...) { (void) fprintf (stdout, __VA_ARGS__); } 43#else 44#define COCOA_DEBUG(...) ((void) 0) 45#endif 46 47#define cgrect(nsrect) (*(CGRect *)&(nsrect)) 48#define COCOA_MOUSE_EVENT \ 49 if (isTabletEnabled) { \ 50 kbd_mouse_event((int)(p.x * 0x7FFF / (screen.width - 1)), (int)((screen.height - p.y) * 0x7FFF / (screen.height - 1)), 0, buttons); \ 51 } else if (isMouseGrabed) { \ 52 kbd_mouse_event((int)[event deltaX], (int)[event deltaY], 0, buttons); \ 53 } else { \ 54 [NSApp sendEvent:event]; \ 55 } 56 57typedef struct { 58 int width; 59 int height; 60 int bitsPerComponent; 61 int bitsPerPixel; 62} QEMUScreen; 63 64int qemu_main(int argc, char **argv); // main defined in qemu/vl.c 65NSWindow *normalWindow; 66id cocoaView; 67static DisplayChangeListener *dcl; 68 69int gArgc; 70char **gArgv; 71 72// keymap conversion 73int keymap[] = 74{ 75// SdlI macI macH SdlH 104xtH 104xtC sdl 76 30, // 0 0x00 0x1e A QZ_a 77 31, // 1 0x01 0x1f S QZ_s 78 32, // 2 0x02 0x20 D QZ_d 79 33, // 3 0x03 0x21 F QZ_f 80 35, // 4 0x04 0x23 H QZ_h 81 34, // 5 0x05 0x22 G QZ_g 82 44, // 6 0x06 0x2c Z QZ_z 83 45, // 7 0x07 0x2d X QZ_x 84 46, // 8 0x08 0x2e C QZ_c 85 47, // 9 0x09 0x2f V QZ_v 86 0, // 10 0x0A Undefined 87 48, // 11 0x0B 0x30 B QZ_b 88 16, // 12 0x0C 0x10 Q QZ_q 89 17, // 13 0x0D 0x11 W QZ_w 90 18, // 14 0x0E 0x12 E QZ_e 91 19, // 15 0x0F 0x13 R QZ_r 92 21, // 16 0x10 0x15 Y QZ_y 93 20, // 17 0x11 0x14 T QZ_t 94 2, // 18 0x12 0x02 1 QZ_1 95 3, // 19 0x13 0x03 2 QZ_2 96 4, // 20 0x14 0x04 3 QZ_3 97 5, // 21 0x15 0x05 4 QZ_4 98 7, // 22 0x16 0x07 6 QZ_6 99 6, // 23 0x17 0x06 5 QZ_5 100 13, // 24 0x18 0x0d = QZ_EQUALS 101 10, // 25 0x19 0x0a 9 QZ_9 102 8, // 26 0x1A 0x08 7 QZ_7 103 12, // 27 0x1B 0x0c - QZ_MINUS 104 9, // 28 0x1C 0x09 8 QZ_8 105 11, // 29 0x1D 0x0b 0 QZ_0 106 27, // 30 0x1E 0x1b ] QZ_RIGHTBRACKET 107 24, // 31 0x1F 0x18 O QZ_o 108 22, // 32 0x20 0x16 U QZ_u 109 26, // 33 0x21 0x1a [ QZ_LEFTBRACKET 110 23, // 34 0x22 0x17 I QZ_i 111 25, // 35 0x23 0x19 P QZ_p 112 28, // 36 0x24 0x1c ENTER QZ_RETURN 113 38, // 37 0x25 0x26 L QZ_l 114 36, // 38 0x26 0x24 J QZ_j 115 40, // 39 0x27 0x28 ' QZ_QUOTE 116 37, // 40 0x28 0x25 K QZ_k 117 39, // 41 0x29 0x27 ; QZ_SEMICOLON 118 43, // 42 0x2A 0x2b \ QZ_BACKSLASH 119 51, // 43 0x2B 0x33 , QZ_COMMA 120 53, // 44 0x2C 0x35 / QZ_SLASH 121 49, // 45 0x2D 0x31 N QZ_n 122 50, // 46 0x2E 0x32 M QZ_m 123 52, // 47 0x2F 0x34 . QZ_PERIOD 124 15, // 48 0x30 0x0f TAB QZ_TAB 125 57, // 49 0x31 0x39 SPACE QZ_SPACE 126 41, // 50 0x32 0x29 ` QZ_BACKQUOTE 127 14, // 51 0x33 0x0e BKSP QZ_BACKSPACE 128 0, // 52 0x34 Undefined 129 1, // 53 0x35 0x01 ESC QZ_ESCAPE 130 0, // 54 0x36 QZ_RMETA 131 0, // 55 0x37 QZ_LMETA 132 42, // 56 0x38 0x2a L SHFT QZ_LSHIFT 133 58, // 57 0x39 0x3a CAPS QZ_CAPSLOCK 134 56, // 58 0x3A 0x38 L ALT QZ_LALT 135 29, // 59 0x3B 0x1d L CTRL QZ_LCTRL 136 54, // 60 0x3C 0x36 R SHFT QZ_RSHIFT 137 184,// 61 0x3D 0xb8 E0,38 R ALT QZ_RALT 138 157,// 62 0x3E 0x9d E0,1D R CTRL QZ_RCTRL 139 0, // 63 0x3F Undefined 140 0, // 64 0x40 Undefined 141 0, // 65 0x41 Undefined 142 0, // 66 0x42 Undefined 143 55, // 67 0x43 0x37 KP * QZ_KP_MULTIPLY 144 0, // 68 0x44 Undefined 145 78, // 69 0x45 0x4e KP + QZ_KP_PLUS 146 0, // 70 0x46 Undefined 147 69, // 71 0x47 0x45 NUM QZ_NUMLOCK 148 0, // 72 0x48 Undefined 149 0, // 73 0x49 Undefined 150 0, // 74 0x4A Undefined 151 181,// 75 0x4B 0xb5 E0,35 KP / QZ_KP_DIVIDE 152 152,// 76 0x4C 0x9c E0,1C KP EN QZ_KP_ENTER 153 0, // 77 0x4D undefined 154 74, // 78 0x4E 0x4a KP - QZ_KP_MINUS 155 0, // 79 0x4F Undefined 156 0, // 80 0x50 Undefined 157 0, // 81 0x51 QZ_KP_EQUALS 158 82, // 82 0x52 0x52 KP 0 QZ_KP0 159 79, // 83 0x53 0x4f KP 1 QZ_KP1 160 80, // 84 0x54 0x50 KP 2 QZ_KP2 161 81, // 85 0x55 0x51 KP 3 QZ_KP3 162 75, // 86 0x56 0x4b KP 4 QZ_KP4 163 76, // 87 0x57 0x4c KP 5 QZ_KP5 164 77, // 88 0x58 0x4d KP 6 QZ_KP6 165 71, // 89 0x59 0x47 KP 7 QZ_KP7 166 0, // 90 0x5A Undefined 167 72, // 91 0x5B 0x48 KP 8 QZ_KP8 168 73, // 92 0x5C 0x49 KP 9 QZ_KP9 169 0, // 93 0x5D Undefined 170 0, // 94 0x5E Undefined 171 0, // 95 0x5F Undefined 172 63, // 96 0x60 0x3f F5 QZ_F5 173 64, // 97 0x61 0x40 F6 QZ_F6 174 65, // 98 0x62 0x41 F7 QZ_F7 175 61, // 99 0x63 0x3d F3 QZ_F3 176 66, // 100 0x64 0x42 F8 QZ_F8 177 67, // 101 0x65 0x43 F9 QZ_F9 178 0, // 102 0x66 Undefined 179 87, // 103 0x67 0x57 F11 QZ_F11 180 0, // 104 0x68 Undefined 181 183,// 105 0x69 0xb7 QZ_PRINT 182 0, // 106 0x6A Undefined 183 70, // 107 0x6B 0x46 SCROLL QZ_SCROLLOCK 184 0, // 108 0x6C Undefined 185 68, // 109 0x6D 0x44 F10 QZ_F10 186 0, // 110 0x6E Undefined 187 88, // 111 0x6F 0x58 F12 QZ_F12 188 0, // 112 0x70 Undefined 189 110,// 113 0x71 0x0 QZ_PAUSE 190 210,// 114 0x72 0xd2 E0,52 INSERT QZ_INSERT 191 199,// 115 0x73 0xc7 E0,47 HOME QZ_HOME 192 201,// 116 0x74 0xc9 E0,49 PG UP QZ_PAGEUP 193 211,// 117 0x75 0xd3 E0,53 DELETE QZ_DELETE 194 62, // 118 0x76 0x3e F4 QZ_F4 195 207,// 119 0x77 0xcf E0,4f END QZ_END 196 60, // 120 0x78 0x3c F2 QZ_F2 197 209,// 121 0x79 0xd1 E0,51 PG DN QZ_PAGEDOWN 198 59, // 122 0x7A 0x3b F1 QZ_F1 199 203,// 123 0x7B 0xcb e0,4B L ARROW QZ_LEFT 200 205,// 124 0x7C 0xcd e0,4D R ARROW QZ_RIGHT 201 208,// 125 0x7D 0xd0 E0,50 D ARROW QZ_DOWN 202 200,// 126 0x7E 0xc8 E0,48 U ARROW QZ_UP 203/* 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 */ 204 205/* Aditional 104 Key XP-Keyboard Scancodes from http://www.computer-engineering.org/ps2keyboard/scancodes1.html */ 206/* 207 219 // 0xdb e0,5b L GUI 208 220 // 0xdc e0,5c R GUI 209 221 // 0xdd e0,5d APPS 210 // E0,2A,E0,37 PRNT SCRN 211 // E1,1D,45,E1,9D,C5 PAUSE 212 83 // 0x53 0x53 KP . 213// ACPI Scan Codes 214 222 // 0xde E0, 5E Power 215 223 // 0xdf E0, 5F Sleep 216 227 // 0xe3 E0, 63 Wake 217// Windows Multimedia Scan Codes 218 153 // 0x99 E0, 19 Next Track 219 144 // 0x90 E0, 10 Previous Track 220 164 // 0xa4 E0, 24 Stop 221 162 // 0xa2 E0, 22 Play/Pause 222 160 // 0xa0 E0, 20 Mute 223 176 // 0xb0 E0, 30 Volume Up 224 174 // 0xae E0, 2E Volume Down 225 237 // 0xed E0, 6D Media Select 226 236 // 0xec E0, 6C E-Mail 227 161 // 0xa1 E0, 21 Calculator 228 235 // 0xeb E0, 6B My Computer 229 229 // 0xe5 E0, 65 WWW Search 230 178 // 0xb2 E0, 32 WWW Home 231 234 // 0xea E0, 6A WWW Back 232 233 // 0xe9 E0, 69 WWW Forward 233 232 // 0xe8 E0, 68 WWW Stop 234 231 // 0xe7 E0, 67 WWW Refresh 235 230 // 0xe6 E0, 66 WWW Favorites 236*/ 237}; 238 239static int cocoa_keycode_to_qemu(int keycode) 240{ 241 if((sizeof(keymap)/sizeof(int)) <= keycode) 242 { 243 printf("(cocoa) warning unknow keycode 0x%x\n", keycode); 244 return 0; 245 } 246 return keymap[keycode]; 247} 248 249 250 251/* 252 ------------------------------------------------------ 253 QemuCocoaView 254 ------------------------------------------------------ 255*/ 256@interface QemuCocoaView : NSView 257{ 258 QEMUScreen screen; 259 NSWindow *fullScreenWindow; 260 float cx,cy,cw,ch,cdx,cdy; 261 CGDataProviderRef dataProviderRef; 262 int modifiers_state[256]; 263 BOOL isMouseGrabed; 264 BOOL isFullscreen; 265 BOOL isAbsoluteEnabled; 266 BOOL isTabletEnabled; 267} 268- (void) resizeContentToWidth:(int)w height:(int)h displayState:(DisplayState *)ds; 269- (void) grabMouse; 270- (void) ungrabMouse; 271- (void) toggleFullScreen:(id)sender; 272- (void) handleEvent:(NSEvent *)event; 273- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled; 274- (BOOL) isMouseGrabed; 275- (BOOL) isAbsoluteEnabled; 276- (float) cdx; 277- (float) cdy; 278- (QEMUScreen) gscreen; 279@end 280 281@implementation QemuCocoaView 282- (id)initWithFrame:(NSRect)frameRect 283{ 284 COCOA_DEBUG("QemuCocoaView: initWithFrame\n"); 285 286 self = [super initWithFrame:frameRect]; 287 if (self) { 288 289 screen.bitsPerComponent = 8; 290 screen.bitsPerPixel = 32; 291 screen.width = frameRect.size.width; 292 screen.height = frameRect.size.height; 293 294 } 295 return self; 296} 297 298- (void) dealloc 299{ 300 COCOA_DEBUG("QemuCocoaView: dealloc\n"); 301 302 if (dataProviderRef) 303 CGDataProviderRelease(dataProviderRef); 304 305 [super dealloc]; 306} 307 308- (BOOL) isOpaque 309{ 310 return YES; 311} 312 313- (void) drawRect:(NSRect) rect 314{ 315 COCOA_DEBUG("QemuCocoaView: drawRect\n"); 316 317 // get CoreGraphic context 318 CGContextRef viewContextRef = [[NSGraphicsContext currentContext] graphicsPort]; 319 CGContextSetInterpolationQuality (viewContextRef, kCGInterpolationNone); 320 CGContextSetShouldAntialias (viewContextRef, NO); 321 322 // draw screen bitmap directly to Core Graphics context 323 if (dataProviderRef) { 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) resizeContentToWidth:(int)w height:(int)h displayState:(DisplayState *)ds 402{ 403 COCOA_DEBUG("QemuCocoaView: resizeContent\n"); 404 405 // update screenBuffer 406 if (dataProviderRef) 407 CGDataProviderRelease(dataProviderRef); 408 409 //sync host window color space with guests 410 screen.bitsPerPixel = ds_get_bits_per_pixel(ds); 411 screen.bitsPerComponent = ds_get_bytes_per_pixel(ds) * 2; 412 413 dataProviderRef = CGDataProviderCreateWithData(NULL, ds_get_data(ds), w * 4 * h, NULL); 414 415 // update windows 416 if (isFullscreen) { 417 [[fullScreenWindow contentView] setFrame:[[NSScreen mainScreen] frame]]; 418 [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]; 419 } else { 420 if (qemu_name) 421 [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]]; 422 [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]; 423 } 424 screen.width = w; 425 screen.height = h; 426 [normalWindow center]; 427 [self setContentDimensions]; 428 [self setFrame:NSMakeRect(cx, cy, cw, ch)]; 429} 430 431- (void) toggleFullScreen:(id)sender 432{ 433 COCOA_DEBUG("QemuCocoaView: toggleFullScreen\n"); 434 435 if (isFullscreen) { // switch from fullscreen to desktop 436 isFullscreen = FALSE; 437 [self ungrabMouse]; 438 [self setContentDimensions]; 439// test if host supports "exitFullScreenModeWithOptions" at compile time 440#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) 441 if ([NSView respondsToSelector:@selector(exitFullScreenModeWithOptions:)]) { // test if "exitFullScreenModeWithOptions" is supported on host at runtime 442 [self exitFullScreenModeWithOptions:nil]; 443 } else { 444#endif 445 [fullScreenWindow close]; 446 [normalWindow setContentView: self]; 447 [normalWindow makeKeyAndOrderFront: self]; 448 [NSMenu setMenuBarVisible:YES]; 449#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) 450 } 451#endif 452 } else { // switch from desktop to fullscreen 453 isFullscreen = TRUE; 454 [self grabMouse]; 455 [self setContentDimensions]; 456// test if host supports "enterFullScreenMode:withOptions" at compile time 457#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) 458 if ([NSView respondsToSelector:@selector(enterFullScreenMode:withOptions:)]) { // test if "enterFullScreenMode:withOptions" is supported on host at runtime 459 [self enterFullScreenMode:[NSScreen mainScreen] withOptions:[NSDictionary dictionaryWithObjectsAndKeys: 460 [NSNumber numberWithBool:NO], NSFullScreenModeAllScreens, 461 [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO], kCGDisplayModeIsStretched, nil], NSFullScreenModeSetting, 462 nil]]; 463 } else { 464#endif 465 [NSMenu setMenuBarVisible:NO]; 466 fullScreenWindow = [[NSWindow alloc] initWithContentRect:[[NSScreen mainScreen] frame] 467 styleMask:NSBorderlessWindowMask 468 backing:NSBackingStoreBuffered 469 defer:NO]; 470 [fullScreenWindow setHasShadow:NO]; 471 [fullScreenWindow setContentView:self]; 472 [fullScreenWindow makeKeyAndOrderFront:self]; 473#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) 474 } 475#endif 476 } 477} 478 479- (void) handleEvent:(NSEvent *)event 480{ 481 COCOA_DEBUG("QemuCocoaView: handleEvent\n"); 482 483 int buttons = 0; 484 int keycode; 485 NSPoint p = [event locationInWindow]; 486 487 switch ([event type]) { 488 case NSFlagsChanged: 489 keycode = cocoa_keycode_to_qemu([event keyCode]); 490 if (keycode) { 491 if (keycode == 58 || keycode == 69) { // emulate caps lock and num lock keydown and keyup 492 kbd_put_keycode(keycode); 493 kbd_put_keycode(keycode | 0x80); 494 } else if (is_graphic_console()) { 495 if (keycode & 0x80) 496 kbd_put_keycode(0xe0); 497 if (modifiers_state[keycode] == 0) { // keydown 498 kbd_put_keycode(keycode & 0x7f); 499 modifiers_state[keycode] = 1; 500 } else { // keyup 501 kbd_put_keycode(keycode | 0x80); 502 modifiers_state[keycode] = 0; 503 } 504 } 505 } 506 507 // release Mouse grab when pressing ctrl+alt 508 if (!isFullscreen && ([event modifierFlags] & NSControlKeyMask) && ([event modifierFlags] & NSAlternateKeyMask)) { 509 [self ungrabMouse]; 510 } 511 break; 512 case NSKeyDown: 513 514 // forward command Key Combos 515 if ([event modifierFlags] & NSCommandKeyMask) { 516 [NSApp sendEvent:event]; 517 return; 518 } 519 520 // default 521 keycode = cocoa_keycode_to_qemu([event keyCode]); 522 523 // handle control + alt Key Combos (ctrl+alt is reserved for QEMU) 524 if (([event modifierFlags] & NSControlKeyMask) && ([event modifierFlags] & NSAlternateKeyMask)) { 525 switch (keycode) { 526 527 // enable graphic console 528 case 0x02 ... 0x0a: // '1' to '9' keys 529 console_select(keycode - 0x02); 530 break; 531 } 532 533 // handle keys for graphic console 534 } else if (is_graphic_console()) { 535 if (keycode & 0x80) //check bit for e0 in front 536 kbd_put_keycode(0xe0); 537 kbd_put_keycode(keycode & 0x7f); //remove e0 bit in front 538 539 // handlekeys for Monitor 540 } else { 541 int keysym = 0; 542 switch([event keyCode]) { 543 case 115: 544 keysym = QEMU_KEY_HOME; 545 break; 546 case 117: 547 keysym = QEMU_KEY_DELETE; 548 break; 549 case 119: 550 keysym = QEMU_KEY_END; 551 break; 552 case 123: 553 keysym = QEMU_KEY_LEFT; 554 break; 555 case 124: 556 keysym = QEMU_KEY_RIGHT; 557 break; 558 case 125: 559 keysym = QEMU_KEY_DOWN; 560 break; 561 case 126: 562 keysym = QEMU_KEY_UP; 563 break; 564 default: 565 { 566 NSString *ks = [event characters]; 567 if ([ks length] > 0) 568 keysym = [ks characterAtIndex:0]; 569 } 570 } 571 if (keysym) 572 kbd_put_keysym(keysym); 573 } 574 break; 575 case NSKeyUp: 576 keycode = cocoa_keycode_to_qemu([event keyCode]); 577 if (is_graphic_console()) { 578 if (keycode & 0x80) 579 kbd_put_keycode(0xe0); 580 kbd_put_keycode(keycode | 0x80); //add 128 to signal release of key 581 } 582 break; 583 case NSMouseMoved: 584 if (isAbsoluteEnabled) { 585 if (p.x < 0 || p.x > screen.width || p.y < 0 || p.y > screen.height || ![[self window] isKeyWindow]) { 586 if (isTabletEnabled) { // if we leave the window, deactivate the tablet 587 [NSCursor unhide]; 588 isTabletEnabled = FALSE; 589 } 590 } else { 591 if (!isTabletEnabled) { // if we enter the window, activate the tablet 592 [NSCursor hide]; 593 isTabletEnabled = TRUE; 594 } 595 } 596 } 597 COCOA_MOUSE_EVENT 598 break; 599 case NSLeftMouseDown: 600 if ([event modifierFlags] & NSCommandKeyMask) { 601 buttons |= MOUSE_EVENT_RBUTTON; 602 } else { 603 buttons |= MOUSE_EVENT_LBUTTON; 604 } 605 COCOA_MOUSE_EVENT 606 break; 607 case NSRightMouseDown: 608 buttons |= MOUSE_EVENT_RBUTTON; 609 COCOA_MOUSE_EVENT 610 break; 611 case NSOtherMouseDown: 612 buttons |= MOUSE_EVENT_MBUTTON; 613 COCOA_MOUSE_EVENT 614 break; 615 case NSLeftMouseDragged: 616 if ([event modifierFlags] & NSCommandKeyMask) { 617 buttons |= MOUSE_EVENT_RBUTTON; 618 } else { 619 buttons |= MOUSE_EVENT_LBUTTON; 620 } 621 COCOA_MOUSE_EVENT 622 break; 623 case NSRightMouseDragged: 624 buttons |= MOUSE_EVENT_RBUTTON; 625 COCOA_MOUSE_EVENT 626 break; 627 case NSOtherMouseDragged: 628 buttons |= MOUSE_EVENT_MBUTTON; 629 COCOA_MOUSE_EVENT 630 break; 631 case NSLeftMouseUp: 632 if (isTabletEnabled) { 633 COCOA_MOUSE_EVENT 634 } else if (!isMouseGrabed) { 635 if (p.x > -1 && p.x < screen.width && p.y > -1 && p.y < screen.height) { 636 [self grabMouse]; 637 } else { 638 [NSApp sendEvent:event]; 639 } 640 } else { 641 COCOA_MOUSE_EVENT 642 } 643 break; 644 case NSRightMouseUp: 645 COCOA_MOUSE_EVENT 646 break; 647 case NSOtherMouseUp: 648 COCOA_MOUSE_EVENT 649 break; 650 case NSScrollWheel: 651 if (isTabletEnabled || isMouseGrabed) { 652 kbd_mouse_event(0, 0, -[event deltaY], 0); 653 } else { 654 [NSApp sendEvent:event]; 655 } 656 break; 657 default: 658 [NSApp sendEvent:event]; 659 } 660} 661 662- (void) grabMouse 663{ 664 COCOA_DEBUG("QemuCocoaView: grabMouse\n"); 665 666 if (!isFullscreen) { 667 if (qemu_name) 668 [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s - (Press ctrl + alt to release Mouse)", qemu_name]]; 669 else 670 [normalWindow setTitle:@"QEMU - (Press ctrl + alt to release Mouse)"]; 671 } 672 [NSCursor hide]; 673 CGAssociateMouseAndMouseCursorPosition(FALSE); 674 isMouseGrabed = TRUE; // while isMouseGrabed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:] 675} 676 677- (void) ungrabMouse 678{ 679 COCOA_DEBUG("QemuCocoaView: ungrabMouse\n"); 680 681 if (!isFullscreen) { 682 if (qemu_name) 683 [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]]; 684 else 685 [normalWindow setTitle:@"QEMU"]; 686 } 687 [NSCursor unhide]; 688 CGAssociateMouseAndMouseCursorPosition(TRUE); 689 isMouseGrabed = FALSE; 690} 691 692- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled {isAbsoluteEnabled = tIsAbsoluteEnabled;} 693- (BOOL) isMouseGrabed {return isMouseGrabed;} 694- (BOOL) isAbsoluteEnabled {return isAbsoluteEnabled;} 695- (float) cdx {return cdx;} 696- (float) cdy {return cdy;} 697- (QEMUScreen) gscreen {return screen;} 698@end 699 700 701 702/* 703 ------------------------------------------------------ 704 QemuCocoaAppController 705 ------------------------------------------------------ 706*/ 707@interface QemuCocoaAppController : NSObject 708{ 709} 710- (void)startEmulationWithArgc:(int)argc argv:(char**)argv; 711- (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo; 712- (void)toggleFullScreen:(id)sender; 713- (void)showQEMUDoc:(id)sender; 714- (void)showQEMUTec:(id)sender; 715@end 716 717@implementation QemuCocoaAppController 718- (id) init 719{ 720 COCOA_DEBUG("QemuCocoaAppController: init\n"); 721 722 self = [super init]; 723 if (self) { 724 725 // create a view and add it to the window 726 cocoaView = [[QemuCocoaView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 640.0, 480.0)]; 727 if(!cocoaView) { 728 fprintf(stderr, "(cocoa) can't create a view\n"); 729 exit(1); 730 } 731 732 // create a window 733 normalWindow = [[NSWindow alloc] initWithContentRect:[cocoaView frame] 734 styleMask:NSTitledWindowMask|NSMiniaturizableWindowMask|NSClosableWindowMask 735 backing:NSBackingStoreBuffered defer:NO]; 736 if(!normalWindow) { 737 fprintf(stderr, "(cocoa) can't create window\n"); 738 exit(1); 739 } 740 [normalWindow setAcceptsMouseMovedEvents:YES]; 741 [normalWindow setTitle:[NSString stringWithFormat:@"QEMU"]]; 742 [normalWindow setContentView:cocoaView]; 743 [normalWindow useOptimizedDrawing:YES]; 744 [normalWindow makeKeyAndOrderFront:self]; 745 [normalWindow center]; 746 747 } 748 return self; 749} 750 751- (void) dealloc 752{ 753 COCOA_DEBUG("QemuCocoaAppController: dealloc\n"); 754 755 if (cocoaView) 756 [cocoaView release]; 757 [super dealloc]; 758} 759 760- (void)applicationDidFinishLaunching: (NSNotification *) note 761{ 762 COCOA_DEBUG("QemuCocoaAppController: applicationDidFinishLaunching\n"); 763 764 // Display an open dialog box if no argument were passed or 765 // if qemu was launched from the finder ( the Finder passes "-psn" ) 766 if( gArgc <= 1 || strncmp ((char *)gArgv[1], "-psn", 4) == 0) { 767 NSOpenPanel *op = [[NSOpenPanel alloc] init]; 768 [op setPrompt:@"Boot image"]; 769 [op setMessage:@"Select the disk image you want to boot.\n\nHit the \"Cancel\" button to quit"]; 770 [op beginSheetForDirectory:nil file:nil types:[NSArray arrayWithObjects:@"img",@"iso",@"dmg",@"qcow",@"cow",@"cloop",@"vmdk",nil] 771 modalForWindow:normalWindow modalDelegate:self 772 didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:) contextInfo:NULL]; 773 } else { 774 // or Launch Qemu, with the global args 775 [self startEmulationWithArgc:gArgc argv:(char **)gArgv]; 776 } 777} 778 779- (void)applicationWillTerminate:(NSNotification *)aNotification 780{ 781 COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n"); 782 783 qemu_system_shutdown_request(); 784 exit(0); 785} 786 787- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication 788{ 789 return YES; 790} 791 792- (void)startEmulationWithArgc:(int)argc argv:(char**)argv 793{ 794 COCOA_DEBUG("QemuCocoaAppController: startEmulationWithArgc\n"); 795 796 int status; 797 status = qemu_main(argc, argv); 798 exit(status); 799} 800 801- (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo 802{ 803 COCOA_DEBUG("QemuCocoaAppController: openPanelDidEnd\n"); 804 805 if(returnCode == NSCancelButton) { 806 exit(0); 807 } else if(returnCode == NSOKButton) { 808 const char *bin = "qemu"; 809 char *img = (char*)[ [ sheet filename ] cStringUsingEncoding:NSASCIIStringEncoding]; 810 811 char **argv = (char**)malloc( sizeof(char*)*3 ); 812 813 asprintf(&argv[0], "%s", bin); 814 asprintf(&argv[1], "-hda"); 815 asprintf(&argv[2], "%s", img); 816 817 printf("Using argc %d argv %s -hda %s\n", 3, bin, img); 818 819 [self startEmulationWithArgc:3 argv:(char**)argv]; 820 } 821} 822- (void)toggleFullScreen:(id)sender 823{ 824 COCOA_DEBUG("QemuCocoaAppController: toggleFullScreen\n"); 825 826 [cocoaView toggleFullScreen:sender]; 827} 828 829- (void)showQEMUDoc:(id)sender 830{ 831 COCOA_DEBUG("QemuCocoaAppController: showQEMUDoc\n"); 832 833 [[NSWorkspace sharedWorkspace] openFile:[NSString stringWithFormat:@"%@/../doc/qemu/qemu-doc.html", 834 [[NSBundle mainBundle] resourcePath]] withApplication:@"Help Viewer"]; 835} 836 837- (void)showQEMUTec:(id)sender 838{ 839 COCOA_DEBUG("QemuCocoaAppController: showQEMUTec\n"); 840 841 [[NSWorkspace sharedWorkspace] openFile:[NSString stringWithFormat:@"%@/../doc/qemu/qemu-tech.html", 842 [[NSBundle mainBundle] resourcePath]] withApplication:@"Help Viewer"]; 843} 844@end 845 846 847 848// Dock Connection 849typedef struct CPSProcessSerNum 850{ 851 UInt32 lo; 852 UInt32 hi; 853} CPSProcessSerNum; 854 855extern OSErr CPSGetCurrentProcess( CPSProcessSerNum *psn); 856extern OSErr CPSEnableForegroundOperation( CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5); 857extern OSErr CPSSetFrontProcess( CPSProcessSerNum *psn); 858 859int main (int argc, const char * argv[]) { 860 861 gArgc = argc; 862 gArgv = (char **)argv; 863 CPSProcessSerNum PSN; 864 int i; 865 866 /* In case we don't need to display a window, let's not do that */ 867 for (i = 1; i < argc; i++) { 868 if (!strcmp(argv[i], "-vnc") || 869 !strcmp(argv[i], "-nographic") || 870 !strcmp(argv[i], "-curses")) { 871 return qemu_main(gArgc, gArgv); 872 } 873 } 874 875 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 876 [NSApplication sharedApplication]; 877 878 if (!CPSGetCurrentProcess(&PSN)) 879 if (!CPSEnableForegroundOperation(&PSN,0x03,0x3C,0x2C,0x1103)) 880 if (!CPSSetFrontProcess(&PSN)) 881 [NSApplication sharedApplication]; 882 883 // Add menus 884 NSMenu *menu; 885 NSMenuItem *menuItem; 886 887 [NSApp setMainMenu:[[NSMenu alloc] init]]; 888 889 // Application menu 890 menu = [[NSMenu alloc] initWithTitle:@""]; 891 [menu addItemWithTitle:@"About QEMU" action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; // About QEMU 892 [menu addItem:[NSMenuItem separatorItem]]; //Separator 893 [menu addItemWithTitle:@"Hide QEMU" action:@selector(hide:) keyEquivalent:@"h"]; //Hide QEMU 894 menuItem = (NSMenuItem *)[menu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; // Hide Others 895 [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; 896 [menu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; // Show All 897 [menu addItem:[NSMenuItem separatorItem]]; //Separator 898 [menu addItemWithTitle:@"Quit QEMU" action:@selector(terminate:) keyEquivalent:@"q"]; 899 menuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" action:nil keyEquivalent:@""]; 900 [menuItem setSubmenu:menu]; 901 [[NSApp mainMenu] addItem:menuItem]; 902 [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; // Workaround (this method is private since 10.4+) 903 904 // View menu 905 menu = [[NSMenu alloc] initWithTitle:@"View"]; 906 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(toggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen 907 menuItem = [[[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""] autorelease]; 908 [menuItem setSubmenu:menu]; 909 [[NSApp mainMenu] addItem:menuItem]; 910 911 // Window menu 912 menu = [[NSMenu alloc] initWithTitle:@"Window"]; 913 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease]]; // Miniaturize 914 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease]; 915 [menuItem setSubmenu:menu]; 916 [[NSApp mainMenu] addItem:menuItem]; 917 [NSApp setWindowsMenu:menu]; 918 919 // Help menu 920 menu = [[NSMenu alloc] initWithTitle:@"Help"]; 921 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Documentation" action:@selector(showQEMUDoc:) keyEquivalent:@"?"] autorelease]]; // QEMU Help 922 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Technology" action:@selector(showQEMUTec:) keyEquivalent:@""] autorelease]]; // QEMU Help 923 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease]; 924 [menuItem setSubmenu:menu]; 925 [[NSApp mainMenu] addItem:menuItem]; 926 927 // Create an Application controller 928 QemuCocoaAppController *appController = [[QemuCocoaAppController alloc] init]; 929 [NSApp setDelegate:appController]; 930 931 // Start the main event loop 932 [NSApp run]; 933 934 [appController release]; 935 [pool release]; 936 937 return 0; 938} 939 940 941 942#pragma mark qemu 943static void cocoa_update(DisplayState *ds, int x, int y, int w, int h) 944{ 945 COCOA_DEBUG("qemu_cocoa: cocoa_update\n"); 946 947 NSRect rect; 948 if ([cocoaView cdx] == 1.0) { 949 rect = NSMakeRect(x, [cocoaView gscreen].height - y - h, w, h); 950 } else { 951 rect = NSMakeRect( 952 x * [cocoaView cdx], 953 ([cocoaView gscreen].height - y - h) * [cocoaView cdy], 954 w * [cocoaView cdx], 955 h * [cocoaView cdy]); 956 } 957 [cocoaView setNeedsDisplayInRect:rect]; 958} 959 960static void cocoa_resize(DisplayState *ds) 961{ 962 COCOA_DEBUG("qemu_cocoa: cocoa_resize\n"); 963 964 [cocoaView resizeContentToWidth:(int)(ds_get_width(ds)) height:(int)(ds_get_height(ds)) displayState:ds]; 965} 966 967static void cocoa_refresh(DisplayState *ds) 968{ 969 COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n"); 970 971 if (kbd_mouse_is_absolute()) { 972 if (![cocoaView isAbsoluteEnabled]) { 973 if ([cocoaView isMouseGrabed]) { 974 [cocoaView ungrabMouse]; 975 } 976 } 977 [cocoaView setAbsoluteEnabled:YES]; 978 } 979 980 NSDate *distantPast; 981 NSEvent *event; 982 distantPast = [NSDate distantPast]; 983 do { 984 event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:distantPast 985 inMode: NSDefaultRunLoopMode dequeue:YES]; 986 if (event != nil) { 987 [cocoaView handleEvent:event]; 988 } 989 } while(event != nil); 990 vga_hw_update(); 991} 992 993static void cocoa_cleanup(void) 994{ 995 COCOA_DEBUG("qemu_cocoa: cocoa_cleanup\n"); 996 qemu_free(dcl); 997} 998 999void cocoa_display_init(DisplayState *ds, int full_screen) 1000{ 1001 COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n"); 1002 1003 dcl = qemu_mallocz(sizeof(DisplayChangeListener)); 1004 1005 // register vga output callbacks 1006 dcl->dpy_update = cocoa_update; 1007 dcl->dpy_resize = cocoa_resize; 1008 dcl->dpy_refresh = cocoa_refresh; 1009 1010 register_displaychangelistener(ds, dcl); 1011 1012 // register cleanup function 1013 atexit(cocoa_cleanup); 1014} 1015