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