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