13e230dd2SCorentin Chary/* 23e230dd2SCorentin Chary * QEMU Cocoa CG display driver 33e230dd2SCorentin Chary * 43e230dd2SCorentin Chary * Copyright (c) 2008 Mike Kronenberg 53e230dd2SCorentin Chary * 63e230dd2SCorentin Chary * Permission is hereby granted, free of charge, to any person obtaining a copy 73e230dd2SCorentin Chary * of this software and associated documentation files (the "Software"), to deal 83e230dd2SCorentin Chary * in the Software without restriction, including without limitation the rights 93e230dd2SCorentin Chary * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 103e230dd2SCorentin Chary * copies of the Software, and to permit persons to whom the Software is 113e230dd2SCorentin Chary * furnished to do so, subject to the following conditions: 123e230dd2SCorentin Chary * 133e230dd2SCorentin Chary * The above copyright notice and this permission notice shall be included in 143e230dd2SCorentin Chary * all copies or substantial portions of the Software. 153e230dd2SCorentin Chary * 163e230dd2SCorentin Chary * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 173e230dd2SCorentin Chary * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 183e230dd2SCorentin Chary * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 193e230dd2SCorentin Chary * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 203e230dd2SCorentin Chary * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 213e230dd2SCorentin Chary * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 223e230dd2SCorentin Chary * THE SOFTWARE. 233e230dd2SCorentin Chary */ 243e230dd2SCorentin Chary 25e4a096b1SPeter Maydell#include "qemu/osdep.h" 26e4a096b1SPeter Maydell 273e230dd2SCorentin Chary#import <Cocoa/Cocoa.h> 283bbbee18SAndreas Färber#include <crt_externs.h> 293e230dd2SCorentin Chary 3049f95221SMarc-André Lureau#include "qemu/help-texts.h" 3188c39c86SMarc-André Lureau#include "qemu-main.h" 327e3e20d8SAkihiko Odaki#include "ui/clipboard.h" 3328ecbaeeSPaolo Bonzini#include "ui/console.h" 3421bae11aSGerd Hoffmann#include "ui/input.h" 356d73bb64SAkihiko Odaki#include "ui/kbd-state.h" 369c17d615SPaolo Bonzini#include "sysemu/sysemu.h" 3754d31236SMarkus Armbruster#include "sysemu/runstate.h" 38b0c3cf94SClaudio Fontana#include "sysemu/cpu-throttle.h" 39e688df6bSMarkus Armbruster#include "qapi/error.h" 4016bf5234SMarkus Armbruster#include "qapi/qapi-commands-block.h" 4190f8c0f9SPhilippe Mathieu-Daudé#include "qapi/qapi-commands-machine.h" 4216bf5234SMarkus Armbruster#include "qapi/qapi-commands-misc.h" 43693a3e01SJohn Arbuckle#include "sysemu/blockdev.h" 449e8204b1SProgrammingkid#include "qemu-version.h" 45e31746ecSAkihiko Odaki#include "qemu/cutils.h" 46db725815SMarkus Armbruster#include "qemu/main-loop.h" 470b8fa32fSMarkus Armbruster#include "qemu/module.h" 48aaac714fSJohn Arbuckle#include <Carbon/Carbon.h> 492e5b09fdSMarkus Armbruster#include "hw/core/cpu.h" 503e230dd2SCorentin Chary 515e24600aSBrendan Shanks#ifndef MAC_OS_X_VERSION_10_13 525e24600aSBrendan Shanks#define MAC_OS_X_VERSION_10_13 101300 535e24600aSBrendan Shanks#endif 543e230dd2SCorentin Chary 555e24600aSBrendan Shanks/* 10.14 deprecates NSOnState and NSOffState in favor of 565e24600aSBrendan Shanks * NSControlStateValueOn/Off, which were introduced in 10.13. 575e24600aSBrendan Shanks * Define for older versions 585e24600aSBrendan Shanks */ 595e24600aSBrendan Shanks#if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_13 605e24600aSBrendan Shanks#define NSControlStateValueOn NSOnState 615e24600aSBrendan Shanks#define NSControlStateValueOff NSOffState 625e24600aSBrendan Shanks#endif 633e230dd2SCorentin Chary 643e230dd2SCorentin Chary//#define DEBUG 653e230dd2SCorentin Chary 663e230dd2SCorentin Chary#ifdef DEBUG 673e230dd2SCorentin Chary#define COCOA_DEBUG(...) { (void) fprintf (stdout, __VA_ARGS__); } 683e230dd2SCorentin Chary#else 693e230dd2SCorentin Chary#define COCOA_DEBUG(...) ((void) 0) 703e230dd2SCorentin Chary#endif 713e230dd2SCorentin Chary 723e230dd2SCorentin Chary#define cgrect(nsrect) (*(CGRect *)&(nsrect)) 733e230dd2SCorentin Chary 743e230dd2SCorentin Charytypedef struct { 753e230dd2SCorentin Chary int width; 763e230dd2SCorentin Chary int height; 773e230dd2SCorentin Chary} QEMUScreen; 783e230dd2SCorentin Chary 79cc7859c3SAkihiko Odakistatic void cocoa_update(DisplayChangeListener *dcl, 80cc7859c3SAkihiko Odaki int x, int y, int w, int h); 81cc7859c3SAkihiko Odaki 82cc7859c3SAkihiko Odakistatic void cocoa_switch(DisplayChangeListener *dcl, 83cc7859c3SAkihiko Odaki DisplaySurface *surface); 84cc7859c3SAkihiko Odaki 85cc7859c3SAkihiko Odakistatic void cocoa_refresh(DisplayChangeListener *dcl); 86cc7859c3SAkihiko Odaki 8799eb313dSAkihiko Odakistatic NSWindow *normalWindow; 88cc7859c3SAkihiko Odakistatic const DisplayChangeListenerOps dcl_ops = { 89cc7859c3SAkihiko Odaki .dpy_name = "cocoa", 90cc7859c3SAkihiko Odaki .dpy_gfx_update = cocoa_update, 91cc7859c3SAkihiko Odaki .dpy_gfx_switch = cocoa_switch, 92cc7859c3SAkihiko Odaki .dpy_refresh = cocoa_refresh, 93cc7859c3SAkihiko Odaki}; 94cc7859c3SAkihiko Odakistatic DisplayChangeListener dcl = { 95cc7859c3SAkihiko Odaki .ops = &dcl_ops, 96cc7859c3SAkihiko Odaki}; 9721bae11aSGerd Hoffmannstatic int last_buttons; 983487da6aSGerd Hoffmannstatic int cursor_hide = 1; 9948941a52SCarwyn Ellisstatic int left_command_key_enabled = 1; 1004797adceSGustavo Noronha Silvastatic bool swap_opt_cmd; 1013e230dd2SCorentin Chary 102cb823408SAkihiko Odakistatic int gArgc; 103cb823408SAkihiko Odakistatic char **gArgv; 104cb823408SAkihiko Odakistatic bool stretch_video; 105cb823408SAkihiko Odakistatic NSTextField *pauseLabel; 1063e230dd2SCorentin Chary 1075588840fSPeter Maydellstatic QemuSemaphore display_init_sem; 1085588840fSPeter Maydellstatic QemuSemaphore app_started_sem; 109dff742adSHikaru Nishidastatic bool allow_events; 1105588840fSPeter Maydell 1117e3e20d8SAkihiko Odakistatic NSInteger cbchangecount = -1; 1127e3e20d8SAkihiko Odakistatic QemuClipboardInfo *cbinfo; 1137e3e20d8SAkihiko Odakistatic QemuEvent cbevent; 1147e3e20d8SAkihiko Odaki 11560105d7aSPeter Maydell// Utility functions to run specified code block with iothread lock held 11631819e95SPeter Maydelltypedef void (^CodeBlock)(void); 11760105d7aSPeter Maydelltypedef bool (^BoolCodeBlock)(void); 11831819e95SPeter Maydell 11931819e95SPeter Maydellstatic void with_iothread_lock(CodeBlock block) 12031819e95SPeter Maydell{ 12131819e95SPeter Maydell bool locked = qemu_mutex_iothread_locked(); 12231819e95SPeter Maydell if (!locked) { 12331819e95SPeter Maydell qemu_mutex_lock_iothread(); 12431819e95SPeter Maydell } 12531819e95SPeter Maydell block(); 12631819e95SPeter Maydell if (!locked) { 12731819e95SPeter Maydell qemu_mutex_unlock_iothread(); 12831819e95SPeter Maydell } 12931819e95SPeter Maydell} 13031819e95SPeter Maydell 13160105d7aSPeter Maydellstatic bool bool_with_iothread_lock(BoolCodeBlock block) 13260105d7aSPeter Maydell{ 13360105d7aSPeter Maydell bool locked = qemu_mutex_iothread_locked(); 13460105d7aSPeter Maydell bool val; 13560105d7aSPeter Maydell 13660105d7aSPeter Maydell if (!locked) { 13760105d7aSPeter Maydell qemu_mutex_lock_iothread(); 13860105d7aSPeter Maydell } 13960105d7aSPeter Maydell val = block(); 14060105d7aSPeter Maydell if (!locked) { 14160105d7aSPeter Maydell qemu_mutex_unlock_iothread(); 14260105d7aSPeter Maydell } 14360105d7aSPeter Maydell return val; 14460105d7aSPeter Maydell} 14560105d7aSPeter Maydell 146aaac714fSJohn Arbuckle// Mac to QKeyCode conversion 147cb823408SAkihiko Odakistatic const int mac_to_qkeycode_map[] = { 148aaac714fSJohn Arbuckle [kVK_ANSI_A] = Q_KEY_CODE_A, 149aaac714fSJohn Arbuckle [kVK_ANSI_B] = Q_KEY_CODE_B, 150aaac714fSJohn Arbuckle [kVK_ANSI_C] = Q_KEY_CODE_C, 151aaac714fSJohn Arbuckle [kVK_ANSI_D] = Q_KEY_CODE_D, 152aaac714fSJohn Arbuckle [kVK_ANSI_E] = Q_KEY_CODE_E, 153aaac714fSJohn Arbuckle [kVK_ANSI_F] = Q_KEY_CODE_F, 154aaac714fSJohn Arbuckle [kVK_ANSI_G] = Q_KEY_CODE_G, 155aaac714fSJohn Arbuckle [kVK_ANSI_H] = Q_KEY_CODE_H, 156aaac714fSJohn Arbuckle [kVK_ANSI_I] = Q_KEY_CODE_I, 157aaac714fSJohn Arbuckle [kVK_ANSI_J] = Q_KEY_CODE_J, 158aaac714fSJohn Arbuckle [kVK_ANSI_K] = Q_KEY_CODE_K, 159aaac714fSJohn Arbuckle [kVK_ANSI_L] = Q_KEY_CODE_L, 160aaac714fSJohn Arbuckle [kVK_ANSI_M] = Q_KEY_CODE_M, 161aaac714fSJohn Arbuckle [kVK_ANSI_N] = Q_KEY_CODE_N, 162aaac714fSJohn Arbuckle [kVK_ANSI_O] = Q_KEY_CODE_O, 163aaac714fSJohn Arbuckle [kVK_ANSI_P] = Q_KEY_CODE_P, 164aaac714fSJohn Arbuckle [kVK_ANSI_Q] = Q_KEY_CODE_Q, 165aaac714fSJohn Arbuckle [kVK_ANSI_R] = Q_KEY_CODE_R, 166aaac714fSJohn Arbuckle [kVK_ANSI_S] = Q_KEY_CODE_S, 167aaac714fSJohn Arbuckle [kVK_ANSI_T] = Q_KEY_CODE_T, 168aaac714fSJohn Arbuckle [kVK_ANSI_U] = Q_KEY_CODE_U, 169aaac714fSJohn Arbuckle [kVK_ANSI_V] = Q_KEY_CODE_V, 170aaac714fSJohn Arbuckle [kVK_ANSI_W] = Q_KEY_CODE_W, 171aaac714fSJohn Arbuckle [kVK_ANSI_X] = Q_KEY_CODE_X, 172aaac714fSJohn Arbuckle [kVK_ANSI_Y] = Q_KEY_CODE_Y, 173aaac714fSJohn Arbuckle [kVK_ANSI_Z] = Q_KEY_CODE_Z, 1743e230dd2SCorentin Chary 175aaac714fSJohn Arbuckle [kVK_ANSI_0] = Q_KEY_CODE_0, 176aaac714fSJohn Arbuckle [kVK_ANSI_1] = Q_KEY_CODE_1, 177aaac714fSJohn Arbuckle [kVK_ANSI_2] = Q_KEY_CODE_2, 178aaac714fSJohn Arbuckle [kVK_ANSI_3] = Q_KEY_CODE_3, 179aaac714fSJohn Arbuckle [kVK_ANSI_4] = Q_KEY_CODE_4, 180aaac714fSJohn Arbuckle [kVK_ANSI_5] = Q_KEY_CODE_5, 181aaac714fSJohn Arbuckle [kVK_ANSI_6] = Q_KEY_CODE_6, 182aaac714fSJohn Arbuckle [kVK_ANSI_7] = Q_KEY_CODE_7, 183aaac714fSJohn Arbuckle [kVK_ANSI_8] = Q_KEY_CODE_8, 184aaac714fSJohn Arbuckle [kVK_ANSI_9] = Q_KEY_CODE_9, 185aaac714fSJohn Arbuckle 186aaac714fSJohn Arbuckle [kVK_ANSI_Grave] = Q_KEY_CODE_GRAVE_ACCENT, 187aaac714fSJohn Arbuckle [kVK_ANSI_Minus] = Q_KEY_CODE_MINUS, 188aaac714fSJohn Arbuckle [kVK_ANSI_Equal] = Q_KEY_CODE_EQUAL, 189aaac714fSJohn Arbuckle [kVK_Delete] = Q_KEY_CODE_BACKSPACE, 190aaac714fSJohn Arbuckle [kVK_CapsLock] = Q_KEY_CODE_CAPS_LOCK, 191aaac714fSJohn Arbuckle [kVK_Tab] = Q_KEY_CODE_TAB, 192aaac714fSJohn Arbuckle [kVK_Return] = Q_KEY_CODE_RET, 193aaac714fSJohn Arbuckle [kVK_ANSI_LeftBracket] = Q_KEY_CODE_BRACKET_LEFT, 194aaac714fSJohn Arbuckle [kVK_ANSI_RightBracket] = Q_KEY_CODE_BRACKET_RIGHT, 195aaac714fSJohn Arbuckle [kVK_ANSI_Backslash] = Q_KEY_CODE_BACKSLASH, 196aaac714fSJohn Arbuckle [kVK_ANSI_Semicolon] = Q_KEY_CODE_SEMICOLON, 197aaac714fSJohn Arbuckle [kVK_ANSI_Quote] = Q_KEY_CODE_APOSTROPHE, 198aaac714fSJohn Arbuckle [kVK_ANSI_Comma] = Q_KEY_CODE_COMMA, 199aaac714fSJohn Arbuckle [kVK_ANSI_Period] = Q_KEY_CODE_DOT, 200aaac714fSJohn Arbuckle [kVK_ANSI_Slash] = Q_KEY_CODE_SLASH, 201aaac714fSJohn Arbuckle [kVK_Space] = Q_KEY_CODE_SPC, 202aaac714fSJohn Arbuckle 203aaac714fSJohn Arbuckle [kVK_ANSI_Keypad0] = Q_KEY_CODE_KP_0, 204aaac714fSJohn Arbuckle [kVK_ANSI_Keypad1] = Q_KEY_CODE_KP_1, 205aaac714fSJohn Arbuckle [kVK_ANSI_Keypad2] = Q_KEY_CODE_KP_2, 206aaac714fSJohn Arbuckle [kVK_ANSI_Keypad3] = Q_KEY_CODE_KP_3, 207aaac714fSJohn Arbuckle [kVK_ANSI_Keypad4] = Q_KEY_CODE_KP_4, 208aaac714fSJohn Arbuckle [kVK_ANSI_Keypad5] = Q_KEY_CODE_KP_5, 209aaac714fSJohn Arbuckle [kVK_ANSI_Keypad6] = Q_KEY_CODE_KP_6, 210aaac714fSJohn Arbuckle [kVK_ANSI_Keypad7] = Q_KEY_CODE_KP_7, 211aaac714fSJohn Arbuckle [kVK_ANSI_Keypad8] = Q_KEY_CODE_KP_8, 212aaac714fSJohn Arbuckle [kVK_ANSI_Keypad9] = Q_KEY_CODE_KP_9, 213aaac714fSJohn Arbuckle [kVK_ANSI_KeypadDecimal] = Q_KEY_CODE_KP_DECIMAL, 214aaac714fSJohn Arbuckle [kVK_ANSI_KeypadEnter] = Q_KEY_CODE_KP_ENTER, 215aaac714fSJohn Arbuckle [kVK_ANSI_KeypadPlus] = Q_KEY_CODE_KP_ADD, 216aaac714fSJohn Arbuckle [kVK_ANSI_KeypadMinus] = Q_KEY_CODE_KP_SUBTRACT, 217aaac714fSJohn Arbuckle [kVK_ANSI_KeypadMultiply] = Q_KEY_CODE_KP_MULTIPLY, 218aaac714fSJohn Arbuckle [kVK_ANSI_KeypadDivide] = Q_KEY_CODE_KP_DIVIDE, 219aaac714fSJohn Arbuckle [kVK_ANSI_KeypadEquals] = Q_KEY_CODE_KP_EQUALS, 220aaac714fSJohn Arbuckle [kVK_ANSI_KeypadClear] = Q_KEY_CODE_NUM_LOCK, 221aaac714fSJohn Arbuckle 222aaac714fSJohn Arbuckle [kVK_UpArrow] = Q_KEY_CODE_UP, 223aaac714fSJohn Arbuckle [kVK_DownArrow] = Q_KEY_CODE_DOWN, 224aaac714fSJohn Arbuckle [kVK_LeftArrow] = Q_KEY_CODE_LEFT, 225aaac714fSJohn Arbuckle [kVK_RightArrow] = Q_KEY_CODE_RIGHT, 226aaac714fSJohn Arbuckle 227aaac714fSJohn Arbuckle [kVK_Help] = Q_KEY_CODE_INSERT, 228aaac714fSJohn Arbuckle [kVK_Home] = Q_KEY_CODE_HOME, 229aaac714fSJohn Arbuckle [kVK_PageUp] = Q_KEY_CODE_PGUP, 230aaac714fSJohn Arbuckle [kVK_PageDown] = Q_KEY_CODE_PGDN, 231aaac714fSJohn Arbuckle [kVK_End] = Q_KEY_CODE_END, 232aaac714fSJohn Arbuckle [kVK_ForwardDelete] = Q_KEY_CODE_DELETE, 233aaac714fSJohn Arbuckle 234aaac714fSJohn Arbuckle [kVK_Escape] = Q_KEY_CODE_ESC, 235aaac714fSJohn Arbuckle 236aaac714fSJohn Arbuckle /* The Power key can't be used directly because the operating system uses 237aaac714fSJohn Arbuckle * it. This key can be emulated by using it in place of another key such as 238aaac714fSJohn Arbuckle * F1. Don't forget to disable the real key binding. 239aaac714fSJohn Arbuckle */ 240aaac714fSJohn Arbuckle /* [kVK_F1] = Q_KEY_CODE_POWER, */ 241aaac714fSJohn Arbuckle 242aaac714fSJohn Arbuckle [kVK_F1] = Q_KEY_CODE_F1, 243aaac714fSJohn Arbuckle [kVK_F2] = Q_KEY_CODE_F2, 244aaac714fSJohn Arbuckle [kVK_F3] = Q_KEY_CODE_F3, 245aaac714fSJohn Arbuckle [kVK_F4] = Q_KEY_CODE_F4, 246aaac714fSJohn Arbuckle [kVK_F5] = Q_KEY_CODE_F5, 247aaac714fSJohn Arbuckle [kVK_F6] = Q_KEY_CODE_F6, 248aaac714fSJohn Arbuckle [kVK_F7] = Q_KEY_CODE_F7, 249aaac714fSJohn Arbuckle [kVK_F8] = Q_KEY_CODE_F8, 250aaac714fSJohn Arbuckle [kVK_F9] = Q_KEY_CODE_F9, 251aaac714fSJohn Arbuckle [kVK_F10] = Q_KEY_CODE_F10, 252aaac714fSJohn Arbuckle [kVK_F11] = Q_KEY_CODE_F11, 253aaac714fSJohn Arbuckle [kVK_F12] = Q_KEY_CODE_F12, 254aaac714fSJohn Arbuckle [kVK_F13] = Q_KEY_CODE_PRINT, 255aaac714fSJohn Arbuckle [kVK_F14] = Q_KEY_CODE_SCROLL_LOCK, 256aaac714fSJohn Arbuckle [kVK_F15] = Q_KEY_CODE_PAUSE, 257aaac714fSJohn Arbuckle 258708b7255SAkihiko Odaki // JIS keyboards only 259708b7255SAkihiko Odaki [kVK_JIS_Yen] = Q_KEY_CODE_YEN, 260708b7255SAkihiko Odaki [kVK_JIS_Underscore] = Q_KEY_CODE_RO, 261708b7255SAkihiko Odaki [kVK_JIS_KeypadComma] = Q_KEY_CODE_KP_COMMA, 262708b7255SAkihiko Odaki [kVK_JIS_Eisu] = Q_KEY_CODE_MUHENKAN, 263708b7255SAkihiko Odaki [kVK_JIS_Kana] = Q_KEY_CODE_HENKAN, 264708b7255SAkihiko Odaki 2653e230dd2SCorentin Chary /* 266aaac714fSJohn Arbuckle * The eject and volume keys can't be used here because they are handled at 267aaac714fSJohn Arbuckle * a lower level than what an Application can see. 2683e230dd2SCorentin Chary */ 2693e230dd2SCorentin Chary}; 2703e230dd2SCorentin Chary 2713e230dd2SCorentin Charystatic int cocoa_keycode_to_qemu(int keycode) 2723e230dd2SCorentin Chary{ 273aaac714fSJohn Arbuckle if (ARRAY_SIZE(mac_to_qkeycode_map) <= keycode) { 2744313739aSAkihiko Odaki error_report("(cocoa) warning unknown keycode 0x%x", keycode); 2753e230dd2SCorentin Chary return 0; 2763e230dd2SCorentin Chary } 277aaac714fSJohn Arbuckle return mac_to_qkeycode_map[keycode]; 2783e230dd2SCorentin Chary} 2793e230dd2SCorentin Chary 280693a3e01SJohn Arbuckle/* Displays an alert dialog box with the specified message */ 281693a3e01SJohn Arbucklestatic void QEMU_Alert(NSString *message) 282693a3e01SJohn Arbuckle{ 283693a3e01SJohn Arbuckle NSAlert *alert; 284693a3e01SJohn Arbuckle alert = [NSAlert new]; 285693a3e01SJohn Arbuckle [alert setMessageText: message]; 286693a3e01SJohn Arbuckle [alert runModal]; 287693a3e01SJohn Arbuckle} 2883e230dd2SCorentin Chary 289693a3e01SJohn Arbuckle/* Handles any errors that happen with a device transaction */ 290693a3e01SJohn Arbucklestatic void handleAnyDeviceErrors(Error * err) 291693a3e01SJohn Arbuckle{ 292693a3e01SJohn Arbuckle if (err) { 293693a3e01SJohn Arbuckle QEMU_Alert([NSString stringWithCString: error_get_pretty(err) 294693a3e01SJohn Arbuckle encoding: NSASCIIStringEncoding]); 295693a3e01SJohn Arbuckle error_free(err); 296693a3e01SJohn Arbuckle } 297693a3e01SJohn Arbuckle} 2983e230dd2SCorentin Chary 2993e230dd2SCorentin Chary/* 3003e230dd2SCorentin Chary ------------------------------------------------------ 3013e230dd2SCorentin Chary QemuCocoaView 3023e230dd2SCorentin Chary ------------------------------------------------------ 3033e230dd2SCorentin Chary*/ 3043e230dd2SCorentin Chary@interface QemuCocoaView : NSView 3053e230dd2SCorentin Chary{ 3063e230dd2SCorentin Chary QEMUScreen screen; 3073e230dd2SCorentin Chary NSWindow *fullScreenWindow; 3083e230dd2SCorentin Chary float cx,cy,cw,ch,cdx,cdy; 3095588840fSPeter Maydell pixman_image_t *pixman_image; 3106d73bb64SAkihiko Odaki QKbdState *kbd; 31149b9bd4dSPeter Maydell BOOL isMouseGrabbed; 3123e230dd2SCorentin Chary BOOL isFullscreen; 3133e230dd2SCorentin Chary BOOL isAbsoluteEnabled; 314f844cdb9SGustavo Noronha Silva CFMachPortRef eventsTap; 3153e230dd2SCorentin Chary} 31672a3e316SPeter Maydell- (void) switchSurface:(pixman_image_t *)image; 3173e230dd2SCorentin Chary- (void) grabMouse; 3183e230dd2SCorentin Chary- (void) ungrabMouse; 3193e230dd2SCorentin Chary- (void) toggleFullScreen:(id)sender; 320f844cdb9SGustavo Noronha Silva- (void) setFullGrab:(id)sender; 3219c3a418eSJohn Arbuckle- (void) handleMonitorInput:(NSEvent *)event; 32260105d7aSPeter Maydell- (bool) handleEvent:(NSEvent *)event; 32360105d7aSPeter Maydell- (bool) handleEventLocked:(NSEvent *)event; 3243e230dd2SCorentin Chary- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled; 325f61c387eSPeter Maydell/* The state surrounding mouse grabbing is potentially confusing. 326f61c387eSPeter Maydell * isAbsoluteEnabled tracks qemu_input_is_absolute() [ie "is the emulated 327f61c387eSPeter Maydell * pointing device an absolute-position one?"], but is only updated on 328f61c387eSPeter Maydell * next refresh. 329f61c387eSPeter Maydell * isMouseGrabbed tracks whether GUI events are directed to the guest; 330f61c387eSPeter Maydell * it controls whether special keys like Cmd get sent to the guest, 331f61c387eSPeter Maydell * and whether we capture the mouse when in non-absolute mode. 332f61c387eSPeter Maydell */ 33349b9bd4dSPeter Maydell- (BOOL) isMouseGrabbed; 3343e230dd2SCorentin Chary- (BOOL) isAbsoluteEnabled; 3353e230dd2SCorentin Chary- (float) cdx; 3363e230dd2SCorentin Chary- (float) cdy; 3373e230dd2SCorentin Chary- (QEMUScreen) gscreen; 3383b178b71SJohn Arbuckle- (void) raiseAllKeys; 3393e230dd2SCorentin Chary@end 3403e230dd2SCorentin Chary 3417fee199cSAndreas FärberQemuCocoaView *cocoaView; 3427fee199cSAndreas Färber 343f844cdb9SGustavo Noronha Silvastatic CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEventRef cgEvent, void *userInfo) 344f844cdb9SGustavo Noronha Silva{ 345f844cdb9SGustavo Noronha Silva QemuCocoaView *cocoaView = userInfo; 346f844cdb9SGustavo Noronha Silva NSEvent *event = [NSEvent eventWithCGEvent:cgEvent]; 347f844cdb9SGustavo Noronha Silva if ([cocoaView isMouseGrabbed] && [cocoaView handleEvent:event]) { 348f844cdb9SGustavo Noronha Silva COCOA_DEBUG("Global events tap: qemu handled the event, capturing!\n"); 349f844cdb9SGustavo Noronha Silva return NULL; 350f844cdb9SGustavo Noronha Silva } 351f844cdb9SGustavo Noronha Silva COCOA_DEBUG("Global events tap: qemu did not handle the event, letting it through...\n"); 352f844cdb9SGustavo Noronha Silva 353f844cdb9SGustavo Noronha Silva return cgEvent; 354f844cdb9SGustavo Noronha Silva} 355f844cdb9SGustavo Noronha Silva 3563e230dd2SCorentin Chary@implementation QemuCocoaView 3573e230dd2SCorentin Chary- (id)initWithFrame:(NSRect)frameRect 3583e230dd2SCorentin Chary{ 3593e230dd2SCorentin Chary COCOA_DEBUG("QemuCocoaView: initWithFrame\n"); 3603e230dd2SCorentin Chary 3613e230dd2SCorentin Chary self = [super initWithFrame:frameRect]; 3623e230dd2SCorentin Chary if (self) { 3633e230dd2SCorentin Chary 3643e230dd2SCorentin Chary screen.width = frameRect.size.width; 3653e230dd2SCorentin Chary screen.height = frameRect.size.height; 3666d73bb64SAkihiko Odaki kbd = qkbd_state_init(dcl.con); 3673e230dd2SCorentin Chary 3683e230dd2SCorentin Chary } 3693e230dd2SCorentin Chary return self; 3703e230dd2SCorentin Chary} 3713e230dd2SCorentin Chary 3723e230dd2SCorentin Chary- (void) dealloc 3733e230dd2SCorentin Chary{ 3743e230dd2SCorentin Chary COCOA_DEBUG("QemuCocoaView: dealloc\n"); 3753e230dd2SCorentin Chary 376c0ff29d1SAkihiko Odaki if (pixman_image) { 3775588840fSPeter Maydell pixman_image_unref(pixman_image); 3785588840fSPeter Maydell } 3793e230dd2SCorentin Chary 3806d73bb64SAkihiko Odaki qkbd_state_free(kbd); 381f844cdb9SGustavo Noronha Silva 382f844cdb9SGustavo Noronha Silva if (eventsTap) { 383f844cdb9SGustavo Noronha Silva CFRelease(eventsTap); 384f844cdb9SGustavo Noronha Silva } 385f844cdb9SGustavo Noronha Silva 3863e230dd2SCorentin Chary [super dealloc]; 3873e230dd2SCorentin Chary} 3883e230dd2SCorentin Chary 3893e230dd2SCorentin Chary- (BOOL) isOpaque 3903e230dd2SCorentin Chary{ 3913e230dd2SCorentin Chary return YES; 3923e230dd2SCorentin Chary} 3933e230dd2SCorentin Chary 3945dd45beeSPeter Maydell- (BOOL) screenContainsPoint:(NSPoint) p 3955dd45beeSPeter Maydell{ 3965dd45beeSPeter Maydell return (p.x > -1 && p.x < screen.width && p.y > -1 && p.y < screen.height); 3975dd45beeSPeter Maydell} 3985dd45beeSPeter Maydell 3992044dff8SChen Zhang/* Get location of event and convert to virtual screen coordinate */ 4002044dff8SChen Zhang- (CGPoint) screenLocationOfEvent:(NSEvent *)ev 4012044dff8SChen Zhang{ 4022044dff8SChen Zhang NSWindow *eventWindow = [ev window]; 4032044dff8SChen Zhang // XXX: Use CGRect and -convertRectFromScreen: to support macOS 10.10 4042044dff8SChen Zhang CGRect r = CGRectZero; 4052044dff8SChen Zhang r.origin = [ev locationInWindow]; 4062044dff8SChen Zhang if (!eventWindow) { 4072044dff8SChen Zhang if (!isFullscreen) { 4082044dff8SChen Zhang return [[self window] convertRectFromScreen:r].origin; 4092044dff8SChen Zhang } else { 4102044dff8SChen Zhang CGPoint locationInSelfWindow = [[self window] convertRectFromScreen:r].origin; 4112044dff8SChen Zhang CGPoint loc = [self convertPoint:locationInSelfWindow fromView:nil]; 4122044dff8SChen Zhang if (stretch_video) { 4132044dff8SChen Zhang loc.x /= cdx; 4142044dff8SChen Zhang loc.y /= cdy; 4152044dff8SChen Zhang } 4162044dff8SChen Zhang return loc; 4172044dff8SChen Zhang } 4182044dff8SChen Zhang } else if ([[self window] isEqual:eventWindow]) { 4192044dff8SChen Zhang if (!isFullscreen) { 4202044dff8SChen Zhang return r.origin; 4212044dff8SChen Zhang } else { 4222044dff8SChen Zhang CGPoint loc = [self convertPoint:r.origin fromView:nil]; 4232044dff8SChen Zhang if (stretch_video) { 4242044dff8SChen Zhang loc.x /= cdx; 4252044dff8SChen Zhang loc.y /= cdy; 4262044dff8SChen Zhang } 4272044dff8SChen Zhang return loc; 4282044dff8SChen Zhang } 4292044dff8SChen Zhang } else { 4302044dff8SChen Zhang return [[self window] convertRectFromScreen:[eventWindow convertRectToScreen:r]].origin; 4312044dff8SChen Zhang } 4322044dff8SChen Zhang} 4332044dff8SChen Zhang 43413aefd30SPeter Maydell- (void) hideCursor 43513aefd30SPeter Maydell{ 43613aefd30SPeter Maydell if (!cursor_hide) { 43713aefd30SPeter Maydell return; 43813aefd30SPeter Maydell } 43913aefd30SPeter Maydell [NSCursor hide]; 44013aefd30SPeter Maydell} 44113aefd30SPeter Maydell 44213aefd30SPeter Maydell- (void) unhideCursor 44313aefd30SPeter Maydell{ 44413aefd30SPeter Maydell if (!cursor_hide) { 44513aefd30SPeter Maydell return; 44613aefd30SPeter Maydell } 44713aefd30SPeter Maydell [NSCursor unhide]; 44813aefd30SPeter Maydell} 44913aefd30SPeter Maydell 4503e230dd2SCorentin Chary- (void) drawRect:(NSRect) rect 4513e230dd2SCorentin Chary{ 4523e230dd2SCorentin Chary COCOA_DEBUG("QemuCocoaView: drawRect\n"); 4533e230dd2SCorentin Chary 4543e230dd2SCorentin Chary // get CoreGraphic context 4555e24600aSBrendan Shanks CGContextRef viewContextRef = [[NSGraphicsContext currentContext] CGContext]; 4565e24600aSBrendan Shanks 4573e230dd2SCorentin Chary CGContextSetInterpolationQuality (viewContextRef, kCGInterpolationNone); 4583e230dd2SCorentin Chary CGContextSetShouldAntialias (viewContextRef, NO); 4593e230dd2SCorentin Chary 4603e230dd2SCorentin Chary // draw screen bitmap directly to Core Graphics context 461c0ff29d1SAkihiko Odaki if (!pixman_image) { 4627d270b1cSPeter Maydell // Draw request before any guest device has set up a framebuffer: 4637d270b1cSPeter Maydell // just draw an opaque black rectangle 4647d270b1cSPeter Maydell CGContextSetRGBFillColor(viewContextRef, 0, 0, 0, 1.0); 4657d270b1cSPeter Maydell CGContextFillRect(viewContextRef, NSRectToCGRect(rect)); 4667d270b1cSPeter Maydell } else { 467c0ff29d1SAkihiko Odaki int w = pixman_image_get_width(pixman_image); 468c0ff29d1SAkihiko Odaki int h = pixman_image_get_height(pixman_image); 469c0ff29d1SAkihiko Odaki int bitsPerPixel = PIXMAN_FORMAT_BPP(pixman_image_get_format(pixman_image)); 470d9c32b8fSAkihiko Odaki int stride = pixman_image_get_stride(pixman_image); 471c0ff29d1SAkihiko Odaki CGDataProviderRef dataProviderRef = CGDataProviderCreateWithData( 472c0ff29d1SAkihiko Odaki NULL, 473c0ff29d1SAkihiko Odaki pixman_image_get_data(pixman_image), 474d9c32b8fSAkihiko Odaki stride * h, 475c0ff29d1SAkihiko Odaki NULL 476c0ff29d1SAkihiko Odaki ); 4773e230dd2SCorentin Chary CGImageRef imageRef = CGImageCreate( 478c0ff29d1SAkihiko Odaki w, //width 479c0ff29d1SAkihiko Odaki h, //height 480d9c32b8fSAkihiko Odaki DIV_ROUND_UP(bitsPerPixel, 8) * 2, //bitsPerComponent 481c0ff29d1SAkihiko Odaki bitsPerPixel, //bitsPerPixel 482d9c32b8fSAkihiko Odaki stride, //bytesPerRow 483ae57d35cSAkihiko Odaki CGColorSpaceCreateWithName(kCGColorSpaceSRGB), //colorspace 484ae57d35cSAkihiko Odaki kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, //bitmapInfo 4853e230dd2SCorentin Chary dataProviderRef, //provider 4863e230dd2SCorentin Chary NULL, //decode 4873e230dd2SCorentin Chary 0, //interpolate 4883e230dd2SCorentin Chary kCGRenderingIntentDefault //intent 4893e230dd2SCorentin Chary ); 4903e230dd2SCorentin Chary // selective drawing code (draws only dirty rectangles) (OS X >= 10.4) 4913e230dd2SCorentin Chary const NSRect *rectList; 4923e230dd2SCorentin Chary NSInteger rectCount; 4933e230dd2SCorentin Chary int i; 4943e230dd2SCorentin Chary CGImageRef clipImageRef; 4953e230dd2SCorentin Chary CGRect clipRect; 4963e230dd2SCorentin Chary 4973e230dd2SCorentin Chary [self getRectsBeingDrawn:&rectList count:&rectCount]; 4983e230dd2SCorentin Chary for (i = 0; i < rectCount; i++) { 4993e230dd2SCorentin Chary clipRect.origin.x = rectList[i].origin.x / cdx; 500c0ff29d1SAkihiko Odaki clipRect.origin.y = (float)h - (rectList[i].origin.y + rectList[i].size.height) / cdy; 5013e230dd2SCorentin Chary clipRect.size.width = rectList[i].size.width / cdx; 5023e230dd2SCorentin Chary clipRect.size.height = rectList[i].size.height / cdy; 5033e230dd2SCorentin Chary clipImageRef = CGImageCreateWithImageInRect( 5043e230dd2SCorentin Chary imageRef, 5053e230dd2SCorentin Chary clipRect 5063e230dd2SCorentin Chary ); 5073e230dd2SCorentin Chary CGContextDrawImage (viewContextRef, cgrect(rectList[i]), clipImageRef); 5083e230dd2SCorentin Chary CGImageRelease (clipImageRef); 5093e230dd2SCorentin Chary } 5103e230dd2SCorentin Chary CGImageRelease (imageRef); 511c0ff29d1SAkihiko Odaki CGDataProviderRelease(dataProviderRef); 5123e230dd2SCorentin Chary } 5133e230dd2SCorentin Chary} 5143e230dd2SCorentin Chary 5153e230dd2SCorentin Chary- (void) setContentDimensions 5163e230dd2SCorentin Chary{ 5173e230dd2SCorentin Chary COCOA_DEBUG("QemuCocoaView: setContentDimensions\n"); 5183e230dd2SCorentin Chary 5193e230dd2SCorentin Chary if (isFullscreen) { 5203e230dd2SCorentin Chary cdx = [[NSScreen mainScreen] frame].size.width / (float)screen.width; 5213e230dd2SCorentin Chary cdy = [[NSScreen mainScreen] frame].size.height / (float)screen.height; 5225d1b2eefSProgrammingkid 5235d1b2eefSProgrammingkid /* stretches video, but keeps same aspect ratio */ 5245d1b2eefSProgrammingkid if (stretch_video == true) { 5255d1b2eefSProgrammingkid /* use smallest stretch value - prevents clipping on sides */ 5265d1b2eefSProgrammingkid if (MIN(cdx, cdy) == cdx) { 5275d1b2eefSProgrammingkid cdy = cdx; 5285d1b2eefSProgrammingkid } else { 5295d1b2eefSProgrammingkid cdx = cdy; 5305d1b2eefSProgrammingkid } 5315d1b2eefSProgrammingkid } else { /* No stretching */ 5325d1b2eefSProgrammingkid cdx = cdy = 1; 5335d1b2eefSProgrammingkid } 5343e230dd2SCorentin Chary cw = screen.width * cdx; 5353e230dd2SCorentin Chary ch = screen.height * cdy; 5363e230dd2SCorentin Chary cx = ([[NSScreen mainScreen] frame].size.width - cw) / 2.0; 5373e230dd2SCorentin Chary cy = ([[NSScreen mainScreen] frame].size.height - ch) / 2.0; 5383e230dd2SCorentin Chary } else { 5393e230dd2SCorentin Chary cx = 0; 5403e230dd2SCorentin Chary cy = 0; 5413e230dd2SCorentin Chary cw = screen.width; 5423e230dd2SCorentin Chary ch = screen.height; 5433e230dd2SCorentin Chary cdx = 1.0; 5443e230dd2SCorentin Chary cdy = 1.0; 5453e230dd2SCorentin Chary } 5463e230dd2SCorentin Chary} 5473e230dd2SCorentin Chary 5488d65dee2SPeter Maydell- (void) updateUIInfoLocked 54915280e85SAkihiko Odaki{ 5508d65dee2SPeter Maydell /* Must be called with the iothread lock, i.e. via updateUIInfo */ 55115280e85SAkihiko Odaki NSSize frameSize; 55215280e85SAkihiko Odaki QemuUIInfo info; 55315280e85SAkihiko Odaki 55415280e85SAkihiko Odaki if (!qemu_console_is_graphic(dcl.con)) { 55515280e85SAkihiko Odaki return; 55615280e85SAkihiko Odaki } 55715280e85SAkihiko Odaki 55815280e85SAkihiko Odaki if ([self window]) { 55915280e85SAkihiko Odaki NSDictionary *description = [[[self window] screen] deviceDescription]; 56015280e85SAkihiko Odaki CGDirectDisplayID display = [[description objectForKey:@"NSScreenNumber"] unsignedIntValue]; 56115280e85SAkihiko Odaki NSSize screenSize = [[[self window] screen] frame].size; 56215280e85SAkihiko Odaki CGSize screenPhysicalSize = CGDisplayScreenSize(display); 56315280e85SAkihiko Odaki 56415280e85SAkihiko Odaki frameSize = isFullscreen ? screenSize : [self frame].size; 56515280e85SAkihiko Odaki info.width_mm = frameSize.width / screenSize.width * screenPhysicalSize.width; 56615280e85SAkihiko Odaki info.height_mm = frameSize.height / screenSize.height * screenPhysicalSize.height; 56715280e85SAkihiko Odaki } else { 56815280e85SAkihiko Odaki frameSize = [self frame].size; 56915280e85SAkihiko Odaki info.width_mm = 0; 57015280e85SAkihiko Odaki info.height_mm = 0; 57115280e85SAkihiko Odaki } 57215280e85SAkihiko Odaki 57315280e85SAkihiko Odaki info.xoff = 0; 57415280e85SAkihiko Odaki info.yoff = 0; 57515280e85SAkihiko Odaki info.width = frameSize.width; 57615280e85SAkihiko Odaki info.height = frameSize.height; 57715280e85SAkihiko Odaki 578ca19ef52SMarc-André Lureau dpy_set_ui_info(dcl.con, &info, TRUE); 57915280e85SAkihiko Odaki} 58015280e85SAkihiko Odaki 5818d65dee2SPeter Maydell- (void) updateUIInfo 5828d65dee2SPeter Maydell{ 5838d65dee2SPeter Maydell if (!allow_events) { 5848d65dee2SPeter Maydell /* 5858d65dee2SPeter Maydell * Don't try to tell QEMU about UI information in the application 5868d65dee2SPeter Maydell * startup phase -- we haven't yet registered dcl with the QEMU UI 5878d65dee2SPeter Maydell * layer, and also trying to take the iothread lock would deadlock. 5888d65dee2SPeter Maydell * When cocoa_display_init() does register the dcl, the UI layer 5898d65dee2SPeter Maydell * will call cocoa_switch(), which will call updateUIInfo, so 5908d65dee2SPeter Maydell * we don't lose any information here. 5918d65dee2SPeter Maydell */ 5928d65dee2SPeter Maydell return; 5938d65dee2SPeter Maydell } 5948d65dee2SPeter Maydell 5958d65dee2SPeter Maydell with_iothread_lock(^{ 5968d65dee2SPeter Maydell [self updateUIInfoLocked]; 5978d65dee2SPeter Maydell }); 5988d65dee2SPeter Maydell} 5998d65dee2SPeter Maydell 60015280e85SAkihiko Odaki- (void)viewDidMoveToWindow 60115280e85SAkihiko Odaki{ 60215280e85SAkihiko Odaki [self updateUIInfo]; 60315280e85SAkihiko Odaki} 60415280e85SAkihiko Odaki 60572a3e316SPeter Maydell- (void) switchSurface:(pixman_image_t *)image 6063e230dd2SCorentin Chary{ 6075e00d3acSGerd Hoffmann COCOA_DEBUG("QemuCocoaView: switchSurface\n"); 6083e230dd2SCorentin Chary 60972a3e316SPeter Maydell int w = pixman_image_get_width(image); 61072a3e316SPeter Maydell int h = pixman_image_get_height(image); 611381600daSPeter Maydell /* cdx == 0 means this is our very first surface, in which case we need 612381600daSPeter Maydell * to recalculate the content dimensions even if it happens to be the size 613381600daSPeter Maydell * of the initial empty window. 614381600daSPeter Maydell */ 615381600daSPeter Maydell bool isResize = (w != screen.width || h != screen.height || cdx == 0.0); 616d3345a04SPeter Maydell 617d3345a04SPeter Maydell int oldh = screen.height; 618d3345a04SPeter Maydell if (isResize) { 619d3345a04SPeter Maydell // Resize before we trigger the redraw, or we'll redraw at the wrong size 620d3345a04SPeter Maydell COCOA_DEBUG("switchSurface: new size %d x %d\n", w, h); 621d3345a04SPeter Maydell screen.width = w; 622d3345a04SPeter Maydell screen.height = h; 623d3345a04SPeter Maydell [self setContentDimensions]; 624d3345a04SPeter Maydell [self setFrame:NSMakeRect(cx, cy, cw, ch)]; 625d3345a04SPeter Maydell } 6268510d91eSPeter Maydell 6273e230dd2SCorentin Chary // update screenBuffer 628c0ff29d1SAkihiko Odaki if (pixman_image) { 6295588840fSPeter Maydell pixman_image_unref(pixman_image); 6305588840fSPeter Maydell } 6313e230dd2SCorentin Chary 6325588840fSPeter Maydell pixman_image = image; 6333e230dd2SCorentin Chary 6343e230dd2SCorentin Chary // update windows 6353e230dd2SCorentin Chary if (isFullscreen) { 6363e230dd2SCorentin Chary [[fullScreenWindow contentView] setFrame:[[NSScreen mainScreen] frame]]; 637d3345a04SPeter 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]; 6383e230dd2SCorentin Chary } else { 6393e230dd2SCorentin Chary if (qemu_name) 6403e230dd2SCorentin Chary [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]]; 641d3345a04SPeter 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]; 6423e230dd2SCorentin Chary } 643d3345a04SPeter Maydell 644d3345a04SPeter Maydell if (isResize) { 6453e230dd2SCorentin Chary [normalWindow center]; 646d3345a04SPeter Maydell } 6473e230dd2SCorentin Chary} 6483e230dd2SCorentin Chary 6493e230dd2SCorentin Chary- (void) toggleFullScreen:(id)sender 6503e230dd2SCorentin Chary{ 6513e230dd2SCorentin Chary COCOA_DEBUG("QemuCocoaView: toggleFullScreen\n"); 6523e230dd2SCorentin Chary 6533e230dd2SCorentin Chary if (isFullscreen) { // switch from fullscreen to desktop 6543e230dd2SCorentin Chary isFullscreen = FALSE; 6553e230dd2SCorentin Chary [self ungrabMouse]; 6563e230dd2SCorentin Chary [self setContentDimensions]; 6573e230dd2SCorentin Chary [fullScreenWindow close]; 6583e230dd2SCorentin Chary [normalWindow setContentView: self]; 6593e230dd2SCorentin Chary [normalWindow makeKeyAndOrderFront: self]; 6603e230dd2SCorentin Chary [NSMenu setMenuBarVisible:YES]; 6613e230dd2SCorentin Chary } else { // switch from desktop to fullscreen 6623e230dd2SCorentin Chary isFullscreen = TRUE; 6635d1b2eefSProgrammingkid [normalWindow orderOut: nil]; /* Hide the window */ 6643e230dd2SCorentin Chary [self grabMouse]; 6653e230dd2SCorentin Chary [self setContentDimensions]; 6663e230dd2SCorentin Chary [NSMenu setMenuBarVisible:NO]; 6673e230dd2SCorentin Chary fullScreenWindow = [[NSWindow alloc] initWithContentRect:[[NSScreen mainScreen] frame] 6684ba967adSBrendan Shanks styleMask:NSWindowStyleMaskBorderless 6693e230dd2SCorentin Chary backing:NSBackingStoreBuffered 6703e230dd2SCorentin Chary defer:NO]; 6715d1b2eefSProgrammingkid [fullScreenWindow setAcceptsMouseMovedEvents: YES]; 6723e230dd2SCorentin Chary [fullScreenWindow setHasShadow:NO]; 6735d1b2eefSProgrammingkid [fullScreenWindow setBackgroundColor: [NSColor blackColor]]; 6745d1b2eefSProgrammingkid [self setFrame:NSMakeRect(cx, cy, cw, ch)]; 6755d1b2eefSProgrammingkid [[fullScreenWindow contentView] addSubview: self]; 6763e230dd2SCorentin Chary [fullScreenWindow makeKeyAndOrderFront:self]; 6773e230dd2SCorentin Chary } 6783e230dd2SCorentin Chary} 6793e230dd2SCorentin Chary 680f844cdb9SGustavo Noronha Silva- (void) setFullGrab:(id)sender 681f844cdb9SGustavo Noronha Silva{ 682f844cdb9SGustavo Noronha Silva COCOA_DEBUG("QemuCocoaView: setFullGrab\n"); 683f844cdb9SGustavo Noronha Silva 684f844cdb9SGustavo Noronha Silva CGEventMask mask = CGEventMaskBit(kCGEventKeyDown) | CGEventMaskBit(kCGEventKeyUp) | CGEventMaskBit(kCGEventFlagsChanged); 685f844cdb9SGustavo Noronha Silva eventsTap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, 686f844cdb9SGustavo Noronha Silva mask, handleTapEvent, self); 687f844cdb9SGustavo Noronha Silva if (!eventsTap) { 688f844cdb9SGustavo Noronha Silva warn_report("Could not create event tap, system key combos will not be captured.\n"); 689f844cdb9SGustavo Noronha Silva return; 690f844cdb9SGustavo Noronha Silva } else { 691f844cdb9SGustavo Noronha Silva COCOA_DEBUG("Global events tap created! Will capture system key combos.\n"); 692f844cdb9SGustavo Noronha Silva } 693f844cdb9SGustavo Noronha Silva 694f844cdb9SGustavo Noronha Silva CFRunLoopRef runLoop = CFRunLoopGetCurrent(); 695f844cdb9SGustavo Noronha Silva if (!runLoop) { 696f844cdb9SGustavo Noronha Silva warn_report("Could not obtain current CF RunLoop, system key combos will not be captured.\n"); 697f844cdb9SGustavo Noronha Silva return; 698f844cdb9SGustavo Noronha Silva } 699f844cdb9SGustavo Noronha Silva 700f844cdb9SGustavo Noronha Silva CFRunLoopSourceRef tapEventsSrc = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventsTap, 0); 701f844cdb9SGustavo Noronha Silva if (!tapEventsSrc ) { 702f844cdb9SGustavo Noronha Silva warn_report("Could not obtain current CF RunLoop, system key combos will not be captured.\n"); 703f844cdb9SGustavo Noronha Silva return; 704f844cdb9SGustavo Noronha Silva } 705f844cdb9SGustavo Noronha Silva 706f844cdb9SGustavo Noronha Silva CFRunLoopAddSource(runLoop, tapEventsSrc, kCFRunLoopDefaultMode); 707f844cdb9SGustavo Noronha Silva CFRelease(tapEventsSrc); 708f844cdb9SGustavo Noronha Silva} 709f844cdb9SGustavo Noronha Silva 7106d73bb64SAkihiko Odaki- (void) toggleKey: (int)keycode { 7116d73bb64SAkihiko Odaki qkbd_state_key_event(kbd, keycode, !qkbd_state_key_get(kbd, keycode)); 712af8862b2SIan McKellar via Qemu-devel} 713af8862b2SIan McKellar via Qemu-devel 7149c3a418eSJohn Arbuckle// Does the work of sending input to the monitor 7159c3a418eSJohn Arbuckle- (void) handleMonitorInput:(NSEvent *)event 7169c3a418eSJohn Arbuckle{ 7179c3a418eSJohn Arbuckle int keysym = 0; 7189c3a418eSJohn Arbuckle int control_key = 0; 7199c3a418eSJohn Arbuckle 7209c3a418eSJohn Arbuckle // if the control key is down 7219c3a418eSJohn Arbuckle if ([event modifierFlags] & NSEventModifierFlagControl) { 7229c3a418eSJohn Arbuckle control_key = 1; 7239c3a418eSJohn Arbuckle } 7249c3a418eSJohn Arbuckle 7259c3a418eSJohn Arbuckle /* translates Macintosh keycodes to QEMU's keysym */ 7269c3a418eSJohn Arbuckle 7279459262dSPhilippe Mathieu-Daudé static const int without_control_translation[] = { 7289c3a418eSJohn Arbuckle [0 ... 0xff] = 0, // invalid key 7299c3a418eSJohn Arbuckle 7309c3a418eSJohn Arbuckle [kVK_UpArrow] = QEMU_KEY_UP, 7319c3a418eSJohn Arbuckle [kVK_DownArrow] = QEMU_KEY_DOWN, 7329c3a418eSJohn Arbuckle [kVK_RightArrow] = QEMU_KEY_RIGHT, 7339c3a418eSJohn Arbuckle [kVK_LeftArrow] = QEMU_KEY_LEFT, 7349c3a418eSJohn Arbuckle [kVK_Home] = QEMU_KEY_HOME, 7359c3a418eSJohn Arbuckle [kVK_End] = QEMU_KEY_END, 7369c3a418eSJohn Arbuckle [kVK_PageUp] = QEMU_KEY_PAGEUP, 7379c3a418eSJohn Arbuckle [kVK_PageDown] = QEMU_KEY_PAGEDOWN, 7389c3a418eSJohn Arbuckle [kVK_ForwardDelete] = QEMU_KEY_DELETE, 7399c3a418eSJohn Arbuckle [kVK_Delete] = QEMU_KEY_BACKSPACE, 7409c3a418eSJohn Arbuckle }; 7419c3a418eSJohn Arbuckle 7429459262dSPhilippe Mathieu-Daudé static const int with_control_translation[] = { 7439c3a418eSJohn Arbuckle [0 ... 0xff] = 0, // invalid key 7449c3a418eSJohn Arbuckle 7459c3a418eSJohn Arbuckle [kVK_UpArrow] = QEMU_KEY_CTRL_UP, 7469c3a418eSJohn Arbuckle [kVK_DownArrow] = QEMU_KEY_CTRL_DOWN, 7479c3a418eSJohn Arbuckle [kVK_RightArrow] = QEMU_KEY_CTRL_RIGHT, 7489c3a418eSJohn Arbuckle [kVK_LeftArrow] = QEMU_KEY_CTRL_LEFT, 7499c3a418eSJohn Arbuckle [kVK_Home] = QEMU_KEY_CTRL_HOME, 7509c3a418eSJohn Arbuckle [kVK_End] = QEMU_KEY_CTRL_END, 7519c3a418eSJohn Arbuckle [kVK_PageUp] = QEMU_KEY_CTRL_PAGEUP, 7529c3a418eSJohn Arbuckle [kVK_PageDown] = QEMU_KEY_CTRL_PAGEDOWN, 7539c3a418eSJohn Arbuckle }; 7549c3a418eSJohn Arbuckle 7559c3a418eSJohn Arbuckle if (control_key != 0) { /* If the control key is being used */ 7569c3a418eSJohn Arbuckle if ([event keyCode] < ARRAY_SIZE(with_control_translation)) { 7579c3a418eSJohn Arbuckle keysym = with_control_translation[[event keyCode]]; 7589c3a418eSJohn Arbuckle } 7599c3a418eSJohn Arbuckle } else { 7609c3a418eSJohn Arbuckle if ([event keyCode] < ARRAY_SIZE(without_control_translation)) { 7619c3a418eSJohn Arbuckle keysym = without_control_translation[[event keyCode]]; 7629c3a418eSJohn Arbuckle } 7639c3a418eSJohn Arbuckle } 7649c3a418eSJohn Arbuckle 7659c3a418eSJohn Arbuckle // if not a key that needs translating 7669c3a418eSJohn Arbuckle if (keysym == 0) { 7679c3a418eSJohn Arbuckle NSString *ks = [event characters]; 7689c3a418eSJohn Arbuckle if ([ks length] > 0) { 7699c3a418eSJohn Arbuckle keysym = [ks characterAtIndex:0]; 7709c3a418eSJohn Arbuckle } 7719c3a418eSJohn Arbuckle } 7729c3a418eSJohn Arbuckle 7739c3a418eSJohn Arbuckle if (keysym) { 7749c3a418eSJohn Arbuckle kbd_put_keysym(keysym); 7759c3a418eSJohn Arbuckle } 7769c3a418eSJohn Arbuckle} 7779c3a418eSJohn Arbuckle 77860105d7aSPeter Maydell- (bool) handleEvent:(NSEvent *)event 7793e230dd2SCorentin Chary{ 780dff742adSHikaru Nishida if(!allow_events) { 781dff742adSHikaru Nishida /* 782dff742adSHikaru Nishida * Just let OSX have all events that arrive before 783dff742adSHikaru Nishida * applicationDidFinishLaunching. 784dff742adSHikaru Nishida * This avoids a deadlock on the iothread lock, which cocoa_display_init() 785dff742adSHikaru Nishida * will not drop until after the app_started_sem is posted. (In theory 786dff742adSHikaru Nishida * there should not be any such events, but OSX Catalina now emits some.) 787dff742adSHikaru Nishida */ 788dff742adSHikaru Nishida return false; 789dff742adSHikaru Nishida } 79060105d7aSPeter Maydell return bool_with_iothread_lock(^{ 79160105d7aSPeter Maydell return [self handleEventLocked:event]; 79231819e95SPeter Maydell }); 79331819e95SPeter Maydell} 7943e230dd2SCorentin Chary 79560105d7aSPeter Maydell- (bool) handleEventLocked:(NSEvent *)event 79631819e95SPeter Maydell{ 79760105d7aSPeter Maydell /* Return true if we handled the event, false if it should be given to OSX */ 79831819e95SPeter Maydell COCOA_DEBUG("QemuCocoaView: handleEvent\n"); 7993e230dd2SCorentin Chary int buttons = 0; 800af8862b2SIan McKellar via Qemu-devel int keycode = 0; 80121bae11aSGerd Hoffmann bool mouse_event = false; 8020c6c4395SJohn Arbuckle static bool switched_to_fullscreen = false; 8032044dff8SChen Zhang // Location of event in virtual screen coordinates 8042044dff8SChen Zhang NSPoint p = [self screenLocationOfEvent:event]; 8056d73bb64SAkihiko Odaki NSUInteger modifiers = [event modifierFlags]; 8066d73bb64SAkihiko Odaki 807ad7f2f8eSAkihiko Odaki /* 808ad7f2f8eSAkihiko Odaki * Check -[NSEvent modifierFlags] here. 809ad7f2f8eSAkihiko Odaki * 810ad7f2f8eSAkihiko Odaki * There is a NSEventType for an event notifying the change of 811ad7f2f8eSAkihiko Odaki * -[NSEvent modifierFlags], NSEventTypeFlagsChanged but these operations 812ad7f2f8eSAkihiko Odaki * are performed for any events because a modifier state may change while 813ad7f2f8eSAkihiko Odaki * the application is inactive (i.e. no events fire) and we don't want to 814ad7f2f8eSAkihiko Odaki * wait for another modifier state change to detect such a change. 815ad7f2f8eSAkihiko Odaki * 816ad7f2f8eSAkihiko Odaki * NSEventModifierFlagCapsLock requires a special treatment. The other flags 817ad7f2f8eSAkihiko Odaki * are handled in similar manners. 818ad7f2f8eSAkihiko Odaki * 819ad7f2f8eSAkihiko Odaki * NSEventModifierFlagCapsLock 820ad7f2f8eSAkihiko Odaki * --------------------------- 821ad7f2f8eSAkihiko Odaki * 822ad7f2f8eSAkihiko Odaki * If CapsLock state is changed, "up" and "down" events will be fired in 823ad7f2f8eSAkihiko Odaki * sequence, effectively updates CapsLock state on the guest. 824ad7f2f8eSAkihiko Odaki * 825ad7f2f8eSAkihiko Odaki * The other flags 826ad7f2f8eSAkihiko Odaki * --------------- 827ad7f2f8eSAkihiko Odaki * 828ad7f2f8eSAkihiko Odaki * If a flag is not set, fire "up" events for all keys which correspond to 829ad7f2f8eSAkihiko Odaki * the flag. Note that "down" events are not fired here because the flags 830ad7f2f8eSAkihiko Odaki * checked here do not tell what exact keys are down. 831ad7f2f8eSAkihiko Odaki * 832ad7f2f8eSAkihiko Odaki * If one of the keys corresponding to a flag is down, we rely on 833ad7f2f8eSAkihiko Odaki * -[NSEvent keyCode] of an event whose -[NSEvent type] is 834ad7f2f8eSAkihiko Odaki * NSEventTypeFlagsChanged to know the exact key which is down, which has 835ad7f2f8eSAkihiko Odaki * the following two downsides: 836ad7f2f8eSAkihiko Odaki * - It does not work when the application is inactive as described above. 837ad7f2f8eSAkihiko Odaki * - It malfactions *after* the modifier state is changed while the 838ad7f2f8eSAkihiko Odaki * application is inactive. It is because -[NSEvent keyCode] does not tell 839ad7f2f8eSAkihiko Odaki * if the key is up or down, and requires to infer the current state from 840ad7f2f8eSAkihiko Odaki * the previous state. It is still possible to fix such a malfanction by 841ad7f2f8eSAkihiko Odaki * completely leaving your hands from the keyboard, which hopefully makes 842ad7f2f8eSAkihiko Odaki * this implementation usable enough. 843ad7f2f8eSAkihiko Odaki */ 8446d73bb64SAkihiko Odaki if (!!(modifiers & NSEventModifierFlagCapsLock) != 8456d73bb64SAkihiko Odaki qkbd_state_modifier_get(kbd, QKBD_MOD_CAPSLOCK)) { 8466d73bb64SAkihiko Odaki qkbd_state_key_event(kbd, Q_KEY_CODE_CAPS_LOCK, true); 8476d73bb64SAkihiko Odaki qkbd_state_key_event(kbd, Q_KEY_CODE_CAPS_LOCK, false); 8486d73bb64SAkihiko Odaki } 8496d73bb64SAkihiko Odaki 8506d73bb64SAkihiko Odaki if (!(modifiers & NSEventModifierFlagShift)) { 8516d73bb64SAkihiko Odaki qkbd_state_key_event(kbd, Q_KEY_CODE_SHIFT, false); 8526d73bb64SAkihiko Odaki qkbd_state_key_event(kbd, Q_KEY_CODE_SHIFT_R, false); 8536d73bb64SAkihiko Odaki } 8546d73bb64SAkihiko Odaki if (!(modifiers & NSEventModifierFlagControl)) { 8556d73bb64SAkihiko Odaki qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL, false); 8566d73bb64SAkihiko Odaki qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL_R, false); 8576d73bb64SAkihiko Odaki } 8586d73bb64SAkihiko Odaki if (!(modifiers & NSEventModifierFlagOption)) { 8594797adceSGustavo Noronha Silva if (swap_opt_cmd) { 8604797adceSGustavo Noronha Silva qkbd_state_key_event(kbd, Q_KEY_CODE_META_L, false); 8614797adceSGustavo Noronha Silva qkbd_state_key_event(kbd, Q_KEY_CODE_META_R, false); 8624797adceSGustavo Noronha Silva } else { 8636d73bb64SAkihiko Odaki qkbd_state_key_event(kbd, Q_KEY_CODE_ALT, false); 8646d73bb64SAkihiko Odaki qkbd_state_key_event(kbd, Q_KEY_CODE_ALT_R, false); 8656d73bb64SAkihiko Odaki } 8664797adceSGustavo Noronha Silva } 8676d73bb64SAkihiko Odaki if (!(modifiers & NSEventModifierFlagCommand)) { 8684797adceSGustavo Noronha Silva if (swap_opt_cmd) { 8694797adceSGustavo Noronha Silva qkbd_state_key_event(kbd, Q_KEY_CODE_ALT, false); 8704797adceSGustavo Noronha Silva qkbd_state_key_event(kbd, Q_KEY_CODE_ALT_R, false); 8714797adceSGustavo Noronha Silva } else { 8726d73bb64SAkihiko Odaki qkbd_state_key_event(kbd, Q_KEY_CODE_META_L, false); 8736d73bb64SAkihiko Odaki qkbd_state_key_event(kbd, Q_KEY_CODE_META_R, false); 8746d73bb64SAkihiko Odaki } 8754797adceSGustavo Noronha Silva } 8763e230dd2SCorentin Chary 8773e230dd2SCorentin Chary switch ([event type]) { 8784ba967adSBrendan Shanks case NSEventTypeFlagsChanged: 8796d73bb64SAkihiko Odaki switch ([event keyCode]) { 8806d73bb64SAkihiko Odaki case kVK_Shift: 8816d73bb64SAkihiko Odaki if (!!(modifiers & NSEventModifierFlagShift)) { 8826d73bb64SAkihiko Odaki [self toggleKey:Q_KEY_CODE_SHIFT]; 8836d73bb64SAkihiko Odaki } 8846d73bb64SAkihiko Odaki break; 885af8862b2SIan McKellar via Qemu-devel 8866d73bb64SAkihiko Odaki case kVK_RightShift: 8876d73bb64SAkihiko Odaki if (!!(modifiers & NSEventModifierFlagShift)) { 8886d73bb64SAkihiko Odaki [self toggleKey:Q_KEY_CODE_SHIFT_R]; 8896d73bb64SAkihiko Odaki } 8906d73bb64SAkihiko Odaki break; 891af8862b2SIan McKellar via Qemu-devel 8926d73bb64SAkihiko Odaki case kVK_Control: 8936d73bb64SAkihiko Odaki if (!!(modifiers & NSEventModifierFlagControl)) { 8946d73bb64SAkihiko Odaki [self toggleKey:Q_KEY_CODE_CTRL]; 895af8862b2SIan McKellar via Qemu-devel } 8966d73bb64SAkihiko Odaki break; 8978895919aSPeter Maydell 8986d73bb64SAkihiko Odaki case kVK_RightControl: 8996d73bb64SAkihiko Odaki if (!!(modifiers & NSEventModifierFlagControl)) { 9006d73bb64SAkihiko Odaki [self toggleKey:Q_KEY_CODE_CTRL_R]; 9016d73bb64SAkihiko Odaki } 9026d73bb64SAkihiko Odaki break; 9036d73bb64SAkihiko Odaki 9046d73bb64SAkihiko Odaki case kVK_Option: 9056d73bb64SAkihiko Odaki if (!!(modifiers & NSEventModifierFlagOption)) { 9064797adceSGustavo Noronha Silva if (swap_opt_cmd) { 9074797adceSGustavo Noronha Silva [self toggleKey:Q_KEY_CODE_META_L]; 9084797adceSGustavo Noronha Silva } else { 9096d73bb64SAkihiko Odaki [self toggleKey:Q_KEY_CODE_ALT]; 9106d73bb64SAkihiko Odaki } 9114797adceSGustavo Noronha Silva } 9126d73bb64SAkihiko Odaki break; 9136d73bb64SAkihiko Odaki 9146d73bb64SAkihiko Odaki case kVK_RightOption: 9156d73bb64SAkihiko Odaki if (!!(modifiers & NSEventModifierFlagOption)) { 9164797adceSGustavo Noronha Silva if (swap_opt_cmd) { 9174797adceSGustavo Noronha Silva [self toggleKey:Q_KEY_CODE_META_R]; 9184797adceSGustavo Noronha Silva } else { 9196d73bb64SAkihiko Odaki [self toggleKey:Q_KEY_CODE_ALT_R]; 9206d73bb64SAkihiko Odaki } 9214797adceSGustavo Noronha Silva } 9226d73bb64SAkihiko Odaki break; 9236d73bb64SAkihiko Odaki 9248895919aSPeter Maydell /* Don't pass command key changes to guest unless mouse is grabbed */ 9256d73bb64SAkihiko Odaki case kVK_Command: 9266d73bb64SAkihiko Odaki if (isMouseGrabbed && 927d6b6dea7SAkihiko Odaki !!(modifiers & NSEventModifierFlagCommand) && 928d6b6dea7SAkihiko Odaki left_command_key_enabled) { 9294797adceSGustavo Noronha Silva if (swap_opt_cmd) { 9304797adceSGustavo Noronha Silva [self toggleKey:Q_KEY_CODE_ALT]; 9314797adceSGustavo Noronha Silva } else { 9326d73bb64SAkihiko Odaki [self toggleKey:Q_KEY_CODE_META_L]; 9338895919aSPeter Maydell } 9344797adceSGustavo Noronha Silva } 9356d73bb64SAkihiko Odaki break; 9368895919aSPeter Maydell 9376d73bb64SAkihiko Odaki case kVK_RightCommand: 9386d73bb64SAkihiko Odaki if (isMouseGrabbed && 9396d73bb64SAkihiko Odaki !!(modifiers & NSEventModifierFlagCommand)) { 9404797adceSGustavo Noronha Silva if (swap_opt_cmd) { 9414797adceSGustavo Noronha Silva [self toggleKey:Q_KEY_CODE_ALT_R]; 9424797adceSGustavo Noronha Silva } else { 9436d73bb64SAkihiko Odaki [self toggleKey:Q_KEY_CODE_META_R]; 9443e230dd2SCorentin Chary } 9454797adceSGustavo Noronha Silva } 9466d73bb64SAkihiko Odaki break; 9473e230dd2SCorentin Chary } 9483e230dd2SCorentin Chary break; 9494ba967adSBrendan Shanks case NSEventTypeKeyDown: 9508895919aSPeter Maydell keycode = cocoa_keycode_to_qemu([event keyCode]); 9513e230dd2SCorentin Chary 9528895919aSPeter Maydell // forward command key combos to the host UI unless the mouse is grabbed 9534ba967adSBrendan Shanks if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) { 9540c6c4395SJohn Arbuckle /* 9550c6c4395SJohn Arbuckle * Prevent the command key from being stuck down in the guest 9560c6c4395SJohn Arbuckle * when using Command-F to switch to full screen mode. 9570c6c4395SJohn Arbuckle */ 9580c6c4395SJohn Arbuckle if (keycode == Q_KEY_CODE_F) { 9590c6c4395SJohn Arbuckle switched_to_fullscreen = true; 9600c6c4395SJohn Arbuckle } 96160105d7aSPeter Maydell return false; 9623e230dd2SCorentin Chary } 9633e230dd2SCorentin Chary 9643e230dd2SCorentin Chary // default 9653e230dd2SCorentin Chary 9665929e36cSJohn Arbuckle // handle control + alt Key Combos (ctrl+alt+[1..9,g] is reserved for QEMU) 9674ba967adSBrendan Shanks if (([event modifierFlags] & NSEventModifierFlagControl) && ([event modifierFlags] & NSEventModifierFlagOption)) { 9685929e36cSJohn Arbuckle NSString *keychar = [event charactersIgnoringModifiers]; 9695929e36cSJohn Arbuckle if ([keychar length] == 1) { 9705929e36cSJohn Arbuckle char key = [keychar characterAtIndex:0]; 9715929e36cSJohn Arbuckle switch (key) { 9723e230dd2SCorentin Chary 9733e230dd2SCorentin Chary // enable graphic console 9745929e36cSJohn Arbuckle case '1' ... '9': 9755929e36cSJohn Arbuckle console_select(key - '0' - 1); /* ascii math */ 97660105d7aSPeter Maydell return true; 9775929e36cSJohn Arbuckle 9785929e36cSJohn Arbuckle // release the mouse grab 9795929e36cSJohn Arbuckle case 'g': 9805929e36cSJohn Arbuckle [self ungrabMouse]; 98160105d7aSPeter Maydell return true; 9825929e36cSJohn Arbuckle } 9833e230dd2SCorentin Chary } 984ef2088f9SPeter Maydell } 9853e230dd2SCorentin Chary 986ef2088f9SPeter Maydell if (qemu_console_is_graphic(NULL)) { 9876d73bb64SAkihiko Odaki qkbd_state_key_event(kbd, keycode, true); 9883e230dd2SCorentin Chary } else { 9899c3a418eSJohn Arbuckle [self handleMonitorInput: event]; 9903e230dd2SCorentin Chary } 9913e230dd2SCorentin Chary break; 9924ba967adSBrendan Shanks case NSEventTypeKeyUp: 9933e230dd2SCorentin Chary keycode = cocoa_keycode_to_qemu([event keyCode]); 9948895919aSPeter Maydell 9958895919aSPeter Maydell // don't pass the guest a spurious key-up if we treated this 9968895919aSPeter Maydell // command-key combo as a host UI action 9974ba967adSBrendan Shanks if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) { 99860105d7aSPeter Maydell return true; 9998895919aSPeter Maydell } 10008895919aSPeter Maydell 100168c0aa6eSPeter Maydell if (qemu_console_is_graphic(NULL)) { 10026d73bb64SAkihiko Odaki qkbd_state_key_event(kbd, keycode, false); 10033e230dd2SCorentin Chary } 10043e230dd2SCorentin Chary break; 10054ba967adSBrendan Shanks case NSEventTypeMouseMoved: 10063e230dd2SCorentin Chary if (isAbsoluteEnabled) { 10072044dff8SChen Zhang // Cursor re-entered into a window might generate events bound to screen coordinates 10082044dff8SChen Zhang // and `nil` window property, and in full screen mode, current window might not be 10092044dff8SChen Zhang // key window, where event location alone should suffice. 10102044dff8SChen Zhang if (![self screenContainsPoint:p] || !([[self window] isKeyWindow] || isFullscreen)) { 1011f61c387eSPeter Maydell if (isMouseGrabbed) { 1012f61c387eSPeter Maydell [self ungrabMouse]; 10133e230dd2SCorentin Chary } 10143e230dd2SCorentin Chary } else { 1015f61c387eSPeter Maydell if (!isMouseGrabbed) { 1016f61c387eSPeter Maydell [self grabMouse]; 10173e230dd2SCorentin Chary } 10183e230dd2SCorentin Chary } 10193e230dd2SCorentin Chary } 102021bae11aSGerd Hoffmann mouse_event = true; 10213e230dd2SCorentin Chary break; 10224ba967adSBrendan Shanks case NSEventTypeLeftMouseDown: 10233e230dd2SCorentin Chary buttons |= MOUSE_EVENT_LBUTTON; 102421bae11aSGerd Hoffmann mouse_event = true; 10253e230dd2SCorentin Chary break; 10264ba967adSBrendan Shanks case NSEventTypeRightMouseDown: 10273e230dd2SCorentin Chary buttons |= MOUSE_EVENT_RBUTTON; 102821bae11aSGerd Hoffmann mouse_event = true; 10293e230dd2SCorentin Chary break; 10304ba967adSBrendan Shanks case NSEventTypeOtherMouseDown: 10313e230dd2SCorentin Chary buttons |= MOUSE_EVENT_MBUTTON; 103221bae11aSGerd Hoffmann mouse_event = true; 10333e230dd2SCorentin Chary break; 10344ba967adSBrendan Shanks case NSEventTypeLeftMouseDragged: 10353e230dd2SCorentin Chary buttons |= MOUSE_EVENT_LBUTTON; 103621bae11aSGerd Hoffmann mouse_event = true; 10373e230dd2SCorentin Chary break; 10384ba967adSBrendan Shanks case NSEventTypeRightMouseDragged: 10393e230dd2SCorentin Chary buttons |= MOUSE_EVENT_RBUTTON; 104021bae11aSGerd Hoffmann mouse_event = true; 10413e230dd2SCorentin Chary break; 10424ba967adSBrendan Shanks case NSEventTypeOtherMouseDragged: 10433e230dd2SCorentin Chary buttons |= MOUSE_EVENT_MBUTTON; 104421bae11aSGerd Hoffmann mouse_event = true; 10453e230dd2SCorentin Chary break; 10464ba967adSBrendan Shanks case NSEventTypeLeftMouseUp: 104721bae11aSGerd Hoffmann mouse_event = true; 1048f61c387eSPeter Maydell if (!isMouseGrabbed && [self screenContainsPoint:p]) { 10498e23e34dSChen Zhang /* 10508e23e34dSChen Zhang * In fullscreen mode, the window of cocoaView may not be the 10518e23e34dSChen Zhang * key window, therefore the position relative to the virtual 10528e23e34dSChen Zhang * screen alone will be sufficient. 10538e23e34dSChen Zhang */ 10548e23e34dSChen Zhang if(isFullscreen || [[self window] isKeyWindow]) { 10553e230dd2SCorentin Chary [self grabMouse]; 10563e230dd2SCorentin Chary } 10579e8204b1SProgrammingkid } 10583e230dd2SCorentin Chary break; 10594ba967adSBrendan Shanks case NSEventTypeRightMouseUp: 106021bae11aSGerd Hoffmann mouse_event = true; 10613e230dd2SCorentin Chary break; 10624ba967adSBrendan Shanks case NSEventTypeOtherMouseUp: 106321bae11aSGerd Hoffmann mouse_event = true; 10643e230dd2SCorentin Chary break; 10654ba967adSBrendan Shanks case NSEventTypeScrollWheel: 1066ae7313e7SJohn Arbuckle /* 1067ae7313e7SJohn Arbuckle * Send wheel events to the guest regardless of window focus. 1068ae7313e7SJohn Arbuckle * This is in-line with standard Mac OS X UI behaviour. 1069ae7313e7SJohn Arbuckle */ 1070ae7313e7SJohn Arbuckle 1071dc3c89d6SJohn Arbuckle /* 1072d70a5de4SDmitry Petrov * We shouldn't have got a scroll event when deltaY and delta Y 1073d70a5de4SDmitry Petrov * are zero, hence no harm in dropping the event 1074dc3c89d6SJohn Arbuckle */ 1075d70a5de4SDmitry Petrov if ([event deltaY] != 0 || [event deltaX] != 0) { 1076ae7313e7SJohn Arbuckle /* Determine if this is a scroll up or scroll down event */ 1077d70a5de4SDmitry Petrov if ([event deltaY] != 0) { 1078dc3c89d6SJohn Arbuckle buttons = ([event deltaY] > 0) ? 1079ae7313e7SJohn Arbuckle INPUT_BUTTON_WHEEL_UP : INPUT_BUTTON_WHEEL_DOWN; 1080d70a5de4SDmitry Petrov } else if ([event deltaX] != 0) { 1081d70a5de4SDmitry Petrov buttons = ([event deltaX] > 0) ? 1082d70a5de4SDmitry Petrov INPUT_BUTTON_WHEEL_LEFT : INPUT_BUTTON_WHEEL_RIGHT; 1083d70a5de4SDmitry Petrov } 1084d70a5de4SDmitry Petrov 1085cc7859c3SAkihiko Odaki qemu_input_queue_btn(dcl.con, buttons, true); 1086ae7313e7SJohn Arbuckle qemu_input_event_sync(); 1087cc7859c3SAkihiko Odaki qemu_input_queue_btn(dcl.con, buttons, false); 1088ae7313e7SJohn Arbuckle qemu_input_event_sync(); 1089dc3c89d6SJohn Arbuckle } 1090d70a5de4SDmitry Petrov 1091ae7313e7SJohn Arbuckle /* 1092d70a5de4SDmitry Petrov * Since deltaX/deltaY also report scroll wheel events we prevent mouse 1093ae7313e7SJohn Arbuckle * movement code from executing. 1094ae7313e7SJohn Arbuckle */ 1095ae7313e7SJohn Arbuckle mouse_event = false; 10963e230dd2SCorentin Chary break; 10973e230dd2SCorentin Chary default: 109860105d7aSPeter Maydell return false; 10993e230dd2SCorentin Chary } 110021bae11aSGerd Hoffmann 110121bae11aSGerd Hoffmann if (mouse_event) { 11028d3a5d9bSPeter Maydell /* Don't send button events to the guest unless we've got a 11038d3a5d9bSPeter Maydell * mouse grab or window focus. If we have neither then this event 11048d3a5d9bSPeter Maydell * is the user clicking on the background window to activate and 11058d3a5d9bSPeter Maydell * bring us to the front, which will be done by the sendEvent 11068d3a5d9bSPeter Maydell * call below. We definitely don't want to pass that click through 11078d3a5d9bSPeter Maydell * to the guest. 11088d3a5d9bSPeter Maydell */ 11098d3a5d9bSPeter Maydell if ((isMouseGrabbed || [[self window] isKeyWindow]) && 11108d3a5d9bSPeter Maydell (last_buttons != buttons)) { 11117fb1cf16SEric Blake static uint32_t bmap[INPUT_BUTTON__MAX] = { 111221bae11aSGerd Hoffmann [INPUT_BUTTON_LEFT] = MOUSE_EVENT_LBUTTON, 111321bae11aSGerd Hoffmann [INPUT_BUTTON_MIDDLE] = MOUSE_EVENT_MBUTTON, 1114ae7313e7SJohn Arbuckle [INPUT_BUTTON_RIGHT] = MOUSE_EVENT_RBUTTON 111521bae11aSGerd Hoffmann }; 1116cc7859c3SAkihiko Odaki qemu_input_update_buttons(dcl.con, bmap, last_buttons, buttons); 111721bae11aSGerd Hoffmann last_buttons = buttons; 111821bae11aSGerd Hoffmann } 1119f61c387eSPeter Maydell if (isMouseGrabbed) { 1120f61c387eSPeter Maydell if (isAbsoluteEnabled) { 1121f61c387eSPeter Maydell /* Note that the origin for Cocoa mouse coords is bottom left, not top left. 1122f61c387eSPeter Maydell * The check on screenContainsPoint is to avoid sending out of range values for 1123f61c387eSPeter Maydell * clicks in the titlebar. 1124f61c387eSPeter Maydell */ 1125f61c387eSPeter Maydell if ([self screenContainsPoint:p]) { 1126cc7859c3SAkihiko Odaki qemu_input_queue_abs(dcl.con, INPUT_AXIS_X, p.x, 0, screen.width); 1127cc7859c3SAkihiko Odaki qemu_input_queue_abs(dcl.con, INPUT_AXIS_Y, screen.height - p.y, 0, screen.height); 1128f61c387eSPeter Maydell } 1129f61c387eSPeter Maydell } else { 1130cc7859c3SAkihiko Odaki qemu_input_queue_rel(dcl.con, INPUT_AXIS_X, (int)[event deltaX]); 1131cc7859c3SAkihiko Odaki qemu_input_queue_rel(dcl.con, INPUT_AXIS_Y, (int)[event deltaY]); 1132f61c387eSPeter Maydell } 113321bae11aSGerd Hoffmann } else { 113460105d7aSPeter Maydell return false; 113521bae11aSGerd Hoffmann } 113621bae11aSGerd Hoffmann qemu_input_event_sync(); 113721bae11aSGerd Hoffmann } 113860105d7aSPeter Maydell return true; 11393e230dd2SCorentin Chary} 11403e230dd2SCorentin Chary 11413e230dd2SCorentin Chary- (void) grabMouse 11423e230dd2SCorentin Chary{ 11433e230dd2SCorentin Chary COCOA_DEBUG("QemuCocoaView: grabMouse\n"); 11443e230dd2SCorentin Chary 11453e230dd2SCorentin Chary if (!isFullscreen) { 11463e230dd2SCorentin Chary if (qemu_name) 11475929e36cSJohn Arbuckle [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s - (Press ctrl + alt + g to release Mouse)", qemu_name]]; 11483e230dd2SCorentin Chary else 11495929e36cSJohn Arbuckle [normalWindow setTitle:@"QEMU - (Press ctrl + alt + g to release Mouse)"]; 11503e230dd2SCorentin Chary } 115113aefd30SPeter Maydell [self hideCursor]; 1152d1929069SAkihiko Odaki CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled); 115349b9bd4dSPeter Maydell isMouseGrabbed = TRUE; // while isMouseGrabbed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:] 11543e230dd2SCorentin Chary} 11553e230dd2SCorentin Chary 11563e230dd2SCorentin Chary- (void) ungrabMouse 11573e230dd2SCorentin Chary{ 11583e230dd2SCorentin Chary COCOA_DEBUG("QemuCocoaView: ungrabMouse\n"); 11593e230dd2SCorentin Chary 11603e230dd2SCorentin Chary if (!isFullscreen) { 11613e230dd2SCorentin Chary if (qemu_name) 11623e230dd2SCorentin Chary [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]]; 11633e230dd2SCorentin Chary else 11643e230dd2SCorentin Chary [normalWindow setTitle:@"QEMU"]; 11653e230dd2SCorentin Chary } 116613aefd30SPeter Maydell [self unhideCursor]; 11673e230dd2SCorentin Chary CGAssociateMouseAndMouseCursorPosition(TRUE); 116849b9bd4dSPeter Maydell isMouseGrabbed = FALSE; 11693e230dd2SCorentin Chary} 11703e230dd2SCorentin Chary 1171d1929069SAkihiko Odaki- (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled { 1172d1929069SAkihiko Odaki isAbsoluteEnabled = tIsAbsoluteEnabled; 1173d1929069SAkihiko Odaki if (isMouseGrabbed) { 1174d1929069SAkihiko Odaki CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled); 1175d1929069SAkihiko Odaki } 1176d1929069SAkihiko Odaki} 117749b9bd4dSPeter Maydell- (BOOL) isMouseGrabbed {return isMouseGrabbed;} 11783e230dd2SCorentin Chary- (BOOL) isAbsoluteEnabled {return isAbsoluteEnabled;} 11793e230dd2SCorentin Chary- (float) cdx {return cdx;} 11803e230dd2SCorentin Chary- (float) cdy {return cdy;} 11813e230dd2SCorentin Chary- (QEMUScreen) gscreen {return screen;} 11823b178b71SJohn Arbuckle 11833b178b71SJohn Arbuckle/* 11843b178b71SJohn Arbuckle * Makes the target think all down keys are being released. 11853b178b71SJohn Arbuckle * This prevents a stuck key problem, since we will not see 11863b178b71SJohn Arbuckle * key up events for those keys after we have lost focus. 11873b178b71SJohn Arbuckle */ 11883b178b71SJohn Arbuckle- (void) raiseAllKeys 11893b178b71SJohn Arbuckle{ 119031819e95SPeter Maydell with_iothread_lock(^{ 11916d73bb64SAkihiko Odaki qkbd_state_lift_all_keys(kbd); 119231819e95SPeter Maydell }); 11933b178b71SJohn Arbuckle} 11943e230dd2SCorentin Chary@end 11953e230dd2SCorentin Chary 11963e230dd2SCorentin Chary 11973e230dd2SCorentin Chary 11983e230dd2SCorentin Chary/* 11993e230dd2SCorentin Chary ------------------------------------------------------ 12003e230dd2SCorentin Chary QemuCocoaAppController 12013e230dd2SCorentin Chary ------------------------------------------------------ 12023e230dd2SCorentin Chary*/ 12033e230dd2SCorentin Chary@interface QemuCocoaAppController : NSObject 1204d9bc14f6SJohn Arbuckle <NSWindowDelegate, NSApplicationDelegate> 12053e230dd2SCorentin Chary{ 12063e230dd2SCorentin Chary} 12075d1b2eefSProgrammingkid- (void)doToggleFullScreen:(id)sender; 12083e230dd2SCorentin Chary- (void)toggleFullScreen:(id)sender; 12093e230dd2SCorentin Chary- (void)showQEMUDoc:(id)sender; 12105d1b2eefSProgrammingkid- (void)zoomToFit:(id) sender; 1211b4c6a112SProgrammingkid- (void)displayConsole:(id)sender; 12128524f1c7SJohn Arbuckle- (void)pauseQEMU:(id)sender; 12138524f1c7SJohn Arbuckle- (void)resumeQEMU:(id)sender; 12148524f1c7SJohn Arbuckle- (void)displayPause; 12158524f1c7SJohn Arbuckle- (void)removePause; 121627074614SJohn Arbuckle- (void)restartQEMU:(id)sender; 121727074614SJohn Arbuckle- (void)powerDownQEMU:(id)sender; 1218693a3e01SJohn Arbuckle- (void)ejectDeviceMedia:(id)sender; 1219693a3e01SJohn Arbuckle- (void)changeDeviceMedia:(id)sender; 1220d9bc14f6SJohn Arbuckle- (BOOL)verifyQuit; 1221f4747900SJohn Arbuckle- (void)openDocumentation:(NSString *)filename; 12229e8204b1SProgrammingkid- (IBAction) do_about_menu_item: (id) sender; 1223e47ec1a9SJohn Arbuckle- (void)adjustSpeed:(id)sender; 12243e230dd2SCorentin Chary@end 12253e230dd2SCorentin Chary 12263e230dd2SCorentin Chary@implementation QemuCocoaAppController 12273e230dd2SCorentin Chary- (id) init 12283e230dd2SCorentin Chary{ 12293e230dd2SCorentin Chary COCOA_DEBUG("QemuCocoaAppController: init\n"); 12303e230dd2SCorentin Chary 12313e230dd2SCorentin Chary self = [super init]; 12323e230dd2SCorentin Chary if (self) { 12333e230dd2SCorentin Chary 12343e230dd2SCorentin Chary // create a view and add it to the window 12353e230dd2SCorentin Chary cocoaView = [[QemuCocoaView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 640.0, 480.0)]; 12363e230dd2SCorentin Chary if(!cocoaView) { 12374313739aSAkihiko Odaki error_report("(cocoa) can't create a view"); 12383e230dd2SCorentin Chary exit(1); 12393e230dd2SCorentin Chary } 12403e230dd2SCorentin Chary 12413e230dd2SCorentin Chary // create a window 12423e230dd2SCorentin Chary normalWindow = [[NSWindow alloc] initWithContentRect:[cocoaView frame] 12434ba967adSBrendan Shanks styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskClosable 12443e230dd2SCorentin Chary backing:NSBackingStoreBuffered defer:NO]; 12453e230dd2SCorentin Chary if(!normalWindow) { 12464313739aSAkihiko Odaki error_report("(cocoa) can't create window"); 12473e230dd2SCorentin Chary exit(1); 12483e230dd2SCorentin Chary } 12493e230dd2SCorentin Chary [normalWindow setAcceptsMouseMovedEvents:YES]; 1250a1dbc05aSJohn Arbuckle [normalWindow setTitle:@"QEMU"]; 12513e230dd2SCorentin Chary [normalWindow setContentView:cocoaView]; 12523e230dd2SCorentin Chary [normalWindow makeKeyAndOrderFront:self]; 12533e230dd2SCorentin Chary [normalWindow center]; 1254d9bc14f6SJohn Arbuckle [normalWindow setDelegate: self]; 12555d1b2eefSProgrammingkid stretch_video = false; 12568524f1c7SJohn Arbuckle 12578524f1c7SJohn Arbuckle /* Used for displaying pause on the screen */ 12588524f1c7SJohn Arbuckle pauseLabel = [NSTextField new]; 12598524f1c7SJohn Arbuckle [pauseLabel setBezeled:YES]; 12608524f1c7SJohn Arbuckle [pauseLabel setDrawsBackground:YES]; 12618524f1c7SJohn Arbuckle [pauseLabel setBackgroundColor: [NSColor whiteColor]]; 12628524f1c7SJohn Arbuckle [pauseLabel setEditable:NO]; 12638524f1c7SJohn Arbuckle [pauseLabel setSelectable:NO]; 12648524f1c7SJohn Arbuckle [pauseLabel setStringValue: @"Paused"]; 12658524f1c7SJohn Arbuckle [pauseLabel setFont: [NSFont fontWithName: @"Helvetica" size: 90]]; 12668524f1c7SJohn Arbuckle [pauseLabel setTextColor: [NSColor blackColor]]; 12678524f1c7SJohn Arbuckle [pauseLabel sizeToFit]; 12683e230dd2SCorentin Chary } 12693e230dd2SCorentin Chary return self; 12703e230dd2SCorentin Chary} 12713e230dd2SCorentin Chary 12723e230dd2SCorentin Chary- (void) dealloc 12733e230dd2SCorentin Chary{ 12743e230dd2SCorentin Chary COCOA_DEBUG("QemuCocoaAppController: dealloc\n"); 12753e230dd2SCorentin Chary 12763e230dd2SCorentin Chary if (cocoaView) 12773e230dd2SCorentin Chary [cocoaView release]; 12783e230dd2SCorentin Chary [super dealloc]; 12793e230dd2SCorentin Chary} 12803e230dd2SCorentin Chary 12813e230dd2SCorentin Chary- (void)applicationDidFinishLaunching: (NSNotification *) note 12823e230dd2SCorentin Chary{ 12833e230dd2SCorentin Chary COCOA_DEBUG("QemuCocoaAppController: applicationDidFinishLaunching\n"); 1284dff742adSHikaru Nishida allow_events = true; 12855588840fSPeter Maydell /* Tell cocoa_display_init to proceed */ 12865588840fSPeter Maydell qemu_sem_post(&app_started_sem); 12873e230dd2SCorentin Chary} 12883e230dd2SCorentin Chary 12893e230dd2SCorentin Chary- (void)applicationWillTerminate:(NSNotification *)aNotification 12903e230dd2SCorentin Chary{ 12913e230dd2SCorentin Chary COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n"); 12923e230dd2SCorentin Chary 1293cf83f140SEric Blake qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI); 129440c01937SAkihiko Odaki 129540c01937SAkihiko Odaki /* 129640c01937SAkihiko Odaki * Sleep here, because returning will cause OSX to kill us 129740c01937SAkihiko Odaki * immediately; the QEMU main loop will handle the shutdown 129840c01937SAkihiko Odaki * request and terminate the process. 129940c01937SAkihiko Odaki */ 130040c01937SAkihiko Odaki [NSThread sleepForTimeInterval:INFINITY]; 13013e230dd2SCorentin Chary} 13023e230dd2SCorentin Chary 13033e230dd2SCorentin Chary- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication 13043e230dd2SCorentin Chary{ 13053e230dd2SCorentin Chary return YES; 13063e230dd2SCorentin Chary} 13073e230dd2SCorentin Chary 1308d9bc14f6SJohn Arbuckle- (NSApplicationTerminateReply)applicationShouldTerminate: 1309d9bc14f6SJohn Arbuckle (NSApplication *)sender 1310d9bc14f6SJohn Arbuckle{ 1311d9bc14f6SJohn Arbuckle COCOA_DEBUG("QemuCocoaAppController: applicationShouldTerminate\n"); 1312d9bc14f6SJohn Arbuckle return [self verifyQuit]; 1313d9bc14f6SJohn Arbuckle} 1314d9bc14f6SJohn Arbuckle 131515280e85SAkihiko Odaki- (void)windowDidChangeScreen:(NSNotification *)notification 131615280e85SAkihiko Odaki{ 131715280e85SAkihiko Odaki [cocoaView updateUIInfo]; 131815280e85SAkihiko Odaki} 131915280e85SAkihiko Odaki 132015280e85SAkihiko Odaki- (void)windowDidResize:(NSNotification *)notification 132115280e85SAkihiko Odaki{ 132215280e85SAkihiko Odaki [cocoaView updateUIInfo]; 132315280e85SAkihiko Odaki} 132415280e85SAkihiko Odaki 1325d9bc14f6SJohn Arbuckle/* Called when the user clicks on a window's close button */ 1326d9bc14f6SJohn Arbuckle- (BOOL)windowShouldClose:(id)sender 1327d9bc14f6SJohn Arbuckle{ 1328d9bc14f6SJohn Arbuckle COCOA_DEBUG("QemuCocoaAppController: windowShouldClose\n"); 1329d9bc14f6SJohn Arbuckle [NSApp terminate: sender]; 1330d9bc14f6SJohn Arbuckle /* If the user allows the application to quit then the call to 1331d9bc14f6SJohn Arbuckle * NSApp terminate will never return. If we get here then the user 1332d9bc14f6SJohn Arbuckle * cancelled the quit, so we should return NO to not permit the 1333d9bc14f6SJohn Arbuckle * closing of this window. 1334d9bc14f6SJohn Arbuckle */ 1335d9bc14f6SJohn Arbuckle return NO; 1336d9bc14f6SJohn Arbuckle} 1337d9bc14f6SJohn Arbuckle 13383b178b71SJohn Arbuckle/* Called when QEMU goes into the background */ 13393b178b71SJohn Arbuckle- (void) applicationWillResignActive: (NSNotification *)aNotification 13403b178b71SJohn Arbuckle{ 13413b178b71SJohn Arbuckle COCOA_DEBUG("QemuCocoaAppController: applicationWillResignActive\n"); 134269221df8SCarwyn Ellis [cocoaView ungrabMouse]; 13433b178b71SJohn Arbuckle [cocoaView raiseAllKeys]; 13443b178b71SJohn Arbuckle} 13453b178b71SJohn Arbuckle 13465d1b2eefSProgrammingkid/* We abstract the method called by the Enter Fullscreen menu item 13475d1b2eefSProgrammingkid * because Mac OS 10.7 and higher disables it. This is because of the 13485d1b2eefSProgrammingkid * menu item's old selector's name toggleFullScreen: 13495d1b2eefSProgrammingkid */ 13505d1b2eefSProgrammingkid- (void) doToggleFullScreen:(id)sender 13515d1b2eefSProgrammingkid{ 13525d1b2eefSProgrammingkid [self toggleFullScreen:(id)sender]; 13535d1b2eefSProgrammingkid} 13545d1b2eefSProgrammingkid 13553e230dd2SCorentin Chary- (void)toggleFullScreen:(id)sender 13563e230dd2SCorentin Chary{ 13573e230dd2SCorentin Chary COCOA_DEBUG("QemuCocoaAppController: toggleFullScreen\n"); 13583e230dd2SCorentin Chary 13593e230dd2SCorentin Chary [cocoaView toggleFullScreen:sender]; 13603e230dd2SCorentin Chary} 13613e230dd2SCorentin Chary 1362f844cdb9SGustavo Noronha Silva- (void) setFullGrab:(id)sender 1363f844cdb9SGustavo Noronha Silva{ 1364f844cdb9SGustavo Noronha Silva COCOA_DEBUG("QemuCocoaAppController: setFullGrab\n"); 1365f844cdb9SGustavo Noronha Silva 1366f844cdb9SGustavo Noronha Silva [cocoaView setFullGrab:sender]; 1367f844cdb9SGustavo Noronha Silva} 1368f844cdb9SGustavo Noronha Silva 1369f4747900SJohn Arbuckle/* Tries to find then open the specified filename */ 1370f4747900SJohn Arbuckle- (void) openDocumentation: (NSString *) filename 1371f4747900SJohn Arbuckle{ 1372f4747900SJohn Arbuckle /* Where to look for local files */ 13738d6fda8cSRoman Bolshakov NSString *path_array[] = {@"../share/doc/qemu/", @"../doc/qemu/", @"docs/"}; 1374f4747900SJohn Arbuckle NSString *full_file_path; 13751ff5a063SRoman Bolshakov NSURL *full_file_url; 1376f4747900SJohn Arbuckle 1377f4747900SJohn Arbuckle /* iterate thru the possible paths until the file is found */ 1378f4747900SJohn Arbuckle int index; 1379f4747900SJohn Arbuckle for (index = 0; index < ARRAY_SIZE(path_array); index++) { 1380f4747900SJohn Arbuckle full_file_path = [[NSBundle mainBundle] executablePath]; 1381f4747900SJohn Arbuckle full_file_path = [full_file_path stringByDeletingLastPathComponent]; 1382f4747900SJohn Arbuckle full_file_path = [NSString stringWithFormat: @"%@/%@%@", full_file_path, 1383f4747900SJohn Arbuckle path_array[index], filename]; 13841ff5a063SRoman Bolshakov full_file_url = [NSURL fileURLWithPath: full_file_path 13851ff5a063SRoman Bolshakov isDirectory: false]; 13861ff5a063SRoman Bolshakov if ([[NSWorkspace sharedWorkspace] openURL: full_file_url] == YES) { 1387f4747900SJohn Arbuckle return; 1388f4747900SJohn Arbuckle } 1389f4747900SJohn Arbuckle } 1390f4747900SJohn Arbuckle 1391f4747900SJohn Arbuckle /* If none of the paths opened a file */ 1392f4747900SJohn Arbuckle NSBeep(); 1393f4747900SJohn Arbuckle QEMU_Alert(@"Failed to open file"); 1394f4747900SJohn Arbuckle} 1395f4747900SJohn Arbuckle 13963e230dd2SCorentin Chary- (void)showQEMUDoc:(id)sender 13973e230dd2SCorentin Chary{ 13983e230dd2SCorentin Chary COCOA_DEBUG("QemuCocoaAppController: showQEMUDoc\n"); 13993e230dd2SCorentin Chary 14001879f241SPeter Maydell [self openDocumentation: @"index.html"]; 14013e230dd2SCorentin Chary} 14023e230dd2SCorentin Chary 14035d1b2eefSProgrammingkid/* Stretches video to fit host monitor size */ 14045d1b2eefSProgrammingkid- (void)zoomToFit:(id) sender 14055d1b2eefSProgrammingkid{ 14065d1b2eefSProgrammingkid stretch_video = !stretch_video; 14075d1b2eefSProgrammingkid if (stretch_video == true) { 14085e24600aSBrendan Shanks [sender setState: NSControlStateValueOn]; 14095d1b2eefSProgrammingkid } else { 14105e24600aSBrendan Shanks [sender setState: NSControlStateValueOff]; 14115d1b2eefSProgrammingkid } 14125d1b2eefSProgrammingkid} 14133e230dd2SCorentin Chary 1414b4c6a112SProgrammingkid/* Displays the console on the screen */ 1415b4c6a112SProgrammingkid- (void)displayConsole:(id)sender 1416b4c6a112SProgrammingkid{ 1417b4c6a112SProgrammingkid console_select([sender tag]); 1418b4c6a112SProgrammingkid} 14198524f1c7SJohn Arbuckle 14208524f1c7SJohn Arbuckle/* Pause the guest */ 14218524f1c7SJohn Arbuckle- (void)pauseQEMU:(id)sender 14228524f1c7SJohn Arbuckle{ 142331819e95SPeter Maydell with_iothread_lock(^{ 14248524f1c7SJohn Arbuckle qmp_stop(NULL); 142531819e95SPeter Maydell }); 14268524f1c7SJohn Arbuckle [sender setEnabled: NO]; 14278524f1c7SJohn Arbuckle [[[sender menu] itemWithTitle: @"Resume"] setEnabled: YES]; 14288524f1c7SJohn Arbuckle [self displayPause]; 14298524f1c7SJohn Arbuckle} 14308524f1c7SJohn Arbuckle 14318524f1c7SJohn Arbuckle/* Resume running the guest operating system */ 14328524f1c7SJohn Arbuckle- (void)resumeQEMU:(id) sender 14338524f1c7SJohn Arbuckle{ 143431819e95SPeter Maydell with_iothread_lock(^{ 14358524f1c7SJohn Arbuckle qmp_cont(NULL); 143631819e95SPeter Maydell }); 14378524f1c7SJohn Arbuckle [sender setEnabled: NO]; 14388524f1c7SJohn Arbuckle [[[sender menu] itemWithTitle: @"Pause"] setEnabled: YES]; 14398524f1c7SJohn Arbuckle [self removePause]; 14408524f1c7SJohn Arbuckle} 14418524f1c7SJohn Arbuckle 14428524f1c7SJohn Arbuckle/* Displays the word pause on the screen */ 14438524f1c7SJohn Arbuckle- (void)displayPause 14448524f1c7SJohn Arbuckle{ 14458524f1c7SJohn Arbuckle /* Coordinates have to be calculated each time because the window can change its size */ 14468524f1c7SJohn Arbuckle int xCoord, yCoord, width, height; 14478524f1c7SJohn Arbuckle xCoord = ([normalWindow frame].size.width - [pauseLabel frame].size.width)/2; 14488524f1c7SJohn Arbuckle yCoord = [normalWindow frame].size.height - [pauseLabel frame].size.height - ([pauseLabel frame].size.height * .5); 14498524f1c7SJohn Arbuckle width = [pauseLabel frame].size.width; 14508524f1c7SJohn Arbuckle height = [pauseLabel frame].size.height; 14518524f1c7SJohn Arbuckle [pauseLabel setFrame: NSMakeRect(xCoord, yCoord, width, height)]; 14528524f1c7SJohn Arbuckle [cocoaView addSubview: pauseLabel]; 14538524f1c7SJohn Arbuckle} 14548524f1c7SJohn Arbuckle 14558524f1c7SJohn Arbuckle/* Removes the word pause from the screen */ 14568524f1c7SJohn Arbuckle- (void)removePause 14578524f1c7SJohn Arbuckle{ 14588524f1c7SJohn Arbuckle [pauseLabel removeFromSuperview]; 14598524f1c7SJohn Arbuckle} 14608524f1c7SJohn Arbuckle 146127074614SJohn Arbuckle/* Restarts QEMU */ 146227074614SJohn Arbuckle- (void)restartQEMU:(id)sender 146327074614SJohn Arbuckle{ 146431819e95SPeter Maydell with_iothread_lock(^{ 146527074614SJohn Arbuckle qmp_system_reset(NULL); 146631819e95SPeter Maydell }); 146727074614SJohn Arbuckle} 146827074614SJohn Arbuckle 146927074614SJohn Arbuckle/* Powers down QEMU */ 147027074614SJohn Arbuckle- (void)powerDownQEMU:(id)sender 147127074614SJohn Arbuckle{ 147231819e95SPeter Maydell with_iothread_lock(^{ 147327074614SJohn Arbuckle qmp_system_powerdown(NULL); 147431819e95SPeter Maydell }); 147527074614SJohn Arbuckle} 147627074614SJohn Arbuckle 1477693a3e01SJohn Arbuckle/* Ejects the media. 1478693a3e01SJohn Arbuckle * Uses sender's tag to figure out the device to eject. 1479693a3e01SJohn Arbuckle */ 1480693a3e01SJohn Arbuckle- (void)ejectDeviceMedia:(id)sender 1481693a3e01SJohn Arbuckle{ 1482693a3e01SJohn Arbuckle NSString * drive; 1483693a3e01SJohn Arbuckle drive = [sender representedObject]; 1484693a3e01SJohn Arbuckle if(drive == nil) { 1485693a3e01SJohn Arbuckle NSBeep(); 1486693a3e01SJohn Arbuckle QEMU_Alert(@"Failed to find drive to eject!"); 1487693a3e01SJohn Arbuckle return; 1488693a3e01SJohn Arbuckle } 1489693a3e01SJohn Arbuckle 149031819e95SPeter Maydell __block Error *err = NULL; 149131819e95SPeter Maydell with_iothread_lock(^{ 1492fbe2d816SKevin Wolf qmp_eject(true, [drive cStringUsingEncoding: NSASCIIStringEncoding], 1493fbe2d816SKevin Wolf false, NULL, false, false, &err); 149431819e95SPeter Maydell }); 1495693a3e01SJohn Arbuckle handleAnyDeviceErrors(err); 1496693a3e01SJohn Arbuckle} 1497693a3e01SJohn Arbuckle 1498693a3e01SJohn Arbuckle/* Displays a dialog box asking the user to select an image file to load. 1499693a3e01SJohn Arbuckle * Uses sender's represented object value to figure out which drive to use. 1500693a3e01SJohn Arbuckle */ 1501693a3e01SJohn Arbuckle- (void)changeDeviceMedia:(id)sender 1502693a3e01SJohn Arbuckle{ 1503693a3e01SJohn Arbuckle /* Find the drive name */ 1504693a3e01SJohn Arbuckle NSString * drive; 1505693a3e01SJohn Arbuckle drive = [sender representedObject]; 1506693a3e01SJohn Arbuckle if(drive == nil) { 1507693a3e01SJohn Arbuckle NSBeep(); 1508693a3e01SJohn Arbuckle QEMU_Alert(@"Could not find drive!"); 1509693a3e01SJohn Arbuckle return; 1510693a3e01SJohn Arbuckle } 1511693a3e01SJohn Arbuckle 1512693a3e01SJohn Arbuckle /* Display the file open dialog */ 1513693a3e01SJohn Arbuckle NSOpenPanel * openPanel; 1514693a3e01SJohn Arbuckle openPanel = [NSOpenPanel openPanel]; 1515693a3e01SJohn Arbuckle [openPanel setCanChooseFiles: YES]; 1516693a3e01SJohn Arbuckle [openPanel setAllowsMultipleSelection: NO]; 1517b5725385SPeter Maydell if([openPanel runModal] == NSModalResponseOK) { 1518693a3e01SJohn Arbuckle NSString * file = [[[openPanel URLs] objectAtIndex: 0] path]; 1519693a3e01SJohn Arbuckle if(file == nil) { 1520693a3e01SJohn Arbuckle NSBeep(); 1521693a3e01SJohn Arbuckle QEMU_Alert(@"Failed to convert URL to file path!"); 1522693a3e01SJohn Arbuckle return; 1523693a3e01SJohn Arbuckle } 1524693a3e01SJohn Arbuckle 152531819e95SPeter Maydell __block Error *err = NULL; 152631819e95SPeter Maydell with_iothread_lock(^{ 152770e2cb3bSKevin Wolf qmp_blockdev_change_medium(true, 152870e2cb3bSKevin Wolf [drive cStringUsingEncoding: 152924fb4133SMax Reitz NSASCIIStringEncoding], 153070e2cb3bSKevin Wolf false, NULL, 153124fb4133SMax Reitz [file cStringUsingEncoding: 153224fb4133SMax Reitz NSASCIIStringEncoding], 153324fb4133SMax Reitz true, "raw", 1534*80dd5affSDenis V. Lunev true, false, 153539ff43d9SMax Reitz false, 0, 1536693a3e01SJohn Arbuckle &err); 153731819e95SPeter Maydell }); 1538693a3e01SJohn Arbuckle handleAnyDeviceErrors(err); 1539693a3e01SJohn Arbuckle } 1540693a3e01SJohn Arbuckle} 1541693a3e01SJohn Arbuckle 1542d9bc14f6SJohn Arbuckle/* Verifies if the user really wants to quit */ 1543d9bc14f6SJohn Arbuckle- (BOOL)verifyQuit 1544d9bc14f6SJohn Arbuckle{ 1545d9bc14f6SJohn Arbuckle NSAlert *alert = [NSAlert new]; 1546d9bc14f6SJohn Arbuckle [alert autorelease]; 1547d9bc14f6SJohn Arbuckle [alert setMessageText: @"Are you sure you want to quit QEMU?"]; 1548d9bc14f6SJohn Arbuckle [alert addButtonWithTitle: @"Cancel"]; 1549d9bc14f6SJohn Arbuckle [alert addButtonWithTitle: @"Quit"]; 1550d9bc14f6SJohn Arbuckle if([alert runModal] == NSAlertSecondButtonReturn) { 1551d9bc14f6SJohn Arbuckle return YES; 1552d9bc14f6SJohn Arbuckle } else { 1553d9bc14f6SJohn Arbuckle return NO; 1554d9bc14f6SJohn Arbuckle } 1555d9bc14f6SJohn Arbuckle} 1556d9bc14f6SJohn Arbuckle 15579e8204b1SProgrammingkid/* The action method for the About menu item */ 15589e8204b1SProgrammingkid- (IBAction) do_about_menu_item: (id) sender 15599e8204b1SProgrammingkid{ 156099eb313dSAkihiko Odaki NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 156199eb313dSAkihiko Odaki char *icon_path_c = get_relocated_path(CONFIG_QEMU_ICONDIR "/hicolor/512x512/apps/qemu.png"); 156299eb313dSAkihiko Odaki NSString *icon_path = [NSString stringWithUTF8String:icon_path_c]; 156399eb313dSAkihiko Odaki g_free(icon_path_c); 156499eb313dSAkihiko Odaki NSImage *icon = [[NSImage alloc] initWithContentsOfFile:icon_path]; 156599eb313dSAkihiko Odaki NSString *version = @"QEMU emulator version " QEMU_FULL_VERSION; 156699eb313dSAkihiko Odaki NSString *copyright = @QEMU_COPYRIGHT; 156799eb313dSAkihiko Odaki NSDictionary *options; 156899eb313dSAkihiko Odaki if (icon) { 156999eb313dSAkihiko Odaki options = @{ 157099eb313dSAkihiko Odaki NSAboutPanelOptionApplicationIcon : icon, 157199eb313dSAkihiko Odaki NSAboutPanelOptionApplicationVersion : version, 157299eb313dSAkihiko Odaki @"Copyright" : copyright, 157399eb313dSAkihiko Odaki }; 157499eb313dSAkihiko Odaki [icon release]; 157599eb313dSAkihiko Odaki } else { 157699eb313dSAkihiko Odaki options = @{ 157799eb313dSAkihiko Odaki NSAboutPanelOptionApplicationVersion : version, 157899eb313dSAkihiko Odaki @"Copyright" : copyright, 157999eb313dSAkihiko Odaki }; 15809e8204b1SProgrammingkid } 158199eb313dSAkihiko Odaki [NSApp orderFrontStandardAboutPanelWithOptions:options]; 158299eb313dSAkihiko Odaki [pool release]; 15839e8204b1SProgrammingkid} 15849e8204b1SProgrammingkid 1585e47ec1a9SJohn Arbuckle/* Used by the Speed menu items */ 1586e47ec1a9SJohn Arbuckle- (void)adjustSpeed:(id)sender 1587e47ec1a9SJohn Arbuckle{ 1588e47ec1a9SJohn Arbuckle int throttle_pct; /* throttle percentage */ 1589e47ec1a9SJohn Arbuckle NSMenu *menu; 1590e47ec1a9SJohn Arbuckle 1591e47ec1a9SJohn Arbuckle menu = [sender menu]; 1592e47ec1a9SJohn Arbuckle if (menu != nil) 1593e47ec1a9SJohn Arbuckle { 1594e47ec1a9SJohn Arbuckle /* Unselect the currently selected item */ 1595e47ec1a9SJohn Arbuckle for (NSMenuItem *item in [menu itemArray]) { 15965e24600aSBrendan Shanks if (item.state == NSControlStateValueOn) { 15975e24600aSBrendan Shanks [item setState: NSControlStateValueOff]; 1598e47ec1a9SJohn Arbuckle break; 1599e47ec1a9SJohn Arbuckle } 1600e47ec1a9SJohn Arbuckle } 1601e47ec1a9SJohn Arbuckle } 1602e47ec1a9SJohn Arbuckle 1603e47ec1a9SJohn Arbuckle // check the menu item 16045e24600aSBrendan Shanks [sender setState: NSControlStateValueOn]; 1605e47ec1a9SJohn Arbuckle 1606e47ec1a9SJohn Arbuckle // get the throttle percentage 1607e47ec1a9SJohn Arbuckle throttle_pct = [sender tag]; 1608e47ec1a9SJohn Arbuckle 160931819e95SPeter Maydell with_iothread_lock(^{ 1610e47ec1a9SJohn Arbuckle cpu_throttle_set(throttle_pct); 161131819e95SPeter Maydell }); 1612e47ec1a9SJohn Arbuckle COCOA_DEBUG("cpu throttling at %d%c\n", cpu_throttle_get_percentage(), '%'); 1613e47ec1a9SJohn Arbuckle} 1614e47ec1a9SJohn Arbuckle 1615b4c6a112SProgrammingkid@end 16163e230dd2SCorentin Chary 161761a2ed44SPeter Maydell@interface QemuApplication : NSApplication 161861a2ed44SPeter Maydell@end 161961a2ed44SPeter Maydell 162061a2ed44SPeter Maydell@implementation QemuApplication 162161a2ed44SPeter Maydell- (void)sendEvent:(NSEvent *)event 162261a2ed44SPeter Maydell{ 162361a2ed44SPeter Maydell COCOA_DEBUG("QemuApplication: sendEvent\n"); 16245588840fSPeter Maydell if (![cocoaView handleEvent:event]) { 162561a2ed44SPeter Maydell [super sendEvent: event]; 162661a2ed44SPeter Maydell } 16275588840fSPeter Maydell} 162861a2ed44SPeter Maydell@end 162961a2ed44SPeter Maydell 1630c6fd6c70SPeter Maydellstatic void create_initial_menus(void) 1631c6fd6c70SPeter Maydell{ 16323e230dd2SCorentin Chary // Add menus 16333e230dd2SCorentin Chary NSMenu *menu; 16343e230dd2SCorentin Chary NSMenuItem *menuItem; 16353e230dd2SCorentin Chary 16363e230dd2SCorentin Chary [NSApp setMainMenu:[[NSMenu alloc] init]]; 16375b6988c1SAkihiko Odaki [NSApp setServicesMenu:[[NSMenu alloc] initWithTitle:@"Services"]]; 16383e230dd2SCorentin Chary 16393e230dd2SCorentin Chary // Application menu 16403e230dd2SCorentin Chary menu = [[NSMenu alloc] initWithTitle:@""]; 16419e8204b1SProgrammingkid [menu addItemWithTitle:@"About QEMU" action:@selector(do_about_menu_item:) keyEquivalent:@""]; // About QEMU 16423e230dd2SCorentin Chary [menu addItem:[NSMenuItem separatorItem]]; //Separator 16435b6988c1SAkihiko Odaki menuItem = [menu addItemWithTitle:@"Services" action:nil keyEquivalent:@""]; 16445b6988c1SAkihiko Odaki [menuItem setSubmenu:[NSApp servicesMenu]]; 16455b6988c1SAkihiko Odaki [menu addItem:[NSMenuItem separatorItem]]; 16463e230dd2SCorentin Chary [menu addItemWithTitle:@"Hide QEMU" action:@selector(hide:) keyEquivalent:@"h"]; //Hide QEMU 16473e230dd2SCorentin Chary menuItem = (NSMenuItem *)[menu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; // Hide Others 16484ba967adSBrendan Shanks [menuItem setKeyEquivalentModifierMask:(NSEventModifierFlagOption|NSEventModifierFlagCommand)]; 16493e230dd2SCorentin Chary [menu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; // Show All 16503e230dd2SCorentin Chary [menu addItem:[NSMenuItem separatorItem]]; //Separator 16513e230dd2SCorentin Chary [menu addItemWithTitle:@"Quit QEMU" action:@selector(terminate:) keyEquivalent:@"q"]; 16523e230dd2SCorentin Chary menuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" action:nil keyEquivalent:@""]; 16533e230dd2SCorentin Chary [menuItem setSubmenu:menu]; 16543e230dd2SCorentin Chary [[NSApp mainMenu] addItem:menuItem]; 16553e230dd2SCorentin Chary [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; // Workaround (this method is private since 10.4+) 16563e230dd2SCorentin Chary 16578524f1c7SJohn Arbuckle // Machine menu 16588524f1c7SJohn Arbuckle menu = [[NSMenu alloc] initWithTitle: @"Machine"]; 16598524f1c7SJohn Arbuckle [menu setAutoenablesItems: NO]; 16608524f1c7SJohn Arbuckle [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Pause" action: @selector(pauseQEMU:) keyEquivalent: @""] autorelease]]; 16618524f1c7SJohn Arbuckle menuItem = [[[NSMenuItem alloc] initWithTitle: @"Resume" action: @selector(resumeQEMU:) keyEquivalent: @""] autorelease]; 16628524f1c7SJohn Arbuckle [menu addItem: menuItem]; 16638524f1c7SJohn Arbuckle [menuItem setEnabled: NO]; 166427074614SJohn Arbuckle [menu addItem: [NSMenuItem separatorItem]]; 166527074614SJohn Arbuckle [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Reset" action: @selector(restartQEMU:) keyEquivalent: @""] autorelease]]; 166627074614SJohn Arbuckle [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Power Down" action: @selector(powerDownQEMU:) keyEquivalent: @""] autorelease]]; 16678524f1c7SJohn Arbuckle menuItem = [[[NSMenuItem alloc] initWithTitle: @"Machine" action:nil keyEquivalent:@""] autorelease]; 16688524f1c7SJohn Arbuckle [menuItem setSubmenu:menu]; 16698524f1c7SJohn Arbuckle [[NSApp mainMenu] addItem:menuItem]; 16708524f1c7SJohn Arbuckle 16713e230dd2SCorentin Chary // View menu 16723e230dd2SCorentin Chary menu = [[NSMenu alloc] initWithTitle:@"View"]; 16735d1b2eefSProgrammingkid [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(doToggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen 16745d1b2eefSProgrammingkid [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Zoom To Fit" action:@selector(zoomToFit:) keyEquivalent:@""] autorelease]]; 16753e230dd2SCorentin Chary menuItem = [[[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""] autorelease]; 16763e230dd2SCorentin Chary [menuItem setSubmenu:menu]; 16773e230dd2SCorentin Chary [[NSApp mainMenu] addItem:menuItem]; 16783e230dd2SCorentin Chary 1679e47ec1a9SJohn Arbuckle // Speed menu 1680e47ec1a9SJohn Arbuckle menu = [[NSMenu alloc] initWithTitle:@"Speed"]; 1681e47ec1a9SJohn Arbuckle 1682e47ec1a9SJohn Arbuckle // Add the rest of the Speed menu items 1683e47ec1a9SJohn Arbuckle int p, percentage, throttle_pct; 1684e47ec1a9SJohn Arbuckle for (p = 10; p >= 0; p--) 1685e47ec1a9SJohn Arbuckle { 1686e47ec1a9SJohn Arbuckle percentage = p * 10 > 1 ? p * 10 : 1; // prevent a 0% menu item 1687e47ec1a9SJohn Arbuckle 1688e47ec1a9SJohn Arbuckle menuItem = [[[NSMenuItem alloc] 1689e47ec1a9SJohn Arbuckle initWithTitle: [NSString stringWithFormat: @"%d%%", percentage] action:@selector(adjustSpeed:) keyEquivalent:@""] autorelease]; 1690e47ec1a9SJohn Arbuckle 1691e47ec1a9SJohn Arbuckle if (percentage == 100) { 16925e24600aSBrendan Shanks [menuItem setState: NSControlStateValueOn]; 1693e47ec1a9SJohn Arbuckle } 1694e47ec1a9SJohn Arbuckle 1695e47ec1a9SJohn Arbuckle /* Calculate the throttle percentage */ 1696e47ec1a9SJohn Arbuckle throttle_pct = -1 * percentage + 100; 1697e47ec1a9SJohn Arbuckle 1698e47ec1a9SJohn Arbuckle [menuItem setTag: throttle_pct]; 1699e47ec1a9SJohn Arbuckle [menu addItem: menuItem]; 1700e47ec1a9SJohn Arbuckle } 1701e47ec1a9SJohn Arbuckle menuItem = [[[NSMenuItem alloc] initWithTitle:@"Speed" action:nil keyEquivalent:@""] autorelease]; 1702e47ec1a9SJohn Arbuckle [menuItem setSubmenu:menu]; 1703e47ec1a9SJohn Arbuckle [[NSApp mainMenu] addItem:menuItem]; 1704e47ec1a9SJohn Arbuckle 17053e230dd2SCorentin Chary // Window menu 17063e230dd2SCorentin Chary menu = [[NSMenu alloc] initWithTitle:@"Window"]; 17073e230dd2SCorentin Chary [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease]]; // Miniaturize 17083e230dd2SCorentin Chary menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease]; 17093e230dd2SCorentin Chary [menuItem setSubmenu:menu]; 17103e230dd2SCorentin Chary [[NSApp mainMenu] addItem:menuItem]; 17113e230dd2SCorentin Chary [NSApp setWindowsMenu:menu]; 17123e230dd2SCorentin Chary 17133e230dd2SCorentin Chary // Help menu 17143e230dd2SCorentin Chary menu = [[NSMenu alloc] initWithTitle:@"Help"]; 17153e230dd2SCorentin Chary [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Documentation" action:@selector(showQEMUDoc:) keyEquivalent:@"?"] autorelease]]; // QEMU Help 17163e230dd2SCorentin Chary menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease]; 17173e230dd2SCorentin Chary [menuItem setSubmenu:menu]; 17183e230dd2SCorentin Chary [[NSApp mainMenu] addItem:menuItem]; 1719c6fd6c70SPeter Maydell} 1720c6fd6c70SPeter Maydell 17218b00e4e7SPeter Maydell/* Returns a name for a given console */ 17228b00e4e7SPeter Maydellstatic NSString * getConsoleName(QemuConsole * console) 17238b00e4e7SPeter Maydell{ 1724ca511604SAkihiko Odaki g_autofree char *label = qemu_console_get_label(console); 1725ca511604SAkihiko Odaki 1726ca511604SAkihiko Odaki return [NSString stringWithUTF8String:label]; 17278b00e4e7SPeter Maydell} 17288b00e4e7SPeter Maydell 17298b00e4e7SPeter Maydell/* Add an entry to the View menu for each console */ 17308b00e4e7SPeter Maydellstatic void add_console_menu_entries(void) 17318b00e4e7SPeter Maydell{ 17328b00e4e7SPeter Maydell NSMenu *menu; 17338b00e4e7SPeter Maydell NSMenuItem *menuItem; 17348b00e4e7SPeter Maydell int index = 0; 17358b00e4e7SPeter Maydell 17368b00e4e7SPeter Maydell menu = [[[NSApp mainMenu] itemWithTitle:@"View"] submenu]; 17378b00e4e7SPeter Maydell 17388b00e4e7SPeter Maydell [menu addItem:[NSMenuItem separatorItem]]; 17398b00e4e7SPeter Maydell 17408b00e4e7SPeter Maydell while (qemu_console_lookup_by_index(index) != NULL) { 17418b00e4e7SPeter Maydell menuItem = [[[NSMenuItem alloc] initWithTitle: getConsoleName(qemu_console_lookup_by_index(index)) 17428b00e4e7SPeter Maydell action: @selector(displayConsole:) keyEquivalent: @""] autorelease]; 17438b00e4e7SPeter Maydell [menuItem setTag: index]; 17448b00e4e7SPeter Maydell [menu addItem: menuItem]; 17458b00e4e7SPeter Maydell index++; 17468b00e4e7SPeter Maydell } 17478b00e4e7SPeter Maydell} 17488b00e4e7SPeter Maydell 17498b00e4e7SPeter Maydell/* Make menu items for all removable devices. 17508b00e4e7SPeter Maydell * Each device is given an 'Eject' and 'Change' menu item. 17518b00e4e7SPeter Maydell */ 17528b00e4e7SPeter Maydellstatic void addRemovableDevicesMenuItems(void) 17538b00e4e7SPeter Maydell{ 17548b00e4e7SPeter Maydell NSMenu *menu; 17558b00e4e7SPeter Maydell NSMenuItem *menuItem; 17568b00e4e7SPeter Maydell BlockInfoList *currentDevice, *pointerToFree; 17578b00e4e7SPeter Maydell NSString *deviceName; 17588b00e4e7SPeter Maydell 17598b00e4e7SPeter Maydell currentDevice = qmp_query_block(NULL); 17608b00e4e7SPeter Maydell pointerToFree = currentDevice; 17618b00e4e7SPeter Maydell 17628b00e4e7SPeter Maydell menu = [[[NSApp mainMenu] itemWithTitle:@"Machine"] submenu]; 17638b00e4e7SPeter Maydell 17648b00e4e7SPeter Maydell // Add a separator between related groups of menu items 17658b00e4e7SPeter Maydell [menu addItem:[NSMenuItem separatorItem]]; 17668b00e4e7SPeter Maydell 17678b00e4e7SPeter Maydell // Set the attributes to the "Removable Media" menu item 17688b00e4e7SPeter Maydell NSString *titleString = @"Removable Media"; 17698b00e4e7SPeter Maydell NSMutableAttributedString *attString=[[NSMutableAttributedString alloc] initWithString:titleString]; 17708b00e4e7SPeter Maydell NSColor *newColor = [NSColor blackColor]; 17718b00e4e7SPeter Maydell NSFontManager *fontManager = [NSFontManager sharedFontManager]; 17728b00e4e7SPeter Maydell NSFont *font = [fontManager fontWithFamily:@"Helvetica" 17738b00e4e7SPeter Maydell traits:NSBoldFontMask|NSItalicFontMask 17748b00e4e7SPeter Maydell weight:0 17758b00e4e7SPeter Maydell size:14]; 17768b00e4e7SPeter Maydell [attString addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, [titleString length])]; 17778b00e4e7SPeter Maydell [attString addAttribute:NSForegroundColorAttributeName value:newColor range:NSMakeRange(0, [titleString length])]; 17788b00e4e7SPeter Maydell [attString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInt: 1] range:NSMakeRange(0, [titleString length])]; 17798b00e4e7SPeter Maydell 17808b00e4e7SPeter Maydell // Add the "Removable Media" menu item 17818b00e4e7SPeter Maydell menuItem = [NSMenuItem new]; 17828b00e4e7SPeter Maydell [menuItem setAttributedTitle: attString]; 17838b00e4e7SPeter Maydell [menuItem setEnabled: NO]; 17848b00e4e7SPeter Maydell [menu addItem: menuItem]; 17858b00e4e7SPeter Maydell 17868b00e4e7SPeter Maydell /* Loop through all the block devices in the emulator */ 17878b00e4e7SPeter Maydell while (currentDevice) { 17888b00e4e7SPeter Maydell deviceName = [[NSString stringWithFormat: @"%s", currentDevice->value->device] retain]; 17898b00e4e7SPeter Maydell 17908b00e4e7SPeter Maydell if(currentDevice->value->removable) { 17918b00e4e7SPeter Maydell menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Change %s...", currentDevice->value->device] 17928b00e4e7SPeter Maydell action: @selector(changeDeviceMedia:) 17938b00e4e7SPeter Maydell keyEquivalent: @""]; 17948b00e4e7SPeter Maydell [menu addItem: menuItem]; 17958b00e4e7SPeter Maydell [menuItem setRepresentedObject: deviceName]; 17968b00e4e7SPeter Maydell [menuItem autorelease]; 17978b00e4e7SPeter Maydell 17988b00e4e7SPeter Maydell menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Eject %s", currentDevice->value->device] 17998b00e4e7SPeter Maydell action: @selector(ejectDeviceMedia:) 18008b00e4e7SPeter Maydell keyEquivalent: @""]; 18018b00e4e7SPeter Maydell [menu addItem: menuItem]; 18028b00e4e7SPeter Maydell [menuItem setRepresentedObject: deviceName]; 18038b00e4e7SPeter Maydell [menuItem autorelease]; 18048b00e4e7SPeter Maydell } 18058b00e4e7SPeter Maydell currentDevice = currentDevice->next; 18068b00e4e7SPeter Maydell } 18078b00e4e7SPeter Maydell qapi_free_BlockInfoList(pointerToFree); 18088b00e4e7SPeter Maydell} 18098b00e4e7SPeter Maydell 18107e3e20d8SAkihiko Odaki@interface QemuCocoaPasteboardTypeOwner : NSObject<NSPasteboardTypeOwner> 18117e3e20d8SAkihiko Odaki@end 18127e3e20d8SAkihiko Odaki 18137e3e20d8SAkihiko Odaki@implementation QemuCocoaPasteboardTypeOwner 18147e3e20d8SAkihiko Odaki 18157e3e20d8SAkihiko Odaki- (void)pasteboard:(NSPasteboard *)sender provideDataForType:(NSPasteboardType)type 18167e3e20d8SAkihiko Odaki{ 18177e3e20d8SAkihiko Odaki if (type != NSPasteboardTypeString) { 18187e3e20d8SAkihiko Odaki return; 18197e3e20d8SAkihiko Odaki } 18207e3e20d8SAkihiko Odaki 18217e3e20d8SAkihiko Odaki with_iothread_lock(^{ 18227e3e20d8SAkihiko Odaki QemuClipboardInfo *info = qemu_clipboard_info_ref(cbinfo); 18237e3e20d8SAkihiko Odaki qemu_event_reset(&cbevent); 18247e3e20d8SAkihiko Odaki qemu_clipboard_request(info, QEMU_CLIPBOARD_TYPE_TEXT); 18257e3e20d8SAkihiko Odaki 18267e3e20d8SAkihiko Odaki while (info == cbinfo && 18277e3e20d8SAkihiko Odaki info->types[QEMU_CLIPBOARD_TYPE_TEXT].available && 18287e3e20d8SAkihiko Odaki info->types[QEMU_CLIPBOARD_TYPE_TEXT].data == NULL) { 18297e3e20d8SAkihiko Odaki qemu_mutex_unlock_iothread(); 18307e3e20d8SAkihiko Odaki qemu_event_wait(&cbevent); 18317e3e20d8SAkihiko Odaki qemu_mutex_lock_iothread(); 18327e3e20d8SAkihiko Odaki } 18337e3e20d8SAkihiko Odaki 18347e3e20d8SAkihiko Odaki if (info == cbinfo) { 18357e3e20d8SAkihiko Odaki NSData *data = [[NSData alloc] initWithBytes:info->types[QEMU_CLIPBOARD_TYPE_TEXT].data 18367e3e20d8SAkihiko Odaki length:info->types[QEMU_CLIPBOARD_TYPE_TEXT].size]; 18377e3e20d8SAkihiko Odaki [sender setData:data forType:NSPasteboardTypeString]; 18387e3e20d8SAkihiko Odaki [data release]; 18397e3e20d8SAkihiko Odaki } 18407e3e20d8SAkihiko Odaki 18417e3e20d8SAkihiko Odaki qemu_clipboard_info_unref(info); 18427e3e20d8SAkihiko Odaki }); 18437e3e20d8SAkihiko Odaki} 18447e3e20d8SAkihiko Odaki 18457e3e20d8SAkihiko Odaki@end 18467e3e20d8SAkihiko Odaki 18477e3e20d8SAkihiko Odakistatic QemuCocoaPasteboardTypeOwner *cbowner; 18487e3e20d8SAkihiko Odaki 18497e3e20d8SAkihiko Odakistatic void cocoa_clipboard_notify(Notifier *notifier, void *data); 18507e3e20d8SAkihiko Odakistatic void cocoa_clipboard_request(QemuClipboardInfo *info, 18517e3e20d8SAkihiko Odaki QemuClipboardType type); 18527e3e20d8SAkihiko Odaki 18537e3e20d8SAkihiko Odakistatic QemuClipboardPeer cbpeer = { 18547e3e20d8SAkihiko Odaki .name = "cocoa", 18551b17f1e9SMarc-André Lureau .notifier = { .notify = cocoa_clipboard_notify }, 18567e3e20d8SAkihiko Odaki .request = cocoa_clipboard_request 18577e3e20d8SAkihiko Odaki}; 18587e3e20d8SAkihiko Odaki 18591b17f1e9SMarc-André Lureaustatic void cocoa_clipboard_update_info(QemuClipboardInfo *info) 18607e3e20d8SAkihiko Odaki{ 18617e3e20d8SAkihiko Odaki if (info->owner == &cbpeer || info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) { 18627e3e20d8SAkihiko Odaki return; 18637e3e20d8SAkihiko Odaki } 18647e3e20d8SAkihiko Odaki 18657e3e20d8SAkihiko Odaki if (info != cbinfo) { 18667e3e20d8SAkihiko Odaki NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 18677e3e20d8SAkihiko Odaki qemu_clipboard_info_unref(cbinfo); 18687e3e20d8SAkihiko Odaki cbinfo = qemu_clipboard_info_ref(info); 18697e3e20d8SAkihiko Odaki cbchangecount = [[NSPasteboard generalPasteboard] declareTypes:@[NSPasteboardTypeString] owner:cbowner]; 18707e3e20d8SAkihiko Odaki [pool release]; 18717e3e20d8SAkihiko Odaki } 18727e3e20d8SAkihiko Odaki 18737e3e20d8SAkihiko Odaki qemu_event_set(&cbevent); 18747e3e20d8SAkihiko Odaki} 18757e3e20d8SAkihiko Odaki 18761b17f1e9SMarc-André Lureaustatic void cocoa_clipboard_notify(Notifier *notifier, void *data) 18771b17f1e9SMarc-André Lureau{ 18781b17f1e9SMarc-André Lureau QemuClipboardNotify *notify = data; 18791b17f1e9SMarc-André Lureau 18801b17f1e9SMarc-André Lureau switch (notify->type) { 18811b17f1e9SMarc-André Lureau case QEMU_CLIPBOARD_UPDATE_INFO: 18821b17f1e9SMarc-André Lureau cocoa_clipboard_update_info(notify->info); 18831b17f1e9SMarc-André Lureau return; 1884505dbf9bSMarc-André Lureau case QEMU_CLIPBOARD_RESET_SERIAL: 1885505dbf9bSMarc-André Lureau /* ignore */ 1886505dbf9bSMarc-André Lureau return; 18871b17f1e9SMarc-André Lureau } 18881b17f1e9SMarc-André Lureau} 18891b17f1e9SMarc-André Lureau 18907e3e20d8SAkihiko Odakistatic void cocoa_clipboard_request(QemuClipboardInfo *info, 18917e3e20d8SAkihiko Odaki QemuClipboardType type) 18927e3e20d8SAkihiko Odaki{ 18937e3e20d8SAkihiko Odaki NSData *text; 18947e3e20d8SAkihiko Odaki 18957e3e20d8SAkihiko Odaki switch (type) { 18967e3e20d8SAkihiko Odaki case QEMU_CLIPBOARD_TYPE_TEXT: 18977e3e20d8SAkihiko Odaki text = [[NSPasteboard generalPasteboard] dataForType:NSPasteboardTypeString]; 18987e3e20d8SAkihiko Odaki if (text) { 18997e3e20d8SAkihiko Odaki qemu_clipboard_set_data(&cbpeer, info, type, 19007e3e20d8SAkihiko Odaki [text length], [text bytes], true); 19017e3e20d8SAkihiko Odaki [text release]; 19027e3e20d8SAkihiko Odaki } 19037e3e20d8SAkihiko Odaki break; 19047e3e20d8SAkihiko Odaki default: 19057e3e20d8SAkihiko Odaki break; 19067e3e20d8SAkihiko Odaki } 19077e3e20d8SAkihiko Odaki} 19087e3e20d8SAkihiko Odaki 19095588840fSPeter Maydell/* 19105588840fSPeter Maydell * The startup process for the OSX/Cocoa UI is complicated, because 19115588840fSPeter Maydell * OSX insists that the UI runs on the initial main thread, and so we 19125588840fSPeter Maydell * need to start a second thread which runs the vl.c qemu_main(): 19135588840fSPeter Maydell * 19145588840fSPeter Maydell * Initial thread: 2nd thread: 19155588840fSPeter Maydell * in main(): 19165588840fSPeter Maydell * create qemu-main thread 19175588840fSPeter Maydell * wait on display_init semaphore 19185588840fSPeter Maydell * call qemu_main() 19195588840fSPeter Maydell * ... 19205588840fSPeter Maydell * in cocoa_display_init(): 19215588840fSPeter Maydell * post the display_init semaphore 19225588840fSPeter Maydell * wait on app_started semaphore 19235588840fSPeter Maydell * create application, menus, etc 19245588840fSPeter Maydell * enter OSX run loop 19255588840fSPeter Maydell * in applicationDidFinishLaunching: 19265588840fSPeter Maydell * post app_started semaphore 19275588840fSPeter Maydell * tell main thread to fullscreen if needed 19285588840fSPeter Maydell * [...] 19295588840fSPeter Maydell * run qemu main-loop 19305588840fSPeter Maydell * 19315588840fSPeter Maydell * We do this in two stages so that we don't do the creation of the 19325588840fSPeter Maydell * GUI application menus and so on for command line options like --help 19335588840fSPeter Maydell * where we want to just print text to stdout and exit immediately. 19345588840fSPeter Maydell */ 1935c6fd6c70SPeter Maydell 19365588840fSPeter Maydellstatic void *call_qemu_main(void *opaque) 19375588840fSPeter Maydell{ 19385588840fSPeter Maydell int status; 19395588840fSPeter Maydell 19405588840fSPeter Maydell COCOA_DEBUG("Second thread: calling qemu_main()\n"); 19415588840fSPeter Maydell status = qemu_main(gArgc, gArgv, *_NSGetEnviron()); 19425588840fSPeter Maydell COCOA_DEBUG("Second thread: qemu_main() returned, exiting\n"); 19437e3e20d8SAkihiko Odaki [cbowner release]; 19445588840fSPeter Maydell exit(status); 19455588840fSPeter Maydell} 19465588840fSPeter Maydell 194740a9aadbSAkihiko Odakiint main (int argc, char **argv) { 19485588840fSPeter Maydell QemuThread thread; 19495588840fSPeter Maydell 19505588840fSPeter Maydell COCOA_DEBUG("Entered main()\n"); 1951c6fd6c70SPeter Maydell gArgc = argc; 195240a9aadbSAkihiko Odaki gArgv = argv; 1953c6fd6c70SPeter Maydell 19545588840fSPeter Maydell qemu_sem_init(&display_init_sem, 0); 19555588840fSPeter Maydell qemu_sem_init(&app_started_sem, 0); 1956c6fd6c70SPeter Maydell 19575588840fSPeter Maydell qemu_thread_create(&thread, "qemu_main", call_qemu_main, 19585588840fSPeter Maydell NULL, QEMU_THREAD_DETACHED); 19595588840fSPeter Maydell 19605588840fSPeter Maydell COCOA_DEBUG("Main thread: waiting for display_init_sem\n"); 19615588840fSPeter Maydell qemu_sem_wait(&display_init_sem); 19625588840fSPeter Maydell COCOA_DEBUG("Main thread: initializing app\n"); 1963c6fd6c70SPeter Maydell 1964c6fd6c70SPeter Maydell NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 1965c6fd6c70SPeter Maydell 1966c6fd6c70SPeter Maydell // Pull this console process up to being a fully-fledged graphical 1967c6fd6c70SPeter Maydell // app with a menubar and Dock icon 1968c6fd6c70SPeter Maydell ProcessSerialNumber psn = { 0, kCurrentProcess }; 1969c6fd6c70SPeter Maydell TransformProcessType(&psn, kProcessTransformToForegroundApplication); 1970c6fd6c70SPeter Maydell 197161a2ed44SPeter Maydell [QemuApplication sharedApplication]; 1972c6fd6c70SPeter Maydell 1973c6fd6c70SPeter Maydell create_initial_menus(); 19743e230dd2SCorentin Chary 19755588840fSPeter Maydell /* 19765588840fSPeter Maydell * Create the menu entries which depend on QEMU state (for consoles 19775588840fSPeter Maydell * and removeable devices). These make calls back into QEMU functions, 19785588840fSPeter Maydell * which is OK because at this point we know that the second thread 19795588840fSPeter Maydell * holds the iothread lock and is synchronously waiting for us to 19805588840fSPeter Maydell * finish. 19815588840fSPeter Maydell */ 19825588840fSPeter Maydell add_console_menu_entries(); 19835588840fSPeter Maydell addRemovableDevicesMenuItems(); 19845588840fSPeter Maydell 19853e230dd2SCorentin Chary // Create an Application controller 19863e230dd2SCorentin Chary QemuCocoaAppController *appController = [[QemuCocoaAppController alloc] init]; 19873e230dd2SCorentin Chary [NSApp setDelegate:appController]; 19883e230dd2SCorentin Chary 19893e230dd2SCorentin Chary // Start the main event loop 19905588840fSPeter Maydell COCOA_DEBUG("Main thread: entering OSX run loop\n"); 19913e230dd2SCorentin Chary [NSApp run]; 19925588840fSPeter Maydell COCOA_DEBUG("Main thread: left OSX run loop, exiting\n"); 19933e230dd2SCorentin Chary 19943e230dd2SCorentin Chary [appController release]; 19953e230dd2SCorentin Chary [pool release]; 19963e230dd2SCorentin Chary 19973e230dd2SCorentin Chary return 0; 19983e230dd2SCorentin Chary} 19993e230dd2SCorentin Chary 20003e230dd2SCorentin Chary 20013e230dd2SCorentin Chary 20023e230dd2SCorentin Chary#pragma mark qemu 20037c20b4a3SGerd Hoffmannstatic void cocoa_update(DisplayChangeListener *dcl, 20047c20b4a3SGerd Hoffmann int x, int y, int w, int h) 20053e230dd2SCorentin Chary{ 20063e230dd2SCorentin Chary COCOA_DEBUG("qemu_cocoa: cocoa_update\n"); 20073e230dd2SCorentin Chary 20085588840fSPeter Maydell dispatch_async(dispatch_get_main_queue(), ^{ 20093e230dd2SCorentin Chary NSRect rect; 20103e230dd2SCorentin Chary if ([cocoaView cdx] == 1.0) { 20113e230dd2SCorentin Chary rect = NSMakeRect(x, [cocoaView gscreen].height - y - h, w, h); 20123e230dd2SCorentin Chary } else { 20133e230dd2SCorentin Chary rect = NSMakeRect( 20143e230dd2SCorentin Chary x * [cocoaView cdx], 20153e230dd2SCorentin Chary ([cocoaView gscreen].height - y - h) * [cocoaView cdy], 20163e230dd2SCorentin Chary w * [cocoaView cdx], 20173e230dd2SCorentin Chary h * [cocoaView cdy]); 20183e230dd2SCorentin Chary } 20193e230dd2SCorentin Chary [cocoaView setNeedsDisplayInRect:rect]; 20205588840fSPeter Maydell }); 20213e230dd2SCorentin Chary} 20223e230dd2SCorentin Chary 2023c12aeb86SGerd Hoffmannstatic void cocoa_switch(DisplayChangeListener *dcl, 2024c12aeb86SGerd Hoffmann DisplaySurface *surface) 20253e230dd2SCorentin Chary{ 20265588840fSPeter Maydell pixman_image_t *image = surface->image; 20273e230dd2SCorentin Chary 20286e657e64SPeter Maydell COCOA_DEBUG("qemu_cocoa: cocoa_switch\n"); 20295588840fSPeter Maydell 20305588840fSPeter Maydell // The DisplaySurface will be freed as soon as this callback returns. 20315588840fSPeter Maydell // We take a reference to the underlying pixman image here so it does 20325588840fSPeter Maydell // not disappear from under our feet; the switchSurface method will 20335588840fSPeter Maydell // deref the old image when it is done with it. 20345588840fSPeter Maydell pixman_image_ref(image); 20355588840fSPeter Maydell 20365588840fSPeter Maydell dispatch_async(dispatch_get_main_queue(), ^{ 20378d65dee2SPeter Maydell [cocoaView updateUIInfo]; 20385588840fSPeter Maydell [cocoaView switchSurface:image]; 20395588840fSPeter Maydell }); 20403e230dd2SCorentin Chary} 20413e230dd2SCorentin Chary 2042bc2ed970SGerd Hoffmannstatic void cocoa_refresh(DisplayChangeListener *dcl) 20433e230dd2SCorentin Chary{ 20446e657e64SPeter Maydell NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 20456e657e64SPeter Maydell 20463e230dd2SCorentin Chary COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n"); 2047468a895bSJohn Arbuckle graphic_hw_update(NULL); 20483e230dd2SCorentin Chary 204921bae11aSGerd Hoffmann if (qemu_input_is_absolute()) { 20505588840fSPeter Maydell dispatch_async(dispatch_get_main_queue(), ^{ 20513e230dd2SCorentin Chary if (![cocoaView isAbsoluteEnabled]) { 205249b9bd4dSPeter Maydell if ([cocoaView isMouseGrabbed]) { 20533e230dd2SCorentin Chary [cocoaView ungrabMouse]; 20543e230dd2SCorentin Chary } 20553e230dd2SCorentin Chary } 20563e230dd2SCorentin Chary [cocoaView setAbsoluteEnabled:YES]; 20575588840fSPeter Maydell }); 20583e230dd2SCorentin Chary } 20597e3e20d8SAkihiko Odaki 20607e3e20d8SAkihiko Odaki if (cbchangecount != [[NSPasteboard generalPasteboard] changeCount]) { 20617e3e20d8SAkihiko Odaki qemu_clipboard_info_unref(cbinfo); 20627e3e20d8SAkihiko Odaki cbinfo = qemu_clipboard_info_new(&cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD); 20637e3e20d8SAkihiko Odaki if ([[NSPasteboard generalPasteboard] availableTypeFromArray:@[NSPasteboardTypeString]]) { 20647e3e20d8SAkihiko Odaki cbinfo->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true; 20657e3e20d8SAkihiko Odaki } 20667e3e20d8SAkihiko Odaki qemu_clipboard_update(cbinfo); 20677e3e20d8SAkihiko Odaki cbchangecount = [[NSPasteboard generalPasteboard] changeCount]; 20687e3e20d8SAkihiko Odaki qemu_event_set(&cbevent); 20697e3e20d8SAkihiko Odaki } 20707e3e20d8SAkihiko Odaki 20716e657e64SPeter Maydell [pool release]; 20723e230dd2SCorentin Chary} 20733e230dd2SCorentin Chary 20745013b9e4SGerd Hoffmannstatic void cocoa_display_init(DisplayState *ds, DisplayOptions *opts) 20753e230dd2SCorentin Chary{ 20763e230dd2SCorentin Chary COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n"); 20773e230dd2SCorentin Chary 20785588840fSPeter Maydell /* Tell main thread to go ahead and create the app and enter the run loop */ 20795588840fSPeter Maydell qemu_sem_post(&display_init_sem); 20805588840fSPeter Maydell qemu_sem_wait(&app_started_sem); 20815588840fSPeter Maydell COCOA_DEBUG("cocoa_display_init: app start completed\n"); 20825588840fSPeter Maydell 2083f844cdb9SGustavo Noronha Silva QemuCocoaAppController *controller = (QemuCocoaAppController *)[[NSApplication sharedApplication] delegate]; 208443227af8SProgrammingkid /* if fullscreen mode is to be used */ 2085767f9bf3SGerd Hoffmann if (opts->has_full_screen && opts->full_screen) { 20865588840fSPeter Maydell dispatch_async(dispatch_get_main_queue(), ^{ 208743227af8SProgrammingkid [NSApp activateIgnoringOtherApps: YES]; 2088f844cdb9SGustavo Noronha Silva [controller toggleFullScreen: nil]; 2089f844cdb9SGustavo Noronha Silva }); 2090f844cdb9SGustavo Noronha Silva } 2091f844cdb9SGustavo Noronha Silva if (opts->u.cocoa.has_full_grab && opts->u.cocoa.full_grab) { 2092f844cdb9SGustavo Noronha Silva dispatch_async(dispatch_get_main_queue(), ^{ 2093f844cdb9SGustavo Noronha Silva [controller setFullGrab: nil]; 20945588840fSPeter Maydell }); 209543227af8SProgrammingkid } 209669221df8SCarwyn Ellis 20973487da6aSGerd Hoffmann if (opts->has_show_cursor && opts->show_cursor) { 20983487da6aSGerd Hoffmann cursor_hide = 0; 20993487da6aSGerd Hoffmann } 21004797adceSGustavo Noronha Silva if (opts->u.cocoa.has_swap_opt_cmd) { 21014797adceSGustavo Noronha Silva swap_opt_cmd = opts->u.cocoa.swap_opt_cmd; 21024797adceSGustavo Noronha Silva } 210343227af8SProgrammingkid 210448941a52SCarwyn Ellis if (opts->u.cocoa.has_left_command_key && !opts->u.cocoa.left_command_key) { 210548941a52SCarwyn Ellis left_command_key_enabled = 0; 210648941a52SCarwyn Ellis } 210748941a52SCarwyn Ellis 21083e230dd2SCorentin Chary // register vga output callbacks 2109cc7859c3SAkihiko Odaki register_displaychangelistener(&dcl); 21107e3e20d8SAkihiko Odaki 21117e3e20d8SAkihiko Odaki qemu_event_init(&cbevent, false); 21127e3e20d8SAkihiko Odaki cbowner = [[QemuCocoaPasteboardTypeOwner alloc] init]; 21137e3e20d8SAkihiko Odaki qemu_clipboard_peer_register(&cbpeer); 21143e230dd2SCorentin Chary} 21155013b9e4SGerd Hoffmann 21165013b9e4SGerd Hoffmannstatic QemuDisplay qemu_display_cocoa = { 21175013b9e4SGerd Hoffmann .type = DISPLAY_TYPE_COCOA, 21185013b9e4SGerd Hoffmann .init = cocoa_display_init, 21195013b9e4SGerd Hoffmann}; 21205013b9e4SGerd Hoffmann 21215013b9e4SGerd Hoffmannstatic void register_cocoa(void) 21225013b9e4SGerd Hoffmann{ 21235013b9e4SGerd Hoffmann qemu_display_register(&qemu_display_cocoa); 21245013b9e4SGerd Hoffmann} 21255013b9e4SGerd Hoffmann 21265013b9e4SGerd Hoffmanntype_init(register_cocoa); 2127