123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619 |
- /*
- * 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.
- */
- #define GL_SILENCE_DEPRECATION
- #include "qemu/osdep.h"
- #import <Cocoa/Cocoa.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 "sysemu/sysemu.h"
- #include "sysemu/runstate.h"
- #include "sysemu/runstate-action.h"
- #include "sysemu/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 "sysemu/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"
- #ifdef CONFIG_EGL
- #include "ui/egl-context.h"
- #endif
- #ifndef MAC_OS_X_VERSION_10_13
- #define MAC_OS_X_VERSION_10_13 101300
- #endif
- /* 10.14 deprecates NSOnState and NSOffState in favor of
- * NSControlStateValueOn/Off, which were introduced in 10.13.
- * Define for older versions
- */
- #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_13
- #define NSControlStateValueOn NSOnState
- #define NSControlStateValueOff NSOffState
- #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 CocoaListener {
- DisplayChangeListener dcl;
- QEMUCursor *cursor;
- int mouse_x;
- int mouse_y;
- int mouse_on;
- #ifdef CONFIG_OPENGL
- uint32_t gl_scanout_id;
- DisplayGLTextureBorrower gl_scanout_borrow;
- bool gl_scanout_y0_top;
- #endif
- } CocoaListener;
- typedef struct {
- int width;
- int height;
- } QEMUScreen;
- static void cocoa_switch(DisplayChangeListener *dcl,
- DisplaySurface *surface);
- static void cocoa_cursor_update(void);
- static NSWindow *normalWindow;
- static CocoaListener *active_listener;
- static CocoaListener *listeners;
- static size_t listeners_count;
- static DisplaySurface *surface;
- static QemuMutex draw_mutex;
- static CGImageRef cursor_cgimage;
- static QKbdState *kbd;
- static int cursor_hide = 1;
- static int left_command_key_enabled = 1;
- static bool swap_opt_cmd;
- static NSTextField *pauseLabel;
- static NSInteger cbchangecount = -1;
- static QemuClipboardInfo *cbinfo;
- static QemuEvent cbevent;
- #ifdef CONFIG_OPENGL
- static GLuint cursor_texture;
- static bool gl_dirty;
- static QEMUGLContext view_ctx;
- #ifdef CONFIG_EGL
- static EGLSurface egl_surface;
- #endif
- static void cocoa_gl_switch(DisplayChangeListener *dcl,
- DisplaySurface *new_surface);
- static void cocoa_gl_cursor_update(void);
- static bool cocoa_gl_is_compatible_dcl(DisplayGLCtx *dgc,
- DisplayChangeListener *dcl);
- static QEMUGLContext cocoa_gl_create_context(DisplayGLCtx *dgc,
- QEMUGLParams *params);
- static void cocoa_gl_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx);
- static int cocoa_gl_make_context_current(DisplayGLCtx *dgc, QEMUGLContext ctx);
- static const DisplayGLCtxOps dgc_ops = {
- .dpy_gl_ctx_is_compatible_dcl = cocoa_gl_is_compatible_dcl,
- .dpy_gl_ctx_create = cocoa_gl_create_context,
- .dpy_gl_ctx_destroy = cocoa_gl_destroy_context,
- .dpy_gl_ctx_make_current = cocoa_gl_make_context_current,
- };
- static DisplayGLCtx dgc = {
- .ops = &dgc_ops,
- };
- #endif
- // Utility functions to run specified code block with iothread lock held
- typedef void (^CodeBlock)(void);
- typedef bool (^BoolCodeBlock)(void);
- static void with_iothread_lock(CodeBlock block)
- {
- bool locked = qemu_mutex_iothread_locked();
- if (!locked) {
- qemu_mutex_lock_iothread();
- }
- block();
- if (!locked) {
- qemu_mutex_unlock_iothread();
- }
- }
- static bool bool_with_iothread_lock(BoolCodeBlock block)
- {
- bool locked = qemu_mutex_iothread_locked();
- bool val;
- if (!locked) {
- qemu_mutex_lock_iothread();
- }
- val = block();
- if (!locked) {
- qemu_mutex_unlock_iothread();
- }
- return val;
- }
- static int cocoa_keycode_to_qemu(int keycode)
- {
- if (qemu_input_map_osx_to_qcode_len <= keycode) {
- error_report("(cocoa) warning unknown keycode 0x%x", keycode);
- return 0;
- }
- return qemu_input_map_osx_to_qcode[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);
- }
- }
- static CGRect compute_cursor_clip_rect(int screen_height,
- int given_mouse_x, int given_mouse_y,
- int cursor_width, int cursor_height)
- {
- CGRect rect;
- rect.origin.x = MAX(0, -given_mouse_x);
- rect.origin.y = 0;
- rect.size.width = MIN(cursor_width, cursor_width + given_mouse_x);
- rect.size.height = cursor_height - rect.origin.x;
- return rect;
- }
- /*
- ------------------------------------------------------
- QemuCocoaView
- ------------------------------------------------------
- */
- @interface QemuCocoaView : NSView
- {
- NSTrackingArea *trackingArea;
- QEMUScreen screen;
- /* 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;
- }
- - (void) grabMouse;
- - (void) ungrabMouse;
- - (void) setFullGrab:(id)sender;
- - (void) handleMonitorInput:(NSEvent *)event;
- - (bool) handleEvent:(NSEvent *)event;
- - (bool) handleEventLocked:(NSEvent *)event;
- - (void) notifyMouseModeChange;
- - (BOOL) isMouseGrabbed;
- - (void) raiseAllKeys;
- @end
- QemuCocoaView *cocoaView;
- static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEventRef cgEvent, void *userInfo)
- {
- QemuCocoaView *cocoaView = userInfo;
- NSEvent *event = [NSEvent eventWithCGEvent:cgEvent];
- if ([cocoaView isMouseGrabbed] && [cocoaView 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) {
- screen.width = frameRect.size.width;
- screen.height = frameRect.size.height;
- }
- return self;
- }
- - (void) dealloc
- {
- COCOA_DEBUG("QemuCocoaView: dealloc\n");
- if (eventsTap) {
- CFRelease(eventsTap);
- }
- [super dealloc];
- }
- - (BOOL) isOpaque
- {
- return YES;
- }
- - (void) removeTrackingRect
- {
- if (trackingArea) {
- [self removeTrackingArea:trackingArea];
- [trackingArea release];
- trackingArea = nil;
- }
- }
- - (void) frameUpdated
- {
- [self removeTrackingRect];
- if ([self window]) {
- NSTrackingAreaOptions options = NSTrackingActiveInKeyWindow |
- NSTrackingMouseEnteredAndExited |
- NSTrackingMouseMoved;
- trackingArea = [[NSTrackingArea alloc] initWithRect:[self frame]
- options:options
- owner:self
- userInfo:nil];
- [self addTrackingArea:trackingArea];
- [self updateUIInfo];
- }
- }
- - (void) viewDidMoveToWindow
- {
- [self resizeWindow];
- [self frameUpdated];
- }
- - (void) viewWillMoveToWindow:(NSWindow *)newWindow
- {
- [self removeTrackingRect];
- }
- - (void) selectConsoleLocked:(unsigned int)index
- {
- DisplaySurface *new_surface;
- if (index >= listeners_count) {
- return;
- }
- active_listener = &listeners[index];
- new_surface = qemu_console_surface(active_listener->dcl.con);
- qkbd_state_lift_all_keys(kbd);
- qkbd_state_free(kbd);
- kbd = qkbd_state_init(active_listener->dcl.con);
- if (display_opengl) {
- #ifdef CONFIG_OPENGL
- cocoa_gl_cursor_update();
- cocoa_gl_switch(&active_listener->dcl, new_surface);
- #else
- g_assert_not_reached();
- #endif
- } else {
- cocoa_cursor_update();
- cocoa_switch(&active_listener->dcl, new_surface);
- }
- [self notifyMouseModeChange];
- [self updateUIInfoLocked];
- }
- - (void) hideCursor
- {
- if (!cursor_hide) {
- return;
- }
- [NSCursor hide];
- }
- - (void) unhideCursor
- {
- if (!cursor_hide) {
- return;
- }
- [NSCursor unhide];
- }
- - (CGRect) convertCursorClipRectToDraw:(CGRect)rect
- screenHeight:(int)screen_height
- mouseX:(int)given_mouse_x
- mouseY:(int)given_mouse_y
- {
- CGFloat d = [self frame].size.height / (CGFloat)screen_height;
- rect.origin.x = (rect.origin.x + given_mouse_x) * d;
- rect.origin.y = (screen_height - rect.origin.y - given_mouse_y - rect.size.height) * d;
- rect.size.width *= d;
- rect.size.height *= d;
- return rect;
- }
- - (void) drawRect:(NSRect) rect
- {
- COCOA_DEBUG("QemuCocoaView: drawRect\n");
- #ifdef CONFIG_OPENGL
- if (display_opengl) {
- return;
- }
- #endif
- // get CoreGraphic context
- CGContextRef viewContextRef = [[NSGraphicsContext currentContext] CGContext];
- CGContextSetInterpolationQuality (viewContextRef, kCGInterpolationNone);
- CGContextSetShouldAntialias (viewContextRef, NO);
- qemu_mutex_lock(&draw_mutex);
- // draw screen bitmap directly to Core Graphics context
- if (!surface) {
- // 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 = surface_width(surface);
- int h = surface_height(surface);
- int bitsPerPixel = PIXMAN_FORMAT_BPP(surface_format(surface));
- int stride = surface_stride(surface);
- CGDataProviderRef dataProviderRef = CGDataProviderCreateWithData(
- NULL,
- surface_data(surface),
- stride * h,
- NULL
- );
- CGImageRef imageRef = CGImageCreate(
- w, //width
- h, //height
- DIV_ROUND_UP(bitsPerPixel, 8) * 2, //bitsPerComponent
- bitsPerPixel, //bitsPerPixel
- stride, //bytesPerRow
- CGColorSpaceCreateWithName(kCGColorSpaceSRGB), //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;
- CGFloat d = (CGFloat)h / [self frame].size.height;
- [self getRectsBeingDrawn:&rectList count:&rectCount];
- for (i = 0; i < rectCount; i++) {
- clipRect.origin.x = rectList[i].origin.x * d;
- clipRect.origin.y = (float)h - (rectList[i].origin.y + rectList[i].size.height) * d;
- clipRect.size.width = rectList[i].size.width * d;
- clipRect.size.height = rectList[i].size.height * d;
- clipImageRef = CGImageCreateWithImageInRect(
- imageRef,
- clipRect
- );
- CGContextDrawImage (viewContextRef, cgrect(rectList[i]), clipImageRef);
- CGImageRelease (clipImageRef);
- }
- CGImageRelease (imageRef);
- CGDataProviderRelease(dataProviderRef);
- if (active_listener->mouse_on) {
- size_t cursor_width = CGImageGetWidth(cursor_cgimage);
- size_t cursor_height = CGImageGetHeight(cursor_cgimage);
- int mouse_x = active_listener->mouse_x;
- int mouse_y = active_listener->mouse_y;
- clipRect = compute_cursor_clip_rect(h, mouse_x, mouse_y,
- cursor_width,
- cursor_height);
- CGRect drawRect = [self convertCursorClipRectToDraw:clipRect
- screenHeight:h
- mouseX:mouse_x
- mouseY:mouse_y];
- clipImageRef = CGImageCreateWithImageInRect(
- cursor_cgimage,
- clipRect
- );
- CGContextDrawImage(viewContextRef, drawRect, clipImageRef);
- CGImageRelease (clipImageRef);
- }
- }
- qemu_mutex_unlock(&draw_mutex);
- }
- - (NSSize) computeUnzoomedSize
- {
- CGFloat width = screen.width / [[self window] backingScaleFactor];
- CGFloat height = screen.height / [[self window] backingScaleFactor];
- return NSMakeSize(width, height);
- }
- - (NSSize) fixZoomedFullScreenSize:(NSSize)proposedSize
- {
- NSSize size;
- size.width = (CGFloat)screen.width * proposedSize.height;
- size.height = (CGFloat)screen.height * proposedSize.width;
- if (size.width < size.height) {
- size.width /= screen.height;
- size.height = proposedSize.height;
- } else {
- size.width = proposedSize.width;
- size.height /= screen.width;
- }
- return size;
- }
- - (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) == 0) {
- [[self window] setContentSize:[self computeUnzoomedSize]];
- [[self window] center];
- } else if (([[self window] styleMask] & NSWindowStyleMaskFullScreen) != 0) {
- [[self window] setContentSize:[self fixZoomedFullScreenSize:[self screenSafeAreaSize]]];
- [[self window] center];
- }
- }
- - (void) updateUIInfoLocked
- {
- /* Must be called with the iothread lock, i.e. via updateUIInfo */
- NSSize frameSize;
- QemuUIInfo info;
- 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);
- CVDisplayLinkRef displayLink;
- if (([[self window] styleMask] & NSWindowStyleMaskFullScreen) == 0) {
- frameSize = [self frame].size;
- } else {
- frameSize = [self screenSafeAreaSize];
- }
- if (!CVDisplayLinkCreateWithCGDisplay(display, &displayLink)) {
- CVTime period = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(displayLink);
- CVDisplayLinkRelease(displayLink);
- if (!(period.flags & kCVTimeIsIndefinite)) {
- update_displaychangelistener(&active_listener->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;
- }
- NSSize frameBackingSize = [self convertSizeToBacking:frameSize];
- info.xoff = 0;
- info.yoff = 0;
- info.width = frameBackingSize.width;
- info.height = frameBackingSize.height;
- dpy_set_ui_info(active_listener->dcl.con, &info, TRUE);
- }
- - (void) updateUIInfo
- {
- if (!listeners) {
- return;
- }
- with_iothread_lock(^{
- [self updateUIInfoLocked];
- });
- }
- - (void) updateScreenWidth:(int)w height:(int)h
- {
- COCOA_DEBUG("QemuCocoaView: updateScreenWidth:height:\n");
- if (w != screen.width || h != screen.height) {
- COCOA_DEBUG("updateScreenWidth:height: new size %d x %d\n", w, h);
- screen.width = w;
- screen.height = h;
- [self resizeWindow];
- }
- }
- - (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) {
- kbd_put_keysym_console(active_listener->dcl.con, keysym);
- }
- }
- - (bool) handleEvent:(NSEvent *)event
- {
- if(!listeners) {
- return false;
- }
- return bool_with_iothread_lock(^{
- 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");
- int buttons = 0;
- 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];
- }
- return true;
- case kVK_RightShift:
- if (!!(modifiers & NSEventModifierFlagShift)) {
- [self toggleKey:Q_KEY_CODE_SHIFT_R];
- }
- return true;
- case kVK_Control:
- if (!!(modifiers & NSEventModifierFlagControl)) {
- [self toggleKey:Q_KEY_CODE_CTRL];
- }
- return true;
- case kVK_RightControl:
- if (!!(modifiers & NSEventModifierFlagControl)) {
- [self toggleKey:Q_KEY_CODE_CTRL_R];
- }
- return true;
- case kVK_Option:
- if (!!(modifiers & NSEventModifierFlagOption)) {
- if (swap_opt_cmd) {
- [self toggleKey:Q_KEY_CODE_META_L];
- } else {
- [self toggleKey:Q_KEY_CODE_ALT];
- }
- }
- return true;
- 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];
- }
- }
- return true;
- /* 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];
- }
- }
- return true;
- 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];
- }
- }
- return true;
- default:
- 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(active_listener->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(active_listener->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.
- */
- /*
- * We shouldn't have got a scroll event when deltaY and delta Y
- * are zero, hence no harm in dropping the event
- */
- if ([event deltaY] != 0 || [event deltaX] != 0) {
- /* Determine if this is a scroll up or scroll down event */
- if ([event deltaY] != 0) {
- buttons = ([event deltaY] > 0) ?
- INPUT_BUTTON_WHEEL_UP : INPUT_BUTTON_WHEEL_DOWN;
- } else if ([event deltaX] != 0) {
- buttons = ([event deltaX] > 0) ?
- INPUT_BUTTON_WHEEL_LEFT : INPUT_BUTTON_WHEEL_RIGHT;
- }
- qemu_input_queue_btn(active_listener->dcl.con, buttons, true);
- qemu_input_event_sync();
- qemu_input_queue_btn(active_listener->dcl.con, buttons, false);
- qemu_input_event_sync();
- }
- /*
- * Since deltaX/deltaY also report scroll wheel events we prevent mouse
- * movement code from executing.
- */
- return true;
- default:
- return false;
- }
- }
- - (void) handleMouseEvent:(NSEvent *)event
- {
- if (!isMouseGrabbed) {
- return;
- }
- with_iothread_lock(^{
- QemuConsole *con = active_listener->dcl.con;
- 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(con, INPUT_AXIS_X, p.x * d, 0, screen.width);
- qemu_input_queue_abs(con, INPUT_AXIS_Y, screen.height - p.y * d, 0, screen.height);
- } else {
- CGFloat d = (CGFloat)screen.height / [self convertSizeToBacking:[self frame].size].height;
- qemu_input_queue_rel(con, INPUT_AXIS_X, [event deltaX] * d);
- qemu_input_queue_rel(con, INPUT_AXIS_Y, [event deltaY] * d);
- }
- qemu_input_event_sync();
- });
- }
- - (void) handleMouseEvent:(NSEvent *)event button:(InputButton)button down:(bool)down
- {
- if (!isMouseGrabbed) {
- return;
- }
- with_iothread_lock(^{
- qemu_input_queue_btn(active_listener->dcl.con, button, down);
- });
- [self handleMouseEvent:event];
- }
- - (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 (!listeners) {
- return;
- }
- if (qemu_name)
- [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s - (Press " UC_CTRL_KEY " " UC_ALT_KEY " G to release Mouse)", qemu_name]];
- else
- [normalWindow 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)
- [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]];
- else
- [normalWindow setTitle:@"QEMU"];
- [self unhideCursor];
- CGAssociateMouseAndMouseCursorPosition(TRUE);
- isMouseGrabbed = FALSE;
- [self raiseAllButtons];
- }
- - (void) notifyMouseModeChange {
- bool tIsAbsoluteEnabled = bool_with_iothread_lock(^{
- return qemu_input_is_absolute();
- });
- if (tIsAbsoluteEnabled == isAbsoluteEnabled) {
- return;
- }
- isAbsoluteEnabled = tIsAbsoluteEnabled;
- if (isMouseGrabbed) {
- if (isAbsoluteEnabled) {
- [self ungrabMouse];
- } else {
- CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled);
- }
- }
- }
- - (BOOL) isMouseGrabbed {return isMouseGrabbed;}
- /*
- * 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_iothread_lock(^{
- qkbd_state_lift_all_keys(kbd);
- });
- }
- - (void) raiseAllButtons
- {
- with_iothread_lock(^{
- qemu_input_queue_btn(active_listener->dcl.con, INPUT_BUTTON_LEFT, false);
- qemu_input_queue_btn(active_listener->dcl.con, INPUT_BUTTON_RIGHT, false);
- qemu_input_queue_btn(active_listener->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
- {
- 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
- normalWindow = [[NSWindow alloc] initWithContentRect:[cocoaView frame]
- styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskClosable
- backing:NSBackingStoreBuffered defer:NO];
- if(!normalWindow) {
- error_report("(cocoa) can't create window");
- exit(1);
- }
- [normalWindow setAcceptsMouseMovedEvents:YES];
- [normalWindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
- [normalWindow setTitle:qemu_name ? [NSString stringWithFormat:@"QEMU %s", qemu_name] : @"QEMU"];
- [normalWindow setContentView:cocoaView];
- [normalWindow makeKeyAndOrderFront:self];
- [normalWindow center];
- [normalWindow 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");
- if (cocoaView)
- [cocoaView release];
- [super dealloc];
- }
- - (void)applicationWillTerminate:(NSNotification *)aNotification
- {
- COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n");
- with_iothread_lock(^{
- 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 frameUpdated];
- }
- /* 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;
- }
- - (NSSize) window:(NSWindow *)window willUseFullScreenContentSize:(NSSize)proposedSize
- {
- if (([normalWindow styleMask] & NSWindowStyleMaskResizable) == 0) {
- return [cocoaView computeUnzoomedSize];
- }
- return [cocoaView fixZoomedFullScreenSize:proposedSize];
- }
- - (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
- {
- [normalWindow 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"];
- }
- /* Toggles the flag which stretches video to fit host window size */
- - (void)zoomToFit:(id) sender
- {
- if (([normalWindow styleMask] & NSWindowStyleMaskResizable) == 0) {
- [normalWindow setStyleMask:[normalWindow styleMask] | NSWindowStyleMaskResizable];
- [sender setState: NSControlStateValueOn];
- } else {
- [normalWindow setStyleMask:[normalWindow styleMask] & ~NSWindowStyleMaskResizable];
- [cocoaView resizeWindow];
- [sender setState: NSControlStateValueOff];
- }
- }
- /* Displays the console on the screen */
- - (void)displayConsole:(id)sender
- {
- with_iothread_lock(^{
- [cocoaView selectConsoleLocked:[sender tag]];
- });
- }
- /* Pause the guest */
- - (void)pauseQEMU:(id)sender
- {
- with_iothread_lock(^{
- 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_iothread_lock(^{
- 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 = ([normalWindow frame].size.width - [pauseLabel frame].size.width)/2;
- yCoord = [normalWindow 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_iothread_lock(^{
- qmp_system_reset(NULL);
- });
- }
- /* Powers down QEMU */
- - (void)powerDownQEMU:(id)sender
- {
- with_iothread_lock(^{
- 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_iothread_lock(^{
- 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_iothread_lock(^{
- qmp_blockdev_change_medium([drive cStringUsingEncoding:
- NSASCIIStringEncoding],
- NULL,
- [file cStringUsingEncoding:
- NSASCIIStringEncoding],
- "raw",
- true, false,
- 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_iothread_lock(^{
- 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
- [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Zoom To Fit" action:@selector(zoomToFit:) keyEquivalent:@""] autorelease]];
- 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;
- size_t index;
- menu = [[[NSApp mainMenu] itemWithTitle:@"View"] submenu];
- [menu addItem:[NSMenuItem separatorItem]];
- for (index = 0; index < listeners_count; index++) {
- menuItem = [[[NSMenuItem alloc] initWithTitle: getConsoleName(listeners[index].dcl.con)
- action: @selector(displayConsole:) keyEquivalent: @""] autorelease];
- [menuItem setTag: index];
- [menu addItem: menuItem];
- }
- }
- /* 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_iothread_lock(^{
- 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) {
- qemu_mutex_unlock_iothread();
- qemu_event_wait(&cbevent);
- qemu_mutex_lock_iothread();
- }
- 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 QemuCocoaPasteboardTypeOwner *cbowner;
- 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;
- }
- }
- /*
- * The startup process for the OSX/Cocoa UI is complicated, because
- * OSX insists that the UI runs on the initial main thread, and so we
- * need to start a second thread which runs the qemu_default_main():
- * in main():
- * in cocoa_display_init():
- * assign cocoa_main to qemu_main
- * create application, menus, etc
- * in cocoa_main():
- * create qemu-main thread
- * enter OSX run loop
- */
- static void *call_qemu_main(void *opaque)
- {
- int status;
- COCOA_DEBUG("Second thread: calling qemu_default_main()\n");
- qemu_mutex_lock_iothread();
- status = qemu_default_main();
- qemu_mutex_unlock_iothread();
- COCOA_DEBUG("Second thread: qemu_default_main() returned, exiting\n");
- #ifdef CONFIG_OPENGL
- qemu_gl_fini_shader(dgc.gls);
- if (view_ctx) {
- cocoa_gl_destroy_context(&dgc, view_ctx);
- }
- #endif
- [cbowner release];
- exit(status);
- }
- static int cocoa_main()
- {
- QemuThread thread;
- COCOA_DEBUG("Entered %s()\n", __func__);
- qemu_mutex_unlock_iothread();
- qemu_thread_create(&thread, "qemu_main", call_qemu_main,
- NULL, QEMU_THREAD_DETACHED);
- // Start the main event loop
- 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)
- {
- DisplaySurface *updated = surface;
- if (container_of(dcl, CocoaListener, dcl) != active_listener) {
- return;
- }
- COCOA_DEBUG("qemu_cocoa: cocoa_update\n");
- dispatch_async(dispatch_get_main_queue(), ^{
- qemu_mutex_lock(&draw_mutex);
- if (updated != surface) {
- qemu_mutex_unlock(&draw_mutex);
- return;
- }
- int full_height = surface_height(surface);
- qemu_mutex_unlock(&draw_mutex);
- CGFloat d = [cocoaView frame].size.height / full_height;
- NSRect rect = NSMakeRect(x * d, (full_height - y - h) * d, w * d, h * d);
- [cocoaView setNeedsDisplayInRect:rect];
- });
- }
- static void cocoa_switch(DisplayChangeListener *dcl,
- DisplaySurface *new_surface)
- {
- COCOA_DEBUG("qemu_cocoa: cocoa_switch\n");
- if (container_of(dcl, CocoaListener, dcl) != active_listener) {
- return;
- }
- qemu_mutex_lock(&draw_mutex);
- surface = new_surface;
- qemu_mutex_unlock(&draw_mutex);
- dispatch_async(dispatch_get_main_queue(), ^{
- qemu_mutex_lock(&draw_mutex);
- int w = surface_width(surface);
- int h = surface_height(surface);
- qemu_mutex_unlock(&draw_mutex);
- [cocoaView updateScreenWidth:w height:h];
- });
- }
- static void cocoa_refresh(DisplayChangeListener *dcl)
- {
- NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
- COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n");
- if (container_of(dcl, CocoaListener, dcl) != active_listener) {
- return;
- }
- 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, int on)
- {
- CocoaListener *listener = container_of(dcl, CocoaListener, dcl);
- qemu_mutex_lock(&draw_mutex);
- int full_height = surface_height(surface);
- int old_x = listener->mouse_x;
- int old_y = listener->mouse_y;
- listener->mouse_x = x;
- listener->mouse_y = y;
- listener->mouse_on = on;
- qemu_mutex_unlock(&draw_mutex);
- if (listener == active_listener && cursor_cgimage) {
- size_t cursor_width = CGImageGetWidth(cursor_cgimage);
- size_t cursor_height = CGImageGetHeight(cursor_cgimage);
- dispatch_async(dispatch_get_main_queue(), ^{
- CGRect clip_rect = compute_cursor_clip_rect(full_height,
- old_x, old_y,
- cursor_width,
- cursor_height);
- CGRect draw_rect =
- [cocoaView convertCursorClipRectToDraw:clip_rect
- screenHeight:full_height
- mouseX:old_x
- mouseY:old_y];
- [cocoaView setNeedsDisplayInRect:draw_rect];
- clip_rect = compute_cursor_clip_rect(full_height, x, y,
- cursor_width,
- cursor_height);
- draw_rect =
- [cocoaView convertCursorClipRectToDraw:clip_rect
- screenHeight:full_height
- mouseX:x
- mouseY:y];
- [cocoaView setNeedsDisplayInRect:draw_rect];
- });
- }
- }
- static void cocoa_cursor_update()
- {
- CGImageRef old_image = cursor_cgimage;
- CGImageRef new_image;
- if (active_listener->cursor) {
- CGDataProviderRef provider = CGDataProviderCreateWithData(
- NULL,
- active_listener->cursor->data,
- active_listener->cursor->width * active_listener->cursor->height * 4,
- NULL
- );
- new_image = CGImageCreate(
- active_listener->cursor->width, //width
- active_listener->cursor->height, //height
- 8, //bitsPerComponent
- 32, //bitsPerPixel
- active_listener->cursor->width * 4, //bytesPerRow
- CGColorSpaceCreateWithName(kCGColorSpaceSRGB), //colorspace
- kCGBitmapByteOrder32Little | kCGImageAlphaFirst, //bitmapInfo
- provider, //provider
- NULL, //decode
- 0, //interpolate
- kCGRenderingIntentDefault //intent
- );
- CGDataProviderRelease(provider);
- } else {
- new_image = NULL;
- }
- qemu_mutex_lock(&draw_mutex);
- cursor_cgimage = new_image;
- qemu_mutex_unlock(&draw_mutex);
- CGImageRelease(old_image);
- }
- static void cocoa_cursor_define(DisplayChangeListener *dcl, QEMUCursor *cursor)
- {
- CocoaListener *listener = container_of(dcl, CocoaListener, dcl);
- listener->cursor = cursor;
- if (listener == active_listener) {
- int full_height = surface_height(surface);
- int width = cursor->width;
- int height = cursor->height;
- int x = listener->mouse_x;
- int y = listener->mouse_y;
- size_t old_width;
- size_t old_height;
- if (cursor_cgimage) {
- old_width = CGImageGetWidth(cursor_cgimage);
- old_height = CGImageGetHeight(cursor_cgimage);
- } else {
- old_width = 0;
- old_height = 0;
- }
- cocoa_cursor_update();
- dispatch_async(dispatch_get_main_queue(), ^{
- CGFloat d = [cocoaView frame].size.height / full_height;
- NSRect rect;
- rect.origin.x = d * x;
- rect.origin.y = d * (full_height - y - old_height);
- rect.size.width = d * old_width;
- rect.size.height = d * old_height;
- [cocoaView setNeedsDisplayInRect:rect];
- rect.size.width = d * width;
- rect.size.height = d * height;
- [cocoaView setNeedsDisplayInRect:rect];
- });
- }
- }
- 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,
- };
- #ifdef CONFIG_OPENGL
- static void with_view_ctx(CodeBlock block)
- {
- #ifdef CONFIG_EGL
- if (egl_surface) {
- eglMakeCurrent(qemu_egl_display, egl_surface, egl_surface, view_ctx);
- block();
- return;
- }
- #endif
- [(NSOpenGLContext *)view_ctx lock];
- [(NSOpenGLContext *)view_ctx makeCurrentContext];
- block();
- [(NSOpenGLContext *)view_ctx unlock];
- }
- static NSOpenGLPixelFormat *cocoa_gl_create_ns_pixel_format(int bpp)
- {
- NSOpenGLPixelFormatAttribute attributes[] = {
- NSOpenGLPFAOpenGLProfile,
- NSOpenGLProfileVersion4_1Core,
- NSOpenGLPFAColorSize,
- bpp,
- NSOpenGLPFADoubleBuffer,
- 0,
- };
- return [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes];
- }
- static int cocoa_gl_make_context_current(DisplayGLCtx *dgc, QEMUGLContext ctx)
- {
- #ifdef CONFIG_EGL
- if (egl_surface) {
- EGLSurface current_surface = ctx == EGL_NO_CONTEXT ? EGL_NO_SURFACE : egl_surface;
- return eglMakeCurrent(qemu_egl_display, current_surface, current_surface, ctx);
- }
- #endif
- if (ctx) {
- [(NSOpenGLContext *)ctx makeCurrentContext];
- } else {
- [NSOpenGLContext clearCurrentContext];
- }
- return 0;
- }
- static QEMUGLContext cocoa_gl_create_context(DisplayGLCtx *dgc,
- QEMUGLParams *params)
- {
- NSOpenGLPixelFormat *format;
- NSOpenGLContext *ctx;
- int bpp;
- #ifdef CONFIG_EGL
- if (egl_surface) {
- eglMakeCurrent(qemu_egl_display, egl_surface, egl_surface, view_ctx);
- return qemu_egl_create_context(dgc, params);
- }
- #endif
- bpp = PIXMAN_FORMAT_BPP(surface_format(surface));
- format = cocoa_gl_create_ns_pixel_format(bpp);
- ctx = [[NSOpenGLContext alloc] initWithFormat:format shareContext:view_ctx];
- [format release];
- return (QEMUGLContext)ctx;
- }
- static void cocoa_gl_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx)
- {
- #ifdef CONFIG_EGL
- if (egl_surface) {
- eglDestroyContext(qemu_egl_display, ctx);
- return;
- }
- #endif
- [(NSOpenGLContext *)ctx release];
- }
- static void cocoa_gl_flush()
- {
- #ifdef CONFIG_EGL
- if (egl_surface) {
- eglSwapBuffers(qemu_egl_display, egl_surface);
- return;
- }
- #endif
- [[NSOpenGLContext currentContext] flushBuffer];
- dispatch_async(dispatch_get_main_queue(), ^{
- [(NSOpenGLContext *)view_ctx update];
- });
- }
- static void cocoa_gl_update(DisplayChangeListener *dcl,
- int x, int y, int w, int h)
- {
- CocoaListener *listener = container_of(dcl, CocoaListener, dcl);
- if (listener != active_listener) {
- return;
- }
- with_view_ctx(^{
- surface_gl_update_texture(dgc.gls, surface, x, y, w, h);
- gl_dirty = true;
- });
- }
- static void cocoa_gl_cursor_render()
- {
- if (!active_listener->mouse_on) {
- return;
- }
- NSSize size = [cocoaView convertSizeToBacking:[cocoaView frame].size];
- CGFloat d = size.height / surface_height(surface);
- glViewport(
- d * active_listener->mouse_x,
- size.height - d * (active_listener->mouse_y + active_listener->cursor->height),
- d * active_listener->cursor->width,
- d * active_listener->cursor->height
- );
- glBindTexture(GL_TEXTURE_2D, cursor_texture);
- glEnable(GL_BLEND);
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- qemu_gl_run_texture_blit(dgc.gls, false);
- glDisable(GL_BLEND);
- }
- static void cocoa_gl_switch(DisplayChangeListener *dcl,
- DisplaySurface *new_surface)
- {
- CocoaListener *listener = container_of(dcl, CocoaListener, dcl);
- if (listener != active_listener) {
- return;
- }
- with_view_ctx(^{
- surface_gl_destroy_texture(dgc.gls, surface);
- surface_gl_create_texture(dgc.gls, new_surface);
- });
- cocoa_switch(dcl, new_surface);
- gl_dirty = true;
- }
- static void cocoa_gl_refresh(DisplayChangeListener *dcl)
- {
- CocoaListener *listener = container_of(dcl, CocoaListener, dcl);
- if (listener != active_listener) {
- return;
- }
- cocoa_refresh(dcl);
- if (gl_dirty) {
- gl_dirty = false;
- with_view_ctx(^{
- NSSize size = [cocoaView convertSizeToBacking:[cocoaView frame].size];
- if (listener->gl_scanout_borrow) {
- bool y0_top;
- GLint texture =
- listener->gl_scanout_borrow(listener->gl_scanout_id,
- &y0_top, NULL, NULL);
- glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0);
- glViewport(0, 0, size.width, size.height);
- glBindTexture(GL_TEXTURE_2D, texture);
- qemu_gl_run_texture_blit(dgc.gls, y0_top);
- } else {
- surface_gl_setup_viewport(dgc.gls, surface,
- size.width, size.height);
- glBindTexture(GL_TEXTURE_2D, surface->texture);
- surface_gl_render_texture(dgc.gls, surface);
- }
- cocoa_gl_cursor_render();
- cocoa_gl_flush();
- });
- }
- }
- static void cocoa_gl_scanout_disable(DisplayChangeListener *dcl)
- {
- CocoaListener *listener = container_of(dcl, CocoaListener, dcl);
- listener->gl_scanout_borrow = NULL;
- if (listener == active_listener) {
- gl_dirty = surface != NULL;
- }
- }
- static void cocoa_gl_cursor_update()
- {
- if (active_listener->cursor) {
- with_view_ctx(^{
- glBindTexture(GL_TEXTURE_2D, cursor_texture);
- glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT,
- active_listener->cursor->width);
- glTexImage2D(GL_TEXTURE_2D, 0,
- epoxy_is_desktop_gl() ? GL_RGBA : GL_BGRA,
- active_listener->cursor->width,
- active_listener->cursor->height,
- 0, GL_BGRA, GL_UNSIGNED_BYTE,
- active_listener->cursor->data);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- });
- }
- gl_dirty = true;
- }
- static void cocoa_gl_cursor_define(DisplayChangeListener *dcl, QEMUCursor *cursor)
- {
- CocoaListener *listener = container_of(dcl, CocoaListener, dcl);
- listener->cursor = cursor;
- if (listener == active_listener) {
- cocoa_gl_cursor_update();
- }
- }
- static void cocoa_gl_scanout_texture(DisplayChangeListener *dcl,
- uint32_t backing_id,
- DisplayGLTextureBorrower backing_borrow,
- uint32_t x, uint32_t y,
- uint32_t w, uint32_t h)
- {
- CocoaListener *listener = container_of(dcl, CocoaListener, dcl);
- listener->gl_scanout_id = backing_id;
- listener->gl_scanout_borrow = backing_borrow;
- gl_dirty = true;
- }
- static void cocoa_gl_scanout_flush(DisplayChangeListener *dcl,
- uint32_t x, uint32_t y,
- uint32_t w, uint32_t h)
- {
- if (container_of(dcl, CocoaListener, dcl) == active_listener) {
- gl_dirty = true;
- }
- }
- static void cocoa_gl_mouse_set(DisplayChangeListener *dcl, int x, int y, int on)
- {
- CocoaListener *listener = container_of(dcl, CocoaListener, dcl);
- listener->mouse_x = x;
- listener->mouse_y = y;
- listener->mouse_on = on;
- if (listener == active_listener) {
- gl_dirty = true;
- }
- }
- static const DisplayChangeListenerOps dcl_gl_ops = {
- .dpy_name = "cocoa-gl",
- .dpy_gfx_update = cocoa_gl_update,
- .dpy_gfx_switch = cocoa_gl_switch,
- .dpy_gfx_check_format = console_gl_check_format,
- .dpy_refresh = cocoa_gl_refresh,
- .dpy_mouse_set = cocoa_gl_mouse_set,
- .dpy_cursor_define = cocoa_gl_cursor_define,
- .dpy_gl_scanout_disable = cocoa_gl_scanout_disable,
- .dpy_gl_scanout_texture = cocoa_gl_scanout_texture,
- .dpy_gl_update = cocoa_gl_scanout_flush,
- };
- static bool cocoa_gl_is_compatible_dcl(DisplayGLCtx *dgc,
- DisplayChangeListener *dcl)
- {
- return dcl->ops == &dcl_gl_ops;
- }
- #endif
- static void cocoa_display_early_init(DisplayOptions *o)
- {
- assert(o->type == DISPLAY_TYPE_COCOA);
- if (o->has_gl && o->gl) {
- display_opengl = 1;
- }
- }
- static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts)
- {
- NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
- const DisplayChangeListenerOps *ops;
- size_t index;
- COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n");
- qemu_main = cocoa_main;
- // 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];
- qemu_mutex_init(&draw_mutex);
- if (display_opengl) {
- #ifdef CONFIG_OPENGL
- if (opts->gl == DISPLAYGL_MODE_ES) {
- #ifdef CONFIG_EGL
- if (qemu_egl_init_dpy_cocoa(DISPLAYGL_MODE_ES)) {
- exit(1);
- }
- view_ctx = qemu_egl_init_ctx();
- if (!view_ctx) {
- exit(1);
- }
- [cocoaView setWantsLayer:YES];
- egl_surface = qemu_egl_init_surface(view_ctx, [cocoaView layer]);
- if (!egl_surface) {
- exit(1);
- }
- #else
- error_report("OpenGLES without EGL is not supported - exiting");
- exit(1);
- #endif
- } else {
- NSOpenGLPixelFormat *format = cocoa_gl_create_ns_pixel_format(32);
- NSOpenGLView *view = [[NSOpenGLView alloc] initWithFrame:[cocoaView frame]
- pixelFormat:format];
- [format release];
- [cocoaView addSubview:view];
- view_ctx = [view openGLContext];
- [view release];
- #ifdef CONFIG_EGL
- egl_surface = EGL_NO_SURFACE;
- #endif
- cocoa_gl_make_context_current(&dgc, view_ctx);
- }
- dgc.gls = qemu_gl_init_shader();
- glGenTextures(1, &cursor_texture);
- // register vga output callbacks
- ops = &dcl_gl_ops;
- #else
- error_report("OpenGL is not enabled - exiting");
- exit(1);
- #endif
- } else {
- // register vga output callbacks
- ops = &dcl_ops;
- }
- while (qemu_console_lookup_by_index(listeners_count)) {
- listeners_count++;
- }
- if (listeners_count) {
- QemuConsole *con = qemu_console_lookup_first_graphic_console();
- listeners = g_new0(CocoaListener, listeners_count);
- active_listener = listeners + qemu_console_get_index(con);
- for (index = 0; index < listeners_count; index++) {
- listeners[index].dcl.con = qemu_console_lookup_by_index(index);
- listeners[index].dcl.ops = ops;
- #ifdef CONFIG_OPENGL
- if (display_opengl) {
- qemu_console_set_display_gl_ctx(listeners[index].dcl.con,
- &dgc);
- }
- #endif
- // register vga output callbacks
- register_displaychangelistener(&listeners[index].dcl);
- }
- kbd = qkbd_state_init(active_listener->dcl.con);
- qemu_add_mouse_mode_change_notifier(&mouse_mode_change_notifier);
- [cocoaView notifyMouseModeChange];
- }
- create_initial_menus();
- /*
- * Create the menu entries which depend on QEMU state (for consoles
- * and removeable devices). These make calls back into QEMU functions,
- * which is OK because at this point we know that the second thread
- * holds the iothread lock and is synchronously waiting for us to
- * finish.
- */
- add_console_menu_entries();
- addRemovableDevicesMenuItems();
- /* if fullscreen mode is to be used */
- if (opts->has_full_screen && opts->full_screen) {
- [normalWindow 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;
- }
- [cocoaView updateUIInfo];
- qemu_event_init(&cbevent, false);
- cbowner = [[QemuCocoaPasteboardTypeOwner alloc] init];
- qemu_clipboard_peer_register(&cbpeer);
- [pool release];
- }
- static QemuDisplay qemu_display_cocoa = {
- .type = DISPLAY_TYPE_COCOA,
- .early_init = cocoa_display_early_init,
- .init = cocoa_display_init,
- };
- static void register_cocoa(void)
- {
- qemu_display_register(&qemu_display_cocoa);
- }
- type_init(register_cocoa);
- #ifdef CONFIG_OPENGL
- module_dep("ui-opengl");
- #endif
|