os_macos.m 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. // SPDX-License-Identifier: Unlicense OR MIT
  2. // +build darwin,!ios
  3. #import <AppKit/AppKit.h>
  4. #include "_cgo_export.h"
  5. __attribute__ ((visibility ("hidden"))) CALayer *gio_layerFactory(BOOL presentWithTrans);
  6. @interface GioAppDelegate : NSObject<NSApplicationDelegate>
  7. @end
  8. @interface GioWindowDelegate : NSObject<NSWindowDelegate>
  9. @end
  10. @interface GioView : NSView <CALayerDelegate,NSTextInputClient>
  11. @property uintptr_t handle;
  12. @property BOOL presentWithTrans;
  13. @end
  14. @implementation GioWindowDelegate
  15. - (void)windowWillMiniaturize:(NSNotification *)notification {
  16. NSWindow *window = (NSWindow *)[notification object];
  17. GioView *view = (GioView *)window.contentView;
  18. gio_onDraw(view.handle);
  19. }
  20. - (void)windowDidDeminiaturize:(NSNotification *)notification {
  21. NSWindow *window = (NSWindow *)[notification object];
  22. GioView *view = (GioView *)window.contentView;
  23. gio_onDraw(view.handle);
  24. }
  25. - (void)windowWillEnterFullScreen:(NSNotification *)notification {
  26. NSWindow *window = (NSWindow *)[notification object];
  27. GioView *view = (GioView *)window.contentView;
  28. gio_onDraw(view.handle);
  29. }
  30. - (void)windowWillExitFullScreen:(NSNotification *)notification {
  31. NSWindow *window = (NSWindow *)[notification object];
  32. GioView *view = (GioView *)window.contentView;
  33. gio_onDraw(view.handle);
  34. }
  35. - (void)windowDidChangeScreen:(NSNotification *)notification {
  36. NSWindow *window = (NSWindow *)[notification object];
  37. CGDirectDisplayID dispID = [[[window screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue];
  38. GioView *view = (GioView *)window.contentView;
  39. gio_onChangeScreen(view.handle, dispID);
  40. }
  41. - (void)windowDidBecomeKey:(NSNotification *)notification {
  42. NSWindow *window = (NSWindow *)[notification object];
  43. GioView *view = (GioView *)window.contentView;
  44. if ([window firstResponder] == view) {
  45. gio_onFocus(view.handle, 1);
  46. }
  47. }
  48. - (void)windowDidResignKey:(NSNotification *)notification {
  49. NSWindow *window = (NSWindow *)[notification object];
  50. GioView *view = (GioView *)window.contentView;
  51. if ([window firstResponder] == view) {
  52. gio_onFocus(view.handle, 0);
  53. }
  54. }
  55. @end
  56. static void handleMouse(GioView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) {
  57. NSPoint p = [view convertPoint:[event locationInWindow] fromView:nil];
  58. if (!event.hasPreciseScrollingDeltas) {
  59. // dx and dy are in rows and columns.
  60. dx *= 10;
  61. dy *= 10;
  62. }
  63. // Origin is in the lower left corner. Convert to upper left.
  64. CGFloat height = view.bounds.size.height;
  65. gio_onMouse(view.handle, (__bridge CFTypeRef)event, typ, event.buttonNumber, p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]);
  66. }
  67. @implementation GioView
  68. - (void)setFrameSize:(NSSize)newSize {
  69. [super setFrameSize:newSize];
  70. [self setNeedsDisplay:YES];
  71. }
  72. // drawRect is called when OpenGL is used, displayLayer otherwise.
  73. // Don't know why.
  74. - (void)drawRect:(NSRect)r {
  75. gio_onDraw(self.handle);
  76. }
  77. - (void)displayLayer:(CALayer *)layer {
  78. layer.contentsScale = self.window.backingScaleFactor;
  79. gio_onDraw(self.handle);
  80. }
  81. - (CALayer *)makeBackingLayer {
  82. CALayer *layer = gio_layerFactory(self.presentWithTrans);
  83. layer.delegate = self;
  84. return layer;
  85. }
  86. - (void)viewDidMoveToWindow {
  87. gio_onAttached(self.handle, self.window != nil ? 1 : 0);
  88. }
  89. - (void)mouseDown:(NSEvent *)event {
  90. handleMouse(self, event, MOUSE_DOWN, 0, 0);
  91. }
  92. - (void)mouseUp:(NSEvent *)event {
  93. handleMouse(self, event, MOUSE_UP, 0, 0);
  94. }
  95. - (void)rightMouseDown:(NSEvent *)event {
  96. handleMouse(self, event, MOUSE_DOWN, 0, 0);
  97. }
  98. - (void)rightMouseUp:(NSEvent *)event {
  99. handleMouse(self, event, MOUSE_UP, 0, 0);
  100. }
  101. - (void)otherMouseDown:(NSEvent *)event {
  102. handleMouse(self, event, MOUSE_DOWN, 0, 0);
  103. }
  104. - (void)otherMouseUp:(NSEvent *)event {
  105. handleMouse(self, event, MOUSE_UP, 0, 0);
  106. }
  107. - (void)mouseMoved:(NSEvent *)event {
  108. handleMouse(self, event, MOUSE_MOVE, 0, 0);
  109. }
  110. - (void)mouseDragged:(NSEvent *)event {
  111. handleMouse(self, event, MOUSE_MOVE, 0, 0);
  112. }
  113. - (void)rightMouseDragged:(NSEvent *)event {
  114. handleMouse(self, event, MOUSE_MOVE, 0, 0);
  115. }
  116. - (void)otherMouseDragged:(NSEvent *)event {
  117. handleMouse(self, event, MOUSE_MOVE, 0, 0);
  118. }
  119. - (void)scrollWheel:(NSEvent *)event {
  120. CGFloat dx = -event.scrollingDeltaX;
  121. CGFloat dy = -event.scrollingDeltaY;
  122. handleMouse(self, event, MOUSE_SCROLL, dx, dy);
  123. }
  124. - (void)keyDown:(NSEvent *)event {
  125. NSString *keys = [event charactersIgnoringModifiers];
  126. gio_onKeys(self.handle, (__bridge CFTypeRef)event, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], true);
  127. }
  128. - (void)flagsChanged:(NSEvent *)event {
  129. [self interpretKeyEvents:[NSArray arrayWithObject:event]];
  130. gio_onFlagsChanged(self.handle, [event modifierFlags]);
  131. }
  132. - (void)keyUp:(NSEvent *)event {
  133. NSString *keys = [event charactersIgnoringModifiers];
  134. gio_onKeys(self.handle, (__bridge CFTypeRef)event, (__bridge CFTypeRef)keys, [event timestamp], [event modifierFlags], false);
  135. }
  136. - (void)insertText:(id)string {
  137. gio_onText(self.handle, (__bridge CFTypeRef)string);
  138. }
  139. - (void)doCommandBySelector:(SEL)action {
  140. if (!gio_onCommandBySelector(self.handle)) {
  141. [super doCommandBySelector:action];
  142. }
  143. }
  144. - (BOOL)hasMarkedText {
  145. int res = gio_hasMarkedText(self.handle);
  146. return res ? YES : NO;
  147. }
  148. - (NSRange)markedRange {
  149. return gio_markedRange(self.handle);
  150. }
  151. - (NSRange)selectedRange {
  152. return gio_selectedRange(self.handle);
  153. }
  154. - (void)unmarkText {
  155. gio_unmarkText(self.handle);
  156. }
  157. - (void)setMarkedText:(id)string
  158. selectedRange:(NSRange)selRange
  159. replacementRange:(NSRange)replaceRange {
  160. NSString *str;
  161. // string is either an NSAttributedString or an NSString.
  162. if ([string isKindOfClass:[NSAttributedString class]]) {
  163. str = [string string];
  164. } else {
  165. str = string;
  166. }
  167. gio_setMarkedText(self.handle, (__bridge CFTypeRef)str, selRange, replaceRange);
  168. }
  169. - (NSArray<NSAttributedStringKey> *)validAttributesForMarkedText {
  170. return nil;
  171. }
  172. - (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range
  173. actualRange:(NSRangePointer)actualRange {
  174. NSString *str = CFBridgingRelease(gio_substringForProposedRange(self.handle, range, actualRange));
  175. return [[NSAttributedString alloc] initWithString:str attributes:nil];
  176. }
  177. - (void)insertText:(id)string
  178. replacementRange:(NSRange)replaceRange {
  179. NSString *str;
  180. // string is either an NSAttributedString or an NSString.
  181. if ([string isKindOfClass:[NSAttributedString class]]) {
  182. str = [string string];
  183. } else {
  184. str = string;
  185. }
  186. gio_insertText(self.handle, (__bridge CFTypeRef)str, replaceRange);
  187. }
  188. - (NSUInteger)characterIndexForPoint:(NSPoint)p {
  189. return gio_characterIndexForPoint(self.handle, p);
  190. }
  191. - (NSRect)firstRectForCharacterRange:(NSRange)rng
  192. actualRange:(NSRangePointer)actual {
  193. NSRect r = gio_firstRectForCharacterRange(self.handle, rng, actual);
  194. r = [self convertRect:r toView:nil];
  195. return [[self window] convertRectToScreen:r];
  196. }
  197. - (void)applicationWillUnhide:(NSNotification *)notification {
  198. gio_onDraw(self.handle);
  199. }
  200. - (void)applicationDidHide:(NSNotification *)notification {
  201. gio_onDraw(self.handle);
  202. }
  203. - (void)dealloc {
  204. gio_onDestroy(self.handle);
  205. }
  206. - (BOOL) becomeFirstResponder {
  207. gio_onFocus(self.handle, 1);
  208. return [super becomeFirstResponder];
  209. }
  210. - (BOOL) resignFirstResponder {
  211. gio_onFocus(self.handle, 0);
  212. return [super resignFirstResponder];
  213. }
  214. @end
  215. // Delegates are weakly referenced from their peers. Nothing
  216. // else holds a strong reference to our window delegate, so
  217. // keep a single global reference instead.
  218. static GioWindowDelegate *globalWindowDel;
  219. static CVReturn displayLinkCallback(CVDisplayLinkRef dl, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *handle) {
  220. gio_onFrameCallback(dl);
  221. return kCVReturnSuccess;
  222. }
  223. CFTypeRef gio_createDisplayLink(void) {
  224. CVDisplayLinkRef dl;
  225. CVDisplayLinkCreateWithActiveCGDisplays(&dl);
  226. CVDisplayLinkSetOutputCallback(dl, displayLinkCallback, nil);
  227. return dl;
  228. }
  229. int gio_startDisplayLink(CFTypeRef dl) {
  230. return CVDisplayLinkStart((CVDisplayLinkRef)dl);
  231. }
  232. int gio_stopDisplayLink(CFTypeRef dl) {
  233. return CVDisplayLinkStop((CVDisplayLinkRef)dl);
  234. }
  235. void gio_releaseDisplayLink(CFTypeRef dl) {
  236. CVDisplayLinkRelease((CVDisplayLinkRef)dl);
  237. }
  238. void gio_setDisplayLinkDisplay(CFTypeRef dl, uint64_t did) {
  239. CVDisplayLinkSetCurrentCGDisplay((CVDisplayLinkRef)dl, (CGDirectDisplayID)did);
  240. }
  241. void gio_hideCursor() {
  242. @autoreleasepool {
  243. [NSCursor hide];
  244. }
  245. }
  246. void gio_showCursor() {
  247. @autoreleasepool {
  248. [NSCursor unhide];
  249. }
  250. }
  251. // some cursors are not public, this tries to use a private cursor
  252. // and uses fallback when the use of private cursor fails.
  253. static void trySetPrivateCursor(SEL cursorName, NSCursor* fallback) {
  254. if ([NSCursor respondsToSelector:cursorName]) {
  255. id object = [NSCursor performSelector:cursorName];
  256. if ([object isKindOfClass:[NSCursor class]]) {
  257. [(NSCursor*)object set];
  258. return;
  259. }
  260. }
  261. [fallback set];
  262. }
  263. void gio_setCursor(NSUInteger curID) {
  264. @autoreleasepool {
  265. switch (curID) {
  266. case 0: // pointer.CursorDefault
  267. [NSCursor.arrowCursor set];
  268. break;
  269. // case 1: // pointer.CursorNone
  270. case 2: // pointer.CursorText
  271. [NSCursor.IBeamCursor set];
  272. break;
  273. case 3: // pointer.CursorVerticalText
  274. [NSCursor.IBeamCursorForVerticalLayout set];
  275. break;
  276. case 4: // pointer.CursorPointer
  277. [NSCursor.pointingHandCursor set];
  278. break;
  279. case 5: // pointer.CursorCrosshair
  280. [NSCursor.crosshairCursor set];
  281. break;
  282. case 6: // pointer.CursorAllScroll
  283. // For some reason, using _moveCursor fails on Monterey.
  284. // trySetPrivateCursor(@selector(_moveCursor), NSCursor.arrowCursor);
  285. [NSCursor.arrowCursor set];
  286. break;
  287. case 7: // pointer.CursorColResize
  288. [NSCursor.resizeLeftRightCursor set];
  289. break;
  290. case 8: // pointer.CursorRowResize
  291. [NSCursor.resizeUpDownCursor set];
  292. break;
  293. case 9: // pointer.CursorGrab
  294. [NSCursor.openHandCursor set];
  295. break;
  296. case 10: // pointer.CursorGrabbing
  297. [NSCursor.closedHandCursor set];
  298. break;
  299. case 11: // pointer.CursorNotAllowed
  300. [NSCursor.operationNotAllowedCursor set];
  301. break;
  302. case 12: // pointer.CursorWait
  303. trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
  304. break;
  305. case 13: // pointer.CursorProgress
  306. trySetPrivateCursor(@selector(busyButClickableCursor), NSCursor.arrowCursor);
  307. break;
  308. case 14: // pointer.CursorNorthWestResize
  309. trySetPrivateCursor(@selector(_windowResizeNorthWestCursor), NSCursor.resizeUpDownCursor);
  310. break;
  311. case 15: // pointer.CursorNorthEastResize
  312. trySetPrivateCursor(@selector(_windowResizeNorthEastCursor), NSCursor.resizeUpDownCursor);
  313. break;
  314. case 16: // pointer.CursorSouthWestResize
  315. trySetPrivateCursor(@selector(_windowResizeSouthWestCursor), NSCursor.resizeUpDownCursor);
  316. break;
  317. case 17: // pointer.CursorSouthEastResize
  318. trySetPrivateCursor(@selector(_windowResizeSouthEastCursor), NSCursor.resizeUpDownCursor);
  319. break;
  320. case 18: // pointer.CursorNorthSouthResize
  321. [NSCursor.resizeUpDownCursor set];
  322. break;
  323. case 19: // pointer.CursorEastWestResize
  324. [NSCursor.resizeLeftRightCursor set];
  325. break;
  326. case 20: // pointer.CursorWestResize
  327. [NSCursor.resizeLeftCursor set];
  328. break;
  329. case 21: // pointer.CursorEastResize
  330. [NSCursor.resizeRightCursor set];
  331. break;
  332. case 22: // pointer.CursorNorthResize
  333. [NSCursor.resizeUpCursor set];
  334. break;
  335. case 23: // pointer.CursorSouthResize
  336. [NSCursor.resizeDownCursor set];
  337. break;
  338. case 24: // pointer.CursorNorthEastSouthWestResize
  339. trySetPrivateCursor(@selector(_windowResizeNorthEastSouthWestCursor), NSCursor.resizeUpDownCursor);
  340. break;
  341. case 25: // pointer.CursorNorthWestSouthEastResize
  342. trySetPrivateCursor(@selector(_windowResizeNorthWestSouthEastCursor), NSCursor.resizeUpDownCursor);
  343. break;
  344. default:
  345. [NSCursor.arrowCursor set];
  346. break;
  347. }
  348. }
  349. }
  350. CFTypeRef gio_createWindow(CFTypeRef viewRef, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight) {
  351. @autoreleasepool {
  352. NSRect rect = NSMakeRect(0, 0, width, height);
  353. NSUInteger styleMask = NSTitledWindowMask |
  354. NSResizableWindowMask |
  355. NSMiniaturizableWindowMask |
  356. NSClosableWindowMask;
  357. NSWindow* window = [[NSWindow alloc] initWithContentRect:rect
  358. styleMask:styleMask
  359. backing:NSBackingStoreBuffered
  360. defer:NO];
  361. if (minWidth > 0 || minHeight > 0) {
  362. window.contentMinSize = NSMakeSize(minWidth, minHeight);
  363. }
  364. if (maxWidth > 0 || maxHeight > 0) {
  365. window.contentMaxSize = NSMakeSize(maxWidth, maxHeight);
  366. }
  367. [window setAcceptsMouseMovedEvents:YES];
  368. NSView *view = (__bridge NSView *)viewRef;
  369. [window setContentView:view];
  370. window.delegate = globalWindowDel;
  371. return (__bridge_retained CFTypeRef)window;
  372. }
  373. }
  374. CFTypeRef gio_createView(int presentWithTrans) {
  375. @autoreleasepool {
  376. NSRect frame = NSMakeRect(0, 0, 0, 0);
  377. GioView* view = [[GioView alloc] initWithFrame:frame];
  378. view.presentWithTrans = presentWithTrans ? YES : NO;
  379. view.wantsLayer = YES;
  380. view.layerContentsRedrawPolicy = NSViewLayerContentsRedrawDuringViewResize;
  381. [[NSNotificationCenter defaultCenter] addObserver:view
  382. selector:@selector(applicationWillUnhide:)
  383. name:NSApplicationWillUnhideNotification
  384. object:nil];
  385. [[NSNotificationCenter defaultCenter] addObserver:view
  386. selector:@selector(applicationDidHide:)
  387. name:NSApplicationDidHideNotification
  388. object:nil];
  389. return CFBridgingRetain(view);
  390. }
  391. }
  392. void gio_viewSetHandle(CFTypeRef viewRef, uintptr_t handle) {
  393. @autoreleasepool {
  394. GioView *v = (__bridge GioView *)viewRef;
  395. v.handle = handle;
  396. }
  397. }
  398. @implementation GioAppDelegate
  399. - (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
  400. [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
  401. [NSApp activateIgnoringOtherApps:YES];
  402. gio_onFinishLaunching();
  403. }
  404. @end
  405. void gio_main() {
  406. @autoreleasepool {
  407. [NSApplication sharedApplication];
  408. GioAppDelegate *del = [[GioAppDelegate alloc] init];
  409. [NSApp setDelegate:del];
  410. NSMenuItem *mainMenu = [NSMenuItem new];
  411. NSMenu *menu = [NSMenu new];
  412. NSMenuItem *hideMenuItem = [[NSMenuItem alloc] initWithTitle:@"Hide"
  413. action:@selector(hide:)
  414. keyEquivalent:@"h"];
  415. [menu addItem:hideMenuItem];
  416. NSMenuItem *quitMenuItem = [[NSMenuItem alloc] initWithTitle:@"Quit"
  417. action:@selector(terminate:)
  418. keyEquivalent:@"q"];
  419. [menu addItem:quitMenuItem];
  420. [mainMenu setSubmenu:menu];
  421. NSMenu *menuBar = [NSMenu new];
  422. [menuBar addItem:mainMenu];
  423. [NSApp setMainMenu:menuBar];
  424. globalWindowDel = [[GioWindowDelegate alloc] init];
  425. [NSApp run];
  426. }
  427. }