xref: /qemu/ui/cocoa.m (revision ccebb9ae)
13e230dd2SCorentin Chary/*
23e230dd2SCorentin Chary * QEMU Cocoa CG display driver
33e230dd2SCorentin Chary *
43e230dd2SCorentin Chary * Copyright (c) 2008 Mike Kronenberg
53e230dd2SCorentin Chary *
63e230dd2SCorentin Chary * Permission is hereby granted, free of charge, to any person obtaining a copy
73e230dd2SCorentin Chary * of this software and associated documentation files (the "Software"), to deal
83e230dd2SCorentin Chary * in the Software without restriction, including without limitation the rights
93e230dd2SCorentin Chary * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
103e230dd2SCorentin Chary * copies of the Software, and to permit persons to whom the Software is
113e230dd2SCorentin Chary * furnished to do so, subject to the following conditions:
123e230dd2SCorentin Chary *
133e230dd2SCorentin Chary * The above copyright notice and this permission notice shall be included in
143e230dd2SCorentin Chary * all copies or substantial portions of the Software.
153e230dd2SCorentin Chary *
163e230dd2SCorentin Chary * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
173e230dd2SCorentin Chary * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
183e230dd2SCorentin Chary * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
193e230dd2SCorentin Chary * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
203e230dd2SCorentin Chary * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
213e230dd2SCorentin Chary * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
223e230dd2SCorentin Chary * THE SOFTWARE.
233e230dd2SCorentin Chary */
243e230dd2SCorentin Chary
25e4a096b1SPeter Maydell#include "qemu/osdep.h"
26e4a096b1SPeter Maydell
273e230dd2SCorentin Chary#import <Cocoa/Cocoa.h>
283bbbee18SAndreas Färber#include <crt_externs.h>
293e230dd2SCorentin Chary
3049f95221SMarc-André Lureau#include "qemu/help-texts.h"
3188c39c86SMarc-André Lureau#include "qemu-main.h"
327e3e20d8SAkihiko Odaki#include "ui/clipboard.h"
3328ecbaeeSPaolo Bonzini#include "ui/console.h"
3421bae11aSGerd Hoffmann#include "ui/input.h"
356d73bb64SAkihiko Odaki#include "ui/kbd-state.h"
369c17d615SPaolo Bonzini#include "sysemu/sysemu.h"
3754d31236SMarkus Armbruster#include "sysemu/runstate.h"
382910abd6SAkihiko Odaki#include "sysemu/runstate-action.h"
39b0c3cf94SClaudio Fontana#include "sysemu/cpu-throttle.h"
40e688df6bSMarkus Armbruster#include "qapi/error.h"
4116bf5234SMarkus Armbruster#include "qapi/qapi-commands-block.h"
4290f8c0f9SPhilippe Mathieu-Daudé#include "qapi/qapi-commands-machine.h"
4316bf5234SMarkus Armbruster#include "qapi/qapi-commands-misc.h"
44693a3e01SJohn Arbuckle#include "sysemu/blockdev.h"
459e8204b1SProgrammingkid#include "qemu-version.h"
46e31746ecSAkihiko Odaki#include "qemu/cutils.h"
47db725815SMarkus Armbruster#include "qemu/main-loop.h"
480b8fa32fSMarkus Armbruster#include "qemu/module.h"
49cc37d98bSRichard Henderson#include "qemu/error-report.h"
50aaac714fSJohn Arbuckle#include <Carbon/Carbon.h>
512e5b09fdSMarkus Armbruster#include "hw/core/cpu.h"
523e230dd2SCorentin Chary
535e24600aSBrendan Shanks#ifndef MAC_OS_X_VERSION_10_13
545e24600aSBrendan Shanks#define MAC_OS_X_VERSION_10_13 101300
555e24600aSBrendan Shanks#endif
563e230dd2SCorentin Chary
57f5af8027SDavid Parsons#ifndef MAC_OS_VERSION_14_0
58f5af8027SDavid Parsons#define MAC_OS_VERSION_14_0 140000
59f5af8027SDavid Parsons#endif
60f5af8027SDavid Parsons
615e24600aSBrendan Shanks/* 10.14 deprecates NSOnState and NSOffState in favor of
625e24600aSBrendan Shanks * NSControlStateValueOn/Off, which were introduced in 10.13.
635e24600aSBrendan Shanks * Define for older versions
645e24600aSBrendan Shanks */
655e24600aSBrendan Shanks#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_13
665e24600aSBrendan Shanks#define NSControlStateValueOn NSOnState
675e24600aSBrendan Shanks#define NSControlStateValueOff NSOffState
685e24600aSBrendan Shanks#endif
693e230dd2SCorentin Chary
703e230dd2SCorentin Chary//#define DEBUG
713e230dd2SCorentin Chary
723e230dd2SCorentin Chary#ifdef DEBUG
733e230dd2SCorentin Chary#define COCOA_DEBUG(...)  { (void) fprintf (stdout, __VA_ARGS__); }
743e230dd2SCorentin Chary#else
753e230dd2SCorentin Chary#define COCOA_DEBUG(...)  ((void) 0)
763e230dd2SCorentin Chary#endif
773e230dd2SCorentin Chary
783e230dd2SCorentin Chary#define cgrect(nsrect) (*(CGRect *)&(nsrect))
793e230dd2SCorentin Chary
8023bdd0deSChristian Schoenebeck#define UC_CTRL_KEY "\xe2\x8c\x83"
8123bdd0deSChristian Schoenebeck#define UC_ALT_KEY "\xe2\x8c\xa5"
8223bdd0deSChristian Schoenebeck
833e230dd2SCorentin Charytypedef struct {
843e230dd2SCorentin Chary    int width;
853e230dd2SCorentin Chary    int height;
863e230dd2SCorentin Chary} QEMUScreen;
873e230dd2SCorentin Chary
88cc7859c3SAkihiko Odakistatic void cocoa_update(DisplayChangeListener *dcl,
89cc7859c3SAkihiko Odaki                         int x, int y, int w, int h);
90cc7859c3SAkihiko Odaki
91cc7859c3SAkihiko Odakistatic void cocoa_switch(DisplayChangeListener *dcl,
92cc7859c3SAkihiko Odaki                         DisplaySurface *surface);
93cc7859c3SAkihiko Odaki
94cc7859c3SAkihiko Odakistatic void cocoa_refresh(DisplayChangeListener *dcl);
95cc7859c3SAkihiko Odaki
96cc7859c3SAkihiko Odakistatic const DisplayChangeListenerOps dcl_ops = {
97cc7859c3SAkihiko Odaki    .dpy_name          = "cocoa",
98cc7859c3SAkihiko Odaki    .dpy_gfx_update = cocoa_update,
99cc7859c3SAkihiko Odaki    .dpy_gfx_switch = cocoa_switch,
100cc7859c3SAkihiko Odaki    .dpy_refresh = cocoa_refresh,
101cc7859c3SAkihiko Odaki};
102cc7859c3SAkihiko Odakistatic DisplayChangeListener dcl = {
103cc7859c3SAkihiko Odaki    .ops = &dcl_ops,
104cc7859c3SAkihiko Odaki};
105ca3de7b5SAkihiko Odakistatic QKbdState *kbd;
1063487da6aSGerd Hoffmannstatic int cursor_hide = 1;
10748941a52SCarwyn Ellisstatic int left_command_key_enabled = 1;
1084797adceSGustavo Noronha Silvastatic bool swap_opt_cmd;
1093e230dd2SCorentin Chary
110e28a909aSCarwyn Ellisstatic CGInterpolationQuality zoom_interpolation = kCGInterpolationNone;
111cb823408SAkihiko Odakistatic NSTextField *pauseLabel;
1123e230dd2SCorentin Chary
113dff742adSHikaru Nishidastatic bool allow_events;
1145588840fSPeter Maydell
1157e3e20d8SAkihiko Odakistatic NSInteger cbchangecount = -1;
1167e3e20d8SAkihiko Odakistatic QemuClipboardInfo *cbinfo;
1177e3e20d8SAkihiko Odakistatic QemuEvent cbevent;
1187e3e20d8SAkihiko Odaki
119a4a411fbSStefan Hajnoczi// Utility functions to run specified code block with the BQL held
12031819e95SPeter Maydelltypedef void (^CodeBlock)(void);
12160105d7aSPeter Maydelltypedef bool (^BoolCodeBlock)(void);
12231819e95SPeter Maydell
123195801d7SStefan Hajnoczistatic void with_bql(CodeBlock block)
12431819e95SPeter Maydell{
125195801d7SStefan Hajnoczi    bool locked = bql_locked();
12631819e95SPeter Maydell    if (!locked) {
127195801d7SStefan Hajnoczi        bql_lock();
12831819e95SPeter Maydell    }
12931819e95SPeter Maydell    block();
13031819e95SPeter Maydell    if (!locked) {
131195801d7SStefan Hajnoczi        bql_unlock();
13231819e95SPeter Maydell    }
13331819e95SPeter Maydell}
13431819e95SPeter Maydell
135195801d7SStefan Hajnoczistatic bool bool_with_bql(BoolCodeBlock block)
13660105d7aSPeter Maydell{
137195801d7SStefan Hajnoczi    bool locked = bql_locked();
13860105d7aSPeter Maydell    bool val;
13960105d7aSPeter Maydell
14060105d7aSPeter Maydell    if (!locked) {
141195801d7SStefan Hajnoczi        bql_lock();
14260105d7aSPeter Maydell    }
14360105d7aSPeter Maydell    val = block();
14460105d7aSPeter Maydell    if (!locked) {
145195801d7SStefan Hajnoczi        bql_unlock();
14660105d7aSPeter Maydell    }
14760105d7aSPeter Maydell    return val;
14860105d7aSPeter Maydell}
14960105d7aSPeter Maydell
150aaac714fSJohn Arbuckle// Mac to QKeyCode conversion
151cb823408SAkihiko Odakistatic const int mac_to_qkeycode_map[] = {
152aaac714fSJohn Arbuckle    [kVK_ANSI_A] = Q_KEY_CODE_A,
153aaac714fSJohn Arbuckle    [kVK_ANSI_B] = Q_KEY_CODE_B,
154aaac714fSJohn Arbuckle    [kVK_ANSI_C] = Q_KEY_CODE_C,
155aaac714fSJohn Arbuckle    [kVK_ANSI_D] = Q_KEY_CODE_D,
156aaac714fSJohn Arbuckle    [kVK_ANSI_E] = Q_KEY_CODE_E,
157aaac714fSJohn Arbuckle    [kVK_ANSI_F] = Q_KEY_CODE_F,
158aaac714fSJohn Arbuckle    [kVK_ANSI_G] = Q_KEY_CODE_G,
159aaac714fSJohn Arbuckle    [kVK_ANSI_H] = Q_KEY_CODE_H,
160aaac714fSJohn Arbuckle    [kVK_ANSI_I] = Q_KEY_CODE_I,
161aaac714fSJohn Arbuckle    [kVK_ANSI_J] = Q_KEY_CODE_J,
162aaac714fSJohn Arbuckle    [kVK_ANSI_K] = Q_KEY_CODE_K,
163aaac714fSJohn Arbuckle    [kVK_ANSI_L] = Q_KEY_CODE_L,
164aaac714fSJohn Arbuckle    [kVK_ANSI_M] = Q_KEY_CODE_M,
165aaac714fSJohn Arbuckle    [kVK_ANSI_N] = Q_KEY_CODE_N,
166aaac714fSJohn Arbuckle    [kVK_ANSI_O] = Q_KEY_CODE_O,
167aaac714fSJohn Arbuckle    [kVK_ANSI_P] = Q_KEY_CODE_P,
168aaac714fSJohn Arbuckle    [kVK_ANSI_Q] = Q_KEY_CODE_Q,
169aaac714fSJohn Arbuckle    [kVK_ANSI_R] = Q_KEY_CODE_R,
170aaac714fSJohn Arbuckle    [kVK_ANSI_S] = Q_KEY_CODE_S,
171aaac714fSJohn Arbuckle    [kVK_ANSI_T] = Q_KEY_CODE_T,
172aaac714fSJohn Arbuckle    [kVK_ANSI_U] = Q_KEY_CODE_U,
173aaac714fSJohn Arbuckle    [kVK_ANSI_V] = Q_KEY_CODE_V,
174aaac714fSJohn Arbuckle    [kVK_ANSI_W] = Q_KEY_CODE_W,
175aaac714fSJohn Arbuckle    [kVK_ANSI_X] = Q_KEY_CODE_X,
176aaac714fSJohn Arbuckle    [kVK_ANSI_Y] = Q_KEY_CODE_Y,
177aaac714fSJohn Arbuckle    [kVK_ANSI_Z] = Q_KEY_CODE_Z,
1783e230dd2SCorentin Chary
179aaac714fSJohn Arbuckle    [kVK_ANSI_0] = Q_KEY_CODE_0,
180aaac714fSJohn Arbuckle    [kVK_ANSI_1] = Q_KEY_CODE_1,
181aaac714fSJohn Arbuckle    [kVK_ANSI_2] = Q_KEY_CODE_2,
182aaac714fSJohn Arbuckle    [kVK_ANSI_3] = Q_KEY_CODE_3,
183aaac714fSJohn Arbuckle    [kVK_ANSI_4] = Q_KEY_CODE_4,
184aaac714fSJohn Arbuckle    [kVK_ANSI_5] = Q_KEY_CODE_5,
185aaac714fSJohn Arbuckle    [kVK_ANSI_6] = Q_KEY_CODE_6,
186aaac714fSJohn Arbuckle    [kVK_ANSI_7] = Q_KEY_CODE_7,
187aaac714fSJohn Arbuckle    [kVK_ANSI_8] = Q_KEY_CODE_8,
188aaac714fSJohn Arbuckle    [kVK_ANSI_9] = Q_KEY_CODE_9,
189aaac714fSJohn Arbuckle
190aaac714fSJohn Arbuckle    [kVK_ANSI_Grave] = Q_KEY_CODE_GRAVE_ACCENT,
191aaac714fSJohn Arbuckle    [kVK_ANSI_Minus] = Q_KEY_CODE_MINUS,
192aaac714fSJohn Arbuckle    [kVK_ANSI_Equal] = Q_KEY_CODE_EQUAL,
193aaac714fSJohn Arbuckle    [kVK_Delete] = Q_KEY_CODE_BACKSPACE,
194aaac714fSJohn Arbuckle    [kVK_CapsLock] = Q_KEY_CODE_CAPS_LOCK,
195aaac714fSJohn Arbuckle    [kVK_Tab] = Q_KEY_CODE_TAB,
196aaac714fSJohn Arbuckle    [kVK_Return] = Q_KEY_CODE_RET,
197aaac714fSJohn Arbuckle    [kVK_ANSI_LeftBracket] = Q_KEY_CODE_BRACKET_LEFT,
198aaac714fSJohn Arbuckle    [kVK_ANSI_RightBracket] = Q_KEY_CODE_BRACKET_RIGHT,
199aaac714fSJohn Arbuckle    [kVK_ANSI_Backslash] = Q_KEY_CODE_BACKSLASH,
200aaac714fSJohn Arbuckle    [kVK_ANSI_Semicolon] = Q_KEY_CODE_SEMICOLON,
201aaac714fSJohn Arbuckle    [kVK_ANSI_Quote] = Q_KEY_CODE_APOSTROPHE,
202aaac714fSJohn Arbuckle    [kVK_ANSI_Comma] = Q_KEY_CODE_COMMA,
203aaac714fSJohn Arbuckle    [kVK_ANSI_Period] = Q_KEY_CODE_DOT,
204aaac714fSJohn Arbuckle    [kVK_ANSI_Slash] = Q_KEY_CODE_SLASH,
205aaac714fSJohn Arbuckle    [kVK_Space] = Q_KEY_CODE_SPC,
206aaac714fSJohn Arbuckle
207aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad0] = Q_KEY_CODE_KP_0,
208aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad1] = Q_KEY_CODE_KP_1,
209aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad2] = Q_KEY_CODE_KP_2,
210aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad3] = Q_KEY_CODE_KP_3,
211aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad4] = Q_KEY_CODE_KP_4,
212aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad5] = Q_KEY_CODE_KP_5,
213aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad6] = Q_KEY_CODE_KP_6,
214aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad7] = Q_KEY_CODE_KP_7,
215aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad8] = Q_KEY_CODE_KP_8,
216aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad9] = Q_KEY_CODE_KP_9,
217aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadDecimal] = Q_KEY_CODE_KP_DECIMAL,
218aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadEnter] = Q_KEY_CODE_KP_ENTER,
219aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadPlus] = Q_KEY_CODE_KP_ADD,
220aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadMinus] = Q_KEY_CODE_KP_SUBTRACT,
221aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadMultiply] = Q_KEY_CODE_KP_MULTIPLY,
222aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadDivide] = Q_KEY_CODE_KP_DIVIDE,
223aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadEquals] = Q_KEY_CODE_KP_EQUALS,
224aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadClear] = Q_KEY_CODE_NUM_LOCK,
225aaac714fSJohn Arbuckle
226aaac714fSJohn Arbuckle    [kVK_UpArrow] = Q_KEY_CODE_UP,
227aaac714fSJohn Arbuckle    [kVK_DownArrow] = Q_KEY_CODE_DOWN,
228aaac714fSJohn Arbuckle    [kVK_LeftArrow] = Q_KEY_CODE_LEFT,
229aaac714fSJohn Arbuckle    [kVK_RightArrow] = Q_KEY_CODE_RIGHT,
230aaac714fSJohn Arbuckle
231aaac714fSJohn Arbuckle    [kVK_Help] = Q_KEY_CODE_INSERT,
232aaac714fSJohn Arbuckle    [kVK_Home] = Q_KEY_CODE_HOME,
233aaac714fSJohn Arbuckle    [kVK_PageUp] = Q_KEY_CODE_PGUP,
234aaac714fSJohn Arbuckle    [kVK_PageDown] = Q_KEY_CODE_PGDN,
235aaac714fSJohn Arbuckle    [kVK_End] = Q_KEY_CODE_END,
236aaac714fSJohn Arbuckle    [kVK_ForwardDelete] = Q_KEY_CODE_DELETE,
237aaac714fSJohn Arbuckle
238aaac714fSJohn Arbuckle    [kVK_Escape] = Q_KEY_CODE_ESC,
239aaac714fSJohn Arbuckle
240aaac714fSJohn Arbuckle    /* The Power key can't be used directly because the operating system uses
241aaac714fSJohn Arbuckle     * it. This key can be emulated by using it in place of another key such as
242aaac714fSJohn Arbuckle     * F1. Don't forget to disable the real key binding.
243aaac714fSJohn Arbuckle     */
244aaac714fSJohn Arbuckle    /* [kVK_F1] = Q_KEY_CODE_POWER, */
245aaac714fSJohn Arbuckle
246aaac714fSJohn Arbuckle    [kVK_F1] = Q_KEY_CODE_F1,
247aaac714fSJohn Arbuckle    [kVK_F2] = Q_KEY_CODE_F2,
248aaac714fSJohn Arbuckle    [kVK_F3] = Q_KEY_CODE_F3,
249aaac714fSJohn Arbuckle    [kVK_F4] = Q_KEY_CODE_F4,
250aaac714fSJohn Arbuckle    [kVK_F5] = Q_KEY_CODE_F5,
251aaac714fSJohn Arbuckle    [kVK_F6] = Q_KEY_CODE_F6,
252aaac714fSJohn Arbuckle    [kVK_F7] = Q_KEY_CODE_F7,
253aaac714fSJohn Arbuckle    [kVK_F8] = Q_KEY_CODE_F8,
254aaac714fSJohn Arbuckle    [kVK_F9] = Q_KEY_CODE_F9,
255aaac714fSJohn Arbuckle    [kVK_F10] = Q_KEY_CODE_F10,
256aaac714fSJohn Arbuckle    [kVK_F11] = Q_KEY_CODE_F11,
257aaac714fSJohn Arbuckle    [kVK_F12] = Q_KEY_CODE_F12,
258aaac714fSJohn Arbuckle    [kVK_F13] = Q_KEY_CODE_PRINT,
259aaac714fSJohn Arbuckle    [kVK_F14] = Q_KEY_CODE_SCROLL_LOCK,
260aaac714fSJohn Arbuckle    [kVK_F15] = Q_KEY_CODE_PAUSE,
261aaac714fSJohn Arbuckle
262708b7255SAkihiko Odaki    // JIS keyboards only
263708b7255SAkihiko Odaki    [kVK_JIS_Yen] = Q_KEY_CODE_YEN,
264708b7255SAkihiko Odaki    [kVK_JIS_Underscore] = Q_KEY_CODE_RO,
265708b7255SAkihiko Odaki    [kVK_JIS_KeypadComma] = Q_KEY_CODE_KP_COMMA,
266708b7255SAkihiko Odaki    [kVK_JIS_Eisu] = Q_KEY_CODE_MUHENKAN,
267708b7255SAkihiko Odaki    [kVK_JIS_Kana] = Q_KEY_CODE_HENKAN,
268708b7255SAkihiko Odaki
2693e230dd2SCorentin Chary    /*
270aaac714fSJohn Arbuckle     * The eject and volume keys can't be used here because they are handled at
271aaac714fSJohn Arbuckle     * a lower level than what an Application can see.
2723e230dd2SCorentin Chary     */
2733e230dd2SCorentin Chary};
2743e230dd2SCorentin Chary
2753e230dd2SCorentin Charystatic int cocoa_keycode_to_qemu(int keycode)
2763e230dd2SCorentin Chary{
277aaac714fSJohn Arbuckle    if (ARRAY_SIZE(mac_to_qkeycode_map) <= keycode) {
2784313739aSAkihiko Odaki        error_report("(cocoa) warning unknown keycode 0x%x", keycode);
2793e230dd2SCorentin Chary        return 0;
2803e230dd2SCorentin Chary    }
281aaac714fSJohn Arbuckle    return mac_to_qkeycode_map[keycode];
2823e230dd2SCorentin Chary}
2833e230dd2SCorentin Chary
284693a3e01SJohn Arbuckle/* Displays an alert dialog box with the specified message */
285693a3e01SJohn Arbucklestatic void QEMU_Alert(NSString *message)
286693a3e01SJohn Arbuckle{
287693a3e01SJohn Arbuckle    NSAlert *alert;
288693a3e01SJohn Arbuckle    alert = [NSAlert new];
289693a3e01SJohn Arbuckle    [alert setMessageText: message];
290693a3e01SJohn Arbuckle    [alert runModal];
291693a3e01SJohn Arbuckle}
2923e230dd2SCorentin Chary
293693a3e01SJohn Arbuckle/* Handles any errors that happen with a device transaction */
294693a3e01SJohn Arbucklestatic void handleAnyDeviceErrors(Error * err)
295693a3e01SJohn Arbuckle{
296693a3e01SJohn Arbuckle    if (err) {
297693a3e01SJohn Arbuckle        QEMU_Alert([NSString stringWithCString: error_get_pretty(err)
298693a3e01SJohn Arbuckle                                      encoding: NSASCIIStringEncoding]);
299693a3e01SJohn Arbuckle        error_free(err);
300693a3e01SJohn Arbuckle    }
301693a3e01SJohn Arbuckle}
3023e230dd2SCorentin Chary
3033e230dd2SCorentin Chary/*
3043e230dd2SCorentin Chary ------------------------------------------------------
3053e230dd2SCorentin Chary    QemuCocoaView
3063e230dd2SCorentin Chary ------------------------------------------------------
3073e230dd2SCorentin Chary*/
3083e230dd2SCorentin Chary@interface QemuCocoaView : NSView
3093e230dd2SCorentin Chary{
3103e230dd2SCorentin Chary    QEMUScreen screen;
3115588840fSPeter Maydell    pixman_image_t *pixman_image;
31249b9bd4dSPeter Maydell    BOOL isMouseGrabbed;
3133e230dd2SCorentin Chary    BOOL isAbsoluteEnabled;
314f844cdb9SGustavo Noronha Silva    CFMachPortRef eventsTap;
3153e230dd2SCorentin Chary}
31672a3e316SPeter Maydell- (void) switchSurface:(pixman_image_t *)image;
3173e230dd2SCorentin Chary- (void) grabMouse;
3183e230dd2SCorentin Chary- (void) ungrabMouse;
319f844cdb9SGustavo Noronha Silva- (void) setFullGrab:(id)sender;
3209c3a418eSJohn Arbuckle- (void) handleMonitorInput:(NSEvent *)event;
32160105d7aSPeter Maydell- (bool) handleEvent:(NSEvent *)event;
32260105d7aSPeter Maydell- (bool) handleEventLocked:(NSEvent *)event;
3233e230dd2SCorentin Chary- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled;
324f61c387eSPeter Maydell/* The state surrounding mouse grabbing is potentially confusing.
325f61c387eSPeter Maydell * isAbsoluteEnabled tracks qemu_input_is_absolute() [ie "is the emulated
326f61c387eSPeter Maydell *   pointing device an absolute-position one?"], but is only updated on
327f61c387eSPeter Maydell *   next refresh.
328f61c387eSPeter Maydell * isMouseGrabbed tracks whether GUI events are directed to the guest;
329f61c387eSPeter Maydell *   it controls whether special keys like Cmd get sent to the guest,
330f61c387eSPeter Maydell *   and whether we capture the mouse when in non-absolute mode.
331f61c387eSPeter Maydell */
33249b9bd4dSPeter Maydell- (BOOL) isMouseGrabbed;
3333e230dd2SCorentin Chary- (BOOL) isAbsoluteEnabled;
3343e230dd2SCorentin Chary- (QEMUScreen) gscreen;
3353b178b71SJohn Arbuckle- (void) raiseAllKeys;
3363e230dd2SCorentin Chary@end
3373e230dd2SCorentin Chary
3387fee199cSAndreas FärberQemuCocoaView *cocoaView;
3397fee199cSAndreas Färber
340f844cdb9SGustavo Noronha Silvastatic CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEventRef cgEvent, void *userInfo)
341f844cdb9SGustavo Noronha Silva{
34221eb752fSPhilippe Mathieu-Daudé    QemuCocoaView *view = userInfo;
343f844cdb9SGustavo Noronha Silva    NSEvent *event = [NSEvent eventWithCGEvent:cgEvent];
34421eb752fSPhilippe Mathieu-Daudé    if ([view isMouseGrabbed] && [view handleEvent:event]) {
345f844cdb9SGustavo Noronha Silva        COCOA_DEBUG("Global events tap: qemu handled the event, capturing!\n");
346f844cdb9SGustavo Noronha Silva        return NULL;
347f844cdb9SGustavo Noronha Silva    }
348f844cdb9SGustavo Noronha Silva    COCOA_DEBUG("Global events tap: qemu did not handle the event, letting it through...\n");
349f844cdb9SGustavo Noronha Silva
350f844cdb9SGustavo Noronha Silva    return cgEvent;
351f844cdb9SGustavo Noronha Silva}
352f844cdb9SGustavo Noronha Silva
3533e230dd2SCorentin Chary@implementation QemuCocoaView
3543e230dd2SCorentin Chary- (id)initWithFrame:(NSRect)frameRect
3553e230dd2SCorentin Chary{
3563e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaView: initWithFrame\n");
3573e230dd2SCorentin Chary
3583e230dd2SCorentin Chary    self = [super initWithFrame:frameRect];
3593e230dd2SCorentin Chary    if (self) {
3603e230dd2SCorentin Chary
361*ccebb9aeSAkihiko Odaki        NSTrackingAreaOptions options = NSTrackingActiveInKeyWindow |
362*ccebb9aeSAkihiko Odaki                                        NSTrackingMouseEnteredAndExited |
363*ccebb9aeSAkihiko Odaki                                        NSTrackingMouseMoved |
364*ccebb9aeSAkihiko Odaki                                        NSTrackingInVisibleRect;
365*ccebb9aeSAkihiko Odaki
366*ccebb9aeSAkihiko Odaki        NSTrackingArea *trackingArea =
367*ccebb9aeSAkihiko Odaki            [[NSTrackingArea alloc] initWithRect:CGRectZero
368*ccebb9aeSAkihiko Odaki                                         options:options
369*ccebb9aeSAkihiko Odaki                                           owner:self
370*ccebb9aeSAkihiko Odaki                                        userInfo:nil];
371*ccebb9aeSAkihiko Odaki
372*ccebb9aeSAkihiko Odaki        [self addTrackingArea:trackingArea];
373*ccebb9aeSAkihiko Odaki        [trackingArea release];
3743e230dd2SCorentin Chary        screen.width = frameRect.size.width;
3753e230dd2SCorentin Chary        screen.height = frameRect.size.height;
376f5af8027SDavid Parsons#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_14_0
377f5af8027SDavid Parsons        [self setClipsToBounds:YES];
378f5af8027SDavid Parsons#endif
3793e230dd2SCorentin Chary
3803e230dd2SCorentin Chary    }
3813e230dd2SCorentin Chary    return self;
3823e230dd2SCorentin Chary}
3833e230dd2SCorentin Chary
3843e230dd2SCorentin Chary- (void) dealloc
3853e230dd2SCorentin Chary{
3863e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaView: dealloc\n");
3873e230dd2SCorentin Chary
388c0ff29d1SAkihiko Odaki    if (pixman_image) {
3895588840fSPeter Maydell        pixman_image_unref(pixman_image);
3905588840fSPeter Maydell    }
3913e230dd2SCorentin Chary
392f844cdb9SGustavo Noronha Silva    if (eventsTap) {
393f844cdb9SGustavo Noronha Silva        CFRelease(eventsTap);
394f844cdb9SGustavo Noronha Silva    }
395f844cdb9SGustavo Noronha Silva
3963e230dd2SCorentin Chary    [super dealloc];
3973e230dd2SCorentin Chary}
3983e230dd2SCorentin Chary
3993e230dd2SCorentin Chary- (BOOL) isOpaque
4003e230dd2SCorentin Chary{
4013e230dd2SCorentin Chary    return YES;
4023e230dd2SCorentin Chary}
4033e230dd2SCorentin Chary
40491aa508dSAkihiko Odaki- (void) viewDidMoveToWindow
40591aa508dSAkihiko Odaki{
40691aa508dSAkihiko Odaki    [self resizeWindow];
4072044dff8SChen Zhang}
4082044dff8SChen Zhang
409ca3de7b5SAkihiko Odaki- (void) selectConsoleLocked:(unsigned int)index
410ca3de7b5SAkihiko Odaki{
411ca3de7b5SAkihiko Odaki    QemuConsole *con = qemu_console_lookup_by_index(index);
412ca3de7b5SAkihiko Odaki    if (!con) {
413ca3de7b5SAkihiko Odaki        return;
414ca3de7b5SAkihiko Odaki    }
415ca3de7b5SAkihiko Odaki
416ca3de7b5SAkihiko Odaki    unregister_displaychangelistener(&dcl);
417ca3de7b5SAkihiko Odaki    qkbd_state_switch_console(kbd, con);
418ca3de7b5SAkihiko Odaki    dcl.con = con;
419ca3de7b5SAkihiko Odaki    register_displaychangelistener(&dcl);
420ca3de7b5SAkihiko Odaki    [self updateUIInfo];
421ca3de7b5SAkihiko Odaki}
422ca3de7b5SAkihiko Odaki
42313aefd30SPeter Maydell- (void) hideCursor
42413aefd30SPeter Maydell{
42513aefd30SPeter Maydell    if (!cursor_hide) {
42613aefd30SPeter Maydell        return;
42713aefd30SPeter Maydell    }
42813aefd30SPeter Maydell    [NSCursor hide];
42913aefd30SPeter Maydell}
43013aefd30SPeter Maydell
43113aefd30SPeter Maydell- (void) unhideCursor
43213aefd30SPeter Maydell{
43313aefd30SPeter Maydell    if (!cursor_hide) {
43413aefd30SPeter Maydell        return;
43513aefd30SPeter Maydell    }
43613aefd30SPeter Maydell    [NSCursor unhide];
43713aefd30SPeter Maydell}
43813aefd30SPeter Maydell
4393e230dd2SCorentin Chary- (void) drawRect:(NSRect) rect
4403e230dd2SCorentin Chary{
4413e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaView: drawRect\n");
4423e230dd2SCorentin Chary
4433e230dd2SCorentin Chary    // get CoreGraphic context
4445e24600aSBrendan Shanks    CGContextRef viewContextRef = [[NSGraphicsContext currentContext] CGContext];
4455e24600aSBrendan Shanks
446e28a909aSCarwyn Ellis    CGContextSetInterpolationQuality (viewContextRef, zoom_interpolation);
4473e230dd2SCorentin Chary    CGContextSetShouldAntialias (viewContextRef, NO);
4483e230dd2SCorentin Chary
4493e230dd2SCorentin Chary    // draw screen bitmap directly to Core Graphics context
450c0ff29d1SAkihiko Odaki    if (!pixman_image) {
4517d270b1cSPeter Maydell        // Draw request before any guest device has set up a framebuffer:
4527d270b1cSPeter Maydell        // just draw an opaque black rectangle
4537d270b1cSPeter Maydell        CGContextSetRGBFillColor(viewContextRef, 0, 0, 0, 1.0);
4547d270b1cSPeter Maydell        CGContextFillRect(viewContextRef, NSRectToCGRect(rect));
4557d270b1cSPeter Maydell    } else {
456c0ff29d1SAkihiko Odaki        int w = pixman_image_get_width(pixman_image);
457c0ff29d1SAkihiko Odaki        int h = pixman_image_get_height(pixman_image);
458c0ff29d1SAkihiko Odaki        int bitsPerPixel = PIXMAN_FORMAT_BPP(pixman_image_get_format(pixman_image));
459d9c32b8fSAkihiko Odaki        int stride = pixman_image_get_stride(pixman_image);
460c0ff29d1SAkihiko Odaki        CGDataProviderRef dataProviderRef = CGDataProviderCreateWithData(
461c0ff29d1SAkihiko Odaki            NULL,
462c0ff29d1SAkihiko Odaki            pixman_image_get_data(pixman_image),
463d9c32b8fSAkihiko Odaki            stride * h,
464c0ff29d1SAkihiko Odaki            NULL
465c0ff29d1SAkihiko Odaki        );
4663e230dd2SCorentin Chary        CGImageRef imageRef = CGImageCreate(
467c0ff29d1SAkihiko Odaki            w, //width
468c0ff29d1SAkihiko Odaki            h, //height
469d9c32b8fSAkihiko Odaki            DIV_ROUND_UP(bitsPerPixel, 8) * 2, //bitsPerComponent
470c0ff29d1SAkihiko Odaki            bitsPerPixel, //bitsPerPixel
471d9c32b8fSAkihiko Odaki            stride, //bytesPerRow
472ae57d35cSAkihiko Odaki            CGColorSpaceCreateWithName(kCGColorSpaceSRGB), //colorspace
473ae57d35cSAkihiko Odaki            kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, //bitmapInfo
4743e230dd2SCorentin Chary            dataProviderRef, //provider
4753e230dd2SCorentin Chary            NULL, //decode
4763e230dd2SCorentin Chary            0, //interpolate
4773e230dd2SCorentin Chary            kCGRenderingIntentDefault //intent
4783e230dd2SCorentin Chary        );
4793e230dd2SCorentin Chary        // selective drawing code (draws only dirty rectangles) (OS X >= 10.4)
4803e230dd2SCorentin Chary        const NSRect *rectList;
4813e230dd2SCorentin Chary        NSInteger rectCount;
4823e230dd2SCorentin Chary        int i;
4833e230dd2SCorentin Chary        CGImageRef clipImageRef;
4843e230dd2SCorentin Chary        CGRect clipRect;
4853e230dd2SCorentin Chary
4863e230dd2SCorentin Chary        [self getRectsBeingDrawn:&rectList count:&rectCount];
4873e230dd2SCorentin Chary        for (i = 0; i < rectCount; i++) {
488fcb03de7SAkihiko Odaki            clipRect = rectList[i];
489fcb03de7SAkihiko Odaki            clipRect.origin.y = (float)h - (clipRect.origin.y + clipRect.size.height);
4903e230dd2SCorentin Chary            clipImageRef = CGImageCreateWithImageInRect(
4913e230dd2SCorentin Chary                                                        imageRef,
4923e230dd2SCorentin Chary                                                        clipRect
4933e230dd2SCorentin Chary                                                        );
4943e230dd2SCorentin Chary            CGContextDrawImage (viewContextRef, cgrect(rectList[i]), clipImageRef);
4953e230dd2SCorentin Chary            CGImageRelease (clipImageRef);
4963e230dd2SCorentin Chary        }
4973e230dd2SCorentin Chary        CGImageRelease (imageRef);
498c0ff29d1SAkihiko Odaki        CGDataProviderRelease(dataProviderRef);
4993e230dd2SCorentin Chary    }
5003e230dd2SCorentin Chary}
5013e230dd2SCorentin Chary
502d2ee0420SAkihiko Odaki- (NSSize)fixAspectRatio:(NSSize)max
503d2ee0420SAkihiko Odaki{
504d2ee0420SAkihiko Odaki    NSSize scaled;
505d2ee0420SAkihiko Odaki    NSSize fixed;
506d2ee0420SAkihiko Odaki
507d2ee0420SAkihiko Odaki    scaled.width = screen.width * max.height;
508d2ee0420SAkihiko Odaki    scaled.height = screen.height * max.width;
509d2ee0420SAkihiko Odaki
510d2ee0420SAkihiko Odaki    /*
511d2ee0420SAkihiko Odaki     * Here screen is our guest's output size, and max is the size of the
512d2ee0420SAkihiko Odaki     * largest possible area of the screen we can display on.
513d2ee0420SAkihiko Odaki     * We want to scale up (screen.width x screen.height) by either:
514d2ee0420SAkihiko Odaki     *   1) max.height / screen.height
515d2ee0420SAkihiko Odaki     *   2) max.width / screen.width
516d2ee0420SAkihiko Odaki     * With the first scale factor the scale will result in an output height of
517d2ee0420SAkihiko Odaki     * max.height (i.e. we will fill the whole height of the available screen
518d2ee0420SAkihiko Odaki     * space and have black bars left and right) and with the second scale
519d2ee0420SAkihiko Odaki     * factor the scaling will result in an output width of max.width (i.e. we
520d2ee0420SAkihiko Odaki     * fill the whole width of the available screen space and have black bars
521d2ee0420SAkihiko Odaki     * top and bottom). We need to pick whichever keeps the whole of the guest
522d2ee0420SAkihiko Odaki     * output on the screen, which is to say the smaller of the two scale
523d2ee0420SAkihiko Odaki     * factors.
524d2ee0420SAkihiko Odaki     * To avoid doing more division than strictly necessary, instead of directly
525d2ee0420SAkihiko Odaki     * comparing scale factors 1 and 2 we instead calculate and compare those
526d2ee0420SAkihiko Odaki     * two scale factors multiplied by (screen.height * screen.width).
527d2ee0420SAkihiko Odaki     */
528d2ee0420SAkihiko Odaki    if (scaled.width < scaled.height) {
529d2ee0420SAkihiko Odaki        fixed.width = scaled.width / screen.height;
530d2ee0420SAkihiko Odaki        fixed.height = max.height;
531d2ee0420SAkihiko Odaki    } else {
532d2ee0420SAkihiko Odaki        fixed.width = max.width;
533d2ee0420SAkihiko Odaki        fixed.height = scaled.height / screen.width;
534d2ee0420SAkihiko Odaki    }
535d2ee0420SAkihiko Odaki
536d2ee0420SAkihiko Odaki    return fixed;
537d2ee0420SAkihiko Odaki}
538d2ee0420SAkihiko Odaki
53991aa508dSAkihiko Odaki- (NSSize) screenSafeAreaSize
5403e230dd2SCorentin Chary{
54191aa508dSAkihiko Odaki    NSSize size = [[[self window] screen] frame].size;
54291aa508dSAkihiko Odaki    NSEdgeInsets insets = [[[self window] screen] safeAreaInsets];
54391aa508dSAkihiko Odaki    size.width -= insets.left + insets.right;
54491aa508dSAkihiko Odaki    size.height -= insets.top + insets.bottom;
54591aa508dSAkihiko Odaki    return size;
5465d1b2eefSProgrammingkid}
54791aa508dSAkihiko Odaki
54891aa508dSAkihiko Odaki- (void) resizeWindow
54991aa508dSAkihiko Odaki{
55091aa508dSAkihiko Odaki    [[self window] setContentAspectRatio:NSMakeSize(screen.width, screen.height)];
55191aa508dSAkihiko Odaki
55255766632SAkihiko Odaki    if (!([[self window] styleMask] & NSWindowStyleMaskResizable)) {
55391aa508dSAkihiko Odaki        [[self window] setContentSize:NSMakeSize(screen.width, screen.height)];
55491aa508dSAkihiko Odaki        [[self window] center];
55591aa508dSAkihiko Odaki    } else if ([[self window] styleMask] & NSWindowStyleMaskFullScreen) {
556d2ee0420SAkihiko Odaki        [[self window] setContentSize:[self fixAspectRatio:[self screenSafeAreaSize]]];
55791aa508dSAkihiko Odaki        [[self window] center];
558d2ee0420SAkihiko Odaki    } else {
559d2ee0420SAkihiko Odaki        [[self window] setContentSize:[self fixAspectRatio:[self frame].size]];
5603e230dd2SCorentin Chary    }
5613e230dd2SCorentin Chary}
5623e230dd2SCorentin Chary
563fcb03de7SAkihiko Odaki- (void) updateBounds
564fcb03de7SAkihiko Odaki{
565fcb03de7SAkihiko Odaki    [self setBoundsSize:NSMakeSize(screen.width, screen.height)];
566fcb03de7SAkihiko Odaki}
567fcb03de7SAkihiko Odaki
5688d65dee2SPeter Maydell- (void) updateUIInfoLocked
56915280e85SAkihiko Odaki{
570a4a411fbSStefan Hajnoczi    /* Must be called with the BQL, i.e. via updateUIInfo */
57115280e85SAkihiko Odaki    NSSize frameSize;
57215280e85SAkihiko Odaki    QemuUIInfo info;
57315280e85SAkihiko Odaki
57415280e85SAkihiko Odaki    if (!qemu_console_is_graphic(dcl.con)) {
57515280e85SAkihiko Odaki        return;
57615280e85SAkihiko Odaki    }
57715280e85SAkihiko Odaki
57815280e85SAkihiko Odaki    if ([self window]) {
57915280e85SAkihiko Odaki        NSDictionary *description = [[[self window] screen] deviceDescription];
58015280e85SAkihiko Odaki        CGDirectDisplayID display = [[description objectForKey:@"NSScreenNumber"] unsignedIntValue];
58115280e85SAkihiko Odaki        NSSize screenSize = [[[self window] screen] frame].size;
58215280e85SAkihiko Odaki        CGSize screenPhysicalSize = CGDisplayScreenSize(display);
58391aa508dSAkihiko Odaki        bool isFullscreen = ([[self window] styleMask] & NSWindowStyleMaskFullScreen) != 0;
58452eaefd3SAkihiko Odaki        CVDisplayLinkRef displayLink;
58515280e85SAkihiko Odaki
58691aa508dSAkihiko Odaki        frameSize = isFullscreen ? [self screenSafeAreaSize] : [self frame].size;
58752eaefd3SAkihiko Odaki
58852eaefd3SAkihiko Odaki        if (!CVDisplayLinkCreateWithCGDisplay(display, &displayLink)) {
58952eaefd3SAkihiko Odaki            CVTime period = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(displayLink);
59052eaefd3SAkihiko Odaki            CVDisplayLinkRelease(displayLink);
59152eaefd3SAkihiko Odaki            if (!(period.flags & kCVTimeIsIndefinite)) {
59252eaefd3SAkihiko Odaki                update_displaychangelistener(&dcl,
59352eaefd3SAkihiko Odaki                                             1000 * period.timeValue / period.timeScale);
59452eaefd3SAkihiko Odaki                info.refresh_rate = (int64_t)1000 * period.timeScale / period.timeValue;
59552eaefd3SAkihiko Odaki            }
59652eaefd3SAkihiko Odaki        }
59752eaefd3SAkihiko Odaki
59815280e85SAkihiko Odaki        info.width_mm = frameSize.width / screenSize.width * screenPhysicalSize.width;
59915280e85SAkihiko Odaki        info.height_mm = frameSize.height / screenSize.height * screenPhysicalSize.height;
60015280e85SAkihiko Odaki    } else {
60115280e85SAkihiko Odaki        frameSize = [self frame].size;
60215280e85SAkihiko Odaki        info.width_mm = 0;
60315280e85SAkihiko Odaki        info.height_mm = 0;
60415280e85SAkihiko Odaki    }
60515280e85SAkihiko Odaki
60615280e85SAkihiko Odaki    info.xoff = 0;
60715280e85SAkihiko Odaki    info.yoff = 0;
60815280e85SAkihiko Odaki    info.width = frameSize.width;
60915280e85SAkihiko Odaki    info.height = frameSize.height;
61015280e85SAkihiko Odaki
611ca19ef52SMarc-André Lureau    dpy_set_ui_info(dcl.con, &info, TRUE);
61215280e85SAkihiko Odaki}
61315280e85SAkihiko Odaki
6148d65dee2SPeter Maydell- (void) updateUIInfo
6158d65dee2SPeter Maydell{
6168d65dee2SPeter Maydell    if (!allow_events) {
6178d65dee2SPeter Maydell        /*
6188d65dee2SPeter Maydell         * Don't try to tell QEMU about UI information in the application
6198d65dee2SPeter Maydell         * startup phase -- we haven't yet registered dcl with the QEMU UI
620bab6a301SAkihiko Odaki         * layer.
6218d65dee2SPeter Maydell         * When cocoa_display_init() does register the dcl, the UI layer
6228d65dee2SPeter Maydell         * will call cocoa_switch(), which will call updateUIInfo, so
6238d65dee2SPeter Maydell         * we don't lose any information here.
6248d65dee2SPeter Maydell         */
6258d65dee2SPeter Maydell        return;
6268d65dee2SPeter Maydell    }
6278d65dee2SPeter Maydell
628195801d7SStefan Hajnoczi    with_bql(^{
6298d65dee2SPeter Maydell        [self updateUIInfoLocked];
6308d65dee2SPeter Maydell    });
6318d65dee2SPeter Maydell}
6328d65dee2SPeter Maydell
63372a3e316SPeter Maydell- (void) switchSurface:(pixman_image_t *)image
6343e230dd2SCorentin Chary{
6355e00d3acSGerd Hoffmann    COCOA_DEBUG("QemuCocoaView: switchSurface\n");
6363e230dd2SCorentin Chary
63772a3e316SPeter Maydell    int w = pixman_image_get_width(image);
63872a3e316SPeter Maydell    int h = pixman_image_get_height(image);
639d3345a04SPeter Maydell
64091aa508dSAkihiko Odaki    if (w != screen.width || h != screen.height) {
641d3345a04SPeter Maydell        // Resize before we trigger the redraw, or we'll redraw at the wrong size
642d3345a04SPeter Maydell        COCOA_DEBUG("switchSurface: new size %d x %d\n", w, h);
643d3345a04SPeter Maydell        screen.width = w;
644d3345a04SPeter Maydell        screen.height = h;
64591aa508dSAkihiko Odaki        [self resizeWindow];
646fcb03de7SAkihiko Odaki        [self updateBounds];
647d3345a04SPeter Maydell    }
6488510d91eSPeter Maydell
6493e230dd2SCorentin Chary    // update screenBuffer
650c0ff29d1SAkihiko Odaki    if (pixman_image) {
6515588840fSPeter Maydell        pixman_image_unref(pixman_image);
6525588840fSPeter Maydell    }
6533e230dd2SCorentin Chary
6545588840fSPeter Maydell    pixman_image = image;
6553e230dd2SCorentin Chary}
6563e230dd2SCorentin Chary
657f844cdb9SGustavo Noronha Silva- (void) setFullGrab:(id)sender
658f844cdb9SGustavo Noronha Silva{
659f844cdb9SGustavo Noronha Silva    COCOA_DEBUG("QemuCocoaView: setFullGrab\n");
660f844cdb9SGustavo Noronha Silva
661f844cdb9SGustavo Noronha Silva    CGEventMask mask = CGEventMaskBit(kCGEventKeyDown) | CGEventMaskBit(kCGEventKeyUp) | CGEventMaskBit(kCGEventFlagsChanged);
662f844cdb9SGustavo Noronha Silva    eventsTap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault,
663f844cdb9SGustavo Noronha Silva                                 mask, handleTapEvent, self);
664f844cdb9SGustavo Noronha Silva    if (!eventsTap) {
665f844cdb9SGustavo Noronha Silva        warn_report("Could not create event tap, system key combos will not be captured.\n");
666f844cdb9SGustavo Noronha Silva        return;
667f844cdb9SGustavo Noronha Silva    } else {
668f844cdb9SGustavo Noronha Silva        COCOA_DEBUG("Global events tap created! Will capture system key combos.\n");
669f844cdb9SGustavo Noronha Silva    }
670f844cdb9SGustavo Noronha Silva
671f844cdb9SGustavo Noronha Silva    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
672f844cdb9SGustavo Noronha Silva    if (!runLoop) {
673f844cdb9SGustavo Noronha Silva        warn_report("Could not obtain current CF RunLoop, system key combos will not be captured.\n");
674f844cdb9SGustavo Noronha Silva        return;
675f844cdb9SGustavo Noronha Silva    }
676f844cdb9SGustavo Noronha Silva
677f844cdb9SGustavo Noronha Silva    CFRunLoopSourceRef tapEventsSrc = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventsTap, 0);
678f844cdb9SGustavo Noronha Silva    if (!tapEventsSrc ) {
679f844cdb9SGustavo Noronha Silva        warn_report("Could not obtain current CF RunLoop, system key combos will not be captured.\n");
680f844cdb9SGustavo Noronha Silva        return;
681f844cdb9SGustavo Noronha Silva    }
682f844cdb9SGustavo Noronha Silva
683f844cdb9SGustavo Noronha Silva    CFRunLoopAddSource(runLoop, tapEventsSrc, kCFRunLoopDefaultMode);
684f844cdb9SGustavo Noronha Silva    CFRelease(tapEventsSrc);
685f844cdb9SGustavo Noronha Silva}
686f844cdb9SGustavo Noronha Silva
6876d73bb64SAkihiko Odaki- (void) toggleKey: (int)keycode {
6886d73bb64SAkihiko Odaki    qkbd_state_key_event(kbd, keycode, !qkbd_state_key_get(kbd, keycode));
689af8862b2SIan McKellar via Qemu-devel}
690af8862b2SIan McKellar via Qemu-devel
6919c3a418eSJohn Arbuckle// Does the work of sending input to the monitor
6929c3a418eSJohn Arbuckle- (void) handleMonitorInput:(NSEvent *)event
6939c3a418eSJohn Arbuckle{
6949c3a418eSJohn Arbuckle    int keysym = 0;
6959c3a418eSJohn Arbuckle    int control_key = 0;
6969c3a418eSJohn Arbuckle
6979c3a418eSJohn Arbuckle    // if the control key is down
6989c3a418eSJohn Arbuckle    if ([event modifierFlags] & NSEventModifierFlagControl) {
6999c3a418eSJohn Arbuckle        control_key = 1;
7009c3a418eSJohn Arbuckle    }
7019c3a418eSJohn Arbuckle
7029c3a418eSJohn Arbuckle    /* translates Macintosh keycodes to QEMU's keysym */
7039c3a418eSJohn Arbuckle
7049459262dSPhilippe Mathieu-Daudé    static const int without_control_translation[] = {
7059c3a418eSJohn Arbuckle        [0 ... 0xff] = 0,   // invalid key
7069c3a418eSJohn Arbuckle
7079c3a418eSJohn Arbuckle        [kVK_UpArrow]       = QEMU_KEY_UP,
7089c3a418eSJohn Arbuckle        [kVK_DownArrow]     = QEMU_KEY_DOWN,
7099c3a418eSJohn Arbuckle        [kVK_RightArrow]    = QEMU_KEY_RIGHT,
7109c3a418eSJohn Arbuckle        [kVK_LeftArrow]     = QEMU_KEY_LEFT,
7119c3a418eSJohn Arbuckle        [kVK_Home]          = QEMU_KEY_HOME,
7129c3a418eSJohn Arbuckle        [kVK_End]           = QEMU_KEY_END,
7139c3a418eSJohn Arbuckle        [kVK_PageUp]        = QEMU_KEY_PAGEUP,
7149c3a418eSJohn Arbuckle        [kVK_PageDown]      = QEMU_KEY_PAGEDOWN,
7159c3a418eSJohn Arbuckle        [kVK_ForwardDelete] = QEMU_KEY_DELETE,
7169c3a418eSJohn Arbuckle        [kVK_Delete]        = QEMU_KEY_BACKSPACE,
7179c3a418eSJohn Arbuckle    };
7189c3a418eSJohn Arbuckle
7199459262dSPhilippe Mathieu-Daudé    static const int with_control_translation[] = {
7209c3a418eSJohn Arbuckle        [0 ... 0xff] = 0,   // invalid key
7219c3a418eSJohn Arbuckle
7229c3a418eSJohn Arbuckle        [kVK_UpArrow]       = QEMU_KEY_CTRL_UP,
7239c3a418eSJohn Arbuckle        [kVK_DownArrow]     = QEMU_KEY_CTRL_DOWN,
7249c3a418eSJohn Arbuckle        [kVK_RightArrow]    = QEMU_KEY_CTRL_RIGHT,
7259c3a418eSJohn Arbuckle        [kVK_LeftArrow]     = QEMU_KEY_CTRL_LEFT,
7269c3a418eSJohn Arbuckle        [kVK_Home]          = QEMU_KEY_CTRL_HOME,
7279c3a418eSJohn Arbuckle        [kVK_End]           = QEMU_KEY_CTRL_END,
7289c3a418eSJohn Arbuckle        [kVK_PageUp]        = QEMU_KEY_CTRL_PAGEUP,
7299c3a418eSJohn Arbuckle        [kVK_PageDown]      = QEMU_KEY_CTRL_PAGEDOWN,
7309c3a418eSJohn Arbuckle    };
7319c3a418eSJohn Arbuckle
7329c3a418eSJohn Arbuckle    if (control_key != 0) { /* If the control key is being used */
7339c3a418eSJohn Arbuckle        if ([event keyCode] < ARRAY_SIZE(with_control_translation)) {
7349c3a418eSJohn Arbuckle            keysym = with_control_translation[[event keyCode]];
7359c3a418eSJohn Arbuckle        }
7369c3a418eSJohn Arbuckle    } else {
7379c3a418eSJohn Arbuckle        if ([event keyCode] < ARRAY_SIZE(without_control_translation)) {
7389c3a418eSJohn Arbuckle            keysym = without_control_translation[[event keyCode]];
7399c3a418eSJohn Arbuckle        }
7409c3a418eSJohn Arbuckle    }
7419c3a418eSJohn Arbuckle
7429c3a418eSJohn Arbuckle    // if not a key that needs translating
7439c3a418eSJohn Arbuckle    if (keysym == 0) {
7449c3a418eSJohn Arbuckle        NSString *ks = [event characters];
7459c3a418eSJohn Arbuckle        if ([ks length] > 0) {
7469c3a418eSJohn Arbuckle            keysym = [ks characterAtIndex:0];
7479c3a418eSJohn Arbuckle        }
7489c3a418eSJohn Arbuckle    }
7499c3a418eSJohn Arbuckle
7509c3a418eSJohn Arbuckle    if (keysym) {
751ca3de7b5SAkihiko Odaki        QemuTextConsole *con = QEMU_TEXT_CONSOLE(dcl.con);
752ca3de7b5SAkihiko Odaki        qemu_text_console_put_keysym(con, keysym);
7539c3a418eSJohn Arbuckle    }
7549c3a418eSJohn Arbuckle}
7559c3a418eSJohn Arbuckle
75660105d7aSPeter Maydell- (bool) handleEvent:(NSEvent *)event
7573e230dd2SCorentin Chary{
758195801d7SStefan Hajnoczi    return bool_with_bql(^{
75960105d7aSPeter Maydell        return [self handleEventLocked:event];
76031819e95SPeter Maydell    });
76131819e95SPeter Maydell}
7623e230dd2SCorentin Chary
76360105d7aSPeter Maydell- (bool) handleEventLocked:(NSEvent *)event
76431819e95SPeter Maydell{
76560105d7aSPeter Maydell    /* Return true if we handled the event, false if it should be given to OSX */
76631819e95SPeter Maydell    COCOA_DEBUG("QemuCocoaView: handleEvent\n");
7670f7be47aSAkihiko Odaki    InputButton button;
768af8862b2SIan McKellar via Qemu-devel    int keycode = 0;
7696d73bb64SAkihiko Odaki    NSUInteger modifiers = [event modifierFlags];
7706d73bb64SAkihiko Odaki
771ad7f2f8eSAkihiko Odaki    /*
772ad7f2f8eSAkihiko Odaki     * Check -[NSEvent modifierFlags] here.
773ad7f2f8eSAkihiko Odaki     *
774ad7f2f8eSAkihiko Odaki     * There is a NSEventType for an event notifying the change of
775ad7f2f8eSAkihiko Odaki     * -[NSEvent modifierFlags], NSEventTypeFlagsChanged but these operations
776ad7f2f8eSAkihiko Odaki     * are performed for any events because a modifier state may change while
777ad7f2f8eSAkihiko Odaki     * the application is inactive (i.e. no events fire) and we don't want to
778ad7f2f8eSAkihiko Odaki     * wait for another modifier state change to detect such a change.
779ad7f2f8eSAkihiko Odaki     *
780ad7f2f8eSAkihiko Odaki     * NSEventModifierFlagCapsLock requires a special treatment. The other flags
781ad7f2f8eSAkihiko Odaki     * are handled in similar manners.
782ad7f2f8eSAkihiko Odaki     *
783ad7f2f8eSAkihiko Odaki     * NSEventModifierFlagCapsLock
784ad7f2f8eSAkihiko Odaki     * ---------------------------
785ad7f2f8eSAkihiko Odaki     *
786ad7f2f8eSAkihiko Odaki     * If CapsLock state is changed, "up" and "down" events will be fired in
787ad7f2f8eSAkihiko Odaki     * sequence, effectively updates CapsLock state on the guest.
788ad7f2f8eSAkihiko Odaki     *
789ad7f2f8eSAkihiko Odaki     * The other flags
790ad7f2f8eSAkihiko Odaki     * ---------------
791ad7f2f8eSAkihiko Odaki     *
792ad7f2f8eSAkihiko Odaki     * If a flag is not set, fire "up" events for all keys which correspond to
793ad7f2f8eSAkihiko Odaki     * the flag. Note that "down" events are not fired here because the flags
794ad7f2f8eSAkihiko Odaki     * checked here do not tell what exact keys are down.
795ad7f2f8eSAkihiko Odaki     *
796ad7f2f8eSAkihiko Odaki     * If one of the keys corresponding to a flag is down, we rely on
797ad7f2f8eSAkihiko Odaki     * -[NSEvent keyCode] of an event whose -[NSEvent type] is
798ad7f2f8eSAkihiko Odaki     * NSEventTypeFlagsChanged to know the exact key which is down, which has
799ad7f2f8eSAkihiko Odaki     * the following two downsides:
800ad7f2f8eSAkihiko Odaki     * - It does not work when the application is inactive as described above.
801ad7f2f8eSAkihiko Odaki     * - It malfactions *after* the modifier state is changed while the
802ad7f2f8eSAkihiko Odaki     *   application is inactive. It is because -[NSEvent keyCode] does not tell
803ad7f2f8eSAkihiko Odaki     *   if the key is up or down, and requires to infer the current state from
804ad7f2f8eSAkihiko Odaki     *   the previous state. It is still possible to fix such a malfanction by
805ad7f2f8eSAkihiko Odaki     *   completely leaving your hands from the keyboard, which hopefully makes
806ad7f2f8eSAkihiko Odaki     *   this implementation usable enough.
807ad7f2f8eSAkihiko Odaki     */
8086d73bb64SAkihiko Odaki    if (!!(modifiers & NSEventModifierFlagCapsLock) !=
8096d73bb64SAkihiko Odaki        qkbd_state_modifier_get(kbd, QKBD_MOD_CAPSLOCK)) {
8106d73bb64SAkihiko Odaki        qkbd_state_key_event(kbd, Q_KEY_CODE_CAPS_LOCK, true);
8116d73bb64SAkihiko Odaki        qkbd_state_key_event(kbd, Q_KEY_CODE_CAPS_LOCK, false);
8126d73bb64SAkihiko Odaki    }
8136d73bb64SAkihiko Odaki
8146d73bb64SAkihiko Odaki    if (!(modifiers & NSEventModifierFlagShift)) {
8156d73bb64SAkihiko Odaki        qkbd_state_key_event(kbd, Q_KEY_CODE_SHIFT, false);
8166d73bb64SAkihiko Odaki        qkbd_state_key_event(kbd, Q_KEY_CODE_SHIFT_R, false);
8176d73bb64SAkihiko Odaki    }
8186d73bb64SAkihiko Odaki    if (!(modifiers & NSEventModifierFlagControl)) {
8196d73bb64SAkihiko Odaki        qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL, false);
8206d73bb64SAkihiko Odaki        qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL_R, false);
8216d73bb64SAkihiko Odaki    }
8226d73bb64SAkihiko Odaki    if (!(modifiers & NSEventModifierFlagOption)) {
8234797adceSGustavo Noronha Silva        if (swap_opt_cmd) {
8244797adceSGustavo Noronha Silva            qkbd_state_key_event(kbd, Q_KEY_CODE_META_L, false);
8254797adceSGustavo Noronha Silva            qkbd_state_key_event(kbd, Q_KEY_CODE_META_R, false);
8264797adceSGustavo Noronha Silva        } else {
8276d73bb64SAkihiko Odaki            qkbd_state_key_event(kbd, Q_KEY_CODE_ALT, false);
8286d73bb64SAkihiko Odaki            qkbd_state_key_event(kbd, Q_KEY_CODE_ALT_R, false);
8296d73bb64SAkihiko Odaki        }
8304797adceSGustavo Noronha Silva    }
8316d73bb64SAkihiko Odaki    if (!(modifiers & NSEventModifierFlagCommand)) {
8324797adceSGustavo Noronha Silva        if (swap_opt_cmd) {
8334797adceSGustavo Noronha Silva            qkbd_state_key_event(kbd, Q_KEY_CODE_ALT, false);
8344797adceSGustavo Noronha Silva            qkbd_state_key_event(kbd, Q_KEY_CODE_ALT_R, false);
8354797adceSGustavo Noronha Silva        } else {
8366d73bb64SAkihiko Odaki            qkbd_state_key_event(kbd, Q_KEY_CODE_META_L, false);
8376d73bb64SAkihiko Odaki            qkbd_state_key_event(kbd, Q_KEY_CODE_META_R, false);
8386d73bb64SAkihiko Odaki        }
8394797adceSGustavo Noronha Silva    }
8403e230dd2SCorentin Chary
8413e230dd2SCorentin Chary    switch ([event type]) {
8424ba967adSBrendan Shanks        case NSEventTypeFlagsChanged:
8436d73bb64SAkihiko Odaki            switch ([event keyCode]) {
8446d73bb64SAkihiko Odaki                case kVK_Shift:
8456d73bb64SAkihiko Odaki                    if (!!(modifiers & NSEventModifierFlagShift)) {
8466d73bb64SAkihiko Odaki                        [self toggleKey:Q_KEY_CODE_SHIFT];
8476d73bb64SAkihiko Odaki                    }
8486d73bb64SAkihiko Odaki                    break;
849af8862b2SIan McKellar via Qemu-devel
8506d73bb64SAkihiko Odaki                case kVK_RightShift:
8516d73bb64SAkihiko Odaki                    if (!!(modifiers & NSEventModifierFlagShift)) {
8526d73bb64SAkihiko Odaki                        [self toggleKey:Q_KEY_CODE_SHIFT_R];
8536d73bb64SAkihiko Odaki                    }
8546d73bb64SAkihiko Odaki                    break;
855af8862b2SIan McKellar via Qemu-devel
8566d73bb64SAkihiko Odaki                case kVK_Control:
8576d73bb64SAkihiko Odaki                    if (!!(modifiers & NSEventModifierFlagControl)) {
8586d73bb64SAkihiko Odaki                        [self toggleKey:Q_KEY_CODE_CTRL];
859af8862b2SIan McKellar via Qemu-devel                    }
8606d73bb64SAkihiko Odaki                    break;
8618895919aSPeter Maydell
8626d73bb64SAkihiko Odaki                case kVK_RightControl:
8636d73bb64SAkihiko Odaki                    if (!!(modifiers & NSEventModifierFlagControl)) {
8646d73bb64SAkihiko Odaki                        [self toggleKey:Q_KEY_CODE_CTRL_R];
8656d73bb64SAkihiko Odaki                    }
8666d73bb64SAkihiko Odaki                    break;
8676d73bb64SAkihiko Odaki
8686d73bb64SAkihiko Odaki                case kVK_Option:
8696d73bb64SAkihiko Odaki                    if (!!(modifiers & NSEventModifierFlagOption)) {
8704797adceSGustavo Noronha Silva                        if (swap_opt_cmd) {
8714797adceSGustavo Noronha Silva                            [self toggleKey:Q_KEY_CODE_META_L];
8724797adceSGustavo Noronha Silva                        } else {
8736d73bb64SAkihiko Odaki                            [self toggleKey:Q_KEY_CODE_ALT];
8746d73bb64SAkihiko Odaki                        }
8754797adceSGustavo Noronha Silva                    }
8766d73bb64SAkihiko Odaki                    break;
8776d73bb64SAkihiko Odaki
8786d73bb64SAkihiko Odaki                case kVK_RightOption:
8796d73bb64SAkihiko Odaki                    if (!!(modifiers & NSEventModifierFlagOption)) {
8804797adceSGustavo Noronha Silva                        if (swap_opt_cmd) {
8814797adceSGustavo Noronha Silva                            [self toggleKey:Q_KEY_CODE_META_R];
8824797adceSGustavo Noronha Silva                        } else {
8836d73bb64SAkihiko Odaki                            [self toggleKey:Q_KEY_CODE_ALT_R];
8846d73bb64SAkihiko Odaki                        }
8854797adceSGustavo Noronha Silva                    }
8866d73bb64SAkihiko Odaki                    break;
8876d73bb64SAkihiko Odaki
8888895919aSPeter Maydell                /* Don't pass command key changes to guest unless mouse is grabbed */
8896d73bb64SAkihiko Odaki                case kVK_Command:
8906d73bb64SAkihiko Odaki                    if (isMouseGrabbed &&
891d6b6dea7SAkihiko Odaki                        !!(modifiers & NSEventModifierFlagCommand) &&
892d6b6dea7SAkihiko Odaki                        left_command_key_enabled) {
8934797adceSGustavo Noronha Silva                        if (swap_opt_cmd) {
8944797adceSGustavo Noronha Silva                            [self toggleKey:Q_KEY_CODE_ALT];
8954797adceSGustavo Noronha Silva                        } else {
8966d73bb64SAkihiko Odaki                            [self toggleKey:Q_KEY_CODE_META_L];
8978895919aSPeter Maydell                        }
8984797adceSGustavo Noronha Silva                    }
8996d73bb64SAkihiko Odaki                    break;
9008895919aSPeter Maydell
9016d73bb64SAkihiko Odaki                case kVK_RightCommand:
9026d73bb64SAkihiko Odaki                    if (isMouseGrabbed &&
9036d73bb64SAkihiko Odaki                        !!(modifiers & NSEventModifierFlagCommand)) {
9044797adceSGustavo Noronha Silva                        if (swap_opt_cmd) {
9054797adceSGustavo Noronha Silva                            [self toggleKey:Q_KEY_CODE_ALT_R];
9064797adceSGustavo Noronha Silva                        } else {
9076d73bb64SAkihiko Odaki                            [self toggleKey:Q_KEY_CODE_META_R];
9083e230dd2SCorentin Chary                        }
9094797adceSGustavo Noronha Silva                    }
9106d73bb64SAkihiko Odaki                    break;
9113e230dd2SCorentin Chary            }
9120f7be47aSAkihiko Odaki            return true;
9134ba967adSBrendan Shanks        case NSEventTypeKeyDown:
9148895919aSPeter Maydell            keycode = cocoa_keycode_to_qemu([event keyCode]);
9153e230dd2SCorentin Chary
9168895919aSPeter Maydell            // forward command key combos to the host UI unless the mouse is grabbed
9174ba967adSBrendan Shanks            if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) {
91860105d7aSPeter Maydell                return false;
9193e230dd2SCorentin Chary            }
9203e230dd2SCorentin Chary
9213e230dd2SCorentin Chary            // default
9223e230dd2SCorentin Chary
9235929e36cSJohn Arbuckle            // handle control + alt Key Combos (ctrl+alt+[1..9,g] is reserved for QEMU)
9244ba967adSBrendan Shanks            if (([event modifierFlags] & NSEventModifierFlagControl) && ([event modifierFlags] & NSEventModifierFlagOption)) {
9255929e36cSJohn Arbuckle                NSString *keychar = [event charactersIgnoringModifiers];
9265929e36cSJohn Arbuckle                if ([keychar length] == 1) {
9275929e36cSJohn Arbuckle                    char key = [keychar characterAtIndex:0];
9285929e36cSJohn Arbuckle                    switch (key) {
9293e230dd2SCorentin Chary
9303e230dd2SCorentin Chary                        // enable graphic console
9315929e36cSJohn Arbuckle                        case '1' ... '9':
932ca3de7b5SAkihiko Odaki                            [self selectConsoleLocked:key - '0' - 1]; /* ascii math */
93360105d7aSPeter Maydell                            return true;
9345929e36cSJohn Arbuckle
9355929e36cSJohn Arbuckle                        // release the mouse grab
9365929e36cSJohn Arbuckle                        case 'g':
9375929e36cSJohn Arbuckle                            [self ungrabMouse];
93860105d7aSPeter Maydell                            return true;
9395929e36cSJohn Arbuckle                    }
9403e230dd2SCorentin Chary                }
941ef2088f9SPeter Maydell            }
9423e230dd2SCorentin Chary
943ca3de7b5SAkihiko Odaki            if (qemu_console_is_graphic(dcl.con)) {
9446d73bb64SAkihiko Odaki                qkbd_state_key_event(kbd, keycode, true);
9453e230dd2SCorentin Chary            } else {
9469c3a418eSJohn Arbuckle                [self handleMonitorInput: event];
9473e230dd2SCorentin Chary            }
9480f7be47aSAkihiko Odaki            return true;
9494ba967adSBrendan Shanks        case NSEventTypeKeyUp:
9503e230dd2SCorentin Chary            keycode = cocoa_keycode_to_qemu([event keyCode]);
9518895919aSPeter Maydell
9528895919aSPeter Maydell            // don't pass the guest a spurious key-up if we treated this
9538895919aSPeter Maydell            // command-key combo as a host UI action
9544ba967adSBrendan Shanks            if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) {
95560105d7aSPeter Maydell                return true;
9568895919aSPeter Maydell            }
9578895919aSPeter Maydell
958ca3de7b5SAkihiko Odaki            if (qemu_console_is_graphic(dcl.con)) {
9596d73bb64SAkihiko Odaki                qkbd_state_key_event(kbd, keycode, false);
9603e230dd2SCorentin Chary            }
9610f7be47aSAkihiko Odaki            return true;
9624ba967adSBrendan Shanks        case NSEventTypeScrollWheel:
963ae7313e7SJohn Arbuckle            /*
964ae7313e7SJohn Arbuckle             * Send wheel events to the guest regardless of window focus.
965ae7313e7SJohn Arbuckle             * This is in-line with standard Mac OS X UI behaviour.
966ae7313e7SJohn Arbuckle             */
967ae7313e7SJohn Arbuckle
9680f7be47aSAkihiko Odaki            /* Determine if this is a scroll up or scroll down event */
9690f7be47aSAkihiko Odaki            if ([event deltaY] != 0) {
9700f7be47aSAkihiko Odaki                button = ([event deltaY] > 0) ?
9710f7be47aSAkihiko Odaki                    INPUT_BUTTON_WHEEL_UP : INPUT_BUTTON_WHEEL_DOWN;
9720f7be47aSAkihiko Odaki            } else if ([event deltaX] != 0) {
9730f7be47aSAkihiko Odaki                button = ([event deltaX] > 0) ?
9740f7be47aSAkihiko Odaki                    INPUT_BUTTON_WHEEL_LEFT : INPUT_BUTTON_WHEEL_RIGHT;
9750f7be47aSAkihiko Odaki            } else {
976dc3c89d6SJohn Arbuckle                /*
977d70a5de4SDmitry Petrov                 * We shouldn't have got a scroll event when deltaY and delta Y
978d70a5de4SDmitry Petrov                 * are zero, hence no harm in dropping the event
979dc3c89d6SJohn Arbuckle                 */
9800f7be47aSAkihiko Odaki                return true;
981d70a5de4SDmitry Petrov            }
982d70a5de4SDmitry Petrov
9830f7be47aSAkihiko Odaki            qemu_input_queue_btn(dcl.con, button, true);
984ae7313e7SJohn Arbuckle            qemu_input_event_sync();
9850f7be47aSAkihiko Odaki            qemu_input_queue_btn(dcl.con, button, false);
986ae7313e7SJohn Arbuckle            qemu_input_event_sync();
987d70a5de4SDmitry Petrov
9880f7be47aSAkihiko Odaki            return true;
9893e230dd2SCorentin Chary        default:
99060105d7aSPeter Maydell            return false;
9913e230dd2SCorentin Chary    }
992af4efbccSAkihiko Odaki}
993af4efbccSAkihiko Odaki
99491aa508dSAkihiko Odaki- (void) handleMouseEvent:(NSEvent *)event button:(InputButton)button down:(bool)down
995af4efbccSAkihiko Odaki{
996af4efbccSAkihiko Odaki    if (!isMouseGrabbed) {
99791aa508dSAkihiko Odaki        return;
998af4efbccSAkihiko Odaki    }
999af4efbccSAkihiko Odaki
100091aa508dSAkihiko Odaki    with_bql(^{
100191aa508dSAkihiko Odaki        qemu_input_queue_btn(dcl.con, button, down);
100291aa508dSAkihiko Odaki    });
100391aa508dSAkihiko Odaki
100491aa508dSAkihiko Odaki    [self handleMouseEvent:event];
100591aa508dSAkihiko Odaki}
100691aa508dSAkihiko Odaki
100791aa508dSAkihiko Odaki- (void) handleMouseEvent:(NSEvent *)event
100891aa508dSAkihiko Odaki{
100991aa508dSAkihiko Odaki    if (!isMouseGrabbed) {
101091aa508dSAkihiko Odaki        return;
101191aa508dSAkihiko Odaki    }
101291aa508dSAkihiko Odaki
101391aa508dSAkihiko Odaki    with_bql(^{
1014f61c387eSPeter Maydell        if (isAbsoluteEnabled) {
101591aa508dSAkihiko Odaki            CGFloat d = (CGFloat)screen.height / [self frame].size.height;
101691aa508dSAkihiko Odaki            NSPoint p = [event locationInWindow];
1017af4efbccSAkihiko Odaki
101891aa508dSAkihiko Odaki            /* Note that the origin for Cocoa mouse coords is bottom left, not top left. */
101991aa508dSAkihiko Odaki            qemu_input_queue_abs(dcl.con, INPUT_AXIS_X, p.x * d, 0, screen.width);
102091aa508dSAkihiko Odaki            qemu_input_queue_abs(dcl.con, INPUT_AXIS_Y, screen.height - p.y * d, 0, screen.height);
1021f61c387eSPeter Maydell        } else {
102291aa508dSAkihiko Odaki            qemu_input_queue_rel(dcl.con, INPUT_AXIS_X, [event deltaX]);
102391aa508dSAkihiko Odaki            qemu_input_queue_rel(dcl.con, INPUT_AXIS_Y, [event deltaY]);
1024f61c387eSPeter Maydell        }
1025af4efbccSAkihiko Odaki
102621bae11aSGerd Hoffmann        qemu_input_event_sync();
102791aa508dSAkihiko Odaki    });
102891aa508dSAkihiko Odaki}
1029af4efbccSAkihiko Odaki
103091aa508dSAkihiko Odaki- (void) mouseExited:(NSEvent *)event
103191aa508dSAkihiko Odaki{
103291aa508dSAkihiko Odaki    if (isAbsoluteEnabled && isMouseGrabbed) {
103391aa508dSAkihiko Odaki        [self ungrabMouse];
103491aa508dSAkihiko Odaki    }
103591aa508dSAkihiko Odaki}
103691aa508dSAkihiko Odaki
103791aa508dSAkihiko Odaki- (void) mouseEntered:(NSEvent *)event
103891aa508dSAkihiko Odaki{
103991aa508dSAkihiko Odaki    if (isAbsoluteEnabled && !isMouseGrabbed) {
104091aa508dSAkihiko Odaki        [self grabMouse];
104191aa508dSAkihiko Odaki    }
104291aa508dSAkihiko Odaki}
104391aa508dSAkihiko Odaki
104491aa508dSAkihiko Odaki- (void) mouseMoved:(NSEvent *)event
104591aa508dSAkihiko Odaki{
104691aa508dSAkihiko Odaki    [self handleMouseEvent:event];
104791aa508dSAkihiko Odaki}
104891aa508dSAkihiko Odaki
104991aa508dSAkihiko Odaki- (void) mouseDown:(NSEvent *)event
105091aa508dSAkihiko Odaki{
105191aa508dSAkihiko Odaki    [self handleMouseEvent:event button:INPUT_BUTTON_LEFT down:true];
105291aa508dSAkihiko Odaki}
105391aa508dSAkihiko Odaki
105491aa508dSAkihiko Odaki- (void) rightMouseDown:(NSEvent *)event
105591aa508dSAkihiko Odaki{
105691aa508dSAkihiko Odaki    [self handleMouseEvent:event button:INPUT_BUTTON_RIGHT down:true];
105791aa508dSAkihiko Odaki}
105891aa508dSAkihiko Odaki
105991aa508dSAkihiko Odaki- (void) otherMouseDown:(NSEvent *)event
106091aa508dSAkihiko Odaki{
106191aa508dSAkihiko Odaki    [self handleMouseEvent:event button:INPUT_BUTTON_MIDDLE down:true];
106291aa508dSAkihiko Odaki}
106391aa508dSAkihiko Odaki
106491aa508dSAkihiko Odaki- (void) mouseDragged:(NSEvent *)event
106591aa508dSAkihiko Odaki{
106691aa508dSAkihiko Odaki    [self handleMouseEvent:event];
106791aa508dSAkihiko Odaki}
106891aa508dSAkihiko Odaki
106991aa508dSAkihiko Odaki- (void) rightMouseDragged:(NSEvent *)event
107091aa508dSAkihiko Odaki{
107191aa508dSAkihiko Odaki    [self handleMouseEvent:event];
107291aa508dSAkihiko Odaki}
107391aa508dSAkihiko Odaki
107491aa508dSAkihiko Odaki- (void) otherMouseDragged:(NSEvent *)event
107591aa508dSAkihiko Odaki{
107691aa508dSAkihiko Odaki    [self handleMouseEvent:event];
107791aa508dSAkihiko Odaki}
107891aa508dSAkihiko Odaki
107991aa508dSAkihiko Odaki- (void) mouseUp:(NSEvent *)event
108091aa508dSAkihiko Odaki{
108191aa508dSAkihiko Odaki    if (!isMouseGrabbed) {
108291aa508dSAkihiko Odaki        [self grabMouse];
108391aa508dSAkihiko Odaki    }
108491aa508dSAkihiko Odaki
108591aa508dSAkihiko Odaki    [self handleMouseEvent:event button:INPUT_BUTTON_LEFT down:false];
108691aa508dSAkihiko Odaki}
108791aa508dSAkihiko Odaki
108891aa508dSAkihiko Odaki- (void) rightMouseUp:(NSEvent *)event
108991aa508dSAkihiko Odaki{
109091aa508dSAkihiko Odaki    [self handleMouseEvent:event button:INPUT_BUTTON_RIGHT down:false];
109191aa508dSAkihiko Odaki}
109291aa508dSAkihiko Odaki
109391aa508dSAkihiko Odaki- (void) otherMouseUp:(NSEvent *)event
109491aa508dSAkihiko Odaki{
109591aa508dSAkihiko Odaki    [self handleMouseEvent:event button:INPUT_BUTTON_MIDDLE down:false];
10963e230dd2SCorentin Chary}
10973e230dd2SCorentin Chary
10983e230dd2SCorentin Chary- (void) grabMouse
10993e230dd2SCorentin Chary{
11003e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaView: grabMouse\n");
11013e230dd2SCorentin Chary
11023e230dd2SCorentin Chary    if (qemu_name)
11030c35886eSAkihiko Odaki        [[self window] setTitle:[NSString stringWithFormat:@"QEMU %s - (Press  " UC_CTRL_KEY " " UC_ALT_KEY " G  to release Mouse)", qemu_name]];
11043e230dd2SCorentin Chary    else
11050c35886eSAkihiko Odaki        [[self window] setTitle:@"QEMU - (Press  " UC_CTRL_KEY " " UC_ALT_KEY " G  to release Mouse)"];
110613aefd30SPeter Maydell    [self hideCursor];
1107d1929069SAkihiko Odaki    CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled);
110849b9bd4dSPeter Maydell    isMouseGrabbed = TRUE; // while isMouseGrabbed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:]
11093e230dd2SCorentin Chary}
11103e230dd2SCorentin Chary
11113e230dd2SCorentin Chary- (void) ungrabMouse
11123e230dd2SCorentin Chary{
11133e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaView: ungrabMouse\n");
11143e230dd2SCorentin Chary
11153e230dd2SCorentin Chary    if (qemu_name)
11160c35886eSAkihiko Odaki        [[self window] setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]];
11173e230dd2SCorentin Chary    else
11180c35886eSAkihiko Odaki        [[self window] setTitle:@"QEMU"];
111913aefd30SPeter Maydell    [self unhideCursor];
11203e230dd2SCorentin Chary    CGAssociateMouseAndMouseCursorPosition(TRUE);
112149b9bd4dSPeter Maydell    isMouseGrabbed = FALSE;
112291aa508dSAkihiko Odaki    [self raiseAllButtons];
11233e230dd2SCorentin Chary}
11243e230dd2SCorentin Chary
1125d1929069SAkihiko Odaki- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled {
1126d1929069SAkihiko Odaki    isAbsoluteEnabled = tIsAbsoluteEnabled;
1127d1929069SAkihiko Odaki    if (isMouseGrabbed) {
1128d1929069SAkihiko Odaki        CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled);
1129d1929069SAkihiko Odaki    }
1130d1929069SAkihiko Odaki}
113149b9bd4dSPeter Maydell- (BOOL) isMouseGrabbed {return isMouseGrabbed;}
11323e230dd2SCorentin Chary- (BOOL) isAbsoluteEnabled {return isAbsoluteEnabled;}
11333e230dd2SCorentin Chary- (QEMUScreen) gscreen {return screen;}
11343b178b71SJohn Arbuckle
11353b178b71SJohn Arbuckle/*
11363b178b71SJohn Arbuckle * Makes the target think all down keys are being released.
11373b178b71SJohn Arbuckle * This prevents a stuck key problem, since we will not see
11383b178b71SJohn Arbuckle * key up events for those keys after we have lost focus.
11393b178b71SJohn Arbuckle */
11403b178b71SJohn Arbuckle- (void) raiseAllKeys
11413b178b71SJohn Arbuckle{
1142195801d7SStefan Hajnoczi    with_bql(^{
11436d73bb64SAkihiko Odaki        qkbd_state_lift_all_keys(kbd);
114431819e95SPeter Maydell    });
11453b178b71SJohn Arbuckle}
114691aa508dSAkihiko Odaki
114791aa508dSAkihiko Odaki- (void) raiseAllButtons
114891aa508dSAkihiko Odaki{
114991aa508dSAkihiko Odaki    with_bql(^{
115091aa508dSAkihiko Odaki        qemu_input_queue_btn(dcl.con, INPUT_BUTTON_LEFT, false);
115191aa508dSAkihiko Odaki        qemu_input_queue_btn(dcl.con, INPUT_BUTTON_RIGHT, false);
115291aa508dSAkihiko Odaki        qemu_input_queue_btn(dcl.con, INPUT_BUTTON_MIDDLE, false);
115391aa508dSAkihiko Odaki    });
115491aa508dSAkihiko Odaki}
11553e230dd2SCorentin Chary@end
11563e230dd2SCorentin Chary
11573e230dd2SCorentin Chary
11583e230dd2SCorentin Chary
11593e230dd2SCorentin Chary/*
11603e230dd2SCorentin Chary ------------------------------------------------------
11613e230dd2SCorentin Chary    QemuCocoaAppController
11623e230dd2SCorentin Chary ------------------------------------------------------
11633e230dd2SCorentin Chary*/
11643e230dd2SCorentin Chary@interface QemuCocoaAppController : NSObject
1165d9bc14f6SJohn Arbuckle                                       <NSWindowDelegate, NSApplicationDelegate>
11663e230dd2SCorentin Chary{
11673e230dd2SCorentin Chary}
11685d1b2eefSProgrammingkid- (void)doToggleFullScreen:(id)sender;
11693e230dd2SCorentin Chary- (void)showQEMUDoc:(id)sender;
11705d1b2eefSProgrammingkid- (void)zoomToFit:(id) sender;
1171b4c6a112SProgrammingkid- (void)displayConsole:(id)sender;
11728524f1c7SJohn Arbuckle- (void)pauseQEMU:(id)sender;
11738524f1c7SJohn Arbuckle- (void)resumeQEMU:(id)sender;
11748524f1c7SJohn Arbuckle- (void)displayPause;
11758524f1c7SJohn Arbuckle- (void)removePause;
117627074614SJohn Arbuckle- (void)restartQEMU:(id)sender;
117727074614SJohn Arbuckle- (void)powerDownQEMU:(id)sender;
1178693a3e01SJohn Arbuckle- (void)ejectDeviceMedia:(id)sender;
1179693a3e01SJohn Arbuckle- (void)changeDeviceMedia:(id)sender;
1180d9bc14f6SJohn Arbuckle- (BOOL)verifyQuit;
1181f4747900SJohn Arbuckle- (void)openDocumentation:(NSString *)filename;
11829e8204b1SProgrammingkid- (IBAction) do_about_menu_item: (id) sender;
1183e47ec1a9SJohn Arbuckle- (void)adjustSpeed:(id)sender;
11843e230dd2SCorentin Chary@end
11853e230dd2SCorentin Chary
11863e230dd2SCorentin Chary@implementation QemuCocoaAppController
11873e230dd2SCorentin Chary- (id) init
11883e230dd2SCorentin Chary{
11890c35886eSAkihiko Odaki    NSWindow *window;
11900c35886eSAkihiko Odaki
11913e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaAppController: init\n");
11923e230dd2SCorentin Chary
11933e230dd2SCorentin Chary    self = [super init];
11943e230dd2SCorentin Chary    if (self) {
11953e230dd2SCorentin Chary
11963e230dd2SCorentin Chary        // create a view and add it to the window
11973e230dd2SCorentin Chary        cocoaView = [[QemuCocoaView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 640.0, 480.0)];
11983e230dd2SCorentin Chary        if(!cocoaView) {
11994313739aSAkihiko Odaki            error_report("(cocoa) can't create a view");
12003e230dd2SCorentin Chary            exit(1);
12013e230dd2SCorentin Chary        }
12023e230dd2SCorentin Chary
12033e230dd2SCorentin Chary        // create a window
12040c35886eSAkihiko Odaki        window = [[NSWindow alloc] initWithContentRect:[cocoaView frame]
12054ba967adSBrendan Shanks            styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskClosable
12063e230dd2SCorentin Chary            backing:NSBackingStoreBuffered defer:NO];
12070c35886eSAkihiko Odaki        if(!window) {
12084313739aSAkihiko Odaki            error_report("(cocoa) can't create window");
12093e230dd2SCorentin Chary            exit(1);
12103e230dd2SCorentin Chary        }
12110c35886eSAkihiko Odaki        [window setAcceptsMouseMovedEvents:YES];
12120c35886eSAkihiko Odaki        [window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
12130c35886eSAkihiko Odaki        [window setTitle:qemu_name ? [NSString stringWithFormat:@"QEMU %s", qemu_name] : @"QEMU"];
12140c35886eSAkihiko Odaki        [window setContentView:cocoaView];
12150c35886eSAkihiko Odaki        [window makeKeyAndOrderFront:self];
12160c35886eSAkihiko Odaki        [window center];
12170c35886eSAkihiko Odaki        [window setDelegate: self];
12188524f1c7SJohn Arbuckle
12198524f1c7SJohn Arbuckle        /* Used for displaying pause on the screen */
12208524f1c7SJohn Arbuckle        pauseLabel = [NSTextField new];
12218524f1c7SJohn Arbuckle        [pauseLabel setBezeled:YES];
12228524f1c7SJohn Arbuckle        [pauseLabel setDrawsBackground:YES];
12238524f1c7SJohn Arbuckle        [pauseLabel setBackgroundColor: [NSColor whiteColor]];
12248524f1c7SJohn Arbuckle        [pauseLabel setEditable:NO];
12258524f1c7SJohn Arbuckle        [pauseLabel setSelectable:NO];
12268524f1c7SJohn Arbuckle        [pauseLabel setStringValue: @"Paused"];
12278524f1c7SJohn Arbuckle        [pauseLabel setFont: [NSFont fontWithName: @"Helvetica" size: 90]];
12288524f1c7SJohn Arbuckle        [pauseLabel setTextColor: [NSColor blackColor]];
12298524f1c7SJohn Arbuckle        [pauseLabel sizeToFit];
12303e230dd2SCorentin Chary    }
12313e230dd2SCorentin Chary    return self;
12323e230dd2SCorentin Chary}
12333e230dd2SCorentin Chary
12343e230dd2SCorentin Chary- (void) dealloc
12353e230dd2SCorentin Chary{
12363e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaAppController: dealloc\n");
12373e230dd2SCorentin Chary
12383e230dd2SCorentin Chary    if (cocoaView)
12393e230dd2SCorentin Chary        [cocoaView release];
12403e230dd2SCorentin Chary    [super dealloc];
12413e230dd2SCorentin Chary}
12423e230dd2SCorentin Chary
12433e230dd2SCorentin Chary- (void)applicationDidFinishLaunching: (NSNotification *) note
12443e230dd2SCorentin Chary{
12453e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaAppController: applicationDidFinishLaunching\n");
1246dff742adSHikaru Nishida    allow_events = true;
12473e230dd2SCorentin Chary}
12483e230dd2SCorentin Chary
12493e230dd2SCorentin Chary- (void)applicationWillTerminate:(NSNotification *)aNotification
12503e230dd2SCorentin Chary{
12513e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n");
12523e230dd2SCorentin Chary
1253195801d7SStefan Hajnoczi    with_bql(^{
12542910abd6SAkihiko Odaki        shutdown_action = SHUTDOWN_ACTION_POWEROFF;
1255cf83f140SEric Blake        qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
12562910abd6SAkihiko Odaki    });
125740c01937SAkihiko Odaki
125840c01937SAkihiko Odaki    /*
125940c01937SAkihiko Odaki     * Sleep here, because returning will cause OSX to kill us
126040c01937SAkihiko Odaki     * immediately; the QEMU main loop will handle the shutdown
126140c01937SAkihiko Odaki     * request and terminate the process.
126240c01937SAkihiko Odaki     */
126340c01937SAkihiko Odaki    [NSThread sleepForTimeInterval:INFINITY];
12643e230dd2SCorentin Chary}
12653e230dd2SCorentin Chary
12663e230dd2SCorentin Chary- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
12673e230dd2SCorentin Chary{
12683e230dd2SCorentin Chary    return YES;
12693e230dd2SCorentin Chary}
12703e230dd2SCorentin Chary
1271d9bc14f6SJohn Arbuckle- (NSApplicationTerminateReply)applicationShouldTerminate:
1272d9bc14f6SJohn Arbuckle                                                         (NSApplication *)sender
1273d9bc14f6SJohn Arbuckle{
1274d9bc14f6SJohn Arbuckle    COCOA_DEBUG("QemuCocoaAppController: applicationShouldTerminate\n");
1275d9bc14f6SJohn Arbuckle    return [self verifyQuit];
1276d9bc14f6SJohn Arbuckle}
1277d9bc14f6SJohn Arbuckle
127815280e85SAkihiko Odaki- (void)windowDidChangeScreen:(NSNotification *)notification
127915280e85SAkihiko Odaki{
128015280e85SAkihiko Odaki    [cocoaView updateUIInfo];
128115280e85SAkihiko Odaki}
128215280e85SAkihiko Odaki
128391aa508dSAkihiko Odaki- (void)windowDidEnterFullScreen:(NSNotification *)notification
128491aa508dSAkihiko Odaki{
128591aa508dSAkihiko Odaki    [cocoaView grabMouse];
128691aa508dSAkihiko Odaki}
128791aa508dSAkihiko Odaki
128891aa508dSAkihiko Odaki- (void)windowDidExitFullScreen:(NSNotification *)notification
128991aa508dSAkihiko Odaki{
129091aa508dSAkihiko Odaki    [cocoaView resizeWindow];
129191aa508dSAkihiko Odaki    [cocoaView ungrabMouse];
129291aa508dSAkihiko Odaki}
129391aa508dSAkihiko Odaki
129415280e85SAkihiko Odaki- (void)windowDidResize:(NSNotification *)notification
129515280e85SAkihiko Odaki{
1296fcb03de7SAkihiko Odaki    [cocoaView updateBounds];
1297*ccebb9aeSAkihiko Odaki    [cocoaView updateUIInfo];
129815280e85SAkihiko Odaki}
129915280e85SAkihiko Odaki
1300d9bc14f6SJohn Arbuckle/* Called when the user clicks on a window's close button */
1301d9bc14f6SJohn Arbuckle- (BOOL)windowShouldClose:(id)sender
1302d9bc14f6SJohn Arbuckle{
1303d9bc14f6SJohn Arbuckle    COCOA_DEBUG("QemuCocoaAppController: windowShouldClose\n");
1304d9bc14f6SJohn Arbuckle    [NSApp terminate: sender];
1305d9bc14f6SJohn Arbuckle    /* If the user allows the application to quit then the call to
1306d9bc14f6SJohn Arbuckle     * NSApp terminate will never return. If we get here then the user
1307d9bc14f6SJohn Arbuckle     * cancelled the quit, so we should return NO to not permit the
1308d9bc14f6SJohn Arbuckle     * closing of this window.
1309d9bc14f6SJohn Arbuckle     */
1310d9bc14f6SJohn Arbuckle    return NO;
1311d9bc14f6SJohn Arbuckle}
1312d9bc14f6SJohn Arbuckle
131391aa508dSAkihiko Odaki- (NSApplicationPresentationOptions) window:(NSWindow *)window
131491aa508dSAkihiko Odaki                                     willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions;
131591aa508dSAkihiko Odaki
131691aa508dSAkihiko Odaki{
131791aa508dSAkihiko Odaki    return (proposedOptions & ~(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)) |
131891aa508dSAkihiko Odaki           NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
131991aa508dSAkihiko Odaki}
132091aa508dSAkihiko Odaki
13219d9bc7dbSAkihiko Odaki/*
13229d9bc7dbSAkihiko Odaki * Called when QEMU goes into the background. Note that
13239d9bc7dbSAkihiko Odaki * [-NSWindowDelegate windowDidResignKey:] is used here instead of
13249d9bc7dbSAkihiko Odaki * [-NSApplicationDelegate applicationWillResignActive:] because it cannot
13259d9bc7dbSAkihiko Odaki * detect that the window loses focus when the deck is clicked on macOS 13.2.1.
13269d9bc7dbSAkihiko Odaki */
13279d9bc7dbSAkihiko Odaki- (void) windowDidResignKey: (NSNotification *)aNotification
13283b178b71SJohn Arbuckle{
13299d9bc7dbSAkihiko Odaki    COCOA_DEBUG("%s\n", __func__);
133069221df8SCarwyn Ellis    [cocoaView ungrabMouse];
13313b178b71SJohn Arbuckle    [cocoaView raiseAllKeys];
13323b178b71SJohn Arbuckle}
13333b178b71SJohn Arbuckle
13345d1b2eefSProgrammingkid/* We abstract the method called by the Enter Fullscreen menu item
13355d1b2eefSProgrammingkid * because Mac OS 10.7 and higher disables it. This is because of the
13365d1b2eefSProgrammingkid * menu item's old selector's name toggleFullScreen:
13375d1b2eefSProgrammingkid */
13385d1b2eefSProgrammingkid- (void) doToggleFullScreen:(id)sender
13395d1b2eefSProgrammingkid{
13400c35886eSAkihiko Odaki    [[cocoaView window] toggleFullScreen:sender];
13413e230dd2SCorentin Chary}
13423e230dd2SCorentin Chary
1343f844cdb9SGustavo Noronha Silva- (void) setFullGrab:(id)sender
1344f844cdb9SGustavo Noronha Silva{
1345f844cdb9SGustavo Noronha Silva    COCOA_DEBUG("QemuCocoaAppController: setFullGrab\n");
1346f844cdb9SGustavo Noronha Silva
1347f844cdb9SGustavo Noronha Silva    [cocoaView setFullGrab:sender];
1348f844cdb9SGustavo Noronha Silva}
1349f844cdb9SGustavo Noronha Silva
1350f4747900SJohn Arbuckle/* Tries to find then open the specified filename */
1351f4747900SJohn Arbuckle- (void) openDocumentation: (NSString *) filename
1352f4747900SJohn Arbuckle{
1353f4747900SJohn Arbuckle    /* Where to look for local files */
13548d6fda8cSRoman Bolshakov    NSString *path_array[] = {@"../share/doc/qemu/", @"../doc/qemu/", @"docs/"};
1355f4747900SJohn Arbuckle    NSString *full_file_path;
13561ff5a063SRoman Bolshakov    NSURL *full_file_url;
1357f4747900SJohn Arbuckle
1358f4747900SJohn Arbuckle    /* iterate thru the possible paths until the file is found */
1359f4747900SJohn Arbuckle    int index;
1360f4747900SJohn Arbuckle    for (index = 0; index < ARRAY_SIZE(path_array); index++) {
1361f4747900SJohn Arbuckle        full_file_path = [[NSBundle mainBundle] executablePath];
1362f4747900SJohn Arbuckle        full_file_path = [full_file_path stringByDeletingLastPathComponent];
1363f4747900SJohn Arbuckle        full_file_path = [NSString stringWithFormat: @"%@/%@%@", full_file_path,
1364f4747900SJohn Arbuckle                          path_array[index], filename];
13651ff5a063SRoman Bolshakov        full_file_url = [NSURL fileURLWithPath: full_file_path
13661ff5a063SRoman Bolshakov                                   isDirectory: false];
13671ff5a063SRoman Bolshakov        if ([[NSWorkspace sharedWorkspace] openURL: full_file_url] == YES) {
1368f4747900SJohn Arbuckle            return;
1369f4747900SJohn Arbuckle        }
1370f4747900SJohn Arbuckle    }
1371f4747900SJohn Arbuckle
1372f4747900SJohn Arbuckle    /* If none of the paths opened a file */
1373f4747900SJohn Arbuckle    NSBeep();
1374f4747900SJohn Arbuckle    QEMU_Alert(@"Failed to open file");
1375f4747900SJohn Arbuckle}
1376f4747900SJohn Arbuckle
13773e230dd2SCorentin Chary- (void)showQEMUDoc:(id)sender
13783e230dd2SCorentin Chary{
13793e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaAppController: showQEMUDoc\n");
13803e230dd2SCorentin Chary
13811879f241SPeter Maydell    [self openDocumentation: @"index.html"];
13823e230dd2SCorentin Chary}
13833e230dd2SCorentin Chary
13845d1b2eefSProgrammingkid/* Stretches video to fit host monitor size */
13855d1b2eefSProgrammingkid- (void)zoomToFit:(id) sender
13865d1b2eefSProgrammingkid{
138755766632SAkihiko Odaki    NSWindowStyleMask styleMask = [[cocoaView window] styleMask] ^ NSWindowStyleMaskResizable;
138855766632SAkihiko Odaki
138955766632SAkihiko Odaki    [[cocoaView window] setStyleMask:styleMask];
139055766632SAkihiko Odaki    [sender setState:styleMask & NSWindowStyleMaskResizable ? NSControlStateValueOn : NSControlStateValueOff];
1391f69a6f04SAkihiko Odaki    [cocoaView resizeWindow];
13925d1b2eefSProgrammingkid}
13933e230dd2SCorentin Chary
1394e28a909aSCarwyn Ellis- (void)toggleZoomInterpolation:(id) sender
1395e28a909aSCarwyn Ellis{
1396e28a909aSCarwyn Ellis    if (zoom_interpolation == kCGInterpolationNone) {
1397e28a909aSCarwyn Ellis        zoom_interpolation = kCGInterpolationLow;
1398e28a909aSCarwyn Ellis        [sender setState: NSControlStateValueOn];
1399e28a909aSCarwyn Ellis    } else {
1400e28a909aSCarwyn Ellis        zoom_interpolation = kCGInterpolationNone;
1401e28a909aSCarwyn Ellis        [sender setState: NSControlStateValueOff];
1402e28a909aSCarwyn Ellis    }
1403e28a909aSCarwyn Ellis}
1404e28a909aSCarwyn Ellis
1405b4c6a112SProgrammingkid/* Displays the console on the screen */
1406b4c6a112SProgrammingkid- (void)displayConsole:(id)sender
1407b4c6a112SProgrammingkid{
14084b49f92cSAkihiko Odaki    with_bql(^{
1409ca3de7b5SAkihiko Odaki        [cocoaView selectConsoleLocked:[sender tag]];
14104b49f92cSAkihiko Odaki    });
1411b4c6a112SProgrammingkid}
14128524f1c7SJohn Arbuckle
14138524f1c7SJohn Arbuckle/* Pause the guest */
14148524f1c7SJohn Arbuckle- (void)pauseQEMU:(id)sender
14158524f1c7SJohn Arbuckle{
1416195801d7SStefan Hajnoczi    with_bql(^{
14178524f1c7SJohn Arbuckle        qmp_stop(NULL);
141831819e95SPeter Maydell    });
14198524f1c7SJohn Arbuckle    [sender setEnabled: NO];
14208524f1c7SJohn Arbuckle    [[[sender menu] itemWithTitle: @"Resume"] setEnabled: YES];
14218524f1c7SJohn Arbuckle    [self displayPause];
14228524f1c7SJohn Arbuckle}
14238524f1c7SJohn Arbuckle
14248524f1c7SJohn Arbuckle/* Resume running the guest operating system */
14258524f1c7SJohn Arbuckle- (void)resumeQEMU:(id) sender
14268524f1c7SJohn Arbuckle{
1427195801d7SStefan Hajnoczi    with_bql(^{
14288524f1c7SJohn Arbuckle        qmp_cont(NULL);
142931819e95SPeter Maydell    });
14308524f1c7SJohn Arbuckle    [sender setEnabled: NO];
14318524f1c7SJohn Arbuckle    [[[sender menu] itemWithTitle: @"Pause"] setEnabled: YES];
14328524f1c7SJohn Arbuckle    [self removePause];
14338524f1c7SJohn Arbuckle}
14348524f1c7SJohn Arbuckle
14358524f1c7SJohn Arbuckle/* Displays the word pause on the screen */
14368524f1c7SJohn Arbuckle- (void)displayPause
14378524f1c7SJohn Arbuckle{
14388524f1c7SJohn Arbuckle    /* Coordinates have to be calculated each time because the window can change its size */
14398524f1c7SJohn Arbuckle    int xCoord, yCoord, width, height;
14401a4b64a5SAkihiko Odaki    xCoord = ([cocoaView frame].size.width - [pauseLabel frame].size.width)/2;
14411a4b64a5SAkihiko Odaki    yCoord = [cocoaView frame].size.height - [pauseLabel frame].size.height - ([pauseLabel frame].size.height * .5);
14428524f1c7SJohn Arbuckle    width = [pauseLabel frame].size.width;
14438524f1c7SJohn Arbuckle    height = [pauseLabel frame].size.height;
14448524f1c7SJohn Arbuckle    [pauseLabel setFrame: NSMakeRect(xCoord, yCoord, width, height)];
14458524f1c7SJohn Arbuckle    [cocoaView addSubview: pauseLabel];
14468524f1c7SJohn Arbuckle}
14478524f1c7SJohn Arbuckle
14488524f1c7SJohn Arbuckle/* Removes the word pause from the screen */
14498524f1c7SJohn Arbuckle- (void)removePause
14508524f1c7SJohn Arbuckle{
14518524f1c7SJohn Arbuckle    [pauseLabel removeFromSuperview];
14528524f1c7SJohn Arbuckle}
14538524f1c7SJohn Arbuckle
145427074614SJohn Arbuckle/* Restarts QEMU */
145527074614SJohn Arbuckle- (void)restartQEMU:(id)sender
145627074614SJohn Arbuckle{
1457195801d7SStefan Hajnoczi    with_bql(^{
145827074614SJohn Arbuckle        qmp_system_reset(NULL);
145931819e95SPeter Maydell    });
146027074614SJohn Arbuckle}
146127074614SJohn Arbuckle
146227074614SJohn Arbuckle/* Powers down QEMU */
146327074614SJohn Arbuckle- (void)powerDownQEMU:(id)sender
146427074614SJohn Arbuckle{
1465195801d7SStefan Hajnoczi    with_bql(^{
146627074614SJohn Arbuckle        qmp_system_powerdown(NULL);
146731819e95SPeter Maydell    });
146827074614SJohn Arbuckle}
146927074614SJohn Arbuckle
1470693a3e01SJohn Arbuckle/* Ejects the media.
1471693a3e01SJohn Arbuckle * Uses sender's tag to figure out the device to eject.
1472693a3e01SJohn Arbuckle */
1473693a3e01SJohn Arbuckle- (void)ejectDeviceMedia:(id)sender
1474693a3e01SJohn Arbuckle{
1475693a3e01SJohn Arbuckle    NSString * drive;
1476693a3e01SJohn Arbuckle    drive = [sender representedObject];
1477693a3e01SJohn Arbuckle    if(drive == nil) {
1478693a3e01SJohn Arbuckle        NSBeep();
1479693a3e01SJohn Arbuckle        QEMU_Alert(@"Failed to find drive to eject!");
1480693a3e01SJohn Arbuckle        return;
1481693a3e01SJohn Arbuckle    }
1482693a3e01SJohn Arbuckle
148331819e95SPeter Maydell    __block Error *err = NULL;
1484195801d7SStefan Hajnoczi    with_bql(^{
148554fde4ffSMarkus Armbruster        qmp_eject([drive cStringUsingEncoding: NSASCIIStringEncoding],
148654fde4ffSMarkus Armbruster                  NULL, false, false, &err);
148731819e95SPeter Maydell    });
1488693a3e01SJohn Arbuckle    handleAnyDeviceErrors(err);
1489693a3e01SJohn Arbuckle}
1490693a3e01SJohn Arbuckle
1491693a3e01SJohn Arbuckle/* Displays a dialog box asking the user to select an image file to load.
1492693a3e01SJohn Arbuckle * Uses sender's represented object value to figure out which drive to use.
1493693a3e01SJohn Arbuckle */
1494693a3e01SJohn Arbuckle- (void)changeDeviceMedia:(id)sender
1495693a3e01SJohn Arbuckle{
1496693a3e01SJohn Arbuckle    /* Find the drive name */
1497693a3e01SJohn Arbuckle    NSString * drive;
1498693a3e01SJohn Arbuckle    drive = [sender representedObject];
1499693a3e01SJohn Arbuckle    if(drive == nil) {
1500693a3e01SJohn Arbuckle        NSBeep();
1501693a3e01SJohn Arbuckle        QEMU_Alert(@"Could not find drive!");
1502693a3e01SJohn Arbuckle        return;
1503693a3e01SJohn Arbuckle    }
1504693a3e01SJohn Arbuckle
1505693a3e01SJohn Arbuckle    /* Display the file open dialog */
1506693a3e01SJohn Arbuckle    NSOpenPanel * openPanel;
1507693a3e01SJohn Arbuckle    openPanel = [NSOpenPanel openPanel];
1508693a3e01SJohn Arbuckle    [openPanel setCanChooseFiles: YES];
1509693a3e01SJohn Arbuckle    [openPanel setAllowsMultipleSelection: NO];
1510b5725385SPeter Maydell    if([openPanel runModal] == NSModalResponseOK) {
1511693a3e01SJohn Arbuckle        NSString * file = [[[openPanel URLs] objectAtIndex: 0] path];
1512693a3e01SJohn Arbuckle        if(file == nil) {
1513693a3e01SJohn Arbuckle            NSBeep();
1514693a3e01SJohn Arbuckle            QEMU_Alert(@"Failed to convert URL to file path!");
1515693a3e01SJohn Arbuckle            return;
1516693a3e01SJohn Arbuckle        }
1517693a3e01SJohn Arbuckle
151831819e95SPeter Maydell        __block Error *err = NULL;
1519195801d7SStefan Hajnoczi        with_bql(^{
152054fde4ffSMarkus Armbruster            qmp_blockdev_change_medium([drive cStringUsingEncoding:
152124fb4133SMax Reitz                                                  NSASCIIStringEncoding],
152254fde4ffSMarkus Armbruster                                       NULL,
152324fb4133SMax Reitz                                       [file cStringUsingEncoding:
152424fb4133SMax Reitz                                                 NSASCIIStringEncoding],
152554fde4ffSMarkus Armbruster                                       "raw",
152680dd5affSDenis V. Lunev                                       true, false,
152739ff43d9SMax Reitz                                       false, 0,
1528693a3e01SJohn Arbuckle                                       &err);
152931819e95SPeter Maydell        });
1530693a3e01SJohn Arbuckle        handleAnyDeviceErrors(err);
1531693a3e01SJohn Arbuckle    }
1532693a3e01SJohn Arbuckle}
1533693a3e01SJohn Arbuckle
1534d9bc14f6SJohn Arbuckle/* Verifies if the user really wants to quit */
1535d9bc14f6SJohn Arbuckle- (BOOL)verifyQuit
1536d9bc14f6SJohn Arbuckle{
1537d9bc14f6SJohn Arbuckle    NSAlert *alert = [NSAlert new];
1538d9bc14f6SJohn Arbuckle    [alert autorelease];
1539d9bc14f6SJohn Arbuckle    [alert setMessageText: @"Are you sure you want to quit QEMU?"];
1540d9bc14f6SJohn Arbuckle    [alert addButtonWithTitle: @"Cancel"];
1541d9bc14f6SJohn Arbuckle    [alert addButtonWithTitle: @"Quit"];
1542d9bc14f6SJohn Arbuckle    if([alert runModal] == NSAlertSecondButtonReturn) {
1543d9bc14f6SJohn Arbuckle        return YES;
1544d9bc14f6SJohn Arbuckle    } else {
1545d9bc14f6SJohn Arbuckle        return NO;
1546d9bc14f6SJohn Arbuckle    }
1547d9bc14f6SJohn Arbuckle}
1548d9bc14f6SJohn Arbuckle
15499e8204b1SProgrammingkid/* The action method for the About menu item */
15509e8204b1SProgrammingkid- (IBAction) do_about_menu_item: (id) sender
15519e8204b1SProgrammingkid{
155299eb313dSAkihiko Odaki    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
155399eb313dSAkihiko Odaki    char *icon_path_c = get_relocated_path(CONFIG_QEMU_ICONDIR "/hicolor/512x512/apps/qemu.png");
155499eb313dSAkihiko Odaki    NSString *icon_path = [NSString stringWithUTF8String:icon_path_c];
155599eb313dSAkihiko Odaki    g_free(icon_path_c);
155699eb313dSAkihiko Odaki    NSImage *icon = [[NSImage alloc] initWithContentsOfFile:icon_path];
155799eb313dSAkihiko Odaki    NSString *version = @"QEMU emulator version " QEMU_FULL_VERSION;
155899eb313dSAkihiko Odaki    NSString *copyright = @QEMU_COPYRIGHT;
155999eb313dSAkihiko Odaki    NSDictionary *options;
156099eb313dSAkihiko Odaki    if (icon) {
156199eb313dSAkihiko Odaki        options = @{
156299eb313dSAkihiko Odaki            NSAboutPanelOptionApplicationIcon : icon,
156399eb313dSAkihiko Odaki            NSAboutPanelOptionApplicationVersion : version,
156499eb313dSAkihiko Odaki            @"Copyright" : copyright,
156599eb313dSAkihiko Odaki        };
156699eb313dSAkihiko Odaki        [icon release];
156799eb313dSAkihiko Odaki    } else {
156899eb313dSAkihiko Odaki        options = @{
156999eb313dSAkihiko Odaki            NSAboutPanelOptionApplicationVersion : version,
157099eb313dSAkihiko Odaki            @"Copyright" : copyright,
157199eb313dSAkihiko Odaki        };
15729e8204b1SProgrammingkid    }
157399eb313dSAkihiko Odaki    [NSApp orderFrontStandardAboutPanelWithOptions:options];
157499eb313dSAkihiko Odaki    [pool release];
15759e8204b1SProgrammingkid}
15769e8204b1SProgrammingkid
1577e47ec1a9SJohn Arbuckle/* Used by the Speed menu items */
1578e47ec1a9SJohn Arbuckle- (void)adjustSpeed:(id)sender
1579e47ec1a9SJohn Arbuckle{
1580e47ec1a9SJohn Arbuckle    int throttle_pct; /* throttle percentage */
1581e47ec1a9SJohn Arbuckle    NSMenu *menu;
1582e47ec1a9SJohn Arbuckle
1583e47ec1a9SJohn Arbuckle    menu = [sender menu];
1584e47ec1a9SJohn Arbuckle    if (menu != nil)
1585e47ec1a9SJohn Arbuckle    {
1586e47ec1a9SJohn Arbuckle        /* Unselect the currently selected item */
1587e47ec1a9SJohn Arbuckle        for (NSMenuItem *item in [menu itemArray]) {
15885e24600aSBrendan Shanks            if (item.state == NSControlStateValueOn) {
15895e24600aSBrendan Shanks                [item setState: NSControlStateValueOff];
1590e47ec1a9SJohn Arbuckle                break;
1591e47ec1a9SJohn Arbuckle            }
1592e47ec1a9SJohn Arbuckle        }
1593e47ec1a9SJohn Arbuckle    }
1594e47ec1a9SJohn Arbuckle
1595e47ec1a9SJohn Arbuckle    // check the menu item
15965e24600aSBrendan Shanks    [sender setState: NSControlStateValueOn];
1597e47ec1a9SJohn Arbuckle
1598e47ec1a9SJohn Arbuckle    // get the throttle percentage
1599e47ec1a9SJohn Arbuckle    throttle_pct = [sender tag];
1600e47ec1a9SJohn Arbuckle
1601195801d7SStefan Hajnoczi    with_bql(^{
1602e47ec1a9SJohn Arbuckle        cpu_throttle_set(throttle_pct);
160331819e95SPeter Maydell    });
1604e47ec1a9SJohn Arbuckle    COCOA_DEBUG("cpu throttling at %d%c\n", cpu_throttle_get_percentage(), '%');
1605e47ec1a9SJohn Arbuckle}
1606e47ec1a9SJohn Arbuckle
1607b4c6a112SProgrammingkid@end
16083e230dd2SCorentin Chary
160961a2ed44SPeter Maydell@interface QemuApplication : NSApplication
161061a2ed44SPeter Maydell@end
161161a2ed44SPeter Maydell
161261a2ed44SPeter Maydell@implementation QemuApplication
161361a2ed44SPeter Maydell- (void)sendEvent:(NSEvent *)event
161461a2ed44SPeter Maydell{
161561a2ed44SPeter Maydell    COCOA_DEBUG("QemuApplication: sendEvent\n");
16165588840fSPeter Maydell    if (![cocoaView handleEvent:event]) {
161761a2ed44SPeter Maydell        [super sendEvent: event];
161861a2ed44SPeter Maydell    }
16195588840fSPeter Maydell}
162061a2ed44SPeter Maydell@end
162161a2ed44SPeter Maydell
1622c6fd6c70SPeter Maydellstatic void create_initial_menus(void)
1623c6fd6c70SPeter Maydell{
16243e230dd2SCorentin Chary    // Add menus
16253e230dd2SCorentin Chary    NSMenu      *menu;
16263e230dd2SCorentin Chary    NSMenuItem  *menuItem;
16273e230dd2SCorentin Chary
16283e230dd2SCorentin Chary    [NSApp setMainMenu:[[NSMenu alloc] init]];
16295b6988c1SAkihiko Odaki    [NSApp setServicesMenu:[[NSMenu alloc] initWithTitle:@"Services"]];
16303e230dd2SCorentin Chary
16313e230dd2SCorentin Chary    // Application menu
16323e230dd2SCorentin Chary    menu = [[NSMenu alloc] initWithTitle:@""];
16339e8204b1SProgrammingkid    [menu addItemWithTitle:@"About QEMU" action:@selector(do_about_menu_item:) keyEquivalent:@""]; // About QEMU
16343e230dd2SCorentin Chary    [menu addItem:[NSMenuItem separatorItem]]; //Separator
16355b6988c1SAkihiko Odaki    menuItem = [menu addItemWithTitle:@"Services" action:nil keyEquivalent:@""];
16365b6988c1SAkihiko Odaki    [menuItem setSubmenu:[NSApp servicesMenu]];
16375b6988c1SAkihiko Odaki    [menu addItem:[NSMenuItem separatorItem]];
16383e230dd2SCorentin Chary    [menu addItemWithTitle:@"Hide QEMU" action:@selector(hide:) keyEquivalent:@"h"]; //Hide QEMU
16393e230dd2SCorentin Chary    menuItem = (NSMenuItem *)[menu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; // Hide Others
16404ba967adSBrendan Shanks    [menuItem setKeyEquivalentModifierMask:(NSEventModifierFlagOption|NSEventModifierFlagCommand)];
16413e230dd2SCorentin Chary    [menu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; // Show All
16423e230dd2SCorentin Chary    [menu addItem:[NSMenuItem separatorItem]]; //Separator
16433e230dd2SCorentin Chary    [menu addItemWithTitle:@"Quit QEMU" action:@selector(terminate:) keyEquivalent:@"q"];
16443e230dd2SCorentin Chary    menuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" action:nil keyEquivalent:@""];
16453e230dd2SCorentin Chary    [menuItem setSubmenu:menu];
16463e230dd2SCorentin Chary    [[NSApp mainMenu] addItem:menuItem];
16473e230dd2SCorentin Chary    [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; // Workaround (this method is private since 10.4+)
16483e230dd2SCorentin Chary
16498524f1c7SJohn Arbuckle    // Machine menu
16508524f1c7SJohn Arbuckle    menu = [[NSMenu alloc] initWithTitle: @"Machine"];
16518524f1c7SJohn Arbuckle    [menu setAutoenablesItems: NO];
16528524f1c7SJohn Arbuckle    [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Pause" action: @selector(pauseQEMU:) keyEquivalent: @""] autorelease]];
16538524f1c7SJohn Arbuckle    menuItem = [[[NSMenuItem alloc] initWithTitle: @"Resume" action: @selector(resumeQEMU:) keyEquivalent: @""] autorelease];
16548524f1c7SJohn Arbuckle    [menu addItem: menuItem];
16558524f1c7SJohn Arbuckle    [menuItem setEnabled: NO];
165627074614SJohn Arbuckle    [menu addItem: [NSMenuItem separatorItem]];
165727074614SJohn Arbuckle    [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Reset" action: @selector(restartQEMU:) keyEquivalent: @""] autorelease]];
165827074614SJohn Arbuckle    [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Power Down" action: @selector(powerDownQEMU:) keyEquivalent: @""] autorelease]];
16598524f1c7SJohn Arbuckle    menuItem = [[[NSMenuItem alloc] initWithTitle: @"Machine" action:nil keyEquivalent:@""] autorelease];
16608524f1c7SJohn Arbuckle    [menuItem setSubmenu:menu];
16618524f1c7SJohn Arbuckle    [[NSApp mainMenu] addItem:menuItem];
16628524f1c7SJohn Arbuckle
16633e230dd2SCorentin Chary    // View menu
16643e230dd2SCorentin Chary    menu = [[NSMenu alloc] initWithTitle:@"View"];
16655d1b2eefSProgrammingkid    [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(doToggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen
16665ec0898bSCarwyn Ellis    menuItem = [[[NSMenuItem alloc] initWithTitle:@"Zoom To Fit" action:@selector(zoomToFit:) keyEquivalent:@""] autorelease];
166755766632SAkihiko Odaki    [menuItem setState: [[cocoaView window] styleMask] & NSWindowStyleMaskResizable ? NSControlStateValueOn : NSControlStateValueOff];
16685ec0898bSCarwyn Ellis    [menu addItem: menuItem];
1669e28a909aSCarwyn Ellis    menuItem = [[[NSMenuItem alloc] initWithTitle:@"Zoom Interpolation" action:@selector(toggleZoomInterpolation:) keyEquivalent:@""] autorelease];
1670e28a909aSCarwyn Ellis    [menuItem setState: zoom_interpolation == kCGInterpolationLow ? NSControlStateValueOn : NSControlStateValueOff];
1671e28a909aSCarwyn Ellis    [menu addItem: menuItem];
16723e230dd2SCorentin Chary    menuItem = [[[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""] autorelease];
16733e230dd2SCorentin Chary    [menuItem setSubmenu:menu];
16743e230dd2SCorentin Chary    [[NSApp mainMenu] addItem:menuItem];
16753e230dd2SCorentin Chary
1676e47ec1a9SJohn Arbuckle    // Speed menu
1677e47ec1a9SJohn Arbuckle    menu = [[NSMenu alloc] initWithTitle:@"Speed"];
1678e47ec1a9SJohn Arbuckle
1679e47ec1a9SJohn Arbuckle    // Add the rest of the Speed menu items
1680e47ec1a9SJohn Arbuckle    int p, percentage, throttle_pct;
1681e47ec1a9SJohn Arbuckle    for (p = 10; p >= 0; p--)
1682e47ec1a9SJohn Arbuckle    {
1683e47ec1a9SJohn Arbuckle        percentage = p * 10 > 1 ? p * 10 : 1; // prevent a 0% menu item
1684e47ec1a9SJohn Arbuckle
1685e47ec1a9SJohn Arbuckle        menuItem = [[[NSMenuItem alloc]
1686e47ec1a9SJohn Arbuckle                   initWithTitle: [NSString stringWithFormat: @"%d%%", percentage] action:@selector(adjustSpeed:) keyEquivalent:@""] autorelease];
1687e47ec1a9SJohn Arbuckle
1688e47ec1a9SJohn Arbuckle        if (percentage == 100) {
16895e24600aSBrendan Shanks            [menuItem setState: NSControlStateValueOn];
1690e47ec1a9SJohn Arbuckle        }
1691e47ec1a9SJohn Arbuckle
1692e47ec1a9SJohn Arbuckle        /* Calculate the throttle percentage */
1693e47ec1a9SJohn Arbuckle        throttle_pct = -1 * percentage + 100;
1694e47ec1a9SJohn Arbuckle
1695e47ec1a9SJohn Arbuckle        [menuItem setTag: throttle_pct];
1696e47ec1a9SJohn Arbuckle        [menu addItem: menuItem];
1697e47ec1a9SJohn Arbuckle    }
1698e47ec1a9SJohn Arbuckle    menuItem = [[[NSMenuItem alloc] initWithTitle:@"Speed" action:nil keyEquivalent:@""] autorelease];
1699e47ec1a9SJohn Arbuckle    [menuItem setSubmenu:menu];
1700e47ec1a9SJohn Arbuckle    [[NSApp mainMenu] addItem:menuItem];
1701e47ec1a9SJohn Arbuckle
17023e230dd2SCorentin Chary    // Window menu
17033e230dd2SCorentin Chary    menu = [[NSMenu alloc] initWithTitle:@"Window"];
17043e230dd2SCorentin Chary    [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease]]; // Miniaturize
17053e230dd2SCorentin Chary    menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease];
17063e230dd2SCorentin Chary    [menuItem setSubmenu:menu];
17073e230dd2SCorentin Chary    [[NSApp mainMenu] addItem:menuItem];
17083e230dd2SCorentin Chary    [NSApp setWindowsMenu:menu];
17093e230dd2SCorentin Chary
17103e230dd2SCorentin Chary    // Help menu
17113e230dd2SCorentin Chary    menu = [[NSMenu alloc] initWithTitle:@"Help"];
17123e230dd2SCorentin Chary    [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Documentation" action:@selector(showQEMUDoc:) keyEquivalent:@"?"] autorelease]]; // QEMU Help
17133e230dd2SCorentin Chary    menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease];
17143e230dd2SCorentin Chary    [menuItem setSubmenu:menu];
17153e230dd2SCorentin Chary    [[NSApp mainMenu] addItem:menuItem];
1716c6fd6c70SPeter Maydell}
1717c6fd6c70SPeter Maydell
17188b00e4e7SPeter Maydell/* Returns a name for a given console */
17198b00e4e7SPeter Maydellstatic NSString * getConsoleName(QemuConsole * console)
17208b00e4e7SPeter Maydell{
1721ca511604SAkihiko Odaki    g_autofree char *label = qemu_console_get_label(console);
1722ca511604SAkihiko Odaki
1723ca511604SAkihiko Odaki    return [NSString stringWithUTF8String:label];
17248b00e4e7SPeter Maydell}
17258b00e4e7SPeter Maydell
17268b00e4e7SPeter Maydell/* Add an entry to the View menu for each console */
17278b00e4e7SPeter Maydellstatic void add_console_menu_entries(void)
17288b00e4e7SPeter Maydell{
17298b00e4e7SPeter Maydell    NSMenu *menu;
17308b00e4e7SPeter Maydell    NSMenuItem *menuItem;
17318b00e4e7SPeter Maydell    int index = 0;
17328b00e4e7SPeter Maydell
17338b00e4e7SPeter Maydell    menu = [[[NSApp mainMenu] itemWithTitle:@"View"] submenu];
17348b00e4e7SPeter Maydell
17358b00e4e7SPeter Maydell    [menu addItem:[NSMenuItem separatorItem]];
17368b00e4e7SPeter Maydell
17378b00e4e7SPeter Maydell    while (qemu_console_lookup_by_index(index) != NULL) {
17388b00e4e7SPeter Maydell        menuItem = [[[NSMenuItem alloc] initWithTitle: getConsoleName(qemu_console_lookup_by_index(index))
17398b00e4e7SPeter Maydell                                               action: @selector(displayConsole:) keyEquivalent: @""] autorelease];
17408b00e4e7SPeter Maydell        [menuItem setTag: index];
17418b00e4e7SPeter Maydell        [menu addItem: menuItem];
17428b00e4e7SPeter Maydell        index++;
17438b00e4e7SPeter Maydell    }
17448b00e4e7SPeter Maydell}
17458b00e4e7SPeter Maydell
17468b00e4e7SPeter Maydell/* Make menu items for all removable devices.
17478b00e4e7SPeter Maydell * Each device is given an 'Eject' and 'Change' menu item.
17488b00e4e7SPeter Maydell */
17498b00e4e7SPeter Maydellstatic void addRemovableDevicesMenuItems(void)
17508b00e4e7SPeter Maydell{
17518b00e4e7SPeter Maydell    NSMenu *menu;
17528b00e4e7SPeter Maydell    NSMenuItem *menuItem;
17538b00e4e7SPeter Maydell    BlockInfoList *currentDevice, *pointerToFree;
17548b00e4e7SPeter Maydell    NSString *deviceName;
17558b00e4e7SPeter Maydell
17568b00e4e7SPeter Maydell    currentDevice = qmp_query_block(NULL);
17578b00e4e7SPeter Maydell    pointerToFree = currentDevice;
17588b00e4e7SPeter Maydell
17598b00e4e7SPeter Maydell    menu = [[[NSApp mainMenu] itemWithTitle:@"Machine"] submenu];
17608b00e4e7SPeter Maydell
17618b00e4e7SPeter Maydell    // Add a separator between related groups of menu items
17628b00e4e7SPeter Maydell    [menu addItem:[NSMenuItem separatorItem]];
17638b00e4e7SPeter Maydell
17648b00e4e7SPeter Maydell    // Set the attributes to the "Removable Media" menu item
17658b00e4e7SPeter Maydell    NSString *titleString = @"Removable Media";
17668b00e4e7SPeter Maydell    NSMutableAttributedString *attString=[[NSMutableAttributedString alloc] initWithString:titleString];
17678b00e4e7SPeter Maydell    NSColor *newColor = [NSColor blackColor];
17688b00e4e7SPeter Maydell    NSFontManager *fontManager = [NSFontManager sharedFontManager];
17698b00e4e7SPeter Maydell    NSFont *font = [fontManager fontWithFamily:@"Helvetica"
17708b00e4e7SPeter Maydell                                          traits:NSBoldFontMask|NSItalicFontMask
17718b00e4e7SPeter Maydell                                          weight:0
17728b00e4e7SPeter Maydell                                            size:14];
17738b00e4e7SPeter Maydell    [attString addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, [titleString length])];
17748b00e4e7SPeter Maydell    [attString addAttribute:NSForegroundColorAttributeName value:newColor range:NSMakeRange(0, [titleString length])];
17758b00e4e7SPeter Maydell    [attString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInt: 1] range:NSMakeRange(0, [titleString length])];
17768b00e4e7SPeter Maydell
17778b00e4e7SPeter Maydell    // Add the "Removable Media" menu item
17788b00e4e7SPeter Maydell    menuItem = [NSMenuItem new];
17798b00e4e7SPeter Maydell    [menuItem setAttributedTitle: attString];
17808b00e4e7SPeter Maydell    [menuItem setEnabled: NO];
17818b00e4e7SPeter Maydell    [menu addItem: menuItem];
17828b00e4e7SPeter Maydell
17838b00e4e7SPeter Maydell    /* Loop through all the block devices in the emulator */
17848b00e4e7SPeter Maydell    while (currentDevice) {
17858b00e4e7SPeter Maydell        deviceName = [[NSString stringWithFormat: @"%s", currentDevice->value->device] retain];
17868b00e4e7SPeter Maydell
17878b00e4e7SPeter Maydell        if(currentDevice->value->removable) {
17888b00e4e7SPeter Maydell            menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Change %s...", currentDevice->value->device]
17898b00e4e7SPeter Maydell                                                  action: @selector(changeDeviceMedia:)
17908b00e4e7SPeter Maydell                                           keyEquivalent: @""];
17918b00e4e7SPeter Maydell            [menu addItem: menuItem];
17928b00e4e7SPeter Maydell            [menuItem setRepresentedObject: deviceName];
17938b00e4e7SPeter Maydell            [menuItem autorelease];
17948b00e4e7SPeter Maydell
17958b00e4e7SPeter Maydell            menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Eject %s", currentDevice->value->device]
17968b00e4e7SPeter Maydell                                                  action: @selector(ejectDeviceMedia:)
17978b00e4e7SPeter Maydell                                           keyEquivalent: @""];
17988b00e4e7SPeter Maydell            [menu addItem: menuItem];
17998b00e4e7SPeter Maydell            [menuItem setRepresentedObject: deviceName];
18008b00e4e7SPeter Maydell            [menuItem autorelease];
18018b00e4e7SPeter Maydell        }
18028b00e4e7SPeter Maydell        currentDevice = currentDevice->next;
18038b00e4e7SPeter Maydell    }
18048b00e4e7SPeter Maydell    qapi_free_BlockInfoList(pointerToFree);
18058b00e4e7SPeter Maydell}
18068b00e4e7SPeter Maydell
18077e3e20d8SAkihiko Odaki@interface QemuCocoaPasteboardTypeOwner : NSObject<NSPasteboardTypeOwner>
18087e3e20d8SAkihiko Odaki@end
18097e3e20d8SAkihiko Odaki
18107e3e20d8SAkihiko Odaki@implementation QemuCocoaPasteboardTypeOwner
18117e3e20d8SAkihiko Odaki
18127e3e20d8SAkihiko Odaki- (void)pasteboard:(NSPasteboard *)sender provideDataForType:(NSPasteboardType)type
18137e3e20d8SAkihiko Odaki{
18147e3e20d8SAkihiko Odaki    if (type != NSPasteboardTypeString) {
18157e3e20d8SAkihiko Odaki        return;
18167e3e20d8SAkihiko Odaki    }
18177e3e20d8SAkihiko Odaki
1818195801d7SStefan Hajnoczi    with_bql(^{
18197e3e20d8SAkihiko Odaki        QemuClipboardInfo *info = qemu_clipboard_info_ref(cbinfo);
18207e3e20d8SAkihiko Odaki        qemu_event_reset(&cbevent);
18217e3e20d8SAkihiko Odaki        qemu_clipboard_request(info, QEMU_CLIPBOARD_TYPE_TEXT);
18227e3e20d8SAkihiko Odaki
18237e3e20d8SAkihiko Odaki        while (info == cbinfo &&
18247e3e20d8SAkihiko Odaki               info->types[QEMU_CLIPBOARD_TYPE_TEXT].available &&
18257e3e20d8SAkihiko Odaki               info->types[QEMU_CLIPBOARD_TYPE_TEXT].data == NULL) {
1826195801d7SStefan Hajnoczi            bql_unlock();
18277e3e20d8SAkihiko Odaki            qemu_event_wait(&cbevent);
1828195801d7SStefan Hajnoczi            bql_lock();
18297e3e20d8SAkihiko Odaki        }
18307e3e20d8SAkihiko Odaki
18317e3e20d8SAkihiko Odaki        if (info == cbinfo) {
18327e3e20d8SAkihiko Odaki            NSData *data = [[NSData alloc] initWithBytes:info->types[QEMU_CLIPBOARD_TYPE_TEXT].data
18337e3e20d8SAkihiko Odaki                                           length:info->types[QEMU_CLIPBOARD_TYPE_TEXT].size];
18347e3e20d8SAkihiko Odaki            [sender setData:data forType:NSPasteboardTypeString];
18357e3e20d8SAkihiko Odaki            [data release];
18367e3e20d8SAkihiko Odaki        }
18377e3e20d8SAkihiko Odaki
18387e3e20d8SAkihiko Odaki        qemu_clipboard_info_unref(info);
18397e3e20d8SAkihiko Odaki    });
18407e3e20d8SAkihiko Odaki}
18417e3e20d8SAkihiko Odaki
18427e3e20d8SAkihiko Odaki@end
18437e3e20d8SAkihiko Odaki
18447e3e20d8SAkihiko Odakistatic QemuCocoaPasteboardTypeOwner *cbowner;
18457e3e20d8SAkihiko Odaki
18467e3e20d8SAkihiko Odakistatic void cocoa_clipboard_notify(Notifier *notifier, void *data);
18477e3e20d8SAkihiko Odakistatic void cocoa_clipboard_request(QemuClipboardInfo *info,
18487e3e20d8SAkihiko Odaki                                    QemuClipboardType type);
18497e3e20d8SAkihiko Odaki
18507e3e20d8SAkihiko Odakistatic QemuClipboardPeer cbpeer = {
18517e3e20d8SAkihiko Odaki    .name = "cocoa",
18521b17f1e9SMarc-André Lureau    .notifier = { .notify = cocoa_clipboard_notify },
18537e3e20d8SAkihiko Odaki    .request = cocoa_clipboard_request
18547e3e20d8SAkihiko Odaki};
18557e3e20d8SAkihiko Odaki
18561b17f1e9SMarc-André Lureaustatic void cocoa_clipboard_update_info(QemuClipboardInfo *info)
18577e3e20d8SAkihiko Odaki{
18587e3e20d8SAkihiko Odaki    if (info->owner == &cbpeer || info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) {
18597e3e20d8SAkihiko Odaki        return;
18607e3e20d8SAkihiko Odaki    }
18617e3e20d8SAkihiko Odaki
18627e3e20d8SAkihiko Odaki    if (info != cbinfo) {
18637e3e20d8SAkihiko Odaki        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
18647e3e20d8SAkihiko Odaki        qemu_clipboard_info_unref(cbinfo);
18657e3e20d8SAkihiko Odaki        cbinfo = qemu_clipboard_info_ref(info);
18667e3e20d8SAkihiko Odaki        cbchangecount = [[NSPasteboard generalPasteboard] declareTypes:@[NSPasteboardTypeString] owner:cbowner];
18677e3e20d8SAkihiko Odaki        [pool release];
18687e3e20d8SAkihiko Odaki    }
18697e3e20d8SAkihiko Odaki
18707e3e20d8SAkihiko Odaki    qemu_event_set(&cbevent);
18717e3e20d8SAkihiko Odaki}
18727e3e20d8SAkihiko Odaki
18731b17f1e9SMarc-André Lureaustatic void cocoa_clipboard_notify(Notifier *notifier, void *data)
18741b17f1e9SMarc-André Lureau{
18751b17f1e9SMarc-André Lureau    QemuClipboardNotify *notify = data;
18761b17f1e9SMarc-André Lureau
18771b17f1e9SMarc-André Lureau    switch (notify->type) {
18781b17f1e9SMarc-André Lureau    case QEMU_CLIPBOARD_UPDATE_INFO:
18791b17f1e9SMarc-André Lureau        cocoa_clipboard_update_info(notify->info);
18801b17f1e9SMarc-André Lureau        return;
1881505dbf9bSMarc-André Lureau    case QEMU_CLIPBOARD_RESET_SERIAL:
1882505dbf9bSMarc-André Lureau        /* ignore */
1883505dbf9bSMarc-André Lureau        return;
18841b17f1e9SMarc-André Lureau    }
18851b17f1e9SMarc-André Lureau}
18861b17f1e9SMarc-André Lureau
18877e3e20d8SAkihiko Odakistatic void cocoa_clipboard_request(QemuClipboardInfo *info,
18887e3e20d8SAkihiko Odaki                                    QemuClipboardType type)
18897e3e20d8SAkihiko Odaki{
18908c0d8024SAkihiko Odaki    NSAutoreleasePool *pool;
18917e3e20d8SAkihiko Odaki    NSData *text;
18927e3e20d8SAkihiko Odaki
18937e3e20d8SAkihiko Odaki    switch (type) {
18947e3e20d8SAkihiko Odaki    case QEMU_CLIPBOARD_TYPE_TEXT:
18958c0d8024SAkihiko Odaki        pool = [[NSAutoreleasePool alloc] init];
18967e3e20d8SAkihiko Odaki        text = [[NSPasteboard generalPasteboard] dataForType:NSPasteboardTypeString];
18977e3e20d8SAkihiko Odaki        if (text) {
18987e3e20d8SAkihiko Odaki            qemu_clipboard_set_data(&cbpeer, info, type,
18997e3e20d8SAkihiko Odaki                                    [text length], [text bytes], true);
19007e3e20d8SAkihiko Odaki        }
19018c0d8024SAkihiko Odaki        [pool release];
19027e3e20d8SAkihiko Odaki        break;
19037e3e20d8SAkihiko Odaki    default:
19047e3e20d8SAkihiko Odaki        break;
19057e3e20d8SAkihiko Odaki    }
19067e3e20d8SAkihiko Odaki}
19077e3e20d8SAkihiko Odaki
19085588840fSPeter Maydell/*
19095588840fSPeter Maydell * The startup process for the OSX/Cocoa UI is complicated, because
19105588840fSPeter Maydell * OSX insists that the UI runs on the initial main thread, and so we
1911bab6a301SAkihiko Odaki * need to start a second thread which runs the qemu_default_main():
19125588840fSPeter Maydell * in main():
19135588840fSPeter Maydell *  in cocoa_display_init():
1914bab6a301SAkihiko Odaki *   assign cocoa_main to qemu_main
19155588840fSPeter Maydell *   create application, menus, etc
1916bab6a301SAkihiko Odaki *  in cocoa_main():
1917bab6a301SAkihiko Odaki *   create qemu-main thread
19185588840fSPeter Maydell *   enter OSX run loop
19195588840fSPeter Maydell */
1920c6fd6c70SPeter Maydell
19215588840fSPeter Maydellstatic void *call_qemu_main(void *opaque)
19225588840fSPeter Maydell{
19235588840fSPeter Maydell    int status;
19245588840fSPeter Maydell
1925bab6a301SAkihiko Odaki    COCOA_DEBUG("Second thread: calling qemu_default_main()\n");
1926195801d7SStefan Hajnoczi    bql_lock();
1927bab6a301SAkihiko Odaki    status = qemu_default_main();
1928195801d7SStefan Hajnoczi    bql_unlock();
1929bab6a301SAkihiko Odaki    COCOA_DEBUG("Second thread: qemu_default_main() returned, exiting\n");
19307e3e20d8SAkihiko Odaki    [cbowner release];
19315588840fSPeter Maydell    exit(status);
19325588840fSPeter Maydell}
19335588840fSPeter Maydell
1934f975033dSPhilippe Mathieu-Daudéstatic int cocoa_main(void)
1935bab6a301SAkihiko Odaki{
19365588840fSPeter Maydell    QemuThread thread;
19375588840fSPeter Maydell
1938bab6a301SAkihiko Odaki    COCOA_DEBUG("Entered %s()\n", __func__);
1939c6fd6c70SPeter Maydell
1940195801d7SStefan Hajnoczi    bql_unlock();
19415588840fSPeter Maydell    qemu_thread_create(&thread, "qemu_main", call_qemu_main,
19425588840fSPeter Maydell                       NULL, QEMU_THREAD_DETACHED);
19435588840fSPeter Maydell
19443e230dd2SCorentin Chary    // Start the main event loop
19455588840fSPeter Maydell    COCOA_DEBUG("Main thread: entering OSX run loop\n");
19463e230dd2SCorentin Chary    [NSApp run];
1947bab6a301SAkihiko Odaki    COCOA_DEBUG("Main thread: left OSX run loop, which should never happen\n");
19483e230dd2SCorentin Chary
1949bab6a301SAkihiko Odaki    abort();
19503e230dd2SCorentin Chary}
19513e230dd2SCorentin Chary
19523e230dd2SCorentin Chary
19533e230dd2SCorentin Chary
19543e230dd2SCorentin Chary#pragma mark qemu
19557c20b4a3SGerd Hoffmannstatic void cocoa_update(DisplayChangeListener *dcl,
19567c20b4a3SGerd Hoffmann                         int x, int y, int w, int h)
19573e230dd2SCorentin Chary{
19583e230dd2SCorentin Chary    COCOA_DEBUG("qemu_cocoa: cocoa_update\n");
19593e230dd2SCorentin Chary
19605588840fSPeter Maydell    dispatch_async(dispatch_get_main_queue(), ^{
1961fcb03de7SAkihiko Odaki        NSRect rect = NSMakeRect(x, [cocoaView gscreen].height - y - h, w, h);
19623e230dd2SCorentin Chary        [cocoaView setNeedsDisplayInRect:rect];
19635588840fSPeter Maydell    });
19643e230dd2SCorentin Chary}
19653e230dd2SCorentin Chary
1966c12aeb86SGerd Hoffmannstatic void cocoa_switch(DisplayChangeListener *dcl,
1967c12aeb86SGerd Hoffmann                         DisplaySurface *surface)
19683e230dd2SCorentin Chary{
19695588840fSPeter Maydell    pixman_image_t *image = surface->image;
19703e230dd2SCorentin Chary
19716e657e64SPeter Maydell    COCOA_DEBUG("qemu_cocoa: cocoa_switch\n");
19725588840fSPeter Maydell
19735588840fSPeter Maydell    // The DisplaySurface will be freed as soon as this callback returns.
19745588840fSPeter Maydell    // We take a reference to the underlying pixman image here so it does
19755588840fSPeter Maydell    // not disappear from under our feet; the switchSurface method will
19765588840fSPeter Maydell    // deref the old image when it is done with it.
19775588840fSPeter Maydell    pixman_image_ref(image);
19785588840fSPeter Maydell
19795588840fSPeter Maydell    dispatch_async(dispatch_get_main_queue(), ^{
19805588840fSPeter Maydell        [cocoaView switchSurface:image];
19815588840fSPeter Maydell    });
19823e230dd2SCorentin Chary}
19833e230dd2SCorentin Chary
1984bc2ed970SGerd Hoffmannstatic void cocoa_refresh(DisplayChangeListener *dcl)
19853e230dd2SCorentin Chary{
19866e657e64SPeter Maydell    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
19876e657e64SPeter Maydell
19883e230dd2SCorentin Chary    COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n");
1989ca3de7b5SAkihiko Odaki    graphic_hw_update(dcl->con);
19903e230dd2SCorentin Chary
19910337e412SAkihiko Odaki    if (qemu_input_is_absolute(dcl->con)) {
19925588840fSPeter Maydell        dispatch_async(dispatch_get_main_queue(), ^{
19933e230dd2SCorentin Chary            if (![cocoaView isAbsoluteEnabled]) {
199449b9bd4dSPeter Maydell                if ([cocoaView isMouseGrabbed]) {
19953e230dd2SCorentin Chary                    [cocoaView ungrabMouse];
19963e230dd2SCorentin Chary                }
19973e230dd2SCorentin Chary            }
19983e230dd2SCorentin Chary            [cocoaView setAbsoluteEnabled:YES];
19995588840fSPeter Maydell        });
20003e230dd2SCorentin Chary    }
20017e3e20d8SAkihiko Odaki
20027e3e20d8SAkihiko Odaki    if (cbchangecount != [[NSPasteboard generalPasteboard] changeCount]) {
20037e3e20d8SAkihiko Odaki        qemu_clipboard_info_unref(cbinfo);
20047e3e20d8SAkihiko Odaki        cbinfo = qemu_clipboard_info_new(&cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD);
20057e3e20d8SAkihiko Odaki        if ([[NSPasteboard generalPasteboard] availableTypeFromArray:@[NSPasteboardTypeString]]) {
20067e3e20d8SAkihiko Odaki            cbinfo->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
20077e3e20d8SAkihiko Odaki        }
20087e3e20d8SAkihiko Odaki        qemu_clipboard_update(cbinfo);
20097e3e20d8SAkihiko Odaki        cbchangecount = [[NSPasteboard generalPasteboard] changeCount];
20107e3e20d8SAkihiko Odaki        qemu_event_set(&cbevent);
20117e3e20d8SAkihiko Odaki    }
20127e3e20d8SAkihiko Odaki
20136e657e64SPeter Maydell    [pool release];
20143e230dd2SCorentin Chary}
20153e230dd2SCorentin Chary
20165013b9e4SGerd Hoffmannstatic void cocoa_display_init(DisplayState *ds, DisplayOptions *opts)
20173e230dd2SCorentin Chary{
2018bab6a301SAkihiko Odaki    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
2019bab6a301SAkihiko Odaki
20203e230dd2SCorentin Chary    COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n");
20213e230dd2SCorentin Chary
2022bab6a301SAkihiko Odaki    qemu_main = cocoa_main;
20235588840fSPeter Maydell
2024bab6a301SAkihiko Odaki    // Pull this console process up to being a fully-fledged graphical
2025bab6a301SAkihiko Odaki    // app with a menubar and Dock icon
2026bab6a301SAkihiko Odaki    ProcessSerialNumber psn = { 0, kCurrentProcess };
2027bab6a301SAkihiko Odaki    TransformProcessType(&psn, kProcessTransformToForegroundApplication);
2028bab6a301SAkihiko Odaki
2029bab6a301SAkihiko Odaki    [QemuApplication sharedApplication];
2030bab6a301SAkihiko Odaki
2031bab6a301SAkihiko Odaki    // Create an Application controller
2032bab6a301SAkihiko Odaki    QemuCocoaAppController *controller = [[QemuCocoaAppController alloc] init];
2033bab6a301SAkihiko Odaki    [NSApp setDelegate:controller];
2034bab6a301SAkihiko Odaki
203543227af8SProgrammingkid    /* if fullscreen mode is to be used */
2036767f9bf3SGerd Hoffmann    if (opts->has_full_screen && opts->full_screen) {
20370c35886eSAkihiko Odaki        [[cocoaView window] toggleFullScreen: nil];
2038f844cdb9SGustavo Noronha Silva    }
2039f844cdb9SGustavo Noronha Silva    if (opts->u.cocoa.has_full_grab && opts->u.cocoa.full_grab) {
2040f844cdb9SGustavo Noronha Silva        [controller setFullGrab: nil];
204143227af8SProgrammingkid    }
204269221df8SCarwyn Ellis
20433487da6aSGerd Hoffmann    if (opts->has_show_cursor && opts->show_cursor) {
20443487da6aSGerd Hoffmann        cursor_hide = 0;
20453487da6aSGerd Hoffmann    }
20464797adceSGustavo Noronha Silva    if (opts->u.cocoa.has_swap_opt_cmd) {
20474797adceSGustavo Noronha Silva        swap_opt_cmd = opts->u.cocoa.swap_opt_cmd;
20484797adceSGustavo Noronha Silva    }
204943227af8SProgrammingkid
205048941a52SCarwyn Ellis    if (opts->u.cocoa.has_left_command_key && !opts->u.cocoa.left_command_key) {
205148941a52SCarwyn Ellis        left_command_key_enabled = 0;
205248941a52SCarwyn Ellis    }
205348941a52SCarwyn Ellis
20545ec0898bSCarwyn Ellis    if (opts->u.cocoa.has_zoom_to_fit && opts->u.cocoa.zoom_to_fit) {
2055b6ee03c2SAkihiko Odaki        [cocoaView window].styleMask |= NSWindowStyleMaskResizable;
20565ec0898bSCarwyn Ellis    }
20575ec0898bSCarwyn Ellis
2058e28a909aSCarwyn Ellis    if (opts->u.cocoa.has_zoom_interpolation && opts->u.cocoa.zoom_interpolation) {
2059e28a909aSCarwyn Ellis        zoom_interpolation = kCGInterpolationLow;
2060e28a909aSCarwyn Ellis    }
2061e28a909aSCarwyn Ellis
20625ec0898bSCarwyn Ellis    create_initial_menus();
20635ec0898bSCarwyn Ellis    /*
20645ec0898bSCarwyn Ellis     * Create the menu entries which depend on QEMU state (for consoles
20655ec0898bSCarwyn Ellis     * and removable devices). These make calls back into QEMU functions,
20665ec0898bSCarwyn Ellis     * which is OK because at this point we know that the second thread
2067a4a411fbSStefan Hajnoczi     * holds the BQL and is synchronously waiting for us to
20685ec0898bSCarwyn Ellis     * finish.
20695ec0898bSCarwyn Ellis     */
20705ec0898bSCarwyn Ellis    add_console_menu_entries();
20715ec0898bSCarwyn Ellis    addRemovableDevicesMenuItems();
20725ec0898bSCarwyn Ellis
2073ca3de7b5SAkihiko Odaki    dcl.con = qemu_console_lookup_default();
2074ca3de7b5SAkihiko Odaki    kbd = qkbd_state_init(dcl.con);
2075ca3de7b5SAkihiko Odaki
20763e230dd2SCorentin Chary    // register vga output callbacks
2077cc7859c3SAkihiko Odaki    register_displaychangelistener(&dcl);
2078ca3de7b5SAkihiko Odaki    [cocoaView updateUIInfo];
20797e3e20d8SAkihiko Odaki
20807e3e20d8SAkihiko Odaki    qemu_event_init(&cbevent, false);
20817e3e20d8SAkihiko Odaki    cbowner = [[QemuCocoaPasteboardTypeOwner alloc] init];
20827e3e20d8SAkihiko Odaki    qemu_clipboard_peer_register(&cbpeer);
2083bab6a301SAkihiko Odaki
2084bab6a301SAkihiko Odaki    [pool release];
20853e230dd2SCorentin Chary}
20865013b9e4SGerd Hoffmann
20875013b9e4SGerd Hoffmannstatic QemuDisplay qemu_display_cocoa = {
20885013b9e4SGerd Hoffmann    .type       = DISPLAY_TYPE_COCOA,
20895013b9e4SGerd Hoffmann    .init       = cocoa_display_init,
20905013b9e4SGerd Hoffmann};
20915013b9e4SGerd Hoffmann
20925013b9e4SGerd Hoffmannstatic void register_cocoa(void)
20935013b9e4SGerd Hoffmann{
20945013b9e4SGerd Hoffmann    qemu_display_register(&qemu_display_cocoa);
20955013b9e4SGerd Hoffmann}
20965013b9e4SGerd Hoffmann
20975013b9e4SGerd Hoffmanntype_init(register_cocoa);
2098