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 "ui/kbd-state.h" 34#include "sysemu/sysemu.h" 35#include "sysemu/runstate.h" 36#include "sysemu/cpu-throttle.h" 37#include "qapi/error.h" 38#include "qapi/qapi-commands-block.h" 39#include "qapi/qapi-commands-machine.h" 40#include "qapi/qapi-commands-misc.h" 41#include "sysemu/blockdev.h" 42#include "qemu-version.h" 43#include "qemu/cutils.h" 44#include "qemu/main-loop.h" 45#include "qemu/module.h" 46#include <Carbon/Carbon.h> 47#include "hw/core/cpu.h" 48 49#ifndef MAC_OS_X_VERSION_10_13 50#define MAC_OS_X_VERSION_10_13 101300 51#endif 52 53/* 10.14 deprecates NSOnState and NSOffState in favor of 54 * NSControlStateValueOn/Off, which were introduced in 10.13. 55 * Define for older versions 56 */ 57#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_13 58#define NSControlStateValueOn NSOnState 59#define NSControlStateValueOff NSOffState 60#endif 61 62//#define DEBUG 63 64#ifdef DEBUG 65#define COCOA_DEBUG(...) { (void) fprintf (stdout, __VA_ARGS__); } 66#else 67#define COCOA_DEBUG(...) ((void) 0) 68#endif 69 70#define cgrect(nsrect) (*(CGRect *)&(nsrect)) 71 72typedef struct { 73 int width; 74 int height; 75} QEMUScreen; 76 77static void cocoa_update(DisplayChangeListener *dcl, 78 int x, int y, int w, int h); 79 80static void cocoa_switch(DisplayChangeListener *dcl, 81 DisplaySurface *surface); 82 83static void cocoa_refresh(DisplayChangeListener *dcl); 84 85static NSWindow *normalWindow, *about_window; 86static const DisplayChangeListenerOps dcl_ops = { 87 .dpy_name = "cocoa", 88 .dpy_gfx_update = cocoa_update, 89 .dpy_gfx_switch = cocoa_switch, 90 .dpy_refresh = cocoa_refresh, 91}; 92static DisplayChangeListener dcl = { 93 .ops = &dcl_ops, 94}; 95static int last_buttons; 96static int cursor_hide = 1; 97 98static int gArgc; 99static char **gArgv; 100static bool stretch_video; 101static NSTextField *pauseLabel; 102static NSArray * supportedImageFileTypes; 103 104static QemuSemaphore display_init_sem; 105static QemuSemaphore app_started_sem; 106static bool allow_events; 107 108// Utility functions to run specified code block with iothread lock held 109typedef void (^CodeBlock)(void); 110typedef bool (^BoolCodeBlock)(void); 111 112static void with_iothread_lock(CodeBlock block) 113{ 114 bool locked = qemu_mutex_iothread_locked(); 115 if (!locked) { 116 qemu_mutex_lock_iothread(); 117 } 118 block(); 119 if (!locked) { 120 qemu_mutex_unlock_iothread(); 121 } 122} 123 124static bool bool_with_iothread_lock(BoolCodeBlock block) 125{ 126 bool locked = qemu_mutex_iothread_locked(); 127 bool val; 128 129 if (!locked) { 130 qemu_mutex_lock_iothread(); 131 } 132 val = block(); 133 if (!locked) { 134 qemu_mutex_unlock_iothread(); 135 } 136 return val; 137} 138 139// Mac to QKeyCode conversion 140static const int mac_to_qkeycode_map[] = { 141 [kVK_ANSI_A] = Q_KEY_CODE_A, 142 [kVK_ANSI_B] = Q_KEY_CODE_B, 143 [kVK_ANSI_C] = Q_KEY_CODE_C, 144 [kVK_ANSI_D] = Q_KEY_CODE_D, 145 [kVK_ANSI_E] = Q_KEY_CODE_E, 146 [kVK_ANSI_F] = Q_KEY_CODE_F, 147 [kVK_ANSI_G] = Q_KEY_CODE_G, 148 [kVK_ANSI_H] = Q_KEY_CODE_H, 149 [kVK_ANSI_I] = Q_KEY_CODE_I, 150 [kVK_ANSI_J] = Q_KEY_CODE_J, 151 [kVK_ANSI_K] = Q_KEY_CODE_K, 152 [kVK_ANSI_L] = Q_KEY_CODE_L, 153 [kVK_ANSI_M] = Q_KEY_CODE_M, 154 [kVK_ANSI_N] = Q_KEY_CODE_N, 155 [kVK_ANSI_O] = Q_KEY_CODE_O, 156 [kVK_ANSI_P] = Q_KEY_CODE_P, 157 [kVK_ANSI_Q] = Q_KEY_CODE_Q, 158 [kVK_ANSI_R] = Q_KEY_CODE_R, 159 [kVK_ANSI_S] = Q_KEY_CODE_S, 160 [kVK_ANSI_T] = Q_KEY_CODE_T, 161 [kVK_ANSI_U] = Q_KEY_CODE_U, 162 [kVK_ANSI_V] = Q_KEY_CODE_V, 163 [kVK_ANSI_W] = Q_KEY_CODE_W, 164 [kVK_ANSI_X] = Q_KEY_CODE_X, 165 [kVK_ANSI_Y] = Q_KEY_CODE_Y, 166 [kVK_ANSI_Z] = Q_KEY_CODE_Z, 167 168 [kVK_ANSI_0] = Q_KEY_CODE_0, 169 [kVK_ANSI_1] = Q_KEY_CODE_1, 170 [kVK_ANSI_2] = Q_KEY_CODE_2, 171 [kVK_ANSI_3] = Q_KEY_CODE_3, 172 [kVK_ANSI_4] = Q_KEY_CODE_4, 173 [kVK_ANSI_5] = Q_KEY_CODE_5, 174 [kVK_ANSI_6] = Q_KEY_CODE_6, 175 [kVK_ANSI_7] = Q_KEY_CODE_7, 176 [kVK_ANSI_8] = Q_KEY_CODE_8, 177 [kVK_ANSI_9] = Q_KEY_CODE_9, 178 179 [kVK_ANSI_Grave] = Q_KEY_CODE_GRAVE_ACCENT, 180 [kVK_ANSI_Minus] = Q_KEY_CODE_MINUS, 181 [kVK_ANSI_Equal] = Q_KEY_CODE_EQUAL, 182 [kVK_Delete] = Q_KEY_CODE_BACKSPACE, 183 [kVK_CapsLock] = Q_KEY_CODE_CAPS_LOCK, 184 [kVK_Tab] = Q_KEY_CODE_TAB, 185 [kVK_Return] = Q_KEY_CODE_RET, 186 [kVK_ANSI_LeftBracket] = Q_KEY_CODE_BRACKET_LEFT, 187 [kVK_ANSI_RightBracket] = Q_KEY_CODE_BRACKET_RIGHT, 188 [kVK_ANSI_Backslash] = Q_KEY_CODE_BACKSLASH, 189 [kVK_ANSI_Semicolon] = Q_KEY_CODE_SEMICOLON, 190 [kVK_ANSI_Quote] = Q_KEY_CODE_APOSTROPHE, 191 [kVK_ANSI_Comma] = Q_KEY_CODE_COMMA, 192 [kVK_ANSI_Period] = Q_KEY_CODE_DOT, 193 [kVK_ANSI_Slash] = Q_KEY_CODE_SLASH, 194 [kVK_Space] = Q_KEY_CODE_SPC, 195 196 [kVK_ANSI_Keypad0] = Q_KEY_CODE_KP_0, 197 [kVK_ANSI_Keypad1] = Q_KEY_CODE_KP_1, 198 [kVK_ANSI_Keypad2] = Q_KEY_CODE_KP_2, 199 [kVK_ANSI_Keypad3] = Q_KEY_CODE_KP_3, 200 [kVK_ANSI_Keypad4] = Q_KEY_CODE_KP_4, 201 [kVK_ANSI_Keypad5] = Q_KEY_CODE_KP_5, 202 [kVK_ANSI_Keypad6] = Q_KEY_CODE_KP_6, 203 [kVK_ANSI_Keypad7] = Q_KEY_CODE_KP_7, 204 [kVK_ANSI_Keypad8] = Q_KEY_CODE_KP_8, 205 [kVK_ANSI_Keypad9] = Q_KEY_CODE_KP_9, 206 [kVK_ANSI_KeypadDecimal] = Q_KEY_CODE_KP_DECIMAL, 207 [kVK_ANSI_KeypadEnter] = Q_KEY_CODE_KP_ENTER, 208 [kVK_ANSI_KeypadPlus] = Q_KEY_CODE_KP_ADD, 209 [kVK_ANSI_KeypadMinus] = Q_KEY_CODE_KP_SUBTRACT, 210 [kVK_ANSI_KeypadMultiply] = Q_KEY_CODE_KP_MULTIPLY, 211 [kVK_ANSI_KeypadDivide] = Q_KEY_CODE_KP_DIVIDE, 212 [kVK_ANSI_KeypadEquals] = Q_KEY_CODE_KP_EQUALS, 213 [kVK_ANSI_KeypadClear] = Q_KEY_CODE_NUM_LOCK, 214 215 [kVK_UpArrow] = Q_KEY_CODE_UP, 216 [kVK_DownArrow] = Q_KEY_CODE_DOWN, 217 [kVK_LeftArrow] = Q_KEY_CODE_LEFT, 218 [kVK_RightArrow] = Q_KEY_CODE_RIGHT, 219 220 [kVK_Help] = Q_KEY_CODE_INSERT, 221 [kVK_Home] = Q_KEY_CODE_HOME, 222 [kVK_PageUp] = Q_KEY_CODE_PGUP, 223 [kVK_PageDown] = Q_KEY_CODE_PGDN, 224 [kVK_End] = Q_KEY_CODE_END, 225 [kVK_ForwardDelete] = Q_KEY_CODE_DELETE, 226 227 [kVK_Escape] = Q_KEY_CODE_ESC, 228 229 /* The Power key can't be used directly because the operating system uses 230 * it. This key can be emulated by using it in place of another key such as 231 * F1. Don't forget to disable the real key binding. 232 */ 233 /* [kVK_F1] = Q_KEY_CODE_POWER, */ 234 235 [kVK_F1] = Q_KEY_CODE_F1, 236 [kVK_F2] = Q_KEY_CODE_F2, 237 [kVK_F3] = Q_KEY_CODE_F3, 238 [kVK_F4] = Q_KEY_CODE_F4, 239 [kVK_F5] = Q_KEY_CODE_F5, 240 [kVK_F6] = Q_KEY_CODE_F6, 241 [kVK_F7] = Q_KEY_CODE_F7, 242 [kVK_F8] = Q_KEY_CODE_F8, 243 [kVK_F9] = Q_KEY_CODE_F9, 244 [kVK_F10] = Q_KEY_CODE_F10, 245 [kVK_F11] = Q_KEY_CODE_F11, 246 [kVK_F12] = Q_KEY_CODE_F12, 247 [kVK_F13] = Q_KEY_CODE_PRINT, 248 [kVK_F14] = Q_KEY_CODE_SCROLL_LOCK, 249 [kVK_F15] = Q_KEY_CODE_PAUSE, 250 251 // JIS keyboards only 252 [kVK_JIS_Yen] = Q_KEY_CODE_YEN, 253 [kVK_JIS_Underscore] = Q_KEY_CODE_RO, 254 [kVK_JIS_KeypadComma] = Q_KEY_CODE_KP_COMMA, 255 [kVK_JIS_Eisu] = Q_KEY_CODE_MUHENKAN, 256 [kVK_JIS_Kana] = Q_KEY_CODE_HENKAN, 257 258 /* 259 * The eject and volume keys can't be used here because they are handled at 260 * a lower level than what an Application can see. 261 */ 262}; 263 264static int cocoa_keycode_to_qemu(int keycode) 265{ 266 if (ARRAY_SIZE(mac_to_qkeycode_map) <= keycode) { 267 error_report("(cocoa) warning unknown keycode 0x%x", keycode); 268 return 0; 269 } 270 return mac_to_qkeycode_map[keycode]; 271} 272 273/* Displays an alert dialog box with the specified message */ 274static void QEMU_Alert(NSString *message) 275{ 276 NSAlert *alert; 277 alert = [NSAlert new]; 278 [alert setMessageText: message]; 279 [alert runModal]; 280} 281 282/* Handles any errors that happen with a device transaction */ 283static void handleAnyDeviceErrors(Error * err) 284{ 285 if (err) { 286 QEMU_Alert([NSString stringWithCString: error_get_pretty(err) 287 encoding: NSASCIIStringEncoding]); 288 error_free(err); 289 } 290} 291 292/* 293 ------------------------------------------------------ 294 QemuCocoaView 295 ------------------------------------------------------ 296*/ 297@interface QemuCocoaView : NSView 298{ 299 QEMUScreen screen; 300 NSWindow *fullScreenWindow; 301 float cx,cy,cw,ch,cdx,cdy; 302 pixman_image_t *pixman_image; 303 QKbdState *kbd; 304 BOOL isMouseGrabbed; 305 BOOL isFullscreen; 306 BOOL isAbsoluteEnabled; 307} 308- (void) switchSurface:(pixman_image_t *)image; 309- (void) grabMouse; 310- (void) ungrabMouse; 311- (void) toggleFullScreen:(id)sender; 312- (void) handleMonitorInput:(NSEvent *)event; 313- (bool) handleEvent:(NSEvent *)event; 314- (bool) handleEventLocked:(NSEvent *)event; 315- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled; 316/* The state surrounding mouse grabbing is potentially confusing. 317 * isAbsoluteEnabled tracks qemu_input_is_absolute() [ie "is the emulated 318 * pointing device an absolute-position one?"], but is only updated on 319 * next refresh. 320 * isMouseGrabbed tracks whether GUI events are directed to the guest; 321 * it controls whether special keys like Cmd get sent to the guest, 322 * and whether we capture the mouse when in non-absolute mode. 323 */ 324- (BOOL) isMouseGrabbed; 325- (BOOL) isAbsoluteEnabled; 326- (float) cdx; 327- (float) cdy; 328- (QEMUScreen) gscreen; 329- (void) raiseAllKeys; 330@end 331 332QemuCocoaView *cocoaView; 333 334@implementation QemuCocoaView 335- (id)initWithFrame:(NSRect)frameRect 336{ 337 COCOA_DEBUG("QemuCocoaView: initWithFrame\n"); 338 339 self = [super initWithFrame:frameRect]; 340 if (self) { 341 342 screen.width = frameRect.size.width; 343 screen.height = frameRect.size.height; 344 kbd = qkbd_state_init(dcl.con); 345 346 } 347 return self; 348} 349 350- (void) dealloc 351{ 352 COCOA_DEBUG("QemuCocoaView: dealloc\n"); 353 354 if (pixman_image) { 355 pixman_image_unref(pixman_image); 356 } 357 358 qkbd_state_free(kbd); 359 [super dealloc]; 360} 361 362- (BOOL) isOpaque 363{ 364 return YES; 365} 366 367- (BOOL) screenContainsPoint:(NSPoint) p 368{ 369 return (p.x > -1 && p.x < screen.width && p.y > -1 && p.y < screen.height); 370} 371 372/* Get location of event and convert to virtual screen coordinate */ 373- (CGPoint) screenLocationOfEvent:(NSEvent *)ev 374{ 375 NSWindow *eventWindow = [ev window]; 376 // XXX: Use CGRect and -convertRectFromScreen: to support macOS 10.10 377 CGRect r = CGRectZero; 378 r.origin = [ev locationInWindow]; 379 if (!eventWindow) { 380 if (!isFullscreen) { 381 return [[self window] convertRectFromScreen:r].origin; 382 } else { 383 CGPoint locationInSelfWindow = [[self window] convertRectFromScreen:r].origin; 384 CGPoint loc = [self convertPoint:locationInSelfWindow fromView:nil]; 385 if (stretch_video) { 386 loc.x /= cdx; 387 loc.y /= cdy; 388 } 389 return loc; 390 } 391 } else if ([[self window] isEqual:eventWindow]) { 392 if (!isFullscreen) { 393 return r.origin; 394 } else { 395 CGPoint loc = [self convertPoint:r.origin fromView:nil]; 396 if (stretch_video) { 397 loc.x /= cdx; 398 loc.y /= cdy; 399 } 400 return loc; 401 } 402 } else { 403 return [[self window] convertRectFromScreen:[eventWindow convertRectToScreen:r]].origin; 404 } 405} 406 407- (void) hideCursor 408{ 409 if (!cursor_hide) { 410 return; 411 } 412 [NSCursor hide]; 413} 414 415- (void) unhideCursor 416{ 417 if (!cursor_hide) { 418 return; 419 } 420 [NSCursor unhide]; 421} 422 423- (void) drawRect:(NSRect) rect 424{ 425 COCOA_DEBUG("QemuCocoaView: drawRect\n"); 426 427 // get CoreGraphic context 428 CGContextRef viewContextRef = [[NSGraphicsContext currentContext] CGContext]; 429 430 CGContextSetInterpolationQuality (viewContextRef, kCGInterpolationNone); 431 CGContextSetShouldAntialias (viewContextRef, NO); 432 433 // draw screen bitmap directly to Core Graphics context 434 if (!pixman_image) { 435 // Draw request before any guest device has set up a framebuffer: 436 // just draw an opaque black rectangle 437 CGContextSetRGBFillColor(viewContextRef, 0, 0, 0, 1.0); 438 CGContextFillRect(viewContextRef, NSRectToCGRect(rect)); 439 } else { 440 int w = pixman_image_get_width(pixman_image); 441 int h = pixman_image_get_height(pixman_image); 442 int bitsPerPixel = PIXMAN_FORMAT_BPP(pixman_image_get_format(pixman_image)); 443 int stride = pixman_image_get_stride(pixman_image); 444 CGDataProviderRef dataProviderRef = CGDataProviderCreateWithData( 445 NULL, 446 pixman_image_get_data(pixman_image), 447 stride * h, 448 NULL 449 ); 450 CGImageRef imageRef = CGImageCreate( 451 w, //width 452 h, //height 453 DIV_ROUND_UP(bitsPerPixel, 8) * 2, //bitsPerComponent 454 bitsPerPixel, //bitsPerPixel 455 stride, //bytesPerRow 456 CGColorSpaceCreateWithName(kCGColorSpaceSRGB), //colorspace 457 kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, //bitmapInfo 458 dataProviderRef, //provider 459 NULL, //decode 460 0, //interpolate 461 kCGRenderingIntentDefault //intent 462 ); 463 // selective drawing code (draws only dirty rectangles) (OS X >= 10.4) 464 const NSRect *rectList; 465 NSInteger rectCount; 466 int i; 467 CGImageRef clipImageRef; 468 CGRect clipRect; 469 470 [self getRectsBeingDrawn:&rectList count:&rectCount]; 471 for (i = 0; i < rectCount; i++) { 472 clipRect.origin.x = rectList[i].origin.x / cdx; 473 clipRect.origin.y = (float)h - (rectList[i].origin.y + rectList[i].size.height) / cdy; 474 clipRect.size.width = rectList[i].size.width / cdx; 475 clipRect.size.height = rectList[i].size.height / cdy; 476 clipImageRef = CGImageCreateWithImageInRect( 477 imageRef, 478 clipRect 479 ); 480 CGContextDrawImage (viewContextRef, cgrect(rectList[i]), clipImageRef); 481 CGImageRelease (clipImageRef); 482 } 483 CGImageRelease (imageRef); 484 CGDataProviderRelease(dataProviderRef); 485 } 486} 487 488- (void) setContentDimensions 489{ 490 COCOA_DEBUG("QemuCocoaView: setContentDimensions\n"); 491 492 if (isFullscreen) { 493 cdx = [[NSScreen mainScreen] frame].size.width / (float)screen.width; 494 cdy = [[NSScreen mainScreen] frame].size.height / (float)screen.height; 495 496 /* stretches video, but keeps same aspect ratio */ 497 if (stretch_video == true) { 498 /* use smallest stretch value - prevents clipping on sides */ 499 if (MIN(cdx, cdy) == cdx) { 500 cdy = cdx; 501 } else { 502 cdx = cdy; 503 } 504 } else { /* No stretching */ 505 cdx = cdy = 1; 506 } 507 cw = screen.width * cdx; 508 ch = screen.height * cdy; 509 cx = ([[NSScreen mainScreen] frame].size.width - cw) / 2.0; 510 cy = ([[NSScreen mainScreen] frame].size.height - ch) / 2.0; 511 } else { 512 cx = 0; 513 cy = 0; 514 cw = screen.width; 515 ch = screen.height; 516 cdx = 1.0; 517 cdy = 1.0; 518 } 519} 520 521- (void) switchSurface:(pixman_image_t *)image 522{ 523 COCOA_DEBUG("QemuCocoaView: switchSurface\n"); 524 525 int w = pixman_image_get_width(image); 526 int h = pixman_image_get_height(image); 527 /* cdx == 0 means this is our very first surface, in which case we need 528 * to recalculate the content dimensions even if it happens to be the size 529 * of the initial empty window. 530 */ 531 bool isResize = (w != screen.width || h != screen.height || cdx == 0.0); 532 533 int oldh = screen.height; 534 if (isResize) { 535 // Resize before we trigger the redraw, or we'll redraw at the wrong size 536 COCOA_DEBUG("switchSurface: new size %d x %d\n", w, h); 537 screen.width = w; 538 screen.height = h; 539 [self setContentDimensions]; 540 [self setFrame:NSMakeRect(cx, cy, cw, ch)]; 541 } 542 543 // update screenBuffer 544 if (pixman_image) { 545 pixman_image_unref(pixman_image); 546 } 547 548 pixman_image = image; 549 550 // update windows 551 if (isFullscreen) { 552 [[fullScreenWindow contentView] setFrame:[[NSScreen mainScreen] frame]]; 553 [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + oldh, w, h + [normalWindow frame].size.height - oldh) display:NO animate:NO]; 554 } else { 555 if (qemu_name) 556 [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]]; 557 [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + oldh, w, h + [normalWindow frame].size.height - oldh) display:YES animate:NO]; 558 } 559 560 if (isResize) { 561 [normalWindow center]; 562 } 563} 564 565- (void) toggleFullScreen:(id)sender 566{ 567 COCOA_DEBUG("QemuCocoaView: toggleFullScreen\n"); 568 569 if (isFullscreen) { // switch from fullscreen to desktop 570 isFullscreen = FALSE; 571 [self ungrabMouse]; 572 [self setContentDimensions]; 573 [fullScreenWindow close]; 574 [normalWindow setContentView: self]; 575 [normalWindow makeKeyAndOrderFront: self]; 576 [NSMenu setMenuBarVisible:YES]; 577 } else { // switch from desktop to fullscreen 578 isFullscreen = TRUE; 579 [normalWindow orderOut: nil]; /* Hide the window */ 580 [self grabMouse]; 581 [self setContentDimensions]; 582 [NSMenu setMenuBarVisible:NO]; 583 fullScreenWindow = [[NSWindow alloc] initWithContentRect:[[NSScreen mainScreen] frame] 584 styleMask:NSWindowStyleMaskBorderless 585 backing:NSBackingStoreBuffered 586 defer:NO]; 587 [fullScreenWindow setAcceptsMouseMovedEvents: YES]; 588 [fullScreenWindow setHasShadow:NO]; 589 [fullScreenWindow setBackgroundColor: [NSColor blackColor]]; 590 [self setFrame:NSMakeRect(cx, cy, cw, ch)]; 591 [[fullScreenWindow contentView] addSubview: self]; 592 [fullScreenWindow makeKeyAndOrderFront:self]; 593 } 594} 595 596- (void) toggleKey: (int)keycode { 597 qkbd_state_key_event(kbd, keycode, !qkbd_state_key_get(kbd, keycode)); 598} 599 600// Does the work of sending input to the monitor 601- (void) handleMonitorInput:(NSEvent *)event 602{ 603 int keysym = 0; 604 int control_key = 0; 605 606 // if the control key is down 607 if ([event modifierFlags] & NSEventModifierFlagControl) { 608 control_key = 1; 609 } 610 611 /* translates Macintosh keycodes to QEMU's keysym */ 612 613 int without_control_translation[] = { 614 [0 ... 0xff] = 0, // invalid key 615 616 [kVK_UpArrow] = QEMU_KEY_UP, 617 [kVK_DownArrow] = QEMU_KEY_DOWN, 618 [kVK_RightArrow] = QEMU_KEY_RIGHT, 619 [kVK_LeftArrow] = QEMU_KEY_LEFT, 620 [kVK_Home] = QEMU_KEY_HOME, 621 [kVK_End] = QEMU_KEY_END, 622 [kVK_PageUp] = QEMU_KEY_PAGEUP, 623 [kVK_PageDown] = QEMU_KEY_PAGEDOWN, 624 [kVK_ForwardDelete] = QEMU_KEY_DELETE, 625 [kVK_Delete] = QEMU_KEY_BACKSPACE, 626 }; 627 628 int with_control_translation[] = { 629 [0 ... 0xff] = 0, // invalid key 630 631 [kVK_UpArrow] = QEMU_KEY_CTRL_UP, 632 [kVK_DownArrow] = QEMU_KEY_CTRL_DOWN, 633 [kVK_RightArrow] = QEMU_KEY_CTRL_RIGHT, 634 [kVK_LeftArrow] = QEMU_KEY_CTRL_LEFT, 635 [kVK_Home] = QEMU_KEY_CTRL_HOME, 636 [kVK_End] = QEMU_KEY_CTRL_END, 637 [kVK_PageUp] = QEMU_KEY_CTRL_PAGEUP, 638 [kVK_PageDown] = QEMU_KEY_CTRL_PAGEDOWN, 639 }; 640 641 if (control_key != 0) { /* If the control key is being used */ 642 if ([event keyCode] < ARRAY_SIZE(with_control_translation)) { 643 keysym = with_control_translation[[event keyCode]]; 644 } 645 } else { 646 if ([event keyCode] < ARRAY_SIZE(without_control_translation)) { 647 keysym = without_control_translation[[event keyCode]]; 648 } 649 } 650 651 // if not a key that needs translating 652 if (keysym == 0) { 653 NSString *ks = [event characters]; 654 if ([ks length] > 0) { 655 keysym = [ks characterAtIndex:0]; 656 } 657 } 658 659 if (keysym) { 660 kbd_put_keysym(keysym); 661 } 662} 663 664- (bool) handleEvent:(NSEvent *)event 665{ 666 if(!allow_events) { 667 /* 668 * Just let OSX have all events that arrive before 669 * applicationDidFinishLaunching. 670 * This avoids a deadlock on the iothread lock, which cocoa_display_init() 671 * will not drop until after the app_started_sem is posted. (In theory 672 * there should not be any such events, but OSX Catalina now emits some.) 673 */ 674 return false; 675 } 676 return bool_with_iothread_lock(^{ 677 return [self handleEventLocked:event]; 678 }); 679} 680 681- (bool) handleEventLocked:(NSEvent *)event 682{ 683 /* Return true if we handled the event, false if it should be given to OSX */ 684 COCOA_DEBUG("QemuCocoaView: handleEvent\n"); 685 int buttons = 0; 686 int keycode = 0; 687 bool mouse_event = false; 688 static bool switched_to_fullscreen = false; 689 // Location of event in virtual screen coordinates 690 NSPoint p = [self screenLocationOfEvent:event]; 691 NSUInteger modifiers = [event modifierFlags]; 692 693 /* 694 * Check -[NSEvent modifierFlags] here. 695 * 696 * There is a NSEventType for an event notifying the change of 697 * -[NSEvent modifierFlags], NSEventTypeFlagsChanged but these operations 698 * are performed for any events because a modifier state may change while 699 * the application is inactive (i.e. no events fire) and we don't want to 700 * wait for another modifier state change to detect such a change. 701 * 702 * NSEventModifierFlagCapsLock requires a special treatment. The other flags 703 * are handled in similar manners. 704 * 705 * NSEventModifierFlagCapsLock 706 * --------------------------- 707 * 708 * If CapsLock state is changed, "up" and "down" events will be fired in 709 * sequence, effectively updates CapsLock state on the guest. 710 * 711 * The other flags 712 * --------------- 713 * 714 * If a flag is not set, fire "up" events for all keys which correspond to 715 * the flag. Note that "down" events are not fired here because the flags 716 * checked here do not tell what exact keys are down. 717 * 718 * If one of the keys corresponding to a flag is down, we rely on 719 * -[NSEvent keyCode] of an event whose -[NSEvent type] is 720 * NSEventTypeFlagsChanged to know the exact key which is down, which has 721 * the following two downsides: 722 * - It does not work when the application is inactive as described above. 723 * - It malfactions *after* the modifier state is changed while the 724 * application is inactive. It is because -[NSEvent keyCode] does not tell 725 * if the key is up or down, and requires to infer the current state from 726 * the previous state. It is still possible to fix such a malfanction by 727 * completely leaving your hands from the keyboard, which hopefully makes 728 * this implementation usable enough. 729 */ 730 if (!!(modifiers & NSEventModifierFlagCapsLock) != 731 qkbd_state_modifier_get(kbd, QKBD_MOD_CAPSLOCK)) { 732 qkbd_state_key_event(kbd, Q_KEY_CODE_CAPS_LOCK, true); 733 qkbd_state_key_event(kbd, Q_KEY_CODE_CAPS_LOCK, false); 734 } 735 736 if (!(modifiers & NSEventModifierFlagShift)) { 737 qkbd_state_key_event(kbd, Q_KEY_CODE_SHIFT, false); 738 qkbd_state_key_event(kbd, Q_KEY_CODE_SHIFT_R, false); 739 } 740 if (!(modifiers & NSEventModifierFlagControl)) { 741 qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL, false); 742 qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL_R, false); 743 } 744 if (!(modifiers & NSEventModifierFlagOption)) { 745 qkbd_state_key_event(kbd, Q_KEY_CODE_ALT, false); 746 qkbd_state_key_event(kbd, Q_KEY_CODE_ALT_R, false); 747 } 748 if (!(modifiers & NSEventModifierFlagCommand)) { 749 qkbd_state_key_event(kbd, Q_KEY_CODE_META_L, false); 750 qkbd_state_key_event(kbd, Q_KEY_CODE_META_R, false); 751 } 752 753 switch ([event type]) { 754 case NSEventTypeFlagsChanged: 755 switch ([event keyCode]) { 756 case kVK_Shift: 757 if (!!(modifiers & NSEventModifierFlagShift)) { 758 [self toggleKey:Q_KEY_CODE_SHIFT]; 759 } 760 break; 761 762 case kVK_RightShift: 763 if (!!(modifiers & NSEventModifierFlagShift)) { 764 [self toggleKey:Q_KEY_CODE_SHIFT_R]; 765 } 766 break; 767 768 case kVK_Control: 769 if (!!(modifiers & NSEventModifierFlagControl)) { 770 [self toggleKey:Q_KEY_CODE_CTRL]; 771 } 772 break; 773 774 case kVK_RightControl: 775 if (!!(modifiers & NSEventModifierFlagControl)) { 776 [self toggleKey:Q_KEY_CODE_CTRL_R]; 777 } 778 break; 779 780 case kVK_Option: 781 if (!!(modifiers & NSEventModifierFlagOption)) { 782 [self toggleKey:Q_KEY_CODE_ALT]; 783 } 784 break; 785 786 case kVK_RightOption: 787 if (!!(modifiers & NSEventModifierFlagOption)) { 788 [self toggleKey:Q_KEY_CODE_ALT_R]; 789 } 790 break; 791 792 /* Don't pass command key changes to guest unless mouse is grabbed */ 793 case kVK_Command: 794 if (isMouseGrabbed && 795 !!(modifiers & NSEventModifierFlagCommand)) { 796 [self toggleKey:Q_KEY_CODE_META_L]; 797 } 798 break; 799 800 case kVK_RightCommand: 801 if (isMouseGrabbed && 802 !!(modifiers & NSEventModifierFlagCommand)) { 803 [self toggleKey:Q_KEY_CODE_META_R]; 804 } 805 break; 806 } 807 break; 808 case NSEventTypeKeyDown: 809 keycode = cocoa_keycode_to_qemu([event keyCode]); 810 811 // forward command key combos to the host UI unless the mouse is grabbed 812 if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) { 813 /* 814 * Prevent the command key from being stuck down in the guest 815 * when using Command-F to switch to full screen mode. 816 */ 817 if (keycode == Q_KEY_CODE_F) { 818 switched_to_fullscreen = true; 819 } 820 return false; 821 } 822 823 // default 824 825 // handle control + alt Key Combos (ctrl+alt+[1..9,g] is reserved for QEMU) 826 if (([event modifierFlags] & NSEventModifierFlagControl) && ([event modifierFlags] & NSEventModifierFlagOption)) { 827 NSString *keychar = [event charactersIgnoringModifiers]; 828 if ([keychar length] == 1) { 829 char key = [keychar characterAtIndex:0]; 830 switch (key) { 831 832 // enable graphic console 833 case '1' ... '9': 834 console_select(key - '0' - 1); /* ascii math */ 835 return true; 836 837 // release the mouse grab 838 case 'g': 839 [self ungrabMouse]; 840 return true; 841 } 842 } 843 } 844 845 if (qemu_console_is_graphic(NULL)) { 846 qkbd_state_key_event(kbd, keycode, true); 847 } else { 848 [self handleMonitorInput: event]; 849 } 850 break; 851 case NSEventTypeKeyUp: 852 keycode = cocoa_keycode_to_qemu([event keyCode]); 853 854 // don't pass the guest a spurious key-up if we treated this 855 // command-key combo as a host UI action 856 if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) { 857 return true; 858 } 859 860 if (qemu_console_is_graphic(NULL)) { 861 qkbd_state_key_event(kbd, keycode, false); 862 } 863 break; 864 case NSEventTypeMouseMoved: 865 if (isAbsoluteEnabled) { 866 // Cursor re-entered into a window might generate events bound to screen coordinates 867 // and `nil` window property, and in full screen mode, current window might not be 868 // key window, where event location alone should suffice. 869 if (![self screenContainsPoint:p] || !([[self window] isKeyWindow] || isFullscreen)) { 870 if (isMouseGrabbed) { 871 [self ungrabMouse]; 872 } 873 } else { 874 if (!isMouseGrabbed) { 875 [self grabMouse]; 876 } 877 } 878 } 879 mouse_event = true; 880 break; 881 case NSEventTypeLeftMouseDown: 882 buttons |= MOUSE_EVENT_LBUTTON; 883 mouse_event = true; 884 break; 885 case NSEventTypeRightMouseDown: 886 buttons |= MOUSE_EVENT_RBUTTON; 887 mouse_event = true; 888 break; 889 case NSEventTypeOtherMouseDown: 890 buttons |= MOUSE_EVENT_MBUTTON; 891 mouse_event = true; 892 break; 893 case NSEventTypeLeftMouseDragged: 894 buttons |= MOUSE_EVENT_LBUTTON; 895 mouse_event = true; 896 break; 897 case NSEventTypeRightMouseDragged: 898 buttons |= MOUSE_EVENT_RBUTTON; 899 mouse_event = true; 900 break; 901 case NSEventTypeOtherMouseDragged: 902 buttons |= MOUSE_EVENT_MBUTTON; 903 mouse_event = true; 904 break; 905 case NSEventTypeLeftMouseUp: 906 mouse_event = true; 907 if (!isMouseGrabbed && [self screenContainsPoint:p]) { 908 /* 909 * In fullscreen mode, the window of cocoaView may not be the 910 * key window, therefore the position relative to the virtual 911 * screen alone will be sufficient. 912 */ 913 if(isFullscreen || [[self window] isKeyWindow]) { 914 [self grabMouse]; 915 } 916 } 917 break; 918 case NSEventTypeRightMouseUp: 919 mouse_event = true; 920 break; 921 case NSEventTypeOtherMouseUp: 922 mouse_event = true; 923 break; 924 case NSEventTypeScrollWheel: 925 /* 926 * Send wheel events to the guest regardless of window focus. 927 * This is in-line with standard Mac OS X UI behaviour. 928 */ 929 930 /* 931 * When deltaY is zero, it means that this scrolling event was 932 * either horizontal, or so fine that it only appears in 933 * scrollingDeltaY. So we drop the event. 934 */ 935 if ([event deltaY] != 0) { 936 /* Determine if this is a scroll up or scroll down event */ 937 buttons = ([event deltaY] > 0) ? 938 INPUT_BUTTON_WHEEL_UP : INPUT_BUTTON_WHEEL_DOWN; 939 qemu_input_queue_btn(dcl.con, buttons, true); 940 qemu_input_event_sync(); 941 qemu_input_queue_btn(dcl.con, buttons, false); 942 qemu_input_event_sync(); 943 } 944 /* 945 * Since deltaY also reports scroll wheel events we prevent mouse 946 * movement code from executing. 947 */ 948 mouse_event = false; 949 break; 950 default: 951 return false; 952 } 953 954 if (mouse_event) { 955 /* Don't send button events to the guest unless we've got a 956 * mouse grab or window focus. If we have neither then this event 957 * is the user clicking on the background window to activate and 958 * bring us to the front, which will be done by the sendEvent 959 * call below. We definitely don't want to pass that click through 960 * to the guest. 961 */ 962 if ((isMouseGrabbed || [[self window] isKeyWindow]) && 963 (last_buttons != buttons)) { 964 static uint32_t bmap[INPUT_BUTTON__MAX] = { 965 [INPUT_BUTTON_LEFT] = MOUSE_EVENT_LBUTTON, 966 [INPUT_BUTTON_MIDDLE] = MOUSE_EVENT_MBUTTON, 967 [INPUT_BUTTON_RIGHT] = MOUSE_EVENT_RBUTTON 968 }; 969 qemu_input_update_buttons(dcl.con, bmap, last_buttons, buttons); 970 last_buttons = buttons; 971 } 972 if (isMouseGrabbed) { 973 if (isAbsoluteEnabled) { 974 /* Note that the origin for Cocoa mouse coords is bottom left, not top left. 975 * The check on screenContainsPoint is to avoid sending out of range values for 976 * clicks in the titlebar. 977 */ 978 if ([self screenContainsPoint:p]) { 979 qemu_input_queue_abs(dcl.con, INPUT_AXIS_X, p.x, 0, screen.width); 980 qemu_input_queue_abs(dcl.con, INPUT_AXIS_Y, screen.height - p.y, 0, screen.height); 981 } 982 } else { 983 qemu_input_queue_rel(dcl.con, INPUT_AXIS_X, (int)[event deltaX]); 984 qemu_input_queue_rel(dcl.con, INPUT_AXIS_Y, (int)[event deltaY]); 985 } 986 } else { 987 return false; 988 } 989 qemu_input_event_sync(); 990 } 991 return true; 992} 993 994- (void) grabMouse 995{ 996 COCOA_DEBUG("QemuCocoaView: grabMouse\n"); 997 998 if (!isFullscreen) { 999 if (qemu_name) 1000 [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s - (Press ctrl + alt + g to release Mouse)", qemu_name]]; 1001 else 1002 [normalWindow setTitle:@"QEMU - (Press ctrl + alt + g to release Mouse)"]; 1003 } 1004 [self hideCursor]; 1005 CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled); 1006 isMouseGrabbed = TRUE; // while isMouseGrabbed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:] 1007} 1008 1009- (void) ungrabMouse 1010{ 1011 COCOA_DEBUG("QemuCocoaView: ungrabMouse\n"); 1012 1013 if (!isFullscreen) { 1014 if (qemu_name) 1015 [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]]; 1016 else 1017 [normalWindow setTitle:@"QEMU"]; 1018 } 1019 [self unhideCursor]; 1020 CGAssociateMouseAndMouseCursorPosition(TRUE); 1021 isMouseGrabbed = FALSE; 1022} 1023 1024- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled { 1025 isAbsoluteEnabled = tIsAbsoluteEnabled; 1026 if (isMouseGrabbed) { 1027 CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled); 1028 } 1029} 1030- (BOOL) isMouseGrabbed {return isMouseGrabbed;} 1031- (BOOL) isAbsoluteEnabled {return isAbsoluteEnabled;} 1032- (float) cdx {return cdx;} 1033- (float) cdy {return cdy;} 1034- (QEMUScreen) gscreen {return screen;} 1035 1036/* 1037 * Makes the target think all down keys are being released. 1038 * This prevents a stuck key problem, since we will not see 1039 * key up events for those keys after we have lost focus. 1040 */ 1041- (void) raiseAllKeys 1042{ 1043 with_iothread_lock(^{ 1044 qkbd_state_lift_all_keys(kbd); 1045 }); 1046} 1047@end 1048 1049 1050 1051/* 1052 ------------------------------------------------------ 1053 QemuCocoaAppController 1054 ------------------------------------------------------ 1055*/ 1056@interface QemuCocoaAppController : NSObject 1057 <NSWindowDelegate, NSApplicationDelegate> 1058{ 1059} 1060- (void)doToggleFullScreen:(id)sender; 1061- (void)toggleFullScreen:(id)sender; 1062- (void)showQEMUDoc:(id)sender; 1063- (void)zoomToFit:(id) sender; 1064- (void)displayConsole:(id)sender; 1065- (void)pauseQEMU:(id)sender; 1066- (void)resumeQEMU:(id)sender; 1067- (void)displayPause; 1068- (void)removePause; 1069- (void)restartQEMU:(id)sender; 1070- (void)powerDownQEMU:(id)sender; 1071- (void)ejectDeviceMedia:(id)sender; 1072- (void)changeDeviceMedia:(id)sender; 1073- (BOOL)verifyQuit; 1074- (void)openDocumentation:(NSString *)filename; 1075- (IBAction) do_about_menu_item: (id) sender; 1076- (void)make_about_window; 1077- (void)adjustSpeed:(id)sender; 1078@end 1079 1080@implementation QemuCocoaAppController 1081- (id) init 1082{ 1083 COCOA_DEBUG("QemuCocoaAppController: init\n"); 1084 1085 self = [super init]; 1086 if (self) { 1087 1088 // create a view and add it to the window 1089 cocoaView = [[QemuCocoaView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 640.0, 480.0)]; 1090 if(!cocoaView) { 1091 error_report("(cocoa) can't create a view"); 1092 exit(1); 1093 } 1094 1095 // create a window 1096 normalWindow = [[NSWindow alloc] initWithContentRect:[cocoaView frame] 1097 styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskClosable 1098 backing:NSBackingStoreBuffered defer:NO]; 1099 if(!normalWindow) { 1100 error_report("(cocoa) can't create window"); 1101 exit(1); 1102 } 1103 [normalWindow setAcceptsMouseMovedEvents:YES]; 1104 [normalWindow setTitle:@"QEMU"]; 1105 [normalWindow setContentView:cocoaView]; 1106 [normalWindow makeKeyAndOrderFront:self]; 1107 [normalWindow center]; 1108 [normalWindow setDelegate: self]; 1109 stretch_video = false; 1110 1111 /* Used for displaying pause on the screen */ 1112 pauseLabel = [NSTextField new]; 1113 [pauseLabel setBezeled:YES]; 1114 [pauseLabel setDrawsBackground:YES]; 1115 [pauseLabel setBackgroundColor: [NSColor whiteColor]]; 1116 [pauseLabel setEditable:NO]; 1117 [pauseLabel setSelectable:NO]; 1118 [pauseLabel setStringValue: @"Paused"]; 1119 [pauseLabel setFont: [NSFont fontWithName: @"Helvetica" size: 90]]; 1120 [pauseLabel setTextColor: [NSColor blackColor]]; 1121 [pauseLabel sizeToFit]; 1122 1123 // set the supported image file types that can be opened 1124 supportedImageFileTypes = [NSArray arrayWithObjects: @"img", @"iso", @"dmg", 1125 @"qcow", @"qcow2", @"cloop", @"vmdk", @"cdr", 1126 @"toast", nil]; 1127 [self make_about_window]; 1128 } 1129 return self; 1130} 1131 1132- (void) dealloc 1133{ 1134 COCOA_DEBUG("QemuCocoaAppController: dealloc\n"); 1135 1136 if (cocoaView) 1137 [cocoaView release]; 1138 [super dealloc]; 1139} 1140 1141- (void)applicationDidFinishLaunching: (NSNotification *) note 1142{ 1143 COCOA_DEBUG("QemuCocoaAppController: applicationDidFinishLaunching\n"); 1144 allow_events = true; 1145 /* Tell cocoa_display_init to proceed */ 1146 qemu_sem_post(&app_started_sem); 1147} 1148 1149- (void)applicationWillTerminate:(NSNotification *)aNotification 1150{ 1151 COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n"); 1152 1153 qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI); 1154 1155 /* 1156 * Sleep here, because returning will cause OSX to kill us 1157 * immediately; the QEMU main loop will handle the shutdown 1158 * request and terminate the process. 1159 */ 1160 [NSThread sleepForTimeInterval:INFINITY]; 1161} 1162 1163- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication 1164{ 1165 return YES; 1166} 1167 1168- (NSApplicationTerminateReply)applicationShouldTerminate: 1169 (NSApplication *)sender 1170{ 1171 COCOA_DEBUG("QemuCocoaAppController: applicationShouldTerminate\n"); 1172 return [self verifyQuit]; 1173} 1174 1175/* Called when the user clicks on a window's close button */ 1176- (BOOL)windowShouldClose:(id)sender 1177{ 1178 COCOA_DEBUG("QemuCocoaAppController: windowShouldClose\n"); 1179 [NSApp terminate: sender]; 1180 /* If the user allows the application to quit then the call to 1181 * NSApp terminate will never return. If we get here then the user 1182 * cancelled the quit, so we should return NO to not permit the 1183 * closing of this window. 1184 */ 1185 return NO; 1186} 1187 1188/* Called when QEMU goes into the background */ 1189- (void) applicationWillResignActive: (NSNotification *)aNotification 1190{ 1191 COCOA_DEBUG("QemuCocoaAppController: applicationWillResignActive\n"); 1192 [cocoaView raiseAllKeys]; 1193} 1194 1195/* We abstract the method called by the Enter Fullscreen menu item 1196 * because Mac OS 10.7 and higher disables it. This is because of the 1197 * menu item's old selector's name toggleFullScreen: 1198 */ 1199- (void) doToggleFullScreen:(id)sender 1200{ 1201 [self toggleFullScreen:(id)sender]; 1202} 1203 1204- (void)toggleFullScreen:(id)sender 1205{ 1206 COCOA_DEBUG("QemuCocoaAppController: toggleFullScreen\n"); 1207 1208 [cocoaView toggleFullScreen:sender]; 1209} 1210 1211/* Tries to find then open the specified filename */ 1212- (void) openDocumentation: (NSString *) filename 1213{ 1214 /* Where to look for local files */ 1215 NSString *path_array[] = {@"../share/doc/qemu/", @"../doc/qemu/", @"docs/"}; 1216 NSString *full_file_path; 1217 NSURL *full_file_url; 1218 1219 /* iterate thru the possible paths until the file is found */ 1220 int index; 1221 for (index = 0; index < ARRAY_SIZE(path_array); index++) { 1222 full_file_path = [[NSBundle mainBundle] executablePath]; 1223 full_file_path = [full_file_path stringByDeletingLastPathComponent]; 1224 full_file_path = [NSString stringWithFormat: @"%@/%@%@", full_file_path, 1225 path_array[index], filename]; 1226 full_file_url = [NSURL fileURLWithPath: full_file_path 1227 isDirectory: false]; 1228 if ([[NSWorkspace sharedWorkspace] openURL: full_file_url] == YES) { 1229 return; 1230 } 1231 } 1232 1233 /* If none of the paths opened a file */ 1234 NSBeep(); 1235 QEMU_Alert(@"Failed to open file"); 1236} 1237 1238- (void)showQEMUDoc:(id)sender 1239{ 1240 COCOA_DEBUG("QemuCocoaAppController: showQEMUDoc\n"); 1241 1242 [self openDocumentation: @"index.html"]; 1243} 1244 1245/* Stretches video to fit host monitor size */ 1246- (void)zoomToFit:(id) sender 1247{ 1248 stretch_video = !stretch_video; 1249 if (stretch_video == true) { 1250 [sender setState: NSControlStateValueOn]; 1251 } else { 1252 [sender setState: NSControlStateValueOff]; 1253 } 1254} 1255 1256/* Displays the console on the screen */ 1257- (void)displayConsole:(id)sender 1258{ 1259 console_select([sender tag]); 1260} 1261 1262/* Pause the guest */ 1263- (void)pauseQEMU:(id)sender 1264{ 1265 with_iothread_lock(^{ 1266 qmp_stop(NULL); 1267 }); 1268 [sender setEnabled: NO]; 1269 [[[sender menu] itemWithTitle: @"Resume"] setEnabled: YES]; 1270 [self displayPause]; 1271} 1272 1273/* Resume running the guest operating system */ 1274- (void)resumeQEMU:(id) sender 1275{ 1276 with_iothread_lock(^{ 1277 qmp_cont(NULL); 1278 }); 1279 [sender setEnabled: NO]; 1280 [[[sender menu] itemWithTitle: @"Pause"] setEnabled: YES]; 1281 [self removePause]; 1282} 1283 1284/* Displays the word pause on the screen */ 1285- (void)displayPause 1286{ 1287 /* Coordinates have to be calculated each time because the window can change its size */ 1288 int xCoord, yCoord, width, height; 1289 xCoord = ([normalWindow frame].size.width - [pauseLabel frame].size.width)/2; 1290 yCoord = [normalWindow frame].size.height - [pauseLabel frame].size.height - ([pauseLabel frame].size.height * .5); 1291 width = [pauseLabel frame].size.width; 1292 height = [pauseLabel frame].size.height; 1293 [pauseLabel setFrame: NSMakeRect(xCoord, yCoord, width, height)]; 1294 [cocoaView addSubview: pauseLabel]; 1295} 1296 1297/* Removes the word pause from the screen */ 1298- (void)removePause 1299{ 1300 [pauseLabel removeFromSuperview]; 1301} 1302 1303/* Restarts QEMU */ 1304- (void)restartQEMU:(id)sender 1305{ 1306 with_iothread_lock(^{ 1307 qmp_system_reset(NULL); 1308 }); 1309} 1310 1311/* Powers down QEMU */ 1312- (void)powerDownQEMU:(id)sender 1313{ 1314 with_iothread_lock(^{ 1315 qmp_system_powerdown(NULL); 1316 }); 1317} 1318 1319/* Ejects the media. 1320 * Uses sender's tag to figure out the device to eject. 1321 */ 1322- (void)ejectDeviceMedia:(id)sender 1323{ 1324 NSString * drive; 1325 drive = [sender representedObject]; 1326 if(drive == nil) { 1327 NSBeep(); 1328 QEMU_Alert(@"Failed to find drive to eject!"); 1329 return; 1330 } 1331 1332 __block Error *err = NULL; 1333 with_iothread_lock(^{ 1334 qmp_eject(true, [drive cStringUsingEncoding: NSASCIIStringEncoding], 1335 false, NULL, false, false, &err); 1336 }); 1337 handleAnyDeviceErrors(err); 1338} 1339 1340/* Displays a dialog box asking the user to select an image file to load. 1341 * Uses sender's represented object value to figure out which drive to use. 1342 */ 1343- (void)changeDeviceMedia:(id)sender 1344{ 1345 /* Find the drive name */ 1346 NSString * drive; 1347 drive = [sender representedObject]; 1348 if(drive == nil) { 1349 NSBeep(); 1350 QEMU_Alert(@"Could not find drive!"); 1351 return; 1352 } 1353 1354 /* Display the file open dialog */ 1355 NSOpenPanel * openPanel; 1356 openPanel = [NSOpenPanel openPanel]; 1357 [openPanel setCanChooseFiles: YES]; 1358 [openPanel setAllowsMultipleSelection: NO]; 1359 [openPanel setAllowedFileTypes: supportedImageFileTypes]; 1360 if([openPanel runModal] == NSModalResponseOK) { 1361 NSString * file = [[[openPanel URLs] objectAtIndex: 0] path]; 1362 if(file == nil) { 1363 NSBeep(); 1364 QEMU_Alert(@"Failed to convert URL to file path!"); 1365 return; 1366 } 1367 1368 __block Error *err = NULL; 1369 with_iothread_lock(^{ 1370 qmp_blockdev_change_medium(true, 1371 [drive cStringUsingEncoding: 1372 NSASCIIStringEncoding], 1373 false, NULL, 1374 [file cStringUsingEncoding: 1375 NSASCIIStringEncoding], 1376 true, "raw", 1377 false, 0, 1378 &err); 1379 }); 1380 handleAnyDeviceErrors(err); 1381 } 1382} 1383 1384/* Verifies if the user really wants to quit */ 1385- (BOOL)verifyQuit 1386{ 1387 NSAlert *alert = [NSAlert new]; 1388 [alert autorelease]; 1389 [alert setMessageText: @"Are you sure you want to quit QEMU?"]; 1390 [alert addButtonWithTitle: @"Cancel"]; 1391 [alert addButtonWithTitle: @"Quit"]; 1392 if([alert runModal] == NSAlertSecondButtonReturn) { 1393 return YES; 1394 } else { 1395 return NO; 1396 } 1397} 1398 1399/* The action method for the About menu item */ 1400- (IBAction) do_about_menu_item: (id) sender 1401{ 1402 [about_window makeKeyAndOrderFront: nil]; 1403} 1404 1405/* Create and display the about dialog */ 1406- (void)make_about_window 1407{ 1408 /* Make the window */ 1409 int x = 0, y = 0, about_width = 400, about_height = 200; 1410 NSRect window_rect = NSMakeRect(x, y, about_width, about_height); 1411 about_window = [[NSWindow alloc] initWithContentRect:window_rect 1412 styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | 1413 NSWindowStyleMaskMiniaturizable 1414 backing:NSBackingStoreBuffered 1415 defer:NO]; 1416 [about_window setTitle: @"About"]; 1417 [about_window setReleasedWhenClosed: NO]; 1418 [about_window center]; 1419 NSView *superView = [about_window contentView]; 1420 1421 /* Create the dimensions of the picture */ 1422 int picture_width = 80, picture_height = 80; 1423 x = (about_width - picture_width)/2; 1424 y = about_height - picture_height - 10; 1425 NSRect picture_rect = NSMakeRect(x, y, picture_width, picture_height); 1426 1427 /* Make the picture of QEMU */ 1428 NSImageView *picture_view = [[NSImageView alloc] initWithFrame: 1429 picture_rect]; 1430 char *qemu_image_path_c = get_relocated_path(CONFIG_QEMU_ICONDIR "/hicolor/512x512/apps/qemu.png"); 1431 NSString *qemu_image_path = [NSString stringWithUTF8String:qemu_image_path_c]; 1432 g_free(qemu_image_path_c); 1433 NSImage *qemu_image = [[NSImage alloc] initWithContentsOfFile:qemu_image_path]; 1434 [picture_view setImage: qemu_image]; 1435 [picture_view setImageScaling: NSImageScaleProportionallyUpOrDown]; 1436 [superView addSubview: picture_view]; 1437 1438 /* Make the name label */ 1439 NSBundle *bundle = [NSBundle mainBundle]; 1440 if (bundle) { 1441 x = 0; 1442 y = y - 25; 1443 int name_width = about_width, name_height = 20; 1444 NSRect name_rect = NSMakeRect(x, y, name_width, name_height); 1445 NSTextField *name_label = [[NSTextField alloc] initWithFrame: name_rect]; 1446 [name_label setEditable: NO]; 1447 [name_label setBezeled: NO]; 1448 [name_label setDrawsBackground: NO]; 1449 [name_label setAlignment: NSTextAlignmentCenter]; 1450 NSString *qemu_name = [[bundle executablePath] lastPathComponent]; 1451 [name_label setStringValue: qemu_name]; 1452 [superView addSubview: name_label]; 1453 } 1454 1455 /* Set the version label's attributes */ 1456 x = 0; 1457 y = 50; 1458 int version_width = about_width, version_height = 20; 1459 NSRect version_rect = NSMakeRect(x, y, version_width, version_height); 1460 NSTextField *version_label = [[NSTextField alloc] initWithFrame: 1461 version_rect]; 1462 [version_label setEditable: NO]; 1463 [version_label setBezeled: NO]; 1464 [version_label setAlignment: NSTextAlignmentCenter]; 1465 [version_label setDrawsBackground: NO]; 1466 1467 /* Create the version string*/ 1468 NSString *version_string; 1469 version_string = [[NSString alloc] initWithFormat: 1470 @"QEMU emulator version %s", QEMU_FULL_VERSION]; 1471 [version_label setStringValue: version_string]; 1472 [superView addSubview: version_label]; 1473 1474 /* Make copyright label */ 1475 x = 0; 1476 y = 35; 1477 int copyright_width = about_width, copyright_height = 20; 1478 NSRect copyright_rect = NSMakeRect(x, y, copyright_width, copyright_height); 1479 NSTextField *copyright_label = [[NSTextField alloc] initWithFrame: 1480 copyright_rect]; 1481 [copyright_label setEditable: NO]; 1482 [copyright_label setBezeled: NO]; 1483 [copyright_label setDrawsBackground: NO]; 1484 [copyright_label setAlignment: NSTextAlignmentCenter]; 1485 [copyright_label setStringValue: [NSString stringWithFormat: @"%s", 1486 QEMU_COPYRIGHT]]; 1487 [superView addSubview: copyright_label]; 1488} 1489 1490/* Used by the Speed menu items */ 1491- (void)adjustSpeed:(id)sender 1492{ 1493 int throttle_pct; /* throttle percentage */ 1494 NSMenu *menu; 1495 1496 menu = [sender menu]; 1497 if (menu != nil) 1498 { 1499 /* Unselect the currently selected item */ 1500 for (NSMenuItem *item in [menu itemArray]) { 1501 if (item.state == NSControlStateValueOn) { 1502 [item setState: NSControlStateValueOff]; 1503 break; 1504 } 1505 } 1506 } 1507 1508 // check the menu item 1509 [sender setState: NSControlStateValueOn]; 1510 1511 // get the throttle percentage 1512 throttle_pct = [sender tag]; 1513 1514 with_iothread_lock(^{ 1515 cpu_throttle_set(throttle_pct); 1516 }); 1517 COCOA_DEBUG("cpu throttling at %d%c\n", cpu_throttle_get_percentage(), '%'); 1518} 1519 1520@end 1521 1522@interface QemuApplication : NSApplication 1523@end 1524 1525@implementation QemuApplication 1526- (void)sendEvent:(NSEvent *)event 1527{ 1528 COCOA_DEBUG("QemuApplication: sendEvent\n"); 1529 if (![cocoaView handleEvent:event]) { 1530 [super sendEvent: event]; 1531 } 1532} 1533@end 1534 1535static void create_initial_menus(void) 1536{ 1537 // Add menus 1538 NSMenu *menu; 1539 NSMenuItem *menuItem; 1540 1541 [NSApp setMainMenu:[[NSMenu alloc] init]]; 1542 1543 // Application menu 1544 menu = [[NSMenu alloc] initWithTitle:@""]; 1545 [menu addItemWithTitle:@"About QEMU" action:@selector(do_about_menu_item:) keyEquivalent:@""]; // About QEMU 1546 [menu addItem:[NSMenuItem separatorItem]]; //Separator 1547 [menu addItemWithTitle:@"Hide QEMU" action:@selector(hide:) keyEquivalent:@"h"]; //Hide QEMU 1548 menuItem = (NSMenuItem *)[menu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; // Hide Others 1549 [menuItem setKeyEquivalentModifierMask:(NSEventModifierFlagOption|NSEventModifierFlagCommand)]; 1550 [menu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; // Show All 1551 [menu addItem:[NSMenuItem separatorItem]]; //Separator 1552 [menu addItemWithTitle:@"Quit QEMU" action:@selector(terminate:) keyEquivalent:@"q"]; 1553 menuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" action:nil keyEquivalent:@""]; 1554 [menuItem setSubmenu:menu]; 1555 [[NSApp mainMenu] addItem:menuItem]; 1556 [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; // Workaround (this method is private since 10.4+) 1557 1558 // Machine menu 1559 menu = [[NSMenu alloc] initWithTitle: @"Machine"]; 1560 [menu setAutoenablesItems: NO]; 1561 [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Pause" action: @selector(pauseQEMU:) keyEquivalent: @""] autorelease]]; 1562 menuItem = [[[NSMenuItem alloc] initWithTitle: @"Resume" action: @selector(resumeQEMU:) keyEquivalent: @""] autorelease]; 1563 [menu addItem: menuItem]; 1564 [menuItem setEnabled: NO]; 1565 [menu addItem: [NSMenuItem separatorItem]]; 1566 [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Reset" action: @selector(restartQEMU:) keyEquivalent: @""] autorelease]]; 1567 [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Power Down" action: @selector(powerDownQEMU:) keyEquivalent: @""] autorelease]]; 1568 menuItem = [[[NSMenuItem alloc] initWithTitle: @"Machine" action:nil keyEquivalent:@""] autorelease]; 1569 [menuItem setSubmenu:menu]; 1570 [[NSApp mainMenu] addItem:menuItem]; 1571 1572 // View menu 1573 menu = [[NSMenu alloc] initWithTitle:@"View"]; 1574 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(doToggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen 1575 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Zoom To Fit" action:@selector(zoomToFit:) keyEquivalent:@""] autorelease]]; 1576 menuItem = [[[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""] autorelease]; 1577 [menuItem setSubmenu:menu]; 1578 [[NSApp mainMenu] addItem:menuItem]; 1579 1580 // Speed menu 1581 menu = [[NSMenu alloc] initWithTitle:@"Speed"]; 1582 1583 // Add the rest of the Speed menu items 1584 int p, percentage, throttle_pct; 1585 for (p = 10; p >= 0; p--) 1586 { 1587 percentage = p * 10 > 1 ? p * 10 : 1; // prevent a 0% menu item 1588 1589 menuItem = [[[NSMenuItem alloc] 1590 initWithTitle: [NSString stringWithFormat: @"%d%%", percentage] action:@selector(adjustSpeed:) keyEquivalent:@""] autorelease]; 1591 1592 if (percentage == 100) { 1593 [menuItem setState: NSControlStateValueOn]; 1594 } 1595 1596 /* Calculate the throttle percentage */ 1597 throttle_pct = -1 * percentage + 100; 1598 1599 [menuItem setTag: throttle_pct]; 1600 [menu addItem: menuItem]; 1601 } 1602 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Speed" action:nil keyEquivalent:@""] autorelease]; 1603 [menuItem setSubmenu:menu]; 1604 [[NSApp mainMenu] addItem:menuItem]; 1605 1606 // Window menu 1607 menu = [[NSMenu alloc] initWithTitle:@"Window"]; 1608 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease]]; // Miniaturize 1609 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease]; 1610 [menuItem setSubmenu:menu]; 1611 [[NSApp mainMenu] addItem:menuItem]; 1612 [NSApp setWindowsMenu:menu]; 1613 1614 // Help menu 1615 menu = [[NSMenu alloc] initWithTitle:@"Help"]; 1616 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Documentation" action:@selector(showQEMUDoc:) keyEquivalent:@"?"] autorelease]]; // QEMU Help 1617 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease]; 1618 [menuItem setSubmenu:menu]; 1619 [[NSApp mainMenu] addItem:menuItem]; 1620} 1621 1622/* Returns a name for a given console */ 1623static NSString * getConsoleName(QemuConsole * console) 1624{ 1625 return [NSString stringWithFormat: @"%s", qemu_console_get_label(console)]; 1626} 1627 1628/* Add an entry to the View menu for each console */ 1629static void add_console_menu_entries(void) 1630{ 1631 NSMenu *menu; 1632 NSMenuItem *menuItem; 1633 int index = 0; 1634 1635 menu = [[[NSApp mainMenu] itemWithTitle:@"View"] submenu]; 1636 1637 [menu addItem:[NSMenuItem separatorItem]]; 1638 1639 while (qemu_console_lookup_by_index(index) != NULL) { 1640 menuItem = [[[NSMenuItem alloc] initWithTitle: getConsoleName(qemu_console_lookup_by_index(index)) 1641 action: @selector(displayConsole:) keyEquivalent: @""] autorelease]; 1642 [menuItem setTag: index]; 1643 [menu addItem: menuItem]; 1644 index++; 1645 } 1646} 1647 1648/* Make menu items for all removable devices. 1649 * Each device is given an 'Eject' and 'Change' menu item. 1650 */ 1651static void addRemovableDevicesMenuItems(void) 1652{ 1653 NSMenu *menu; 1654 NSMenuItem *menuItem; 1655 BlockInfoList *currentDevice, *pointerToFree; 1656 NSString *deviceName; 1657 1658 currentDevice = qmp_query_block(NULL); 1659 pointerToFree = currentDevice; 1660 if(currentDevice == NULL) { 1661 NSBeep(); 1662 QEMU_Alert(@"Failed to query for block devices!"); 1663 return; 1664 } 1665 1666 menu = [[[NSApp mainMenu] itemWithTitle:@"Machine"] submenu]; 1667 1668 // Add a separator between related groups of menu items 1669 [menu addItem:[NSMenuItem separatorItem]]; 1670 1671 // Set the attributes to the "Removable Media" menu item 1672 NSString *titleString = @"Removable Media"; 1673 NSMutableAttributedString *attString=[[NSMutableAttributedString alloc] initWithString:titleString]; 1674 NSColor *newColor = [NSColor blackColor]; 1675 NSFontManager *fontManager = [NSFontManager sharedFontManager]; 1676 NSFont *font = [fontManager fontWithFamily:@"Helvetica" 1677 traits:NSBoldFontMask|NSItalicFontMask 1678 weight:0 1679 size:14]; 1680 [attString addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, [titleString length])]; 1681 [attString addAttribute:NSForegroundColorAttributeName value:newColor range:NSMakeRange(0, [titleString length])]; 1682 [attString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInt: 1] range:NSMakeRange(0, [titleString length])]; 1683 1684 // Add the "Removable Media" menu item 1685 menuItem = [NSMenuItem new]; 1686 [menuItem setAttributedTitle: attString]; 1687 [menuItem setEnabled: NO]; 1688 [menu addItem: menuItem]; 1689 1690 /* Loop through all the block devices in the emulator */ 1691 while (currentDevice) { 1692 deviceName = [[NSString stringWithFormat: @"%s", currentDevice->value->device] retain]; 1693 1694 if(currentDevice->value->removable) { 1695 menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Change %s...", currentDevice->value->device] 1696 action: @selector(changeDeviceMedia:) 1697 keyEquivalent: @""]; 1698 [menu addItem: menuItem]; 1699 [menuItem setRepresentedObject: deviceName]; 1700 [menuItem autorelease]; 1701 1702 menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Eject %s", currentDevice->value->device] 1703 action: @selector(ejectDeviceMedia:) 1704 keyEquivalent: @""]; 1705 [menu addItem: menuItem]; 1706 [menuItem setRepresentedObject: deviceName]; 1707 [menuItem autorelease]; 1708 } 1709 currentDevice = currentDevice->next; 1710 } 1711 qapi_free_BlockInfoList(pointerToFree); 1712} 1713 1714/* 1715 * The startup process for the OSX/Cocoa UI is complicated, because 1716 * OSX insists that the UI runs on the initial main thread, and so we 1717 * need to start a second thread which runs the vl.c qemu_main(): 1718 * 1719 * Initial thread: 2nd thread: 1720 * in main(): 1721 * create qemu-main thread 1722 * wait on display_init semaphore 1723 * call qemu_main() 1724 * ... 1725 * in cocoa_display_init(): 1726 * post the display_init semaphore 1727 * wait on app_started semaphore 1728 * create application, menus, etc 1729 * enter OSX run loop 1730 * in applicationDidFinishLaunching: 1731 * post app_started semaphore 1732 * tell main thread to fullscreen if needed 1733 * [...] 1734 * run qemu main-loop 1735 * 1736 * We do this in two stages so that we don't do the creation of the 1737 * GUI application menus and so on for command line options like --help 1738 * where we want to just print text to stdout and exit immediately. 1739 */ 1740 1741static void *call_qemu_main(void *opaque) 1742{ 1743 int status; 1744 1745 COCOA_DEBUG("Second thread: calling qemu_main()\n"); 1746 status = qemu_main(gArgc, gArgv, *_NSGetEnviron()); 1747 COCOA_DEBUG("Second thread: qemu_main() returned, exiting\n"); 1748 exit(status); 1749} 1750 1751int main (int argc, const char * argv[]) { 1752 QemuThread thread; 1753 1754 COCOA_DEBUG("Entered main()\n"); 1755 gArgc = argc; 1756 gArgv = (char **)argv; 1757 1758 qemu_sem_init(&display_init_sem, 0); 1759 qemu_sem_init(&app_started_sem, 0); 1760 1761 qemu_thread_create(&thread, "qemu_main", call_qemu_main, 1762 NULL, QEMU_THREAD_DETACHED); 1763 1764 COCOA_DEBUG("Main thread: waiting for display_init_sem\n"); 1765 qemu_sem_wait(&display_init_sem); 1766 COCOA_DEBUG("Main thread: initializing app\n"); 1767 1768 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 1769 1770 // Pull this console process up to being a fully-fledged graphical 1771 // app with a menubar and Dock icon 1772 ProcessSerialNumber psn = { 0, kCurrentProcess }; 1773 TransformProcessType(&psn, kProcessTransformToForegroundApplication); 1774 1775 [QemuApplication sharedApplication]; 1776 1777 create_initial_menus(); 1778 1779 /* 1780 * Create the menu entries which depend on QEMU state (for consoles 1781 * and removeable devices). These make calls back into QEMU functions, 1782 * which is OK because at this point we know that the second thread 1783 * holds the iothread lock and is synchronously waiting for us to 1784 * finish. 1785 */ 1786 add_console_menu_entries(); 1787 addRemovableDevicesMenuItems(); 1788 1789 // Create an Application controller 1790 QemuCocoaAppController *appController = [[QemuCocoaAppController alloc] init]; 1791 [NSApp setDelegate:appController]; 1792 1793 // Start the main event loop 1794 COCOA_DEBUG("Main thread: entering OSX run loop\n"); 1795 [NSApp run]; 1796 COCOA_DEBUG("Main thread: left OSX run loop, exiting\n"); 1797 1798 [appController release]; 1799 [pool release]; 1800 1801 return 0; 1802} 1803 1804 1805 1806#pragma mark qemu 1807static void cocoa_update(DisplayChangeListener *dcl, 1808 int x, int y, int w, int h) 1809{ 1810 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 1811 1812 COCOA_DEBUG("qemu_cocoa: cocoa_update\n"); 1813 1814 dispatch_async(dispatch_get_main_queue(), ^{ 1815 NSRect rect; 1816 if ([cocoaView cdx] == 1.0) { 1817 rect = NSMakeRect(x, [cocoaView gscreen].height - y - h, w, h); 1818 } else { 1819 rect = NSMakeRect( 1820 x * [cocoaView cdx], 1821 ([cocoaView gscreen].height - y - h) * [cocoaView cdy], 1822 w * [cocoaView cdx], 1823 h * [cocoaView cdy]); 1824 } 1825 [cocoaView setNeedsDisplayInRect:rect]; 1826 }); 1827 1828 [pool release]; 1829} 1830 1831static void cocoa_switch(DisplayChangeListener *dcl, 1832 DisplaySurface *surface) 1833{ 1834 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 1835 pixman_image_t *image = surface->image; 1836 1837 COCOA_DEBUG("qemu_cocoa: cocoa_switch\n"); 1838 1839 // The DisplaySurface will be freed as soon as this callback returns. 1840 // We take a reference to the underlying pixman image here so it does 1841 // not disappear from under our feet; the switchSurface method will 1842 // deref the old image when it is done with it. 1843 pixman_image_ref(image); 1844 1845 dispatch_async(dispatch_get_main_queue(), ^{ 1846 [cocoaView switchSurface:image]; 1847 }); 1848 [pool release]; 1849} 1850 1851static void cocoa_refresh(DisplayChangeListener *dcl) 1852{ 1853 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 1854 1855 COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n"); 1856 graphic_hw_update(NULL); 1857 1858 if (qemu_input_is_absolute()) { 1859 dispatch_async(dispatch_get_main_queue(), ^{ 1860 if (![cocoaView isAbsoluteEnabled]) { 1861 if ([cocoaView isMouseGrabbed]) { 1862 [cocoaView ungrabMouse]; 1863 } 1864 } 1865 [cocoaView setAbsoluteEnabled:YES]; 1866 }); 1867 } 1868 [pool release]; 1869} 1870 1871static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts) 1872{ 1873 COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n"); 1874 1875 /* Tell main thread to go ahead and create the app and enter the run loop */ 1876 qemu_sem_post(&display_init_sem); 1877 qemu_sem_wait(&app_started_sem); 1878 COCOA_DEBUG("cocoa_display_init: app start completed\n"); 1879 1880 /* if fullscreen mode is to be used */ 1881 if (opts->has_full_screen && opts->full_screen) { 1882 dispatch_async(dispatch_get_main_queue(), ^{ 1883 [NSApp activateIgnoringOtherApps: YES]; 1884 [(QemuCocoaAppController *)[[NSApplication sharedApplication] delegate] toggleFullScreen: nil]; 1885 }); 1886 } 1887 if (opts->has_show_cursor && opts->show_cursor) { 1888 cursor_hide = 0; 1889 } 1890 1891 // register vga output callbacks 1892 register_displaychangelistener(&dcl); 1893} 1894 1895static QemuDisplay qemu_display_cocoa = { 1896 .type = DISPLAY_TYPE_COCOA, 1897 .init = cocoa_display_init, 1898}; 1899 1900static void register_cocoa(void) 1901{ 1902 qemu_display_register(&qemu_display_cocoa); 1903} 1904 1905type_init(register_cocoa); 1906