xref: /openbmc/qemu/ui/cocoa.m (revision 0b2ff2ce)
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#import <Cocoa/Cocoa.h>
26#include <crt_externs.h>
27
28#include "qemu-common.h"
29#include "ui/console.h"
30#include "ui/input.h"
31#include "sysemu/sysemu.h"
32
33#ifndef MAC_OS_X_VERSION_10_5
34#define MAC_OS_X_VERSION_10_5 1050
35#endif
36#ifndef MAC_OS_X_VERSION_10_6
37#define MAC_OS_X_VERSION_10_6 1060
38#endif
39#ifndef MAC_OS_X_VERSION_10_10
40#define MAC_OS_X_VERSION_10_10 101000
41#endif
42
43
44//#define DEBUG
45
46#ifdef DEBUG
47#define COCOA_DEBUG(...)  { (void) fprintf (stdout, __VA_ARGS__); }
48#else
49#define COCOA_DEBUG(...)  ((void) 0)
50#endif
51
52#define cgrect(nsrect) (*(CGRect *)&(nsrect))
53
54typedef struct {
55    int width;
56    int height;
57    int bitsPerComponent;
58    int bitsPerPixel;
59} QEMUScreen;
60
61NSWindow *normalWindow;
62static DisplayChangeListener *dcl;
63static int last_buttons;
64
65int gArgc;
66char **gArgv;
67bool stretch_video;
68
69// keymap conversion
70int keymap[] =
71{
72//  SdlI    macI    macH    SdlH    104xtH  104xtC  sdl
73    30, //  0       0x00    0x1e            A       QZ_a
74    31, //  1       0x01    0x1f            S       QZ_s
75    32, //  2       0x02    0x20            D       QZ_d
76    33, //  3       0x03    0x21            F       QZ_f
77    35, //  4       0x04    0x23            H       QZ_h
78    34, //  5       0x05    0x22            G       QZ_g
79    44, //  6       0x06    0x2c            Z       QZ_z
80    45, //  7       0x07    0x2d            X       QZ_x
81    46, //  8       0x08    0x2e            C       QZ_c
82    47, //  9       0x09    0x2f            V       QZ_v
83    0,  //  10      0x0A    Undefined
84    48, //  11      0x0B    0x30            B       QZ_b
85    16, //  12      0x0C    0x10            Q       QZ_q
86    17, //  13      0x0D    0x11            W       QZ_w
87    18, //  14      0x0E    0x12            E       QZ_e
88    19, //  15      0x0F    0x13            R       QZ_r
89    21, //  16      0x10    0x15            Y       QZ_y
90    20, //  17      0x11    0x14            T       QZ_t
91    2,  //  18      0x12    0x02            1       QZ_1
92    3,  //  19      0x13    0x03            2       QZ_2
93    4,  //  20      0x14    0x04            3       QZ_3
94    5,  //  21      0x15    0x05            4       QZ_4
95    7,  //  22      0x16    0x07            6       QZ_6
96    6,  //  23      0x17    0x06            5       QZ_5
97    13, //  24      0x18    0x0d            =       QZ_EQUALS
98    10, //  25      0x19    0x0a            9       QZ_9
99    8,  //  26      0x1A    0x08            7       QZ_7
100    12, //  27      0x1B    0x0c            -       QZ_MINUS
101    9,  //  28      0x1C    0x09            8       QZ_8
102    11, //  29      0x1D    0x0b            0       QZ_0
103    27, //  30      0x1E    0x1b            ]       QZ_RIGHTBRACKET
104    24, //  31      0x1F    0x18            O       QZ_o
105    22, //  32      0x20    0x16            U       QZ_u
106    26, //  33      0x21    0x1a            [       QZ_LEFTBRACKET
107    23, //  34      0x22    0x17            I       QZ_i
108    25, //  35      0x23    0x19            P       QZ_p
109    28, //  36      0x24    0x1c            ENTER   QZ_RETURN
110    38, //  37      0x25    0x26            L       QZ_l
111    36, //  38      0x26    0x24            J       QZ_j
112    40, //  39      0x27    0x28            '       QZ_QUOTE
113    37, //  40      0x28    0x25            K       QZ_k
114    39, //  41      0x29    0x27            ;       QZ_SEMICOLON
115    43, //  42      0x2A    0x2b            \       QZ_BACKSLASH
116    51, //  43      0x2B    0x33            ,       QZ_COMMA
117    53, //  44      0x2C    0x35            /       QZ_SLASH
118    49, //  45      0x2D    0x31            N       QZ_n
119    50, //  46      0x2E    0x32            M       QZ_m
120    52, //  47      0x2F    0x34            .       QZ_PERIOD
121    15, //  48      0x30    0x0f            TAB     QZ_TAB
122    57, //  49      0x31    0x39            SPACE   QZ_SPACE
123    41, //  50      0x32    0x29            `       QZ_BACKQUOTE
124    14, //  51      0x33    0x0e            BKSP    QZ_BACKSPACE
125    0,  //  52      0x34    Undefined
126    1,  //  53      0x35    0x01            ESC     QZ_ESCAPE
127    220, // 54      0x36    0xdc    E0,5C   R GUI   QZ_RMETA
128    219, // 55      0x37    0xdb    E0,5B   L GUI   QZ_LMETA
129    42, //  56      0x38    0x2a            L SHFT  QZ_LSHIFT
130    58, //  57      0x39    0x3a            CAPS    QZ_CAPSLOCK
131    56, //  58      0x3A    0x38            L ALT   QZ_LALT
132    29, //  59      0x3B    0x1d            L CTRL  QZ_LCTRL
133    54, //  60      0x3C    0x36            R SHFT  QZ_RSHIFT
134    184,//  61      0x3D    0xb8    E0,38   R ALT   QZ_RALT
135    157,//  62      0x3E    0x9d    E0,1D   R CTRL  QZ_RCTRL
136    0,  //  63      0x3F    Undefined
137    0,  //  64      0x40    Undefined
138    0,  //  65      0x41    Undefined
139    0,  //  66      0x42    Undefined
140    55, //  67      0x43    0x37            KP *    QZ_KP_MULTIPLY
141    0,  //  68      0x44    Undefined
142    78, //  69      0x45    0x4e            KP +    QZ_KP_PLUS
143    0,  //  70      0x46    Undefined
144    69, //  71      0x47    0x45            NUM     QZ_NUMLOCK
145    0,  //  72      0x48    Undefined
146    0,  //  73      0x49    Undefined
147    0,  //  74      0x4A    Undefined
148    181,//  75      0x4B    0xb5    E0,35   KP /    QZ_KP_DIVIDE
149    152,//  76      0x4C    0x9c    E0,1C   KP EN   QZ_KP_ENTER
150    0,  //  77      0x4D    undefined
151    74, //  78      0x4E    0x4a            KP -    QZ_KP_MINUS
152    0,  //  79      0x4F    Undefined
153    0,  //  80      0x50    Undefined
154    0,  //  81      0x51                            QZ_KP_EQUALS
155    82, //  82      0x52    0x52            KP 0    QZ_KP0
156    79, //  83      0x53    0x4f            KP 1    QZ_KP1
157    80, //  84      0x54    0x50            KP 2    QZ_KP2
158    81, //  85      0x55    0x51            KP 3    QZ_KP3
159    75, //  86      0x56    0x4b            KP 4    QZ_KP4
160    76, //  87      0x57    0x4c            KP 5    QZ_KP5
161    77, //  88      0x58    0x4d            KP 6    QZ_KP6
162    71, //  89      0x59    0x47            KP 7    QZ_KP7
163    0,  //  90      0x5A    Undefined
164    72, //  91      0x5B    0x48            KP 8    QZ_KP8
165    73, //  92      0x5C    0x49            KP 9    QZ_KP9
166    0,  //  93      0x5D    Undefined
167    0,  //  94      0x5E    Undefined
168    0,  //  95      0x5F    Undefined
169    63, //  96      0x60    0x3f            F5      QZ_F5
170    64, //  97      0x61    0x40            F6      QZ_F6
171    65, //  98      0x62    0x41            F7      QZ_F7
172    61, //  99      0x63    0x3d            F3      QZ_F3
173    66, //  100     0x64    0x42            F8      QZ_F8
174    67, //  101     0x65    0x43            F9      QZ_F9
175    0,  //  102     0x66    Undefined
176    87, //  103     0x67    0x57            F11     QZ_F11
177    0,  //  104     0x68    Undefined
178    183,//  105     0x69    0xb7                    QZ_PRINT
179    0,  //  106     0x6A    Undefined
180    70, //  107     0x6B    0x46            SCROLL  QZ_SCROLLOCK
181    0,  //  108     0x6C    Undefined
182    68, //  109     0x6D    0x44            F10     QZ_F10
183    0,  //  110     0x6E    Undefined
184    88, //  111     0x6F    0x58            F12     QZ_F12
185    0,  //  112     0x70    Undefined
186    110,//  113     0x71    0x0                     QZ_PAUSE
187    210,//  114     0x72    0xd2    E0,52   INSERT  QZ_INSERT
188    199,//  115     0x73    0xc7    E0,47   HOME    QZ_HOME
189    201,//  116     0x74    0xc9    E0,49   PG UP   QZ_PAGEUP
190    211,//  117     0x75    0xd3    E0,53   DELETE  QZ_DELETE
191    62, //  118     0x76    0x3e            F4      QZ_F4
192    207,//  119     0x77    0xcf    E0,4f   END     QZ_END
193    60, //  120     0x78    0x3c            F2      QZ_F2
194    209,//  121     0x79    0xd1    E0,51   PG DN   QZ_PAGEDOWN
195    59, //  122     0x7A    0x3b            F1      QZ_F1
196    203,//  123     0x7B    0xcb    e0,4B   L ARROW QZ_LEFT
197    205,//  124     0x7C    0xcd    e0,4D   R ARROW QZ_RIGHT
198    208,//  125     0x7D    0xd0    E0,50   D ARROW QZ_DOWN
199    200,//  126     0x7E    0xc8    E0,48   U ARROW QZ_UP
200/* completed according to http://www.libsdl.org/cgi/cvsweb.cgi/SDL12/src/video/quartz/SDL_QuartzKeys.h?rev=1.6&content-type=text/x-cvsweb-markup */
201
202/* Additional 104 Key XP-Keyboard Scancodes from http://www.computer-engineering.org/ps2keyboard/scancodes1.html */
203/*
204    221 //          0xdd            e0,5d   APPS
205        //              E0,2A,E0,37         PRNT SCRN
206        //              E1,1D,45,E1,9D,C5   PAUSE
207    83  //          0x53    0x53            KP .
208// ACPI Scan Codes
209    222 //          0xde            E0, 5E  Power
210    223 //          0xdf            E0, 5F  Sleep
211    227 //          0xe3            E0, 63  Wake
212// Windows Multimedia Scan Codes
213    153 //          0x99            E0, 19  Next Track
214    144 //          0x90            E0, 10  Previous Track
215    164 //          0xa4            E0, 24  Stop
216    162 //          0xa2            E0, 22  Play/Pause
217    160 //          0xa0            E0, 20  Mute
218    176 //          0xb0            E0, 30  Volume Up
219    174 //          0xae            E0, 2E  Volume Down
220    237 //          0xed            E0, 6D  Media Select
221    236 //          0xec            E0, 6C  E-Mail
222    161 //          0xa1            E0, 21  Calculator
223    235 //          0xeb            E0, 6B  My Computer
224    229 //          0xe5            E0, 65  WWW Search
225    178 //          0xb2            E0, 32  WWW Home
226    234 //          0xea            E0, 6A  WWW Back
227    233 //          0xe9            E0, 69  WWW Forward
228    232 //          0xe8            E0, 68  WWW Stop
229    231 //          0xe7            E0, 67  WWW Refresh
230    230 //          0xe6            E0, 66  WWW Favorites
231*/
232};
233
234static int cocoa_keycode_to_qemu(int keycode)
235{
236    if (ARRAY_SIZE(keymap) <= keycode) {
237        fprintf(stderr, "(cocoa) warning unknown keycode 0x%x\n", keycode);
238        return 0;
239    }
240    return keymap[keycode];
241}
242
243
244
245/*
246 ------------------------------------------------------
247    QemuCocoaView
248 ------------------------------------------------------
249*/
250@interface QemuCocoaView : NSView
251{
252    QEMUScreen screen;
253    NSWindow *fullScreenWindow;
254    float cx,cy,cw,ch,cdx,cdy;
255    CGDataProviderRef dataProviderRef;
256    int modifiers_state[256];
257    BOOL isMouseGrabbed;
258    BOOL isFullscreen;
259    BOOL isAbsoluteEnabled;
260    BOOL isMouseDeassociated;
261}
262- (void) switchSurface:(DisplaySurface *)surface;
263- (void) grabMouse;
264- (void) ungrabMouse;
265- (void) toggleFullScreen:(id)sender;
266- (void) handleEvent:(NSEvent *)event;
267- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled;
268/* The state surrounding mouse grabbing is potentially confusing.
269 * isAbsoluteEnabled tracks qemu_input_is_absolute() [ie "is the emulated
270 *   pointing device an absolute-position one?"], but is only updated on
271 *   next refresh.
272 * isMouseGrabbed tracks whether GUI events are directed to the guest;
273 *   it controls whether special keys like Cmd get sent to the guest,
274 *   and whether we capture the mouse when in non-absolute mode.
275 * isMouseDeassociated tracks whether we've told MacOSX to disassociate
276 *   the mouse and mouse cursor position by calling
277 *   CGAssociateMouseAndMouseCursorPosition(FALSE)
278 *   (which basically happens if we grab in non-absolute mode).
279 */
280- (BOOL) isMouseGrabbed;
281- (BOOL) isAbsoluteEnabled;
282- (BOOL) isMouseDeassociated;
283- (float) cdx;
284- (float) cdy;
285- (QEMUScreen) gscreen;
286@end
287
288QemuCocoaView *cocoaView;
289
290@implementation QemuCocoaView
291- (id)initWithFrame:(NSRect)frameRect
292{
293    COCOA_DEBUG("QemuCocoaView: initWithFrame\n");
294
295    self = [super initWithFrame:frameRect];
296    if (self) {
297
298        screen.bitsPerComponent = 8;
299        screen.bitsPerPixel = 32;
300        screen.width = frameRect.size.width;
301        screen.height = frameRect.size.height;
302
303    }
304    return self;
305}
306
307- (void) dealloc
308{
309    COCOA_DEBUG("QemuCocoaView: dealloc\n");
310
311    if (dataProviderRef)
312        CGDataProviderRelease(dataProviderRef);
313
314    [super dealloc];
315}
316
317- (BOOL) isOpaque
318{
319    return YES;
320}
321
322- (BOOL) screenContainsPoint:(NSPoint) p
323{
324    return (p.x > -1 && p.x < screen.width && p.y > -1 && p.y < screen.height);
325}
326
327- (void) hideCursor
328{
329    if (!cursor_hide) {
330        return;
331    }
332    [NSCursor hide];
333}
334
335- (void) unhideCursor
336{
337    if (!cursor_hide) {
338        return;
339    }
340    [NSCursor unhide];
341}
342
343- (void) drawRect:(NSRect) rect
344{
345    COCOA_DEBUG("QemuCocoaView: drawRect\n");
346
347    // get CoreGraphic context
348    CGContextRef viewContextRef = [[NSGraphicsContext currentContext] graphicsPort];
349    CGContextSetInterpolationQuality (viewContextRef, kCGInterpolationNone);
350    CGContextSetShouldAntialias (viewContextRef, NO);
351
352    // draw screen bitmap directly to Core Graphics context
353    if (!dataProviderRef) {
354        // Draw request before any guest device has set up a framebuffer:
355        // just draw an opaque black rectangle
356        CGContextSetRGBFillColor(viewContextRef, 0, 0, 0, 1.0);
357        CGContextFillRect(viewContextRef, NSRectToCGRect(rect));
358    } else {
359        CGImageRef imageRef = CGImageCreate(
360            screen.width, //width
361            screen.height, //height
362            screen.bitsPerComponent, //bitsPerComponent
363            screen.bitsPerPixel, //bitsPerPixel
364            (screen.width * (screen.bitsPerComponent/2)), //bytesPerRow
365#ifdef __LITTLE_ENDIAN__
366            CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB), //colorspace for OS X >= 10.4
367            kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst,
368#else
369            CGColorSpaceCreateDeviceRGB(), //colorspace for OS X < 10.4 (actually ppc)
370            kCGImageAlphaNoneSkipFirst, //bitmapInfo
371#endif
372            dataProviderRef, //provider
373            NULL, //decode
374            0, //interpolate
375            kCGRenderingIntentDefault //intent
376        );
377        // selective drawing code (draws only dirty rectangles) (OS X >= 10.4)
378        const NSRect *rectList;
379        NSInteger rectCount;
380        int i;
381        CGImageRef clipImageRef;
382        CGRect clipRect;
383
384        [self getRectsBeingDrawn:&rectList count:&rectCount];
385        for (i = 0; i < rectCount; i++) {
386            clipRect.origin.x = rectList[i].origin.x / cdx;
387            clipRect.origin.y = (float)screen.height - (rectList[i].origin.y + rectList[i].size.height) / cdy;
388            clipRect.size.width = rectList[i].size.width / cdx;
389            clipRect.size.height = rectList[i].size.height / cdy;
390            clipImageRef = CGImageCreateWithImageInRect(
391                                                        imageRef,
392                                                        clipRect
393                                                        );
394            CGContextDrawImage (viewContextRef, cgrect(rectList[i]), clipImageRef);
395            CGImageRelease (clipImageRef);
396        }
397        CGImageRelease (imageRef);
398    }
399}
400
401- (void) setContentDimensions
402{
403    COCOA_DEBUG("QemuCocoaView: setContentDimensions\n");
404
405    if (isFullscreen) {
406        cdx = [[NSScreen mainScreen] frame].size.width / (float)screen.width;
407        cdy = [[NSScreen mainScreen] frame].size.height / (float)screen.height;
408
409        /* stretches video, but keeps same aspect ratio */
410        if (stretch_video == true) {
411            /* use smallest stretch value - prevents clipping on sides */
412            if (MIN(cdx, cdy) == cdx) {
413                cdy = cdx;
414            } else {
415                cdx = cdy;
416            }
417        } else {  /* No stretching */
418            cdx = cdy = 1;
419        }
420        cw = screen.width * cdx;
421        ch = screen.height * cdy;
422        cx = ([[NSScreen mainScreen] frame].size.width - cw) / 2.0;
423        cy = ([[NSScreen mainScreen] frame].size.height - ch) / 2.0;
424    } else {
425        cx = 0;
426        cy = 0;
427        cw = screen.width;
428        ch = screen.height;
429        cdx = 1.0;
430        cdy = 1.0;
431    }
432}
433
434- (void) switchSurface:(DisplaySurface *)surface
435{
436    COCOA_DEBUG("QemuCocoaView: switchSurface\n");
437
438    int w = surface_width(surface);
439    int h = surface_height(surface);
440    /* cdx == 0 means this is our very first surface, in which case we need
441     * to recalculate the content dimensions even if it happens to be the size
442     * of the initial empty window.
443     */
444    bool isResize = (w != screen.width || h != screen.height || cdx == 0.0);
445
446    int oldh = screen.height;
447    if (isResize) {
448        // Resize before we trigger the redraw, or we'll redraw at the wrong size
449        COCOA_DEBUG("switchSurface: new size %d x %d\n", w, h);
450        screen.width = w;
451        screen.height = h;
452        [self setContentDimensions];
453        [self setFrame:NSMakeRect(cx, cy, cw, ch)];
454    }
455
456    // update screenBuffer
457    if (dataProviderRef)
458        CGDataProviderRelease(dataProviderRef);
459
460    //sync host window color space with guests
461    screen.bitsPerPixel = surface_bits_per_pixel(surface);
462    screen.bitsPerComponent = surface_bytes_per_pixel(surface) * 2;
463
464    dataProviderRef = CGDataProviderCreateWithData(NULL, surface_data(surface), w * 4 * h, NULL);
465
466    // update windows
467    if (isFullscreen) {
468        [[fullScreenWindow contentView] setFrame:[[NSScreen mainScreen] frame]];
469        [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + oldh, w, h + [normalWindow frame].size.height - oldh) display:NO animate:NO];
470    } else {
471        if (qemu_name)
472            [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]];
473        [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + oldh, w, h + [normalWindow frame].size.height - oldh) display:YES animate:NO];
474    }
475
476    if (isResize) {
477        [normalWindow center];
478    }
479}
480
481- (void) toggleFullScreen:(id)sender
482{
483    COCOA_DEBUG("QemuCocoaView: toggleFullScreen\n");
484
485    if (isFullscreen) { // switch from fullscreen to desktop
486        isFullscreen = FALSE;
487        [self ungrabMouse];
488        [self setContentDimensions];
489        if ([NSView respondsToSelector:@selector(exitFullScreenModeWithOptions:)]) { // test if "exitFullScreenModeWithOptions" is supported on host at runtime
490            [self exitFullScreenModeWithOptions:nil];
491        } else {
492            [fullScreenWindow close];
493            [normalWindow setContentView: self];
494            [normalWindow makeKeyAndOrderFront: self];
495            [NSMenu setMenuBarVisible:YES];
496        }
497    } else { // switch from desktop to fullscreen
498        isFullscreen = TRUE;
499        [normalWindow orderOut: nil]; /* Hide the window */
500        [self grabMouse];
501        [self setContentDimensions];
502        if ([NSView respondsToSelector:@selector(enterFullScreenMode:withOptions:)]) { // test if "enterFullScreenMode:withOptions" is supported on host at runtime
503            [self enterFullScreenMode:[NSScreen mainScreen] withOptions:[NSDictionary dictionaryWithObjectsAndKeys:
504                [NSNumber numberWithBool:NO], NSFullScreenModeAllScreens,
505                [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO], kCGDisplayModeIsStretched, nil], NSFullScreenModeSetting,
506                 nil]];
507        } else {
508            [NSMenu setMenuBarVisible:NO];
509            fullScreenWindow = [[NSWindow alloc] initWithContentRect:[[NSScreen mainScreen] frame]
510                styleMask:NSBorderlessWindowMask
511                backing:NSBackingStoreBuffered
512                defer:NO];
513            [fullScreenWindow setAcceptsMouseMovedEvents: YES];
514            [fullScreenWindow setHasShadow:NO];
515            [fullScreenWindow setBackgroundColor: [NSColor blackColor]];
516            [self setFrame:NSMakeRect(cx, cy, cw, ch)];
517            [[fullScreenWindow contentView] addSubview: self];
518            [fullScreenWindow makeKeyAndOrderFront:self];
519        }
520    }
521}
522
523- (void) handleEvent:(NSEvent *)event
524{
525    COCOA_DEBUG("QemuCocoaView: handleEvent\n");
526
527    int buttons = 0;
528    int keycode;
529    bool mouse_event = false;
530    NSPoint p = [event locationInWindow];
531
532    switch ([event type]) {
533        case NSFlagsChanged:
534            keycode = cocoa_keycode_to_qemu([event keyCode]);
535
536            if ((keycode == 219 || keycode == 220) && !isMouseGrabbed) {
537              /* Don't pass command key changes to guest unless mouse is grabbed */
538              keycode = 0;
539            }
540
541            if (keycode) {
542                if (keycode == 58 || keycode == 69) { // emulate caps lock and num lock keydown and keyup
543                    qemu_input_event_send_key_number(dcl->con, keycode, true);
544                    qemu_input_event_send_key_number(dcl->con, keycode, false);
545                } else if (qemu_console_is_graphic(NULL)) {
546                    if (modifiers_state[keycode] == 0) { // keydown
547                        qemu_input_event_send_key_number(dcl->con, keycode, true);
548                        modifiers_state[keycode] = 1;
549                    } else { // keyup
550                        qemu_input_event_send_key_number(dcl->con, keycode, false);
551                        modifiers_state[keycode] = 0;
552                    }
553                }
554            }
555
556            // release Mouse grab when pressing ctrl+alt
557            if (([event modifierFlags] & NSControlKeyMask) && ([event modifierFlags] & NSAlternateKeyMask)) {
558                [self ungrabMouse];
559            }
560            break;
561        case NSKeyDown:
562            keycode = cocoa_keycode_to_qemu([event keyCode]);
563
564            // forward command key combos to the host UI unless the mouse is grabbed
565            if (!isMouseGrabbed && ([event modifierFlags] & NSCommandKeyMask)) {
566                [NSApp sendEvent:event];
567                return;
568            }
569
570            // default
571
572            // handle control + alt Key Combos (ctrl+alt is reserved for QEMU)
573            if (([event modifierFlags] & NSControlKeyMask) && ([event modifierFlags] & NSAlternateKeyMask)) {
574                switch (keycode) {
575
576                    // enable graphic console
577                    case 0x02 ... 0x0a: // '1' to '9' keys
578                        console_select(keycode - 0x02);
579                        break;
580                }
581
582            // handle keys for graphic console
583            } else if (qemu_console_is_graphic(NULL)) {
584                qemu_input_event_send_key_number(dcl->con, keycode, true);
585
586            // handlekeys for Monitor
587            } else {
588                int keysym = 0;
589                switch([event keyCode]) {
590                case 115:
591                    keysym = QEMU_KEY_HOME;
592                    break;
593                case 117:
594                    keysym = QEMU_KEY_DELETE;
595                    break;
596                case 119:
597                    keysym = QEMU_KEY_END;
598                    break;
599                case 123:
600                    keysym = QEMU_KEY_LEFT;
601                    break;
602                case 124:
603                    keysym = QEMU_KEY_RIGHT;
604                    break;
605                case 125:
606                    keysym = QEMU_KEY_DOWN;
607                    break;
608                case 126:
609                    keysym = QEMU_KEY_UP;
610                    break;
611                default:
612                    {
613                        NSString *ks = [event characters];
614                        if ([ks length] > 0)
615                            keysym = [ks characterAtIndex:0];
616                    }
617                }
618                if (keysym)
619                    kbd_put_keysym(keysym);
620            }
621            break;
622        case NSKeyUp:
623            keycode = cocoa_keycode_to_qemu([event keyCode]);
624
625            // don't pass the guest a spurious key-up if we treated this
626            // command-key combo as a host UI action
627            if (!isMouseGrabbed && ([event modifierFlags] & NSCommandKeyMask)) {
628                return;
629            }
630
631            if (qemu_console_is_graphic(NULL)) {
632                qemu_input_event_send_key_number(dcl->con, keycode, false);
633            }
634            break;
635        case NSMouseMoved:
636            if (isAbsoluteEnabled) {
637                if (![self screenContainsPoint:p] || ![[self window] isKeyWindow]) {
638                    if (isMouseGrabbed) {
639                        [self ungrabMouse];
640                    }
641                } else {
642                    if (!isMouseGrabbed) {
643                        [self grabMouse];
644                    }
645                }
646            }
647            mouse_event = true;
648            break;
649        case NSLeftMouseDown:
650            if ([event modifierFlags] & NSCommandKeyMask) {
651                buttons |= MOUSE_EVENT_RBUTTON;
652            } else {
653                buttons |= MOUSE_EVENT_LBUTTON;
654            }
655            mouse_event = true;
656            break;
657        case NSRightMouseDown:
658            buttons |= MOUSE_EVENT_RBUTTON;
659            mouse_event = true;
660            break;
661        case NSOtherMouseDown:
662            buttons |= MOUSE_EVENT_MBUTTON;
663            mouse_event = true;
664            break;
665        case NSLeftMouseDragged:
666            if ([event modifierFlags] & NSCommandKeyMask) {
667                buttons |= MOUSE_EVENT_RBUTTON;
668            } else {
669                buttons |= MOUSE_EVENT_LBUTTON;
670            }
671            mouse_event = true;
672            break;
673        case NSRightMouseDragged:
674            buttons |= MOUSE_EVENT_RBUTTON;
675            mouse_event = true;
676            break;
677        case NSOtherMouseDragged:
678            buttons |= MOUSE_EVENT_MBUTTON;
679            mouse_event = true;
680            break;
681        case NSLeftMouseUp:
682            mouse_event = true;
683            if (!isMouseGrabbed && [self screenContainsPoint:p]) {
684                [self grabMouse];
685            }
686            break;
687        case NSRightMouseUp:
688            mouse_event = true;
689            break;
690        case NSOtherMouseUp:
691            mouse_event = true;
692            break;
693        case NSScrollWheel:
694            if (isMouseGrabbed) {
695                buttons |= ([event deltaY] < 0) ?
696                    MOUSE_EVENT_WHEELUP : MOUSE_EVENT_WHEELDN;
697            }
698            mouse_event = true;
699            break;
700        default:
701            [NSApp sendEvent:event];
702    }
703
704    if (mouse_event) {
705        if (last_buttons != buttons) {
706            static uint32_t bmap[INPUT_BUTTON_MAX] = {
707                [INPUT_BUTTON_LEFT]       = MOUSE_EVENT_LBUTTON,
708                [INPUT_BUTTON_MIDDLE]     = MOUSE_EVENT_MBUTTON,
709                [INPUT_BUTTON_RIGHT]      = MOUSE_EVENT_RBUTTON,
710                [INPUT_BUTTON_WHEEL_UP]   = MOUSE_EVENT_WHEELUP,
711                [INPUT_BUTTON_WHEEL_DOWN] = MOUSE_EVENT_WHEELDN,
712            };
713            qemu_input_update_buttons(dcl->con, bmap, last_buttons, buttons);
714            last_buttons = buttons;
715        }
716        if (isMouseGrabbed) {
717            if (isAbsoluteEnabled) {
718                /* Note that the origin for Cocoa mouse coords is bottom left, not top left.
719                 * The check on screenContainsPoint is to avoid sending out of range values for
720                 * clicks in the titlebar.
721                 */
722                if ([self screenContainsPoint:p]) {
723                    qemu_input_queue_abs(dcl->con, INPUT_AXIS_X, p.x, screen.width);
724                    qemu_input_queue_abs(dcl->con, INPUT_AXIS_Y, screen.height - p.y, screen.height);
725                }
726            } else {
727                qemu_input_queue_rel(dcl->con, INPUT_AXIS_X, (int)[event deltaX]);
728                qemu_input_queue_rel(dcl->con, INPUT_AXIS_Y, (int)[event deltaY]);
729            }
730        } else {
731            [NSApp sendEvent:event];
732        }
733        qemu_input_event_sync();
734    }
735}
736
737- (void) grabMouse
738{
739    COCOA_DEBUG("QemuCocoaView: grabMouse\n");
740
741    if (!isFullscreen) {
742        if (qemu_name)
743            [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s - (Press ctrl + alt to release Mouse)", qemu_name]];
744        else
745            [normalWindow setTitle:@"QEMU - (Press ctrl + alt to release Mouse)"];
746    }
747    [self hideCursor];
748    if (!isAbsoluteEnabled) {
749        isMouseDeassociated = TRUE;
750        CGAssociateMouseAndMouseCursorPosition(FALSE);
751    }
752    isMouseGrabbed = TRUE; // while isMouseGrabbed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:]
753}
754
755- (void) ungrabMouse
756{
757    COCOA_DEBUG("QemuCocoaView: ungrabMouse\n");
758
759    if (!isFullscreen) {
760        if (qemu_name)
761            [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]];
762        else
763            [normalWindow setTitle:@"QEMU"];
764    }
765    [self unhideCursor];
766    if (isMouseDeassociated) {
767        CGAssociateMouseAndMouseCursorPosition(TRUE);
768        isMouseDeassociated = FALSE;
769    }
770    isMouseGrabbed = FALSE;
771}
772
773- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled {isAbsoluteEnabled = tIsAbsoluteEnabled;}
774- (BOOL) isMouseGrabbed {return isMouseGrabbed;}
775- (BOOL) isAbsoluteEnabled {return isAbsoluteEnabled;}
776- (BOOL) isMouseDeassociated {return isMouseDeassociated;}
777- (float) cdx {return cdx;}
778- (float) cdy {return cdy;}
779- (QEMUScreen) gscreen {return screen;}
780@end
781
782
783
784/*
785 ------------------------------------------------------
786    QemuCocoaAppController
787 ------------------------------------------------------
788*/
789@interface QemuCocoaAppController : NSObject
790#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6)
791                                             <NSApplicationDelegate>
792#endif
793{
794}
795- (void)startEmulationWithArgc:(int)argc argv:(char**)argv;
796- (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo;
797- (void)doToggleFullScreen:(id)sender;
798- (void)toggleFullScreen:(id)sender;
799- (void)showQEMUDoc:(id)sender;
800- (void)showQEMUTec:(id)sender;
801- (void)zoomToFit:(id) sender;
802- (void)displayConsole:(id)sender;
803@end
804
805@implementation QemuCocoaAppController
806- (id) init
807{
808    COCOA_DEBUG("QemuCocoaAppController: init\n");
809
810    self = [super init];
811    if (self) {
812
813        // create a view and add it to the window
814        cocoaView = [[QemuCocoaView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 640.0, 480.0)];
815        if(!cocoaView) {
816            fprintf(stderr, "(cocoa) can't create a view\n");
817            exit(1);
818        }
819
820        // create a window
821        normalWindow = [[NSWindow alloc] initWithContentRect:[cocoaView frame]
822            styleMask:NSTitledWindowMask|NSMiniaturizableWindowMask|NSClosableWindowMask
823            backing:NSBackingStoreBuffered defer:NO];
824        if(!normalWindow) {
825            fprintf(stderr, "(cocoa) can't create window\n");
826            exit(1);
827        }
828        [normalWindow setAcceptsMouseMovedEvents:YES];
829        [normalWindow setTitle:[NSString stringWithFormat:@"QEMU"]];
830        [normalWindow setContentView:cocoaView];
831#if (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10)
832        [normalWindow useOptimizedDrawing:YES];
833#endif
834        [normalWindow makeKeyAndOrderFront:self];
835        [normalWindow center];
836        stretch_video = false;
837    }
838    return self;
839}
840
841- (void) dealloc
842{
843    COCOA_DEBUG("QemuCocoaAppController: dealloc\n");
844
845    if (cocoaView)
846        [cocoaView release];
847    [super dealloc];
848}
849
850- (void)applicationDidFinishLaunching: (NSNotification *) note
851{
852    COCOA_DEBUG("QemuCocoaAppController: applicationDidFinishLaunching\n");
853
854    // Display an open dialog box if no arguments were passed or
855    // if qemu was launched from the finder ( the Finder passes "-psn" )
856    if( gArgc <= 1 || strncmp ((char *)gArgv[1], "-psn", 4) == 0) {
857        NSOpenPanel *op = [[NSOpenPanel alloc] init];
858        [op setPrompt:@"Boot image"];
859        [op setMessage:@"Select the disk image you want to boot.\n\nHit the \"Cancel\" button to quit"];
860        NSArray *filetypes = [NSArray arrayWithObjects:@"img", @"iso", @"dmg",
861                                 @"qcow", @"qcow2", @"cloop", @"vmdk", nil];
862#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6)
863        [op setAllowedFileTypes:filetypes];
864        [op beginSheetModalForWindow:normalWindow
865            completionHandler:^(NSInteger returnCode)
866            { [self openPanelDidEnd:op
867                  returnCode:returnCode contextInfo:NULL ]; } ];
868#else
869        // Compatibility code for pre-10.6, using deprecated method
870        [op beginSheetForDirectory:nil file:nil types:filetypes
871              modalForWindow:normalWindow modalDelegate:self
872              didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:) contextInfo:NULL];
873#endif
874    } else {
875        // or launch QEMU, with the global args
876        [self startEmulationWithArgc:gArgc argv:(char **)gArgv];
877    }
878}
879
880- (void)applicationWillTerminate:(NSNotification *)aNotification
881{
882    COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n");
883
884    qemu_system_shutdown_request();
885    exit(0);
886}
887
888- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
889{
890    return YES;
891}
892
893- (void)startEmulationWithArgc:(int)argc argv:(char**)argv
894{
895    COCOA_DEBUG("QemuCocoaAppController: startEmulationWithArgc\n");
896
897    int status;
898    status = qemu_main(argc, argv, *_NSGetEnviron());
899    exit(status);
900}
901
902- (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
903{
904    COCOA_DEBUG("QemuCocoaAppController: openPanelDidEnd\n");
905
906    /* The NSFileHandlingPanelOKButton/NSFileHandlingPanelCancelButton values for
907     * returnCode strictly only apply for the 10.6-and-up beginSheetModalForWindow
908     * API. For the legacy pre-10.6 beginSheetForDirectory API they are NSOKButton
909     * and NSCancelButton. However conveniently the values are the same.
910     * We use the non-legacy names because the others are deprecated in OSX 10.10.
911     */
912    if (returnCode == NSFileHandlingPanelCancelButton) {
913        exit(0);
914    } else if (returnCode == NSFileHandlingPanelOKButton) {
915        char *img = (char*)[ [ [ sheet URL ] path ] cStringUsingEncoding:NSASCIIStringEncoding];
916
917        char **argv = g_new(char *, 4);
918
919        [sheet close];
920
921        argv[0] = g_strdup(gArgv[0]);
922        argv[1] = g_strdup("-hda");
923        argv[2] = g_strdup(img);
924        argv[3] = NULL;
925
926        // printf("Using argc %d argv %s -hda %s\n", 3, gArgv[0], img);
927
928        [self startEmulationWithArgc:3 argv:(char**)argv];
929    }
930}
931
932/* We abstract the method called by the Enter Fullscreen menu item
933 * because Mac OS 10.7 and higher disables it. This is because of the
934 * menu item's old selector's name toggleFullScreen:
935 */
936- (void) doToggleFullScreen:(id)sender
937{
938    [self toggleFullScreen:(id)sender];
939}
940
941- (void)toggleFullScreen:(id)sender
942{
943    COCOA_DEBUG("QemuCocoaAppController: toggleFullScreen\n");
944
945    [cocoaView toggleFullScreen:sender];
946}
947
948- (void)showQEMUDoc:(id)sender
949{
950    COCOA_DEBUG("QemuCocoaAppController: showQEMUDoc\n");
951
952    [[NSWorkspace sharedWorkspace] openFile:[NSString stringWithFormat:@"%@/../doc/qemu/qemu-doc.html",
953        [[NSBundle mainBundle] resourcePath]] withApplication:@"Help Viewer"];
954}
955
956- (void)showQEMUTec:(id)sender
957{
958    COCOA_DEBUG("QemuCocoaAppController: showQEMUTec\n");
959
960    [[NSWorkspace sharedWorkspace] openFile:[NSString stringWithFormat:@"%@/../doc/qemu/qemu-tech.html",
961        [[NSBundle mainBundle] resourcePath]] withApplication:@"Help Viewer"];
962}
963
964/* Stretches video to fit host monitor size */
965- (void)zoomToFit:(id) sender
966{
967    stretch_video = !stretch_video;
968    if (stretch_video == true) {
969        [sender setState: NSOnState];
970    } else {
971        [sender setState: NSOffState];
972    }
973}
974
975/* Displays the console on the screen */
976- (void)displayConsole:(id)sender
977{
978    console_select([sender tag]);
979}
980@end
981
982
983int main (int argc, const char * argv[]) {
984
985    gArgc = argc;
986    gArgv = (char **)argv;
987    int i;
988
989    /* In case we don't need to display a window, let's not do that */
990    for (i = 1; i < argc; i++) {
991        const char *opt = argv[i];
992
993        if (opt[0] == '-') {
994            /* Treat --foo the same as -foo.  */
995            if (opt[1] == '-') {
996                opt++;
997            }
998            if (!strcmp(opt, "-h") || !strcmp(opt, "-help") ||
999                !strcmp(opt, "-vnc") ||
1000                !strcmp(opt, "-nographic") ||
1001                !strcmp(opt, "-version") ||
1002                !strcmp(opt, "-curses") ||
1003                !strcmp(opt, "-qtest")) {
1004                return qemu_main(gArgc, gArgv, *_NSGetEnviron());
1005            }
1006        }
1007    }
1008
1009    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
1010
1011    // Pull this console process up to being a fully-fledged graphical
1012    // app with a menubar and Dock icon
1013    ProcessSerialNumber psn = { 0, kCurrentProcess };
1014    TransformProcessType(&psn, kProcessTransformToForegroundApplication);
1015
1016    [NSApplication sharedApplication];
1017
1018    // Add menus
1019    NSMenu      *menu;
1020    NSMenuItem  *menuItem;
1021
1022    [NSApp setMainMenu:[[NSMenu alloc] init]];
1023
1024    // Application menu
1025    menu = [[NSMenu alloc] initWithTitle:@""];
1026    [menu addItemWithTitle:@"About QEMU" action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; // About QEMU
1027    [menu addItem:[NSMenuItem separatorItem]]; //Separator
1028    [menu addItemWithTitle:@"Hide QEMU" action:@selector(hide:) keyEquivalent:@"h"]; //Hide QEMU
1029    menuItem = (NSMenuItem *)[menu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; // Hide Others
1030    [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)];
1031    [menu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; // Show All
1032    [menu addItem:[NSMenuItem separatorItem]]; //Separator
1033    [menu addItemWithTitle:@"Quit QEMU" action:@selector(terminate:) keyEquivalent:@"q"];
1034    menuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" action:nil keyEquivalent:@""];
1035    [menuItem setSubmenu:menu];
1036    [[NSApp mainMenu] addItem:menuItem];
1037    [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; // Workaround (this method is private since 10.4+)
1038
1039    // View menu
1040    menu = [[NSMenu alloc] initWithTitle:@"View"];
1041    [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(doToggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen
1042    [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Zoom To Fit" action:@selector(zoomToFit:) keyEquivalent:@""] autorelease]];
1043    menuItem = [[[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""] autorelease];
1044    [menuItem setSubmenu:menu];
1045    [[NSApp mainMenu] addItem:menuItem];
1046
1047    // Window menu
1048    menu = [[NSMenu alloc] initWithTitle:@"Window"];
1049    [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease]]; // Miniaturize
1050    menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease];
1051    [menuItem setSubmenu:menu];
1052    [[NSApp mainMenu] addItem:menuItem];
1053    [NSApp setWindowsMenu:menu];
1054
1055    // Help menu
1056    menu = [[NSMenu alloc] initWithTitle:@"Help"];
1057    [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Documentation" action:@selector(showQEMUDoc:) keyEquivalent:@"?"] autorelease]]; // QEMU Help
1058    [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Technology" action:@selector(showQEMUTec:) keyEquivalent:@""] autorelease]]; // QEMU Help
1059    menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease];
1060    [menuItem setSubmenu:menu];
1061    [[NSApp mainMenu] addItem:menuItem];
1062
1063    // Create an Application controller
1064    QemuCocoaAppController *appController = [[QemuCocoaAppController alloc] init];
1065    [NSApp setDelegate:appController];
1066
1067    // Start the main event loop
1068    [NSApp run];
1069
1070    [appController release];
1071    [pool release];
1072
1073    return 0;
1074}
1075
1076
1077
1078#pragma mark qemu
1079static void cocoa_update(DisplayChangeListener *dcl,
1080                         int x, int y, int w, int h)
1081{
1082    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
1083
1084    COCOA_DEBUG("qemu_cocoa: cocoa_update\n");
1085
1086    NSRect rect;
1087    if ([cocoaView cdx] == 1.0) {
1088        rect = NSMakeRect(x, [cocoaView gscreen].height - y - h, w, h);
1089    } else {
1090        rect = NSMakeRect(
1091            x * [cocoaView cdx],
1092            ([cocoaView gscreen].height - y - h) * [cocoaView cdy],
1093            w * [cocoaView cdx],
1094            h * [cocoaView cdy]);
1095    }
1096    [cocoaView setNeedsDisplayInRect:rect];
1097
1098    [pool release];
1099}
1100
1101static void cocoa_switch(DisplayChangeListener *dcl,
1102                         DisplaySurface *surface)
1103{
1104    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
1105
1106    COCOA_DEBUG("qemu_cocoa: cocoa_switch\n");
1107    [cocoaView switchSurface:surface];
1108    [pool release];
1109}
1110
1111static void cocoa_refresh(DisplayChangeListener *dcl)
1112{
1113    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
1114
1115    COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n");
1116
1117    if (qemu_input_is_absolute()) {
1118        if (![cocoaView isAbsoluteEnabled]) {
1119            if ([cocoaView isMouseGrabbed]) {
1120                [cocoaView ungrabMouse];
1121            }
1122        }
1123        [cocoaView setAbsoluteEnabled:YES];
1124    }
1125
1126    NSDate *distantPast;
1127    NSEvent *event;
1128    distantPast = [NSDate distantPast];
1129    do {
1130        event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:distantPast
1131                        inMode: NSDefaultRunLoopMode dequeue:YES];
1132        if (event != nil) {
1133            [cocoaView handleEvent:event];
1134        }
1135    } while(event != nil);
1136    graphic_hw_update(NULL);
1137    [pool release];
1138}
1139
1140static void cocoa_cleanup(void)
1141{
1142    COCOA_DEBUG("qemu_cocoa: cocoa_cleanup\n");
1143    g_free(dcl);
1144}
1145
1146static const DisplayChangeListenerOps dcl_ops = {
1147    .dpy_name          = "cocoa",
1148    .dpy_gfx_update = cocoa_update,
1149    .dpy_gfx_switch = cocoa_switch,
1150    .dpy_refresh = cocoa_refresh,
1151};
1152
1153/* Returns a name for a given console */
1154static NSString * getConsoleName(QemuConsole * console)
1155{
1156    return [NSString stringWithFormat: @"%s", qemu_console_get_label(console)];
1157}
1158
1159/* Add an entry to the View menu for each console */
1160static void add_console_menu_entries(void)
1161{
1162    NSMenu *menu;
1163    NSMenuItem *menuItem;
1164    int index = 0;
1165
1166    menu = [[[NSApp mainMenu] itemWithTitle:@"View"] submenu];
1167
1168    [menu addItem:[NSMenuItem separatorItem]];
1169
1170    while (qemu_console_lookup_by_index(index) != NULL) {
1171        menuItem = [[[NSMenuItem alloc] initWithTitle: getConsoleName(qemu_console_lookup_by_index(index))
1172                                               action: @selector(displayConsole:) keyEquivalent: @""] autorelease];
1173        [menuItem setTag: index];
1174        [menu addItem: menuItem];
1175        index++;
1176    }
1177}
1178
1179void cocoa_display_init(DisplayState *ds, int full_screen)
1180{
1181    COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n");
1182
1183    /* if fullscreen mode is to be used */
1184    if (full_screen == true) {
1185        [NSApp activateIgnoringOtherApps: YES];
1186        [(QemuCocoaAppController *)[[NSApplication sharedApplication] delegate] toggleFullScreen: nil];
1187    }
1188
1189    dcl = g_malloc0(sizeof(DisplayChangeListener));
1190
1191    // register vga output callbacks
1192    dcl->ops = &dcl_ops;
1193    register_displaychangelistener(dcl);
1194
1195    // register cleanup function
1196    atexit(cocoa_cleanup);
1197
1198    /* At this point QEMU has created all the consoles, so we can add View
1199     * menu entries for them.
1200     */
1201    add_console_menu_entries();
1202}
1203