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#include "qemu/osdep.h" 26 27#import <Cocoa/Cocoa.h> 28#include <crt_externs.h> 29 30#include "qemu-common.h" 31#include "ui/console.h" 32#include "ui/input.h" 33#include "sysemu/sysemu.h" 34#include "qmp-commands.h" 35#include "sysemu/blockdev.h" 36#include "qemu-version.h" 37#include <Carbon/Carbon.h> 38 39#ifndef MAC_OS_X_VERSION_10_5 40#define MAC_OS_X_VERSION_10_5 1050 41#endif 42#ifndef MAC_OS_X_VERSION_10_6 43#define MAC_OS_X_VERSION_10_6 1060 44#endif 45#ifndef MAC_OS_X_VERSION_10_10 46#define MAC_OS_X_VERSION_10_10 101000 47#endif 48 49 50//#define DEBUG 51 52#ifdef DEBUG 53#define COCOA_DEBUG(...) { (void) fprintf (stdout, __VA_ARGS__); } 54#else 55#define COCOA_DEBUG(...) ((void) 0) 56#endif 57 58#define cgrect(nsrect) (*(CGRect *)&(nsrect)) 59 60typedef struct { 61 int width; 62 int height; 63 int bitsPerComponent; 64 int bitsPerPixel; 65} QEMUScreen; 66 67NSWindow *normalWindow, *about_window; 68static DisplayChangeListener *dcl; 69static int last_buttons; 70 71int gArgc; 72char **gArgv; 73bool stretch_video; 74NSTextField *pauseLabel; 75NSArray * supportedImageFileTypes; 76 77// Mac to QKeyCode conversion 78const int mac_to_qkeycode_map[] = { 79 [kVK_ANSI_A] = Q_KEY_CODE_A, 80 [kVK_ANSI_B] = Q_KEY_CODE_B, 81 [kVK_ANSI_C] = Q_KEY_CODE_C, 82 [kVK_ANSI_D] = Q_KEY_CODE_D, 83 [kVK_ANSI_E] = Q_KEY_CODE_E, 84 [kVK_ANSI_F] = Q_KEY_CODE_F, 85 [kVK_ANSI_G] = Q_KEY_CODE_G, 86 [kVK_ANSI_H] = Q_KEY_CODE_H, 87 [kVK_ANSI_I] = Q_KEY_CODE_I, 88 [kVK_ANSI_J] = Q_KEY_CODE_J, 89 [kVK_ANSI_K] = Q_KEY_CODE_K, 90 [kVK_ANSI_L] = Q_KEY_CODE_L, 91 [kVK_ANSI_M] = Q_KEY_CODE_M, 92 [kVK_ANSI_N] = Q_KEY_CODE_N, 93 [kVK_ANSI_O] = Q_KEY_CODE_O, 94 [kVK_ANSI_P] = Q_KEY_CODE_P, 95 [kVK_ANSI_Q] = Q_KEY_CODE_Q, 96 [kVK_ANSI_R] = Q_KEY_CODE_R, 97 [kVK_ANSI_S] = Q_KEY_CODE_S, 98 [kVK_ANSI_T] = Q_KEY_CODE_T, 99 [kVK_ANSI_U] = Q_KEY_CODE_U, 100 [kVK_ANSI_V] = Q_KEY_CODE_V, 101 [kVK_ANSI_W] = Q_KEY_CODE_W, 102 [kVK_ANSI_X] = Q_KEY_CODE_X, 103 [kVK_ANSI_Y] = Q_KEY_CODE_Y, 104 [kVK_ANSI_Z] = Q_KEY_CODE_Z, 105 106 [kVK_ANSI_0] = Q_KEY_CODE_0, 107 [kVK_ANSI_1] = Q_KEY_CODE_1, 108 [kVK_ANSI_2] = Q_KEY_CODE_2, 109 [kVK_ANSI_3] = Q_KEY_CODE_3, 110 [kVK_ANSI_4] = Q_KEY_CODE_4, 111 [kVK_ANSI_5] = Q_KEY_CODE_5, 112 [kVK_ANSI_6] = Q_KEY_CODE_6, 113 [kVK_ANSI_7] = Q_KEY_CODE_7, 114 [kVK_ANSI_8] = Q_KEY_CODE_8, 115 [kVK_ANSI_9] = Q_KEY_CODE_9, 116 117 [kVK_ANSI_Grave] = Q_KEY_CODE_GRAVE_ACCENT, 118 [kVK_ANSI_Minus] = Q_KEY_CODE_MINUS, 119 [kVK_ANSI_Equal] = Q_KEY_CODE_EQUAL, 120 [kVK_Delete] = Q_KEY_CODE_BACKSPACE, 121 [kVK_CapsLock] = Q_KEY_CODE_CAPS_LOCK, 122 [kVK_Tab] = Q_KEY_CODE_TAB, 123 [kVK_Return] = Q_KEY_CODE_RET, 124 [kVK_ANSI_LeftBracket] = Q_KEY_CODE_BRACKET_LEFT, 125 [kVK_ANSI_RightBracket] = Q_KEY_CODE_BRACKET_RIGHT, 126 [kVK_ANSI_Backslash] = Q_KEY_CODE_BACKSLASH, 127 [kVK_ANSI_Semicolon] = Q_KEY_CODE_SEMICOLON, 128 [kVK_ANSI_Quote] = Q_KEY_CODE_APOSTROPHE, 129 [kVK_ANSI_Comma] = Q_KEY_CODE_COMMA, 130 [kVK_ANSI_Period] = Q_KEY_CODE_DOT, 131 [kVK_ANSI_Slash] = Q_KEY_CODE_SLASH, 132 [kVK_Shift] = Q_KEY_CODE_SHIFT, 133 [kVK_RightShift] = Q_KEY_CODE_SHIFT_R, 134 [kVK_Control] = Q_KEY_CODE_CTRL, 135 [kVK_RightControl] = Q_KEY_CODE_CTRL_R, 136 [kVK_Option] = Q_KEY_CODE_ALT, 137 [kVK_RightOption] = Q_KEY_CODE_ALT_R, 138 [kVK_Command] = Q_KEY_CODE_META_L, 139 [0x36] = Q_KEY_CODE_META_R, /* There is no kVK_RightCommand */ 140 [kVK_Space] = Q_KEY_CODE_SPC, 141 142 [kVK_ANSI_Keypad0] = Q_KEY_CODE_KP_0, 143 [kVK_ANSI_Keypad1] = Q_KEY_CODE_KP_1, 144 [kVK_ANSI_Keypad2] = Q_KEY_CODE_KP_2, 145 [kVK_ANSI_Keypad3] = Q_KEY_CODE_KP_3, 146 [kVK_ANSI_Keypad4] = Q_KEY_CODE_KP_4, 147 [kVK_ANSI_Keypad5] = Q_KEY_CODE_KP_5, 148 [kVK_ANSI_Keypad6] = Q_KEY_CODE_KP_6, 149 [kVK_ANSI_Keypad7] = Q_KEY_CODE_KP_7, 150 [kVK_ANSI_Keypad8] = Q_KEY_CODE_KP_8, 151 [kVK_ANSI_Keypad9] = Q_KEY_CODE_KP_9, 152 [kVK_ANSI_KeypadDecimal] = Q_KEY_CODE_KP_DECIMAL, 153 [kVK_ANSI_KeypadEnter] = Q_KEY_CODE_KP_ENTER, 154 [kVK_ANSI_KeypadPlus] = Q_KEY_CODE_KP_ADD, 155 [kVK_ANSI_KeypadMinus] = Q_KEY_CODE_KP_SUBTRACT, 156 [kVK_ANSI_KeypadMultiply] = Q_KEY_CODE_KP_MULTIPLY, 157 [kVK_ANSI_KeypadDivide] = Q_KEY_CODE_KP_DIVIDE, 158 [kVK_ANSI_KeypadEquals] = Q_KEY_CODE_KP_EQUALS, 159 [kVK_ANSI_KeypadClear] = Q_KEY_CODE_NUM_LOCK, 160 161 [kVK_UpArrow] = Q_KEY_CODE_UP, 162 [kVK_DownArrow] = Q_KEY_CODE_DOWN, 163 [kVK_LeftArrow] = Q_KEY_CODE_LEFT, 164 [kVK_RightArrow] = Q_KEY_CODE_RIGHT, 165 166 [kVK_Help] = Q_KEY_CODE_INSERT, 167 [kVK_Home] = Q_KEY_CODE_HOME, 168 [kVK_PageUp] = Q_KEY_CODE_PGUP, 169 [kVK_PageDown] = Q_KEY_CODE_PGDN, 170 [kVK_End] = Q_KEY_CODE_END, 171 [kVK_ForwardDelete] = Q_KEY_CODE_DELETE, 172 173 [kVK_Escape] = Q_KEY_CODE_ESC, 174 175 /* The Power key can't be used directly because the operating system uses 176 * it. This key can be emulated by using it in place of another key such as 177 * F1. Don't forget to disable the real key binding. 178 */ 179 /* [kVK_F1] = Q_KEY_CODE_POWER, */ 180 181 [kVK_F1] = Q_KEY_CODE_F1, 182 [kVK_F2] = Q_KEY_CODE_F2, 183 [kVK_F3] = Q_KEY_CODE_F3, 184 [kVK_F4] = Q_KEY_CODE_F4, 185 [kVK_F5] = Q_KEY_CODE_F5, 186 [kVK_F6] = Q_KEY_CODE_F6, 187 [kVK_F7] = Q_KEY_CODE_F7, 188 [kVK_F8] = Q_KEY_CODE_F8, 189 [kVK_F9] = Q_KEY_CODE_F9, 190 [kVK_F10] = Q_KEY_CODE_F10, 191 [kVK_F11] = Q_KEY_CODE_F11, 192 [kVK_F12] = Q_KEY_CODE_F12, 193 [kVK_F13] = Q_KEY_CODE_PRINT, 194 [kVK_F14] = Q_KEY_CODE_SCROLL_LOCK, 195 [kVK_F15] = Q_KEY_CODE_PAUSE, 196 197 /* 198 * The eject and volume keys can't be used here because they are handled at 199 * a lower level than what an Application can see. 200 */ 201}; 202 203static int cocoa_keycode_to_qemu(int keycode) 204{ 205 if (ARRAY_SIZE(mac_to_qkeycode_map) <= keycode) { 206 fprintf(stderr, "(cocoa) warning unknown keycode 0x%x\n", keycode); 207 return 0; 208 } 209 return mac_to_qkeycode_map[keycode]; 210} 211 212/* Displays an alert dialog box with the specified message */ 213static void QEMU_Alert(NSString *message) 214{ 215 NSAlert *alert; 216 alert = [NSAlert new]; 217 [alert setMessageText: message]; 218 [alert runModal]; 219} 220 221/* Handles any errors that happen with a device transaction */ 222static void handleAnyDeviceErrors(Error * err) 223{ 224 if (err) { 225 QEMU_Alert([NSString stringWithCString: error_get_pretty(err) 226 encoding: NSASCIIStringEncoding]); 227 error_free(err); 228 } 229} 230 231/* 232 ------------------------------------------------------ 233 QemuCocoaView 234 ------------------------------------------------------ 235*/ 236@interface QemuCocoaView : NSView 237{ 238 QEMUScreen screen; 239 NSWindow *fullScreenWindow; 240 float cx,cy,cw,ch,cdx,cdy; 241 CGDataProviderRef dataProviderRef; 242 int modifiers_state[256]; 243 BOOL isMouseGrabbed; 244 BOOL isFullscreen; 245 BOOL isAbsoluteEnabled; 246 BOOL isMouseDeassociated; 247} 248- (void) switchSurface:(DisplaySurface *)surface; 249- (void) grabMouse; 250- (void) ungrabMouse; 251- (void) toggleFullScreen:(id)sender; 252- (void) handleEvent:(NSEvent *)event; 253- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled; 254/* The state surrounding mouse grabbing is potentially confusing. 255 * isAbsoluteEnabled tracks qemu_input_is_absolute() [ie "is the emulated 256 * pointing device an absolute-position one?"], but is only updated on 257 * next refresh. 258 * isMouseGrabbed tracks whether GUI events are directed to the guest; 259 * it controls whether special keys like Cmd get sent to the guest, 260 * and whether we capture the mouse when in non-absolute mode. 261 * isMouseDeassociated tracks whether we've told MacOSX to disassociate 262 * the mouse and mouse cursor position by calling 263 * CGAssociateMouseAndMouseCursorPosition(FALSE) 264 * (which basically happens if we grab in non-absolute mode). 265 */ 266- (BOOL) isMouseGrabbed; 267- (BOOL) isAbsoluteEnabled; 268- (BOOL) isMouseDeassociated; 269- (float) cdx; 270- (float) cdy; 271- (QEMUScreen) gscreen; 272- (void) raiseAllKeys; 273@end 274 275QemuCocoaView *cocoaView; 276 277@implementation QemuCocoaView 278- (id)initWithFrame:(NSRect)frameRect 279{ 280 COCOA_DEBUG("QemuCocoaView: initWithFrame\n"); 281 282 self = [super initWithFrame:frameRect]; 283 if (self) { 284 285 screen.bitsPerComponent = 8; 286 screen.bitsPerPixel = 32; 287 screen.width = frameRect.size.width; 288 screen.height = frameRect.size.height; 289 290 } 291 return self; 292} 293 294- (void) dealloc 295{ 296 COCOA_DEBUG("QemuCocoaView: dealloc\n"); 297 298 if (dataProviderRef) 299 CGDataProviderRelease(dataProviderRef); 300 301 [super dealloc]; 302} 303 304- (BOOL) isOpaque 305{ 306 return YES; 307} 308 309- (BOOL) screenContainsPoint:(NSPoint) p 310{ 311 return (p.x > -1 && p.x < screen.width && p.y > -1 && p.y < screen.height); 312} 313 314- (void) hideCursor 315{ 316 if (!cursor_hide) { 317 return; 318 } 319 [NSCursor hide]; 320} 321 322- (void) unhideCursor 323{ 324 if (!cursor_hide) { 325 return; 326 } 327 [NSCursor unhide]; 328} 329 330- (void) drawRect:(NSRect) rect 331{ 332 COCOA_DEBUG("QemuCocoaView: drawRect\n"); 333 334 // get CoreGraphic context 335 CGContextRef viewContextRef = [[NSGraphicsContext currentContext] graphicsPort]; 336 CGContextSetInterpolationQuality (viewContextRef, kCGInterpolationNone); 337 CGContextSetShouldAntialias (viewContextRef, NO); 338 339 // draw screen bitmap directly to Core Graphics context 340 if (!dataProviderRef) { 341 // Draw request before any guest device has set up a framebuffer: 342 // just draw an opaque black rectangle 343 CGContextSetRGBFillColor(viewContextRef, 0, 0, 0, 1.0); 344 CGContextFillRect(viewContextRef, NSRectToCGRect(rect)); 345 } else { 346 CGImageRef imageRef = CGImageCreate( 347 screen.width, //width 348 screen.height, //height 349 screen.bitsPerComponent, //bitsPerComponent 350 screen.bitsPerPixel, //bitsPerPixel 351 (screen.width * (screen.bitsPerComponent/2)), //bytesPerRow 352#ifdef __LITTLE_ENDIAN__ 353 CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB), //colorspace for OS X >= 10.4 354 kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, 355#else 356 CGColorSpaceCreateDeviceRGB(), //colorspace for OS X < 10.4 (actually ppc) 357 kCGImageAlphaNoneSkipFirst, //bitmapInfo 358#endif 359 dataProviderRef, //provider 360 NULL, //decode 361 0, //interpolate 362 kCGRenderingIntentDefault //intent 363 ); 364 // selective drawing code (draws only dirty rectangles) (OS X >= 10.4) 365 const NSRect *rectList; 366 NSInteger rectCount; 367 int i; 368 CGImageRef clipImageRef; 369 CGRect clipRect; 370 371 [self getRectsBeingDrawn:&rectList count:&rectCount]; 372 for (i = 0; i < rectCount; i++) { 373 clipRect.origin.x = rectList[i].origin.x / cdx; 374 clipRect.origin.y = (float)screen.height - (rectList[i].origin.y + rectList[i].size.height) / cdy; 375 clipRect.size.width = rectList[i].size.width / cdx; 376 clipRect.size.height = rectList[i].size.height / cdy; 377 clipImageRef = CGImageCreateWithImageInRect( 378 imageRef, 379 clipRect 380 ); 381 CGContextDrawImage (viewContextRef, cgrect(rectList[i]), clipImageRef); 382 CGImageRelease (clipImageRef); 383 } 384 CGImageRelease (imageRef); 385 } 386} 387 388- (void) setContentDimensions 389{ 390 COCOA_DEBUG("QemuCocoaView: setContentDimensions\n"); 391 392 if (isFullscreen) { 393 cdx = [[NSScreen mainScreen] frame].size.width / (float)screen.width; 394 cdy = [[NSScreen mainScreen] frame].size.height / (float)screen.height; 395 396 /* stretches video, but keeps same aspect ratio */ 397 if (stretch_video == true) { 398 /* use smallest stretch value - prevents clipping on sides */ 399 if (MIN(cdx, cdy) == cdx) { 400 cdy = cdx; 401 } else { 402 cdx = cdy; 403 } 404 } else { /* No stretching */ 405 cdx = cdy = 1; 406 } 407 cw = screen.width * cdx; 408 ch = screen.height * cdy; 409 cx = ([[NSScreen mainScreen] frame].size.width - cw) / 2.0; 410 cy = ([[NSScreen mainScreen] frame].size.height - ch) / 2.0; 411 } else { 412 cx = 0; 413 cy = 0; 414 cw = screen.width; 415 ch = screen.height; 416 cdx = 1.0; 417 cdy = 1.0; 418 } 419} 420 421- (void) switchSurface:(DisplaySurface *)surface 422{ 423 COCOA_DEBUG("QemuCocoaView: switchSurface\n"); 424 425 int w = surface_width(surface); 426 int h = surface_height(surface); 427 /* cdx == 0 means this is our very first surface, in which case we need 428 * to recalculate the content dimensions even if it happens to be the size 429 * of the initial empty window. 430 */ 431 bool isResize = (w != screen.width || h != screen.height || cdx == 0.0); 432 433 int oldh = screen.height; 434 if (isResize) { 435 // Resize before we trigger the redraw, or we'll redraw at the wrong size 436 COCOA_DEBUG("switchSurface: new size %d x %d\n", w, h); 437 screen.width = w; 438 screen.height = h; 439 [self setContentDimensions]; 440 [self setFrame:NSMakeRect(cx, cy, cw, ch)]; 441 } 442 443 // update screenBuffer 444 if (dataProviderRef) 445 CGDataProviderRelease(dataProviderRef); 446 447 //sync host window color space with guests 448 screen.bitsPerPixel = surface_bits_per_pixel(surface); 449 screen.bitsPerComponent = surface_bytes_per_pixel(surface) * 2; 450 451 dataProviderRef = CGDataProviderCreateWithData(NULL, surface_data(surface), w * 4 * h, NULL); 452 453 // update windows 454 if (isFullscreen) { 455 [[fullScreenWindow contentView] setFrame:[[NSScreen mainScreen] frame]]; 456 [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + oldh, w, h + [normalWindow frame].size.height - oldh) display:NO animate:NO]; 457 } else { 458 if (qemu_name) 459 [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]]; 460 [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + oldh, w, h + [normalWindow frame].size.height - oldh) display:YES animate:NO]; 461 } 462 463 if (isResize) { 464 [normalWindow center]; 465 } 466} 467 468- (void) toggleFullScreen:(id)sender 469{ 470 COCOA_DEBUG("QemuCocoaView: toggleFullScreen\n"); 471 472 if (isFullscreen) { // switch from fullscreen to desktop 473 isFullscreen = FALSE; 474 [self ungrabMouse]; 475 [self setContentDimensions]; 476 if ([NSView respondsToSelector:@selector(exitFullScreenModeWithOptions:)]) { // test if "exitFullScreenModeWithOptions" is supported on host at runtime 477 [self exitFullScreenModeWithOptions:nil]; 478 } else { 479 [fullScreenWindow close]; 480 [normalWindow setContentView: self]; 481 [normalWindow makeKeyAndOrderFront: self]; 482 [NSMenu setMenuBarVisible:YES]; 483 } 484 } else { // switch from desktop to fullscreen 485 isFullscreen = TRUE; 486 [normalWindow orderOut: nil]; /* Hide the window */ 487 [self grabMouse]; 488 [self setContentDimensions]; 489 if ([NSView respondsToSelector:@selector(enterFullScreenMode:withOptions:)]) { // test if "enterFullScreenMode:withOptions" is supported on host at runtime 490 [self enterFullScreenMode:[NSScreen mainScreen] withOptions:[NSDictionary dictionaryWithObjectsAndKeys: 491 [NSNumber numberWithBool:NO], NSFullScreenModeAllScreens, 492 [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO], kCGDisplayModeIsStretched, nil], NSFullScreenModeSetting, 493 nil]]; 494 } else { 495 [NSMenu setMenuBarVisible:NO]; 496 fullScreenWindow = [[NSWindow alloc] initWithContentRect:[[NSScreen mainScreen] frame] 497 styleMask:NSBorderlessWindowMask 498 backing:NSBackingStoreBuffered 499 defer:NO]; 500 [fullScreenWindow setAcceptsMouseMovedEvents: YES]; 501 [fullScreenWindow setHasShadow:NO]; 502 [fullScreenWindow setBackgroundColor: [NSColor blackColor]]; 503 [self setFrame:NSMakeRect(cx, cy, cw, ch)]; 504 [[fullScreenWindow contentView] addSubview: self]; 505 [fullScreenWindow makeKeyAndOrderFront:self]; 506 } 507 } 508} 509 510- (void) handleEvent:(NSEvent *)event 511{ 512 COCOA_DEBUG("QemuCocoaView: handleEvent\n"); 513 514 int buttons = 0; 515 int keycode; 516 bool mouse_event = false; 517 NSPoint p = [event locationInWindow]; 518 519 switch ([event type]) { 520 case NSFlagsChanged: 521 keycode = cocoa_keycode_to_qemu([event keyCode]); 522 523 if ((keycode == Q_KEY_CODE_META_L || keycode == Q_KEY_CODE_META_R) 524 && !isMouseGrabbed) { 525 /* Don't pass command key changes to guest unless mouse is grabbed */ 526 keycode = 0; 527 } 528 529 if (keycode) { 530 // emulate caps lock and num lock keydown and keyup 531 if (keycode == Q_KEY_CODE_CAPS_LOCK || 532 keycode == Q_KEY_CODE_NUM_LOCK) { 533 qemu_input_event_send_key_qcode(dcl->con, keycode, true); 534 qemu_input_event_send_key_qcode(dcl->con, keycode, false); 535 } else if (qemu_console_is_graphic(NULL)) { 536 if (modifiers_state[keycode] == 0) { // keydown 537 qemu_input_event_send_key_qcode(dcl->con, keycode, true); 538 modifiers_state[keycode] = 1; 539 } else { // keyup 540 qemu_input_event_send_key_qcode(dcl->con, keycode, false); 541 modifiers_state[keycode] = 0; 542 } 543 } 544 } 545 546 // release Mouse grab when pressing ctrl+alt 547 if (([event modifierFlags] & NSControlKeyMask) && ([event modifierFlags] & NSAlternateKeyMask)) { 548 [self ungrabMouse]; 549 } 550 break; 551 case NSKeyDown: 552 keycode = cocoa_keycode_to_qemu([event keyCode]); 553 554 // forward command key combos to the host UI unless the mouse is grabbed 555 if (!isMouseGrabbed && ([event modifierFlags] & NSCommandKeyMask)) { 556 [NSApp sendEvent:event]; 557 return; 558 } 559 560 // default 561 562 // handle control + alt Key Combos (ctrl+alt is reserved for QEMU) 563 if (([event modifierFlags] & NSControlKeyMask) && ([event modifierFlags] & NSAlternateKeyMask)) { 564 switch (keycode) { 565 566 // enable graphic console 567 case Q_KEY_CODE_1 ... Q_KEY_CODE_9: // '1' to '9' keys 568 console_select(keycode - 11); 569 break; 570 } 571 572 // handle keys for graphic console 573 } else if (qemu_console_is_graphic(NULL)) { 574 qemu_input_event_send_key_qcode(dcl->con, keycode, true); 575 576 // handlekeys for Monitor 577 } else { 578 int keysym = 0; 579 switch([event keyCode]) { 580 case 115: 581 keysym = QEMU_KEY_HOME; 582 break; 583 case 117: 584 keysym = QEMU_KEY_DELETE; 585 break; 586 case 119: 587 keysym = QEMU_KEY_END; 588 break; 589 case 123: 590 keysym = QEMU_KEY_LEFT; 591 break; 592 case 124: 593 keysym = QEMU_KEY_RIGHT; 594 break; 595 case 125: 596 keysym = QEMU_KEY_DOWN; 597 break; 598 case 126: 599 keysym = QEMU_KEY_UP; 600 break; 601 default: 602 { 603 NSString *ks = [event characters]; 604 if ([ks length] > 0) 605 keysym = [ks characterAtIndex:0]; 606 } 607 } 608 if (keysym) 609 kbd_put_keysym(keysym); 610 } 611 break; 612 case NSKeyUp: 613 keycode = cocoa_keycode_to_qemu([event keyCode]); 614 615 // don't pass the guest a spurious key-up if we treated this 616 // command-key combo as a host UI action 617 if (!isMouseGrabbed && ([event modifierFlags] & NSCommandKeyMask)) { 618 return; 619 } 620 621 if (qemu_console_is_graphic(NULL)) { 622 qemu_input_event_send_key_qcode(dcl->con, keycode, false); 623 } 624 break; 625 case NSMouseMoved: 626 if (isAbsoluteEnabled) { 627 if (![self screenContainsPoint:p] || ![[self window] isKeyWindow]) { 628 if (isMouseGrabbed) { 629 [self ungrabMouse]; 630 } 631 } else { 632 if (!isMouseGrabbed) { 633 [self grabMouse]; 634 } 635 } 636 } 637 mouse_event = true; 638 break; 639 case NSLeftMouseDown: 640 if ([event modifierFlags] & NSCommandKeyMask) { 641 buttons |= MOUSE_EVENT_RBUTTON; 642 } else { 643 buttons |= MOUSE_EVENT_LBUTTON; 644 } 645 mouse_event = true; 646 break; 647 case NSRightMouseDown: 648 buttons |= MOUSE_EVENT_RBUTTON; 649 mouse_event = true; 650 break; 651 case NSOtherMouseDown: 652 buttons |= MOUSE_EVENT_MBUTTON; 653 mouse_event = true; 654 break; 655 case NSLeftMouseDragged: 656 if ([event modifierFlags] & NSCommandKeyMask) { 657 buttons |= MOUSE_EVENT_RBUTTON; 658 } else { 659 buttons |= MOUSE_EVENT_LBUTTON; 660 } 661 mouse_event = true; 662 break; 663 case NSRightMouseDragged: 664 buttons |= MOUSE_EVENT_RBUTTON; 665 mouse_event = true; 666 break; 667 case NSOtherMouseDragged: 668 buttons |= MOUSE_EVENT_MBUTTON; 669 mouse_event = true; 670 break; 671 case NSLeftMouseUp: 672 mouse_event = true; 673 if (!isMouseGrabbed && [self screenContainsPoint:p]) { 674 if([[self window] isKeyWindow]) { 675 [self grabMouse]; 676 } 677 } 678 break; 679 case NSRightMouseUp: 680 mouse_event = true; 681 break; 682 case NSOtherMouseUp: 683 mouse_event = true; 684 break; 685 case NSScrollWheel: 686 if (isMouseGrabbed) { 687 buttons |= ([event deltaY] < 0) ? 688 MOUSE_EVENT_WHEELUP : MOUSE_EVENT_WHEELDN; 689 } 690 mouse_event = true; 691 break; 692 default: 693 [NSApp sendEvent:event]; 694 } 695 696 if (mouse_event) { 697 /* Don't send button events to the guest unless we've got a 698 * mouse grab or window focus. If we have neither then this event 699 * is the user clicking on the background window to activate and 700 * bring us to the front, which will be done by the sendEvent 701 * call below. We definitely don't want to pass that click through 702 * to the guest. 703 */ 704 if ((isMouseGrabbed || [[self window] isKeyWindow]) && 705 (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 781/* 782 * Makes the target think all down keys are being released. 783 * This prevents a stuck key problem, since we will not see 784 * key up events for those keys after we have lost focus. 785 */ 786- (void) raiseAllKeys 787{ 788 int index; 789 const int max_index = ARRAY_SIZE(modifiers_state); 790 791 for (index = 0; index < max_index; index++) { 792 if (modifiers_state[index]) { 793 modifiers_state[index] = 0; 794 qemu_input_event_send_key_qcode(dcl->con, index, false); 795 } 796 } 797} 798@end 799 800 801 802/* 803 ------------------------------------------------------ 804 QemuCocoaAppController 805 ------------------------------------------------------ 806*/ 807@interface QemuCocoaAppController : NSObject 808#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6) 809 <NSWindowDelegate, NSApplicationDelegate> 810#endif 811{ 812} 813- (void)startEmulationWithArgc:(int)argc argv:(char**)argv; 814- (void)doToggleFullScreen:(id)sender; 815- (void)toggleFullScreen:(id)sender; 816- (void)showQEMUDoc:(id)sender; 817- (void)zoomToFit:(id) sender; 818- (void)displayConsole:(id)sender; 819- (void)pauseQEMU:(id)sender; 820- (void)resumeQEMU:(id)sender; 821- (void)displayPause; 822- (void)removePause; 823- (void)restartQEMU:(id)sender; 824- (void)powerDownQEMU:(id)sender; 825- (void)ejectDeviceMedia:(id)sender; 826- (void)changeDeviceMedia:(id)sender; 827- (BOOL)verifyQuit; 828- (void)openDocumentation:(NSString *)filename; 829- (IBAction) do_about_menu_item: (id) sender; 830- (void)make_about_window; 831@end 832 833@implementation QemuCocoaAppController 834- (id) init 835{ 836 COCOA_DEBUG("QemuCocoaAppController: init\n"); 837 838 self = [super init]; 839 if (self) { 840 841 // create a view and add it to the window 842 cocoaView = [[QemuCocoaView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 640.0, 480.0)]; 843 if(!cocoaView) { 844 fprintf(stderr, "(cocoa) can't create a view\n"); 845 exit(1); 846 } 847 848 // create a window 849 normalWindow = [[NSWindow alloc] initWithContentRect:[cocoaView frame] 850 styleMask:NSTitledWindowMask|NSMiniaturizableWindowMask|NSClosableWindowMask 851 backing:NSBackingStoreBuffered defer:NO]; 852 if(!normalWindow) { 853 fprintf(stderr, "(cocoa) can't create window\n"); 854 exit(1); 855 } 856 [normalWindow setAcceptsMouseMovedEvents:YES]; 857 [normalWindow setTitle:@"QEMU"]; 858 [normalWindow setContentView:cocoaView]; 859#if (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10) 860 [normalWindow useOptimizedDrawing:YES]; 861#endif 862 [normalWindow makeKeyAndOrderFront:self]; 863 [normalWindow center]; 864 [normalWindow setDelegate: self]; 865 stretch_video = false; 866 867 /* Used for displaying pause on the screen */ 868 pauseLabel = [NSTextField new]; 869 [pauseLabel setBezeled:YES]; 870 [pauseLabel setDrawsBackground:YES]; 871 [pauseLabel setBackgroundColor: [NSColor whiteColor]]; 872 [pauseLabel setEditable:NO]; 873 [pauseLabel setSelectable:NO]; 874 [pauseLabel setStringValue: @"Paused"]; 875 [pauseLabel setFont: [NSFont fontWithName: @"Helvetica" size: 90]]; 876 [pauseLabel setTextColor: [NSColor blackColor]]; 877 [pauseLabel sizeToFit]; 878 879 // set the supported image file types that can be opened 880 supportedImageFileTypes = [NSArray arrayWithObjects: @"img", @"iso", @"dmg", 881 @"qcow", @"qcow2", @"cloop", @"vmdk", @"cdr", 882 @"toast", nil]; 883 [self make_about_window]; 884 } 885 return self; 886} 887 888- (void) dealloc 889{ 890 COCOA_DEBUG("QemuCocoaAppController: dealloc\n"); 891 892 if (cocoaView) 893 [cocoaView release]; 894 [super dealloc]; 895} 896 897- (void)applicationDidFinishLaunching: (NSNotification *) note 898{ 899 COCOA_DEBUG("QemuCocoaAppController: applicationDidFinishLaunching\n"); 900 // launch QEMU, with the global args 901 [self startEmulationWithArgc:gArgc argv:(char **)gArgv]; 902} 903 904- (void)applicationWillTerminate:(NSNotification *)aNotification 905{ 906 COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n"); 907 908 qemu_system_shutdown_request(); 909 exit(0); 910} 911 912- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication 913{ 914 return YES; 915} 916 917- (NSApplicationTerminateReply)applicationShouldTerminate: 918 (NSApplication *)sender 919{ 920 COCOA_DEBUG("QemuCocoaAppController: applicationShouldTerminate\n"); 921 return [self verifyQuit]; 922} 923 924/* Called when the user clicks on a window's close button */ 925- (BOOL)windowShouldClose:(id)sender 926{ 927 COCOA_DEBUG("QemuCocoaAppController: windowShouldClose\n"); 928 [NSApp terminate: sender]; 929 /* If the user allows the application to quit then the call to 930 * NSApp terminate will never return. If we get here then the user 931 * cancelled the quit, so we should return NO to not permit the 932 * closing of this window. 933 */ 934 return NO; 935} 936 937/* Called when QEMU goes into the background */ 938- (void) applicationWillResignActive: (NSNotification *)aNotification 939{ 940 COCOA_DEBUG("QemuCocoaAppController: applicationWillResignActive\n"); 941 [cocoaView raiseAllKeys]; 942} 943 944- (void)startEmulationWithArgc:(int)argc argv:(char**)argv 945{ 946 COCOA_DEBUG("QemuCocoaAppController: startEmulationWithArgc\n"); 947 948 int status; 949 status = qemu_main(argc, argv, *_NSGetEnviron()); 950 exit(status); 951} 952 953/* We abstract the method called by the Enter Fullscreen menu item 954 * because Mac OS 10.7 and higher disables it. This is because of the 955 * menu item's old selector's name toggleFullScreen: 956 */ 957- (void) doToggleFullScreen:(id)sender 958{ 959 [self toggleFullScreen:(id)sender]; 960} 961 962- (void)toggleFullScreen:(id)sender 963{ 964 COCOA_DEBUG("QemuCocoaAppController: toggleFullScreen\n"); 965 966 [cocoaView toggleFullScreen:sender]; 967} 968 969/* Tries to find then open the specified filename */ 970- (void) openDocumentation: (NSString *) filename 971{ 972 /* Where to look for local files */ 973 NSString *path_array[] = {@"../share/doc/qemu/", @"../doc/qemu/", @"../"}; 974 NSString *full_file_path; 975 976 /* iterate thru the possible paths until the file is found */ 977 int index; 978 for (index = 0; index < ARRAY_SIZE(path_array); index++) { 979 full_file_path = [[NSBundle mainBundle] executablePath]; 980 full_file_path = [full_file_path stringByDeletingLastPathComponent]; 981 full_file_path = [NSString stringWithFormat: @"%@/%@%@", full_file_path, 982 path_array[index], filename]; 983 if ([[NSWorkspace sharedWorkspace] openFile: full_file_path] == YES) { 984 return; 985 } 986 } 987 988 /* If none of the paths opened a file */ 989 NSBeep(); 990 QEMU_Alert(@"Failed to open file"); 991} 992 993- (void)showQEMUDoc:(id)sender 994{ 995 COCOA_DEBUG("QemuCocoaAppController: showQEMUDoc\n"); 996 997 [self openDocumentation: @"qemu-doc.html"]; 998} 999 1000/* Stretches video to fit host monitor size */ 1001- (void)zoomToFit:(id) sender 1002{ 1003 stretch_video = !stretch_video; 1004 if (stretch_video == true) { 1005 [sender setState: NSOnState]; 1006 } else { 1007 [sender setState: NSOffState]; 1008 } 1009} 1010 1011/* Displays the console on the screen */ 1012- (void)displayConsole:(id)sender 1013{ 1014 console_select([sender tag]); 1015} 1016 1017/* Pause the guest */ 1018- (void)pauseQEMU:(id)sender 1019{ 1020 qmp_stop(NULL); 1021 [sender setEnabled: NO]; 1022 [[[sender menu] itemWithTitle: @"Resume"] setEnabled: YES]; 1023 [self displayPause]; 1024} 1025 1026/* Resume running the guest operating system */ 1027- (void)resumeQEMU:(id) sender 1028{ 1029 qmp_cont(NULL); 1030 [sender setEnabled: NO]; 1031 [[[sender menu] itemWithTitle: @"Pause"] setEnabled: YES]; 1032 [self removePause]; 1033} 1034 1035/* Displays the word pause on the screen */ 1036- (void)displayPause 1037{ 1038 /* Coordinates have to be calculated each time because the window can change its size */ 1039 int xCoord, yCoord, width, height; 1040 xCoord = ([normalWindow frame].size.width - [pauseLabel frame].size.width)/2; 1041 yCoord = [normalWindow frame].size.height - [pauseLabel frame].size.height - ([pauseLabel frame].size.height * .5); 1042 width = [pauseLabel frame].size.width; 1043 height = [pauseLabel frame].size.height; 1044 [pauseLabel setFrame: NSMakeRect(xCoord, yCoord, width, height)]; 1045 [cocoaView addSubview: pauseLabel]; 1046} 1047 1048/* Removes the word pause from the screen */ 1049- (void)removePause 1050{ 1051 [pauseLabel removeFromSuperview]; 1052} 1053 1054/* Restarts QEMU */ 1055- (void)restartQEMU:(id)sender 1056{ 1057 qmp_system_reset(NULL); 1058} 1059 1060/* Powers down QEMU */ 1061- (void)powerDownQEMU:(id)sender 1062{ 1063 qmp_system_powerdown(NULL); 1064} 1065 1066/* Ejects the media. 1067 * Uses sender's tag to figure out the device to eject. 1068 */ 1069- (void)ejectDeviceMedia:(id)sender 1070{ 1071 NSString * drive; 1072 drive = [sender representedObject]; 1073 if(drive == nil) { 1074 NSBeep(); 1075 QEMU_Alert(@"Failed to find drive to eject!"); 1076 return; 1077 } 1078 1079 Error *err = NULL; 1080 qmp_eject(true, [drive cStringUsingEncoding: NSASCIIStringEncoding], 1081 false, NULL, false, false, &err); 1082 handleAnyDeviceErrors(err); 1083} 1084 1085/* Displays a dialog box asking the user to select an image file to load. 1086 * Uses sender's represented object value to figure out which drive to use. 1087 */ 1088- (void)changeDeviceMedia:(id)sender 1089{ 1090 /* Find the drive name */ 1091 NSString * drive; 1092 drive = [sender representedObject]; 1093 if(drive == nil) { 1094 NSBeep(); 1095 QEMU_Alert(@"Could not find drive!"); 1096 return; 1097 } 1098 1099 /* Display the file open dialog */ 1100 NSOpenPanel * openPanel; 1101 openPanel = [NSOpenPanel openPanel]; 1102 [openPanel setCanChooseFiles: YES]; 1103 [openPanel setAllowsMultipleSelection: NO]; 1104 [openPanel setAllowedFileTypes: supportedImageFileTypes]; 1105 if([openPanel runModal] == NSFileHandlingPanelOKButton) { 1106 NSString * file = [[[openPanel URLs] objectAtIndex: 0] path]; 1107 if(file == nil) { 1108 NSBeep(); 1109 QEMU_Alert(@"Failed to convert URL to file path!"); 1110 return; 1111 } 1112 1113 Error *err = NULL; 1114 qmp_blockdev_change_medium(true, 1115 [drive cStringUsingEncoding: 1116 NSASCIIStringEncoding], 1117 false, NULL, 1118 [file cStringUsingEncoding: 1119 NSASCIIStringEncoding], 1120 true, "raw", 1121 false, 0, 1122 &err); 1123 handleAnyDeviceErrors(err); 1124 } 1125} 1126 1127/* Verifies if the user really wants to quit */ 1128- (BOOL)verifyQuit 1129{ 1130 NSAlert *alert = [NSAlert new]; 1131 [alert autorelease]; 1132 [alert setMessageText: @"Are you sure you want to quit QEMU?"]; 1133 [alert addButtonWithTitle: @"Cancel"]; 1134 [alert addButtonWithTitle: @"Quit"]; 1135 if([alert runModal] == NSAlertSecondButtonReturn) { 1136 return YES; 1137 } else { 1138 return NO; 1139 } 1140} 1141 1142/* The action method for the About menu item */ 1143- (IBAction) do_about_menu_item: (id) sender 1144{ 1145 [about_window makeKeyAndOrderFront: nil]; 1146} 1147 1148/* Create and display the about dialog */ 1149- (void)make_about_window 1150{ 1151 /* Make the window */ 1152 int x = 0, y = 0, about_width = 400, about_height = 200; 1153 NSRect window_rect = NSMakeRect(x, y, about_width, about_height); 1154 about_window = [[NSWindow alloc] initWithContentRect:window_rect 1155 styleMask:NSTitledWindowMask | NSClosableWindowMask | 1156 NSMiniaturizableWindowMask 1157 backing:NSBackingStoreBuffered 1158 defer:NO]; 1159 [about_window setTitle: @"About"]; 1160 [about_window setReleasedWhenClosed: NO]; 1161 [about_window center]; 1162 NSView *superView = [about_window contentView]; 1163 1164 /* Create the dimensions of the picture */ 1165 int picture_width = 80, picture_height = 80; 1166 x = (about_width - picture_width)/2; 1167 y = about_height - picture_height - 10; 1168 NSRect picture_rect = NSMakeRect(x, y, picture_width, picture_height); 1169 1170 /* Get the path to the QEMU binary */ 1171 NSString *binary_name = [NSString stringWithCString: gArgv[0] 1172 encoding: NSASCIIStringEncoding]; 1173 binary_name = [binary_name lastPathComponent]; 1174 NSString *program_path = [[NSString alloc] initWithFormat: @"%@/%@", 1175 [[NSBundle mainBundle] bundlePath], binary_name]; 1176 1177 /* Make the picture of QEMU */ 1178 NSImageView *picture_view = [[NSImageView alloc] initWithFrame: 1179 picture_rect]; 1180 NSImage *qemu_image = [[NSWorkspace sharedWorkspace] iconForFile: 1181 program_path]; 1182 [picture_view setImage: qemu_image]; 1183 [picture_view setImageScaling: NSImageScaleProportionallyUpOrDown]; 1184 [superView addSubview: picture_view]; 1185 1186 /* Make the name label */ 1187 x = 0; 1188 y = y - 25; 1189 int name_width = about_width, name_height = 20; 1190 NSRect name_rect = NSMakeRect(x, y, name_width, name_height); 1191 NSTextField *name_label = [[NSTextField alloc] initWithFrame: name_rect]; 1192 [name_label setEditable: NO]; 1193 [name_label setBezeled: NO]; 1194 [name_label setDrawsBackground: NO]; 1195 [name_label setAlignment: NSCenterTextAlignment]; 1196 NSString *qemu_name = [[NSString alloc] initWithCString: gArgv[0] 1197 encoding: NSASCIIStringEncoding]; 1198 qemu_name = [qemu_name lastPathComponent]; 1199 [name_label setStringValue: qemu_name]; 1200 [superView addSubview: name_label]; 1201 1202 /* Set the version label's attributes */ 1203 x = 0; 1204 y = 50; 1205 int version_width = about_width, version_height = 20; 1206 NSRect version_rect = NSMakeRect(x, y, version_width, version_height); 1207 NSTextField *version_label = [[NSTextField alloc] initWithFrame: 1208 version_rect]; 1209 [version_label setEditable: NO]; 1210 [version_label setBezeled: NO]; 1211 [version_label setAlignment: NSCenterTextAlignment]; 1212 [version_label setDrawsBackground: NO]; 1213 1214 /* Create the version string*/ 1215 NSString *version_string; 1216 version_string = [[NSString alloc] initWithFormat: 1217 @"QEMU emulator version %s%s", QEMU_VERSION, QEMU_PKGVERSION]; 1218 [version_label setStringValue: version_string]; 1219 [superView addSubview: version_label]; 1220 1221 /* Make copyright label */ 1222 x = 0; 1223 y = 35; 1224 int copyright_width = about_width, copyright_height = 20; 1225 NSRect copyright_rect = NSMakeRect(x, y, copyright_width, copyright_height); 1226 NSTextField *copyright_label = [[NSTextField alloc] initWithFrame: 1227 copyright_rect]; 1228 [copyright_label setEditable: NO]; 1229 [copyright_label setBezeled: NO]; 1230 [copyright_label setDrawsBackground: NO]; 1231 [copyright_label setAlignment: NSCenterTextAlignment]; 1232 [copyright_label setStringValue: [NSString stringWithFormat: @"%s", 1233 QEMU_COPYRIGHT]]; 1234 [superView addSubview: copyright_label]; 1235} 1236 1237@end 1238 1239 1240int main (int argc, const char * argv[]) { 1241 1242 gArgc = argc; 1243 gArgv = (char **)argv; 1244 int i; 1245 1246 /* In case we don't need to display a window, let's not do that */ 1247 for (i = 1; i < argc; i++) { 1248 const char *opt = argv[i]; 1249 1250 if (opt[0] == '-') { 1251 /* Treat --foo the same as -foo. */ 1252 if (opt[1] == '-') { 1253 opt++; 1254 } 1255 if (!strcmp(opt, "-h") || !strcmp(opt, "-help") || 1256 !strcmp(opt, "-vnc") || 1257 !strcmp(opt, "-nographic") || 1258 !strcmp(opt, "-version") || 1259 !strcmp(opt, "-curses") || 1260 !strcmp(opt, "-display") || 1261 !strcmp(opt, "-qtest")) { 1262 return qemu_main(gArgc, gArgv, *_NSGetEnviron()); 1263 } 1264 } 1265 } 1266 1267 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 1268 1269 // Pull this console process up to being a fully-fledged graphical 1270 // app with a menubar and Dock icon 1271 ProcessSerialNumber psn = { 0, kCurrentProcess }; 1272 TransformProcessType(&psn, kProcessTransformToForegroundApplication); 1273 1274 [NSApplication sharedApplication]; 1275 1276 // Add menus 1277 NSMenu *menu; 1278 NSMenuItem *menuItem; 1279 1280 [NSApp setMainMenu:[[NSMenu alloc] init]]; 1281 1282 // Application menu 1283 menu = [[NSMenu alloc] initWithTitle:@""]; 1284 [menu addItemWithTitle:@"About QEMU" action:@selector(do_about_menu_item:) keyEquivalent:@""]; // About QEMU 1285 [menu addItem:[NSMenuItem separatorItem]]; //Separator 1286 [menu addItemWithTitle:@"Hide QEMU" action:@selector(hide:) keyEquivalent:@"h"]; //Hide QEMU 1287 menuItem = (NSMenuItem *)[menu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; // Hide Others 1288 [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; 1289 [menu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; // Show All 1290 [menu addItem:[NSMenuItem separatorItem]]; //Separator 1291 [menu addItemWithTitle:@"Quit QEMU" action:@selector(terminate:) keyEquivalent:@"q"]; 1292 menuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" action:nil keyEquivalent:@""]; 1293 [menuItem setSubmenu:menu]; 1294 [[NSApp mainMenu] addItem:menuItem]; 1295 [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; // Workaround (this method is private since 10.4+) 1296 1297 // Machine menu 1298 menu = [[NSMenu alloc] initWithTitle: @"Machine"]; 1299 [menu setAutoenablesItems: NO]; 1300 [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Pause" action: @selector(pauseQEMU:) keyEquivalent: @""] autorelease]]; 1301 menuItem = [[[NSMenuItem alloc] initWithTitle: @"Resume" action: @selector(resumeQEMU:) keyEquivalent: @""] autorelease]; 1302 [menu addItem: menuItem]; 1303 [menuItem setEnabled: NO]; 1304 [menu addItem: [NSMenuItem separatorItem]]; 1305 [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Reset" action: @selector(restartQEMU:) keyEquivalent: @""] autorelease]]; 1306 [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Power Down" action: @selector(powerDownQEMU:) keyEquivalent: @""] autorelease]]; 1307 menuItem = [[[NSMenuItem alloc] initWithTitle: @"Machine" action:nil keyEquivalent:@""] autorelease]; 1308 [menuItem setSubmenu:menu]; 1309 [[NSApp mainMenu] addItem:menuItem]; 1310 1311 // View menu 1312 menu = [[NSMenu alloc] initWithTitle:@"View"]; 1313 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(doToggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen 1314 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Zoom To Fit" action:@selector(zoomToFit:) keyEquivalent:@""] autorelease]]; 1315 menuItem = [[[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""] autorelease]; 1316 [menuItem setSubmenu:menu]; 1317 [[NSApp mainMenu] addItem:menuItem]; 1318 1319 // Window menu 1320 menu = [[NSMenu alloc] initWithTitle:@"Window"]; 1321 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease]]; // Miniaturize 1322 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease]; 1323 [menuItem setSubmenu:menu]; 1324 [[NSApp mainMenu] addItem:menuItem]; 1325 [NSApp setWindowsMenu:menu]; 1326 1327 // Help menu 1328 menu = [[NSMenu alloc] initWithTitle:@"Help"]; 1329 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Documentation" action:@selector(showQEMUDoc:) keyEquivalent:@"?"] autorelease]]; // QEMU Help 1330 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease]; 1331 [menuItem setSubmenu:menu]; 1332 [[NSApp mainMenu] addItem:menuItem]; 1333 1334 // Create an Application controller 1335 QemuCocoaAppController *appController = [[QemuCocoaAppController alloc] init]; 1336 [NSApp setDelegate:appController]; 1337 1338 // Start the main event loop 1339 [NSApp run]; 1340 1341 [appController release]; 1342 [pool release]; 1343 1344 return 0; 1345} 1346 1347 1348 1349#pragma mark qemu 1350static void cocoa_update(DisplayChangeListener *dcl, 1351 int x, int y, int w, int h) 1352{ 1353 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 1354 1355 COCOA_DEBUG("qemu_cocoa: cocoa_update\n"); 1356 1357 NSRect rect; 1358 if ([cocoaView cdx] == 1.0) { 1359 rect = NSMakeRect(x, [cocoaView gscreen].height - y - h, w, h); 1360 } else { 1361 rect = NSMakeRect( 1362 x * [cocoaView cdx], 1363 ([cocoaView gscreen].height - y - h) * [cocoaView cdy], 1364 w * [cocoaView cdx], 1365 h * [cocoaView cdy]); 1366 } 1367 [cocoaView setNeedsDisplayInRect:rect]; 1368 1369 [pool release]; 1370} 1371 1372static void cocoa_switch(DisplayChangeListener *dcl, 1373 DisplaySurface *surface) 1374{ 1375 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 1376 1377 COCOA_DEBUG("qemu_cocoa: cocoa_switch\n"); 1378 [cocoaView switchSurface:surface]; 1379 [pool release]; 1380} 1381 1382static void cocoa_refresh(DisplayChangeListener *dcl) 1383{ 1384 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 1385 1386 COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n"); 1387 graphic_hw_update(NULL); 1388 1389 if (qemu_input_is_absolute()) { 1390 if (![cocoaView isAbsoluteEnabled]) { 1391 if ([cocoaView isMouseGrabbed]) { 1392 [cocoaView ungrabMouse]; 1393 } 1394 } 1395 [cocoaView setAbsoluteEnabled:YES]; 1396 } 1397 1398 NSDate *distantPast; 1399 NSEvent *event; 1400 distantPast = [NSDate distantPast]; 1401 do { 1402 event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:distantPast 1403 inMode: NSDefaultRunLoopMode dequeue:YES]; 1404 if (event != nil) { 1405 [cocoaView handleEvent:event]; 1406 } 1407 } while(event != nil); 1408 [pool release]; 1409} 1410 1411static void cocoa_cleanup(void) 1412{ 1413 COCOA_DEBUG("qemu_cocoa: cocoa_cleanup\n"); 1414 g_free(dcl); 1415} 1416 1417static const DisplayChangeListenerOps dcl_ops = { 1418 .dpy_name = "cocoa", 1419 .dpy_gfx_update = cocoa_update, 1420 .dpy_gfx_switch = cocoa_switch, 1421 .dpy_refresh = cocoa_refresh, 1422}; 1423 1424/* Returns a name for a given console */ 1425static NSString * getConsoleName(QemuConsole * console) 1426{ 1427 return [NSString stringWithFormat: @"%s", qemu_console_get_label(console)]; 1428} 1429 1430/* Add an entry to the View menu for each console */ 1431static void add_console_menu_entries(void) 1432{ 1433 NSMenu *menu; 1434 NSMenuItem *menuItem; 1435 int index = 0; 1436 1437 menu = [[[NSApp mainMenu] itemWithTitle:@"View"] submenu]; 1438 1439 [menu addItem:[NSMenuItem separatorItem]]; 1440 1441 while (qemu_console_lookup_by_index(index) != NULL) { 1442 menuItem = [[[NSMenuItem alloc] initWithTitle: getConsoleName(qemu_console_lookup_by_index(index)) 1443 action: @selector(displayConsole:) keyEquivalent: @""] autorelease]; 1444 [menuItem setTag: index]; 1445 [menu addItem: menuItem]; 1446 index++; 1447 } 1448} 1449 1450/* Make menu items for all removable devices. 1451 * Each device is given an 'Eject' and 'Change' menu item. 1452 */ 1453static void addRemovableDevicesMenuItems(void) 1454{ 1455 NSMenu *menu; 1456 NSMenuItem *menuItem; 1457 BlockInfoList *currentDevice, *pointerToFree; 1458 NSString *deviceName; 1459 1460 currentDevice = qmp_query_block(NULL); 1461 pointerToFree = currentDevice; 1462 if(currentDevice == NULL) { 1463 NSBeep(); 1464 QEMU_Alert(@"Failed to query for block devices!"); 1465 return; 1466 } 1467 1468 menu = [[[NSApp mainMenu] itemWithTitle:@"Machine"] submenu]; 1469 1470 // Add a separator between related groups of menu items 1471 [menu addItem:[NSMenuItem separatorItem]]; 1472 1473 // Set the attributes to the "Removable Media" menu item 1474 NSString *titleString = @"Removable Media"; 1475 NSMutableAttributedString *attString=[[NSMutableAttributedString alloc] initWithString:titleString]; 1476 NSColor *newColor = [NSColor blackColor]; 1477 NSFontManager *fontManager = [NSFontManager sharedFontManager]; 1478 NSFont *font = [fontManager fontWithFamily:@"Helvetica" 1479 traits:NSBoldFontMask|NSItalicFontMask 1480 weight:0 1481 size:14]; 1482 [attString addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, [titleString length])]; 1483 [attString addAttribute:NSForegroundColorAttributeName value:newColor range:NSMakeRange(0, [titleString length])]; 1484 [attString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInt: 1] range:NSMakeRange(0, [titleString length])]; 1485 1486 // Add the "Removable Media" menu item 1487 menuItem = [NSMenuItem new]; 1488 [menuItem setAttributedTitle: attString]; 1489 [menuItem setEnabled: NO]; 1490 [menu addItem: menuItem]; 1491 1492 /* Loop through all the block devices in the emulator */ 1493 while (currentDevice) { 1494 deviceName = [[NSString stringWithFormat: @"%s", currentDevice->value->device] retain]; 1495 1496 if(currentDevice->value->removable) { 1497 menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Change %s...", currentDevice->value->device] 1498 action: @selector(changeDeviceMedia:) 1499 keyEquivalent: @""]; 1500 [menu addItem: menuItem]; 1501 [menuItem setRepresentedObject: deviceName]; 1502 [menuItem autorelease]; 1503 1504 menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Eject %s", currentDevice->value->device] 1505 action: @selector(ejectDeviceMedia:) 1506 keyEquivalent: @""]; 1507 [menu addItem: menuItem]; 1508 [menuItem setRepresentedObject: deviceName]; 1509 [menuItem autorelease]; 1510 } 1511 currentDevice = currentDevice->next; 1512 } 1513 qapi_free_BlockInfoList(pointerToFree); 1514} 1515 1516void cocoa_display_init(DisplayState *ds, int full_screen) 1517{ 1518 COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n"); 1519 1520 /* if fullscreen mode is to be used */ 1521 if (full_screen == true) { 1522 [NSApp activateIgnoringOtherApps: YES]; 1523 [(QemuCocoaAppController *)[[NSApplication sharedApplication] delegate] toggleFullScreen: nil]; 1524 } 1525 1526 dcl = g_malloc0(sizeof(DisplayChangeListener)); 1527 1528 // register vga output callbacks 1529 dcl->ops = &dcl_ops; 1530 register_displaychangelistener(dcl); 1531 1532 // register cleanup function 1533 atexit(cocoa_cleanup); 1534 1535 /* At this point QEMU has created all the consoles, so we can add View 1536 * menu entries for them. 1537 */ 1538 add_console_menu_entries(); 1539 1540 /* Give all removable devices a menu item. 1541 * Has to be called after QEMU has started to 1542 * find out what removable devices it has. 1543 */ 1544 addRemovableDevicesMenuItems(); 1545} 1546