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 isMouseDeassociated; 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/* The state surrounding mouse grabbing is potentially confusing. 268 * isAbsoluteEnabled tracks qemu_input_is_absolute() [ie "is the emulated 269 * pointing device an absolute-position one?"], but is only updated on 270 * next refresh. 271 * isMouseGrabbed tracks whether GUI events are directed to the guest; 272 * it controls whether special keys like Cmd get sent to the guest, 273 * and whether we capture the mouse when in non-absolute mode. 274 * isMouseDeassociated tracks whether we've told MacOSX to disassociate 275 * the mouse and mouse cursor position by calling 276 * CGAssociateMouseAndMouseCursorPosition(FALSE) 277 * (which basically happens if we grab in non-absolute mode). 278 */ 279- (BOOL) isMouseGrabbed; 280- (BOOL) isAbsoluteEnabled; 281- (BOOL) isMouseDeassociated; 282- (float) cdx; 283- (float) cdy; 284- (QEMUScreen) gscreen; 285@end 286 287QemuCocoaView *cocoaView; 288 289@implementation QemuCocoaView 290- (id)initWithFrame:(NSRect)frameRect 291{ 292 COCOA_DEBUG("QemuCocoaView: initWithFrame\n"); 293 294 self = [super initWithFrame:frameRect]; 295 if (self) { 296 297 screen.bitsPerComponent = 8; 298 screen.bitsPerPixel = 32; 299 screen.width = frameRect.size.width; 300 screen.height = frameRect.size.height; 301 302 } 303 return self; 304} 305 306- (void) dealloc 307{ 308 COCOA_DEBUG("QemuCocoaView: dealloc\n"); 309 310 if (dataProviderRef) 311 CGDataProviderRelease(dataProviderRef); 312 313 [super dealloc]; 314} 315 316- (BOOL) isOpaque 317{ 318 return YES; 319} 320 321- (BOOL) screenContainsPoint:(NSPoint) p 322{ 323 return (p.x > -1 && p.x < screen.width && p.y > -1 && p.y < screen.height); 324} 325 326- (void) hideCursor 327{ 328 if (!cursor_hide) { 329 return; 330 } 331 [NSCursor hide]; 332} 333 334- (void) unhideCursor 335{ 336 if (!cursor_hide) { 337 return; 338 } 339 [NSCursor unhide]; 340} 341 342- (void) drawRect:(NSRect) rect 343{ 344 COCOA_DEBUG("QemuCocoaView: drawRect\n"); 345 346 // get CoreGraphic context 347 CGContextRef viewContextRef = [[NSGraphicsContext currentContext] graphicsPort]; 348 CGContextSetInterpolationQuality (viewContextRef, kCGInterpolationNone); 349 CGContextSetShouldAntialias (viewContextRef, NO); 350 351 // draw screen bitmap directly to Core Graphics context 352 if (!dataProviderRef) { 353 // Draw request before any guest device has set up a framebuffer: 354 // just draw an opaque black rectangle 355 CGContextSetRGBFillColor(viewContextRef, 0, 0, 0, 1.0); 356 CGContextFillRect(viewContextRef, NSRectToCGRect(rect)); 357 } else { 358 CGImageRef imageRef = CGImageCreate( 359 screen.width, //width 360 screen.height, //height 361 screen.bitsPerComponent, //bitsPerComponent 362 screen.bitsPerPixel, //bitsPerPixel 363 (screen.width * (screen.bitsPerComponent/2)), //bytesPerRow 364#ifdef __LITTLE_ENDIAN__ 365 CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB), //colorspace for OS X >= 10.4 366 kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, 367#else 368 CGColorSpaceCreateDeviceRGB(), //colorspace for OS X < 10.4 (actually ppc) 369 kCGImageAlphaNoneSkipFirst, //bitmapInfo 370#endif 371 dataProviderRef, //provider 372 NULL, //decode 373 0, //interpolate 374 kCGRenderingIntentDefault //intent 375 ); 376// test if host supports "CGImageCreateWithImageInRect" at compile time 377#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4) 378 if (CGImageCreateWithImageInRect == NULL) { // test if "CGImageCreateWithImageInRect" is supported on host at runtime 379#endif 380 // compatibility drawing code (draws everything) (OS X < 10.4) 381 CGContextDrawImage (viewContextRef, CGRectMake(0, 0, [self bounds].size.width, [self bounds].size.height), imageRef); 382#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4) 383 } else { 384 // selective drawing code (draws only dirty rectangles) (OS X >= 10.4) 385 const NSRect *rectList; 386#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) 387 NSInteger rectCount; 388#else 389 int rectCount; 390#endif 391 int i; 392 CGImageRef clipImageRef; 393 CGRect clipRect; 394 395 [self getRectsBeingDrawn:&rectList count:&rectCount]; 396 for (i = 0; i < rectCount; i++) { 397 clipRect.origin.x = rectList[i].origin.x / cdx; 398 clipRect.origin.y = (float)screen.height - (rectList[i].origin.y + rectList[i].size.height) / cdy; 399 clipRect.size.width = rectList[i].size.width / cdx; 400 clipRect.size.height = rectList[i].size.height / cdy; 401 clipImageRef = CGImageCreateWithImageInRect( 402 imageRef, 403 clipRect 404 ); 405 CGContextDrawImage (viewContextRef, cgrect(rectList[i]), clipImageRef); 406 CGImageRelease (clipImageRef); 407 } 408 } 409#endif 410 CGImageRelease (imageRef); 411 } 412} 413 414- (void) setContentDimensions 415{ 416 COCOA_DEBUG("QemuCocoaView: setContentDimensions\n"); 417 418 if (isFullscreen) { 419 cdx = [[NSScreen mainScreen] frame].size.width / (float)screen.width; 420 cdy = [[NSScreen mainScreen] frame].size.height / (float)screen.height; 421 cw = screen.width * cdx; 422 ch = screen.height * cdy; 423 cx = ([[NSScreen mainScreen] frame].size.width - cw) / 2.0; 424 cy = ([[NSScreen mainScreen] frame].size.height - ch) / 2.0; 425 } else { 426 cx = 0; 427 cy = 0; 428 cw = screen.width; 429 ch = screen.height; 430 cdx = 1.0; 431 cdy = 1.0; 432 } 433} 434 435- (void) switchSurface:(DisplaySurface *)surface 436{ 437 COCOA_DEBUG("QemuCocoaView: switchSurface\n"); 438 439 int w = surface_width(surface); 440 int h = surface_height(surface); 441 /* cdx == 0 means this is our very first surface, in which case we need 442 * to recalculate the content dimensions even if it happens to be the size 443 * of the initial empty window. 444 */ 445 bool isResize = (w != screen.width || h != screen.height || cdx == 0.0); 446 447 int oldh = screen.height; 448 if (isResize) { 449 // Resize before we trigger the redraw, or we'll redraw at the wrong size 450 COCOA_DEBUG("switchSurface: new size %d x %d\n", w, h); 451 screen.width = w; 452 screen.height = h; 453 [self setContentDimensions]; 454 [self setFrame:NSMakeRect(cx, cy, cw, ch)]; 455 } 456 457 // update screenBuffer 458 if (dataProviderRef) 459 CGDataProviderRelease(dataProviderRef); 460 461 //sync host window color space with guests 462 screen.bitsPerPixel = surface_bits_per_pixel(surface); 463 screen.bitsPerComponent = surface_bytes_per_pixel(surface) * 2; 464 465 dataProviderRef = CGDataProviderCreateWithData(NULL, surface_data(surface), w * 4 * h, NULL); 466 467 // update windows 468 if (isFullscreen) { 469 [[fullScreenWindow contentView] setFrame:[[NSScreen mainScreen] frame]]; 470 [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + oldh, w, h + [normalWindow frame].size.height - oldh) display:NO animate:NO]; 471 } else { 472 if (qemu_name) 473 [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]]; 474 [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + oldh, w, h + [normalWindow frame].size.height - oldh) display:YES animate:NO]; 475 } 476 477 if (isResize) { 478 [normalWindow center]; 479 } 480} 481 482- (void) toggleFullScreen:(id)sender 483{ 484 COCOA_DEBUG("QemuCocoaView: toggleFullScreen\n"); 485 486 if (isFullscreen) { // switch from fullscreen to desktop 487 isFullscreen = FALSE; 488 [self ungrabMouse]; 489 [self setContentDimensions]; 490// test if host supports "exitFullScreenModeWithOptions" at compile time 491#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) 492 if ([NSView respondsToSelector:@selector(exitFullScreenModeWithOptions:)]) { // test if "exitFullScreenModeWithOptions" is supported on host at runtime 493 [self exitFullScreenModeWithOptions:nil]; 494 } else { 495#endif 496 [fullScreenWindow close]; 497 [normalWindow setContentView: self]; 498 [normalWindow makeKeyAndOrderFront: self]; 499 [NSMenu setMenuBarVisible:YES]; 500#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) 501 } 502#endif 503 } else { // switch from desktop to fullscreen 504 isFullscreen = TRUE; 505 [self grabMouse]; 506 [self setContentDimensions]; 507// test if host supports "enterFullScreenMode:withOptions" at compile time 508#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) 509 if ([NSView respondsToSelector:@selector(enterFullScreenMode:withOptions:)]) { // test if "enterFullScreenMode:withOptions" is supported on host at runtime 510 [self enterFullScreenMode:[NSScreen mainScreen] withOptions:[NSDictionary dictionaryWithObjectsAndKeys: 511 [NSNumber numberWithBool:NO], NSFullScreenModeAllScreens, 512 [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO], kCGDisplayModeIsStretched, nil], NSFullScreenModeSetting, 513 nil]]; 514 } else { 515#endif 516 [NSMenu setMenuBarVisible:NO]; 517 fullScreenWindow = [[NSWindow alloc] initWithContentRect:[[NSScreen mainScreen] frame] 518 styleMask:NSBorderlessWindowMask 519 backing:NSBackingStoreBuffered 520 defer:NO]; 521 [fullScreenWindow setHasShadow:NO]; 522 [fullScreenWindow setContentView:self]; 523 [fullScreenWindow makeKeyAndOrderFront:self]; 524#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5) 525 } 526#endif 527 } 528} 529 530- (void) handleEvent:(NSEvent *)event 531{ 532 COCOA_DEBUG("QemuCocoaView: handleEvent\n"); 533 534 int buttons = 0; 535 int keycode; 536 bool mouse_event = false; 537 NSPoint p = [event locationInWindow]; 538 539 switch ([event type]) { 540 case NSFlagsChanged: 541 keycode = cocoa_keycode_to_qemu([event keyCode]); 542 543 if ((keycode == 219 || keycode == 220) && !isMouseGrabbed) { 544 /* Don't pass command key changes to guest unless mouse is grabbed */ 545 keycode = 0; 546 } 547 548 if (keycode) { 549 if (keycode == 58 || keycode == 69) { // emulate caps lock and num lock keydown and keyup 550 qemu_input_event_send_key_number(dcl->con, keycode, true); 551 qemu_input_event_send_key_number(dcl->con, keycode, false); 552 } else if (qemu_console_is_graphic(NULL)) { 553 if (modifiers_state[keycode] == 0) { // keydown 554 qemu_input_event_send_key_number(dcl->con, keycode, true); 555 modifiers_state[keycode] = 1; 556 } else { // keyup 557 qemu_input_event_send_key_number(dcl->con, keycode, false); 558 modifiers_state[keycode] = 0; 559 } 560 } 561 } 562 563 // release Mouse grab when pressing ctrl+alt 564 if (!isFullscreen && ([event modifierFlags] & NSControlKeyMask) && ([event modifierFlags] & NSAlternateKeyMask)) { 565 [self ungrabMouse]; 566 } 567 break; 568 case NSKeyDown: 569 keycode = cocoa_keycode_to_qemu([event keyCode]); 570 571 // forward command key combos to the host UI unless the mouse is grabbed 572 if (!isMouseGrabbed && ([event modifierFlags] & NSCommandKeyMask)) { 573 [NSApp sendEvent:event]; 574 return; 575 } 576 577 // default 578 579 // handle control + alt Key Combos (ctrl+alt is reserved for QEMU) 580 if (([event modifierFlags] & NSControlKeyMask) && ([event modifierFlags] & NSAlternateKeyMask)) { 581 switch (keycode) { 582 583 // enable graphic console 584 case 0x02 ... 0x0a: // '1' to '9' keys 585 console_select(keycode - 0x02); 586 break; 587 } 588 589 // handle keys for graphic console 590 } else if (qemu_console_is_graphic(NULL)) { 591 qemu_input_event_send_key_number(dcl->con, keycode, true); 592 593 // handlekeys for Monitor 594 } else { 595 int keysym = 0; 596 switch([event keyCode]) { 597 case 115: 598 keysym = QEMU_KEY_HOME; 599 break; 600 case 117: 601 keysym = QEMU_KEY_DELETE; 602 break; 603 case 119: 604 keysym = QEMU_KEY_END; 605 break; 606 case 123: 607 keysym = QEMU_KEY_LEFT; 608 break; 609 case 124: 610 keysym = QEMU_KEY_RIGHT; 611 break; 612 case 125: 613 keysym = QEMU_KEY_DOWN; 614 break; 615 case 126: 616 keysym = QEMU_KEY_UP; 617 break; 618 default: 619 { 620 NSString *ks = [event characters]; 621 if ([ks length] > 0) 622 keysym = [ks characterAtIndex:0]; 623 } 624 } 625 if (keysym) 626 kbd_put_keysym(keysym); 627 } 628 break; 629 case NSKeyUp: 630 keycode = cocoa_keycode_to_qemu([event keyCode]); 631 632 // don't pass the guest a spurious key-up if we treated this 633 // command-key combo as a host UI action 634 if (!isMouseGrabbed && ([event modifierFlags] & NSCommandKeyMask)) { 635 return; 636 } 637 638 if (qemu_console_is_graphic(NULL)) { 639 qemu_input_event_send_key_number(dcl->con, keycode, false); 640 } 641 break; 642 case NSMouseMoved: 643 if (isAbsoluteEnabled) { 644 if (![self screenContainsPoint:p] || ![[self window] isKeyWindow]) { 645 if (isMouseGrabbed) { 646 [self ungrabMouse]; 647 } 648 } else { 649 if (!isMouseGrabbed) { 650 [self grabMouse]; 651 } 652 } 653 } 654 mouse_event = true; 655 break; 656 case NSLeftMouseDown: 657 if ([event modifierFlags] & NSCommandKeyMask) { 658 buttons |= MOUSE_EVENT_RBUTTON; 659 } else { 660 buttons |= MOUSE_EVENT_LBUTTON; 661 } 662 mouse_event = true; 663 break; 664 case NSRightMouseDown: 665 buttons |= MOUSE_EVENT_RBUTTON; 666 mouse_event = true; 667 break; 668 case NSOtherMouseDown: 669 buttons |= MOUSE_EVENT_MBUTTON; 670 mouse_event = true; 671 break; 672 case NSLeftMouseDragged: 673 if ([event modifierFlags] & NSCommandKeyMask) { 674 buttons |= MOUSE_EVENT_RBUTTON; 675 } else { 676 buttons |= MOUSE_EVENT_LBUTTON; 677 } 678 mouse_event = true; 679 break; 680 case NSRightMouseDragged: 681 buttons |= MOUSE_EVENT_RBUTTON; 682 mouse_event = true; 683 break; 684 case NSOtherMouseDragged: 685 buttons |= MOUSE_EVENT_MBUTTON; 686 mouse_event = true; 687 break; 688 case NSLeftMouseUp: 689 mouse_event = true; 690 if (!isMouseGrabbed && [self screenContainsPoint:p]) { 691 [self grabMouse]; 692 } 693 break; 694 case NSRightMouseUp: 695 mouse_event = true; 696 break; 697 case NSOtherMouseUp: 698 mouse_event = true; 699 break; 700 case NSScrollWheel: 701 if (isMouseGrabbed) { 702 buttons |= ([event deltaY] < 0) ? 703 MOUSE_EVENT_WHEELUP : MOUSE_EVENT_WHEELDN; 704 } 705 mouse_event = true; 706 break; 707 default: 708 [NSApp sendEvent:event]; 709 } 710 711 if (mouse_event) { 712 if (last_buttons != buttons) { 713 static uint32_t bmap[INPUT_BUTTON_MAX] = { 714 [INPUT_BUTTON_LEFT] = MOUSE_EVENT_LBUTTON, 715 [INPUT_BUTTON_MIDDLE] = MOUSE_EVENT_MBUTTON, 716 [INPUT_BUTTON_RIGHT] = MOUSE_EVENT_RBUTTON, 717 [INPUT_BUTTON_WHEEL_UP] = MOUSE_EVENT_WHEELUP, 718 [INPUT_BUTTON_WHEEL_DOWN] = MOUSE_EVENT_WHEELDN, 719 }; 720 qemu_input_update_buttons(dcl->con, bmap, last_buttons, buttons); 721 last_buttons = buttons; 722 } 723 if (isMouseGrabbed) { 724 if (isAbsoluteEnabled) { 725 /* Note that the origin for Cocoa mouse coords is bottom left, not top left. 726 * The check on screenContainsPoint is to avoid sending out of range values for 727 * clicks in the titlebar. 728 */ 729 if ([self screenContainsPoint:p]) { 730 qemu_input_queue_abs(dcl->con, INPUT_AXIS_X, p.x, screen.width); 731 qemu_input_queue_abs(dcl->con, INPUT_AXIS_Y, screen.height - p.y, screen.height); 732 } 733 } else { 734 qemu_input_queue_rel(dcl->con, INPUT_AXIS_X, (int)[event deltaX]); 735 qemu_input_queue_rel(dcl->con, INPUT_AXIS_Y, (int)[event deltaY]); 736 } 737 } else { 738 [NSApp sendEvent:event]; 739 } 740 qemu_input_event_sync(); 741 } 742} 743 744- (void) grabMouse 745{ 746 COCOA_DEBUG("QemuCocoaView: grabMouse\n"); 747 748 if (!isFullscreen) { 749 if (qemu_name) 750 [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s - (Press ctrl + alt to release Mouse)", qemu_name]]; 751 else 752 [normalWindow setTitle:@"QEMU - (Press ctrl + alt to release Mouse)"]; 753 } 754 [self hideCursor]; 755 if (!isAbsoluteEnabled) { 756 isMouseDeassociated = TRUE; 757 CGAssociateMouseAndMouseCursorPosition(FALSE); 758 } 759 isMouseGrabbed = TRUE; // while isMouseGrabbed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:] 760} 761 762- (void) ungrabMouse 763{ 764 COCOA_DEBUG("QemuCocoaView: ungrabMouse\n"); 765 766 if (!isFullscreen) { 767 if (qemu_name) 768 [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]]; 769 else 770 [normalWindow setTitle:@"QEMU"]; 771 } 772 [self unhideCursor]; 773 if (isMouseDeassociated) { 774 CGAssociateMouseAndMouseCursorPosition(TRUE); 775 isMouseDeassociated = FALSE; 776 } 777 isMouseGrabbed = FALSE; 778} 779 780- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled {isAbsoluteEnabled = tIsAbsoluteEnabled;} 781- (BOOL) isMouseGrabbed {return isMouseGrabbed;} 782- (BOOL) isAbsoluteEnabled {return isAbsoluteEnabled;} 783- (BOOL) isMouseDeassociated {return isMouseDeassociated;} 784- (float) cdx {return cdx;} 785- (float) cdy {return cdy;} 786- (QEMUScreen) gscreen {return screen;} 787@end 788 789 790 791/* 792 ------------------------------------------------------ 793 QemuCocoaAppController 794 ------------------------------------------------------ 795*/ 796@interface QemuCocoaAppController : NSObject 797{ 798} 799- (void)startEmulationWithArgc:(int)argc argv:(char**)argv; 800- (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo; 801- (void)toggleFullScreen:(id)sender; 802- (void)showQEMUDoc:(id)sender; 803- (void)showQEMUTec:(id)sender; 804@end 805 806@implementation QemuCocoaAppController 807- (id) init 808{ 809 COCOA_DEBUG("QemuCocoaAppController: init\n"); 810 811 self = [super init]; 812 if (self) { 813 814 // create a view and add it to the window 815 cocoaView = [[QemuCocoaView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 640.0, 480.0)]; 816 if(!cocoaView) { 817 fprintf(stderr, "(cocoa) can't create a view\n"); 818 exit(1); 819 } 820 821 // create a window 822 normalWindow = [[NSWindow alloc] initWithContentRect:[cocoaView frame] 823 styleMask:NSTitledWindowMask|NSMiniaturizableWindowMask|NSClosableWindowMask 824 backing:NSBackingStoreBuffered defer:NO]; 825 if(!normalWindow) { 826 fprintf(stderr, "(cocoa) can't create window\n"); 827 exit(1); 828 } 829 [normalWindow setAcceptsMouseMovedEvents:YES]; 830 [normalWindow setTitle:[NSString stringWithFormat:@"QEMU"]]; 831 [normalWindow setContentView:cocoaView]; 832 [normalWindow useOptimizedDrawing:YES]; 833 [normalWindow makeKeyAndOrderFront:self]; 834 [normalWindow center]; 835 836 } 837 return self; 838} 839 840- (void) dealloc 841{ 842 COCOA_DEBUG("QemuCocoaAppController: dealloc\n"); 843 844 if (cocoaView) 845 [cocoaView release]; 846 [super dealloc]; 847} 848 849- (void)applicationDidFinishLaunching: (NSNotification *) note 850{ 851 COCOA_DEBUG("QemuCocoaAppController: applicationDidFinishLaunching\n"); 852 853 // Display an open dialog box if no arguments were passed or 854 // if qemu was launched from the finder ( the Finder passes "-psn" ) 855 if( gArgc <= 1 || strncmp ((char *)gArgv[1], "-psn", 4) == 0) { 856 NSOpenPanel *op = [[NSOpenPanel alloc] init]; 857 [op setPrompt:@"Boot image"]; 858 [op setMessage:@"Select the disk image you want to boot.\n\nHit the \"Cancel\" button to quit"]; 859 NSArray *filetypes = [NSArray arrayWithObjects:@"img", @"iso", @"dmg", 860 @"qcow", @"qcow2", @"cow", @"cloop", @"vmdk", nil]; 861#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6) 862 [op setAllowedFileTypes:filetypes]; 863 [op beginSheetModalForWindow:normalWindow 864 completionHandler:^(NSInteger returnCode) 865 { [self openPanelDidEnd:op 866 returnCode:returnCode contextInfo:NULL ]; } ]; 867#else 868 // Compatibility code for pre-10.6, using deprecated method 869 [op beginSheetForDirectory:nil file:nil types:filetypes 870 modalForWindow:normalWindow modalDelegate:self 871 didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:) contextInfo:NULL]; 872#endif 873 } else { 874 // or launch QEMU, with the global args 875 [self startEmulationWithArgc:gArgc argv:(char **)gArgv]; 876 } 877} 878 879- (void)applicationWillTerminate:(NSNotification *)aNotification 880{ 881 COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n"); 882 883 qemu_system_shutdown_request(); 884 exit(0); 885} 886 887- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication 888{ 889 return YES; 890} 891 892- (void)startEmulationWithArgc:(int)argc argv:(char**)argv 893{ 894 COCOA_DEBUG("QemuCocoaAppController: startEmulationWithArgc\n"); 895 896 int status; 897 status = qemu_main(argc, argv, *_NSGetEnviron()); 898 exit(status); 899} 900 901- (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo 902{ 903 COCOA_DEBUG("QemuCocoaAppController: openPanelDidEnd\n"); 904 905 if(returnCode == NSCancelButton) { 906 exit(0); 907 } else if(returnCode == NSOKButton) { 908 char *img = (char*)[ [ [ sheet URL ] path ] cStringUsingEncoding:NSASCIIStringEncoding]; 909 910 char **argv = g_new(char *, 4); 911 912 [sheet close]; 913 914 argv[0] = g_strdup(gArgv[0]); 915 argv[1] = g_strdup("-hda"); 916 argv[2] = g_strdup(img); 917 argv[3] = NULL; 918 919 // printf("Using argc %d argv %s -hda %s\n", 3, gArgv[0], img); 920 921 [self startEmulationWithArgc:3 argv:(char**)argv]; 922 } 923} 924- (void)toggleFullScreen:(id)sender 925{ 926 COCOA_DEBUG("QemuCocoaAppController: toggleFullScreen\n"); 927 928 [cocoaView toggleFullScreen:sender]; 929} 930 931- (void)showQEMUDoc:(id)sender 932{ 933 COCOA_DEBUG("QemuCocoaAppController: showQEMUDoc\n"); 934 935 [[NSWorkspace sharedWorkspace] openFile:[NSString stringWithFormat:@"%@/../doc/qemu/qemu-doc.html", 936 [[NSBundle mainBundle] resourcePath]] withApplication:@"Help Viewer"]; 937} 938 939- (void)showQEMUTec:(id)sender 940{ 941 COCOA_DEBUG("QemuCocoaAppController: showQEMUTec\n"); 942 943 [[NSWorkspace sharedWorkspace] openFile:[NSString stringWithFormat:@"%@/../doc/qemu/qemu-tech.html", 944 [[NSBundle mainBundle] resourcePath]] withApplication:@"Help Viewer"]; 945} 946@end 947 948 949 950int main (int argc, const char * argv[]) { 951 952 gArgc = argc; 953 gArgv = (char **)argv; 954 int i; 955 956 /* In case we don't need to display a window, let's not do that */ 957 for (i = 1; i < argc; i++) { 958 const char *opt = argv[i]; 959 960 if (opt[0] == '-') { 961 /* Treat --foo the same as -foo. */ 962 if (opt[1] == '-') { 963 opt++; 964 } 965 if (!strcmp(opt, "-h") || !strcmp(opt, "-help") || 966 !strcmp(opt, "-vnc") || 967 !strcmp(opt, "-nographic") || 968 !strcmp(opt, "-version") || 969 !strcmp(opt, "-curses") || 970 !strcmp(opt, "-qtest")) { 971 return qemu_main(gArgc, gArgv, *_NSGetEnviron()); 972 } 973 } 974 } 975 976 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 977 978 // Pull this console process up to being a fully-fledged graphical 979 // app with a menubar and Dock icon 980 ProcessSerialNumber psn = { 0, kCurrentProcess }; 981 TransformProcessType(&psn, kProcessTransformToForegroundApplication); 982 983 [NSApplication sharedApplication]; 984 985 // Add menus 986 NSMenu *menu; 987 NSMenuItem *menuItem; 988 989 [NSApp setMainMenu:[[NSMenu alloc] init]]; 990 991 // Application menu 992 menu = [[NSMenu alloc] initWithTitle:@""]; 993 [menu addItemWithTitle:@"About QEMU" action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; // About QEMU 994 [menu addItem:[NSMenuItem separatorItem]]; //Separator 995 [menu addItemWithTitle:@"Hide QEMU" action:@selector(hide:) keyEquivalent:@"h"]; //Hide QEMU 996 menuItem = (NSMenuItem *)[menu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; // Hide Others 997 [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; 998 [menu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; // Show All 999 [menu addItem:[NSMenuItem separatorItem]]; //Separator 1000 [menu addItemWithTitle:@"Quit QEMU" action:@selector(terminate:) keyEquivalent:@"q"]; 1001 menuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" action:nil keyEquivalent:@""]; 1002 [menuItem setSubmenu:menu]; 1003 [[NSApp mainMenu] addItem:menuItem]; 1004 [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; // Workaround (this method is private since 10.4+) 1005 1006 // View menu 1007 menu = [[NSMenu alloc] initWithTitle:@"View"]; 1008 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(toggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen 1009 menuItem = [[[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""] autorelease]; 1010 [menuItem setSubmenu:menu]; 1011 [[NSApp mainMenu] addItem:menuItem]; 1012 1013 // Window menu 1014 menu = [[NSMenu alloc] initWithTitle:@"Window"]; 1015 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease]]; // Miniaturize 1016 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease]; 1017 [menuItem setSubmenu:menu]; 1018 [[NSApp mainMenu] addItem:menuItem]; 1019 [NSApp setWindowsMenu:menu]; 1020 1021 // Help menu 1022 menu = [[NSMenu alloc] initWithTitle:@"Help"]; 1023 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Documentation" action:@selector(showQEMUDoc:) keyEquivalent:@"?"] autorelease]]; // QEMU Help 1024 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Technology" action:@selector(showQEMUTec:) keyEquivalent:@""] autorelease]]; // QEMU Help 1025 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease]; 1026 [menuItem setSubmenu:menu]; 1027 [[NSApp mainMenu] addItem:menuItem]; 1028 1029 // Create an Application controller 1030 QemuCocoaAppController *appController = [[QemuCocoaAppController alloc] init]; 1031 [NSApp setDelegate:appController]; 1032 1033 // Start the main event loop 1034 [NSApp run]; 1035 1036 [appController release]; 1037 [pool release]; 1038 1039 return 0; 1040} 1041 1042 1043 1044#pragma mark qemu 1045static void cocoa_update(DisplayChangeListener *dcl, 1046 int x, int y, int w, int h) 1047{ 1048 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 1049 1050 COCOA_DEBUG("qemu_cocoa: cocoa_update\n"); 1051 1052 NSRect rect; 1053 if ([cocoaView cdx] == 1.0) { 1054 rect = NSMakeRect(x, [cocoaView gscreen].height - y - h, w, h); 1055 } else { 1056 rect = NSMakeRect( 1057 x * [cocoaView cdx], 1058 ([cocoaView gscreen].height - y - h) * [cocoaView cdy], 1059 w * [cocoaView cdx], 1060 h * [cocoaView cdy]); 1061 } 1062 [cocoaView setNeedsDisplayInRect:rect]; 1063 1064 [pool release]; 1065} 1066 1067static void cocoa_switch(DisplayChangeListener *dcl, 1068 DisplaySurface *surface) 1069{ 1070 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 1071 1072 COCOA_DEBUG("qemu_cocoa: cocoa_switch\n"); 1073 [cocoaView switchSurface:surface]; 1074 [pool release]; 1075} 1076 1077static void cocoa_refresh(DisplayChangeListener *dcl) 1078{ 1079 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 1080 1081 COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n"); 1082 1083 if (qemu_input_is_absolute()) { 1084 if (![cocoaView isAbsoluteEnabled]) { 1085 if ([cocoaView isMouseGrabbed]) { 1086 [cocoaView ungrabMouse]; 1087 } 1088 } 1089 [cocoaView setAbsoluteEnabled:YES]; 1090 } 1091 1092 NSDate *distantPast; 1093 NSEvent *event; 1094 distantPast = [NSDate distantPast]; 1095 do { 1096 event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:distantPast 1097 inMode: NSDefaultRunLoopMode dequeue:YES]; 1098 if (event != nil) { 1099 [cocoaView handleEvent:event]; 1100 } 1101 } while(event != nil); 1102 graphic_hw_update(NULL); 1103 [pool release]; 1104} 1105 1106static void cocoa_cleanup(void) 1107{ 1108 COCOA_DEBUG("qemu_cocoa: cocoa_cleanup\n"); 1109 g_free(dcl); 1110} 1111 1112static const DisplayChangeListenerOps dcl_ops = { 1113 .dpy_name = "cocoa", 1114 .dpy_gfx_update = cocoa_update, 1115 .dpy_gfx_switch = cocoa_switch, 1116 .dpy_refresh = cocoa_refresh, 1117}; 1118 1119void cocoa_display_init(DisplayState *ds, int full_screen) 1120{ 1121 COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n"); 1122 1123 dcl = g_malloc0(sizeof(DisplayChangeListener)); 1124 1125 // register vga output callbacks 1126 dcl->ops = &dcl_ops; 1127 register_displaychangelistener(dcl); 1128 1129 // register cleanup function 1130 atexit(cocoa_cleanup); 1131} 1132