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 // emulate caps lock keydown and keyup 694 if (!!(modifiers & NSEventModifierFlagCapsLock) != 695 qkbd_state_modifier_get(kbd, QKBD_MOD_CAPSLOCK)) { 696 qkbd_state_key_event(kbd, Q_KEY_CODE_CAPS_LOCK, true); 697 qkbd_state_key_event(kbd, Q_KEY_CODE_CAPS_LOCK, false); 698 } 699 700 if (!(modifiers & NSEventModifierFlagShift)) { 701 qkbd_state_key_event(kbd, Q_KEY_CODE_SHIFT, false); 702 qkbd_state_key_event(kbd, Q_KEY_CODE_SHIFT_R, false); 703 } 704 if (!(modifiers & NSEventModifierFlagControl)) { 705 qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL, false); 706 qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL_R, false); 707 } 708 if (!(modifiers & NSEventModifierFlagOption)) { 709 qkbd_state_key_event(kbd, Q_KEY_CODE_ALT, false); 710 qkbd_state_key_event(kbd, Q_KEY_CODE_ALT_R, false); 711 } 712 if (!(modifiers & NSEventModifierFlagCommand)) { 713 qkbd_state_key_event(kbd, Q_KEY_CODE_META_L, false); 714 qkbd_state_key_event(kbd, Q_KEY_CODE_META_R, false); 715 } 716 717 switch ([event type]) { 718 case NSEventTypeFlagsChanged: 719 switch ([event keyCode]) { 720 case kVK_Shift: 721 if (!!(modifiers & NSEventModifierFlagShift)) { 722 [self toggleKey:Q_KEY_CODE_SHIFT]; 723 } 724 break; 725 726 case kVK_RightShift: 727 if (!!(modifiers & NSEventModifierFlagShift)) { 728 [self toggleKey:Q_KEY_CODE_SHIFT_R]; 729 } 730 break; 731 732 case kVK_Control: 733 if (!!(modifiers & NSEventModifierFlagControl)) { 734 [self toggleKey:Q_KEY_CODE_CTRL]; 735 } 736 break; 737 738 case kVK_RightControl: 739 if (!!(modifiers & NSEventModifierFlagControl)) { 740 [self toggleKey:Q_KEY_CODE_CTRL_R]; 741 } 742 break; 743 744 case kVK_Option: 745 if (!!(modifiers & NSEventModifierFlagOption)) { 746 [self toggleKey:Q_KEY_CODE_ALT]; 747 } 748 break; 749 750 case kVK_RightOption: 751 if (!!(modifiers & NSEventModifierFlagOption)) { 752 [self toggleKey:Q_KEY_CODE_ALT_R]; 753 } 754 break; 755 756 /* Don't pass command key changes to guest unless mouse is grabbed */ 757 case kVK_Command: 758 if (isMouseGrabbed && 759 !!(modifiers & NSEventModifierFlagCommand)) { 760 [self toggleKey:Q_KEY_CODE_META_L]; 761 } 762 break; 763 764 case kVK_RightCommand: 765 if (isMouseGrabbed && 766 !!(modifiers & NSEventModifierFlagCommand)) { 767 [self toggleKey:Q_KEY_CODE_META_R]; 768 } 769 break; 770 } 771 break; 772 case NSEventTypeKeyDown: 773 keycode = cocoa_keycode_to_qemu([event keyCode]); 774 775 // forward command key combos to the host UI unless the mouse is grabbed 776 if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) { 777 /* 778 * Prevent the command key from being stuck down in the guest 779 * when using Command-F to switch to full screen mode. 780 */ 781 if (keycode == Q_KEY_CODE_F) { 782 switched_to_fullscreen = true; 783 } 784 return false; 785 } 786 787 // default 788 789 // handle control + alt Key Combos (ctrl+alt+[1..9,g] is reserved for QEMU) 790 if (([event modifierFlags] & NSEventModifierFlagControl) && ([event modifierFlags] & NSEventModifierFlagOption)) { 791 NSString *keychar = [event charactersIgnoringModifiers]; 792 if ([keychar length] == 1) { 793 char key = [keychar characterAtIndex:0]; 794 switch (key) { 795 796 // enable graphic console 797 case '1' ... '9': 798 console_select(key - '0' - 1); /* ascii math */ 799 return true; 800 801 // release the mouse grab 802 case 'g': 803 [self ungrabMouse]; 804 return true; 805 } 806 } 807 } 808 809 if (qemu_console_is_graphic(NULL)) { 810 qkbd_state_key_event(kbd, keycode, true); 811 } else { 812 [self handleMonitorInput: event]; 813 } 814 break; 815 case NSEventTypeKeyUp: 816 keycode = cocoa_keycode_to_qemu([event keyCode]); 817 818 // don't pass the guest a spurious key-up if we treated this 819 // command-key combo as a host UI action 820 if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) { 821 return true; 822 } 823 824 if (qemu_console_is_graphic(NULL)) { 825 qkbd_state_key_event(kbd, keycode, false); 826 } 827 break; 828 case NSEventTypeMouseMoved: 829 if (isAbsoluteEnabled) { 830 // Cursor re-entered into a window might generate events bound to screen coordinates 831 // and `nil` window property, and in full screen mode, current window might not be 832 // key window, where event location alone should suffice. 833 if (![self screenContainsPoint:p] || !([[self window] isKeyWindow] || isFullscreen)) { 834 if (isMouseGrabbed) { 835 [self ungrabMouse]; 836 } 837 } else { 838 if (!isMouseGrabbed) { 839 [self grabMouse]; 840 } 841 } 842 } 843 mouse_event = true; 844 break; 845 case NSEventTypeLeftMouseDown: 846 buttons |= MOUSE_EVENT_LBUTTON; 847 mouse_event = true; 848 break; 849 case NSEventTypeRightMouseDown: 850 buttons |= MOUSE_EVENT_RBUTTON; 851 mouse_event = true; 852 break; 853 case NSEventTypeOtherMouseDown: 854 buttons |= MOUSE_EVENT_MBUTTON; 855 mouse_event = true; 856 break; 857 case NSEventTypeLeftMouseDragged: 858 buttons |= MOUSE_EVENT_LBUTTON; 859 mouse_event = true; 860 break; 861 case NSEventTypeRightMouseDragged: 862 buttons |= MOUSE_EVENT_RBUTTON; 863 mouse_event = true; 864 break; 865 case NSEventTypeOtherMouseDragged: 866 buttons |= MOUSE_EVENT_MBUTTON; 867 mouse_event = true; 868 break; 869 case NSEventTypeLeftMouseUp: 870 mouse_event = true; 871 if (!isMouseGrabbed && [self screenContainsPoint:p]) { 872 /* 873 * In fullscreen mode, the window of cocoaView may not be the 874 * key window, therefore the position relative to the virtual 875 * screen alone will be sufficient. 876 */ 877 if(isFullscreen || [[self window] isKeyWindow]) { 878 [self grabMouse]; 879 } 880 } 881 break; 882 case NSEventTypeRightMouseUp: 883 mouse_event = true; 884 break; 885 case NSEventTypeOtherMouseUp: 886 mouse_event = true; 887 break; 888 case NSEventTypeScrollWheel: 889 /* 890 * Send wheel events to the guest regardless of window focus. 891 * This is in-line with standard Mac OS X UI behaviour. 892 */ 893 894 /* 895 * When deltaY is zero, it means that this scrolling event was 896 * either horizontal, or so fine that it only appears in 897 * scrollingDeltaY. So we drop the event. 898 */ 899 if ([event deltaY] != 0) { 900 /* Determine if this is a scroll up or scroll down event */ 901 buttons = ([event deltaY] > 0) ? 902 INPUT_BUTTON_WHEEL_UP : INPUT_BUTTON_WHEEL_DOWN; 903 qemu_input_queue_btn(dcl.con, buttons, true); 904 qemu_input_event_sync(); 905 qemu_input_queue_btn(dcl.con, buttons, false); 906 qemu_input_event_sync(); 907 } 908 /* 909 * Since deltaY also reports scroll wheel events we prevent mouse 910 * movement code from executing. 911 */ 912 mouse_event = false; 913 break; 914 default: 915 return false; 916 } 917 918 if (mouse_event) { 919 /* Don't send button events to the guest unless we've got a 920 * mouse grab or window focus. If we have neither then this event 921 * is the user clicking on the background window to activate and 922 * bring us to the front, which will be done by the sendEvent 923 * call below. We definitely don't want to pass that click through 924 * to the guest. 925 */ 926 if ((isMouseGrabbed || [[self window] isKeyWindow]) && 927 (last_buttons != buttons)) { 928 static uint32_t bmap[INPUT_BUTTON__MAX] = { 929 [INPUT_BUTTON_LEFT] = MOUSE_EVENT_LBUTTON, 930 [INPUT_BUTTON_MIDDLE] = MOUSE_EVENT_MBUTTON, 931 [INPUT_BUTTON_RIGHT] = MOUSE_EVENT_RBUTTON 932 }; 933 qemu_input_update_buttons(dcl.con, bmap, last_buttons, buttons); 934 last_buttons = buttons; 935 } 936 if (isMouseGrabbed) { 937 if (isAbsoluteEnabled) { 938 /* Note that the origin for Cocoa mouse coords is bottom left, not top left. 939 * The check on screenContainsPoint is to avoid sending out of range values for 940 * clicks in the titlebar. 941 */ 942 if ([self screenContainsPoint:p]) { 943 qemu_input_queue_abs(dcl.con, INPUT_AXIS_X, p.x, 0, screen.width); 944 qemu_input_queue_abs(dcl.con, INPUT_AXIS_Y, screen.height - p.y, 0, screen.height); 945 } 946 } else { 947 qemu_input_queue_rel(dcl.con, INPUT_AXIS_X, (int)[event deltaX]); 948 qemu_input_queue_rel(dcl.con, INPUT_AXIS_Y, (int)[event deltaY]); 949 } 950 } else { 951 return false; 952 } 953 qemu_input_event_sync(); 954 } 955 return true; 956} 957 958- (void) grabMouse 959{ 960 COCOA_DEBUG("QemuCocoaView: grabMouse\n"); 961 962 if (!isFullscreen) { 963 if (qemu_name) 964 [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s - (Press ctrl + alt + g to release Mouse)", qemu_name]]; 965 else 966 [normalWindow setTitle:@"QEMU - (Press ctrl + alt + g to release Mouse)"]; 967 } 968 [self hideCursor]; 969 CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled); 970 isMouseGrabbed = TRUE; // while isMouseGrabbed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:] 971} 972 973- (void) ungrabMouse 974{ 975 COCOA_DEBUG("QemuCocoaView: ungrabMouse\n"); 976 977 if (!isFullscreen) { 978 if (qemu_name) 979 [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]]; 980 else 981 [normalWindow setTitle:@"QEMU"]; 982 } 983 [self unhideCursor]; 984 CGAssociateMouseAndMouseCursorPosition(TRUE); 985 isMouseGrabbed = FALSE; 986} 987 988- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled { 989 isAbsoluteEnabled = tIsAbsoluteEnabled; 990 if (isMouseGrabbed) { 991 CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled); 992 } 993} 994- (BOOL) isMouseGrabbed {return isMouseGrabbed;} 995- (BOOL) isAbsoluteEnabled {return isAbsoluteEnabled;} 996- (float) cdx {return cdx;} 997- (float) cdy {return cdy;} 998- (QEMUScreen) gscreen {return screen;} 999 1000/* 1001 * Makes the target think all down keys are being released. 1002 * This prevents a stuck key problem, since we will not see 1003 * key up events for those keys after we have lost focus. 1004 */ 1005- (void) raiseAllKeys 1006{ 1007 with_iothread_lock(^{ 1008 qkbd_state_lift_all_keys(kbd); 1009 }); 1010} 1011@end 1012 1013 1014 1015/* 1016 ------------------------------------------------------ 1017 QemuCocoaAppController 1018 ------------------------------------------------------ 1019*/ 1020@interface QemuCocoaAppController : NSObject 1021 <NSWindowDelegate, NSApplicationDelegate> 1022{ 1023} 1024- (void)doToggleFullScreen:(id)sender; 1025- (void)toggleFullScreen:(id)sender; 1026- (void)showQEMUDoc:(id)sender; 1027- (void)zoomToFit:(id) sender; 1028- (void)displayConsole:(id)sender; 1029- (void)pauseQEMU:(id)sender; 1030- (void)resumeQEMU:(id)sender; 1031- (void)displayPause; 1032- (void)removePause; 1033- (void)restartQEMU:(id)sender; 1034- (void)powerDownQEMU:(id)sender; 1035- (void)ejectDeviceMedia:(id)sender; 1036- (void)changeDeviceMedia:(id)sender; 1037- (BOOL)verifyQuit; 1038- (void)openDocumentation:(NSString *)filename; 1039- (IBAction) do_about_menu_item: (id) sender; 1040- (void)make_about_window; 1041- (void)adjustSpeed:(id)sender; 1042@end 1043 1044@implementation QemuCocoaAppController 1045- (id) init 1046{ 1047 COCOA_DEBUG("QemuCocoaAppController: init\n"); 1048 1049 self = [super init]; 1050 if (self) { 1051 1052 // create a view and add it to the window 1053 cocoaView = [[QemuCocoaView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 640.0, 480.0)]; 1054 if(!cocoaView) { 1055 error_report("(cocoa) can't create a view"); 1056 exit(1); 1057 } 1058 1059 // create a window 1060 normalWindow = [[NSWindow alloc] initWithContentRect:[cocoaView frame] 1061 styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskClosable 1062 backing:NSBackingStoreBuffered defer:NO]; 1063 if(!normalWindow) { 1064 error_report("(cocoa) can't create window"); 1065 exit(1); 1066 } 1067 [normalWindow setAcceptsMouseMovedEvents:YES]; 1068 [normalWindow setTitle:@"QEMU"]; 1069 [normalWindow setContentView:cocoaView]; 1070 [normalWindow makeKeyAndOrderFront:self]; 1071 [normalWindow center]; 1072 [normalWindow setDelegate: self]; 1073 stretch_video = false; 1074 1075 /* Used for displaying pause on the screen */ 1076 pauseLabel = [NSTextField new]; 1077 [pauseLabel setBezeled:YES]; 1078 [pauseLabel setDrawsBackground:YES]; 1079 [pauseLabel setBackgroundColor: [NSColor whiteColor]]; 1080 [pauseLabel setEditable:NO]; 1081 [pauseLabel setSelectable:NO]; 1082 [pauseLabel setStringValue: @"Paused"]; 1083 [pauseLabel setFont: [NSFont fontWithName: @"Helvetica" size: 90]]; 1084 [pauseLabel setTextColor: [NSColor blackColor]]; 1085 [pauseLabel sizeToFit]; 1086 1087 // set the supported image file types that can be opened 1088 supportedImageFileTypes = [NSArray arrayWithObjects: @"img", @"iso", @"dmg", 1089 @"qcow", @"qcow2", @"cloop", @"vmdk", @"cdr", 1090 @"toast", nil]; 1091 [self make_about_window]; 1092 } 1093 return self; 1094} 1095 1096- (void) dealloc 1097{ 1098 COCOA_DEBUG("QemuCocoaAppController: dealloc\n"); 1099 1100 if (cocoaView) 1101 [cocoaView release]; 1102 [super dealloc]; 1103} 1104 1105- (void)applicationDidFinishLaunching: (NSNotification *) note 1106{ 1107 COCOA_DEBUG("QemuCocoaAppController: applicationDidFinishLaunching\n"); 1108 allow_events = true; 1109 /* Tell cocoa_display_init to proceed */ 1110 qemu_sem_post(&app_started_sem); 1111} 1112 1113- (void)applicationWillTerminate:(NSNotification *)aNotification 1114{ 1115 COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n"); 1116 1117 qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI); 1118 exit(0); 1119} 1120 1121- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication 1122{ 1123 return YES; 1124} 1125 1126- (NSApplicationTerminateReply)applicationShouldTerminate: 1127 (NSApplication *)sender 1128{ 1129 COCOA_DEBUG("QemuCocoaAppController: applicationShouldTerminate\n"); 1130 return [self verifyQuit]; 1131} 1132 1133/* Called when the user clicks on a window's close button */ 1134- (BOOL)windowShouldClose:(id)sender 1135{ 1136 COCOA_DEBUG("QemuCocoaAppController: windowShouldClose\n"); 1137 [NSApp terminate: sender]; 1138 /* If the user allows the application to quit then the call to 1139 * NSApp terminate will never return. If we get here then the user 1140 * cancelled the quit, so we should return NO to not permit the 1141 * closing of this window. 1142 */ 1143 return NO; 1144} 1145 1146/* Called when QEMU goes into the background */ 1147- (void) applicationWillResignActive: (NSNotification *)aNotification 1148{ 1149 COCOA_DEBUG("QemuCocoaAppController: applicationWillResignActive\n"); 1150 [cocoaView raiseAllKeys]; 1151} 1152 1153/* We abstract the method called by the Enter Fullscreen menu item 1154 * because Mac OS 10.7 and higher disables it. This is because of the 1155 * menu item's old selector's name toggleFullScreen: 1156 */ 1157- (void) doToggleFullScreen:(id)sender 1158{ 1159 [self toggleFullScreen:(id)sender]; 1160} 1161 1162- (void)toggleFullScreen:(id)sender 1163{ 1164 COCOA_DEBUG("QemuCocoaAppController: toggleFullScreen\n"); 1165 1166 [cocoaView toggleFullScreen:sender]; 1167} 1168 1169/* Tries to find then open the specified filename */ 1170- (void) openDocumentation: (NSString *) filename 1171{ 1172 /* Where to look for local files */ 1173 NSString *path_array[] = {@"../share/doc/qemu/", @"../doc/qemu/", @"docs/"}; 1174 NSString *full_file_path; 1175 NSURL *full_file_url; 1176 1177 /* iterate thru the possible paths until the file is found */ 1178 int index; 1179 for (index = 0; index < ARRAY_SIZE(path_array); index++) { 1180 full_file_path = [[NSBundle mainBundle] executablePath]; 1181 full_file_path = [full_file_path stringByDeletingLastPathComponent]; 1182 full_file_path = [NSString stringWithFormat: @"%@/%@%@", full_file_path, 1183 path_array[index], filename]; 1184 full_file_url = [NSURL fileURLWithPath: full_file_path 1185 isDirectory: false]; 1186 if ([[NSWorkspace sharedWorkspace] openURL: full_file_url] == YES) { 1187 return; 1188 } 1189 } 1190 1191 /* If none of the paths opened a file */ 1192 NSBeep(); 1193 QEMU_Alert(@"Failed to open file"); 1194} 1195 1196- (void)showQEMUDoc:(id)sender 1197{ 1198 COCOA_DEBUG("QemuCocoaAppController: showQEMUDoc\n"); 1199 1200 [self openDocumentation: @"index.html"]; 1201} 1202 1203/* Stretches video to fit host monitor size */ 1204- (void)zoomToFit:(id) sender 1205{ 1206 stretch_video = !stretch_video; 1207 if (stretch_video == true) { 1208 [sender setState: NSControlStateValueOn]; 1209 } else { 1210 [sender setState: NSControlStateValueOff]; 1211 } 1212} 1213 1214/* Displays the console on the screen */ 1215- (void)displayConsole:(id)sender 1216{ 1217 console_select([sender tag]); 1218} 1219 1220/* Pause the guest */ 1221- (void)pauseQEMU:(id)sender 1222{ 1223 with_iothread_lock(^{ 1224 qmp_stop(NULL); 1225 }); 1226 [sender setEnabled: NO]; 1227 [[[sender menu] itemWithTitle: @"Resume"] setEnabled: YES]; 1228 [self displayPause]; 1229} 1230 1231/* Resume running the guest operating system */ 1232- (void)resumeQEMU:(id) sender 1233{ 1234 with_iothread_lock(^{ 1235 qmp_cont(NULL); 1236 }); 1237 [sender setEnabled: NO]; 1238 [[[sender menu] itemWithTitle: @"Pause"] setEnabled: YES]; 1239 [self removePause]; 1240} 1241 1242/* Displays the word pause on the screen */ 1243- (void)displayPause 1244{ 1245 /* Coordinates have to be calculated each time because the window can change its size */ 1246 int xCoord, yCoord, width, height; 1247 xCoord = ([normalWindow frame].size.width - [pauseLabel frame].size.width)/2; 1248 yCoord = [normalWindow frame].size.height - [pauseLabel frame].size.height - ([pauseLabel frame].size.height * .5); 1249 width = [pauseLabel frame].size.width; 1250 height = [pauseLabel frame].size.height; 1251 [pauseLabel setFrame: NSMakeRect(xCoord, yCoord, width, height)]; 1252 [cocoaView addSubview: pauseLabel]; 1253} 1254 1255/* Removes the word pause from the screen */ 1256- (void)removePause 1257{ 1258 [pauseLabel removeFromSuperview]; 1259} 1260 1261/* Restarts QEMU */ 1262- (void)restartQEMU:(id)sender 1263{ 1264 with_iothread_lock(^{ 1265 qmp_system_reset(NULL); 1266 }); 1267} 1268 1269/* Powers down QEMU */ 1270- (void)powerDownQEMU:(id)sender 1271{ 1272 with_iothread_lock(^{ 1273 qmp_system_powerdown(NULL); 1274 }); 1275} 1276 1277/* Ejects the media. 1278 * Uses sender's tag to figure out the device to eject. 1279 */ 1280- (void)ejectDeviceMedia:(id)sender 1281{ 1282 NSString * drive; 1283 drive = [sender representedObject]; 1284 if(drive == nil) { 1285 NSBeep(); 1286 QEMU_Alert(@"Failed to find drive to eject!"); 1287 return; 1288 } 1289 1290 __block Error *err = NULL; 1291 with_iothread_lock(^{ 1292 qmp_eject(true, [drive cStringUsingEncoding: NSASCIIStringEncoding], 1293 false, NULL, false, false, &err); 1294 }); 1295 handleAnyDeviceErrors(err); 1296} 1297 1298/* Displays a dialog box asking the user to select an image file to load. 1299 * Uses sender's represented object value to figure out which drive to use. 1300 */ 1301- (void)changeDeviceMedia:(id)sender 1302{ 1303 /* Find the drive name */ 1304 NSString * drive; 1305 drive = [sender representedObject]; 1306 if(drive == nil) { 1307 NSBeep(); 1308 QEMU_Alert(@"Could not find drive!"); 1309 return; 1310 } 1311 1312 /* Display the file open dialog */ 1313 NSOpenPanel * openPanel; 1314 openPanel = [NSOpenPanel openPanel]; 1315 [openPanel setCanChooseFiles: YES]; 1316 [openPanel setAllowsMultipleSelection: NO]; 1317 [openPanel setAllowedFileTypes: supportedImageFileTypes]; 1318 if([openPanel runModal] == NSModalResponseOK) { 1319 NSString * file = [[[openPanel URLs] objectAtIndex: 0] path]; 1320 if(file == nil) { 1321 NSBeep(); 1322 QEMU_Alert(@"Failed to convert URL to file path!"); 1323 return; 1324 } 1325 1326 __block Error *err = NULL; 1327 with_iothread_lock(^{ 1328 qmp_blockdev_change_medium(true, 1329 [drive cStringUsingEncoding: 1330 NSASCIIStringEncoding], 1331 false, NULL, 1332 [file cStringUsingEncoding: 1333 NSASCIIStringEncoding], 1334 true, "raw", 1335 false, 0, 1336 &err); 1337 }); 1338 handleAnyDeviceErrors(err); 1339 } 1340} 1341 1342/* Verifies if the user really wants to quit */ 1343- (BOOL)verifyQuit 1344{ 1345 NSAlert *alert = [NSAlert new]; 1346 [alert autorelease]; 1347 [alert setMessageText: @"Are you sure you want to quit QEMU?"]; 1348 [alert addButtonWithTitle: @"Cancel"]; 1349 [alert addButtonWithTitle: @"Quit"]; 1350 if([alert runModal] == NSAlertSecondButtonReturn) { 1351 return YES; 1352 } else { 1353 return NO; 1354 } 1355} 1356 1357/* The action method for the About menu item */ 1358- (IBAction) do_about_menu_item: (id) sender 1359{ 1360 [about_window makeKeyAndOrderFront: nil]; 1361} 1362 1363/* Create and display the about dialog */ 1364- (void)make_about_window 1365{ 1366 /* Make the window */ 1367 int x = 0, y = 0, about_width = 400, about_height = 200; 1368 NSRect window_rect = NSMakeRect(x, y, about_width, about_height); 1369 about_window = [[NSWindow alloc] initWithContentRect:window_rect 1370 styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | 1371 NSWindowStyleMaskMiniaturizable 1372 backing:NSBackingStoreBuffered 1373 defer:NO]; 1374 [about_window setTitle: @"About"]; 1375 [about_window setReleasedWhenClosed: NO]; 1376 [about_window center]; 1377 NSView *superView = [about_window contentView]; 1378 1379 /* Create the dimensions of the picture */ 1380 int picture_width = 80, picture_height = 80; 1381 x = (about_width - picture_width)/2; 1382 y = about_height - picture_height - 10; 1383 NSRect picture_rect = NSMakeRect(x, y, picture_width, picture_height); 1384 1385 /* Make the picture of QEMU */ 1386 NSImageView *picture_view = [[NSImageView alloc] initWithFrame: 1387 picture_rect]; 1388 char *qemu_image_path_c = get_relocated_path(CONFIG_QEMU_ICONDIR "/hicolor/512x512/apps/qemu.png"); 1389 NSString *qemu_image_path = [NSString stringWithUTF8String:qemu_image_path_c]; 1390 g_free(qemu_image_path_c); 1391 NSImage *qemu_image = [[NSImage alloc] initWithContentsOfFile:qemu_image_path]; 1392 [picture_view setImage: qemu_image]; 1393 [picture_view setImageScaling: NSImageScaleProportionallyUpOrDown]; 1394 [superView addSubview: picture_view]; 1395 1396 /* Make the name label */ 1397 NSBundle *bundle = [NSBundle mainBundle]; 1398 if (bundle) { 1399 x = 0; 1400 y = y - 25; 1401 int name_width = about_width, name_height = 20; 1402 NSRect name_rect = NSMakeRect(x, y, name_width, name_height); 1403 NSTextField *name_label = [[NSTextField alloc] initWithFrame: name_rect]; 1404 [name_label setEditable: NO]; 1405 [name_label setBezeled: NO]; 1406 [name_label setDrawsBackground: NO]; 1407 [name_label setAlignment: NSTextAlignmentCenter]; 1408 NSString *qemu_name = [[bundle executablePath] lastPathComponent]; 1409 [name_label setStringValue: qemu_name]; 1410 [superView addSubview: name_label]; 1411 } 1412 1413 /* Set the version label's attributes */ 1414 x = 0; 1415 y = 50; 1416 int version_width = about_width, version_height = 20; 1417 NSRect version_rect = NSMakeRect(x, y, version_width, version_height); 1418 NSTextField *version_label = [[NSTextField alloc] initWithFrame: 1419 version_rect]; 1420 [version_label setEditable: NO]; 1421 [version_label setBezeled: NO]; 1422 [version_label setAlignment: NSTextAlignmentCenter]; 1423 [version_label setDrawsBackground: NO]; 1424 1425 /* Create the version string*/ 1426 NSString *version_string; 1427 version_string = [[NSString alloc] initWithFormat: 1428 @"QEMU emulator version %s", QEMU_FULL_VERSION]; 1429 [version_label setStringValue: version_string]; 1430 [superView addSubview: version_label]; 1431 1432 /* Make copyright label */ 1433 x = 0; 1434 y = 35; 1435 int copyright_width = about_width, copyright_height = 20; 1436 NSRect copyright_rect = NSMakeRect(x, y, copyright_width, copyright_height); 1437 NSTextField *copyright_label = [[NSTextField alloc] initWithFrame: 1438 copyright_rect]; 1439 [copyright_label setEditable: NO]; 1440 [copyright_label setBezeled: NO]; 1441 [copyright_label setDrawsBackground: NO]; 1442 [copyright_label setAlignment: NSTextAlignmentCenter]; 1443 [copyright_label setStringValue: [NSString stringWithFormat: @"%s", 1444 QEMU_COPYRIGHT]]; 1445 [superView addSubview: copyright_label]; 1446} 1447 1448/* Used by the Speed menu items */ 1449- (void)adjustSpeed:(id)sender 1450{ 1451 int throttle_pct; /* throttle percentage */ 1452 NSMenu *menu; 1453 1454 menu = [sender menu]; 1455 if (menu != nil) 1456 { 1457 /* Unselect the currently selected item */ 1458 for (NSMenuItem *item in [menu itemArray]) { 1459 if (item.state == NSControlStateValueOn) { 1460 [item setState: NSControlStateValueOff]; 1461 break; 1462 } 1463 } 1464 } 1465 1466 // check the menu item 1467 [sender setState: NSControlStateValueOn]; 1468 1469 // get the throttle percentage 1470 throttle_pct = [sender tag]; 1471 1472 with_iothread_lock(^{ 1473 cpu_throttle_set(throttle_pct); 1474 }); 1475 COCOA_DEBUG("cpu throttling at %d%c\n", cpu_throttle_get_percentage(), '%'); 1476} 1477 1478@end 1479 1480@interface QemuApplication : NSApplication 1481@end 1482 1483@implementation QemuApplication 1484- (void)sendEvent:(NSEvent *)event 1485{ 1486 COCOA_DEBUG("QemuApplication: sendEvent\n"); 1487 if (![cocoaView handleEvent:event]) { 1488 [super sendEvent: event]; 1489 } 1490} 1491@end 1492 1493static void create_initial_menus(void) 1494{ 1495 // Add menus 1496 NSMenu *menu; 1497 NSMenuItem *menuItem; 1498 1499 [NSApp setMainMenu:[[NSMenu alloc] init]]; 1500 1501 // Application menu 1502 menu = [[NSMenu alloc] initWithTitle:@""]; 1503 [menu addItemWithTitle:@"About QEMU" action:@selector(do_about_menu_item:) keyEquivalent:@""]; // About QEMU 1504 [menu addItem:[NSMenuItem separatorItem]]; //Separator 1505 [menu addItemWithTitle:@"Hide QEMU" action:@selector(hide:) keyEquivalent:@"h"]; //Hide QEMU 1506 menuItem = (NSMenuItem *)[menu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; // Hide Others 1507 [menuItem setKeyEquivalentModifierMask:(NSEventModifierFlagOption|NSEventModifierFlagCommand)]; 1508 [menu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; // Show All 1509 [menu addItem:[NSMenuItem separatorItem]]; //Separator 1510 [menu addItemWithTitle:@"Quit QEMU" action:@selector(terminate:) keyEquivalent:@"q"]; 1511 menuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" action:nil keyEquivalent:@""]; 1512 [menuItem setSubmenu:menu]; 1513 [[NSApp mainMenu] addItem:menuItem]; 1514 [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; // Workaround (this method is private since 10.4+) 1515 1516 // Machine menu 1517 menu = [[NSMenu alloc] initWithTitle: @"Machine"]; 1518 [menu setAutoenablesItems: NO]; 1519 [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Pause" action: @selector(pauseQEMU:) keyEquivalent: @""] autorelease]]; 1520 menuItem = [[[NSMenuItem alloc] initWithTitle: @"Resume" action: @selector(resumeQEMU:) keyEquivalent: @""] autorelease]; 1521 [menu addItem: menuItem]; 1522 [menuItem setEnabled: NO]; 1523 [menu addItem: [NSMenuItem separatorItem]]; 1524 [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Reset" action: @selector(restartQEMU:) keyEquivalent: @""] autorelease]]; 1525 [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Power Down" action: @selector(powerDownQEMU:) keyEquivalent: @""] autorelease]]; 1526 menuItem = [[[NSMenuItem alloc] initWithTitle: @"Machine" action:nil keyEquivalent:@""] autorelease]; 1527 [menuItem setSubmenu:menu]; 1528 [[NSApp mainMenu] addItem:menuItem]; 1529 1530 // View menu 1531 menu = [[NSMenu alloc] initWithTitle:@"View"]; 1532 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(doToggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen 1533 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Zoom To Fit" action:@selector(zoomToFit:) keyEquivalent:@""] autorelease]]; 1534 menuItem = [[[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""] autorelease]; 1535 [menuItem setSubmenu:menu]; 1536 [[NSApp mainMenu] addItem:menuItem]; 1537 1538 // Speed menu 1539 menu = [[NSMenu alloc] initWithTitle:@"Speed"]; 1540 1541 // Add the rest of the Speed menu items 1542 int p, percentage, throttle_pct; 1543 for (p = 10; p >= 0; p--) 1544 { 1545 percentage = p * 10 > 1 ? p * 10 : 1; // prevent a 0% menu item 1546 1547 menuItem = [[[NSMenuItem alloc] 1548 initWithTitle: [NSString stringWithFormat: @"%d%%", percentage] action:@selector(adjustSpeed:) keyEquivalent:@""] autorelease]; 1549 1550 if (percentage == 100) { 1551 [menuItem setState: NSControlStateValueOn]; 1552 } 1553 1554 /* Calculate the throttle percentage */ 1555 throttle_pct = -1 * percentage + 100; 1556 1557 [menuItem setTag: throttle_pct]; 1558 [menu addItem: menuItem]; 1559 } 1560 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Speed" action:nil keyEquivalent:@""] autorelease]; 1561 [menuItem setSubmenu:menu]; 1562 [[NSApp mainMenu] addItem:menuItem]; 1563 1564 // Window menu 1565 menu = [[NSMenu alloc] initWithTitle:@"Window"]; 1566 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease]]; // Miniaturize 1567 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease]; 1568 [menuItem setSubmenu:menu]; 1569 [[NSApp mainMenu] addItem:menuItem]; 1570 [NSApp setWindowsMenu:menu]; 1571 1572 // Help menu 1573 menu = [[NSMenu alloc] initWithTitle:@"Help"]; 1574 [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Documentation" action:@selector(showQEMUDoc:) keyEquivalent:@"?"] autorelease]]; // QEMU Help 1575 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease]; 1576 [menuItem setSubmenu:menu]; 1577 [[NSApp mainMenu] addItem:menuItem]; 1578} 1579 1580/* Returns a name for a given console */ 1581static NSString * getConsoleName(QemuConsole * console) 1582{ 1583 return [NSString stringWithFormat: @"%s", qemu_console_get_label(console)]; 1584} 1585 1586/* Add an entry to the View menu for each console */ 1587static void add_console_menu_entries(void) 1588{ 1589 NSMenu *menu; 1590 NSMenuItem *menuItem; 1591 int index = 0; 1592 1593 menu = [[[NSApp mainMenu] itemWithTitle:@"View"] submenu]; 1594 1595 [menu addItem:[NSMenuItem separatorItem]]; 1596 1597 while (qemu_console_lookup_by_index(index) != NULL) { 1598 menuItem = [[[NSMenuItem alloc] initWithTitle: getConsoleName(qemu_console_lookup_by_index(index)) 1599 action: @selector(displayConsole:) keyEquivalent: @""] autorelease]; 1600 [menuItem setTag: index]; 1601 [menu addItem: menuItem]; 1602 index++; 1603 } 1604} 1605 1606/* Make menu items for all removable devices. 1607 * Each device is given an 'Eject' and 'Change' menu item. 1608 */ 1609static void addRemovableDevicesMenuItems(void) 1610{ 1611 NSMenu *menu; 1612 NSMenuItem *menuItem; 1613 BlockInfoList *currentDevice, *pointerToFree; 1614 NSString *deviceName; 1615 1616 currentDevice = qmp_query_block(NULL); 1617 pointerToFree = currentDevice; 1618 if(currentDevice == NULL) { 1619 NSBeep(); 1620 QEMU_Alert(@"Failed to query for block devices!"); 1621 return; 1622 } 1623 1624 menu = [[[NSApp mainMenu] itemWithTitle:@"Machine"] submenu]; 1625 1626 // Add a separator between related groups of menu items 1627 [menu addItem:[NSMenuItem separatorItem]]; 1628 1629 // Set the attributes to the "Removable Media" menu item 1630 NSString *titleString = @"Removable Media"; 1631 NSMutableAttributedString *attString=[[NSMutableAttributedString alloc] initWithString:titleString]; 1632 NSColor *newColor = [NSColor blackColor]; 1633 NSFontManager *fontManager = [NSFontManager sharedFontManager]; 1634 NSFont *font = [fontManager fontWithFamily:@"Helvetica" 1635 traits:NSBoldFontMask|NSItalicFontMask 1636 weight:0 1637 size:14]; 1638 [attString addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, [titleString length])]; 1639 [attString addAttribute:NSForegroundColorAttributeName value:newColor range:NSMakeRange(0, [titleString length])]; 1640 [attString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInt: 1] range:NSMakeRange(0, [titleString length])]; 1641 1642 // Add the "Removable Media" menu item 1643 menuItem = [NSMenuItem new]; 1644 [menuItem setAttributedTitle: attString]; 1645 [menuItem setEnabled: NO]; 1646 [menu addItem: menuItem]; 1647 1648 /* Loop through all the block devices in the emulator */ 1649 while (currentDevice) { 1650 deviceName = [[NSString stringWithFormat: @"%s", currentDevice->value->device] retain]; 1651 1652 if(currentDevice->value->removable) { 1653 menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Change %s...", currentDevice->value->device] 1654 action: @selector(changeDeviceMedia:) 1655 keyEquivalent: @""]; 1656 [menu addItem: menuItem]; 1657 [menuItem setRepresentedObject: deviceName]; 1658 [menuItem autorelease]; 1659 1660 menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Eject %s", currentDevice->value->device] 1661 action: @selector(ejectDeviceMedia:) 1662 keyEquivalent: @""]; 1663 [menu addItem: menuItem]; 1664 [menuItem setRepresentedObject: deviceName]; 1665 [menuItem autorelease]; 1666 } 1667 currentDevice = currentDevice->next; 1668 } 1669 qapi_free_BlockInfoList(pointerToFree); 1670} 1671 1672/* 1673 * The startup process for the OSX/Cocoa UI is complicated, because 1674 * OSX insists that the UI runs on the initial main thread, and so we 1675 * need to start a second thread which runs the vl.c qemu_main(): 1676 * 1677 * Initial thread: 2nd thread: 1678 * in main(): 1679 * create qemu-main thread 1680 * wait on display_init semaphore 1681 * call qemu_main() 1682 * ... 1683 * in cocoa_display_init(): 1684 * post the display_init semaphore 1685 * wait on app_started semaphore 1686 * create application, menus, etc 1687 * enter OSX run loop 1688 * in applicationDidFinishLaunching: 1689 * post app_started semaphore 1690 * tell main thread to fullscreen if needed 1691 * [...] 1692 * run qemu main-loop 1693 * 1694 * We do this in two stages so that we don't do the creation of the 1695 * GUI application menus and so on for command line options like --help 1696 * where we want to just print text to stdout and exit immediately. 1697 */ 1698 1699static void *call_qemu_main(void *opaque) 1700{ 1701 int status; 1702 1703 COCOA_DEBUG("Second thread: calling qemu_main()\n"); 1704 status = qemu_main(gArgc, gArgv, *_NSGetEnviron()); 1705 COCOA_DEBUG("Second thread: qemu_main() returned, exiting\n"); 1706 exit(status); 1707} 1708 1709int main (int argc, const char * argv[]) { 1710 QemuThread thread; 1711 1712 COCOA_DEBUG("Entered main()\n"); 1713 gArgc = argc; 1714 gArgv = (char **)argv; 1715 1716 qemu_sem_init(&display_init_sem, 0); 1717 qemu_sem_init(&app_started_sem, 0); 1718 1719 qemu_thread_create(&thread, "qemu_main", call_qemu_main, 1720 NULL, QEMU_THREAD_DETACHED); 1721 1722 COCOA_DEBUG("Main thread: waiting for display_init_sem\n"); 1723 qemu_sem_wait(&display_init_sem); 1724 COCOA_DEBUG("Main thread: initializing app\n"); 1725 1726 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 1727 1728 // Pull this console process up to being a fully-fledged graphical 1729 // app with a menubar and Dock icon 1730 ProcessSerialNumber psn = { 0, kCurrentProcess }; 1731 TransformProcessType(&psn, kProcessTransformToForegroundApplication); 1732 1733 [QemuApplication sharedApplication]; 1734 1735 create_initial_menus(); 1736 1737 /* 1738 * Create the menu entries which depend on QEMU state (for consoles 1739 * and removeable devices). These make calls back into QEMU functions, 1740 * which is OK because at this point we know that the second thread 1741 * holds the iothread lock and is synchronously waiting for us to 1742 * finish. 1743 */ 1744 add_console_menu_entries(); 1745 addRemovableDevicesMenuItems(); 1746 1747 // Create an Application controller 1748 QemuCocoaAppController *appController = [[QemuCocoaAppController alloc] init]; 1749 [NSApp setDelegate:appController]; 1750 1751 // Start the main event loop 1752 COCOA_DEBUG("Main thread: entering OSX run loop\n"); 1753 [NSApp run]; 1754 COCOA_DEBUG("Main thread: left OSX run loop, exiting\n"); 1755 1756 [appController release]; 1757 [pool release]; 1758 1759 return 0; 1760} 1761 1762 1763 1764#pragma mark qemu 1765static void cocoa_update(DisplayChangeListener *dcl, 1766 int x, int y, int w, int h) 1767{ 1768 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 1769 1770 COCOA_DEBUG("qemu_cocoa: cocoa_update\n"); 1771 1772 dispatch_async(dispatch_get_main_queue(), ^{ 1773 NSRect rect; 1774 if ([cocoaView cdx] == 1.0) { 1775 rect = NSMakeRect(x, [cocoaView gscreen].height - y - h, w, h); 1776 } else { 1777 rect = NSMakeRect( 1778 x * [cocoaView cdx], 1779 ([cocoaView gscreen].height - y - h) * [cocoaView cdy], 1780 w * [cocoaView cdx], 1781 h * [cocoaView cdy]); 1782 } 1783 [cocoaView setNeedsDisplayInRect:rect]; 1784 }); 1785 1786 [pool release]; 1787} 1788 1789static void cocoa_switch(DisplayChangeListener *dcl, 1790 DisplaySurface *surface) 1791{ 1792 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 1793 pixman_image_t *image = surface->image; 1794 1795 COCOA_DEBUG("qemu_cocoa: cocoa_switch\n"); 1796 1797 // The DisplaySurface will be freed as soon as this callback returns. 1798 // We take a reference to the underlying pixman image here so it does 1799 // not disappear from under our feet; the switchSurface method will 1800 // deref the old image when it is done with it. 1801 pixman_image_ref(image); 1802 1803 dispatch_async(dispatch_get_main_queue(), ^{ 1804 [cocoaView switchSurface:image]; 1805 }); 1806 [pool release]; 1807} 1808 1809static void cocoa_refresh(DisplayChangeListener *dcl) 1810{ 1811 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 1812 1813 COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n"); 1814 graphic_hw_update(NULL); 1815 1816 if (qemu_input_is_absolute()) { 1817 dispatch_async(dispatch_get_main_queue(), ^{ 1818 if (![cocoaView isAbsoluteEnabled]) { 1819 if ([cocoaView isMouseGrabbed]) { 1820 [cocoaView ungrabMouse]; 1821 } 1822 } 1823 [cocoaView setAbsoluteEnabled:YES]; 1824 }); 1825 } 1826 [pool release]; 1827} 1828 1829static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts) 1830{ 1831 COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n"); 1832 1833 /* Tell main thread to go ahead and create the app and enter the run loop */ 1834 qemu_sem_post(&display_init_sem); 1835 qemu_sem_wait(&app_started_sem); 1836 COCOA_DEBUG("cocoa_display_init: app start completed\n"); 1837 1838 /* if fullscreen mode is to be used */ 1839 if (opts->has_full_screen && opts->full_screen) { 1840 dispatch_async(dispatch_get_main_queue(), ^{ 1841 [NSApp activateIgnoringOtherApps: YES]; 1842 [(QemuCocoaAppController *)[[NSApplication sharedApplication] delegate] toggleFullScreen: nil]; 1843 }); 1844 } 1845 if (opts->has_show_cursor && opts->show_cursor) { 1846 cursor_hide = 0; 1847 } 1848 1849 // register vga output callbacks 1850 register_displaychangelistener(&dcl); 1851} 1852 1853static QemuDisplay qemu_display_cocoa = { 1854 .type = DISPLAY_TYPE_COCOA, 1855 .init = cocoa_display_init, 1856}; 1857 1858static void register_cocoa(void) 1859{ 1860 qemu_display_register(&qemu_display_cocoa); 1861} 1862 1863type_init(register_cocoa); 1864