cocoa.m 81 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619
  1. /*
  2. * QEMU Cocoa CG display driver
  3. *
  4. * Copyright (c) 2008 Mike Kronenberg
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining a copy
  7. * of this software and associated documentation files (the "Software"), to deal
  8. * in the Software without restriction, including without limitation the rights
  9. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. * copies of the Software, and to permit persons to whom the Software is
  11. * furnished to do so, subject to the following conditions:
  12. *
  13. * The above copyright notice and this permission notice shall be included in
  14. * all copies or substantial portions of the Software.
  15. *
  16. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  19. * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. * THE SOFTWARE.
  23. */
  24. #define GL_SILENCE_DEPRECATION
  25. #include "qemu/osdep.h"
  26. #import <Cocoa/Cocoa.h>
  27. #include <crt_externs.h>
  28. #include "qemu/help-texts.h"
  29. #include "qemu-main.h"
  30. #include "ui/clipboard.h"
  31. #include "ui/console.h"
  32. #include "ui/input.h"
  33. #include "ui/kbd-state.h"
  34. #include "sysemu/sysemu.h"
  35. #include "sysemu/runstate.h"
  36. #include "sysemu/runstate-action.h"
  37. #include "sysemu/cpu-throttle.h"
  38. #include "qapi/error.h"
  39. #include "qapi/qapi-commands-block.h"
  40. #include "qapi/qapi-commands-machine.h"
  41. #include "qapi/qapi-commands-misc.h"
  42. #include "sysemu/blockdev.h"
  43. #include "qemu-version.h"
  44. #include "qemu/cutils.h"
  45. #include "qemu/main-loop.h"
  46. #include "qemu/module.h"
  47. #include "qemu/error-report.h"
  48. #include <Carbon/Carbon.h>
  49. #include "hw/core/cpu.h"
  50. #ifdef CONFIG_EGL
  51. #include "ui/egl-context.h"
  52. #endif
  53. #ifndef MAC_OS_X_VERSION_10_13
  54. #define MAC_OS_X_VERSION_10_13 101300
  55. #endif
  56. /* 10.14 deprecates NSOnState and NSOffState in favor of
  57. * NSControlStateValueOn/Off, which were introduced in 10.13.
  58. * Define for older versions
  59. */
  60. #if MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_13
  61. #define NSControlStateValueOn NSOnState
  62. #define NSControlStateValueOff NSOffState
  63. #endif
  64. //#define DEBUG
  65. #ifdef DEBUG
  66. #define COCOA_DEBUG(...) { (void) fprintf (stdout, __VA_ARGS__); }
  67. #else
  68. #define COCOA_DEBUG(...) ((void) 0)
  69. #endif
  70. #define cgrect(nsrect) (*(CGRect *)&(nsrect))
  71. #define UC_CTRL_KEY "\xe2\x8c\x83"
  72. #define UC_ALT_KEY "\xe2\x8c\xa5"
  73. typedef struct CocoaListener {
  74. DisplayChangeListener dcl;
  75. QEMUCursor *cursor;
  76. int mouse_x;
  77. int mouse_y;
  78. int mouse_on;
  79. #ifdef CONFIG_OPENGL
  80. uint32_t gl_scanout_id;
  81. DisplayGLTextureBorrower gl_scanout_borrow;
  82. bool gl_scanout_y0_top;
  83. #endif
  84. } CocoaListener;
  85. typedef struct {
  86. int width;
  87. int height;
  88. } QEMUScreen;
  89. static void cocoa_switch(DisplayChangeListener *dcl,
  90. DisplaySurface *surface);
  91. static void cocoa_cursor_update(void);
  92. static NSWindow *normalWindow;
  93. static CocoaListener *active_listener;
  94. static CocoaListener *listeners;
  95. static size_t listeners_count;
  96. static DisplaySurface *surface;
  97. static QemuMutex draw_mutex;
  98. static CGImageRef cursor_cgimage;
  99. static QKbdState *kbd;
  100. static int cursor_hide = 1;
  101. static int left_command_key_enabled = 1;
  102. static bool swap_opt_cmd;
  103. static NSTextField *pauseLabel;
  104. static NSInteger cbchangecount = -1;
  105. static QemuClipboardInfo *cbinfo;
  106. static QemuEvent cbevent;
  107. #ifdef CONFIG_OPENGL
  108. static GLuint cursor_texture;
  109. static bool gl_dirty;
  110. static QEMUGLContext view_ctx;
  111. #ifdef CONFIG_EGL
  112. static EGLSurface egl_surface;
  113. #endif
  114. static void cocoa_gl_switch(DisplayChangeListener *dcl,
  115. DisplaySurface *new_surface);
  116. static void cocoa_gl_cursor_update(void);
  117. static bool cocoa_gl_is_compatible_dcl(DisplayGLCtx *dgc,
  118. DisplayChangeListener *dcl);
  119. static QEMUGLContext cocoa_gl_create_context(DisplayGLCtx *dgc,
  120. QEMUGLParams *params);
  121. static void cocoa_gl_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx);
  122. static int cocoa_gl_make_context_current(DisplayGLCtx *dgc, QEMUGLContext ctx);
  123. static const DisplayGLCtxOps dgc_ops = {
  124. .dpy_gl_ctx_is_compatible_dcl = cocoa_gl_is_compatible_dcl,
  125. .dpy_gl_ctx_create = cocoa_gl_create_context,
  126. .dpy_gl_ctx_destroy = cocoa_gl_destroy_context,
  127. .dpy_gl_ctx_make_current = cocoa_gl_make_context_current,
  128. };
  129. static DisplayGLCtx dgc = {
  130. .ops = &dgc_ops,
  131. };
  132. #endif
  133. // Utility functions to run specified code block with iothread lock held
  134. typedef void (^CodeBlock)(void);
  135. typedef bool (^BoolCodeBlock)(void);
  136. static void with_iothread_lock(CodeBlock block)
  137. {
  138. bool locked = qemu_mutex_iothread_locked();
  139. if (!locked) {
  140. qemu_mutex_lock_iothread();
  141. }
  142. block();
  143. if (!locked) {
  144. qemu_mutex_unlock_iothread();
  145. }
  146. }
  147. static bool bool_with_iothread_lock(BoolCodeBlock block)
  148. {
  149. bool locked = qemu_mutex_iothread_locked();
  150. bool val;
  151. if (!locked) {
  152. qemu_mutex_lock_iothread();
  153. }
  154. val = block();
  155. if (!locked) {
  156. qemu_mutex_unlock_iothread();
  157. }
  158. return val;
  159. }
  160. static int cocoa_keycode_to_qemu(int keycode)
  161. {
  162. if (qemu_input_map_osx_to_qcode_len <= keycode) {
  163. error_report("(cocoa) warning unknown keycode 0x%x", keycode);
  164. return 0;
  165. }
  166. return qemu_input_map_osx_to_qcode[keycode];
  167. }
  168. /* Displays an alert dialog box with the specified message */
  169. static void QEMU_Alert(NSString *message)
  170. {
  171. NSAlert *alert;
  172. alert = [NSAlert new];
  173. [alert setMessageText: message];
  174. [alert runModal];
  175. }
  176. /* Handles any errors that happen with a device transaction */
  177. static void handleAnyDeviceErrors(Error * err)
  178. {
  179. if (err) {
  180. QEMU_Alert([NSString stringWithCString: error_get_pretty(err)
  181. encoding: NSASCIIStringEncoding]);
  182. error_free(err);
  183. }
  184. }
  185. static CGRect compute_cursor_clip_rect(int screen_height,
  186. int given_mouse_x, int given_mouse_y,
  187. int cursor_width, int cursor_height)
  188. {
  189. CGRect rect;
  190. rect.origin.x = MAX(0, -given_mouse_x);
  191. rect.origin.y = 0;
  192. rect.size.width = MIN(cursor_width, cursor_width + given_mouse_x);
  193. rect.size.height = cursor_height - rect.origin.x;
  194. return rect;
  195. }
  196. /*
  197. ------------------------------------------------------
  198. QemuCocoaView
  199. ------------------------------------------------------
  200. */
  201. @interface QemuCocoaView : NSView
  202. {
  203. NSTrackingArea *trackingArea;
  204. QEMUScreen screen;
  205. /* The state surrounding mouse grabbing is potentially confusing.
  206. * isAbsoluteEnabled tracks qemu_input_is_absolute() [ie "is the emulated
  207. * pointing device an absolute-position one?"], but is only updated on
  208. * next refresh.
  209. * isMouseGrabbed tracks whether GUI events are directed to the guest;
  210. * it controls whether special keys like Cmd get sent to the guest,
  211. * and whether we capture the mouse when in non-absolute mode.
  212. */
  213. BOOL isMouseGrabbed;
  214. BOOL isAbsoluteEnabled;
  215. CFMachPortRef eventsTap;
  216. }
  217. - (void) grabMouse;
  218. - (void) ungrabMouse;
  219. - (void) setFullGrab:(id)sender;
  220. - (void) handleMonitorInput:(NSEvent *)event;
  221. - (bool) handleEvent:(NSEvent *)event;
  222. - (bool) handleEventLocked:(NSEvent *)event;
  223. - (void) notifyMouseModeChange;
  224. - (BOOL) isMouseGrabbed;
  225. - (void) raiseAllKeys;
  226. @end
  227. QemuCocoaView *cocoaView;
  228. static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEventRef cgEvent, void *userInfo)
  229. {
  230. QemuCocoaView *cocoaView = userInfo;
  231. NSEvent *event = [NSEvent eventWithCGEvent:cgEvent];
  232. if ([cocoaView isMouseGrabbed] && [cocoaView handleEvent:event]) {
  233. COCOA_DEBUG("Global events tap: qemu handled the event, capturing!\n");
  234. return NULL;
  235. }
  236. COCOA_DEBUG("Global events tap: qemu did not handle the event, letting it through...\n");
  237. return cgEvent;
  238. }
  239. @implementation QemuCocoaView
  240. - (id)initWithFrame:(NSRect)frameRect
  241. {
  242. COCOA_DEBUG("QemuCocoaView: initWithFrame\n");
  243. self = [super initWithFrame:frameRect];
  244. if (self) {
  245. screen.width = frameRect.size.width;
  246. screen.height = frameRect.size.height;
  247. }
  248. return self;
  249. }
  250. - (void) dealloc
  251. {
  252. COCOA_DEBUG("QemuCocoaView: dealloc\n");
  253. if (eventsTap) {
  254. CFRelease(eventsTap);
  255. }
  256. [super dealloc];
  257. }
  258. - (BOOL) isOpaque
  259. {
  260. return YES;
  261. }
  262. - (void) removeTrackingRect
  263. {
  264. if (trackingArea) {
  265. [self removeTrackingArea:trackingArea];
  266. [trackingArea release];
  267. trackingArea = nil;
  268. }
  269. }
  270. - (void) frameUpdated
  271. {
  272. [self removeTrackingRect];
  273. if ([self window]) {
  274. NSTrackingAreaOptions options = NSTrackingActiveInKeyWindow |
  275. NSTrackingMouseEnteredAndExited |
  276. NSTrackingMouseMoved;
  277. trackingArea = [[NSTrackingArea alloc] initWithRect:[self frame]
  278. options:options
  279. owner:self
  280. userInfo:nil];
  281. [self addTrackingArea:trackingArea];
  282. [self updateUIInfo];
  283. }
  284. }
  285. - (void) viewDidMoveToWindow
  286. {
  287. [self resizeWindow];
  288. [self frameUpdated];
  289. }
  290. - (void) viewWillMoveToWindow:(NSWindow *)newWindow
  291. {
  292. [self removeTrackingRect];
  293. }
  294. - (void) selectConsoleLocked:(unsigned int)index
  295. {
  296. DisplaySurface *new_surface;
  297. if (index >= listeners_count) {
  298. return;
  299. }
  300. active_listener = &listeners[index];
  301. new_surface = qemu_console_surface(active_listener->dcl.con);
  302. qkbd_state_lift_all_keys(kbd);
  303. qkbd_state_free(kbd);
  304. kbd = qkbd_state_init(active_listener->dcl.con);
  305. if (display_opengl) {
  306. #ifdef CONFIG_OPENGL
  307. cocoa_gl_cursor_update();
  308. cocoa_gl_switch(&active_listener->dcl, new_surface);
  309. #else
  310. g_assert_not_reached();
  311. #endif
  312. } else {
  313. cocoa_cursor_update();
  314. cocoa_switch(&active_listener->dcl, new_surface);
  315. }
  316. [self notifyMouseModeChange];
  317. [self updateUIInfoLocked];
  318. }
  319. - (void) hideCursor
  320. {
  321. if (!cursor_hide) {
  322. return;
  323. }
  324. [NSCursor hide];
  325. }
  326. - (void) unhideCursor
  327. {
  328. if (!cursor_hide) {
  329. return;
  330. }
  331. [NSCursor unhide];
  332. }
  333. - (CGRect) convertCursorClipRectToDraw:(CGRect)rect
  334. screenHeight:(int)screen_height
  335. mouseX:(int)given_mouse_x
  336. mouseY:(int)given_mouse_y
  337. {
  338. CGFloat d = [self frame].size.height / (CGFloat)screen_height;
  339. rect.origin.x = (rect.origin.x + given_mouse_x) * d;
  340. rect.origin.y = (screen_height - rect.origin.y - given_mouse_y - rect.size.height) * d;
  341. rect.size.width *= d;
  342. rect.size.height *= d;
  343. return rect;
  344. }
  345. - (void) drawRect:(NSRect) rect
  346. {
  347. COCOA_DEBUG("QemuCocoaView: drawRect\n");
  348. #ifdef CONFIG_OPENGL
  349. if (display_opengl) {
  350. return;
  351. }
  352. #endif
  353. // get CoreGraphic context
  354. CGContextRef viewContextRef = [[NSGraphicsContext currentContext] CGContext];
  355. CGContextSetInterpolationQuality (viewContextRef, kCGInterpolationNone);
  356. CGContextSetShouldAntialias (viewContextRef, NO);
  357. qemu_mutex_lock(&draw_mutex);
  358. // draw screen bitmap directly to Core Graphics context
  359. if (!surface) {
  360. // Draw request before any guest device has set up a framebuffer:
  361. // just draw an opaque black rectangle
  362. CGContextSetRGBFillColor(viewContextRef, 0, 0, 0, 1.0);
  363. CGContextFillRect(viewContextRef, NSRectToCGRect(rect));
  364. } else {
  365. int w = surface_width(surface);
  366. int h = surface_height(surface);
  367. int bitsPerPixel = PIXMAN_FORMAT_BPP(surface_format(surface));
  368. int stride = surface_stride(surface);
  369. CGDataProviderRef dataProviderRef = CGDataProviderCreateWithData(
  370. NULL,
  371. surface_data(surface),
  372. stride * h,
  373. NULL
  374. );
  375. CGImageRef imageRef = CGImageCreate(
  376. w, //width
  377. h, //height
  378. DIV_ROUND_UP(bitsPerPixel, 8) * 2, //bitsPerComponent
  379. bitsPerPixel, //bitsPerPixel
  380. stride, //bytesPerRow
  381. CGColorSpaceCreateWithName(kCGColorSpaceSRGB), //colorspace
  382. kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, //bitmapInfo
  383. dataProviderRef, //provider
  384. NULL, //decode
  385. 0, //interpolate
  386. kCGRenderingIntentDefault //intent
  387. );
  388. // selective drawing code (draws only dirty rectangles) (OS X >= 10.4)
  389. const NSRect *rectList;
  390. NSInteger rectCount;
  391. int i;
  392. CGImageRef clipImageRef;
  393. CGRect clipRect;
  394. CGFloat d = (CGFloat)h / [self frame].size.height;
  395. [self getRectsBeingDrawn:&rectList count:&rectCount];
  396. for (i = 0; i < rectCount; i++) {
  397. clipRect.origin.x = rectList[i].origin.x * d;
  398. clipRect.origin.y = (float)h - (rectList[i].origin.y + rectList[i].size.height) * d;
  399. clipRect.size.width = rectList[i].size.width * d;
  400. clipRect.size.height = rectList[i].size.height * d;
  401. clipImageRef = CGImageCreateWithImageInRect(
  402. imageRef,
  403. clipRect
  404. );
  405. CGContextDrawImage (viewContextRef, cgrect(rectList[i]), clipImageRef);
  406. CGImageRelease (clipImageRef);
  407. }
  408. CGImageRelease (imageRef);
  409. CGDataProviderRelease(dataProviderRef);
  410. if (active_listener->mouse_on) {
  411. size_t cursor_width = CGImageGetWidth(cursor_cgimage);
  412. size_t cursor_height = CGImageGetHeight(cursor_cgimage);
  413. int mouse_x = active_listener->mouse_x;
  414. int mouse_y = active_listener->mouse_y;
  415. clipRect = compute_cursor_clip_rect(h, mouse_x, mouse_y,
  416. cursor_width,
  417. cursor_height);
  418. CGRect drawRect = [self convertCursorClipRectToDraw:clipRect
  419. screenHeight:h
  420. mouseX:mouse_x
  421. mouseY:mouse_y];
  422. clipImageRef = CGImageCreateWithImageInRect(
  423. cursor_cgimage,
  424. clipRect
  425. );
  426. CGContextDrawImage(viewContextRef, drawRect, clipImageRef);
  427. CGImageRelease (clipImageRef);
  428. }
  429. }
  430. qemu_mutex_unlock(&draw_mutex);
  431. }
  432. - (NSSize) computeUnzoomedSize
  433. {
  434. CGFloat width = screen.width / [[self window] backingScaleFactor];
  435. CGFloat height = screen.height / [[self window] backingScaleFactor];
  436. return NSMakeSize(width, height);
  437. }
  438. - (NSSize) fixZoomedFullScreenSize:(NSSize)proposedSize
  439. {
  440. NSSize size;
  441. size.width = (CGFloat)screen.width * proposedSize.height;
  442. size.height = (CGFloat)screen.height * proposedSize.width;
  443. if (size.width < size.height) {
  444. size.width /= screen.height;
  445. size.height = proposedSize.height;
  446. } else {
  447. size.width = proposedSize.width;
  448. size.height /= screen.width;
  449. }
  450. return size;
  451. }
  452. - (NSSize) screenSafeAreaSize
  453. {
  454. NSSize size = [[[self window] screen] frame].size;
  455. NSEdgeInsets insets = [[[self window] screen] safeAreaInsets];
  456. size.width -= insets.left + insets.right;
  457. size.height -= insets.top + insets.bottom;
  458. return size;
  459. }
  460. - (void) resizeWindow
  461. {
  462. [[self window] setContentAspectRatio:NSMakeSize(screen.width, screen.height)];
  463. if (([[self window] styleMask] & NSWindowStyleMaskResizable) == 0) {
  464. [[self window] setContentSize:[self computeUnzoomedSize]];
  465. [[self window] center];
  466. } else if (([[self window] styleMask] & NSWindowStyleMaskFullScreen) != 0) {
  467. [[self window] setContentSize:[self fixZoomedFullScreenSize:[self screenSafeAreaSize]]];
  468. [[self window] center];
  469. }
  470. }
  471. - (void) updateUIInfoLocked
  472. {
  473. /* Must be called with the iothread lock, i.e. via updateUIInfo */
  474. NSSize frameSize;
  475. QemuUIInfo info;
  476. if ([self window]) {
  477. NSDictionary *description = [[[self window] screen] deviceDescription];
  478. CGDirectDisplayID display = [[description objectForKey:@"NSScreenNumber"] unsignedIntValue];
  479. NSSize screenSize = [[[self window] screen] frame].size;
  480. CGSize screenPhysicalSize = CGDisplayScreenSize(display);
  481. CVDisplayLinkRef displayLink;
  482. if (([[self window] styleMask] & NSWindowStyleMaskFullScreen) == 0) {
  483. frameSize = [self frame].size;
  484. } else {
  485. frameSize = [self screenSafeAreaSize];
  486. }
  487. if (!CVDisplayLinkCreateWithCGDisplay(display, &displayLink)) {
  488. CVTime period = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(displayLink);
  489. CVDisplayLinkRelease(displayLink);
  490. if (!(period.flags & kCVTimeIsIndefinite)) {
  491. update_displaychangelistener(&active_listener->dcl,
  492. 1000 * period.timeValue / period.timeScale);
  493. info.refresh_rate = (int64_t)1000 * period.timeScale / period.timeValue;
  494. }
  495. }
  496. info.width_mm = frameSize.width / screenSize.width * screenPhysicalSize.width;
  497. info.height_mm = frameSize.height / screenSize.height * screenPhysicalSize.height;
  498. } else {
  499. frameSize = [self frame].size;
  500. info.width_mm = 0;
  501. info.height_mm = 0;
  502. }
  503. NSSize frameBackingSize = [self convertSizeToBacking:frameSize];
  504. info.xoff = 0;
  505. info.yoff = 0;
  506. info.width = frameBackingSize.width;
  507. info.height = frameBackingSize.height;
  508. dpy_set_ui_info(active_listener->dcl.con, &info, TRUE);
  509. }
  510. - (void) updateUIInfo
  511. {
  512. if (!listeners) {
  513. return;
  514. }
  515. with_iothread_lock(^{
  516. [self updateUIInfoLocked];
  517. });
  518. }
  519. - (void) updateScreenWidth:(int)w height:(int)h
  520. {
  521. COCOA_DEBUG("QemuCocoaView: updateScreenWidth:height:\n");
  522. if (w != screen.width || h != screen.height) {
  523. COCOA_DEBUG("updateScreenWidth:height: new size %d x %d\n", w, h);
  524. screen.width = w;
  525. screen.height = h;
  526. [self resizeWindow];
  527. }
  528. }
  529. - (void) setFullGrab:(id)sender
  530. {
  531. COCOA_DEBUG("QemuCocoaView: setFullGrab\n");
  532. CGEventMask mask = CGEventMaskBit(kCGEventKeyDown) | CGEventMaskBit(kCGEventKeyUp) | CGEventMaskBit(kCGEventFlagsChanged);
  533. eventsTap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault,
  534. mask, handleTapEvent, self);
  535. if (!eventsTap) {
  536. warn_report("Could not create event tap, system key combos will not be captured.\n");
  537. return;
  538. } else {
  539. COCOA_DEBUG("Global events tap created! Will capture system key combos.\n");
  540. }
  541. CFRunLoopRef runLoop = CFRunLoopGetCurrent();
  542. if (!runLoop) {
  543. warn_report("Could not obtain current CF RunLoop, system key combos will not be captured.\n");
  544. return;
  545. }
  546. CFRunLoopSourceRef tapEventsSrc = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventsTap, 0);
  547. if (!tapEventsSrc ) {
  548. warn_report("Could not obtain current CF RunLoop, system key combos will not be captured.\n");
  549. return;
  550. }
  551. CFRunLoopAddSource(runLoop, tapEventsSrc, kCFRunLoopDefaultMode);
  552. CFRelease(tapEventsSrc);
  553. }
  554. - (void) toggleKey: (int)keycode {
  555. qkbd_state_key_event(kbd, keycode, !qkbd_state_key_get(kbd, keycode));
  556. }
  557. // Does the work of sending input to the monitor
  558. - (void) handleMonitorInput:(NSEvent *)event
  559. {
  560. int keysym = 0;
  561. int control_key = 0;
  562. // if the control key is down
  563. if ([event modifierFlags] & NSEventModifierFlagControl) {
  564. control_key = 1;
  565. }
  566. /* translates Macintosh keycodes to QEMU's keysym */
  567. static const int without_control_translation[] = {
  568. [0 ... 0xff] = 0, // invalid key
  569. [kVK_UpArrow] = QEMU_KEY_UP,
  570. [kVK_DownArrow] = QEMU_KEY_DOWN,
  571. [kVK_RightArrow] = QEMU_KEY_RIGHT,
  572. [kVK_LeftArrow] = QEMU_KEY_LEFT,
  573. [kVK_Home] = QEMU_KEY_HOME,
  574. [kVK_End] = QEMU_KEY_END,
  575. [kVK_PageUp] = QEMU_KEY_PAGEUP,
  576. [kVK_PageDown] = QEMU_KEY_PAGEDOWN,
  577. [kVK_ForwardDelete] = QEMU_KEY_DELETE,
  578. [kVK_Delete] = QEMU_KEY_BACKSPACE,
  579. };
  580. static const int with_control_translation[] = {
  581. [0 ... 0xff] = 0, // invalid key
  582. [kVK_UpArrow] = QEMU_KEY_CTRL_UP,
  583. [kVK_DownArrow] = QEMU_KEY_CTRL_DOWN,
  584. [kVK_RightArrow] = QEMU_KEY_CTRL_RIGHT,
  585. [kVK_LeftArrow] = QEMU_KEY_CTRL_LEFT,
  586. [kVK_Home] = QEMU_KEY_CTRL_HOME,
  587. [kVK_End] = QEMU_KEY_CTRL_END,
  588. [kVK_PageUp] = QEMU_KEY_CTRL_PAGEUP,
  589. [kVK_PageDown] = QEMU_KEY_CTRL_PAGEDOWN,
  590. };
  591. if (control_key != 0) { /* If the control key is being used */
  592. if ([event keyCode] < ARRAY_SIZE(with_control_translation)) {
  593. keysym = with_control_translation[[event keyCode]];
  594. }
  595. } else {
  596. if ([event keyCode] < ARRAY_SIZE(without_control_translation)) {
  597. keysym = without_control_translation[[event keyCode]];
  598. }
  599. }
  600. // if not a key that needs translating
  601. if (keysym == 0) {
  602. NSString *ks = [event characters];
  603. if ([ks length] > 0) {
  604. keysym = [ks characterAtIndex:0];
  605. }
  606. }
  607. if (keysym) {
  608. kbd_put_keysym_console(active_listener->dcl.con, keysym);
  609. }
  610. }
  611. - (bool) handleEvent:(NSEvent *)event
  612. {
  613. if(!listeners) {
  614. return false;
  615. }
  616. return bool_with_iothread_lock(^{
  617. return [self handleEventLocked:event];
  618. });
  619. }
  620. - (bool) handleEventLocked:(NSEvent *)event
  621. {
  622. /* Return true if we handled the event, false if it should be given to OSX */
  623. COCOA_DEBUG("QemuCocoaView: handleEvent\n");
  624. int buttons = 0;
  625. int keycode = 0;
  626. NSUInteger modifiers = [event modifierFlags];
  627. /*
  628. * Check -[NSEvent modifierFlags] here.
  629. *
  630. * There is a NSEventType for an event notifying the change of
  631. * -[NSEvent modifierFlags], NSEventTypeFlagsChanged but these operations
  632. * are performed for any events because a modifier state may change while
  633. * the application is inactive (i.e. no events fire) and we don't want to
  634. * wait for another modifier state change to detect such a change.
  635. *
  636. * NSEventModifierFlagCapsLock requires a special treatment. The other flags
  637. * are handled in similar manners.
  638. *
  639. * NSEventModifierFlagCapsLock
  640. * ---------------------------
  641. *
  642. * If CapsLock state is changed, "up" and "down" events will be fired in
  643. * sequence, effectively updates CapsLock state on the guest.
  644. *
  645. * The other flags
  646. * ---------------
  647. *
  648. * If a flag is not set, fire "up" events for all keys which correspond to
  649. * the flag. Note that "down" events are not fired here because the flags
  650. * checked here do not tell what exact keys are down.
  651. *
  652. * If one of the keys corresponding to a flag is down, we rely on
  653. * -[NSEvent keyCode] of an event whose -[NSEvent type] is
  654. * NSEventTypeFlagsChanged to know the exact key which is down, which has
  655. * the following two downsides:
  656. * - It does not work when the application is inactive as described above.
  657. * - It malfactions *after* the modifier state is changed while the
  658. * application is inactive. It is because -[NSEvent keyCode] does not tell
  659. * if the key is up or down, and requires to infer the current state from
  660. * the previous state. It is still possible to fix such a malfanction by
  661. * completely leaving your hands from the keyboard, which hopefully makes
  662. * this implementation usable enough.
  663. */
  664. if (!!(modifiers & NSEventModifierFlagCapsLock) !=
  665. qkbd_state_modifier_get(kbd, QKBD_MOD_CAPSLOCK)) {
  666. qkbd_state_key_event(kbd, Q_KEY_CODE_CAPS_LOCK, true);
  667. qkbd_state_key_event(kbd, Q_KEY_CODE_CAPS_LOCK, false);
  668. }
  669. if (!(modifiers & NSEventModifierFlagShift)) {
  670. qkbd_state_key_event(kbd, Q_KEY_CODE_SHIFT, false);
  671. qkbd_state_key_event(kbd, Q_KEY_CODE_SHIFT_R, false);
  672. }
  673. if (!(modifiers & NSEventModifierFlagControl)) {
  674. qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL, false);
  675. qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL_R, false);
  676. }
  677. if (!(modifiers & NSEventModifierFlagOption)) {
  678. if (swap_opt_cmd) {
  679. qkbd_state_key_event(kbd, Q_KEY_CODE_META_L, false);
  680. qkbd_state_key_event(kbd, Q_KEY_CODE_META_R, false);
  681. } else {
  682. qkbd_state_key_event(kbd, Q_KEY_CODE_ALT, false);
  683. qkbd_state_key_event(kbd, Q_KEY_CODE_ALT_R, false);
  684. }
  685. }
  686. if (!(modifiers & NSEventModifierFlagCommand)) {
  687. if (swap_opt_cmd) {
  688. qkbd_state_key_event(kbd, Q_KEY_CODE_ALT, false);
  689. qkbd_state_key_event(kbd, Q_KEY_CODE_ALT_R, false);
  690. } else {
  691. qkbd_state_key_event(kbd, Q_KEY_CODE_META_L, false);
  692. qkbd_state_key_event(kbd, Q_KEY_CODE_META_R, false);
  693. }
  694. }
  695. switch ([event type]) {
  696. case NSEventTypeFlagsChanged:
  697. switch ([event keyCode]) {
  698. case kVK_Shift:
  699. if (!!(modifiers & NSEventModifierFlagShift)) {
  700. [self toggleKey:Q_KEY_CODE_SHIFT];
  701. }
  702. return true;
  703. case kVK_RightShift:
  704. if (!!(modifiers & NSEventModifierFlagShift)) {
  705. [self toggleKey:Q_KEY_CODE_SHIFT_R];
  706. }
  707. return true;
  708. case kVK_Control:
  709. if (!!(modifiers & NSEventModifierFlagControl)) {
  710. [self toggleKey:Q_KEY_CODE_CTRL];
  711. }
  712. return true;
  713. case kVK_RightControl:
  714. if (!!(modifiers & NSEventModifierFlagControl)) {
  715. [self toggleKey:Q_KEY_CODE_CTRL_R];
  716. }
  717. return true;
  718. case kVK_Option:
  719. if (!!(modifiers & NSEventModifierFlagOption)) {
  720. if (swap_opt_cmd) {
  721. [self toggleKey:Q_KEY_CODE_META_L];
  722. } else {
  723. [self toggleKey:Q_KEY_CODE_ALT];
  724. }
  725. }
  726. return true;
  727. case kVK_RightOption:
  728. if (!!(modifiers & NSEventModifierFlagOption)) {
  729. if (swap_opt_cmd) {
  730. [self toggleKey:Q_KEY_CODE_META_R];
  731. } else {
  732. [self toggleKey:Q_KEY_CODE_ALT_R];
  733. }
  734. }
  735. return true;
  736. /* Don't pass command key changes to guest unless mouse is grabbed */
  737. case kVK_Command:
  738. if (isMouseGrabbed &&
  739. !!(modifiers & NSEventModifierFlagCommand) &&
  740. left_command_key_enabled) {
  741. if (swap_opt_cmd) {
  742. [self toggleKey:Q_KEY_CODE_ALT];
  743. } else {
  744. [self toggleKey:Q_KEY_CODE_META_L];
  745. }
  746. }
  747. return true;
  748. case kVK_RightCommand:
  749. if (isMouseGrabbed &&
  750. !!(modifiers & NSEventModifierFlagCommand)) {
  751. if (swap_opt_cmd) {
  752. [self toggleKey:Q_KEY_CODE_ALT_R];
  753. } else {
  754. [self toggleKey:Q_KEY_CODE_META_R];
  755. }
  756. }
  757. return true;
  758. default:
  759. return true;
  760. }
  761. case NSEventTypeKeyDown:
  762. keycode = cocoa_keycode_to_qemu([event keyCode]);
  763. // forward command key combos to the host UI unless the mouse is grabbed
  764. if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) {
  765. return false;
  766. }
  767. // default
  768. // handle control + alt Key Combos (ctrl+alt+[1..9,g] is reserved for QEMU)
  769. if (([event modifierFlags] & NSEventModifierFlagControl) && ([event modifierFlags] & NSEventModifierFlagOption)) {
  770. NSString *keychar = [event charactersIgnoringModifiers];
  771. if ([keychar length] == 1) {
  772. char key = [keychar characterAtIndex:0];
  773. switch (key) {
  774. // enable graphic console
  775. case '1' ... '9':
  776. [self selectConsoleLocked:key - '0' - 1]; /* ascii math */
  777. return true;
  778. // release the mouse grab
  779. case 'g':
  780. [self ungrabMouse];
  781. return true;
  782. }
  783. }
  784. }
  785. if (qemu_console_is_graphic(active_listener->dcl.con)) {
  786. qkbd_state_key_event(kbd, keycode, true);
  787. } else {
  788. [self handleMonitorInput: event];
  789. }
  790. return true;
  791. case NSEventTypeKeyUp:
  792. keycode = cocoa_keycode_to_qemu([event keyCode]);
  793. // don't pass the guest a spurious key-up if we treated this
  794. // command-key combo as a host UI action
  795. if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) {
  796. return true;
  797. }
  798. if (qemu_console_is_graphic(active_listener->dcl.con)) {
  799. qkbd_state_key_event(kbd, keycode, false);
  800. }
  801. return true;
  802. case NSEventTypeScrollWheel:
  803. /*
  804. * Send wheel events to the guest regardless of window focus.
  805. * This is in-line with standard Mac OS X UI behaviour.
  806. */
  807. /*
  808. * We shouldn't have got a scroll event when deltaY and delta Y
  809. * are zero, hence no harm in dropping the event
  810. */
  811. if ([event deltaY] != 0 || [event deltaX] != 0) {
  812. /* Determine if this is a scroll up or scroll down event */
  813. if ([event deltaY] != 0) {
  814. buttons = ([event deltaY] > 0) ?
  815. INPUT_BUTTON_WHEEL_UP : INPUT_BUTTON_WHEEL_DOWN;
  816. } else if ([event deltaX] != 0) {
  817. buttons = ([event deltaX] > 0) ?
  818. INPUT_BUTTON_WHEEL_LEFT : INPUT_BUTTON_WHEEL_RIGHT;
  819. }
  820. qemu_input_queue_btn(active_listener->dcl.con, buttons, true);
  821. qemu_input_event_sync();
  822. qemu_input_queue_btn(active_listener->dcl.con, buttons, false);
  823. qemu_input_event_sync();
  824. }
  825. /*
  826. * Since deltaX/deltaY also report scroll wheel events we prevent mouse
  827. * movement code from executing.
  828. */
  829. return true;
  830. default:
  831. return false;
  832. }
  833. }
  834. - (void) handleMouseEvent:(NSEvent *)event
  835. {
  836. if (!isMouseGrabbed) {
  837. return;
  838. }
  839. with_iothread_lock(^{
  840. QemuConsole *con = active_listener->dcl.con;
  841. if (isAbsoluteEnabled) {
  842. CGFloat d = (CGFloat)screen.height / [self frame].size.height;
  843. NSPoint p = [event locationInWindow];
  844. // Note that the origin for Cocoa mouse coords is bottom left, not top left.
  845. qemu_input_queue_abs(con, INPUT_AXIS_X, p.x * d, 0, screen.width);
  846. qemu_input_queue_abs(con, INPUT_AXIS_Y, screen.height - p.y * d, 0, screen.height);
  847. } else {
  848. CGFloat d = (CGFloat)screen.height / [self convertSizeToBacking:[self frame].size].height;
  849. qemu_input_queue_rel(con, INPUT_AXIS_X, [event deltaX] * d);
  850. qemu_input_queue_rel(con, INPUT_AXIS_Y, [event deltaY] * d);
  851. }
  852. qemu_input_event_sync();
  853. });
  854. }
  855. - (void) handleMouseEvent:(NSEvent *)event button:(InputButton)button down:(bool)down
  856. {
  857. if (!isMouseGrabbed) {
  858. return;
  859. }
  860. with_iothread_lock(^{
  861. qemu_input_queue_btn(active_listener->dcl.con, button, down);
  862. });
  863. [self handleMouseEvent:event];
  864. }
  865. - (void) mouseExited:(NSEvent *)event
  866. {
  867. if (isAbsoluteEnabled && isMouseGrabbed) {
  868. [self ungrabMouse];
  869. }
  870. }
  871. - (void) mouseEntered:(NSEvent *)event
  872. {
  873. if (isAbsoluteEnabled && !isMouseGrabbed) {
  874. [self grabMouse];
  875. }
  876. }
  877. - (void) mouseMoved:(NSEvent *)event
  878. {
  879. [self handleMouseEvent:event];
  880. }
  881. - (void) mouseDown:(NSEvent *)event
  882. {
  883. [self handleMouseEvent:event button:INPUT_BUTTON_LEFT down:true];
  884. }
  885. - (void) rightMouseDown:(NSEvent *)event
  886. {
  887. [self handleMouseEvent:event button:INPUT_BUTTON_RIGHT down:true];
  888. }
  889. - (void) otherMouseDown:(NSEvent *)event
  890. {
  891. [self handleMouseEvent:event button:INPUT_BUTTON_MIDDLE down:true];
  892. }
  893. - (void) mouseDragged:(NSEvent *)event
  894. {
  895. [self handleMouseEvent:event];
  896. }
  897. - (void) rightMouseDragged:(NSEvent *)event
  898. {
  899. [self handleMouseEvent:event];
  900. }
  901. - (void) otherMouseDragged:(NSEvent *)event
  902. {
  903. [self handleMouseEvent:event];
  904. }
  905. - (void) mouseUp:(NSEvent *)event
  906. {
  907. if (!isMouseGrabbed) {
  908. [self grabMouse];
  909. }
  910. [self handleMouseEvent:event button:INPUT_BUTTON_LEFT down:false];
  911. }
  912. - (void) rightMouseUp:(NSEvent *)event
  913. {
  914. [self handleMouseEvent:event button:INPUT_BUTTON_RIGHT down:false];
  915. }
  916. - (void) otherMouseUp:(NSEvent *)event
  917. {
  918. [self handleMouseEvent:event button:INPUT_BUTTON_MIDDLE down:false];
  919. }
  920. - (void) grabMouse
  921. {
  922. COCOA_DEBUG("QemuCocoaView: grabMouse\n");
  923. if (!listeners) {
  924. return;
  925. }
  926. if (qemu_name)
  927. [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s - (Press " UC_CTRL_KEY " " UC_ALT_KEY " G to release Mouse)", qemu_name]];
  928. else
  929. [normalWindow setTitle:@"QEMU - (Press " UC_CTRL_KEY " " UC_ALT_KEY " G to release Mouse)"];
  930. [self hideCursor];
  931. CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled);
  932. isMouseGrabbed = TRUE; // while isMouseGrabbed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:]
  933. }
  934. - (void) ungrabMouse
  935. {
  936. COCOA_DEBUG("QemuCocoaView: ungrabMouse\n");
  937. if (qemu_name)
  938. [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]];
  939. else
  940. [normalWindow setTitle:@"QEMU"];
  941. [self unhideCursor];
  942. CGAssociateMouseAndMouseCursorPosition(TRUE);
  943. isMouseGrabbed = FALSE;
  944. [self raiseAllButtons];
  945. }
  946. - (void) notifyMouseModeChange {
  947. bool tIsAbsoluteEnabled = bool_with_iothread_lock(^{
  948. return qemu_input_is_absolute();
  949. });
  950. if (tIsAbsoluteEnabled == isAbsoluteEnabled) {
  951. return;
  952. }
  953. isAbsoluteEnabled = tIsAbsoluteEnabled;
  954. if (isMouseGrabbed) {
  955. if (isAbsoluteEnabled) {
  956. [self ungrabMouse];
  957. } else {
  958. CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled);
  959. }
  960. }
  961. }
  962. - (BOOL) isMouseGrabbed {return isMouseGrabbed;}
  963. /*
  964. * Makes the target think all down keys are being released.
  965. * This prevents a stuck key problem, since we will not see
  966. * key up events for those keys after we have lost focus.
  967. */
  968. - (void) raiseAllKeys
  969. {
  970. with_iothread_lock(^{
  971. qkbd_state_lift_all_keys(kbd);
  972. });
  973. }
  974. - (void) raiseAllButtons
  975. {
  976. with_iothread_lock(^{
  977. qemu_input_queue_btn(active_listener->dcl.con, INPUT_BUTTON_LEFT, false);
  978. qemu_input_queue_btn(active_listener->dcl.con, INPUT_BUTTON_RIGHT, false);
  979. qemu_input_queue_btn(active_listener->dcl.con, INPUT_BUTTON_MIDDLE, false);
  980. });
  981. }
  982. @end
  983. /*
  984. ------------------------------------------------------
  985. QemuCocoaAppController
  986. ------------------------------------------------------
  987. */
  988. @interface QemuCocoaAppController : NSObject
  989. <NSWindowDelegate, NSApplicationDelegate>
  990. {
  991. }
  992. - (void)doToggleFullScreen:(id)sender;
  993. - (void)showQEMUDoc:(id)sender;
  994. - (void)zoomToFit:(id) sender;
  995. - (void)displayConsole:(id)sender;
  996. - (void)pauseQEMU:(id)sender;
  997. - (void)resumeQEMU:(id)sender;
  998. - (void)displayPause;
  999. - (void)removePause;
  1000. - (void)restartQEMU:(id)sender;
  1001. - (void)powerDownQEMU:(id)sender;
  1002. - (void)ejectDeviceMedia:(id)sender;
  1003. - (void)changeDeviceMedia:(id)sender;
  1004. - (BOOL)verifyQuit;
  1005. - (void)openDocumentation:(NSString *)filename;
  1006. - (IBAction) do_about_menu_item: (id) sender;
  1007. - (void)adjustSpeed:(id)sender;
  1008. @end
  1009. @implementation QemuCocoaAppController
  1010. - (id) init
  1011. {
  1012. COCOA_DEBUG("QemuCocoaAppController: init\n");
  1013. self = [super init];
  1014. if (self) {
  1015. // create a view and add it to the window
  1016. cocoaView = [[QemuCocoaView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 640.0, 480.0)];
  1017. if(!cocoaView) {
  1018. error_report("(cocoa) can't create a view");
  1019. exit(1);
  1020. }
  1021. // create a window
  1022. normalWindow = [[NSWindow alloc] initWithContentRect:[cocoaView frame]
  1023. styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskClosable
  1024. backing:NSBackingStoreBuffered defer:NO];
  1025. if(!normalWindow) {
  1026. error_report("(cocoa) can't create window");
  1027. exit(1);
  1028. }
  1029. [normalWindow setAcceptsMouseMovedEvents:YES];
  1030. [normalWindow setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
  1031. [normalWindow setTitle:qemu_name ? [NSString stringWithFormat:@"QEMU %s", qemu_name] : @"QEMU"];
  1032. [normalWindow setContentView:cocoaView];
  1033. [normalWindow makeKeyAndOrderFront:self];
  1034. [normalWindow center];
  1035. [normalWindow setDelegate: self];
  1036. /* Used for displaying pause on the screen */
  1037. pauseLabel = [NSTextField new];
  1038. [pauseLabel setBezeled:YES];
  1039. [pauseLabel setDrawsBackground:YES];
  1040. [pauseLabel setBackgroundColor: [NSColor whiteColor]];
  1041. [pauseLabel setEditable:NO];
  1042. [pauseLabel setSelectable:NO];
  1043. [pauseLabel setStringValue: @"Paused"];
  1044. [pauseLabel setFont: [NSFont fontWithName: @"Helvetica" size: 90]];
  1045. [pauseLabel setTextColor: [NSColor blackColor]];
  1046. [pauseLabel sizeToFit];
  1047. }
  1048. return self;
  1049. }
  1050. - (void) dealloc
  1051. {
  1052. COCOA_DEBUG("QemuCocoaAppController: dealloc\n");
  1053. if (cocoaView)
  1054. [cocoaView release];
  1055. [super dealloc];
  1056. }
  1057. - (void)applicationWillTerminate:(NSNotification *)aNotification
  1058. {
  1059. COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n");
  1060. with_iothread_lock(^{
  1061. shutdown_action = SHUTDOWN_ACTION_POWEROFF;
  1062. qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
  1063. });
  1064. /*
  1065. * Sleep here, because returning will cause OSX to kill us
  1066. * immediately; the QEMU main loop will handle the shutdown
  1067. * request and terminate the process.
  1068. */
  1069. [NSThread sleepForTimeInterval:INFINITY];
  1070. }
  1071. - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
  1072. {
  1073. return YES;
  1074. }
  1075. - (NSApplicationTerminateReply)applicationShouldTerminate:
  1076. (NSApplication *)sender
  1077. {
  1078. COCOA_DEBUG("QemuCocoaAppController: applicationShouldTerminate\n");
  1079. return [self verifyQuit];
  1080. }
  1081. - (void)windowDidChangeScreen:(NSNotification *)notification
  1082. {
  1083. [cocoaView updateUIInfo];
  1084. }
  1085. - (void)windowDidEnterFullScreen:(NSNotification *)notification
  1086. {
  1087. [cocoaView grabMouse];
  1088. }
  1089. - (void)windowDidExitFullScreen:(NSNotification *)notification
  1090. {
  1091. [cocoaView resizeWindow];
  1092. [cocoaView ungrabMouse];
  1093. }
  1094. - (void)windowDidResize:(NSNotification *)notification
  1095. {
  1096. [cocoaView frameUpdated];
  1097. }
  1098. /* Called when the user clicks on a window's close button */
  1099. - (BOOL)windowShouldClose:(id)sender
  1100. {
  1101. COCOA_DEBUG("QemuCocoaAppController: windowShouldClose\n");
  1102. [NSApp terminate: sender];
  1103. /* If the user allows the application to quit then the call to
  1104. * NSApp terminate will never return. If we get here then the user
  1105. * cancelled the quit, so we should return NO to not permit the
  1106. * closing of this window.
  1107. */
  1108. return NO;
  1109. }
  1110. - (NSSize) window:(NSWindow *)window willUseFullScreenContentSize:(NSSize)proposedSize
  1111. {
  1112. if (([normalWindow styleMask] & NSWindowStyleMaskResizable) == 0) {
  1113. return [cocoaView computeUnzoomedSize];
  1114. }
  1115. return [cocoaView fixZoomedFullScreenSize:proposedSize];
  1116. }
  1117. - (NSApplicationPresentationOptions) window:(NSWindow *)window
  1118. willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions;
  1119. {
  1120. return (proposedOptions & ~(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)) |
  1121. NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
  1122. }
  1123. /*
  1124. * Called when QEMU goes into the background. Note that
  1125. * [-NSWindowDelegate windowDidResignKey:] is used here instead of
  1126. * [-NSApplicationDelegate applicationWillResignActive:] because it cannot
  1127. * detect that the window loses focus when the deck is clicked on macOS 13.2.1.
  1128. */
  1129. - (void) windowDidResignKey: (NSNotification *)aNotification
  1130. {
  1131. COCOA_DEBUG("%s\n", __func__);
  1132. [cocoaView ungrabMouse];
  1133. [cocoaView raiseAllKeys];
  1134. }
  1135. /* We abstract the method called by the Enter Fullscreen menu item
  1136. * because Mac OS 10.7 and higher disables it. This is because of the
  1137. * menu item's old selector's name toggleFullScreen:
  1138. */
  1139. - (void) doToggleFullScreen:(id)sender
  1140. {
  1141. [normalWindow toggleFullScreen:sender];
  1142. }
  1143. - (void) setFullGrab:(id)sender
  1144. {
  1145. COCOA_DEBUG("QemuCocoaAppController: setFullGrab\n");
  1146. [cocoaView setFullGrab:sender];
  1147. }
  1148. /* Tries to find then open the specified filename */
  1149. - (void) openDocumentation: (NSString *) filename
  1150. {
  1151. /* Where to look for local files */
  1152. NSString *path_array[] = {@"../share/doc/qemu/", @"../doc/qemu/", @"docs/"};
  1153. NSString *full_file_path;
  1154. NSURL *full_file_url;
  1155. /* iterate thru the possible paths until the file is found */
  1156. int index;
  1157. for (index = 0; index < ARRAY_SIZE(path_array); index++) {
  1158. full_file_path = [[NSBundle mainBundle] executablePath];
  1159. full_file_path = [full_file_path stringByDeletingLastPathComponent];
  1160. full_file_path = [NSString stringWithFormat: @"%@/%@%@", full_file_path,
  1161. path_array[index], filename];
  1162. full_file_url = [NSURL fileURLWithPath: full_file_path
  1163. isDirectory: false];
  1164. if ([[NSWorkspace sharedWorkspace] openURL: full_file_url] == YES) {
  1165. return;
  1166. }
  1167. }
  1168. /* If none of the paths opened a file */
  1169. NSBeep();
  1170. QEMU_Alert(@"Failed to open file");
  1171. }
  1172. - (void)showQEMUDoc:(id)sender
  1173. {
  1174. COCOA_DEBUG("QemuCocoaAppController: showQEMUDoc\n");
  1175. [self openDocumentation: @"index.html"];
  1176. }
  1177. /* Toggles the flag which stretches video to fit host window size */
  1178. - (void)zoomToFit:(id) sender
  1179. {
  1180. if (([normalWindow styleMask] & NSWindowStyleMaskResizable) == 0) {
  1181. [normalWindow setStyleMask:[normalWindow styleMask] | NSWindowStyleMaskResizable];
  1182. [sender setState: NSControlStateValueOn];
  1183. } else {
  1184. [normalWindow setStyleMask:[normalWindow styleMask] & ~NSWindowStyleMaskResizable];
  1185. [cocoaView resizeWindow];
  1186. [sender setState: NSControlStateValueOff];
  1187. }
  1188. }
  1189. /* Displays the console on the screen */
  1190. - (void)displayConsole:(id)sender
  1191. {
  1192. with_iothread_lock(^{
  1193. [cocoaView selectConsoleLocked:[sender tag]];
  1194. });
  1195. }
  1196. /* Pause the guest */
  1197. - (void)pauseQEMU:(id)sender
  1198. {
  1199. with_iothread_lock(^{
  1200. qmp_stop(NULL);
  1201. });
  1202. [sender setEnabled: NO];
  1203. [[[sender menu] itemWithTitle: @"Resume"] setEnabled: YES];
  1204. [self displayPause];
  1205. }
  1206. /* Resume running the guest operating system */
  1207. - (void)resumeQEMU:(id) sender
  1208. {
  1209. with_iothread_lock(^{
  1210. qmp_cont(NULL);
  1211. });
  1212. [sender setEnabled: NO];
  1213. [[[sender menu] itemWithTitle: @"Pause"] setEnabled: YES];
  1214. [self removePause];
  1215. }
  1216. /* Displays the word pause on the screen */
  1217. - (void)displayPause
  1218. {
  1219. /* Coordinates have to be calculated each time because the window can change its size */
  1220. int xCoord, yCoord, width, height;
  1221. xCoord = ([normalWindow frame].size.width - [pauseLabel frame].size.width)/2;
  1222. yCoord = [normalWindow frame].size.height - [pauseLabel frame].size.height - ([pauseLabel frame].size.height * .5);
  1223. width = [pauseLabel frame].size.width;
  1224. height = [pauseLabel frame].size.height;
  1225. [pauseLabel setFrame: NSMakeRect(xCoord, yCoord, width, height)];
  1226. [cocoaView addSubview: pauseLabel];
  1227. }
  1228. /* Removes the word pause from the screen */
  1229. - (void)removePause
  1230. {
  1231. [pauseLabel removeFromSuperview];
  1232. }
  1233. /* Restarts QEMU */
  1234. - (void)restartQEMU:(id)sender
  1235. {
  1236. with_iothread_lock(^{
  1237. qmp_system_reset(NULL);
  1238. });
  1239. }
  1240. /* Powers down QEMU */
  1241. - (void)powerDownQEMU:(id)sender
  1242. {
  1243. with_iothread_lock(^{
  1244. qmp_system_powerdown(NULL);
  1245. });
  1246. }
  1247. /* Ejects the media.
  1248. * Uses sender's tag to figure out the device to eject.
  1249. */
  1250. - (void)ejectDeviceMedia:(id)sender
  1251. {
  1252. NSString * drive;
  1253. drive = [sender representedObject];
  1254. if(drive == nil) {
  1255. NSBeep();
  1256. QEMU_Alert(@"Failed to find drive to eject!");
  1257. return;
  1258. }
  1259. __block Error *err = NULL;
  1260. with_iothread_lock(^{
  1261. qmp_eject([drive cStringUsingEncoding: NSASCIIStringEncoding],
  1262. NULL, false, false, &err);
  1263. });
  1264. handleAnyDeviceErrors(err);
  1265. }
  1266. /* Displays a dialog box asking the user to select an image file to load.
  1267. * Uses sender's represented object value to figure out which drive to use.
  1268. */
  1269. - (void)changeDeviceMedia:(id)sender
  1270. {
  1271. /* Find the drive name */
  1272. NSString * drive;
  1273. drive = [sender representedObject];
  1274. if(drive == nil) {
  1275. NSBeep();
  1276. QEMU_Alert(@"Could not find drive!");
  1277. return;
  1278. }
  1279. /* Display the file open dialog */
  1280. NSOpenPanel * openPanel;
  1281. openPanel = [NSOpenPanel openPanel];
  1282. [openPanel setCanChooseFiles: YES];
  1283. [openPanel setAllowsMultipleSelection: NO];
  1284. if([openPanel runModal] == NSModalResponseOK) {
  1285. NSString * file = [[[openPanel URLs] objectAtIndex: 0] path];
  1286. if(file == nil) {
  1287. NSBeep();
  1288. QEMU_Alert(@"Failed to convert URL to file path!");
  1289. return;
  1290. }
  1291. __block Error *err = NULL;
  1292. with_iothread_lock(^{
  1293. qmp_blockdev_change_medium([drive cStringUsingEncoding:
  1294. NSASCIIStringEncoding],
  1295. NULL,
  1296. [file cStringUsingEncoding:
  1297. NSASCIIStringEncoding],
  1298. "raw",
  1299. true, false,
  1300. false, 0,
  1301. &err);
  1302. });
  1303. handleAnyDeviceErrors(err);
  1304. }
  1305. }
  1306. /* Verifies if the user really wants to quit */
  1307. - (BOOL)verifyQuit
  1308. {
  1309. NSAlert *alert = [NSAlert new];
  1310. [alert autorelease];
  1311. [alert setMessageText: @"Are you sure you want to quit QEMU?"];
  1312. [alert addButtonWithTitle: @"Cancel"];
  1313. [alert addButtonWithTitle: @"Quit"];
  1314. if([alert runModal] == NSAlertSecondButtonReturn) {
  1315. return YES;
  1316. } else {
  1317. return NO;
  1318. }
  1319. }
  1320. /* The action method for the About menu item */
  1321. - (IBAction) do_about_menu_item: (id) sender
  1322. {
  1323. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  1324. char *icon_path_c = get_relocated_path(CONFIG_QEMU_ICONDIR "/hicolor/512x512/apps/qemu.png");
  1325. NSString *icon_path = [NSString stringWithUTF8String:icon_path_c];
  1326. g_free(icon_path_c);
  1327. NSImage *icon = [[NSImage alloc] initWithContentsOfFile:icon_path];
  1328. NSString *version = @"QEMU emulator version " QEMU_FULL_VERSION;
  1329. NSString *copyright = @QEMU_COPYRIGHT;
  1330. NSDictionary *options;
  1331. if (icon) {
  1332. options = @{
  1333. NSAboutPanelOptionApplicationIcon : icon,
  1334. NSAboutPanelOptionApplicationVersion : version,
  1335. @"Copyright" : copyright,
  1336. };
  1337. [icon release];
  1338. } else {
  1339. options = @{
  1340. NSAboutPanelOptionApplicationVersion : version,
  1341. @"Copyright" : copyright,
  1342. };
  1343. }
  1344. [NSApp orderFrontStandardAboutPanelWithOptions:options];
  1345. [pool release];
  1346. }
  1347. /* Used by the Speed menu items */
  1348. - (void)adjustSpeed:(id)sender
  1349. {
  1350. int throttle_pct; /* throttle percentage */
  1351. NSMenu *menu;
  1352. menu = [sender menu];
  1353. if (menu != nil)
  1354. {
  1355. /* Unselect the currently selected item */
  1356. for (NSMenuItem *item in [menu itemArray]) {
  1357. if (item.state == NSControlStateValueOn) {
  1358. [item setState: NSControlStateValueOff];
  1359. break;
  1360. }
  1361. }
  1362. }
  1363. // check the menu item
  1364. [sender setState: NSControlStateValueOn];
  1365. // get the throttle percentage
  1366. throttle_pct = [sender tag];
  1367. with_iothread_lock(^{
  1368. cpu_throttle_set(throttle_pct);
  1369. });
  1370. COCOA_DEBUG("cpu throttling at %d%c\n", cpu_throttle_get_percentage(), '%');
  1371. }
  1372. @end
  1373. @interface QemuApplication : NSApplication
  1374. @end
  1375. @implementation QemuApplication
  1376. - (void)sendEvent:(NSEvent *)event
  1377. {
  1378. COCOA_DEBUG("QemuApplication: sendEvent\n");
  1379. if (![cocoaView handleEvent:event]) {
  1380. [super sendEvent: event];
  1381. }
  1382. }
  1383. @end
  1384. static void create_initial_menus(void)
  1385. {
  1386. // Add menus
  1387. NSMenu *menu;
  1388. NSMenuItem *menuItem;
  1389. [NSApp setMainMenu:[[NSMenu alloc] init]];
  1390. [NSApp setServicesMenu:[[NSMenu alloc] initWithTitle:@"Services"]];
  1391. // Application menu
  1392. menu = [[NSMenu alloc] initWithTitle:@""];
  1393. [menu addItemWithTitle:@"About QEMU" action:@selector(do_about_menu_item:) keyEquivalent:@""]; // About QEMU
  1394. [menu addItem:[NSMenuItem separatorItem]]; //Separator
  1395. menuItem = [menu addItemWithTitle:@"Services" action:nil keyEquivalent:@""];
  1396. [menuItem setSubmenu:[NSApp servicesMenu]];
  1397. [menu addItem:[NSMenuItem separatorItem]];
  1398. [menu addItemWithTitle:@"Hide QEMU" action:@selector(hide:) keyEquivalent:@"h"]; //Hide QEMU
  1399. menuItem = (NSMenuItem *)[menu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; // Hide Others
  1400. [menuItem setKeyEquivalentModifierMask:(NSEventModifierFlagOption|NSEventModifierFlagCommand)];
  1401. [menu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; // Show All
  1402. [menu addItem:[NSMenuItem separatorItem]]; //Separator
  1403. [menu addItemWithTitle:@"Quit QEMU" action:@selector(terminate:) keyEquivalent:@"q"];
  1404. menuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" action:nil keyEquivalent:@""];
  1405. [menuItem setSubmenu:menu];
  1406. [[NSApp mainMenu] addItem:menuItem];
  1407. [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; // Workaround (this method is private since 10.4+)
  1408. // Machine menu
  1409. menu = [[NSMenu alloc] initWithTitle: @"Machine"];
  1410. [menu setAutoenablesItems: NO];
  1411. [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Pause" action: @selector(pauseQEMU:) keyEquivalent: @""] autorelease]];
  1412. menuItem = [[[NSMenuItem alloc] initWithTitle: @"Resume" action: @selector(resumeQEMU:) keyEquivalent: @""] autorelease];
  1413. [menu addItem: menuItem];
  1414. [menuItem setEnabled: NO];
  1415. [menu addItem: [NSMenuItem separatorItem]];
  1416. [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Reset" action: @selector(restartQEMU:) keyEquivalent: @""] autorelease]];
  1417. [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Power Down" action: @selector(powerDownQEMU:) keyEquivalent: @""] autorelease]];
  1418. menuItem = [[[NSMenuItem alloc] initWithTitle: @"Machine" action:nil keyEquivalent:@""] autorelease];
  1419. [menuItem setSubmenu:menu];
  1420. [[NSApp mainMenu] addItem:menuItem];
  1421. // View menu
  1422. menu = [[NSMenu alloc] initWithTitle:@"View"];
  1423. [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(doToggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen
  1424. [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Zoom To Fit" action:@selector(zoomToFit:) keyEquivalent:@""] autorelease]];
  1425. menuItem = [[[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""] autorelease];
  1426. [menuItem setSubmenu:menu];
  1427. [[NSApp mainMenu] addItem:menuItem];
  1428. // Speed menu
  1429. menu = [[NSMenu alloc] initWithTitle:@"Speed"];
  1430. // Add the rest of the Speed menu items
  1431. int p, percentage, throttle_pct;
  1432. for (p = 10; p >= 0; p--)
  1433. {
  1434. percentage = p * 10 > 1 ? p * 10 : 1; // prevent a 0% menu item
  1435. menuItem = [[[NSMenuItem alloc]
  1436. initWithTitle: [NSString stringWithFormat: @"%d%%", percentage] action:@selector(adjustSpeed:) keyEquivalent:@""] autorelease];
  1437. if (percentage == 100) {
  1438. [menuItem setState: NSControlStateValueOn];
  1439. }
  1440. /* Calculate the throttle percentage */
  1441. throttle_pct = -1 * percentage + 100;
  1442. [menuItem setTag: throttle_pct];
  1443. [menu addItem: menuItem];
  1444. }
  1445. menuItem = [[[NSMenuItem alloc] initWithTitle:@"Speed" action:nil keyEquivalent:@""] autorelease];
  1446. [menuItem setSubmenu:menu];
  1447. [[NSApp mainMenu] addItem:menuItem];
  1448. // Window menu
  1449. menu = [[NSMenu alloc] initWithTitle:@"Window"];
  1450. [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease]]; // Miniaturize
  1451. menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease];
  1452. [menuItem setSubmenu:menu];
  1453. [[NSApp mainMenu] addItem:menuItem];
  1454. [NSApp setWindowsMenu:menu];
  1455. // Help menu
  1456. menu = [[NSMenu alloc] initWithTitle:@"Help"];
  1457. [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Documentation" action:@selector(showQEMUDoc:) keyEquivalent:@"?"] autorelease]]; // QEMU Help
  1458. menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease];
  1459. [menuItem setSubmenu:menu];
  1460. [[NSApp mainMenu] addItem:menuItem];
  1461. }
  1462. /* Returns a name for a given console */
  1463. static NSString * getConsoleName(QemuConsole * console)
  1464. {
  1465. g_autofree char *label = qemu_console_get_label(console);
  1466. return [NSString stringWithUTF8String:label];
  1467. }
  1468. /* Add an entry to the View menu for each console */
  1469. static void add_console_menu_entries(void)
  1470. {
  1471. NSMenu *menu;
  1472. NSMenuItem *menuItem;
  1473. size_t index;
  1474. menu = [[[NSApp mainMenu] itemWithTitle:@"View"] submenu];
  1475. [menu addItem:[NSMenuItem separatorItem]];
  1476. for (index = 0; index < listeners_count; index++) {
  1477. menuItem = [[[NSMenuItem alloc] initWithTitle: getConsoleName(listeners[index].dcl.con)
  1478. action: @selector(displayConsole:) keyEquivalent: @""] autorelease];
  1479. [menuItem setTag: index];
  1480. [menu addItem: menuItem];
  1481. }
  1482. }
  1483. /* Make menu items for all removable devices.
  1484. * Each device is given an 'Eject' and 'Change' menu item.
  1485. */
  1486. static void addRemovableDevicesMenuItems(void)
  1487. {
  1488. NSMenu *menu;
  1489. NSMenuItem *menuItem;
  1490. BlockInfoList *currentDevice, *pointerToFree;
  1491. NSString *deviceName;
  1492. currentDevice = qmp_query_block(NULL);
  1493. pointerToFree = currentDevice;
  1494. menu = [[[NSApp mainMenu] itemWithTitle:@"Machine"] submenu];
  1495. // Add a separator between related groups of menu items
  1496. [menu addItem:[NSMenuItem separatorItem]];
  1497. // Set the attributes to the "Removable Media" menu item
  1498. NSString *titleString = @"Removable Media";
  1499. NSMutableAttributedString *attString=[[NSMutableAttributedString alloc] initWithString:titleString];
  1500. NSColor *newColor = [NSColor blackColor];
  1501. NSFontManager *fontManager = [NSFontManager sharedFontManager];
  1502. NSFont *font = [fontManager fontWithFamily:@"Helvetica"
  1503. traits:NSBoldFontMask|NSItalicFontMask
  1504. weight:0
  1505. size:14];
  1506. [attString addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, [titleString length])];
  1507. [attString addAttribute:NSForegroundColorAttributeName value:newColor range:NSMakeRange(0, [titleString length])];
  1508. [attString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInt: 1] range:NSMakeRange(0, [titleString length])];
  1509. // Add the "Removable Media" menu item
  1510. menuItem = [NSMenuItem new];
  1511. [menuItem setAttributedTitle: attString];
  1512. [menuItem setEnabled: NO];
  1513. [menu addItem: menuItem];
  1514. /* Loop through all the block devices in the emulator */
  1515. while (currentDevice) {
  1516. deviceName = [[NSString stringWithFormat: @"%s", currentDevice->value->device] retain];
  1517. if(currentDevice->value->removable) {
  1518. menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Change %s...", currentDevice->value->device]
  1519. action: @selector(changeDeviceMedia:)
  1520. keyEquivalent: @""];
  1521. [menu addItem: menuItem];
  1522. [menuItem setRepresentedObject: deviceName];
  1523. [menuItem autorelease];
  1524. menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Eject %s", currentDevice->value->device]
  1525. action: @selector(ejectDeviceMedia:)
  1526. keyEquivalent: @""];
  1527. [menu addItem: menuItem];
  1528. [menuItem setRepresentedObject: deviceName];
  1529. [menuItem autorelease];
  1530. }
  1531. currentDevice = currentDevice->next;
  1532. }
  1533. qapi_free_BlockInfoList(pointerToFree);
  1534. }
  1535. static void cocoa_mouse_mode_change_notify(Notifier *notifier, void *data)
  1536. {
  1537. dispatch_async(dispatch_get_main_queue(), ^{
  1538. [cocoaView notifyMouseModeChange];
  1539. });
  1540. }
  1541. static Notifier mouse_mode_change_notifier = {
  1542. .notify = cocoa_mouse_mode_change_notify
  1543. };
  1544. @interface QemuCocoaPasteboardTypeOwner : NSObject<NSPasteboardTypeOwner>
  1545. @end
  1546. @implementation QemuCocoaPasteboardTypeOwner
  1547. - (void)pasteboard:(NSPasteboard *)sender provideDataForType:(NSPasteboardType)type
  1548. {
  1549. if (type != NSPasteboardTypeString) {
  1550. return;
  1551. }
  1552. with_iothread_lock(^{
  1553. QemuClipboardInfo *info = qemu_clipboard_info_ref(cbinfo);
  1554. qemu_event_reset(&cbevent);
  1555. qemu_clipboard_request(info, QEMU_CLIPBOARD_TYPE_TEXT);
  1556. while (info == cbinfo &&
  1557. info->types[QEMU_CLIPBOARD_TYPE_TEXT].available &&
  1558. info->types[QEMU_CLIPBOARD_TYPE_TEXT].data == NULL) {
  1559. qemu_mutex_unlock_iothread();
  1560. qemu_event_wait(&cbevent);
  1561. qemu_mutex_lock_iothread();
  1562. }
  1563. if (info == cbinfo) {
  1564. NSData *data = [[NSData alloc] initWithBytes:info->types[QEMU_CLIPBOARD_TYPE_TEXT].data
  1565. length:info->types[QEMU_CLIPBOARD_TYPE_TEXT].size];
  1566. [sender setData:data forType:NSPasteboardTypeString];
  1567. [data release];
  1568. }
  1569. qemu_clipboard_info_unref(info);
  1570. });
  1571. }
  1572. @end
  1573. static QemuCocoaPasteboardTypeOwner *cbowner;
  1574. static void cocoa_clipboard_notify(Notifier *notifier, void *data);
  1575. static void cocoa_clipboard_request(QemuClipboardInfo *info,
  1576. QemuClipboardType type);
  1577. static QemuClipboardPeer cbpeer = {
  1578. .name = "cocoa",
  1579. .notifier = { .notify = cocoa_clipboard_notify },
  1580. .request = cocoa_clipboard_request
  1581. };
  1582. static void cocoa_clipboard_update_info(QemuClipboardInfo *info)
  1583. {
  1584. if (info->owner == &cbpeer || info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) {
  1585. return;
  1586. }
  1587. if (info != cbinfo) {
  1588. NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
  1589. qemu_clipboard_info_unref(cbinfo);
  1590. cbinfo = qemu_clipboard_info_ref(info);
  1591. cbchangecount = [[NSPasteboard generalPasteboard] declareTypes:@[NSPasteboardTypeString] owner:cbowner];
  1592. [pool release];
  1593. }
  1594. qemu_event_set(&cbevent);
  1595. }
  1596. static void cocoa_clipboard_notify(Notifier *notifier, void *data)
  1597. {
  1598. QemuClipboardNotify *notify = data;
  1599. switch (notify->type) {
  1600. case QEMU_CLIPBOARD_UPDATE_INFO:
  1601. cocoa_clipboard_update_info(notify->info);
  1602. return;
  1603. case QEMU_CLIPBOARD_RESET_SERIAL:
  1604. /* ignore */
  1605. return;
  1606. }
  1607. }
  1608. static void cocoa_clipboard_request(QemuClipboardInfo *info,
  1609. QemuClipboardType type)
  1610. {
  1611. NSAutoreleasePool *pool;
  1612. NSData *text;
  1613. switch (type) {
  1614. case QEMU_CLIPBOARD_TYPE_TEXT:
  1615. pool = [[NSAutoreleasePool alloc] init];
  1616. text = [[NSPasteboard generalPasteboard] dataForType:NSPasteboardTypeString];
  1617. if (text) {
  1618. qemu_clipboard_set_data(&cbpeer, info, type,
  1619. [text length], [text bytes], true);
  1620. }
  1621. [pool release];
  1622. break;
  1623. default:
  1624. break;
  1625. }
  1626. }
  1627. /*
  1628. * The startup process for the OSX/Cocoa UI is complicated, because
  1629. * OSX insists that the UI runs on the initial main thread, and so we
  1630. * need to start a second thread which runs the qemu_default_main():
  1631. * in main():
  1632. * in cocoa_display_init():
  1633. * assign cocoa_main to qemu_main
  1634. * create application, menus, etc
  1635. * in cocoa_main():
  1636. * create qemu-main thread
  1637. * enter OSX run loop
  1638. */
  1639. static void *call_qemu_main(void *opaque)
  1640. {
  1641. int status;
  1642. COCOA_DEBUG("Second thread: calling qemu_default_main()\n");
  1643. qemu_mutex_lock_iothread();
  1644. status = qemu_default_main();
  1645. qemu_mutex_unlock_iothread();
  1646. COCOA_DEBUG("Second thread: qemu_default_main() returned, exiting\n");
  1647. #ifdef CONFIG_OPENGL
  1648. qemu_gl_fini_shader(dgc.gls);
  1649. if (view_ctx) {
  1650. cocoa_gl_destroy_context(&dgc, view_ctx);
  1651. }
  1652. #endif
  1653. [cbowner release];
  1654. exit(status);
  1655. }
  1656. static int cocoa_main()
  1657. {
  1658. QemuThread thread;
  1659. COCOA_DEBUG("Entered %s()\n", __func__);
  1660. qemu_mutex_unlock_iothread();
  1661. qemu_thread_create(&thread, "qemu_main", call_qemu_main,
  1662. NULL, QEMU_THREAD_DETACHED);
  1663. // Start the main event loop
  1664. COCOA_DEBUG("Main thread: entering OSX run loop\n");
  1665. [NSApp run];
  1666. COCOA_DEBUG("Main thread: left OSX run loop, which should never happen\n");
  1667. abort();
  1668. }
  1669. #pragma mark qemu
  1670. static void cocoa_update(DisplayChangeListener *dcl,
  1671. int x, int y, int w, int h)
  1672. {
  1673. DisplaySurface *updated = surface;
  1674. if (container_of(dcl, CocoaListener, dcl) != active_listener) {
  1675. return;
  1676. }
  1677. COCOA_DEBUG("qemu_cocoa: cocoa_update\n");
  1678. dispatch_async(dispatch_get_main_queue(), ^{
  1679. qemu_mutex_lock(&draw_mutex);
  1680. if (updated != surface) {
  1681. qemu_mutex_unlock(&draw_mutex);
  1682. return;
  1683. }
  1684. int full_height = surface_height(surface);
  1685. qemu_mutex_unlock(&draw_mutex);
  1686. CGFloat d = [cocoaView frame].size.height / full_height;
  1687. NSRect rect = NSMakeRect(x * d, (full_height - y - h) * d, w * d, h * d);
  1688. [cocoaView setNeedsDisplayInRect:rect];
  1689. });
  1690. }
  1691. static void cocoa_switch(DisplayChangeListener *dcl,
  1692. DisplaySurface *new_surface)
  1693. {
  1694. COCOA_DEBUG("qemu_cocoa: cocoa_switch\n");
  1695. if (container_of(dcl, CocoaListener, dcl) != active_listener) {
  1696. return;
  1697. }
  1698. qemu_mutex_lock(&draw_mutex);
  1699. surface = new_surface;
  1700. qemu_mutex_unlock(&draw_mutex);
  1701. dispatch_async(dispatch_get_main_queue(), ^{
  1702. qemu_mutex_lock(&draw_mutex);
  1703. int w = surface_width(surface);
  1704. int h = surface_height(surface);
  1705. qemu_mutex_unlock(&draw_mutex);
  1706. [cocoaView updateScreenWidth:w height:h];
  1707. });
  1708. }
  1709. static void cocoa_refresh(DisplayChangeListener *dcl)
  1710. {
  1711. NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
  1712. COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n");
  1713. if (container_of(dcl, CocoaListener, dcl) != active_listener) {
  1714. return;
  1715. }
  1716. graphic_hw_update(dcl->con);
  1717. if (cbchangecount != [[NSPasteboard generalPasteboard] changeCount]) {
  1718. qemu_clipboard_info_unref(cbinfo);
  1719. cbinfo = qemu_clipboard_info_new(&cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD);
  1720. if ([[NSPasteboard generalPasteboard] availableTypeFromArray:@[NSPasteboardTypeString]]) {
  1721. cbinfo->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
  1722. }
  1723. qemu_clipboard_update(cbinfo);
  1724. cbchangecount = [[NSPasteboard generalPasteboard] changeCount];
  1725. qemu_event_set(&cbevent);
  1726. }
  1727. [pool release];
  1728. }
  1729. static void cocoa_mouse_set(DisplayChangeListener *dcl, int x, int y, int on)
  1730. {
  1731. CocoaListener *listener = container_of(dcl, CocoaListener, dcl);
  1732. qemu_mutex_lock(&draw_mutex);
  1733. int full_height = surface_height(surface);
  1734. int old_x = listener->mouse_x;
  1735. int old_y = listener->mouse_y;
  1736. listener->mouse_x = x;
  1737. listener->mouse_y = y;
  1738. listener->mouse_on = on;
  1739. qemu_mutex_unlock(&draw_mutex);
  1740. if (listener == active_listener && cursor_cgimage) {
  1741. size_t cursor_width = CGImageGetWidth(cursor_cgimage);
  1742. size_t cursor_height = CGImageGetHeight(cursor_cgimage);
  1743. dispatch_async(dispatch_get_main_queue(), ^{
  1744. CGRect clip_rect = compute_cursor_clip_rect(full_height,
  1745. old_x, old_y,
  1746. cursor_width,
  1747. cursor_height);
  1748. CGRect draw_rect =
  1749. [cocoaView convertCursorClipRectToDraw:clip_rect
  1750. screenHeight:full_height
  1751. mouseX:old_x
  1752. mouseY:old_y];
  1753. [cocoaView setNeedsDisplayInRect:draw_rect];
  1754. clip_rect = compute_cursor_clip_rect(full_height, x, y,
  1755. cursor_width,
  1756. cursor_height);
  1757. draw_rect =
  1758. [cocoaView convertCursorClipRectToDraw:clip_rect
  1759. screenHeight:full_height
  1760. mouseX:x
  1761. mouseY:y];
  1762. [cocoaView setNeedsDisplayInRect:draw_rect];
  1763. });
  1764. }
  1765. }
  1766. static void cocoa_cursor_update()
  1767. {
  1768. CGImageRef old_image = cursor_cgimage;
  1769. CGImageRef new_image;
  1770. if (active_listener->cursor) {
  1771. CGDataProviderRef provider = CGDataProviderCreateWithData(
  1772. NULL,
  1773. active_listener->cursor->data,
  1774. active_listener->cursor->width * active_listener->cursor->height * 4,
  1775. NULL
  1776. );
  1777. new_image = CGImageCreate(
  1778. active_listener->cursor->width, //width
  1779. active_listener->cursor->height, //height
  1780. 8, //bitsPerComponent
  1781. 32, //bitsPerPixel
  1782. active_listener->cursor->width * 4, //bytesPerRow
  1783. CGColorSpaceCreateWithName(kCGColorSpaceSRGB), //colorspace
  1784. kCGBitmapByteOrder32Little | kCGImageAlphaFirst, //bitmapInfo
  1785. provider, //provider
  1786. NULL, //decode
  1787. 0, //interpolate
  1788. kCGRenderingIntentDefault //intent
  1789. );
  1790. CGDataProviderRelease(provider);
  1791. } else {
  1792. new_image = NULL;
  1793. }
  1794. qemu_mutex_lock(&draw_mutex);
  1795. cursor_cgimage = new_image;
  1796. qemu_mutex_unlock(&draw_mutex);
  1797. CGImageRelease(old_image);
  1798. }
  1799. static void cocoa_cursor_define(DisplayChangeListener *dcl, QEMUCursor *cursor)
  1800. {
  1801. CocoaListener *listener = container_of(dcl, CocoaListener, dcl);
  1802. listener->cursor = cursor;
  1803. if (listener == active_listener) {
  1804. int full_height = surface_height(surface);
  1805. int width = cursor->width;
  1806. int height = cursor->height;
  1807. int x = listener->mouse_x;
  1808. int y = listener->mouse_y;
  1809. size_t old_width;
  1810. size_t old_height;
  1811. if (cursor_cgimage) {
  1812. old_width = CGImageGetWidth(cursor_cgimage);
  1813. old_height = CGImageGetHeight(cursor_cgimage);
  1814. } else {
  1815. old_width = 0;
  1816. old_height = 0;
  1817. }
  1818. cocoa_cursor_update();
  1819. dispatch_async(dispatch_get_main_queue(), ^{
  1820. CGFloat d = [cocoaView frame].size.height / full_height;
  1821. NSRect rect;
  1822. rect.origin.x = d * x;
  1823. rect.origin.y = d * (full_height - y - old_height);
  1824. rect.size.width = d * old_width;
  1825. rect.size.height = d * old_height;
  1826. [cocoaView setNeedsDisplayInRect:rect];
  1827. rect.size.width = d * width;
  1828. rect.size.height = d * height;
  1829. [cocoaView setNeedsDisplayInRect:rect];
  1830. });
  1831. }
  1832. }
  1833. static const DisplayChangeListenerOps dcl_ops = {
  1834. .dpy_name = "cocoa",
  1835. .dpy_gfx_update = cocoa_update,
  1836. .dpy_gfx_switch = cocoa_switch,
  1837. .dpy_refresh = cocoa_refresh,
  1838. .dpy_mouse_set = cocoa_mouse_set,
  1839. .dpy_cursor_define = cocoa_cursor_define,
  1840. };
  1841. #ifdef CONFIG_OPENGL
  1842. static void with_view_ctx(CodeBlock block)
  1843. {
  1844. #ifdef CONFIG_EGL
  1845. if (egl_surface) {
  1846. eglMakeCurrent(qemu_egl_display, egl_surface, egl_surface, view_ctx);
  1847. block();
  1848. return;
  1849. }
  1850. #endif
  1851. [(NSOpenGLContext *)view_ctx lock];
  1852. [(NSOpenGLContext *)view_ctx makeCurrentContext];
  1853. block();
  1854. [(NSOpenGLContext *)view_ctx unlock];
  1855. }
  1856. static NSOpenGLPixelFormat *cocoa_gl_create_ns_pixel_format(int bpp)
  1857. {
  1858. NSOpenGLPixelFormatAttribute attributes[] = {
  1859. NSOpenGLPFAOpenGLProfile,
  1860. NSOpenGLProfileVersion4_1Core,
  1861. NSOpenGLPFAColorSize,
  1862. bpp,
  1863. NSOpenGLPFADoubleBuffer,
  1864. 0,
  1865. };
  1866. return [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes];
  1867. }
  1868. static int cocoa_gl_make_context_current(DisplayGLCtx *dgc, QEMUGLContext ctx)
  1869. {
  1870. #ifdef CONFIG_EGL
  1871. if (egl_surface) {
  1872. EGLSurface current_surface = ctx == EGL_NO_CONTEXT ? EGL_NO_SURFACE : egl_surface;
  1873. return eglMakeCurrent(qemu_egl_display, current_surface, current_surface, ctx);
  1874. }
  1875. #endif
  1876. if (ctx) {
  1877. [(NSOpenGLContext *)ctx makeCurrentContext];
  1878. } else {
  1879. [NSOpenGLContext clearCurrentContext];
  1880. }
  1881. return 0;
  1882. }
  1883. static QEMUGLContext cocoa_gl_create_context(DisplayGLCtx *dgc,
  1884. QEMUGLParams *params)
  1885. {
  1886. NSOpenGLPixelFormat *format;
  1887. NSOpenGLContext *ctx;
  1888. int bpp;
  1889. #ifdef CONFIG_EGL
  1890. if (egl_surface) {
  1891. eglMakeCurrent(qemu_egl_display, egl_surface, egl_surface, view_ctx);
  1892. return qemu_egl_create_context(dgc, params);
  1893. }
  1894. #endif
  1895. bpp = PIXMAN_FORMAT_BPP(surface_format(surface));
  1896. format = cocoa_gl_create_ns_pixel_format(bpp);
  1897. ctx = [[NSOpenGLContext alloc] initWithFormat:format shareContext:view_ctx];
  1898. [format release];
  1899. return (QEMUGLContext)ctx;
  1900. }
  1901. static void cocoa_gl_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx)
  1902. {
  1903. #ifdef CONFIG_EGL
  1904. if (egl_surface) {
  1905. eglDestroyContext(qemu_egl_display, ctx);
  1906. return;
  1907. }
  1908. #endif
  1909. [(NSOpenGLContext *)ctx release];
  1910. }
  1911. static void cocoa_gl_flush()
  1912. {
  1913. #ifdef CONFIG_EGL
  1914. if (egl_surface) {
  1915. eglSwapBuffers(qemu_egl_display, egl_surface);
  1916. return;
  1917. }
  1918. #endif
  1919. [[NSOpenGLContext currentContext] flushBuffer];
  1920. dispatch_async(dispatch_get_main_queue(), ^{
  1921. [(NSOpenGLContext *)view_ctx update];
  1922. });
  1923. }
  1924. static void cocoa_gl_update(DisplayChangeListener *dcl,
  1925. int x, int y, int w, int h)
  1926. {
  1927. CocoaListener *listener = container_of(dcl, CocoaListener, dcl);
  1928. if (listener != active_listener) {
  1929. return;
  1930. }
  1931. with_view_ctx(^{
  1932. surface_gl_update_texture(dgc.gls, surface, x, y, w, h);
  1933. gl_dirty = true;
  1934. });
  1935. }
  1936. static void cocoa_gl_cursor_render()
  1937. {
  1938. if (!active_listener->mouse_on) {
  1939. return;
  1940. }
  1941. NSSize size = [cocoaView convertSizeToBacking:[cocoaView frame].size];
  1942. CGFloat d = size.height / surface_height(surface);
  1943. glViewport(
  1944. d * active_listener->mouse_x,
  1945. size.height - d * (active_listener->mouse_y + active_listener->cursor->height),
  1946. d * active_listener->cursor->width,
  1947. d * active_listener->cursor->height
  1948. );
  1949. glBindTexture(GL_TEXTURE_2D, cursor_texture);
  1950. glEnable(GL_BLEND);
  1951. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  1952. qemu_gl_run_texture_blit(dgc.gls, false);
  1953. glDisable(GL_BLEND);
  1954. }
  1955. static void cocoa_gl_switch(DisplayChangeListener *dcl,
  1956. DisplaySurface *new_surface)
  1957. {
  1958. CocoaListener *listener = container_of(dcl, CocoaListener, dcl);
  1959. if (listener != active_listener) {
  1960. return;
  1961. }
  1962. with_view_ctx(^{
  1963. surface_gl_destroy_texture(dgc.gls, surface);
  1964. surface_gl_create_texture(dgc.gls, new_surface);
  1965. });
  1966. cocoa_switch(dcl, new_surface);
  1967. gl_dirty = true;
  1968. }
  1969. static void cocoa_gl_refresh(DisplayChangeListener *dcl)
  1970. {
  1971. CocoaListener *listener = container_of(dcl, CocoaListener, dcl);
  1972. if (listener != active_listener) {
  1973. return;
  1974. }
  1975. cocoa_refresh(dcl);
  1976. if (gl_dirty) {
  1977. gl_dirty = false;
  1978. with_view_ctx(^{
  1979. NSSize size = [cocoaView convertSizeToBacking:[cocoaView frame].size];
  1980. if (listener->gl_scanout_borrow) {
  1981. bool y0_top;
  1982. GLint texture =
  1983. listener->gl_scanout_borrow(listener->gl_scanout_id,
  1984. &y0_top, NULL, NULL);
  1985. glBindFramebuffer(GL_FRAMEBUFFER_EXT, 0);
  1986. glViewport(0, 0, size.width, size.height);
  1987. glBindTexture(GL_TEXTURE_2D, texture);
  1988. qemu_gl_run_texture_blit(dgc.gls, y0_top);
  1989. } else {
  1990. surface_gl_setup_viewport(dgc.gls, surface,
  1991. size.width, size.height);
  1992. glBindTexture(GL_TEXTURE_2D, surface->texture);
  1993. surface_gl_render_texture(dgc.gls, surface);
  1994. }
  1995. cocoa_gl_cursor_render();
  1996. cocoa_gl_flush();
  1997. });
  1998. }
  1999. }
  2000. static void cocoa_gl_scanout_disable(DisplayChangeListener *dcl)
  2001. {
  2002. CocoaListener *listener = container_of(dcl, CocoaListener, dcl);
  2003. listener->gl_scanout_borrow = NULL;
  2004. if (listener == active_listener) {
  2005. gl_dirty = surface != NULL;
  2006. }
  2007. }
  2008. static void cocoa_gl_cursor_update()
  2009. {
  2010. if (active_listener->cursor) {
  2011. with_view_ctx(^{
  2012. glBindTexture(GL_TEXTURE_2D, cursor_texture);
  2013. glPixelStorei(GL_UNPACK_ROW_LENGTH_EXT,
  2014. active_listener->cursor->width);
  2015. glTexImage2D(GL_TEXTURE_2D, 0,
  2016. epoxy_is_desktop_gl() ? GL_RGBA : GL_BGRA,
  2017. active_listener->cursor->width,
  2018. active_listener->cursor->height,
  2019. 0, GL_BGRA, GL_UNSIGNED_BYTE,
  2020. active_listener->cursor->data);
  2021. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  2022. glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
  2023. });
  2024. }
  2025. gl_dirty = true;
  2026. }
  2027. static void cocoa_gl_cursor_define(DisplayChangeListener *dcl, QEMUCursor *cursor)
  2028. {
  2029. CocoaListener *listener = container_of(dcl, CocoaListener, dcl);
  2030. listener->cursor = cursor;
  2031. if (listener == active_listener) {
  2032. cocoa_gl_cursor_update();
  2033. }
  2034. }
  2035. static void cocoa_gl_scanout_texture(DisplayChangeListener *dcl,
  2036. uint32_t backing_id,
  2037. DisplayGLTextureBorrower backing_borrow,
  2038. uint32_t x, uint32_t y,
  2039. uint32_t w, uint32_t h)
  2040. {
  2041. CocoaListener *listener = container_of(dcl, CocoaListener, dcl);
  2042. listener->gl_scanout_id = backing_id;
  2043. listener->gl_scanout_borrow = backing_borrow;
  2044. gl_dirty = true;
  2045. }
  2046. static void cocoa_gl_scanout_flush(DisplayChangeListener *dcl,
  2047. uint32_t x, uint32_t y,
  2048. uint32_t w, uint32_t h)
  2049. {
  2050. if (container_of(dcl, CocoaListener, dcl) == active_listener) {
  2051. gl_dirty = true;
  2052. }
  2053. }
  2054. static void cocoa_gl_mouse_set(DisplayChangeListener *dcl, int x, int y, int on)
  2055. {
  2056. CocoaListener *listener = container_of(dcl, CocoaListener, dcl);
  2057. listener->mouse_x = x;
  2058. listener->mouse_y = y;
  2059. listener->mouse_on = on;
  2060. if (listener == active_listener) {
  2061. gl_dirty = true;
  2062. }
  2063. }
  2064. static const DisplayChangeListenerOps dcl_gl_ops = {
  2065. .dpy_name = "cocoa-gl",
  2066. .dpy_gfx_update = cocoa_gl_update,
  2067. .dpy_gfx_switch = cocoa_gl_switch,
  2068. .dpy_gfx_check_format = console_gl_check_format,
  2069. .dpy_refresh = cocoa_gl_refresh,
  2070. .dpy_mouse_set = cocoa_gl_mouse_set,
  2071. .dpy_cursor_define = cocoa_gl_cursor_define,
  2072. .dpy_gl_scanout_disable = cocoa_gl_scanout_disable,
  2073. .dpy_gl_scanout_texture = cocoa_gl_scanout_texture,
  2074. .dpy_gl_update = cocoa_gl_scanout_flush,
  2075. };
  2076. static bool cocoa_gl_is_compatible_dcl(DisplayGLCtx *dgc,
  2077. DisplayChangeListener *dcl)
  2078. {
  2079. return dcl->ops == &dcl_gl_ops;
  2080. }
  2081. #endif
  2082. static void cocoa_display_early_init(DisplayOptions *o)
  2083. {
  2084. assert(o->type == DISPLAY_TYPE_COCOA);
  2085. if (o->has_gl && o->gl) {
  2086. display_opengl = 1;
  2087. }
  2088. }
  2089. static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts)
  2090. {
  2091. NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
  2092. const DisplayChangeListenerOps *ops;
  2093. size_t index;
  2094. COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n");
  2095. qemu_main = cocoa_main;
  2096. // Pull this console process up to being a fully-fledged graphical
  2097. // app with a menubar and Dock icon
  2098. ProcessSerialNumber psn = { 0, kCurrentProcess };
  2099. TransformProcessType(&psn, kProcessTransformToForegroundApplication);
  2100. [QemuApplication sharedApplication];
  2101. // Create an Application controller
  2102. QemuCocoaAppController *controller = [[QemuCocoaAppController alloc] init];
  2103. [NSApp setDelegate:controller];
  2104. qemu_mutex_init(&draw_mutex);
  2105. if (display_opengl) {
  2106. #ifdef CONFIG_OPENGL
  2107. if (opts->gl == DISPLAYGL_MODE_ES) {
  2108. #ifdef CONFIG_EGL
  2109. if (qemu_egl_init_dpy_cocoa(DISPLAYGL_MODE_ES)) {
  2110. exit(1);
  2111. }
  2112. view_ctx = qemu_egl_init_ctx();
  2113. if (!view_ctx) {
  2114. exit(1);
  2115. }
  2116. [cocoaView setWantsLayer:YES];
  2117. egl_surface = qemu_egl_init_surface(view_ctx, [cocoaView layer]);
  2118. if (!egl_surface) {
  2119. exit(1);
  2120. }
  2121. #else
  2122. error_report("OpenGLES without EGL is not supported - exiting");
  2123. exit(1);
  2124. #endif
  2125. } else {
  2126. NSOpenGLPixelFormat *format = cocoa_gl_create_ns_pixel_format(32);
  2127. NSOpenGLView *view = [[NSOpenGLView alloc] initWithFrame:[cocoaView frame]
  2128. pixelFormat:format];
  2129. [format release];
  2130. [cocoaView addSubview:view];
  2131. view_ctx = [view openGLContext];
  2132. [view release];
  2133. #ifdef CONFIG_EGL
  2134. egl_surface = EGL_NO_SURFACE;
  2135. #endif
  2136. cocoa_gl_make_context_current(&dgc, view_ctx);
  2137. }
  2138. dgc.gls = qemu_gl_init_shader();
  2139. glGenTextures(1, &cursor_texture);
  2140. // register vga output callbacks
  2141. ops = &dcl_gl_ops;
  2142. #else
  2143. error_report("OpenGL is not enabled - exiting");
  2144. exit(1);
  2145. #endif
  2146. } else {
  2147. // register vga output callbacks
  2148. ops = &dcl_ops;
  2149. }
  2150. while (qemu_console_lookup_by_index(listeners_count)) {
  2151. listeners_count++;
  2152. }
  2153. if (listeners_count) {
  2154. QemuConsole *con = qemu_console_lookup_first_graphic_console();
  2155. listeners = g_new0(CocoaListener, listeners_count);
  2156. active_listener = listeners + qemu_console_get_index(con);
  2157. for (index = 0; index < listeners_count; index++) {
  2158. listeners[index].dcl.con = qemu_console_lookup_by_index(index);
  2159. listeners[index].dcl.ops = ops;
  2160. #ifdef CONFIG_OPENGL
  2161. if (display_opengl) {
  2162. qemu_console_set_display_gl_ctx(listeners[index].dcl.con,
  2163. &dgc);
  2164. }
  2165. #endif
  2166. // register vga output callbacks
  2167. register_displaychangelistener(&listeners[index].dcl);
  2168. }
  2169. kbd = qkbd_state_init(active_listener->dcl.con);
  2170. qemu_add_mouse_mode_change_notifier(&mouse_mode_change_notifier);
  2171. [cocoaView notifyMouseModeChange];
  2172. }
  2173. create_initial_menus();
  2174. /*
  2175. * Create the menu entries which depend on QEMU state (for consoles
  2176. * and removeable devices). These make calls back into QEMU functions,
  2177. * which is OK because at this point we know that the second thread
  2178. * holds the iothread lock and is synchronously waiting for us to
  2179. * finish.
  2180. */
  2181. add_console_menu_entries();
  2182. addRemovableDevicesMenuItems();
  2183. /* if fullscreen mode is to be used */
  2184. if (opts->has_full_screen && opts->full_screen) {
  2185. [normalWindow toggleFullScreen: nil];
  2186. }
  2187. if (opts->u.cocoa.has_full_grab && opts->u.cocoa.full_grab) {
  2188. [controller setFullGrab: nil];
  2189. }
  2190. if (opts->has_show_cursor && opts->show_cursor) {
  2191. cursor_hide = 0;
  2192. }
  2193. if (opts->u.cocoa.has_swap_opt_cmd) {
  2194. swap_opt_cmd = opts->u.cocoa.swap_opt_cmd;
  2195. }
  2196. if (opts->u.cocoa.has_left_command_key && !opts->u.cocoa.left_command_key) {
  2197. left_command_key_enabled = 0;
  2198. }
  2199. [cocoaView updateUIInfo];
  2200. qemu_event_init(&cbevent, false);
  2201. cbowner = [[QemuCocoaPasteboardTypeOwner alloc] init];
  2202. qemu_clipboard_peer_register(&cbpeer);
  2203. [pool release];
  2204. }
  2205. static QemuDisplay qemu_display_cocoa = {
  2206. .type = DISPLAY_TYPE_COCOA,
  2207. .early_init = cocoa_display_early_init,
  2208. .init = cocoa_display_init,
  2209. };
  2210. static void register_cocoa(void)
  2211. {
  2212. qemu_display_register(&qemu_display_cocoa);
  2213. }
  2214. type_init(register_cocoa);
  2215. #ifdef CONFIG_OPENGL
  2216. module_dep("ui-opengl");
  2217. #endif