xref: /openbmc/qemu/ui/cocoa.m (revision 712f7150a8fba24c4afdc6bafa035cd702841f4d)
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>
28d2277f02SAkihiko Odaki#import <QuartzCore/QuartzCore.h>
293bbbee18SAndreas Färber#include <crt_externs.h>
303e230dd2SCorentin Chary
3149f95221SMarc-André Lureau#include "qemu/help-texts.h"
3288c39c86SMarc-André Lureau#include "qemu-main.h"
337e3e20d8SAkihiko Odaki#include "ui/clipboard.h"
3428ecbaeeSPaolo Bonzini#include "ui/console.h"
3521bae11aSGerd Hoffmann#include "ui/input.h"
366d73bb64SAkihiko Odaki#include "ui/kbd-state.h"
379c17d615SPaolo Bonzini#include "sysemu/sysemu.h"
3854d31236SMarkus Armbruster#include "sysemu/runstate.h"
392910abd6SAkihiko Odaki#include "sysemu/runstate-action.h"
40b0c3cf94SClaudio Fontana#include "sysemu/cpu-throttle.h"
41e688df6bSMarkus Armbruster#include "qapi/error.h"
4216bf5234SMarkus Armbruster#include "qapi/qapi-commands-block.h"
4390f8c0f9SPhilippe Mathieu-Daudé#include "qapi/qapi-commands-machine.h"
4416bf5234SMarkus Armbruster#include "qapi/qapi-commands-misc.h"
45693a3e01SJohn Arbuckle#include "sysemu/blockdev.h"
469e8204b1SProgrammingkid#include "qemu-version.h"
47e31746ecSAkihiko Odaki#include "qemu/cutils.h"
48db725815SMarkus Armbruster#include "qemu/main-loop.h"
490b8fa32fSMarkus Armbruster#include "qemu/module.h"
50cc37d98bSRichard Henderson#include "qemu/error-report.h"
51aaac714fSJohn Arbuckle#include <Carbon/Carbon.h>
522e5b09fdSMarkus Armbruster#include "hw/core/cpu.h"
533e230dd2SCorentin Chary
54f5af8027SDavid Parsons#ifndef MAC_OS_VERSION_14_0
55f5af8027SDavid Parsons#define MAC_OS_VERSION_14_0 140000
56f5af8027SDavid Parsons#endif
57f5af8027SDavid Parsons
583e230dd2SCorentin Chary//#define DEBUG
593e230dd2SCorentin Chary
603e230dd2SCorentin Chary#ifdef DEBUG
613e230dd2SCorentin Chary#define COCOA_DEBUG(...)  { (void) fprintf (stdout, __VA_ARGS__); }
623e230dd2SCorentin Chary#else
633e230dd2SCorentin Chary#define COCOA_DEBUG(...)  ((void) 0)
643e230dd2SCorentin Chary#endif
653e230dd2SCorentin Chary
663e230dd2SCorentin Chary#define cgrect(nsrect) (*(CGRect *)&(nsrect))
673e230dd2SCorentin Chary
6823bdd0deSChristian Schoenebeck#define UC_CTRL_KEY "\xe2\x8c\x83"
6923bdd0deSChristian Schoenebeck#define UC_ALT_KEY "\xe2\x8c\xa5"
7023bdd0deSChristian Schoenebeck
713e230dd2SCorentin Charytypedef struct {
723e230dd2SCorentin Chary    int width;
733e230dd2SCorentin Chary    int height;
743e230dd2SCorentin Chary} QEMUScreen;
753e230dd2SCorentin Chary
76cc7859c3SAkihiko Odakistatic void cocoa_update(DisplayChangeListener *dcl,
77cc7859c3SAkihiko Odaki                         int x, int y, int w, int h);
78cc7859c3SAkihiko Odaki
79cc7859c3SAkihiko Odakistatic void cocoa_switch(DisplayChangeListener *dcl,
80cc7859c3SAkihiko Odaki                         DisplaySurface *surface);
81cc7859c3SAkihiko Odaki
82cc7859c3SAkihiko Odakistatic void cocoa_refresh(DisplayChangeListener *dcl);
83d2277f02SAkihiko Odakistatic void cocoa_mouse_set(DisplayChangeListener *dcl, int x, int y, bool on);
84d2277f02SAkihiko Odakistatic void cocoa_cursor_define(DisplayChangeListener *dcl, QEMUCursor *cursor);
85cc7859c3SAkihiko Odaki
86cc7859c3SAkihiko Odakistatic const DisplayChangeListenerOps dcl_ops = {
87cc7859c3SAkihiko Odaki    .dpy_name          = "cocoa",
88cc7859c3SAkihiko Odaki    .dpy_gfx_update = cocoa_update,
89cc7859c3SAkihiko Odaki    .dpy_gfx_switch = cocoa_switch,
90cc7859c3SAkihiko Odaki    .dpy_refresh = cocoa_refresh,
91d2277f02SAkihiko Odaki    .dpy_mouse_set = cocoa_mouse_set,
92d2277f02SAkihiko Odaki    .dpy_cursor_define = cocoa_cursor_define,
93cc7859c3SAkihiko Odaki};
94cc7859c3SAkihiko Odakistatic DisplayChangeListener dcl = {
95cc7859c3SAkihiko Odaki    .ops = &dcl_ops,
96cc7859c3SAkihiko Odaki};
97ca3de7b5SAkihiko Odakistatic QKbdState *kbd;
983487da6aSGerd Hoffmannstatic int cursor_hide = 1;
9948941a52SCarwyn Ellisstatic int left_command_key_enabled = 1;
1004797adceSGustavo Noronha Silvastatic bool swap_opt_cmd;
1013e230dd2SCorentin Chary
102e28a909aSCarwyn Ellisstatic CGInterpolationQuality zoom_interpolation = kCGInterpolationNone;
103cb823408SAkihiko Odakistatic NSTextField *pauseLabel;
1043e230dd2SCorentin Chary
105dff742adSHikaru Nishidastatic bool allow_events;
1065588840fSPeter Maydell
1077e3e20d8SAkihiko Odakistatic NSInteger cbchangecount = -1;
1087e3e20d8SAkihiko Odakistatic QemuClipboardInfo *cbinfo;
1097e3e20d8SAkihiko Odakistatic QemuEvent cbevent;
1107e3e20d8SAkihiko Odaki
111a4a411fbSStefan Hajnoczi// Utility functions to run specified code block with the BQL held
11231819e95SPeter Maydelltypedef void (^CodeBlock)(void);
11360105d7aSPeter Maydelltypedef bool (^BoolCodeBlock)(void);
11431819e95SPeter Maydell
115195801d7SStefan Hajnoczistatic void with_bql(CodeBlock block)
11631819e95SPeter Maydell{
117195801d7SStefan Hajnoczi    bool locked = bql_locked();
11831819e95SPeter Maydell    if (!locked) {
119195801d7SStefan Hajnoczi        bql_lock();
12031819e95SPeter Maydell    }
12131819e95SPeter Maydell    block();
12231819e95SPeter Maydell    if (!locked) {
123195801d7SStefan Hajnoczi        bql_unlock();
12431819e95SPeter Maydell    }
12531819e95SPeter Maydell}
12631819e95SPeter Maydell
127195801d7SStefan Hajnoczistatic bool bool_with_bql(BoolCodeBlock block)
12860105d7aSPeter Maydell{
129195801d7SStefan Hajnoczi    bool locked = bql_locked();
13060105d7aSPeter Maydell    bool val;
13160105d7aSPeter Maydell
13260105d7aSPeter Maydell    if (!locked) {
133195801d7SStefan Hajnoczi        bql_lock();
13460105d7aSPeter Maydell    }
13560105d7aSPeter Maydell    val = block();
13660105d7aSPeter Maydell    if (!locked) {
137195801d7SStefan Hajnoczi        bql_unlock();
13860105d7aSPeter Maydell    }
13960105d7aSPeter Maydell    return val;
14060105d7aSPeter Maydell}
14160105d7aSPeter Maydell
142aaac714fSJohn Arbuckle// Mac to QKeyCode conversion
143cb823408SAkihiko Odakistatic const int mac_to_qkeycode_map[] = {
144aaac714fSJohn Arbuckle    [kVK_ANSI_A] = Q_KEY_CODE_A,
145aaac714fSJohn Arbuckle    [kVK_ANSI_B] = Q_KEY_CODE_B,
146aaac714fSJohn Arbuckle    [kVK_ANSI_C] = Q_KEY_CODE_C,
147aaac714fSJohn Arbuckle    [kVK_ANSI_D] = Q_KEY_CODE_D,
148aaac714fSJohn Arbuckle    [kVK_ANSI_E] = Q_KEY_CODE_E,
149aaac714fSJohn Arbuckle    [kVK_ANSI_F] = Q_KEY_CODE_F,
150aaac714fSJohn Arbuckle    [kVK_ANSI_G] = Q_KEY_CODE_G,
151aaac714fSJohn Arbuckle    [kVK_ANSI_H] = Q_KEY_CODE_H,
152aaac714fSJohn Arbuckle    [kVK_ANSI_I] = Q_KEY_CODE_I,
153aaac714fSJohn Arbuckle    [kVK_ANSI_J] = Q_KEY_CODE_J,
154aaac714fSJohn Arbuckle    [kVK_ANSI_K] = Q_KEY_CODE_K,
155aaac714fSJohn Arbuckle    [kVK_ANSI_L] = Q_KEY_CODE_L,
156aaac714fSJohn Arbuckle    [kVK_ANSI_M] = Q_KEY_CODE_M,
157aaac714fSJohn Arbuckle    [kVK_ANSI_N] = Q_KEY_CODE_N,
158aaac714fSJohn Arbuckle    [kVK_ANSI_O] = Q_KEY_CODE_O,
159aaac714fSJohn Arbuckle    [kVK_ANSI_P] = Q_KEY_CODE_P,
160aaac714fSJohn Arbuckle    [kVK_ANSI_Q] = Q_KEY_CODE_Q,
161aaac714fSJohn Arbuckle    [kVK_ANSI_R] = Q_KEY_CODE_R,
162aaac714fSJohn Arbuckle    [kVK_ANSI_S] = Q_KEY_CODE_S,
163aaac714fSJohn Arbuckle    [kVK_ANSI_T] = Q_KEY_CODE_T,
164aaac714fSJohn Arbuckle    [kVK_ANSI_U] = Q_KEY_CODE_U,
165aaac714fSJohn Arbuckle    [kVK_ANSI_V] = Q_KEY_CODE_V,
166aaac714fSJohn Arbuckle    [kVK_ANSI_W] = Q_KEY_CODE_W,
167aaac714fSJohn Arbuckle    [kVK_ANSI_X] = Q_KEY_CODE_X,
168aaac714fSJohn Arbuckle    [kVK_ANSI_Y] = Q_KEY_CODE_Y,
169aaac714fSJohn Arbuckle    [kVK_ANSI_Z] = Q_KEY_CODE_Z,
1703e230dd2SCorentin Chary
171aaac714fSJohn Arbuckle    [kVK_ANSI_0] = Q_KEY_CODE_0,
172aaac714fSJohn Arbuckle    [kVK_ANSI_1] = Q_KEY_CODE_1,
173aaac714fSJohn Arbuckle    [kVK_ANSI_2] = Q_KEY_CODE_2,
174aaac714fSJohn Arbuckle    [kVK_ANSI_3] = Q_KEY_CODE_3,
175aaac714fSJohn Arbuckle    [kVK_ANSI_4] = Q_KEY_CODE_4,
176aaac714fSJohn Arbuckle    [kVK_ANSI_5] = Q_KEY_CODE_5,
177aaac714fSJohn Arbuckle    [kVK_ANSI_6] = Q_KEY_CODE_6,
178aaac714fSJohn Arbuckle    [kVK_ANSI_7] = Q_KEY_CODE_7,
179aaac714fSJohn Arbuckle    [kVK_ANSI_8] = Q_KEY_CODE_8,
180aaac714fSJohn Arbuckle    [kVK_ANSI_9] = Q_KEY_CODE_9,
181aaac714fSJohn Arbuckle
182aaac714fSJohn Arbuckle    [kVK_ANSI_Grave] = Q_KEY_CODE_GRAVE_ACCENT,
183aaac714fSJohn Arbuckle    [kVK_ANSI_Minus] = Q_KEY_CODE_MINUS,
184aaac714fSJohn Arbuckle    [kVK_ANSI_Equal] = Q_KEY_CODE_EQUAL,
185aaac714fSJohn Arbuckle    [kVK_Delete] = Q_KEY_CODE_BACKSPACE,
186aaac714fSJohn Arbuckle    [kVK_CapsLock] = Q_KEY_CODE_CAPS_LOCK,
187aaac714fSJohn Arbuckle    [kVK_Tab] = Q_KEY_CODE_TAB,
188aaac714fSJohn Arbuckle    [kVK_Return] = Q_KEY_CODE_RET,
189aaac714fSJohn Arbuckle    [kVK_ANSI_LeftBracket] = Q_KEY_CODE_BRACKET_LEFT,
190aaac714fSJohn Arbuckle    [kVK_ANSI_RightBracket] = Q_KEY_CODE_BRACKET_RIGHT,
191aaac714fSJohn Arbuckle    [kVK_ANSI_Backslash] = Q_KEY_CODE_BACKSLASH,
192aaac714fSJohn Arbuckle    [kVK_ANSI_Semicolon] = Q_KEY_CODE_SEMICOLON,
193aaac714fSJohn Arbuckle    [kVK_ANSI_Quote] = Q_KEY_CODE_APOSTROPHE,
194aaac714fSJohn Arbuckle    [kVK_ANSI_Comma] = Q_KEY_CODE_COMMA,
195aaac714fSJohn Arbuckle    [kVK_ANSI_Period] = Q_KEY_CODE_DOT,
196aaac714fSJohn Arbuckle    [kVK_ANSI_Slash] = Q_KEY_CODE_SLASH,
197aaac714fSJohn Arbuckle    [kVK_Space] = Q_KEY_CODE_SPC,
198aaac714fSJohn Arbuckle
199aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad0] = Q_KEY_CODE_KP_0,
200aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad1] = Q_KEY_CODE_KP_1,
201aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad2] = Q_KEY_CODE_KP_2,
202aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad3] = Q_KEY_CODE_KP_3,
203aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad4] = Q_KEY_CODE_KP_4,
204aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad5] = Q_KEY_CODE_KP_5,
205aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad6] = Q_KEY_CODE_KP_6,
206aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad7] = Q_KEY_CODE_KP_7,
207aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad8] = Q_KEY_CODE_KP_8,
208aaac714fSJohn Arbuckle    [kVK_ANSI_Keypad9] = Q_KEY_CODE_KP_9,
209aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadDecimal] = Q_KEY_CODE_KP_DECIMAL,
210aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadEnter] = Q_KEY_CODE_KP_ENTER,
211aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadPlus] = Q_KEY_CODE_KP_ADD,
212aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadMinus] = Q_KEY_CODE_KP_SUBTRACT,
213aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadMultiply] = Q_KEY_CODE_KP_MULTIPLY,
214aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadDivide] = Q_KEY_CODE_KP_DIVIDE,
215aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadEquals] = Q_KEY_CODE_KP_EQUALS,
216aaac714fSJohn Arbuckle    [kVK_ANSI_KeypadClear] = Q_KEY_CODE_NUM_LOCK,
217aaac714fSJohn Arbuckle
218aaac714fSJohn Arbuckle    [kVK_UpArrow] = Q_KEY_CODE_UP,
219aaac714fSJohn Arbuckle    [kVK_DownArrow] = Q_KEY_CODE_DOWN,
220aaac714fSJohn Arbuckle    [kVK_LeftArrow] = Q_KEY_CODE_LEFT,
221aaac714fSJohn Arbuckle    [kVK_RightArrow] = Q_KEY_CODE_RIGHT,
222aaac714fSJohn Arbuckle
223aaac714fSJohn Arbuckle    [kVK_Help] = Q_KEY_CODE_INSERT,
224aaac714fSJohn Arbuckle    [kVK_Home] = Q_KEY_CODE_HOME,
225aaac714fSJohn Arbuckle    [kVK_PageUp] = Q_KEY_CODE_PGUP,
226aaac714fSJohn Arbuckle    [kVK_PageDown] = Q_KEY_CODE_PGDN,
227aaac714fSJohn Arbuckle    [kVK_End] = Q_KEY_CODE_END,
228aaac714fSJohn Arbuckle    [kVK_ForwardDelete] = Q_KEY_CODE_DELETE,
229aaac714fSJohn Arbuckle
230aaac714fSJohn Arbuckle    [kVK_Escape] = Q_KEY_CODE_ESC,
231aaac714fSJohn Arbuckle
232aaac714fSJohn Arbuckle    /* The Power key can't be used directly because the operating system uses
233aaac714fSJohn Arbuckle     * it. This key can be emulated by using it in place of another key such as
234aaac714fSJohn Arbuckle     * F1. Don't forget to disable the real key binding.
235aaac714fSJohn Arbuckle     */
236aaac714fSJohn Arbuckle    /* [kVK_F1] = Q_KEY_CODE_POWER, */
237aaac714fSJohn Arbuckle
238aaac714fSJohn Arbuckle    [kVK_F1] = Q_KEY_CODE_F1,
239aaac714fSJohn Arbuckle    [kVK_F2] = Q_KEY_CODE_F2,
240aaac714fSJohn Arbuckle    [kVK_F3] = Q_KEY_CODE_F3,
241aaac714fSJohn Arbuckle    [kVK_F4] = Q_KEY_CODE_F4,
242aaac714fSJohn Arbuckle    [kVK_F5] = Q_KEY_CODE_F5,
243aaac714fSJohn Arbuckle    [kVK_F6] = Q_KEY_CODE_F6,
244aaac714fSJohn Arbuckle    [kVK_F7] = Q_KEY_CODE_F7,
245aaac714fSJohn Arbuckle    [kVK_F8] = Q_KEY_CODE_F8,
246aaac714fSJohn Arbuckle    [kVK_F9] = Q_KEY_CODE_F9,
247aaac714fSJohn Arbuckle    [kVK_F10] = Q_KEY_CODE_F10,
248aaac714fSJohn Arbuckle    [kVK_F11] = Q_KEY_CODE_F11,
249aaac714fSJohn Arbuckle    [kVK_F12] = Q_KEY_CODE_F12,
250aaac714fSJohn Arbuckle    [kVK_F13] = Q_KEY_CODE_PRINT,
251aaac714fSJohn Arbuckle    [kVK_F14] = Q_KEY_CODE_SCROLL_LOCK,
252aaac714fSJohn Arbuckle    [kVK_F15] = Q_KEY_CODE_PAUSE,
253aaac714fSJohn Arbuckle
254708b7255SAkihiko Odaki    // JIS keyboards only
255708b7255SAkihiko Odaki    [kVK_JIS_Yen] = Q_KEY_CODE_YEN,
256708b7255SAkihiko Odaki    [kVK_JIS_Underscore] = Q_KEY_CODE_RO,
257708b7255SAkihiko Odaki    [kVK_JIS_KeypadComma] = Q_KEY_CODE_KP_COMMA,
258708b7255SAkihiko Odaki    [kVK_JIS_Eisu] = Q_KEY_CODE_MUHENKAN,
259708b7255SAkihiko Odaki    [kVK_JIS_Kana] = Q_KEY_CODE_HENKAN,
260708b7255SAkihiko Odaki
2613e230dd2SCorentin Chary    /*
262aaac714fSJohn Arbuckle     * The eject and volume keys can't be used here because they are handled at
263aaac714fSJohn Arbuckle     * a lower level than what an Application can see.
2643e230dd2SCorentin Chary     */
2653e230dd2SCorentin Chary};
2663e230dd2SCorentin Chary
2673e230dd2SCorentin Charystatic int cocoa_keycode_to_qemu(int keycode)
2683e230dd2SCorentin Chary{
269aaac714fSJohn Arbuckle    if (ARRAY_SIZE(mac_to_qkeycode_map) <= keycode) {
2704313739aSAkihiko Odaki        error_report("(cocoa) warning unknown keycode 0x%x", keycode);
2713e230dd2SCorentin Chary        return 0;
2723e230dd2SCorentin Chary    }
273aaac714fSJohn Arbuckle    return mac_to_qkeycode_map[keycode];
2743e230dd2SCorentin Chary}
2753e230dd2SCorentin Chary
276693a3e01SJohn Arbuckle/* Displays an alert dialog box with the specified message */
277693a3e01SJohn Arbucklestatic void QEMU_Alert(NSString *message)
278693a3e01SJohn Arbuckle{
279693a3e01SJohn Arbuckle    NSAlert *alert;
280693a3e01SJohn Arbuckle    alert = [NSAlert new];
281693a3e01SJohn Arbuckle    [alert setMessageText: message];
282693a3e01SJohn Arbuckle    [alert runModal];
283693a3e01SJohn Arbuckle}
2843e230dd2SCorentin Chary
285693a3e01SJohn Arbuckle/* Handles any errors that happen with a device transaction */
286693a3e01SJohn Arbucklestatic void handleAnyDeviceErrors(Error * err)
287693a3e01SJohn Arbuckle{
288693a3e01SJohn Arbuckle    if (err) {
289693a3e01SJohn Arbuckle        QEMU_Alert([NSString stringWithCString: error_get_pretty(err)
290693a3e01SJohn Arbuckle                                      encoding: NSASCIIStringEncoding]);
291693a3e01SJohn Arbuckle        error_free(err);
292693a3e01SJohn Arbuckle    }
293693a3e01SJohn Arbuckle}
2943e230dd2SCorentin Chary
2953e230dd2SCorentin Chary/*
2963e230dd2SCorentin Chary ------------------------------------------------------
2973e230dd2SCorentin Chary    QemuCocoaView
2983e230dd2SCorentin Chary ------------------------------------------------------
2993e230dd2SCorentin Chary*/
3003e230dd2SCorentin Chary@interface QemuCocoaView : NSView
3013e230dd2SCorentin Chary{
3023e230dd2SCorentin Chary    QEMUScreen screen;
3035588840fSPeter Maydell    pixman_image_t *pixman_image;
304e7b53d16SAkihiko Odaki    /* The state surrounding mouse grabbing is potentially confusing.
305e7b53d16SAkihiko Odaki     * isAbsoluteEnabled tracks qemu_input_is_absolute() [ie "is the emulated
306e7b53d16SAkihiko Odaki     *   pointing device an absolute-position one?"], but is only updated on
307e7b53d16SAkihiko Odaki     *   next refresh.
308e7b53d16SAkihiko Odaki     * isMouseGrabbed tracks whether GUI events are directed to the guest;
309e7b53d16SAkihiko Odaki     *   it controls whether special keys like Cmd get sent to the guest,
310e7b53d16SAkihiko Odaki     *   and whether we capture the mouse when in non-absolute mode.
311e7b53d16SAkihiko Odaki     */
31249b9bd4dSPeter Maydell    BOOL isMouseGrabbed;
3133e230dd2SCorentin Chary    BOOL isAbsoluteEnabled;
314f844cdb9SGustavo Noronha Silva    CFMachPortRef eventsTap;
3153f5ef05fSAkihiko Odaki    CGColorSpaceRef colorspace;
316d2277f02SAkihiko Odaki    CALayer *cursorLayer;
317d2277f02SAkihiko Odaki    QEMUCursor *cursor;
318d2277f02SAkihiko Odaki    int mouseX;
319d2277f02SAkihiko Odaki    int mouseY;
320d2277f02SAkihiko Odaki    bool mouseOn;
3213e230dd2SCorentin Chary}
32272a3e316SPeter Maydell- (void) switchSurface:(pixman_image_t *)image;
3233e230dd2SCorentin Chary- (void) grabMouse;
3243e230dd2SCorentin Chary- (void) ungrabMouse;
325f844cdb9SGustavo Noronha Silva- (void) setFullGrab:(id)sender;
3269c3a418eSJohn Arbuckle- (void) handleMonitorInput:(NSEvent *)event;
32760105d7aSPeter Maydell- (bool) handleEvent:(NSEvent *)event;
32860105d7aSPeter Maydell- (bool) handleEventLocked:(NSEvent *)event;
329e7b53d16SAkihiko Odaki- (void) notifyMouseModeChange;
33049b9bd4dSPeter Maydell- (BOOL) isMouseGrabbed;
3313e230dd2SCorentin Chary- (QEMUScreen) gscreen;
3323b178b71SJohn Arbuckle- (void) raiseAllKeys;
3333e230dd2SCorentin Chary@end
3343e230dd2SCorentin Chary
3357fee199cSAndreas FärberQemuCocoaView *cocoaView;
3367fee199cSAndreas Färber
337f844cdb9SGustavo Noronha Silvastatic CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEventRef cgEvent, void *userInfo)
338f844cdb9SGustavo Noronha Silva{
33921eb752fSPhilippe Mathieu-Daudé    QemuCocoaView *view = userInfo;
340f844cdb9SGustavo Noronha Silva    NSEvent *event = [NSEvent eventWithCGEvent:cgEvent];
34121eb752fSPhilippe Mathieu-Daudé    if ([view isMouseGrabbed] && [view handleEvent:event]) {
342f844cdb9SGustavo Noronha Silva        COCOA_DEBUG("Global events tap: qemu handled the event, capturing!\n");
343f844cdb9SGustavo Noronha Silva        return NULL;
344f844cdb9SGustavo Noronha Silva    }
345f844cdb9SGustavo Noronha Silva    COCOA_DEBUG("Global events tap: qemu did not handle the event, letting it through...\n");
346f844cdb9SGustavo Noronha Silva
347f844cdb9SGustavo Noronha Silva    return cgEvent;
348f844cdb9SGustavo Noronha Silva}
349f844cdb9SGustavo Noronha Silva
3503e230dd2SCorentin Chary@implementation QemuCocoaView
3513e230dd2SCorentin Chary- (id)initWithFrame:(NSRect)frameRect
3523e230dd2SCorentin Chary{
3533e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaView: initWithFrame\n");
3543e230dd2SCorentin Chary
3553e230dd2SCorentin Chary    self = [super initWithFrame:frameRect];
3563e230dd2SCorentin Chary    if (self) {
3573e230dd2SCorentin Chary
358ccebb9aeSAkihiko Odaki        NSTrackingAreaOptions options = NSTrackingActiveInKeyWindow |
359ccebb9aeSAkihiko Odaki                                        NSTrackingMouseEnteredAndExited |
360ccebb9aeSAkihiko Odaki                                        NSTrackingMouseMoved |
361ccebb9aeSAkihiko Odaki                                        NSTrackingInVisibleRect;
362ccebb9aeSAkihiko Odaki
363ccebb9aeSAkihiko Odaki        NSTrackingArea *trackingArea =
364ccebb9aeSAkihiko Odaki            [[NSTrackingArea alloc] initWithRect:CGRectZero
365ccebb9aeSAkihiko Odaki                                         options:options
366ccebb9aeSAkihiko Odaki                                           owner:self
367ccebb9aeSAkihiko Odaki                                        userInfo:nil];
368ccebb9aeSAkihiko Odaki
369ccebb9aeSAkihiko Odaki        [self addTrackingArea:trackingArea];
370ccebb9aeSAkihiko Odaki        [trackingArea release];
3713e230dd2SCorentin Chary        screen.width = frameRect.size.width;
3723e230dd2SCorentin Chary        screen.height = frameRect.size.height;
3733f5ef05fSAkihiko Odaki        colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
374f5af8027SDavid Parsons#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_14_0
375f5af8027SDavid Parsons        [self setClipsToBounds:YES];
376f5af8027SDavid Parsons#endif
377d2277f02SAkihiko Odaki        [self setWantsLayer:YES];
378d2277f02SAkihiko Odaki        cursorLayer = [[CALayer alloc] init];
379d2277f02SAkihiko Odaki        [cursorLayer setAnchorPoint:CGPointMake(0, 1)];
380d2277f02SAkihiko Odaki        [cursorLayer setAutoresizingMask:kCALayerMaxXMargin |
381d2277f02SAkihiko Odaki                                         kCALayerMinYMargin];
382d2277f02SAkihiko Odaki        [[self layer] addSublayer:cursorLayer];
3833e230dd2SCorentin Chary
3843e230dd2SCorentin Chary    }
3853e230dd2SCorentin Chary    return self;
3863e230dd2SCorentin Chary}
3873e230dd2SCorentin Chary
3883e230dd2SCorentin Chary- (void) dealloc
3893e230dd2SCorentin Chary{
3903e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaView: dealloc\n");
3913e230dd2SCorentin Chary
392c0ff29d1SAkihiko Odaki    if (pixman_image) {
3935588840fSPeter Maydell        pixman_image_unref(pixman_image);
3945588840fSPeter Maydell    }
3953e230dd2SCorentin Chary
396f844cdb9SGustavo Noronha Silva    if (eventsTap) {
397f844cdb9SGustavo Noronha Silva        CFRelease(eventsTap);
398f844cdb9SGustavo Noronha Silva    }
399f844cdb9SGustavo Noronha Silva
4003f5ef05fSAkihiko Odaki    CGColorSpaceRelease(colorspace);
401d2277f02SAkihiko Odaki    [cursorLayer release];
402d2277f02SAkihiko Odaki    cursor_unref(cursor);
4033e230dd2SCorentin Chary    [super dealloc];
4043e230dd2SCorentin Chary}
4053e230dd2SCorentin Chary
4063e230dd2SCorentin Chary- (BOOL) isOpaque
4073e230dd2SCorentin Chary{
4083e230dd2SCorentin Chary    return YES;
4093e230dd2SCorentin Chary}
4103e230dd2SCorentin Chary
41191aa508dSAkihiko Odaki- (void) viewDidMoveToWindow
41291aa508dSAkihiko Odaki{
41391aa508dSAkihiko Odaki    [self resizeWindow];
4142044dff8SChen Zhang}
4152044dff8SChen Zhang
416ca3de7b5SAkihiko Odaki- (void) selectConsoleLocked:(unsigned int)index
417ca3de7b5SAkihiko Odaki{
418ca3de7b5SAkihiko Odaki    QemuConsole *con = qemu_console_lookup_by_index(index);
419ca3de7b5SAkihiko Odaki    if (!con) {
420ca3de7b5SAkihiko Odaki        return;
421ca3de7b5SAkihiko Odaki    }
422ca3de7b5SAkihiko Odaki
423ca3de7b5SAkihiko Odaki    unregister_displaychangelistener(&dcl);
424ca3de7b5SAkihiko Odaki    qkbd_state_switch_console(kbd, con);
425ca3de7b5SAkihiko Odaki    dcl.con = con;
426ca3de7b5SAkihiko Odaki    register_displaychangelistener(&dcl);
427e7b53d16SAkihiko Odaki    [self notifyMouseModeChange];
428ca3de7b5SAkihiko Odaki    [self updateUIInfo];
429ca3de7b5SAkihiko Odaki}
430ca3de7b5SAkihiko Odaki
43113aefd30SPeter Maydell- (void) hideCursor
43213aefd30SPeter Maydell{
43313aefd30SPeter Maydell    if (!cursor_hide) {
43413aefd30SPeter Maydell        return;
43513aefd30SPeter Maydell    }
43613aefd30SPeter Maydell    [NSCursor hide];
43713aefd30SPeter Maydell}
43813aefd30SPeter Maydell
43913aefd30SPeter Maydell- (void) unhideCursor
44013aefd30SPeter Maydell{
44113aefd30SPeter Maydell    if (!cursor_hide) {
44213aefd30SPeter Maydell        return;
44313aefd30SPeter Maydell    }
44413aefd30SPeter Maydell    [NSCursor unhide];
44513aefd30SPeter Maydell}
44613aefd30SPeter Maydell
447d2277f02SAkihiko Odaki- (void)setMouseX:(int)x y:(int)y on:(bool)on
448d2277f02SAkihiko Odaki{
449d2277f02SAkihiko Odaki    CGPoint position;
450d2277f02SAkihiko Odaki
451d2277f02SAkihiko Odaki    mouseX = x;
452d2277f02SAkihiko Odaki    mouseY = y;
453d2277f02SAkihiko Odaki    mouseOn = on;
454d2277f02SAkihiko Odaki
455d2277f02SAkihiko Odaki    position.x = mouseX;
456d2277f02SAkihiko Odaki    position.y = screen.height - mouseY;
457d2277f02SAkihiko Odaki
458d2277f02SAkihiko Odaki    [CATransaction begin];
459d2277f02SAkihiko Odaki    [CATransaction setDisableActions:YES];
460d2277f02SAkihiko Odaki    [cursorLayer setPosition:position];
461d2277f02SAkihiko Odaki    [cursorLayer setHidden:!mouseOn];
462d2277f02SAkihiko Odaki    [CATransaction commit];
463d2277f02SAkihiko Odaki}
464d2277f02SAkihiko Odaki
465d2277f02SAkihiko Odaki- (void)setCursor:(QEMUCursor *)given_cursor
466d2277f02SAkihiko Odaki{
467d2277f02SAkihiko Odaki    CGDataProviderRef provider;
468d2277f02SAkihiko Odaki    CGImageRef image;
469d2277f02SAkihiko Odaki    CGRect bounds = CGRectZero;
470d2277f02SAkihiko Odaki
471d2277f02SAkihiko Odaki    cursor_unref(cursor);
472d2277f02SAkihiko Odaki    cursor = given_cursor;
473d2277f02SAkihiko Odaki
474d2277f02SAkihiko Odaki    if (!cursor) {
475d2277f02SAkihiko Odaki        return;
476d2277f02SAkihiko Odaki    }
477d2277f02SAkihiko Odaki
478d2277f02SAkihiko Odaki    cursor_ref(cursor);
479d2277f02SAkihiko Odaki
480d2277f02SAkihiko Odaki    bounds.size.width = cursor->width;
481d2277f02SAkihiko Odaki    bounds.size.height = cursor->height;
482d2277f02SAkihiko Odaki
483d2277f02SAkihiko Odaki    provider = CGDataProviderCreateWithData(
484d2277f02SAkihiko Odaki        NULL,
485d2277f02SAkihiko Odaki        cursor->data,
486d2277f02SAkihiko Odaki        cursor->width * cursor->height * 4,
487d2277f02SAkihiko Odaki        NULL
488d2277f02SAkihiko Odaki    );
489d2277f02SAkihiko Odaki
490d2277f02SAkihiko Odaki    image = CGImageCreate(
491d2277f02SAkihiko Odaki        cursor->width, //width
492d2277f02SAkihiko Odaki        cursor->height, //height
493d2277f02SAkihiko Odaki        8, //bitsPerComponent
494d2277f02SAkihiko Odaki        32, //bitsPerPixel
495d2277f02SAkihiko Odaki        cursor->width * 4, //bytesPerRow
496d2277f02SAkihiko Odaki        colorspace, //colorspace
497d2277f02SAkihiko Odaki        kCGBitmapByteOrder32Little | kCGImageAlphaFirst, //bitmapInfo
498d2277f02SAkihiko Odaki        provider, //provider
499d2277f02SAkihiko Odaki        NULL, //decode
500d2277f02SAkihiko Odaki        0, //interpolate
501d2277f02SAkihiko Odaki        kCGRenderingIntentDefault //intent
502d2277f02SAkihiko Odaki    );
503d2277f02SAkihiko Odaki
504d2277f02SAkihiko Odaki    CGDataProviderRelease(provider);
505d2277f02SAkihiko Odaki    [CATransaction begin];
506d2277f02SAkihiko Odaki    [CATransaction setDisableActions:YES];
507d2277f02SAkihiko Odaki    [cursorLayer setBounds:bounds];
508d2277f02SAkihiko Odaki    [cursorLayer setContents:(id)image];
509d2277f02SAkihiko Odaki    [CATransaction commit];
510d2277f02SAkihiko Odaki    CGImageRelease(image);
511d2277f02SAkihiko Odaki}
512d2277f02SAkihiko Odaki
5133e230dd2SCorentin Chary- (void) drawRect:(NSRect) rect
5143e230dd2SCorentin Chary{
5153e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaView: drawRect\n");
5163e230dd2SCorentin Chary
5173e230dd2SCorentin Chary    // get CoreGraphic context
5185e24600aSBrendan Shanks    CGContextRef viewContextRef = [[NSGraphicsContext currentContext] CGContext];
5195e24600aSBrendan Shanks
520e28a909aSCarwyn Ellis    CGContextSetInterpolationQuality (viewContextRef, zoom_interpolation);
5213e230dd2SCorentin Chary    CGContextSetShouldAntialias (viewContextRef, NO);
5223e230dd2SCorentin Chary
5233e230dd2SCorentin Chary    // draw screen bitmap directly to Core Graphics context
524c0ff29d1SAkihiko Odaki    if (!pixman_image) {
5257d270b1cSPeter Maydell        // Draw request before any guest device has set up a framebuffer:
5267d270b1cSPeter Maydell        // just draw an opaque black rectangle
5277d270b1cSPeter Maydell        CGContextSetRGBFillColor(viewContextRef, 0, 0, 0, 1.0);
5287d270b1cSPeter Maydell        CGContextFillRect(viewContextRef, NSRectToCGRect(rect));
5297d270b1cSPeter Maydell    } else {
530c0ff29d1SAkihiko Odaki        int w = pixman_image_get_width(pixman_image);
531c0ff29d1SAkihiko Odaki        int h = pixman_image_get_height(pixman_image);
532c0ff29d1SAkihiko Odaki        int bitsPerPixel = PIXMAN_FORMAT_BPP(pixman_image_get_format(pixman_image));
533d9c32b8fSAkihiko Odaki        int stride = pixman_image_get_stride(pixman_image);
534c0ff29d1SAkihiko Odaki        CGDataProviderRef dataProviderRef = CGDataProviderCreateWithData(
535c0ff29d1SAkihiko Odaki            NULL,
536c0ff29d1SAkihiko Odaki            pixman_image_get_data(pixman_image),
537d9c32b8fSAkihiko Odaki            stride * h,
538c0ff29d1SAkihiko Odaki            NULL
539c0ff29d1SAkihiko Odaki        );
5403e230dd2SCorentin Chary        CGImageRef imageRef = CGImageCreate(
541c0ff29d1SAkihiko Odaki            w, //width
542c0ff29d1SAkihiko Odaki            h, //height
543d9c32b8fSAkihiko Odaki            DIV_ROUND_UP(bitsPerPixel, 8) * 2, //bitsPerComponent
544c0ff29d1SAkihiko Odaki            bitsPerPixel, //bitsPerPixel
545d9c32b8fSAkihiko Odaki            stride, //bytesPerRow
5463f5ef05fSAkihiko Odaki            colorspace, //colorspace
547ae57d35cSAkihiko Odaki            kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, //bitmapInfo
5483e230dd2SCorentin Chary            dataProviderRef, //provider
5493e230dd2SCorentin Chary            NULL, //decode
5503e230dd2SCorentin Chary            0, //interpolate
5513e230dd2SCorentin Chary            kCGRenderingIntentDefault //intent
5523e230dd2SCorentin Chary        );
5533e230dd2SCorentin Chary        // selective drawing code (draws only dirty rectangles) (OS X >= 10.4)
5543e230dd2SCorentin Chary        const NSRect *rectList;
5553e230dd2SCorentin Chary        NSInteger rectCount;
5563e230dd2SCorentin Chary        int i;
5573e230dd2SCorentin Chary        CGImageRef clipImageRef;
5583e230dd2SCorentin Chary        CGRect clipRect;
5593e230dd2SCorentin Chary
5603e230dd2SCorentin Chary        [self getRectsBeingDrawn:&rectList count:&rectCount];
5613e230dd2SCorentin Chary        for (i = 0; i < rectCount; i++) {
562fcb03de7SAkihiko Odaki            clipRect = rectList[i];
563fcb03de7SAkihiko Odaki            clipRect.origin.y = (float)h - (clipRect.origin.y + clipRect.size.height);
5643e230dd2SCorentin Chary            clipImageRef = CGImageCreateWithImageInRect(
5653e230dd2SCorentin Chary                                                        imageRef,
5663e230dd2SCorentin Chary                                                        clipRect
5673e230dd2SCorentin Chary                                                        );
5683e230dd2SCorentin Chary            CGContextDrawImage (viewContextRef, cgrect(rectList[i]), clipImageRef);
5693e230dd2SCorentin Chary            CGImageRelease (clipImageRef);
5703e230dd2SCorentin Chary        }
5713e230dd2SCorentin Chary        CGImageRelease (imageRef);
572c0ff29d1SAkihiko Odaki        CGDataProviderRelease(dataProviderRef);
5733e230dd2SCorentin Chary    }
5743e230dd2SCorentin Chary}
5753e230dd2SCorentin Chary
576d2ee0420SAkihiko Odaki- (NSSize)fixAspectRatio:(NSSize)max
577d2ee0420SAkihiko Odaki{
578d2ee0420SAkihiko Odaki    NSSize scaled;
579d2ee0420SAkihiko Odaki    NSSize fixed;
580d2ee0420SAkihiko Odaki
581d2ee0420SAkihiko Odaki    scaled.width = screen.width * max.height;
582d2ee0420SAkihiko Odaki    scaled.height = screen.height * max.width;
583d2ee0420SAkihiko Odaki
584d2ee0420SAkihiko Odaki    /*
585d2ee0420SAkihiko Odaki     * Here screen is our guest's output size, and max is the size of the
586d2ee0420SAkihiko Odaki     * largest possible area of the screen we can display on.
587d2ee0420SAkihiko Odaki     * We want to scale up (screen.width x screen.height) by either:
588d2ee0420SAkihiko Odaki     *   1) max.height / screen.height
589d2ee0420SAkihiko Odaki     *   2) max.width / screen.width
590d2ee0420SAkihiko Odaki     * With the first scale factor the scale will result in an output height of
591d2ee0420SAkihiko Odaki     * max.height (i.e. we will fill the whole height of the available screen
592d2ee0420SAkihiko Odaki     * space and have black bars left and right) and with the second scale
593d2ee0420SAkihiko Odaki     * factor the scaling will result in an output width of max.width (i.e. we
594d2ee0420SAkihiko Odaki     * fill the whole width of the available screen space and have black bars
595d2ee0420SAkihiko Odaki     * top and bottom). We need to pick whichever keeps the whole of the guest
596d2ee0420SAkihiko Odaki     * output on the screen, which is to say the smaller of the two scale
597d2ee0420SAkihiko Odaki     * factors.
598d2ee0420SAkihiko Odaki     * To avoid doing more division than strictly necessary, instead of directly
599d2ee0420SAkihiko Odaki     * comparing scale factors 1 and 2 we instead calculate and compare those
600d2ee0420SAkihiko Odaki     * two scale factors multiplied by (screen.height * screen.width).
601d2ee0420SAkihiko Odaki     */
602d2ee0420SAkihiko Odaki    if (scaled.width < scaled.height) {
603d2ee0420SAkihiko Odaki        fixed.width = scaled.width / screen.height;
604d2ee0420SAkihiko Odaki        fixed.height = max.height;
605d2ee0420SAkihiko Odaki    } else {
606d2ee0420SAkihiko Odaki        fixed.width = max.width;
607d2ee0420SAkihiko Odaki        fixed.height = scaled.height / screen.width;
608d2ee0420SAkihiko Odaki    }
609d2ee0420SAkihiko Odaki
610d2ee0420SAkihiko Odaki    return fixed;
611d2ee0420SAkihiko Odaki}
612d2ee0420SAkihiko Odaki
61391aa508dSAkihiko Odaki- (NSSize) screenSafeAreaSize
6143e230dd2SCorentin Chary{
61591aa508dSAkihiko Odaki    NSSize size = [[[self window] screen] frame].size;
61691aa508dSAkihiko Odaki    NSEdgeInsets insets = [[[self window] screen] safeAreaInsets];
61791aa508dSAkihiko Odaki    size.width -= insets.left + insets.right;
61891aa508dSAkihiko Odaki    size.height -= insets.top + insets.bottom;
61991aa508dSAkihiko Odaki    return size;
6205d1b2eefSProgrammingkid}
62191aa508dSAkihiko Odaki
62291aa508dSAkihiko Odaki- (void) resizeWindow
62391aa508dSAkihiko Odaki{
62491aa508dSAkihiko Odaki    [[self window] setContentAspectRatio:NSMakeSize(screen.width, screen.height)];
62591aa508dSAkihiko Odaki
62655766632SAkihiko Odaki    if (!([[self window] styleMask] & NSWindowStyleMaskResizable)) {
62791aa508dSAkihiko Odaki        [[self window] setContentSize:NSMakeSize(screen.width, screen.height)];
62891aa508dSAkihiko Odaki        [[self window] center];
62991aa508dSAkihiko Odaki    } else if ([[self window] styleMask] & NSWindowStyleMaskFullScreen) {
630d2ee0420SAkihiko Odaki        [[self window] setContentSize:[self fixAspectRatio:[self screenSafeAreaSize]]];
63191aa508dSAkihiko Odaki        [[self window] center];
632d2ee0420SAkihiko Odaki    } else {
633d2ee0420SAkihiko Odaki        [[self window] setContentSize:[self fixAspectRatio:[self frame].size]];
6343e230dd2SCorentin Chary    }
6353e230dd2SCorentin Chary}
6363e230dd2SCorentin Chary
637fcb03de7SAkihiko Odaki- (void) updateBounds
638fcb03de7SAkihiko Odaki{
639fcb03de7SAkihiko Odaki    [self setBoundsSize:NSMakeSize(screen.width, screen.height)];
640fcb03de7SAkihiko Odaki}
641fcb03de7SAkihiko Odaki
642*9cf6e41fSPhilippe Mathieu-Daudé#pragma clang diagnostic push
643*9cf6e41fSPhilippe Mathieu-Daudé#pragma clang diagnostic ignored "-Wdeprecated-declarations"
644*9cf6e41fSPhilippe Mathieu-Daudé
6458d65dee2SPeter Maydell- (void) updateUIInfoLocked
64615280e85SAkihiko Odaki{
647a4a411fbSStefan Hajnoczi    /* Must be called with the BQL, i.e. via updateUIInfo */
64815280e85SAkihiko Odaki    NSSize frameSize;
64915280e85SAkihiko Odaki    QemuUIInfo info;
65015280e85SAkihiko Odaki
65115280e85SAkihiko Odaki    if (!qemu_console_is_graphic(dcl.con)) {
65215280e85SAkihiko Odaki        return;
65315280e85SAkihiko Odaki    }
65415280e85SAkihiko Odaki
65515280e85SAkihiko Odaki    if ([self window]) {
65615280e85SAkihiko Odaki        NSDictionary *description = [[[self window] screen] deviceDescription];
65715280e85SAkihiko Odaki        CGDirectDisplayID display = [[description objectForKey:@"NSScreenNumber"] unsignedIntValue];
65815280e85SAkihiko Odaki        NSSize screenSize = [[[self window] screen] frame].size;
65915280e85SAkihiko Odaki        CGSize screenPhysicalSize = CGDisplayScreenSize(display);
66091aa508dSAkihiko Odaki        bool isFullscreen = ([[self window] styleMask] & NSWindowStyleMaskFullScreen) != 0;
66152eaefd3SAkihiko Odaki        CVDisplayLinkRef displayLink;
66215280e85SAkihiko Odaki
66391aa508dSAkihiko Odaki        frameSize = isFullscreen ? [self screenSafeAreaSize] : [self frame].size;
66452eaefd3SAkihiko Odaki
66552eaefd3SAkihiko Odaki        if (!CVDisplayLinkCreateWithCGDisplay(display, &displayLink)) {
66652eaefd3SAkihiko Odaki            CVTime period = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(displayLink);
66752eaefd3SAkihiko Odaki            CVDisplayLinkRelease(displayLink);
66852eaefd3SAkihiko Odaki            if (!(period.flags & kCVTimeIsIndefinite)) {
66952eaefd3SAkihiko Odaki                update_displaychangelistener(&dcl,
67052eaefd3SAkihiko Odaki                                             1000 * period.timeValue / period.timeScale);
67152eaefd3SAkihiko Odaki                info.refresh_rate = (int64_t)1000 * period.timeScale / period.timeValue;
67252eaefd3SAkihiko Odaki            }
67352eaefd3SAkihiko Odaki        }
67452eaefd3SAkihiko Odaki
67515280e85SAkihiko Odaki        info.width_mm = frameSize.width / screenSize.width * screenPhysicalSize.width;
67615280e85SAkihiko Odaki        info.height_mm = frameSize.height / screenSize.height * screenPhysicalSize.height;
67715280e85SAkihiko Odaki    } else {
67815280e85SAkihiko Odaki        frameSize = [self frame].size;
67915280e85SAkihiko Odaki        info.width_mm = 0;
68015280e85SAkihiko Odaki        info.height_mm = 0;
68115280e85SAkihiko Odaki    }
68215280e85SAkihiko Odaki
68315280e85SAkihiko Odaki    info.xoff = 0;
68415280e85SAkihiko Odaki    info.yoff = 0;
68515280e85SAkihiko Odaki    info.width = frameSize.width;
68615280e85SAkihiko Odaki    info.height = frameSize.height;
68715280e85SAkihiko Odaki
688ca19ef52SMarc-André Lureau    dpy_set_ui_info(dcl.con, &info, TRUE);
68915280e85SAkihiko Odaki}
69015280e85SAkihiko Odaki
691*9cf6e41fSPhilippe Mathieu-Daudé#pragma clang diagnostic pop
692*9cf6e41fSPhilippe Mathieu-Daudé
6938d65dee2SPeter Maydell- (void) updateUIInfo
6948d65dee2SPeter Maydell{
6958d65dee2SPeter Maydell    if (!allow_events) {
6968d65dee2SPeter Maydell        /*
6978d65dee2SPeter Maydell         * Don't try to tell QEMU about UI information in the application
6988d65dee2SPeter Maydell         * startup phase -- we haven't yet registered dcl with the QEMU UI
699bab6a301SAkihiko Odaki         * layer.
7008d65dee2SPeter Maydell         * When cocoa_display_init() does register the dcl, the UI layer
7018d65dee2SPeter Maydell         * will call cocoa_switch(), which will call updateUIInfo, so
7028d65dee2SPeter Maydell         * we don't lose any information here.
7038d65dee2SPeter Maydell         */
7048d65dee2SPeter Maydell        return;
7058d65dee2SPeter Maydell    }
7068d65dee2SPeter Maydell
707195801d7SStefan Hajnoczi    with_bql(^{
7088d65dee2SPeter Maydell        [self updateUIInfoLocked];
7098d65dee2SPeter Maydell    });
7108d65dee2SPeter Maydell}
7118d65dee2SPeter Maydell
71272a3e316SPeter Maydell- (void) switchSurface:(pixman_image_t *)image
7133e230dd2SCorentin Chary{
7145e00d3acSGerd Hoffmann    COCOA_DEBUG("QemuCocoaView: switchSurface\n");
7153e230dd2SCorentin Chary
71672a3e316SPeter Maydell    int w = pixman_image_get_width(image);
71772a3e316SPeter Maydell    int h = pixman_image_get_height(image);
718d3345a04SPeter Maydell
71991aa508dSAkihiko Odaki    if (w != screen.width || h != screen.height) {
720d3345a04SPeter Maydell        // Resize before we trigger the redraw, or we'll redraw at the wrong size
721d3345a04SPeter Maydell        COCOA_DEBUG("switchSurface: new size %d x %d\n", w, h);
722d3345a04SPeter Maydell        screen.width = w;
723d3345a04SPeter Maydell        screen.height = h;
72491aa508dSAkihiko Odaki        [self resizeWindow];
725fcb03de7SAkihiko Odaki        [self updateBounds];
726d3345a04SPeter Maydell    }
7278510d91eSPeter Maydell
7283e230dd2SCorentin Chary    // update screenBuffer
729c0ff29d1SAkihiko Odaki    if (pixman_image) {
7305588840fSPeter Maydell        pixman_image_unref(pixman_image);
7315588840fSPeter Maydell    }
7323e230dd2SCorentin Chary
7335588840fSPeter Maydell    pixman_image = image;
7343e230dd2SCorentin Chary}
7353e230dd2SCorentin Chary
736f844cdb9SGustavo Noronha Silva- (void) setFullGrab:(id)sender
737f844cdb9SGustavo Noronha Silva{
738f844cdb9SGustavo Noronha Silva    COCOA_DEBUG("QemuCocoaView: setFullGrab\n");
739f844cdb9SGustavo Noronha Silva
740f844cdb9SGustavo Noronha Silva    CGEventMask mask = CGEventMaskBit(kCGEventKeyDown) | CGEventMaskBit(kCGEventKeyUp) | CGEventMaskBit(kCGEventFlagsChanged);
741f844cdb9SGustavo Noronha Silva    eventsTap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault,
742f844cdb9SGustavo Noronha Silva                                 mask, handleTapEvent, self);
743f844cdb9SGustavo Noronha Silva    if (!eventsTap) {
744f844cdb9SGustavo Noronha Silva        warn_report("Could not create event tap, system key combos will not be captured.\n");
745f844cdb9SGustavo Noronha Silva        return;
746f844cdb9SGustavo Noronha Silva    } else {
747f844cdb9SGustavo Noronha Silva        COCOA_DEBUG("Global events tap created! Will capture system key combos.\n");
748f844cdb9SGustavo Noronha Silva    }
749f844cdb9SGustavo Noronha Silva
750f844cdb9SGustavo Noronha Silva    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
751f844cdb9SGustavo Noronha Silva    if (!runLoop) {
752f844cdb9SGustavo Noronha Silva        warn_report("Could not obtain current CF RunLoop, system key combos will not be captured.\n");
753f844cdb9SGustavo Noronha Silva        return;
754f844cdb9SGustavo Noronha Silva    }
755f844cdb9SGustavo Noronha Silva
756f844cdb9SGustavo Noronha Silva    CFRunLoopSourceRef tapEventsSrc = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventsTap, 0);
757f844cdb9SGustavo Noronha Silva    if (!tapEventsSrc ) {
758f844cdb9SGustavo Noronha Silva        warn_report("Could not obtain current CF RunLoop, system key combos will not be captured.\n");
759f844cdb9SGustavo Noronha Silva        return;
760f844cdb9SGustavo Noronha Silva    }
761f844cdb9SGustavo Noronha Silva
762f844cdb9SGustavo Noronha Silva    CFRunLoopAddSource(runLoop, tapEventsSrc, kCFRunLoopDefaultMode);
763f844cdb9SGustavo Noronha Silva    CFRelease(tapEventsSrc);
764f844cdb9SGustavo Noronha Silva}
765f844cdb9SGustavo Noronha Silva
7666d73bb64SAkihiko Odaki- (void) toggleKey: (int)keycode {
7676d73bb64SAkihiko Odaki    qkbd_state_key_event(kbd, keycode, !qkbd_state_key_get(kbd, keycode));
768af8862b2SIan McKellar via Qemu-devel}
769af8862b2SIan McKellar via Qemu-devel
7709c3a418eSJohn Arbuckle// Does the work of sending input to the monitor
7719c3a418eSJohn Arbuckle- (void) handleMonitorInput:(NSEvent *)event
7729c3a418eSJohn Arbuckle{
7739c3a418eSJohn Arbuckle    int keysym = 0;
7749c3a418eSJohn Arbuckle    int control_key = 0;
7759c3a418eSJohn Arbuckle
7769c3a418eSJohn Arbuckle    // if the control key is down
7779c3a418eSJohn Arbuckle    if ([event modifierFlags] & NSEventModifierFlagControl) {
7789c3a418eSJohn Arbuckle        control_key = 1;
7799c3a418eSJohn Arbuckle    }
7809c3a418eSJohn Arbuckle
7819c3a418eSJohn Arbuckle    /* translates Macintosh keycodes to QEMU's keysym */
7829c3a418eSJohn Arbuckle
7839459262dSPhilippe Mathieu-Daudé    static const int without_control_translation[] = {
7849c3a418eSJohn Arbuckle        [0 ... 0xff] = 0,   // invalid key
7859c3a418eSJohn Arbuckle
7869c3a418eSJohn Arbuckle        [kVK_UpArrow]       = QEMU_KEY_UP,
7879c3a418eSJohn Arbuckle        [kVK_DownArrow]     = QEMU_KEY_DOWN,
7889c3a418eSJohn Arbuckle        [kVK_RightArrow]    = QEMU_KEY_RIGHT,
7899c3a418eSJohn Arbuckle        [kVK_LeftArrow]     = QEMU_KEY_LEFT,
7909c3a418eSJohn Arbuckle        [kVK_Home]          = QEMU_KEY_HOME,
7919c3a418eSJohn Arbuckle        [kVK_End]           = QEMU_KEY_END,
7929c3a418eSJohn Arbuckle        [kVK_PageUp]        = QEMU_KEY_PAGEUP,
7939c3a418eSJohn Arbuckle        [kVK_PageDown]      = QEMU_KEY_PAGEDOWN,
7949c3a418eSJohn Arbuckle        [kVK_ForwardDelete] = QEMU_KEY_DELETE,
7959c3a418eSJohn Arbuckle        [kVK_Delete]        = QEMU_KEY_BACKSPACE,
7969c3a418eSJohn Arbuckle    };
7979c3a418eSJohn Arbuckle
7989459262dSPhilippe Mathieu-Daudé    static const int with_control_translation[] = {
7999c3a418eSJohn Arbuckle        [0 ... 0xff] = 0,   // invalid key
8009c3a418eSJohn Arbuckle
8019c3a418eSJohn Arbuckle        [kVK_UpArrow]       = QEMU_KEY_CTRL_UP,
8029c3a418eSJohn Arbuckle        [kVK_DownArrow]     = QEMU_KEY_CTRL_DOWN,
8039c3a418eSJohn Arbuckle        [kVK_RightArrow]    = QEMU_KEY_CTRL_RIGHT,
8049c3a418eSJohn Arbuckle        [kVK_LeftArrow]     = QEMU_KEY_CTRL_LEFT,
8059c3a418eSJohn Arbuckle        [kVK_Home]          = QEMU_KEY_CTRL_HOME,
8069c3a418eSJohn Arbuckle        [kVK_End]           = QEMU_KEY_CTRL_END,
8079c3a418eSJohn Arbuckle        [kVK_PageUp]        = QEMU_KEY_CTRL_PAGEUP,
8089c3a418eSJohn Arbuckle        [kVK_PageDown]      = QEMU_KEY_CTRL_PAGEDOWN,
8099c3a418eSJohn Arbuckle    };
8109c3a418eSJohn Arbuckle
8119c3a418eSJohn Arbuckle    if (control_key != 0) { /* If the control key is being used */
8129c3a418eSJohn Arbuckle        if ([event keyCode] < ARRAY_SIZE(with_control_translation)) {
8139c3a418eSJohn Arbuckle            keysym = with_control_translation[[event keyCode]];
8149c3a418eSJohn Arbuckle        }
8159c3a418eSJohn Arbuckle    } else {
8169c3a418eSJohn Arbuckle        if ([event keyCode] < ARRAY_SIZE(without_control_translation)) {
8179c3a418eSJohn Arbuckle            keysym = without_control_translation[[event keyCode]];
8189c3a418eSJohn Arbuckle        }
8199c3a418eSJohn Arbuckle    }
8209c3a418eSJohn Arbuckle
8219c3a418eSJohn Arbuckle    // if not a key that needs translating
8229c3a418eSJohn Arbuckle    if (keysym == 0) {
8239c3a418eSJohn Arbuckle        NSString *ks = [event characters];
8249c3a418eSJohn Arbuckle        if ([ks length] > 0) {
8259c3a418eSJohn Arbuckle            keysym = [ks characterAtIndex:0];
8269c3a418eSJohn Arbuckle        }
8279c3a418eSJohn Arbuckle    }
8289c3a418eSJohn Arbuckle
8299c3a418eSJohn Arbuckle    if (keysym) {
830ca3de7b5SAkihiko Odaki        QemuTextConsole *con = QEMU_TEXT_CONSOLE(dcl.con);
831ca3de7b5SAkihiko Odaki        qemu_text_console_put_keysym(con, keysym);
8329c3a418eSJohn Arbuckle    }
8339c3a418eSJohn Arbuckle}
8349c3a418eSJohn Arbuckle
83560105d7aSPeter Maydell- (bool) handleEvent:(NSEvent *)event
8363e230dd2SCorentin Chary{
837195801d7SStefan Hajnoczi    return bool_with_bql(^{
83860105d7aSPeter Maydell        return [self handleEventLocked:event];
83931819e95SPeter Maydell    });
84031819e95SPeter Maydell}
8413e230dd2SCorentin Chary
84260105d7aSPeter Maydell- (bool) handleEventLocked:(NSEvent *)event
84331819e95SPeter Maydell{
84460105d7aSPeter Maydell    /* Return true if we handled the event, false if it should be given to OSX */
84531819e95SPeter Maydell    COCOA_DEBUG("QemuCocoaView: handleEvent\n");
8460f7be47aSAkihiko Odaki    InputButton button;
847af8862b2SIan McKellar via Qemu-devel    int keycode = 0;
8486d73bb64SAkihiko Odaki    NSUInteger modifiers = [event modifierFlags];
8496d73bb64SAkihiko Odaki
850ad7f2f8eSAkihiko Odaki    /*
851ad7f2f8eSAkihiko Odaki     * Check -[NSEvent modifierFlags] here.
852ad7f2f8eSAkihiko Odaki     *
853ad7f2f8eSAkihiko Odaki     * There is a NSEventType for an event notifying the change of
854ad7f2f8eSAkihiko Odaki     * -[NSEvent modifierFlags], NSEventTypeFlagsChanged but these operations
855ad7f2f8eSAkihiko Odaki     * are performed for any events because a modifier state may change while
856ad7f2f8eSAkihiko Odaki     * the application is inactive (i.e. no events fire) and we don't want to
857ad7f2f8eSAkihiko Odaki     * wait for another modifier state change to detect such a change.
858ad7f2f8eSAkihiko Odaki     *
859ad7f2f8eSAkihiko Odaki     * NSEventModifierFlagCapsLock requires a special treatment. The other flags
860ad7f2f8eSAkihiko Odaki     * are handled in similar manners.
861ad7f2f8eSAkihiko Odaki     *
862ad7f2f8eSAkihiko Odaki     * NSEventModifierFlagCapsLock
863ad7f2f8eSAkihiko Odaki     * ---------------------------
864ad7f2f8eSAkihiko Odaki     *
865ad7f2f8eSAkihiko Odaki     * If CapsLock state is changed, "up" and "down" events will be fired in
866ad7f2f8eSAkihiko Odaki     * sequence, effectively updates CapsLock state on the guest.
867ad7f2f8eSAkihiko Odaki     *
868ad7f2f8eSAkihiko Odaki     * The other flags
869ad7f2f8eSAkihiko Odaki     * ---------------
870ad7f2f8eSAkihiko Odaki     *
871ad7f2f8eSAkihiko Odaki     * If a flag is not set, fire "up" events for all keys which correspond to
872ad7f2f8eSAkihiko Odaki     * the flag. Note that "down" events are not fired here because the flags
873ad7f2f8eSAkihiko Odaki     * checked here do not tell what exact keys are down.
874ad7f2f8eSAkihiko Odaki     *
875ad7f2f8eSAkihiko Odaki     * If one of the keys corresponding to a flag is down, we rely on
876ad7f2f8eSAkihiko Odaki     * -[NSEvent keyCode] of an event whose -[NSEvent type] is
877ad7f2f8eSAkihiko Odaki     * NSEventTypeFlagsChanged to know the exact key which is down, which has
878ad7f2f8eSAkihiko Odaki     * the following two downsides:
879ad7f2f8eSAkihiko Odaki     * - It does not work when the application is inactive as described above.
880ad7f2f8eSAkihiko Odaki     * - It malfactions *after* the modifier state is changed while the
881ad7f2f8eSAkihiko Odaki     *   application is inactive. It is because -[NSEvent keyCode] does not tell
882ad7f2f8eSAkihiko Odaki     *   if the key is up or down, and requires to infer the current state from
883ad7f2f8eSAkihiko Odaki     *   the previous state. It is still possible to fix such a malfanction by
884ad7f2f8eSAkihiko Odaki     *   completely leaving your hands from the keyboard, which hopefully makes
885ad7f2f8eSAkihiko Odaki     *   this implementation usable enough.
886ad7f2f8eSAkihiko Odaki     */
8876d73bb64SAkihiko Odaki    if (!!(modifiers & NSEventModifierFlagCapsLock) !=
8886d73bb64SAkihiko Odaki        qkbd_state_modifier_get(kbd, QKBD_MOD_CAPSLOCK)) {
8896d73bb64SAkihiko Odaki        qkbd_state_key_event(kbd, Q_KEY_CODE_CAPS_LOCK, true);
8906d73bb64SAkihiko Odaki        qkbd_state_key_event(kbd, Q_KEY_CODE_CAPS_LOCK, false);
8916d73bb64SAkihiko Odaki    }
8926d73bb64SAkihiko Odaki
8936d73bb64SAkihiko Odaki    if (!(modifiers & NSEventModifierFlagShift)) {
8946d73bb64SAkihiko Odaki        qkbd_state_key_event(kbd, Q_KEY_CODE_SHIFT, false);
8956d73bb64SAkihiko Odaki        qkbd_state_key_event(kbd, Q_KEY_CODE_SHIFT_R, false);
8966d73bb64SAkihiko Odaki    }
8976d73bb64SAkihiko Odaki    if (!(modifiers & NSEventModifierFlagControl)) {
8986d73bb64SAkihiko Odaki        qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL, false);
8996d73bb64SAkihiko Odaki        qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL_R, false);
9006d73bb64SAkihiko Odaki    }
9016d73bb64SAkihiko Odaki    if (!(modifiers & NSEventModifierFlagOption)) {
9024797adceSGustavo Noronha Silva        if (swap_opt_cmd) {
9034797adceSGustavo Noronha Silva            qkbd_state_key_event(kbd, Q_KEY_CODE_META_L, false);
9044797adceSGustavo Noronha Silva            qkbd_state_key_event(kbd, Q_KEY_CODE_META_R, false);
9054797adceSGustavo Noronha Silva        } else {
9066d73bb64SAkihiko Odaki            qkbd_state_key_event(kbd, Q_KEY_CODE_ALT, false);
9076d73bb64SAkihiko Odaki            qkbd_state_key_event(kbd, Q_KEY_CODE_ALT_R, false);
9086d73bb64SAkihiko Odaki        }
9094797adceSGustavo Noronha Silva    }
9106d73bb64SAkihiko Odaki    if (!(modifiers & NSEventModifierFlagCommand)) {
9114797adceSGustavo Noronha Silva        if (swap_opt_cmd) {
9124797adceSGustavo Noronha Silva            qkbd_state_key_event(kbd, Q_KEY_CODE_ALT, false);
9134797adceSGustavo Noronha Silva            qkbd_state_key_event(kbd, Q_KEY_CODE_ALT_R, false);
9144797adceSGustavo Noronha Silva        } else {
9156d73bb64SAkihiko Odaki            qkbd_state_key_event(kbd, Q_KEY_CODE_META_L, false);
9166d73bb64SAkihiko Odaki            qkbd_state_key_event(kbd, Q_KEY_CODE_META_R, false);
9176d73bb64SAkihiko Odaki        }
9184797adceSGustavo Noronha Silva    }
9193e230dd2SCorentin Chary
9203e230dd2SCorentin Chary    switch ([event type]) {
9214ba967adSBrendan Shanks        case NSEventTypeFlagsChanged:
9226d73bb64SAkihiko Odaki            switch ([event keyCode]) {
9236d73bb64SAkihiko Odaki                case kVK_Shift:
9246d73bb64SAkihiko Odaki                    if (!!(modifiers & NSEventModifierFlagShift)) {
9256d73bb64SAkihiko Odaki                        [self toggleKey:Q_KEY_CODE_SHIFT];
9266d73bb64SAkihiko Odaki                    }
9276d73bb64SAkihiko Odaki                    break;
928af8862b2SIan McKellar via Qemu-devel
9296d73bb64SAkihiko Odaki                case kVK_RightShift:
9306d73bb64SAkihiko Odaki                    if (!!(modifiers & NSEventModifierFlagShift)) {
9316d73bb64SAkihiko Odaki                        [self toggleKey:Q_KEY_CODE_SHIFT_R];
9326d73bb64SAkihiko Odaki                    }
9336d73bb64SAkihiko Odaki                    break;
934af8862b2SIan McKellar via Qemu-devel
9356d73bb64SAkihiko Odaki                case kVK_Control:
9366d73bb64SAkihiko Odaki                    if (!!(modifiers & NSEventModifierFlagControl)) {
9376d73bb64SAkihiko Odaki                        [self toggleKey:Q_KEY_CODE_CTRL];
938af8862b2SIan McKellar via Qemu-devel                    }
9396d73bb64SAkihiko Odaki                    break;
9408895919aSPeter Maydell
9416d73bb64SAkihiko Odaki                case kVK_RightControl:
9426d73bb64SAkihiko Odaki                    if (!!(modifiers & NSEventModifierFlagControl)) {
9436d73bb64SAkihiko Odaki                        [self toggleKey:Q_KEY_CODE_CTRL_R];
9446d73bb64SAkihiko Odaki                    }
9456d73bb64SAkihiko Odaki                    break;
9466d73bb64SAkihiko Odaki
9476d73bb64SAkihiko Odaki                case kVK_Option:
9486d73bb64SAkihiko Odaki                    if (!!(modifiers & NSEventModifierFlagOption)) {
9494797adceSGustavo Noronha Silva                        if (swap_opt_cmd) {
9504797adceSGustavo Noronha Silva                            [self toggleKey:Q_KEY_CODE_META_L];
9514797adceSGustavo Noronha Silva                        } else {
9526d73bb64SAkihiko Odaki                            [self toggleKey:Q_KEY_CODE_ALT];
9536d73bb64SAkihiko Odaki                        }
9544797adceSGustavo Noronha Silva                    }
9556d73bb64SAkihiko Odaki                    break;
9566d73bb64SAkihiko Odaki
9576d73bb64SAkihiko Odaki                case kVK_RightOption:
9586d73bb64SAkihiko Odaki                    if (!!(modifiers & NSEventModifierFlagOption)) {
9594797adceSGustavo Noronha Silva                        if (swap_opt_cmd) {
9604797adceSGustavo Noronha Silva                            [self toggleKey:Q_KEY_CODE_META_R];
9614797adceSGustavo Noronha Silva                        } else {
9626d73bb64SAkihiko Odaki                            [self toggleKey:Q_KEY_CODE_ALT_R];
9636d73bb64SAkihiko Odaki                        }
9644797adceSGustavo Noronha Silva                    }
9656d73bb64SAkihiko Odaki                    break;
9666d73bb64SAkihiko Odaki
9678895919aSPeter Maydell                /* Don't pass command key changes to guest unless mouse is grabbed */
9686d73bb64SAkihiko Odaki                case kVK_Command:
9696d73bb64SAkihiko Odaki                    if (isMouseGrabbed &&
970d6b6dea7SAkihiko Odaki                        !!(modifiers & NSEventModifierFlagCommand) &&
971d6b6dea7SAkihiko Odaki                        left_command_key_enabled) {
9724797adceSGustavo Noronha Silva                        if (swap_opt_cmd) {
9734797adceSGustavo Noronha Silva                            [self toggleKey:Q_KEY_CODE_ALT];
9744797adceSGustavo Noronha Silva                        } else {
9756d73bb64SAkihiko Odaki                            [self toggleKey:Q_KEY_CODE_META_L];
9768895919aSPeter Maydell                        }
9774797adceSGustavo Noronha Silva                    }
9786d73bb64SAkihiko Odaki                    break;
9798895919aSPeter Maydell
9806d73bb64SAkihiko Odaki                case kVK_RightCommand:
9816d73bb64SAkihiko Odaki                    if (isMouseGrabbed &&
9826d73bb64SAkihiko Odaki                        !!(modifiers & NSEventModifierFlagCommand)) {
9834797adceSGustavo Noronha Silva                        if (swap_opt_cmd) {
9844797adceSGustavo Noronha Silva                            [self toggleKey:Q_KEY_CODE_ALT_R];
9854797adceSGustavo Noronha Silva                        } else {
9866d73bb64SAkihiko Odaki                            [self toggleKey:Q_KEY_CODE_META_R];
9873e230dd2SCorentin Chary                        }
9884797adceSGustavo Noronha Silva                    }
9896d73bb64SAkihiko Odaki                    break;
9903e230dd2SCorentin Chary            }
9910f7be47aSAkihiko Odaki            return true;
9924ba967adSBrendan Shanks        case NSEventTypeKeyDown:
9938895919aSPeter Maydell            keycode = cocoa_keycode_to_qemu([event keyCode]);
9943e230dd2SCorentin Chary
9958895919aSPeter Maydell            // forward command key combos to the host UI unless the mouse is grabbed
9964ba967adSBrendan Shanks            if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) {
99760105d7aSPeter Maydell                return false;
9983e230dd2SCorentin Chary            }
9993e230dd2SCorentin Chary
10003e230dd2SCorentin Chary            // default
10013e230dd2SCorentin Chary
10025929e36cSJohn Arbuckle            // handle control + alt Key Combos (ctrl+alt+[1..9,g] is reserved for QEMU)
10034ba967adSBrendan Shanks            if (([event modifierFlags] & NSEventModifierFlagControl) && ([event modifierFlags] & NSEventModifierFlagOption)) {
10045929e36cSJohn Arbuckle                NSString *keychar = [event charactersIgnoringModifiers];
10055929e36cSJohn Arbuckle                if ([keychar length] == 1) {
10065929e36cSJohn Arbuckle                    char key = [keychar characterAtIndex:0];
10075929e36cSJohn Arbuckle                    switch (key) {
10083e230dd2SCorentin Chary
10093e230dd2SCorentin Chary                        // enable graphic console
10105929e36cSJohn Arbuckle                        case '1' ... '9':
1011ca3de7b5SAkihiko Odaki                            [self selectConsoleLocked:key - '0' - 1]; /* ascii math */
101260105d7aSPeter Maydell                            return true;
10135929e36cSJohn Arbuckle
10145929e36cSJohn Arbuckle                        // release the mouse grab
10155929e36cSJohn Arbuckle                        case 'g':
10165929e36cSJohn Arbuckle                            [self ungrabMouse];
101760105d7aSPeter Maydell                            return true;
10185929e36cSJohn Arbuckle                    }
10193e230dd2SCorentin Chary                }
1020ef2088f9SPeter Maydell            }
10213e230dd2SCorentin Chary
1022ca3de7b5SAkihiko Odaki            if (qemu_console_is_graphic(dcl.con)) {
10236d73bb64SAkihiko Odaki                qkbd_state_key_event(kbd, keycode, true);
10243e230dd2SCorentin Chary            } else {
10259c3a418eSJohn Arbuckle                [self handleMonitorInput: event];
10263e230dd2SCorentin Chary            }
10270f7be47aSAkihiko Odaki            return true;
10284ba967adSBrendan Shanks        case NSEventTypeKeyUp:
10293e230dd2SCorentin Chary            keycode = cocoa_keycode_to_qemu([event keyCode]);
10308895919aSPeter Maydell
10318895919aSPeter Maydell            // don't pass the guest a spurious key-up if we treated this
10328895919aSPeter Maydell            // command-key combo as a host UI action
10334ba967adSBrendan Shanks            if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) {
103460105d7aSPeter Maydell                return true;
10358895919aSPeter Maydell            }
10368895919aSPeter Maydell
1037ca3de7b5SAkihiko Odaki            if (qemu_console_is_graphic(dcl.con)) {
10386d73bb64SAkihiko Odaki                qkbd_state_key_event(kbd, keycode, false);
10393e230dd2SCorentin Chary            }
10400f7be47aSAkihiko Odaki            return true;
10414ba967adSBrendan Shanks        case NSEventTypeScrollWheel:
1042ae7313e7SJohn Arbuckle            /*
1043ae7313e7SJohn Arbuckle             * Send wheel events to the guest regardless of window focus.
1044ae7313e7SJohn Arbuckle             * This is in-line with standard Mac OS X UI behaviour.
1045ae7313e7SJohn Arbuckle             */
1046ae7313e7SJohn Arbuckle
10470f7be47aSAkihiko Odaki            /* Determine if this is a scroll up or scroll down event */
10480f7be47aSAkihiko Odaki            if ([event deltaY] != 0) {
10490f7be47aSAkihiko Odaki                button = ([event deltaY] > 0) ?
10500f7be47aSAkihiko Odaki                    INPUT_BUTTON_WHEEL_UP : INPUT_BUTTON_WHEEL_DOWN;
10510f7be47aSAkihiko Odaki            } else if ([event deltaX] != 0) {
10520f7be47aSAkihiko Odaki                button = ([event deltaX] > 0) ?
10530f7be47aSAkihiko Odaki                    INPUT_BUTTON_WHEEL_LEFT : INPUT_BUTTON_WHEEL_RIGHT;
10540f7be47aSAkihiko Odaki            } else {
1055dc3c89d6SJohn Arbuckle                /*
1056d70a5de4SDmitry Petrov                 * We shouldn't have got a scroll event when deltaY and delta Y
1057d70a5de4SDmitry Petrov                 * are zero, hence no harm in dropping the event
1058dc3c89d6SJohn Arbuckle                 */
10590f7be47aSAkihiko Odaki                return true;
1060d70a5de4SDmitry Petrov            }
1061d70a5de4SDmitry Petrov
10620f7be47aSAkihiko Odaki            qemu_input_queue_btn(dcl.con, button, true);
1063ae7313e7SJohn Arbuckle            qemu_input_event_sync();
10640f7be47aSAkihiko Odaki            qemu_input_queue_btn(dcl.con, button, false);
1065ae7313e7SJohn Arbuckle            qemu_input_event_sync();
1066d70a5de4SDmitry Petrov
10670f7be47aSAkihiko Odaki            return true;
10683e230dd2SCorentin Chary        default:
106960105d7aSPeter Maydell            return false;
10703e230dd2SCorentin Chary    }
1071af4efbccSAkihiko Odaki}
1072af4efbccSAkihiko Odaki
107391aa508dSAkihiko Odaki- (void) handleMouseEvent:(NSEvent *)event button:(InputButton)button down:(bool)down
1074af4efbccSAkihiko Odaki{
1075af4efbccSAkihiko Odaki    if (!isMouseGrabbed) {
107691aa508dSAkihiko Odaki        return;
1077af4efbccSAkihiko Odaki    }
1078af4efbccSAkihiko Odaki
107991aa508dSAkihiko Odaki    with_bql(^{
108091aa508dSAkihiko Odaki        qemu_input_queue_btn(dcl.con, button, down);
108191aa508dSAkihiko Odaki    });
108291aa508dSAkihiko Odaki
108391aa508dSAkihiko Odaki    [self handleMouseEvent:event];
108491aa508dSAkihiko Odaki}
108591aa508dSAkihiko Odaki
108691aa508dSAkihiko Odaki- (void) handleMouseEvent:(NSEvent *)event
108791aa508dSAkihiko Odaki{
108891aa508dSAkihiko Odaki    if (!isMouseGrabbed) {
108991aa508dSAkihiko Odaki        return;
109091aa508dSAkihiko Odaki    }
109191aa508dSAkihiko Odaki
109291aa508dSAkihiko Odaki    with_bql(^{
1093f61c387eSPeter Maydell        if (isAbsoluteEnabled) {
109491aa508dSAkihiko Odaki            CGFloat d = (CGFloat)screen.height / [self frame].size.height;
109591aa508dSAkihiko Odaki            NSPoint p = [event locationInWindow];
1096af4efbccSAkihiko Odaki
109791aa508dSAkihiko Odaki            /* Note that the origin for Cocoa mouse coords is bottom left, not top left. */
109891aa508dSAkihiko Odaki            qemu_input_queue_abs(dcl.con, INPUT_AXIS_X, p.x * d, 0, screen.width);
109991aa508dSAkihiko Odaki            qemu_input_queue_abs(dcl.con, INPUT_AXIS_Y, screen.height - p.y * d, 0, screen.height);
1100f61c387eSPeter Maydell        } else {
110191aa508dSAkihiko Odaki            qemu_input_queue_rel(dcl.con, INPUT_AXIS_X, [event deltaX]);
110291aa508dSAkihiko Odaki            qemu_input_queue_rel(dcl.con, INPUT_AXIS_Y, [event deltaY]);
1103f61c387eSPeter Maydell        }
1104af4efbccSAkihiko Odaki
110521bae11aSGerd Hoffmann        qemu_input_event_sync();
110691aa508dSAkihiko Odaki    });
110791aa508dSAkihiko Odaki}
1108af4efbccSAkihiko Odaki
110991aa508dSAkihiko Odaki- (void) mouseExited:(NSEvent *)event
111091aa508dSAkihiko Odaki{
111191aa508dSAkihiko Odaki    if (isAbsoluteEnabled && isMouseGrabbed) {
111291aa508dSAkihiko Odaki        [self ungrabMouse];
111391aa508dSAkihiko Odaki    }
111491aa508dSAkihiko Odaki}
111591aa508dSAkihiko Odaki
111691aa508dSAkihiko Odaki- (void) mouseEntered:(NSEvent *)event
111791aa508dSAkihiko Odaki{
111891aa508dSAkihiko Odaki    if (isAbsoluteEnabled && !isMouseGrabbed) {
111991aa508dSAkihiko Odaki        [self grabMouse];
112091aa508dSAkihiko Odaki    }
112191aa508dSAkihiko Odaki}
112291aa508dSAkihiko Odaki
112391aa508dSAkihiko Odaki- (void) mouseMoved:(NSEvent *)event
112491aa508dSAkihiko Odaki{
112591aa508dSAkihiko Odaki    [self handleMouseEvent:event];
112691aa508dSAkihiko Odaki}
112791aa508dSAkihiko Odaki
112891aa508dSAkihiko Odaki- (void) mouseDown:(NSEvent *)event
112991aa508dSAkihiko Odaki{
113091aa508dSAkihiko Odaki    [self handleMouseEvent:event button:INPUT_BUTTON_LEFT down:true];
113191aa508dSAkihiko Odaki}
113291aa508dSAkihiko Odaki
113391aa508dSAkihiko Odaki- (void) rightMouseDown:(NSEvent *)event
113491aa508dSAkihiko Odaki{
113591aa508dSAkihiko Odaki    [self handleMouseEvent:event button:INPUT_BUTTON_RIGHT down:true];
113691aa508dSAkihiko Odaki}
113791aa508dSAkihiko Odaki
113891aa508dSAkihiko Odaki- (void) otherMouseDown:(NSEvent *)event
113991aa508dSAkihiko Odaki{
114091aa508dSAkihiko Odaki    [self handleMouseEvent:event button:INPUT_BUTTON_MIDDLE down:true];
114191aa508dSAkihiko Odaki}
114291aa508dSAkihiko Odaki
114391aa508dSAkihiko Odaki- (void) mouseDragged:(NSEvent *)event
114491aa508dSAkihiko Odaki{
114591aa508dSAkihiko Odaki    [self handleMouseEvent:event];
114691aa508dSAkihiko Odaki}
114791aa508dSAkihiko Odaki
114891aa508dSAkihiko Odaki- (void) rightMouseDragged:(NSEvent *)event
114991aa508dSAkihiko Odaki{
115091aa508dSAkihiko Odaki    [self handleMouseEvent:event];
115191aa508dSAkihiko Odaki}
115291aa508dSAkihiko Odaki
115391aa508dSAkihiko Odaki- (void) otherMouseDragged:(NSEvent *)event
115491aa508dSAkihiko Odaki{
115591aa508dSAkihiko Odaki    [self handleMouseEvent:event];
115691aa508dSAkihiko Odaki}
115791aa508dSAkihiko Odaki
115891aa508dSAkihiko Odaki- (void) mouseUp:(NSEvent *)event
115991aa508dSAkihiko Odaki{
116091aa508dSAkihiko Odaki    if (!isMouseGrabbed) {
116191aa508dSAkihiko Odaki        [self grabMouse];
116291aa508dSAkihiko Odaki    }
116391aa508dSAkihiko Odaki
116491aa508dSAkihiko Odaki    [self handleMouseEvent:event button:INPUT_BUTTON_LEFT down:false];
116591aa508dSAkihiko Odaki}
116691aa508dSAkihiko Odaki
116791aa508dSAkihiko Odaki- (void) rightMouseUp:(NSEvent *)event
116891aa508dSAkihiko Odaki{
116991aa508dSAkihiko Odaki    [self handleMouseEvent:event button:INPUT_BUTTON_RIGHT down:false];
117091aa508dSAkihiko Odaki}
117191aa508dSAkihiko Odaki
117291aa508dSAkihiko Odaki- (void) otherMouseUp:(NSEvent *)event
117391aa508dSAkihiko Odaki{
117491aa508dSAkihiko Odaki    [self handleMouseEvent:event button:INPUT_BUTTON_MIDDLE down:false];
11753e230dd2SCorentin Chary}
11763e230dd2SCorentin Chary
11773e230dd2SCorentin Chary- (void) grabMouse
11783e230dd2SCorentin Chary{
11793e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaView: grabMouse\n");
11803e230dd2SCorentin Chary
11813e230dd2SCorentin Chary    if (qemu_name)
11820c35886eSAkihiko Odaki        [[self window] setTitle:[NSString stringWithFormat:@"QEMU %s - (Press  " UC_CTRL_KEY " " UC_ALT_KEY " G  to release Mouse)", qemu_name]];
11833e230dd2SCorentin Chary    else
11840c35886eSAkihiko Odaki        [[self window] setTitle:@"QEMU - (Press  " UC_CTRL_KEY " " UC_ALT_KEY " G  to release Mouse)"];
118513aefd30SPeter Maydell    [self hideCursor];
1186d1929069SAkihiko Odaki    CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled);
118749b9bd4dSPeter Maydell    isMouseGrabbed = TRUE; // while isMouseGrabbed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:]
11883e230dd2SCorentin Chary}
11893e230dd2SCorentin Chary
11903e230dd2SCorentin Chary- (void) ungrabMouse
11913e230dd2SCorentin Chary{
11923e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaView: ungrabMouse\n");
11933e230dd2SCorentin Chary
11943e230dd2SCorentin Chary    if (qemu_name)
11950c35886eSAkihiko Odaki        [[self window] setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]];
11963e230dd2SCorentin Chary    else
11970c35886eSAkihiko Odaki        [[self window] setTitle:@"QEMU"];
119813aefd30SPeter Maydell    [self unhideCursor];
11993e230dd2SCorentin Chary    CGAssociateMouseAndMouseCursorPosition(TRUE);
120049b9bd4dSPeter Maydell    isMouseGrabbed = FALSE;
120191aa508dSAkihiko Odaki    [self raiseAllButtons];
12023e230dd2SCorentin Chary}
12033e230dd2SCorentin Chary
1204e7b53d16SAkihiko Odaki- (void) notifyMouseModeChange {
1205e7b53d16SAkihiko Odaki    bool tIsAbsoluteEnabled = bool_with_bql(^{
1206e7b53d16SAkihiko Odaki        return qemu_input_is_absolute(dcl.con);
1207e7b53d16SAkihiko Odaki    });
1208e7b53d16SAkihiko Odaki
1209e7b53d16SAkihiko Odaki    if (tIsAbsoluteEnabled == isAbsoluteEnabled) {
1210e7b53d16SAkihiko Odaki        return;
1211e7b53d16SAkihiko Odaki    }
1212e7b53d16SAkihiko Odaki
1213d1929069SAkihiko Odaki    isAbsoluteEnabled = tIsAbsoluteEnabled;
1214e7b53d16SAkihiko Odaki
1215d1929069SAkihiko Odaki    if (isMouseGrabbed) {
1216e7b53d16SAkihiko Odaki        if (isAbsoluteEnabled) {
1217e7b53d16SAkihiko Odaki            [self ungrabMouse];
1218e7b53d16SAkihiko Odaki        } else {
1219d1929069SAkihiko Odaki            CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled);
1220d1929069SAkihiko Odaki        }
1221d1929069SAkihiko Odaki    }
1222e7b53d16SAkihiko Odaki}
122349b9bd4dSPeter Maydell- (BOOL) isMouseGrabbed {return isMouseGrabbed;}
12243e230dd2SCorentin Chary- (QEMUScreen) gscreen {return screen;}
12253b178b71SJohn Arbuckle
12263b178b71SJohn Arbuckle/*
12273b178b71SJohn Arbuckle * Makes the target think all down keys are being released.
12283b178b71SJohn Arbuckle * This prevents a stuck key problem, since we will not see
12293b178b71SJohn Arbuckle * key up events for those keys after we have lost focus.
12303b178b71SJohn Arbuckle */
12313b178b71SJohn Arbuckle- (void) raiseAllKeys
12323b178b71SJohn Arbuckle{
1233195801d7SStefan Hajnoczi    with_bql(^{
12346d73bb64SAkihiko Odaki        qkbd_state_lift_all_keys(kbd);
123531819e95SPeter Maydell    });
12363b178b71SJohn Arbuckle}
123791aa508dSAkihiko Odaki
123891aa508dSAkihiko Odaki- (void) raiseAllButtons
123991aa508dSAkihiko Odaki{
124091aa508dSAkihiko Odaki    with_bql(^{
124191aa508dSAkihiko Odaki        qemu_input_queue_btn(dcl.con, INPUT_BUTTON_LEFT, false);
124291aa508dSAkihiko Odaki        qemu_input_queue_btn(dcl.con, INPUT_BUTTON_RIGHT, false);
124391aa508dSAkihiko Odaki        qemu_input_queue_btn(dcl.con, INPUT_BUTTON_MIDDLE, false);
124491aa508dSAkihiko Odaki    });
124591aa508dSAkihiko Odaki}
12463e230dd2SCorentin Chary@end
12473e230dd2SCorentin Chary
12483e230dd2SCorentin Chary
12493e230dd2SCorentin Chary
12503e230dd2SCorentin Chary/*
12513e230dd2SCorentin Chary ------------------------------------------------------
12523e230dd2SCorentin Chary    QemuCocoaAppController
12533e230dd2SCorentin Chary ------------------------------------------------------
12543e230dd2SCorentin Chary*/
12553e230dd2SCorentin Chary@interface QemuCocoaAppController : NSObject
1256d9bc14f6SJohn Arbuckle                                       <NSWindowDelegate, NSApplicationDelegate>
12573e230dd2SCorentin Chary{
12583e230dd2SCorentin Chary}
12595d1b2eefSProgrammingkid- (void)doToggleFullScreen:(id)sender;
12603e230dd2SCorentin Chary- (void)showQEMUDoc:(id)sender;
12615d1b2eefSProgrammingkid- (void)zoomToFit:(id) sender;
1262b4c6a112SProgrammingkid- (void)displayConsole:(id)sender;
12638524f1c7SJohn Arbuckle- (void)pauseQEMU:(id)sender;
12648524f1c7SJohn Arbuckle- (void)resumeQEMU:(id)sender;
12658524f1c7SJohn Arbuckle- (void)displayPause;
12668524f1c7SJohn Arbuckle- (void)removePause;
126727074614SJohn Arbuckle- (void)restartQEMU:(id)sender;
126827074614SJohn Arbuckle- (void)powerDownQEMU:(id)sender;
1269693a3e01SJohn Arbuckle- (void)ejectDeviceMedia:(id)sender;
1270693a3e01SJohn Arbuckle- (void)changeDeviceMedia:(id)sender;
1271d9bc14f6SJohn Arbuckle- (BOOL)verifyQuit;
1272f4747900SJohn Arbuckle- (void)openDocumentation:(NSString *)filename;
12739e8204b1SProgrammingkid- (IBAction) do_about_menu_item: (id) sender;
1274e47ec1a9SJohn Arbuckle- (void)adjustSpeed:(id)sender;
12753e230dd2SCorentin Chary@end
12763e230dd2SCorentin Chary
12773e230dd2SCorentin Chary@implementation QemuCocoaAppController
12783e230dd2SCorentin Chary- (id) init
12793e230dd2SCorentin Chary{
12800c35886eSAkihiko Odaki    NSWindow *window;
12810c35886eSAkihiko Odaki
12823e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaAppController: init\n");
12833e230dd2SCorentin Chary
12843e230dd2SCorentin Chary    self = [super init];
12853e230dd2SCorentin Chary    if (self) {
12863e230dd2SCorentin Chary
12873e230dd2SCorentin Chary        // create a view and add it to the window
12883e230dd2SCorentin Chary        cocoaView = [[QemuCocoaView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 640.0, 480.0)];
12893e230dd2SCorentin Chary        if(!cocoaView) {
12904313739aSAkihiko Odaki            error_report("(cocoa) can't create a view");
12913e230dd2SCorentin Chary            exit(1);
12923e230dd2SCorentin Chary        }
12933e230dd2SCorentin Chary
12943e230dd2SCorentin Chary        // create a window
12950c35886eSAkihiko Odaki        window = [[NSWindow alloc] initWithContentRect:[cocoaView frame]
12964ba967adSBrendan Shanks            styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskClosable
12973e230dd2SCorentin Chary            backing:NSBackingStoreBuffered defer:NO];
12980c35886eSAkihiko Odaki        if(!window) {
12994313739aSAkihiko Odaki            error_report("(cocoa) can't create window");
13003e230dd2SCorentin Chary            exit(1);
13013e230dd2SCorentin Chary        }
13020c35886eSAkihiko Odaki        [window setAcceptsMouseMovedEvents:YES];
13030c35886eSAkihiko Odaki        [window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
13040c35886eSAkihiko Odaki        [window setTitle:qemu_name ? [NSString stringWithFormat:@"QEMU %s", qemu_name] : @"QEMU"];
13050c35886eSAkihiko Odaki        [window setContentView:cocoaView];
13060c35886eSAkihiko Odaki        [window makeKeyAndOrderFront:self];
13070c35886eSAkihiko Odaki        [window center];
13080c35886eSAkihiko Odaki        [window setDelegate: self];
13098524f1c7SJohn Arbuckle
13108524f1c7SJohn Arbuckle        /* Used for displaying pause on the screen */
13118524f1c7SJohn Arbuckle        pauseLabel = [NSTextField new];
13128524f1c7SJohn Arbuckle        [pauseLabel setBezeled:YES];
13138524f1c7SJohn Arbuckle        [pauseLabel setDrawsBackground:YES];
13148524f1c7SJohn Arbuckle        [pauseLabel setBackgroundColor: [NSColor whiteColor]];
13158524f1c7SJohn Arbuckle        [pauseLabel setEditable:NO];
13168524f1c7SJohn Arbuckle        [pauseLabel setSelectable:NO];
13178524f1c7SJohn Arbuckle        [pauseLabel setStringValue: @"Paused"];
13188524f1c7SJohn Arbuckle        [pauseLabel setFont: [NSFont fontWithName: @"Helvetica" size: 90]];
13198524f1c7SJohn Arbuckle        [pauseLabel setTextColor: [NSColor blackColor]];
13208524f1c7SJohn Arbuckle        [pauseLabel sizeToFit];
13213e230dd2SCorentin Chary    }
13223e230dd2SCorentin Chary    return self;
13233e230dd2SCorentin Chary}
13243e230dd2SCorentin Chary
13253e230dd2SCorentin Chary- (void) dealloc
13263e230dd2SCorentin Chary{
13273e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaAppController: dealloc\n");
13283e230dd2SCorentin Chary
13293e230dd2SCorentin Chary    if (cocoaView)
13303e230dd2SCorentin Chary        [cocoaView release];
13313e230dd2SCorentin Chary    [super dealloc];
13323e230dd2SCorentin Chary}
13333e230dd2SCorentin Chary
13343e230dd2SCorentin Chary- (void)applicationDidFinishLaunching: (NSNotification *) note
13353e230dd2SCorentin Chary{
13363e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaAppController: applicationDidFinishLaunching\n");
1337dff742adSHikaru Nishida    allow_events = true;
13383e230dd2SCorentin Chary}
13393e230dd2SCorentin Chary
13403e230dd2SCorentin Chary- (void)applicationWillTerminate:(NSNotification *)aNotification
13413e230dd2SCorentin Chary{
13423e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n");
13433e230dd2SCorentin Chary
1344195801d7SStefan Hajnoczi    with_bql(^{
13452910abd6SAkihiko Odaki        shutdown_action = SHUTDOWN_ACTION_POWEROFF;
1346cf83f140SEric Blake        qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
13472910abd6SAkihiko Odaki    });
134840c01937SAkihiko Odaki
134940c01937SAkihiko Odaki    /*
135040c01937SAkihiko Odaki     * Sleep here, because returning will cause OSX to kill us
135140c01937SAkihiko Odaki     * immediately; the QEMU main loop will handle the shutdown
135240c01937SAkihiko Odaki     * request and terminate the process.
135340c01937SAkihiko Odaki     */
135440c01937SAkihiko Odaki    [NSThread sleepForTimeInterval:INFINITY];
13553e230dd2SCorentin Chary}
13563e230dd2SCorentin Chary
13573e230dd2SCorentin Chary- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
13583e230dd2SCorentin Chary{
13593e230dd2SCorentin Chary    return YES;
13603e230dd2SCorentin Chary}
13613e230dd2SCorentin Chary
1362d9bc14f6SJohn Arbuckle- (NSApplicationTerminateReply)applicationShouldTerminate:
1363d9bc14f6SJohn Arbuckle                                                         (NSApplication *)sender
1364d9bc14f6SJohn Arbuckle{
1365d9bc14f6SJohn Arbuckle    COCOA_DEBUG("QemuCocoaAppController: applicationShouldTerminate\n");
1366d9bc14f6SJohn Arbuckle    return [self verifyQuit];
1367d9bc14f6SJohn Arbuckle}
1368d9bc14f6SJohn Arbuckle
136915280e85SAkihiko Odaki- (void)windowDidChangeScreen:(NSNotification *)notification
137015280e85SAkihiko Odaki{
137115280e85SAkihiko Odaki    [cocoaView updateUIInfo];
137215280e85SAkihiko Odaki}
137315280e85SAkihiko Odaki
137491aa508dSAkihiko Odaki- (void)windowDidEnterFullScreen:(NSNotification *)notification
137591aa508dSAkihiko Odaki{
137691aa508dSAkihiko Odaki    [cocoaView grabMouse];
137791aa508dSAkihiko Odaki}
137891aa508dSAkihiko Odaki
137991aa508dSAkihiko Odaki- (void)windowDidExitFullScreen:(NSNotification *)notification
138091aa508dSAkihiko Odaki{
138191aa508dSAkihiko Odaki    [cocoaView resizeWindow];
138291aa508dSAkihiko Odaki    [cocoaView ungrabMouse];
138391aa508dSAkihiko Odaki}
138491aa508dSAkihiko Odaki
138515280e85SAkihiko Odaki- (void)windowDidResize:(NSNotification *)notification
138615280e85SAkihiko Odaki{
1387fcb03de7SAkihiko Odaki    [cocoaView updateBounds];
1388ccebb9aeSAkihiko Odaki    [cocoaView updateUIInfo];
138915280e85SAkihiko Odaki}
139015280e85SAkihiko Odaki
1391d9bc14f6SJohn Arbuckle/* Called when the user clicks on a window's close button */
1392d9bc14f6SJohn Arbuckle- (BOOL)windowShouldClose:(id)sender
1393d9bc14f6SJohn Arbuckle{
1394d9bc14f6SJohn Arbuckle    COCOA_DEBUG("QemuCocoaAppController: windowShouldClose\n");
1395d9bc14f6SJohn Arbuckle    [NSApp terminate: sender];
1396d9bc14f6SJohn Arbuckle    /* If the user allows the application to quit then the call to
1397d9bc14f6SJohn Arbuckle     * NSApp terminate will never return. If we get here then the user
1398d9bc14f6SJohn Arbuckle     * cancelled the quit, so we should return NO to not permit the
1399d9bc14f6SJohn Arbuckle     * closing of this window.
1400d9bc14f6SJohn Arbuckle     */
1401d9bc14f6SJohn Arbuckle    return NO;
1402d9bc14f6SJohn Arbuckle}
1403d9bc14f6SJohn Arbuckle
140491aa508dSAkihiko Odaki- (NSApplicationPresentationOptions) window:(NSWindow *)window
140591aa508dSAkihiko Odaki                                     willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions;
140691aa508dSAkihiko Odaki
140791aa508dSAkihiko Odaki{
140891aa508dSAkihiko Odaki    return (proposedOptions & ~(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)) |
140991aa508dSAkihiko Odaki           NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
141091aa508dSAkihiko Odaki}
141191aa508dSAkihiko Odaki
14129d9bc7dbSAkihiko Odaki/*
14139d9bc7dbSAkihiko Odaki * Called when QEMU goes into the background. Note that
14149d9bc7dbSAkihiko Odaki * [-NSWindowDelegate windowDidResignKey:] is used here instead of
14159d9bc7dbSAkihiko Odaki * [-NSApplicationDelegate applicationWillResignActive:] because it cannot
14169d9bc7dbSAkihiko Odaki * detect that the window loses focus when the deck is clicked on macOS 13.2.1.
14179d9bc7dbSAkihiko Odaki */
14189d9bc7dbSAkihiko Odaki- (void) windowDidResignKey: (NSNotification *)aNotification
14193b178b71SJohn Arbuckle{
14209d9bc7dbSAkihiko Odaki    COCOA_DEBUG("%s\n", __func__);
142169221df8SCarwyn Ellis    [cocoaView ungrabMouse];
14223b178b71SJohn Arbuckle    [cocoaView raiseAllKeys];
14233b178b71SJohn Arbuckle}
14243b178b71SJohn Arbuckle
14255d1b2eefSProgrammingkid/* We abstract the method called by the Enter Fullscreen menu item
14265d1b2eefSProgrammingkid * because Mac OS 10.7 and higher disables it. This is because of the
14275d1b2eefSProgrammingkid * menu item's old selector's name toggleFullScreen:
14285d1b2eefSProgrammingkid */
14295d1b2eefSProgrammingkid- (void) doToggleFullScreen:(id)sender
14305d1b2eefSProgrammingkid{
14310c35886eSAkihiko Odaki    [[cocoaView window] toggleFullScreen:sender];
14323e230dd2SCorentin Chary}
14333e230dd2SCorentin Chary
1434f844cdb9SGustavo Noronha Silva- (void) setFullGrab:(id)sender
1435f844cdb9SGustavo Noronha Silva{
1436f844cdb9SGustavo Noronha Silva    COCOA_DEBUG("QemuCocoaAppController: setFullGrab\n");
1437f844cdb9SGustavo Noronha Silva
1438f844cdb9SGustavo Noronha Silva    [cocoaView setFullGrab:sender];
1439f844cdb9SGustavo Noronha Silva}
1440f844cdb9SGustavo Noronha Silva
1441f4747900SJohn Arbuckle/* Tries to find then open the specified filename */
1442f4747900SJohn Arbuckle- (void) openDocumentation: (NSString *) filename
1443f4747900SJohn Arbuckle{
1444f4747900SJohn Arbuckle    /* Where to look for local files */
14458d6fda8cSRoman Bolshakov    NSString *path_array[] = {@"../share/doc/qemu/", @"../doc/qemu/", @"docs/"};
1446f4747900SJohn Arbuckle    NSString *full_file_path;
14471ff5a063SRoman Bolshakov    NSURL *full_file_url;
1448f4747900SJohn Arbuckle
1449f4747900SJohn Arbuckle    /* iterate thru the possible paths until the file is found */
1450f4747900SJohn Arbuckle    int index;
1451f4747900SJohn Arbuckle    for (index = 0; index < ARRAY_SIZE(path_array); index++) {
1452f4747900SJohn Arbuckle        full_file_path = [[NSBundle mainBundle] executablePath];
1453f4747900SJohn Arbuckle        full_file_path = [full_file_path stringByDeletingLastPathComponent];
1454f4747900SJohn Arbuckle        full_file_path = [NSString stringWithFormat: @"%@/%@%@", full_file_path,
1455f4747900SJohn Arbuckle                          path_array[index], filename];
14561ff5a063SRoman Bolshakov        full_file_url = [NSURL fileURLWithPath: full_file_path
14571ff5a063SRoman Bolshakov                                   isDirectory: false];
14581ff5a063SRoman Bolshakov        if ([[NSWorkspace sharedWorkspace] openURL: full_file_url] == YES) {
1459f4747900SJohn Arbuckle            return;
1460f4747900SJohn Arbuckle        }
1461f4747900SJohn Arbuckle    }
1462f4747900SJohn Arbuckle
1463f4747900SJohn Arbuckle    /* If none of the paths opened a file */
1464f4747900SJohn Arbuckle    NSBeep();
1465f4747900SJohn Arbuckle    QEMU_Alert(@"Failed to open file");
1466f4747900SJohn Arbuckle}
1467f4747900SJohn Arbuckle
14683e230dd2SCorentin Chary- (void)showQEMUDoc:(id)sender
14693e230dd2SCorentin Chary{
14703e230dd2SCorentin Chary    COCOA_DEBUG("QemuCocoaAppController: showQEMUDoc\n");
14713e230dd2SCorentin Chary
14721879f241SPeter Maydell    [self openDocumentation: @"index.html"];
14733e230dd2SCorentin Chary}
14743e230dd2SCorentin Chary
14755d1b2eefSProgrammingkid/* Stretches video to fit host monitor size */
14765d1b2eefSProgrammingkid- (void)zoomToFit:(id) sender
14775d1b2eefSProgrammingkid{
147855766632SAkihiko Odaki    NSWindowStyleMask styleMask = [[cocoaView window] styleMask] ^ NSWindowStyleMaskResizable;
147955766632SAkihiko Odaki
148055766632SAkihiko Odaki    [[cocoaView window] setStyleMask:styleMask];
148155766632SAkihiko Odaki    [sender setState:styleMask & NSWindowStyleMaskResizable ? NSControlStateValueOn : NSControlStateValueOff];
1482f69a6f04SAkihiko Odaki    [cocoaView resizeWindow];
14835d1b2eefSProgrammingkid}
14843e230dd2SCorentin Chary
1485e28a909aSCarwyn Ellis- (void)toggleZoomInterpolation:(id) sender
1486e28a909aSCarwyn Ellis{
1487e28a909aSCarwyn Ellis    if (zoom_interpolation == kCGInterpolationNone) {
1488e28a909aSCarwyn Ellis        zoom_interpolation = kCGInterpolationLow;
1489e28a909aSCarwyn Ellis        [sender setState: NSControlStateValueOn];
1490e28a909aSCarwyn Ellis    } else {
1491e28a909aSCarwyn Ellis        zoom_interpolation = kCGInterpolationNone;
1492e28a909aSCarwyn Ellis        [sender setState: NSControlStateValueOff];
1493e28a909aSCarwyn Ellis    }
1494e28a909aSCarwyn Ellis}
1495e28a909aSCarwyn Ellis
1496b4c6a112SProgrammingkid/* Displays the console on the screen */
1497b4c6a112SProgrammingkid- (void)displayConsole:(id)sender
1498b4c6a112SProgrammingkid{
14994b49f92cSAkihiko Odaki    with_bql(^{
1500ca3de7b5SAkihiko Odaki        [cocoaView selectConsoleLocked:[sender tag]];
15014b49f92cSAkihiko Odaki    });
1502b4c6a112SProgrammingkid}
15038524f1c7SJohn Arbuckle
15048524f1c7SJohn Arbuckle/* Pause the guest */
15058524f1c7SJohn Arbuckle- (void)pauseQEMU:(id)sender
15068524f1c7SJohn Arbuckle{
1507195801d7SStefan Hajnoczi    with_bql(^{
15088524f1c7SJohn Arbuckle        qmp_stop(NULL);
150931819e95SPeter Maydell    });
15108524f1c7SJohn Arbuckle    [sender setEnabled: NO];
15118524f1c7SJohn Arbuckle    [[[sender menu] itemWithTitle: @"Resume"] setEnabled: YES];
15128524f1c7SJohn Arbuckle    [self displayPause];
15138524f1c7SJohn Arbuckle}
15148524f1c7SJohn Arbuckle
15158524f1c7SJohn Arbuckle/* Resume running the guest operating system */
15168524f1c7SJohn Arbuckle- (void)resumeQEMU:(id) sender
15178524f1c7SJohn Arbuckle{
1518195801d7SStefan Hajnoczi    with_bql(^{
15198524f1c7SJohn Arbuckle        qmp_cont(NULL);
152031819e95SPeter Maydell    });
15218524f1c7SJohn Arbuckle    [sender setEnabled: NO];
15228524f1c7SJohn Arbuckle    [[[sender menu] itemWithTitle: @"Pause"] setEnabled: YES];
15238524f1c7SJohn Arbuckle    [self removePause];
15248524f1c7SJohn Arbuckle}
15258524f1c7SJohn Arbuckle
15268524f1c7SJohn Arbuckle/* Displays the word pause on the screen */
15278524f1c7SJohn Arbuckle- (void)displayPause
15288524f1c7SJohn Arbuckle{
15298524f1c7SJohn Arbuckle    /* Coordinates have to be calculated each time because the window can change its size */
15308524f1c7SJohn Arbuckle    int xCoord, yCoord, width, height;
15311a4b64a5SAkihiko Odaki    xCoord = ([cocoaView frame].size.width - [pauseLabel frame].size.width)/2;
15321a4b64a5SAkihiko Odaki    yCoord = [cocoaView frame].size.height - [pauseLabel frame].size.height - ([pauseLabel frame].size.height * .5);
15338524f1c7SJohn Arbuckle    width = [pauseLabel frame].size.width;
15348524f1c7SJohn Arbuckle    height = [pauseLabel frame].size.height;
15358524f1c7SJohn Arbuckle    [pauseLabel setFrame: NSMakeRect(xCoord, yCoord, width, height)];
15368524f1c7SJohn Arbuckle    [cocoaView addSubview: pauseLabel];
15378524f1c7SJohn Arbuckle}
15388524f1c7SJohn Arbuckle
15398524f1c7SJohn Arbuckle/* Removes the word pause from the screen */
15408524f1c7SJohn Arbuckle- (void)removePause
15418524f1c7SJohn Arbuckle{
15428524f1c7SJohn Arbuckle    [pauseLabel removeFromSuperview];
15438524f1c7SJohn Arbuckle}
15448524f1c7SJohn Arbuckle
154527074614SJohn Arbuckle/* Restarts QEMU */
154627074614SJohn Arbuckle- (void)restartQEMU:(id)sender
154727074614SJohn Arbuckle{
1548195801d7SStefan Hajnoczi    with_bql(^{
154927074614SJohn Arbuckle        qmp_system_reset(NULL);
155031819e95SPeter Maydell    });
155127074614SJohn Arbuckle}
155227074614SJohn Arbuckle
155327074614SJohn Arbuckle/* Powers down QEMU */
155427074614SJohn Arbuckle- (void)powerDownQEMU:(id)sender
155527074614SJohn Arbuckle{
1556195801d7SStefan Hajnoczi    with_bql(^{
155727074614SJohn Arbuckle        qmp_system_powerdown(NULL);
155831819e95SPeter Maydell    });
155927074614SJohn Arbuckle}
156027074614SJohn Arbuckle
1561693a3e01SJohn Arbuckle/* Ejects the media.
1562693a3e01SJohn Arbuckle * Uses sender's tag to figure out the device to eject.
1563693a3e01SJohn Arbuckle */
1564693a3e01SJohn Arbuckle- (void)ejectDeviceMedia:(id)sender
1565693a3e01SJohn Arbuckle{
1566693a3e01SJohn Arbuckle    NSString * drive;
1567693a3e01SJohn Arbuckle    drive = [sender representedObject];
1568693a3e01SJohn Arbuckle    if(drive == nil) {
1569693a3e01SJohn Arbuckle        NSBeep();
1570693a3e01SJohn Arbuckle        QEMU_Alert(@"Failed to find drive to eject!");
1571693a3e01SJohn Arbuckle        return;
1572693a3e01SJohn Arbuckle    }
1573693a3e01SJohn Arbuckle
157431819e95SPeter Maydell    __block Error *err = NULL;
1575195801d7SStefan Hajnoczi    with_bql(^{
157654fde4ffSMarkus Armbruster        qmp_eject([drive cStringUsingEncoding: NSASCIIStringEncoding],
157754fde4ffSMarkus Armbruster                  NULL, false, false, &err);
157831819e95SPeter Maydell    });
1579693a3e01SJohn Arbuckle    handleAnyDeviceErrors(err);
1580693a3e01SJohn Arbuckle}
1581693a3e01SJohn Arbuckle
1582693a3e01SJohn Arbuckle/* Displays a dialog box asking the user to select an image file to load.
1583693a3e01SJohn Arbuckle * Uses sender's represented object value to figure out which drive to use.
1584693a3e01SJohn Arbuckle */
1585693a3e01SJohn Arbuckle- (void)changeDeviceMedia:(id)sender
1586693a3e01SJohn Arbuckle{
1587693a3e01SJohn Arbuckle    /* Find the drive name */
1588693a3e01SJohn Arbuckle    NSString * drive;
1589693a3e01SJohn Arbuckle    drive = [sender representedObject];
1590693a3e01SJohn Arbuckle    if(drive == nil) {
1591693a3e01SJohn Arbuckle        NSBeep();
1592693a3e01SJohn Arbuckle        QEMU_Alert(@"Could not find drive!");
1593693a3e01SJohn Arbuckle        return;
1594693a3e01SJohn Arbuckle    }
1595693a3e01SJohn Arbuckle
1596693a3e01SJohn Arbuckle    /* Display the file open dialog */
1597693a3e01SJohn Arbuckle    NSOpenPanel * openPanel;
1598693a3e01SJohn Arbuckle    openPanel = [NSOpenPanel openPanel];
1599693a3e01SJohn Arbuckle    [openPanel setCanChooseFiles: YES];
1600693a3e01SJohn Arbuckle    [openPanel setAllowsMultipleSelection: NO];
1601b5725385SPeter Maydell    if([openPanel runModal] == NSModalResponseOK) {
1602693a3e01SJohn Arbuckle        NSString * file = [[[openPanel URLs] objectAtIndex: 0] path];
1603693a3e01SJohn Arbuckle        if(file == nil) {
1604693a3e01SJohn Arbuckle            NSBeep();
1605693a3e01SJohn Arbuckle            QEMU_Alert(@"Failed to convert URL to file path!");
1606693a3e01SJohn Arbuckle            return;
1607693a3e01SJohn Arbuckle        }
1608693a3e01SJohn Arbuckle
160931819e95SPeter Maydell        __block Error *err = NULL;
1610195801d7SStefan Hajnoczi        with_bql(^{
161154fde4ffSMarkus Armbruster            qmp_blockdev_change_medium([drive cStringUsingEncoding:
161224fb4133SMax Reitz                                                  NSASCIIStringEncoding],
161354fde4ffSMarkus Armbruster                                       NULL,
161424fb4133SMax Reitz                                       [file cStringUsingEncoding:
161524fb4133SMax Reitz                                                 NSASCIIStringEncoding],
161654fde4ffSMarkus Armbruster                                       "raw",
161780dd5affSDenis V. Lunev                                       true, false,
161839ff43d9SMax Reitz                                       false, 0,
1619693a3e01SJohn Arbuckle                                       &err);
162031819e95SPeter Maydell        });
1621693a3e01SJohn Arbuckle        handleAnyDeviceErrors(err);
1622693a3e01SJohn Arbuckle    }
1623693a3e01SJohn Arbuckle}
1624693a3e01SJohn Arbuckle
1625d9bc14f6SJohn Arbuckle/* Verifies if the user really wants to quit */
1626d9bc14f6SJohn Arbuckle- (BOOL)verifyQuit
1627d9bc14f6SJohn Arbuckle{
1628d9bc14f6SJohn Arbuckle    NSAlert *alert = [NSAlert new];
1629d9bc14f6SJohn Arbuckle    [alert autorelease];
1630d9bc14f6SJohn Arbuckle    [alert setMessageText: @"Are you sure you want to quit QEMU?"];
1631d9bc14f6SJohn Arbuckle    [alert addButtonWithTitle: @"Cancel"];
1632d9bc14f6SJohn Arbuckle    [alert addButtonWithTitle: @"Quit"];
1633d9bc14f6SJohn Arbuckle    if([alert runModal] == NSAlertSecondButtonReturn) {
1634d9bc14f6SJohn Arbuckle        return YES;
1635d9bc14f6SJohn Arbuckle    } else {
1636d9bc14f6SJohn Arbuckle        return NO;
1637d9bc14f6SJohn Arbuckle    }
1638d9bc14f6SJohn Arbuckle}
1639d9bc14f6SJohn Arbuckle
16409e8204b1SProgrammingkid/* The action method for the About menu item */
16419e8204b1SProgrammingkid- (IBAction) do_about_menu_item: (id) sender
16429e8204b1SProgrammingkid{
164399eb313dSAkihiko Odaki    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
164499eb313dSAkihiko Odaki    char *icon_path_c = get_relocated_path(CONFIG_QEMU_ICONDIR "/hicolor/512x512/apps/qemu.png");
164599eb313dSAkihiko Odaki    NSString *icon_path = [NSString stringWithUTF8String:icon_path_c];
164699eb313dSAkihiko Odaki    g_free(icon_path_c);
164799eb313dSAkihiko Odaki    NSImage *icon = [[NSImage alloc] initWithContentsOfFile:icon_path];
164899eb313dSAkihiko Odaki    NSString *version = @"QEMU emulator version " QEMU_FULL_VERSION;
164999eb313dSAkihiko Odaki    NSString *copyright = @QEMU_COPYRIGHT;
165099eb313dSAkihiko Odaki    NSDictionary *options;
165199eb313dSAkihiko Odaki    if (icon) {
165299eb313dSAkihiko Odaki        options = @{
165399eb313dSAkihiko Odaki            NSAboutPanelOptionApplicationIcon : icon,
165499eb313dSAkihiko Odaki            NSAboutPanelOptionApplicationVersion : version,
165599eb313dSAkihiko Odaki            @"Copyright" : copyright,
165699eb313dSAkihiko Odaki        };
165799eb313dSAkihiko Odaki        [icon release];
165899eb313dSAkihiko Odaki    } else {
165999eb313dSAkihiko Odaki        options = @{
166099eb313dSAkihiko Odaki            NSAboutPanelOptionApplicationVersion : version,
166199eb313dSAkihiko Odaki            @"Copyright" : copyright,
166299eb313dSAkihiko Odaki        };
16639e8204b1SProgrammingkid    }
166499eb313dSAkihiko Odaki    [NSApp orderFrontStandardAboutPanelWithOptions:options];
166599eb313dSAkihiko Odaki    [pool release];
16669e8204b1SProgrammingkid}
16679e8204b1SProgrammingkid
1668e47ec1a9SJohn Arbuckle/* Used by the Speed menu items */
1669e47ec1a9SJohn Arbuckle- (void)adjustSpeed:(id)sender
1670e47ec1a9SJohn Arbuckle{
1671e47ec1a9SJohn Arbuckle    int throttle_pct; /* throttle percentage */
1672e47ec1a9SJohn Arbuckle    NSMenu *menu;
1673e47ec1a9SJohn Arbuckle
1674e47ec1a9SJohn Arbuckle    menu = [sender menu];
1675e47ec1a9SJohn Arbuckle    if (menu != nil)
1676e47ec1a9SJohn Arbuckle    {
1677e47ec1a9SJohn Arbuckle        /* Unselect the currently selected item */
1678e47ec1a9SJohn Arbuckle        for (NSMenuItem *item in [menu itemArray]) {
16795e24600aSBrendan Shanks            if (item.state == NSControlStateValueOn) {
16805e24600aSBrendan Shanks                [item setState: NSControlStateValueOff];
1681e47ec1a9SJohn Arbuckle                break;
1682e47ec1a9SJohn Arbuckle            }
1683e47ec1a9SJohn Arbuckle        }
1684e47ec1a9SJohn Arbuckle    }
1685e47ec1a9SJohn Arbuckle
1686e47ec1a9SJohn Arbuckle    // check the menu item
16875e24600aSBrendan Shanks    [sender setState: NSControlStateValueOn];
1688e47ec1a9SJohn Arbuckle
1689e47ec1a9SJohn Arbuckle    // get the throttle percentage
1690e47ec1a9SJohn Arbuckle    throttle_pct = [sender tag];
1691e47ec1a9SJohn Arbuckle
1692195801d7SStefan Hajnoczi    with_bql(^{
1693e47ec1a9SJohn Arbuckle        cpu_throttle_set(throttle_pct);
169431819e95SPeter Maydell    });
1695e47ec1a9SJohn Arbuckle    COCOA_DEBUG("cpu throttling at %d%c\n", cpu_throttle_get_percentage(), '%');
1696e47ec1a9SJohn Arbuckle}
1697e47ec1a9SJohn Arbuckle
1698b4c6a112SProgrammingkid@end
16993e230dd2SCorentin Chary
170061a2ed44SPeter Maydell@interface QemuApplication : NSApplication
170161a2ed44SPeter Maydell@end
170261a2ed44SPeter Maydell
170361a2ed44SPeter Maydell@implementation QemuApplication
170461a2ed44SPeter Maydell- (void)sendEvent:(NSEvent *)event
170561a2ed44SPeter Maydell{
170661a2ed44SPeter Maydell    COCOA_DEBUG("QemuApplication: sendEvent\n");
17075588840fSPeter Maydell    if (![cocoaView handleEvent:event]) {
170861a2ed44SPeter Maydell        [super sendEvent: event];
170961a2ed44SPeter Maydell    }
17105588840fSPeter Maydell}
171161a2ed44SPeter Maydell@end
171261a2ed44SPeter Maydell
1713c6fd6c70SPeter Maydellstatic void create_initial_menus(void)
1714c6fd6c70SPeter Maydell{
17153e230dd2SCorentin Chary    // Add menus
17163e230dd2SCorentin Chary    NSMenu      *menu;
17173e230dd2SCorentin Chary    NSMenuItem  *menuItem;
17183e230dd2SCorentin Chary
17193e230dd2SCorentin Chary    [NSApp setMainMenu:[[NSMenu alloc] init]];
17205b6988c1SAkihiko Odaki    [NSApp setServicesMenu:[[NSMenu alloc] initWithTitle:@"Services"]];
17213e230dd2SCorentin Chary
17223e230dd2SCorentin Chary    // Application menu
17233e230dd2SCorentin Chary    menu = [[NSMenu alloc] initWithTitle:@""];
17249e8204b1SProgrammingkid    [menu addItemWithTitle:@"About QEMU" action:@selector(do_about_menu_item:) keyEquivalent:@""]; // About QEMU
17253e230dd2SCorentin Chary    [menu addItem:[NSMenuItem separatorItem]]; //Separator
17265b6988c1SAkihiko Odaki    menuItem = [menu addItemWithTitle:@"Services" action:nil keyEquivalent:@""];
17275b6988c1SAkihiko Odaki    [menuItem setSubmenu:[NSApp servicesMenu]];
17285b6988c1SAkihiko Odaki    [menu addItem:[NSMenuItem separatorItem]];
17293e230dd2SCorentin Chary    [menu addItemWithTitle:@"Hide QEMU" action:@selector(hide:) keyEquivalent:@"h"]; //Hide QEMU
17303e230dd2SCorentin Chary    menuItem = (NSMenuItem *)[menu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; // Hide Others
17314ba967adSBrendan Shanks    [menuItem setKeyEquivalentModifierMask:(NSEventModifierFlagOption|NSEventModifierFlagCommand)];
17323e230dd2SCorentin Chary    [menu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; // Show All
17333e230dd2SCorentin Chary    [menu addItem:[NSMenuItem separatorItem]]; //Separator
17343e230dd2SCorentin Chary    [menu addItemWithTitle:@"Quit QEMU" action:@selector(terminate:) keyEquivalent:@"q"];
17353e230dd2SCorentin Chary    menuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" action:nil keyEquivalent:@""];
17363e230dd2SCorentin Chary    [menuItem setSubmenu:menu];
17373e230dd2SCorentin Chary    [[NSApp mainMenu] addItem:menuItem];
17383e230dd2SCorentin Chary    [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; // Workaround (this method is private since 10.4+)
17393e230dd2SCorentin Chary
17408524f1c7SJohn Arbuckle    // Machine menu
17418524f1c7SJohn Arbuckle    menu = [[NSMenu alloc] initWithTitle: @"Machine"];
17428524f1c7SJohn Arbuckle    [menu setAutoenablesItems: NO];
17438524f1c7SJohn Arbuckle    [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Pause" action: @selector(pauseQEMU:) keyEquivalent: @""] autorelease]];
17448524f1c7SJohn Arbuckle    menuItem = [[[NSMenuItem alloc] initWithTitle: @"Resume" action: @selector(resumeQEMU:) keyEquivalent: @""] autorelease];
17458524f1c7SJohn Arbuckle    [menu addItem: menuItem];
17468524f1c7SJohn Arbuckle    [menuItem setEnabled: NO];
174727074614SJohn Arbuckle    [menu addItem: [NSMenuItem separatorItem]];
174827074614SJohn Arbuckle    [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Reset" action: @selector(restartQEMU:) keyEquivalent: @""] autorelease]];
174927074614SJohn Arbuckle    [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Power Down" action: @selector(powerDownQEMU:) keyEquivalent: @""] autorelease]];
17508524f1c7SJohn Arbuckle    menuItem = [[[NSMenuItem alloc] initWithTitle: @"Machine" action:nil keyEquivalent:@""] autorelease];
17518524f1c7SJohn Arbuckle    [menuItem setSubmenu:menu];
17528524f1c7SJohn Arbuckle    [[NSApp mainMenu] addItem:menuItem];
17538524f1c7SJohn Arbuckle
17543e230dd2SCorentin Chary    // View menu
17553e230dd2SCorentin Chary    menu = [[NSMenu alloc] initWithTitle:@"View"];
17565d1b2eefSProgrammingkid    [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(doToggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen
17575ec0898bSCarwyn Ellis    menuItem = [[[NSMenuItem alloc] initWithTitle:@"Zoom To Fit" action:@selector(zoomToFit:) keyEquivalent:@""] autorelease];
175855766632SAkihiko Odaki    [menuItem setState: [[cocoaView window] styleMask] & NSWindowStyleMaskResizable ? NSControlStateValueOn : NSControlStateValueOff];
17595ec0898bSCarwyn Ellis    [menu addItem: menuItem];
1760e28a909aSCarwyn Ellis    menuItem = [[[NSMenuItem alloc] initWithTitle:@"Zoom Interpolation" action:@selector(toggleZoomInterpolation:) keyEquivalent:@""] autorelease];
1761e28a909aSCarwyn Ellis    [menuItem setState: zoom_interpolation == kCGInterpolationLow ? NSControlStateValueOn : NSControlStateValueOff];
1762e28a909aSCarwyn Ellis    [menu addItem: menuItem];
17633e230dd2SCorentin Chary    menuItem = [[[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""] autorelease];
17643e230dd2SCorentin Chary    [menuItem setSubmenu:menu];
17653e230dd2SCorentin Chary    [[NSApp mainMenu] addItem:menuItem];
17663e230dd2SCorentin Chary
1767e47ec1a9SJohn Arbuckle    // Speed menu
1768e47ec1a9SJohn Arbuckle    menu = [[NSMenu alloc] initWithTitle:@"Speed"];
1769e47ec1a9SJohn Arbuckle
1770e47ec1a9SJohn Arbuckle    // Add the rest of the Speed menu items
1771e47ec1a9SJohn Arbuckle    int p, percentage, throttle_pct;
1772e47ec1a9SJohn Arbuckle    for (p = 10; p >= 0; p--)
1773e47ec1a9SJohn Arbuckle    {
1774e47ec1a9SJohn Arbuckle        percentage = p * 10 > 1 ? p * 10 : 1; // prevent a 0% menu item
1775e47ec1a9SJohn Arbuckle
1776e47ec1a9SJohn Arbuckle        menuItem = [[[NSMenuItem alloc]
1777e47ec1a9SJohn Arbuckle                   initWithTitle: [NSString stringWithFormat: @"%d%%", percentage] action:@selector(adjustSpeed:) keyEquivalent:@""] autorelease];
1778e47ec1a9SJohn Arbuckle
1779e47ec1a9SJohn Arbuckle        if (percentage == 100) {
17805e24600aSBrendan Shanks            [menuItem setState: NSControlStateValueOn];
1781e47ec1a9SJohn Arbuckle        }
1782e47ec1a9SJohn Arbuckle
1783e47ec1a9SJohn Arbuckle        /* Calculate the throttle percentage */
1784e47ec1a9SJohn Arbuckle        throttle_pct = -1 * percentage + 100;
1785e47ec1a9SJohn Arbuckle
1786e47ec1a9SJohn Arbuckle        [menuItem setTag: throttle_pct];
1787e47ec1a9SJohn Arbuckle        [menu addItem: menuItem];
1788e47ec1a9SJohn Arbuckle    }
1789e47ec1a9SJohn Arbuckle    menuItem = [[[NSMenuItem alloc] initWithTitle:@"Speed" action:nil keyEquivalent:@""] autorelease];
1790e47ec1a9SJohn Arbuckle    [menuItem setSubmenu:menu];
1791e47ec1a9SJohn Arbuckle    [[NSApp mainMenu] addItem:menuItem];
1792e47ec1a9SJohn Arbuckle
17933e230dd2SCorentin Chary    // Window menu
17943e230dd2SCorentin Chary    menu = [[NSMenu alloc] initWithTitle:@"Window"];
17953e230dd2SCorentin Chary    [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease]]; // Miniaturize
17963e230dd2SCorentin Chary    menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease];
17973e230dd2SCorentin Chary    [menuItem setSubmenu:menu];
17983e230dd2SCorentin Chary    [[NSApp mainMenu] addItem:menuItem];
17993e230dd2SCorentin Chary    [NSApp setWindowsMenu:menu];
18003e230dd2SCorentin Chary
18013e230dd2SCorentin Chary    // Help menu
18023e230dd2SCorentin Chary    menu = [[NSMenu alloc] initWithTitle:@"Help"];
18033e230dd2SCorentin Chary    [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Documentation" action:@selector(showQEMUDoc:) keyEquivalent:@"?"] autorelease]]; // QEMU Help
18043e230dd2SCorentin Chary    menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease];
18053e230dd2SCorentin Chary    [menuItem setSubmenu:menu];
18063e230dd2SCorentin Chary    [[NSApp mainMenu] addItem:menuItem];
1807c6fd6c70SPeter Maydell}
1808c6fd6c70SPeter Maydell
18098b00e4e7SPeter Maydell/* Returns a name for a given console */
18108b00e4e7SPeter Maydellstatic NSString * getConsoleName(QemuConsole * console)
18118b00e4e7SPeter Maydell{
1812ca511604SAkihiko Odaki    g_autofree char *label = qemu_console_get_label(console);
1813ca511604SAkihiko Odaki
1814ca511604SAkihiko Odaki    return [NSString stringWithUTF8String:label];
18158b00e4e7SPeter Maydell}
18168b00e4e7SPeter Maydell
18178b00e4e7SPeter Maydell/* Add an entry to the View menu for each console */
18188b00e4e7SPeter Maydellstatic void add_console_menu_entries(void)
18198b00e4e7SPeter Maydell{
18208b00e4e7SPeter Maydell    NSMenu *menu;
18218b00e4e7SPeter Maydell    NSMenuItem *menuItem;
18228b00e4e7SPeter Maydell    int index = 0;
18238b00e4e7SPeter Maydell
18248b00e4e7SPeter Maydell    menu = [[[NSApp mainMenu] itemWithTitle:@"View"] submenu];
18258b00e4e7SPeter Maydell
18268b00e4e7SPeter Maydell    [menu addItem:[NSMenuItem separatorItem]];
18278b00e4e7SPeter Maydell
18288b00e4e7SPeter Maydell    while (qemu_console_lookup_by_index(index) != NULL) {
18298b00e4e7SPeter Maydell        menuItem = [[[NSMenuItem alloc] initWithTitle: getConsoleName(qemu_console_lookup_by_index(index))
18308b00e4e7SPeter Maydell                                               action: @selector(displayConsole:) keyEquivalent: @""] autorelease];
18318b00e4e7SPeter Maydell        [menuItem setTag: index];
18328b00e4e7SPeter Maydell        [menu addItem: menuItem];
18338b00e4e7SPeter Maydell        index++;
18348b00e4e7SPeter Maydell    }
18358b00e4e7SPeter Maydell}
18368b00e4e7SPeter Maydell
18378b00e4e7SPeter Maydell/* Make menu items for all removable devices.
18388b00e4e7SPeter Maydell * Each device is given an 'Eject' and 'Change' menu item.
18398b00e4e7SPeter Maydell */
18408b00e4e7SPeter Maydellstatic void addRemovableDevicesMenuItems(void)
18418b00e4e7SPeter Maydell{
18428b00e4e7SPeter Maydell    NSMenu *menu;
18438b00e4e7SPeter Maydell    NSMenuItem *menuItem;
18448b00e4e7SPeter Maydell    BlockInfoList *currentDevice, *pointerToFree;
18458b00e4e7SPeter Maydell    NSString *deviceName;
18468b00e4e7SPeter Maydell
18478b00e4e7SPeter Maydell    currentDevice = qmp_query_block(NULL);
18488b00e4e7SPeter Maydell    pointerToFree = currentDevice;
18498b00e4e7SPeter Maydell
18508b00e4e7SPeter Maydell    menu = [[[NSApp mainMenu] itemWithTitle:@"Machine"] submenu];
18518b00e4e7SPeter Maydell
18528b00e4e7SPeter Maydell    // Add a separator between related groups of menu items
18538b00e4e7SPeter Maydell    [menu addItem:[NSMenuItem separatorItem]];
18548b00e4e7SPeter Maydell
18558b00e4e7SPeter Maydell    // Set the attributes to the "Removable Media" menu item
18568b00e4e7SPeter Maydell    NSString *titleString = @"Removable Media";
18578b00e4e7SPeter Maydell    NSMutableAttributedString *attString=[[NSMutableAttributedString alloc] initWithString:titleString];
18588b00e4e7SPeter Maydell    NSColor *newColor = [NSColor blackColor];
18598b00e4e7SPeter Maydell    NSFontManager *fontManager = [NSFontManager sharedFontManager];
18608b00e4e7SPeter Maydell    NSFont *font = [fontManager fontWithFamily:@"Helvetica"
18618b00e4e7SPeter Maydell                                          traits:NSBoldFontMask|NSItalicFontMask
18628b00e4e7SPeter Maydell                                          weight:0
18638b00e4e7SPeter Maydell                                            size:14];
18648b00e4e7SPeter Maydell    [attString addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, [titleString length])];
18658b00e4e7SPeter Maydell    [attString addAttribute:NSForegroundColorAttributeName value:newColor range:NSMakeRange(0, [titleString length])];
18668b00e4e7SPeter Maydell    [attString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInt: 1] range:NSMakeRange(0, [titleString length])];
18678b00e4e7SPeter Maydell
18688b00e4e7SPeter Maydell    // Add the "Removable Media" menu item
18698b00e4e7SPeter Maydell    menuItem = [NSMenuItem new];
18708b00e4e7SPeter Maydell    [menuItem setAttributedTitle: attString];
18718b00e4e7SPeter Maydell    [menuItem setEnabled: NO];
18728b00e4e7SPeter Maydell    [menu addItem: menuItem];
18738b00e4e7SPeter Maydell
18748b00e4e7SPeter Maydell    /* Loop through all the block devices in the emulator */
18758b00e4e7SPeter Maydell    while (currentDevice) {
18768b00e4e7SPeter Maydell        deviceName = [[NSString stringWithFormat: @"%s", currentDevice->value->device] retain];
18778b00e4e7SPeter Maydell
18788b00e4e7SPeter Maydell        if(currentDevice->value->removable) {
18798b00e4e7SPeter Maydell            menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Change %s...", currentDevice->value->device]
18808b00e4e7SPeter Maydell                                                  action: @selector(changeDeviceMedia:)
18818b00e4e7SPeter Maydell                                           keyEquivalent: @""];
18828b00e4e7SPeter Maydell            [menu addItem: menuItem];
18838b00e4e7SPeter Maydell            [menuItem setRepresentedObject: deviceName];
18848b00e4e7SPeter Maydell            [menuItem autorelease];
18858b00e4e7SPeter Maydell
18868b00e4e7SPeter Maydell            menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Eject %s", currentDevice->value->device]
18878b00e4e7SPeter Maydell                                                  action: @selector(ejectDeviceMedia:)
18888b00e4e7SPeter Maydell                                           keyEquivalent: @""];
18898b00e4e7SPeter Maydell            [menu addItem: menuItem];
18908b00e4e7SPeter Maydell            [menuItem setRepresentedObject: deviceName];
18918b00e4e7SPeter Maydell            [menuItem autorelease];
18928b00e4e7SPeter Maydell        }
18938b00e4e7SPeter Maydell        currentDevice = currentDevice->next;
18948b00e4e7SPeter Maydell    }
18958b00e4e7SPeter Maydell    qapi_free_BlockInfoList(pointerToFree);
18968b00e4e7SPeter Maydell}
18978b00e4e7SPeter Maydell
1898e7b53d16SAkihiko Odakistatic void cocoa_mouse_mode_change_notify(Notifier *notifier, void *data)
1899e7b53d16SAkihiko Odaki{
1900e7b53d16SAkihiko Odaki    dispatch_async(dispatch_get_main_queue(), ^{
1901e7b53d16SAkihiko Odaki        [cocoaView notifyMouseModeChange];
1902e7b53d16SAkihiko Odaki    });
1903e7b53d16SAkihiko Odaki}
1904e7b53d16SAkihiko Odaki
1905e7b53d16SAkihiko Odakistatic Notifier mouse_mode_change_notifier = {
1906e7b53d16SAkihiko Odaki    .notify = cocoa_mouse_mode_change_notify
1907e7b53d16SAkihiko Odaki};
1908e7b53d16SAkihiko Odaki
19097e3e20d8SAkihiko Odaki@interface QemuCocoaPasteboardTypeOwner : NSObject<NSPasteboardTypeOwner>
19107e3e20d8SAkihiko Odaki@end
19117e3e20d8SAkihiko Odaki
19127e3e20d8SAkihiko Odaki@implementation QemuCocoaPasteboardTypeOwner
19137e3e20d8SAkihiko Odaki
19147e3e20d8SAkihiko Odaki- (void)pasteboard:(NSPasteboard *)sender provideDataForType:(NSPasteboardType)type
19157e3e20d8SAkihiko Odaki{
19167e3e20d8SAkihiko Odaki    if (type != NSPasteboardTypeString) {
19177e3e20d8SAkihiko Odaki        return;
19187e3e20d8SAkihiko Odaki    }
19197e3e20d8SAkihiko Odaki
1920195801d7SStefan Hajnoczi    with_bql(^{
19217e3e20d8SAkihiko Odaki        QemuClipboardInfo *info = qemu_clipboard_info_ref(cbinfo);
19227e3e20d8SAkihiko Odaki        qemu_event_reset(&cbevent);
19237e3e20d8SAkihiko Odaki        qemu_clipboard_request(info, QEMU_CLIPBOARD_TYPE_TEXT);
19247e3e20d8SAkihiko Odaki
19257e3e20d8SAkihiko Odaki        while (info == cbinfo &&
19267e3e20d8SAkihiko Odaki               info->types[QEMU_CLIPBOARD_TYPE_TEXT].available &&
19277e3e20d8SAkihiko Odaki               info->types[QEMU_CLIPBOARD_TYPE_TEXT].data == NULL) {
1928195801d7SStefan Hajnoczi            bql_unlock();
19297e3e20d8SAkihiko Odaki            qemu_event_wait(&cbevent);
1930195801d7SStefan Hajnoczi            bql_lock();
19317e3e20d8SAkihiko Odaki        }
19327e3e20d8SAkihiko Odaki
19337e3e20d8SAkihiko Odaki        if (info == cbinfo) {
19347e3e20d8SAkihiko Odaki            NSData *data = [[NSData alloc] initWithBytes:info->types[QEMU_CLIPBOARD_TYPE_TEXT].data
19357e3e20d8SAkihiko Odaki                                           length:info->types[QEMU_CLIPBOARD_TYPE_TEXT].size];
19367e3e20d8SAkihiko Odaki            [sender setData:data forType:NSPasteboardTypeString];
19377e3e20d8SAkihiko Odaki            [data release];
19387e3e20d8SAkihiko Odaki        }
19397e3e20d8SAkihiko Odaki
19407e3e20d8SAkihiko Odaki        qemu_clipboard_info_unref(info);
19417e3e20d8SAkihiko Odaki    });
19427e3e20d8SAkihiko Odaki}
19437e3e20d8SAkihiko Odaki
19447e3e20d8SAkihiko Odaki@end
19457e3e20d8SAkihiko Odaki
19467e3e20d8SAkihiko Odakistatic QemuCocoaPasteboardTypeOwner *cbowner;
19477e3e20d8SAkihiko Odaki
19487e3e20d8SAkihiko Odakistatic void cocoa_clipboard_notify(Notifier *notifier, void *data);
19497e3e20d8SAkihiko Odakistatic void cocoa_clipboard_request(QemuClipboardInfo *info,
19507e3e20d8SAkihiko Odaki                                    QemuClipboardType type);
19517e3e20d8SAkihiko Odaki
19527e3e20d8SAkihiko Odakistatic QemuClipboardPeer cbpeer = {
19537e3e20d8SAkihiko Odaki    .name = "cocoa",
19541b17f1e9SMarc-André Lureau    .notifier = { .notify = cocoa_clipboard_notify },
19557e3e20d8SAkihiko Odaki    .request = cocoa_clipboard_request
19567e3e20d8SAkihiko Odaki};
19577e3e20d8SAkihiko Odaki
19581b17f1e9SMarc-André Lureaustatic void cocoa_clipboard_update_info(QemuClipboardInfo *info)
19597e3e20d8SAkihiko Odaki{
19607e3e20d8SAkihiko Odaki    if (info->owner == &cbpeer || info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) {
19617e3e20d8SAkihiko Odaki        return;
19627e3e20d8SAkihiko Odaki    }
19637e3e20d8SAkihiko Odaki
19647e3e20d8SAkihiko Odaki    if (info != cbinfo) {
19657e3e20d8SAkihiko Odaki        NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
19667e3e20d8SAkihiko Odaki        qemu_clipboard_info_unref(cbinfo);
19677e3e20d8SAkihiko Odaki        cbinfo = qemu_clipboard_info_ref(info);
19687e3e20d8SAkihiko Odaki        cbchangecount = [[NSPasteboard generalPasteboard] declareTypes:@[NSPasteboardTypeString] owner:cbowner];
19697e3e20d8SAkihiko Odaki        [pool release];
19707e3e20d8SAkihiko Odaki    }
19717e3e20d8SAkihiko Odaki
19727e3e20d8SAkihiko Odaki    qemu_event_set(&cbevent);
19737e3e20d8SAkihiko Odaki}
19747e3e20d8SAkihiko Odaki
19751b17f1e9SMarc-André Lureaustatic void cocoa_clipboard_notify(Notifier *notifier, void *data)
19761b17f1e9SMarc-André Lureau{
19771b17f1e9SMarc-André Lureau    QemuClipboardNotify *notify = data;
19781b17f1e9SMarc-André Lureau
19791b17f1e9SMarc-André Lureau    switch (notify->type) {
19801b17f1e9SMarc-André Lureau    case QEMU_CLIPBOARD_UPDATE_INFO:
19811b17f1e9SMarc-André Lureau        cocoa_clipboard_update_info(notify->info);
19821b17f1e9SMarc-André Lureau        return;
1983505dbf9bSMarc-André Lureau    case QEMU_CLIPBOARD_RESET_SERIAL:
1984505dbf9bSMarc-André Lureau        /* ignore */
1985505dbf9bSMarc-André Lureau        return;
19861b17f1e9SMarc-André Lureau    }
19871b17f1e9SMarc-André Lureau}
19881b17f1e9SMarc-André Lureau
19897e3e20d8SAkihiko Odakistatic void cocoa_clipboard_request(QemuClipboardInfo *info,
19907e3e20d8SAkihiko Odaki                                    QemuClipboardType type)
19917e3e20d8SAkihiko Odaki{
19928c0d8024SAkihiko Odaki    NSAutoreleasePool *pool;
19937e3e20d8SAkihiko Odaki    NSData *text;
19947e3e20d8SAkihiko Odaki
19957e3e20d8SAkihiko Odaki    switch (type) {
19967e3e20d8SAkihiko Odaki    case QEMU_CLIPBOARD_TYPE_TEXT:
19978c0d8024SAkihiko Odaki        pool = [[NSAutoreleasePool alloc] init];
19987e3e20d8SAkihiko Odaki        text = [[NSPasteboard generalPasteboard] dataForType:NSPasteboardTypeString];
19997e3e20d8SAkihiko Odaki        if (text) {
20007e3e20d8SAkihiko Odaki            qemu_clipboard_set_data(&cbpeer, info, type,
20017e3e20d8SAkihiko Odaki                                    [text length], [text bytes], true);
20027e3e20d8SAkihiko Odaki        }
20038c0d8024SAkihiko Odaki        [pool release];
20047e3e20d8SAkihiko Odaki        break;
20057e3e20d8SAkihiko Odaki    default:
20067e3e20d8SAkihiko Odaki        break;
20077e3e20d8SAkihiko Odaki    }
20087e3e20d8SAkihiko Odaki}
20097e3e20d8SAkihiko Odaki
20105588840fSPeter Maydell/*
20115588840fSPeter Maydell * The startup process for the OSX/Cocoa UI is complicated, because
20125588840fSPeter Maydell * OSX insists that the UI runs on the initial main thread, and so we
2013bab6a301SAkihiko Odaki * need to start a second thread which runs the qemu_default_main():
20145588840fSPeter Maydell * in main():
20155588840fSPeter Maydell *  in cocoa_display_init():
2016bab6a301SAkihiko Odaki *   assign cocoa_main to qemu_main
20175588840fSPeter Maydell *   create application, menus, etc
2018bab6a301SAkihiko Odaki *  in cocoa_main():
2019bab6a301SAkihiko Odaki *   create qemu-main thread
20205588840fSPeter Maydell *   enter OSX run loop
20215588840fSPeter Maydell */
2022c6fd6c70SPeter Maydell
20235588840fSPeter Maydellstatic void *call_qemu_main(void *opaque)
20245588840fSPeter Maydell{
20255588840fSPeter Maydell    int status;
20265588840fSPeter Maydell
2027bab6a301SAkihiko Odaki    COCOA_DEBUG("Second thread: calling qemu_default_main()\n");
2028195801d7SStefan Hajnoczi    bql_lock();
2029bab6a301SAkihiko Odaki    status = qemu_default_main();
2030195801d7SStefan Hajnoczi    bql_unlock();
2031bab6a301SAkihiko Odaki    COCOA_DEBUG("Second thread: qemu_default_main() returned, exiting\n");
20327e3e20d8SAkihiko Odaki    [cbowner release];
20335588840fSPeter Maydell    exit(status);
20345588840fSPeter Maydell}
20355588840fSPeter Maydell
2036f975033dSPhilippe Mathieu-Daudéstatic int cocoa_main(void)
2037bab6a301SAkihiko Odaki{
20385588840fSPeter Maydell    QemuThread thread;
20395588840fSPeter Maydell
2040bab6a301SAkihiko Odaki    COCOA_DEBUG("Entered %s()\n", __func__);
2041c6fd6c70SPeter Maydell
2042195801d7SStefan Hajnoczi    bql_unlock();
20435588840fSPeter Maydell    qemu_thread_create(&thread, "qemu_main", call_qemu_main,
20445588840fSPeter Maydell                       NULL, QEMU_THREAD_DETACHED);
20455588840fSPeter Maydell
20463e230dd2SCorentin Chary    // Start the main event loop
20475588840fSPeter Maydell    COCOA_DEBUG("Main thread: entering OSX run loop\n");
20483e230dd2SCorentin Chary    [NSApp run];
2049bab6a301SAkihiko Odaki    COCOA_DEBUG("Main thread: left OSX run loop, which should never happen\n");
20503e230dd2SCorentin Chary
2051bab6a301SAkihiko Odaki    abort();
20523e230dd2SCorentin Chary}
20533e230dd2SCorentin Chary
20543e230dd2SCorentin Chary
20553e230dd2SCorentin Chary
20563e230dd2SCorentin Chary#pragma mark qemu
20577c20b4a3SGerd Hoffmannstatic void cocoa_update(DisplayChangeListener *dcl,
20587c20b4a3SGerd Hoffmann                         int x, int y, int w, int h)
20593e230dd2SCorentin Chary{
20603e230dd2SCorentin Chary    COCOA_DEBUG("qemu_cocoa: cocoa_update\n");
20613e230dd2SCorentin Chary
20625588840fSPeter Maydell    dispatch_async(dispatch_get_main_queue(), ^{
2063fcb03de7SAkihiko Odaki        NSRect rect = NSMakeRect(x, [cocoaView gscreen].height - y - h, w, h);
20643e230dd2SCorentin Chary        [cocoaView setNeedsDisplayInRect:rect];
20655588840fSPeter Maydell    });
20663e230dd2SCorentin Chary}
20673e230dd2SCorentin Chary
2068c12aeb86SGerd Hoffmannstatic void cocoa_switch(DisplayChangeListener *dcl,
2069c12aeb86SGerd Hoffmann                         DisplaySurface *surface)
20703e230dd2SCorentin Chary{
20715588840fSPeter Maydell    pixman_image_t *image = surface->image;
20723e230dd2SCorentin Chary
20736e657e64SPeter Maydell    COCOA_DEBUG("qemu_cocoa: cocoa_switch\n");
20745588840fSPeter Maydell
20755588840fSPeter Maydell    // The DisplaySurface will be freed as soon as this callback returns.
20765588840fSPeter Maydell    // We take a reference to the underlying pixman image here so it does
20775588840fSPeter Maydell    // not disappear from under our feet; the switchSurface method will
20785588840fSPeter Maydell    // deref the old image when it is done with it.
20795588840fSPeter Maydell    pixman_image_ref(image);
20805588840fSPeter Maydell
20815588840fSPeter Maydell    dispatch_async(dispatch_get_main_queue(), ^{
20825588840fSPeter Maydell        [cocoaView switchSurface:image];
20835588840fSPeter Maydell    });
20843e230dd2SCorentin Chary}
20853e230dd2SCorentin Chary
2086bc2ed970SGerd Hoffmannstatic void cocoa_refresh(DisplayChangeListener *dcl)
20873e230dd2SCorentin Chary{
20886e657e64SPeter Maydell    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
20896e657e64SPeter Maydell
20903e230dd2SCorentin Chary    COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n");
2091ca3de7b5SAkihiko Odaki    graphic_hw_update(dcl->con);
20923e230dd2SCorentin Chary
20937e3e20d8SAkihiko Odaki    if (cbchangecount != [[NSPasteboard generalPasteboard] changeCount]) {
20947e3e20d8SAkihiko Odaki        qemu_clipboard_info_unref(cbinfo);
20957e3e20d8SAkihiko Odaki        cbinfo = qemu_clipboard_info_new(&cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD);
20967e3e20d8SAkihiko Odaki        if ([[NSPasteboard generalPasteboard] availableTypeFromArray:@[NSPasteboardTypeString]]) {
20977e3e20d8SAkihiko Odaki            cbinfo->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
20987e3e20d8SAkihiko Odaki        }
20997e3e20d8SAkihiko Odaki        qemu_clipboard_update(cbinfo);
21007e3e20d8SAkihiko Odaki        cbchangecount = [[NSPasteboard generalPasteboard] changeCount];
21017e3e20d8SAkihiko Odaki        qemu_event_set(&cbevent);
21027e3e20d8SAkihiko Odaki    }
21037e3e20d8SAkihiko Odaki
21046e657e64SPeter Maydell    [pool release];
21053e230dd2SCorentin Chary}
21063e230dd2SCorentin Chary
2107d2277f02SAkihiko Odakistatic void cocoa_mouse_set(DisplayChangeListener *dcl, int x, int y, bool on)
2108d2277f02SAkihiko Odaki{
2109d2277f02SAkihiko Odaki    dispatch_async(dispatch_get_main_queue(), ^{
2110d2277f02SAkihiko Odaki        [cocoaView setMouseX:x y:y on:on];
2111d2277f02SAkihiko Odaki    });
2112d2277f02SAkihiko Odaki}
2113d2277f02SAkihiko Odaki
2114d2277f02SAkihiko Odakistatic void cocoa_cursor_define(DisplayChangeListener *dcl, QEMUCursor *cursor)
2115d2277f02SAkihiko Odaki{
2116d2277f02SAkihiko Odaki    dispatch_async(dispatch_get_main_queue(), ^{
2117d2277f02SAkihiko Odaki        BQL_LOCK_GUARD();
2118d2277f02SAkihiko Odaki        [cocoaView setCursor:qemu_console_get_cursor(dcl->con)];
2119d2277f02SAkihiko Odaki    });
2120d2277f02SAkihiko Odaki}
2121d2277f02SAkihiko Odaki
21225013b9e4SGerd Hoffmannstatic void cocoa_display_init(DisplayState *ds, DisplayOptions *opts)
21233e230dd2SCorentin Chary{
2124bab6a301SAkihiko Odaki    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
2125bab6a301SAkihiko Odaki
21263e230dd2SCorentin Chary    COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n");
21273e230dd2SCorentin Chary
2128bab6a301SAkihiko Odaki    qemu_main = cocoa_main;
21295588840fSPeter Maydell
2130bab6a301SAkihiko Odaki    // Pull this console process up to being a fully-fledged graphical
2131bab6a301SAkihiko Odaki    // app with a menubar and Dock icon
2132bab6a301SAkihiko Odaki    ProcessSerialNumber psn = { 0, kCurrentProcess };
2133bab6a301SAkihiko Odaki    TransformProcessType(&psn, kProcessTransformToForegroundApplication);
2134bab6a301SAkihiko Odaki
2135bab6a301SAkihiko Odaki    [QemuApplication sharedApplication];
2136bab6a301SAkihiko Odaki
2137bab6a301SAkihiko Odaki    // Create an Application controller
2138bab6a301SAkihiko Odaki    QemuCocoaAppController *controller = [[QemuCocoaAppController alloc] init];
2139bab6a301SAkihiko Odaki    [NSApp setDelegate:controller];
2140bab6a301SAkihiko Odaki
214143227af8SProgrammingkid    /* if fullscreen mode is to be used */
2142767f9bf3SGerd Hoffmann    if (opts->has_full_screen && opts->full_screen) {
21430c35886eSAkihiko Odaki        [[cocoaView window] toggleFullScreen: nil];
2144f844cdb9SGustavo Noronha Silva    }
2145f844cdb9SGustavo Noronha Silva    if (opts->u.cocoa.has_full_grab && opts->u.cocoa.full_grab) {
2146f844cdb9SGustavo Noronha Silva        [controller setFullGrab: nil];
214743227af8SProgrammingkid    }
214869221df8SCarwyn Ellis
21493487da6aSGerd Hoffmann    if (opts->has_show_cursor && opts->show_cursor) {
21503487da6aSGerd Hoffmann        cursor_hide = 0;
21513487da6aSGerd Hoffmann    }
21524797adceSGustavo Noronha Silva    if (opts->u.cocoa.has_swap_opt_cmd) {
21534797adceSGustavo Noronha Silva        swap_opt_cmd = opts->u.cocoa.swap_opt_cmd;
21544797adceSGustavo Noronha Silva    }
215543227af8SProgrammingkid
215648941a52SCarwyn Ellis    if (opts->u.cocoa.has_left_command_key && !opts->u.cocoa.left_command_key) {
215748941a52SCarwyn Ellis        left_command_key_enabled = 0;
215848941a52SCarwyn Ellis    }
215948941a52SCarwyn Ellis
21605ec0898bSCarwyn Ellis    if (opts->u.cocoa.has_zoom_to_fit && opts->u.cocoa.zoom_to_fit) {
2161b6ee03c2SAkihiko Odaki        [cocoaView window].styleMask |= NSWindowStyleMaskResizable;
21625ec0898bSCarwyn Ellis    }
21635ec0898bSCarwyn Ellis
2164e28a909aSCarwyn Ellis    if (opts->u.cocoa.has_zoom_interpolation && opts->u.cocoa.zoom_interpolation) {
2165e28a909aSCarwyn Ellis        zoom_interpolation = kCGInterpolationLow;
2166e28a909aSCarwyn Ellis    }
2167e28a909aSCarwyn Ellis
21685ec0898bSCarwyn Ellis    create_initial_menus();
21695ec0898bSCarwyn Ellis    /*
21705ec0898bSCarwyn Ellis     * Create the menu entries which depend on QEMU state (for consoles
21715ec0898bSCarwyn Ellis     * and removable devices). These make calls back into QEMU functions,
21725ec0898bSCarwyn Ellis     * which is OK because at this point we know that the second thread
2173a4a411fbSStefan Hajnoczi     * holds the BQL and is synchronously waiting for us to
21745ec0898bSCarwyn Ellis     * finish.
21755ec0898bSCarwyn Ellis     */
21765ec0898bSCarwyn Ellis    add_console_menu_entries();
21775ec0898bSCarwyn Ellis    addRemovableDevicesMenuItems();
21785ec0898bSCarwyn Ellis
2179ca3de7b5SAkihiko Odaki    dcl.con = qemu_console_lookup_default();
2180ca3de7b5SAkihiko Odaki    kbd = qkbd_state_init(dcl.con);
2181ca3de7b5SAkihiko Odaki
21823e230dd2SCorentin Chary    // register vga output callbacks
2183cc7859c3SAkihiko Odaki    register_displaychangelistener(&dcl);
2184e7b53d16SAkihiko Odaki    qemu_add_mouse_mode_change_notifier(&mouse_mode_change_notifier);
2185e7b53d16SAkihiko Odaki    [cocoaView notifyMouseModeChange];
2186ca3de7b5SAkihiko Odaki    [cocoaView updateUIInfo];
21877e3e20d8SAkihiko Odaki
21887e3e20d8SAkihiko Odaki    qemu_event_init(&cbevent, false);
21897e3e20d8SAkihiko Odaki    cbowner = [[QemuCocoaPasteboardTypeOwner alloc] init];
21907e3e20d8SAkihiko Odaki    qemu_clipboard_peer_register(&cbpeer);
2191bab6a301SAkihiko Odaki
2192bab6a301SAkihiko Odaki    [pool release];
21933e230dd2SCorentin Chary}
21945013b9e4SGerd Hoffmann
21955013b9e4SGerd Hoffmannstatic QemuDisplay qemu_display_cocoa = {
21965013b9e4SGerd Hoffmann    .type       = DISPLAY_TYPE_COCOA,
21975013b9e4SGerd Hoffmann    .init       = cocoa_display_init,
21985013b9e4SGerd Hoffmann};
21995013b9e4SGerd Hoffmann
22005013b9e4SGerd Hoffmannstatic void register_cocoa(void)
22015013b9e4SGerd Hoffmann{
22025013b9e4SGerd Hoffmann    qemu_display_register(&qemu_display_cocoa);
22035013b9e4SGerd Hoffmann}
22045013b9e4SGerd Hoffmann
22055013b9e4SGerd Hoffmanntype_init(register_cocoa);
2206