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