qapi-visit: Add visitor.type classification
[qemu/ar7.git] / ui / cocoa.m
blob60a7c07eca487a1195f36cf60e8acff40587fe56
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  */
25 #include "qemu/osdep.h"
27 #import <Cocoa/Cocoa.h>
28 #include <crt_externs.h>
30 #include "qemu-common.h"
31 #include "ui/console.h"
32 #include "ui/input.h"
33 #include "sysemu/sysemu.h"
34 #include "qmp-commands.h"
35 #include "sysemu/blockdev.h"
36 #include <Carbon/Carbon.h>
38 #ifndef MAC_OS_X_VERSION_10_5
39 #define MAC_OS_X_VERSION_10_5 1050
40 #endif
41 #ifndef MAC_OS_X_VERSION_10_6
42 #define MAC_OS_X_VERSION_10_6 1060
43 #endif
44 #ifndef MAC_OS_X_VERSION_10_10
45 #define MAC_OS_X_VERSION_10_10 101000
46 #endif
49 //#define DEBUG
51 #ifdef DEBUG
52 #define COCOA_DEBUG(...)  { (void) fprintf (stdout, __VA_ARGS__); }
53 #else
54 #define COCOA_DEBUG(...)  ((void) 0)
55 #endif
57 #define cgrect(nsrect) (*(CGRect *)&(nsrect))
59 typedef struct {
60     int width;
61     int height;
62     int bitsPerComponent;
63     int bitsPerPixel;
64 } QEMUScreen;
66 NSWindow *normalWindow;
67 static DisplayChangeListener *dcl;
68 static int last_buttons;
70 int gArgc;
71 char **gArgv;
72 bool stretch_video;
73 NSTextField *pauseLabel;
74 NSArray * supportedImageFileTypes;
76 // Mac to QKeyCode conversion
77 const int mac_to_qkeycode_map[] = {
78     [kVK_ANSI_A] = Q_KEY_CODE_A,
79     [kVK_ANSI_B] = Q_KEY_CODE_B,
80     [kVK_ANSI_C] = Q_KEY_CODE_C,
81     [kVK_ANSI_D] = Q_KEY_CODE_D,
82     [kVK_ANSI_E] = Q_KEY_CODE_E,
83     [kVK_ANSI_F] = Q_KEY_CODE_F,
84     [kVK_ANSI_G] = Q_KEY_CODE_G,
85     [kVK_ANSI_H] = Q_KEY_CODE_H,
86     [kVK_ANSI_I] = Q_KEY_CODE_I,
87     [kVK_ANSI_J] = Q_KEY_CODE_J,
88     [kVK_ANSI_K] = Q_KEY_CODE_K,
89     [kVK_ANSI_L] = Q_KEY_CODE_L,
90     [kVK_ANSI_M] = Q_KEY_CODE_M,
91     [kVK_ANSI_N] = Q_KEY_CODE_N,
92     [kVK_ANSI_O] = Q_KEY_CODE_O,
93     [kVK_ANSI_P] = Q_KEY_CODE_P,
94     [kVK_ANSI_Q] = Q_KEY_CODE_Q,
95     [kVK_ANSI_R] = Q_KEY_CODE_R,
96     [kVK_ANSI_S] = Q_KEY_CODE_S,
97     [kVK_ANSI_T] = Q_KEY_CODE_T,
98     [kVK_ANSI_U] = Q_KEY_CODE_U,
99     [kVK_ANSI_V] = Q_KEY_CODE_V,
100     [kVK_ANSI_W] = Q_KEY_CODE_W,
101     [kVK_ANSI_X] = Q_KEY_CODE_X,
102     [kVK_ANSI_Y] = Q_KEY_CODE_Y,
103     [kVK_ANSI_Z] = Q_KEY_CODE_Z,
105     [kVK_ANSI_0] = Q_KEY_CODE_0,
106     [kVK_ANSI_1] = Q_KEY_CODE_1,
107     [kVK_ANSI_2] = Q_KEY_CODE_2,
108     [kVK_ANSI_3] = Q_KEY_CODE_3,
109     [kVK_ANSI_4] = Q_KEY_CODE_4,
110     [kVK_ANSI_5] = Q_KEY_CODE_5,
111     [kVK_ANSI_6] = Q_KEY_CODE_6,
112     [kVK_ANSI_7] = Q_KEY_CODE_7,
113     [kVK_ANSI_8] = Q_KEY_CODE_8,
114     [kVK_ANSI_9] = Q_KEY_CODE_9,
116     [kVK_ANSI_Grave] = Q_KEY_CODE_GRAVE_ACCENT,
117     [kVK_ANSI_Minus] = Q_KEY_CODE_MINUS,
118     [kVK_ANSI_Equal] = Q_KEY_CODE_EQUAL,
119     [kVK_Delete] = Q_KEY_CODE_BACKSPACE,
120     [kVK_CapsLock] = Q_KEY_CODE_CAPS_LOCK,
121     [kVK_Tab] = Q_KEY_CODE_TAB,
122     [kVK_Return] = Q_KEY_CODE_RET,
123     [kVK_ANSI_LeftBracket] = Q_KEY_CODE_BRACKET_LEFT,
124     [kVK_ANSI_RightBracket] = Q_KEY_CODE_BRACKET_RIGHT,
125     [kVK_ANSI_Backslash] = Q_KEY_CODE_BACKSLASH,
126     [kVK_ANSI_Semicolon] = Q_KEY_CODE_SEMICOLON,
127     [kVK_ANSI_Quote] = Q_KEY_CODE_APOSTROPHE,
128     [kVK_ANSI_Comma] = Q_KEY_CODE_COMMA,
129     [kVK_ANSI_Period] = Q_KEY_CODE_DOT,
130     [kVK_ANSI_Slash] = Q_KEY_CODE_SLASH,
131     [kVK_Shift] = Q_KEY_CODE_SHIFT,
132     [kVK_RightShift] = Q_KEY_CODE_SHIFT_R,
133     [kVK_Control] = Q_KEY_CODE_CTRL,
134     [kVK_RightControl] = Q_KEY_CODE_CTRL_R,
135     [kVK_Option] = Q_KEY_CODE_ALT,
136     [kVK_RightOption] = Q_KEY_CODE_ALT_R,
137     [kVK_Command] = Q_KEY_CODE_META_L,
138     [0x36] = Q_KEY_CODE_META_R, /* There is no kVK_RightCommand */
139     [kVK_Space] = Q_KEY_CODE_SPC,
141     [kVK_ANSI_Keypad0] = Q_KEY_CODE_KP_0,
142     [kVK_ANSI_Keypad1] = Q_KEY_CODE_KP_1,
143     [kVK_ANSI_Keypad2] = Q_KEY_CODE_KP_2,
144     [kVK_ANSI_Keypad3] = Q_KEY_CODE_KP_3,
145     [kVK_ANSI_Keypad4] = Q_KEY_CODE_KP_4,
146     [kVK_ANSI_Keypad5] = Q_KEY_CODE_KP_5,
147     [kVK_ANSI_Keypad6] = Q_KEY_CODE_KP_6,
148     [kVK_ANSI_Keypad7] = Q_KEY_CODE_KP_7,
149     [kVK_ANSI_Keypad8] = Q_KEY_CODE_KP_8,
150     [kVK_ANSI_Keypad9] = Q_KEY_CODE_KP_9,
151     [kVK_ANSI_KeypadDecimal] = Q_KEY_CODE_KP_DECIMAL,
152     [kVK_ANSI_KeypadEnter] = Q_KEY_CODE_KP_ENTER,
153     [kVK_ANSI_KeypadPlus] = Q_KEY_CODE_KP_ADD,
154     [kVK_ANSI_KeypadMinus] = Q_KEY_CODE_KP_SUBTRACT,
155     [kVK_ANSI_KeypadMultiply] = Q_KEY_CODE_KP_MULTIPLY,
156     [kVK_ANSI_KeypadDivide] = Q_KEY_CODE_KP_DIVIDE,
157     [kVK_ANSI_KeypadEquals] = Q_KEY_CODE_KP_EQUALS,
158     [kVK_ANSI_KeypadClear] = Q_KEY_CODE_NUM_LOCK,
160     [kVK_UpArrow] = Q_KEY_CODE_UP,
161     [kVK_DownArrow] = Q_KEY_CODE_DOWN,
162     [kVK_LeftArrow] = Q_KEY_CODE_LEFT,
163     [kVK_RightArrow] = Q_KEY_CODE_RIGHT,
165     [kVK_Help] = Q_KEY_CODE_INSERT,
166     [kVK_Home] = Q_KEY_CODE_HOME,
167     [kVK_PageUp] = Q_KEY_CODE_PGUP,
168     [kVK_PageDown] = Q_KEY_CODE_PGDN,
169     [kVK_End] = Q_KEY_CODE_END,
170     [kVK_ForwardDelete] = Q_KEY_CODE_DELETE,
172     [kVK_Escape] = Q_KEY_CODE_ESC,
174     /* The Power key can't be used directly because the operating system uses
175      * it. This key can be emulated by using it in place of another key such as
176      * F1. Don't forget to disable the real key binding.
177      */
178     /* [kVK_F1] = Q_KEY_CODE_POWER, */
180     [kVK_F1] = Q_KEY_CODE_F1,
181     [kVK_F2] = Q_KEY_CODE_F2,
182     [kVK_F3] = Q_KEY_CODE_F3,
183     [kVK_F4] = Q_KEY_CODE_F4,
184     [kVK_F5] = Q_KEY_CODE_F5,
185     [kVK_F6] = Q_KEY_CODE_F6,
186     [kVK_F7] = Q_KEY_CODE_F7,
187     [kVK_F8] = Q_KEY_CODE_F8,
188     [kVK_F9] = Q_KEY_CODE_F9,
189     [kVK_F10] = Q_KEY_CODE_F10,
190     [kVK_F11] = Q_KEY_CODE_F11,
191     [kVK_F12] = Q_KEY_CODE_F12,
192     [kVK_F13] = Q_KEY_CODE_PRINT,
193     [kVK_F14] = Q_KEY_CODE_SCROLL_LOCK,
194     [kVK_F15] = Q_KEY_CODE_PAUSE,
196     /*
197      * The eject and volume keys can't be used here because they are handled at
198      * a lower level than what an Application can see.
199      */
202 static int cocoa_keycode_to_qemu(int keycode)
204     if (ARRAY_SIZE(mac_to_qkeycode_map) <= keycode) {
205         fprintf(stderr, "(cocoa) warning unknown keycode 0x%x\n", keycode);
206         return 0;
207     }
208     return mac_to_qkeycode_map[keycode];
211 /* Displays an alert dialog box with the specified message */
212 static void QEMU_Alert(NSString *message)
214     NSAlert *alert;
215     alert = [NSAlert new];
216     [alert setMessageText: message];
217     [alert runModal];
220 /* Handles any errors that happen with a device transaction */
221 static void handleAnyDeviceErrors(Error * err)
223     if (err) {
224         QEMU_Alert([NSString stringWithCString: error_get_pretty(err)
225                                       encoding: NSASCIIStringEncoding]);
226         error_free(err);
227     }
231  ------------------------------------------------------
232     QemuCocoaView
233  ------------------------------------------------------
235 @interface QemuCocoaView : NSView
237     QEMUScreen screen;
238     NSWindow *fullScreenWindow;
239     float cx,cy,cw,ch,cdx,cdy;
240     CGDataProviderRef dataProviderRef;
241     int modifiers_state[256];
242     BOOL isMouseGrabbed;
243     BOOL isFullscreen;
244     BOOL isAbsoluteEnabled;
245     BOOL isMouseDeassociated;
247 - (void) switchSurface:(DisplaySurface *)surface;
248 - (void) grabMouse;
249 - (void) ungrabMouse;
250 - (void) toggleFullScreen:(id)sender;
251 - (void) handleEvent:(NSEvent *)event;
252 - (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled;
253 /* The state surrounding mouse grabbing is potentially confusing.
254  * isAbsoluteEnabled tracks qemu_input_is_absolute() [ie "is the emulated
255  *   pointing device an absolute-position one?"], but is only updated on
256  *   next refresh.
257  * isMouseGrabbed tracks whether GUI events are directed to the guest;
258  *   it controls whether special keys like Cmd get sent to the guest,
259  *   and whether we capture the mouse when in non-absolute mode.
260  * isMouseDeassociated tracks whether we've told MacOSX to disassociate
261  *   the mouse and mouse cursor position by calling
262  *   CGAssociateMouseAndMouseCursorPosition(FALSE)
263  *   (which basically happens if we grab in non-absolute mode).
264  */
265 - (BOOL) isMouseGrabbed;
266 - (BOOL) isAbsoluteEnabled;
267 - (BOOL) isMouseDeassociated;
268 - (float) cdx;
269 - (float) cdy;
270 - (QEMUScreen) gscreen;
271 - (void) raiseAllKeys;
272 @end
274 QemuCocoaView *cocoaView;
276 @implementation QemuCocoaView
277 - (id)initWithFrame:(NSRect)frameRect
279     COCOA_DEBUG("QemuCocoaView: initWithFrame\n");
281     self = [super initWithFrame:frameRect];
282     if (self) {
284         screen.bitsPerComponent = 8;
285         screen.bitsPerPixel = 32;
286         screen.width = frameRect.size.width;
287         screen.height = frameRect.size.height;
289     }
290     return self;
293 - (void) dealloc
295     COCOA_DEBUG("QemuCocoaView: dealloc\n");
297     if (dataProviderRef)
298         CGDataProviderRelease(dataProviderRef);
300     [super dealloc];
303 - (BOOL) isOpaque
305     return YES;
308 - (BOOL) screenContainsPoint:(NSPoint) p
310     return (p.x > -1 && p.x < screen.width && p.y > -1 && p.y < screen.height);
313 - (void) hideCursor
315     if (!cursor_hide) {
316         return;
317     }
318     [NSCursor hide];
321 - (void) unhideCursor
323     if (!cursor_hide) {
324         return;
325     }
326     [NSCursor unhide];
329 - (void) drawRect:(NSRect) rect
331     COCOA_DEBUG("QemuCocoaView: drawRect\n");
333     // get CoreGraphic context
334     CGContextRef viewContextRef = [[NSGraphicsContext currentContext] graphicsPort];
335     CGContextSetInterpolationQuality (viewContextRef, kCGInterpolationNone);
336     CGContextSetShouldAntialias (viewContextRef, NO);
338     // draw screen bitmap directly to Core Graphics context
339     if (!dataProviderRef) {
340         // Draw request before any guest device has set up a framebuffer:
341         // just draw an opaque black rectangle
342         CGContextSetRGBFillColor(viewContextRef, 0, 0, 0, 1.0);
343         CGContextFillRect(viewContextRef, NSRectToCGRect(rect));
344     } else {
345         CGImageRef imageRef = CGImageCreate(
346             screen.width, //width
347             screen.height, //height
348             screen.bitsPerComponent, //bitsPerComponent
349             screen.bitsPerPixel, //bitsPerPixel
350             (screen.width * (screen.bitsPerComponent/2)), //bytesPerRow
351 #ifdef __LITTLE_ENDIAN__
352             CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB), //colorspace for OS X >= 10.4
353             kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst,
354 #else
355             CGColorSpaceCreateDeviceRGB(), //colorspace for OS X < 10.4 (actually ppc)
356             kCGImageAlphaNoneSkipFirst, //bitmapInfo
357 #endif
358             dataProviderRef, //provider
359             NULL, //decode
360             0, //interpolate
361             kCGRenderingIntentDefault //intent
362         );
363         // selective drawing code (draws only dirty rectangles) (OS X >= 10.4)
364         const NSRect *rectList;
365         NSInteger rectCount;
366         int i;
367         CGImageRef clipImageRef;
368         CGRect clipRect;
370         [self getRectsBeingDrawn:&rectList count:&rectCount];
371         for (i = 0; i < rectCount; i++) {
372             clipRect.origin.x = rectList[i].origin.x / cdx;
373             clipRect.origin.y = (float)screen.height - (rectList[i].origin.y + rectList[i].size.height) / cdy;
374             clipRect.size.width = rectList[i].size.width / cdx;
375             clipRect.size.height = rectList[i].size.height / cdy;
376             clipImageRef = CGImageCreateWithImageInRect(
377                                                         imageRef,
378                                                         clipRect
379                                                         );
380             CGContextDrawImage (viewContextRef, cgrect(rectList[i]), clipImageRef);
381             CGImageRelease (clipImageRef);
382         }
383         CGImageRelease (imageRef);
384     }
387 - (void) setContentDimensions
389     COCOA_DEBUG("QemuCocoaView: setContentDimensions\n");
391     if (isFullscreen) {
392         cdx = [[NSScreen mainScreen] frame].size.width / (float)screen.width;
393         cdy = [[NSScreen mainScreen] frame].size.height / (float)screen.height;
395         /* stretches video, but keeps same aspect ratio */
396         if (stretch_video == true) {
397             /* use smallest stretch value - prevents clipping on sides */
398             if (MIN(cdx, cdy) == cdx) {
399                 cdy = cdx;
400             } else {
401                 cdx = cdy;
402             }
403         } else {  /* No stretching */
404             cdx = cdy = 1;
405         }
406         cw = screen.width * cdx;
407         ch = screen.height * cdy;
408         cx = ([[NSScreen mainScreen] frame].size.width - cw) / 2.0;
409         cy = ([[NSScreen mainScreen] frame].size.height - ch) / 2.0;
410     } else {
411         cx = 0;
412         cy = 0;
413         cw = screen.width;
414         ch = screen.height;
415         cdx = 1.0;
416         cdy = 1.0;
417     }
420 - (void) switchSurface:(DisplaySurface *)surface
422     COCOA_DEBUG("QemuCocoaView: switchSurface\n");
424     int w = surface_width(surface);
425     int h = surface_height(surface);
426     /* cdx == 0 means this is our very first surface, in which case we need
427      * to recalculate the content dimensions even if it happens to be the size
428      * of the initial empty window.
429      */
430     bool isResize = (w != screen.width || h != screen.height || cdx == 0.0);
432     int oldh = screen.height;
433     if (isResize) {
434         // Resize before we trigger the redraw, or we'll redraw at the wrong size
435         COCOA_DEBUG("switchSurface: new size %d x %d\n", w, h);
436         screen.width = w;
437         screen.height = h;
438         [self setContentDimensions];
439         [self setFrame:NSMakeRect(cx, cy, cw, ch)];
440     }
442     // update screenBuffer
443     if (dataProviderRef)
444         CGDataProviderRelease(dataProviderRef);
446     //sync host window color space with guests
447     screen.bitsPerPixel = surface_bits_per_pixel(surface);
448     screen.bitsPerComponent = surface_bytes_per_pixel(surface) * 2;
450     dataProviderRef = CGDataProviderCreateWithData(NULL, surface_data(surface), w * 4 * h, NULL);
452     // update windows
453     if (isFullscreen) {
454         [[fullScreenWindow contentView] setFrame:[[NSScreen mainScreen] frame]];
455         [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + oldh, w, h + [normalWindow frame].size.height - oldh) display:NO animate:NO];
456     } else {
457         if (qemu_name)
458             [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]];
459         [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + oldh, w, h + [normalWindow frame].size.height - oldh) display:YES animate:NO];
460     }
462     if (isResize) {
463         [normalWindow center];
464     }
467 - (void) toggleFullScreen:(id)sender
469     COCOA_DEBUG("QemuCocoaView: toggleFullScreen\n");
471     if (isFullscreen) { // switch from fullscreen to desktop
472         isFullscreen = FALSE;
473         [self ungrabMouse];
474         [self setContentDimensions];
475         if ([NSView respondsToSelector:@selector(exitFullScreenModeWithOptions:)]) { // test if "exitFullScreenModeWithOptions" is supported on host at runtime
476             [self exitFullScreenModeWithOptions:nil];
477         } else {
478             [fullScreenWindow close];
479             [normalWindow setContentView: self];
480             [normalWindow makeKeyAndOrderFront: self];
481             [NSMenu setMenuBarVisible:YES];
482         }
483     } else { // switch from desktop to fullscreen
484         isFullscreen = TRUE;
485         [normalWindow orderOut: nil]; /* Hide the window */
486         [self grabMouse];
487         [self setContentDimensions];
488         if ([NSView respondsToSelector:@selector(enterFullScreenMode:withOptions:)]) { // test if "enterFullScreenMode:withOptions" is supported on host at runtime
489             [self enterFullScreenMode:[NSScreen mainScreen] withOptions:[NSDictionary dictionaryWithObjectsAndKeys:
490                 [NSNumber numberWithBool:NO], NSFullScreenModeAllScreens,
491                 [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO], kCGDisplayModeIsStretched, nil], NSFullScreenModeSetting,
492                  nil]];
493         } else {
494             [NSMenu setMenuBarVisible:NO];
495             fullScreenWindow = [[NSWindow alloc] initWithContentRect:[[NSScreen mainScreen] frame]
496                 styleMask:NSBorderlessWindowMask
497                 backing:NSBackingStoreBuffered
498                 defer:NO];
499             [fullScreenWindow setAcceptsMouseMovedEvents: YES];
500             [fullScreenWindow setHasShadow:NO];
501             [fullScreenWindow setBackgroundColor: [NSColor blackColor]];
502             [self setFrame:NSMakeRect(cx, cy, cw, ch)];
503             [[fullScreenWindow contentView] addSubview: self];
504             [fullScreenWindow makeKeyAndOrderFront:self];
505         }
506     }
509 - (void) handleEvent:(NSEvent *)event
511     COCOA_DEBUG("QemuCocoaView: handleEvent\n");
513     int buttons = 0;
514     int keycode;
515     bool mouse_event = false;
516     NSPoint p = [event locationInWindow];
518     switch ([event type]) {
519         case NSFlagsChanged:
520             keycode = cocoa_keycode_to_qemu([event keyCode]);
522             if ((keycode == Q_KEY_CODE_META_L || keycode == Q_KEY_CODE_META_R)
523                && !isMouseGrabbed) {
524               /* Don't pass command key changes to guest unless mouse is grabbed */
525               keycode = 0;
526             }
528             if (keycode) {
529                 // emulate caps lock and num lock keydown and keyup
530                 if (keycode == Q_KEY_CODE_CAPS_LOCK ||
531                     keycode == Q_KEY_CODE_NUM_LOCK) {
532                     qemu_input_event_send_key_qcode(dcl->con, keycode, true);
533                     qemu_input_event_send_key_qcode(dcl->con, keycode, false);
534                 } else if (qemu_console_is_graphic(NULL)) {
535                     if (modifiers_state[keycode] == 0) { // keydown
536                         qemu_input_event_send_key_qcode(dcl->con, keycode, true);
537                         modifiers_state[keycode] = 1;
538                     } else { // keyup
539                         qemu_input_event_send_key_qcode(dcl->con, keycode, false);
540                         modifiers_state[keycode] = 0;
541                     }
542                 }
543             }
545             // release Mouse grab when pressing ctrl+alt
546             if (([event modifierFlags] & NSControlKeyMask) && ([event modifierFlags] & NSAlternateKeyMask)) {
547                 [self ungrabMouse];
548             }
549             break;
550         case NSKeyDown:
551             keycode = cocoa_keycode_to_qemu([event keyCode]);
553             // forward command key combos to the host UI unless the mouse is grabbed
554             if (!isMouseGrabbed && ([event modifierFlags] & NSCommandKeyMask)) {
555                 [NSApp sendEvent:event];
556                 return;
557             }
559             // default
561             // handle control + alt Key Combos (ctrl+alt is reserved for QEMU)
562             if (([event modifierFlags] & NSControlKeyMask) && ([event modifierFlags] & NSAlternateKeyMask)) {
563                 switch (keycode) {
565                     // enable graphic console
566                     case Q_KEY_CODE_1 ... Q_KEY_CODE_9: // '1' to '9' keys
567                         console_select(keycode - 11);
568                         break;
569                 }
571             // handle keys for graphic console
572             } else if (qemu_console_is_graphic(NULL)) {
573                 qemu_input_event_send_key_qcode(dcl->con, keycode, true);
575             // handlekeys for Monitor
576             } else {
577                 int keysym = 0;
578                 switch([event keyCode]) {
579                 case 115:
580                     keysym = QEMU_KEY_HOME;
581                     break;
582                 case 117:
583                     keysym = QEMU_KEY_DELETE;
584                     break;
585                 case 119:
586                     keysym = QEMU_KEY_END;
587                     break;
588                 case 123:
589                     keysym = QEMU_KEY_LEFT;
590                     break;
591                 case 124:
592                     keysym = QEMU_KEY_RIGHT;
593                     break;
594                 case 125:
595                     keysym = QEMU_KEY_DOWN;
596                     break;
597                 case 126:
598                     keysym = QEMU_KEY_UP;
599                     break;
600                 default:
601                     {
602                         NSString *ks = [event characters];
603                         if ([ks length] > 0)
604                             keysym = [ks characterAtIndex:0];
605                     }
606                 }
607                 if (keysym)
608                     kbd_put_keysym(keysym);
609             }
610             break;
611         case NSKeyUp:
612             keycode = cocoa_keycode_to_qemu([event keyCode]);
614             // don't pass the guest a spurious key-up if we treated this
615             // command-key combo as a host UI action
616             if (!isMouseGrabbed && ([event modifierFlags] & NSCommandKeyMask)) {
617                 return;
618             }
620             if (qemu_console_is_graphic(NULL)) {
621                 qemu_input_event_send_key_qcode(dcl->con, keycode, false);
622             }
623             break;
624         case NSMouseMoved:
625             if (isAbsoluteEnabled) {
626                 if (![self screenContainsPoint:p] || ![[self window] isKeyWindow]) {
627                     if (isMouseGrabbed) {
628                         [self ungrabMouse];
629                     }
630                 } else {
631                     if (!isMouseGrabbed) {
632                         [self grabMouse];
633                     }
634                 }
635             }
636             mouse_event = true;
637             break;
638         case NSLeftMouseDown:
639             if ([event modifierFlags] & NSCommandKeyMask) {
640                 buttons |= MOUSE_EVENT_RBUTTON;
641             } else {
642                 buttons |= MOUSE_EVENT_LBUTTON;
643             }
644             mouse_event = true;
645             break;
646         case NSRightMouseDown:
647             buttons |= MOUSE_EVENT_RBUTTON;
648             mouse_event = true;
649             break;
650         case NSOtherMouseDown:
651             buttons |= MOUSE_EVENT_MBUTTON;
652             mouse_event = true;
653             break;
654         case NSLeftMouseDragged:
655             if ([event modifierFlags] & NSCommandKeyMask) {
656                 buttons |= MOUSE_EVENT_RBUTTON;
657             } else {
658                 buttons |= MOUSE_EVENT_LBUTTON;
659             }
660             mouse_event = true;
661             break;
662         case NSRightMouseDragged:
663             buttons |= MOUSE_EVENT_RBUTTON;
664             mouse_event = true;
665             break;
666         case NSOtherMouseDragged:
667             buttons |= MOUSE_EVENT_MBUTTON;
668             mouse_event = true;
669             break;
670         case NSLeftMouseUp:
671             mouse_event = true;
672             if (!isMouseGrabbed && [self screenContainsPoint:p]) {
673                 [self grabMouse];
674             }
675             break;
676         case NSRightMouseUp:
677             mouse_event = true;
678             break;
679         case NSOtherMouseUp:
680             mouse_event = true;
681             break;
682         case NSScrollWheel:
683             if (isMouseGrabbed) {
684                 buttons |= ([event deltaY] < 0) ?
685                     MOUSE_EVENT_WHEELUP : MOUSE_EVENT_WHEELDN;
686             }
687             mouse_event = true;
688             break;
689         default:
690             [NSApp sendEvent:event];
691     }
693     if (mouse_event) {
694         /* Don't send button events to the guest unless we've got a
695          * mouse grab or window focus. If we have neither then this event
696          * is the user clicking on the background window to activate and
697          * bring us to the front, which will be done by the sendEvent
698          * call below. We definitely don't want to pass that click through
699          * to the guest.
700          */
701         if ((isMouseGrabbed || [[self window] isKeyWindow]) &&
702             (last_buttons != buttons)) {
703             static uint32_t bmap[INPUT_BUTTON__MAX] = {
704                 [INPUT_BUTTON_LEFT]       = MOUSE_EVENT_LBUTTON,
705                 [INPUT_BUTTON_MIDDLE]     = MOUSE_EVENT_MBUTTON,
706                 [INPUT_BUTTON_RIGHT]      = MOUSE_EVENT_RBUTTON,
707                 [INPUT_BUTTON_WHEEL_UP]   = MOUSE_EVENT_WHEELUP,
708                 [INPUT_BUTTON_WHEEL_DOWN] = MOUSE_EVENT_WHEELDN,
709             };
710             qemu_input_update_buttons(dcl->con, bmap, last_buttons, buttons);
711             last_buttons = buttons;
712         }
713         if (isMouseGrabbed) {
714             if (isAbsoluteEnabled) {
715                 /* Note that the origin for Cocoa mouse coords is bottom left, not top left.
716                  * The check on screenContainsPoint is to avoid sending out of range values for
717                  * clicks in the titlebar.
718                  */
719                 if ([self screenContainsPoint:p]) {
720                     qemu_input_queue_abs(dcl->con, INPUT_AXIS_X, p.x, screen.width);
721                     qemu_input_queue_abs(dcl->con, INPUT_AXIS_Y, screen.height - p.y, screen.height);
722                 }
723             } else {
724                 qemu_input_queue_rel(dcl->con, INPUT_AXIS_X, (int)[event deltaX]);
725                 qemu_input_queue_rel(dcl->con, INPUT_AXIS_Y, (int)[event deltaY]);
726             }
727         } else {
728             [NSApp sendEvent:event];
729         }
730         qemu_input_event_sync();
731     }
734 - (void) grabMouse
736     COCOA_DEBUG("QemuCocoaView: grabMouse\n");
738     if (!isFullscreen) {
739         if (qemu_name)
740             [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s - (Press ctrl + alt to release Mouse)", qemu_name]];
741         else
742             [normalWindow setTitle:@"QEMU - (Press ctrl + alt to release Mouse)"];
743     }
744     [self hideCursor];
745     if (!isAbsoluteEnabled) {
746         isMouseDeassociated = TRUE;
747         CGAssociateMouseAndMouseCursorPosition(FALSE);
748     }
749     isMouseGrabbed = TRUE; // while isMouseGrabbed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:]
752 - (void) ungrabMouse
754     COCOA_DEBUG("QemuCocoaView: ungrabMouse\n");
756     if (!isFullscreen) {
757         if (qemu_name)
758             [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]];
759         else
760             [normalWindow setTitle:@"QEMU"];
761     }
762     [self unhideCursor];
763     if (isMouseDeassociated) {
764         CGAssociateMouseAndMouseCursorPosition(TRUE);
765         isMouseDeassociated = FALSE;
766     }
767     isMouseGrabbed = FALSE;
770 - (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled {isAbsoluteEnabled = tIsAbsoluteEnabled;}
771 - (BOOL) isMouseGrabbed {return isMouseGrabbed;}
772 - (BOOL) isAbsoluteEnabled {return isAbsoluteEnabled;}
773 - (BOOL) isMouseDeassociated {return isMouseDeassociated;}
774 - (float) cdx {return cdx;}
775 - (float) cdy {return cdy;}
776 - (QEMUScreen) gscreen {return screen;}
779  * Makes the target think all down keys are being released.
780  * This prevents a stuck key problem, since we will not see
781  * key up events for those keys after we have lost focus.
782  */
783 - (void) raiseAllKeys
785     int index;
786     const int max_index = ARRAY_SIZE(modifiers_state);
788    for (index = 0; index < max_index; index++) {
789        if (modifiers_state[index]) {
790            modifiers_state[index] = 0;
791            qemu_input_event_send_key_qcode(dcl->con, index, false);
792        }
793    }
795 @end
800  ------------------------------------------------------
801     QemuCocoaAppController
802  ------------------------------------------------------
804 @interface QemuCocoaAppController : NSObject
805 #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6)
806                                        <NSWindowDelegate, NSApplicationDelegate>
807 #endif
810 - (void)startEmulationWithArgc:(int)argc argv:(char**)argv;
811 - (void)doToggleFullScreen:(id)sender;
812 - (void)toggleFullScreen:(id)sender;
813 - (void)showQEMUDoc:(id)sender;
814 - (void)showQEMUTec:(id)sender;
815 - (void)zoomToFit:(id) sender;
816 - (void)displayConsole:(id)sender;
817 - (void)pauseQEMU:(id)sender;
818 - (void)resumeQEMU:(id)sender;
819 - (void)displayPause;
820 - (void)removePause;
821 - (void)restartQEMU:(id)sender;
822 - (void)powerDownQEMU:(id)sender;
823 - (void)ejectDeviceMedia:(id)sender;
824 - (void)changeDeviceMedia:(id)sender;
825 - (BOOL)verifyQuit;
826 - (void)openDocumentation:(NSString *)filename;
827 @end
829 @implementation QemuCocoaAppController
830 - (id) init
832     COCOA_DEBUG("QemuCocoaAppController: init\n");
834     self = [super init];
835     if (self) {
837         // create a view and add it to the window
838         cocoaView = [[QemuCocoaView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 640.0, 480.0)];
839         if(!cocoaView) {
840             fprintf(stderr, "(cocoa) can't create a view\n");
841             exit(1);
842         }
844         // create a window
845         normalWindow = [[NSWindow alloc] initWithContentRect:[cocoaView frame]
846             styleMask:NSTitledWindowMask|NSMiniaturizableWindowMask|NSClosableWindowMask
847             backing:NSBackingStoreBuffered defer:NO];
848         if(!normalWindow) {
849             fprintf(stderr, "(cocoa) can't create window\n");
850             exit(1);
851         }
852         [normalWindow setAcceptsMouseMovedEvents:YES];
853         [normalWindow setTitle:@"QEMU"];
854         [normalWindow setContentView:cocoaView];
855 #if (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10)
856         [normalWindow useOptimizedDrawing:YES];
857 #endif
858         [normalWindow makeKeyAndOrderFront:self];
859         [normalWindow center];
860         [normalWindow setDelegate: self];
861         stretch_video = false;
863         /* Used for displaying pause on the screen */
864         pauseLabel = [NSTextField new];
865         [pauseLabel setBezeled:YES];
866         [pauseLabel setDrawsBackground:YES];
867         [pauseLabel setBackgroundColor: [NSColor whiteColor]];
868         [pauseLabel setEditable:NO];
869         [pauseLabel setSelectable:NO];
870         [pauseLabel setStringValue: @"Paused"];
871         [pauseLabel setFont: [NSFont fontWithName: @"Helvetica" size: 90]];
872         [pauseLabel setTextColor: [NSColor blackColor]];
873         [pauseLabel sizeToFit];
875         // set the supported image file types that can be opened
876         supportedImageFileTypes = [NSArray arrayWithObjects: @"img", @"iso", @"dmg",
877                                  @"qcow", @"qcow2", @"cloop", @"vmdk", @"cdr",
878                                   nil];
879     }
880     return self;
883 - (void) dealloc
885     COCOA_DEBUG("QemuCocoaAppController: dealloc\n");
887     if (cocoaView)
888         [cocoaView release];
889     [super dealloc];
892 - (void)applicationDidFinishLaunching: (NSNotification *) note
894     COCOA_DEBUG("QemuCocoaAppController: applicationDidFinishLaunching\n");
895     // launch QEMU, with the global args
896     [self startEmulationWithArgc:gArgc argv:(char **)gArgv];
899 - (void)applicationWillTerminate:(NSNotification *)aNotification
901     COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n");
903     qemu_system_shutdown_request();
904     exit(0);
907 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
909     return YES;
912 - (NSApplicationTerminateReply)applicationShouldTerminate:
913                                                          (NSApplication *)sender
915     COCOA_DEBUG("QemuCocoaAppController: applicationShouldTerminate\n");
916     return [self verifyQuit];
919 /* Called when the user clicks on a window's close button */
920 - (BOOL)windowShouldClose:(id)sender
922     COCOA_DEBUG("QemuCocoaAppController: windowShouldClose\n");
923     [NSApp terminate: sender];
924     /* If the user allows the application to quit then the call to
925      * NSApp terminate will never return. If we get here then the user
926      * cancelled the quit, so we should return NO to not permit the
927      * closing of this window.
928      */
929     return NO;
932 /* Called when QEMU goes into the background */
933 - (void) applicationWillResignActive: (NSNotification *)aNotification
935     COCOA_DEBUG("QemuCocoaAppController: applicationWillResignActive\n");
936     [cocoaView raiseAllKeys];
939 - (void)startEmulationWithArgc:(int)argc argv:(char**)argv
941     COCOA_DEBUG("QemuCocoaAppController: startEmulationWithArgc\n");
943     int status;
944     status = qemu_main(argc, argv, *_NSGetEnviron());
945     exit(status);
948 /* We abstract the method called by the Enter Fullscreen menu item
949  * because Mac OS 10.7 and higher disables it. This is because of the
950  * menu item's old selector's name toggleFullScreen:
951  */
952 - (void) doToggleFullScreen:(id)sender
954     [self toggleFullScreen:(id)sender];
957 - (void)toggleFullScreen:(id)sender
959     COCOA_DEBUG("QemuCocoaAppController: toggleFullScreen\n");
961     [cocoaView toggleFullScreen:sender];
964 /* Tries to find then open the specified filename */
965 - (void) openDocumentation: (NSString *) filename
967     /* Where to look for local files */
968     NSString *path_array[] = {@"../share/doc/qemu/", @"../doc/qemu/", @"../"};
969     NSString *full_file_path;
971     /* iterate thru the possible paths until the file is found */
972     int index;
973     for (index = 0; index < ARRAY_SIZE(path_array); index++) {
974         full_file_path = [[NSBundle mainBundle] executablePath];
975         full_file_path = [full_file_path stringByDeletingLastPathComponent];
976         full_file_path = [NSString stringWithFormat: @"%@/%@%@", full_file_path,
977                           path_array[index], filename];
978         if ([[NSWorkspace sharedWorkspace] openFile: full_file_path] == YES) {
979             return;
980         }
981     }
983     /* If none of the paths opened a file */
984     NSBeep();
985     QEMU_Alert(@"Failed to open file");
988 - (void)showQEMUDoc:(id)sender
990     COCOA_DEBUG("QemuCocoaAppController: showQEMUDoc\n");
992     [self openDocumentation: @"qemu-doc.html"];
995 - (void)showQEMUTec:(id)sender
997     COCOA_DEBUG("QemuCocoaAppController: showQEMUTec\n");
999     [self openDocumentation: @"qemu-tech.html"];
1002 /* Stretches video to fit host monitor size */
1003 - (void)zoomToFit:(id) sender
1005     stretch_video = !stretch_video;
1006     if (stretch_video == true) {
1007         [sender setState: NSOnState];
1008     } else {
1009         [sender setState: NSOffState];
1010     }
1013 /* Displays the console on the screen */
1014 - (void)displayConsole:(id)sender
1016     console_select([sender tag]);
1019 /* Pause the guest */
1020 - (void)pauseQEMU:(id)sender
1022     qmp_stop(NULL);
1023     [sender setEnabled: NO];
1024     [[[sender menu] itemWithTitle: @"Resume"] setEnabled: YES];
1025     [self displayPause];
1028 /* Resume running the guest operating system */
1029 - (void)resumeQEMU:(id) sender
1031     qmp_cont(NULL);
1032     [sender setEnabled: NO];
1033     [[[sender menu] itemWithTitle: @"Pause"] setEnabled: YES];
1034     [self removePause];
1037 /* Displays the word pause on the screen */
1038 - (void)displayPause
1040     /* Coordinates have to be calculated each time because the window can change its size */
1041     int xCoord, yCoord, width, height;
1042     xCoord = ([normalWindow frame].size.width - [pauseLabel frame].size.width)/2;
1043     yCoord = [normalWindow frame].size.height - [pauseLabel frame].size.height - ([pauseLabel frame].size.height * .5);
1044     width = [pauseLabel frame].size.width;
1045     height = [pauseLabel frame].size.height;
1046     [pauseLabel setFrame: NSMakeRect(xCoord, yCoord, width, height)];
1047     [cocoaView addSubview: pauseLabel];
1050 /* Removes the word pause from the screen */
1051 - (void)removePause
1053     [pauseLabel removeFromSuperview];
1056 /* Restarts QEMU */
1057 - (void)restartQEMU:(id)sender
1059     qmp_system_reset(NULL);
1062 /* Powers down QEMU */
1063 - (void)powerDownQEMU:(id)sender
1065     qmp_system_powerdown(NULL);
1068 /* Ejects the media.
1069  * Uses sender's tag to figure out the device to eject.
1070  */
1071 - (void)ejectDeviceMedia:(id)sender
1073     NSString * drive;
1074     drive = [sender representedObject];
1075     if(drive == nil) {
1076         NSBeep();
1077         QEMU_Alert(@"Failed to find drive to eject!");
1078         return;
1079     }
1081     Error *err = NULL;
1082     qmp_eject([drive cStringUsingEncoding: NSASCIIStringEncoding], false, false, &err);
1083     handleAnyDeviceErrors(err);
1086 /* Displays a dialog box asking the user to select an image file to load.
1087  * Uses sender's represented object value to figure out which drive to use.
1088  */
1089 - (void)changeDeviceMedia:(id)sender
1091     /* Find the drive name */
1092     NSString * drive;
1093     drive = [sender representedObject];
1094     if(drive == nil) {
1095         NSBeep();
1096         QEMU_Alert(@"Could not find drive!");
1097         return;
1098     }
1100     /* Display the file open dialog */
1101     NSOpenPanel * openPanel;
1102     openPanel = [NSOpenPanel openPanel];
1103     [openPanel setCanChooseFiles: YES];
1104     [openPanel setAllowsMultipleSelection: NO];
1105     [openPanel setAllowedFileTypes: supportedImageFileTypes];
1106     if([openPanel runModal] == NSFileHandlingPanelOKButton) {
1107         NSString * file = [[[openPanel URLs] objectAtIndex: 0] path];
1108         if(file == nil) {
1109             NSBeep();
1110             QEMU_Alert(@"Failed to convert URL to file path!");
1111             return;
1112         }
1114         Error *err = NULL;
1115         qmp_blockdev_change_medium([drive cStringUsingEncoding:
1116                                           NSASCIIStringEncoding],
1117                                    [file cStringUsingEncoding:
1118                                          NSASCIIStringEncoding],
1119                                    true, "raw",
1120                                    false, 0,
1121                                    &err);
1122         handleAnyDeviceErrors(err);
1123     }
1126 /* Verifies if the user really wants to quit */
1127 - (BOOL)verifyQuit
1129     NSAlert *alert = [NSAlert new];
1130     [alert autorelease];
1131     [alert setMessageText: @"Are you sure you want to quit QEMU?"];
1132     [alert addButtonWithTitle: @"Cancel"];
1133     [alert addButtonWithTitle: @"Quit"];
1134     if([alert runModal] == NSAlertSecondButtonReturn) {
1135         return YES;
1136     } else {
1137         return NO;
1138     }
1141 @end
1144 int main (int argc, const char * argv[]) {
1146     gArgc = argc;
1147     gArgv = (char **)argv;
1148     int i;
1150     /* In case we don't need to display a window, let's not do that */
1151     for (i = 1; i < argc; i++) {
1152         const char *opt = argv[i];
1154         if (opt[0] == '-') {
1155             /* Treat --foo the same as -foo.  */
1156             if (opt[1] == '-') {
1157                 opt++;
1158             }
1159             if (!strcmp(opt, "-h") || !strcmp(opt, "-help") ||
1160                 !strcmp(opt, "-vnc") ||
1161                 !strcmp(opt, "-nographic") ||
1162                 !strcmp(opt, "-version") ||
1163                 !strcmp(opt, "-curses") ||
1164                 !strcmp(opt, "-display") ||
1165                 !strcmp(opt, "-qtest")) {
1166                 return qemu_main(gArgc, gArgv, *_NSGetEnviron());
1167             }
1168         }
1169     }
1171     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
1173     // Pull this console process up to being a fully-fledged graphical
1174     // app with a menubar and Dock icon
1175     ProcessSerialNumber psn = { 0, kCurrentProcess };
1176     TransformProcessType(&psn, kProcessTransformToForegroundApplication);
1178     [NSApplication sharedApplication];
1180     // Add menus
1181     NSMenu      *menu;
1182     NSMenuItem  *menuItem;
1184     [NSApp setMainMenu:[[NSMenu alloc] init]];
1186     // Application menu
1187     menu = [[NSMenu alloc] initWithTitle:@""];
1188     [menu addItemWithTitle:@"About QEMU" action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; // About QEMU
1189     [menu addItem:[NSMenuItem separatorItem]]; //Separator
1190     [menu addItemWithTitle:@"Hide QEMU" action:@selector(hide:) keyEquivalent:@"h"]; //Hide QEMU
1191     menuItem = (NSMenuItem *)[menu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; // Hide Others
1192     [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)];
1193     [menu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; // Show All
1194     [menu addItem:[NSMenuItem separatorItem]]; //Separator
1195     [menu addItemWithTitle:@"Quit QEMU" action:@selector(terminate:) keyEquivalent:@"q"];
1196     menuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" action:nil keyEquivalent:@""];
1197     [menuItem setSubmenu:menu];
1198     [[NSApp mainMenu] addItem:menuItem];
1199     [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; // Workaround (this method is private since 10.4+)
1201     // Machine menu
1202     menu = [[NSMenu alloc] initWithTitle: @"Machine"];
1203     [menu setAutoenablesItems: NO];
1204     [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Pause" action: @selector(pauseQEMU:) keyEquivalent: @""] autorelease]];
1205     menuItem = [[[NSMenuItem alloc] initWithTitle: @"Resume" action: @selector(resumeQEMU:) keyEquivalent: @""] autorelease];
1206     [menu addItem: menuItem];
1207     [menuItem setEnabled: NO];
1208     [menu addItem: [NSMenuItem separatorItem]];
1209     [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Reset" action: @selector(restartQEMU:) keyEquivalent: @""] autorelease]];
1210     [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Power Down" action: @selector(powerDownQEMU:) keyEquivalent: @""] autorelease]];
1211     menuItem = [[[NSMenuItem alloc] initWithTitle: @"Machine" action:nil keyEquivalent:@""] autorelease];
1212     [menuItem setSubmenu:menu];
1213     [[NSApp mainMenu] addItem:menuItem];
1215     // View menu
1216     menu = [[NSMenu alloc] initWithTitle:@"View"];
1217     [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(doToggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen
1218     [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Zoom To Fit" action:@selector(zoomToFit:) keyEquivalent:@""] autorelease]];
1219     menuItem = [[[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""] autorelease];
1220     [menuItem setSubmenu:menu];
1221     [[NSApp mainMenu] addItem:menuItem];
1223     // Window menu
1224     menu = [[NSMenu alloc] initWithTitle:@"Window"];
1225     [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease]]; // Miniaturize
1226     menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease];
1227     [menuItem setSubmenu:menu];
1228     [[NSApp mainMenu] addItem:menuItem];
1229     [NSApp setWindowsMenu:menu];
1231     // Help menu
1232     menu = [[NSMenu alloc] initWithTitle:@"Help"];
1233     [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Documentation" action:@selector(showQEMUDoc:) keyEquivalent:@"?"] autorelease]]; // QEMU Help
1234     [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Technology" action:@selector(showQEMUTec:) keyEquivalent:@""] autorelease]]; // QEMU Help
1235     menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease];
1236     [menuItem setSubmenu:menu];
1237     [[NSApp mainMenu] addItem:menuItem];
1239     // Create an Application controller
1240     QemuCocoaAppController *appController = [[QemuCocoaAppController alloc] init];
1241     [NSApp setDelegate:appController];
1243     // Start the main event loop
1244     [NSApp run];
1246     [appController release];
1247     [pool release];
1249     return 0;
1254 #pragma mark qemu
1255 static void cocoa_update(DisplayChangeListener *dcl,
1256                          int x, int y, int w, int h)
1258     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
1260     COCOA_DEBUG("qemu_cocoa: cocoa_update\n");
1262     NSRect rect;
1263     if ([cocoaView cdx] == 1.0) {
1264         rect = NSMakeRect(x, [cocoaView gscreen].height - y - h, w, h);
1265     } else {
1266         rect = NSMakeRect(
1267             x * [cocoaView cdx],
1268             ([cocoaView gscreen].height - y - h) * [cocoaView cdy],
1269             w * [cocoaView cdx],
1270             h * [cocoaView cdy]);
1271     }
1272     [cocoaView setNeedsDisplayInRect:rect];
1274     [pool release];
1277 static void cocoa_switch(DisplayChangeListener *dcl,
1278                          DisplaySurface *surface)
1280     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
1282     COCOA_DEBUG("qemu_cocoa: cocoa_switch\n");
1283     [cocoaView switchSurface:surface];
1284     [pool release];
1287 static void cocoa_refresh(DisplayChangeListener *dcl)
1289     NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
1291     COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n");
1292     graphic_hw_update(NULL);
1294     if (qemu_input_is_absolute()) {
1295         if (![cocoaView isAbsoluteEnabled]) {
1296             if ([cocoaView isMouseGrabbed]) {
1297                 [cocoaView ungrabMouse];
1298             }
1299         }
1300         [cocoaView setAbsoluteEnabled:YES];
1301     }
1303     NSDate *distantPast;
1304     NSEvent *event;
1305     distantPast = [NSDate distantPast];
1306     do {
1307         event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:distantPast
1308                         inMode: NSDefaultRunLoopMode dequeue:YES];
1309         if (event != nil) {
1310             [cocoaView handleEvent:event];
1311         }
1312     } while(event != nil);
1313     [pool release];
1316 static void cocoa_cleanup(void)
1318     COCOA_DEBUG("qemu_cocoa: cocoa_cleanup\n");
1319     g_free(dcl);
1322 static const DisplayChangeListenerOps dcl_ops = {
1323     .dpy_name          = "cocoa",
1324     .dpy_gfx_update = cocoa_update,
1325     .dpy_gfx_switch = cocoa_switch,
1326     .dpy_refresh = cocoa_refresh,
1329 /* Returns a name for a given console */
1330 static NSString * getConsoleName(QemuConsole * console)
1332     return [NSString stringWithFormat: @"%s", qemu_console_get_label(console)];
1335 /* Add an entry to the View menu for each console */
1336 static void add_console_menu_entries(void)
1338     NSMenu *menu;
1339     NSMenuItem *menuItem;
1340     int index = 0;
1342     menu = [[[NSApp mainMenu] itemWithTitle:@"View"] submenu];
1344     [menu addItem:[NSMenuItem separatorItem]];
1346     while (qemu_console_lookup_by_index(index) != NULL) {
1347         menuItem = [[[NSMenuItem alloc] initWithTitle: getConsoleName(qemu_console_lookup_by_index(index))
1348                                                action: @selector(displayConsole:) keyEquivalent: @""] autorelease];
1349         [menuItem setTag: index];
1350         [menu addItem: menuItem];
1351         index++;
1352     }
1355 /* Make menu items for all removable devices.
1356  * Each device is given an 'Eject' and 'Change' menu item.
1357  */
1358 static void addRemovableDevicesMenuItems(void)
1360     NSMenu *menu;
1361     NSMenuItem *menuItem;
1362     BlockInfoList *currentDevice, *pointerToFree;
1363     NSString *deviceName;
1365     currentDevice = qmp_query_block(NULL);
1366     pointerToFree = currentDevice;
1367     if(currentDevice == NULL) {
1368         NSBeep();
1369         QEMU_Alert(@"Failed to query for block devices!");
1370         return;
1371     }
1373     menu = [[[NSApp mainMenu] itemWithTitle:@"Machine"] submenu];
1375     // Add a separator between related groups of menu items
1376     [menu addItem:[NSMenuItem separatorItem]];
1378     // Set the attributes to the "Removable Media" menu item
1379     NSString *titleString = @"Removable Media";
1380     NSMutableAttributedString *attString=[[NSMutableAttributedString alloc] initWithString:titleString];
1381     NSColor *newColor = [NSColor blackColor];
1382     NSFontManager *fontManager = [NSFontManager sharedFontManager];
1383     NSFont *font = [fontManager fontWithFamily:@"Helvetica"
1384                                           traits:NSBoldFontMask|NSItalicFontMask
1385                                           weight:0
1386                                             size:14];
1387     [attString addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, [titleString length])];
1388     [attString addAttribute:NSForegroundColorAttributeName value:newColor range:NSMakeRange(0, [titleString length])];
1389     [attString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInt: 1] range:NSMakeRange(0, [titleString length])];
1391     // Add the "Removable Media" menu item
1392     menuItem = [NSMenuItem new];
1393     [menuItem setAttributedTitle: attString];
1394     [menuItem setEnabled: NO];
1395     [menu addItem: menuItem];
1397     /* Loop thru all the block devices in the emulator */
1398     while (currentDevice) {
1399         deviceName = [[NSString stringWithFormat: @"%s", currentDevice->value->device] retain];
1401         if(currentDevice->value->removable) {
1402             menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Change %s...", currentDevice->value->device]
1403                                                   action: @selector(changeDeviceMedia:)
1404                                            keyEquivalent: @""];
1405             [menu addItem: menuItem];
1406             [menuItem setRepresentedObject: deviceName];
1407             [menuItem autorelease];
1409             menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Eject %s", currentDevice->value->device]
1410                                                   action: @selector(ejectDeviceMedia:)
1411                                            keyEquivalent: @""];
1412             [menu addItem: menuItem];
1413             [menuItem setRepresentedObject: deviceName];
1414             [menuItem autorelease];
1415         }
1416         currentDevice = currentDevice->next;
1417     }
1418     qapi_free_BlockInfoList(pointerToFree);
1421 void cocoa_display_init(DisplayState *ds, int full_screen)
1423     COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n");
1425     /* if fullscreen mode is to be used */
1426     if (full_screen == true) {
1427         [NSApp activateIgnoringOtherApps: YES];
1428         [(QemuCocoaAppController *)[[NSApplication sharedApplication] delegate] toggleFullScreen: nil];
1429     }
1431     dcl = g_malloc0(sizeof(DisplayChangeListener));
1433     // register vga output callbacks
1434     dcl->ops = &dcl_ops;
1435     register_displaychangelistener(dcl);
1437     // register cleanup function
1438     atexit(cocoa_cleanup);
1440     /* At this point QEMU has created all the consoles, so we can add View
1441      * menu entries for them.
1442      */
1443     add_console_menu_entries();
1445     /* Give all removable devices a menu item.
1446      * Has to be called after QEMU has started to
1447      * find out what removable devices it has.
1448      */
1449     addRemovableDevicesMenuItems();