xref: /openbmc/qemu/ui/cocoa.m (revision 80dd5aff1b3273e0ee5bec379df62647c0b39c1c)
13e230dd2SCorentin Chary/*
23e230dd2SCorentin Chary * QEMU Cocoa CG display driver
33e230dd2SCorentin Chary *
43e230dd2SCorentin Chary * Copyright (c) 2008 Mike Kronenberg
53e230dd2SCorentin Chary *
63e230dd2SCorentin Chary * Permission is hereby granted, free of charge, to any person obtaining a copy
73e230dd2SCorentin Chary * of this software and associated documentation files (the "Software"), to deal
83e230dd2SCorentin Chary * in the Software without restriction, including without limitation the rights
93e230dd2SCorentin Chary * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
103e230dd2SCorentin Chary * copies of the Software, and to permit persons to whom the Software is
113e230dd2SCorentin Chary * furnished to do so, subject to the following conditions:
123e230dd2SCorentin Chary *
133e230dd2SCorentin Chary * The above copyright notice and this permission notice shall be included in
143e230dd2SCorentin Chary * all copies or substantial portions of the Software.
153e230dd2SCorentin Chary *
163e230dd2SCorentin Chary * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
173e230dd2SCorentin Chary * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
183e230dd2SCorentin Chary * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
193e230dd2SCorentin Chary * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
203e230dd2SCorentin Chary * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
213e230dd2SCorentin Chary * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
223e230dd2SCorentin Chary * THE SOFTWARE.
233e230dd2SCorentin Chary */
243e230dd2SCorentin Chary
25e4a096b1SPeter Maydell#include "qemu/osdep.h"
26e4a096b1SPeter Maydell
273e230dd2SCorentin Chary#import <Cocoa/Cocoa.h>
283bbbee18SAndreas Färber#include <crt_externs.h>
293e230dd2SCorentin Chary
3049f95221SMarc-André Lureau#include "qemu/help-texts.h"
3188c39c86SMarc-André Lureau#include "qemu-main.h"
327e3e20d8SAkihiko Odaki#include "ui/clipboard.h"
3328ecbaeeSPaolo Bonzini#include "ui/console.h"
3421bae11aSGerd Hoffmann#include "ui/input.h"
356d73bb64SAkihiko Odaki#include "ui/kbd-state.h"
369c17d615SPaolo Bonzini#include "sysemu/sysemu.h"
3754d31236SMarkus Armbruster#include "sysemu/runstate.h"
38b0c3cf94SClaudio Fontana#include "sysemu/cpu-throttle.h"
39e688df6bSMarkus Armbruster#include "qapi/error.h"
4016bf5234SMarkus Armbruster#include "qapi/qapi-commands-block.h"
4190f8c0f9SPhilippe Mathieu-Daudé#include "qapi/qapi-commands-machine.h"
4216bf5234SMarkus Armbruster#include "qapi/qapi-commands-misc.h"
43693a3e01SJohn Arbuckle#include "sysemu/blockdev.h"
449e8204b1SProgrammingkid#include "qemu-version.h"
45e31746ecSAkihiko Odaki#include "qemu/cutils.h"
46db725815SMarkus Armbruster#include "qemu/main-loop.h"
470b8fa32fSMarkus Armbruster#include "qemu/module.h"
48aaac714fSJohn Arbuckle#include <Carbon/Carbon.h>
492e5b09fdSMarkus Armbruster#include "hw/core/cpu.h"
503e230dd2SCorentin Chary
515e24600aSBrendan Shanks#ifndef MAC_OS_X_VERSION_10_13
525e24600aSBrendan Shanks#define MAC_OS_X_VERSION_10_13 101300
535e24600aSBrendan Shanks#endif
543e230dd2SCorentin Chary
555e24600aSBrendan Shanks/* 10.14 deprecates NSOnState and NSOffState in favor of
565e24600aSBrendan Shanks * NSControlStateValueOn/Off, which were introduced in 10.13.
575e24600aSBrendan Shanks * Define for older versions
585e24600aSBrendan Shanks */
595e24600aSBrendan Shanks#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_13
605e24600aSBrendan Shanks#define NSControlStateValueOn NSOnState
615e24600aSBrendan Shanks#define NSControlStateValueOff NSOffState
625e24600aSBrendan Shanks#endif
633e230dd2SCorentin Chary
643e230dd2SCorentin Chary//#define DEBUG
653e230dd2SCorentin Chary
663e230dd2SCorentin Chary#ifdef DEBUG
673e230dd2SCorentin Chary#define COCOA_DEBUG(...)  { (void) fprintf (stdout, __VA_ARGS__); }
683e230dd2SCorentin Chary#else
693e230dd2SCorentin Chary#define COCOA_DEBUG(...)  ((void) 0)
703e230dd2SCorentin Chary#endif
713e230dd2SCorentin Chary
723e230dd2SCorentin Chary#define cgrect(nsrect) (*(CGRect *)&(nsrect))
733e230dd2SCorentin Chary
743e230dd2SCorentin Charytypedef struct {
753e230dd2SCorentin Chary    int width;
763e230dd2SCorentin Chary    int height;
773e230dd2SCorentin Chary} QEMUScreen;
783e230dd2SCorentin Chary
79cc7859c3SAkihiko Odakistatic void cocoa_update(DisplayChangeListener *dcl,
80cc7859c3SAkihiko Odaki                         int x, int y, int w, int h);
81cc7859c3SAkihiko Odaki
82cc7859c3SAkihiko Odakistatic void cocoa_switch(DisplayChangeListener *dcl,
83cc7859c3SAkihiko Odaki                         DisplaySurface *surface);
84cc7859c3SAkihiko Odaki
85cc7859c3SAkihiko Odakistatic void cocoa_refresh(DisplayChangeListener *dcl);
86cc7859c3SAkihiko Odaki
8799eb313dSAkihiko Odakistatic NSWindow *normalWindow;
88cc7859c3SAkihiko Odakistatic const DisplayChangeListenerOps dcl_ops = {
89cc7859c3SAkihiko Odaki    .dpy_name          = "cocoa",
90cc7859c3SAkihiko Odaki    .dpy_gfx_update = cocoa_update,
91cc7859c3SAkihiko Odaki    .dpy_gfx_switch = cocoa_switch,
92cc7859c3SAkihiko Odaki    .dpy_refresh = cocoa_refresh,
93cc7859c3SAkihiko Odaki};
94cc7859c3SAkihiko Odakistatic DisplayChangeListener dcl = {
95cc7859c3SAkihiko Odaki    .ops = &dcl_ops,
96cc7859c3SAkihiko Odaki};
9721bae11aSGerd Hoffmannstatic int last_buttons;
983487da6aSGerd Hoffmannstatic int cursor_hide = 1;
9948941a52SCarwyn Ellisstatic int left_command_key_enabled = 1;
1004797adceSGustavo Noronha Silvastatic bool swap_opt_cmd;
1013e230dd2SCorentin Chary
102cb823408SAkihiko Odakistatic int gArgc;
103cb823408SAkihiko Odakistatic char **gArgv;
104cb823408SAkihiko Odakistatic bool stretch_video;
105cb823408SAkihiko Odakistatic NSTextField *pauseLabel;
1063e230dd2SCorentin Chary
1075588840fSPeter Maydellstatic QemuSemaphore display_init_sem;
1085588840fSPeter Maydellstatic QemuSemaphore app_started_sem;
109dff742adSHikaru Nishidastatic bool allow_events;
1105588840fSPeter Maydell
1117e3e20d8SAkihiko Odakistatic NSInteger cbchangecount = -1;
1127e3e20d8SAkihiko Odakistatic QemuClipboardInfo *cbinfo;
1137e3e20d8SAkihiko Odakistatic QemuEvent cbevent;
1147e3e20d8SAkihiko Odaki
11560105d7aSPeter Maydell// Utility functions to run specified code block with iothread lock held
11631819e95SPeter Maydelltypedef void (^CodeBlock)(void);
11760105d7aSPeter Maydelltypedef bool (^BoolCodeBlock)(void);
11831819e95SPeter Maydell
11931819e95SPeter Maydellstatic void with_iothread_lock(CodeBlock block)
12031819e95SPeter Maydell{
12131819e95SPeter Maydell    bool locked = qemu_mutex_iothread_locked();
12231819e95SPeter Maydell    if (!locked) {
12331819e95SPeter Maydell        qemu_mutex_lock_iothread();
12431819e95SPeter Maydell    }
12531819e95SPeter Maydell    block();
12631819e95SPeter Maydell    if (!locked) {
12731819e95SPeter Maydell        qemu_mutex_unlock_iothread();
12831819e95SPeter Maydell    }
12931819e95SPeter Maydell}
13031819e95SPeter Maydell
13160105d7aSPeter Maydellstatic bool bool_with_iothread_lock(BoolCodeBlock block)
13260105d7aSPeter Maydell{
13360105d7aSPeter Maydell    bool locked = qemu_mutex_iothread_locked();
13460105d7aSPeter Maydell    bool val;
13560105d7aSPeter Maydell
13660105d7aSPeter Maydell    if (!locked) {
13760105d7aSPeter Maydell        qemu_mutex_lock_iothread();
13860105d7aSPeter Maydell    }
13960105d7aSPeter Maydell    val = block();
14060105d7aSPeter Maydell    if (!locked) {
14160105d7aSPeter Maydell        qemu_mutex_unlock_iothread();
14260105d7aSPeter Maydell    }
14360105d7aSPeter Maydell    return val;
14460105d7aSPeter Maydell}
14560105d7aSPeter Maydell
146aaac714fSJohn Arbuckle// Mac to QKeyCode conversion
147cb823408SAkihiko Odakistatic const int mac_to_qkeycode_map[] = {
148aaac714fSJohn Arbuckle    [kVK_ANSI_A] = Q_KEY_CODE_A,
149aaac714fSJohn Arbuckle    [kVK_ANSI_B] = Q_KEY_CODE_B,
150aaac714fSJohn Arbuckle    [kVK_ANSI_C] = Q_KEY_CODE_C,
151aaac714fSJohn Arbuckle    [kVK_ANSI_D] = Q_KEY_CODE_D,
152aaac714fSJohn Arbuckle    [kVK_ANSI_E] = Q_KEY_CODE_E,
153aaac714fSJohn Arbuckle    [kVK_ANSI_F] = Q_KEY_CODE_F,
154aaac714fSJohn Arbuckle    [kVK_ANSI_G] = Q_KEY_CODE_G,
155aaac714fSJohn Arbuckle    [kVK_ANSI_H] = Q_KEY_CODE_H,
156aaac714fSJohn Arbuckle    [kVK_ANSI_I] = Q_KEY_CODE_I,
157aaac714fSJohn Arbuckle    [kVK_ANSI_J] = Q_KEY_CODE_J,
158aaac714fSJohn Arbuckle    [kVK_ANSI_K] = Q_KEY_CODE_K,
159aaac714fSJohn Arbuckle    [kVK_ANSI_L] = Q_KEY_CODE_L,
160aaac714fSJohn Arbuckle    [kVK_ANSI_M] = Q_KEY_CODE_M,
161aaac714fSJohn Arbuckle    [kVK_ANSI_N] = Q_KEY_CODE_N,
162aaac714fSJohn Arbuckle    [kVK_ANSI_O] = Q_KEY_CODE_O,
163aaac714fSJohn Arbuckle    [kVK_ANSI_P] = Q_KEY_CODE_P,
164aaac714fSJohn Arbuckle    [kVK_ANSI_Q] = Q_KEY_CODE_Q,
165aaac714fSJohn Arbuckle    [kVK_ANSI_R] = Q_KEY_CODE_R,
166aaac714fSJohn Arbuckle    [kVK_ANSI_S] = Q_KEY_CODE_S,
167aaac714fSJohn Arbuckle    [kVK_ANSI_T] = Q_KEY_CODE_T,
168aaac714fSJohn Arbuckle    [kVK_ANSI_U] = Q_KEY_CODE_U,
169aaac714fSJohn Arbuckle    [kVK_ANSI_V] = Q_KEY_CODE_V,
170aaac714fSJohn Arbuckle    [kVK_ANSI_W] = Q_KEY_CODE_W,
171aaac714fSJohn Arbuckle    [kVK_ANSI_X] = Q_KEY_CODE_X,
172aaac714fSJohn Arbuckle    [kVK_ANSI_Y] = Q_KEY_CODE_Y,
173aaac714fSJohn Arbuckle    [kVK_ANSI_Z] = Q_KEY_CODE_Z,
1743e230dd2SCorentin Chary
175aaac714fSJohn Arbuckle    [kVK_ANSI_0] = Q_KEY_CODE_0,
176aaac714fSJohn Arbuckle    [kVK_ANSI_1] = Q_KEY_CODE_1,
177aaac714fSJohn Arbuckle    [kVK_ANSI_2] = Q_KEY_CODE_2,
178aaac714fSJohn Arbuckle    [kVK_ANSI_3] = Q_KEY_CODE_3,
179aaac714fSJohn Arbuckle    [kVK_ANSI_4] = Q_KEY_CODE_4,
180aaac714fSJohn Arbuckle    [kVK_ANSI_5] = Q_KEY_CODE_5,
181aaac714fSJohn Arbuckle    [kVK_ANSI_6] = Q_KEY_CODE_6,
182aaac714fSJohn Arbuckle    [kVK_ANSI_7] = Q_KEY_CODE_7,
183aaac714fSJohn Arbuckle    [kVK_ANSI_8] = Q_KEY_CODE_8,
184aaac714fSJohn Arbuckle    [kVK_ANSI_9] = Q_KEY_CODE_9,
185aaac714fSJohn Arbuckle
186aaac714fSJohn Arbuckle    [kVK_ANSI_Grave] = Q_KEY_CODE_GRAVE_ACCENT,
187aaac714fSJohn Arbuckle    [kVK_ANSI_Minus] = Q_KEY_CODE_MINUS,
188aaac714fSJohn Arbuckle    [kVK_ANSI_Equal] = Q_KEY_CODE_EQUAL,
189aaac714fSJohn Arbuckle    [kVK_Delete] = Q_KEY_CODE_BACKSPACE,
190aaac714fSJohn Arbuckle    [kVK_CapsLock] = Q_KEY_CODE_CAPS_LOCK,
191aaac714fSJohn Arbuckle    [kVK_Tab] = Q_KEY_CODE_TAB,
192aaac714fSJohn Arbuckle    [kVK_Return] = Q_KEY_CODE_RET,
193aaac714fSJohn Arbuckle    [kVK_ANSI_LeftBracket] = Q_KEY_CODE_BRACKET_LEFT,
194aaac714fSJohn Arbuckle    [kVK_ANSI_RightBracket] = Q_KEY_CODE_BRACKET_RIGHT,
195aaac714fSJohn Arbuckle    [kVK_ANSI_Backslash] = Q_KEY_CODE_BACKSLASH,
196aaac714fSJohn Arbuckle    [kVK_ANSI_Semicolon] = Q_KEY_CODE_SEMICOLON,
197aaac714fSJohn Arbuckle    [kVK_ANSI_Quote] = Q_KEY_CODE_APOSTROPHE,
198aaac714fSJohn Arbuckle    [kVK_ANSI_Comma] = Q_KEY_CODE_COMMA,
199aaac714fSJohn Arbuckle    [kVK_ANSI_Period] = Q_KEY_CODE_DOT,
200aaac714fSJohn Arbuckle    [kVK_ANSI_Slash] = Q_KEY_CODE_SLASH,
201aaac714fSJohn Arbuckle    [kVK_Space] = Q_KEY_CODE_SPC,
202aaac714fSJohn Arbuckle
203aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad0] = Q_KEY_CODE_KP_0,
204aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad1] = Q_KEY_CODE_KP_1,
205aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad2] = Q_KEY_CODE_KP_2,
206aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad3] = Q_KEY_CODE_KP_3,
207aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad4] = Q_KEY_CODE_KP_4,
208aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad5] = Q_KEY_CODE_KP_5,
209aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad6] = Q_KEY_CODE_KP_6,
210aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad7] = Q_KEY_CODE_KP_7,
211aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad8] = Q_KEY_CODE_KP_8,
212aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad9] = Q_KEY_CODE_KP_9,
213aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadDecimal] = Q_KEY_CODE_KP_DECIMAL,
214aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadEnter] = Q_KEY_CODE_KP_ENTER,
215aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadPlus] = Q_KEY_CODE_KP_ADD,
216aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadMinus] = Q_KEY_CODE_KP_SUBTRACT,
217aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadMultiply] = Q_KEY_CODE_KP_MULTIPLY,
218aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadDivide] = Q_KEY_CODE_KP_DIVIDE,
219aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadEquals] = Q_KEY_CODE_KP_EQUALS,
220aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadClear] = Q_KEY_CODE_NUM_LOCK,
221aaac714fSJohn Arbuckle
222aaac714fSJohn Arbuckle    [kVK_UpArrow] = Q_KEY_CODE_UP,
223aaac714fSJohn Arbuckle    [kVK_DownArrow] = Q_KEY_CODE_DOWN,
224aaac714fSJohn Arbuckle    [kVK_LeftArrow] = Q_KEY_CODE_LEFT,
225aaac714fSJohn Arbuckle    [kVK_RightArrow] = Q_KEY_CODE_RIGHT,
226aaac714fSJohn Arbuckle
227aaac714fSJohn Arbuckle    [kVK_Help] = Q_KEY_CODE_INSERT,
228aaac714fSJohn Arbuckle    [kVK_Home] = Q_KEY_CODE_HOME,
229aaac714fSJohn Arbuckle    [kVK_PageUp] = Q_KEY_CODE_PGUP,
230aaac714fSJohn Arbuckle    [kVK_PageDown] = Q_KEY_CODE_PGDN,
231aaac714fSJohn Arbuckle    [kVK_End] = Q_KEY_CODE_END,
232aaac714fSJohn Arbuckle    [kVK_ForwardDelete] = Q_KEY_CODE_DELETE,
233aaac714fSJohn Arbuckle
234aaac714fSJohn Arbuckle    [kVK_Escape] = Q_KEY_CODE_ESC,
235aaac714fSJohn Arbuckle
236aaac714fSJohn Arbuckle    /* The Power key can't be used directly because the operating system uses
237aaac714fSJohn Arbuckle     * it. This key can be emulated by using it in place of another key such as
238aaac714fSJohn Arbuckle     * F1. Don't forget to disable the real key binding.
239aaac714fSJohn Arbuckle     */
240aaac714fSJohn Arbuckle    /* [kVK_F1] = Q_KEY_CODE_POWER, */
241aaac714fSJohn Arbuckle
242aaac714fSJohn Arbuckle    [kVK_F1] = Q_KEY_CODE_F1,
243aaac714fSJohn Arbuckle    [kVK_F2] = Q_KEY_CODE_F2,
244aaac714fSJohn Arbuckle    [kVK_F3] = Q_KEY_CODE_F3,
245aaac714fSJohn Arbuckle    [kVK_F4] = Q_KEY_CODE_F4,
246aaac714fSJohn Arbuckle    [kVK_F5] = Q_KEY_CODE_F5,
247aaac714fSJohn Arbuckle    [kVK_F6] = Q_KEY_CODE_F6,
248aaac714fSJohn Arbuckle    [kVK_F7] = Q_KEY_CODE_F7,
249aaac714fSJohn Arbuckle    [kVK_F8] = Q_KEY_CODE_F8,
250aaac714fSJohn Arbuckle    [kVK_F9] = Q_KEY_CODE_F9,
251aaac714fSJohn Arbuckle    [kVK_F10] = Q_KEY_CODE_F10,
252aaac714fSJohn Arbuckle    [kVK_F11] = Q_KEY_CODE_F11,
253aaac714fSJohn Arbuckle    [kVK_F12] = Q_KEY_CODE_F12,
254aaac714fSJohn Arbuckle    [kVK_F13] = Q_KEY_CODE_PRINT,
255aaac714fSJohn Arbuckle    [kVK_F14] = Q_KEY_CODE_SCROLL_LOCK,
256aaac714fSJohn Arbuckle    [kVK_F15] = Q_KEY_CODE_PAUSE,
257aaac714fSJohn Arbuckle
258708b7255SAkihiko Odaki    // JIS keyboards only
259708b7255SAkihiko Odaki    [kVK_JIS_Yen] = Q_KEY_CODE_YEN,
260708b7255SAkihiko Odaki    [kVK_JIS_Underscore] = Q_KEY_CODE_RO,
261708b7255SAkihiko Odaki    [kVK_JIS_KeypadComma] = Q_KEY_CODE_KP_COMMA,
262708b7255SAkihiko Odaki    [kVK_JIS_Eisu] = Q_KEY_CODE_MUHENKAN,
263708b7255SAkihiko Odaki    [kVK_JIS_Kana] = Q_KEY_CODE_HENKAN,
264708b7255SAkihiko Odaki
2653e230dd2SCorentin Chary    /*
266aaac714fSJohn Arbuckle     * The eject and volume keys can't be used here because they are handled at
267aaac714fSJohn Arbuckle     * a lower level than what an Application can see.
2683e230dd2SCorentin Chary     */
2693e230dd2SCorentin Chary};
2703e230dd2SCorentin Chary
2713e230dd2SCorentin Charystatic int cocoa_keycode_to_qemu(int keycode)
2723e230dd2SCorentin Chary{
273aaac714fSJohn Arbuckle    if (ARRAY_SIZE(mac_to_qkeycode_map) <= keycode) {
2744313739aSAkihiko Odaki        error_report("(cocoa) warning unknown keycode 0x%x", keycode);
2753e230dd2SCorentin Chary        return 0;
2763e230dd2SCorentin Chary    }
277aaac714fSJohn Arbuckle    return mac_to_qkeycode_map[keycode];
2783e230dd2SCorentin Chary}
2793e230dd2SCorentin Chary
280693a3e01SJohn Arbuckle/* Displays an alert dialog box with the specified message */
281693a3e01SJohn Arbucklestatic void QEMU_Alert(NSString *message)
282693a3e01SJohn Arbuckle{
283693a3e01SJohn Arbuckle    NSAlert *alert;
284693a3e01SJohn Arbuckle    alert = [NSAlert new];
285693a3e01SJohn Arbuckle    [alert setMessageText: message];
286693a3e01SJohn Arbuckle    [alert runModal];
287693a3e01SJohn Arbuckle}
2883e230dd2SCorentin Chary
289693a3e01SJohn Arbuckle/* Handles any errors that happen with a device transaction */
290693a3e01SJohn Arbucklestatic void handleAnyDeviceErrors(Error * err)
291693a3e01SJohn Arbuckle{
292693a3e01SJohn Arbuckle    if (err) {
293693a3e01SJohn Arbuckle        QEMU_Alert([NSString stringWithCString: error_get_pretty(err)
294693a3e01SJohn Arbuckle                                      encoding: NSASCIIStringEncoding]);
295693a3e01SJohn Arbuckle        error_free(err);
296693a3e01SJohn Arbuckle    }
297693a3e01SJohn Arbuckle}
2983e230dd2SCorentin Chary
2993e230dd2SCorentin Chary/*
3003e230dd2SCorentin Chary ------------------------------------------------------
3013e230dd2SCorentin Chary    QemuCocoaView
3023e230dd2SCorentin Chary ------------------------------------------------------
3033e230dd2SCorentin Chary*/
3043e230dd2SCorentin Chary@interface QemuCocoaView : NSView
3053e230dd2SCorentin Chary{
3063e230dd2SCorentin Chary    QEMUScreen screen;
3073e230dd2SCorentin Chary    NSWindow *fullScreenWindow;
3083e230dd2SCorentin Chary    float cx,cy,cw,ch,cdx,cdy;
3095588840fSPeter Maydell    pixman_image_t *pixman_image;
3106d73bb64SAkihiko Odaki    QKbdState *kbd;
31149b9bd4dSPeter Maydell    BOOL isMouseGrabbed;
3123e230dd2SCorentin Chary    BOOL isFullscreen;
3133e230dd2SCorentin Chary    BOOL isAbsoluteEnabled;
314f844cdb9SGustavo Noronha Silva    CFMachPortRef eventsTap;
3153e230dd2SCorentin Chary}
31672a3e316SPeter Maydell- (void) switchSurface:(pixman_image_t *)image;
3173e230dd2SCorentin Chary- (void) grabMouse;
3183e230dd2SCorentin Chary- (void) ungrabMouse;
3193e230dd2SCorentin Chary- (void) toggleFullScreen:(id)sender;
320f844cdb9SGustavo Noronha Silva- (void) setFullGrab:(id)sender;
3219c3a418eSJohn Arbuckle- (void) handleMonitorInput:(NSEvent *)event;
32260105d7aSPeter Maydell- (bool) handleEvent:(NSEvent *)event;
32360105d7aSPeter Maydell- (bool) handleEventLocked:(NSEvent *)event;
3243e230dd2SCorentin Chary- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled;
325f61c387eSPeter Maydell/* The state surrounding mouse grabbing is potentially confusing.
326f61c387eSPeter Maydell * isAbsoluteEnabled tracks qemu_input_is_absolute() [ie "is the emulated
327f61c387eSPeter Maydell *   pointing device an absolute-position one?"], but is only updated on
328f61c387eSPeter Maydell *   next refresh.
329f61c387eSPeter Maydell * isMouseGrabbed tracks whether GUI events are directed to the guest;
330f61c387eSPeter Maydell *   it controls whether special keys like Cmd get sent to the guest,
331f61c387eSPeter Maydell *   and whether we capture the mouse when in non-absolute mode.
332f61c387eSPeter Maydell */
33349b9bd4dSPeter Maydell- (BOOL) isMouseGrabbed;
3343e230dd2SCorentin Chary- (BOOL) isAbsoluteEnabled;
3353e230dd2SCorentin Chary- (float) cdx;
3363e230dd2SCorentin Chary- (float) cdy;
3373e230dd2SCorentin Chary- (QEMUScreen) gscreen;
3383b178b71SJohn Arbuckle- (void) raiseAllKeys;
3393e230dd2SCorentin Chary@end
3403e230dd2SCorentin Chary
3417fee199cSAndreas FärberQemuCocoaView *cocoaView;
3427fee199cSAndreas Färber
343f844cdb9SGustavo Noronha Silvastatic CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEventRef cgEvent, void *userInfo)
344f844cdb9SGustavo Noronha Silva{
345f844cdb9SGustavo Noronha Silva    QemuCocoaView *cocoaView = userInfo;
346f844cdb9SGustavo Noronha Silva    NSEvent *event = [NSEvent eventWithCGEvent:cgEvent];
347f844cdb9SGustavo Noronha Silva    if ([cocoaView isMouseGrabbed] && [cocoaView handleEvent:event]) {
348f844cdb9SGustavo Noronha Silva        COCOA_DEBUG("Global events tap: qemu handled the event, capturing!\n");
349f844cdb9SGustavo Noronha Silva        return NULL;
350f844cdb9SGustavo Noronha Silva    }
351f844cdb9SGustavo Noronha Silva    COCOA_DEBUG("Global events tap: qemu did not handle the event, letting it through...\n");
352f844cdb9SGustavo Noronha Silva
353f844cdb9SGustavo Noronha Silva    return cgEvent;
354f844cdb9SGustavo Noronha Silva}
355f844cdb9SGustavo Noronha Silva
3563e230dd2SCorentin Chary@implementation QemuCocoaView
3573e230dd2SCorentin Chary- (id)initWithFrame:(NSRect)frameRect
3583e230dd2SCorentin Chary{
3593e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaView: initWithFrame\n");
3603e230dd2SCorentin Chary
3613e230dd2SCorentin Chary    self = [super initWithFrame:frameRect];
3623e230dd2SCorentin Chary    if (self) {
3633e230dd2SCorentin Chary
3643e230dd2SCorentin Chary        screen.width = frameRect.size.width;
3653e230dd2SCorentin Chary        screen.height = frameRect.size.height;
3666d73bb64SAkihiko Odaki        kbd = qkbd_state_init(dcl.con);
3673e230dd2SCorentin Chary
3683e230dd2SCorentin Chary    }
3693e230dd2SCorentin Chary    return self;
3703e230dd2SCorentin Chary}
3713e230dd2SCorentin Chary
3723e230dd2SCorentin Chary- (void) dealloc
3733e230dd2SCorentin Chary{
3743e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaView: dealloc\n");
3753e230dd2SCorentin Chary
376c0ff29d1SAkihiko Odaki    if (pixman_image) {
3775588840fSPeter Maydell        pixman_image_unref(pixman_image);
3785588840fSPeter Maydell    }
3793e230dd2SCorentin Chary
3806d73bb64SAkihiko Odaki    qkbd_state_free(kbd);
381f844cdb9SGustavo Noronha Silva
382f844cdb9SGustavo Noronha Silva    if (eventsTap) {
383f844cdb9SGustavo Noronha Silva        CFRelease(eventsTap);
384f844cdb9SGustavo Noronha Silva    }
385f844cdb9SGustavo Noronha Silva
3863e230dd2SCorentin Chary    [super dealloc];
3873e230dd2SCorentin Chary}
3883e230dd2SCorentin Chary
3893e230dd2SCorentin Chary- (BOOL) isOpaque
3903e230dd2SCorentin Chary{
3913e230dd2SCorentin Chary    return YES;
3923e230dd2SCorentin Chary}
3933e230dd2SCorentin Chary
3945dd45beeSPeter Maydell- (BOOL) screenContainsPoint:(NSPoint) p
3955dd45beeSPeter Maydell{
3965dd45beeSPeter Maydell    return (p.x > -1 && p.x < screen.width && p.y > -1 && p.y < screen.height);
3975dd45beeSPeter Maydell}
3985dd45beeSPeter Maydell
3992044dff8SChen Zhang/* Get location of event and convert to virtual screen coordinate */
4002044dff8SChen Zhang- (CGPoint) screenLocationOfEvent:(NSEvent *)ev
4012044dff8SChen Zhang{
4022044dff8SChen Zhang    NSWindow *eventWindow = [ev window];
4032044dff8SChen Zhang    // XXX: Use CGRect and -convertRectFromScreen: to support macOS 10.10
4042044dff8SChen Zhang    CGRect r = CGRectZero;
4052044dff8SChen Zhang    r.origin = [ev locationInWindow];
4062044dff8SChen Zhang    if (!eventWindow) {
4072044dff8SChen Zhang        if (!isFullscreen) {
4082044dff8SChen Zhang            return [[self window] convertRectFromScreen:r].origin;
4092044dff8SChen Zhang        } else {
4102044dff8SChen Zhang            CGPoint locationInSelfWindow = [[self window] convertRectFromScreen:r].origin;
4112044dff8SChen Zhang            CGPoint loc = [self convertPoint:locationInSelfWindow fromView:nil];
4122044dff8SChen Zhang            if (stretch_video) {
4132044dff8SChen Zhang                loc.x /= cdx;
4142044dff8SChen Zhang                loc.y /= cdy;
4152044dff8SChen Zhang            }
4162044dff8SChen Zhang            return loc;
4172044dff8SChen Zhang        }
4182044dff8SChen Zhang    } else if ([[self window] isEqual:eventWindow]) {
4192044dff8SChen Zhang        if (!isFullscreen) {
4202044dff8SChen Zhang            return r.origin;
4212044dff8SChen Zhang        } else {
4222044dff8SChen Zhang            CGPoint loc = [self convertPoint:r.origin fromView:nil];
4232044dff8SChen Zhang            if (stretch_video) {
4242044dff8SChen Zhang                loc.x /= cdx;
4252044dff8SChen Zhang                loc.y /= cdy;
4262044dff8SChen Zhang            }
4272044dff8SChen Zhang            return loc;
4282044dff8SChen Zhang        }
4292044dff8SChen Zhang    } else {
4302044dff8SChen Zhang        return [[self window] convertRectFromScreen:[eventWindow convertRectToScreen:r]].origin;
4312044dff8SChen Zhang    }
4322044dff8SChen Zhang}
4332044dff8SChen Zhang
43413aefd30SPeter Maydell- (void) hideCursor
43513aefd30SPeter Maydell{
43613aefd30SPeter Maydell    if (!cursor_hide) {
43713aefd30SPeter Maydell        return;
43813aefd30SPeter Maydell    }
43913aefd30SPeter Maydell    [NSCursor hide];
44013aefd30SPeter Maydell}
44113aefd30SPeter Maydell
44213aefd30SPeter Maydell- (void) unhideCursor
44313aefd30SPeter Maydell{
44413aefd30SPeter Maydell    if (!cursor_hide) {
44513aefd30SPeter Maydell        return;
44613aefd30SPeter Maydell    }
44713aefd30SPeter Maydell    [NSCursor unhide];
44813aefd30SPeter Maydell}
44913aefd30SPeter Maydell
4503e230dd2SCorentin Chary- (void) drawRect:(NSRect) rect
4513e230dd2SCorentin Chary{
4523e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaView: drawRect\n");
4533e230dd2SCorentin Chary
4543e230dd2SCorentin Chary    // get CoreGraphic context
4555e24600aSBrendan Shanks    CGContextRef viewContextRef = [[NSGraphicsContext currentContext] CGContext];
4565e24600aSBrendan Shanks
4573e230dd2SCorentin Chary    CGContextSetInterpolationQuality (viewContextRef, kCGInterpolationNone);
4583e230dd2SCorentin Chary    CGContextSetShouldAntialias (viewContextRef, NO);
4593e230dd2SCorentin Chary
4603e230dd2SCorentin Chary    // draw screen bitmap directly to Core Graphics context
461c0ff29d1SAkihiko Odaki    if (!pixman_image) {
4627d270b1cSPeter Maydell        // Draw request before any guest device has set up a framebuffer:
4637d270b1cSPeter Maydell        // just draw an opaque black rectangle
4647d270b1cSPeter Maydell        CGContextSetRGBFillColor(viewContextRef, 0, 0, 0, 1.0);
4657d270b1cSPeter Maydell        CGContextFillRect(viewContextRef, NSRectToCGRect(rect));
4667d270b1cSPeter Maydell    } else {
467c0ff29d1SAkihiko Odaki        int w = pixman_image_get_width(pixman_image);
468c0ff29d1SAkihiko Odaki        int h = pixman_image_get_height(pixman_image);
469c0ff29d1SAkihiko Odaki        int bitsPerPixel = PIXMAN_FORMAT_BPP(pixman_image_get_format(pixman_image));
470d9c32b8fSAkihiko Odaki        int stride = pixman_image_get_stride(pixman_image);
471c0ff29d1SAkihiko Odaki        CGDataProviderRef dataProviderRef = CGDataProviderCreateWithData(
472c0ff29d1SAkihiko Odaki            NULL,
473c0ff29d1SAkihiko Odaki            pixman_image_get_data(pixman_image),
474d9c32b8fSAkihiko Odaki            stride * h,
475c0ff29d1SAkihiko Odaki            NULL
476c0ff29d1SAkihiko Odaki        );
4773e230dd2SCorentin Chary        CGImageRef imageRef = CGImageCreate(
478c0ff29d1SAkihiko Odaki            w, //width
479c0ff29d1SAkihiko Odaki            h, //height
480d9c32b8fSAkihiko Odaki            DIV_ROUND_UP(bitsPerPixel, 8) * 2, //bitsPerComponent
481c0ff29d1SAkihiko Odaki            bitsPerPixel, //bitsPerPixel
482d9c32b8fSAkihiko Odaki            stride, //bytesPerRow
483ae57d35cSAkihiko Odaki            CGColorSpaceCreateWithName(kCGColorSpaceSRGB), //colorspace
484ae57d35cSAkihiko Odaki            kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, //bitmapInfo
4853e230dd2SCorentin Chary            dataProviderRef, //provider
4863e230dd2SCorentin Chary            NULL, //decode
4873e230dd2SCorentin Chary            0, //interpolate
4883e230dd2SCorentin Chary            kCGRenderingIntentDefault //intent
4893e230dd2SCorentin Chary        );
4903e230dd2SCorentin Chary        // selective drawing code (draws only dirty rectangles) (OS X >= 10.4)
4913e230dd2SCorentin Chary        const NSRect *rectList;
4923e230dd2SCorentin Chary        NSInteger rectCount;
4933e230dd2SCorentin Chary        int i;
4943e230dd2SCorentin Chary        CGImageRef clipImageRef;
4953e230dd2SCorentin Chary        CGRect clipRect;
4963e230dd2SCorentin Chary
4973e230dd2SCorentin Chary        [self getRectsBeingDrawn:&rectList count:&rectCount];
4983e230dd2SCorentin Chary        for (i = 0; i < rectCount; i++) {
4993e230dd2SCorentin Chary            clipRect.origin.x = rectList[i].origin.x / cdx;
500c0ff29d1SAkihiko Odaki            clipRect.origin.y = (float)h - (rectList[i].origin.y + rectList[i].size.height) / cdy;
5013e230dd2SCorentin Chary            clipRect.size.width = rectList[i].size.width / cdx;
5023e230dd2SCorentin Chary            clipRect.size.height = rectList[i].size.height / cdy;
5033e230dd2SCorentin Chary            clipImageRef = CGImageCreateWithImageInRect(
5043e230dd2SCorentin Chary                                                        imageRef,
5053e230dd2SCorentin Chary                                                        clipRect
5063e230dd2SCorentin Chary                                                        );
5073e230dd2SCorentin Chary            CGContextDrawImage (viewContextRef, cgrect(rectList[i]), clipImageRef);
5083e230dd2SCorentin Chary            CGImageRelease (clipImageRef);
5093e230dd2SCorentin Chary        }
5103e230dd2SCorentin Chary        CGImageRelease (imageRef);
511c0ff29d1SAkihiko Odaki        CGDataProviderRelease(dataProviderRef);
5123e230dd2SCorentin Chary    }
5133e230dd2SCorentin Chary}
5143e230dd2SCorentin Chary
5153e230dd2SCorentin Chary- (void) setContentDimensions
5163e230dd2SCorentin Chary{
5173e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaView: setContentDimensions\n");
5183e230dd2SCorentin Chary
5193e230dd2SCorentin Chary    if (isFullscreen) {
5203e230dd2SCorentin Chary        cdx = [[NSScreen mainScreen] frame].size.width / (float)screen.width;
5213e230dd2SCorentin Chary        cdy = [[NSScreen mainScreen] frame].size.height / (float)screen.height;
5225d1b2eefSProgrammingkid
5235d1b2eefSProgrammingkid        /* stretches video, but keeps same aspect ratio */
5245d1b2eefSProgrammingkid        if (stretch_video == true) {
5255d1b2eefSProgrammingkid            /* use smallest stretch value - prevents clipping on sides */
5265d1b2eefSProgrammingkid            if (MIN(cdx, cdy) == cdx) {
5275d1b2eefSProgrammingkid                cdy = cdx;
5285d1b2eefSProgrammingkid            } else {
5295d1b2eefSProgrammingkid                cdx = cdy;
5305d1b2eefSProgrammingkid            }
5315d1b2eefSProgrammingkid        } else {  /* No stretching */
5325d1b2eefSProgrammingkid            cdx = cdy = 1;
5335d1b2eefSProgrammingkid        }
5343e230dd2SCorentin Chary        cw = screen.width * cdx;
5353e230dd2SCorentin Chary        ch = screen.height * cdy;
5363e230dd2SCorentin Chary        cx = ([[NSScreen mainScreen] frame].size.width - cw) / 2.0;
5373e230dd2SCorentin Chary        cy = ([[NSScreen mainScreen] frame].size.height - ch) / 2.0;
5383e230dd2SCorentin Chary    } else {
5393e230dd2SCorentin Chary        cx = 0;
5403e230dd2SCorentin Chary        cy = 0;
5413e230dd2SCorentin Chary        cw = screen.width;
5423e230dd2SCorentin Chary        ch = screen.height;
5433e230dd2SCorentin Chary        cdx = 1.0;
5443e230dd2SCorentin Chary        cdy = 1.0;
5453e230dd2SCorentin Chary    }
5463e230dd2SCorentin Chary}
5473e230dd2SCorentin Chary
5488d65dee2SPeter Maydell- (void) updateUIInfoLocked
54915280e85SAkihiko Odaki{
5508d65dee2SPeter Maydell    /* Must be called with the iothread lock, i.e. via updateUIInfo */
55115280e85SAkihiko Odaki    NSSize frameSize;
55215280e85SAkihiko Odaki    QemuUIInfo info;
55315280e85SAkihiko Odaki
55415280e85SAkihiko Odaki    if (!qemu_console_is_graphic(dcl.con)) {
55515280e85SAkihiko Odaki        return;
55615280e85SAkihiko Odaki    }
55715280e85SAkihiko Odaki
55815280e85SAkihiko Odaki    if ([self window]) {
55915280e85SAkihiko Odaki        NSDictionary *description = [[[self window] screen] deviceDescription];
56015280e85SAkihiko Odaki        CGDirectDisplayID display = [[description objectForKey:@"NSScreenNumber"] unsignedIntValue];
56115280e85SAkihiko Odaki        NSSize screenSize = [[[self window] screen] frame].size;
56215280e85SAkihiko Odaki        CGSize screenPhysicalSize = CGDisplayScreenSize(display);
56315280e85SAkihiko Odaki
56415280e85SAkihiko Odaki        frameSize = isFullscreen ? screenSize : [self frame].size;
56515280e85SAkihiko Odaki        info.width_mm = frameSize.width / screenSize.width * screenPhysicalSize.width;
56615280e85SAkihiko Odaki        info.height_mm = frameSize.height / screenSize.height * screenPhysicalSize.height;
56715280e85SAkihiko Odaki    } else {
56815280e85SAkihiko Odaki        frameSize = [self frame].size;
56915280e85SAkihiko Odaki        info.width_mm = 0;
57015280e85SAkihiko Odaki        info.height_mm = 0;
57115280e85SAkihiko Odaki    }
57215280e85SAkihiko Odaki
57315280e85SAkihiko Odaki    info.xoff = 0;
57415280e85SAkihiko Odaki    info.yoff = 0;
57515280e85SAkihiko Odaki    info.width = frameSize.width;
57615280e85SAkihiko Odaki    info.height = frameSize.height;
57715280e85SAkihiko Odaki
578ca19ef52SMarc-André Lureau    dpy_set_ui_info(dcl.con, &info, TRUE);
57915280e85SAkihiko Odaki}
58015280e85SAkihiko Odaki
5818d65dee2SPeter Maydell- (void) updateUIInfo
5828d65dee2SPeter Maydell{
5838d65dee2SPeter Maydell    if (!allow_events) {
5848d65dee2SPeter Maydell        /*
5858d65dee2SPeter Maydell         * Don't try to tell QEMU about UI information in the application
5868d65dee2SPeter Maydell         * startup phase -- we haven't yet registered dcl with the QEMU UI
5878d65dee2SPeter Maydell         * layer, and also trying to take the iothread lock would deadlock.
5888d65dee2SPeter Maydell         * When cocoa_display_init() does register the dcl, the UI layer
5898d65dee2SPeter Maydell         * will call cocoa_switch(), which will call updateUIInfo, so
5908d65dee2SPeter Maydell         * we don't lose any information here.
5918d65dee2SPeter Maydell         */
5928d65dee2SPeter Maydell        return;
5938d65dee2SPeter Maydell    }
5948d65dee2SPeter Maydell
5958d65dee2SPeter Maydell    with_iothread_lock(^{
5968d65dee2SPeter Maydell        [self updateUIInfoLocked];
5978d65dee2SPeter Maydell    });
5988d65dee2SPeter Maydell}
5998d65dee2SPeter Maydell
60015280e85SAkihiko Odaki- (void)viewDidMoveToWindow
60115280e85SAkihiko Odaki{
60215280e85SAkihiko Odaki    [self updateUIInfo];
60315280e85SAkihiko Odaki}
60415280e85SAkihiko Odaki
60572a3e316SPeter Maydell- (void) switchSurface:(pixman_image_t *)image
6063e230dd2SCorentin Chary{
6075e00d3acSGerd Hoffmann    COCOA_DEBUG("QemuCocoaView: switchSurface\n");
6083e230dd2SCorentin Chary
60972a3e316SPeter Maydell    int w = pixman_image_get_width(image);
61072a3e316SPeter Maydell    int h = pixman_image_get_height(image);
611381600daSPeter Maydell    /* cdx == 0 means this is our very first surface, in which case we need
612381600daSPeter Maydell     * to recalculate the content dimensions even if it happens to be the size
613381600daSPeter Maydell     * of the initial empty window.
614381600daSPeter Maydell     */
615381600daSPeter Maydell    bool isResize = (w != screen.width || h != screen.height || cdx == 0.0);
616d3345a04SPeter Maydell
617d3345a04SPeter Maydell    int oldh = screen.height;
618d3345a04SPeter Maydell    if (isResize) {
619d3345a04SPeter Maydell        // Resize before we trigger the redraw, or we'll redraw at the wrong size
620d3345a04SPeter Maydell        COCOA_DEBUG("switchSurface: new size %d x %d\n", w, h);
621d3345a04SPeter Maydell        screen.width = w;
622d3345a04SPeter Maydell        screen.height = h;
623d3345a04SPeter Maydell        [self setContentDimensions];
624d3345a04SPeter Maydell        [self setFrame:NSMakeRect(cx, cy, cw, ch)];
625d3345a04SPeter Maydell    }
6268510d91eSPeter Maydell
6273e230dd2SCorentin Chary    // update screenBuffer
628c0ff29d1SAkihiko Odaki    if (pixman_image) {
6295588840fSPeter Maydell        pixman_image_unref(pixman_image);
6305588840fSPeter Maydell    }
6313e230dd2SCorentin Chary
6325588840fSPeter Maydell    pixman_image = image;
6333e230dd2SCorentin Chary
6343e230dd2SCorentin Chary    // update windows
6353e230dd2SCorentin Chary    if (isFullscreen) {
6363e230dd2SCorentin Chary        [[fullScreenWindow contentView] setFrame:[[NSScreen mainScreen] frame]];
637d3345a04SPeter Maydell        [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + oldh, w, h + [normalWindow frame].size.height - oldh) display:NO animate:NO];
6383e230dd2SCorentin Chary    } else {
6393e230dd2SCorentin Chary        if (qemu_name)
6403e230dd2SCorentin Chary            [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]];
641d3345a04SPeter Maydell        [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + oldh, w, h + [normalWindow frame].size.height - oldh) display:YES animate:NO];
6423e230dd2SCorentin Chary    }
643d3345a04SPeter Maydell
644d3345a04SPeter Maydell    if (isResize) {
6453e230dd2SCorentin Chary        [normalWindow center];
646d3345a04SPeter Maydell    }
6473e230dd2SCorentin Chary}
6483e230dd2SCorentin Chary
6493e230dd2SCorentin Chary- (void) toggleFullScreen:(id)sender
6503e230dd2SCorentin Chary{
6513e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaView: toggleFullScreen\n");
6523e230dd2SCorentin Chary
6533e230dd2SCorentin Chary    if (isFullscreen) { // switch from fullscreen to desktop
6543e230dd2SCorentin Chary        isFullscreen = FALSE;
6553e230dd2SCorentin Chary        [self ungrabMouse];
6563e230dd2SCorentin Chary        [self setContentDimensions];
6573e230dd2SCorentin Chary        [fullScreenWindow close];
6583e230dd2SCorentin Chary        [normalWindow setContentView: self];
6593e230dd2SCorentin Chary        [normalWindow makeKeyAndOrderFront: self];
6603e230dd2SCorentin Chary        [NSMenu setMenuBarVisible:YES];
6613e230dd2SCorentin Chary    } else { // switch from desktop to fullscreen
6623e230dd2SCorentin Chary        isFullscreen = TRUE;
6635d1b2eefSProgrammingkid        [normalWindow orderOut: nil]; /* Hide the window */
6643e230dd2SCorentin Chary        [self grabMouse];
6653e230dd2SCorentin Chary        [self setContentDimensions];
6663e230dd2SCorentin Chary        [NSMenu setMenuBarVisible:NO];
6673e230dd2SCorentin Chary        fullScreenWindow = [[NSWindow alloc] initWithContentRect:[[NSScreen mainScreen] frame]
6684ba967adSBrendan Shanks            styleMask:NSWindowStyleMaskBorderless
6693e230dd2SCorentin Chary            backing:NSBackingStoreBuffered
6703e230dd2SCorentin Chary            defer:NO];
6715d1b2eefSProgrammingkid        [fullScreenWindow setAcceptsMouseMovedEvents: YES];
6723e230dd2SCorentin Chary        [fullScreenWindow setHasShadow:NO];
6735d1b2eefSProgrammingkid        [fullScreenWindow setBackgroundColor: [NSColor blackColor]];
6745d1b2eefSProgrammingkid        [self setFrame:NSMakeRect(cx, cy, cw, ch)];
6755d1b2eefSProgrammingkid        [[fullScreenWindow contentView] addSubview: self];
6763e230dd2SCorentin Chary        [fullScreenWindow makeKeyAndOrderFront:self];
6773e230dd2SCorentin Chary    }
6783e230dd2SCorentin Chary}
6793e230dd2SCorentin Chary
680f844cdb9SGustavo Noronha Silva- (void) setFullGrab:(id)sender
681f844cdb9SGustavo Noronha Silva{
682f844cdb9SGustavo Noronha Silva    COCOA_DEBUG("QemuCocoaView: setFullGrab\n");
683f844cdb9SGustavo Noronha Silva
684f844cdb9SGustavo Noronha Silva    CGEventMask mask = CGEventMaskBit(kCGEventKeyDown) | CGEventMaskBit(kCGEventKeyUp) | CGEventMaskBit(kCGEventFlagsChanged);
685f844cdb9SGustavo Noronha Silva    eventsTap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault,
686f844cdb9SGustavo Noronha Silva                                 mask, handleTapEvent, self);
687f844cdb9SGustavo Noronha Silva    if (!eventsTap) {
688f844cdb9SGustavo Noronha Silva        warn_report("Could not create event tap, system key combos will not be captured.\n");
689f844cdb9SGustavo Noronha Silva        return;
690f844cdb9SGustavo Noronha Silva    } else {
691f844cdb9SGustavo Noronha Silva        COCOA_DEBUG("Global events tap created! Will capture system key combos.\n");
692f844cdb9SGustavo Noronha Silva    }
693f844cdb9SGustavo Noronha Silva
694f844cdb9SGustavo Noronha Silva    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
695f844cdb9SGustavo Noronha Silva    if (!runLoop) {
696f844cdb9SGustavo Noronha Silva        warn_report("Could not obtain current CF RunLoop, system key combos will not be captured.\n");
697f844cdb9SGustavo Noronha Silva        return;
698f844cdb9SGustavo Noronha Silva    }
699f844cdb9SGustavo Noronha Silva
700f844cdb9SGustavo Noronha Silva    CFRunLoopSourceRef tapEventsSrc = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventsTap, 0);
701f844cdb9SGustavo Noronha Silva    if (!tapEventsSrc ) {
702f844cdb9SGustavo Noronha Silva        warn_report("Could not obtain current CF RunLoop, system key combos will not be captured.\n");
703f844cdb9SGustavo Noronha Silva        return;
704f844cdb9SGustavo Noronha Silva    }
705f844cdb9SGustavo Noronha Silva
706f844cdb9SGustavo Noronha Silva    CFRunLoopAddSource(runLoop, tapEventsSrc, kCFRunLoopDefaultMode);
707f844cdb9SGustavo Noronha Silva    CFRelease(tapEventsSrc);
708f844cdb9SGustavo Noronha Silva}
709f844cdb9SGustavo Noronha Silva
7106d73bb64SAkihiko Odaki- (void) toggleKey: (int)keycode {
7116d73bb64SAkihiko Odaki    qkbd_state_key_event(kbd, keycode, !qkbd_state_key_get(kbd, keycode));
712af8862b2SIan McKellar via Qemu-devel}
713af8862b2SIan McKellar via Qemu-devel
7149c3a418eSJohn Arbuckle// Does the work of sending input to the monitor
7159c3a418eSJohn Arbuckle- (void) handleMonitorInput:(NSEvent *)event
7169c3a418eSJohn Arbuckle{
7179c3a418eSJohn Arbuckle    int keysym = 0;
7189c3a418eSJohn Arbuckle    int control_key = 0;
7199c3a418eSJohn Arbuckle
7209c3a418eSJohn Arbuckle    // if the control key is down
7219c3a418eSJohn Arbuckle    if ([event modifierFlags] & NSEventModifierFlagControl) {
7229c3a418eSJohn Arbuckle        control_key = 1;
7239c3a418eSJohn Arbuckle    }
7249c3a418eSJohn Arbuckle
7259c3a418eSJohn Arbuckle    /* translates Macintosh keycodes to QEMU's keysym */
7269c3a418eSJohn Arbuckle
7279459262dSPhilippe Mathieu-Daudé    static const int without_control_translation[] = {
7289c3a418eSJohn Arbuckle        [0 ... 0xff] = 0,   // invalid key
7299c3a418eSJohn Arbuckle
7309c3a418eSJohn Arbuckle        [kVK_UpArrow]       = QEMU_KEY_UP,
7319c3a418eSJohn Arbuckle        [kVK_DownArrow]     = QEMU_KEY_DOWN,
7329c3a418eSJohn Arbuckle        [kVK_RightArrow]    = QEMU_KEY_RIGHT,
7339c3a418eSJohn Arbuckle        [kVK_LeftArrow]     = QEMU_KEY_LEFT,
7349c3a418eSJohn Arbuckle        [kVK_Home]          = QEMU_KEY_HOME,
7359c3a418eSJohn Arbuckle        [kVK_End]           = QEMU_KEY_END,
7369c3a418eSJohn Arbuckle        [kVK_PageUp]        = QEMU_KEY_PAGEUP,
7379c3a418eSJohn Arbuckle        [kVK_PageDown]      = QEMU_KEY_PAGEDOWN,
7389c3a418eSJohn Arbuckle        [kVK_ForwardDelete] = QEMU_KEY_DELETE,
7399c3a418eSJohn Arbuckle        [kVK_Delete]        = QEMU_KEY_BACKSPACE,
7409c3a418eSJohn Arbuckle    };
7419c3a418eSJohn Arbuckle
7429459262dSPhilippe Mathieu-Daudé    static const int with_control_translation[] = {
7439c3a418eSJohn Arbuckle        [0 ... 0xff] = 0,   // invalid key
7449c3a418eSJohn Arbuckle
7459c3a418eSJohn Arbuckle        [kVK_UpArrow]       = QEMU_KEY_CTRL_UP,
7469c3a418eSJohn Arbuckle        [kVK_DownArrow]     = QEMU_KEY_CTRL_DOWN,
7479c3a418eSJohn Arbuckle        [kVK_RightArrow]    = QEMU_KEY_CTRL_RIGHT,
7489c3a418eSJohn Arbuckle        [kVK_LeftArrow]     = QEMU_KEY_CTRL_LEFT,
7499c3a418eSJohn Arbuckle        [kVK_Home]          = QEMU_KEY_CTRL_HOME,
7509c3a418eSJohn Arbuckle        [kVK_End]           = QEMU_KEY_CTRL_END,
7519c3a418eSJohn Arbuckle        [kVK_PageUp]        = QEMU_KEY_CTRL_PAGEUP,
7529c3a418eSJohn Arbuckle        [kVK_PageDown]      = QEMU_KEY_CTRL_PAGEDOWN,
7539c3a418eSJohn Arbuckle    };
7549c3a418eSJohn Arbuckle
7559c3a418eSJohn Arbuckle    if (control_key != 0) { /* If the control key is being used */
7569c3a418eSJohn Arbuckle        if ([event keyCode] < ARRAY_SIZE(with_control_translation)) {
7579c3a418eSJohn Arbuckle            keysym = with_control_translation[[event keyCode]];
7589c3a418eSJohn Arbuckle        }
7599c3a418eSJohn Arbuckle    } else {
7609c3a418eSJohn Arbuckle        if ([event keyCode] < ARRAY_SIZE(without_control_translation)) {
7619c3a418eSJohn Arbuckle            keysym = without_control_translation[[event keyCode]];
7629c3a418eSJohn Arbuckle        }
7639c3a418eSJohn Arbuckle    }
7649c3a418eSJohn Arbuckle
7659c3a418eSJohn Arbuckle    // if not a key that needs translating
7669c3a418eSJohn Arbuckle    if (keysym == 0) {
7679c3a418eSJohn Arbuckle        NSString *ks = [event characters];
7689c3a418eSJohn Arbuckle        if ([ks length] > 0) {
7699c3a418eSJohn Arbuckle            keysym = [ks characterAtIndex:0];
7709c3a418eSJohn Arbuckle        }
7719c3a418eSJohn Arbuckle    }
7729c3a418eSJohn Arbuckle
7739c3a418eSJohn Arbuckle    if (keysym) {
7749c3a418eSJohn Arbuckle        kbd_put_keysym(keysym);
7759c3a418eSJohn Arbuckle    }
7769c3a418eSJohn Arbuckle}
7779c3a418eSJohn Arbuckle
77860105d7aSPeter Maydell- (bool) handleEvent:(NSEvent *)event
7793e230dd2SCorentin Chary{
780dff742adSHikaru Nishida    if(!allow_events) {
781dff742adSHikaru Nishida        /*
782dff742adSHikaru Nishida         * Just let OSX have all events that arrive before
783dff742adSHikaru Nishida         * applicationDidFinishLaunching.
784dff742adSHikaru Nishida         * This avoids a deadlock on the iothread lock, which cocoa_display_init()
785dff742adSHikaru Nishida         * will not drop until after the app_started_sem is posted. (In theory
786dff742adSHikaru Nishida         * there should not be any such events, but OSX Catalina now emits some.)
787dff742adSHikaru Nishida         */
788dff742adSHikaru Nishida        return false;
789dff742adSHikaru Nishida    }
79060105d7aSPeter Maydell    return bool_with_iothread_lock(^{
79160105d7aSPeter Maydell        return [self handleEventLocked:event];
79231819e95SPeter Maydell    });
79331819e95SPeter Maydell}
7943e230dd2SCorentin Chary
79560105d7aSPeter Maydell- (bool) handleEventLocked:(NSEvent *)event
79631819e95SPeter Maydell{
79760105d7aSPeter Maydell    /* Return true if we handled the event, false if it should be given to OSX */
79831819e95SPeter Maydell    COCOA_DEBUG("QemuCocoaView: handleEvent\n");
7993e230dd2SCorentin Chary    int buttons = 0;
800af8862b2SIan McKellar via Qemu-devel    int keycode = 0;
80121bae11aSGerd Hoffmann    bool mouse_event = false;
8020c6c4395SJohn Arbuckle    static bool switched_to_fullscreen = false;
8032044dff8SChen Zhang    // Location of event in virtual screen coordinates
8042044dff8SChen Zhang    NSPoint p = [self screenLocationOfEvent:event];
8056d73bb64SAkihiko Odaki    NSUInteger modifiers = [event modifierFlags];
8066d73bb64SAkihiko Odaki
807ad7f2f8eSAkihiko Odaki    /*
808ad7f2f8eSAkihiko Odaki     * Check -[NSEvent modifierFlags] here.
809ad7f2f8eSAkihiko Odaki     *
810ad7f2f8eSAkihiko Odaki     * There is a NSEventType for an event notifying the change of
811ad7f2f8eSAkihiko Odaki     * -[NSEvent modifierFlags], NSEventTypeFlagsChanged but these operations
812ad7f2f8eSAkihiko Odaki     * are performed for any events because a modifier state may change while
813ad7f2f8eSAkihiko Odaki     * the application is inactive (i.e. no events fire) and we don't want to
814ad7f2f8eSAkihiko Odaki     * wait for another modifier state change to detect such a change.
815ad7f2f8eSAkihiko Odaki     *
816ad7f2f8eSAkihiko Odaki     * NSEventModifierFlagCapsLock requires a special treatment. The other flags
817ad7f2f8eSAkihiko Odaki     * are handled in similar manners.
818ad7f2f8eSAkihiko Odaki     *
819ad7f2f8eSAkihiko Odaki     * NSEventModifierFlagCapsLock
820ad7f2f8eSAkihiko Odaki     * ---------------------------
821ad7f2f8eSAkihiko Odaki     *
822ad7f2f8eSAkihiko Odaki     * If CapsLock state is changed, "up" and "down" events will be fired in
823ad7f2f8eSAkihiko Odaki     * sequence, effectively updates CapsLock state on the guest.
824ad7f2f8eSAkihiko Odaki     *
825ad7f2f8eSAkihiko Odaki     * The other flags
826ad7f2f8eSAkihiko Odaki     * ---------------
827ad7f2f8eSAkihiko Odaki     *
828ad7f2f8eSAkihiko Odaki     * If a flag is not set, fire "up" events for all keys which correspond to
829ad7f2f8eSAkihiko Odaki     * the flag. Note that "down" events are not fired here because the flags
830ad7f2f8eSAkihiko Odaki     * checked here do not tell what exact keys are down.
831ad7f2f8eSAkihiko Odaki     *
832ad7f2f8eSAkihiko Odaki     * If one of the keys corresponding to a flag is down, we rely on
833ad7f2f8eSAkihiko Odaki     * -[NSEvent keyCode] of an event whose -[NSEvent type] is
834ad7f2f8eSAkihiko Odaki     * NSEventTypeFlagsChanged to know the exact key which is down, which has
835ad7f2f8eSAkihiko Odaki     * the following two downsides:
836ad7f2f8eSAkihiko Odaki     * - It does not work when the application is inactive as described above.
837ad7f2f8eSAkihiko Odaki     * - It malfactions *after* the modifier state is changed while the
838ad7f2f8eSAkihiko Odaki     *   application is inactive. It is because -[NSEvent keyCode] does not tell
839ad7f2f8eSAkihiko Odaki     *   if the key is up or down, and requires to infer the current state from
840ad7f2f8eSAkihiko Odaki     *   the previous state. It is still possible to fix such a malfanction by
841ad7f2f8eSAkihiko Odaki     *   completely leaving your hands from the keyboard, which hopefully makes
842ad7f2f8eSAkihiko Odaki     *   this implementation usable enough.
843ad7f2f8eSAkihiko Odaki     */
8446d73bb64SAkihiko Odaki    if (!!(modifiers & NSEventModifierFlagCapsLock) !=
8456d73bb64SAkihiko Odaki        qkbd_state_modifier_get(kbd, QKBD_MOD_CAPSLOCK)) {
8466d73bb64SAkihiko Odaki        qkbd_state_key_event(kbd, Q_KEY_CODE_CAPS_LOCK, true);
8476d73bb64SAkihiko Odaki        qkbd_state_key_event(kbd, Q_KEY_CODE_CAPS_LOCK, false);
8486d73bb64SAkihiko Odaki    }
8496d73bb64SAkihiko Odaki
8506d73bb64SAkihiko Odaki    if (!(modifiers & NSEventModifierFlagShift)) {
8516d73bb64SAkihiko Odaki        qkbd_state_key_event(kbd, Q_KEY_CODE_SHIFT, false);
8526d73bb64SAkihiko Odaki        qkbd_state_key_event(kbd, Q_KEY_CODE_SHIFT_R, false);
8536d73bb64SAkihiko Odaki    }
8546d73bb64SAkihiko Odaki    if (!(modifiers & NSEventModifierFlagControl)) {
8556d73bb64SAkihiko Odaki        qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL, false);
8566d73bb64SAkihiko Odaki        qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL_R, false);
8576d73bb64SAkihiko Odaki    }
8586d73bb64SAkihiko Odaki    if (!(modifiers & NSEventModifierFlagOption)) {
8594797adceSGustavo Noronha Silva        if (swap_opt_cmd) {
8604797adceSGustavo Noronha Silva            qkbd_state_key_event(kbd, Q_KEY_CODE_META_L, false);
8614797adceSGustavo Noronha Silva            qkbd_state_key_event(kbd, Q_KEY_CODE_META_R, false);
8624797adceSGustavo Noronha Silva        } else {
8636d73bb64SAkihiko Odaki            qkbd_state_key_event(kbd, Q_KEY_CODE_ALT, false);
8646d73bb64SAkihiko Odaki            qkbd_state_key_event(kbd, Q_KEY_CODE_ALT_R, false);
8656d73bb64SAkihiko Odaki        }
8664797adceSGustavo Noronha Silva    }
8676d73bb64SAkihiko Odaki    if (!(modifiers & NSEventModifierFlagCommand)) {
8684797adceSGustavo Noronha Silva        if (swap_opt_cmd) {
8694797adceSGustavo Noronha Silva            qkbd_state_key_event(kbd, Q_KEY_CODE_ALT, false);
8704797adceSGustavo Noronha Silva            qkbd_state_key_event(kbd, Q_KEY_CODE_ALT_R, false);
8714797adceSGustavo Noronha Silva        } else {
8726d73bb64SAkihiko Odaki            qkbd_state_key_event(kbd, Q_KEY_CODE_META_L, false);
8736d73bb64SAkihiko Odaki            qkbd_state_key_event(kbd, Q_KEY_CODE_META_R, false);
8746d73bb64SAkihiko Odaki        }
8754797adceSGustavo Noronha Silva    }
8763e230dd2SCorentin Chary
8773e230dd2SCorentin Chary    switch ([event type]) {
8784ba967adSBrendan Shanks        case NSEventTypeFlagsChanged:
8796d73bb64SAkihiko Odaki            switch ([event keyCode]) {
8806d73bb64SAkihiko Odaki                case kVK_Shift:
8816d73bb64SAkihiko Odaki                    if (!!(modifiers & NSEventModifierFlagShift)) {
8826d73bb64SAkihiko Odaki                        [self toggleKey:Q_KEY_CODE_SHIFT];
8836d73bb64SAkihiko Odaki                    }
8846d73bb64SAkihiko Odaki                    break;
885af8862b2SIan McKellar via Qemu-devel
8866d73bb64SAkihiko Odaki                case kVK_RightShift:
8876d73bb64SAkihiko Odaki                    if (!!(modifiers & NSEventModifierFlagShift)) {
8886d73bb64SAkihiko Odaki                        [self toggleKey:Q_KEY_CODE_SHIFT_R];
8896d73bb64SAkihiko Odaki                    }
8906d73bb64SAkihiko Odaki                    break;
891af8862b2SIan McKellar via Qemu-devel
8926d73bb64SAkihiko Odaki                case kVK_Control:
8936d73bb64SAkihiko Odaki                    if (!!(modifiers & NSEventModifierFlagControl)) {
8946d73bb64SAkihiko Odaki                        [self toggleKey:Q_KEY_CODE_CTRL];
895af8862b2SIan McKellar via Qemu-devel                    }
8966d73bb64SAkihiko Odaki                    break;
8978895919aSPeter Maydell
8986d73bb64SAkihiko Odaki                case kVK_RightControl:
8996d73bb64SAkihiko Odaki                    if (!!(modifiers & NSEventModifierFlagControl)) {
9006d73bb64SAkihiko Odaki                        [self toggleKey:Q_KEY_CODE_CTRL_R];
9016d73bb64SAkihiko Odaki                    }
9026d73bb64SAkihiko Odaki                    break;
9036d73bb64SAkihiko Odaki
9046d73bb64SAkihiko Odaki                case kVK_Option:
9056d73bb64SAkihiko Odaki                    if (!!(modifiers & NSEventModifierFlagOption)) {
9064797adceSGustavo Noronha Silva                        if (swap_opt_cmd) {
9074797adceSGustavo Noronha Silva                            [self toggleKey:Q_KEY_CODE_META_L];
9084797adceSGustavo Noronha Silva                        } else {
9096d73bb64SAkihiko Odaki                            [self toggleKey:Q_KEY_CODE_ALT];
9106d73bb64SAkihiko Odaki                        }
9114797adceSGustavo Noronha Silva                    }
9126d73bb64SAkihiko Odaki                    break;
9136d73bb64SAkihiko Odaki
9146d73bb64SAkihiko Odaki                case kVK_RightOption:
9156d73bb64SAkihiko Odaki                    if (!!(modifiers & NSEventModifierFlagOption)) {
9164797adceSGustavo Noronha Silva                        if (swap_opt_cmd) {
9174797adceSGustavo Noronha Silva                            [self toggleKey:Q_KEY_CODE_META_R];
9184797adceSGustavo Noronha Silva                        } else {
9196d73bb64SAkihiko Odaki                            [self toggleKey:Q_KEY_CODE_ALT_R];
9206d73bb64SAkihiko Odaki                        }
9214797adceSGustavo Noronha Silva                    }
9226d73bb64SAkihiko Odaki                    break;
9236d73bb64SAkihiko Odaki
9248895919aSPeter Maydell                /* Don't pass command key changes to guest unless mouse is grabbed */
9256d73bb64SAkihiko Odaki                case kVK_Command:
9266d73bb64SAkihiko Odaki                    if (isMouseGrabbed &&
927d6b6dea7SAkihiko Odaki                        !!(modifiers & NSEventModifierFlagCommand) &&
928d6b6dea7SAkihiko Odaki                        left_command_key_enabled) {
9294797adceSGustavo Noronha Silva                        if (swap_opt_cmd) {
9304797adceSGustavo Noronha Silva                            [self toggleKey:Q_KEY_CODE_ALT];
9314797adceSGustavo Noronha Silva                        } else {
9326d73bb64SAkihiko Odaki                            [self toggleKey:Q_KEY_CODE_META_L];
9338895919aSPeter Maydell                        }
9344797adceSGustavo Noronha Silva                    }
9356d73bb64SAkihiko Odaki                    break;
9368895919aSPeter Maydell
9376d73bb64SAkihiko Odaki                case kVK_RightCommand:
9386d73bb64SAkihiko Odaki                    if (isMouseGrabbed &&
9396d73bb64SAkihiko Odaki                        !!(modifiers & NSEventModifierFlagCommand)) {
9404797adceSGustavo Noronha Silva                        if (swap_opt_cmd) {
9414797adceSGustavo Noronha Silva                            [self toggleKey:Q_KEY_CODE_ALT_R];
9424797adceSGustavo Noronha Silva                        } else {
9436d73bb64SAkihiko Odaki                            [self toggleKey:Q_KEY_CODE_META_R];
9443e230dd2SCorentin Chary                        }
9454797adceSGustavo Noronha Silva                    }
9466d73bb64SAkihiko Odaki                    break;
9473e230dd2SCorentin Chary            }
9483e230dd2SCorentin Chary            break;
9494ba967adSBrendan Shanks        case NSEventTypeKeyDown:
9508895919aSPeter Maydell            keycode = cocoa_keycode_to_qemu([event keyCode]);
9513e230dd2SCorentin Chary
9528895919aSPeter Maydell            // forward command key combos to the host UI unless the mouse is grabbed
9534ba967adSBrendan Shanks            if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) {
9540c6c4395SJohn Arbuckle                /*
9550c6c4395SJohn Arbuckle                 * Prevent the command key from being stuck down in the guest
9560c6c4395SJohn Arbuckle                 * when using Command-F to switch to full screen mode.
9570c6c4395SJohn Arbuckle                 */
9580c6c4395SJohn Arbuckle                if (keycode == Q_KEY_CODE_F) {
9590c6c4395SJohn Arbuckle                    switched_to_fullscreen = true;
9600c6c4395SJohn Arbuckle                }
96160105d7aSPeter Maydell                return false;
9623e230dd2SCorentin Chary            }
9633e230dd2SCorentin Chary
9643e230dd2SCorentin Chary            // default
9653e230dd2SCorentin Chary
9665929e36cSJohn Arbuckle            // handle control + alt Key Combos (ctrl+alt+[1..9,g] is reserved for QEMU)
9674ba967adSBrendan Shanks            if (([event modifierFlags] & NSEventModifierFlagControl) && ([event modifierFlags] & NSEventModifierFlagOption)) {
9685929e36cSJohn Arbuckle                NSString *keychar = [event charactersIgnoringModifiers];
9695929e36cSJohn Arbuckle                if ([keychar length] == 1) {
9705929e36cSJohn Arbuckle                    char key = [keychar characterAtIndex:0];
9715929e36cSJohn Arbuckle                    switch (key) {
9723e230dd2SCorentin Chary
9733e230dd2SCorentin Chary                        // enable graphic console
9745929e36cSJohn Arbuckle                        case '1' ... '9':
9755929e36cSJohn Arbuckle                            console_select(key - '0' - 1); /* ascii math */
97660105d7aSPeter Maydell                            return true;
9775929e36cSJohn Arbuckle
9785929e36cSJohn Arbuckle                        // release the mouse grab
9795929e36cSJohn Arbuckle                        case 'g':
9805929e36cSJohn Arbuckle                            [self ungrabMouse];
98160105d7aSPeter Maydell                            return true;
9825929e36cSJohn Arbuckle                    }
9833e230dd2SCorentin Chary                }
984ef2088f9SPeter Maydell            }
9853e230dd2SCorentin Chary
986ef2088f9SPeter Maydell            if (qemu_console_is_graphic(NULL)) {
9876d73bb64SAkihiko Odaki                qkbd_state_key_event(kbd, keycode, true);
9883e230dd2SCorentin Chary            } else {
9899c3a418eSJohn Arbuckle                [self handleMonitorInput: event];
9903e230dd2SCorentin Chary            }
9913e230dd2SCorentin Chary            break;
9924ba967adSBrendan Shanks        case NSEventTypeKeyUp:
9933e230dd2SCorentin Chary            keycode = cocoa_keycode_to_qemu([event keyCode]);
9948895919aSPeter Maydell
9958895919aSPeter Maydell            // don't pass the guest a spurious key-up if we treated this
9968895919aSPeter Maydell            // command-key combo as a host UI action
9974ba967adSBrendan Shanks            if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) {
99860105d7aSPeter Maydell                return true;
9998895919aSPeter Maydell            }
10008895919aSPeter Maydell
100168c0aa6eSPeter Maydell            if (qemu_console_is_graphic(NULL)) {
10026d73bb64SAkihiko Odaki                qkbd_state_key_event(kbd, keycode, false);
10033e230dd2SCorentin Chary            }
10043e230dd2SCorentin Chary            break;
10054ba967adSBrendan Shanks        case NSEventTypeMouseMoved:
10063e230dd2SCorentin Chary            if (isAbsoluteEnabled) {
10072044dff8SChen Zhang                // Cursor re-entered into a window might generate events bound to screen coordinates
10082044dff8SChen Zhang                // and `nil` window property, and in full screen mode, current window might not be
10092044dff8SChen Zhang                // key window, where event location alone should suffice.
10102044dff8SChen Zhang                if (![self screenContainsPoint:p] || !([[self window] isKeyWindow] || isFullscreen)) {
1011f61c387eSPeter Maydell                    if (isMouseGrabbed) {
1012f61c387eSPeter Maydell                        [self ungrabMouse];
10133e230dd2SCorentin Chary                    }
10143e230dd2SCorentin Chary                } else {
1015f61c387eSPeter Maydell                    if (!isMouseGrabbed) {
1016f61c387eSPeter Maydell                        [self grabMouse];
10173e230dd2SCorentin Chary                    }
10183e230dd2SCorentin Chary                }
10193e230dd2SCorentin Chary            }
102021bae11aSGerd Hoffmann            mouse_event = true;
10213e230dd2SCorentin Chary            break;
10224ba967adSBrendan Shanks        case NSEventTypeLeftMouseDown:
10233e230dd2SCorentin Chary            buttons |= MOUSE_EVENT_LBUTTON;
102421bae11aSGerd Hoffmann            mouse_event = true;
10253e230dd2SCorentin Chary            break;
10264ba967adSBrendan Shanks        case NSEventTypeRightMouseDown:
10273e230dd2SCorentin Chary            buttons |= MOUSE_EVENT_RBUTTON;
102821bae11aSGerd Hoffmann            mouse_event = true;
10293e230dd2SCorentin Chary            break;
10304ba967adSBrendan Shanks        case NSEventTypeOtherMouseDown:
10313e230dd2SCorentin Chary            buttons |= MOUSE_EVENT_MBUTTON;
103221bae11aSGerd Hoffmann            mouse_event = true;
10333e230dd2SCorentin Chary            break;
10344ba967adSBrendan Shanks        case NSEventTypeLeftMouseDragged:
10353e230dd2SCorentin Chary            buttons |= MOUSE_EVENT_LBUTTON;
103621bae11aSGerd Hoffmann            mouse_event = true;
10373e230dd2SCorentin Chary            break;
10384ba967adSBrendan Shanks        case NSEventTypeRightMouseDragged:
10393e230dd2SCorentin Chary            buttons |= MOUSE_EVENT_RBUTTON;
104021bae11aSGerd Hoffmann            mouse_event = true;
10413e230dd2SCorentin Chary            break;
10424ba967adSBrendan Shanks        case NSEventTypeOtherMouseDragged:
10433e230dd2SCorentin Chary            buttons |= MOUSE_EVENT_MBUTTON;
104421bae11aSGerd Hoffmann            mouse_event = true;
10453e230dd2SCorentin Chary            break;
10464ba967adSBrendan Shanks        case NSEventTypeLeftMouseUp:
104721bae11aSGerd Hoffmann            mouse_event = true;
1048f61c387eSPeter Maydell            if (!isMouseGrabbed && [self screenContainsPoint:p]) {
10498e23e34dSChen Zhang                /*
10508e23e34dSChen Zhang                 * In fullscreen mode, the window of cocoaView may not be the
10518e23e34dSChen Zhang                 * key window, therefore the position relative to the virtual
10528e23e34dSChen Zhang                 * screen alone will be sufficient.
10538e23e34dSChen Zhang                 */
10548e23e34dSChen Zhang                if(isFullscreen || [[self window] isKeyWindow]) {
10553e230dd2SCorentin Chary                    [self grabMouse];
10563e230dd2SCorentin Chary                }
10579e8204b1SProgrammingkid            }
10583e230dd2SCorentin Chary            break;
10594ba967adSBrendan Shanks        case NSEventTypeRightMouseUp:
106021bae11aSGerd Hoffmann            mouse_event = true;
10613e230dd2SCorentin Chary            break;
10624ba967adSBrendan Shanks        case NSEventTypeOtherMouseUp:
106321bae11aSGerd Hoffmann            mouse_event = true;
10643e230dd2SCorentin Chary            break;
10654ba967adSBrendan Shanks        case NSEventTypeScrollWheel:
1066ae7313e7SJohn Arbuckle            /*
1067ae7313e7SJohn Arbuckle             * Send wheel events to the guest regardless of window focus.
1068ae7313e7SJohn Arbuckle             * This is in-line with standard Mac OS X UI behaviour.
1069ae7313e7SJohn Arbuckle             */
1070ae7313e7SJohn Arbuckle
1071dc3c89d6SJohn Arbuckle            /*
1072d70a5de4SDmitry Petrov             * We shouldn't have got a scroll event when deltaY and delta Y
1073d70a5de4SDmitry Petrov             * are zero, hence no harm in dropping the event
1074dc3c89d6SJohn Arbuckle             */
1075d70a5de4SDmitry Petrov            if ([event deltaY] != 0 || [event deltaX] != 0) {
1076ae7313e7SJohn Arbuckle            /* Determine if this is a scroll up or scroll down event */
1077d70a5de4SDmitry Petrov                if ([event deltaY] != 0) {
1078dc3c89d6SJohn Arbuckle                  buttons = ([event deltaY] > 0) ?
1079ae7313e7SJohn Arbuckle                    INPUT_BUTTON_WHEEL_UP : INPUT_BUTTON_WHEEL_DOWN;
1080d70a5de4SDmitry Petrov                } else if ([event deltaX] != 0) {
1081d70a5de4SDmitry Petrov                  buttons = ([event deltaX] > 0) ?
1082d70a5de4SDmitry Petrov                    INPUT_BUTTON_WHEEL_LEFT : INPUT_BUTTON_WHEEL_RIGHT;
1083d70a5de4SDmitry Petrov                }
1084d70a5de4SDmitry Petrov
1085cc7859c3SAkihiko Odaki                qemu_input_queue_btn(dcl.con, buttons, true);
1086ae7313e7SJohn Arbuckle                qemu_input_event_sync();
1087cc7859c3SAkihiko Odaki                qemu_input_queue_btn(dcl.con, buttons, false);
1088ae7313e7SJohn Arbuckle                qemu_input_event_sync();
1089dc3c89d6SJohn Arbuckle            }
1090d70a5de4SDmitry Petrov
1091ae7313e7SJohn Arbuckle            /*
1092d70a5de4SDmitry Petrov             * Since deltaX/deltaY also report scroll wheel events we prevent mouse
1093ae7313e7SJohn Arbuckle             * movement code from executing.
1094ae7313e7SJohn Arbuckle             */
1095ae7313e7SJohn Arbuckle            mouse_event = false;
10963e230dd2SCorentin Chary            break;
10973e230dd2SCorentin Chary        default:
109860105d7aSPeter Maydell            return false;
10993e230dd2SCorentin Chary    }
110021bae11aSGerd Hoffmann
110121bae11aSGerd Hoffmann    if (mouse_event) {
11028d3a5d9bSPeter Maydell        /* Don't send button events to the guest unless we've got a
11038d3a5d9bSPeter Maydell         * mouse grab or window focus. If we have neither then this event
11048d3a5d9bSPeter Maydell         * is the user clicking on the background window to activate and
11058d3a5d9bSPeter Maydell         * bring us to the front, which will be done by the sendEvent
11068d3a5d9bSPeter Maydell         * call below. We definitely don't want to pass that click through
11078d3a5d9bSPeter Maydell         * to the guest.
11088d3a5d9bSPeter Maydell         */
11098d3a5d9bSPeter Maydell        if ((isMouseGrabbed || [[self window] isKeyWindow]) &&
11108d3a5d9bSPeter Maydell            (last_buttons != buttons)) {
11117fb1cf16SEric Blake            static uint32_t bmap[INPUT_BUTTON__MAX] = {
111221bae11aSGerd Hoffmann                [INPUT_BUTTON_LEFT]       = MOUSE_EVENT_LBUTTON,
111321bae11aSGerd Hoffmann                [INPUT_BUTTON_MIDDLE]     = MOUSE_EVENT_MBUTTON,
1114ae7313e7SJohn Arbuckle                [INPUT_BUTTON_RIGHT]      = MOUSE_EVENT_RBUTTON
111521bae11aSGerd Hoffmann            };
1116cc7859c3SAkihiko Odaki            qemu_input_update_buttons(dcl.con, bmap, last_buttons, buttons);
111721bae11aSGerd Hoffmann            last_buttons = buttons;
111821bae11aSGerd Hoffmann        }
1119f61c387eSPeter Maydell        if (isMouseGrabbed) {
1120f61c387eSPeter Maydell            if (isAbsoluteEnabled) {
1121f61c387eSPeter Maydell                /* Note that the origin for Cocoa mouse coords is bottom left, not top left.
1122f61c387eSPeter Maydell                 * The check on screenContainsPoint is to avoid sending out of range values for
1123f61c387eSPeter Maydell                 * clicks in the titlebar.
1124f61c387eSPeter Maydell                 */
1125f61c387eSPeter Maydell                if ([self screenContainsPoint:p]) {
1126cc7859c3SAkihiko Odaki                    qemu_input_queue_abs(dcl.con, INPUT_AXIS_X, p.x, 0, screen.width);
1127cc7859c3SAkihiko Odaki                    qemu_input_queue_abs(dcl.con, INPUT_AXIS_Y, screen.height - p.y, 0, screen.height);
1128f61c387eSPeter Maydell                }
1129f61c387eSPeter Maydell            } else {
1130cc7859c3SAkihiko Odaki                qemu_input_queue_rel(dcl.con, INPUT_AXIS_X, (int)[event deltaX]);
1131cc7859c3SAkihiko Odaki                qemu_input_queue_rel(dcl.con, INPUT_AXIS_Y, (int)[event deltaY]);
1132f61c387eSPeter Maydell            }
113321bae11aSGerd Hoffmann        } else {
113460105d7aSPeter Maydell            return false;
113521bae11aSGerd Hoffmann        }
113621bae11aSGerd Hoffmann        qemu_input_event_sync();
113721bae11aSGerd Hoffmann    }
113860105d7aSPeter Maydell    return true;
11393e230dd2SCorentin Chary}
11403e230dd2SCorentin Chary
11413e230dd2SCorentin Chary- (void) grabMouse
11423e230dd2SCorentin Chary{
11433e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaView: grabMouse\n");
11443e230dd2SCorentin Chary
11453e230dd2SCorentin Chary    if (!isFullscreen) {
11463e230dd2SCorentin Chary        if (qemu_name)
11475929e36cSJohn Arbuckle            [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s - (Press ctrl + alt + g to release Mouse)", qemu_name]];
11483e230dd2SCorentin Chary        else
11495929e36cSJohn Arbuckle            [normalWindow setTitle:@"QEMU - (Press ctrl + alt + g to release Mouse)"];
11503e230dd2SCorentin Chary    }
115113aefd30SPeter Maydell    [self hideCursor];
1152d1929069SAkihiko Odaki    CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled);
115349b9bd4dSPeter Maydell    isMouseGrabbed = TRUE; // while isMouseGrabbed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:]
11543e230dd2SCorentin Chary}
11553e230dd2SCorentin Chary
11563e230dd2SCorentin Chary- (void) ungrabMouse
11573e230dd2SCorentin Chary{
11583e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaView: ungrabMouse\n");
11593e230dd2SCorentin Chary
11603e230dd2SCorentin Chary    if (!isFullscreen) {
11613e230dd2SCorentin Chary        if (qemu_name)
11623e230dd2SCorentin Chary            [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]];
11633e230dd2SCorentin Chary        else
11643e230dd2SCorentin Chary            [normalWindow setTitle:@"QEMU"];
11653e230dd2SCorentin Chary    }
116613aefd30SPeter Maydell    [self unhideCursor];
11673e230dd2SCorentin Chary    CGAssociateMouseAndMouseCursorPosition(TRUE);
116849b9bd4dSPeter Maydell    isMouseGrabbed = FALSE;
11693e230dd2SCorentin Chary}
11703e230dd2SCorentin Chary
1171d1929069SAkihiko Odaki- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled {
1172d1929069SAkihiko Odaki    isAbsoluteEnabled = tIsAbsoluteEnabled;
1173d1929069SAkihiko Odaki    if (isMouseGrabbed) {
1174d1929069SAkihiko Odaki        CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled);
1175d1929069SAkihiko Odaki    }
1176d1929069SAkihiko Odaki}
117749b9bd4dSPeter Maydell- (BOOL) isMouseGrabbed {return isMouseGrabbed;}
11783e230dd2SCorentin Chary- (BOOL) isAbsoluteEnabled {return isAbsoluteEnabled;}
11793e230dd2SCorentin Chary- (float) cdx {return cdx;}
11803e230dd2SCorentin Chary- (float) cdy {return cdy;}
11813e230dd2SCorentin Chary- (QEMUScreen) gscreen {return screen;}
11823b178b71SJohn Arbuckle
11833b178b71SJohn Arbuckle/*
11843b178b71SJohn Arbuckle * Makes the target think all down keys are being released.
11853b178b71SJohn Arbuckle * This prevents a stuck key problem, since we will not see
11863b178b71SJohn Arbuckle * key up events for those keys after we have lost focus.
11873b178b71SJohn Arbuckle */
11883b178b71SJohn Arbuckle- (void) raiseAllKeys
11893b178b71SJohn Arbuckle{
119031819e95SPeter Maydell    with_iothread_lock(^{
11916d73bb64SAkihiko Odaki        qkbd_state_lift_all_keys(kbd);
119231819e95SPeter Maydell    });
11933b178b71SJohn Arbuckle}
11943e230dd2SCorentin Chary@end
11953e230dd2SCorentin Chary
11963e230dd2SCorentin Chary
11973e230dd2SCorentin Chary
11983e230dd2SCorentin Chary/*
11993e230dd2SCorentin Chary ------------------------------------------------------
12003e230dd2SCorentin Chary    QemuCocoaAppController
12013e230dd2SCorentin Chary ------------------------------------------------------
12023e230dd2SCorentin Chary*/
12033e230dd2SCorentin Chary@interface QemuCocoaAppController : NSObject
1204d9bc14f6SJohn Arbuckle                                       <NSWindowDelegate, NSApplicationDelegate>
12053e230dd2SCorentin Chary{
12063e230dd2SCorentin Chary}
12075d1b2eefSProgrammingkid- (void)doToggleFullScreen:(id)sender;
12083e230dd2SCorentin Chary- (void)toggleFullScreen:(id)sender;
12093e230dd2SCorentin Chary- (void)showQEMUDoc:(id)sender;
12105d1b2eefSProgrammingkid- (void)zoomToFit:(id) sender;
1211b4c6a112SProgrammingkid- (void)displayConsole:(id)sender;
12128524f1c7SJohn Arbuckle- (void)pauseQEMU:(id)sender;
12138524f1c7SJohn Arbuckle- (void)resumeQEMU:(id)sender;
12148524f1c7SJohn Arbuckle- (void)displayPause;
12158524f1c7SJohn Arbuckle- (void)removePause;
121627074614SJohn Arbuckle- (void)restartQEMU:(id)sender;
121727074614SJohn Arbuckle- (void)powerDownQEMU:(id)sender;
1218693a3e01SJohn Arbuckle- (void)ejectDeviceMedia:(id)sender;
1219693a3e01SJohn Arbuckle- (void)changeDeviceMedia:(id)sender;
1220d9bc14f6SJohn Arbuckle- (BOOL)verifyQuit;
1221f4747900SJohn Arbuckle- (void)openDocumentation:(NSString *)filename;
12229e8204b1SProgrammingkid- (IBAction) do_about_menu_item: (id) sender;
1223e47ec1a9SJohn Arbuckle- (void)adjustSpeed:(id)sender;
12243e230dd2SCorentin Chary@end
12253e230dd2SCorentin Chary
12263e230dd2SCorentin Chary@implementation QemuCocoaAppController
12273e230dd2SCorentin Chary- (id) init
12283e230dd2SCorentin Chary{
12293e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaAppController: init\n");
12303e230dd2SCorentin Chary
12313e230dd2SCorentin Chary    self = [super init];
12323e230dd2SCorentin Chary    if (self) {
12333e230dd2SCorentin Chary
12343e230dd2SCorentin Chary        // create a view and add it to the window
12353e230dd2SCorentin Chary        cocoaView = [[QemuCocoaView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 640.0, 480.0)];
12363e230dd2SCorentin Chary        if(!cocoaView) {
12374313739aSAkihiko Odaki            error_report("(cocoa) can't create a view");
12383e230dd2SCorentin Chary            exit(1);
12393e230dd2SCorentin Chary        }
12403e230dd2SCorentin Chary
12413e230dd2SCorentin Chary        // create a window
12423e230dd2SCorentin Chary        normalWindow = [[NSWindow alloc] initWithContentRect:[cocoaView frame]
12434ba967adSBrendan Shanks            styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskClosable
12443e230dd2SCorentin Chary            backing:NSBackingStoreBuffered defer:NO];
12453e230dd2SCorentin Chary        if(!normalWindow) {
12464313739aSAkihiko Odaki            error_report("(cocoa) can't create window");
12473e230dd2SCorentin Chary            exit(1);
12483e230dd2SCorentin Chary        }
12493e230dd2SCorentin Chary        [normalWindow setAcceptsMouseMovedEvents:YES];
1250a1dbc05aSJohn Arbuckle        [normalWindow setTitle:@"QEMU"];
12513e230dd2SCorentin Chary        [normalWindow setContentView:cocoaView];
12523e230dd2SCorentin Chary        [normalWindow makeKeyAndOrderFront:self];
12533e230dd2SCorentin Chary        [normalWindow center];
1254d9bc14f6SJohn Arbuckle        [normalWindow setDelegate: self];
12555d1b2eefSProgrammingkid        stretch_video = false;
12568524f1c7SJohn Arbuckle
12578524f1c7SJohn Arbuckle        /* Used for displaying pause on the screen */
12588524f1c7SJohn Arbuckle        pauseLabel = [NSTextField new];
12598524f1c7SJohn Arbuckle        [pauseLabel setBezeled:YES];
12608524f1c7SJohn Arbuckle        [pauseLabel setDrawsBackground:YES];
12618524f1c7SJohn Arbuckle        [pauseLabel setBackgroundColor: [NSColor whiteColor]];
12628524f1c7SJohn Arbuckle        [pauseLabel setEditable:NO];
12638524f1c7SJohn Arbuckle        [pauseLabel setSelectable:NO];
12648524f1c7SJohn Arbuckle        [pauseLabel setStringValue: @"Paused"];
12658524f1c7SJohn Arbuckle        [pauseLabel setFont: [NSFont fontWithName: @"Helvetica" size: 90]];
12668524f1c7SJohn Arbuckle        [pauseLabel setTextColor: [NSColor blackColor]];
12678524f1c7SJohn Arbuckle        [pauseLabel sizeToFit];
12683e230dd2SCorentin Chary    }
12693e230dd2SCorentin Chary    return self;
12703e230dd2SCorentin Chary}
12713e230dd2SCorentin Chary
12723e230dd2SCorentin Chary- (void) dealloc
12733e230dd2SCorentin Chary{
12743e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaAppController: dealloc\n");
12753e230dd2SCorentin Chary
12763e230dd2SCorentin Chary    if (cocoaView)
12773e230dd2SCorentin Chary        [cocoaView release];
12783e230dd2SCorentin Chary    [super dealloc];
12793e230dd2SCorentin Chary}
12803e230dd2SCorentin Chary
12813e230dd2SCorentin Chary- (void)applicationDidFinishLaunching: (NSNotification *) note
12823e230dd2SCorentin Chary{
12833e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaAppController: applicationDidFinishLaunching\n");
1284dff742adSHikaru Nishida    allow_events = true;
12855588840fSPeter Maydell    /* Tell cocoa_display_init to proceed */
12865588840fSPeter Maydell    qemu_sem_post(&app_started_sem);
12873e230dd2SCorentin Chary}
12883e230dd2SCorentin Chary
12893e230dd2SCorentin Chary- (void)applicationWillTerminate:(NSNotification *)aNotification
12903e230dd2SCorentin Chary{
12913e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n");
12923e230dd2SCorentin Chary
1293cf83f140SEric Blake    qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
129440c01937SAkihiko Odaki
129540c01937SAkihiko Odaki    /*
129640c01937SAkihiko Odaki     * Sleep here, because returning will cause OSX to kill us
129740c01937SAkihiko Odaki     * immediately; the QEMU main loop will handle the shutdown
129840c01937SAkihiko Odaki     * request and terminate the process.
129940c01937SAkihiko Odaki     */
130040c01937SAkihiko Odaki    [NSThread sleepForTimeInterval:INFINITY];
13013e230dd2SCorentin Chary}
13023e230dd2SCorentin Chary
13033e230dd2SCorentin Chary- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
13043e230dd2SCorentin Chary{
13053e230dd2SCorentin Chary    return YES;
13063e230dd2SCorentin Chary}
13073e230dd2SCorentin Chary
1308d9bc14f6SJohn Arbuckle- (NSApplicationTerminateReply)applicationShouldTerminate:
1309d9bc14f6SJohn Arbuckle                                                         (NSApplication *)sender
1310d9bc14f6SJohn Arbuckle{
1311d9bc14f6SJohn Arbuckle    COCOA_DEBUG("QemuCocoaAppController: applicationShouldTerminate\n");
1312d9bc14f6SJohn Arbuckle    return [self verifyQuit];
1313d9bc14f6SJohn Arbuckle}
1314d9bc14f6SJohn Arbuckle
131515280e85SAkihiko Odaki- (void)windowDidChangeScreen:(NSNotification *)notification
131615280e85SAkihiko Odaki{
131715280e85SAkihiko Odaki    [cocoaView updateUIInfo];
131815280e85SAkihiko Odaki}
131915280e85SAkihiko Odaki
132015280e85SAkihiko Odaki- (void)windowDidResize:(NSNotification *)notification
132115280e85SAkihiko Odaki{
132215280e85SAkihiko Odaki    [cocoaView updateUIInfo];
132315280e85SAkihiko Odaki}
132415280e85SAkihiko Odaki
1325d9bc14f6SJohn Arbuckle/* Called when the user clicks on a window's close button */
1326d9bc14f6SJohn Arbuckle- (BOOL)windowShouldClose:(id)sender
1327d9bc14f6SJohn Arbuckle{
1328d9bc14f6SJohn Arbuckle    COCOA_DEBUG("QemuCocoaAppController: windowShouldClose\n");
1329d9bc14f6SJohn Arbuckle    [NSApp terminate: sender];
1330d9bc14f6SJohn Arbuckle    /* If the user allows the application to quit then the call to
1331d9bc14f6SJohn Arbuckle     * NSApp terminate will never return. If we get here then the user
1332d9bc14f6SJohn Arbuckle     * cancelled the quit, so we should return NO to not permit the
1333d9bc14f6SJohn Arbuckle     * closing of this window.
1334d9bc14f6SJohn Arbuckle     */
1335d9bc14f6SJohn Arbuckle    return NO;
1336d9bc14f6SJohn Arbuckle}
1337d9bc14f6SJohn Arbuckle
13383b178b71SJohn Arbuckle/* Called when QEMU goes into the background */
13393b178b71SJohn Arbuckle- (void) applicationWillResignActive: (NSNotification *)aNotification
13403b178b71SJohn Arbuckle{
13413b178b71SJohn Arbuckle    COCOA_DEBUG("QemuCocoaAppController: applicationWillResignActive\n");
134269221df8SCarwyn Ellis    [cocoaView ungrabMouse];
13433b178b71SJohn Arbuckle    [cocoaView raiseAllKeys];
13443b178b71SJohn Arbuckle}
13453b178b71SJohn Arbuckle
13465d1b2eefSProgrammingkid/* We abstract the method called by the Enter Fullscreen menu item
13475d1b2eefSProgrammingkid * because Mac OS 10.7 and higher disables it. This is because of the
13485d1b2eefSProgrammingkid * menu item's old selector's name toggleFullScreen:
13495d1b2eefSProgrammingkid */
13505d1b2eefSProgrammingkid- (void) doToggleFullScreen:(id)sender
13515d1b2eefSProgrammingkid{
13525d1b2eefSProgrammingkid    [self toggleFullScreen:(id)sender];
13535d1b2eefSProgrammingkid}
13545d1b2eefSProgrammingkid
13553e230dd2SCorentin Chary- (void)toggleFullScreen:(id)sender
13563e230dd2SCorentin Chary{
13573e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaAppController: toggleFullScreen\n");
13583e230dd2SCorentin Chary
13593e230dd2SCorentin Chary    [cocoaView toggleFullScreen:sender];
13603e230dd2SCorentin Chary}
13613e230dd2SCorentin Chary
1362f844cdb9SGustavo Noronha Silva- (void) setFullGrab:(id)sender
1363f844cdb9SGustavo Noronha Silva{
1364f844cdb9SGustavo Noronha Silva    COCOA_DEBUG("QemuCocoaAppController: setFullGrab\n");
1365f844cdb9SGustavo Noronha Silva
1366f844cdb9SGustavo Noronha Silva    [cocoaView setFullGrab:sender];
1367f844cdb9SGustavo Noronha Silva}
1368f844cdb9SGustavo Noronha Silva
1369f4747900SJohn Arbuckle/* Tries to find then open the specified filename */
1370f4747900SJohn Arbuckle- (void) openDocumentation: (NSString *) filename
1371f4747900SJohn Arbuckle{
1372f4747900SJohn Arbuckle    /* Where to look for local files */
13738d6fda8cSRoman Bolshakov    NSString *path_array[] = {@"../share/doc/qemu/", @"../doc/qemu/", @"docs/"};
1374f4747900SJohn Arbuckle    NSString *full_file_path;
13751ff5a063SRoman Bolshakov    NSURL *full_file_url;
1376f4747900SJohn Arbuckle
1377f4747900SJohn Arbuckle    /* iterate thru the possible paths until the file is found */
1378f4747900SJohn Arbuckle    int index;
1379f4747900SJohn Arbuckle    for (index = 0; index < ARRAY_SIZE(path_array); index++) {
1380f4747900SJohn Arbuckle        full_file_path = [[NSBundle mainBundle] executablePath];
1381f4747900SJohn Arbuckle        full_file_path = [full_file_path stringByDeletingLastPathComponent];
1382f4747900SJohn Arbuckle        full_file_path = [NSString stringWithFormat: @"%@/%@%@", full_file_path,
1383f4747900SJohn Arbuckle                          path_array[index], filename];
13841ff5a063SRoman Bolshakov        full_file_url = [NSURL fileURLWithPath: full_file_path
13851ff5a063SRoman Bolshakov                                   isDirectory: false];
13861ff5a063SRoman Bolshakov        if ([[NSWorkspace sharedWorkspace] openURL: full_file_url] == YES) {
1387f4747900SJohn Arbuckle            return;
1388f4747900SJohn Arbuckle        }
1389f4747900SJohn Arbuckle    }
1390f4747900SJohn Arbuckle
1391f4747900SJohn Arbuckle    /* If none of the paths opened a file */
1392f4747900SJohn Arbuckle    NSBeep();
1393f4747900SJohn Arbuckle    QEMU_Alert(@"Failed to open file");
1394f4747900SJohn Arbuckle}
1395f4747900SJohn Arbuckle
13963e230dd2SCorentin Chary- (void)showQEMUDoc:(id)sender
13973e230dd2SCorentin Chary{
13983e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaAppController: showQEMUDoc\n");
13993e230dd2SCorentin Chary
14001879f241SPeter Maydell    [self openDocumentation: @"index.html"];
14013e230dd2SCorentin Chary}
14023e230dd2SCorentin Chary
14035d1b2eefSProgrammingkid/* Stretches video to fit host monitor size */
14045d1b2eefSProgrammingkid- (void)zoomToFit:(id) sender
14055d1b2eefSProgrammingkid{
14065d1b2eefSProgrammingkid    stretch_video = !stretch_video;
14075d1b2eefSProgrammingkid    if (stretch_video == true) {
14085e24600aSBrendan Shanks        [sender setState: NSControlStateValueOn];
14095d1b2eefSProgrammingkid    } else {
14105e24600aSBrendan Shanks        [sender setState: NSControlStateValueOff];
14115d1b2eefSProgrammingkid    }
14125d1b2eefSProgrammingkid}
14133e230dd2SCorentin Chary
1414b4c6a112SProgrammingkid/* Displays the console on the screen */
1415b4c6a112SProgrammingkid- (void)displayConsole:(id)sender
1416b4c6a112SProgrammingkid{
1417b4c6a112SProgrammingkid    console_select([sender tag]);
1418b4c6a112SProgrammingkid}
14198524f1c7SJohn Arbuckle
14208524f1c7SJohn Arbuckle/* Pause the guest */
14218524f1c7SJohn Arbuckle- (void)pauseQEMU:(id)sender
14228524f1c7SJohn Arbuckle{
142331819e95SPeter Maydell    with_iothread_lock(^{
14248524f1c7SJohn Arbuckle        qmp_stop(NULL);
142531819e95SPeter Maydell    });
14268524f1c7SJohn Arbuckle    [sender setEnabled: NO];
14278524f1c7SJohn Arbuckle    [[[sender menu] itemWithTitle: @"Resume"] setEnabled: YES];
14288524f1c7SJohn Arbuckle    [self displayPause];
14298524f1c7SJohn Arbuckle}
14308524f1c7SJohn Arbuckle
14318524f1c7SJohn Arbuckle/* Resume running the guest operating system */
14328524f1c7SJohn Arbuckle- (void)resumeQEMU:(id) sender
14338524f1c7SJohn Arbuckle{
143431819e95SPeter Maydell    with_iothread_lock(^{
14358524f1c7SJohn Arbuckle        qmp_cont(NULL);
143631819e95SPeter Maydell    });
14378524f1c7SJohn Arbuckle    [sender setEnabled: NO];
14388524f1c7SJohn Arbuckle    [[[sender menu] itemWithTitle: @"Pause"] setEnabled: YES];
14398524f1c7SJohn Arbuckle    [self removePause];
14408524f1c7SJohn Arbuckle}
14418524f1c7SJohn Arbuckle
14428524f1c7SJohn Arbuckle/* Displays the word pause on the screen */
14438524f1c7SJohn Arbuckle- (void)displayPause
14448524f1c7SJohn Arbuckle{
14458524f1c7SJohn Arbuckle    /* Coordinates have to be calculated each time because the window can change its size */
14468524f1c7SJohn Arbuckle    int xCoord, yCoord, width, height;
14478524f1c7SJohn Arbuckle    xCoord = ([normalWindow frame].size.width - [pauseLabel frame].size.width)/2;
14488524f1c7SJohn Arbuckle    yCoord = [normalWindow frame].size.height - [pauseLabel frame].size.height - ([pauseLabel frame].size.height * .5);
14498524f1c7SJohn Arbuckle    width = [pauseLabel frame].size.width;
14508524f1c7SJohn Arbuckle    height = [pauseLabel frame].size.height;
14518524f1c7SJohn Arbuckle    [pauseLabel setFrame: NSMakeRect(xCoord, yCoord, width, height)];
14528524f1c7SJohn Arbuckle    [cocoaView addSubview: pauseLabel];
14538524f1c7SJohn Arbuckle}
14548524f1c7SJohn Arbuckle
14558524f1c7SJohn Arbuckle/* Removes the word pause from the screen */
14568524f1c7SJohn Arbuckle- (void)removePause
14578524f1c7SJohn Arbuckle{
14588524f1c7SJohn Arbuckle    [pauseLabel removeFromSuperview];
14598524f1c7SJohn Arbuckle}
14608524f1c7SJohn Arbuckle
146127074614SJohn Arbuckle/* Restarts QEMU */
146227074614SJohn Arbuckle- (void)restartQEMU:(id)sender
146327074614SJohn Arbuckle{
146431819e95SPeter Maydell    with_iothread_lock(^{
146527074614SJohn Arbuckle        qmp_system_reset(NULL);
146631819e95SPeter Maydell    });
146727074614SJohn Arbuckle}
146827074614SJohn Arbuckle
146927074614SJohn Arbuckle/* Powers down QEMU */
147027074614SJohn Arbuckle- (void)powerDownQEMU:(id)sender
147127074614SJohn Arbuckle{
147231819e95SPeter Maydell    with_iothread_lock(^{
147327074614SJohn Arbuckle        qmp_system_powerdown(NULL);
147431819e95SPeter Maydell    });
147527074614SJohn Arbuckle}
147627074614SJohn Arbuckle
1477693a3e01SJohn Arbuckle/* Ejects the media.
1478693a3e01SJohn Arbuckle * Uses sender's tag to figure out the device to eject.
1479693a3e01SJohn Arbuckle */
1480693a3e01SJohn Arbuckle- (void)ejectDeviceMedia:(id)sender
1481693a3e01SJohn Arbuckle{
1482693a3e01SJohn Arbuckle    NSString * drive;
1483693a3e01SJohn Arbuckle    drive = [sender representedObject];
1484693a3e01SJohn Arbuckle    if(drive == nil) {
1485693a3e01SJohn Arbuckle        NSBeep();
1486693a3e01SJohn Arbuckle        QEMU_Alert(@"Failed to find drive to eject!");
1487693a3e01SJohn Arbuckle        return;
1488693a3e01SJohn Arbuckle    }
1489693a3e01SJohn Arbuckle
149031819e95SPeter Maydell    __block Error *err = NULL;
149131819e95SPeter Maydell    with_iothread_lock(^{
1492fbe2d816SKevin Wolf        qmp_eject(true, [drive cStringUsingEncoding: NSASCIIStringEncoding],
1493fbe2d816SKevin Wolf                  false, NULL, false, false, &err);
149431819e95SPeter Maydell    });
1495693a3e01SJohn Arbuckle    handleAnyDeviceErrors(err);
1496693a3e01SJohn Arbuckle}
1497693a3e01SJohn Arbuckle
1498693a3e01SJohn Arbuckle/* Displays a dialog box asking the user to select an image file to load.
1499693a3e01SJohn Arbuckle * Uses sender's represented object value to figure out which drive to use.
1500693a3e01SJohn Arbuckle */
1501693a3e01SJohn Arbuckle- (void)changeDeviceMedia:(id)sender
1502693a3e01SJohn Arbuckle{
1503693a3e01SJohn Arbuckle    /* Find the drive name */
1504693a3e01SJohn Arbuckle    NSString * drive;
1505693a3e01SJohn Arbuckle    drive = [sender representedObject];
1506693a3e01SJohn Arbuckle    if(drive == nil) {
1507693a3e01SJohn Arbuckle        NSBeep();
1508693a3e01SJohn Arbuckle        QEMU_Alert(@"Could not find drive!");
1509693a3e01SJohn Arbuckle        return;
1510693a3e01SJohn Arbuckle    }
1511693a3e01SJohn Arbuckle
1512693a3e01SJohn Arbuckle    /* Display the file open dialog */
1513693a3e01SJohn Arbuckle    NSOpenPanel * openPanel;
1514693a3e01SJohn Arbuckle    openPanel = [NSOpenPanel openPanel];
1515693a3e01SJohn Arbuckle    [openPanel setCanChooseFiles: YES];
1516693a3e01SJohn Arbuckle    [openPanel setAllowsMultipleSelection: NO];
1517b5725385SPeter Maydell    if([openPanel runModal] == NSModalResponseOK) {
1518693a3e01SJohn Arbuckle        NSString * file = [[[openPanel URLs] objectAtIndex: 0] path];
1519693a3e01SJohn Arbuckle        if(file == nil) {
1520693a3e01SJohn Arbuckle            NSBeep();
1521693a3e01SJohn Arbuckle            QEMU_Alert(@"Failed to convert URL to file path!");
1522693a3e01SJohn Arbuckle            return;
1523693a3e01SJohn Arbuckle        }
1524693a3e01SJohn Arbuckle
152531819e95SPeter Maydell        __block Error *err = NULL;
152631819e95SPeter Maydell        with_iothread_lock(^{
152770e2cb3bSKevin Wolf            qmp_blockdev_change_medium(true,
152870e2cb3bSKevin Wolf                                       [drive cStringUsingEncoding:
152924fb4133SMax Reitz                                                  NSASCIIStringEncoding],
153070e2cb3bSKevin Wolf                                       false, NULL,
153124fb4133SMax Reitz                                       [file cStringUsingEncoding:
153224fb4133SMax Reitz                                                 NSASCIIStringEncoding],
153324fb4133SMax Reitz                                       true, "raw",
1534*80dd5affSDenis V. Lunev                                       true, false,
153539ff43d9SMax Reitz                                       false, 0,
1536693a3e01SJohn Arbuckle                                       &err);
153731819e95SPeter Maydell        });
1538693a3e01SJohn Arbuckle        handleAnyDeviceErrors(err);
1539693a3e01SJohn Arbuckle    }
1540693a3e01SJohn Arbuckle}
1541693a3e01SJohn Arbuckle
1542d9bc14f6SJohn Arbuckle/* Verifies if the user really wants to quit */
1543d9bc14f6SJohn Arbuckle- (BOOL)verifyQuit
1544d9bc14f6SJohn Arbuckle{
1545d9bc14f6SJohn Arbuckle    NSAlert *alert = [NSAlert new];
1546d9bc14f6SJohn Arbuckle    [alert autorelease];
1547d9bc14f6SJohn Arbuckle    [alert setMessageText: @"Are you sure you want to quit QEMU?"];
1548d9bc14f6SJohn Arbuckle    [alert addButtonWithTitle: @"Cancel"];
1549d9bc14f6SJohn Arbuckle    [alert addButtonWithTitle: @"Quit"];
1550d9bc14f6SJohn Arbuckle    if([alert runModal] == NSAlertSecondButtonReturn) {
1551d9bc14f6SJohn Arbuckle        return YES;
1552d9bc14f6SJohn Arbuckle    } else {
1553d9bc14f6SJohn Arbuckle        return NO;
1554d9bc14f6SJohn Arbuckle    }
1555d9bc14f6SJohn Arbuckle}
1556d9bc14f6SJohn Arbuckle
15579e8204b1SProgrammingkid/* The action method for the About menu item */
15589e8204b1SProgrammingkid- (IBAction) do_about_menu_item: (id) sender
15599e8204b1SProgrammingkid{
156099eb313dSAkihiko Odaki    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
156199eb313dSAkihiko Odaki    char *icon_path_c = get_relocated_path(CONFIG_QEMU_ICONDIR "/hicolor/512x512/apps/qemu.png");
156299eb313dSAkihiko Odaki    NSString *icon_path = [NSString stringWithUTF8String:icon_path_c];
156399eb313dSAkihiko Odaki    g_free(icon_path_c);
156499eb313dSAkihiko Odaki    NSImage *icon = [[NSImage alloc] initWithContentsOfFile:icon_path];
156599eb313dSAkihiko Odaki    NSString *version = @"QEMU emulator version " QEMU_FULL_VERSION;
156699eb313dSAkihiko Odaki    NSString *copyright = @QEMU_COPYRIGHT;
156799eb313dSAkihiko Odaki    NSDictionary *options;
156899eb313dSAkihiko Odaki    if (icon) {
156999eb313dSAkihiko Odaki        options = @{
157099eb313dSAkihiko Odaki            NSAboutPanelOptionApplicationIcon : icon,
157199eb313dSAkihiko Odaki            NSAboutPanelOptionApplicationVersion : version,
157299eb313dSAkihiko Odaki            @"Copyright" : copyright,
157399eb313dSAkihiko Odaki        };
157499eb313dSAkihiko Odaki        [icon release];
157599eb313dSAkihiko Odaki    } else {
157699eb313dSAkihiko Odaki        options = @{
157799eb313dSAkihiko Odaki            NSAboutPanelOptionApplicationVersion : version,
157899eb313dSAkihiko Odaki            @"Copyright" : copyright,
157999eb313dSAkihiko Odaki        };
15809e8204b1SProgrammingkid    }
158199eb313dSAkihiko Odaki    [NSApp orderFrontStandardAboutPanelWithOptions:options];
158299eb313dSAkihiko Odaki    [pool release];
15839e8204b1SProgrammingkid}
15849e8204b1SProgrammingkid
1585e47ec1a9SJohn Arbuckle/* Used by the Speed menu items */
1586e47ec1a9SJohn Arbuckle- (void)adjustSpeed:(id)sender
1587e47ec1a9SJohn Arbuckle{
1588e47ec1a9SJohn Arbuckle    int throttle_pct; /* throttle percentage */
1589e47ec1a9SJohn Arbuckle    NSMenu *menu;
1590e47ec1a9SJohn Arbuckle
1591e47ec1a9SJohn Arbuckle    menu = [sender menu];
1592e47ec1a9SJohn Arbuckle    if (menu != nil)
1593e47ec1a9SJohn Arbuckle    {
1594e47ec1a9SJohn Arbuckle        /* Unselect the currently selected item */
1595e47ec1a9SJohn Arbuckle        for (NSMenuItem *item in [menu itemArray]) {
15965e24600aSBrendan Shanks            if (item.state == NSControlStateValueOn) {
15975e24600aSBrendan Shanks                [item setState: NSControlStateValueOff];
1598e47ec1a9SJohn Arbuckle                break;
1599e47ec1a9SJohn Arbuckle            }
1600e47ec1a9SJohn Arbuckle        }
1601e47ec1a9SJohn Arbuckle    }
1602e47ec1a9SJohn Arbuckle
1603e47ec1a9SJohn Arbuckle    // check the menu item
16045e24600aSBrendan Shanks    [sender setState: NSControlStateValueOn];
1605e47ec1a9SJohn Arbuckle
1606e47ec1a9SJohn Arbuckle    // get the throttle percentage
1607e47ec1a9SJohn Arbuckle    throttle_pct = [sender tag];
1608e47ec1a9SJohn Arbuckle
160931819e95SPeter Maydell    with_iothread_lock(^{
1610e47ec1a9SJohn Arbuckle        cpu_throttle_set(throttle_pct);
161131819e95SPeter Maydell    });
1612e47ec1a9SJohn Arbuckle    COCOA_DEBUG("cpu throttling at %d%c\n", cpu_throttle_get_percentage(), '%');
1613e47ec1a9SJohn Arbuckle}
1614e47ec1a9SJohn Arbuckle
1615b4c6a112SProgrammingkid@end
16163e230dd2SCorentin Chary
161761a2ed44SPeter Maydell@interface QemuApplication : NSApplication
161861a2ed44SPeter Maydell@end
161961a2ed44SPeter Maydell
162061a2ed44SPeter Maydell@implementation QemuApplication
162161a2ed44SPeter Maydell- (void)sendEvent:(NSEvent *)event
162261a2ed44SPeter Maydell{
162361a2ed44SPeter Maydell    COCOA_DEBUG("QemuApplication: sendEvent\n");
16245588840fSPeter Maydell    if (![cocoaView handleEvent:event]) {
162561a2ed44SPeter Maydell        [super sendEvent: event];
162661a2ed44SPeter Maydell    }
16275588840fSPeter Maydell}
162861a2ed44SPeter Maydell@end
162961a2ed44SPeter Maydell
1630c6fd6c70SPeter Maydellstatic void create_initial_menus(void)
1631c6fd6c70SPeter Maydell{
16323e230dd2SCorentin Chary    // Add menus
16333e230dd2SCorentin Chary    NSMenu      *menu;
16343e230dd2SCorentin Chary    NSMenuItem  *menuItem;
16353e230dd2SCorentin Chary
16363e230dd2SCorentin Chary    [NSApp setMainMenu:[[NSMenu alloc] init]];
16375b6988c1SAkihiko Odaki    [NSApp setServicesMenu:[[NSMenu alloc] initWithTitle:@"Services"]];
16383e230dd2SCorentin Chary
16393e230dd2SCorentin Chary    // Application menu
16403e230dd2SCorentin Chary    menu = [[NSMenu alloc] initWithTitle:@""];
16419e8204b1SProgrammingkid    [menu addItemWithTitle:@"About QEMU" action:@selector(do_about_menu_item:) keyEquivalent:@""]; // About QEMU
16423e230dd2SCorentin Chary    [menu addItem:[NSMenuItem separatorItem]]; //Separator
16435b6988c1SAkihiko Odaki    menuItem = [menu addItemWithTitle:@"Services" action:nil keyEquivalent:@""];
16445b6988c1SAkihiko Odaki    [menuItem setSubmenu:[NSApp servicesMenu]];
16455b6988c1SAkihiko Odaki    [menu addItem:[NSMenuItem separatorItem]];
16463e230dd2SCorentin Chary    [menu addItemWithTitle:@"Hide QEMU" action:@selector(hide:) keyEquivalent:@"h"]; //Hide QEMU
16473e230dd2SCorentin Chary    menuItem = (NSMenuItem *)[menu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; // Hide Others
16484ba967adSBrendan Shanks    [menuItem setKeyEquivalentModifierMask:(NSEventModifierFlagOption|NSEventModifierFlagCommand)];
16493e230dd2SCorentin Chary    [menu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; // Show All
16503e230dd2SCorentin Chary    [menu addItem:[NSMenuItem separatorItem]]; //Separator
16513e230dd2SCorentin Chary    [menu addItemWithTitle:@"Quit QEMU" action:@selector(terminate:) keyEquivalent:@"q"];
16523e230dd2SCorentin Chary    menuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" action:nil keyEquivalent:@""];
16533e230dd2SCorentin Chary    [menuItem setSubmenu:menu];
16543e230dd2SCorentin Chary    [[NSApp mainMenu] addItem:menuItem];
16553e230dd2SCorentin Chary    [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; // Workaround (this method is private since 10.4+)
16563e230dd2SCorentin Chary
16578524f1c7SJohn Arbuckle    // Machine menu
16588524f1c7SJohn Arbuckle    menu = [[NSMenu alloc] initWithTitle: @"Machine"];
16598524f1c7SJohn Arbuckle    [menu setAutoenablesItems: NO];
16608524f1c7SJohn Arbuckle    [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Pause" action: @selector(pauseQEMU:) keyEquivalent: @""] autorelease]];
16618524f1c7SJohn Arbuckle    menuItem = [[[NSMenuItem alloc] initWithTitle: @"Resume" action: @selector(resumeQEMU:) keyEquivalent: @""] autorelease];
16628524f1c7SJohn Arbuckle    [menu addItem: menuItem];
16638524f1c7SJohn Arbuckle    [menuItem setEnabled: NO];
166427074614SJohn Arbuckle    [menu addItem: [NSMenuItem separatorItem]];
166527074614SJohn Arbuckle    [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Reset" action: @selector(restartQEMU:) keyEquivalent: @""] autorelease]];
166627074614SJohn Arbuckle    [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Power Down" action: @selector(powerDownQEMU:) keyEquivalent: @""] autorelease]];
16678524f1c7SJohn Arbuckle    menuItem = [[[NSMenuItem alloc] initWithTitle: @"Machine" action:nil keyEquivalent:@""] autorelease];
16688524f1c7SJohn Arbuckle    [menuItem setSubmenu:menu];
16698524f1c7SJohn Arbuckle    [[NSApp mainMenu] addItem:menuItem];
16708524f1c7SJohn Arbuckle
16713e230dd2SCorentin Chary    // View menu
16723e230dd2SCorentin Chary    menu = [[NSMenu alloc] initWithTitle:@"View"];
16735d1b2eefSProgrammingkid    [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(doToggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen
16745d1b2eefSProgrammingkid    [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Zoom To Fit" action:@selector(zoomToFit:) keyEquivalent:@""] autorelease]];
16753e230dd2SCorentin Chary    menuItem = [[[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""] autorelease];
16763e230dd2SCorentin Chary    [menuItem setSubmenu:menu];
16773e230dd2SCorentin Chary    [[NSApp mainMenu] addItem:menuItem];
16783e230dd2SCorentin Chary
1679e47ec1a9SJohn Arbuckle    // Speed menu
1680e47ec1a9SJohn Arbuckle    menu = [[NSMenu alloc] initWithTitle:@"Speed"];
1681e47ec1a9SJohn Arbuckle
1682e47ec1a9SJohn Arbuckle    // Add the rest of the Speed menu items
1683e47ec1a9SJohn Arbuckle    int p, percentage, throttle_pct;
1684e47ec1a9SJohn Arbuckle    for (p = 10; p >= 0; p--)
1685e47ec1a9SJohn Arbuckle    {
1686e47ec1a9SJohn Arbuckle        percentage = p * 10 > 1 ? p * 10 : 1; // prevent a 0% menu item
1687e47ec1a9SJohn Arbuckle
1688e47ec1a9SJohn Arbuckle        menuItem = [[[NSMenuItem alloc]
1689e47ec1a9SJohn Arbuckle                   initWithTitle: [NSString stringWithFormat: @"%d%%", percentage] action:@selector(adjustSpeed:) keyEquivalent:@""] autorelease];
1690e47ec1a9SJohn Arbuckle
1691e47ec1a9SJohn Arbuckle        if (percentage == 100) {
16925e24600aSBrendan Shanks            [menuItem setState: NSControlStateValueOn];
1693e47ec1a9SJohn Arbuckle        }
1694e47ec1a9SJohn Arbuckle
1695e47ec1a9SJohn Arbuckle        /* Calculate the throttle percentage */
1696e47ec1a9SJohn Arbuckle        throttle_pct = -1 * percentage + 100;
1697e47ec1a9SJohn Arbuckle
1698e47ec1a9SJohn Arbuckle        [menuItem setTag: throttle_pct];
1699e47ec1a9SJohn Arbuckle        [menu addItem: menuItem];
1700e47ec1a9SJohn Arbuckle    }
1701e47ec1a9SJohn Arbuckle    menuItem = [[[NSMenuItem alloc] initWithTitle:@"Speed" action:nil keyEquivalent:@""] autorelease];
1702e47ec1a9SJohn Arbuckle    [menuItem setSubmenu:menu];
1703e47ec1a9SJohn Arbuckle    [[NSApp mainMenu] addItem:menuItem];
1704e47ec1a9SJohn Arbuckle
17053e230dd2SCorentin Chary    // Window menu
17063e230dd2SCorentin Chary    menu = [[NSMenu alloc] initWithTitle:@"Window"];
17073e230dd2SCorentin Chary    [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease]]; // Miniaturize
17083e230dd2SCorentin Chary    menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease];
17093e230dd2SCorentin Chary    [menuItem setSubmenu:menu];
17103e230dd2SCorentin Chary    [[NSApp mainMenu] addItem:menuItem];
17113e230dd2SCorentin Chary    [NSApp setWindowsMenu:menu];
17123e230dd2SCorentin Chary
17133e230dd2SCorentin Chary    // Help menu
17143e230dd2SCorentin Chary    menu = [[NSMenu alloc] initWithTitle:@"Help"];
17153e230dd2SCorentin Chary    [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Documentation" action:@selector(showQEMUDoc:) keyEquivalent:@"?"] autorelease]]; // QEMU Help
17163e230dd2SCorentin Chary    menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease];
17173e230dd2SCorentin Chary    [menuItem setSubmenu:menu];
17183e230dd2SCorentin Chary    [[NSApp mainMenu] addItem:menuItem];
1719c6fd6c70SPeter Maydell}
1720c6fd6c70SPeter Maydell
17218b00e4e7SPeter Maydell/* Returns a name for a given console */
17228b00e4e7SPeter Maydellstatic NSString * getConsoleName(QemuConsole * console)
17238b00e4e7SPeter Maydell{
1724ca511604SAkihiko Odaki    g_autofree char *label = qemu_console_get_label(console);
1725ca511604SAkihiko Odaki
1726ca511604SAkihiko Odaki    return [NSString stringWithUTF8String:label];
17278b00e4e7SPeter Maydell}
17288b00e4e7SPeter Maydell
17298b00e4e7SPeter Maydell/* Add an entry to the View menu for each console */
17308b00e4e7SPeter Maydellstatic void add_console_menu_entries(void)
17318b00e4e7SPeter Maydell{
17328b00e4e7SPeter Maydell    NSMenu *menu;
17338b00e4e7SPeter Maydell    NSMenuItem *menuItem;
17348b00e4e7SPeter Maydell    int index = 0;
17358b00e4e7SPeter Maydell
17368b00e4e7SPeter Maydell    menu = [[[NSApp mainMenu] itemWithTitle:@"View"] submenu];
17378b00e4e7SPeter Maydell
17388b00e4e7SPeter Maydell    [menu addItem:[NSMenuItem separatorItem]];
17398b00e4e7SPeter Maydell
17408b00e4e7SPeter Maydell    while (qemu_console_lookup_by_index(index) != NULL) {
17418b00e4e7SPeter Maydell        menuItem = [[[NSMenuItem alloc] initWithTitle: getConsoleName(qemu_console_lookup_by_index(index))
17428b00e4e7SPeter Maydell                                               action: @selector(displayConsole:) keyEquivalent: @""] autorelease];
17438b00e4e7SPeter Maydell        [menuItem setTag: index];
17448b00e4e7SPeter Maydell        [menu addItem: menuItem];
17458b00e4e7SPeter Maydell        index++;
17468b00e4e7SPeter Maydell    }
17478b00e4e7SPeter Maydell}
17488b00e4e7SPeter Maydell
17498b00e4e7SPeter Maydell/* Make menu items for all removable devices.
17508b00e4e7SPeter Maydell * Each device is given an 'Eject' and 'Change' menu item.
17518b00e4e7SPeter Maydell */
17528b00e4e7SPeter Maydellstatic void addRemovableDevicesMenuItems(void)
17538b00e4e7SPeter Maydell{
17548b00e4e7SPeter Maydell    NSMenu *menu;
17558b00e4e7SPeter Maydell    NSMenuItem *menuItem;
17568b00e4e7SPeter Maydell    BlockInfoList *currentDevice, *pointerToFree;
17578b00e4e7SPeter Maydell    NSString *deviceName;
17588b00e4e7SPeter Maydell
17598b00e4e7SPeter Maydell    currentDevice = qmp_query_block(NULL);
17608b00e4e7SPeter Maydell    pointerToFree = currentDevice;
17618b00e4e7SPeter Maydell
17628b00e4e7SPeter Maydell    menu = [[[NSApp mainMenu] itemWithTitle:@"Machine"] submenu];
17638b00e4e7SPeter Maydell
17648b00e4e7SPeter Maydell    // Add a separator between related groups of menu items
17658b00e4e7SPeter Maydell    [menu addItem:[NSMenuItem separatorItem]];
17668b00e4e7SPeter Maydell
17678b00e4e7SPeter Maydell    // Set the attributes to the "Removable Media" menu item
17688b00e4e7SPeter Maydell    NSString *titleString = @"Removable Media";
17698b00e4e7SPeter Maydell    NSMutableAttributedString *attString=[[NSMutableAttributedString alloc] initWithString:titleString];
17708b00e4e7SPeter Maydell    NSColor *newColor = [NSColor blackColor];
17718b00e4e7SPeter Maydell    NSFontManager *fontManager = [NSFontManager sharedFontManager];
17728b00e4e7SPeter Maydell    NSFont *font = [fontManager fontWithFamily:@"Helvetica"
17738b00e4e7SPeter Maydell                                          traits:NSBoldFontMask|NSItalicFontMask
17748b00e4e7SPeter Maydell                                          weight:0
17758b00e4e7SPeter Maydell                                            size:14];
17768b00e4e7SPeter Maydell    [attString addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, [titleString length])];
17778b00e4e7SPeter Maydell    [attString addAttribute:NSForegroundColorAttributeName value:newColor range:NSMakeRange(0, [titleString length])];
17788b00e4e7SPeter Maydell    [attString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInt: 1] range:NSMakeRange(0, [titleString length])];
17798b00e4e7SPeter Maydell
17808b00e4e7SPeter Maydell    // Add the "Removable Media" menu item
17818b00e4e7SPeter Maydell    menuItem = [NSMenuItem new];
17828b00e4e7SPeter Maydell    [menuItem setAttributedTitle: attString];
17838b00e4e7SPeter Maydell    [menuItem setEnabled: NO];
17848b00e4e7SPeter Maydell    [menu addItem: menuItem];
17858b00e4e7SPeter Maydell
17868b00e4e7SPeter Maydell    /* Loop through all the block devices in the emulator */
17878b00e4e7SPeter Maydell    while (currentDevice) {
17888b00e4e7SPeter Maydell        deviceName = [[NSString stringWithFormat: @"%s", currentDevice->value->device] retain];
17898b00e4e7SPeter Maydell
17908b00e4e7SPeter Maydell        if(currentDevice->value->removable) {
17918b00e4e7SPeter Maydell            menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Change %s...", currentDevice->value->device]
17928b00e4e7SPeter Maydell                                                  action: @selector(changeDeviceMedia:)
17938b00e4e7SPeter Maydell                                           keyEquivalent: @""];
17948b00e4e7SPeter Maydell            [menu addItem: menuItem];
17958b00e4e7SPeter Maydell            [menuItem setRepresentedObject: deviceName];
17968b00e4e7SPeter Maydell            [menuItem autorelease];
17978b00e4e7SPeter Maydell
17988b00e4e7SPeter Maydell            menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Eject %s", currentDevice->value->device]
17998b00e4e7SPeter Maydell                                                  action: @selector(ejectDeviceMedia:)
18008b00e4e7SPeter Maydell                                           keyEquivalent: @""];
18018b00e4e7SPeter Maydell            [menu addItem: menuItem];
18028b00e4e7SPeter Maydell            [menuItem setRepresentedObject: deviceName];
18038b00e4e7SPeter Maydell            [menuItem autorelease];
18048b00e4e7SPeter Maydell        }
18058b00e4e7SPeter Maydell        currentDevice = currentDevice->next;
18068b00e4e7SPeter Maydell    }
18078b00e4e7SPeter Maydell    qapi_free_BlockInfoList(pointerToFree);
18088b00e4e7SPeter Maydell}
18098b00e4e7SPeter Maydell
18107e3e20d8SAkihiko Odaki@interface QemuCocoaPasteboardTypeOwner : NSObject<NSPasteboardTypeOwner>
18117e3e20d8SAkihiko Odaki@end
18127e3e20d8SAkihiko Odaki
18137e3e20d8SAkihiko Odaki@implementation QemuCocoaPasteboardTypeOwner
18147e3e20d8SAkihiko Odaki
18157e3e20d8SAkihiko Odaki- (void)pasteboard:(NSPasteboard *)sender provideDataForType:(NSPasteboardType)type
18167e3e20d8SAkihiko Odaki{
18177e3e20d8SAkihiko Odaki    if (type != NSPasteboardTypeString) {
18187e3e20d8SAkihiko Odaki        return;
18197e3e20d8SAkihiko Odaki    }
18207e3e20d8SAkihiko Odaki
18217e3e20d8SAkihiko Odaki    with_iothread_lock(^{
18227e3e20d8SAkihiko Odaki        QemuClipboardInfo *info = qemu_clipboard_info_ref(cbinfo);
18237e3e20d8SAkihiko Odaki        qemu_event_reset(&cbevent);
18247e3e20d8SAkihiko Odaki        qemu_clipboard_request(info, QEMU_CLIPBOARD_TYPE_TEXT);
18257e3e20d8SAkihiko Odaki
18267e3e20d8SAkihiko Odaki        while (info == cbinfo &&
18277e3e20d8SAkihiko Odaki               info->types[QEMU_CLIPBOARD_TYPE_TEXT].available &&
18287e3e20d8SAkihiko Odaki               info->types[QEMU_CLIPBOARD_TYPE_TEXT].data == NULL) {
18297e3e20d8SAkihiko Odaki            qemu_mutex_unlock_iothread();
18307e3e20d8SAkihiko Odaki            qemu_event_wait(&cbevent);
18317e3e20d8SAkihiko Odaki            qemu_mutex_lock_iothread();
18327e3e20d8SAkihiko Odaki        }
18337e3e20d8SAkihiko Odaki
18347e3e20d8SAkihiko Odaki        if (info == cbinfo) {
18357e3e20d8SAkihiko Odaki            NSData *data = [[NSData alloc] initWithBytes:info->types[QEMU_CLIPBOARD_TYPE_TEXT].data
18367e3e20d8SAkihiko Odaki                                           length:info->types[QEMU_CLIPBOARD_TYPE_TEXT].size];
18377e3e20d8SAkihiko Odaki            [sender setData:data forType:NSPasteboardTypeString];
18387e3e20d8SAkihiko Odaki            [data release];
18397e3e20d8SAkihiko Odaki        }
18407e3e20d8SAkihiko Odaki
18417e3e20d8SAkihiko Odaki        qemu_clipboard_info_unref(info);
18427e3e20d8SAkihiko Odaki    });
18437e3e20d8SAkihiko Odaki}
18447e3e20d8SAkihiko Odaki
18457e3e20d8SAkihiko Odaki@end
18467e3e20d8SAkihiko Odaki
18477e3e20d8SAkihiko Odakistatic QemuCocoaPasteboardTypeOwner *cbowner;
18487e3e20d8SAkihiko Odaki
18497e3e20d8SAkihiko Odakistatic void cocoa_clipboard_notify(Notifier *notifier, void *data);
18507e3e20d8SAkihiko Odakistatic void cocoa_clipboard_request(QemuClipboardInfo *info,
18517e3e20d8SAkihiko Odaki                                    QemuClipboardType type);
18527e3e20d8SAkihiko Odaki
18537e3e20d8SAkihiko Odakistatic QemuClipboardPeer cbpeer = {
18547e3e20d8SAkihiko Odaki    .name = "cocoa",
18551b17f1e9SMarc-André Lureau    .notifier = { .notify = cocoa_clipboard_notify },
18567e3e20d8SAkihiko Odaki    .request = cocoa_clipboard_request
18577e3e20d8SAkihiko Odaki};
18587e3e20d8SAkihiko Odaki
18591b17f1e9SMarc-André Lureaustatic void cocoa_clipboard_update_info(QemuClipboardInfo *info)
18607e3e20d8SAkihiko Odaki{
18617e3e20d8SAkihiko Odaki    if (info->owner == &cbpeer || info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) {
18627e3e20d8SAkihiko Odaki        return;
18637e3e20d8SAkihiko Odaki    }
18647e3e20d8SAkihiko Odaki
18657e3e20d8SAkihiko Odaki    if (info != cbinfo) {
18667e3e20d8SAkihiko Odaki        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
18677e3e20d8SAkihiko Odaki        qemu_clipboard_info_unref(cbinfo);
18687e3e20d8SAkihiko Odaki        cbinfo = qemu_clipboard_info_ref(info);
18697e3e20d8SAkihiko Odaki        cbchangecount = [[NSPasteboard generalPasteboard] declareTypes:@[NSPasteboardTypeString] owner:cbowner];
18707e3e20d8SAkihiko Odaki        [pool release];
18717e3e20d8SAkihiko Odaki    }
18727e3e20d8SAkihiko Odaki
18737e3e20d8SAkihiko Odaki    qemu_event_set(&cbevent);
18747e3e20d8SAkihiko Odaki}
18757e3e20d8SAkihiko Odaki
18761b17f1e9SMarc-André Lureaustatic void cocoa_clipboard_notify(Notifier *notifier, void *data)
18771b17f1e9SMarc-André Lureau{
18781b17f1e9SMarc-André Lureau    QemuClipboardNotify *notify = data;
18791b17f1e9SMarc-André Lureau
18801b17f1e9SMarc-André Lureau    switch (notify->type) {
18811b17f1e9SMarc-André Lureau    case QEMU_CLIPBOARD_UPDATE_INFO:
18821b17f1e9SMarc-André Lureau        cocoa_clipboard_update_info(notify->info);
18831b17f1e9SMarc-André Lureau        return;
1884505dbf9bSMarc-André Lureau    case QEMU_CLIPBOARD_RESET_SERIAL:
1885505dbf9bSMarc-André Lureau        /* ignore */
1886505dbf9bSMarc-André Lureau        return;
18871b17f1e9SMarc-André Lureau    }
18881b17f1e9SMarc-André Lureau}
18891b17f1e9SMarc-André Lureau
18907e3e20d8SAkihiko Odakistatic void cocoa_clipboard_request(QemuClipboardInfo *info,
18917e3e20d8SAkihiko Odaki                                    QemuClipboardType type)
18927e3e20d8SAkihiko Odaki{
18937e3e20d8SAkihiko Odaki    NSData *text;
18947e3e20d8SAkihiko Odaki
18957e3e20d8SAkihiko Odaki    switch (type) {
18967e3e20d8SAkihiko Odaki    case QEMU_CLIPBOARD_TYPE_TEXT:
18977e3e20d8SAkihiko Odaki        text = [[NSPasteboard generalPasteboard] dataForType:NSPasteboardTypeString];
18987e3e20d8SAkihiko Odaki        if (text) {
18997e3e20d8SAkihiko Odaki            qemu_clipboard_set_data(&cbpeer, info, type,
19007e3e20d8SAkihiko Odaki                                    [text length], [text bytes], true);
19017e3e20d8SAkihiko Odaki            [text release];
19027e3e20d8SAkihiko Odaki        }
19037e3e20d8SAkihiko Odaki        break;
19047e3e20d8SAkihiko Odaki    default:
19057e3e20d8SAkihiko Odaki        break;
19067e3e20d8SAkihiko Odaki    }
19077e3e20d8SAkihiko Odaki}
19087e3e20d8SAkihiko Odaki
19095588840fSPeter Maydell/*
19105588840fSPeter Maydell * The startup process for the OSX/Cocoa UI is complicated, because
19115588840fSPeter Maydell * OSX insists that the UI runs on the initial main thread, and so we
19125588840fSPeter Maydell * need to start a second thread which runs the vl.c qemu_main():
19135588840fSPeter Maydell *
19145588840fSPeter Maydell * Initial thread:                    2nd thread:
19155588840fSPeter Maydell * in main():
19165588840fSPeter Maydell *  create qemu-main thread
19175588840fSPeter Maydell *  wait on display_init semaphore
19185588840fSPeter Maydell *                                    call qemu_main()
19195588840fSPeter Maydell *                                    ...
19205588840fSPeter Maydell *                                    in cocoa_display_init():
19215588840fSPeter Maydell *                                     post the display_init semaphore
19225588840fSPeter Maydell *                                     wait on app_started semaphore
19235588840fSPeter Maydell *  create application, menus, etc
19245588840fSPeter Maydell *  enter OSX run loop
19255588840fSPeter Maydell * in applicationDidFinishLaunching:
19265588840fSPeter Maydell *  post app_started semaphore
19275588840fSPeter Maydell *                                     tell main thread to fullscreen if needed
19285588840fSPeter Maydell *                                    [...]
19295588840fSPeter Maydell *                                    run qemu main-loop
19305588840fSPeter Maydell *
19315588840fSPeter Maydell * We do this in two stages so that we don't do the creation of the
19325588840fSPeter Maydell * GUI application menus and so on for command line options like --help
19335588840fSPeter Maydell * where we want to just print text to stdout and exit immediately.
19345588840fSPeter Maydell */
1935c6fd6c70SPeter Maydell
19365588840fSPeter Maydellstatic void *call_qemu_main(void *opaque)
19375588840fSPeter Maydell{
19385588840fSPeter Maydell    int status;
19395588840fSPeter Maydell
19405588840fSPeter Maydell    COCOA_DEBUG("Second thread: calling qemu_main()\n");
19415588840fSPeter Maydell    status = qemu_main(gArgc, gArgv, *_NSGetEnviron());
19425588840fSPeter Maydell    COCOA_DEBUG("Second thread: qemu_main() returned, exiting\n");
19437e3e20d8SAkihiko Odaki    [cbowner release];
19445588840fSPeter Maydell    exit(status);
19455588840fSPeter Maydell}
19465588840fSPeter Maydell
194740a9aadbSAkihiko Odakiint main (int argc, char **argv) {
19485588840fSPeter Maydell    QemuThread thread;
19495588840fSPeter Maydell
19505588840fSPeter Maydell    COCOA_DEBUG("Entered main()\n");
1951c6fd6c70SPeter Maydell    gArgc = argc;
195240a9aadbSAkihiko Odaki    gArgv = argv;
1953c6fd6c70SPeter Maydell
19545588840fSPeter Maydell    qemu_sem_init(&display_init_sem, 0);
19555588840fSPeter Maydell    qemu_sem_init(&app_started_sem, 0);
1956c6fd6c70SPeter Maydell
19575588840fSPeter Maydell    qemu_thread_create(&thread, "qemu_main", call_qemu_main,
19585588840fSPeter Maydell                       NULL, QEMU_THREAD_DETACHED);
19595588840fSPeter Maydell
19605588840fSPeter Maydell    COCOA_DEBUG("Main thread: waiting for display_init_sem\n");
19615588840fSPeter Maydell    qemu_sem_wait(&display_init_sem);
19625588840fSPeter Maydell    COCOA_DEBUG("Main thread: initializing app\n");
1963c6fd6c70SPeter Maydell
1964c6fd6c70SPeter Maydell    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
1965c6fd6c70SPeter Maydell
1966c6fd6c70SPeter Maydell    // Pull this console process up to being a fully-fledged graphical
1967c6fd6c70SPeter Maydell    // app with a menubar and Dock icon
1968c6fd6c70SPeter Maydell    ProcessSerialNumber psn = { 0, kCurrentProcess };
1969c6fd6c70SPeter Maydell    TransformProcessType(&psn, kProcessTransformToForegroundApplication);
1970c6fd6c70SPeter Maydell
197161a2ed44SPeter Maydell    [QemuApplication sharedApplication];
1972c6fd6c70SPeter Maydell
1973c6fd6c70SPeter Maydell    create_initial_menus();
19743e230dd2SCorentin Chary
19755588840fSPeter Maydell    /*
19765588840fSPeter Maydell     * Create the menu entries which depend on QEMU state (for consoles
19775588840fSPeter Maydell     * and removeable devices). These make calls back into QEMU functions,
19785588840fSPeter Maydell     * which is OK because at this point we know that the second thread
19795588840fSPeter Maydell     * holds the iothread lock and is synchronously waiting for us to
19805588840fSPeter Maydell     * finish.
19815588840fSPeter Maydell     */
19825588840fSPeter Maydell    add_console_menu_entries();
19835588840fSPeter Maydell    addRemovableDevicesMenuItems();
19845588840fSPeter Maydell
19853e230dd2SCorentin Chary    // Create an Application controller
19863e230dd2SCorentin Chary    QemuCocoaAppController *appController = [[QemuCocoaAppController alloc] init];
19873e230dd2SCorentin Chary    [NSApp setDelegate:appController];
19883e230dd2SCorentin Chary
19893e230dd2SCorentin Chary    // Start the main event loop
19905588840fSPeter Maydell    COCOA_DEBUG("Main thread: entering OSX run loop\n");
19913e230dd2SCorentin Chary    [NSApp run];
19925588840fSPeter Maydell    COCOA_DEBUG("Main thread: left OSX run loop, exiting\n");
19933e230dd2SCorentin Chary
19943e230dd2SCorentin Chary    [appController release];
19953e230dd2SCorentin Chary    [pool release];
19963e230dd2SCorentin Chary
19973e230dd2SCorentin Chary    return 0;
19983e230dd2SCorentin Chary}
19993e230dd2SCorentin Chary
20003e230dd2SCorentin Chary
20013e230dd2SCorentin Chary
20023e230dd2SCorentin Chary#pragma mark qemu
20037c20b4a3SGerd Hoffmannstatic void cocoa_update(DisplayChangeListener *dcl,
20047c20b4a3SGerd Hoffmann                         int x, int y, int w, int h)
20053e230dd2SCorentin Chary{
20063e230dd2SCorentin Chary    COCOA_DEBUG("qemu_cocoa: cocoa_update\n");
20073e230dd2SCorentin Chary
20085588840fSPeter Maydell    dispatch_async(dispatch_get_main_queue(), ^{
20093e230dd2SCorentin Chary        NSRect rect;
20103e230dd2SCorentin Chary        if ([cocoaView cdx] == 1.0) {
20113e230dd2SCorentin Chary            rect = NSMakeRect(x, [cocoaView gscreen].height - y - h, w, h);
20123e230dd2SCorentin Chary        } else {
20133e230dd2SCorentin Chary            rect = NSMakeRect(
20143e230dd2SCorentin Chary                x * [cocoaView cdx],
20153e230dd2SCorentin Chary                ([cocoaView gscreen].height - y - h) * [cocoaView cdy],
20163e230dd2SCorentin Chary                w * [cocoaView cdx],
20173e230dd2SCorentin Chary                h * [cocoaView cdy]);
20183e230dd2SCorentin Chary        }
20193e230dd2SCorentin Chary        [cocoaView setNeedsDisplayInRect:rect];
20205588840fSPeter Maydell    });
20213e230dd2SCorentin Chary}
20223e230dd2SCorentin Chary
2023c12aeb86SGerd Hoffmannstatic void cocoa_switch(DisplayChangeListener *dcl,
2024c12aeb86SGerd Hoffmann                         DisplaySurface *surface)
20253e230dd2SCorentin Chary{
20265588840fSPeter Maydell    pixman_image_t *image = surface->image;
20273e230dd2SCorentin Chary
20286e657e64SPeter Maydell    COCOA_DEBUG("qemu_cocoa: cocoa_switch\n");
20295588840fSPeter Maydell
20305588840fSPeter Maydell    // The DisplaySurface will be freed as soon as this callback returns.
20315588840fSPeter Maydell    // We take a reference to the underlying pixman image here so it does
20325588840fSPeter Maydell    // not disappear from under our feet; the switchSurface method will
20335588840fSPeter Maydell    // deref the old image when it is done with it.
20345588840fSPeter Maydell    pixman_image_ref(image);
20355588840fSPeter Maydell
20365588840fSPeter Maydell    dispatch_async(dispatch_get_main_queue(), ^{
20378d65dee2SPeter Maydell        [cocoaView updateUIInfo];
20385588840fSPeter Maydell        [cocoaView switchSurface:image];
20395588840fSPeter Maydell    });
20403e230dd2SCorentin Chary}
20413e230dd2SCorentin Chary
2042bc2ed970SGerd Hoffmannstatic void cocoa_refresh(DisplayChangeListener *dcl)
20433e230dd2SCorentin Chary{
20446e657e64SPeter Maydell    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
20456e657e64SPeter Maydell
20463e230dd2SCorentin Chary    COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n");
2047468a895bSJohn Arbuckle    graphic_hw_update(NULL);
20483e230dd2SCorentin Chary
204921bae11aSGerd Hoffmann    if (qemu_input_is_absolute()) {
20505588840fSPeter Maydell        dispatch_async(dispatch_get_main_queue(), ^{
20513e230dd2SCorentin Chary            if (![cocoaView isAbsoluteEnabled]) {
205249b9bd4dSPeter Maydell                if ([cocoaView isMouseGrabbed]) {
20533e230dd2SCorentin Chary                    [cocoaView ungrabMouse];
20543e230dd2SCorentin Chary                }
20553e230dd2SCorentin Chary            }
20563e230dd2SCorentin Chary            [cocoaView setAbsoluteEnabled:YES];
20575588840fSPeter Maydell        });
20583e230dd2SCorentin Chary    }
20597e3e20d8SAkihiko Odaki
20607e3e20d8SAkihiko Odaki    if (cbchangecount != [[NSPasteboard generalPasteboard] changeCount]) {
20617e3e20d8SAkihiko Odaki        qemu_clipboard_info_unref(cbinfo);
20627e3e20d8SAkihiko Odaki        cbinfo = qemu_clipboard_info_new(&cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD);
20637e3e20d8SAkihiko Odaki        if ([[NSPasteboard generalPasteboard] availableTypeFromArray:@[NSPasteboardTypeString]]) {
20647e3e20d8SAkihiko Odaki            cbinfo->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
20657e3e20d8SAkihiko Odaki        }
20667e3e20d8SAkihiko Odaki        qemu_clipboard_update(cbinfo);
20677e3e20d8SAkihiko Odaki        cbchangecount = [[NSPasteboard generalPasteboard] changeCount];
20687e3e20d8SAkihiko Odaki        qemu_event_set(&cbevent);
20697e3e20d8SAkihiko Odaki    }
20707e3e20d8SAkihiko Odaki
20716e657e64SPeter Maydell    [pool release];
20723e230dd2SCorentin Chary}
20733e230dd2SCorentin Chary
20745013b9e4SGerd Hoffmannstatic void cocoa_display_init(DisplayState *ds, DisplayOptions *opts)
20753e230dd2SCorentin Chary{
20763e230dd2SCorentin Chary    COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n");
20773e230dd2SCorentin Chary
20785588840fSPeter Maydell    /* Tell main thread to go ahead and create the app and enter the run loop */
20795588840fSPeter Maydell    qemu_sem_post(&display_init_sem);
20805588840fSPeter Maydell    qemu_sem_wait(&app_started_sem);
20815588840fSPeter Maydell    COCOA_DEBUG("cocoa_display_init: app start completed\n");
20825588840fSPeter Maydell
2083f844cdb9SGustavo Noronha Silva    QemuCocoaAppController *controller = (QemuCocoaAppController *)[[NSApplication sharedApplication] delegate];
208443227af8SProgrammingkid    /* if fullscreen mode is to be used */
2085767f9bf3SGerd Hoffmann    if (opts->has_full_screen && opts->full_screen) {
20865588840fSPeter Maydell        dispatch_async(dispatch_get_main_queue(), ^{
208743227af8SProgrammingkid            [NSApp activateIgnoringOtherApps: YES];
2088f844cdb9SGustavo Noronha Silva            [controller toggleFullScreen: nil];
2089f844cdb9SGustavo Noronha Silva        });
2090f844cdb9SGustavo Noronha Silva    }
2091f844cdb9SGustavo Noronha Silva    if (opts->u.cocoa.has_full_grab && opts->u.cocoa.full_grab) {
2092f844cdb9SGustavo Noronha Silva        dispatch_async(dispatch_get_main_queue(), ^{
2093f844cdb9SGustavo Noronha Silva            [controller setFullGrab: nil];
20945588840fSPeter Maydell        });
209543227af8SProgrammingkid    }
209669221df8SCarwyn Ellis
20973487da6aSGerd Hoffmann    if (opts->has_show_cursor && opts->show_cursor) {
20983487da6aSGerd Hoffmann        cursor_hide = 0;
20993487da6aSGerd Hoffmann    }
21004797adceSGustavo Noronha Silva    if (opts->u.cocoa.has_swap_opt_cmd) {
21014797adceSGustavo Noronha Silva        swap_opt_cmd = opts->u.cocoa.swap_opt_cmd;
21024797adceSGustavo Noronha Silva    }
210343227af8SProgrammingkid
210448941a52SCarwyn Ellis    if (opts->u.cocoa.has_left_command_key && !opts->u.cocoa.left_command_key) {
210548941a52SCarwyn Ellis        left_command_key_enabled = 0;
210648941a52SCarwyn Ellis    }
210748941a52SCarwyn Ellis
21083e230dd2SCorentin Chary    // register vga output callbacks
2109cc7859c3SAkihiko Odaki    register_displaychangelistener(&dcl);
21107e3e20d8SAkihiko Odaki
21117e3e20d8SAkihiko Odaki    qemu_event_init(&cbevent, false);
21127e3e20d8SAkihiko Odaki    cbowner = [[QemuCocoaPasteboardTypeOwner alloc] init];
21137e3e20d8SAkihiko Odaki    qemu_clipboard_peer_register(&cbpeer);
21143e230dd2SCorentin Chary}
21155013b9e4SGerd Hoffmann
21165013b9e4SGerd Hoffmannstatic QemuDisplay qemu_display_cocoa = {
21175013b9e4SGerd Hoffmann    .type       = DISPLAY_TYPE_COCOA,
21185013b9e4SGerd Hoffmann    .init       = cocoa_display_init,
21195013b9e4SGerd Hoffmann};
21205013b9e4SGerd Hoffmann
21215013b9e4SGerd Hoffmannstatic void register_cocoa(void)
21225013b9e4SGerd Hoffmann{
21235013b9e4SGerd Hoffmann    qemu_display_register(&qemu_display_cocoa);
21245013b9e4SGerd Hoffmann}
21255013b9e4SGerd Hoffmann
21265013b9e4SGerd Hoffmanntype_init(register_cocoa);
2127