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