cachepc-qemu

Fork of AMDESE/qemu with changes for cachepc side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-qemu
Log | Files | Refs | Submodules | LICENSE | sfeed.txt

cocoa.m (70742B)


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