BsMacOSPlatform.mm 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912
  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 "MacOS/BsMacOSPlatform.h"
  5. #include "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 "MacOS/BsMacOSDropTarget.h"
  11. #include "String/BsUnicode.h"
  12. #include "BsCoreApplication.h"
  13. #include <atomic>
  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 currently open modal window. */
  53. struct ModalWindowInfo
  54. {
  55. CocoaWindow* window;
  56. NSModalSession session;
  57. };
  58. struct Platform::Pimpl
  59. {
  60. BSAppDelegate* appDelegate = nil;
  61. CocoaWindow* mainWindow = nullptr;
  62. Vector<CocoaWindow*> allWindows;
  63. Vector<ModalWindowInfo> modalWindows;
  64. BSPlatform* platformManager = nil;
  65. // Cursor
  66. BSCursor* cursorManager = nil;
  67. Mutex cursorMutex;
  68. bool cursorIsHidden = false;
  69. Vector2I cursorPos;
  70. // Clipboard
  71. Mutex clipboardMutex;
  72. WString cachedClipboardData;
  73. INT32 clipboardChangeCount = -1;
  74. };
  75. }
  76. /**
  77. * Contains cursor specific functionality. Encapsulated in objective C so its selectors can be triggered from other
  78. * threads.
  79. */
  80. @interface BSCursor : NSObject
  81. @property NSCursor* currentCursor;
  82. -(BSCursor*) initWithPlatformData:(bs::Platform::Pimpl*)platformData;
  83. -(bs::Vector2I) getPosition;
  84. -(void) setPosition:(const bs::Vector2I&) position;
  85. -(BOOL) clipCursor:(bs::Vector2I&) position;
  86. -(void) updateClipBounds:(NSWindow*) window;
  87. -(void) clipCursorToWindow:(NSValue*) windowValue;
  88. -(void) clipCursorToRect:(NSValue*) rectValue;
  89. -(void) clipCursorDisable;
  90. -(void) setCursor:(NSArray*) params;
  91. -(void) unregisterWindow:(NSWindow*) window;
  92. @end
  93. @implementation BSCursor
  94. {
  95. bs::Platform::Pimpl* platformData;
  96. bool cursorClipEnabled;
  97. bs::Rect2I cursorClipRect;
  98. NSWindow* cursorClipWindow;
  99. }
  100. - (BSCursor*)initWithPlatformData:(bs::Platform::Pimpl*)data
  101. {
  102. self = [super init];
  103. platformData = data;
  104. return self;
  105. }
  106. - (bs::Vector2I)getPosition
  107. {
  108. NSPoint point = [NSEvent mouseLocation];
  109. for (NSScreen* screen in [NSScreen screens])
  110. {
  111. NSRect frame = [screen frame];
  112. if (NSMouseInRect(point, frame, NO))
  113. bs::flipY(screen, point);
  114. }
  115. bs::Vector2I output;
  116. output.x = (int32_t)point.x;
  117. output.y = (int32_t)point.y;
  118. return output;
  119. }
  120. - (void)setPosition:(const bs::Vector2I&)position
  121. {
  122. NSPoint point = NSMakePoint(position.x, position.y);
  123. CGWarpMouseCursorPosition(point);
  124. Lock lock(platformData->cursorMutex);
  125. platformData->cursorPos = position;
  126. }
  127. - (BOOL)clipCursor:(bs::Vector2I&)position
  128. {
  129. if(!cursorClipEnabled)
  130. return false;
  131. int32_t clippedX = position.x - cursorClipRect.x;
  132. int32_t clippedY = position.y - cursorClipRect.y;
  133. if(clippedX < 0)
  134. clippedX = 0;
  135. else if(clippedX >= (int32_t)cursorClipRect.width)
  136. clippedX = cursorClipRect.width > 0 ? cursorClipRect.width - 1 : 0;
  137. if(clippedY < 0)
  138. clippedY = 0;
  139. else if(clippedY >= (int32_t)cursorClipRect.height)
  140. clippedY = cursorClipRect.height > 0 ? cursorClipRect.height - 1 : 0;
  141. clippedX += cursorClipRect.x;
  142. clippedY += cursorClipRect.y;
  143. if(clippedX != position.x || clippedY != position.y)
  144. {
  145. position.x = clippedX;
  146. position.y = clippedY;
  147. return true;
  148. }
  149. return false;
  150. }
  151. - (void)updateClipBounds:(NSWindow*)window
  152. {
  153. if(!cursorClipEnabled || cursorClipWindow != window)
  154. return;
  155. NSRect rect = [window contentRectForFrameRect:[window frame]];
  156. bs::flipY([window screen], rect);
  157. cursorClipRect.x = (int32_t)rect.origin.x;
  158. cursorClipRect.y = (int32_t)rect.origin.y;
  159. cursorClipRect.width = (uint32_t)rect.size.width;
  160. cursorClipRect.height = (uint32_t)rect.size.height;
  161. }
  162. - (void)clipCursorToWindow:(NSValue*)windowValue
  163. {
  164. bs::CocoaWindow* window;
  165. [windowValue getValue:&window];
  166. cursorClipEnabled = true;
  167. cursorClipWindow = window->_getPrivateData()->window;
  168. [self updateClipBounds:cursorClipWindow];
  169. bs::Vector2I pos = [self getPosition];
  170. if([self clipCursor:pos])
  171. [self setPosition:pos];
  172. }
  173. - (void)clipCursorToRect:(NSValue*)rectValue
  174. {
  175. bs::Rect2I rect;
  176. [rectValue getValue:&rect];
  177. cursorClipEnabled = true;
  178. cursorClipRect = rect;
  179. cursorClipWindow = nullptr;
  180. bs::Vector2I pos = [self getPosition];
  181. if([self clipCursor:pos])
  182. [self setPosition:pos];
  183. }
  184. - (void)clipCursorDisable
  185. {
  186. cursorClipEnabled = false;
  187. cursorClipWindow = nullptr;
  188. }
  189. - (void)setCursor:(NSArray*)params
  190. {
  191. NSCursor* cursor = params[0];
  192. NSValue* hotSpotValue = params[1];
  193. NSPoint hotSpot;
  194. [hotSpotValue getValue:&hotSpot];
  195. [self setCurrentCursor:cursor];
  196. for(auto& entry : platformData->allWindows)
  197. {
  198. NSWindow* window = entry->_getPrivateData()->window;
  199. [window
  200. performSelectorOnMainThread:@selector(invalidateCursorRectsForView:)
  201. withObject:[window contentView]
  202. waitUntilDone:NO];
  203. }
  204. }
  205. - (void)unregisterWindow:(NSWindow*)window
  206. {
  207. if(cursorClipEnabled && cursorClipWindow == window)
  208. [self clipCursorDisable];
  209. }
  210. @end
  211. /** Contains platform specific functionality that is meant to be delayed executed from the sim thread, through Platform. */
  212. @interface BSPlatform : NSObject
  213. -(BSPlatform*) initWithPlatformData:(bs::Platform::Pimpl*)platformData;
  214. -(void) setCaptionNonClientAreas:(NSArray*) params;
  215. -(void) resetNonClientAreas:(NSValue*) windowValue;
  216. -(void) openFolder:(NSURL*) url;
  217. -(void) setClipboardText:(NSString*) text;
  218. -(NSString*) getClipboardText;
  219. -(int32_t) getClipboardChangeCount;
  220. @end
  221. @implementation BSPlatform
  222. {
  223. bs::Platform::Pimpl* mPlatformData;
  224. }
  225. - (BSPlatform*)initWithPlatformData:(bs::Platform::Pimpl*)platformData
  226. {
  227. self = [super init];
  228. mPlatformData = platformData;
  229. return self;
  230. }
  231. - (void)setCaptionNonClientAreas:(NSArray*)params
  232. {
  233. NSValue* windowValue = params[0];
  234. bs::CocoaWindow* window = (bs::CocoaWindow*)[windowValue pointerValue];
  235. NSUInteger numEntries = [params count] - 1;
  236. bs::Vector<bs::Rect2I> areas;
  237. for(NSUInteger i = 0; i < numEntries; i++)
  238. {
  239. NSValue* value = params[i];
  240. bs::Rect2I area;
  241. [value getValue:&area];
  242. areas.push_back(area);
  243. }
  244. window->_setDragZones(areas);
  245. }
  246. - (void)resetNonClientAreas:(NSValue*) windowValue
  247. {
  248. bs::CocoaWindow* window = (bs::CocoaWindow*)[windowValue pointerValue];
  249. window->_setDragZones({});
  250. }
  251. - (void)openFolder:(NSURL*)url
  252. {
  253. [[NSWorkspace sharedWorkspace] openURL:url];
  254. }
  255. - (void) setClipboardText:(NSString*) text
  256. { @autoreleasepool {
  257. NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
  258. [pasteboard clearContents];
  259. NSArray* objects = [NSArray arrayWithObject:text];
  260. [pasteboard writeObjects:objects];
  261. }}
  262. - (NSString*) getClipboardText
  263. { @autoreleasepool {
  264. NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
  265. NSArray* classes = [NSArray arrayWithObjects:[NSString class], nil];
  266. NSDictionary* options = [NSDictionary dictionary];
  267. NSArray* items = [pasteboard readObjectsForClasses:classes options:options];
  268. if(!items)
  269. return nil;
  270. return (NSString*)[items objectAtIndex:0];
  271. }}
  272. - (int32_t)getClipboardChangeCount
  273. {
  274. return (int32_t)[[NSPasteboard generalPasteboard] changeCount];
  275. }
  276. @end
  277. namespace bs
  278. {
  279. void flipY(NSScreen* screen, NSRect& rect)
  280. {
  281. NSRect screenFrame = [screen frame];
  282. rect.origin.y = screenFrame.size.height - (rect.origin.y + rect.size.height);
  283. }
  284. void flipY(NSScreen* screen, NSPoint &point)
  285. {
  286. NSRect screenFrame = [screen frame];
  287. point.y = screenFrame.size.height - point.y;
  288. }
  289. void flipYWindow(NSWindow* window, NSPoint &point)
  290. {
  291. NSRect windowFrame = [window frame];
  292. point.y = windowFrame.size.height - point.y;
  293. }
  294. /** Returns the name of the current application based on the information in the app. bundle. */
  295. static NSString* getAppName()
  296. {
  297. NSString* appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
  298. if (!appName)
  299. appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
  300. if (![appName length]) {
  301. appName = [[NSProcessInfo processInfo] processName];
  302. }
  303. return appName;
  304. }
  305. /** Creates the default menu for the application menu bar. */
  306. static void createApplicationMenu()
  307. { @autoreleasepool {
  308. NSMenu* mainMenu = [[NSMenu alloc] init];
  309. [NSApp setMainMenu:mainMenu];
  310. NSString* appName = getAppName();
  311. NSMenu* appleMenu = [[NSMenu alloc] initWithTitle:@""];
  312. NSString* aboutTitle = [@"About " stringByAppendingString:appName];
  313. [appleMenu addItemWithTitle:aboutTitle
  314. action:@selector(orderFrontStandardAboutPanel:)
  315. keyEquivalent:@""];
  316. [appleMenu addItem:[NSMenuItem separatorItem]];
  317. NSString* hideTitle = [@"Hide " stringByAppendingString:appName];
  318. [appleMenu addItemWithTitle:hideTitle action:@selector(hide:) keyEquivalent:@"h"];
  319. NSMenuItem* hideOthersMenuItem = [appleMenu
  320. addItemWithTitle:@"Hide Others"
  321. action:@selector(hideOtherApplications:)
  322. keyEquivalent:@"h"];
  323. [hideOthersMenuItem setKeyEquivalentModifierMask:(NSEventModifierFlagOption|NSEventModifierFlagCommand)];
  324. [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""];
  325. [appleMenu addItem:[NSMenuItem separatorItem]];
  326. NSString* quitTitle = [@"Quit " stringByAppendingString:appName];
  327. [appleMenu addItemWithTitle:quitTitle action:@selector(terminate:) keyEquivalent:@"q"];
  328. NSMenuItem* appleMenuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
  329. [appleMenuItem setSubmenu:appleMenu];
  330. [[NSApp mainMenu] addItem:appleMenuItem];
  331. }}
  332. Event<void(const Vector2I &, const OSPointerButtonStates &)> Platform::onCursorMoved;
  333. Event<void(const Vector2I &, OSMouseButton button, const OSPointerButtonStates &)> Platform::onCursorButtonPressed;
  334. Event<void(const Vector2I &, OSMouseButton button, const OSPointerButtonStates &)> Platform::onCursorButtonReleased;
  335. Event<void(const Vector2I &, const OSPointerButtonStates &)> Platform::onCursorDoubleClick;
  336. Event<void(InputCommandType)> Platform::onInputCommand;
  337. Event<void(float)> Platform::onMouseWheelScrolled;
  338. Event<void(UINT32)> Platform::onCharInput;
  339. Event<void()> Platform::onMouseCaptureChanged;
  340. Platform::Pimpl* Platform::mData = bs_new<Platform::Pimpl>();
  341. Platform::~Platform()
  342. {
  343. }
  344. Vector2I Platform::getCursorPosition()
  345. {
  346. Lock lock(mData->cursorMutex);
  347. return mData->cursorPos;
  348. }
  349. void Platform::setCursorPosition(const Vector2I& screenPos)
  350. {
  351. NSPoint point = NSMakePoint(screenPos.x, screenPos.y);
  352. CGWarpMouseCursorPosition(point);
  353. Lock lock(mData->cursorMutex);
  354. mData->cursorPos = screenPos;
  355. }
  356. void Platform::captureMouse(const RenderWindow& window)
  357. {
  358. // Do nothing
  359. }
  360. void Platform::releaseMouseCapture()
  361. {
  362. // Do nothing
  363. }
  364. bool Platform::isPointOverWindow(const RenderWindow& window, const Vector2I& screenPos)
  365. {
  366. CFArrayRef windowDicts = CGWindowListCopyWindowInfo(
  367. kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements,
  368. kCGNullWindowID);
  369. if(!windowDicts)
  370. return nil;
  371. CocoaWindow* cocoaWindow;
  372. window.getCustomAttribute("COCOA_WINDOW", &cocoaWindow);
  373. int32_t requestedWindowNumber = (int32_t)cocoaWindow->_getPrivateData()->windowNumber;
  374. CGPoint point = CGPointMake(screenPos.x, screenPos.y);
  375. CFIndex numEntries = CFArrayGetCount(windowDicts);
  376. for(CFIndex i = 0; i < numEntries; i++)
  377. {
  378. CFDictionaryRef dict = (CFDictionaryRef)CFArrayGetValueAtIndex(windowDicts, i);
  379. CFNumberRef layerRef = (CFNumberRef) CFDictionaryGetValue(dict, kCGWindowLayer);
  380. if(!layerRef)
  381. continue;
  382. // Ignore windows outside of layer 0, as those appear to be desktop elements
  383. int32_t layer;
  384. CFNumberGetValue(layerRef, kCFNumberIntType, &layer);
  385. if(layer != 0)
  386. continue;
  387. CFDictionaryRef boundsRef = (CFDictionaryRef)CFDictionaryGetValue(dict, kCGWindowBounds);
  388. CGRect rect;
  389. CGRectMakeWithDictionaryRepresentation(boundsRef, &rect);
  390. if(CGRectContainsPoint(rect, point))
  391. {
  392. // Windows are ordered front to back intrinsically, so the first one we are within bounds of is the one we want
  393. CFNumberRef windowNumRef = (CFNumberRef)CFDictionaryGetValue(dict, kCGWindowNumber);
  394. int32_t windowNumber;
  395. CFNumberGetValue(windowNumRef, kCGWindowIDCFNumberType, &windowNumber);
  396. return requestedWindowNumber == windowNumber;
  397. }
  398. }
  399. return false;
  400. }
  401. void Platform::hideCursor()
  402. {
  403. Lock lock(mData->cursorMutex);
  404. if(!mData->cursorIsHidden)
  405. {
  406. [NSCursor performSelectorOnMainThread:@selector(unhide) withObject:nil waitUntilDone:NO];
  407. mData->cursorIsHidden = true;
  408. }
  409. }
  410. void Platform::showCursor()
  411. {
  412. Lock lock(mData->cursorMutex);
  413. if(mData->cursorIsHidden)
  414. {
  415. [NSCursor performSelectorOnMainThread:@selector(hide) withObject:nil waitUntilDone:NO];
  416. mData->cursorIsHidden = false;
  417. }
  418. }
  419. bool Platform::isCursorHidden()
  420. {
  421. Lock lock(mData->cursorMutex);
  422. return mData->cursorIsHidden;
  423. }
  424. void Platform::clipCursorToWindow(const RenderWindow& window)
  425. {
  426. CocoaWindow* cocoaWindow;
  427. window.getCustomAttribute("COCOA_WINDOW", &cocoaWindow);
  428. [mData->cursorManager
  429. performSelectorOnMainThread:@selector(clipCursorToWindow:)
  430. withObject:[NSValue valueWithPointer:cocoaWindow]
  431. waitUntilDone:NO];
  432. }
  433. void Platform::clipCursorToRect(const Rect2I& screenRect)
  434. {
  435. [mData->cursorManager
  436. performSelectorOnMainThread:@selector(clipCursorToRect:)
  437. withObject:[NSValue value:&screenRect withObjCType:@encode(Rect2I)]
  438. waitUntilDone:NO];
  439. }
  440. void Platform::clipCursorDisable()
  441. {
  442. [mData->cursorManager
  443. performSelectorOnMainThread:@selector(clipCursorDisable)
  444. withObject:nil
  445. waitUntilDone:NO];
  446. }
  447. void Platform::setCursor(PixelData& pixelData, const Vector2I& hotSpot)
  448. { @autoreleasepool {
  449. NSImage* image = MacOSPlatform::createNSImage(pixelData);
  450. NSPoint point = NSMakePoint(hotSpot.x, hotSpot.y);
  451. NSCursor* cursor = [[NSCursor alloc] initWithImage:image hotSpot:point];
  452. NSArray* params = @[cursor, [NSValue valueWithPoint:point]];
  453. [mData->cursorManager
  454. performSelectorOnMainThread:@selector(setCursor:) withObject:params waitUntilDone:NO];
  455. }}
  456. void Platform::setIcon(const PixelData& pixelData)
  457. { @autoreleasepool {
  458. NSImage* image = MacOSPlatform::createNSImage(pixelData);
  459. [NSApp performSelectorOnMainThread:@selector(setApplicationIconImage:) withObject:image waitUntilDone:NO];
  460. }}
  461. void Platform::setCaptionNonClientAreas(const ct::RenderWindow& window, const Vector<Rect2I>& nonClientAreas)
  462. { @autoreleasepool {
  463. NSMutableArray* params = [[NSMutableArray alloc] init];
  464. CocoaWindow* cocoaWindow;
  465. window.getCustomAttribute("COCOA_WINDOW", &cocoaWindow);
  466. [params addObject:[NSValue valueWithPointer:cocoaWindow]];
  467. for(auto& entry : nonClientAreas)
  468. [params addObject:[NSValue value:&entry withObjCType:@encode(bs::Rect2I)]];
  469. [mData->platformManager
  470. performSelectorOnMainThread:@selector(setCaptionNonClientAreas:)
  471. withObject:params
  472. waitUntilDone:NO];
  473. }}
  474. void Platform::setResizeNonClientAreas(const ct::RenderWindow& window, const Vector<NonClientResizeArea>& nonClientAreas)
  475. {
  476. // Do nothing, custom resize areas not needed on MacOS
  477. }
  478. void Platform::resetNonClientAreas(const ct::RenderWindow& window)
  479. {
  480. CocoaWindow* cocoaWindow;
  481. window.getCustomAttribute("COCOA_WINDOW", &cocoaWindow);
  482. NSValue* windowValue = [NSValue valueWithPointer:cocoaWindow];
  483. [mData->platformManager
  484. performSelectorOnMainThread:@selector(resetNonClientAreas:)
  485. withObject:windowValue
  486. waitUntilDone:NO];
  487. }
  488. void Platform::sleep(UINT32 duration)
  489. {
  490. usleep(duration * 1000);
  491. }
  492. void Platform::copyToClipboard(const WString& string)
  493. { @autoreleasepool {
  494. String utf8String = UTF8::fromWide(string);
  495. NSString* text = [NSString stringWithUTF8String:utf8String.c_str()];
  496. [mData->platformManager performSelectorOnMainThread:@selector(setClipboardText:)
  497. withObject:text
  498. waitUntilDone:NO];
  499. }}
  500. WString Platform::copyFromClipboard()
  501. {
  502. Lock lock(mData->clipboardMutex);
  503. return mData->cachedClipboardData;
  504. }
  505. WString Platform::keyCodeToUnicode(UINT32 keyCode)
  506. {
  507. TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
  508. CFDataRef layoutData = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData);
  509. const UCKeyboardLayout* keyLayout = (const UCKeyboardLayout*)CFDataGetBytePtr(layoutData);
  510. UINT32 keysDown = 0;
  511. UniChar chars[4];
  512. UniCharCount length = 0;
  513. UCKeyTranslate(
  514. keyLayout,
  515. (unsigned short)keyCode,
  516. kUCKeyActionDisplay,
  517. 0,
  518. LMGetKbdType(),
  519. kUCKeyTranslateNoDeadKeysBit,
  520. &keysDown,
  521. sizeof(chars) / sizeof(chars[0]),
  522. &length,
  523. chars);
  524. CFRelease(keyLayout);
  525. U16String u16String((char16_t*)chars, (size_t)length);
  526. String utf8String = UTF8::fromUTF16(u16String);
  527. return UTF8::toWide(utf8String);
  528. }
  529. void Platform::openFolder(const Path& path)
  530. {
  531. String pathStr = path.toString();
  532. NSURL* url = [NSURL fileURLWithPath:[NSString stringWithUTF8String:pathStr.c_str()]];
  533. [mData->platformManager
  534. performSelectorOnMainThread:@selector(openFolder:)
  535. withObject:url
  536. waitUntilDone:NO];
  537. }
  538. void Platform::_startUp()
  539. {
  540. mData->appDelegate = [[BSAppDelegate alloc] init];
  541. mData->cursorManager = [[BSCursor alloc] initWithPlatformData:mData];
  542. mData->platformManager = [[BSPlatform alloc] initWithPlatformData:mData];
  543. [BSApplication sharedApplication];
  544. [NSApp setDelegate:mData->appDelegate];
  545. [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
  546. createApplicationMenu();
  547. [NSApp finishLaunching];
  548. }
  549. void Platform::_update()
  550. {
  551. CocoaDragAndDrop::update();
  552. }
  553. void Platform::_coreUpdate()
  554. {
  555. {
  556. Lock lock(mData->cursorMutex);
  557. mData->cursorPos = [mData->cursorManager getPosition];
  558. }
  559. CocoaDragAndDrop::coreUpdate();
  560. INT32 changeCount = [mData->platformManager getClipboardChangeCount];
  561. if(mData->clipboardChangeCount != changeCount)
  562. {
  563. NSString* string = [mData->platformManager getClipboardText];
  564. String utf8String = [string UTF8String];
  565. {
  566. Lock lock(mData->clipboardMutex);
  567. mData->cachedClipboardData = UTF8::toWide(utf8String);
  568. }
  569. mData->clipboardChangeCount = changeCount;
  570. }
  571. _messagePump();
  572. }
  573. void Platform::_shutDown()
  574. {
  575. // Do nothing
  576. }
  577. void Platform::_messagePump()
  578. { @autoreleasepool {
  579. while(true)
  580. {
  581. if(!mData->modalWindows.empty())
  582. {
  583. if([NSApp runModalSession:mData->modalWindows.back().session] != NSModalResponseContinue)
  584. break;
  585. }
  586. else
  587. {
  588. NSEvent* event = [NSApp
  589. nextEventMatchingMask:NSEventMaskAny
  590. untilDate:[NSDate distantPast]
  591. inMode:NSDefaultRunLoopMode
  592. dequeue:YES];
  593. if (!event)
  594. break;
  595. [NSApp sendEvent:event];
  596. }
  597. }
  598. }}
  599. void MacOSPlatform::registerWindow(CocoaWindow* window)
  600. {
  601. // First window is assumed to be main
  602. if(!mData->mainWindow)
  603. mData->mainWindow = window;
  604. mData->allWindows.push_back(window);
  605. CocoaWindow::Pimpl* windowData = window->_getPrivateData();
  606. if(windowData->isModal)
  607. {
  608. ModalWindowInfo info;
  609. info.window = window;
  610. info.session = [NSApp beginModalSessionForWindow:windowData->window];
  611. mData->modalWindows.push_back(info);
  612. }
  613. }
  614. void MacOSPlatform::unregisterWindow(CocoaWindow* window)
  615. {
  616. CocoaWindow::Pimpl* windowData = window->_getPrivateData();
  617. if(windowData->isModal)
  618. {
  619. auto findIter = std::find_if(mData->modalWindows.begin(), mData->modalWindows.begin(),
  620. [window](const ModalWindowInfo& x)
  621. {
  622. return x.window == window;
  623. });
  624. if(findIter != mData->modalWindows.end())
  625. {
  626. [NSApp endModalSession:findIter->session];
  627. mData->modalWindows.erase(findIter);
  628. }
  629. }
  630. auto findIter = std::find(mData->allWindows.begin(), mData->allWindows.end(), window);
  631. if(findIter != mData->allWindows.end())
  632. mData->allWindows.erase(findIter);
  633. [mData->cursorManager unregisterWindow:windowData->window];
  634. // Shut down app when the main window is closed
  635. if(mData->mainWindow == window)
  636. {
  637. bs::gCoreApplication().quitRequested();
  638. mData->mainWindow = nullptr;
  639. }
  640. }
  641. NSImage* MacOSPlatform::createNSImage(const PixelData& data)
  642. {
  643. // Premultiply alpha
  644. Vector<Color> colors = data.getColors();
  645. for(auto& color : colors)
  646. {
  647. color.r *= color.a;
  648. color.g *= color.a;
  649. color.b *= color.a;
  650. }
  651. // Convert to RGBA
  652. SPtr<PixelData> rgbaData = PixelData::create(data.getWidth(), data.getHeight(), 1, PF_RGBA8);
  653. rgbaData->setColors(colors);
  654. @autoreleasepool
  655. {
  656. INT32 pitch = data.getWidth() * sizeof(UINT32);
  657. NSBitmapImageRep* imageRep = [[NSBitmapImageRep alloc]
  658. initWithBitmapDataPlanes:nullptr
  659. pixelsWide:data.getWidth()
  660. pixelsHigh:data.getHeight()
  661. bitsPerSample:8
  662. samplesPerPixel:4
  663. hasAlpha:YES
  664. isPlanar:NO
  665. colorSpaceName:NSDeviceRGBColorSpace
  666. bytesPerRow:pitch
  667. bitsPerPixel:32];
  668. unsigned char* pixels = [imageRep bitmapData];
  669. memcpy(pixels, rgbaData->getData(), data.getHeight() * pitch);
  670. NSImage* image = [[NSImage alloc] initWithSize:NSMakeSize(data.getWidth(), data.getHeight())];
  671. [image addRepresentation:imageRep];
  672. return image;
  673. }
  674. }
  675. void MacOSPlatform::sendInputCommandEvent(InputCommandType inputCommand)
  676. {
  677. onInputCommand(inputCommand);
  678. }
  679. void MacOSPlatform::sendCharInputEvent(UINT32 character)
  680. {
  681. onCharInput(character);
  682. }
  683. void MacOSPlatform::sendPointerButtonPressedEvent(
  684. const Vector2I& pos,
  685. OSMouseButton button,
  686. const OSPointerButtonStates& buttonStates)
  687. {
  688. onCursorButtonPressed(pos, button, buttonStates);
  689. }
  690. void MacOSPlatform::sendPointerButtonReleasedEvent(
  691. const Vector2I& pos,
  692. OSMouseButton button,
  693. const OSPointerButtonStates& buttonStates)
  694. {
  695. onCursorButtonReleased(pos, button, buttonStates);
  696. }
  697. void MacOSPlatform::sendPointerDoubleClickEvent(const Vector2I& pos, const OSPointerButtonStates& buttonStates)
  698. {
  699. onCursorDoubleClick(pos, buttonStates);
  700. }
  701. void MacOSPlatform::sendPointerMovedEvent(const Vector2I& pos, const OSPointerButtonStates& buttonStates)
  702. {
  703. onCursorMoved(pos, buttonStates);
  704. }
  705. void MacOSPlatform::sendMouseWheelScrollEvent(float delta)
  706. {
  707. onMouseWheelScrolled(delta);
  708. }
  709. NSCursor* MacOSPlatform::_getCurrentCursor()
  710. {
  711. return [mData->cursorManager currentCursor];
  712. }
  713. bool MacOSPlatform::_clipCursor(Vector2I& pos)
  714. {
  715. return [mData->cursorManager clipCursor:pos];
  716. }
  717. void MacOSPlatform::_updateClipBounds(NSWindow* window)
  718. {
  719. [mData->cursorManager updateClipBounds:window];
  720. }
  721. void MacOSPlatform::_setCursorPosition(const Vector2I& position)
  722. {
  723. [mData->cursorManager setPosition:position];
  724. }
  725. }