123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178 |
- /*
- * QEMU Cocoa CG display driver
- *
- * Copyright (c) 2008 Mike Kronenberg
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
- #include "qemu/osdep.h"
- #import <Cocoa/Cocoa.h>
- #import <QuartzCore/QuartzCore.h>
- #include <crt_externs.h>
- #include "qemu/help-texts.h"
- #include "qemu-main.h"
- #include "ui/clipboard.h"
- #include "ui/console.h"
- #include "ui/input.h"
- #include "ui/kbd-state.h"
- #include "system/system.h"
- #include "system/runstate.h"
- #include "system/runstate-action.h"
- #include "system/cpu-throttle.h"
- #include "qapi/error.h"
- #include "qapi/qapi-commands-block.h"
- #include "qapi/qapi-commands-machine.h"
- #include "qapi/qapi-commands-misc.h"
- #include "system/blockdev.h"
- #include "qemu-version.h"
- #include "qemu/cutils.h"
- #include "qemu/main-loop.h"
- #include "qemu/module.h"
- #include "qemu/error-report.h"
- #include <Carbon/Carbon.h>
- #include "hw/core/cpu.h"
- #ifndef MAC_OS_VERSION_14_0
- #define MAC_OS_VERSION_14_0 140000
- #endif
- //#define DEBUG
- #ifdef DEBUG
- #define COCOA_DEBUG(...) { (void) fprintf (stdout, __VA_ARGS__); }
- #else
- #define COCOA_DEBUG(...) ((void) 0)
- #endif
- #define cgrect(nsrect) (*(CGRect *)&(nsrect))
- #define UC_CTRL_KEY "\xe2\x8c\x83"
- #define UC_ALT_KEY "\xe2\x8c\xa5"
- typedef struct {
- int width;
- int height;
- } QEMUScreen;
- @class QemuCocoaPasteboardTypeOwner;
- static void cocoa_update(DisplayChangeListener *dcl,
- int x, int y, int w, int h);
- static void cocoa_switch(DisplayChangeListener *dcl,
- DisplaySurface *surface);
- static void cocoa_refresh(DisplayChangeListener *dcl);
- static void cocoa_mouse_set(DisplayChangeListener *dcl, int x, int y, bool on);
- static void cocoa_cursor_define(DisplayChangeListener *dcl, QEMUCursor *cursor);
- static const DisplayChangeListenerOps dcl_ops = {
- .dpy_name = "cocoa",
- .dpy_gfx_update = cocoa_update,
- .dpy_gfx_switch = cocoa_switch,
- .dpy_refresh = cocoa_refresh,
- .dpy_mouse_set = cocoa_mouse_set,
- .dpy_cursor_define = cocoa_cursor_define,
- };
- static DisplayChangeListener dcl = {
- .ops = &dcl_ops,
- };
- static QKbdState *kbd;
- static int cursor_hide = 1;
- static int left_command_key_enabled = 1;
- static bool swap_opt_cmd;
- static CGInterpolationQuality zoom_interpolation = kCGInterpolationNone;
- static NSTextField *pauseLabel;
- static bool allow_events;
- static NSInteger cbchangecount = -1;
- static QemuClipboardInfo *cbinfo;
- static QemuEvent cbevent;
- static QemuCocoaPasteboardTypeOwner *cbowner;
- // Utility functions to run specified code block with the BQL held
- typedef void (^CodeBlock)(void);
- typedef bool (^BoolCodeBlock)(void);
- static void with_bql(CodeBlock block)
- {
- bool locked = bql_locked();
- if (!locked) {
- bql_lock();
- }
- block();
- if (!locked) {
- bql_unlock();
- }
- }
- static bool bool_with_bql(BoolCodeBlock block)
- {
- bool locked = bql_locked();
- bool val;
- if (!locked) {
- bql_lock();
- }
- val = block();
- if (!locked) {
- bql_unlock();
- }
- return val;
- }
- // Mac to QKeyCode conversion
- static const int mac_to_qkeycode_map[] = {
- [kVK_ANSI_A] = Q_KEY_CODE_A,
- [kVK_ANSI_B] = Q_KEY_CODE_B,
- [kVK_ANSI_C] = Q_KEY_CODE_C,
- [kVK_ANSI_D] = Q_KEY_CODE_D,
- [kVK_ANSI_E] = Q_KEY_CODE_E,
- [kVK_ANSI_F] = Q_KEY_CODE_F,
- [kVK_ANSI_G] = Q_KEY_CODE_G,
- [kVK_ANSI_H] = Q_KEY_CODE_H,
- [kVK_ANSI_I] = Q_KEY_CODE_I,
- [kVK_ANSI_J] = Q_KEY_CODE_J,
- [kVK_ANSI_K] = Q_KEY_CODE_K,
- [kVK_ANSI_L] = Q_KEY_CODE_L,
- [kVK_ANSI_M] = Q_KEY_CODE_M,
- [kVK_ANSI_N] = Q_KEY_CODE_N,
- [kVK_ANSI_O] = Q_KEY_CODE_O,
- [kVK_ANSI_P] = Q_KEY_CODE_P,
- [kVK_ANSI_Q] = Q_KEY_CODE_Q,
- [kVK_ANSI_R] = Q_KEY_CODE_R,
- [kVK_ANSI_S] = Q_KEY_CODE_S,
- [kVK_ANSI_T] = Q_KEY_CODE_T,
- [kVK_ANSI_U] = Q_KEY_CODE_U,
- [kVK_ANSI_V] = Q_KEY_CODE_V,
- [kVK_ANSI_W] = Q_KEY_CODE_W,
- [kVK_ANSI_X] = Q_KEY_CODE_X,
- [kVK_ANSI_Y] = Q_KEY_CODE_Y,
- [kVK_ANSI_Z] = Q_KEY_CODE_Z,
- [kVK_ANSI_0] = Q_KEY_CODE_0,
- [kVK_ANSI_1] = Q_KEY_CODE_1,
- [kVK_ANSI_2] = Q_KEY_CODE_2,
- [kVK_ANSI_3] = Q_KEY_CODE_3,
- [kVK_ANSI_4] = Q_KEY_CODE_4,
- [kVK_ANSI_5] = Q_KEY_CODE_5,
- [kVK_ANSI_6] = Q_KEY_CODE_6,
- [kVK_ANSI_7] = Q_KEY_CODE_7,
- [kVK_ANSI_8] = Q_KEY_CODE_8,
- [kVK_ANSI_9] = Q_KEY_CODE_9,
- [kVK_ANSI_Grave] = Q_KEY_CODE_GRAVE_ACCENT,
- [kVK_ANSI_Minus] = Q_KEY_CODE_MINUS,
- [kVK_ANSI_Equal] = Q_KEY_CODE_EQUAL,
- [kVK_Delete] = Q_KEY_CODE_BACKSPACE,
- [kVK_CapsLock] = Q_KEY_CODE_CAPS_LOCK,
- [kVK_Tab] = Q_KEY_CODE_TAB,
- [kVK_Return] = Q_KEY_CODE_RET,
- [kVK_ANSI_LeftBracket] = Q_KEY_CODE_BRACKET_LEFT,
- [kVK_ANSI_RightBracket] = Q_KEY_CODE_BRACKET_RIGHT,
- [kVK_ANSI_Backslash] = Q_KEY_CODE_BACKSLASH,
- [kVK_ANSI_Semicolon] = Q_KEY_CODE_SEMICOLON,
- [kVK_ANSI_Quote] = Q_KEY_CODE_APOSTROPHE,
- [kVK_ANSI_Comma] = Q_KEY_CODE_COMMA,
- [kVK_ANSI_Period] = Q_KEY_CODE_DOT,
- [kVK_ANSI_Slash] = Q_KEY_CODE_SLASH,
- [kVK_Space] = Q_KEY_CODE_SPC,
- [kVK_ANSI_Keypad0] = Q_KEY_CODE_KP_0,
- [kVK_ANSI_Keypad1] = Q_KEY_CODE_KP_1,
- [kVK_ANSI_Keypad2] = Q_KEY_CODE_KP_2,
- [kVK_ANSI_Keypad3] = Q_KEY_CODE_KP_3,
- [kVK_ANSI_Keypad4] = Q_KEY_CODE_KP_4,
- [kVK_ANSI_Keypad5] = Q_KEY_CODE_KP_5,
- [kVK_ANSI_Keypad6] = Q_KEY_CODE_KP_6,
- [kVK_ANSI_Keypad7] = Q_KEY_CODE_KP_7,
- [kVK_ANSI_Keypad8] = Q_KEY_CODE_KP_8,
- [kVK_ANSI_Keypad9] = Q_KEY_CODE_KP_9,
- [kVK_ANSI_KeypadDecimal] = Q_KEY_CODE_KP_DECIMAL,
- [kVK_ANSI_KeypadEnter] = Q_KEY_CODE_KP_ENTER,
- [kVK_ANSI_KeypadPlus] = Q_KEY_CODE_KP_ADD,
- [kVK_ANSI_KeypadMinus] = Q_KEY_CODE_KP_SUBTRACT,
- [kVK_ANSI_KeypadMultiply] = Q_KEY_CODE_KP_MULTIPLY,
- [kVK_ANSI_KeypadDivide] = Q_KEY_CODE_KP_DIVIDE,
- [kVK_ANSI_KeypadEquals] = Q_KEY_CODE_KP_EQUALS,
- [kVK_ANSI_KeypadClear] = Q_KEY_CODE_NUM_LOCK,
- [kVK_UpArrow] = Q_KEY_CODE_UP,
- [kVK_DownArrow] = Q_KEY_CODE_DOWN,
- [kVK_LeftArrow] = Q_KEY_CODE_LEFT,
- [kVK_RightArrow] = Q_KEY_CODE_RIGHT,
- [kVK_Help] = Q_KEY_CODE_INSERT,
- [kVK_Home] = Q_KEY_CODE_HOME,
- [kVK_PageUp] = Q_KEY_CODE_PGUP,
- [kVK_PageDown] = Q_KEY_CODE_PGDN,
- [kVK_End] = Q_KEY_CODE_END,
- [kVK_ForwardDelete] = Q_KEY_CODE_DELETE,
- [kVK_Escape] = Q_KEY_CODE_ESC,
- /* The Power key can't be used directly because the operating system uses
- * it. This key can be emulated by using it in place of another key such as
- * F1. Don't forget to disable the real key binding.
- */
- /* [kVK_F1] = Q_KEY_CODE_POWER, */
- [kVK_F1] = Q_KEY_CODE_F1,
- [kVK_F2] = Q_KEY_CODE_F2,
- [kVK_F3] = Q_KEY_CODE_F3,
- [kVK_F4] = Q_KEY_CODE_F4,
- [kVK_F5] = Q_KEY_CODE_F5,
- [kVK_F6] = Q_KEY_CODE_F6,
- [kVK_F7] = Q_KEY_CODE_F7,
- [kVK_F8] = Q_KEY_CODE_F8,
- [kVK_F9] = Q_KEY_CODE_F9,
- [kVK_F10] = Q_KEY_CODE_F10,
- [kVK_F11] = Q_KEY_CODE_F11,
- [kVK_F12] = Q_KEY_CODE_F12,
- [kVK_F13] = Q_KEY_CODE_PRINT,
- [kVK_F14] = Q_KEY_CODE_SCROLL_LOCK,
- [kVK_F15] = Q_KEY_CODE_PAUSE,
- // JIS keyboards only
- [kVK_JIS_Yen] = Q_KEY_CODE_YEN,
- [kVK_JIS_Underscore] = Q_KEY_CODE_RO,
- [kVK_JIS_KeypadComma] = Q_KEY_CODE_KP_COMMA,
- [kVK_JIS_Eisu] = Q_KEY_CODE_MUHENKAN,
- [kVK_JIS_Kana] = Q_KEY_CODE_HENKAN,
- /*
- * The eject and volume keys can't be used here because they are handled at
- * a lower level than what an Application can see.
- */
- };
- static int cocoa_keycode_to_qemu(int keycode)
- {
- if (ARRAY_SIZE(mac_to_qkeycode_map) <= keycode) {
- error_report("(cocoa) warning unknown keycode 0x%x", keycode);
- return 0;
- }
- return mac_to_qkeycode_map[keycode];
- }
- /* Displays an alert dialog box with the specified message */
- static void QEMU_Alert(NSString *message)
- {
- NSAlert *alert;
- alert = [NSAlert new];
- [alert setMessageText: message];
- [alert runModal];
- }
- /* Handles any errors that happen with a device transaction */
- static void handleAnyDeviceErrors(Error * err)
- {
- if (err) {
- QEMU_Alert([NSString stringWithCString: error_get_pretty(err)
- encoding: NSASCIIStringEncoding]);
- error_free(err);
- }
- }
- /*
- ------------------------------------------------------
- QemuCocoaView
- ------------------------------------------------------
- */
- @interface QemuCocoaView : NSView
- {
- QEMUScreen screen;
- pixman_image_t *pixman_image;
- /* The state surrounding mouse grabbing is potentially confusing.
- * isAbsoluteEnabled tracks qemu_input_is_absolute() [ie "is the emulated
- * pointing device an absolute-position one?"], but is only updated on
- * next refresh.
- * isMouseGrabbed tracks whether GUI events are directed to the guest;
- * it controls whether special keys like Cmd get sent to the guest,
- * and whether we capture the mouse when in non-absolute mode.
- */
- BOOL isMouseGrabbed;
- BOOL isAbsoluteEnabled;
- CFMachPortRef eventsTap;
- CGColorSpaceRef colorspace;
- CALayer *cursorLayer;
- QEMUCursor *cursor;
- int mouseX;
- int mouseY;
- bool mouseOn;
- }
- - (void) switchSurface:(pixman_image_t *)image;
- - (void) grabMouse;
- - (void) ungrabMouse;
- - (void) setFullGrab:(id)sender;
- - (void) handleMonitorInput:(NSEvent *)event;
- - (bool) handleEvent:(NSEvent *)event;
- - (bool) handleEventLocked:(NSEvent *)event;
- - (void) notifyMouseModeChange;
- - (BOOL) isMouseGrabbed;
- - (QEMUScreen) gscreen;
- - (void) raiseAllKeys;
- @end
- QemuCocoaView *cocoaView;
- static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEventRef cgEvent, void *userInfo)
- {
- QemuCocoaView *view = userInfo;
- NSEvent *event = [NSEvent eventWithCGEvent:cgEvent];
- if ([view isMouseGrabbed] && [view handleEvent:event]) {
- COCOA_DEBUG("Global events tap: qemu handled the event, capturing!\n");
- return NULL;
- }
- COCOA_DEBUG("Global events tap: qemu did not handle the event, letting it through...\n");
- return cgEvent;
- }
- @implementation QemuCocoaView
- - (id)initWithFrame:(NSRect)frameRect
- {
- COCOA_DEBUG("QemuCocoaView: initWithFrame\n");
- self = [super initWithFrame:frameRect];
- if (self) {
- NSTrackingAreaOptions options = NSTrackingActiveInKeyWindow |
- NSTrackingMouseEnteredAndExited |
- NSTrackingMouseMoved |
- NSTrackingInVisibleRect;
- NSTrackingArea *trackingArea =
- [[NSTrackingArea alloc] initWithRect:CGRectZero
- options:options
- owner:self
- userInfo:nil];
- [self addTrackingArea:trackingArea];
- [trackingArea release];
- screen.width = frameRect.size.width;
- screen.height = frameRect.size.height;
- colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
- #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_14_0
- [self setClipsToBounds:YES];
- #endif
- [self setWantsLayer:YES];
- cursorLayer = [[CALayer alloc] init];
- [cursorLayer setAnchorPoint:CGPointMake(0, 1)];
- [cursorLayer setAutoresizingMask:kCALayerMaxXMargin |
- kCALayerMinYMargin];
- [[self layer] addSublayer:cursorLayer];
- }
- return self;
- }
- - (void) dealloc
- {
- COCOA_DEBUG("QemuCocoaView: dealloc\n");
- if (pixman_image) {
- pixman_image_unref(pixman_image);
- }
- if (eventsTap) {
- CFRelease(eventsTap);
- }
- CGColorSpaceRelease(colorspace);
- [cursorLayer release];
- cursor_unref(cursor);
- [super dealloc];
- }
- - (BOOL) isOpaque
- {
- return YES;
- }
- - (void) viewDidMoveToWindow
- {
- [self resizeWindow];
- }
- - (void) selectConsoleLocked:(unsigned int)index
- {
- QemuConsole *con = qemu_console_lookup_by_index(index);
- if (!con) {
- return;
- }
- unregister_displaychangelistener(&dcl);
- qkbd_state_switch_console(kbd, con);
- dcl.con = con;
- register_displaychangelistener(&dcl);
- [self notifyMouseModeChange];
- [self updateUIInfo];
- }
- - (void) hideCursor
- {
- if (!cursor_hide) {
- return;
- }
- [NSCursor hide];
- }
- - (void) unhideCursor
- {
- if (!cursor_hide) {
- return;
- }
- [NSCursor unhide];
- }
- - (void)setMouseX:(int)x y:(int)y on:(bool)on
- {
- CGPoint position;
- mouseX = x;
- mouseY = y;
- mouseOn = on;
- position.x = mouseX;
- position.y = screen.height - mouseY;
- [CATransaction begin];
- [CATransaction setDisableActions:YES];
- [cursorLayer setPosition:position];
- [cursorLayer setHidden:!mouseOn];
- [CATransaction commit];
- }
- - (void)setCursor:(QEMUCursor *)given_cursor
- {
- CGDataProviderRef provider;
- CGImageRef image;
- CGRect bounds = CGRectZero;
- cursor_unref(cursor);
- cursor = given_cursor;
- if (!cursor) {
- return;
- }
- cursor_ref(cursor);
- bounds.size.width = cursor->width;
- bounds.size.height = cursor->height;
- provider = CGDataProviderCreateWithData(
- NULL,
- cursor->data,
- cursor->width * cursor->height * 4,
- NULL
- );
- image = CGImageCreate(
- cursor->width, //width
- cursor->height, //height
- 8, //bitsPerComponent
- 32, //bitsPerPixel
- cursor->width * 4, //bytesPerRow
- colorspace, //colorspace
- kCGBitmapByteOrder32Little | kCGImageAlphaFirst, //bitmapInfo
- provider, //provider
- NULL, //decode
- 0, //interpolate
- kCGRenderingIntentDefault //intent
- );
- CGDataProviderRelease(provider);
- [CATransaction begin];
- [CATransaction setDisableActions:YES];
- [cursorLayer setBounds:bounds];
- [cursorLayer setContents:(id)image];
- [CATransaction commit];
- CGImageRelease(image);
- }
- - (void) drawRect:(NSRect) rect
- {
- COCOA_DEBUG("QemuCocoaView: drawRect\n");
- // get CoreGraphic context
- CGContextRef viewContextRef = [[NSGraphicsContext currentContext] CGContext];
- CGContextSetInterpolationQuality (viewContextRef, zoom_interpolation);
- CGContextSetShouldAntialias (viewContextRef, NO);
- // draw screen bitmap directly to Core Graphics context
- if (!pixman_image) {
- // Draw request before any guest device has set up a framebuffer:
- // just draw an opaque black rectangle
- CGContextSetRGBFillColor(viewContextRef, 0, 0, 0, 1.0);
- CGContextFillRect(viewContextRef, NSRectToCGRect(rect));
- } else {
- int w = pixman_image_get_width(pixman_image);
- int h = pixman_image_get_height(pixman_image);
- int bitsPerPixel = PIXMAN_FORMAT_BPP(pixman_image_get_format(pixman_image));
- int stride = pixman_image_get_stride(pixman_image);
- CGDataProviderRef dataProviderRef = CGDataProviderCreateWithData(
- NULL,
- pixman_image_get_data(pixman_image),
- stride * h,
- NULL
- );
- CGImageRef imageRef = CGImageCreate(
- w, //width
- h, //height
- DIV_ROUND_UP(bitsPerPixel, 8) * 2, //bitsPerComponent
- bitsPerPixel, //bitsPerPixel
- stride, //bytesPerRow
- colorspace, //colorspace
- kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, //bitmapInfo
- dataProviderRef, //provider
- NULL, //decode
- 0, //interpolate
- kCGRenderingIntentDefault //intent
- );
- // selective drawing code (draws only dirty rectangles) (OS X >= 10.4)
- const NSRect *rectList;
- NSInteger rectCount;
- int i;
- CGImageRef clipImageRef;
- CGRect clipRect;
- [self getRectsBeingDrawn:&rectList count:&rectCount];
- for (i = 0; i < rectCount; i++) {
- clipRect = rectList[i];
- clipRect.origin.y = (float)h - (clipRect.origin.y + clipRect.size.height);
- clipImageRef = CGImageCreateWithImageInRect(
- imageRef,
- clipRect
- );
- CGContextDrawImage (viewContextRef, cgrect(rectList[i]), clipImageRef);
- CGImageRelease (clipImageRef);
- }
- CGImageRelease (imageRef);
- CGDataProviderRelease(dataProviderRef);
- }
- }
- - (NSSize)fixAspectRatio:(NSSize)max
- {
- NSSize scaled;
- NSSize fixed;
- scaled.width = screen.width * max.height;
- scaled.height = screen.height * max.width;
- /*
- * Here screen is our guest's output size, and max is the size of the
- * largest possible area of the screen we can display on.
- * We want to scale up (screen.width x screen.height) by either:
- * 1) max.height / screen.height
- * 2) max.width / screen.width
- * With the first scale factor the scale will result in an output height of
- * max.height (i.e. we will fill the whole height of the available screen
- * space and have black bars left and right) and with the second scale
- * factor the scaling will result in an output width of max.width (i.e. we
- * fill the whole width of the available screen space and have black bars
- * top and bottom). We need to pick whichever keeps the whole of the guest
- * output on the screen, which is to say the smaller of the two scale
- * factors.
- * To avoid doing more division than strictly necessary, instead of directly
- * comparing scale factors 1 and 2 we instead calculate and compare those
- * two scale factors multiplied by (screen.height * screen.width).
- */
- if (scaled.width < scaled.height) {
- fixed.width = scaled.width / screen.height;
- fixed.height = max.height;
- } else {
- fixed.width = max.width;
- fixed.height = scaled.height / screen.width;
- }
- return fixed;
- }
- - (NSSize) screenSafeAreaSize
- {
- NSSize size = [[[self window] screen] frame].size;
- NSEdgeInsets insets = [[[self window] screen] safeAreaInsets];
- size.width -= insets.left + insets.right;
- size.height -= insets.top + insets.bottom;
- return size;
- }
- - (void) resizeWindow
- {
- [[self window] setContentAspectRatio:NSMakeSize(screen.width, screen.height)];
- if (!([[self window] styleMask] & NSWindowStyleMaskResizable)) {
- [[self window] setContentSize:NSMakeSize(screen.width, screen.height)];
- [[self window] center];
- } else if ([[self window] styleMask] & NSWindowStyleMaskFullScreen) {
- [[self window] setContentSize:[self fixAspectRatio:[self screenSafeAreaSize]]];
- [[self window] center];
- } else {
- [[self window] setContentSize:[self fixAspectRatio:[self frame].size]];
- }
- }
- - (void) updateBounds
- {
- [self setBoundsSize:NSMakeSize(screen.width, screen.height)];
- }
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wdeprecated-declarations"
- - (void) updateUIInfoLocked
- {
- /* Must be called with the BQL, i.e. via updateUIInfo */
- NSSize frameSize;
- QemuUIInfo info;
- if (!qemu_console_is_graphic(dcl.con)) {
- return;
- }
- if ([self window]) {
- NSDictionary *description = [[[self window] screen] deviceDescription];
- CGDirectDisplayID display = [[description objectForKey:@"NSScreenNumber"] unsignedIntValue];
- NSSize screenSize = [[[self window] screen] frame].size;
- CGSize screenPhysicalSize = CGDisplayScreenSize(display);
- bool isFullscreen = ([[self window] styleMask] & NSWindowStyleMaskFullScreen) != 0;
- CVDisplayLinkRef displayLink;
- frameSize = isFullscreen ? [self screenSafeAreaSize] : [self frame].size;
- if (!CVDisplayLinkCreateWithCGDisplay(display, &displayLink)) {
- CVTime period = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(displayLink);
- CVDisplayLinkRelease(displayLink);
- if (!(period.flags & kCVTimeIsIndefinite)) {
- update_displaychangelistener(&dcl,
- 1000 * period.timeValue / period.timeScale);
- info.refresh_rate = (int64_t)1000 * period.timeScale / period.timeValue;
- }
- }
- info.width_mm = frameSize.width / screenSize.width * screenPhysicalSize.width;
- info.height_mm = frameSize.height / screenSize.height * screenPhysicalSize.height;
- } else {
- frameSize = [self frame].size;
- info.width_mm = 0;
- info.height_mm = 0;
- }
- info.xoff = 0;
- info.yoff = 0;
- info.width = frameSize.width;
- info.height = frameSize.height;
- dpy_set_ui_info(dcl.con, &info, TRUE);
- }
- #pragma clang diagnostic pop
- - (void) updateUIInfo
- {
- if (!allow_events) {
- /*
- * Don't try to tell QEMU about UI information in the application
- * startup phase -- we haven't yet registered dcl with the QEMU UI
- * layer.
- * When cocoa_display_init() does register the dcl, the UI layer
- * will call cocoa_switch(), which will call updateUIInfo, so
- * we don't lose any information here.
- */
- return;
- }
- with_bql(^{
- [self updateUIInfoLocked];
- });
- }
- - (void) switchSurface:(pixman_image_t *)image
- {
- COCOA_DEBUG("QemuCocoaView: switchSurface\n");
- int w = pixman_image_get_width(image);
- int h = pixman_image_get_height(image);
- if (w != screen.width || h != screen.height) {
- // Resize before we trigger the redraw, or we'll redraw at the wrong size
- COCOA_DEBUG("switchSurface: new size %d x %d\n", w, h);
- screen.width = w;
- screen.height = h;
- [self resizeWindow];
- [self updateBounds];
- }
- // update screenBuffer
- if (pixman_image) {
- pixman_image_unref(pixman_image);
- }
- pixman_image = image;
- }
- - (void) setFullGrab:(id)sender
- {
- COCOA_DEBUG("QemuCocoaView: setFullGrab\n");
- CGEventMask mask = CGEventMaskBit(kCGEventKeyDown) | CGEventMaskBit(kCGEventKeyUp) | CGEventMaskBit(kCGEventFlagsChanged);
- eventsTap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault,
- mask, handleTapEvent, self);
- if (!eventsTap) {
- warn_report("Could not create event tap, system key combos will not be captured.\n");
- return;
- } else {
- COCOA_DEBUG("Global events tap created! Will capture system key combos.\n");
- }
- CFRunLoopRef runLoop = CFRunLoopGetCurrent();
- if (!runLoop) {
- warn_report("Could not obtain current CF RunLoop, system key combos will not be captured.\n");
- return;
- }
- CFRunLoopSourceRef tapEventsSrc = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventsTap, 0);
- if (!tapEventsSrc ) {
- warn_report("Could not obtain current CF RunLoop, system key combos will not be captured.\n");
- return;
- }
- CFRunLoopAddSource(runLoop, tapEventsSrc, kCFRunLoopDefaultMode);
- CFRelease(tapEventsSrc);
- }
- - (void) toggleKey: (int)keycode {
- qkbd_state_key_event(kbd, keycode, !qkbd_state_key_get(kbd, keycode));
- }
- // Does the work of sending input to the monitor
- - (void) handleMonitorInput:(NSEvent *)event
- {
- int keysym = 0;
- int control_key = 0;
- // if the control key is down
- if ([event modifierFlags] & NSEventModifierFlagControl) {
- control_key = 1;
- }
- /* translates Macintosh keycodes to QEMU's keysym */
- static const int without_control_translation[] = {
- [0 ... 0xff] = 0, // invalid key
- [kVK_UpArrow] = QEMU_KEY_UP,
- [kVK_DownArrow] = QEMU_KEY_DOWN,
- [kVK_RightArrow] = QEMU_KEY_RIGHT,
- [kVK_LeftArrow] = QEMU_KEY_LEFT,
- [kVK_Home] = QEMU_KEY_HOME,
- [kVK_End] = QEMU_KEY_END,
- [kVK_PageUp] = QEMU_KEY_PAGEUP,
- [kVK_PageDown] = QEMU_KEY_PAGEDOWN,
- [kVK_ForwardDelete] = QEMU_KEY_DELETE,
- [kVK_Delete] = QEMU_KEY_BACKSPACE,
- };
- static const int with_control_translation[] = {
- [0 ... 0xff] = 0, // invalid key
- [kVK_UpArrow] = QEMU_KEY_CTRL_UP,
- [kVK_DownArrow] = QEMU_KEY_CTRL_DOWN,
- [kVK_RightArrow] = QEMU_KEY_CTRL_RIGHT,
- [kVK_LeftArrow] = QEMU_KEY_CTRL_LEFT,
- [kVK_Home] = QEMU_KEY_CTRL_HOME,
- [kVK_End] = QEMU_KEY_CTRL_END,
- [kVK_PageUp] = QEMU_KEY_CTRL_PAGEUP,
- [kVK_PageDown] = QEMU_KEY_CTRL_PAGEDOWN,
- };
- if (control_key != 0) { /* If the control key is being used */
- if ([event keyCode] < ARRAY_SIZE(with_control_translation)) {
- keysym = with_control_translation[[event keyCode]];
- }
- } else {
- if ([event keyCode] < ARRAY_SIZE(without_control_translation)) {
- keysym = without_control_translation[[event keyCode]];
- }
- }
- // if not a key that needs translating
- if (keysym == 0) {
- NSString *ks = [event characters];
- if ([ks length] > 0) {
- keysym = [ks characterAtIndex:0];
- }
- }
- if (keysym) {
- QemuTextConsole *con = QEMU_TEXT_CONSOLE(dcl.con);
- qemu_text_console_put_keysym(con, keysym);
- }
- }
- - (bool) handleEvent:(NSEvent *)event
- {
- return bool_with_bql(^{
- return [self handleEventLocked:event];
- });
- }
- - (bool) handleEventLocked:(NSEvent *)event
- {
- /* Return true if we handled the event, false if it should be given to OSX */
- COCOA_DEBUG("QemuCocoaView: handleEvent\n");
- InputButton button;
- int keycode = 0;
- NSUInteger modifiers = [event modifierFlags];
- /*
- * Check -[NSEvent modifierFlags] here.
- *
- * There is a NSEventType for an event notifying the change of
- * -[NSEvent modifierFlags], NSEventTypeFlagsChanged but these operations
- * are performed for any events because a modifier state may change while
- * the application is inactive (i.e. no events fire) and we don't want to
- * wait for another modifier state change to detect such a change.
- *
- * NSEventModifierFlagCapsLock requires a special treatment. The other flags
- * are handled in similar manners.
- *
- * NSEventModifierFlagCapsLock
- * ---------------------------
- *
- * If CapsLock state is changed, "up" and "down" events will be fired in
- * sequence, effectively updates CapsLock state on the guest.
- *
- * The other flags
- * ---------------
- *
- * If a flag is not set, fire "up" events for all keys which correspond to
- * the flag. Note that "down" events are not fired here because the flags
- * checked here do not tell what exact keys are down.
- *
- * If one of the keys corresponding to a flag is down, we rely on
- * -[NSEvent keyCode] of an event whose -[NSEvent type] is
- * NSEventTypeFlagsChanged to know the exact key which is down, which has
- * the following two downsides:
- * - It does not work when the application is inactive as described above.
- * - It malfactions *after* the modifier state is changed while the
- * application is inactive. It is because -[NSEvent keyCode] does not tell
- * if the key is up or down, and requires to infer the current state from
- * the previous state. It is still possible to fix such a malfanction by
- * completely leaving your hands from the keyboard, which hopefully makes
- * this implementation usable enough.
- */
- if (!!(modifiers & NSEventModifierFlagCapsLock) !=
- qkbd_state_modifier_get(kbd, QKBD_MOD_CAPSLOCK)) {
- qkbd_state_key_event(kbd, Q_KEY_CODE_CAPS_LOCK, true);
- qkbd_state_key_event(kbd, Q_KEY_CODE_CAPS_LOCK, false);
- }
- if (!(modifiers & NSEventModifierFlagShift)) {
- qkbd_state_key_event(kbd, Q_KEY_CODE_SHIFT, false);
- qkbd_state_key_event(kbd, Q_KEY_CODE_SHIFT_R, false);
- }
- if (!(modifiers & NSEventModifierFlagControl)) {
- qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL, false);
- qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL_R, false);
- }
- if (!(modifiers & NSEventModifierFlagOption)) {
- if (swap_opt_cmd) {
- qkbd_state_key_event(kbd, Q_KEY_CODE_META_L, false);
- qkbd_state_key_event(kbd, Q_KEY_CODE_META_R, false);
- } else {
- qkbd_state_key_event(kbd, Q_KEY_CODE_ALT, false);
- qkbd_state_key_event(kbd, Q_KEY_CODE_ALT_R, false);
- }
- }
- if (!(modifiers & NSEventModifierFlagCommand)) {
- if (swap_opt_cmd) {
- qkbd_state_key_event(kbd, Q_KEY_CODE_ALT, false);
- qkbd_state_key_event(kbd, Q_KEY_CODE_ALT_R, false);
- } else {
- qkbd_state_key_event(kbd, Q_KEY_CODE_META_L, false);
- qkbd_state_key_event(kbd, Q_KEY_CODE_META_R, false);
- }
- }
- switch ([event type]) {
- case NSEventTypeFlagsChanged:
- switch ([event keyCode]) {
- case kVK_Shift:
- if (!!(modifiers & NSEventModifierFlagShift)) {
- [self toggleKey:Q_KEY_CODE_SHIFT];
- }
- break;
- case kVK_RightShift:
- if (!!(modifiers & NSEventModifierFlagShift)) {
- [self toggleKey:Q_KEY_CODE_SHIFT_R];
- }
- break;
- case kVK_Control:
- if (!!(modifiers & NSEventModifierFlagControl)) {
- [self toggleKey:Q_KEY_CODE_CTRL];
- }
- break;
- case kVK_RightControl:
- if (!!(modifiers & NSEventModifierFlagControl)) {
- [self toggleKey:Q_KEY_CODE_CTRL_R];
- }
- break;
- case kVK_Option:
- if (!!(modifiers & NSEventModifierFlagOption)) {
- if (swap_opt_cmd) {
- [self toggleKey:Q_KEY_CODE_META_L];
- } else {
- [self toggleKey:Q_KEY_CODE_ALT];
- }
- }
- break;
- case kVK_RightOption:
- if (!!(modifiers & NSEventModifierFlagOption)) {
- if (swap_opt_cmd) {
- [self toggleKey:Q_KEY_CODE_META_R];
- } else {
- [self toggleKey:Q_KEY_CODE_ALT_R];
- }
- }
- break;
- /* Don't pass command key changes to guest unless mouse is grabbed */
- case kVK_Command:
- if (isMouseGrabbed &&
- !!(modifiers & NSEventModifierFlagCommand) &&
- left_command_key_enabled) {
- if (swap_opt_cmd) {
- [self toggleKey:Q_KEY_CODE_ALT];
- } else {
- [self toggleKey:Q_KEY_CODE_META_L];
- }
- }
- break;
- case kVK_RightCommand:
- if (isMouseGrabbed &&
- !!(modifiers & NSEventModifierFlagCommand)) {
- if (swap_opt_cmd) {
- [self toggleKey:Q_KEY_CODE_ALT_R];
- } else {
- [self toggleKey:Q_KEY_CODE_META_R];
- }
- }
- break;
- }
- return true;
- case NSEventTypeKeyDown:
- keycode = cocoa_keycode_to_qemu([event keyCode]);
- // forward command key combos to the host UI unless the mouse is grabbed
- if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) {
- return false;
- }
- // default
- // handle control + alt Key Combos (ctrl+alt+[1..9,g] is reserved for QEMU)
- if (([event modifierFlags] & NSEventModifierFlagControl) && ([event modifierFlags] & NSEventModifierFlagOption)) {
- NSString *keychar = [event charactersIgnoringModifiers];
- if ([keychar length] == 1) {
- char key = [keychar characterAtIndex:0];
- switch (key) {
- // enable graphic console
- case '1' ... '9':
- [self selectConsoleLocked:key - '0' - 1]; /* ascii math */
- return true;
- // release the mouse grab
- case 'g':
- [self ungrabMouse];
- return true;
- }
- }
- }
- if (qemu_console_is_graphic(dcl.con)) {
- qkbd_state_key_event(kbd, keycode, true);
- } else {
- [self handleMonitorInput: event];
- }
- return true;
- case NSEventTypeKeyUp:
- keycode = cocoa_keycode_to_qemu([event keyCode]);
- // don't pass the guest a spurious key-up if we treated this
- // command-key combo as a host UI action
- if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) {
- return true;
- }
- if (qemu_console_is_graphic(dcl.con)) {
- qkbd_state_key_event(kbd, keycode, false);
- }
- return true;
- case NSEventTypeScrollWheel:
- /*
- * Send wheel events to the guest regardless of window focus.
- * This is in-line with standard Mac OS X UI behaviour.
- */
- /* Determine if this is a scroll up or scroll down event */
- if ([event deltaY] != 0) {
- button = ([event deltaY] > 0) ?
- INPUT_BUTTON_WHEEL_UP : INPUT_BUTTON_WHEEL_DOWN;
- } else if ([event deltaX] != 0) {
- button = ([event deltaX] > 0) ?
- INPUT_BUTTON_WHEEL_LEFT : INPUT_BUTTON_WHEEL_RIGHT;
- } else {
- /*
- * We shouldn't have got a scroll event when deltaY and delta Y
- * are zero, hence no harm in dropping the event
- */
- return true;
- }
- qemu_input_queue_btn(dcl.con, button, true);
- qemu_input_event_sync();
- qemu_input_queue_btn(dcl.con, button, false);
- qemu_input_event_sync();
- return true;
- default:
- return false;
- }
- }
- - (void) handleMouseEvent:(NSEvent *)event button:(InputButton)button down:(bool)down
- {
- if (!isMouseGrabbed) {
- return;
- }
- with_bql(^{
- qemu_input_queue_btn(dcl.con, button, down);
- });
- [self handleMouseEvent:event];
- }
- - (void) handleMouseEvent:(NSEvent *)event
- {
- if (!isMouseGrabbed) {
- return;
- }
- with_bql(^{
- if (isAbsoluteEnabled) {
- CGFloat d = (CGFloat)screen.height / [self frame].size.height;
- NSPoint p = [event locationInWindow];
- /* Note that the origin for Cocoa mouse coords is bottom left, not top left. */
- qemu_input_queue_abs(dcl.con, INPUT_AXIS_X, p.x * d, 0, screen.width);
- qemu_input_queue_abs(dcl.con, INPUT_AXIS_Y, screen.height - p.y * d, 0, screen.height);
- } else {
- qemu_input_queue_rel(dcl.con, INPUT_AXIS_X, [event deltaX]);
- qemu_input_queue_rel(dcl.con, INPUT_AXIS_Y, [event deltaY]);
- }
- qemu_input_event_sync();
- });
- }
- - (void) mouseExited:(NSEvent *)event
- {
- if (isAbsoluteEnabled && isMouseGrabbed) {
- [self ungrabMouse];
- }
- }
- - (void) mouseEntered:(NSEvent *)event
- {
- if (isAbsoluteEnabled && !isMouseGrabbed) {
- [self grabMouse];
- }
- }
- - (void) mouseMoved:(NSEvent *)event
- {
- [self handleMouseEvent:event];
- }
- - (void) mouseDown:(NSEvent *)event
- {
- [self handleMouseEvent:event button:INPUT_BUTTON_LEFT down:true];
- }
- - (void) rightMouseDown:(NSEvent *)event
- {
- [self handleMouseEvent:event button:INPUT_BUTTON_RIGHT down:true];
- }
- - (void) otherMouseDown:(NSEvent *)event
- {
- [self handleMouseEvent:event button:INPUT_BUTTON_MIDDLE down:true];
- }
- - (void) mouseDragged:(NSEvent *)event
- {
- [self handleMouseEvent:event];
- }
- - (void) rightMouseDragged:(NSEvent *)event
- {
- [self handleMouseEvent:event];
- }
- - (void) otherMouseDragged:(NSEvent *)event
- {
- [self handleMouseEvent:event];
- }
- - (void) mouseUp:(NSEvent *)event
- {
- if (!isMouseGrabbed) {
- [self grabMouse];
- }
- [self handleMouseEvent:event button:INPUT_BUTTON_LEFT down:false];
- }
- - (void) rightMouseUp:(NSEvent *)event
- {
- [self handleMouseEvent:event button:INPUT_BUTTON_RIGHT down:false];
- }
- - (void) otherMouseUp:(NSEvent *)event
- {
- [self handleMouseEvent:event button:INPUT_BUTTON_MIDDLE down:false];
- }
- - (void) grabMouse
- {
- COCOA_DEBUG("QemuCocoaView: grabMouse\n");
- if (qemu_name)
- [[self window] setTitle:[NSString stringWithFormat:@"QEMU %s - (Press " UC_CTRL_KEY " " UC_ALT_KEY " G to release Mouse)", qemu_name]];
- else
- [[self window] setTitle:@"QEMU - (Press " UC_CTRL_KEY " " UC_ALT_KEY " G to release Mouse)"];
- [self hideCursor];
- CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled);
- isMouseGrabbed = TRUE; // while isMouseGrabbed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:]
- }
- - (void) ungrabMouse
- {
- COCOA_DEBUG("QemuCocoaView: ungrabMouse\n");
- if (qemu_name)
- [[self window] setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]];
- else
- [[self window] setTitle:@"QEMU"];
- [self unhideCursor];
- CGAssociateMouseAndMouseCursorPosition(TRUE);
- isMouseGrabbed = FALSE;
- [self raiseAllButtons];
- }
- - (void) notifyMouseModeChange {
- bool tIsAbsoluteEnabled = bool_with_bql(^{
- return qemu_input_is_absolute(dcl.con);
- });
- if (tIsAbsoluteEnabled == isAbsoluteEnabled) {
- return;
- }
- isAbsoluteEnabled = tIsAbsoluteEnabled;
- if (isMouseGrabbed) {
- if (isAbsoluteEnabled) {
- [self ungrabMouse];
- } else {
- CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled);
- }
- }
- }
- - (BOOL) isMouseGrabbed {return isMouseGrabbed;}
- - (QEMUScreen) gscreen {return screen;}
- /*
- * Makes the target think all down keys are being released.
- * This prevents a stuck key problem, since we will not see
- * key up events for those keys after we have lost focus.
- */
- - (void) raiseAllKeys
- {
- with_bql(^{
- qkbd_state_lift_all_keys(kbd);
- });
- }
- - (void) raiseAllButtons
- {
- with_bql(^{
- qemu_input_queue_btn(dcl.con, INPUT_BUTTON_LEFT, false);
- qemu_input_queue_btn(dcl.con, INPUT_BUTTON_RIGHT, false);
- qemu_input_queue_btn(dcl.con, INPUT_BUTTON_MIDDLE, false);
- });
- }
- @end
- /*
- ------------------------------------------------------
- QemuCocoaAppController
- ------------------------------------------------------
- */
- @interface QemuCocoaAppController : NSObject
- <NSWindowDelegate, NSApplicationDelegate>
- {
- }
- - (void)doToggleFullScreen:(id)sender;
- - (void)showQEMUDoc:(id)sender;
- - (void)zoomToFit:(id) sender;
- - (void)displayConsole:(id)sender;
- - (void)pauseQEMU:(id)sender;
- - (void)resumeQEMU:(id)sender;
- - (void)displayPause;
- - (void)removePause;
- - (void)restartQEMU:(id)sender;
- - (void)powerDownQEMU:(id)sender;
- - (void)ejectDeviceMedia:(id)sender;
- - (void)changeDeviceMedia:(id)sender;
- - (BOOL)verifyQuit;
- - (void)openDocumentation:(NSString *)filename;
- - (IBAction) do_about_menu_item: (id) sender;
- - (void)adjustSpeed:(id)sender;
- @end
- @implementation QemuCocoaAppController
- - (id) init
- {
- NSWindow *window;
- COCOA_DEBUG("QemuCocoaAppController: init\n");
- self = [super init];
- if (self) {
- // create a view and add it to the window
- cocoaView = [[QemuCocoaView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 640.0, 480.0)];
- if(!cocoaView) {
- error_report("(cocoa) can't create a view");
- exit(1);
- }
- // create a window
- window = [[NSWindow alloc] initWithContentRect:[cocoaView frame]
- styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskClosable
- backing:NSBackingStoreBuffered defer:NO];
- if(!window) {
- error_report("(cocoa) can't create window");
- exit(1);
- }
- [window setAcceptsMouseMovedEvents:YES];
- [window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
- [window setTitle:qemu_name ? [NSString stringWithFormat:@"QEMU %s", qemu_name] : @"QEMU"];
- [window setContentView:cocoaView];
- [window makeKeyAndOrderFront:self];
- [window center];
- [window setDelegate: self];
- /* Used for displaying pause on the screen */
- pauseLabel = [NSTextField new];
- [pauseLabel setBezeled:YES];
- [pauseLabel setDrawsBackground:YES];
- [pauseLabel setBackgroundColor: [NSColor whiteColor]];
- [pauseLabel setEditable:NO];
- [pauseLabel setSelectable:NO];
- [pauseLabel setStringValue: @"Paused"];
- [pauseLabel setFont: [NSFont fontWithName: @"Helvetica" size: 90]];
- [pauseLabel setTextColor: [NSColor blackColor]];
- [pauseLabel sizeToFit];
- }
- return self;
- }
- - (void) dealloc
- {
- COCOA_DEBUG("QemuCocoaAppController: dealloc\n");
- [cocoaView release];
- [cbowner release];
- cbowner = nil;
- [super dealloc];
- }
- - (void)applicationDidFinishLaunching: (NSNotification *) note
- {
- COCOA_DEBUG("QemuCocoaAppController: applicationDidFinishLaunching\n");
- allow_events = true;
- }
- - (void)applicationWillTerminate:(NSNotification *)aNotification
- {
- COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n");
- with_bql(^{
- shutdown_action = SHUTDOWN_ACTION_POWEROFF;
- qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
- });
- /*
- * Sleep here, because returning will cause OSX to kill us
- * immediately; the QEMU main loop will handle the shutdown
- * request and terminate the process.
- */
- [NSThread sleepForTimeInterval:INFINITY];
- }
- - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
- {
- return YES;
- }
- - (NSApplicationTerminateReply)applicationShouldTerminate:
- (NSApplication *)sender
- {
- COCOA_DEBUG("QemuCocoaAppController: applicationShouldTerminate\n");
- return [self verifyQuit];
- }
- - (void)windowDidChangeScreen:(NSNotification *)notification
- {
- [cocoaView updateUIInfo];
- }
- - (void)windowDidEnterFullScreen:(NSNotification *)notification
- {
- [cocoaView grabMouse];
- }
- - (void)windowDidExitFullScreen:(NSNotification *)notification
- {
- [cocoaView resizeWindow];
- [cocoaView ungrabMouse];
- }
- - (void)windowDidResize:(NSNotification *)notification
- {
- [cocoaView updateBounds];
- [cocoaView updateUIInfo];
- }
- /* Called when the user clicks on a window's close button */
- - (BOOL)windowShouldClose:(id)sender
- {
- COCOA_DEBUG("QemuCocoaAppController: windowShouldClose\n");
- [NSApp terminate: sender];
- /* If the user allows the application to quit then the call to
- * NSApp terminate will never return. If we get here then the user
- * cancelled the quit, so we should return NO to not permit the
- * closing of this window.
- */
- return NO;
- }
- - (NSApplicationPresentationOptions) window:(NSWindow *)window
- willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions;
- {
- return (proposedOptions & ~(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)) |
- NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
- }
- /*
- * Called when QEMU goes into the background. Note that
- * [-NSWindowDelegate windowDidResignKey:] is used here instead of
- * [-NSApplicationDelegate applicationWillResignActive:] because it cannot
- * detect that the window loses focus when the deck is clicked on macOS 13.2.1.
- */
- - (void) windowDidResignKey: (NSNotification *)aNotification
- {
- COCOA_DEBUG("%s\n", __func__);
- [cocoaView ungrabMouse];
- [cocoaView raiseAllKeys];
- }
- /* We abstract the method called by the Enter Fullscreen menu item
- * because Mac OS 10.7 and higher disables it. This is because of the
- * menu item's old selector's name toggleFullScreen:
- */
- - (void) doToggleFullScreen:(id)sender
- {
- [[cocoaView window] toggleFullScreen:sender];
- }
- - (void) setFullGrab:(id)sender
- {
- COCOA_DEBUG("QemuCocoaAppController: setFullGrab\n");
- [cocoaView setFullGrab:sender];
- }
- /* Tries to find then open the specified filename */
- - (void) openDocumentation: (NSString *) filename
- {
- /* Where to look for local files */
- NSString *path_array[] = {@"../share/doc/qemu/", @"../doc/qemu/", @"docs/"};
- NSString *full_file_path;
- NSURL *full_file_url;
- /* iterate thru the possible paths until the file is found */
- int index;
- for (index = 0; index < ARRAY_SIZE(path_array); index++) {
- full_file_path = [[NSBundle mainBundle] executablePath];
- full_file_path = [full_file_path stringByDeletingLastPathComponent];
- full_file_path = [NSString stringWithFormat: @"%@/%@%@", full_file_path,
- path_array[index], filename];
- full_file_url = [NSURL fileURLWithPath: full_file_path
- isDirectory: false];
- if ([[NSWorkspace sharedWorkspace] openURL: full_file_url] == YES) {
- return;
- }
- }
- /* If none of the paths opened a file */
- NSBeep();
- QEMU_Alert(@"Failed to open file");
- }
- - (void)showQEMUDoc:(id)sender
- {
- COCOA_DEBUG("QemuCocoaAppController: showQEMUDoc\n");
- [self openDocumentation: @"index.html"];
- }
- /* Stretches video to fit host monitor size */
- - (void)zoomToFit:(id) sender
- {
- NSWindowStyleMask styleMask = [[cocoaView window] styleMask] ^ NSWindowStyleMaskResizable;
- [[cocoaView window] setStyleMask:styleMask];
- [sender setState:styleMask & NSWindowStyleMaskResizable ? NSControlStateValueOn : NSControlStateValueOff];
- [cocoaView resizeWindow];
- }
- - (void)toggleZoomInterpolation:(id) sender
- {
- if (zoom_interpolation == kCGInterpolationNone) {
- zoom_interpolation = kCGInterpolationLow;
- [sender setState: NSControlStateValueOn];
- } else {
- zoom_interpolation = kCGInterpolationNone;
- [sender setState: NSControlStateValueOff];
- }
- }
- /* Displays the console on the screen */
- - (void)displayConsole:(id)sender
- {
- with_bql(^{
- [cocoaView selectConsoleLocked:[sender tag]];
- });
- }
- /* Pause the guest */
- - (void)pauseQEMU:(id)sender
- {
- with_bql(^{
- qmp_stop(NULL);
- });
- [sender setEnabled: NO];
- [[[sender menu] itemWithTitle: @"Resume"] setEnabled: YES];
- [self displayPause];
- }
- /* Resume running the guest operating system */
- - (void)resumeQEMU:(id) sender
- {
- with_bql(^{
- qmp_cont(NULL);
- });
- [sender setEnabled: NO];
- [[[sender menu] itemWithTitle: @"Pause"] setEnabled: YES];
- [self removePause];
- }
- /* Displays the word pause on the screen */
- - (void)displayPause
- {
- /* Coordinates have to be calculated each time because the window can change its size */
- int xCoord, yCoord, width, height;
- xCoord = ([cocoaView frame].size.width - [pauseLabel frame].size.width)/2;
- yCoord = [cocoaView frame].size.height - [pauseLabel frame].size.height - ([pauseLabel frame].size.height * .5);
- width = [pauseLabel frame].size.width;
- height = [pauseLabel frame].size.height;
- [pauseLabel setFrame: NSMakeRect(xCoord, yCoord, width, height)];
- [cocoaView addSubview: pauseLabel];
- }
- /* Removes the word pause from the screen */
- - (void)removePause
- {
- [pauseLabel removeFromSuperview];
- }
- /* Restarts QEMU */
- - (void)restartQEMU:(id)sender
- {
- with_bql(^{
- qmp_system_reset(NULL);
- });
- }
- /* Powers down QEMU */
- - (void)powerDownQEMU:(id)sender
- {
- with_bql(^{
- qmp_system_powerdown(NULL);
- });
- }
- /* Ejects the media.
- * Uses sender's tag to figure out the device to eject.
- */
- - (void)ejectDeviceMedia:(id)sender
- {
- NSString * drive;
- drive = [sender representedObject];
- if(drive == nil) {
- NSBeep();
- QEMU_Alert(@"Failed to find drive to eject!");
- return;
- }
- __block Error *err = NULL;
- with_bql(^{
- qmp_eject([drive cStringUsingEncoding: NSASCIIStringEncoding],
- NULL, false, false, &err);
- });
- handleAnyDeviceErrors(err);
- }
- /* Displays a dialog box asking the user to select an image file to load.
- * Uses sender's represented object value to figure out which drive to use.
- */
- - (void)changeDeviceMedia:(id)sender
- {
- /* Find the drive name */
- NSString * drive;
- drive = [sender representedObject];
- if(drive == nil) {
- NSBeep();
- QEMU_Alert(@"Could not find drive!");
- return;
- }
- /* Display the file open dialog */
- NSOpenPanel * openPanel;
- openPanel = [NSOpenPanel openPanel];
- [openPanel setCanChooseFiles: YES];
- [openPanel setAllowsMultipleSelection: NO];
- if([openPanel runModal] == NSModalResponseOK) {
- NSString * file = [[[openPanel URLs] objectAtIndex: 0] path];
- if(file == nil) {
- NSBeep();
- QEMU_Alert(@"Failed to convert URL to file path!");
- return;
- }
- __block Error *err = NULL;
- with_bql(^{
- qmp_blockdev_change_medium([drive cStringUsingEncoding:
- NSASCIIStringEncoding],
- NULL,
- [file cStringUsingEncoding:
- NSASCIIStringEncoding],
- "raw",
- true, false,
- false, 0,
- false, 0,
- &err);
- });
- handleAnyDeviceErrors(err);
- }
- }
- /* Verifies if the user really wants to quit */
- - (BOOL)verifyQuit
- {
- NSAlert *alert = [NSAlert new];
- [alert autorelease];
- [alert setMessageText: @"Are you sure you want to quit QEMU?"];
- [alert addButtonWithTitle: @"Cancel"];
- [alert addButtonWithTitle: @"Quit"];
- if([alert runModal] == NSAlertSecondButtonReturn) {
- return YES;
- } else {
- return NO;
- }
- }
- /* The action method for the About menu item */
- - (IBAction) do_about_menu_item: (id) sender
- {
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- char *icon_path_c = get_relocated_path(CONFIG_QEMU_ICONDIR "/hicolor/512x512/apps/qemu.png");
- NSString *icon_path = [NSString stringWithUTF8String:icon_path_c];
- g_free(icon_path_c);
- NSImage *icon = [[NSImage alloc] initWithContentsOfFile:icon_path];
- NSString *version = @"QEMU emulator version " QEMU_FULL_VERSION;
- NSString *copyright = @QEMU_COPYRIGHT;
- NSDictionary *options;
- if (icon) {
- options = @{
- NSAboutPanelOptionApplicationIcon : icon,
- NSAboutPanelOptionApplicationVersion : version,
- @"Copyright" : copyright,
- };
- [icon release];
- } else {
- options = @{
- NSAboutPanelOptionApplicationVersion : version,
- @"Copyright" : copyright,
- };
- }
- [NSApp orderFrontStandardAboutPanelWithOptions:options];
- [pool release];
- }
- /* Used by the Speed menu items */
- - (void)adjustSpeed:(id)sender
- {
- int throttle_pct; /* throttle percentage */
- NSMenu *menu;
- menu = [sender menu];
- if (menu != nil)
- {
- /* Unselect the currently selected item */
- for (NSMenuItem *item in [menu itemArray]) {
- if (item.state == NSControlStateValueOn) {
- [item setState: NSControlStateValueOff];
- break;
- }
- }
- }
- // check the menu item
- [sender setState: NSControlStateValueOn];
- // get the throttle percentage
- throttle_pct = [sender tag];
- with_bql(^{
- cpu_throttle_set(throttle_pct);
- });
- COCOA_DEBUG("cpu throttling at %d%c\n", cpu_throttle_get_percentage(), '%');
- }
- @end
- @interface QemuApplication : NSApplication
- @end
- @implementation QemuApplication
- - (void)sendEvent:(NSEvent *)event
- {
- COCOA_DEBUG("QemuApplication: sendEvent\n");
- if (![cocoaView handleEvent:event]) {
- [super sendEvent: event];
- }
- }
- @end
- static void create_initial_menus(void)
- {
- // Add menus
- NSMenu *menu;
- NSMenuItem *menuItem;
- [NSApp setMainMenu:[[NSMenu alloc] init]];
- [NSApp setServicesMenu:[[NSMenu alloc] initWithTitle:@"Services"]];
- // Application menu
- menu = [[NSMenu alloc] initWithTitle:@""];
- [menu addItemWithTitle:@"About QEMU" action:@selector(do_about_menu_item:) keyEquivalent:@""]; // About QEMU
- [menu addItem:[NSMenuItem separatorItem]]; //Separator
- menuItem = [menu addItemWithTitle:@"Services" action:nil keyEquivalent:@""];
- [menuItem setSubmenu:[NSApp servicesMenu]];
- [menu addItem:[NSMenuItem separatorItem]];
- [menu addItemWithTitle:@"Hide QEMU" action:@selector(hide:) keyEquivalent:@"h"]; //Hide QEMU
- menuItem = (NSMenuItem *)[menu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; // Hide Others
- [menuItem setKeyEquivalentModifierMask:(NSEventModifierFlagOption|NSEventModifierFlagCommand)];
- [menu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; // Show All
- [menu addItem:[NSMenuItem separatorItem]]; //Separator
- [menu addItemWithTitle:@"Quit QEMU" action:@selector(terminate:) keyEquivalent:@"q"];
- menuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" action:nil keyEquivalent:@""];
- [menuItem setSubmenu:menu];
- [[NSApp mainMenu] addItem:menuItem];
- [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; // Workaround (this method is private since 10.4+)
- // Machine menu
- menu = [[NSMenu alloc] initWithTitle: @"Machine"];
- [menu setAutoenablesItems: NO];
- [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Pause" action: @selector(pauseQEMU:) keyEquivalent: @""] autorelease]];
- menuItem = [[[NSMenuItem alloc] initWithTitle: @"Resume" action: @selector(resumeQEMU:) keyEquivalent: @""] autorelease];
- [menu addItem: menuItem];
- [menuItem setEnabled: NO];
- [menu addItem: [NSMenuItem separatorItem]];
- [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Reset" action: @selector(restartQEMU:) keyEquivalent: @""] autorelease]];
- [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Power Down" action: @selector(powerDownQEMU:) keyEquivalent: @""] autorelease]];
- menuItem = [[[NSMenuItem alloc] initWithTitle: @"Machine" action:nil keyEquivalent:@""] autorelease];
- [menuItem setSubmenu:menu];
- [[NSApp mainMenu] addItem:menuItem];
- // View menu
- menu = [[NSMenu alloc] initWithTitle:@"View"];
- [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(doToggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen
- menuItem = [[[NSMenuItem alloc] initWithTitle:@"Zoom To Fit" action:@selector(zoomToFit:) keyEquivalent:@""] autorelease];
- [menuItem setState: [[cocoaView window] styleMask] & NSWindowStyleMaskResizable ? NSControlStateValueOn : NSControlStateValueOff];
- [menu addItem: menuItem];
- menuItem = [[[NSMenuItem alloc] initWithTitle:@"Zoom Interpolation" action:@selector(toggleZoomInterpolation:) keyEquivalent:@""] autorelease];
- [menuItem setState: zoom_interpolation == kCGInterpolationLow ? NSControlStateValueOn : NSControlStateValueOff];
- [menu addItem: menuItem];
- menuItem = [[[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""] autorelease];
- [menuItem setSubmenu:menu];
- [[NSApp mainMenu] addItem:menuItem];
- // Speed menu
- menu = [[NSMenu alloc] initWithTitle:@"Speed"];
- // Add the rest of the Speed menu items
- int p, percentage, throttle_pct;
- for (p = 10; p >= 0; p--)
- {
- percentage = p * 10 > 1 ? p * 10 : 1; // prevent a 0% menu item
- menuItem = [[[NSMenuItem alloc]
- initWithTitle: [NSString stringWithFormat: @"%d%%", percentage] action:@selector(adjustSpeed:) keyEquivalent:@""] autorelease];
- if (percentage == 100) {
- [menuItem setState: NSControlStateValueOn];
- }
- /* Calculate the throttle percentage */
- throttle_pct = -1 * percentage + 100;
- [menuItem setTag: throttle_pct];
- [menu addItem: menuItem];
- }
- menuItem = [[[NSMenuItem alloc] initWithTitle:@"Speed" action:nil keyEquivalent:@""] autorelease];
- [menuItem setSubmenu:menu];
- [[NSApp mainMenu] addItem:menuItem];
- // Window menu
- menu = [[NSMenu alloc] initWithTitle:@"Window"];
- [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease]]; // Miniaturize
- menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease];
- [menuItem setSubmenu:menu];
- [[NSApp mainMenu] addItem:menuItem];
- [NSApp setWindowsMenu:menu];
- // Help menu
- menu = [[NSMenu alloc] initWithTitle:@"Help"];
- [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Documentation" action:@selector(showQEMUDoc:) keyEquivalent:@"?"] autorelease]]; // QEMU Help
- menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease];
- [menuItem setSubmenu:menu];
- [[NSApp mainMenu] addItem:menuItem];
- }
- /* Returns a name for a given console */
- static NSString * getConsoleName(QemuConsole * console)
- {
- g_autofree char *label = qemu_console_get_label(console);
- return [NSString stringWithUTF8String:label];
- }
- /* Add an entry to the View menu for each console */
- static void add_console_menu_entries(void)
- {
- NSMenu *menu;
- NSMenuItem *menuItem;
- int index = 0;
- menu = [[[NSApp mainMenu] itemWithTitle:@"View"] submenu];
- [menu addItem:[NSMenuItem separatorItem]];
- while (qemu_console_lookup_by_index(index) != NULL) {
- menuItem = [[[NSMenuItem alloc] initWithTitle: getConsoleName(qemu_console_lookup_by_index(index))
- action: @selector(displayConsole:) keyEquivalent: @""] autorelease];
- [menuItem setTag: index];
- [menu addItem: menuItem];
- index++;
- }
- }
- /* Make menu items for all removable devices.
- * Each device is given an 'Eject' and 'Change' menu item.
- */
- static void addRemovableDevicesMenuItems(void)
- {
- NSMenu *menu;
- NSMenuItem *menuItem;
- BlockInfoList *currentDevice, *pointerToFree;
- NSString *deviceName;
- currentDevice = qmp_query_block(NULL);
- pointerToFree = currentDevice;
- menu = [[[NSApp mainMenu] itemWithTitle:@"Machine"] submenu];
- // Add a separator between related groups of menu items
- [menu addItem:[NSMenuItem separatorItem]];
- // Set the attributes to the "Removable Media" menu item
- NSString *titleString = @"Removable Media";
- NSMutableAttributedString *attString=[[NSMutableAttributedString alloc] initWithString:titleString];
- NSColor *newColor = [NSColor blackColor];
- NSFontManager *fontManager = [NSFontManager sharedFontManager];
- NSFont *font = [fontManager fontWithFamily:@"Helvetica"
- traits:NSBoldFontMask|NSItalicFontMask
- weight:0
- size:14];
- [attString addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, [titleString length])];
- [attString addAttribute:NSForegroundColorAttributeName value:newColor range:NSMakeRange(0, [titleString length])];
- [attString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInt: 1] range:NSMakeRange(0, [titleString length])];
- // Add the "Removable Media" menu item
- menuItem = [NSMenuItem new];
- [menuItem setAttributedTitle: attString];
- [menuItem setEnabled: NO];
- [menu addItem: menuItem];
- /* Loop through all the block devices in the emulator */
- while (currentDevice) {
- deviceName = [[NSString stringWithFormat: @"%s", currentDevice->value->device] retain];
- if(currentDevice->value->removable) {
- menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Change %s...", currentDevice->value->device]
- action: @selector(changeDeviceMedia:)
- keyEquivalent: @""];
- [menu addItem: menuItem];
- [menuItem setRepresentedObject: deviceName];
- [menuItem autorelease];
- menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Eject %s", currentDevice->value->device]
- action: @selector(ejectDeviceMedia:)
- keyEquivalent: @""];
- [menu addItem: menuItem];
- [menuItem setRepresentedObject: deviceName];
- [menuItem autorelease];
- }
- currentDevice = currentDevice->next;
- }
- qapi_free_BlockInfoList(pointerToFree);
- }
- static void cocoa_mouse_mode_change_notify(Notifier *notifier, void *data)
- {
- dispatch_async(dispatch_get_main_queue(), ^{
- [cocoaView notifyMouseModeChange];
- });
- }
- static Notifier mouse_mode_change_notifier = {
- .notify = cocoa_mouse_mode_change_notify
- };
- @interface QemuCocoaPasteboardTypeOwner : NSObject<NSPasteboardTypeOwner>
- @end
- @implementation QemuCocoaPasteboardTypeOwner
- - (void)pasteboard:(NSPasteboard *)sender provideDataForType:(NSPasteboardType)type
- {
- if (type != NSPasteboardTypeString) {
- return;
- }
- with_bql(^{
- QemuClipboardInfo *info = qemu_clipboard_info_ref(cbinfo);
- qemu_event_reset(&cbevent);
- qemu_clipboard_request(info, QEMU_CLIPBOARD_TYPE_TEXT);
- while (info == cbinfo &&
- info->types[QEMU_CLIPBOARD_TYPE_TEXT].available &&
- info->types[QEMU_CLIPBOARD_TYPE_TEXT].data == NULL) {
- bql_unlock();
- qemu_event_wait(&cbevent);
- bql_lock();
- }
- if (info == cbinfo) {
- NSData *data = [[NSData alloc] initWithBytes:info->types[QEMU_CLIPBOARD_TYPE_TEXT].data
- length:info->types[QEMU_CLIPBOARD_TYPE_TEXT].size];
- [sender setData:data forType:NSPasteboardTypeString];
- [data release];
- }
- qemu_clipboard_info_unref(info);
- });
- }
- @end
- static void cocoa_clipboard_notify(Notifier *notifier, void *data);
- static void cocoa_clipboard_request(QemuClipboardInfo *info,
- QemuClipboardType type);
- static QemuClipboardPeer cbpeer = {
- .name = "cocoa",
- .notifier = { .notify = cocoa_clipboard_notify },
- .request = cocoa_clipboard_request
- };
- static void cocoa_clipboard_update_info(QemuClipboardInfo *info)
- {
- if (info->owner == &cbpeer || info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) {
- return;
- }
- if (info != cbinfo) {
- NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
- qemu_clipboard_info_unref(cbinfo);
- cbinfo = qemu_clipboard_info_ref(info);
- cbchangecount = [[NSPasteboard generalPasteboard] declareTypes:@[NSPasteboardTypeString] owner:cbowner];
- [pool release];
- }
- qemu_event_set(&cbevent);
- }
- static void cocoa_clipboard_notify(Notifier *notifier, void *data)
- {
- QemuClipboardNotify *notify = data;
- switch (notify->type) {
- case QEMU_CLIPBOARD_UPDATE_INFO:
- cocoa_clipboard_update_info(notify->info);
- return;
- case QEMU_CLIPBOARD_RESET_SERIAL:
- /* ignore */
- return;
- }
- }
- static void cocoa_clipboard_request(QemuClipboardInfo *info,
- QemuClipboardType type)
- {
- NSAutoreleasePool *pool;
- NSData *text;
- switch (type) {
- case QEMU_CLIPBOARD_TYPE_TEXT:
- pool = [[NSAutoreleasePool alloc] init];
- text = [[NSPasteboard generalPasteboard] dataForType:NSPasteboardTypeString];
- if (text) {
- qemu_clipboard_set_data(&cbpeer, info, type,
- [text length], [text bytes], true);
- }
- [pool release];
- break;
- default:
- break;
- }
- }
- static int cocoa_main(void)
- {
- COCOA_DEBUG("Main thread: entering OSX run loop\n");
- [NSApp run];
- COCOA_DEBUG("Main thread: left OSX run loop, which should never happen\n");
- abort();
- }
- #pragma mark qemu
- static void cocoa_update(DisplayChangeListener *dcl,
- int x, int y, int w, int h)
- {
- COCOA_DEBUG("qemu_cocoa: cocoa_update\n");
- dispatch_async(dispatch_get_main_queue(), ^{
- NSRect rect = NSMakeRect(x, [cocoaView gscreen].height - y - h, w, h);
- [cocoaView setNeedsDisplayInRect:rect];
- });
- }
- static void cocoa_switch(DisplayChangeListener *dcl,
- DisplaySurface *surface)
- {
- pixman_image_t *image = surface->image;
- COCOA_DEBUG("qemu_cocoa: cocoa_switch\n");
- // The DisplaySurface will be freed as soon as this callback returns.
- // We take a reference to the underlying pixman image here so it does
- // not disappear from under our feet; the switchSurface method will
- // deref the old image when it is done with it.
- pixman_image_ref(image);
- dispatch_async(dispatch_get_main_queue(), ^{
- [cocoaView switchSurface:image];
- });
- }
- static void cocoa_refresh(DisplayChangeListener *dcl)
- {
- NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
- COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n");
- graphic_hw_update(dcl->con);
- if (cbchangecount != [[NSPasteboard generalPasteboard] changeCount]) {
- qemu_clipboard_info_unref(cbinfo);
- cbinfo = qemu_clipboard_info_new(&cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD);
- if ([[NSPasteboard generalPasteboard] availableTypeFromArray:@[NSPasteboardTypeString]]) {
- cbinfo->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
- }
- qemu_clipboard_update(cbinfo);
- cbchangecount = [[NSPasteboard generalPasteboard] changeCount];
- qemu_event_set(&cbevent);
- }
- [pool release];
- }
- static void cocoa_mouse_set(DisplayChangeListener *dcl, int x, int y, bool on)
- {
- dispatch_async(dispatch_get_main_queue(), ^{
- [cocoaView setMouseX:x y:y on:on];
- });
- }
- static void cocoa_cursor_define(DisplayChangeListener *dcl, QEMUCursor *cursor)
- {
- dispatch_async(dispatch_get_main_queue(), ^{
- BQL_LOCK_GUARD();
- [cocoaView setCursor:qemu_console_get_cursor(dcl->con)];
- });
- }
- static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts)
- {
- NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
- COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n");
- // Pull this console process up to being a fully-fledged graphical
- // app with a menubar and Dock icon
- ProcessSerialNumber psn = { 0, kCurrentProcess };
- TransformProcessType(&psn, kProcessTransformToForegroundApplication);
- [QemuApplication sharedApplication];
- // Create an Application controller
- QemuCocoaAppController *controller = [[QemuCocoaAppController alloc] init];
- [NSApp setDelegate:controller];
- /* if fullscreen mode is to be used */
- if (opts->has_full_screen && opts->full_screen) {
- [[cocoaView window] toggleFullScreen: nil];
- }
- if (opts->u.cocoa.has_full_grab && opts->u.cocoa.full_grab) {
- [controller setFullGrab: nil];
- }
- if (opts->has_show_cursor && opts->show_cursor) {
- cursor_hide = 0;
- }
- if (opts->u.cocoa.has_swap_opt_cmd) {
- swap_opt_cmd = opts->u.cocoa.swap_opt_cmd;
- }
- if (opts->u.cocoa.has_left_command_key && !opts->u.cocoa.left_command_key) {
- left_command_key_enabled = 0;
- }
- if (opts->u.cocoa.has_zoom_to_fit && opts->u.cocoa.zoom_to_fit) {
- [cocoaView window].styleMask |= NSWindowStyleMaskResizable;
- }
- if (opts->u.cocoa.has_zoom_interpolation && opts->u.cocoa.zoom_interpolation) {
- zoom_interpolation = kCGInterpolationLow;
- }
- create_initial_menus();
- /*
- * Create the menu entries which depend on QEMU state (for consoles
- * and removable devices). These make calls back into QEMU functions,
- * which is OK because at this point we know that the second thread
- * holds the BQL and is synchronously waiting for us to
- * finish.
- */
- add_console_menu_entries();
- addRemovableDevicesMenuItems();
- dcl.con = qemu_console_lookup_default();
- kbd = qkbd_state_init(dcl.con);
- // register vga output callbacks
- register_displaychangelistener(&dcl);
- qemu_add_mouse_mode_change_notifier(&mouse_mode_change_notifier);
- [cocoaView notifyMouseModeChange];
- [cocoaView updateUIInfo];
- qemu_event_init(&cbevent, false);
- cbowner = [[QemuCocoaPasteboardTypeOwner alloc] init];
- qemu_clipboard_peer_register(&cbpeer);
- [pool release];
- /*
- * The Cocoa UI will run the NSApplication runloop on the main thread
- * rather than the default Core Foundation one.
- */
- qemu_main = cocoa_main;
- }
- static QemuDisplay qemu_display_cocoa = {
- .type = DISPLAY_TYPE_COCOA,
- .init = cocoa_display_init,
- };
- static void register_cocoa(void)
- {
- qemu_display_register(&qemu_display_cocoa);
- }
- type_init(register_cocoa);
|