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