BsMacOSPlatform.mm 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969
  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];
  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. void flipY(NSScreen* screen, NSRect& rect)
  288. {
  289. NSRect screenFrame = [screen frame];
  290. rect.origin.y = screenFrame.size.height - (rect.origin.y + rect.size.height);
  291. }
  292. void flipY(NSScreen* screen, NSPoint &point)
  293. {
  294. NSRect screenFrame = [screen frame];
  295. point.y = screenFrame.size.height - point.y;
  296. }
  297. void flipYWindow(NSWindow* window, NSPoint &point)
  298. {
  299. NSRect windowFrame = [window frame];
  300. point.y = windowFrame.size.height - point.y;
  301. }
  302. /** Returns the name of the current application based on the information in the app. bundle. */
  303. static NSString* getAppName()
  304. {
  305. NSString* appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
  306. if (!appName)
  307. appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
  308. if (![appName length]) {
  309. appName = [[NSProcessInfo processInfo] processName];
  310. }
  311. return appName;
  312. }
  313. /** Creates the default menu for the application menu bar. */
  314. static void createApplicationMenu()
  315. { @autoreleasepool {
  316. NSMenu* mainMenu = [[NSMenu alloc] init];
  317. [NSApp setMainMenu:mainMenu];
  318. NSString* appName = getAppName();
  319. NSMenu* appleMenu = [[NSMenu alloc] initWithTitle:@""];
  320. NSString* aboutTitle = [@"About " stringByAppendingString:appName];
  321. [appleMenu addItemWithTitle:aboutTitle
  322. action:@selector(orderFrontStandardAboutPanel:)
  323. keyEquivalent:@""];
  324. [appleMenu addItem:[NSMenuItem separatorItem]];
  325. NSString* hideTitle = [@"Hide " stringByAppendingString:appName];
  326. [appleMenu addItemWithTitle:hideTitle action:@selector(hide:) keyEquivalent:@"h"];
  327. NSMenuItem* hideOthersMenuItem = [appleMenu
  328. addItemWithTitle:@"Hide Others"
  329. action:@selector(hideOtherApplications:)
  330. keyEquivalent:@"h"];
  331. [hideOthersMenuItem setKeyEquivalentModifierMask:(NSEventModifierFlagOption|NSEventModifierFlagCommand)];
  332. [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
  333. [appleMenu addItem:[NSMenuItem separatorItem]];
  334. NSString* quitTitle = [@"Quit " stringByAppendingString:appName];
  335. [appleMenu addItemWithTitle:quitTitle action:@selector(terminate:) keyEquivalent:@"q"];
  336. NSMenuItem* appleMenuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
  337. [appleMenuItem setSubmenu:appleMenu];
  338. [[NSApp mainMenu] addItem:appleMenuItem];
  339. }}
  340. Event<void(const Vector2I &, const OSPointerButtonStates &)> Platform::onCursorMoved;
  341. Event<void(const Vector2I &, OSMouseButton button, const OSPointerButtonStates &)> Platform::onCursorButtonPressed;
  342. Event<void(const Vector2I &, OSMouseButton button, const OSPointerButtonStates &)> Platform::onCursorButtonReleased;
  343. Event<void(const Vector2I &, const OSPointerButtonStates &)> Platform::onCursorDoubleClick;
  344. Event<void(InputCommandType)> Platform::onInputCommand;
  345. Event<void(float)> Platform::onMouseWheelScrolled;
  346. Event<void(UINT32)> Platform::onCharInput;
  347. Event<void()> Platform::onMouseCaptureChanged;
  348. Platform::Pimpl* Platform::mData = bs_new<Platform::Pimpl>();
  349. Platform::~Platform()
  350. {
  351. }
  352. Vector2I Platform::getCursorPosition()
  353. {
  354. Lock lock(mData->cursorMutex);
  355. return mData->cursorPos;
  356. }
  357. void Platform::setCursorPosition(const Vector2I& screenPos)
  358. {
  359. [mData->cursorManager setPosition:screenPos];
  360. }
  361. void Platform::captureMouse(const RenderWindow& window)
  362. {
  363. // Do nothing
  364. }
  365. void Platform::releaseMouseCapture()
  366. {
  367. // Do nothing
  368. }
  369. bool Platform::isPointOverWindow(const RenderWindow& window, const Vector2I& screenPos)
  370. {
  371. CFArrayRef windowDicts = CGWindowListCopyWindowInfo(
  372. kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements,
  373. kCGNullWindowID);
  374. if(!windowDicts)
  375. return nil;
  376. CocoaWindow* cocoaWindow;
  377. window.getCustomAttribute("COCOA_WINDOW", &cocoaWindow);
  378. int32_t requestedWindowNumber = (int32_t)[cocoaWindow->_getPrivateData()->window windowNumber];
  379. CGPoint point = CGPointMake(screenPos.x, screenPos.y);
  380. CFIndex numEntries = CFArrayGetCount(windowDicts);
  381. for(CFIndex i = 0; i < numEntries; i++)
  382. {
  383. CFDictionaryRef dict = (CFDictionaryRef)CFArrayGetValueAtIndex(windowDicts, i);
  384. CFNumberRef layerRef = (CFNumberRef) CFDictionaryGetValue(dict, kCGWindowLayer);
  385. if(!layerRef)
  386. continue;
  387. // Ignore windows outside of layer 0, as those appear to be desktop elements
  388. int32_t layer;
  389. CFNumberGetValue(layerRef, kCFNumberIntType, &layer);
  390. // Layer 0 appear to be normal windows
  391. // Layer 25 appear to be fullscreen windows
  392. // Note: This is based on experimentation, as no documentation about it exists
  393. if(layer != 0 && layer != 25)
  394. continue;
  395. CFDictionaryRef boundsRef = (CFDictionaryRef)CFDictionaryGetValue(dict, kCGWindowBounds);
  396. CGRect rect;
  397. CGRectMakeWithDictionaryRepresentation(boundsRef, &rect);
  398. if(CGRectContainsPoint(rect, point))
  399. {
  400. // Windows are ordered front to back intrinsically, so the first one we are within bounds of is the one we want
  401. CFNumberRef windowNumRef = (CFNumberRef)CFDictionaryGetValue(dict, kCGWindowNumber);
  402. int32_t windowNumber;
  403. CFNumberGetValue(windowNumRef, kCGWindowIDCFNumberType, &windowNumber);
  404. return requestedWindowNumber == windowNumber;
  405. }
  406. }
  407. return false;
  408. }
  409. void Platform::hideCursor()
  410. {
  411. Lock lock(mData->cursorMutex);
  412. if(!mData->cursorIsHidden)
  413. {
  414. [NSCursor performSelectorOnMainThread:@selector(hide) withObject:nil waitUntilDone:NO];
  415. mData->cursorIsHidden = true;
  416. }
  417. }
  418. void Platform::showCursor()
  419. {
  420. Lock lock(mData->cursorMutex);
  421. if(mData->cursorIsHidden)
  422. {
  423. [NSCursor performSelectorOnMainThread:@selector(unhide) withObject:nil waitUntilDone:NO];
  424. mData->cursorIsHidden = false;
  425. }
  426. }
  427. bool Platform::isCursorHidden()
  428. {
  429. Lock lock(mData->cursorMutex);
  430. return mData->cursorIsHidden;
  431. }
  432. void Platform::clipCursorToWindow(const RenderWindow& window)
  433. {
  434. CocoaWindow* cocoaWindow;
  435. window.getCustomAttribute("COCOA_WINDOW", &cocoaWindow);
  436. [mData->cursorManager
  437. performSelectorOnMainThread:@selector(clipCursorToWindow:)
  438. withObject:[NSValue valueWithPointer:cocoaWindow]
  439. waitUntilDone:NO];
  440. }
  441. void Platform::clipCursorToRect(const Rect2I& screenRect)
  442. {
  443. [mData->cursorManager
  444. performSelectorOnMainThread:@selector(clipCursorToRect:)
  445. withObject:[NSValue value:&screenRect withObjCType:@encode(Rect2I)]
  446. waitUntilDone:NO];
  447. }
  448. void Platform::clipCursorDisable()
  449. {
  450. [mData->cursorManager
  451. performSelectorOnMainThread:@selector(clipCursorDisable)
  452. withObject:nil
  453. waitUntilDone:NO];
  454. }
  455. void Platform::setCursor(PixelData& pixelData, const Vector2I& hotSpot)
  456. { @autoreleasepool {
  457. NSImage* image = MacOSPlatform::createNSImage(pixelData);
  458. NSPoint point = NSMakePoint(hotSpot.x, hotSpot.y);
  459. NSCursor* cursor = [[NSCursor alloc] initWithImage:image hotSpot:point];
  460. NSArray* params = @[cursor, [NSValue valueWithPoint:point]];
  461. [mData->cursorManager
  462. performSelectorOnMainThread:@selector(setCursor:) withObject:params waitUntilDone:NO];
  463. }}
  464. void Platform::setIcon(const PixelData& pixelData)
  465. { @autoreleasepool {
  466. NSImage* image = MacOSPlatform::createNSImage(pixelData);
  467. [NSApp performSelectorOnMainThread:@selector(setApplicationIconImage:) withObject:image waitUntilDone:NO];
  468. }}
  469. void Platform::setCaptionNonClientAreas(const ct::RenderWindow& window, const Vector<Rect2I>& nonClientAreas)
  470. { @autoreleasepool {
  471. NSMutableArray* params = [[NSMutableArray alloc] init];
  472. UINT32 windowId;
  473. window.getCustomAttribute("WINDOW_ID", &windowId);
  474. NSValue* windowIdValue = [NSValue valueWithBytes:&windowId objCType:@encode(UINT32)];
  475. [params addObject:windowIdValue];
  476. for(auto& entry : nonClientAreas)
  477. [params addObject:[NSValue value:&entry withObjCType:@encode(bs::Rect2I)]];
  478. [mData->platformManager
  479. performSelectorOnMainThread:@selector(setCaptionNonClientAreas:)
  480. withObject:params
  481. waitUntilDone:NO];
  482. }}
  483. void Platform::setResizeNonClientAreas(const ct::RenderWindow& window, const Vector<NonClientResizeArea>& nonClientAreas)
  484. {
  485. // Do nothing, custom resize areas not needed on MacOS
  486. }
  487. void Platform::resetNonClientAreas(const ct::RenderWindow& window)
  488. {
  489. UINT32 windowId;
  490. window.getCustomAttribute("WINDOW_ID", &windowId);
  491. NSValue* windowIdValue = [NSValue valueWithBytes:&windowId objCType:@encode(UINT32)];
  492. [mData->platformManager
  493. performSelectorOnMainThread:@selector(resetNonClientAreas:)
  494. withObject:windowIdValue
  495. waitUntilDone:NO];
  496. }
  497. void Platform::sleep(UINT32 duration)
  498. {
  499. usleep(duration * 1000);
  500. }
  501. void Platform::copyToClipboard(const WString& string)
  502. { @autoreleasepool {
  503. String utf8String = UTF8::fromWide(string);
  504. NSString* text = [NSString stringWithUTF8String:utf8String.c_str()];
  505. [mData->platformManager performSelectorOnMainThread:@selector(setClipboardText:)
  506. withObject:text
  507. waitUntilDone:NO];
  508. }}
  509. WString Platform::copyFromClipboard()
  510. {
  511. Lock lock(mData->clipboardMutex);
  512. return mData->cachedClipboardData;
  513. }
  514. WString Platform::keyCodeToUnicode(UINT32 keyCode)
  515. {
  516. TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
  517. CFDataRef layoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData);
  518. const UCKeyboardLayout* keyLayout = (const UCKeyboardLayout*)CFDataGetBytePtr(layoutData);
  519. UINT32 keysDown = 0;
  520. UniChar chars[4];
  521. UniCharCount length = 0;
  522. UCKeyTranslate(
  523. keyLayout,
  524. (unsigned short)keyCode,
  525. kUCKeyActionDisplay,
  526. 0,
  527. LMGetKbdType(),
  528. kUCKeyTranslateNoDeadKeysBit,
  529. &keysDown,
  530. sizeof(chars) / sizeof(chars[0]),
  531. &length,
  532. chars);
  533. CFRelease(keyLayout);
  534. U16String u16String((char16_t*)chars, (size_t)length);
  535. String utf8String = UTF8::fromUTF16(u16String);
  536. return UTF8::toWide(utf8String);
  537. }
  538. void Platform::openFolder(const Path& path)
  539. {
  540. String pathStr = path.toString();
  541. NSURL* url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:pathStr.c_str()]];
  542. [mData->platformManager
  543. performSelectorOnMainThread:@selector(openFolder:)
  544. withObject:url
  545. waitUntilDone:NO];
  546. }
  547. void Platform::_startUp()
  548. {
  549. mData->appDelegate = [[BSAppDelegate alloc] init];
  550. mData->cursorManager = [[BSCursor alloc] initWithPlatformData:mData];
  551. mData->platformManager = [[BSPlatform alloc] initWithPlatformData:mData];
  552. [BSApplication sharedApplication];
  553. [NSApp setDelegate:mData->appDelegate];
  554. [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
  555. createApplicationMenu();
  556. [NSApp finishLaunching];
  557. }
  558. void Platform::_update()
  559. {
  560. CocoaDragAndDrop::update();
  561. {
  562. Lock lock(mData->cursorMutex);
  563. mData->cursorPos = [mData->cursorManager getPosition];
  564. }
  565. INT32 changeCount = [mData->platformManager getClipboardChangeCount];
  566. if(mData->clipboardChangeCount != changeCount)
  567. {
  568. NSString* string = [mData->platformManager getClipboardText];
  569. String utf8String;
  570. if(string)
  571. utf8String = [string UTF8String];
  572. {
  573. Lock lock(mData->clipboardMutex);
  574. mData->cachedClipboardData = UTF8::toWide(utf8String);
  575. }
  576. mData->clipboardChangeCount = changeCount;
  577. }
  578. _messagePump();
  579. }
  580. void Platform::_coreUpdate()
  581. {
  582. // Do nothing
  583. }
  584. void Platform::_shutDown()
  585. {
  586. // Do nothing
  587. }
  588. void Platform::_messagePump()
  589. { @autoreleasepool {
  590. while(true)
  591. {
  592. if(!mData->modalWindows.empty())
  593. {
  594. NSModalSession session = mData->modalWindows.back().session;
  595. if([NSApp runModalSession:session] != NSModalResponseContinue)
  596. break;
  597. }
  598. else
  599. {
  600. NSEvent* event = [NSApp
  601. nextEventMatchingMask:NSEventMaskAny
  602. untilDate:[NSDate distantPast]
  603. inMode:NSDefaultRunLoopMode
  604. dequeue:YES];
  605. if (!event)
  606. break;
  607. [NSApp sendEvent:event];
  608. }
  609. }
  610. }}
  611. void MacOSPlatform::registerWindow(CocoaWindow* window)
  612. {
  613. // First window is assumed to be main
  614. if(!mData->mainWindow)
  615. mData->mainWindow = window;
  616. CocoaWindow::Pimpl* windowData = window->_getPrivateData();
  617. if(windowData->isModal)
  618. {
  619. ModalWindowInfo info = { window->_getWindowId(), windowData->modalSession };
  620. mData->modalWindows.push_back(info);
  621. }
  622. Lock lock(mData->windowMutex);
  623. mData->allWindows[window->_getWindowId()] = window;
  624. }
  625. void MacOSPlatform::unregisterWindow(CocoaWindow* window)
  626. {
  627. CocoaWindow::Pimpl* windowData = window->_getPrivateData();
  628. if(windowData->isModal)
  629. {
  630. UINT32 windowId = window->_getWindowId();
  631. auto iterFind = std::find_if(mData->modalWindows.begin(), mData->modalWindows.end(),
  632. [windowId](const ModalWindowInfo& x)
  633. {
  634. return x.windowId == windowId;
  635. });
  636. if(iterFind != mData->modalWindows.end())
  637. mData->modalWindows.erase(iterFind);
  638. }
  639. Lock lock(mData->windowMutex);
  640. mData->allWindows.erase(window->_getWindowId());
  641. [mData->cursorManager unregisterWindow:windowData->window];
  642. // Shut down app when the main window is closed
  643. if(mData->mainWindow == window)
  644. {
  645. bs::gCoreApplication().quitRequested();
  646. mData->mainWindow = nullptr;
  647. }
  648. }
  649. void MacOSPlatform::lockWindows()
  650. {
  651. mData->windowMutex.lock();
  652. }
  653. void MacOSPlatform::unlockWindows()
  654. {
  655. mData->windowMutex.unlock();
  656. }
  657. CocoaWindow* MacOSPlatform::getWindow(UINT32 windowId)
  658. {
  659. auto iterFind = mData->allWindows.find(windowId);
  660. if(iterFind == mData->allWindows.end())
  661. return nullptr;
  662. return iterFind->second;
  663. }
  664. NSImage* MacOSPlatform::createNSImage(const PixelData& data)
  665. {
  666. // Premultiply alpha
  667. Vector<Color> colors = data.getColors();
  668. for(auto& color : colors)
  669. {
  670. color.r *= color.a;
  671. color.g *= color.a;
  672. color.b *= color.a;
  673. }
  674. // Convert to RGBA
  675. SPtr<PixelData> rgbaData = PixelData::create(data.getWidth(), data.getHeight(), 1, PF_RGBA8);
  676. rgbaData->setColors(colors);
  677. @autoreleasepool
  678. {
  679. INT32 pitch = data.getWidth() * sizeof(UINT32);
  680. NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc]
  681. initWithBitmapDataPlanes:nullptr
  682. pixelsWide:data.getWidth()
  683. pixelsHigh:data.getHeight()
  684. bitsPerSample:8
  685. samplesPerPixel:4
  686. hasAlpha:YES
  687. isPlanar:NO
  688. colorSpaceName:NSDeviceRGBColorSpace
  689. bytesPerRow:pitch
  690. bitsPerPixel:32];
  691. unsigned char* pixels = [imageRep bitmapData];
  692. memcpy(pixels, rgbaData->getData(), data.getHeight() * pitch);
  693. NSImage* image = [[NSImage alloc] initWithSize:NSMakeSize(data.getWidth(), data.getHeight())];
  694. [image addRepresentation:imageRep];
  695. return image;
  696. }
  697. }
  698. void MacOSPlatform::sendInputCommandEvent(InputCommandType inputCommand)
  699. {
  700. onInputCommand(inputCommand);
  701. }
  702. void MacOSPlatform::sendCharInputEvent(UINT32 character)
  703. {
  704. onCharInput(character);
  705. }
  706. void MacOSPlatform::sendPointerButtonPressedEvent(
  707. const Vector2I& pos,
  708. OSMouseButton button,
  709. const OSPointerButtonStates& buttonStates)
  710. {
  711. onCursorButtonPressed(pos, button, buttonStates);
  712. }
  713. void MacOSPlatform::sendPointerButtonReleasedEvent(
  714. const Vector2I& pos,
  715. OSMouseButton button,
  716. const OSPointerButtonStates& buttonStates)
  717. {
  718. onCursorButtonReleased(pos, button, buttonStates);
  719. }
  720. void MacOSPlatform::sendPointerDoubleClickEvent(const Vector2I& pos, const OSPointerButtonStates& buttonStates)
  721. {
  722. onCursorDoubleClick(pos, buttonStates);
  723. }
  724. void MacOSPlatform::sendPointerMovedEvent(const Vector2I& pos, const OSPointerButtonStates& buttonStates)
  725. {
  726. onCursorMoved(pos, buttonStates);
  727. }
  728. void MacOSPlatform::sendMouseWheelScrollEvent(float delta)
  729. {
  730. onMouseWheelScrolled(delta);
  731. }
  732. void MacOSPlatform::notifyWindowEvent(bs::WindowEventType type, bs::UINT32 windowId)
  733. {
  734. CocoaWindow* window = nullptr;
  735. {
  736. auto iterFind = mData->allWindows.find(windowId);
  737. if(iterFind == mData->allWindows.end())
  738. return;
  739. window = iterFind->second;
  740. }
  741. auto renderWindow = (RenderWindow*)window->_getUserData();
  742. if(renderWindow == nullptr)
  743. {
  744. // If it's a render window we allow the client code to handle the message, otherwise we just destroy it
  745. if(type == WindowEventType::CloseRequested)
  746. window->_destroy();
  747. return;
  748. }
  749. renderWindow->_notifyWindowEvent(type);
  750. }
  751. NSCursor* MacOSPlatform::_getCurrentCursor()
  752. {
  753. return [mData->cursorManager currentCursor];
  754. }
  755. bool MacOSPlatform::_clipCursor(Vector2I& pos)
  756. {
  757. return [mData->cursorManager clipCursor:pos];
  758. }
  759. void MacOSPlatform::_updateClipBounds(NSWindow* window)
  760. {
  761. [mData->cursorManager updateClipBounds:window];
  762. }
  763. void MacOSPlatform::_setCursorPosition(const Vector2I& position)
  764. {
  765. [mData->cursorManager setPosition:position];
  766. }
  767. }