cocoa.m 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982
  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. #import <Cocoa/Cocoa.h>
  25. #include "qemu-common.h"
  26. #include "console.h"
  27. #include "sysemu.h"
  28. //#define DEBUG
  29. #ifdef DEBUG
  30. #define COCOA_DEBUG(...) { (void) fprintf (stdout, __VA_ARGS__); }
  31. #else
  32. #define COCOA_DEBUG(...) ((void) 0)
  33. #endif
  34. #define cgrect(nsrect) (*(CGRect *)&(nsrect))
  35. #define COCOA_MOUSE_EVENT \
  36. if (isTabletEnabled) { \
  37. kbd_mouse_event((int)(p.x * 0x7FFF / (screen.width - 1)), (int)((screen.height - p.y) * 0x7FFF / (screen.height - 1)), 0, buttons); \
  38. } else if (isMouseGrabed) { \
  39. kbd_mouse_event((int)[event deltaX], (int)[event deltaY], 0, buttons); \
  40. } else { \
  41. [NSApp sendEvent:event]; \
  42. }
  43. typedef struct {
  44. int width;
  45. int height;
  46. int bitsPerComponent;
  47. int bitsPerPixel;
  48. } QEMUScreen;
  49. int qemu_main(int argc, char **argv); // main defined in qemu/vl.c
  50. NSWindow *normalWindow;
  51. id cocoaView;
  52. static DisplayChangeListener *dcl;
  53. int gArgc;
  54. char **gArgv;
  55. // keymap conversion
  56. int keymap[] =
  57. {
  58. // SdlI macI macH SdlH 104xtH 104xtC sdl
  59. 30, // 0 0x00 0x1e A QZ_a
  60. 31, // 1 0x01 0x1f S QZ_s
  61. 32, // 2 0x02 0x20 D QZ_d
  62. 33, // 3 0x03 0x21 F QZ_f
  63. 35, // 4 0x04 0x23 H QZ_h
  64. 34, // 5 0x05 0x22 G QZ_g
  65. 44, // 6 0x06 0x2c Z QZ_z
  66. 45, // 7 0x07 0x2d X QZ_x
  67. 46, // 8 0x08 0x2e C QZ_c
  68. 47, // 9 0x09 0x2f V QZ_v
  69. 0, // 10 0x0A Undefined
  70. 48, // 11 0x0B 0x30 B QZ_b
  71. 16, // 12 0x0C 0x10 Q QZ_q
  72. 17, // 13 0x0D 0x11 W QZ_w
  73. 18, // 14 0x0E 0x12 E QZ_e
  74. 19, // 15 0x0F 0x13 R QZ_r
  75. 21, // 16 0x10 0x15 Y QZ_y
  76. 20, // 17 0x11 0x14 T QZ_t
  77. 2, // 18 0x12 0x02 1 QZ_1
  78. 3, // 19 0x13 0x03 2 QZ_2
  79. 4, // 20 0x14 0x04 3 QZ_3
  80. 5, // 21 0x15 0x05 4 QZ_4
  81. 7, // 22 0x16 0x07 6 QZ_6
  82. 6, // 23 0x17 0x06 5 QZ_5
  83. 13, // 24 0x18 0x0d = QZ_EQUALS
  84. 10, // 25 0x19 0x0a 9 QZ_9
  85. 8, // 26 0x1A 0x08 7 QZ_7
  86. 12, // 27 0x1B 0x0c - QZ_MINUS
  87. 9, // 28 0x1C 0x09 8 QZ_8
  88. 11, // 29 0x1D 0x0b 0 QZ_0
  89. 27, // 30 0x1E 0x1b ] QZ_RIGHTBRACKET
  90. 24, // 31 0x1F 0x18 O QZ_o
  91. 22, // 32 0x20 0x16 U QZ_u
  92. 26, // 33 0x21 0x1a [ QZ_LEFTBRACKET
  93. 23, // 34 0x22 0x17 I QZ_i
  94. 25, // 35 0x23 0x19 P QZ_p
  95. 28, // 36 0x24 0x1c ENTER QZ_RETURN
  96. 38, // 37 0x25 0x26 L QZ_l
  97. 36, // 38 0x26 0x24 J QZ_j
  98. 40, // 39 0x27 0x28 ' QZ_QUOTE
  99. 37, // 40 0x28 0x25 K QZ_k
  100. 39, // 41 0x29 0x27 ; QZ_SEMICOLON
  101. 43, // 42 0x2A 0x2b \ QZ_BACKSLASH
  102. 51, // 43 0x2B 0x33 , QZ_COMMA
  103. 53, // 44 0x2C 0x35 / QZ_SLASH
  104. 49, // 45 0x2D 0x31 N QZ_n
  105. 50, // 46 0x2E 0x32 M QZ_m
  106. 52, // 47 0x2F 0x34 . QZ_PERIOD
  107. 15, // 48 0x30 0x0f TAB QZ_TAB
  108. 57, // 49 0x31 0x39 SPACE QZ_SPACE
  109. 41, // 50 0x32 0x29 ` QZ_BACKQUOTE
  110. 14, // 51 0x33 0x0e BKSP QZ_BACKSPACE
  111. 0, // 52 0x34 Undefined
  112. 1, // 53 0x35 0x01 ESC QZ_ESCAPE
  113. 0, // 54 0x36 QZ_RMETA
  114. 0, // 55 0x37 QZ_LMETA
  115. 42, // 56 0x38 0x2a L SHFT QZ_LSHIFT
  116. 58, // 57 0x39 0x3a CAPS QZ_CAPSLOCK
  117. 56, // 58 0x3A 0x38 L ALT QZ_LALT
  118. 29, // 59 0x3B 0x1d L CTRL QZ_LCTRL
  119. 54, // 60 0x3C 0x36 R SHFT QZ_RSHIFT
  120. 184,// 61 0x3D 0xb8 E0,38 R ALT QZ_RALT
  121. 157,// 62 0x3E 0x9d E0,1D R CTRL QZ_RCTRL
  122. 0, // 63 0x3F Undefined
  123. 0, // 64 0x40 Undefined
  124. 0, // 65 0x41 Undefined
  125. 0, // 66 0x42 Undefined
  126. 55, // 67 0x43 0x37 KP * QZ_KP_MULTIPLY
  127. 0, // 68 0x44 Undefined
  128. 78, // 69 0x45 0x4e KP + QZ_KP_PLUS
  129. 0, // 70 0x46 Undefined
  130. 69, // 71 0x47 0x45 NUM QZ_NUMLOCK
  131. 0, // 72 0x48 Undefined
  132. 0, // 73 0x49 Undefined
  133. 0, // 74 0x4A Undefined
  134. 181,// 75 0x4B 0xb5 E0,35 KP / QZ_KP_DIVIDE
  135. 152,// 76 0x4C 0x9c E0,1C KP EN QZ_KP_ENTER
  136. 0, // 77 0x4D undefined
  137. 74, // 78 0x4E 0x4a KP - QZ_KP_MINUS
  138. 0, // 79 0x4F Undefined
  139. 0, // 80 0x50 Undefined
  140. 0, // 81 0x51 QZ_KP_EQUALS
  141. 82, // 82 0x52 0x52 KP 0 QZ_KP0
  142. 79, // 83 0x53 0x4f KP 1 QZ_KP1
  143. 80, // 84 0x54 0x50 KP 2 QZ_KP2
  144. 81, // 85 0x55 0x51 KP 3 QZ_KP3
  145. 75, // 86 0x56 0x4b KP 4 QZ_KP4
  146. 76, // 87 0x57 0x4c KP 5 QZ_KP5
  147. 77, // 88 0x58 0x4d KP 6 QZ_KP6
  148. 71, // 89 0x59 0x47 KP 7 QZ_KP7
  149. 0, // 90 0x5A Undefined
  150. 72, // 91 0x5B 0x48 KP 8 QZ_KP8
  151. 73, // 92 0x5C 0x49 KP 9 QZ_KP9
  152. 0, // 93 0x5D Undefined
  153. 0, // 94 0x5E Undefined
  154. 0, // 95 0x5F Undefined
  155. 63, // 96 0x60 0x3f F5 QZ_F5
  156. 64, // 97 0x61 0x40 F6 QZ_F6
  157. 65, // 98 0x62 0x41 F7 QZ_F7
  158. 61, // 99 0x63 0x3d F3 QZ_F3
  159. 66, // 100 0x64 0x42 F8 QZ_F8
  160. 67, // 101 0x65 0x43 F9 QZ_F9
  161. 0, // 102 0x66 Undefined
  162. 87, // 103 0x67 0x57 F11 QZ_F11
  163. 0, // 104 0x68 Undefined
  164. 183,// 105 0x69 0xb7 QZ_PRINT
  165. 0, // 106 0x6A Undefined
  166. 70, // 107 0x6B 0x46 SCROLL QZ_SCROLLOCK
  167. 0, // 108 0x6C Undefined
  168. 68, // 109 0x6D 0x44 F10 QZ_F10
  169. 0, // 110 0x6E Undefined
  170. 88, // 111 0x6F 0x58 F12 QZ_F12
  171. 0, // 112 0x70 Undefined
  172. 110,// 113 0x71 0x0 QZ_PAUSE
  173. 210,// 114 0x72 0xd2 E0,52 INSERT QZ_INSERT
  174. 199,// 115 0x73 0xc7 E0,47 HOME QZ_HOME
  175. 201,// 116 0x74 0xc9 E0,49 PG UP QZ_PAGEUP
  176. 211,// 117 0x75 0xd3 E0,53 DELETE QZ_DELETE
  177. 62, // 118 0x76 0x3e F4 QZ_F4
  178. 207,// 119 0x77 0xcf E0,4f END QZ_END
  179. 60, // 120 0x78 0x3c F2 QZ_F2
  180. 209,// 121 0x79 0xd1 E0,51 PG DN QZ_PAGEDOWN
  181. 59, // 122 0x7A 0x3b F1 QZ_F1
  182. 203,// 123 0x7B 0xcb e0,4B L ARROW QZ_LEFT
  183. 205,// 124 0x7C 0xcd e0,4D R ARROW QZ_RIGHT
  184. 208,// 125 0x7D 0xd0 E0,50 D ARROW QZ_DOWN
  185. 200,// 126 0x7E 0xc8 E0,48 U ARROW QZ_UP
  186. /* completed according to http://www.libsdl.org/cgi/cvsweb.cgi/SDL12/src/video/quartz/SDL_QuartzKeys.h?rev=1.6&content-type=text/x-cvsweb-markup */
  187. /* Aditional 104 Key XP-Keyboard Scancodes from http://www.computer-engineering.org/ps2keyboard/scancodes1.html */
  188. /*
  189. 219 // 0xdb e0,5b L GUI
  190. 220 // 0xdc e0,5c R GUI
  191. 221 // 0xdd e0,5d APPS
  192. // E0,2A,E0,37 PRNT SCRN
  193. // E1,1D,45,E1,9D,C5 PAUSE
  194. 83 // 0x53 0x53 KP .
  195. // ACPI Scan Codes
  196. 222 // 0xde E0, 5E Power
  197. 223 // 0xdf E0, 5F Sleep
  198. 227 // 0xe3 E0, 63 Wake
  199. // Windows Multimedia Scan Codes
  200. 153 // 0x99 E0, 19 Next Track
  201. 144 // 0x90 E0, 10 Previous Track
  202. 164 // 0xa4 E0, 24 Stop
  203. 162 // 0xa2 E0, 22 Play/Pause
  204. 160 // 0xa0 E0, 20 Mute
  205. 176 // 0xb0 E0, 30 Volume Up
  206. 174 // 0xae E0, 2E Volume Down
  207. 237 // 0xed E0, 6D Media Select
  208. 236 // 0xec E0, 6C E-Mail
  209. 161 // 0xa1 E0, 21 Calculator
  210. 235 // 0xeb E0, 6B My Computer
  211. 229 // 0xe5 E0, 65 WWW Search
  212. 178 // 0xb2 E0, 32 WWW Home
  213. 234 // 0xea E0, 6A WWW Back
  214. 233 // 0xe9 E0, 69 WWW Forward
  215. 232 // 0xe8 E0, 68 WWW Stop
  216. 231 // 0xe7 E0, 67 WWW Refresh
  217. 230 // 0xe6 E0, 66 WWW Favorites
  218. */
  219. };
  220. int cocoa_keycode_to_qemu(int keycode)
  221. {
  222. if((sizeof(keymap)/sizeof(int)) <= keycode)
  223. {
  224. printf("(cocoa) warning unknow keycode 0x%x\n", keycode);
  225. return 0;
  226. }
  227. return keymap[keycode];
  228. }
  229. /*
  230. ------------------------------------------------------
  231. QemuCocoaView
  232. ------------------------------------------------------
  233. */
  234. @interface QemuCocoaView : NSView
  235. {
  236. QEMUScreen screen;
  237. NSWindow *fullScreenWindow;
  238. float cx,cy,cw,ch,cdx,cdy;
  239. CGDataProviderRef dataProviderRef;
  240. int modifiers_state[256];
  241. BOOL isMouseGrabed;
  242. BOOL isFullscreen;
  243. BOOL isAbsoluteEnabled;
  244. BOOL isTabletEnabled;
  245. }
  246. - (void) resizeContentToWidth:(int)w height:(int)h displayState:(DisplayState *)ds;
  247. - (void) grabMouse;
  248. - (void) ungrabMouse;
  249. - (void) toggleFullScreen:(id)sender;
  250. - (void) handleEvent:(NSEvent *)event;
  251. - (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled;
  252. - (BOOL) isMouseGrabed;
  253. - (BOOL) isAbsoluteEnabled;
  254. - (float) cdx;
  255. - (float) cdy;
  256. - (QEMUScreen) gscreen;
  257. @end
  258. @implementation QemuCocoaView
  259. - (id)initWithFrame:(NSRect)frameRect
  260. {
  261. COCOA_DEBUG("QemuCocoaView: initWithFrame\n");
  262. self = [super initWithFrame:frameRect];
  263. if (self) {
  264. screen.bitsPerComponent = 8;
  265. screen.bitsPerPixel = 32;
  266. screen.width = frameRect.size.width;
  267. screen.height = frameRect.size.height;
  268. }
  269. return self;
  270. }
  271. - (void) dealloc
  272. {
  273. COCOA_DEBUG("QemuCocoaView: dealloc\n");
  274. if (dataProviderRef)
  275. CGDataProviderRelease(dataProviderRef);
  276. [super dealloc];
  277. }
  278. - (void) drawRect:(NSRect) rect
  279. {
  280. COCOA_DEBUG("QemuCocoaView: drawRect\n");
  281. // get CoreGraphic context
  282. CGContextRef viewContextRef = [[NSGraphicsContext currentContext] graphicsPort];
  283. CGContextSetInterpolationQuality (viewContextRef, kCGInterpolationNone);
  284. CGContextSetShouldAntialias (viewContextRef, NO);
  285. // draw screen bitmap directly to Core Graphics context
  286. if (dataProviderRef) {
  287. CGImageRef imageRef = CGImageCreate(
  288. screen.width, //width
  289. screen.height, //height
  290. screen.bitsPerComponent, //bitsPerComponent
  291. screen.bitsPerPixel, //bitsPerPixel
  292. (screen.width * (screen.bitsPerComponent/2)), //bytesPerRow
  293. #if __LITTLE_ENDIAN__
  294. CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB), //colorspace for OS X >= 10.4
  295. kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst,
  296. #else
  297. CGColorSpaceCreateDeviceRGB(), //colorspace for OS X < 10.4 (actually ppc)
  298. kCGImageAlphaNoneSkipFirst, //bitmapInfo
  299. #endif
  300. dataProviderRef, //provider
  301. NULL, //decode
  302. 0, //interpolate
  303. kCGRenderingIntentDefault //intent
  304. );
  305. // test if host support "CGImageCreateWithImageInRect" at compiletime
  306. #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4)
  307. if (CGImageCreateWithImageInRect == NULL) { // test if "CGImageCreateWithImageInRect" is supported on host at runtime
  308. #endif
  309. // compatibility drawing code (draws everything) (OS X < 10.4)
  310. CGContextDrawImage (viewContextRef, CGRectMake(0, 0, [self bounds].size.width, [self bounds].size.height), imageRef);
  311. #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4)
  312. } else {
  313. // selective drawing code (draws only dirty rectangles) (OS X >= 10.4)
  314. const NSRect *rectList;
  315. int rectCount;
  316. int i;
  317. CGImageRef clipImageRef;
  318. CGRect clipRect;
  319. [self getRectsBeingDrawn:&rectList count:&rectCount];
  320. for (i = 0; i < rectCount; i++) {
  321. clipRect.origin.x = rectList[i].origin.x / cdx;
  322. clipRect.origin.y = (float)screen.height - (rectList[i].origin.y + rectList[i].size.height) / cdy;
  323. clipRect.size.width = rectList[i].size.width / cdx;
  324. clipRect.size.height = rectList[i].size.height / cdy;
  325. clipImageRef = CGImageCreateWithImageInRect(
  326. imageRef,
  327. clipRect
  328. );
  329. CGContextDrawImage (viewContextRef, cgrect(rectList[i]), clipImageRef);
  330. CGImageRelease (clipImageRef);
  331. }
  332. }
  333. #endif
  334. CGImageRelease (imageRef);
  335. }
  336. }
  337. - (void) setContentDimensions
  338. {
  339. COCOA_DEBUG("QemuCocoaView: setContentDimensions\n");
  340. if (isFullscreen) {
  341. cdx = [[NSScreen mainScreen] frame].size.width / (float)screen.width;
  342. cdy = [[NSScreen mainScreen] frame].size.height / (float)screen.height;
  343. cw = screen.width * cdx;
  344. ch = screen.height * cdy;
  345. cx = ([[NSScreen mainScreen] frame].size.width - cw) / 2.0;
  346. cy = ([[NSScreen mainScreen] frame].size.height - ch) / 2.0;
  347. } else {
  348. cx = 0;
  349. cy = 0;
  350. cw = screen.width;
  351. ch = screen.height;
  352. cdx = 1.0;
  353. cdy = 1.0;
  354. }
  355. }
  356. - (void) resizeContentToWidth:(int)w height:(int)h displayState:(DisplayState *)ds
  357. {
  358. COCOA_DEBUG("QemuCocoaView: resizeContent\n");
  359. // update screenBuffer
  360. if (dataProviderRef)
  361. CGDataProviderRelease(dataProviderRef);
  362. //sync host window color space with guests
  363. screen.bitsPerPixel = ds_get_bits_per_pixel(ds);
  364. screen.bitsPerComponent = ds_get_bytes_per_pixel(ds) * 2;
  365. dataProviderRef = CGDataProviderCreateWithData(NULL, ds_get_data(ds), w * 4 * h, NULL);
  366. // update windows
  367. if (isFullscreen) {
  368. [[fullScreenWindow contentView] setFrame:[[NSScreen mainScreen] frame]];
  369. [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + screen.height, w, h + [normalWindow frame].size.height - screen.height) display:NO animate:NO];
  370. } else {
  371. if (qemu_name)
  372. [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]];
  373. [normalWindow setFrame:NSMakeRect([normalWindow frame].origin.x, [normalWindow frame].origin.y - h + screen.height, w, h + [normalWindow frame].size.height - screen.height) display:YES animate:YES];
  374. }
  375. screen.width = w;
  376. screen.height = h;
  377. [normalWindow center];
  378. [self setContentDimensions];
  379. [self setFrame:NSMakeRect(cx, cy, cw, ch)];
  380. }
  381. - (void) toggleFullScreen:(id)sender
  382. {
  383. COCOA_DEBUG("QemuCocoaView: toggleFullScreen\n");
  384. if (isFullscreen) { // switch from fullscreen to desktop
  385. isFullscreen = FALSE;
  386. [self ungrabMouse];
  387. [self setContentDimensions];
  388. // test if host support "enterFullScreenMode:withOptions" at compiletime
  389. #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4)
  390. if ([NSView respondsToSelector:@selector(exitFullScreenModeWithOptions:)]) { // test if "exitFullScreenModeWithOptions" is supported on host at runtime
  391. [self exitFullScreenModeWithOptions:nil];
  392. } else {
  393. #endif
  394. [fullScreenWindow close];
  395. [normalWindow setContentView: self];
  396. [normalWindow makeKeyAndOrderFront: self];
  397. [NSMenu setMenuBarVisible:YES];
  398. #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4)
  399. }
  400. #endif
  401. } else { // switch from desktop to fullscreen
  402. isFullscreen = TRUE;
  403. [self grabMouse];
  404. [self setContentDimensions];
  405. // test if host support "enterFullScreenMode:withOptions" at compiletime
  406. #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4)
  407. if ([NSView respondsToSelector:@selector(enterFullScreenMode:withOptions:)]) { // test if "enterFullScreenMode:withOptions" is supported on host at runtime
  408. [self enterFullScreenMode:[NSScreen mainScreen] withOptions:[NSDictionary dictionaryWithObjectsAndKeys:
  409. [NSNumber numberWithBool:NO], NSFullScreenModeAllScreens,
  410. [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO], kCGDisplayModeIsStretched, nil], NSFullScreenModeSetting,
  411. nil]];
  412. } else {
  413. #endif
  414. [NSMenu setMenuBarVisible:NO];
  415. fullScreenWindow = [[NSWindow alloc] initWithContentRect:[[NSScreen mainScreen] frame]
  416. styleMask:NSBorderlessWindowMask
  417. backing:NSBackingStoreBuffered
  418. defer:NO];
  419. [fullScreenWindow setHasShadow:NO];
  420. [fullScreenWindow setContentView:self];
  421. [fullScreenWindow makeKeyAndOrderFront:self];
  422. #if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4)
  423. }
  424. #endif
  425. }
  426. }
  427. - (void) handleEvent:(NSEvent *)event
  428. {
  429. COCOA_DEBUG("QemuCocoaView: handleEvent\n");
  430. int buttons = 0;
  431. int keycode;
  432. NSPoint p = [event locationInWindow];
  433. switch ([event type]) {
  434. case NSFlagsChanged:
  435. keycode = cocoa_keycode_to_qemu([event keyCode]);
  436. if (keycode) {
  437. if (keycode == 58 || keycode == 69) { // emulate caps lock and num lock keydown and keyup
  438. kbd_put_keycode(keycode);
  439. kbd_put_keycode(keycode | 0x80);
  440. } else if (is_graphic_console()) {
  441. if (keycode & 0x80)
  442. kbd_put_keycode(0xe0);
  443. if (modifiers_state[keycode] == 0) { // keydown
  444. kbd_put_keycode(keycode & 0x7f);
  445. modifiers_state[keycode] = 1;
  446. } else { // keyup
  447. kbd_put_keycode(keycode | 0x80);
  448. modifiers_state[keycode] = 0;
  449. }
  450. }
  451. }
  452. // release Mouse grab when pressing ctrl+alt
  453. if (!isFullscreen && ([event modifierFlags] & NSControlKeyMask) && ([event modifierFlags] & NSAlternateKeyMask)) {
  454. [self ungrabMouse];
  455. }
  456. break;
  457. case NSKeyDown:
  458. // forward command Key Combos
  459. if ([event modifierFlags] & NSCommandKeyMask) {
  460. [NSApp sendEvent:event];
  461. return;
  462. }
  463. // default
  464. keycode = cocoa_keycode_to_qemu([event keyCode]);
  465. // handle control + alt Key Combos (ctrl+alt is reserved for QEMU)
  466. if (([event modifierFlags] & NSControlKeyMask) && ([event modifierFlags] & NSAlternateKeyMask)) {
  467. switch (keycode) {
  468. // enable graphic console
  469. case 0x02 ... 0x0a: // '1' to '9' keys
  470. console_select(keycode - 0x02);
  471. break;
  472. }
  473. // handle keys for graphic console
  474. } else if (is_graphic_console()) {
  475. if (keycode & 0x80) //check bit for e0 in front
  476. kbd_put_keycode(0xe0);
  477. kbd_put_keycode(keycode & 0x7f); //remove e0 bit in front
  478. // handlekeys for Monitor
  479. } else {
  480. int keysym = 0;
  481. switch([event keyCode]) {
  482. case 115:
  483. keysym = QEMU_KEY_HOME;
  484. break;
  485. case 117:
  486. keysym = QEMU_KEY_DELETE;
  487. break;
  488. case 119:
  489. keysym = QEMU_KEY_END;
  490. break;
  491. case 123:
  492. keysym = QEMU_KEY_LEFT;
  493. break;
  494. case 124:
  495. keysym = QEMU_KEY_RIGHT;
  496. break;
  497. case 125:
  498. keysym = QEMU_KEY_DOWN;
  499. break;
  500. case 126:
  501. keysym = QEMU_KEY_UP;
  502. break;
  503. default:
  504. {
  505. NSString *ks = [event characters];
  506. if ([ks length] > 0)
  507. keysym = [ks characterAtIndex:0];
  508. }
  509. }
  510. if (keysym)
  511. kbd_put_keysym(keysym);
  512. }
  513. break;
  514. case NSKeyUp:
  515. keycode = cocoa_keycode_to_qemu([event keyCode]);
  516. if (is_graphic_console()) {
  517. if (keycode & 0x80)
  518. kbd_put_keycode(0xe0);
  519. kbd_put_keycode(keycode | 0x80); //add 128 to signal release of key
  520. }
  521. break;
  522. case NSMouseMoved:
  523. if (isAbsoluteEnabled) {
  524. if (p.x < 0 || p.x > screen.width || p.y < 0 || p.y > screen.height || ![[self window] isKeyWindow]) {
  525. if (isTabletEnabled) { // if we leave the window, deactivate the tablet
  526. [NSCursor unhide];
  527. isTabletEnabled = FALSE;
  528. }
  529. } else {
  530. if (!isTabletEnabled) { // if we enter the window, activate the tablet
  531. [NSCursor hide];
  532. isTabletEnabled = TRUE;
  533. }
  534. }
  535. }
  536. COCOA_MOUSE_EVENT
  537. break;
  538. case NSLeftMouseDown:
  539. if ([event modifierFlags] & NSCommandKeyMask) {
  540. buttons |= MOUSE_EVENT_RBUTTON;
  541. } else {
  542. buttons |= MOUSE_EVENT_LBUTTON;
  543. }
  544. COCOA_MOUSE_EVENT
  545. break;
  546. case NSRightMouseDown:
  547. buttons |= MOUSE_EVENT_RBUTTON;
  548. COCOA_MOUSE_EVENT
  549. break;
  550. case NSOtherMouseDown:
  551. buttons |= MOUSE_EVENT_MBUTTON;
  552. COCOA_MOUSE_EVENT
  553. break;
  554. case NSLeftMouseDragged:
  555. if ([event modifierFlags] & NSCommandKeyMask) {
  556. buttons |= MOUSE_EVENT_RBUTTON;
  557. } else {
  558. buttons |= MOUSE_EVENT_LBUTTON;
  559. }
  560. COCOA_MOUSE_EVENT
  561. break;
  562. case NSRightMouseDragged:
  563. buttons |= MOUSE_EVENT_RBUTTON;
  564. COCOA_MOUSE_EVENT
  565. break;
  566. case NSOtherMouseDragged:
  567. buttons |= MOUSE_EVENT_MBUTTON;
  568. COCOA_MOUSE_EVENT
  569. break;
  570. case NSLeftMouseUp:
  571. if (isTabletEnabled) {
  572. COCOA_MOUSE_EVENT
  573. } else if (!isMouseGrabed) {
  574. if (p.x > -1 && p.x < screen.width && p.y > -1 && p.y < screen.height) {
  575. [self grabMouse];
  576. } else {
  577. [NSApp sendEvent:event];
  578. }
  579. } else {
  580. COCOA_MOUSE_EVENT
  581. }
  582. break;
  583. case NSRightMouseUp:
  584. COCOA_MOUSE_EVENT
  585. break;
  586. case NSOtherMouseUp:
  587. COCOA_MOUSE_EVENT
  588. break;
  589. case NSScrollWheel:
  590. if (isTabletEnabled || isMouseGrabed) {
  591. kbd_mouse_event(0, 0, -[event deltaY], 0);
  592. } else {
  593. [NSApp sendEvent:event];
  594. }
  595. break;
  596. default:
  597. [NSApp sendEvent:event];
  598. }
  599. }
  600. - (void) grabMouse
  601. {
  602. COCOA_DEBUG("QemuCocoaView: grabMouse\n");
  603. if (!isFullscreen) {
  604. if (qemu_name)
  605. [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s - (Press ctrl + alt to release Mouse)", qemu_name]];
  606. else
  607. [normalWindow setTitle:@"QEMU - (Press ctrl + alt to release Mouse)"];
  608. }
  609. [NSCursor hide];
  610. CGAssociateMouseAndMouseCursorPosition(FALSE);
  611. isMouseGrabed = TRUE; // while isMouseGrabed = TRUE, QemuCocoaApp sends all events to [cocoaView handleEvent:]
  612. }
  613. - (void) ungrabMouse
  614. {
  615. COCOA_DEBUG("QemuCocoaView: ungrabMouse\n");
  616. if (!isFullscreen) {
  617. if (qemu_name)
  618. [normalWindow setTitle:[NSString stringWithFormat:@"QEMU %s", qemu_name]];
  619. else
  620. [normalWindow setTitle:@"QEMU"];
  621. }
  622. [NSCursor unhide];
  623. CGAssociateMouseAndMouseCursorPosition(TRUE);
  624. isMouseGrabed = FALSE;
  625. }
  626. - (void) setAbsoluteEnabled:(BOOL)tIsAbsoluteEnabled {isAbsoluteEnabled = tIsAbsoluteEnabled;}
  627. - (BOOL) isMouseGrabed {return isMouseGrabed;}
  628. - (BOOL) isAbsoluteEnabled {return isAbsoluteEnabled;}
  629. - (float) cdx {return cdx;}
  630. - (float) cdy {return cdy;}
  631. - (QEMUScreen) gscreen {return screen;}
  632. @end
  633. /*
  634. ------------------------------------------------------
  635. QemuCocoaAppController
  636. ------------------------------------------------------
  637. */
  638. @interface QemuCocoaAppController : NSObject
  639. {
  640. }
  641. - (void)startEmulationWithArgc:(int)argc argv:(char**)argv;
  642. - (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo;
  643. - (void)toggleFullScreen:(id)sender;
  644. - (void)showQEMUDoc:(id)sender;
  645. - (void)showQEMUTec:(id)sender;
  646. @end
  647. @implementation QemuCocoaAppController
  648. - (id) init
  649. {
  650. COCOA_DEBUG("QemuCocoaAppController: init\n");
  651. self = [super init];
  652. if (self) {
  653. // create a view and add it to the window
  654. cocoaView = [[QemuCocoaView alloc] initWithFrame:NSMakeRect(0.0, 0.0, 640.0, 480.0)];
  655. if(!cocoaView) {
  656. fprintf(stderr, "(cocoa) can't create a view\n");
  657. exit(1);
  658. }
  659. // create a window
  660. normalWindow = [[NSWindow alloc] initWithContentRect:[cocoaView frame]
  661. styleMask:NSTitledWindowMask|NSMiniaturizableWindowMask|NSClosableWindowMask
  662. backing:NSBackingStoreBuffered defer:NO];
  663. if(!normalWindow) {
  664. fprintf(stderr, "(cocoa) can't create window\n");
  665. exit(1);
  666. }
  667. [normalWindow setAcceptsMouseMovedEvents:YES];
  668. [normalWindow setTitle:[NSString stringWithFormat:@"QEMU"]];
  669. [normalWindow setContentView:cocoaView];
  670. [normalWindow makeKeyAndOrderFront:self];
  671. [normalWindow center];
  672. }
  673. return self;
  674. }
  675. - (void) dealloc
  676. {
  677. COCOA_DEBUG("QemuCocoaAppController: dealloc\n");
  678. if (cocoaView)
  679. [cocoaView release];
  680. [super dealloc];
  681. }
  682. - (void)applicationDidFinishLaunching: (NSNotification *) note
  683. {
  684. COCOA_DEBUG("QemuCocoaAppController: applicationDidFinishLaunching\n");
  685. // Display an open dialog box if no argument were passed or
  686. // if qemu was launched from the finder ( the Finder passes "-psn" )
  687. if( gArgc <= 1 || strncmp ((char *)gArgv[1], "-psn", 4) == 0) {
  688. NSOpenPanel *op = [[NSOpenPanel alloc] init];
  689. [op setPrompt:@"Boot image"];
  690. [op setMessage:@"Select the disk image you want to boot.\n\nHit the \"Cancel\" button to quit"];
  691. [op beginSheetForDirectory:nil file:nil types:[NSArray arrayWithObjects:@"img",@"iso",@"dmg",@"qcow",@"cow",@"cloop",@"vmdk",nil]
  692. modalForWindow:normalWindow modalDelegate:self
  693. didEndSelector:@selector(openPanelDidEnd:returnCode:contextInfo:) contextInfo:NULL];
  694. } else {
  695. // or Launch Qemu, with the global args
  696. [self startEmulationWithArgc:gArgc argv:(char **)gArgv];
  697. }
  698. }
  699. - (void)applicationWillTerminate:(NSNotification *)aNotification
  700. {
  701. COCOA_DEBUG("QemuCocoaAppController: applicationWillTerminate\n");
  702. qemu_system_shutdown_request();
  703. exit(0);
  704. }
  705. - (void)startEmulationWithArgc:(int)argc argv:(char**)argv
  706. {
  707. COCOA_DEBUG("QemuCocoaAppController: startEmulationWithArgc\n");
  708. int status;
  709. status = qemu_main(argc, argv);
  710. exit(status);
  711. }
  712. - (void)openPanelDidEnd:(NSOpenPanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
  713. {
  714. COCOA_DEBUG("QemuCocoaAppController: openPanelDidEnd\n");
  715. if(returnCode == NSCancelButton) {
  716. exit(0);
  717. } else if(returnCode == NSOKButton) {
  718. char *bin = "qemu";
  719. char *img = (char*)[ [ sheet filename ] cStringUsingEncoding:NSASCIIStringEncoding];
  720. char **argv = (char**)malloc( sizeof(char*)*3 );
  721. asprintf(&argv[0], "%s", bin);
  722. asprintf(&argv[1], "-hda");
  723. asprintf(&argv[2], "%s", img);
  724. printf("Using argc %d argv %s -hda %s\n", 3, bin, img);
  725. [self startEmulationWithArgc:3 argv:(char**)argv];
  726. }
  727. }
  728. - (void)toggleFullScreen:(id)sender
  729. {
  730. COCOA_DEBUG("QemuCocoaAppController: toggleFullScreen\n");
  731. [cocoaView toggleFullScreen:sender];
  732. }
  733. - (void)showQEMUDoc:(id)sender
  734. {
  735. COCOA_DEBUG("QemuCocoaAppController: showQEMUDoc\n");
  736. [[NSWorkspace sharedWorkspace] openFile:[NSString stringWithFormat:@"%@/../doc/qemu/qemu-doc.html",
  737. [[NSBundle mainBundle] resourcePath]] withApplication:@"Help Viewer"];
  738. }
  739. - (void)showQEMUTec:(id)sender
  740. {
  741. COCOA_DEBUG("QemuCocoaAppController: showQEMUTec\n");
  742. [[NSWorkspace sharedWorkspace] openFile:[NSString stringWithFormat:@"%@/../doc/qemu/qemu-tech.html",
  743. [[NSBundle mainBundle] resourcePath]] withApplication:@"Help Viewer"];
  744. }
  745. @end
  746. // Dock Connection
  747. typedef struct CPSProcessSerNum
  748. {
  749. UInt32 lo;
  750. UInt32 hi;
  751. } CPSProcessSerNum;
  752. extern OSErr CPSGetCurrentProcess( CPSProcessSerNum *psn);
  753. extern OSErr CPSEnableForegroundOperation( CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5);
  754. extern OSErr CPSSetFrontProcess( CPSProcessSerNum *psn);
  755. int main (int argc, const char * argv[]) {
  756. gArgc = argc;
  757. gArgv = (char **)argv;
  758. CPSProcessSerNum PSN;
  759. NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
  760. [NSApplication sharedApplication];
  761. if (!CPSGetCurrentProcess(&PSN))
  762. if (!CPSEnableForegroundOperation(&PSN,0x03,0x3C,0x2C,0x1103))
  763. if (!CPSSetFrontProcess(&PSN))
  764. [NSApplication sharedApplication];
  765. // Add menus
  766. NSMenu *menu;
  767. NSMenuItem *menuItem;
  768. [NSApp setMainMenu:[[NSMenu alloc] init]];
  769. // Application menu
  770. menu = [[NSMenu alloc] initWithTitle:@""];
  771. [menu addItemWithTitle:@"About QEMU" action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; // About QEMU
  772. [menu addItem:[NSMenuItem separatorItem]]; //Separator
  773. [menu addItemWithTitle:@"Hide QEMU" action:@selector(hide:) keyEquivalent:@"h"]; //Hide QEMU
  774. menuItem = (NSMenuItem *)[menu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; // Hide Others
  775. [menuItem setKeyEquivalentModifierMask:(NSAlternateKeyMask|NSCommandKeyMask)];
  776. [menu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; // Show All
  777. [menu addItem:[NSMenuItem separatorItem]]; //Separator
  778. [menu addItemWithTitle:@"Quit QEMU" action:@selector(terminate:) keyEquivalent:@"q"];
  779. menuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" action:nil keyEquivalent:@""];
  780. [menuItem setSubmenu:menu];
  781. [[NSApp mainMenu] addItem:menuItem];
  782. [NSApp performSelector:@selector(setAppleMenu:) withObject:menu]; // Workaround (this method is private since 10.4+)
  783. // View menu
  784. menu = [[NSMenu alloc] initWithTitle:@"View"];
  785. [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Enter Fullscreen" action:@selector(toggleFullScreen:) keyEquivalent:@"f"] autorelease]]; // Fullscreen
  786. menuItem = [[[NSMenuItem alloc] initWithTitle:@"View" action:nil keyEquivalent:@""] autorelease];
  787. [menuItem setSubmenu:menu];
  788. [[NSApp mainMenu] addItem:menuItem];
  789. // Window menu
  790. menu = [[NSMenu alloc] initWithTitle:@"Window"];
  791. [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"] autorelease]]; // Miniaturize
  792. menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease];
  793. [menuItem setSubmenu:menu];
  794. [[NSApp mainMenu] addItem:menuItem];
  795. [NSApp setWindowsMenu:menu];
  796. // Help menu
  797. menu = [[NSMenu alloc] initWithTitle:@"Help"];
  798. [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Documentation" action:@selector(showQEMUDoc:) keyEquivalent:@"?"] autorelease]]; // QEMU Help
  799. [menu addItem: [[[NSMenuItem alloc] initWithTitle:@"QEMU Technology" action:@selector(showQEMUTec:) keyEquivalent:@""] autorelease]]; // QEMU Help
  800. menuItem = [[[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""] autorelease];
  801. [menuItem setSubmenu:menu];
  802. [[NSApp mainMenu] addItem:menuItem];
  803. // Create an Application controller
  804. QemuCocoaAppController *appController = [[QemuCocoaAppController alloc] init];
  805. [NSApp setDelegate:appController];
  806. // Start the main event loop
  807. [NSApp run];
  808. [appController release];
  809. [pool release];
  810. return 0;
  811. }
  812. #pragma mark qemu
  813. static void cocoa_update(DisplayState *ds, int x, int y, int w, int h)
  814. {
  815. COCOA_DEBUG("qemu_cocoa: cocoa_update\n");
  816. NSRect rect;
  817. if ([cocoaView cdx] == 1.0) {
  818. rect = NSMakeRect(x, [cocoaView gscreen].height - y - h, w, h);
  819. } else {
  820. rect = NSMakeRect(
  821. x * [cocoaView cdx],
  822. ([cocoaView gscreen].height - y - h) * [cocoaView cdy],
  823. w * [cocoaView cdx],
  824. h * [cocoaView cdy]);
  825. }
  826. [cocoaView displayRect:rect];
  827. }
  828. static void cocoa_resize(DisplayState *ds)
  829. {
  830. COCOA_DEBUG("qemu_cocoa: cocoa_resize\n");
  831. [cocoaView resizeContentToWidth:(int)(ds_get_width(ds)) height:(int)(ds_get_height(ds)) displayState:ds];
  832. }
  833. static void cocoa_refresh(DisplayState *ds)
  834. {
  835. COCOA_DEBUG("qemu_cocoa: cocoa_refresh\n");
  836. if (kbd_mouse_is_absolute()) {
  837. if (![cocoaView isAbsoluteEnabled]) {
  838. if ([cocoaView isMouseGrabed]) {
  839. [cocoaView ungrabMouse];
  840. }
  841. }
  842. [cocoaView setAbsoluteEnabled:YES];
  843. }
  844. NSDate *distantPast;
  845. NSEvent *event;
  846. distantPast = [NSDate distantPast];
  847. do {
  848. event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:distantPast
  849. inMode: NSDefaultRunLoopMode dequeue:YES];
  850. if (event != nil) {
  851. [cocoaView handleEvent:event];
  852. }
  853. } while(event != nil);
  854. vga_hw_update();
  855. }
  856. static void cocoa_cleanup(void)
  857. {
  858. COCOA_DEBUG("qemu_cocoa: cocoa_cleanup\n");
  859. qemu_free(dcl);
  860. }
  861. void cocoa_display_init(DisplayState *ds, int full_screen)
  862. {
  863. COCOA_DEBUG("qemu_cocoa: cocoa_display_init\n");
  864. dcl = qemu_mallocz(sizeof(DisplayChangeListener));
  865. // register vga output callbacks
  866. dcl->dpy_update = cocoa_update;
  867. dcl->dpy_resize = cocoa_resize;
  868. dcl->dpy_refresh = cocoa_refresh;
  869. register_displaychangelistener(ds, dcl);
  870. // register cleanup function
  871. atexit(cocoa_cleanup);
  872. }