BsMacOSPlatform.mm 28 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. #define BS_COCOA_INTERNALS 1
  4. #include "Private/MacOS/BsMacOSPlatform.h"
  5. #include "Private/MacOS/BsMacOSWindow.h"
  6. #include "Input/BsInputFwd.h"
  7. #include "Image/BsPixelData.h"
  8. #include "Image/BsColor.h"
  9. #include "RenderAPI/BsRenderWindow.h"
  10. #include "Private/MacOS/BsMacOSDropTarget.h"
  11. #include "String/BsUnicode.h"
  12. #include "BsCoreApplication.h"
  13. #import <Cocoa/Cocoa.h>
  14. #import <Carbon/Carbon.h>
  15. /** Application implementation that overrides the terminate logic with custom shutdown, and tracks Esc key presses. */
  16. @interface BSApplication : NSApplication
  17. -(void)terminate:(nullable id)sender;
  18. -(void)sendEvent:(NSEvent*)event;
  19. @end
  20. @implementation BSApplication
  21. -(void)terminate:(nullable id)sender
  22. {
  23. bs::gCoreApplication().quitRequested();
  24. }
  25. -(void)sendEvent:(NSEvent *)event
  26. {
  27. // Handle Esc key here, as it doesn't seem to be reported elsewhere
  28. if([event type] == NSEventTypeKeyDown)
  29. {
  30. if([event keyCode] == 53) // Escape key
  31. {
  32. bs::InputCommandType ic = bs::InputCommandType ::Escape;
  33. bs::MacOSPlatform::sendInputCommandEvent(ic);
  34. }
  35. }
  36. [super sendEvent:event];
  37. }
  38. @end
  39. /** Application delegate implementation that activates the application when it finishes launching. */
  40. @interface BSAppDelegate : NSObject<NSApplicationDelegate>
  41. @end
  42. @implementation BSAppDelegate : NSObject
  43. -(void)applicationDidFinishLaunching:(NSNotification *)notification
  44. {
  45. [NSApp activateIgnoringOtherApps:YES];
  46. }
  47. @end
  48. @class BSCursor;
  49. @class BSPlatform;
  50. namespace bs
  51. {
  52. /** Contains information about a modal window session. */
  53. struct ModalWindowInfo
  54. {
  55. UINT32 windowId;
  56. NSModalSession session;
  57. };
  58. struct Platform::Pimpl
  59. {
  60. BSAppDelegate* appDelegate = nil;
  61. Mutex windowMutex;
  62. CocoaWindow* mainWindow = nullptr;
  63. UnorderedMap<UINT32, CocoaWindow*> allWindows;
  64. Vector<ModalWindowInfo> modalWindows;
  65. BSPlatform* platformManager = nil;
  66. // Cursor
  67. BSCursor* cursorManager = nil;
  68. Mutex cursorMutex;
  69. bool cursorIsHidden = false;
  70. Vector2I cursorPos;
  71. // Clipboard
  72. Mutex clipboardMutex;
  73. WString cachedClipboardData;
  74. INT32 clipboardChangeCount = -1;
  75. };
  76. }
  77. /**
  78. * Contains cursor specific functionality. Encapsulated in objective C so its selectors can be triggered from other
  79. * threads.
  80. */
  81. @interface BSCursor : NSObject
  82. @property NSCursor* currentCursor;
  83. -(BSCursor*) initWithPlatformData:(bs::Platform::Pimpl*)platformData;
  84. -(bs::Vector2I) getPosition;
  85. -(void) setPosition:(const bs::Vector2I&) position;
  86. -(BOOL) clipCursor:(bs::Vector2I&) position;
  87. -(void) updateClipBounds:(NSWindow*) window;
  88. -(void) clipCursorToWindow:(NSValue*) windowValue;
  89. -(void) clipCursorToRect:(NSValue*) rectValue;
  90. -(void) clipCursorDisable;
  91. -(void) setCursor:(NSArray*) params;
  92. -(void) unregisterWindow:(NSWindow*) window;
  93. @end
  94. @implementation BSCursor
  95. {
  96. bs::Platform::Pimpl* platformData;
  97. bool cursorClipEnabled;
  98. bs::Rect2I cursorClipRect;
  99. NSWindow* cursorClipWindow;
  100. }
  101. - (BSCursor*)initWithPlatformData:(bs::Platform::Pimpl*)data
  102. {
  103. self = [super init];
  104. platformData = data;
  105. return self;
  106. }
  107. - (bs::Vector2I)getPosition
  108. {
  109. NSPoint point = [NSEvent mouseLocation];
  110. for (NSScreen* screen in [NSScreen screens])
  111. {
  112. NSRect frame = [screen frame];
  113. if (NSMouseInRect(point, frame, NO))
  114. bs::flipY(screen, point);
  115. }
  116. bs::Vector2I output;
  117. output.x = (int32_t)point.x;
  118. output.y = (int32_t)point.y;
  119. return output;
  120. }
  121. - (void)setPosition:(const bs::Vector2I&)position
  122. {
  123. NSPoint point = NSMakePoint(position.x, position.y);
  124. CGWarpMouseCursorPosition(point);
  125. Lock lock(platformData->cursorMutex);
  126. platformData->cursorPos = position;
  127. }
  128. - (BOOL)clipCursor:(bs::Vector2I&)position
  129. {
  130. if(!cursorClipEnabled)
  131. return false;
  132. int32_t clippedX = position.x - cursorClipRect.x;
  133. int32_t clippedY = position.y - cursorClipRect.y;
  134. if(clippedX < 0)
  135. clippedX = 0;
  136. else if(clippedX >= (int32_t)cursorClipRect.width)
  137. clippedX = cursorClipRect.width > 0 ? cursorClipRect.width - 1 : 0;
  138. if(clippedY < 0)
  139. clippedY = 0;
  140. else if(clippedY >= (int32_t)cursorClipRect.height)
  141. clippedY = cursorClipRect.height > 0 ? cursorClipRect.height - 1 : 0;
  142. clippedX += cursorClipRect.x;
  143. clippedY += cursorClipRect.y;
  144. if(clippedX != position.x || clippedY != position.y)
  145. {
  146. position.x = clippedX;
  147. position.y = clippedY;
  148. return true;
  149. }
  150. return false;
  151. }
  152. - (void)updateClipBounds:(NSWindow*)window
  153. {
  154. if(!cursorClipEnabled || cursorClipWindow != window)
  155. return;
  156. NSRect rect = [window contentRectForFrameRect:[window frame]];
  157. bs::flipY([window screen], rect);
  158. cursorClipRect.x = (int32_t)rect.origin.x;
  159. cursorClipRect.y = (int32_t)rect.origin.y;
  160. cursorClipRect.width = (uint32_t)rect.size.width;
  161. cursorClipRect.height = (uint32_t)rect.size.height;
  162. }
  163. - (void)clipCursorToWindow:(NSValue*)windowValue
  164. {
  165. bs::CocoaWindow* window;
  166. [windowValue getValue:&window];
  167. cursorClipEnabled = true;
  168. cursorClipWindow = window->_getPrivateData()->window;
  169. [self updateClipBounds:cursorClipWindow];
  170. bs::Vector2I pos = [self getPosition];
  171. if([self clipCursor:pos])
  172. [self setPosition:pos];
  173. }
  174. - (void)clipCursorToRect:(NSValue*)rectValue
  175. {
  176. bs::Rect2I rect;
  177. [rectValue getValue:&rect];
  178. cursorClipEnabled = true;
  179. cursorClipRect = rect;
  180. cursorClipWindow = nullptr;
  181. bs::Vector2I pos = [self getPosition];
  182. if([self clipCursor:pos])
  183. [self setPosition:pos];
  184. }
  185. - (void)clipCursorDisable
  186. {
  187. cursorClipEnabled = false;
  188. cursorClipWindow = nullptr;
  189. }
  190. - (void)setCursor:(NSArray*)params
  191. {
  192. NSCursor* cursor = params[0];
  193. NSValue* hotSpotValue = params[1];
  194. NSPoint hotSpot;
  195. [hotSpotValue getValue:&hotSpot];
  196. [self setCurrentCursor:cursor];
  197. for(auto& entry : platformData->allWindows)
  198. {
  199. NSWindow* window = entry.second->_getPrivateData()->window;
  200. [window invalidateCursorRectsForView:[window contentView]];
  201. }
  202. }
  203. - (void)unregisterWindow:(NSWindow*)window
  204. {
  205. if(cursorClipEnabled && cursorClipWindow == window)
  206. [self clipCursorDisable];
  207. }
  208. @end
  209. /** Contains platform specific functionality that is meant to be delayed executed from the sim thread, through Platform. */
  210. @interface BSPlatform : NSObject
  211. -(BSPlatform*) initWithPlatformData:(bs::Platform::Pimpl*)platformData;
  212. -(void) setCaptionNonClientAreas:(NSArray*) params;
  213. -(void) resetNonClientAreas:(NSValue*) windowIdValue;
  214. -(void) openFolder:(NSURL*) url;
  215. -(void) setClipboardText:(NSString*) text;
  216. -(NSString*) getClipboardText;
  217. -(int32_t) getClipboardChangeCount;
  218. @end
  219. @implementation BSPlatform
  220. {
  221. bs::Platform::Pimpl* mPlatformData;
  222. }
  223. - (BSPlatform*)initWithPlatformData:(bs::Platform::Pimpl*)platformData
  224. {
  225. self = [super init];
  226. mPlatformData = platformData;
  227. return self;
  228. }
  229. - (void)setCaptionNonClientAreas:(NSArray*)params
  230. {
  231. NSValue* windowIdValue = params[0];
  232. bs::UINT32 windowId;
  233. [windowIdValue getValue:&windowId];
  234. auto iterFind = mPlatformData->allWindows.find(windowId);
  235. if(iterFind == mPlatformData->allWindows.end())
  236. return;
  237. bs::CocoaWindow* window = iterFind->second;
  238. NSUInteger numEntries = [params count] - 1;
  239. bs::Vector<bs::Rect2I> areas;
  240. for(NSUInteger i = 0; i < numEntries; i++)
  241. {
  242. NSValue* value = params[i + 1];
  243. bs::Rect2I area;
  244. [value getValue:&area];
  245. areas.push_back(area);
  246. }
  247. window->_setDragZones(areas);
  248. }
  249. - (void)resetNonClientAreas:(NSValue*) windowIdValue
  250. {
  251. bs::UINT32 windowId;
  252. [windowIdValue getValue:&windowId];
  253. auto iterFind = mPlatformData->allWindows.find(windowId);
  254. if(iterFind == mPlatformData->allWindows.end())
  255. return;
  256. bs::CocoaWindow* window = iterFind->second;
  257. window->_setDragZones({});
  258. }
  259. - (void)openFolder:(NSURL*)url
  260. {
  261. [[NSWorkspace sharedWorkspace] openURL:url];
  262. }
  263. - (void) setClipboardText:(NSString*) text
  264. { @autoreleasepool {
  265. NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
  266. [pasteboard clearContents];
  267. NSArray* objects = [NSArray arrayWithObject:text];
  268. [pasteboard writeObjects:objects];
  269. }}
  270. - (NSString*) getClipboardText
  271. { @autoreleasepool {
  272. NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
  273. NSArray* classes = [NSArray arrayWithObjects:[NSString class], nil];
  274. NSDictionary* options = [NSDictionary dictionary];
  275. NSArray* items = [pasteboard readObjectsForClasses:classes options:options];
  276. if(!items || items.count == 0)
  277. return nil;
  278. return (NSString*) items[0];
  279. }}
  280. - (int32_t)getClipboardChangeCount
  281. {
  282. return (int32_t)[[NSPasteboard generalPasteboard] changeCount];
  283. }
  284. @end
  285. namespace bs
  286. {
  287. // Maps macOS keycodes to bs button codes
  288. static constexpr ButtonCode KeyCodeMapping[] =
  289. {
  290. /* 0 */ BC_A,
  291. /* 1 */ BC_S,
  292. /* 2 */ BC_D,
  293. /* 3 */ BC_F,
  294. /* 4 */ BC_H,
  295. /* 5 */ BC_G,
  296. /* 6 */ BC_Z,
  297. /* 7 */ BC_X,
  298. /* 8 */ BC_C,
  299. /* 9 */ BC_V,
  300. /* 10 */ BC_GRAVE,
  301. /* 11 */ BC_B,
  302. /* 12 */ BC_Q,
  303. /* 13 */ BC_W,
  304. /* 14 */ BC_E,
  305. /* 15 */ BC_R,
  306. /* 16 */ BC_Y,
  307. /* 17 */ BC_T,
  308. /* 18 */ BC_1,
  309. /* 19 */ BC_2,
  310. /* 20 */ BC_3,
  311. /* 21 */ BC_4,
  312. /* 22 */ BC_6,
  313. /* 23 */ BC_5,
  314. /* 24 */ BC_EQUALS,
  315. /* 25 */ BC_9,
  316. /* 26 */ BC_7,
  317. /* 27 */ BC_MINUS,
  318. /* 28 */ BC_8,
  319. /* 29 */ BC_0,
  320. /* 30 */ BC_RBRACKET,
  321. /* 31 */ BC_O,
  322. /* 32 */ BC_U,
  323. /* 33 */ BC_LBRACKET,
  324. /* 34 */ BC_I,
  325. /* 35 */ BC_P,
  326. /* 36 */ BC_RETURN,
  327. /* 37 */ BC_L,
  328. /* 38 */ BC_J,
  329. /* 39 */ BC_APOSTROPHE,
  330. /* 40 */ BC_K,
  331. /* 41 */ BC_SEMICOLON,
  332. /* 42 */ BC_BACKSLASH,
  333. /* 43 */ BC_COMMA,
  334. /* 44 */ BC_SLASH,
  335. /* 45 */ BC_N,
  336. /* 46 */ BC_M,
  337. /* 47 */ BC_PERIOD,
  338. /* 48 */ BC_TAB,
  339. /* 49 */ BC_SPACE,
  340. /* 50 */ BC_GRAVE,
  341. /* 51 */ BC_BACK,
  342. /* 52 */ BC_NUMPADENTER,
  343. /* 53 */ BC_ESCAPE,
  344. /* 54 */ BC_RWIN,
  345. /* 55 */ BC_LWIN,
  346. /* 56 */ BC_LSHIFT,
  347. /* 57 */ BC_CAPITAL,
  348. /* 58 */ BC_LMENU,
  349. /* 59 */ BC_LCONTROL,
  350. /* 60 */ BC_RSHIFT,
  351. /* 61 */ BC_RMENU,
  352. /* 62 */ BC_RCONTROL,
  353. /* 63 */ BC_RWIN,
  354. /* 64 */ BC_UNASSIGNED,
  355. /* 65 */ BC_DECIMAL,
  356. /* 66 */ BC_UNASSIGNED,
  357. /* 67 */ BC_MULTIPLY,
  358. /* 68 */ BC_UNASSIGNED,
  359. /* 69 */ BC_ADD,
  360. /* 70 */ BC_UNASSIGNED,
  361. /* 71 */ BC_NUMLOCK,
  362. /* 72 */ BC_VOLUMEUP,
  363. /* 73 */ BC_VOLUMEDOWN,
  364. /* 74 */ BC_MUTE,
  365. /* 75 */ BC_DIVIDE,
  366. /* 76 */ BC_NUMPADENTER,
  367. /* 77 */ BC_UNASSIGNED,
  368. /* 78 */ BC_SUBTRACT,
  369. /* 79 */ BC_UNASSIGNED,
  370. /* 80 */ BC_UNASSIGNED,
  371. /* 81 */ BC_NUMPADEQUALS,
  372. /* 82 */ BC_NUMPAD0,
  373. /* 83 */ BC_NUMPAD1,
  374. /* 84 */ BC_NUMPAD2,
  375. /* 85 */ BC_NUMPAD3,
  376. /* 86 */ BC_NUMPAD4,
  377. /* 87 */ BC_NUMPAD5,
  378. /* 88 */ BC_NUMPAD6,
  379. /* 89 */ BC_NUMPAD7,
  380. /* 90 */ BC_UNASSIGNED,
  381. /* 91 */ BC_NUMPAD8,
  382. /* 92 */ BC_NUMPAD9,
  383. /* 93 */ BC_CONVERT,
  384. /* 94 */ BC_NOCONVERT,
  385. /* 95 */ BC_NUMPADCOMMA,
  386. /* 96 */ BC_F5,
  387. /* 97 */ BC_F6,
  388. /* 98 */ BC_F7,
  389. /* 99 */ BC_F3,
  390. /* 100 */ BC_F8,
  391. /* 101 */ BC_F9,
  392. /* 102 */ BC_UNASSIGNED,
  393. /* 103 */ BC_F11,
  394. /* 104 */ BC_UNASSIGNED,
  395. /* 105 */ BC_UNASSIGNED,
  396. /* 106 */ BC_UNASSIGNED,
  397. /* 107 */ BC_SCROLL,
  398. /* 108 */ BC_UNASSIGNED,
  399. /* 109 */ BC_F10,
  400. /* 110 */ BC_UNASSIGNED,
  401. /* 111 */ BC_F12,
  402. /* 112 */ BC_UNASSIGNED,
  403. /* 113 */ BC_PAUSE,
  404. /* 114 */ BC_INSERT,
  405. /* 115 */ BC_HOME,
  406. /* 116 */ BC_PGUP,
  407. /* 117 */ BC_DELETE,
  408. /* 118 */ BC_F4,
  409. /* 119 */ BC_END,
  410. /* 120 */ BC_F2,
  411. /* 121 */ BC_PGDOWN,
  412. /* 122 */ BC_F1,
  413. /* 123 */ BC_LEFT,
  414. /* 124 */ BC_RIGHT,
  415. /* 125 */ BC_DOWN,
  416. /* 126 */ BC_UP,
  417. /* 127 */ BC_POWER
  418. };
  419. static UINT32 ButtonCodeToKeyCode[255];
  420. static void initKeyCodeMapping()
  421. {
  422. memset(ButtonCodeToKeyCode, 0, sizeof(ButtonCodeToKeyCode));
  423. UINT32 numKeyCodes = sizeof(KeyCodeMapping) / sizeof(KeyCodeMapping[0]);
  424. for(UINT32 i = 0; i < numKeyCodes; i++)
  425. ButtonCodeToKeyCode[KeyCodeMapping[i]] = i;
  426. }
  427. void flipY(NSScreen* screen, NSRect& rect)
  428. {
  429. NSRect screenFrame = [screen frame];
  430. rect.origin.y = screenFrame.size.height - (rect.origin.y + rect.size.height);
  431. }
  432. void flipY(NSScreen* screen, NSPoint &point)
  433. {
  434. NSRect screenFrame = [screen frame];
  435. point.y = screenFrame.size.height - point.y;
  436. }
  437. void flipYWindow(NSWindow* window, NSPoint &point)
  438. {
  439. NSRect windowFrame = [window frame];
  440. point.y = windowFrame.size.height - point.y;
  441. }
  442. /** Returns the name of the current application based on the information in the app. bundle. */
  443. static NSString* getAppName()
  444. {
  445. NSString* appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
  446. if (!appName)
  447. appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
  448. if (![appName length]) {
  449. appName = [[NSProcessInfo processInfo] processName];
  450. }
  451. return appName;
  452. }
  453. /** Creates the default menu for the application menu bar. */
  454. static void createApplicationMenu()
  455. { @autoreleasepool {
  456. NSMenu* mainMenu = [[NSMenu alloc] init];
  457. [NSApp setMainMenu:mainMenu];
  458. NSString* appName = getAppName();
  459. NSMenu* appleMenu = [[NSMenu alloc] initWithTitle:@""];
  460. NSString* aboutTitle = [@"About " stringByAppendingString:appName];
  461. [appleMenu addItemWithTitle:aboutTitle
  462. action:@selector(orderFrontStandardAboutPanel:)
  463. keyEquivalent:@""];
  464. [appleMenu addItem:[NSMenuItem separatorItem]];
  465. NSString* hideTitle = [@"Hide " stringByAppendingString:appName];
  466. [appleMenu addItemWithTitle:hideTitle action:@selector(hide:) keyEquivalent:@"h"];
  467. NSMenuItem* hideOthersMenuItem = [appleMenu
  468. addItemWithTitle:@"Hide Others"
  469. action:@selector(hideOtherApplications:)
  470. keyEquivalent:@"h"];
  471. [hideOthersMenuItem setKeyEquivalentModifierMask:(NSEventModifierFlagOption|NSEventModifierFlagCommand)];
  472. [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
  473. [appleMenu addItem:[NSMenuItem separatorItem]];
  474. NSString* quitTitle = [@"Quit " stringByAppendingString:appName];
  475. [appleMenu addItemWithTitle:quitTitle action:@selector(terminate:) keyEquivalent:@"q"];
  476. NSMenuItem* appleMenuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
  477. [appleMenuItem setSubmenu:appleMenu];
  478. [[NSApp mainMenu] addItem:appleMenuItem];
  479. }}
  480. Event<void(const Vector2I &, const OSPointerButtonStates &)> Platform::onCursorMoved;
  481. Event<void(const Vector2I &, OSMouseButton button, const OSPointerButtonStates &)> Platform::onCursorButtonPressed;
  482. Event<void(const Vector2I &, OSMouseButton button, const OSPointerButtonStates &)> Platform::onCursorButtonReleased;
  483. Event<void(const Vector2I &, const OSPointerButtonStates &)> Platform::onCursorDoubleClick;
  484. Event<void(InputCommandType)> Platform::onInputCommand;
  485. Event<void(float)> Platform::onMouseWheelScrolled;
  486. Event<void(UINT32)> Platform::onCharInput;
  487. Event<void()> Platform::onMouseCaptureChanged;
  488. Platform::Pimpl* Platform::mData = bs_new<Platform::Pimpl>();
  489. Platform::~Platform()
  490. {
  491. }
  492. Vector2I Platform::getCursorPosition()
  493. {
  494. Lock lock(mData->cursorMutex);
  495. return mData->cursorPos;
  496. }
  497. void Platform::setCursorPosition(const Vector2I& screenPos)
  498. {
  499. [mData->cursorManager setPosition:screenPos];
  500. }
  501. void Platform::captureMouse(const RenderWindow& window)
  502. {
  503. // Do nothing
  504. }
  505. void Platform::releaseMouseCapture()
  506. {
  507. // Do nothing
  508. }
  509. bool Platform::isPointOverWindow(const RenderWindow& window, const Vector2I& screenPos)
  510. {
  511. CFArrayRef windowDicts = CGWindowListCopyWindowInfo(
  512. kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements,
  513. kCGNullWindowID);
  514. if(!windowDicts)
  515. return nil;
  516. CocoaWindow* cocoaWindow;
  517. window.getCustomAttribute("COCOA_WINDOW", &cocoaWindow);
  518. int32_t requestedWindowNumber = (int32_t)[cocoaWindow->_getPrivateData()->window windowNumber];
  519. CGPoint point = CGPointMake(screenPos.x, screenPos.y);
  520. CFIndex numEntries = CFArrayGetCount(windowDicts);
  521. for(CFIndex i = 0; i < numEntries; i++)
  522. {
  523. CFDictionaryRef dict = (CFDictionaryRef)CFArrayGetValueAtIndex(windowDicts, i);
  524. CFNumberRef layerRef = (CFNumberRef) CFDictionaryGetValue(dict, kCGWindowLayer);
  525. if(!layerRef)
  526. continue;
  527. // Ignore windows outside of layer 0, as those appear to be desktop elements
  528. int32_t layer;
  529. CFNumberGetValue(layerRef, kCFNumberIntType, &layer);
  530. // Layer 0 appear to be normal windows
  531. // Layer 3 appear to be floating windows
  532. // Layer 8 appear to be modal windows
  533. // Layer 25 appear to be fullscreen windows
  534. // Note: This is based on experimentation, as no documentation about it exists
  535. if(layer != 0 && layer != 3 && layer != 8 && layer != 25)
  536. continue;
  537. CFDictionaryRef boundsRef = (CFDictionaryRef)CFDictionaryGetValue(dict, kCGWindowBounds);
  538. CGRect rect;
  539. CGRectMakeWithDictionaryRepresentation(boundsRef, &rect);
  540. if(CGRectContainsPoint(rect, point))
  541. {
  542. // Windows are ordered front to back intrinsically, so the first one we are within bounds of is the one we want
  543. CFNumberRef windowNumRef = (CFNumberRef)CFDictionaryGetValue(dict, kCGWindowNumber);
  544. int32_t windowNumber;
  545. CFNumberGetValue(windowNumRef, kCGWindowIDCFNumberType, &windowNumber);
  546. return requestedWindowNumber == windowNumber;
  547. }
  548. }
  549. return false;
  550. }
  551. void Platform::hideCursor()
  552. {
  553. Lock lock(mData->cursorMutex);
  554. if(!mData->cursorIsHidden)
  555. {
  556. [NSCursor performSelectorOnMainThread:@selector(hide) withObject:nil waitUntilDone:NO];
  557. mData->cursorIsHidden = true;
  558. }
  559. }
  560. void Platform::showCursor()
  561. {
  562. Lock lock(mData->cursorMutex);
  563. if(mData->cursorIsHidden)
  564. {
  565. [NSCursor performSelectorOnMainThread:@selector(unhide) withObject:nil waitUntilDone:NO];
  566. mData->cursorIsHidden = false;
  567. }
  568. }
  569. bool Platform::isCursorHidden()
  570. {
  571. Lock lock(mData->cursorMutex);
  572. return mData->cursorIsHidden;
  573. }
  574. void Platform::clipCursorToWindow(const RenderWindow& window)
  575. {
  576. CocoaWindow* cocoaWindow;
  577. window.getCustomAttribute("COCOA_WINDOW", &cocoaWindow);
  578. [mData->cursorManager
  579. performSelectorOnMainThread:@selector(clipCursorToWindow:)
  580. withObject:[NSValue valueWithPointer:cocoaWindow]
  581. waitUntilDone:NO];
  582. }
  583. void Platform::clipCursorToRect(const Rect2I& screenRect)
  584. {
  585. [mData->cursorManager
  586. performSelectorOnMainThread:@selector(clipCursorToRect:)
  587. withObject:[NSValue value:&screenRect withObjCType:@encode(Rect2I)]
  588. waitUntilDone:NO];
  589. }
  590. void Platform::clipCursorDisable()
  591. {
  592. [mData->cursorManager
  593. performSelectorOnMainThread:@selector(clipCursorDisable)
  594. withObject:nil
  595. waitUntilDone:NO];
  596. }
  597. void Platform::setCursor(PixelData& pixelData, const Vector2I& hotSpot)
  598. { @autoreleasepool {
  599. NSImage* image = MacOSPlatform::createNSImage(pixelData);
  600. NSPoint point = NSMakePoint(hotSpot.x, hotSpot.y);
  601. NSCursor* cursor = [[NSCursor alloc] initWithImage:image hotSpot:point];
  602. NSArray* params = @[cursor, [NSValue valueWithPoint:point]];
  603. [mData->cursorManager
  604. performSelectorOnMainThread:@selector(setCursor:) withObject:params waitUntilDone:NO];
  605. }}
  606. void Platform::setIcon(const PixelData& pixelData)
  607. { @autoreleasepool {
  608. NSImage* image = MacOSPlatform::createNSImage(pixelData);
  609. [NSApp performSelectorOnMainThread:@selector(setApplicationIconImage:) withObject:image waitUntilDone:NO];
  610. }}
  611. void Platform::setCaptionNonClientAreas(const ct::RenderWindow& window, const Vector<Rect2I>& nonClientAreas)
  612. { @autoreleasepool {
  613. NSMutableArray* params = [[NSMutableArray alloc] init];
  614. UINT32 windowId;
  615. window.getCustomAttribute("WINDOW_ID", &windowId);
  616. NSValue* windowIdValue = [NSValue valueWithBytes:&windowId objCType:@encode(UINT32)];
  617. [params addObject:windowIdValue];
  618. for(auto& entry : nonClientAreas)
  619. [params addObject:[NSValue value:&entry withObjCType:@encode(bs::Rect2I)]];
  620. [mData->platformManager
  621. performSelectorOnMainThread:@selector(setCaptionNonClientAreas:)
  622. withObject:params
  623. waitUntilDone:NO];
  624. }}
  625. void Platform::setResizeNonClientAreas(const ct::RenderWindow& window, const Vector<NonClientResizeArea>& nonClientAreas)
  626. {
  627. // Do nothing, custom resize areas not needed on MacOS
  628. }
  629. void Platform::resetNonClientAreas(const ct::RenderWindow& window)
  630. {
  631. UINT32 windowId;
  632. window.getCustomAttribute("WINDOW_ID", &windowId);
  633. NSValue* windowIdValue = [NSValue valueWithBytes:&windowId objCType:@encode(UINT32)];
  634. [mData->platformManager
  635. performSelectorOnMainThread:@selector(resetNonClientAreas:)
  636. withObject:windowIdValue
  637. waitUntilDone:NO];
  638. }
  639. void Platform::sleep(UINT32 duration)
  640. {
  641. usleep(duration * 1000);
  642. }
  643. void Platform::copyToClipboard(const WString& string)
  644. { @autoreleasepool {
  645. String utf8String = UTF8::fromWide(string);
  646. NSString* text = [NSString stringWithUTF8String:utf8String.c_str()];
  647. [mData->platformManager performSelectorOnMainThread:@selector(setClipboardText:)
  648. withObject:text
  649. waitUntilDone:NO];
  650. }}
  651. WString Platform::copyFromClipboard()
  652. {
  653. Lock lock(mData->clipboardMutex);
  654. return mData->cachedClipboardData;
  655. }
  656. WString Platform::keyCodeToUnicode(UINT32 buttonCode)
  657. {
  658. UINT32 keyCode = ButtonCodeToKeyCode[buttonCode];
  659. TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
  660. if(!currentKeyboard)
  661. return L"";
  662. auto layoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData);
  663. CFRelease(currentKeyboard);
  664. if(!layoutData)
  665. {
  666. currentKeyboard = TISCopyCurrentASCIICapableKeyboardInputSource();
  667. layoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData);
  668. CFRelease(currentKeyboard);
  669. }
  670. if(!layoutData)
  671. return L"";
  672. auto keyLayout = (const UCKeyboardLayout*)CFDataGetBytePtr(layoutData);
  673. UINT32 keysDown = 0;
  674. UniChar chars[4];
  675. UniCharCount length = 0;
  676. UCKeyTranslate(
  677. keyLayout,
  678. (unsigned short)keyCode,
  679. kUCKeyActionDisplay,
  680. 0,
  681. LMGetKbdType(),
  682. kUCKeyTranslateNoDeadKeysBit,
  683. &keysDown,
  684. sizeof(chars) / sizeof(chars[0]),
  685. &length,
  686. chars);
  687. U16String u16String((char16_t*)chars, (size_t)length);
  688. String utf8String = UTF8::fromUTF16(u16String);
  689. return UTF8::toWide(utf8String);
  690. }
  691. void Platform::openFolder(const Path& path)
  692. {
  693. String pathStr = path.toString();
  694. NSURL* url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:pathStr.c_str()]];
  695. [mData->platformManager
  696. performSelectorOnMainThread:@selector(openFolder:)
  697. withObject:url
  698. waitUntilDone:NO];
  699. }
  700. void Platform::_startUp()
  701. {
  702. initKeyCodeMapping();
  703. mData->appDelegate = [[BSAppDelegate alloc] init];
  704. mData->cursorManager = [[BSCursor alloc] initWithPlatformData:mData];
  705. mData->platformManager = [[BSPlatform alloc] initWithPlatformData:mData];
  706. [BSApplication sharedApplication];
  707. [NSApp setDelegate:mData->appDelegate];
  708. [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
  709. createApplicationMenu();
  710. [NSApp finishLaunching];
  711. }
  712. void Platform::_update()
  713. {
  714. CocoaDragAndDrop::update();
  715. {
  716. Lock lock(mData->cursorMutex);
  717. mData->cursorPos = [mData->cursorManager getPosition];
  718. }
  719. INT32 changeCount = [mData->platformManager getClipboardChangeCount];
  720. if(mData->clipboardChangeCount != changeCount)
  721. {
  722. NSString* string = [mData->platformManager getClipboardText];
  723. String utf8String;
  724. if(string)
  725. utf8String = [string UTF8String];
  726. {
  727. Lock lock(mData->clipboardMutex);
  728. mData->cachedClipboardData = UTF8::toWide(utf8String);
  729. }
  730. mData->clipboardChangeCount = changeCount;
  731. }
  732. _messagePump();
  733. }
  734. void Platform::_coreUpdate()
  735. {
  736. // Do nothing
  737. }
  738. void Platform::_shutDown()
  739. {
  740. // Do nothing
  741. }
  742. void Platform::_messagePump()
  743. { @autoreleasepool {
  744. while(true)
  745. {
  746. if(!mData->modalWindows.empty())
  747. {
  748. NSModalSession session = mData->modalWindows.back().session;
  749. [NSApp runModalSession:session];
  750. break;
  751. }
  752. else
  753. {
  754. NSEvent* event = [NSApp
  755. nextEventMatchingMask:NSEventMaskAny
  756. untilDate:[NSDate distantPast]
  757. inMode:NSDefaultRunLoopMode
  758. dequeue:YES];
  759. if (!event)
  760. break;
  761. [NSApp sendEvent:event];
  762. }
  763. }
  764. }}
  765. void MacOSPlatform::registerWindow(CocoaWindow* window)
  766. {
  767. // First window is assumed to be main
  768. if(!mData->mainWindow)
  769. mData->mainWindow = window;
  770. CocoaWindow::Pimpl* windowData = window->_getPrivateData();
  771. if(windowData->isModal)
  772. {
  773. ModalWindowInfo info = { window->_getWindowId(), windowData->modalSession };
  774. mData->modalWindows.push_back(info);
  775. }
  776. Lock lock(mData->windowMutex);
  777. mData->allWindows[window->_getWindowId()] = window;
  778. }
  779. void MacOSPlatform::unregisterWindow(CocoaWindow* window)
  780. {
  781. CocoaWindow::Pimpl* windowData = window->_getPrivateData();
  782. if(windowData->isModal)
  783. {
  784. UINT32 windowId = window->_getWindowId();
  785. auto iterFind = std::find_if(mData->modalWindows.begin(), mData->modalWindows.end(),
  786. [windowId](const ModalWindowInfo& x)
  787. {
  788. return x.windowId == windowId;
  789. });
  790. if(iterFind != mData->modalWindows.end())
  791. mData->modalWindows.erase(iterFind);
  792. }
  793. Lock lock(mData->windowMutex);
  794. mData->allWindows.erase(window->_getWindowId());
  795. [mData->cursorManager unregisterWindow:windowData->window];
  796. // Shut down app when the main window is closed
  797. if(mData->mainWindow == window)
  798. {
  799. bs::gCoreApplication().quitRequested();
  800. mData->mainWindow = nullptr;
  801. }
  802. }
  803. void MacOSPlatform::lockWindows()
  804. {
  805. mData->windowMutex.lock();
  806. }
  807. void MacOSPlatform::unlockWindows()
  808. {
  809. mData->windowMutex.unlock();
  810. }
  811. CocoaWindow* MacOSPlatform::getWindow(UINT32 windowId)
  812. {
  813. auto iterFind = mData->allWindows.find(windowId);
  814. if(iterFind == mData->allWindows.end())
  815. return nullptr;
  816. return iterFind->second;
  817. }
  818. NSImage* MacOSPlatform::createNSImage(const PixelData& data)
  819. {
  820. // Premultiply alpha
  821. Vector<Color> colors = data.getColors();
  822. for(auto& color : colors)
  823. {
  824. color.r *= color.a;
  825. color.g *= color.a;
  826. color.b *= color.a;
  827. }
  828. // Convert to RGBA
  829. SPtr<PixelData> rgbaData = PixelData::create(data.getWidth(), data.getHeight(), 1, PF_RGBA8);
  830. rgbaData->setColors(colors);
  831. @autoreleasepool
  832. {
  833. INT32 pitch = data.getWidth() * sizeof(UINT32);
  834. NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc]
  835. initWithBitmapDataPlanes:nullptr
  836. pixelsWide:data.getWidth()
  837. pixelsHigh:data.getHeight()
  838. bitsPerSample:8
  839. samplesPerPixel:4
  840. hasAlpha:YES
  841. isPlanar:NO
  842. colorSpaceName:NSDeviceRGBColorSpace
  843. bytesPerRow:pitch
  844. bitsPerPixel:32];
  845. unsigned char* pixels = [imageRep bitmapData];
  846. memcpy(pixels, rgbaData->getData(), data.getHeight() * pitch);
  847. NSImage* image = [[NSImage alloc] initWithSize:NSMakeSize(data.getWidth(), data.getHeight())];
  848. [image addRepresentation:imageRep];
  849. return image;
  850. }
  851. }
  852. void MacOSPlatform::sendInputCommandEvent(InputCommandType inputCommand)
  853. {
  854. onInputCommand(inputCommand);
  855. }
  856. void MacOSPlatform::sendCharInputEvent(UINT32 character)
  857. {
  858. onCharInput(character);
  859. }
  860. void MacOSPlatform::sendPointerButtonPressedEvent(
  861. const Vector2I& pos,
  862. OSMouseButton button,
  863. const OSPointerButtonStates& buttonStates)
  864. {
  865. onCursorButtonPressed(pos, button, buttonStates);
  866. }
  867. void MacOSPlatform::sendPointerButtonReleasedEvent(
  868. const Vector2I& pos,
  869. OSMouseButton button,
  870. const OSPointerButtonStates& buttonStates)
  871. {
  872. onCursorButtonReleased(pos, button, buttonStates);
  873. }
  874. void MacOSPlatform::sendPointerDoubleClickEvent(const Vector2I& pos, const OSPointerButtonStates& buttonStates)
  875. {
  876. onCursorDoubleClick(pos, buttonStates);
  877. }
  878. void MacOSPlatform::sendPointerMovedEvent(const Vector2I& pos, const OSPointerButtonStates& buttonStates)
  879. {
  880. onCursorMoved(pos, buttonStates);
  881. }
  882. void MacOSPlatform::sendMouseWheelScrollEvent(float delta)
  883. {
  884. onMouseWheelScrolled(delta);
  885. }
  886. void MacOSPlatform::notifyWindowEvent(bs::WindowEventType type, bs::UINT32 windowId)
  887. {
  888. CocoaWindow* window = nullptr;
  889. {
  890. auto iterFind = mData->allWindows.find(windowId);
  891. if(iterFind == mData->allWindows.end())
  892. return;
  893. window = iterFind->second;
  894. }
  895. auto renderWindow = (RenderWindow*)window->_getUserData();
  896. if(renderWindow == nullptr)
  897. {
  898. // If it's a render window we allow the client code to handle the message, otherwise we just destroy it
  899. if(type == WindowEventType::CloseRequested)
  900. window->_destroy();
  901. return;
  902. }
  903. renderWindow->_notifyWindowEvent(type);
  904. }
  905. NSCursor* MacOSPlatform::_getCurrentCursor()
  906. {
  907. return [mData->cursorManager currentCursor];
  908. }
  909. bool MacOSPlatform::_clipCursor(Vector2I& pos)
  910. {
  911. return [mData->cursorManager clipCursor:pos];
  912. }
  913. void MacOSPlatform::_updateClipBounds(NSWindow* window)
  914. {
  915. [mData->cursorManager updateClipBounds:window];
  916. }
  917. void MacOSPlatform::_setCursorPosition(const Vector2I& position)
  918. {
  919. [mData->cursorManager setPosition:position];
  920. }
  921. }