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)showQEMUTec:(id)sender; 818- (void)zoomToFit:(id) sender; 819- (void)displayConsole:(id)sender; 820- (void)pauseQEMU:(id)sender; 821- (void)resumeQEMU:(id)sender; 822- (void)displayPause; 823- (void)removePause; 824- (void)restartQEMU:(id)sender; 825- (void)powerDownQEMU:(id)sender; 826- (void)ejectDeviceMedia:(id)sender; 827- (void)changeDeviceMedia:(id)sender; 828- (BOOL)verifyQuit; 829- (void)openDocumentation:(NSString *)filename; 830- (IBAction) do_about_menu_item: (id) sender; 831- (void)make_about_window; 832@end 833 834@implementation QemuCocoaAppController 835- (id) init 836{ 837 COCOA_DEBUG("QemuCocoaAppController: init\n"); 838 839 self = [super init]; 840 if (self) { 841 842 // create a view and add it to the window 843 cocoaView = [[QemuCocoaView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 640.0, 480.0)]; 844 if(!cocoaView) { 845 fprintf(stderr, "(cocoa) can't create a view\n"); 846 exit(1); 847 } 848 849 // create a window 850 normalWindow = [[NSWindow alloc] initWithContentRect:[cocoaView frame] 851 styleMask:NSTitledWindowMask|NSMiniaturizableWindowMask|NSClosableWindowMask 852 backing:NSBackingStoreBuffered defer:NO]; 853 if(!normalWindow) { 854 fprintf(stderr, "(cocoa) can't create window\n"); 855 exit(1); 856 } 857 [normalWindow setAcceptsMouseMovedEvents:YES]; 858 [normalWindow setTitle:@"QEMU"]; 859 [normalWindow setContentView:cocoaView]; 860#if (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10) 861 [normalWindow useOptimizedDrawing:YES]; 862#endif 863 [normalWindow makeKeyAndOrderFront:self]; 864 [normalWindow center]; 865 [normalWindow setDelegate: self]; 866 stretch_video = false; 867 868 /* Used for displaying pause on the screen */ 869 pauseLabel = [NSTextField new]; 870 [pauseLabel setBezeled:YES]; 871 [pauseLabel setDrawsBackground:YES]; 872 [pauseLabel setBackgroundColor: [NSColor whiteColor]]; 873 [pauseLabel setEditable:NO]; 874 [pauseLabel setSelectable:NO]; 875 [pauseLabel setStringValue: @"Paused"]; 876 [pauseLabel setFont: [NSFont fontWithName: @"Helvetica" size: 90]]; 877 [pauseLabel setTextColor: [NSColor blackColor]]; 878 [pauseLabel sizeToFit]; 879 880 // set the supported image file types that can be opened 881 supportedImageFileTypes = [NSArray arrayWithObjects: @"img", @"iso", @"dmg", 882 @"qcow", @"qcow2", @"cloop", @"vmdk", @"cdr", 883 nil]; 884 [self make_about_window]; 885 } 886 return self; 887} 888 889- (void) dealloc 890{ 891 COCOA_DEBUG("QemuCocoaAppController: dealloc\n"); 892 893 if (cocoaView) 894 [cocoaView release]; 895 [super dealloc]; 896} 897 898- (void)applicationDidFinishLaunching: (NSNotification *) note 899{ 900 COCOA_DEBUG("QemuCocoaAppController: applicationDidFinishLaunching\n"); 901 // launch QEMU, with the global args 902 [self startEmulationWithArgc:gArgc argv:(char **)gArgv]; 903} 904 905- (void)applicationWillTerminate:(NSNotification *)aNotification 906{ 907 COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n"); 908 909 qemu_system_shutdown_request(); 910 exit(0); 911} 912 913- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication 914{ 915 return YES; 916} 917 918- (NSApplicationTerminateReply)applicationShouldTerminate: 919 (NSApplication *)sender 920{ 921 COCOA_DEBUG("QemuCocoaAppController: applicationShouldTerminate\n"); 922 return [self verifyQuit]; 923} 924 925/* Called when the user clicks on a window's close button */ 926- (BOOL)windowShouldClose:(id)sender 927{ 928 COCOA_DEBUG("QemuCocoaAppController: windowShouldClose\n"); 929 [NSApp terminate: sender]; 930 /* If the user allows the application to quit then the call to 931 * NSApp terminate will never return. If we get here then the user 932 * cancelled the quit, so we should return NO to not permit the 933 * closing of this window. 934 */ 935 return NO; 936} 937 938/* Called when QEMU goes into the background */ 939- (void) applicationWillResignActive: (NSNotification *)aNotification 940{ 941 COCOA_DEBUG("QemuCocoaAppController: applicationWillResignActive\n"); 942 [cocoaView raiseAllKeys]; 943} 944 945- (void)startEmulationWithArgc:(int)argc argv:(char**)argv 946{ 947 COCOA_DEBUG("QemuCocoaAppController: startEmulationWithArgc\n"); 948 949 int status; 950 status = qemu_main(argc, argv, *_NSGetEnviron()); 951 exit(status); 952} 953 954/* We abstract the method called by the Enter Fullscreen menu item 955 * because Mac OS 10.7 and higher disables it. This is because of the 956 * menu item's old selector's name toggleFullScreen: 957 */ 958- (void) doToggleFullScreen:(id)sender 959{ 960 [self toggleFullScreen:(id)sender]; 961} 962 963- (void)toggleFullScreen:(id)sender 964{ 965 COCOA_DEBUG("QemuCocoaAppController: toggleFullScreen\n"); 966 967 [cocoaView toggleFullScreen:sender]; 968} 969 970/* Tries to find then open the specified filename */ 971- (void) openDocumentation: (NSString *) filename 972{ 973 /* Where to look for local files */ 974 NSString *path_array[] = {@"../share/doc/qemu/", @"../doc/qemu/", @"../"}; 975 NSString *full_file_path; 976 977 /* iterate thru the possible paths until the file is found */ 978 int index; 979 for (index = 0; index < ARRAY_SIZE(path_array); index++) { 980 full_file_path = [[NSBundle mainBundle] executablePath]; 981 full_file_path = [full_file_path stringByDeletingLastPathComponent]; 982 full_file_path = [NSString stringWithFormat: @"%@/%@%@", full_file_path, 983 path_array[index], filename]; 984 if ([[NSWorkspace sharedWorkspace] openFile: full_file_path] == YES) { 985 return; 986 } 987 } 988 989 /* If none of the paths opened a file */ 990 NSBeep(); 991 QEMU_Alert(@"Failed to open file"); 992} 993 994- (void)showQEMUDoc:(id)sender 995{ 996 COCOA_DEBUG("QemuCocoaAppController: showQEMUDoc\n"); 997 998 [self openDocumentation: @"qemu-doc.html"]; 999} 1000 1001- (void)showQEMUTec:(id)sender 1002{ 1003 COCOA_DEBUG("QemuCocoaAppController: showQEMUTec\n"); 1004 1005 [self openDocumentation: @"qemu-tech.html"]; 1006} 1007 1008/* Stretches video to fit host monitor size */ 1009- (void)zoomToFit:(id) sender 1010{ 1011 stretch_video = !stretch_video; 1012 if (stretch_video == true) { 1013 [sender setState: NSOnState]; 1014 } else { 1015 [sender setState: NSOffState]; 1016 } 1017} 1018 1019/* Displays the console on the screen */ 1020- (void)displayConsole:(id)sender 1021{ 1022 console_select([sender tag]); 1023} 1024 1025/* Pause the guest */ 1026- (void)pauseQEMU:(id)sender 1027{ 1028 qmp_stop(NULL); 1029 [sender setEnabled: NO]; 1030 [[[sender menu] itemWithTitle: @"Resume"] setEnabled: YES]; 1031 [self displayPause]; 1032} 1033 1034/* Resume running the guest operating system */ 1035- (void)resumeQEMU:(id) sender 1036{ 1037 qmp_cont(NULL); 1038 [sender setEnabled: NO]; 1039 [[[sender menu] itemWithTitle: @"Pause"] setEnabled: YES]; 1040 [self removePause]; 1041} 1042 1043/* Displays the word pause on the screen */ 1044- (void)displayPause 1045{ 1046 /* Coordinates have to be calculated each time because the window can change its size */ 1047 int xCoord, yCoord, width, height; 1048 xCoord = ([normalWindow frame].size.width - [pauseLabel frame].size.width)/2; 1049 yCoord = [normalWindow frame].size.height - [pauseLabel frame].size.height - ([pauseLabel frame].size.height * .5); 1050 width = [pauseLabel frame].size.width; 1051 height = [pauseLabel frame].size.height; 1052 [pauseLabel setFrame: NSMakeRect(xCoord, yCoord, width, height)]; 1053 [cocoaView addSubview: pauseLabel]; 1054} 1055 1056/* Removes the word pause from the screen */ 1057- (void)removePause 1058{ 1059 [pauseLabel removeFromSuperview]; 1060} 1061 1062/* Restarts QEMU */ 1063- (void)restartQEMU:(id)sender 1064{ 1065 qmp_system_reset(NULL); 1066} 1067 1068/* Powers down QEMU */ 1069- (void)powerDownQEMU:(id)sender 1070{ 1071 qmp_system_powerdown(NULL); 1072} 1073 1074/* Ejects the media. 1075 * Uses sender's tag to figure out the device to eject. 1076 */ 1077- (void)ejectDeviceMedia:(id)sender 1078{ 1079 NSString * drive; 1080 drive = [sender representedObject]; 1081 if(drive == nil) { 1082 NSBeep(); 1083 QEMU_Alert(@"Failed to find drive to eject!"); 1084 return; 1085 } 1086 1087 Error *err = NULL; 1088 qmp_eject([drive cStringUsingEncoding: NSASCIIStringEncoding], false, false, &err); 1089 handleAnyDeviceErrors(err); 1090} 1091 1092/* Displays a dialog box asking the user to select an image file to load. 1093 * Uses sender's represented object value to figure out which drive to use. 1094 */ 1095- (void)changeDeviceMedia:(id)sender 1096{ 1097 /* Find the drive name */ 1098 NSString * drive; 1099 drive = [sender representedObject]; 1100 if(drive == nil) { 1101 NSBeep(); 1102 QEMU_Alert(@"Could not find drive!"); 1103 return; 1104 } 1105 1106 /* Display the file open dialog */ 1107 NSOpenPanel * openPanel; 1108 openPanel = [NSOpenPanel openPanel]; 1109 [openPanel setCanChooseFiles: YES]; 1110 [openPanel setAllowsMultipleSelection: NO]; 1111 [openPanel setAllowedFileTypes: supportedImageFileTypes]; 1112 if([openPanel runModal] == NSFileHandlingPanelOKButton) { 1113 NSString * file = [[[openPanel URLs] objectAtIndex: 0] path]; 1114 if(file == nil) { 1115 NSBeep(); 1116 QEMU_Alert(@"Failed to convert URL to file path!"); 1117 return; 1118 } 1119 1120 Error *err = NULL; 1121 qmp_blockdev_change_medium([drive cStringUsingEncoding: 1122 NSASCIIStringEncoding], 1123 [file cStringUsingEncoding: 1124 NSASCIIStringEncoding], 1125 true, "raw", 1126 false, 0, 1127 &err); 1128 handleAnyDeviceErrors(err); 1129 } 1130} 1131 1132/* Verifies if the user really wants to quit */ 1133- (BOOL)verifyQuit 1134{ 1135 NSAlert *alert = [NSAlert new]; 1136 [alert autorelease]; 1137 [alert setMessageText: @"Are you sure you want to quit QEMU?"]; 1138 [alert addButtonWithTitle: @"Cancel"]; 1139 [alert addButtonWithTitle: @"Quit"]; 1140 if([alert runModal] == NSAlertSecondButtonReturn) { 1141 return YES; 1142 } else { 1143 return NO; 1144 } 1145} 1146 1147/* The action method for the About menu item */ 1148- (IBAction) do_about_menu_item: (id) sender 1149{ 1150 [about_window makeKeyAndOrderFront: nil]; 1151} 1152 1153/* Create and display the about dialog */ 1154- (void)make_about_window 1155{ 1156 /* Make the window */ 1157 int x = 0, y = 0, about_width = 400, about_height = 200; 1158 NSRect window_rect = NSMakeRect(x, y, about_width, about_height); 1159 about_window = [[NSWindow alloc] initWithContentRect:window_rect 1160 styleMask:NSTitledWindowMask | NSClosableWindowMask | 1161 NSMiniaturizableWindowMask 1162 backing:NSBackingStoreBuffered 1163 defer:NO]; 1164 [about_window setTitle: @"About"]; 1165 [about_window setReleasedWhenClosed: NO]; 1166 [about_window center]; 1167 NSView *superView = [about_window contentView]; 1168 1169 /* Create the dimensions of the picture */ 1170 int picture_width = 80, picture_height = 80; 1171 x = (about_width - picture_width)/2; 1172 y = about_height - picture_height - 10; 1173 NSRect picture_rect = NSMakeRect(x, y, picture_width, picture_height); 1174 1175 /* Get the path to the QEMU binary */ 1176 NSString *binary_name = [NSString stringWithCString: gArgv[0] 1177 encoding: NSASCIIStringEncoding]; 1178 binary_name = [binary_name lastPathComponent]; 1179 NSString *program_path = [[NSString alloc] initWithFormat: @"%@/%@", 1180 [[NSBundle mainBundle] bundlePath], binary_name]; 1181 1182 /* Make the picture of QEMU */ 1183 NSImageView *picture_view = [[NSImageView alloc] initWithFrame: 1184 picture_rect]; 1185 NSImage *qemu_image = [[NSWorkspace sharedWorkspace] iconForFile: 1186 program_path]; 1187 [picture_view setImage: qemu_image]; 1188 [picture_view setImageScaling: NSImageScaleProportionallyUpOrDown]; 1189 [superView addSubview: picture_view]; 1190 1191 /* Make the name label */ 1192 x = 0; 1193 y = y - 25; 1194 int name_width = about_width, name_height = 20; 1195 NSRect name_rect = NSMakeRect(x, y, name_width, name_height); 1196 NSTextField *name_label = [[NSTextField alloc] initWithFrame: name_rect]; 1197 [name_label setEditable: NO]; 1198 [name_label setBezeled: NO]; 1199 [name_label setDrawsBackground: NO]; 1200 [name_label setAlignment: NSCenterTextAlignment]; 1201 NSString *qemu_name = [[NSString alloc] initWithCString: gArgv[0] 1202 encoding: NSASCIIStringEncoding]; 1203 qemu_name = [qemu_name lastPathComponent]; 1204 [name_label setStringValue: qemu_name]; 1205 [superView addSubview: name_label]; 1206 1207 /* Set the version label's attributes */ 1208 x = 0; 1209 y = 50; 1210 int version_width = about_width, version_height = 20; 1211 NSRect version_rect = NSMakeRect(x, y, version_width, version_height); 1212 NSTextField *version_label = [[NSTextField alloc] initWithFrame: 1213 version_rect]; 1214 [version_label setEditable: NO]; 1215 [version_label setBezeled: NO]; 1216 [version_label setAlignment: NSCenterTextAlignment]; 1217 [version_label setDrawsBackground: NO]; 1218 1219 /* Create the version string*/ 1220 NSString *version_string; 1221 version_string = [[NSString alloc] initWithFormat: 1222 @"QEMU emulator version %s%s", QEMU_VERSION, QEMU_PKGVERSION]; 1223 [version_label setStringValue: version_string]; 1224 [superView addSubview: version_label]; 1225 1226 /* Make copyright label */ 1227 x = 0; 1228 y = 35; 1229 int copyright_width = about_width, copyright_height = 20; 1230 NSRect copyright_rect = NSMakeRect(x, y, copyright_width, copyright_height); 1231 NSTextField *copyright_label = [[NSTextField alloc] initWithFrame: 1232 copyright_rect]; 1233 [copyright_label setEditable: NO]; 1234 [copyright_label setBezeled: NO]; 1235 [copyright_label setDrawsBackground: NO]; 1236 [copyright_label setAlignment: NSCenterTextAlignment]; 1237 [copyright_label setStringValue: [NSString stringWithFormat: @"%s", 1238 QEMU_COPYRIGHT]]; 1239 [superView addSubview: copyright_label]; 1240} 1241 1242@end 1243 1244 1245int main (int argc, const char * argv[]) { 1246 1247 gArgc = argc; 1248 gArgv = (char **)argv; 1249 int i; 1250 1251 /* In case we don't need to display a window, let's not do that */ 1252 for (i = 1; i < argc; i++) { 1253 const char *opt = argv[i]; 1254 1255 if (opt[0] == '-') { 1256 /* Treat --foo the same as -foo. */ 1257 if (opt[1] == '-') { 1258 opt++; 1259 } 1260 if (!strcmp(opt, "-h") || !strcmp(opt, "-help") || 1261 !strcmp(opt, "-vnc") || 1262 !strcmp(opt, "-nographic") || 1263 !strcmp(opt, "-version") || 1264 !strcmp(opt, "-curses") || 1265 !strcmp(opt, "-display") || 1266 !strcmp(opt, "-qtest")) { 1267 return qemu_main(gArgc, gArgv, *_NSGetEnviron()); 1268 } 1269 } 1270 } 1271 1272 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 1273 1274 // Pull this console process up to being a fully-fledged graphical 1275 // app with a menubar and Dock icon 1276 ProcessSerialNumber psn = { 0, kCurrentProcess }; 1277 TransformProcessType(&psn, kProcessTransformToForegroundApplication); 1278 1279 [NSApplication sharedApplication]; 1280 1281 // Add menus 1282 NSMenu *menu; 1283 NSMenuItem *menuItem; 1284 1285 [NSApp setMainMenu:[[NSMenu alloc] init]]; 1286 1287 // Application menu 1288 menu = [[NSMenu alloc] initWithTitle:@""]; 1289 [menu addItemWithTitle:@"About QEMU" action:@selector(do_about_menu_item:) keyEquivalent:@""]; // About QEMU 1290 [menu addItem:[NSMenuItem separatorItem]]; //Separator 1291 [menu addItemWithTitle:@"Hide QEMU" action:@selector(hide:) keyEquivalent:@"h"]; //Hide QEMU 1292 menuItem = (NSMenuItem *)[menu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; // Hide Others 1293 [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)]; 1294 [menu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; // Show All 1295 [menu addItem:[NSMenuItem separatorItem]]; //Separator 1296 [menu addItemWithTitle:@"Quit QEMU" action:@selector(terminate:) keyEquivalent:@"q"]; 1297 menuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" action:nil keyEquivalent:@""]; 1298 [menuItem setSubmenu:menu]; 1299 [[NSApp mainMenu] addItem:menuItem]; 1300 [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; // Workaround (this method is private since 10.4+) 1301 1302 // Machine menu 1303 menu = [[NSMenu alloc] initWithTitle: @"Machine"]; 1304 [menu setAutoenablesItems: NO]; 1305 [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Pause" action: @selector(pauseQEMU:) keyEquivalent: @""] autorelease]]; 1306 menuItem = [[[NSMenuItem alloc] initWithTitle: @"Resume" action: @selector(resumeQEMU:) keyEquivalent: @""] autorelease]; 1307 [menu addItem: menuItem]; 1308 [menuItem setEnabled: NO]; 1309 [menu addItem: [NSMenuItem separatorItem]]; 1310 [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Reset" action: @selector(restartQEMU:) keyEquivalent: @""] autorelease]]; 1311 [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Power Down" action: @selector(powerDownQEMU:) keyEquivalent: @""] autorelease]]; 1312 menuItem = [[[NSMenuItem alloc] initWithTitle: @"Machine" action:nil keyEquivalent:@""] autorelease]; 1313 [menuItem setSubmenu:menu]; 1314 [[NSApp mainMenu] addItem:menuItem]; 1315 1316 // View menu 1317 menu = [[NSMenu alloc] initWithTitle:@"View"]; 1318 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(doToggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen 1319 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Zoom To Fit" action:@selector(zoomToFit:) keyEquivalent:@""] autorelease]]; 1320 menuItem = [[[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""] autorelease]; 1321 [menuItem setSubmenu:menu]; 1322 [[NSApp mainMenu] addItem:menuItem]; 1323 1324 // Window menu 1325 menu = [[NSMenu alloc] initWithTitle:@"Window"]; 1326 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease]]; // Miniaturize 1327 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease]; 1328 [menuItem setSubmenu:menu]; 1329 [[NSApp mainMenu] addItem:menuItem]; 1330 [NSApp setWindowsMenu:menu]; 1331 1332 // Help menu 1333 menu = [[NSMenu alloc] initWithTitle:@"Help"]; 1334 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Documentation" action:@selector(showQEMUDoc:) keyEquivalent:@"?"] autorelease]]; // QEMU Help 1335 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Technology" action:@selector(showQEMUTec:) keyEquivalent:@""] autorelease]]; // QEMU Help 1336 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease]; 1337 [menuItem setSubmenu:menu]; 1338 [[NSApp mainMenu] addItem:menuItem]; 1339 1340 // Create an Application controller 1341 QemuCocoaAppController *appController = [[QemuCocoaAppController alloc] init]; 1342 [NSApp setDelegate:appController]; 1343 1344 // Start the main event loop 1345 [NSApp run]; 1346 1347 [appController release]; 1348 [pool release]; 1349 1350 return 0; 1351} 1352 1353 1354 1355#pragma mark qemu 1356static void cocoa_update(DisplayChangeListener *dcl, 1357 int x, int y, int w, int h) 1358{ 1359 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 1360 1361 COCOA_DEBUG("qemu_cocoa: cocoa_update\n"); 1362 1363 NSRect rect; 1364 if ([cocoaView cdx] == 1.0) { 1365 rect = NSMakeRect(x, [cocoaView gscreen].height - y - h, w, h); 1366 } else { 1367 rect = NSMakeRect( 1368 x * [cocoaView cdx], 1369 ([cocoaView gscreen].height - y - h) * [cocoaView cdy], 1370 w * [cocoaView cdx], 1371 h * [cocoaView cdy]); 1372 } 1373 [cocoaView setNeedsDisplayInRect:rect]; 1374 1375 [pool release]; 1376} 1377 1378static void cocoa_switch(DisplayChangeListener *dcl, 1379 DisplaySurface *surface) 1380{ 1381 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 1382 1383 COCOA_DEBUG("qemu_cocoa: cocoa_switch\n"); 1384 [cocoaView switchSurface:surface]; 1385 [pool release]; 1386} 1387 1388static void cocoa_refresh(DisplayChangeListener *dcl) 1389{ 1390 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 1391 1392 COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n"); 1393 graphic_hw_update(NULL); 1394 1395 if (qemu_input_is_absolute()) { 1396 if (![cocoaView isAbsoluteEnabled]) { 1397 if ([cocoaView isMouseGrabbed]) { 1398 [cocoaView ungrabMouse]; 1399 } 1400 } 1401 [cocoaView setAbsoluteEnabled:YES]; 1402 } 1403 1404 NSDate *distantPast; 1405 NSEvent *event; 1406 distantPast = [NSDate distantPast]; 1407 do { 1408 event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:distantPast 1409 inMode: NSDefaultRunLoopMode dequeue:YES]; 1410 if (event != nil) { 1411 [cocoaView handleEvent:event]; 1412 } 1413 } while(event != nil); 1414 [pool release]; 1415} 1416 1417static void cocoa_cleanup(void) 1418{ 1419 COCOA_DEBUG("qemu_cocoa: cocoa_cleanup\n"); 1420 g_free(dcl); 1421} 1422 1423static const DisplayChangeListenerOps dcl_ops = { 1424 .dpy_name = "cocoa", 1425 .dpy_gfx_update = cocoa_update, 1426 .dpy_gfx_switch = cocoa_switch, 1427 .dpy_refresh = cocoa_refresh, 1428}; 1429 1430/* Returns a name for a given console */ 1431static NSString * getConsoleName(QemuConsole * console) 1432{ 1433 return [NSString stringWithFormat: @"%s", qemu_console_get_label(console)]; 1434} 1435 1436/* Add an entry to the View menu for each console */ 1437static void add_console_menu_entries(void) 1438{ 1439 NSMenu *menu; 1440 NSMenuItem *menuItem; 1441 int index = 0; 1442 1443 menu = [[[NSApp mainMenu] itemWithTitle:@"View"] submenu]; 1444 1445 [menu addItem:[NSMenuItem separatorItem]]; 1446 1447 while (qemu_console_lookup_by_index(index) != NULL) { 1448 menuItem = [[[NSMenuItem alloc] initWithTitle: getConsoleName(qemu_console_lookup_by_index(index)) 1449 action: @selector(displayConsole:) keyEquivalent: @""] autorelease]; 1450 [menuItem setTag: index]; 1451 [menu addItem: menuItem]; 1452 index++; 1453 } 1454} 1455 1456/* Make menu items for all removable devices. 1457 * Each device is given an 'Eject' and 'Change' menu item. 1458 */ 1459static void addRemovableDevicesMenuItems(void) 1460{ 1461 NSMenu *menu; 1462 NSMenuItem *menuItem; 1463 BlockInfoList *currentDevice, *pointerToFree; 1464 NSString *deviceName; 1465 1466 currentDevice = qmp_query_block(NULL); 1467 pointerToFree = currentDevice; 1468 if(currentDevice == NULL) { 1469 NSBeep(); 1470 QEMU_Alert(@"Failed to query for block devices!"); 1471 return; 1472 } 1473 1474 menu = [[[NSApp mainMenu] itemWithTitle:@"Machine"] submenu]; 1475 1476 // Add a separator between related groups of menu items 1477 [menu addItem:[NSMenuItem separatorItem]]; 1478 1479 // Set the attributes to the "Removable Media" menu item 1480 NSString *titleString = @"Removable Media"; 1481 NSMutableAttributedString *attString=[[NSMutableAttributedString alloc] initWithString:titleString]; 1482 NSColor *newColor = [NSColor blackColor]; 1483 NSFontManager *fontManager = [NSFontManager sharedFontManager]; 1484 NSFont *font = [fontManager fontWithFamily:@"Helvetica" 1485 traits:NSBoldFontMask|NSItalicFontMask 1486 weight:0 1487 size:14]; 1488 [attString addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, [titleString length])]; 1489 [attString addAttribute:NSForegroundColorAttributeName value:newColor range:NSMakeRange(0, [titleString length])]; 1490 [attString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInt: 1] range:NSMakeRange(0, [titleString length])]; 1491 1492 // Add the "Removable Media" menu item 1493 menuItem = [NSMenuItem new]; 1494 [menuItem setAttributedTitle: attString]; 1495 [menuItem setEnabled: NO]; 1496 [menu addItem: menuItem]; 1497 1498 /* Loop through all the block devices in the emulator */ 1499 while (currentDevice) { 1500 deviceName = [[NSString stringWithFormat: @"%s", currentDevice->value->device] retain]; 1501 1502 if(currentDevice->value->removable) { 1503 menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Change %s...", currentDevice->value->device] 1504 action: @selector(changeDeviceMedia:) 1505 keyEquivalent: @""]; 1506 [menu addItem: menuItem]; 1507 [menuItem setRepresentedObject: deviceName]; 1508 [menuItem autorelease]; 1509 1510 menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Eject %s", currentDevice->value->device] 1511 action: @selector(ejectDeviceMedia:) 1512 keyEquivalent: @""]; 1513 [menu addItem: menuItem]; 1514 [menuItem setRepresentedObject: deviceName]; 1515 [menuItem autorelease]; 1516 } 1517 currentDevice = currentDevice->next; 1518 } 1519 qapi_free_BlockInfoList(pointerToFree); 1520} 1521 1522void cocoa_display_init(DisplayState *ds, int full_screen) 1523{ 1524 COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n"); 1525 1526 /* if fullscreen mode is to be used */ 1527 if (full_screen == true) { 1528 [NSApp activateIgnoringOtherApps: YES]; 1529 [(QemuCocoaAppController *)[[NSApplication sharedApplication] delegate] toggleFullScreen: nil]; 1530 } 1531 1532 dcl = g_malloc0(sizeof(DisplayChangeListener)); 1533 1534 // register vga output callbacks 1535 dcl->ops = &dcl_ops; 1536 register_displaychangelistener(dcl); 1537 1538 // register cleanup function 1539 atexit(cocoa_cleanup); 1540 1541 /* At this point QEMU has created all the consoles, so we can add View 1542 * menu entries for them. 1543 */ 1544 add_console_menu_entries(); 1545 1546 /* Give all removable devices a menu item. 1547 * Has to be called after QEMU has started to 1548 * find out what removable devices it has. 1549 */ 1550 addRemovableDevicesMenuItems(); 1551} 1552