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