xref: /qemu/ui/cocoa.m (revision ae57d35c)
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
303e230dd2SCorentin Chary#include "qemu-common.h"
3128ecbaeeSPaolo Bonzini#include "ui/console.h"
3221bae11aSGerd Hoffmann#include "ui/input.h"
339c17d615SPaolo Bonzini#include "sysemu/sysemu.h"
3454d31236SMarkus Armbruster#include "sysemu/runstate.h"
35b0c3cf94SClaudio Fontana#include "sysemu/cpu-throttle.h"
36e688df6bSMarkus Armbruster#include "qapi/error.h"
3716bf5234SMarkus Armbruster#include "qapi/qapi-commands-block.h"
3890f8c0f9SPhilippe Mathieu-Daudé#include "qapi/qapi-commands-machine.h"
3916bf5234SMarkus Armbruster#include "qapi/qapi-commands-misc.h"
40693a3e01SJohn Arbuckle#include "sysemu/blockdev.h"
419e8204b1SProgrammingkid#include "qemu-version.h"
42db725815SMarkus Armbruster#include "qemu/main-loop.h"
430b8fa32fSMarkus Armbruster#include "qemu/module.h"
44aaac714fSJohn Arbuckle#include <Carbon/Carbon.h>
452e5b09fdSMarkus Armbruster#include "hw/core/cpu.h"
463e230dd2SCorentin Chary
475e24600aSBrendan Shanks#ifndef MAC_OS_X_VERSION_10_13
485e24600aSBrendan Shanks#define MAC_OS_X_VERSION_10_13 101300
495e24600aSBrendan Shanks#endif
503e230dd2SCorentin Chary
515e24600aSBrendan Shanks/* 10.14 deprecates NSOnState and NSOffState in favor of
525e24600aSBrendan Shanks * NSControlStateValueOn/Off, which were introduced in 10.13.
535e24600aSBrendan Shanks * Define for older versions
545e24600aSBrendan Shanks */
555e24600aSBrendan Shanks#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_13
565e24600aSBrendan Shanks#define NSControlStateValueOn NSOnState
575e24600aSBrendan Shanks#define NSControlStateValueOff NSOffState
585e24600aSBrendan Shanks#endif
593e230dd2SCorentin Chary
603e230dd2SCorentin Chary//#define DEBUG
613e230dd2SCorentin Chary
623e230dd2SCorentin Chary#ifdef DEBUG
633e230dd2SCorentin Chary#define COCOA_DEBUG(...)  { (void) fprintf (stdout, __VA_ARGS__); }
643e230dd2SCorentin Chary#else
653e230dd2SCorentin Chary#define COCOA_DEBUG(...)  ((void) 0)
663e230dd2SCorentin Chary#endif
673e230dd2SCorentin Chary
683e230dd2SCorentin Chary#define cgrect(nsrect) (*(CGRect *)&(nsrect))
693e230dd2SCorentin Chary
703e230dd2SCorentin Charytypedef struct {
713e230dd2SCorentin Chary    int width;
723e230dd2SCorentin Chary    int height;
733e230dd2SCorentin Chary} QEMUScreen;
743e230dd2SCorentin Chary
75cc7859c3SAkihiko Odakistatic void cocoa_update(DisplayChangeListener *dcl,
76cc7859c3SAkihiko Odaki                         int x, int y, int w, int h);
77cc7859c3SAkihiko Odaki
78cc7859c3SAkihiko Odakistatic void cocoa_switch(DisplayChangeListener *dcl,
79cc7859c3SAkihiko Odaki                         DisplaySurface *surface);
80cc7859c3SAkihiko Odaki
81cc7859c3SAkihiko Odakistatic void cocoa_refresh(DisplayChangeListener *dcl);
82cc7859c3SAkihiko Odaki
839e8204b1SProgrammingkidNSWindow *normalWindow, *about_window;
84cc7859c3SAkihiko Odakistatic const DisplayChangeListenerOps dcl_ops = {
85cc7859c3SAkihiko Odaki    .dpy_name          = "cocoa",
86cc7859c3SAkihiko Odaki    .dpy_gfx_update = cocoa_update,
87cc7859c3SAkihiko Odaki    .dpy_gfx_switch = cocoa_switch,
88cc7859c3SAkihiko Odaki    .dpy_refresh = cocoa_refresh,
89cc7859c3SAkihiko Odaki};
90cc7859c3SAkihiko Odakistatic DisplayChangeListener dcl = {
91cc7859c3SAkihiko Odaki    .ops = &dcl_ops,
92cc7859c3SAkihiko Odaki};
9321bae11aSGerd Hoffmannstatic int last_buttons;
943487da6aSGerd Hoffmannstatic int cursor_hide = 1;
953e230dd2SCorentin Chary
963e230dd2SCorentin Charyint gArgc;
973e230dd2SCorentin Charychar **gArgv;
985d1b2eefSProgrammingkidbool stretch_video;
998524f1c7SJohn ArbuckleNSTextField *pauseLabel;
100693a3e01SJohn ArbuckleNSArray * supportedImageFileTypes;
1013e230dd2SCorentin Chary
1025588840fSPeter Maydellstatic QemuSemaphore display_init_sem;
1035588840fSPeter Maydellstatic QemuSemaphore app_started_sem;
104dff742adSHikaru Nishidastatic bool allow_events;
1055588840fSPeter Maydell
10660105d7aSPeter Maydell// Utility functions to run specified code block with iothread lock held
10731819e95SPeter Maydelltypedef void (^CodeBlock)(void);
10860105d7aSPeter Maydelltypedef bool (^BoolCodeBlock)(void);
10931819e95SPeter Maydell
11031819e95SPeter Maydellstatic void with_iothread_lock(CodeBlock block)
11131819e95SPeter Maydell{
11231819e95SPeter Maydell    bool locked = qemu_mutex_iothread_locked();
11331819e95SPeter Maydell    if (!locked) {
11431819e95SPeter Maydell        qemu_mutex_lock_iothread();
11531819e95SPeter Maydell    }
11631819e95SPeter Maydell    block();
11731819e95SPeter Maydell    if (!locked) {
11831819e95SPeter Maydell        qemu_mutex_unlock_iothread();
11931819e95SPeter Maydell    }
12031819e95SPeter Maydell}
12131819e95SPeter Maydell
12260105d7aSPeter Maydellstatic bool bool_with_iothread_lock(BoolCodeBlock block)
12360105d7aSPeter Maydell{
12460105d7aSPeter Maydell    bool locked = qemu_mutex_iothread_locked();
12560105d7aSPeter Maydell    bool val;
12660105d7aSPeter Maydell
12760105d7aSPeter Maydell    if (!locked) {
12860105d7aSPeter Maydell        qemu_mutex_lock_iothread();
12960105d7aSPeter Maydell    }
13060105d7aSPeter Maydell    val = block();
13160105d7aSPeter Maydell    if (!locked) {
13260105d7aSPeter Maydell        qemu_mutex_unlock_iothread();
13360105d7aSPeter Maydell    }
13460105d7aSPeter Maydell    return val;
13560105d7aSPeter Maydell}
13660105d7aSPeter Maydell
137aaac714fSJohn Arbuckle// Mac to QKeyCode conversion
138aaac714fSJohn Arbuckleconst int mac_to_qkeycode_map[] = {
139aaac714fSJohn Arbuckle    [kVK_ANSI_A] = Q_KEY_CODE_A,
140aaac714fSJohn Arbuckle    [kVK_ANSI_B] = Q_KEY_CODE_B,
141aaac714fSJohn Arbuckle    [kVK_ANSI_C] = Q_KEY_CODE_C,
142aaac714fSJohn Arbuckle    [kVK_ANSI_D] = Q_KEY_CODE_D,
143aaac714fSJohn Arbuckle    [kVK_ANSI_E] = Q_KEY_CODE_E,
144aaac714fSJohn Arbuckle    [kVK_ANSI_F] = Q_KEY_CODE_F,
145aaac714fSJohn Arbuckle    [kVK_ANSI_G] = Q_KEY_CODE_G,
146aaac714fSJohn Arbuckle    [kVK_ANSI_H] = Q_KEY_CODE_H,
147aaac714fSJohn Arbuckle    [kVK_ANSI_I] = Q_KEY_CODE_I,
148aaac714fSJohn Arbuckle    [kVK_ANSI_J] = Q_KEY_CODE_J,
149aaac714fSJohn Arbuckle    [kVK_ANSI_K] = Q_KEY_CODE_K,
150aaac714fSJohn Arbuckle    [kVK_ANSI_L] = Q_KEY_CODE_L,
151aaac714fSJohn Arbuckle    [kVK_ANSI_M] = Q_KEY_CODE_M,
152aaac714fSJohn Arbuckle    [kVK_ANSI_N] = Q_KEY_CODE_N,
153aaac714fSJohn Arbuckle    [kVK_ANSI_O] = Q_KEY_CODE_O,
154aaac714fSJohn Arbuckle    [kVK_ANSI_P] = Q_KEY_CODE_P,
155aaac714fSJohn Arbuckle    [kVK_ANSI_Q] = Q_KEY_CODE_Q,
156aaac714fSJohn Arbuckle    [kVK_ANSI_R] = Q_KEY_CODE_R,
157aaac714fSJohn Arbuckle    [kVK_ANSI_S] = Q_KEY_CODE_S,
158aaac714fSJohn Arbuckle    [kVK_ANSI_T] = Q_KEY_CODE_T,
159aaac714fSJohn Arbuckle    [kVK_ANSI_U] = Q_KEY_CODE_U,
160aaac714fSJohn Arbuckle    [kVK_ANSI_V] = Q_KEY_CODE_V,
161aaac714fSJohn Arbuckle    [kVK_ANSI_W] = Q_KEY_CODE_W,
162aaac714fSJohn Arbuckle    [kVK_ANSI_X] = Q_KEY_CODE_X,
163aaac714fSJohn Arbuckle    [kVK_ANSI_Y] = Q_KEY_CODE_Y,
164aaac714fSJohn Arbuckle    [kVK_ANSI_Z] = Q_KEY_CODE_Z,
1653e230dd2SCorentin Chary
166aaac714fSJohn Arbuckle    [kVK_ANSI_0] = Q_KEY_CODE_0,
167aaac714fSJohn Arbuckle    [kVK_ANSI_1] = Q_KEY_CODE_1,
168aaac714fSJohn Arbuckle    [kVK_ANSI_2] = Q_KEY_CODE_2,
169aaac714fSJohn Arbuckle    [kVK_ANSI_3] = Q_KEY_CODE_3,
170aaac714fSJohn Arbuckle    [kVK_ANSI_4] = Q_KEY_CODE_4,
171aaac714fSJohn Arbuckle    [kVK_ANSI_5] = Q_KEY_CODE_5,
172aaac714fSJohn Arbuckle    [kVK_ANSI_6] = Q_KEY_CODE_6,
173aaac714fSJohn Arbuckle    [kVK_ANSI_7] = Q_KEY_CODE_7,
174aaac714fSJohn Arbuckle    [kVK_ANSI_8] = Q_KEY_CODE_8,
175aaac714fSJohn Arbuckle    [kVK_ANSI_9] = Q_KEY_CODE_9,
176aaac714fSJohn Arbuckle
177aaac714fSJohn Arbuckle    [kVK_ANSI_Grave] = Q_KEY_CODE_GRAVE_ACCENT,
178aaac714fSJohn Arbuckle    [kVK_ANSI_Minus] = Q_KEY_CODE_MINUS,
179aaac714fSJohn Arbuckle    [kVK_ANSI_Equal] = Q_KEY_CODE_EQUAL,
180aaac714fSJohn Arbuckle    [kVK_Delete] = Q_KEY_CODE_BACKSPACE,
181aaac714fSJohn Arbuckle    [kVK_CapsLock] = Q_KEY_CODE_CAPS_LOCK,
182aaac714fSJohn Arbuckle    [kVK_Tab] = Q_KEY_CODE_TAB,
183aaac714fSJohn Arbuckle    [kVK_Return] = Q_KEY_CODE_RET,
184aaac714fSJohn Arbuckle    [kVK_ANSI_LeftBracket] = Q_KEY_CODE_BRACKET_LEFT,
185aaac714fSJohn Arbuckle    [kVK_ANSI_RightBracket] = Q_KEY_CODE_BRACKET_RIGHT,
186aaac714fSJohn Arbuckle    [kVK_ANSI_Backslash] = Q_KEY_CODE_BACKSLASH,
187aaac714fSJohn Arbuckle    [kVK_ANSI_Semicolon] = Q_KEY_CODE_SEMICOLON,
188aaac714fSJohn Arbuckle    [kVK_ANSI_Quote] = Q_KEY_CODE_APOSTROPHE,
189aaac714fSJohn Arbuckle    [kVK_ANSI_Comma] = Q_KEY_CODE_COMMA,
190aaac714fSJohn Arbuckle    [kVK_ANSI_Period] = Q_KEY_CODE_DOT,
191aaac714fSJohn Arbuckle    [kVK_ANSI_Slash] = Q_KEY_CODE_SLASH,
192aaac714fSJohn Arbuckle    [kVK_Shift] = Q_KEY_CODE_SHIFT,
193aaac714fSJohn Arbuckle    [kVK_RightShift] = Q_KEY_CODE_SHIFT_R,
194aaac714fSJohn Arbuckle    [kVK_Control] = Q_KEY_CODE_CTRL,
195aaac714fSJohn Arbuckle    [kVK_RightControl] = Q_KEY_CODE_CTRL_R,
196aaac714fSJohn Arbuckle    [kVK_Option] = Q_KEY_CODE_ALT,
197aaac714fSJohn Arbuckle    [kVK_RightOption] = Q_KEY_CODE_ALT_R,
198aaac714fSJohn Arbuckle    [kVK_Command] = Q_KEY_CODE_META_L,
199aaac714fSJohn Arbuckle    [0x36] = Q_KEY_CODE_META_R, /* There is no kVK_RightCommand */
200aaac714fSJohn Arbuckle    [kVK_Space] = Q_KEY_CODE_SPC,
201aaac714fSJohn Arbuckle
202aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad0] = Q_KEY_CODE_KP_0,
203aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad1] = Q_KEY_CODE_KP_1,
204aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad2] = Q_KEY_CODE_KP_2,
205aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad3] = Q_KEY_CODE_KP_3,
206aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad4] = Q_KEY_CODE_KP_4,
207aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad5] = Q_KEY_CODE_KP_5,
208aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad6] = Q_KEY_CODE_KP_6,
209aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad7] = Q_KEY_CODE_KP_7,
210aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad8] = Q_KEY_CODE_KP_8,
211aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad9] = Q_KEY_CODE_KP_9,
212aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadDecimal] = Q_KEY_CODE_KP_DECIMAL,
213aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadEnter] = Q_KEY_CODE_KP_ENTER,
214aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadPlus] = Q_KEY_CODE_KP_ADD,
215aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadMinus] = Q_KEY_CODE_KP_SUBTRACT,
216aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadMultiply] = Q_KEY_CODE_KP_MULTIPLY,
217aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadDivide] = Q_KEY_CODE_KP_DIVIDE,
218aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadEquals] = Q_KEY_CODE_KP_EQUALS,
219aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadClear] = Q_KEY_CODE_NUM_LOCK,
220aaac714fSJohn Arbuckle
221aaac714fSJohn Arbuckle    [kVK_UpArrow] = Q_KEY_CODE_UP,
222aaac714fSJohn Arbuckle    [kVK_DownArrow] = Q_KEY_CODE_DOWN,
223aaac714fSJohn Arbuckle    [kVK_LeftArrow] = Q_KEY_CODE_LEFT,
224aaac714fSJohn Arbuckle    [kVK_RightArrow] = Q_KEY_CODE_RIGHT,
225aaac714fSJohn Arbuckle
226aaac714fSJohn Arbuckle    [kVK_Help] = Q_KEY_CODE_INSERT,
227aaac714fSJohn Arbuckle    [kVK_Home] = Q_KEY_CODE_HOME,
228aaac714fSJohn Arbuckle    [kVK_PageUp] = Q_KEY_CODE_PGUP,
229aaac714fSJohn Arbuckle    [kVK_PageDown] = Q_KEY_CODE_PGDN,
230aaac714fSJohn Arbuckle    [kVK_End] = Q_KEY_CODE_END,
231aaac714fSJohn Arbuckle    [kVK_ForwardDelete] = Q_KEY_CODE_DELETE,
232aaac714fSJohn Arbuckle
233aaac714fSJohn Arbuckle    [kVK_Escape] = Q_KEY_CODE_ESC,
234aaac714fSJohn Arbuckle
235aaac714fSJohn Arbuckle    /* The Power key can't be used directly because the operating system uses
236aaac714fSJohn Arbuckle     * it. This key can be emulated by using it in place of another key such as
237aaac714fSJohn Arbuckle     * F1. Don't forget to disable the real key binding.
238aaac714fSJohn Arbuckle     */
239aaac714fSJohn Arbuckle    /* [kVK_F1] = Q_KEY_CODE_POWER, */
240aaac714fSJohn Arbuckle
241aaac714fSJohn Arbuckle    [kVK_F1] = Q_KEY_CODE_F1,
242aaac714fSJohn Arbuckle    [kVK_F2] = Q_KEY_CODE_F2,
243aaac714fSJohn Arbuckle    [kVK_F3] = Q_KEY_CODE_F3,
244aaac714fSJohn Arbuckle    [kVK_F4] = Q_KEY_CODE_F4,
245aaac714fSJohn Arbuckle    [kVK_F5] = Q_KEY_CODE_F5,
246aaac714fSJohn Arbuckle    [kVK_F6] = Q_KEY_CODE_F6,
247aaac714fSJohn Arbuckle    [kVK_F7] = Q_KEY_CODE_F7,
248aaac714fSJohn Arbuckle    [kVK_F8] = Q_KEY_CODE_F8,
249aaac714fSJohn Arbuckle    [kVK_F9] = Q_KEY_CODE_F9,
250aaac714fSJohn Arbuckle    [kVK_F10] = Q_KEY_CODE_F10,
251aaac714fSJohn Arbuckle    [kVK_F11] = Q_KEY_CODE_F11,
252aaac714fSJohn Arbuckle    [kVK_F12] = Q_KEY_CODE_F12,
253aaac714fSJohn Arbuckle    [kVK_F13] = Q_KEY_CODE_PRINT,
254aaac714fSJohn Arbuckle    [kVK_F14] = Q_KEY_CODE_SCROLL_LOCK,
255aaac714fSJohn Arbuckle    [kVK_F15] = Q_KEY_CODE_PAUSE,
256aaac714fSJohn Arbuckle
257708b7255SAkihiko Odaki    // JIS keyboards only
258708b7255SAkihiko Odaki    [kVK_JIS_Yen] = Q_KEY_CODE_YEN,
259708b7255SAkihiko Odaki    [kVK_JIS_Underscore] = Q_KEY_CODE_RO,
260708b7255SAkihiko Odaki    [kVK_JIS_KeypadComma] = Q_KEY_CODE_KP_COMMA,
261708b7255SAkihiko Odaki    [kVK_JIS_Eisu] = Q_KEY_CODE_MUHENKAN,
262708b7255SAkihiko Odaki    [kVK_JIS_Kana] = Q_KEY_CODE_HENKAN,
263708b7255SAkihiko Odaki
2643e230dd2SCorentin Chary    /*
265aaac714fSJohn Arbuckle     * The eject and volume keys can't be used here because they are handled at
266aaac714fSJohn Arbuckle     * a lower level than what an Application can see.
2673e230dd2SCorentin Chary     */
2683e230dd2SCorentin Chary};
2693e230dd2SCorentin Chary
2703e230dd2SCorentin Charystatic int cocoa_keycode_to_qemu(int keycode)
2713e230dd2SCorentin Chary{
272aaac714fSJohn Arbuckle    if (ARRAY_SIZE(mac_to_qkeycode_map) <= keycode) {
2734313739aSAkihiko Odaki        error_report("(cocoa) warning unknown keycode 0x%x", keycode);
2743e230dd2SCorentin Chary        return 0;
2753e230dd2SCorentin Chary    }
276aaac714fSJohn Arbuckle    return mac_to_qkeycode_map[keycode];
2773e230dd2SCorentin Chary}
2783e230dd2SCorentin Chary
279693a3e01SJohn Arbuckle/* Displays an alert dialog box with the specified message */
280693a3e01SJohn Arbucklestatic void QEMU_Alert(NSString *message)
281693a3e01SJohn Arbuckle{
282693a3e01SJohn Arbuckle    NSAlert *alert;
283693a3e01SJohn Arbuckle    alert = [NSAlert new];
284693a3e01SJohn Arbuckle    [alert setMessageText: message];
285693a3e01SJohn Arbuckle    [alert runModal];
286693a3e01SJohn Arbuckle}
2873e230dd2SCorentin Chary
288693a3e01SJohn Arbuckle/* Handles any errors that happen with a device transaction */
289693a3e01SJohn Arbucklestatic void handleAnyDeviceErrors(Error * err)
290693a3e01SJohn Arbuckle{
291693a3e01SJohn Arbuckle    if (err) {
292693a3e01SJohn Arbuckle        QEMU_Alert([NSString stringWithCString: error_get_pretty(err)
293693a3e01SJohn Arbuckle                                      encoding: NSASCIIStringEncoding]);
294693a3e01SJohn Arbuckle        error_free(err);
295693a3e01SJohn Arbuckle    }
296693a3e01SJohn Arbuckle}
2973e230dd2SCorentin Chary
2983e230dd2SCorentin Chary/*
2993e230dd2SCorentin Chary ------------------------------------------------------
3003e230dd2SCorentin Chary    QemuCocoaView
3013e230dd2SCorentin Chary ------------------------------------------------------
3023e230dd2SCorentin Chary*/
3033e230dd2SCorentin Chary@interface QemuCocoaView : NSView
3043e230dd2SCorentin Chary{
3053e230dd2SCorentin Chary    QEMUScreen screen;
3063e230dd2SCorentin Chary    NSWindow *fullScreenWindow;
3073e230dd2SCorentin Chary    float cx,cy,cw,ch,cdx,cdy;
3085588840fSPeter Maydell    pixman_image_t *pixman_image;
309af8862b2SIan McKellar via Qemu-devel    BOOL modifiers_state[256];
31049b9bd4dSPeter Maydell    BOOL isMouseGrabbed;
3113e230dd2SCorentin Chary    BOOL isFullscreen;
3123e230dd2SCorentin Chary    BOOL isAbsoluteEnabled;
313f61c387eSPeter Maydell    BOOL isMouseDeassociated;
3143e230dd2SCorentin Chary}
31572a3e316SPeter Maydell- (void) switchSurface:(pixman_image_t *)image;
3163e230dd2SCorentin Chary- (void) grabMouse;
3173e230dd2SCorentin Chary- (void) ungrabMouse;
3183e230dd2SCorentin Chary- (void) toggleFullScreen:(id)sender;
3199c3a418eSJohn Arbuckle- (void) handleMonitorInput:(NSEvent *)event;
32060105d7aSPeter Maydell- (bool) handleEvent:(NSEvent *)event;
32160105d7aSPeter Maydell- (bool) handleEventLocked:(NSEvent *)event;
3223e230dd2SCorentin Chary- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled;
323f61c387eSPeter Maydell/* The state surrounding mouse grabbing is potentially confusing.
324f61c387eSPeter Maydell * isAbsoluteEnabled tracks qemu_input_is_absolute() [ie "is the emulated
325f61c387eSPeter Maydell *   pointing device an absolute-position one?"], but is only updated on
326f61c387eSPeter Maydell *   next refresh.
327f61c387eSPeter Maydell * isMouseGrabbed tracks whether GUI events are directed to the guest;
328f61c387eSPeter Maydell *   it controls whether special keys like Cmd get sent to the guest,
329f61c387eSPeter Maydell *   and whether we capture the mouse when in non-absolute mode.
330f61c387eSPeter Maydell * isMouseDeassociated tracks whether we've told MacOSX to disassociate
331f61c387eSPeter Maydell *   the mouse and mouse cursor position by calling
332f61c387eSPeter Maydell *   CGAssociateMouseAndMouseCursorPosition(FALSE)
333f61c387eSPeter Maydell *   (which basically happens if we grab in non-absolute mode).
334f61c387eSPeter Maydell */
33549b9bd4dSPeter Maydell- (BOOL) isMouseGrabbed;
3363e230dd2SCorentin Chary- (BOOL) isAbsoluteEnabled;
337f61c387eSPeter Maydell- (BOOL) isMouseDeassociated;
3383e230dd2SCorentin Chary- (float) cdx;
3393e230dd2SCorentin Chary- (float) cdy;
3403e230dd2SCorentin Chary- (QEMUScreen) gscreen;
3413b178b71SJohn Arbuckle- (void) raiseAllKeys;
3423e230dd2SCorentin Chary@end
3433e230dd2SCorentin Chary
3447fee199cSAndreas FärberQemuCocoaView *cocoaView;
3457fee199cSAndreas Färber
3463e230dd2SCorentin Chary@implementation QemuCocoaView
3473e230dd2SCorentin Chary- (id)initWithFrame:(NSRect)frameRect
3483e230dd2SCorentin Chary{
3493e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaView: initWithFrame\n");
3503e230dd2SCorentin Chary
3513e230dd2SCorentin Chary    self = [super initWithFrame:frameRect];
3523e230dd2SCorentin Chary    if (self) {
3533e230dd2SCorentin Chary
3543e230dd2SCorentin Chary        screen.width = frameRect.size.width;
3553e230dd2SCorentin Chary        screen.height = frameRect.size.height;
3563e230dd2SCorentin Chary
3573e230dd2SCorentin Chary    }
3583e230dd2SCorentin Chary    return self;
3593e230dd2SCorentin Chary}
3603e230dd2SCorentin Chary
3613e230dd2SCorentin Chary- (void) dealloc
3623e230dd2SCorentin Chary{
3633e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaView: dealloc\n");
3643e230dd2SCorentin Chary
365c0ff29d1SAkihiko Odaki    if (pixman_image) {
3665588840fSPeter Maydell        pixman_image_unref(pixman_image);
3675588840fSPeter Maydell    }
3683e230dd2SCorentin Chary
3693e230dd2SCorentin Chary    [super dealloc];
3703e230dd2SCorentin Chary}
3713e230dd2SCorentin Chary
3723e230dd2SCorentin Chary- (BOOL) isOpaque
3733e230dd2SCorentin Chary{
3743e230dd2SCorentin Chary    return YES;
3753e230dd2SCorentin Chary}
3763e230dd2SCorentin Chary
3775dd45beeSPeter Maydell- (BOOL) screenContainsPoint:(NSPoint) p
3785dd45beeSPeter Maydell{
3795dd45beeSPeter Maydell    return (p.x > -1 && p.x < screen.width && p.y > -1 && p.y < screen.height);
3805dd45beeSPeter Maydell}
3815dd45beeSPeter Maydell
3822044dff8SChen Zhang/* Get location of event and convert to virtual screen coordinate */
3832044dff8SChen Zhang- (CGPoint) screenLocationOfEvent:(NSEvent *)ev
3842044dff8SChen Zhang{
3852044dff8SChen Zhang    NSWindow *eventWindow = [ev window];
3862044dff8SChen Zhang    // XXX: Use CGRect and -convertRectFromScreen: to support macOS 10.10
3872044dff8SChen Zhang    CGRect r = CGRectZero;
3882044dff8SChen Zhang    r.origin = [ev locationInWindow];
3892044dff8SChen Zhang    if (!eventWindow) {
3902044dff8SChen Zhang        if (!isFullscreen) {
3912044dff8SChen Zhang            return [[self window] convertRectFromScreen:r].origin;
3922044dff8SChen Zhang        } else {
3932044dff8SChen Zhang            CGPoint locationInSelfWindow = [[self window] convertRectFromScreen:r].origin;
3942044dff8SChen Zhang            CGPoint loc = [self convertPoint:locationInSelfWindow fromView:nil];
3952044dff8SChen Zhang            if (stretch_video) {
3962044dff8SChen Zhang                loc.x /= cdx;
3972044dff8SChen Zhang                loc.y /= cdy;
3982044dff8SChen Zhang            }
3992044dff8SChen Zhang            return loc;
4002044dff8SChen Zhang        }
4012044dff8SChen Zhang    } else if ([[self window] isEqual:eventWindow]) {
4022044dff8SChen Zhang        if (!isFullscreen) {
4032044dff8SChen Zhang            return r.origin;
4042044dff8SChen Zhang        } else {
4052044dff8SChen Zhang            CGPoint loc = [self convertPoint:r.origin fromView:nil];
4062044dff8SChen Zhang            if (stretch_video) {
4072044dff8SChen Zhang                loc.x /= cdx;
4082044dff8SChen Zhang                loc.y /= cdy;
4092044dff8SChen Zhang            }
4102044dff8SChen Zhang            return loc;
4112044dff8SChen Zhang        }
4122044dff8SChen Zhang    } else {
4132044dff8SChen Zhang        return [[self window] convertRectFromScreen:[eventWindow convertRectToScreen:r]].origin;
4142044dff8SChen Zhang    }
4152044dff8SChen Zhang}
4162044dff8SChen Zhang
41713aefd30SPeter Maydell- (void) hideCursor
41813aefd30SPeter Maydell{
41913aefd30SPeter Maydell    if (!cursor_hide) {
42013aefd30SPeter Maydell        return;
42113aefd30SPeter Maydell    }
42213aefd30SPeter Maydell    [NSCursor hide];
42313aefd30SPeter Maydell}
42413aefd30SPeter Maydell
42513aefd30SPeter Maydell- (void) unhideCursor
42613aefd30SPeter Maydell{
42713aefd30SPeter Maydell    if (!cursor_hide) {
42813aefd30SPeter Maydell        return;
42913aefd30SPeter Maydell    }
43013aefd30SPeter Maydell    [NSCursor unhide];
43113aefd30SPeter Maydell}
43213aefd30SPeter Maydell
4333e230dd2SCorentin Chary- (void) drawRect:(NSRect) rect
4343e230dd2SCorentin Chary{
4353e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaView: drawRect\n");
4363e230dd2SCorentin Chary
4373e230dd2SCorentin Chary    // get CoreGraphic context
4385e24600aSBrendan Shanks    CGContextRef viewContextRef = [[NSGraphicsContext currentContext] CGContext];
4395e24600aSBrendan Shanks
4403e230dd2SCorentin Chary    CGContextSetInterpolationQuality (viewContextRef, kCGInterpolationNone);
4413e230dd2SCorentin Chary    CGContextSetShouldAntialias (viewContextRef, NO);
4423e230dd2SCorentin Chary
4433e230dd2SCorentin Chary    // draw screen bitmap directly to Core Graphics context
444c0ff29d1SAkihiko Odaki    if (!pixman_image) {
4457d270b1cSPeter Maydell        // Draw request before any guest device has set up a framebuffer:
4467d270b1cSPeter Maydell        // just draw an opaque black rectangle
4477d270b1cSPeter Maydell        CGContextSetRGBFillColor(viewContextRef, 0, 0, 0, 1.0);
4487d270b1cSPeter Maydell        CGContextFillRect(viewContextRef, NSRectToCGRect(rect));
4497d270b1cSPeter Maydell    } else {
450c0ff29d1SAkihiko Odaki        int w = pixman_image_get_width(pixman_image);
451c0ff29d1SAkihiko Odaki        int h = pixman_image_get_height(pixman_image);
452c0ff29d1SAkihiko Odaki        int bitsPerPixel = PIXMAN_FORMAT_BPP(pixman_image_get_format(pixman_image));
453d9c32b8fSAkihiko Odaki        int stride = pixman_image_get_stride(pixman_image);
454c0ff29d1SAkihiko Odaki        CGDataProviderRef dataProviderRef = CGDataProviderCreateWithData(
455c0ff29d1SAkihiko Odaki            NULL,
456c0ff29d1SAkihiko Odaki            pixman_image_get_data(pixman_image),
457d9c32b8fSAkihiko Odaki            stride * h,
458c0ff29d1SAkihiko Odaki            NULL
459c0ff29d1SAkihiko Odaki        );
4603e230dd2SCorentin Chary        CGImageRef imageRef = CGImageCreate(
461c0ff29d1SAkihiko Odaki            w, //width
462c0ff29d1SAkihiko Odaki            h, //height
463d9c32b8fSAkihiko Odaki            DIV_ROUND_UP(bitsPerPixel, 8) * 2, //bitsPerComponent
464c0ff29d1SAkihiko Odaki            bitsPerPixel, //bitsPerPixel
465d9c32b8fSAkihiko Odaki            stride, //bytesPerRow
466*ae57d35cSAkihiko Odaki            CGColorSpaceCreateWithName(kCGColorSpaceSRGB), //colorspace
467*ae57d35cSAkihiko Odaki            kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, //bitmapInfo
4683e230dd2SCorentin Chary            dataProviderRef, //provider
4693e230dd2SCorentin Chary            NULL, //decode
4703e230dd2SCorentin Chary            0, //interpolate
4713e230dd2SCorentin Chary            kCGRenderingIntentDefault //intent
4723e230dd2SCorentin Chary        );
4733e230dd2SCorentin Chary        // selective drawing code (draws only dirty rectangles) (OS X >= 10.4)
4743e230dd2SCorentin Chary        const NSRect *rectList;
4753e230dd2SCorentin Chary        NSInteger rectCount;
4763e230dd2SCorentin Chary        int i;
4773e230dd2SCorentin Chary        CGImageRef clipImageRef;
4783e230dd2SCorentin Chary        CGRect clipRect;
4793e230dd2SCorentin Chary
4803e230dd2SCorentin Chary        [self getRectsBeingDrawn:&rectList count:&rectCount];
4813e230dd2SCorentin Chary        for (i = 0; i < rectCount; i++) {
4823e230dd2SCorentin Chary            clipRect.origin.x = rectList[i].origin.x / cdx;
483c0ff29d1SAkihiko Odaki            clipRect.origin.y = (float)h - (rectList[i].origin.y + rectList[i].size.height) / cdy;
4843e230dd2SCorentin Chary            clipRect.size.width = rectList[i].size.width / cdx;
4853e230dd2SCorentin Chary            clipRect.size.height = rectList[i].size.height / cdy;
4863e230dd2SCorentin Chary            clipImageRef = CGImageCreateWithImageInRect(
4873e230dd2SCorentin Chary                                                        imageRef,
4883e230dd2SCorentin Chary                                                        clipRect
4893e230dd2SCorentin Chary                                                        );
4903e230dd2SCorentin Chary            CGContextDrawImage (viewContextRef, cgrect(rectList[i]), clipImageRef);
4913e230dd2SCorentin Chary            CGImageRelease (clipImageRef);
4923e230dd2SCorentin Chary        }
4933e230dd2SCorentin Chary        CGImageRelease (imageRef);
494c0ff29d1SAkihiko Odaki        CGDataProviderRelease(dataProviderRef);
4953e230dd2SCorentin Chary    }
4963e230dd2SCorentin Chary}
4973e230dd2SCorentin Chary
4983e230dd2SCorentin Chary- (void) setContentDimensions
4993e230dd2SCorentin Chary{
5003e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaView: setContentDimensions\n");
5013e230dd2SCorentin Chary
5023e230dd2SCorentin Chary    if (isFullscreen) {
5033e230dd2SCorentin Chary        cdx = [[NSScreen mainScreen] frame].size.width / (float)screen.width;
5043e230dd2SCorentin Chary        cdy = [[NSScreen mainScreen] frame].size.height / (float)screen.height;
5055d1b2eefSProgrammingkid
5065d1b2eefSProgrammingkid        /* stretches video, but keeps same aspect ratio */
5075d1b2eefSProgrammingkid        if (stretch_video == true) {
5085d1b2eefSProgrammingkid            /* use smallest stretch value - prevents clipping on sides */
5095d1b2eefSProgrammingkid            if (MIN(cdx, cdy) == cdx) {
5105d1b2eefSProgrammingkid                cdy = cdx;
5115d1b2eefSProgrammingkid            } else {
5125d1b2eefSProgrammingkid                cdx = cdy;
5135d1b2eefSProgrammingkid            }
5145d1b2eefSProgrammingkid        } else {  /* No stretching */
5155d1b2eefSProgrammingkid            cdx = cdy = 1;
5165d1b2eefSProgrammingkid        }
5173e230dd2SCorentin Chary        cw = screen.width * cdx;
5183e230dd2SCorentin Chary        ch = screen.height * cdy;
5193e230dd2SCorentin Chary        cx = ([[NSScreen mainScreen] frame].size.width - cw) / 2.0;
5203e230dd2SCorentin Chary        cy = ([[NSScreen mainScreen] frame].size.height - ch) / 2.0;
5213e230dd2SCorentin Chary    } else {
5223e230dd2SCorentin Chary        cx = 0;
5233e230dd2SCorentin Chary        cy = 0;
5243e230dd2SCorentin Chary        cw = screen.width;
5253e230dd2SCorentin Chary        ch = screen.height;
5263e230dd2SCorentin Chary        cdx = 1.0;
5273e230dd2SCorentin Chary        cdy = 1.0;
5283e230dd2SCorentin Chary    }
5293e230dd2SCorentin Chary}
5303e230dd2SCorentin Chary
53172a3e316SPeter Maydell- (void) switchSurface:(pixman_image_t *)image
5323e230dd2SCorentin Chary{
5335e00d3acSGerd Hoffmann    COCOA_DEBUG("QemuCocoaView: switchSurface\n");
5343e230dd2SCorentin Chary
53572a3e316SPeter Maydell    int w = pixman_image_get_width(image);
53672a3e316SPeter Maydell    int h = pixman_image_get_height(image);
537381600daSPeter Maydell    /* cdx == 0 means this is our very first surface, in which case we need
538381600daSPeter Maydell     * to recalculate the content dimensions even if it happens to be the size
539381600daSPeter Maydell     * of the initial empty window.
540381600daSPeter Maydell     */
541381600daSPeter Maydell    bool isResize = (w != screen.width || h != screen.height || cdx == 0.0);
542d3345a04SPeter Maydell
543d3345a04SPeter Maydell    int oldh = screen.height;
544d3345a04SPeter Maydell    if (isResize) {
545d3345a04SPeter Maydell        // Resize before we trigger the redraw, or we'll redraw at the wrong size
546d3345a04SPeter Maydell        COCOA_DEBUG("switchSurface: new size %d x %d\n", w, h);
547d3345a04SPeter Maydell        screen.width = w;
548d3345a04SPeter Maydell        screen.height = h;
549d3345a04SPeter Maydell        [self setContentDimensions];
550d3345a04SPeter Maydell        [self setFrame:NSMakeRect(cx, cy, cw, ch)];
551d3345a04SPeter Maydell    }
5528510d91eSPeter Maydell
5533e230dd2SCorentin Chary    // update screenBuffer
554c0ff29d1SAkihiko Odaki    if (pixman_image) {
5555588840fSPeter Maydell        pixman_image_unref(pixman_image);
5565588840fSPeter Maydell    }
5573e230dd2SCorentin Chary
5585588840fSPeter Maydell    pixman_image = image;
5593e230dd2SCorentin Chary
5603e230dd2SCorentin Chary    // update windows
5613e230dd2SCorentin Chary    if (isFullscreen) {
5623e230dd2SCorentin Chary        [[fullScreenWindow contentView] setFrame:[[NSScreen mainScreen] frame]];
563d3345a04SPeter 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];
5643e230dd2SCorentin Chary    } else {
5653e230dd2SCorentin Chary        if (qemu_name)
5663e230dd2SCorentin Chary            [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]];
567d3345a04SPeter 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];
5683e230dd2SCorentin Chary    }
569d3345a04SPeter Maydell
570d3345a04SPeter Maydell    if (isResize) {
5713e230dd2SCorentin Chary        [normalWindow center];
572d3345a04SPeter Maydell    }
5733e230dd2SCorentin Chary}
5743e230dd2SCorentin Chary
5753e230dd2SCorentin Chary- (void) toggleFullScreen:(id)sender
5763e230dd2SCorentin Chary{
5773e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaView: toggleFullScreen\n");
5783e230dd2SCorentin Chary
5793e230dd2SCorentin Chary    if (isFullscreen) { // switch from fullscreen to desktop
5803e230dd2SCorentin Chary        isFullscreen = FALSE;
5813e230dd2SCorentin Chary        [self ungrabMouse];
5823e230dd2SCorentin Chary        [self setContentDimensions];
5833e230dd2SCorentin Chary        [fullScreenWindow close];
5843e230dd2SCorentin Chary        [normalWindow setContentView: self];
5853e230dd2SCorentin Chary        [normalWindow makeKeyAndOrderFront: self];
5863e230dd2SCorentin Chary        [NSMenu setMenuBarVisible:YES];
5873e230dd2SCorentin Chary    } else { // switch from desktop to fullscreen
5883e230dd2SCorentin Chary        isFullscreen = TRUE;
5895d1b2eefSProgrammingkid        [normalWindow orderOut: nil]; /* Hide the window */
5903e230dd2SCorentin Chary        [self grabMouse];
5913e230dd2SCorentin Chary        [self setContentDimensions];
5923e230dd2SCorentin Chary        [NSMenu setMenuBarVisible:NO];
5933e230dd2SCorentin Chary        fullScreenWindow = [[NSWindow alloc] initWithContentRect:[[NSScreen mainScreen] frame]
5944ba967adSBrendan Shanks            styleMask:NSWindowStyleMaskBorderless
5953e230dd2SCorentin Chary            backing:NSBackingStoreBuffered
5963e230dd2SCorentin Chary            defer:NO];
5975d1b2eefSProgrammingkid        [fullScreenWindow setAcceptsMouseMovedEvents: YES];
5983e230dd2SCorentin Chary        [fullScreenWindow setHasShadow:NO];
5995d1b2eefSProgrammingkid        [fullScreenWindow setBackgroundColor: [NSColor blackColor]];
6005d1b2eefSProgrammingkid        [self setFrame:NSMakeRect(cx, cy, cw, ch)];
6015d1b2eefSProgrammingkid        [[fullScreenWindow contentView] addSubview: self];
6023e230dd2SCorentin Chary        [fullScreenWindow makeKeyAndOrderFront:self];
6033e230dd2SCorentin Chary    }
6043e230dd2SCorentin Chary}
6053e230dd2SCorentin Chary
606af8862b2SIan McKellar via Qemu-devel- (void) toggleModifier: (int)keycode {
607af8862b2SIan McKellar via Qemu-devel    // Toggle the stored state.
608af8862b2SIan McKellar via Qemu-devel    modifiers_state[keycode] = !modifiers_state[keycode];
609af8862b2SIan McKellar via Qemu-devel    // Send a keyup or keydown depending on the state.
610cc7859c3SAkihiko Odaki    qemu_input_event_send_key_qcode(dcl.con, keycode, modifiers_state[keycode]);
611af8862b2SIan McKellar via Qemu-devel}
612af8862b2SIan McKellar via Qemu-devel
613af8862b2SIan McKellar via Qemu-devel- (void) toggleStatefulModifier: (int)keycode {
614af8862b2SIan McKellar via Qemu-devel    // Toggle the stored state.
615af8862b2SIan McKellar via Qemu-devel    modifiers_state[keycode] = !modifiers_state[keycode];
616af8862b2SIan McKellar via Qemu-devel    // Generate keydown and keyup.
617cc7859c3SAkihiko Odaki    qemu_input_event_send_key_qcode(dcl.con, keycode, true);
618cc7859c3SAkihiko Odaki    qemu_input_event_send_key_qcode(dcl.con, keycode, false);
619af8862b2SIan McKellar via Qemu-devel}
620af8862b2SIan McKellar via Qemu-devel
6219c3a418eSJohn Arbuckle// Does the work of sending input to the monitor
6229c3a418eSJohn Arbuckle- (void) handleMonitorInput:(NSEvent *)event
6239c3a418eSJohn Arbuckle{
6249c3a418eSJohn Arbuckle    int keysym = 0;
6259c3a418eSJohn Arbuckle    int control_key = 0;
6269c3a418eSJohn Arbuckle
6279c3a418eSJohn Arbuckle    // if the control key is down
6289c3a418eSJohn Arbuckle    if ([event modifierFlags] & NSEventModifierFlagControl) {
6299c3a418eSJohn Arbuckle        control_key = 1;
6309c3a418eSJohn Arbuckle    }
6319c3a418eSJohn Arbuckle
6329c3a418eSJohn Arbuckle    /* translates Macintosh keycodes to QEMU's keysym */
6339c3a418eSJohn Arbuckle
6349c3a418eSJohn Arbuckle    int without_control_translation[] = {
6359c3a418eSJohn Arbuckle        [0 ... 0xff] = 0,   // invalid key
6369c3a418eSJohn Arbuckle
6379c3a418eSJohn Arbuckle        [kVK_UpArrow]       = QEMU_KEY_UP,
6389c3a418eSJohn Arbuckle        [kVK_DownArrow]     = QEMU_KEY_DOWN,
6399c3a418eSJohn Arbuckle        [kVK_RightArrow]    = QEMU_KEY_RIGHT,
6409c3a418eSJohn Arbuckle        [kVK_LeftArrow]     = QEMU_KEY_LEFT,
6419c3a418eSJohn Arbuckle        [kVK_Home]          = QEMU_KEY_HOME,
6429c3a418eSJohn Arbuckle        [kVK_End]           = QEMU_KEY_END,
6439c3a418eSJohn Arbuckle        [kVK_PageUp]        = QEMU_KEY_PAGEUP,
6449c3a418eSJohn Arbuckle        [kVK_PageDown]      = QEMU_KEY_PAGEDOWN,
6459c3a418eSJohn Arbuckle        [kVK_ForwardDelete] = QEMU_KEY_DELETE,
6469c3a418eSJohn Arbuckle        [kVK_Delete]        = QEMU_KEY_BACKSPACE,
6479c3a418eSJohn Arbuckle    };
6489c3a418eSJohn Arbuckle
6499c3a418eSJohn Arbuckle    int with_control_translation[] = {
6509c3a418eSJohn Arbuckle        [0 ... 0xff] = 0,   // invalid key
6519c3a418eSJohn Arbuckle
6529c3a418eSJohn Arbuckle        [kVK_UpArrow]       = QEMU_KEY_CTRL_UP,
6539c3a418eSJohn Arbuckle        [kVK_DownArrow]     = QEMU_KEY_CTRL_DOWN,
6549c3a418eSJohn Arbuckle        [kVK_RightArrow]    = QEMU_KEY_CTRL_RIGHT,
6559c3a418eSJohn Arbuckle        [kVK_LeftArrow]     = QEMU_KEY_CTRL_LEFT,
6569c3a418eSJohn Arbuckle        [kVK_Home]          = QEMU_KEY_CTRL_HOME,
6579c3a418eSJohn Arbuckle        [kVK_End]           = QEMU_KEY_CTRL_END,
6589c3a418eSJohn Arbuckle        [kVK_PageUp]        = QEMU_KEY_CTRL_PAGEUP,
6599c3a418eSJohn Arbuckle        [kVK_PageDown]      = QEMU_KEY_CTRL_PAGEDOWN,
6609c3a418eSJohn Arbuckle    };
6619c3a418eSJohn Arbuckle
6629c3a418eSJohn Arbuckle    if (control_key != 0) { /* If the control key is being used */
6639c3a418eSJohn Arbuckle        if ([event keyCode] < ARRAY_SIZE(with_control_translation)) {
6649c3a418eSJohn Arbuckle            keysym = with_control_translation[[event keyCode]];
6659c3a418eSJohn Arbuckle        }
6669c3a418eSJohn Arbuckle    } else {
6679c3a418eSJohn Arbuckle        if ([event keyCode] < ARRAY_SIZE(without_control_translation)) {
6689c3a418eSJohn Arbuckle            keysym = without_control_translation[[event keyCode]];
6699c3a418eSJohn Arbuckle        }
6709c3a418eSJohn Arbuckle    }
6719c3a418eSJohn Arbuckle
6729c3a418eSJohn Arbuckle    // if not a key that needs translating
6739c3a418eSJohn Arbuckle    if (keysym == 0) {
6749c3a418eSJohn Arbuckle        NSString *ks = [event characters];
6759c3a418eSJohn Arbuckle        if ([ks length] > 0) {
6769c3a418eSJohn Arbuckle            keysym = [ks characterAtIndex:0];
6779c3a418eSJohn Arbuckle        }
6789c3a418eSJohn Arbuckle    }
6799c3a418eSJohn Arbuckle
6809c3a418eSJohn Arbuckle    if (keysym) {
6819c3a418eSJohn Arbuckle        kbd_put_keysym(keysym);
6829c3a418eSJohn Arbuckle    }
6839c3a418eSJohn Arbuckle}
6849c3a418eSJohn Arbuckle
68560105d7aSPeter Maydell- (bool) handleEvent:(NSEvent *)event
6863e230dd2SCorentin Chary{
687dff742adSHikaru Nishida    if(!allow_events) {
688dff742adSHikaru Nishida        /*
689dff742adSHikaru Nishida         * Just let OSX have all events that arrive before
690dff742adSHikaru Nishida         * applicationDidFinishLaunching.
691dff742adSHikaru Nishida         * This avoids a deadlock on the iothread lock, which cocoa_display_init()
692dff742adSHikaru Nishida         * will not drop until after the app_started_sem is posted. (In theory
693dff742adSHikaru Nishida         * there should not be any such events, but OSX Catalina now emits some.)
694dff742adSHikaru Nishida         */
695dff742adSHikaru Nishida        return false;
696dff742adSHikaru Nishida    }
69760105d7aSPeter Maydell    return bool_with_iothread_lock(^{
69860105d7aSPeter Maydell        return [self handleEventLocked:event];
69931819e95SPeter Maydell    });
70031819e95SPeter Maydell}
7013e230dd2SCorentin Chary
70260105d7aSPeter Maydell- (bool) handleEventLocked:(NSEvent *)event
70331819e95SPeter Maydell{
70460105d7aSPeter Maydell    /* Return true if we handled the event, false if it should be given to OSX */
70531819e95SPeter Maydell    COCOA_DEBUG("QemuCocoaView: handleEvent\n");
7063e230dd2SCorentin Chary    int buttons = 0;
707af8862b2SIan McKellar via Qemu-devel    int keycode = 0;
70821bae11aSGerd Hoffmann    bool mouse_event = false;
7090c6c4395SJohn Arbuckle    static bool switched_to_fullscreen = false;
7102044dff8SChen Zhang    // Location of event in virtual screen coordinates
7112044dff8SChen Zhang    NSPoint p = [self screenLocationOfEvent:event];
7123e230dd2SCorentin Chary
7133e230dd2SCorentin Chary    switch ([event type]) {
7144ba967adSBrendan Shanks        case NSEventTypeFlagsChanged:
715af8862b2SIan McKellar via Qemu-devel            if ([event keyCode] == 0) {
716af8862b2SIan McKellar via Qemu-devel                // When the Cocoa keyCode is zero that means keys should be
717af8862b2SIan McKellar via Qemu-devel                // synthesized based on the values in in the eventModifiers
718af8862b2SIan McKellar via Qemu-devel                // bitmask.
719af8862b2SIan McKellar via Qemu-devel
720af8862b2SIan McKellar via Qemu-devel                if (qemu_console_is_graphic(NULL)) {
721e7206249SJohn Arbuckle                    NSUInteger modifiers = [event modifierFlags];
722af8862b2SIan McKellar via Qemu-devel
723af8862b2SIan McKellar via Qemu-devel                    if (!!(modifiers & NSEventModifierFlagCapsLock) != !!modifiers_state[Q_KEY_CODE_CAPS_LOCK]) {
724af8862b2SIan McKellar via Qemu-devel                        [self toggleStatefulModifier:Q_KEY_CODE_CAPS_LOCK];
725af8862b2SIan McKellar via Qemu-devel                    }
726af8862b2SIan McKellar via Qemu-devel                    if (!!(modifiers & NSEventModifierFlagShift) != !!modifiers_state[Q_KEY_CODE_SHIFT]) {
727af8862b2SIan McKellar via Qemu-devel                        [self toggleModifier:Q_KEY_CODE_SHIFT];
728af8862b2SIan McKellar via Qemu-devel                    }
729af8862b2SIan McKellar via Qemu-devel                    if (!!(modifiers & NSEventModifierFlagControl) != !!modifiers_state[Q_KEY_CODE_CTRL]) {
730af8862b2SIan McKellar via Qemu-devel                        [self toggleModifier:Q_KEY_CODE_CTRL];
731af8862b2SIan McKellar via Qemu-devel                    }
732af8862b2SIan McKellar via Qemu-devel                    if (!!(modifiers & NSEventModifierFlagOption) != !!modifiers_state[Q_KEY_CODE_ALT]) {
733af8862b2SIan McKellar via Qemu-devel                        [self toggleModifier:Q_KEY_CODE_ALT];
734af8862b2SIan McKellar via Qemu-devel                    }
735af8862b2SIan McKellar via Qemu-devel                    if (!!(modifiers & NSEventModifierFlagCommand) != !!modifiers_state[Q_KEY_CODE_META_L]) {
736af8862b2SIan McKellar via Qemu-devel                        [self toggleModifier:Q_KEY_CODE_META_L];
737af8862b2SIan McKellar via Qemu-devel                    }
738af8862b2SIan McKellar via Qemu-devel                }
739af8862b2SIan McKellar via Qemu-devel            } else {
7403e230dd2SCorentin Chary                keycode = cocoa_keycode_to_qemu([event keyCode]);
741af8862b2SIan McKellar via Qemu-devel            }
7428895919aSPeter Maydell
743aaac714fSJohn Arbuckle            if ((keycode == Q_KEY_CODE_META_L || keycode == Q_KEY_CODE_META_R)
744aaac714fSJohn Arbuckle               && !isMouseGrabbed) {
7458895919aSPeter Maydell              /* Don't pass command key changes to guest unless mouse is grabbed */
7468895919aSPeter Maydell              keycode = 0;
7478895919aSPeter Maydell            }
7488895919aSPeter Maydell
7493e230dd2SCorentin Chary            if (keycode) {
750aaac714fSJohn Arbuckle                // emulate caps lock and num lock keydown and keyup
751aaac714fSJohn Arbuckle                if (keycode == Q_KEY_CODE_CAPS_LOCK ||
752aaac714fSJohn Arbuckle                    keycode == Q_KEY_CODE_NUM_LOCK) {
753af8862b2SIan McKellar via Qemu-devel                    [self toggleStatefulModifier:keycode];
75468c0aa6eSPeter Maydell                } else if (qemu_console_is_graphic(NULL)) {
7550c6c4395SJohn Arbuckle                    if (switched_to_fullscreen) {
7560c6c4395SJohn Arbuckle                        switched_to_fullscreen = false;
7570c6c4395SJohn Arbuckle                    } else {
758af8862b2SIan McKellar via Qemu-devel                        [self toggleModifier:keycode];
7593e230dd2SCorentin Chary                    }
7603e230dd2SCorentin Chary                }
7610c6c4395SJohn Arbuckle            }
7623e230dd2SCorentin Chary
7633e230dd2SCorentin Chary            break;
7644ba967adSBrendan Shanks        case NSEventTypeKeyDown:
7658895919aSPeter Maydell            keycode = cocoa_keycode_to_qemu([event keyCode]);
7663e230dd2SCorentin Chary
7678895919aSPeter Maydell            // forward command key combos to the host UI unless the mouse is grabbed
7684ba967adSBrendan Shanks            if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) {
7690c6c4395SJohn Arbuckle                /*
7700c6c4395SJohn Arbuckle                 * Prevent the command key from being stuck down in the guest
7710c6c4395SJohn Arbuckle                 * when using Command-F to switch to full screen mode.
7720c6c4395SJohn Arbuckle                 */
7730c6c4395SJohn Arbuckle                if (keycode == Q_KEY_CODE_F) {
7740c6c4395SJohn Arbuckle                    switched_to_fullscreen = true;
7750c6c4395SJohn Arbuckle                }
77660105d7aSPeter Maydell                return false;
7773e230dd2SCorentin Chary            }
7783e230dd2SCorentin Chary
7793e230dd2SCorentin Chary            // default
7803e230dd2SCorentin Chary
7815929e36cSJohn Arbuckle            // handle control + alt Key Combos (ctrl+alt+[1..9,g] is reserved for QEMU)
7824ba967adSBrendan Shanks            if (([event modifierFlags] & NSEventModifierFlagControl) && ([event modifierFlags] & NSEventModifierFlagOption)) {
7835929e36cSJohn Arbuckle                NSString *keychar = [event charactersIgnoringModifiers];
7845929e36cSJohn Arbuckle                if ([keychar length] == 1) {
7855929e36cSJohn Arbuckle                    char key = [keychar characterAtIndex:0];
7865929e36cSJohn Arbuckle                    switch (key) {
7873e230dd2SCorentin Chary
7883e230dd2SCorentin Chary                        // enable graphic console
7895929e36cSJohn Arbuckle                        case '1' ... '9':
7905929e36cSJohn Arbuckle                            console_select(key - '0' - 1); /* ascii math */
79160105d7aSPeter Maydell                            return true;
7925929e36cSJohn Arbuckle
7935929e36cSJohn Arbuckle                        // release the mouse grab
7945929e36cSJohn Arbuckle                        case 'g':
7955929e36cSJohn Arbuckle                            [self ungrabMouse];
79660105d7aSPeter Maydell                            return true;
7975929e36cSJohn Arbuckle                    }
7983e230dd2SCorentin Chary                }
799ef2088f9SPeter Maydell            }
8003e230dd2SCorentin Chary
801ef2088f9SPeter Maydell            if (qemu_console_is_graphic(NULL)) {
802cc7859c3SAkihiko Odaki                qemu_input_event_send_key_qcode(dcl.con, keycode, true);
8033e230dd2SCorentin Chary            } else {
8049c3a418eSJohn Arbuckle                [self handleMonitorInput: event];
8053e230dd2SCorentin Chary            }
8063e230dd2SCorentin Chary            break;
8074ba967adSBrendan Shanks        case NSEventTypeKeyUp:
8083e230dd2SCorentin Chary            keycode = cocoa_keycode_to_qemu([event keyCode]);
8098895919aSPeter Maydell
8108895919aSPeter Maydell            // don't pass the guest a spurious key-up if we treated this
8118895919aSPeter Maydell            // command-key combo as a host UI action
8124ba967adSBrendan Shanks            if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) {
81360105d7aSPeter Maydell                return true;
8148895919aSPeter Maydell            }
8158895919aSPeter Maydell
81668c0aa6eSPeter Maydell            if (qemu_console_is_graphic(NULL)) {
817cc7859c3SAkihiko Odaki                qemu_input_event_send_key_qcode(dcl.con, keycode, false);
8183e230dd2SCorentin Chary            }
8193e230dd2SCorentin Chary            break;
8204ba967adSBrendan Shanks        case NSEventTypeMouseMoved:
8213e230dd2SCorentin Chary            if (isAbsoluteEnabled) {
8222044dff8SChen Zhang                // Cursor re-entered into a window might generate events bound to screen coordinates
8232044dff8SChen Zhang                // and `nil` window property, and in full screen mode, current window might not be
8242044dff8SChen Zhang                // key window, where event location alone should suffice.
8252044dff8SChen Zhang                if (![self screenContainsPoint:p] || !([[self window] isKeyWindow] || isFullscreen)) {
826f61c387eSPeter Maydell                    if (isMouseGrabbed) {
827f61c387eSPeter Maydell                        [self ungrabMouse];
8283e230dd2SCorentin Chary                    }
8293e230dd2SCorentin Chary                } else {
830f61c387eSPeter Maydell                    if (!isMouseGrabbed) {
831f61c387eSPeter Maydell                        [self grabMouse];
8323e230dd2SCorentin Chary                    }
8333e230dd2SCorentin Chary                }
8343e230dd2SCorentin Chary            }
83521bae11aSGerd Hoffmann            mouse_event = true;
8363e230dd2SCorentin Chary            break;
8374ba967adSBrendan Shanks        case NSEventTypeLeftMouseDown:
8383e230dd2SCorentin Chary            buttons |= MOUSE_EVENT_LBUTTON;
83921bae11aSGerd Hoffmann            mouse_event = true;
8403e230dd2SCorentin Chary            break;
8414ba967adSBrendan Shanks        case NSEventTypeRightMouseDown:
8423e230dd2SCorentin Chary            buttons |= MOUSE_EVENT_RBUTTON;
84321bae11aSGerd Hoffmann            mouse_event = true;
8443e230dd2SCorentin Chary            break;
8454ba967adSBrendan Shanks        case NSEventTypeOtherMouseDown:
8463e230dd2SCorentin Chary            buttons |= MOUSE_EVENT_MBUTTON;
84721bae11aSGerd Hoffmann            mouse_event = true;
8483e230dd2SCorentin Chary            break;
8494ba967adSBrendan Shanks        case NSEventTypeLeftMouseDragged:
8503e230dd2SCorentin Chary            buttons |= MOUSE_EVENT_LBUTTON;
85121bae11aSGerd Hoffmann            mouse_event = true;
8523e230dd2SCorentin Chary            break;
8534ba967adSBrendan Shanks        case NSEventTypeRightMouseDragged:
8543e230dd2SCorentin Chary            buttons |= MOUSE_EVENT_RBUTTON;
85521bae11aSGerd Hoffmann            mouse_event = true;
8563e230dd2SCorentin Chary            break;
8574ba967adSBrendan Shanks        case NSEventTypeOtherMouseDragged:
8583e230dd2SCorentin Chary            buttons |= MOUSE_EVENT_MBUTTON;
85921bae11aSGerd Hoffmann            mouse_event = true;
8603e230dd2SCorentin Chary            break;
8614ba967adSBrendan Shanks        case NSEventTypeLeftMouseUp:
86221bae11aSGerd Hoffmann            mouse_event = true;
863f61c387eSPeter Maydell            if (!isMouseGrabbed && [self screenContainsPoint:p]) {
8648e23e34dSChen Zhang                /*
8658e23e34dSChen Zhang                 * In fullscreen mode, the window of cocoaView may not be the
8668e23e34dSChen Zhang                 * key window, therefore the position relative to the virtual
8678e23e34dSChen Zhang                 * screen alone will be sufficient.
8688e23e34dSChen Zhang                 */
8698e23e34dSChen Zhang                if(isFullscreen || [[self window] isKeyWindow]) {
8703e230dd2SCorentin Chary                    [self grabMouse];
8713e230dd2SCorentin Chary                }
8729e8204b1SProgrammingkid            }
8733e230dd2SCorentin Chary            break;
8744ba967adSBrendan Shanks        case NSEventTypeRightMouseUp:
87521bae11aSGerd Hoffmann            mouse_event = true;
8763e230dd2SCorentin Chary            break;
8774ba967adSBrendan Shanks        case NSEventTypeOtherMouseUp:
87821bae11aSGerd Hoffmann            mouse_event = true;
8793e230dd2SCorentin Chary            break;
8804ba967adSBrendan Shanks        case NSEventTypeScrollWheel:
881ae7313e7SJohn Arbuckle            /*
882ae7313e7SJohn Arbuckle             * Send wheel events to the guest regardless of window focus.
883ae7313e7SJohn Arbuckle             * This is in-line with standard Mac OS X UI behaviour.
884ae7313e7SJohn Arbuckle             */
885ae7313e7SJohn Arbuckle
886dc3c89d6SJohn Arbuckle            /*
887dc3c89d6SJohn Arbuckle             * When deltaY is zero, it means that this scrolling event was
888dc3c89d6SJohn Arbuckle             * either horizontal, or so fine that it only appears in
889dc3c89d6SJohn Arbuckle             * scrollingDeltaY. So we drop the event.
890dc3c89d6SJohn Arbuckle             */
891dc3c89d6SJohn Arbuckle            if ([event deltaY] != 0) {
892ae7313e7SJohn Arbuckle            /* Determine if this is a scroll up or scroll down event */
893dc3c89d6SJohn Arbuckle                buttons = ([event deltaY] > 0) ?
894ae7313e7SJohn Arbuckle                    INPUT_BUTTON_WHEEL_UP : INPUT_BUTTON_WHEEL_DOWN;
895cc7859c3SAkihiko Odaki                qemu_input_queue_btn(dcl.con, buttons, true);
896ae7313e7SJohn Arbuckle                qemu_input_event_sync();
897cc7859c3SAkihiko Odaki                qemu_input_queue_btn(dcl.con, buttons, false);
898ae7313e7SJohn Arbuckle                qemu_input_event_sync();
899dc3c89d6SJohn Arbuckle            }
900ae7313e7SJohn Arbuckle            /*
901ae7313e7SJohn Arbuckle             * Since deltaY also reports scroll wheel events we prevent mouse
902ae7313e7SJohn Arbuckle             * movement code from executing.
903ae7313e7SJohn Arbuckle             */
904ae7313e7SJohn Arbuckle            mouse_event = false;
9053e230dd2SCorentin Chary            break;
9063e230dd2SCorentin Chary        default:
90760105d7aSPeter Maydell            return false;
9083e230dd2SCorentin Chary    }
90921bae11aSGerd Hoffmann
91021bae11aSGerd Hoffmann    if (mouse_event) {
9118d3a5d9bSPeter Maydell        /* Don't send button events to the guest unless we've got a
9128d3a5d9bSPeter Maydell         * mouse grab or window focus. If we have neither then this event
9138d3a5d9bSPeter Maydell         * is the user clicking on the background window to activate and
9148d3a5d9bSPeter Maydell         * bring us to the front, which will be done by the sendEvent
9158d3a5d9bSPeter Maydell         * call below. We definitely don't want to pass that click through
9168d3a5d9bSPeter Maydell         * to the guest.
9178d3a5d9bSPeter Maydell         */
9188d3a5d9bSPeter Maydell        if ((isMouseGrabbed || [[self window] isKeyWindow]) &&
9198d3a5d9bSPeter Maydell            (last_buttons != buttons)) {
9207fb1cf16SEric Blake            static uint32_t bmap[INPUT_BUTTON__MAX] = {
92121bae11aSGerd Hoffmann                [INPUT_BUTTON_LEFT]       = MOUSE_EVENT_LBUTTON,
92221bae11aSGerd Hoffmann                [INPUT_BUTTON_MIDDLE]     = MOUSE_EVENT_MBUTTON,
923ae7313e7SJohn Arbuckle                [INPUT_BUTTON_RIGHT]      = MOUSE_EVENT_RBUTTON
92421bae11aSGerd Hoffmann            };
925cc7859c3SAkihiko Odaki            qemu_input_update_buttons(dcl.con, bmap, last_buttons, buttons);
92621bae11aSGerd Hoffmann            last_buttons = buttons;
92721bae11aSGerd Hoffmann        }
928f61c387eSPeter Maydell        if (isMouseGrabbed) {
929f61c387eSPeter Maydell            if (isAbsoluteEnabled) {
930f61c387eSPeter Maydell                /* Note that the origin for Cocoa mouse coords is bottom left, not top left.
931f61c387eSPeter Maydell                 * The check on screenContainsPoint is to avoid sending out of range values for
932f61c387eSPeter Maydell                 * clicks in the titlebar.
933f61c387eSPeter Maydell                 */
934f61c387eSPeter Maydell                if ([self screenContainsPoint:p]) {
935cc7859c3SAkihiko Odaki                    qemu_input_queue_abs(dcl.con, INPUT_AXIS_X, p.x, 0, screen.width);
936cc7859c3SAkihiko Odaki                    qemu_input_queue_abs(dcl.con, INPUT_AXIS_Y, screen.height - p.y, 0, screen.height);
937f61c387eSPeter Maydell                }
938f61c387eSPeter Maydell            } else {
939cc7859c3SAkihiko Odaki                qemu_input_queue_rel(dcl.con, INPUT_AXIS_X, (int)[event deltaX]);
940cc7859c3SAkihiko Odaki                qemu_input_queue_rel(dcl.con, INPUT_AXIS_Y, (int)[event deltaY]);
941f61c387eSPeter Maydell            }
94221bae11aSGerd Hoffmann        } else {
94360105d7aSPeter Maydell            return false;
94421bae11aSGerd Hoffmann        }
94521bae11aSGerd Hoffmann        qemu_input_event_sync();
94621bae11aSGerd Hoffmann    }
94760105d7aSPeter Maydell    return true;
9483e230dd2SCorentin Chary}
9493e230dd2SCorentin Chary
9503e230dd2SCorentin Chary- (void) grabMouse
9513e230dd2SCorentin Chary{
9523e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaView: grabMouse\n");
9533e230dd2SCorentin Chary
9543e230dd2SCorentin Chary    if (!isFullscreen) {
9553e230dd2SCorentin Chary        if (qemu_name)
9565929e36cSJohn Arbuckle            [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s - (Press ctrl + alt + g to release Mouse)", qemu_name]];
9573e230dd2SCorentin Chary        else
9585929e36cSJohn Arbuckle            [normalWindow setTitle:@"QEMU - (Press ctrl + alt + g to release Mouse)"];
9593e230dd2SCorentin Chary    }
96013aefd30SPeter Maydell    [self hideCursor];
961f61c387eSPeter Maydell    if (!isAbsoluteEnabled) {
962f61c387eSPeter Maydell        isMouseDeassociated = TRUE;
9633e230dd2SCorentin Chary        CGAssociateMouseAndMouseCursorPosition(FALSE);
964f61c387eSPeter Maydell    }
96549b9bd4dSPeter Maydell    isMouseGrabbed = TRUE; // while isMouseGrabbed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:]
9663e230dd2SCorentin Chary}
9673e230dd2SCorentin Chary
9683e230dd2SCorentin Chary- (void) ungrabMouse
9693e230dd2SCorentin Chary{
9703e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaView: ungrabMouse\n");
9713e230dd2SCorentin Chary
9723e230dd2SCorentin Chary    if (!isFullscreen) {
9733e230dd2SCorentin Chary        if (qemu_name)
9743e230dd2SCorentin Chary            [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]];
9753e230dd2SCorentin Chary        else
9763e230dd2SCorentin Chary            [normalWindow setTitle:@"QEMU"];
9773e230dd2SCorentin Chary    }
97813aefd30SPeter Maydell    [self unhideCursor];
979f61c387eSPeter Maydell    if (isMouseDeassociated) {
9803e230dd2SCorentin Chary        CGAssociateMouseAndMouseCursorPosition(TRUE);
981f61c387eSPeter Maydell        isMouseDeassociated = FALSE;
982f61c387eSPeter Maydell    }
98349b9bd4dSPeter Maydell    isMouseGrabbed = FALSE;
9843e230dd2SCorentin Chary}
9853e230dd2SCorentin Chary
9863e230dd2SCorentin Chary- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled {isAbsoluteEnabled = tIsAbsoluteEnabled;}
98749b9bd4dSPeter Maydell- (BOOL) isMouseGrabbed {return isMouseGrabbed;}
9883e230dd2SCorentin Chary- (BOOL) isAbsoluteEnabled {return isAbsoluteEnabled;}
989f61c387eSPeter Maydell- (BOOL) isMouseDeassociated {return isMouseDeassociated;}
9903e230dd2SCorentin Chary- (float) cdx {return cdx;}
9913e230dd2SCorentin Chary- (float) cdy {return cdy;}
9923e230dd2SCorentin Chary- (QEMUScreen) gscreen {return screen;}
9933b178b71SJohn Arbuckle
9943b178b71SJohn Arbuckle/*
9953b178b71SJohn Arbuckle * Makes the target think all down keys are being released.
9963b178b71SJohn Arbuckle * This prevents a stuck key problem, since we will not see
9973b178b71SJohn Arbuckle * key up events for those keys after we have lost focus.
9983b178b71SJohn Arbuckle */
9993b178b71SJohn Arbuckle- (void) raiseAllKeys
10003b178b71SJohn Arbuckle{
10013b178b71SJohn Arbuckle    const int max_index = ARRAY_SIZE(modifiers_state);
10023b178b71SJohn Arbuckle
100331819e95SPeter Maydell    with_iothread_lock(^{
100431819e95SPeter Maydell        int index;
100531819e95SPeter Maydell
10063b178b71SJohn Arbuckle        for (index = 0; index < max_index; index++) {
10073b178b71SJohn Arbuckle            if (modifiers_state[index]) {
10083b178b71SJohn Arbuckle                modifiers_state[index] = 0;
1009cc7859c3SAkihiko Odaki                qemu_input_event_send_key_qcode(dcl.con, index, false);
10103b178b71SJohn Arbuckle            }
10113b178b71SJohn Arbuckle        }
101231819e95SPeter Maydell    });
10133b178b71SJohn Arbuckle}
10143e230dd2SCorentin Chary@end
10153e230dd2SCorentin Chary
10163e230dd2SCorentin Chary
10173e230dd2SCorentin Chary
10183e230dd2SCorentin Chary/*
10193e230dd2SCorentin Chary ------------------------------------------------------
10203e230dd2SCorentin Chary    QemuCocoaAppController
10213e230dd2SCorentin Chary ------------------------------------------------------
10223e230dd2SCorentin Chary*/
10233e230dd2SCorentin Chary@interface QemuCocoaAppController : NSObject
1024d9bc14f6SJohn Arbuckle                                       <NSWindowDelegate, NSApplicationDelegate>
10253e230dd2SCorentin Chary{
10263e230dd2SCorentin Chary}
10275d1b2eefSProgrammingkid- (void)doToggleFullScreen:(id)sender;
10283e230dd2SCorentin Chary- (void)toggleFullScreen:(id)sender;
10293e230dd2SCorentin Chary- (void)showQEMUDoc:(id)sender;
10305d1b2eefSProgrammingkid- (void)zoomToFit:(id) sender;
1031b4c6a112SProgrammingkid- (void)displayConsole:(id)sender;
10328524f1c7SJohn Arbuckle- (void)pauseQEMU:(id)sender;
10338524f1c7SJohn Arbuckle- (void)resumeQEMU:(id)sender;
10348524f1c7SJohn Arbuckle- (void)displayPause;
10358524f1c7SJohn Arbuckle- (void)removePause;
103627074614SJohn Arbuckle- (void)restartQEMU:(id)sender;
103727074614SJohn Arbuckle- (void)powerDownQEMU:(id)sender;
1038693a3e01SJohn Arbuckle- (void)ejectDeviceMedia:(id)sender;
1039693a3e01SJohn Arbuckle- (void)changeDeviceMedia:(id)sender;
1040d9bc14f6SJohn Arbuckle- (BOOL)verifyQuit;
1041f4747900SJohn Arbuckle- (void)openDocumentation:(NSString *)filename;
10429e8204b1SProgrammingkid- (IBAction) do_about_menu_item: (id) sender;
10439e8204b1SProgrammingkid- (void)make_about_window;
1044e47ec1a9SJohn Arbuckle- (void)adjustSpeed:(id)sender;
10453e230dd2SCorentin Chary@end
10463e230dd2SCorentin Chary
10473e230dd2SCorentin Chary@implementation QemuCocoaAppController
10483e230dd2SCorentin Chary- (id) init
10493e230dd2SCorentin Chary{
10503e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaAppController: init\n");
10513e230dd2SCorentin Chary
10523e230dd2SCorentin Chary    self = [super init];
10533e230dd2SCorentin Chary    if (self) {
10543e230dd2SCorentin Chary
10553e230dd2SCorentin Chary        // create a view and add it to the window
10563e230dd2SCorentin Chary        cocoaView = [[QemuCocoaView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 640.0, 480.0)];
10573e230dd2SCorentin Chary        if(!cocoaView) {
10584313739aSAkihiko Odaki            error_report("(cocoa) can't create a view");
10593e230dd2SCorentin Chary            exit(1);
10603e230dd2SCorentin Chary        }
10613e230dd2SCorentin Chary
10623e230dd2SCorentin Chary        // create a window
10633e230dd2SCorentin Chary        normalWindow = [[NSWindow alloc] initWithContentRect:[cocoaView frame]
10644ba967adSBrendan Shanks            styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskClosable
10653e230dd2SCorentin Chary            backing:NSBackingStoreBuffered defer:NO];
10663e230dd2SCorentin Chary        if(!normalWindow) {
10674313739aSAkihiko Odaki            error_report("(cocoa) can't create window");
10683e230dd2SCorentin Chary            exit(1);
10693e230dd2SCorentin Chary        }
10703e230dd2SCorentin Chary        [normalWindow setAcceptsMouseMovedEvents:YES];
1071a1dbc05aSJohn Arbuckle        [normalWindow setTitle:@"QEMU"];
10723e230dd2SCorentin Chary        [normalWindow setContentView:cocoaView];
10733e230dd2SCorentin Chary        [normalWindow makeKeyAndOrderFront:self];
10743e230dd2SCorentin Chary        [normalWindow center];
1075d9bc14f6SJohn Arbuckle        [normalWindow setDelegate: self];
10765d1b2eefSProgrammingkid        stretch_video = false;
10778524f1c7SJohn Arbuckle
10788524f1c7SJohn Arbuckle        /* Used for displaying pause on the screen */
10798524f1c7SJohn Arbuckle        pauseLabel = [NSTextField new];
10808524f1c7SJohn Arbuckle        [pauseLabel setBezeled:YES];
10818524f1c7SJohn Arbuckle        [pauseLabel setDrawsBackground:YES];
10828524f1c7SJohn Arbuckle        [pauseLabel setBackgroundColor: [NSColor whiteColor]];
10838524f1c7SJohn Arbuckle        [pauseLabel setEditable:NO];
10848524f1c7SJohn Arbuckle        [pauseLabel setSelectable:NO];
10858524f1c7SJohn Arbuckle        [pauseLabel setStringValue: @"Paused"];
10868524f1c7SJohn Arbuckle        [pauseLabel setFont: [NSFont fontWithName: @"Helvetica" size: 90]];
10878524f1c7SJohn Arbuckle        [pauseLabel setTextColor: [NSColor blackColor]];
10888524f1c7SJohn Arbuckle        [pauseLabel sizeToFit];
1089693a3e01SJohn Arbuckle
1090693a3e01SJohn Arbuckle        // set the supported image file types that can be opened
1091693a3e01SJohn Arbuckle        supportedImageFileTypes = [NSArray arrayWithObjects: @"img", @"iso", @"dmg",
10929d227f19SJohn Arbuckle                                 @"qcow", @"qcow2", @"cloop", @"vmdk", @"cdr",
10935f26fcfbSProgrammingkid                                  @"toast", nil];
10949e8204b1SProgrammingkid        [self make_about_window];
10953e230dd2SCorentin Chary    }
10963e230dd2SCorentin Chary    return self;
10973e230dd2SCorentin Chary}
10983e230dd2SCorentin Chary
10993e230dd2SCorentin Chary- (void) dealloc
11003e230dd2SCorentin Chary{
11013e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaAppController: dealloc\n");
11023e230dd2SCorentin Chary
11033e230dd2SCorentin Chary    if (cocoaView)
11043e230dd2SCorentin Chary        [cocoaView release];
11053e230dd2SCorentin Chary    [super dealloc];
11063e230dd2SCorentin Chary}
11073e230dd2SCorentin Chary
11083e230dd2SCorentin Chary- (void)applicationDidFinishLaunching: (NSNotification *) note
11093e230dd2SCorentin Chary{
11103e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaAppController: applicationDidFinishLaunching\n");
1111dff742adSHikaru Nishida    allow_events = true;
11125588840fSPeter Maydell    /* Tell cocoa_display_init to proceed */
11135588840fSPeter Maydell    qemu_sem_post(&app_started_sem);
11143e230dd2SCorentin Chary}
11153e230dd2SCorentin Chary
11163e230dd2SCorentin Chary- (void)applicationWillTerminate:(NSNotification *)aNotification
11173e230dd2SCorentin Chary{
11183e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n");
11193e230dd2SCorentin Chary
1120cf83f140SEric Blake    qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
11213e230dd2SCorentin Chary    exit(0);
11223e230dd2SCorentin Chary}
11233e230dd2SCorentin Chary
11243e230dd2SCorentin Chary- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
11253e230dd2SCorentin Chary{
11263e230dd2SCorentin Chary    return YES;
11273e230dd2SCorentin Chary}
11283e230dd2SCorentin Chary
1129d9bc14f6SJohn Arbuckle- (NSApplicationTerminateReply)applicationShouldTerminate:
1130d9bc14f6SJohn Arbuckle                                                         (NSApplication *)sender
1131d9bc14f6SJohn Arbuckle{
1132d9bc14f6SJohn Arbuckle    COCOA_DEBUG("QemuCocoaAppController: applicationShouldTerminate\n");
1133d9bc14f6SJohn Arbuckle    return [self verifyQuit];
1134d9bc14f6SJohn Arbuckle}
1135d9bc14f6SJohn Arbuckle
1136d9bc14f6SJohn Arbuckle/* Called when the user clicks on a window's close button */
1137d9bc14f6SJohn Arbuckle- (BOOL)windowShouldClose:(id)sender
1138d9bc14f6SJohn Arbuckle{
1139d9bc14f6SJohn Arbuckle    COCOA_DEBUG("QemuCocoaAppController: windowShouldClose\n");
1140d9bc14f6SJohn Arbuckle    [NSApp terminate: sender];
1141d9bc14f6SJohn Arbuckle    /* If the user allows the application to quit then the call to
1142d9bc14f6SJohn Arbuckle     * NSApp terminate will never return. If we get here then the user
1143d9bc14f6SJohn Arbuckle     * cancelled the quit, so we should return NO to not permit the
1144d9bc14f6SJohn Arbuckle     * closing of this window.
1145d9bc14f6SJohn Arbuckle     */
1146d9bc14f6SJohn Arbuckle    return NO;
1147d9bc14f6SJohn Arbuckle}
1148d9bc14f6SJohn Arbuckle
11493b178b71SJohn Arbuckle/* Called when QEMU goes into the background */
11503b178b71SJohn Arbuckle- (void) applicationWillResignActive: (NSNotification *)aNotification
11513b178b71SJohn Arbuckle{
11523b178b71SJohn Arbuckle    COCOA_DEBUG("QemuCocoaAppController: applicationWillResignActive\n");
11533b178b71SJohn Arbuckle    [cocoaView raiseAllKeys];
11543b178b71SJohn Arbuckle}
11553b178b71SJohn Arbuckle
11565d1b2eefSProgrammingkid/* We abstract the method called by the Enter Fullscreen menu item
11575d1b2eefSProgrammingkid * because Mac OS 10.7 and higher disables it. This is because of the
11585d1b2eefSProgrammingkid * menu item's old selector's name toggleFullScreen:
11595d1b2eefSProgrammingkid */
11605d1b2eefSProgrammingkid- (void) doToggleFullScreen:(id)sender
11615d1b2eefSProgrammingkid{
11625d1b2eefSProgrammingkid    [self toggleFullScreen:(id)sender];
11635d1b2eefSProgrammingkid}
11645d1b2eefSProgrammingkid
11653e230dd2SCorentin Chary- (void)toggleFullScreen:(id)sender
11663e230dd2SCorentin Chary{
11673e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaAppController: toggleFullScreen\n");
11683e230dd2SCorentin Chary
11693e230dd2SCorentin Chary    [cocoaView toggleFullScreen:sender];
11703e230dd2SCorentin Chary}
11713e230dd2SCorentin Chary
1172f4747900SJohn Arbuckle/* Tries to find then open the specified filename */
1173f4747900SJohn Arbuckle- (void) openDocumentation: (NSString *) filename
1174f4747900SJohn Arbuckle{
1175f4747900SJohn Arbuckle    /* Where to look for local files */
11768d6fda8cSRoman Bolshakov    NSString *path_array[] = {@"../share/doc/qemu/", @"../doc/qemu/", @"docs/"};
1177f4747900SJohn Arbuckle    NSString *full_file_path;
11781ff5a063SRoman Bolshakov    NSURL *full_file_url;
1179f4747900SJohn Arbuckle
1180f4747900SJohn Arbuckle    /* iterate thru the possible paths until the file is found */
1181f4747900SJohn Arbuckle    int index;
1182f4747900SJohn Arbuckle    for (index = 0; index < ARRAY_SIZE(path_array); index++) {
1183f4747900SJohn Arbuckle        full_file_path = [[NSBundle mainBundle] executablePath];
1184f4747900SJohn Arbuckle        full_file_path = [full_file_path stringByDeletingLastPathComponent];
1185f4747900SJohn Arbuckle        full_file_path = [NSString stringWithFormat: @"%@/%@%@", full_file_path,
1186f4747900SJohn Arbuckle                          path_array[index], filename];
11871ff5a063SRoman Bolshakov        full_file_url = [NSURL fileURLWithPath: full_file_path
11881ff5a063SRoman Bolshakov                                   isDirectory: false];
11891ff5a063SRoman Bolshakov        if ([[NSWorkspace sharedWorkspace] openURL: full_file_url] == YES) {
1190f4747900SJohn Arbuckle            return;
1191f4747900SJohn Arbuckle        }
1192f4747900SJohn Arbuckle    }
1193f4747900SJohn Arbuckle
1194f4747900SJohn Arbuckle    /* If none of the paths opened a file */
1195f4747900SJohn Arbuckle    NSBeep();
1196f4747900SJohn Arbuckle    QEMU_Alert(@"Failed to open file");
1197f4747900SJohn Arbuckle}
1198f4747900SJohn Arbuckle
11993e230dd2SCorentin Chary- (void)showQEMUDoc:(id)sender
12003e230dd2SCorentin Chary{
12013e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaAppController: showQEMUDoc\n");
12023e230dd2SCorentin Chary
12031879f241SPeter Maydell    [self openDocumentation: @"index.html"];
12043e230dd2SCorentin Chary}
12053e230dd2SCorentin Chary
12065d1b2eefSProgrammingkid/* Stretches video to fit host monitor size */
12075d1b2eefSProgrammingkid- (void)zoomToFit:(id) sender
12085d1b2eefSProgrammingkid{
12095d1b2eefSProgrammingkid    stretch_video = !stretch_video;
12105d1b2eefSProgrammingkid    if (stretch_video == true) {
12115e24600aSBrendan Shanks        [sender setState: NSControlStateValueOn];
12125d1b2eefSProgrammingkid    } else {
12135e24600aSBrendan Shanks        [sender setState: NSControlStateValueOff];
12145d1b2eefSProgrammingkid    }
12155d1b2eefSProgrammingkid}
12163e230dd2SCorentin Chary
1217b4c6a112SProgrammingkid/* Displays the console on the screen */
1218b4c6a112SProgrammingkid- (void)displayConsole:(id)sender
1219b4c6a112SProgrammingkid{
1220b4c6a112SProgrammingkid    console_select([sender tag]);
1221b4c6a112SProgrammingkid}
12228524f1c7SJohn Arbuckle
12238524f1c7SJohn Arbuckle/* Pause the guest */
12248524f1c7SJohn Arbuckle- (void)pauseQEMU:(id)sender
12258524f1c7SJohn Arbuckle{
122631819e95SPeter Maydell    with_iothread_lock(^{
12278524f1c7SJohn Arbuckle        qmp_stop(NULL);
122831819e95SPeter Maydell    });
12298524f1c7SJohn Arbuckle    [sender setEnabled: NO];
12308524f1c7SJohn Arbuckle    [[[sender menu] itemWithTitle: @"Resume"] setEnabled: YES];
12318524f1c7SJohn Arbuckle    [self displayPause];
12328524f1c7SJohn Arbuckle}
12338524f1c7SJohn Arbuckle
12348524f1c7SJohn Arbuckle/* Resume running the guest operating system */
12358524f1c7SJohn Arbuckle- (void)resumeQEMU:(id) sender
12368524f1c7SJohn Arbuckle{
123731819e95SPeter Maydell    with_iothread_lock(^{
12388524f1c7SJohn Arbuckle        qmp_cont(NULL);
123931819e95SPeter Maydell    });
12408524f1c7SJohn Arbuckle    [sender setEnabled: NO];
12418524f1c7SJohn Arbuckle    [[[sender menu] itemWithTitle: @"Pause"] setEnabled: YES];
12428524f1c7SJohn Arbuckle    [self removePause];
12438524f1c7SJohn Arbuckle}
12448524f1c7SJohn Arbuckle
12458524f1c7SJohn Arbuckle/* Displays the word pause on the screen */
12468524f1c7SJohn Arbuckle- (void)displayPause
12478524f1c7SJohn Arbuckle{
12488524f1c7SJohn Arbuckle    /* Coordinates have to be calculated each time because the window can change its size */
12498524f1c7SJohn Arbuckle    int xCoord, yCoord, width, height;
12508524f1c7SJohn Arbuckle    xCoord = ([normalWindow frame].size.width - [pauseLabel frame].size.width)/2;
12518524f1c7SJohn Arbuckle    yCoord = [normalWindow frame].size.height - [pauseLabel frame].size.height - ([pauseLabel frame].size.height * .5);
12528524f1c7SJohn Arbuckle    width = [pauseLabel frame].size.width;
12538524f1c7SJohn Arbuckle    height = [pauseLabel frame].size.height;
12548524f1c7SJohn Arbuckle    [pauseLabel setFrame: NSMakeRect(xCoord, yCoord, width, height)];
12558524f1c7SJohn Arbuckle    [cocoaView addSubview: pauseLabel];
12568524f1c7SJohn Arbuckle}
12578524f1c7SJohn Arbuckle
12588524f1c7SJohn Arbuckle/* Removes the word pause from the screen */
12598524f1c7SJohn Arbuckle- (void)removePause
12608524f1c7SJohn Arbuckle{
12618524f1c7SJohn Arbuckle    [pauseLabel removeFromSuperview];
12628524f1c7SJohn Arbuckle}
12638524f1c7SJohn Arbuckle
126427074614SJohn Arbuckle/* Restarts QEMU */
126527074614SJohn Arbuckle- (void)restartQEMU:(id)sender
126627074614SJohn Arbuckle{
126731819e95SPeter Maydell    with_iothread_lock(^{
126827074614SJohn Arbuckle        qmp_system_reset(NULL);
126931819e95SPeter Maydell    });
127027074614SJohn Arbuckle}
127127074614SJohn Arbuckle
127227074614SJohn Arbuckle/* Powers down QEMU */
127327074614SJohn Arbuckle- (void)powerDownQEMU:(id)sender
127427074614SJohn Arbuckle{
127531819e95SPeter Maydell    with_iothread_lock(^{
127627074614SJohn Arbuckle        qmp_system_powerdown(NULL);
127731819e95SPeter Maydell    });
127827074614SJohn Arbuckle}
127927074614SJohn Arbuckle
1280693a3e01SJohn Arbuckle/* Ejects the media.
1281693a3e01SJohn Arbuckle * Uses sender's tag to figure out the device to eject.
1282693a3e01SJohn Arbuckle */
1283693a3e01SJohn Arbuckle- (void)ejectDeviceMedia:(id)sender
1284693a3e01SJohn Arbuckle{
1285693a3e01SJohn Arbuckle    NSString * drive;
1286693a3e01SJohn Arbuckle    drive = [sender representedObject];
1287693a3e01SJohn Arbuckle    if(drive == nil) {
1288693a3e01SJohn Arbuckle        NSBeep();
1289693a3e01SJohn Arbuckle        QEMU_Alert(@"Failed to find drive to eject!");
1290693a3e01SJohn Arbuckle        return;
1291693a3e01SJohn Arbuckle    }
1292693a3e01SJohn Arbuckle
129331819e95SPeter Maydell    __block Error *err = NULL;
129431819e95SPeter Maydell    with_iothread_lock(^{
1295fbe2d816SKevin Wolf        qmp_eject(true, [drive cStringUsingEncoding: NSASCIIStringEncoding],
1296fbe2d816SKevin Wolf                  false, NULL, false, false, &err);
129731819e95SPeter Maydell    });
1298693a3e01SJohn Arbuckle    handleAnyDeviceErrors(err);
1299693a3e01SJohn Arbuckle}
1300693a3e01SJohn Arbuckle
1301693a3e01SJohn Arbuckle/* Displays a dialog box asking the user to select an image file to load.
1302693a3e01SJohn Arbuckle * Uses sender's represented object value to figure out which drive to use.
1303693a3e01SJohn Arbuckle */
1304693a3e01SJohn Arbuckle- (void)changeDeviceMedia:(id)sender
1305693a3e01SJohn Arbuckle{
1306693a3e01SJohn Arbuckle    /* Find the drive name */
1307693a3e01SJohn Arbuckle    NSString * drive;
1308693a3e01SJohn Arbuckle    drive = [sender representedObject];
1309693a3e01SJohn Arbuckle    if(drive == nil) {
1310693a3e01SJohn Arbuckle        NSBeep();
1311693a3e01SJohn Arbuckle        QEMU_Alert(@"Could not find drive!");
1312693a3e01SJohn Arbuckle        return;
1313693a3e01SJohn Arbuckle    }
1314693a3e01SJohn Arbuckle
1315693a3e01SJohn Arbuckle    /* Display the file open dialog */
1316693a3e01SJohn Arbuckle    NSOpenPanel * openPanel;
1317693a3e01SJohn Arbuckle    openPanel = [NSOpenPanel openPanel];
1318693a3e01SJohn Arbuckle    [openPanel setCanChooseFiles: YES];
1319693a3e01SJohn Arbuckle    [openPanel setAllowsMultipleSelection: NO];
1320693a3e01SJohn Arbuckle    [openPanel setAllowedFileTypes: supportedImageFileTypes];
1321b5725385SPeter Maydell    if([openPanel runModal] == NSModalResponseOK) {
1322693a3e01SJohn Arbuckle        NSString * file = [[[openPanel URLs] objectAtIndex: 0] path];
1323693a3e01SJohn Arbuckle        if(file == nil) {
1324693a3e01SJohn Arbuckle            NSBeep();
1325693a3e01SJohn Arbuckle            QEMU_Alert(@"Failed to convert URL to file path!");
1326693a3e01SJohn Arbuckle            return;
1327693a3e01SJohn Arbuckle        }
1328693a3e01SJohn Arbuckle
132931819e95SPeter Maydell        __block Error *err = NULL;
133031819e95SPeter Maydell        with_iothread_lock(^{
133170e2cb3bSKevin Wolf            qmp_blockdev_change_medium(true,
133270e2cb3bSKevin Wolf                                       [drive cStringUsingEncoding:
133324fb4133SMax Reitz                                                  NSASCIIStringEncoding],
133470e2cb3bSKevin Wolf                                       false, NULL,
133524fb4133SMax Reitz                                       [file cStringUsingEncoding:
133624fb4133SMax Reitz                                                 NSASCIIStringEncoding],
133724fb4133SMax Reitz                                       true, "raw",
133839ff43d9SMax Reitz                                       false, 0,
1339693a3e01SJohn Arbuckle                                       &err);
134031819e95SPeter Maydell        });
1341693a3e01SJohn Arbuckle        handleAnyDeviceErrors(err);
1342693a3e01SJohn Arbuckle    }
1343693a3e01SJohn Arbuckle}
1344693a3e01SJohn Arbuckle
1345d9bc14f6SJohn Arbuckle/* Verifies if the user really wants to quit */
1346d9bc14f6SJohn Arbuckle- (BOOL)verifyQuit
1347d9bc14f6SJohn Arbuckle{
1348d9bc14f6SJohn Arbuckle    NSAlert *alert = [NSAlert new];
1349d9bc14f6SJohn Arbuckle    [alert autorelease];
1350d9bc14f6SJohn Arbuckle    [alert setMessageText: @"Are you sure you want to quit QEMU?"];
1351d9bc14f6SJohn Arbuckle    [alert addButtonWithTitle: @"Cancel"];
1352d9bc14f6SJohn Arbuckle    [alert addButtonWithTitle: @"Quit"];
1353d9bc14f6SJohn Arbuckle    if([alert runModal] == NSAlertSecondButtonReturn) {
1354d9bc14f6SJohn Arbuckle        return YES;
1355d9bc14f6SJohn Arbuckle    } else {
1356d9bc14f6SJohn Arbuckle        return NO;
1357d9bc14f6SJohn Arbuckle    }
1358d9bc14f6SJohn Arbuckle}
1359d9bc14f6SJohn Arbuckle
13609e8204b1SProgrammingkid/* The action method for the About menu item */
13619e8204b1SProgrammingkid- (IBAction) do_about_menu_item: (id) sender
13629e8204b1SProgrammingkid{
13639e8204b1SProgrammingkid    [about_window makeKeyAndOrderFront: nil];
13649e8204b1SProgrammingkid}
13659e8204b1SProgrammingkid
13669e8204b1SProgrammingkid/* Create and display the about dialog */
13679e8204b1SProgrammingkid- (void)make_about_window
13689e8204b1SProgrammingkid{
13699e8204b1SProgrammingkid    /* Make the window */
13709e8204b1SProgrammingkid    int x = 0, y = 0, about_width = 400, about_height = 200;
13719e8204b1SProgrammingkid    NSRect window_rect = NSMakeRect(x, y, about_width, about_height);
13729e8204b1SProgrammingkid    about_window = [[NSWindow alloc] initWithContentRect:window_rect
13734ba967adSBrendan Shanks                    styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable |
13744ba967adSBrendan Shanks                    NSWindowStyleMaskMiniaturizable
13759e8204b1SProgrammingkid                    backing:NSBackingStoreBuffered
13769e8204b1SProgrammingkid                    defer:NO];
13779e8204b1SProgrammingkid    [about_window setTitle: @"About"];
13789e8204b1SProgrammingkid    [about_window setReleasedWhenClosed: NO];
13799e8204b1SProgrammingkid    [about_window center];
13809e8204b1SProgrammingkid    NSView *superView = [about_window contentView];
13819e8204b1SProgrammingkid
13829e8204b1SProgrammingkid    /* Create the dimensions of the picture */
13839e8204b1SProgrammingkid    int picture_width = 80, picture_height = 80;
13849e8204b1SProgrammingkid    x = (about_width - picture_width)/2;
13859e8204b1SProgrammingkid    y = about_height - picture_height - 10;
13869e8204b1SProgrammingkid    NSRect picture_rect = NSMakeRect(x, y, picture_width, picture_height);
13879e8204b1SProgrammingkid
13889e8204b1SProgrammingkid    /* Get the path to the QEMU binary */
13899e8204b1SProgrammingkid    NSString *binary_name = [NSString stringWithCString: gArgv[0]
13909e8204b1SProgrammingkid                                      encoding: NSASCIIStringEncoding];
13919e8204b1SProgrammingkid    binary_name = [binary_name lastPathComponent];
13929e8204b1SProgrammingkid    NSString *program_path = [[NSString alloc] initWithFormat: @"%@/%@",
13939e8204b1SProgrammingkid    [[NSBundle mainBundle] bundlePath], binary_name];
13949e8204b1SProgrammingkid
13959e8204b1SProgrammingkid    /* Make the picture of QEMU */
13969e8204b1SProgrammingkid    NSImageView *picture_view = [[NSImageView alloc] initWithFrame:
13979e8204b1SProgrammingkid                                                     picture_rect];
13989e8204b1SProgrammingkid    NSImage *qemu_image = [[NSWorkspace sharedWorkspace] iconForFile:
13999e8204b1SProgrammingkid                                                         program_path];
14009e8204b1SProgrammingkid    [picture_view setImage: qemu_image];
14019e8204b1SProgrammingkid    [picture_view setImageScaling: NSImageScaleProportionallyUpOrDown];
14029e8204b1SProgrammingkid    [superView addSubview: picture_view];
14039e8204b1SProgrammingkid
14049e8204b1SProgrammingkid    /* Make the name label */
14059e8204b1SProgrammingkid    x = 0;
14069e8204b1SProgrammingkid    y = y - 25;
14079e8204b1SProgrammingkid    int name_width = about_width, name_height = 20;
14089e8204b1SProgrammingkid    NSRect name_rect = NSMakeRect(x, y, name_width, name_height);
14099e8204b1SProgrammingkid    NSTextField *name_label = [[NSTextField alloc] initWithFrame: name_rect];
14109e8204b1SProgrammingkid    [name_label setEditable: NO];
14119e8204b1SProgrammingkid    [name_label setBezeled: NO];
14129e8204b1SProgrammingkid    [name_label setDrawsBackground: NO];
14134ba967adSBrendan Shanks    [name_label setAlignment: NSTextAlignmentCenter];
14149e8204b1SProgrammingkid    NSString *qemu_name = [[NSString alloc] initWithCString: gArgv[0]
14159e8204b1SProgrammingkid                                            encoding: NSASCIIStringEncoding];
14169e8204b1SProgrammingkid    qemu_name = [qemu_name lastPathComponent];
14179e8204b1SProgrammingkid    [name_label setStringValue: qemu_name];
14189e8204b1SProgrammingkid    [superView addSubview: name_label];
14199e8204b1SProgrammingkid
14209e8204b1SProgrammingkid    /* Set the version label's attributes */
14219e8204b1SProgrammingkid    x = 0;
14229e8204b1SProgrammingkid    y = 50;
14239e8204b1SProgrammingkid    int version_width = about_width, version_height = 20;
14249e8204b1SProgrammingkid    NSRect version_rect = NSMakeRect(x, y, version_width, version_height);
14259e8204b1SProgrammingkid    NSTextField *version_label = [[NSTextField alloc] initWithFrame:
14269e8204b1SProgrammingkid                                                      version_rect];
14279e8204b1SProgrammingkid    [version_label setEditable: NO];
14289e8204b1SProgrammingkid    [version_label setBezeled: NO];
14294ba967adSBrendan Shanks    [version_label setAlignment: NSTextAlignmentCenter];
14309e8204b1SProgrammingkid    [version_label setDrawsBackground: NO];
14319e8204b1SProgrammingkid
14329e8204b1SProgrammingkid    /* Create the version string*/
14339e8204b1SProgrammingkid    NSString *version_string;
14349e8204b1SProgrammingkid    version_string = [[NSString alloc] initWithFormat:
14357e563bfbSThomas Huth    @"QEMU emulator version %s", QEMU_FULL_VERSION];
14369e8204b1SProgrammingkid    [version_label setStringValue: version_string];
14379e8204b1SProgrammingkid    [superView addSubview: version_label];
14389e8204b1SProgrammingkid
14399e8204b1SProgrammingkid    /* Make copyright label */
14409e8204b1SProgrammingkid    x = 0;
14419e8204b1SProgrammingkid    y = 35;
14429e8204b1SProgrammingkid    int copyright_width = about_width, copyright_height = 20;
14439e8204b1SProgrammingkid    NSRect copyright_rect = NSMakeRect(x, y, copyright_width, copyright_height);
14449e8204b1SProgrammingkid    NSTextField *copyright_label = [[NSTextField alloc] initWithFrame:
14459e8204b1SProgrammingkid                                                        copyright_rect];
14469e8204b1SProgrammingkid    [copyright_label setEditable: NO];
14479e8204b1SProgrammingkid    [copyright_label setBezeled: NO];
14489e8204b1SProgrammingkid    [copyright_label setDrawsBackground: NO];
14494ba967adSBrendan Shanks    [copyright_label setAlignment: NSTextAlignmentCenter];
14509e8204b1SProgrammingkid    [copyright_label setStringValue: [NSString stringWithFormat: @"%s",
14519e8204b1SProgrammingkid                                     QEMU_COPYRIGHT]];
14529e8204b1SProgrammingkid    [superView addSubview: copyright_label];
14539e8204b1SProgrammingkid}
14549e8204b1SProgrammingkid
1455e47ec1a9SJohn Arbuckle/* Used by the Speed menu items */
1456e47ec1a9SJohn Arbuckle- (void)adjustSpeed:(id)sender
1457e47ec1a9SJohn Arbuckle{
1458e47ec1a9SJohn Arbuckle    int throttle_pct; /* throttle percentage */
1459e47ec1a9SJohn Arbuckle    NSMenu *menu;
1460e47ec1a9SJohn Arbuckle
1461e47ec1a9SJohn Arbuckle    menu = [sender menu];
1462e47ec1a9SJohn Arbuckle    if (menu != nil)
1463e47ec1a9SJohn Arbuckle    {
1464e47ec1a9SJohn Arbuckle        /* Unselect the currently selected item */
1465e47ec1a9SJohn Arbuckle        for (NSMenuItem *item in [menu itemArray]) {
14665e24600aSBrendan Shanks            if (item.state == NSControlStateValueOn) {
14675e24600aSBrendan Shanks                [item setState: NSControlStateValueOff];
1468e47ec1a9SJohn Arbuckle                break;
1469e47ec1a9SJohn Arbuckle            }
1470e47ec1a9SJohn Arbuckle        }
1471e47ec1a9SJohn Arbuckle    }
1472e47ec1a9SJohn Arbuckle
1473e47ec1a9SJohn Arbuckle    // check the menu item
14745e24600aSBrendan Shanks    [sender setState: NSControlStateValueOn];
1475e47ec1a9SJohn Arbuckle
1476e47ec1a9SJohn Arbuckle    // get the throttle percentage
1477e47ec1a9SJohn Arbuckle    throttle_pct = [sender tag];
1478e47ec1a9SJohn Arbuckle
147931819e95SPeter Maydell    with_iothread_lock(^{
1480e47ec1a9SJohn Arbuckle        cpu_throttle_set(throttle_pct);
148131819e95SPeter Maydell    });
1482e47ec1a9SJohn Arbuckle    COCOA_DEBUG("cpu throttling at %d%c\n", cpu_throttle_get_percentage(), '%');
1483e47ec1a9SJohn Arbuckle}
1484e47ec1a9SJohn Arbuckle
1485b4c6a112SProgrammingkid@end
14863e230dd2SCorentin Chary
148761a2ed44SPeter Maydell@interface QemuApplication : NSApplication
148861a2ed44SPeter Maydell@end
148961a2ed44SPeter Maydell
149061a2ed44SPeter Maydell@implementation QemuApplication
149161a2ed44SPeter Maydell- (void)sendEvent:(NSEvent *)event
149261a2ed44SPeter Maydell{
149361a2ed44SPeter Maydell    COCOA_DEBUG("QemuApplication: sendEvent\n");
14945588840fSPeter Maydell    if (![cocoaView handleEvent:event]) {
149561a2ed44SPeter Maydell        [super sendEvent: event];
149661a2ed44SPeter Maydell    }
14975588840fSPeter Maydell}
149861a2ed44SPeter Maydell@end
149961a2ed44SPeter Maydell
1500c6fd6c70SPeter Maydellstatic void create_initial_menus(void)
1501c6fd6c70SPeter Maydell{
15023e230dd2SCorentin Chary    // Add menus
15033e230dd2SCorentin Chary    NSMenu      *menu;
15043e230dd2SCorentin Chary    NSMenuItem  *menuItem;
15053e230dd2SCorentin Chary
15063e230dd2SCorentin Chary    [NSApp setMainMenu:[[NSMenu alloc] init]];
15073e230dd2SCorentin Chary
15083e230dd2SCorentin Chary    // Application menu
15093e230dd2SCorentin Chary    menu = [[NSMenu alloc] initWithTitle:@""];
15109e8204b1SProgrammingkid    [menu addItemWithTitle:@"About QEMU" action:@selector(do_about_menu_item:) keyEquivalent:@""]; // About QEMU
15113e230dd2SCorentin Chary    [menu addItem:[NSMenuItem separatorItem]]; //Separator
15123e230dd2SCorentin Chary    [menu addItemWithTitle:@"Hide QEMU" action:@selector(hide:) keyEquivalent:@"h"]; //Hide QEMU
15133e230dd2SCorentin Chary    menuItem = (NSMenuItem *)[menu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; // Hide Others
15144ba967adSBrendan Shanks    [menuItem setKeyEquivalentModifierMask:(NSEventModifierFlagOption|NSEventModifierFlagCommand)];
15153e230dd2SCorentin Chary    [menu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; // Show All
15163e230dd2SCorentin Chary    [menu addItem:[NSMenuItem separatorItem]]; //Separator
15173e230dd2SCorentin Chary    [menu addItemWithTitle:@"Quit QEMU" action:@selector(terminate:) keyEquivalent:@"q"];
15183e230dd2SCorentin Chary    menuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" action:nil keyEquivalent:@""];
15193e230dd2SCorentin Chary    [menuItem setSubmenu:menu];
15203e230dd2SCorentin Chary    [[NSApp mainMenu] addItem:menuItem];
15213e230dd2SCorentin Chary    [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; // Workaround (this method is private since 10.4+)
15223e230dd2SCorentin Chary
15238524f1c7SJohn Arbuckle    // Machine menu
15248524f1c7SJohn Arbuckle    menu = [[NSMenu alloc] initWithTitle: @"Machine"];
15258524f1c7SJohn Arbuckle    [menu setAutoenablesItems: NO];
15268524f1c7SJohn Arbuckle    [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Pause" action: @selector(pauseQEMU:) keyEquivalent: @""] autorelease]];
15278524f1c7SJohn Arbuckle    menuItem = [[[NSMenuItem alloc] initWithTitle: @"Resume" action: @selector(resumeQEMU:) keyEquivalent: @""] autorelease];
15288524f1c7SJohn Arbuckle    [menu addItem: menuItem];
15298524f1c7SJohn Arbuckle    [menuItem setEnabled: NO];
153027074614SJohn Arbuckle    [menu addItem: [NSMenuItem separatorItem]];
153127074614SJohn Arbuckle    [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Reset" action: @selector(restartQEMU:) keyEquivalent: @""] autorelease]];
153227074614SJohn Arbuckle    [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Power Down" action: @selector(powerDownQEMU:) keyEquivalent: @""] autorelease]];
15338524f1c7SJohn Arbuckle    menuItem = [[[NSMenuItem alloc] initWithTitle: @"Machine" action:nil keyEquivalent:@""] autorelease];
15348524f1c7SJohn Arbuckle    [menuItem setSubmenu:menu];
15358524f1c7SJohn Arbuckle    [[NSApp mainMenu] addItem:menuItem];
15368524f1c7SJohn Arbuckle
15373e230dd2SCorentin Chary    // View menu
15383e230dd2SCorentin Chary    menu = [[NSMenu alloc] initWithTitle:@"View"];
15395d1b2eefSProgrammingkid    [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(doToggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen
15405d1b2eefSProgrammingkid    [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Zoom To Fit" action:@selector(zoomToFit:) keyEquivalent:@""] autorelease]];
15413e230dd2SCorentin Chary    menuItem = [[[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""] autorelease];
15423e230dd2SCorentin Chary    [menuItem setSubmenu:menu];
15433e230dd2SCorentin Chary    [[NSApp mainMenu] addItem:menuItem];
15443e230dd2SCorentin Chary
1545e47ec1a9SJohn Arbuckle    // Speed menu
1546e47ec1a9SJohn Arbuckle    menu = [[NSMenu alloc] initWithTitle:@"Speed"];
1547e47ec1a9SJohn Arbuckle
1548e47ec1a9SJohn Arbuckle    // Add the rest of the Speed menu items
1549e47ec1a9SJohn Arbuckle    int p, percentage, throttle_pct;
1550e47ec1a9SJohn Arbuckle    for (p = 10; p >= 0; p--)
1551e47ec1a9SJohn Arbuckle    {
1552e47ec1a9SJohn Arbuckle        percentage = p * 10 > 1 ? p * 10 : 1; // prevent a 0% menu item
1553e47ec1a9SJohn Arbuckle
1554e47ec1a9SJohn Arbuckle        menuItem = [[[NSMenuItem alloc]
1555e47ec1a9SJohn Arbuckle                   initWithTitle: [NSString stringWithFormat: @"%d%%", percentage] action:@selector(adjustSpeed:) keyEquivalent:@""] autorelease];
1556e47ec1a9SJohn Arbuckle
1557e47ec1a9SJohn Arbuckle        if (percentage == 100) {
15585e24600aSBrendan Shanks            [menuItem setState: NSControlStateValueOn];
1559e47ec1a9SJohn Arbuckle        }
1560e47ec1a9SJohn Arbuckle
1561e47ec1a9SJohn Arbuckle        /* Calculate the throttle percentage */
1562e47ec1a9SJohn Arbuckle        throttle_pct = -1 * percentage + 100;
1563e47ec1a9SJohn Arbuckle
1564e47ec1a9SJohn Arbuckle        [menuItem setTag: throttle_pct];
1565e47ec1a9SJohn Arbuckle        [menu addItem: menuItem];
1566e47ec1a9SJohn Arbuckle    }
1567e47ec1a9SJohn Arbuckle    menuItem = [[[NSMenuItem alloc] initWithTitle:@"Speed" action:nil keyEquivalent:@""] autorelease];
1568e47ec1a9SJohn Arbuckle    [menuItem setSubmenu:menu];
1569e47ec1a9SJohn Arbuckle    [[NSApp mainMenu] addItem:menuItem];
1570e47ec1a9SJohn Arbuckle
15713e230dd2SCorentin Chary    // Window menu
15723e230dd2SCorentin Chary    menu = [[NSMenu alloc] initWithTitle:@"Window"];
15733e230dd2SCorentin Chary    [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease]]; // Miniaturize
15743e230dd2SCorentin Chary    menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease];
15753e230dd2SCorentin Chary    [menuItem setSubmenu:menu];
15763e230dd2SCorentin Chary    [[NSApp mainMenu] addItem:menuItem];
15773e230dd2SCorentin Chary    [NSApp setWindowsMenu:menu];
15783e230dd2SCorentin Chary
15793e230dd2SCorentin Chary    // Help menu
15803e230dd2SCorentin Chary    menu = [[NSMenu alloc] initWithTitle:@"Help"];
15813e230dd2SCorentin Chary    [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Documentation" action:@selector(showQEMUDoc:) keyEquivalent:@"?"] autorelease]]; // QEMU Help
15823e230dd2SCorentin Chary    menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease];
15833e230dd2SCorentin Chary    [menuItem setSubmenu:menu];
15843e230dd2SCorentin Chary    [[NSApp mainMenu] addItem:menuItem];
1585c6fd6c70SPeter Maydell}
1586c6fd6c70SPeter Maydell
15878b00e4e7SPeter Maydell/* Returns a name for a given console */
15888b00e4e7SPeter Maydellstatic NSString * getConsoleName(QemuConsole * console)
15898b00e4e7SPeter Maydell{
15908b00e4e7SPeter Maydell    return [NSString stringWithFormat: @"%s", qemu_console_get_label(console)];
15918b00e4e7SPeter Maydell}
15928b00e4e7SPeter Maydell
15938b00e4e7SPeter Maydell/* Add an entry to the View menu for each console */
15948b00e4e7SPeter Maydellstatic void add_console_menu_entries(void)
15958b00e4e7SPeter Maydell{
15968b00e4e7SPeter Maydell    NSMenu *menu;
15978b00e4e7SPeter Maydell    NSMenuItem *menuItem;
15988b00e4e7SPeter Maydell    int index = 0;
15998b00e4e7SPeter Maydell
16008b00e4e7SPeter Maydell    menu = [[[NSApp mainMenu] itemWithTitle:@"View"] submenu];
16018b00e4e7SPeter Maydell
16028b00e4e7SPeter Maydell    [menu addItem:[NSMenuItem separatorItem]];
16038b00e4e7SPeter Maydell
16048b00e4e7SPeter Maydell    while (qemu_console_lookup_by_index(index) != NULL) {
16058b00e4e7SPeter Maydell        menuItem = [[[NSMenuItem alloc] initWithTitle: getConsoleName(qemu_console_lookup_by_index(index))
16068b00e4e7SPeter Maydell                                               action: @selector(displayConsole:) keyEquivalent: @""] autorelease];
16078b00e4e7SPeter Maydell        [menuItem setTag: index];
16088b00e4e7SPeter Maydell        [menu addItem: menuItem];
16098b00e4e7SPeter Maydell        index++;
16108b00e4e7SPeter Maydell    }
16118b00e4e7SPeter Maydell}
16128b00e4e7SPeter Maydell
16138b00e4e7SPeter Maydell/* Make menu items for all removable devices.
16148b00e4e7SPeter Maydell * Each device is given an 'Eject' and 'Change' menu item.
16158b00e4e7SPeter Maydell */
16168b00e4e7SPeter Maydellstatic void addRemovableDevicesMenuItems(void)
16178b00e4e7SPeter Maydell{
16188b00e4e7SPeter Maydell    NSMenu *menu;
16198b00e4e7SPeter Maydell    NSMenuItem *menuItem;
16208b00e4e7SPeter Maydell    BlockInfoList *currentDevice, *pointerToFree;
16218b00e4e7SPeter Maydell    NSString *deviceName;
16228b00e4e7SPeter Maydell
16238b00e4e7SPeter Maydell    currentDevice = qmp_query_block(NULL);
16248b00e4e7SPeter Maydell    pointerToFree = currentDevice;
16258b00e4e7SPeter Maydell    if(currentDevice == NULL) {
16268b00e4e7SPeter Maydell        NSBeep();
16278b00e4e7SPeter Maydell        QEMU_Alert(@"Failed to query for block devices!");
16288b00e4e7SPeter Maydell        return;
16298b00e4e7SPeter Maydell    }
16308b00e4e7SPeter Maydell
16318b00e4e7SPeter Maydell    menu = [[[NSApp mainMenu] itemWithTitle:@"Machine"] submenu];
16328b00e4e7SPeter Maydell
16338b00e4e7SPeter Maydell    // Add a separator between related groups of menu items
16348b00e4e7SPeter Maydell    [menu addItem:[NSMenuItem separatorItem]];
16358b00e4e7SPeter Maydell
16368b00e4e7SPeter Maydell    // Set the attributes to the "Removable Media" menu item
16378b00e4e7SPeter Maydell    NSString *titleString = @"Removable Media";
16388b00e4e7SPeter Maydell    NSMutableAttributedString *attString=[[NSMutableAttributedString alloc] initWithString:titleString];
16398b00e4e7SPeter Maydell    NSColor *newColor = [NSColor blackColor];
16408b00e4e7SPeter Maydell    NSFontManager *fontManager = [NSFontManager sharedFontManager];
16418b00e4e7SPeter Maydell    NSFont *font = [fontManager fontWithFamily:@"Helvetica"
16428b00e4e7SPeter Maydell                                          traits:NSBoldFontMask|NSItalicFontMask
16438b00e4e7SPeter Maydell                                          weight:0
16448b00e4e7SPeter Maydell                                            size:14];
16458b00e4e7SPeter Maydell    [attString addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, [titleString length])];
16468b00e4e7SPeter Maydell    [attString addAttribute:NSForegroundColorAttributeName value:newColor range:NSMakeRange(0, [titleString length])];
16478b00e4e7SPeter Maydell    [attString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInt: 1] range:NSMakeRange(0, [titleString length])];
16488b00e4e7SPeter Maydell
16498b00e4e7SPeter Maydell    // Add the "Removable Media" menu item
16508b00e4e7SPeter Maydell    menuItem = [NSMenuItem new];
16518b00e4e7SPeter Maydell    [menuItem setAttributedTitle: attString];
16528b00e4e7SPeter Maydell    [menuItem setEnabled: NO];
16538b00e4e7SPeter Maydell    [menu addItem: menuItem];
16548b00e4e7SPeter Maydell
16558b00e4e7SPeter Maydell    /* Loop through all the block devices in the emulator */
16568b00e4e7SPeter Maydell    while (currentDevice) {
16578b00e4e7SPeter Maydell        deviceName = [[NSString stringWithFormat: @"%s", currentDevice->value->device] retain];
16588b00e4e7SPeter Maydell
16598b00e4e7SPeter Maydell        if(currentDevice->value->removable) {
16608b00e4e7SPeter Maydell            menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Change %s...", currentDevice->value->device]
16618b00e4e7SPeter Maydell                                                  action: @selector(changeDeviceMedia:)
16628b00e4e7SPeter Maydell                                           keyEquivalent: @""];
16638b00e4e7SPeter Maydell            [menu addItem: menuItem];
16648b00e4e7SPeter Maydell            [menuItem setRepresentedObject: deviceName];
16658b00e4e7SPeter Maydell            [menuItem autorelease];
16668b00e4e7SPeter Maydell
16678b00e4e7SPeter Maydell            menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Eject %s", currentDevice->value->device]
16688b00e4e7SPeter Maydell                                                  action: @selector(ejectDeviceMedia:)
16698b00e4e7SPeter Maydell                                           keyEquivalent: @""];
16708b00e4e7SPeter Maydell            [menu addItem: menuItem];
16718b00e4e7SPeter Maydell            [menuItem setRepresentedObject: deviceName];
16728b00e4e7SPeter Maydell            [menuItem autorelease];
16738b00e4e7SPeter Maydell        }
16748b00e4e7SPeter Maydell        currentDevice = currentDevice->next;
16758b00e4e7SPeter Maydell    }
16768b00e4e7SPeter Maydell    qapi_free_BlockInfoList(pointerToFree);
16778b00e4e7SPeter Maydell}
16788b00e4e7SPeter Maydell
16795588840fSPeter Maydell/*
16805588840fSPeter Maydell * The startup process for the OSX/Cocoa UI is complicated, because
16815588840fSPeter Maydell * OSX insists that the UI runs on the initial main thread, and so we
16825588840fSPeter Maydell * need to start a second thread which runs the vl.c qemu_main():
16835588840fSPeter Maydell *
16845588840fSPeter Maydell * Initial thread:                    2nd thread:
16855588840fSPeter Maydell * in main():
16865588840fSPeter Maydell *  create qemu-main thread
16875588840fSPeter Maydell *  wait on display_init semaphore
16885588840fSPeter Maydell *                                    call qemu_main()
16895588840fSPeter Maydell *                                    ...
16905588840fSPeter Maydell *                                    in cocoa_display_init():
16915588840fSPeter Maydell *                                     post the display_init semaphore
16925588840fSPeter Maydell *                                     wait on app_started semaphore
16935588840fSPeter Maydell *  create application, menus, etc
16945588840fSPeter Maydell *  enter OSX run loop
16955588840fSPeter Maydell * in applicationDidFinishLaunching:
16965588840fSPeter Maydell *  post app_started semaphore
16975588840fSPeter Maydell *                                     tell main thread to fullscreen if needed
16985588840fSPeter Maydell *                                    [...]
16995588840fSPeter Maydell *                                    run qemu main-loop
17005588840fSPeter Maydell *
17015588840fSPeter Maydell * We do this in two stages so that we don't do the creation of the
17025588840fSPeter Maydell * GUI application menus and so on for command line options like --help
17035588840fSPeter Maydell * where we want to just print text to stdout and exit immediately.
17045588840fSPeter Maydell */
1705c6fd6c70SPeter Maydell
17065588840fSPeter Maydellstatic void *call_qemu_main(void *opaque)
17075588840fSPeter Maydell{
17085588840fSPeter Maydell    int status;
17095588840fSPeter Maydell
17105588840fSPeter Maydell    COCOA_DEBUG("Second thread: calling qemu_main()\n");
17115588840fSPeter Maydell    status = qemu_main(gArgc, gArgv, *_NSGetEnviron());
17125588840fSPeter Maydell    COCOA_DEBUG("Second thread: qemu_main() returned, exiting\n");
17135588840fSPeter Maydell    exit(status);
17145588840fSPeter Maydell}
17155588840fSPeter Maydell
17165588840fSPeter Maydellint main (int argc, const char * argv[]) {
17175588840fSPeter Maydell    QemuThread thread;
17185588840fSPeter Maydell
17195588840fSPeter Maydell    COCOA_DEBUG("Entered main()\n");
1720c6fd6c70SPeter Maydell    gArgc = argc;
1721c6fd6c70SPeter Maydell    gArgv = (char **)argv;
1722c6fd6c70SPeter Maydell
17235588840fSPeter Maydell    qemu_sem_init(&display_init_sem, 0);
17245588840fSPeter Maydell    qemu_sem_init(&app_started_sem, 0);
1725c6fd6c70SPeter Maydell
17265588840fSPeter Maydell    qemu_thread_create(&thread, "qemu_main", call_qemu_main,
17275588840fSPeter Maydell                       NULL, QEMU_THREAD_DETACHED);
17285588840fSPeter Maydell
17295588840fSPeter Maydell    COCOA_DEBUG("Main thread: waiting for display_init_sem\n");
17305588840fSPeter Maydell    qemu_sem_wait(&display_init_sem);
17315588840fSPeter Maydell    COCOA_DEBUG("Main thread: initializing app\n");
1732c6fd6c70SPeter Maydell
1733c6fd6c70SPeter Maydell    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
1734c6fd6c70SPeter Maydell
1735c6fd6c70SPeter Maydell    // Pull this console process up to being a fully-fledged graphical
1736c6fd6c70SPeter Maydell    // app with a menubar and Dock icon
1737c6fd6c70SPeter Maydell    ProcessSerialNumber psn = { 0, kCurrentProcess };
1738c6fd6c70SPeter Maydell    TransformProcessType(&psn, kProcessTransformToForegroundApplication);
1739c6fd6c70SPeter Maydell
174061a2ed44SPeter Maydell    [QemuApplication sharedApplication];
1741c6fd6c70SPeter Maydell
1742c6fd6c70SPeter Maydell    create_initial_menus();
17433e230dd2SCorentin Chary
17445588840fSPeter Maydell    /*
17455588840fSPeter Maydell     * Create the menu entries which depend on QEMU state (for consoles
17465588840fSPeter Maydell     * and removeable devices). These make calls back into QEMU functions,
17475588840fSPeter Maydell     * which is OK because at this point we know that the second thread
17485588840fSPeter Maydell     * holds the iothread lock and is synchronously waiting for us to
17495588840fSPeter Maydell     * finish.
17505588840fSPeter Maydell     */
17515588840fSPeter Maydell    add_console_menu_entries();
17525588840fSPeter Maydell    addRemovableDevicesMenuItems();
17535588840fSPeter Maydell
17543e230dd2SCorentin Chary    // Create an Application controller
17553e230dd2SCorentin Chary    QemuCocoaAppController *appController = [[QemuCocoaAppController alloc] init];
17563e230dd2SCorentin Chary    [NSApp setDelegate:appController];
17573e230dd2SCorentin Chary
17583e230dd2SCorentin Chary    // Start the main event loop
17595588840fSPeter Maydell    COCOA_DEBUG("Main thread: entering OSX run loop\n");
17603e230dd2SCorentin Chary    [NSApp run];
17615588840fSPeter Maydell    COCOA_DEBUG("Main thread: left OSX run loop, exiting\n");
17623e230dd2SCorentin Chary
17633e230dd2SCorentin Chary    [appController release];
17643e230dd2SCorentin Chary    [pool release];
17653e230dd2SCorentin Chary
17663e230dd2SCorentin Chary    return 0;
17673e230dd2SCorentin Chary}
17683e230dd2SCorentin Chary
17693e230dd2SCorentin Chary
17703e230dd2SCorentin Chary
17713e230dd2SCorentin Chary#pragma mark qemu
17727c20b4a3SGerd Hoffmannstatic void cocoa_update(DisplayChangeListener *dcl,
17737c20b4a3SGerd Hoffmann                         int x, int y, int w, int h)
17743e230dd2SCorentin Chary{
17756e657e64SPeter Maydell    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
17766e657e64SPeter Maydell
17773e230dd2SCorentin Chary    COCOA_DEBUG("qemu_cocoa: cocoa_update\n");
17783e230dd2SCorentin Chary
17795588840fSPeter Maydell    dispatch_async(dispatch_get_main_queue(), ^{
17803e230dd2SCorentin Chary        NSRect rect;
17813e230dd2SCorentin Chary        if ([cocoaView cdx] == 1.0) {
17823e230dd2SCorentin Chary            rect = NSMakeRect(x, [cocoaView gscreen].height - y - h, w, h);
17833e230dd2SCorentin Chary        } else {
17843e230dd2SCorentin Chary            rect = NSMakeRect(
17853e230dd2SCorentin Chary                x * [cocoaView cdx],
17863e230dd2SCorentin Chary                ([cocoaView gscreen].height - y - h) * [cocoaView cdy],
17873e230dd2SCorentin Chary                w * [cocoaView cdx],
17883e230dd2SCorentin Chary                h * [cocoaView cdy]);
17893e230dd2SCorentin Chary        }
17903e230dd2SCorentin Chary        [cocoaView setNeedsDisplayInRect:rect];
17915588840fSPeter Maydell    });
17926e657e64SPeter Maydell
17936e657e64SPeter Maydell    [pool release];
17943e230dd2SCorentin Chary}
17953e230dd2SCorentin Chary
1796c12aeb86SGerd Hoffmannstatic void cocoa_switch(DisplayChangeListener *dcl,
1797c12aeb86SGerd Hoffmann                         DisplaySurface *surface)
17983e230dd2SCorentin Chary{
17996e657e64SPeter Maydell    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
18005588840fSPeter Maydell    pixman_image_t *image = surface->image;
18013e230dd2SCorentin Chary
18026e657e64SPeter Maydell    COCOA_DEBUG("qemu_cocoa: cocoa_switch\n");
18035588840fSPeter Maydell
18045588840fSPeter Maydell    // The DisplaySurface will be freed as soon as this callback returns.
18055588840fSPeter Maydell    // We take a reference to the underlying pixman image here so it does
18065588840fSPeter Maydell    // not disappear from under our feet; the switchSurface method will
18075588840fSPeter Maydell    // deref the old image when it is done with it.
18085588840fSPeter Maydell    pixman_image_ref(image);
18095588840fSPeter Maydell
18105588840fSPeter Maydell    dispatch_async(dispatch_get_main_queue(), ^{
18115588840fSPeter Maydell        [cocoaView switchSurface:image];
18125588840fSPeter Maydell    });
18136e657e64SPeter Maydell    [pool release];
18143e230dd2SCorentin Chary}
18153e230dd2SCorentin Chary
1816bc2ed970SGerd Hoffmannstatic void cocoa_refresh(DisplayChangeListener *dcl)
18173e230dd2SCorentin Chary{
18186e657e64SPeter Maydell    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
18196e657e64SPeter Maydell
18203e230dd2SCorentin Chary    COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n");
1821468a895bSJohn Arbuckle    graphic_hw_update(NULL);
18223e230dd2SCorentin Chary
182321bae11aSGerd Hoffmann    if (qemu_input_is_absolute()) {
18245588840fSPeter Maydell        dispatch_async(dispatch_get_main_queue(), ^{
18253e230dd2SCorentin Chary            if (![cocoaView isAbsoluteEnabled]) {
182649b9bd4dSPeter Maydell                if ([cocoaView isMouseGrabbed]) {
18273e230dd2SCorentin Chary                    [cocoaView ungrabMouse];
18283e230dd2SCorentin Chary                }
18293e230dd2SCorentin Chary            }
18303e230dd2SCorentin Chary            [cocoaView setAbsoluteEnabled:YES];
18315588840fSPeter Maydell        });
18323e230dd2SCorentin Chary    }
18336e657e64SPeter Maydell    [pool release];
18343e230dd2SCorentin Chary}
18353e230dd2SCorentin Chary
18365013b9e4SGerd Hoffmannstatic void cocoa_display_init(DisplayState *ds, DisplayOptions *opts)
18373e230dd2SCorentin Chary{
18383e230dd2SCorentin Chary    COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n");
18393e230dd2SCorentin Chary
18405588840fSPeter Maydell    /* Tell main thread to go ahead and create the app and enter the run loop */
18415588840fSPeter Maydell    qemu_sem_post(&display_init_sem);
18425588840fSPeter Maydell    qemu_sem_wait(&app_started_sem);
18435588840fSPeter Maydell    COCOA_DEBUG("cocoa_display_init: app start completed\n");
18445588840fSPeter Maydell
184543227af8SProgrammingkid    /* if fullscreen mode is to be used */
1846767f9bf3SGerd Hoffmann    if (opts->has_full_screen && opts->full_screen) {
18475588840fSPeter Maydell        dispatch_async(dispatch_get_main_queue(), ^{
184843227af8SProgrammingkid            [NSApp activateIgnoringOtherApps: YES];
184943227af8SProgrammingkid            [(QemuCocoaAppController *)[[NSApplication sharedApplication] delegate] toggleFullScreen: nil];
18505588840fSPeter Maydell        });
185143227af8SProgrammingkid    }
18523487da6aSGerd Hoffmann    if (opts->has_show_cursor && opts->show_cursor) {
18533487da6aSGerd Hoffmann        cursor_hide = 0;
18543487da6aSGerd Hoffmann    }
185543227af8SProgrammingkid
18563e230dd2SCorentin Chary    // register vga output callbacks
1857cc7859c3SAkihiko Odaki    register_displaychangelistener(&dcl);
18583e230dd2SCorentin Chary}
18595013b9e4SGerd Hoffmann
18605013b9e4SGerd Hoffmannstatic QemuDisplay qemu_display_cocoa = {
18615013b9e4SGerd Hoffmann    .type       = DISPLAY_TYPE_COCOA,
18625013b9e4SGerd Hoffmann    .init       = cocoa_display_init,
18635013b9e4SGerd Hoffmann};
18645013b9e4SGerd Hoffmann
18655013b9e4SGerd Hoffmannstatic void register_cocoa(void)
18665013b9e4SGerd Hoffmann{
18675013b9e4SGerd Hoffmann    qemu_display_register(&qemu_display_cocoa);
18685013b9e4SGerd Hoffmann}
18695013b9e4SGerd Hoffmann
18705013b9e4SGerd Hoffmanntype_init(register_cocoa);
1871