cocoa.m 69 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178
  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. #include "qemu/osdep.h"
  25. #import <Cocoa/Cocoa.h>
  26. #import <QuartzCore/QuartzCore.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 "system/system.h"
  35. #include "system/runstate.h"
  36. #include "system/runstate-action.h"
  37. #include "system/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 "system/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. #ifndef MAC_OS_VERSION_14_0
  51. #define MAC_OS_VERSION_14_0 140000
  52. #endif
  53. //#define DEBUG
  54. #ifdef DEBUG
  55. #define COCOA_DEBUG(...) { (void) fprintf (stdout, __VA_ARGS__); }
  56. #else
  57. #define COCOA_DEBUG(...) ((void) 0)
  58. #endif
  59. #define cgrect(nsrect) (*(CGRect *)&(nsrect))
  60. #define UC_CTRL_KEY "\xe2\x8c\x83"
  61. #define UC_ALT_KEY "\xe2\x8c\xa5"
  62. typedef struct {
  63. int width;
  64. int height;
  65. } QEMUScreen;
  66. @class QemuCocoaPasteboardTypeOwner;
  67. static void cocoa_update(DisplayChangeListener *dcl,
  68. int x, int y, int w, int h);
  69. static void cocoa_switch(DisplayChangeListener *dcl,
  70. DisplaySurface *surface);
  71. static void cocoa_refresh(DisplayChangeListener *dcl);
  72. static void cocoa_mouse_set(DisplayChangeListener *dcl, int x, int y, bool on);
  73. static void cocoa_cursor_define(DisplayChangeListener *dcl, QEMUCursor *cursor);
  74. static const DisplayChangeListenerOps dcl_ops = {
  75. .dpy_name = "cocoa",
  76. .dpy_gfx_update = cocoa_update,
  77. .dpy_gfx_switch = cocoa_switch,
  78. .dpy_refresh = cocoa_refresh,
  79. .dpy_mouse_set = cocoa_mouse_set,
  80. .dpy_cursor_define = cocoa_cursor_define,
  81. };
  82. static DisplayChangeListener dcl = {
  83. .ops = &dcl_ops,
  84. };
  85. static QKbdState *kbd;
  86. static int cursor_hide = 1;
  87. static int left_command_key_enabled = 1;
  88. static bool swap_opt_cmd;
  89. static CGInterpolationQuality zoom_interpolation = kCGInterpolationNone;
  90. static NSTextField *pauseLabel;
  91. static bool allow_events;
  92. static NSInteger cbchangecount = -1;
  93. static QemuClipboardInfo *cbinfo;
  94. static QemuEvent cbevent;
  95. static QemuCocoaPasteboardTypeOwner *cbowner;
  96. // Utility functions to run specified code block with the BQL held
  97. typedef void (^CodeBlock)(void);
  98. typedef bool (^BoolCodeBlock)(void);
  99. static void with_bql(CodeBlock block)
  100. {
  101. bool locked = bql_locked();
  102. if (!locked) {
  103. bql_lock();
  104. }
  105. block();
  106. if (!locked) {
  107. bql_unlock();
  108. }
  109. }
  110. static bool bool_with_bql(BoolCodeBlock block)
  111. {
  112. bool locked = bql_locked();
  113. bool val;
  114. if (!locked) {
  115. bql_lock();
  116. }
  117. val = block();
  118. if (!locked) {
  119. bql_unlock();
  120. }
  121. return val;
  122. }
  123. // Mac to QKeyCode conversion
  124. static const int mac_to_qkeycode_map[] = {
  125. [kVK_ANSI_A] = Q_KEY_CODE_A,
  126. [kVK_ANSI_B] = Q_KEY_CODE_B,
  127. [kVK_ANSI_C] = Q_KEY_CODE_C,
  128. [kVK_ANSI_D] = Q_KEY_CODE_D,
  129. [kVK_ANSI_E] = Q_KEY_CODE_E,
  130. [kVK_ANSI_F] = Q_KEY_CODE_F,
  131. [kVK_ANSI_G] = Q_KEY_CODE_G,
  132. [kVK_ANSI_H] = Q_KEY_CODE_H,
  133. [kVK_ANSI_I] = Q_KEY_CODE_I,
  134. [kVK_ANSI_J] = Q_KEY_CODE_J,
  135. [kVK_ANSI_K] = Q_KEY_CODE_K,
  136. [kVK_ANSI_L] = Q_KEY_CODE_L,
  137. [kVK_ANSI_M] = Q_KEY_CODE_M,
  138. [kVK_ANSI_N] = Q_KEY_CODE_N,
  139. [kVK_ANSI_O] = Q_KEY_CODE_O,
  140. [kVK_ANSI_P] = Q_KEY_CODE_P,
  141. [kVK_ANSI_Q] = Q_KEY_CODE_Q,
  142. [kVK_ANSI_R] = Q_KEY_CODE_R,
  143. [kVK_ANSI_S] = Q_KEY_CODE_S,
  144. [kVK_ANSI_T] = Q_KEY_CODE_T,
  145. [kVK_ANSI_U] = Q_KEY_CODE_U,
  146. [kVK_ANSI_V] = Q_KEY_CODE_V,
  147. [kVK_ANSI_W] = Q_KEY_CODE_W,
  148. [kVK_ANSI_X] = Q_KEY_CODE_X,
  149. [kVK_ANSI_Y] = Q_KEY_CODE_Y,
  150. [kVK_ANSI_Z] = Q_KEY_CODE_Z,
  151. [kVK_ANSI_0] = Q_KEY_CODE_0,
  152. [kVK_ANSI_1] = Q_KEY_CODE_1,
  153. [kVK_ANSI_2] = Q_KEY_CODE_2,
  154. [kVK_ANSI_3] = Q_KEY_CODE_3,
  155. [kVK_ANSI_4] = Q_KEY_CODE_4,
  156. [kVK_ANSI_5] = Q_KEY_CODE_5,
  157. [kVK_ANSI_6] = Q_KEY_CODE_6,
  158. [kVK_ANSI_7] = Q_KEY_CODE_7,
  159. [kVK_ANSI_8] = Q_KEY_CODE_8,
  160. [kVK_ANSI_9] = Q_KEY_CODE_9,
  161. [kVK_ANSI_Grave] = Q_KEY_CODE_GRAVE_ACCENT,
  162. [kVK_ANSI_Minus] = Q_KEY_CODE_MINUS,
  163. [kVK_ANSI_Equal] = Q_KEY_CODE_EQUAL,
  164. [kVK_Delete] = Q_KEY_CODE_BACKSPACE,
  165. [kVK_CapsLock] = Q_KEY_CODE_CAPS_LOCK,
  166. [kVK_Tab] = Q_KEY_CODE_TAB,
  167. [kVK_Return] = Q_KEY_CODE_RET,
  168. [kVK_ANSI_LeftBracket] = Q_KEY_CODE_BRACKET_LEFT,
  169. [kVK_ANSI_RightBracket] = Q_KEY_CODE_BRACKET_RIGHT,
  170. [kVK_ANSI_Backslash] = Q_KEY_CODE_BACKSLASH,
  171. [kVK_ANSI_Semicolon] = Q_KEY_CODE_SEMICOLON,
  172. [kVK_ANSI_Quote] = Q_KEY_CODE_APOSTROPHE,
  173. [kVK_ANSI_Comma] = Q_KEY_CODE_COMMA,
  174. [kVK_ANSI_Period] = Q_KEY_CODE_DOT,
  175. [kVK_ANSI_Slash] = Q_KEY_CODE_SLASH,
  176. [kVK_Space] = Q_KEY_CODE_SPC,
  177. [kVK_ANSI_Keypad0] = Q_KEY_CODE_KP_0,
  178. [kVK_ANSI_Keypad1] = Q_KEY_CODE_KP_1,
  179. [kVK_ANSI_Keypad2] = Q_KEY_CODE_KP_2,
  180. [kVK_ANSI_Keypad3] = Q_KEY_CODE_KP_3,
  181. [kVK_ANSI_Keypad4] = Q_KEY_CODE_KP_4,
  182. [kVK_ANSI_Keypad5] = Q_KEY_CODE_KP_5,
  183. [kVK_ANSI_Keypad6] = Q_KEY_CODE_KP_6,
  184. [kVK_ANSI_Keypad7] = Q_KEY_CODE_KP_7,
  185. [kVK_ANSI_Keypad8] = Q_KEY_CODE_KP_8,
  186. [kVK_ANSI_Keypad9] = Q_KEY_CODE_KP_9,
  187. [kVK_ANSI_KeypadDecimal] = Q_KEY_CODE_KP_DECIMAL,
  188. [kVK_ANSI_KeypadEnter] = Q_KEY_CODE_KP_ENTER,
  189. [kVK_ANSI_KeypadPlus] = Q_KEY_CODE_KP_ADD,
  190. [kVK_ANSI_KeypadMinus] = Q_KEY_CODE_KP_SUBTRACT,
  191. [kVK_ANSI_KeypadMultiply] = Q_KEY_CODE_KP_MULTIPLY,
  192. [kVK_ANSI_KeypadDivide] = Q_KEY_CODE_KP_DIVIDE,
  193. [kVK_ANSI_KeypadEquals] = Q_KEY_CODE_KP_EQUALS,
  194. [kVK_ANSI_KeypadClear] = Q_KEY_CODE_NUM_LOCK,
  195. [kVK_UpArrow] = Q_KEY_CODE_UP,
  196. [kVK_DownArrow] = Q_KEY_CODE_DOWN,
  197. [kVK_LeftArrow] = Q_KEY_CODE_LEFT,
  198. [kVK_RightArrow] = Q_KEY_CODE_RIGHT,
  199. [kVK_Help] = Q_KEY_CODE_INSERT,
  200. [kVK_Home] = Q_KEY_CODE_HOME,
  201. [kVK_PageUp] = Q_KEY_CODE_PGUP,
  202. [kVK_PageDown] = Q_KEY_CODE_PGDN,
  203. [kVK_End] = Q_KEY_CODE_END,
  204. [kVK_ForwardDelete] = Q_KEY_CODE_DELETE,
  205. [kVK_Escape] = Q_KEY_CODE_ESC,
  206. /* The Power key can't be used directly because the operating system uses
  207. * it. This key can be emulated by using it in place of another key such as
  208. * F1. Don't forget to disable the real key binding.
  209. */
  210. /* [kVK_F1] = Q_KEY_CODE_POWER, */
  211. [kVK_F1] = Q_KEY_CODE_F1,
  212. [kVK_F2] = Q_KEY_CODE_F2,
  213. [kVK_F3] = Q_KEY_CODE_F3,
  214. [kVK_F4] = Q_KEY_CODE_F4,
  215. [kVK_F5] = Q_KEY_CODE_F5,
  216. [kVK_F6] = Q_KEY_CODE_F6,
  217. [kVK_F7] = Q_KEY_CODE_F7,
  218. [kVK_F8] = Q_KEY_CODE_F8,
  219. [kVK_F9] = Q_KEY_CODE_F9,
  220. [kVK_F10] = Q_KEY_CODE_F10,
  221. [kVK_F11] = Q_KEY_CODE_F11,
  222. [kVK_F12] = Q_KEY_CODE_F12,
  223. [kVK_F13] = Q_KEY_CODE_PRINT,
  224. [kVK_F14] = Q_KEY_CODE_SCROLL_LOCK,
  225. [kVK_F15] = Q_KEY_CODE_PAUSE,
  226. // JIS keyboards only
  227. [kVK_JIS_Yen] = Q_KEY_CODE_YEN,
  228. [kVK_JIS_Underscore] = Q_KEY_CODE_RO,
  229. [kVK_JIS_KeypadComma] = Q_KEY_CODE_KP_COMMA,
  230. [kVK_JIS_Eisu] = Q_KEY_CODE_MUHENKAN,
  231. [kVK_JIS_Kana] = Q_KEY_CODE_HENKAN,
  232. /*
  233. * The eject and volume keys can't be used here because they are handled at
  234. * a lower level than what an Application can see.
  235. */
  236. };
  237. static int cocoa_keycode_to_qemu(int keycode)
  238. {
  239. if (ARRAY_SIZE(mac_to_qkeycode_map) <= keycode) {
  240. error_report("(cocoa) warning unknown keycode 0x%x", keycode);
  241. return 0;
  242. }
  243. return mac_to_qkeycode_map[keycode];
  244. }
  245. /* Displays an alert dialog box with the specified message */
  246. static void QEMU_Alert(NSString *message)
  247. {
  248. NSAlert *alert;
  249. alert = [NSAlert new];
  250. [alert setMessageText: message];
  251. [alert runModal];
  252. }
  253. /* Handles any errors that happen with a device transaction */
  254. static void handleAnyDeviceErrors(Error * err)
  255. {
  256. if (err) {
  257. QEMU_Alert([NSString stringWithCString: error_get_pretty(err)
  258. encoding: NSASCIIStringEncoding]);
  259. error_free(err);
  260. }
  261. }
  262. /*
  263. ------------------------------------------------------
  264. QemuCocoaView
  265. ------------------------------------------------------
  266. */
  267. @interface QemuCocoaView : NSView
  268. {
  269. QEMUScreen screen;
  270. pixman_image_t *pixman_image;
  271. /* The state surrounding mouse grabbing is potentially confusing.
  272. * isAbsoluteEnabled tracks qemu_input_is_absolute() [ie "is the emulated
  273. * pointing device an absolute-position one?"], but is only updated on
  274. * next refresh.
  275. * isMouseGrabbed tracks whether GUI events are directed to the guest;
  276. * it controls whether special keys like Cmd get sent to the guest,
  277. * and whether we capture the mouse when in non-absolute mode.
  278. */
  279. BOOL isMouseGrabbed;
  280. BOOL isAbsoluteEnabled;
  281. CFMachPortRef eventsTap;
  282. CGColorSpaceRef colorspace;
  283. CALayer *cursorLayer;
  284. QEMUCursor *cursor;
  285. int mouseX;
  286. int mouseY;
  287. bool mouseOn;
  288. }
  289. - (void) switchSurface:(pixman_image_t *)image;
  290. - (void) grabMouse;
  291. - (void) ungrabMouse;
  292. - (void) setFullGrab:(id)sender;
  293. - (void) handleMonitorInput:(NSEvent *)event;
  294. - (bool) handleEvent:(NSEvent *)event;
  295. - (bool) handleEventLocked:(NSEvent *)event;
  296. - (void) notifyMouseModeChange;
  297. - (BOOL) isMouseGrabbed;
  298. - (QEMUScreen) gscreen;
  299. - (void) raiseAllKeys;
  300. @end
  301. QemuCocoaView *cocoaView;
  302. static CGEventRef handleTapEvent(CGEventTapProxy proxy, CGEventType type, CGEventRef cgEvent, void *userInfo)
  303. {
  304. QemuCocoaView *view = userInfo;
  305. NSEvent *event = [NSEvent eventWithCGEvent:cgEvent];
  306. if ([view isMouseGrabbed] && [view handleEvent:event]) {
  307. COCOA_DEBUG("Global events tap: qemu handled the event, capturing!\n");
  308. return NULL;
  309. }
  310. COCOA_DEBUG("Global events tap: qemu did not handle the event, letting it through...\n");
  311. return cgEvent;
  312. }
  313. @implementation QemuCocoaView
  314. - (id)initWithFrame:(NSRect)frameRect
  315. {
  316. COCOA_DEBUG("QemuCocoaView: initWithFrame\n");
  317. self = [super initWithFrame:frameRect];
  318. if (self) {
  319. NSTrackingAreaOptions options = NSTrackingActiveInKeyWindow |
  320. NSTrackingMouseEnteredAndExited |
  321. NSTrackingMouseMoved |
  322. NSTrackingInVisibleRect;
  323. NSTrackingArea *trackingArea =
  324. [[NSTrackingArea alloc] initWithRect:CGRectZero
  325. options:options
  326. owner:self
  327. userInfo:nil];
  328. [self addTrackingArea:trackingArea];
  329. [trackingArea release];
  330. screen.width = frameRect.size.width;
  331. screen.height = frameRect.size.height;
  332. colorspace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
  333. #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_14_0
  334. [self setClipsToBounds:YES];
  335. #endif
  336. [self setWantsLayer:YES];
  337. cursorLayer = [[CALayer alloc] init];
  338. [cursorLayer setAnchorPoint:CGPointMake(0, 1)];
  339. [cursorLayer setAutoresizingMask:kCALayerMaxXMargin |
  340. kCALayerMinYMargin];
  341. [[self layer] addSublayer:cursorLayer];
  342. }
  343. return self;
  344. }
  345. - (void) dealloc
  346. {
  347. COCOA_DEBUG("QemuCocoaView: dealloc\n");
  348. if (pixman_image) {
  349. pixman_image_unref(pixman_image);
  350. }
  351. if (eventsTap) {
  352. CFRelease(eventsTap);
  353. }
  354. CGColorSpaceRelease(colorspace);
  355. [cursorLayer release];
  356. cursor_unref(cursor);
  357. [super dealloc];
  358. }
  359. - (BOOL) isOpaque
  360. {
  361. return YES;
  362. }
  363. - (void) viewDidMoveToWindow
  364. {
  365. [self resizeWindow];
  366. }
  367. - (void) selectConsoleLocked:(unsigned int)index
  368. {
  369. QemuConsole *con = qemu_console_lookup_by_index(index);
  370. if (!con) {
  371. return;
  372. }
  373. unregister_displaychangelistener(&dcl);
  374. qkbd_state_switch_console(kbd, con);
  375. dcl.con = con;
  376. register_displaychangelistener(&dcl);
  377. [self notifyMouseModeChange];
  378. [self updateUIInfo];
  379. }
  380. - (void) hideCursor
  381. {
  382. if (!cursor_hide) {
  383. return;
  384. }
  385. [NSCursor hide];
  386. }
  387. - (void) unhideCursor
  388. {
  389. if (!cursor_hide) {
  390. return;
  391. }
  392. [NSCursor unhide];
  393. }
  394. - (void)setMouseX:(int)x y:(int)y on:(bool)on
  395. {
  396. CGPoint position;
  397. mouseX = x;
  398. mouseY = y;
  399. mouseOn = on;
  400. position.x = mouseX;
  401. position.y = screen.height - mouseY;
  402. [CATransaction begin];
  403. [CATransaction setDisableActions:YES];
  404. [cursorLayer setPosition:position];
  405. [cursorLayer setHidden:!mouseOn];
  406. [CATransaction commit];
  407. }
  408. - (void)setCursor:(QEMUCursor *)given_cursor
  409. {
  410. CGDataProviderRef provider;
  411. CGImageRef image;
  412. CGRect bounds = CGRectZero;
  413. cursor_unref(cursor);
  414. cursor = given_cursor;
  415. if (!cursor) {
  416. return;
  417. }
  418. cursor_ref(cursor);
  419. bounds.size.width = cursor->width;
  420. bounds.size.height = cursor->height;
  421. provider = CGDataProviderCreateWithData(
  422. NULL,
  423. cursor->data,
  424. cursor->width * cursor->height * 4,
  425. NULL
  426. );
  427. image = CGImageCreate(
  428. cursor->width, //width
  429. cursor->height, //height
  430. 8, //bitsPerComponent
  431. 32, //bitsPerPixel
  432. cursor->width * 4, //bytesPerRow
  433. colorspace, //colorspace
  434. kCGBitmapByteOrder32Little | kCGImageAlphaFirst, //bitmapInfo
  435. provider, //provider
  436. NULL, //decode
  437. 0, //interpolate
  438. kCGRenderingIntentDefault //intent
  439. );
  440. CGDataProviderRelease(provider);
  441. [CATransaction begin];
  442. [CATransaction setDisableActions:YES];
  443. [cursorLayer setBounds:bounds];
  444. [cursorLayer setContents:(id)image];
  445. [CATransaction commit];
  446. CGImageRelease(image);
  447. }
  448. - (void) drawRect:(NSRect) rect
  449. {
  450. COCOA_DEBUG("QemuCocoaView: drawRect\n");
  451. // get CoreGraphic context
  452. CGContextRef viewContextRef = [[NSGraphicsContext currentContext] CGContext];
  453. CGContextSetInterpolationQuality (viewContextRef, zoom_interpolation);
  454. CGContextSetShouldAntialias (viewContextRef, NO);
  455. // draw screen bitmap directly to Core Graphics context
  456. if (!pixman_image) {
  457. // Draw request before any guest device has set up a framebuffer:
  458. // just draw an opaque black rectangle
  459. CGContextSetRGBFillColor(viewContextRef, 0, 0, 0, 1.0);
  460. CGContextFillRect(viewContextRef, NSRectToCGRect(rect));
  461. } else {
  462. int w = pixman_image_get_width(pixman_image);
  463. int h = pixman_image_get_height(pixman_image);
  464. int bitsPerPixel = PIXMAN_FORMAT_BPP(pixman_image_get_format(pixman_image));
  465. int stride = pixman_image_get_stride(pixman_image);
  466. CGDataProviderRef dataProviderRef = CGDataProviderCreateWithData(
  467. NULL,
  468. pixman_image_get_data(pixman_image),
  469. stride * h,
  470. NULL
  471. );
  472. CGImageRef imageRef = CGImageCreate(
  473. w, //width
  474. h, //height
  475. DIV_ROUND_UP(bitsPerPixel, 8) * 2, //bitsPerComponent
  476. bitsPerPixel, //bitsPerPixel
  477. stride, //bytesPerRow
  478. colorspace, //colorspace
  479. kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, //bitmapInfo
  480. dataProviderRef, //provider
  481. NULL, //decode
  482. 0, //interpolate
  483. kCGRenderingIntentDefault //intent
  484. );
  485. // selective drawing code (draws only dirty rectangles) (OS X >= 10.4)
  486. const NSRect *rectList;
  487. NSInteger rectCount;
  488. int i;
  489. CGImageRef clipImageRef;
  490. CGRect clipRect;
  491. [self getRectsBeingDrawn:&rectList count:&rectCount];
  492. for (i = 0; i < rectCount; i++) {
  493. clipRect = rectList[i];
  494. clipRect.origin.y = (float)h - (clipRect.origin.y + clipRect.size.height);
  495. clipImageRef = CGImageCreateWithImageInRect(
  496. imageRef,
  497. clipRect
  498. );
  499. CGContextDrawImage (viewContextRef, cgrect(rectList[i]), clipImageRef);
  500. CGImageRelease (clipImageRef);
  501. }
  502. CGImageRelease (imageRef);
  503. CGDataProviderRelease(dataProviderRef);
  504. }
  505. }
  506. - (NSSize)fixAspectRatio:(NSSize)max
  507. {
  508. NSSize scaled;
  509. NSSize fixed;
  510. scaled.width = screen.width * max.height;
  511. scaled.height = screen.height * max.width;
  512. /*
  513. * Here screen is our guest's output size, and max is the size of the
  514. * largest possible area of the screen we can display on.
  515. * We want to scale up (screen.width x screen.height) by either:
  516. * 1) max.height / screen.height
  517. * 2) max.width / screen.width
  518. * With the first scale factor the scale will result in an output height of
  519. * max.height (i.e. we will fill the whole height of the available screen
  520. * space and have black bars left and right) and with the second scale
  521. * factor the scaling will result in an output width of max.width (i.e. we
  522. * fill the whole width of the available screen space and have black bars
  523. * top and bottom). We need to pick whichever keeps the whole of the guest
  524. * output on the screen, which is to say the smaller of the two scale
  525. * factors.
  526. * To avoid doing more division than strictly necessary, instead of directly
  527. * comparing scale factors 1 and 2 we instead calculate and compare those
  528. * two scale factors multiplied by (screen.height * screen.width).
  529. */
  530. if (scaled.width < scaled.height) {
  531. fixed.width = scaled.width / screen.height;
  532. fixed.height = max.height;
  533. } else {
  534. fixed.width = max.width;
  535. fixed.height = scaled.height / screen.width;
  536. }
  537. return fixed;
  538. }
  539. - (NSSize) screenSafeAreaSize
  540. {
  541. NSSize size = [[[self window] screen] frame].size;
  542. NSEdgeInsets insets = [[[self window] screen] safeAreaInsets];
  543. size.width -= insets.left + insets.right;
  544. size.height -= insets.top + insets.bottom;
  545. return size;
  546. }
  547. - (void) resizeWindow
  548. {
  549. [[self window] setContentAspectRatio:NSMakeSize(screen.width, screen.height)];
  550. if (!([[self window] styleMask] & NSWindowStyleMaskResizable)) {
  551. [[self window] setContentSize:NSMakeSize(screen.width, screen.height)];
  552. [[self window] center];
  553. } else if ([[self window] styleMask] & NSWindowStyleMaskFullScreen) {
  554. [[self window] setContentSize:[self fixAspectRatio:[self screenSafeAreaSize]]];
  555. [[self window] center];
  556. } else {
  557. [[self window] setContentSize:[self fixAspectRatio:[self frame].size]];
  558. }
  559. }
  560. - (void) updateBounds
  561. {
  562. [self setBoundsSize:NSMakeSize(screen.width, screen.height)];
  563. }
  564. #pragma clang diagnostic push
  565. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  566. - (void) updateUIInfoLocked
  567. {
  568. /* Must be called with the BQL, i.e. via updateUIInfo */
  569. NSSize frameSize;
  570. QemuUIInfo info;
  571. if (!qemu_console_is_graphic(dcl.con)) {
  572. return;
  573. }
  574. if ([self window]) {
  575. NSDictionary *description = [[[self window] screen] deviceDescription];
  576. CGDirectDisplayID display = [[description objectForKey:@"NSScreenNumber"] unsignedIntValue];
  577. NSSize screenSize = [[[self window] screen] frame].size;
  578. CGSize screenPhysicalSize = CGDisplayScreenSize(display);
  579. bool isFullscreen = ([[self window] styleMask] & NSWindowStyleMaskFullScreen) != 0;
  580. CVDisplayLinkRef displayLink;
  581. frameSize = isFullscreen ? [self screenSafeAreaSize] : [self frame].size;
  582. if (!CVDisplayLinkCreateWithCGDisplay(display, &displayLink)) {
  583. CVTime period = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(displayLink);
  584. CVDisplayLinkRelease(displayLink);
  585. if (!(period.flags & kCVTimeIsIndefinite)) {
  586. update_displaychangelistener(&dcl,
  587. 1000 * period.timeValue / period.timeScale);
  588. info.refresh_rate = (int64_t)1000 * period.timeScale / period.timeValue;
  589. }
  590. }
  591. info.width_mm = frameSize.width / screenSize.width * screenPhysicalSize.width;
  592. info.height_mm = frameSize.height / screenSize.height * screenPhysicalSize.height;
  593. } else {
  594. frameSize = [self frame].size;
  595. info.width_mm = 0;
  596. info.height_mm = 0;
  597. }
  598. info.xoff = 0;
  599. info.yoff = 0;
  600. info.width = frameSize.width;
  601. info.height = frameSize.height;
  602. dpy_set_ui_info(dcl.con, &info, TRUE);
  603. }
  604. #pragma clang diagnostic pop
  605. - (void) updateUIInfo
  606. {
  607. if (!allow_events) {
  608. /*
  609. * Don't try to tell QEMU about UI information in the application
  610. * startup phase -- we haven't yet registered dcl with the QEMU UI
  611. * layer.
  612. * When cocoa_display_init() does register the dcl, the UI layer
  613. * will call cocoa_switch(), which will call updateUIInfo, so
  614. * we don't lose any information here.
  615. */
  616. return;
  617. }
  618. with_bql(^{
  619. [self updateUIInfoLocked];
  620. });
  621. }
  622. - (void) switchSurface:(pixman_image_t *)image
  623. {
  624. COCOA_DEBUG("QemuCocoaView: switchSurface\n");
  625. int w = pixman_image_get_width(image);
  626. int h = pixman_image_get_height(image);
  627. if (w != screen.width || h != screen.height) {
  628. // Resize before we trigger the redraw, or we'll redraw at the wrong size
  629. COCOA_DEBUG("switchSurface: new size %d x %d\n", w, h);
  630. screen.width = w;
  631. screen.height = h;
  632. [self resizeWindow];
  633. [self updateBounds];
  634. }
  635. // update screenBuffer
  636. if (pixman_image) {
  637. pixman_image_unref(pixman_image);
  638. }
  639. pixman_image = image;
  640. }
  641. - (void) setFullGrab:(id)sender
  642. {
  643. COCOA_DEBUG("QemuCocoaView: setFullGrab\n");
  644. CGEventMask mask = CGEventMaskBit(kCGEventKeyDown) | CGEventMaskBit(kCGEventKeyUp) | CGEventMaskBit(kCGEventFlagsChanged);
  645. eventsTap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault,
  646. mask, handleTapEvent, self);
  647. if (!eventsTap) {
  648. warn_report("Could not create event tap, system key combos will not be captured.\n");
  649. return;
  650. } else {
  651. COCOA_DEBUG("Global events tap created! Will capture system key combos.\n");
  652. }
  653. CFRunLoopRef runLoop = CFRunLoopGetCurrent();
  654. if (!runLoop) {
  655. warn_report("Could not obtain current CF RunLoop, system key combos will not be captured.\n");
  656. return;
  657. }
  658. CFRunLoopSourceRef tapEventsSrc = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventsTap, 0);
  659. if (!tapEventsSrc ) {
  660. warn_report("Could not obtain current CF RunLoop, system key combos will not be captured.\n");
  661. return;
  662. }
  663. CFRunLoopAddSource(runLoop, tapEventsSrc, kCFRunLoopDefaultMode);
  664. CFRelease(tapEventsSrc);
  665. }
  666. - (void) toggleKey: (int)keycode {
  667. qkbd_state_key_event(kbd, keycode, !qkbd_state_key_get(kbd, keycode));
  668. }
  669. // Does the work of sending input to the monitor
  670. - (void) handleMonitorInput:(NSEvent *)event
  671. {
  672. int keysym = 0;
  673. int control_key = 0;
  674. // if the control key is down
  675. if ([event modifierFlags] & NSEventModifierFlagControl) {
  676. control_key = 1;
  677. }
  678. /* translates Macintosh keycodes to QEMU's keysym */
  679. static const int without_control_translation[] = {
  680. [0 ... 0xff] = 0, // invalid key
  681. [kVK_UpArrow] = QEMU_KEY_UP,
  682. [kVK_DownArrow] = QEMU_KEY_DOWN,
  683. [kVK_RightArrow] = QEMU_KEY_RIGHT,
  684. [kVK_LeftArrow] = QEMU_KEY_LEFT,
  685. [kVK_Home] = QEMU_KEY_HOME,
  686. [kVK_End] = QEMU_KEY_END,
  687. [kVK_PageUp] = QEMU_KEY_PAGEUP,
  688. [kVK_PageDown] = QEMU_KEY_PAGEDOWN,
  689. [kVK_ForwardDelete] = QEMU_KEY_DELETE,
  690. [kVK_Delete] = QEMU_KEY_BACKSPACE,
  691. };
  692. static const int with_control_translation[] = {
  693. [0 ... 0xff] = 0, // invalid key
  694. [kVK_UpArrow] = QEMU_KEY_CTRL_UP,
  695. [kVK_DownArrow] = QEMU_KEY_CTRL_DOWN,
  696. [kVK_RightArrow] = QEMU_KEY_CTRL_RIGHT,
  697. [kVK_LeftArrow] = QEMU_KEY_CTRL_LEFT,
  698. [kVK_Home] = QEMU_KEY_CTRL_HOME,
  699. [kVK_End] = QEMU_KEY_CTRL_END,
  700. [kVK_PageUp] = QEMU_KEY_CTRL_PAGEUP,
  701. [kVK_PageDown] = QEMU_KEY_CTRL_PAGEDOWN,
  702. };
  703. if (control_key != 0) { /* If the control key is being used */
  704. if ([event keyCode] < ARRAY_SIZE(with_control_translation)) {
  705. keysym = with_control_translation[[event keyCode]];
  706. }
  707. } else {
  708. if ([event keyCode] < ARRAY_SIZE(without_control_translation)) {
  709. keysym = without_control_translation[[event keyCode]];
  710. }
  711. }
  712. // if not a key that needs translating
  713. if (keysym == 0) {
  714. NSString *ks = [event characters];
  715. if ([ks length] > 0) {
  716. keysym = [ks characterAtIndex:0];
  717. }
  718. }
  719. if (keysym) {
  720. QemuTextConsole *con = QEMU_TEXT_CONSOLE(dcl.con);
  721. qemu_text_console_put_keysym(con, keysym);
  722. }
  723. }
  724. - (bool) handleEvent:(NSEvent *)event
  725. {
  726. return bool_with_bql(^{
  727. return [self handleEventLocked:event];
  728. });
  729. }
  730. - (bool) handleEventLocked:(NSEvent *)event
  731. {
  732. /* Return true if we handled the event, false if it should be given to OSX */
  733. COCOA_DEBUG("QemuCocoaView: handleEvent\n");
  734. InputButton button;
  735. int keycode = 0;
  736. NSUInteger modifiers = [event modifierFlags];
  737. /*
  738. * Check -[NSEvent modifierFlags] here.
  739. *
  740. * There is a NSEventType for an event notifying the change of
  741. * -[NSEvent modifierFlags], NSEventTypeFlagsChanged but these operations
  742. * are performed for any events because a modifier state may change while
  743. * the application is inactive (i.e. no events fire) and we don't want to
  744. * wait for another modifier state change to detect such a change.
  745. *
  746. * NSEventModifierFlagCapsLock requires a special treatment. The other flags
  747. * are handled in similar manners.
  748. *
  749. * NSEventModifierFlagCapsLock
  750. * ---------------------------
  751. *
  752. * If CapsLock state is changed, "up" and "down" events will be fired in
  753. * sequence, effectively updates CapsLock state on the guest.
  754. *
  755. * The other flags
  756. * ---------------
  757. *
  758. * If a flag is not set, fire "up" events for all keys which correspond to
  759. * the flag. Note that "down" events are not fired here because the flags
  760. * checked here do not tell what exact keys are down.
  761. *
  762. * If one of the keys corresponding to a flag is down, we rely on
  763. * -[NSEvent keyCode] of an event whose -[NSEvent type] is
  764. * NSEventTypeFlagsChanged to know the exact key which is down, which has
  765. * the following two downsides:
  766. * - It does not work when the application is inactive as described above.
  767. * - It malfactions *after* the modifier state is changed while the
  768. * application is inactive. It is because -[NSEvent keyCode] does not tell
  769. * if the key is up or down, and requires to infer the current state from
  770. * the previous state. It is still possible to fix such a malfanction by
  771. * completely leaving your hands from the keyboard, which hopefully makes
  772. * this implementation usable enough.
  773. */
  774. if (!!(modifiers & NSEventModifierFlagCapsLock) !=
  775. qkbd_state_modifier_get(kbd, QKBD_MOD_CAPSLOCK)) {
  776. qkbd_state_key_event(kbd, Q_KEY_CODE_CAPS_LOCK, true);
  777. qkbd_state_key_event(kbd, Q_KEY_CODE_CAPS_LOCK, false);
  778. }
  779. if (!(modifiers & NSEventModifierFlagShift)) {
  780. qkbd_state_key_event(kbd, Q_KEY_CODE_SHIFT, false);
  781. qkbd_state_key_event(kbd, Q_KEY_CODE_SHIFT_R, false);
  782. }
  783. if (!(modifiers & NSEventModifierFlagControl)) {
  784. qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL, false);
  785. qkbd_state_key_event(kbd, Q_KEY_CODE_CTRL_R, false);
  786. }
  787. if (!(modifiers & NSEventModifierFlagOption)) {
  788. if (swap_opt_cmd) {
  789. qkbd_state_key_event(kbd, Q_KEY_CODE_META_L, false);
  790. qkbd_state_key_event(kbd, Q_KEY_CODE_META_R, false);
  791. } else {
  792. qkbd_state_key_event(kbd, Q_KEY_CODE_ALT, false);
  793. qkbd_state_key_event(kbd, Q_KEY_CODE_ALT_R, false);
  794. }
  795. }
  796. if (!(modifiers & NSEventModifierFlagCommand)) {
  797. if (swap_opt_cmd) {
  798. qkbd_state_key_event(kbd, Q_KEY_CODE_ALT, false);
  799. qkbd_state_key_event(kbd, Q_KEY_CODE_ALT_R, false);
  800. } else {
  801. qkbd_state_key_event(kbd, Q_KEY_CODE_META_L, false);
  802. qkbd_state_key_event(kbd, Q_KEY_CODE_META_R, false);
  803. }
  804. }
  805. switch ([event type]) {
  806. case NSEventTypeFlagsChanged:
  807. switch ([event keyCode]) {
  808. case kVK_Shift:
  809. if (!!(modifiers & NSEventModifierFlagShift)) {
  810. [self toggleKey:Q_KEY_CODE_SHIFT];
  811. }
  812. break;
  813. case kVK_RightShift:
  814. if (!!(modifiers & NSEventModifierFlagShift)) {
  815. [self toggleKey:Q_KEY_CODE_SHIFT_R];
  816. }
  817. break;
  818. case kVK_Control:
  819. if (!!(modifiers & NSEventModifierFlagControl)) {
  820. [self toggleKey:Q_KEY_CODE_CTRL];
  821. }
  822. break;
  823. case kVK_RightControl:
  824. if (!!(modifiers & NSEventModifierFlagControl)) {
  825. [self toggleKey:Q_KEY_CODE_CTRL_R];
  826. }
  827. break;
  828. case kVK_Option:
  829. if (!!(modifiers & NSEventModifierFlagOption)) {
  830. if (swap_opt_cmd) {
  831. [self toggleKey:Q_KEY_CODE_META_L];
  832. } else {
  833. [self toggleKey:Q_KEY_CODE_ALT];
  834. }
  835. }
  836. break;
  837. case kVK_RightOption:
  838. if (!!(modifiers & NSEventModifierFlagOption)) {
  839. if (swap_opt_cmd) {
  840. [self toggleKey:Q_KEY_CODE_META_R];
  841. } else {
  842. [self toggleKey:Q_KEY_CODE_ALT_R];
  843. }
  844. }
  845. break;
  846. /* Don't pass command key changes to guest unless mouse is grabbed */
  847. case kVK_Command:
  848. if (isMouseGrabbed &&
  849. !!(modifiers & NSEventModifierFlagCommand) &&
  850. left_command_key_enabled) {
  851. if (swap_opt_cmd) {
  852. [self toggleKey:Q_KEY_CODE_ALT];
  853. } else {
  854. [self toggleKey:Q_KEY_CODE_META_L];
  855. }
  856. }
  857. break;
  858. case kVK_RightCommand:
  859. if (isMouseGrabbed &&
  860. !!(modifiers & NSEventModifierFlagCommand)) {
  861. if (swap_opt_cmd) {
  862. [self toggleKey:Q_KEY_CODE_ALT_R];
  863. } else {
  864. [self toggleKey:Q_KEY_CODE_META_R];
  865. }
  866. }
  867. break;
  868. }
  869. return true;
  870. case NSEventTypeKeyDown:
  871. keycode = cocoa_keycode_to_qemu([event keyCode]);
  872. // forward command key combos to the host UI unless the mouse is grabbed
  873. if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) {
  874. return false;
  875. }
  876. // default
  877. // handle control + alt Key Combos (ctrl+alt+[1..9,g] is reserved for QEMU)
  878. if (([event modifierFlags] & NSEventModifierFlagControl) && ([event modifierFlags] & NSEventModifierFlagOption)) {
  879. NSString *keychar = [event charactersIgnoringModifiers];
  880. if ([keychar length] == 1) {
  881. char key = [keychar characterAtIndex:0];
  882. switch (key) {
  883. // enable graphic console
  884. case '1' ... '9':
  885. [self selectConsoleLocked:key - '0' - 1]; /* ascii math */
  886. return true;
  887. // release the mouse grab
  888. case 'g':
  889. [self ungrabMouse];
  890. return true;
  891. }
  892. }
  893. }
  894. if (qemu_console_is_graphic(dcl.con)) {
  895. qkbd_state_key_event(kbd, keycode, true);
  896. } else {
  897. [self handleMonitorInput: event];
  898. }
  899. return true;
  900. case NSEventTypeKeyUp:
  901. keycode = cocoa_keycode_to_qemu([event keyCode]);
  902. // don't pass the guest a spurious key-up if we treated this
  903. // command-key combo as a host UI action
  904. if (!isMouseGrabbed && ([event modifierFlags] & NSEventModifierFlagCommand)) {
  905. return true;
  906. }
  907. if (qemu_console_is_graphic(dcl.con)) {
  908. qkbd_state_key_event(kbd, keycode, false);
  909. }
  910. return true;
  911. case NSEventTypeScrollWheel:
  912. /*
  913. * Send wheel events to the guest regardless of window focus.
  914. * This is in-line with standard Mac OS X UI behaviour.
  915. */
  916. /* Determine if this is a scroll up or scroll down event */
  917. if ([event deltaY] != 0) {
  918. button = ([event deltaY] > 0) ?
  919. INPUT_BUTTON_WHEEL_UP : INPUT_BUTTON_WHEEL_DOWN;
  920. } else if ([event deltaX] != 0) {
  921. button = ([event deltaX] > 0) ?
  922. INPUT_BUTTON_WHEEL_LEFT : INPUT_BUTTON_WHEEL_RIGHT;
  923. } else {
  924. /*
  925. * We shouldn't have got a scroll event when deltaY and delta Y
  926. * are zero, hence no harm in dropping the event
  927. */
  928. return true;
  929. }
  930. qemu_input_queue_btn(dcl.con, button, true);
  931. qemu_input_event_sync();
  932. qemu_input_queue_btn(dcl.con, button, false);
  933. qemu_input_event_sync();
  934. return true;
  935. default:
  936. return false;
  937. }
  938. }
  939. - (void) handleMouseEvent:(NSEvent *)event button:(InputButton)button down:(bool)down
  940. {
  941. if (!isMouseGrabbed) {
  942. return;
  943. }
  944. with_bql(^{
  945. qemu_input_queue_btn(dcl.con, button, down);
  946. });
  947. [self handleMouseEvent:event];
  948. }
  949. - (void) handleMouseEvent:(NSEvent *)event
  950. {
  951. if (!isMouseGrabbed) {
  952. return;
  953. }
  954. with_bql(^{
  955. if (isAbsoluteEnabled) {
  956. CGFloat d = (CGFloat)screen.height / [self frame].size.height;
  957. NSPoint p = [event locationInWindow];
  958. /* Note that the origin for Cocoa mouse coords is bottom left, not top left. */
  959. qemu_input_queue_abs(dcl.con, INPUT_AXIS_X, p.x * d, 0, screen.width);
  960. qemu_input_queue_abs(dcl.con, INPUT_AXIS_Y, screen.height - p.y * d, 0, screen.height);
  961. } else {
  962. qemu_input_queue_rel(dcl.con, INPUT_AXIS_X, [event deltaX]);
  963. qemu_input_queue_rel(dcl.con, INPUT_AXIS_Y, [event deltaY]);
  964. }
  965. qemu_input_event_sync();
  966. });
  967. }
  968. - (void) mouseExited:(NSEvent *)event
  969. {
  970. if (isAbsoluteEnabled && isMouseGrabbed) {
  971. [self ungrabMouse];
  972. }
  973. }
  974. - (void) mouseEntered:(NSEvent *)event
  975. {
  976. if (isAbsoluteEnabled && !isMouseGrabbed) {
  977. [self grabMouse];
  978. }
  979. }
  980. - (void) mouseMoved:(NSEvent *)event
  981. {
  982. [self handleMouseEvent:event];
  983. }
  984. - (void) mouseDown:(NSEvent *)event
  985. {
  986. [self handleMouseEvent:event button:INPUT_BUTTON_LEFT down:true];
  987. }
  988. - (void) rightMouseDown:(NSEvent *)event
  989. {
  990. [self handleMouseEvent:event button:INPUT_BUTTON_RIGHT down:true];
  991. }
  992. - (void) otherMouseDown:(NSEvent *)event
  993. {
  994. [self handleMouseEvent:event button:INPUT_BUTTON_MIDDLE down:true];
  995. }
  996. - (void) mouseDragged:(NSEvent *)event
  997. {
  998. [self handleMouseEvent:event];
  999. }
  1000. - (void) rightMouseDragged:(NSEvent *)event
  1001. {
  1002. [self handleMouseEvent:event];
  1003. }
  1004. - (void) otherMouseDragged:(NSEvent *)event
  1005. {
  1006. [self handleMouseEvent:event];
  1007. }
  1008. - (void) mouseUp:(NSEvent *)event
  1009. {
  1010. if (!isMouseGrabbed) {
  1011. [self grabMouse];
  1012. }
  1013. [self handleMouseEvent:event button:INPUT_BUTTON_LEFT down:false];
  1014. }
  1015. - (void) rightMouseUp:(NSEvent *)event
  1016. {
  1017. [self handleMouseEvent:event button:INPUT_BUTTON_RIGHT down:false];
  1018. }
  1019. - (void) otherMouseUp:(NSEvent *)event
  1020. {
  1021. [self handleMouseEvent:event button:INPUT_BUTTON_MIDDLE down:false];
  1022. }
  1023. - (void) grabMouse
  1024. {
  1025. COCOA_DEBUG("QemuCocoaView: grabMouse\n");
  1026. if (qemu_name)
  1027. [[self window] setTitle:[NSString stringWithFormat:@"QEMU %s - (Press " UC_CTRL_KEY " " UC_ALT_KEY " G to release Mouse)", qemu_name]];
  1028. else
  1029. [[self window] setTitle:@"QEMU - (Press " UC_CTRL_KEY " " UC_ALT_KEY " G to release Mouse)"];
  1030. [self hideCursor];
  1031. CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled);
  1032. isMouseGrabbed = TRUE; // while isMouseGrabbed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:]
  1033. }
  1034. - (void) ungrabMouse
  1035. {
  1036. COCOA_DEBUG("QemuCocoaView: ungrabMouse\n");
  1037. if (qemu_name)
  1038. [[self window] setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]];
  1039. else
  1040. [[self window] setTitle:@"QEMU"];
  1041. [self unhideCursor];
  1042. CGAssociateMouseAndMouseCursorPosition(TRUE);
  1043. isMouseGrabbed = FALSE;
  1044. [self raiseAllButtons];
  1045. }
  1046. - (void) notifyMouseModeChange {
  1047. bool tIsAbsoluteEnabled = bool_with_bql(^{
  1048. return qemu_input_is_absolute(dcl.con);
  1049. });
  1050. if (tIsAbsoluteEnabled == isAbsoluteEnabled) {
  1051. return;
  1052. }
  1053. isAbsoluteEnabled = tIsAbsoluteEnabled;
  1054. if (isMouseGrabbed) {
  1055. if (isAbsoluteEnabled) {
  1056. [self ungrabMouse];
  1057. } else {
  1058. CGAssociateMouseAndMouseCursorPosition(isAbsoluteEnabled);
  1059. }
  1060. }
  1061. }
  1062. - (BOOL) isMouseGrabbed {return isMouseGrabbed;}
  1063. - (QEMUScreen) gscreen {return screen;}
  1064. /*
  1065. * Makes the target think all down keys are being released.
  1066. * This prevents a stuck key problem, since we will not see
  1067. * key up events for those keys after we have lost focus.
  1068. */
  1069. - (void) raiseAllKeys
  1070. {
  1071. with_bql(^{
  1072. qkbd_state_lift_all_keys(kbd);
  1073. });
  1074. }
  1075. - (void) raiseAllButtons
  1076. {
  1077. with_bql(^{
  1078. qemu_input_queue_btn(dcl.con, INPUT_BUTTON_LEFT, false);
  1079. qemu_input_queue_btn(dcl.con, INPUT_BUTTON_RIGHT, false);
  1080. qemu_input_queue_btn(dcl.con, INPUT_BUTTON_MIDDLE, false);
  1081. });
  1082. }
  1083. @end
  1084. /*
  1085. ------------------------------------------------------
  1086. QemuCocoaAppController
  1087. ------------------------------------------------------
  1088. */
  1089. @interface QemuCocoaAppController : NSObject
  1090. <NSWindowDelegate, NSApplicationDelegate>
  1091. {
  1092. }
  1093. - (void)doToggleFullScreen:(id)sender;
  1094. - (void)showQEMUDoc:(id)sender;
  1095. - (void)zoomToFit:(id) sender;
  1096. - (void)displayConsole:(id)sender;
  1097. - (void)pauseQEMU:(id)sender;
  1098. - (void)resumeQEMU:(id)sender;
  1099. - (void)displayPause;
  1100. - (void)removePause;
  1101. - (void)restartQEMU:(id)sender;
  1102. - (void)powerDownQEMU:(id)sender;
  1103. - (void)ejectDeviceMedia:(id)sender;
  1104. - (void)changeDeviceMedia:(id)sender;
  1105. - (BOOL)verifyQuit;
  1106. - (void)openDocumentation:(NSString *)filename;
  1107. - (IBAction) do_about_menu_item: (id) sender;
  1108. - (void)adjustSpeed:(id)sender;
  1109. @end
  1110. @implementation QemuCocoaAppController
  1111. - (id) init
  1112. {
  1113. NSWindow *window;
  1114. COCOA_DEBUG("QemuCocoaAppController: init\n");
  1115. self = [super init];
  1116. if (self) {
  1117. // create a view and add it to the window
  1118. cocoaView = [[QemuCocoaView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 640.0, 480.0)];
  1119. if(!cocoaView) {
  1120. error_report("(cocoa) can't create a view");
  1121. exit(1);
  1122. }
  1123. // create a window
  1124. window = [[NSWindow alloc] initWithContentRect:[cocoaView frame]
  1125. styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskClosable
  1126. backing:NSBackingStoreBuffered defer:NO];
  1127. if(!window) {
  1128. error_report("(cocoa) can't create window");
  1129. exit(1);
  1130. }
  1131. [window setAcceptsMouseMovedEvents:YES];
  1132. [window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
  1133. [window setTitle:qemu_name ? [NSString stringWithFormat:@"QEMU %s", qemu_name] : @"QEMU"];
  1134. [window setContentView:cocoaView];
  1135. [window makeKeyAndOrderFront:self];
  1136. [window center];
  1137. [window setDelegate: self];
  1138. /* Used for displaying pause on the screen */
  1139. pauseLabel = [NSTextField new];
  1140. [pauseLabel setBezeled:YES];
  1141. [pauseLabel setDrawsBackground:YES];
  1142. [pauseLabel setBackgroundColor: [NSColor whiteColor]];
  1143. [pauseLabel setEditable:NO];
  1144. [pauseLabel setSelectable:NO];
  1145. [pauseLabel setStringValue: @"Paused"];
  1146. [pauseLabel setFont: [NSFont fontWithName: @"Helvetica" size: 90]];
  1147. [pauseLabel setTextColor: [NSColor blackColor]];
  1148. [pauseLabel sizeToFit];
  1149. }
  1150. return self;
  1151. }
  1152. - (void) dealloc
  1153. {
  1154. COCOA_DEBUG("QemuCocoaAppController: dealloc\n");
  1155. [cocoaView release];
  1156. [cbowner release];
  1157. cbowner = nil;
  1158. [super dealloc];
  1159. }
  1160. - (void)applicationDidFinishLaunching: (NSNotification *) note
  1161. {
  1162. COCOA_DEBUG("QemuCocoaAppController: applicationDidFinishLaunching\n");
  1163. allow_events = true;
  1164. }
  1165. - (void)applicationWillTerminate:(NSNotification *)aNotification
  1166. {
  1167. COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n");
  1168. with_bql(^{
  1169. shutdown_action = SHUTDOWN_ACTION_POWEROFF;
  1170. qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
  1171. });
  1172. /*
  1173. * Sleep here, because returning will cause OSX to kill us
  1174. * immediately; the QEMU main loop will handle the shutdown
  1175. * request and terminate the process.
  1176. */
  1177. [NSThread sleepForTimeInterval:INFINITY];
  1178. }
  1179. - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)theApplication
  1180. {
  1181. return YES;
  1182. }
  1183. - (NSApplicationTerminateReply)applicationShouldTerminate:
  1184. (NSApplication *)sender
  1185. {
  1186. COCOA_DEBUG("QemuCocoaAppController: applicationShouldTerminate\n");
  1187. return [self verifyQuit];
  1188. }
  1189. - (void)windowDidChangeScreen:(NSNotification *)notification
  1190. {
  1191. [cocoaView updateUIInfo];
  1192. }
  1193. - (void)windowDidEnterFullScreen:(NSNotification *)notification
  1194. {
  1195. [cocoaView grabMouse];
  1196. }
  1197. - (void)windowDidExitFullScreen:(NSNotification *)notification
  1198. {
  1199. [cocoaView resizeWindow];
  1200. [cocoaView ungrabMouse];
  1201. }
  1202. - (void)windowDidResize:(NSNotification *)notification
  1203. {
  1204. [cocoaView updateBounds];
  1205. [cocoaView updateUIInfo];
  1206. }
  1207. /* Called when the user clicks on a window's close button */
  1208. - (BOOL)windowShouldClose:(id)sender
  1209. {
  1210. COCOA_DEBUG("QemuCocoaAppController: windowShouldClose\n");
  1211. [NSApp terminate: sender];
  1212. /* If the user allows the application to quit then the call to
  1213. * NSApp terminate will never return. If we get here then the user
  1214. * cancelled the quit, so we should return NO to not permit the
  1215. * closing of this window.
  1216. */
  1217. return NO;
  1218. }
  1219. - (NSApplicationPresentationOptions) window:(NSWindow *)window
  1220. willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)proposedOptions;
  1221. {
  1222. return (proposedOptions & ~(NSApplicationPresentationAutoHideDock | NSApplicationPresentationAutoHideMenuBar)) |
  1223. NSApplicationPresentationHideDock | NSApplicationPresentationHideMenuBar;
  1224. }
  1225. /*
  1226. * Called when QEMU goes into the background. Note that
  1227. * [-NSWindowDelegate windowDidResignKey:] is used here instead of
  1228. * [-NSApplicationDelegate applicationWillResignActive:] because it cannot
  1229. * detect that the window loses focus when the deck is clicked on macOS 13.2.1.
  1230. */
  1231. - (void) windowDidResignKey: (NSNotification *)aNotification
  1232. {
  1233. COCOA_DEBUG("%s\n", __func__);
  1234. [cocoaView ungrabMouse];
  1235. [cocoaView raiseAllKeys];
  1236. }
  1237. /* We abstract the method called by the Enter Fullscreen menu item
  1238. * because Mac OS 10.7 and higher disables it. This is because of the
  1239. * menu item's old selector's name toggleFullScreen:
  1240. */
  1241. - (void) doToggleFullScreen:(id)sender
  1242. {
  1243. [[cocoaView window] toggleFullScreen:sender];
  1244. }
  1245. - (void) setFullGrab:(id)sender
  1246. {
  1247. COCOA_DEBUG("QemuCocoaAppController: setFullGrab\n");
  1248. [cocoaView setFullGrab:sender];
  1249. }
  1250. /* Tries to find then open the specified filename */
  1251. - (void) openDocumentation: (NSString *) filename
  1252. {
  1253. /* Where to look for local files */
  1254. NSString *path_array[] = {@"../share/doc/qemu/", @"../doc/qemu/", @"docs/"};
  1255. NSString *full_file_path;
  1256. NSURL *full_file_url;
  1257. /* iterate thru the possible paths until the file is found */
  1258. int index;
  1259. for (index = 0; index < ARRAY_SIZE(path_array); index++) {
  1260. full_file_path = [[NSBundle mainBundle] executablePath];
  1261. full_file_path = [full_file_path stringByDeletingLastPathComponent];
  1262. full_file_path = [NSString stringWithFormat: @"%@/%@%@", full_file_path,
  1263. path_array[index], filename];
  1264. full_file_url = [NSURL fileURLWithPath: full_file_path
  1265. isDirectory: false];
  1266. if ([[NSWorkspace sharedWorkspace] openURL: full_file_url] == YES) {
  1267. return;
  1268. }
  1269. }
  1270. /* If none of the paths opened a file */
  1271. NSBeep();
  1272. QEMU_Alert(@"Failed to open file");
  1273. }
  1274. - (void)showQEMUDoc:(id)sender
  1275. {
  1276. COCOA_DEBUG("QemuCocoaAppController: showQEMUDoc\n");
  1277. [self openDocumentation: @"index.html"];
  1278. }
  1279. /* Stretches video to fit host monitor size */
  1280. - (void)zoomToFit:(id) sender
  1281. {
  1282. NSWindowStyleMask styleMask = [[cocoaView window] styleMask] ^ NSWindowStyleMaskResizable;
  1283. [[cocoaView window] setStyleMask:styleMask];
  1284. [sender setState:styleMask & NSWindowStyleMaskResizable ? NSControlStateValueOn : NSControlStateValueOff];
  1285. [cocoaView resizeWindow];
  1286. }
  1287. - (void)toggleZoomInterpolation:(id) sender
  1288. {
  1289. if (zoom_interpolation == kCGInterpolationNone) {
  1290. zoom_interpolation = kCGInterpolationLow;
  1291. [sender setState: NSControlStateValueOn];
  1292. } else {
  1293. zoom_interpolation = kCGInterpolationNone;
  1294. [sender setState: NSControlStateValueOff];
  1295. }
  1296. }
  1297. /* Displays the console on the screen */
  1298. - (void)displayConsole:(id)sender
  1299. {
  1300. with_bql(^{
  1301. [cocoaView selectConsoleLocked:[sender tag]];
  1302. });
  1303. }
  1304. /* Pause the guest */
  1305. - (void)pauseQEMU:(id)sender
  1306. {
  1307. with_bql(^{
  1308. qmp_stop(NULL);
  1309. });
  1310. [sender setEnabled: NO];
  1311. [[[sender menu] itemWithTitle: @"Resume"] setEnabled: YES];
  1312. [self displayPause];
  1313. }
  1314. /* Resume running the guest operating system */
  1315. - (void)resumeQEMU:(id) sender
  1316. {
  1317. with_bql(^{
  1318. qmp_cont(NULL);
  1319. });
  1320. [sender setEnabled: NO];
  1321. [[[sender menu] itemWithTitle: @"Pause"] setEnabled: YES];
  1322. [self removePause];
  1323. }
  1324. /* Displays the word pause on the screen */
  1325. - (void)displayPause
  1326. {
  1327. /* Coordinates have to be calculated each time because the window can change its size */
  1328. int xCoord, yCoord, width, height;
  1329. xCoord = ([cocoaView frame].size.width - [pauseLabel frame].size.width)/2;
  1330. yCoord = [cocoaView frame].size.height - [pauseLabel frame].size.height - ([pauseLabel frame].size.height * .5);
  1331. width = [pauseLabel frame].size.width;
  1332. height = [pauseLabel frame].size.height;
  1333. [pauseLabel setFrame: NSMakeRect(xCoord, yCoord, width, height)];
  1334. [cocoaView addSubview: pauseLabel];
  1335. }
  1336. /* Removes the word pause from the screen */
  1337. - (void)removePause
  1338. {
  1339. [pauseLabel removeFromSuperview];
  1340. }
  1341. /* Restarts QEMU */
  1342. - (void)restartQEMU:(id)sender
  1343. {
  1344. with_bql(^{
  1345. qmp_system_reset(NULL);
  1346. });
  1347. }
  1348. /* Powers down QEMU */
  1349. - (void)powerDownQEMU:(id)sender
  1350. {
  1351. with_bql(^{
  1352. qmp_system_powerdown(NULL);
  1353. });
  1354. }
  1355. /* Ejects the media.
  1356. * Uses sender's tag to figure out the device to eject.
  1357. */
  1358. - (void)ejectDeviceMedia:(id)sender
  1359. {
  1360. NSString * drive;
  1361. drive = [sender representedObject];
  1362. if(drive == nil) {
  1363. NSBeep();
  1364. QEMU_Alert(@"Failed to find drive to eject!");
  1365. return;
  1366. }
  1367. __block Error *err = NULL;
  1368. with_bql(^{
  1369. qmp_eject([drive cStringUsingEncoding: NSASCIIStringEncoding],
  1370. NULL, false, false, &err);
  1371. });
  1372. handleAnyDeviceErrors(err);
  1373. }
  1374. /* Displays a dialog box asking the user to select an image file to load.
  1375. * Uses sender's represented object value to figure out which drive to use.
  1376. */
  1377. - (void)changeDeviceMedia:(id)sender
  1378. {
  1379. /* Find the drive name */
  1380. NSString * drive;
  1381. drive = [sender representedObject];
  1382. if(drive == nil) {
  1383. NSBeep();
  1384. QEMU_Alert(@"Could not find drive!");
  1385. return;
  1386. }
  1387. /* Display the file open dialog */
  1388. NSOpenPanel * openPanel;
  1389. openPanel = [NSOpenPanel openPanel];
  1390. [openPanel setCanChooseFiles: YES];
  1391. [openPanel setAllowsMultipleSelection: NO];
  1392. if([openPanel runModal] == NSModalResponseOK) {
  1393. NSString * file = [[[openPanel URLs] objectAtIndex: 0] path];
  1394. if(file == nil) {
  1395. NSBeep();
  1396. QEMU_Alert(@"Failed to convert URL to file path!");
  1397. return;
  1398. }
  1399. __block Error *err = NULL;
  1400. with_bql(^{
  1401. qmp_blockdev_change_medium([drive cStringUsingEncoding:
  1402. NSASCIIStringEncoding],
  1403. NULL,
  1404. [file cStringUsingEncoding:
  1405. NSASCIIStringEncoding],
  1406. "raw",
  1407. true, false,
  1408. false, 0,
  1409. false, 0,
  1410. &err);
  1411. });
  1412. handleAnyDeviceErrors(err);
  1413. }
  1414. }
  1415. /* Verifies if the user really wants to quit */
  1416. - (BOOL)verifyQuit
  1417. {
  1418. NSAlert *alert = [NSAlert new];
  1419. [alert autorelease];
  1420. [alert setMessageText: @"Are you sure you want to quit QEMU?"];
  1421. [alert addButtonWithTitle: @"Cancel"];
  1422. [alert addButtonWithTitle: @"Quit"];
  1423. if([alert runModal] == NSAlertSecondButtonReturn) {
  1424. return YES;
  1425. } else {
  1426. return NO;
  1427. }
  1428. }
  1429. /* The action method for the About menu item */
  1430. - (IBAction) do_about_menu_item: (id) sender
  1431. {
  1432. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  1433. char *icon_path_c = get_relocated_path(CONFIG_QEMU_ICONDIR "/hicolor/512x512/apps/qemu.png");
  1434. NSString *icon_path = [NSString stringWithUTF8String:icon_path_c];
  1435. g_free(icon_path_c);
  1436. NSImage *icon = [[NSImage alloc] initWithContentsOfFile:icon_path];
  1437. NSString *version = @"QEMU emulator version " QEMU_FULL_VERSION;
  1438. NSString *copyright = @QEMU_COPYRIGHT;
  1439. NSDictionary *options;
  1440. if (icon) {
  1441. options = @{
  1442. NSAboutPanelOptionApplicationIcon : icon,
  1443. NSAboutPanelOptionApplicationVersion : version,
  1444. @"Copyright" : copyright,
  1445. };
  1446. [icon release];
  1447. } else {
  1448. options = @{
  1449. NSAboutPanelOptionApplicationVersion : version,
  1450. @"Copyright" : copyright,
  1451. };
  1452. }
  1453. [NSApp orderFrontStandardAboutPanelWithOptions:options];
  1454. [pool release];
  1455. }
  1456. /* Used by the Speed menu items */
  1457. - (void)adjustSpeed:(id)sender
  1458. {
  1459. int throttle_pct; /* throttle percentage */
  1460. NSMenu *menu;
  1461. menu = [sender menu];
  1462. if (menu != nil)
  1463. {
  1464. /* Unselect the currently selected item */
  1465. for (NSMenuItem *item in [menu itemArray]) {
  1466. if (item.state == NSControlStateValueOn) {
  1467. [item setState: NSControlStateValueOff];
  1468. break;
  1469. }
  1470. }
  1471. }
  1472. // check the menu item
  1473. [sender setState: NSControlStateValueOn];
  1474. // get the throttle percentage
  1475. throttle_pct = [sender tag];
  1476. with_bql(^{
  1477. cpu_throttle_set(throttle_pct);
  1478. });
  1479. COCOA_DEBUG("cpu throttling at %d%c\n", cpu_throttle_get_percentage(), '%');
  1480. }
  1481. @end
  1482. @interface QemuApplication : NSApplication
  1483. @end
  1484. @implementation QemuApplication
  1485. - (void)sendEvent:(NSEvent *)event
  1486. {
  1487. COCOA_DEBUG("QemuApplication: sendEvent\n");
  1488. if (![cocoaView handleEvent:event]) {
  1489. [super sendEvent: event];
  1490. }
  1491. }
  1492. @end
  1493. static void create_initial_menus(void)
  1494. {
  1495. // Add menus
  1496. NSMenu *menu;
  1497. NSMenuItem *menuItem;
  1498. [NSApp setMainMenu:[[NSMenu alloc] init]];
  1499. [NSApp setServicesMenu:[[NSMenu alloc] initWithTitle:@"Services"]];
  1500. // Application menu
  1501. menu = [[NSMenu alloc] initWithTitle:@""];
  1502. [menu addItemWithTitle:@"About QEMU" action:@selector(do_about_menu_item:) keyEquivalent:@""]; // About QEMU
  1503. [menu addItem:[NSMenuItem separatorItem]]; //Separator
  1504. menuItem = [menu addItemWithTitle:@"Services" action:nil keyEquivalent:@""];
  1505. [menuItem setSubmenu:[NSApp servicesMenu]];
  1506. [menu addItem:[NSMenuItem separatorItem]];
  1507. [menu addItemWithTitle:@"Hide QEMU" action:@selector(hide:) keyEquivalent:@"h"]; //Hide QEMU
  1508. menuItem = (NSMenuItem *)[menu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; // Hide Others
  1509. [menuItem setKeyEquivalentModifierMask:(NSEventModifierFlagOption|NSEventModifierFlagCommand)];
  1510. [menu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; // Show All
  1511. [menu addItem:[NSMenuItem separatorItem]]; //Separator
  1512. [menu addItemWithTitle:@"Quit QEMU" action:@selector(terminate:) keyEquivalent:@"q"];
  1513. menuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" action:nil keyEquivalent:@""];
  1514. [menuItem setSubmenu:menu];
  1515. [[NSApp mainMenu] addItem:menuItem];
  1516. [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; // Workaround (this method is private since 10.4+)
  1517. // Machine menu
  1518. menu = [[NSMenu alloc] initWithTitle: @"Machine"];
  1519. [menu setAutoenablesItems: NO];
  1520. [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Pause" action: @selector(pauseQEMU:) keyEquivalent: @""] autorelease]];
  1521. menuItem = [[[NSMenuItem alloc] initWithTitle: @"Resume" action: @selector(resumeQEMU:) keyEquivalent: @""] autorelease];
  1522. [menu addItem: menuItem];
  1523. [menuItem setEnabled: NO];
  1524. [menu addItem: [NSMenuItem separatorItem]];
  1525. [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Reset" action: @selector(restartQEMU:) keyEquivalent: @""] autorelease]];
  1526. [menu addItem: [[[NSMenuItem alloc] initWithTitle: @"Power Down" action: @selector(powerDownQEMU:) keyEquivalent: @""] autorelease]];
  1527. menuItem = [[[NSMenuItem alloc] initWithTitle: @"Machine" action:nil keyEquivalent:@""] autorelease];
  1528. [menuItem setSubmenu:menu];
  1529. [[NSApp mainMenu] addItem:menuItem];
  1530. // View menu
  1531. menu = [[NSMenu alloc] initWithTitle:@"View"];
  1532. [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(doToggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen
  1533. menuItem = [[[NSMenuItem alloc] initWithTitle:@"Zoom To Fit" action:@selector(zoomToFit:) keyEquivalent:@""] autorelease];
  1534. [menuItem setState: [[cocoaView window] styleMask] & NSWindowStyleMaskResizable ? NSControlStateValueOn : NSControlStateValueOff];
  1535. [menu addItem: menuItem];
  1536. menuItem = [[[NSMenuItem alloc] initWithTitle:@"Zoom Interpolation" action:@selector(toggleZoomInterpolation:) keyEquivalent:@""] autorelease];
  1537. [menuItem setState: zoom_interpolation == kCGInterpolationLow ? NSControlStateValueOn : NSControlStateValueOff];
  1538. [menu addItem: menuItem];
  1539. menuItem = [[[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""] autorelease];
  1540. [menuItem setSubmenu:menu];
  1541. [[NSApp mainMenu] addItem:menuItem];
  1542. // Speed menu
  1543. menu = [[NSMenu alloc] initWithTitle:@"Speed"];
  1544. // Add the rest of the Speed menu items
  1545. int p, percentage, throttle_pct;
  1546. for (p = 10; p >= 0; p--)
  1547. {
  1548. percentage = p * 10 > 1 ? p * 10 : 1; // prevent a 0% menu item
  1549. menuItem = [[[NSMenuItem alloc]
  1550. initWithTitle: [NSString stringWithFormat: @"%d%%", percentage] action:@selector(adjustSpeed:) keyEquivalent:@""] autorelease];
  1551. if (percentage == 100) {
  1552. [menuItem setState: NSControlStateValueOn];
  1553. }
  1554. /* Calculate the throttle percentage */
  1555. throttle_pct = -1 * percentage + 100;
  1556. [menuItem setTag: throttle_pct];
  1557. [menu addItem: menuItem];
  1558. }
  1559. menuItem = [[[NSMenuItem alloc] initWithTitle:@"Speed" action:nil keyEquivalent:@""] autorelease];
  1560. [menuItem setSubmenu:menu];
  1561. [[NSApp mainMenu] addItem:menuItem];
  1562. // Window menu
  1563. menu = [[NSMenu alloc] initWithTitle:@"Window"];
  1564. [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease]]; // Miniaturize
  1565. menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease];
  1566. [menuItem setSubmenu:menu];
  1567. [[NSApp mainMenu] addItem:menuItem];
  1568. [NSApp setWindowsMenu:menu];
  1569. // Help menu
  1570. menu = [[NSMenu alloc] initWithTitle:@"Help"];
  1571. [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Documentation" action:@selector(showQEMUDoc:) keyEquivalent:@"?"] autorelease]]; // QEMU Help
  1572. menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease];
  1573. [menuItem setSubmenu:menu];
  1574. [[NSApp mainMenu] addItem:menuItem];
  1575. }
  1576. /* Returns a name for a given console */
  1577. static NSString * getConsoleName(QemuConsole * console)
  1578. {
  1579. g_autofree char *label = qemu_console_get_label(console);
  1580. return [NSString stringWithUTF8String:label];
  1581. }
  1582. /* Add an entry to the View menu for each console */
  1583. static void add_console_menu_entries(void)
  1584. {
  1585. NSMenu *menu;
  1586. NSMenuItem *menuItem;
  1587. int index = 0;
  1588. menu = [[[NSApp mainMenu] itemWithTitle:@"View"] submenu];
  1589. [menu addItem:[NSMenuItem separatorItem]];
  1590. while (qemu_console_lookup_by_index(index) != NULL) {
  1591. menuItem = [[[NSMenuItem alloc] initWithTitle: getConsoleName(qemu_console_lookup_by_index(index))
  1592. action: @selector(displayConsole:) keyEquivalent: @""] autorelease];
  1593. [menuItem setTag: index];
  1594. [menu addItem: menuItem];
  1595. index++;
  1596. }
  1597. }
  1598. /* Make menu items for all removable devices.
  1599. * Each device is given an 'Eject' and 'Change' menu item.
  1600. */
  1601. static void addRemovableDevicesMenuItems(void)
  1602. {
  1603. NSMenu *menu;
  1604. NSMenuItem *menuItem;
  1605. BlockInfoList *currentDevice, *pointerToFree;
  1606. NSString *deviceName;
  1607. currentDevice = qmp_query_block(NULL);
  1608. pointerToFree = currentDevice;
  1609. menu = [[[NSApp mainMenu] itemWithTitle:@"Machine"] submenu];
  1610. // Add a separator between related groups of menu items
  1611. [menu addItem:[NSMenuItem separatorItem]];
  1612. // Set the attributes to the "Removable Media" menu item
  1613. NSString *titleString = @"Removable Media";
  1614. NSMutableAttributedString *attString=[[NSMutableAttributedString alloc] initWithString:titleString];
  1615. NSColor *newColor = [NSColor blackColor];
  1616. NSFontManager *fontManager = [NSFontManager sharedFontManager];
  1617. NSFont *font = [fontManager fontWithFamily:@"Helvetica"
  1618. traits:NSBoldFontMask|NSItalicFontMask
  1619. weight:0
  1620. size:14];
  1621. [attString addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, [titleString length])];
  1622. [attString addAttribute:NSForegroundColorAttributeName value:newColor range:NSMakeRange(0, [titleString length])];
  1623. [attString addAttribute:NSUnderlineStyleAttributeName value:[NSNumber numberWithInt: 1] range:NSMakeRange(0, [titleString length])];
  1624. // Add the "Removable Media" menu item
  1625. menuItem = [NSMenuItem new];
  1626. [menuItem setAttributedTitle: attString];
  1627. [menuItem setEnabled: NO];
  1628. [menu addItem: menuItem];
  1629. /* Loop through all the block devices in the emulator */
  1630. while (currentDevice) {
  1631. deviceName = [[NSString stringWithFormat: @"%s", currentDevice->value->device] retain];
  1632. if(currentDevice->value->removable) {
  1633. menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Change %s...", currentDevice->value->device]
  1634. action: @selector(changeDeviceMedia:)
  1635. keyEquivalent: @""];
  1636. [menu addItem: menuItem];
  1637. [menuItem setRepresentedObject: deviceName];
  1638. [menuItem autorelease];
  1639. menuItem = [[NSMenuItem alloc] initWithTitle: [NSString stringWithFormat: @"Eject %s", currentDevice->value->device]
  1640. action: @selector(ejectDeviceMedia:)
  1641. keyEquivalent: @""];
  1642. [menu addItem: menuItem];
  1643. [menuItem setRepresentedObject: deviceName];
  1644. [menuItem autorelease];
  1645. }
  1646. currentDevice = currentDevice->next;
  1647. }
  1648. qapi_free_BlockInfoList(pointerToFree);
  1649. }
  1650. static void cocoa_mouse_mode_change_notify(Notifier *notifier, void *data)
  1651. {
  1652. dispatch_async(dispatch_get_main_queue(), ^{
  1653. [cocoaView notifyMouseModeChange];
  1654. });
  1655. }
  1656. static Notifier mouse_mode_change_notifier = {
  1657. .notify = cocoa_mouse_mode_change_notify
  1658. };
  1659. @interface QemuCocoaPasteboardTypeOwner : NSObject<NSPasteboardTypeOwner>
  1660. @end
  1661. @implementation QemuCocoaPasteboardTypeOwner
  1662. - (void)pasteboard:(NSPasteboard *)sender provideDataForType:(NSPasteboardType)type
  1663. {
  1664. if (type != NSPasteboardTypeString) {
  1665. return;
  1666. }
  1667. with_bql(^{
  1668. QemuClipboardInfo *info = qemu_clipboard_info_ref(cbinfo);
  1669. qemu_event_reset(&cbevent);
  1670. qemu_clipboard_request(info, QEMU_CLIPBOARD_TYPE_TEXT);
  1671. while (info == cbinfo &&
  1672. info->types[QEMU_CLIPBOARD_TYPE_TEXT].available &&
  1673. info->types[QEMU_CLIPBOARD_TYPE_TEXT].data == NULL) {
  1674. bql_unlock();
  1675. qemu_event_wait(&cbevent);
  1676. bql_lock();
  1677. }
  1678. if (info == cbinfo) {
  1679. NSData *data = [[NSData alloc] initWithBytes:info->types[QEMU_CLIPBOARD_TYPE_TEXT].data
  1680. length:info->types[QEMU_CLIPBOARD_TYPE_TEXT].size];
  1681. [sender setData:data forType:NSPasteboardTypeString];
  1682. [data release];
  1683. }
  1684. qemu_clipboard_info_unref(info);
  1685. });
  1686. }
  1687. @end
  1688. static void cocoa_clipboard_notify(Notifier *notifier, void *data);
  1689. static void cocoa_clipboard_request(QemuClipboardInfo *info,
  1690. QemuClipboardType type);
  1691. static QemuClipboardPeer cbpeer = {
  1692. .name = "cocoa",
  1693. .notifier = { .notify = cocoa_clipboard_notify },
  1694. .request = cocoa_clipboard_request
  1695. };
  1696. static void cocoa_clipboard_update_info(QemuClipboardInfo *info)
  1697. {
  1698. if (info->owner == &cbpeer || info->selection != QEMU_CLIPBOARD_SELECTION_CLIPBOARD) {
  1699. return;
  1700. }
  1701. if (info != cbinfo) {
  1702. NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
  1703. qemu_clipboard_info_unref(cbinfo);
  1704. cbinfo = qemu_clipboard_info_ref(info);
  1705. cbchangecount = [[NSPasteboard generalPasteboard] declareTypes:@[NSPasteboardTypeString] owner:cbowner];
  1706. [pool release];
  1707. }
  1708. qemu_event_set(&cbevent);
  1709. }
  1710. static void cocoa_clipboard_notify(Notifier *notifier, void *data)
  1711. {
  1712. QemuClipboardNotify *notify = data;
  1713. switch (notify->type) {
  1714. case QEMU_CLIPBOARD_UPDATE_INFO:
  1715. cocoa_clipboard_update_info(notify->info);
  1716. return;
  1717. case QEMU_CLIPBOARD_RESET_SERIAL:
  1718. /* ignore */
  1719. return;
  1720. }
  1721. }
  1722. static void cocoa_clipboard_request(QemuClipboardInfo *info,
  1723. QemuClipboardType type)
  1724. {
  1725. NSAutoreleasePool *pool;
  1726. NSData *text;
  1727. switch (type) {
  1728. case QEMU_CLIPBOARD_TYPE_TEXT:
  1729. pool = [[NSAutoreleasePool alloc] init];
  1730. text = [[NSPasteboard generalPasteboard] dataForType:NSPasteboardTypeString];
  1731. if (text) {
  1732. qemu_clipboard_set_data(&cbpeer, info, type,
  1733. [text length], [text bytes], true);
  1734. }
  1735. [pool release];
  1736. break;
  1737. default:
  1738. break;
  1739. }
  1740. }
  1741. static int cocoa_main(void)
  1742. {
  1743. COCOA_DEBUG("Main thread: entering OSX run loop\n");
  1744. [NSApp run];
  1745. COCOA_DEBUG("Main thread: left OSX run loop, which should never happen\n");
  1746. abort();
  1747. }
  1748. #pragma mark qemu
  1749. static void cocoa_update(DisplayChangeListener *dcl,
  1750. int x, int y, int w, int h)
  1751. {
  1752. COCOA_DEBUG("qemu_cocoa: cocoa_update\n");
  1753. dispatch_async(dispatch_get_main_queue(), ^{
  1754. NSRect rect = NSMakeRect(x, [cocoaView gscreen].height - y - h, w, h);
  1755. [cocoaView setNeedsDisplayInRect:rect];
  1756. });
  1757. }
  1758. static void cocoa_switch(DisplayChangeListener *dcl,
  1759. DisplaySurface *surface)
  1760. {
  1761. pixman_image_t *image = surface->image;
  1762. COCOA_DEBUG("qemu_cocoa: cocoa_switch\n");
  1763. // The DisplaySurface will be freed as soon as this callback returns.
  1764. // We take a reference to the underlying pixman image here so it does
  1765. // not disappear from under our feet; the switchSurface method will
  1766. // deref the old image when it is done with it.
  1767. pixman_image_ref(image);
  1768. dispatch_async(dispatch_get_main_queue(), ^{
  1769. [cocoaView switchSurface:image];
  1770. });
  1771. }
  1772. static void cocoa_refresh(DisplayChangeListener *dcl)
  1773. {
  1774. NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
  1775. COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n");
  1776. graphic_hw_update(dcl->con);
  1777. if (cbchangecount != [[NSPasteboard generalPasteboard] changeCount]) {
  1778. qemu_clipboard_info_unref(cbinfo);
  1779. cbinfo = qemu_clipboard_info_new(&cbpeer, QEMU_CLIPBOARD_SELECTION_CLIPBOARD);
  1780. if ([[NSPasteboard generalPasteboard] availableTypeFromArray:@[NSPasteboardTypeString]]) {
  1781. cbinfo->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
  1782. }
  1783. qemu_clipboard_update(cbinfo);
  1784. cbchangecount = [[NSPasteboard generalPasteboard] changeCount];
  1785. qemu_event_set(&cbevent);
  1786. }
  1787. [pool release];
  1788. }
  1789. static void cocoa_mouse_set(DisplayChangeListener *dcl, int x, int y, bool on)
  1790. {
  1791. dispatch_async(dispatch_get_main_queue(), ^{
  1792. [cocoaView setMouseX:x y:y on:on];
  1793. });
  1794. }
  1795. static void cocoa_cursor_define(DisplayChangeListener *dcl, QEMUCursor *cursor)
  1796. {
  1797. dispatch_async(dispatch_get_main_queue(), ^{
  1798. BQL_LOCK_GUARD();
  1799. [cocoaView setCursor:qemu_console_get_cursor(dcl->con)];
  1800. });
  1801. }
  1802. static void cocoa_display_init(DisplayState *ds, DisplayOptions *opts)
  1803. {
  1804. NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
  1805. COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n");
  1806. // Pull this console process up to being a fully-fledged graphical
  1807. // app with a menubar and Dock icon
  1808. ProcessSerialNumber psn = { 0, kCurrentProcess };
  1809. TransformProcessType(&psn, kProcessTransformToForegroundApplication);
  1810. [QemuApplication sharedApplication];
  1811. // Create an Application controller
  1812. QemuCocoaAppController *controller = [[QemuCocoaAppController alloc] init];
  1813. [NSApp setDelegate:controller];
  1814. /* if fullscreen mode is to be used */
  1815. if (opts->has_full_screen && opts->full_screen) {
  1816. [[cocoaView window] toggleFullScreen: nil];
  1817. }
  1818. if (opts->u.cocoa.has_full_grab && opts->u.cocoa.full_grab) {
  1819. [controller setFullGrab: nil];
  1820. }
  1821. if (opts->has_show_cursor && opts->show_cursor) {
  1822. cursor_hide = 0;
  1823. }
  1824. if (opts->u.cocoa.has_swap_opt_cmd) {
  1825. swap_opt_cmd = opts->u.cocoa.swap_opt_cmd;
  1826. }
  1827. if (opts->u.cocoa.has_left_command_key && !opts->u.cocoa.left_command_key) {
  1828. left_command_key_enabled = 0;
  1829. }
  1830. if (opts->u.cocoa.has_zoom_to_fit && opts->u.cocoa.zoom_to_fit) {
  1831. [cocoaView window].styleMask |= NSWindowStyleMaskResizable;
  1832. }
  1833. if (opts->u.cocoa.has_zoom_interpolation && opts->u.cocoa.zoom_interpolation) {
  1834. zoom_interpolation = kCGInterpolationLow;
  1835. }
  1836. create_initial_menus();
  1837. /*
  1838. * Create the menu entries which depend on QEMU state (for consoles
  1839. * and removable devices). These make calls back into QEMU functions,
  1840. * which is OK because at this point we know that the second thread
  1841. * holds the BQL and is synchronously waiting for us to
  1842. * finish.
  1843. */
  1844. add_console_menu_entries();
  1845. addRemovableDevicesMenuItems();
  1846. dcl.con = qemu_console_lookup_default();
  1847. kbd = qkbd_state_init(dcl.con);
  1848. // register vga output callbacks
  1849. register_displaychangelistener(&dcl);
  1850. qemu_add_mouse_mode_change_notifier(&mouse_mode_change_notifier);
  1851. [cocoaView notifyMouseModeChange];
  1852. [cocoaView updateUIInfo];
  1853. qemu_event_init(&cbevent, false);
  1854. cbowner = [[QemuCocoaPasteboardTypeOwner alloc] init];
  1855. qemu_clipboard_peer_register(&cbpeer);
  1856. [pool release];
  1857. /*
  1858. * The Cocoa UI will run the NSApplication runloop on the main thread
  1859. * rather than the default Core Foundation one.
  1860. */
  1861. qemu_main = cocoa_main;
  1862. }
  1863. static QemuDisplay qemu_display_cocoa = {
  1864. .type = DISPLAY_TYPE_COCOA,
  1865. .init = cocoa_display_init,
  1866. };
  1867. static void register_cocoa(void)
  1868. {
  1869. qemu_display_register(&qemu_display_cocoa);
  1870. }
  1871. type_init(register_cocoa);