github.com/cybriq/giocore@v0.0.7-0.20210703034601-cfb9cb5f3900/app/internal/wm/os_macos.m (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 // +build darwin,!ios 4 5 @import AppKit; 6 7 #include "_cgo_export.h" 8 9 @interface GioAppDelegate : NSObject<NSApplicationDelegate> 10 @end 11 12 @interface GioWindowDelegate : NSObject<NSWindowDelegate> 13 @end 14 15 @implementation GioWindowDelegate 16 - (void)windowWillMiniaturize:(NSNotification *)notification { 17 NSWindow *window = (NSWindow *)[notification object]; 18 gio_onHide((__bridge CFTypeRef)window.contentView); 19 } 20 - (void)windowDidDeminiaturize:(NSNotification *)notification { 21 NSWindow *window = (NSWindow *)[notification object]; 22 gio_onShow((__bridge CFTypeRef)window.contentView); 23 } 24 - (void)windowDidChangeScreen:(NSNotification *)notification { 25 NSWindow *window = (NSWindow *)[notification object]; 26 CGDirectDisplayID dispID = [[[window screen] deviceDescription][@"NSScreenNumber"] unsignedIntValue]; 27 CFTypeRef view = (__bridge CFTypeRef)window.contentView; 28 gio_onChangeScreen(view, dispID); 29 } 30 - (void)windowDidBecomeKey:(NSNotification *)notification { 31 NSWindow *window = (NSWindow *)[notification object]; 32 gio_onFocus((__bridge CFTypeRef)window.contentView, 1); 33 } 34 - (void)windowDidResignKey:(NSNotification *)notification { 35 NSWindow *window = (NSWindow *)[notification object]; 36 gio_onFocus((__bridge CFTypeRef)window.contentView, 0); 37 } 38 - (void)windowWillClose:(NSNotification *)notification { 39 NSWindow *window = (NSWindow *)[notification object]; 40 window.delegate = nil; 41 gio_onClose((__bridge CFTypeRef)window.contentView); 42 } 43 @end 44 45 static void handleMouse(NSView *view, NSEvent *event, int typ, CGFloat dx, CGFloat dy) { 46 NSPoint p = [view convertPoint:[event locationInWindow] fromView:nil]; 47 if (!event.hasPreciseScrollingDeltas) { 48 // dx and dy are in rows and columns. 49 dx *= 10; 50 dy *= 10; 51 } 52 // Origin is in the lower left corner. Convert to upper left. 53 CGFloat height = view.bounds.size.height; 54 gio_onMouse((__bridge CFTypeRef)view, typ, [NSEvent pressedMouseButtons], p.x, height - p.y, dx, dy, [event timestamp], [event modifierFlags]); 55 } 56 57 @interface GioView : NSView 58 @end 59 60 @implementation GioView 61 - (void)drawRect:(NSRect)r { 62 gio_onDraw((__bridge CFTypeRef)self); 63 } 64 - (void)mouseDown:(NSEvent *)event { 65 handleMouse(self, event, GIO_MOUSE_DOWN, 0, 0); 66 } 67 - (void)mouseUp:(NSEvent *)event { 68 handleMouse(self, event, GIO_MOUSE_UP, 0, 0); 69 } 70 - (void)middleMouseDown:(NSEvent *)event { 71 handleMouse(self, event, GIO_MOUSE_DOWN, 0, 0); 72 } 73 - (void)middletMouseUp:(NSEvent *)event { 74 handleMouse(self, event, GIO_MOUSE_UP, 0, 0); 75 } 76 - (void)rightMouseDown:(NSEvent *)event { 77 handleMouse(self, event, GIO_MOUSE_DOWN, 0, 0); 78 } 79 - (void)rightMouseUp:(NSEvent *)event { 80 handleMouse(self, event, GIO_MOUSE_UP, 0, 0); 81 } 82 - (void)mouseMoved:(NSEvent *)event { 83 handleMouse(self, event, GIO_MOUSE_MOVE, 0, 0); 84 } 85 - (void)mouseDragged:(NSEvent *)event { 86 handleMouse(self, event, GIO_MOUSE_MOVE, 0, 0); 87 } 88 - (void)scrollWheel:(NSEvent *)event { 89 CGFloat dx = -event.scrollingDeltaX; 90 CGFloat dy = -event.scrollingDeltaY; 91 handleMouse(self, event, GIO_MOUSE_SCROLL, dx, dy); 92 } 93 - (void)keyDown:(NSEvent *)event { 94 NSString *keys = [event charactersIgnoringModifiers]; 95 gio_onKeys((__bridge CFTypeRef)self, (char *)[keys UTF8String], [event timestamp], [event modifierFlags], true); 96 [self interpretKeyEvents:[NSArray arrayWithObject:event]]; 97 } 98 - (void)keyUp:(NSEvent *)event { 99 NSString *keys = [event charactersIgnoringModifiers]; 100 gio_onKeys((__bridge CFTypeRef)self, (char *)[keys UTF8String], [event timestamp], [event modifierFlags], false); 101 } 102 - (void)insertText:(id)string { 103 const char *utf8 = [string UTF8String]; 104 gio_onText((__bridge CFTypeRef)self, (char *)utf8); 105 } 106 - (void)doCommandBySelector:(SEL)sel { 107 // Don't pass commands up the responder chain. 108 // They will end up in a beep. 109 } 110 @end 111 // Delegates are weakly referenced from their peers. Nothing 112 // else holds a strong reference to our window delegate, so 113 // keep a single global reference instead. 114 static GioWindowDelegate *globalWindowDel; 115 116 void gio_writeClipboard(unichar *chars, NSUInteger length) { 117 @autoreleasepool { 118 NSString *s = [NSString string]; 119 if (length > 0) { 120 s = [NSString stringWithCharacters:chars length:length]; 121 } 122 NSPasteboard *p = NSPasteboard.generalPasteboard; 123 [p declareTypes:@[NSPasteboardTypeString] owner:nil]; 124 [p setString:s forType:NSPasteboardTypeString]; 125 } 126 } 127 128 CFTypeRef gio_readClipboard(void) { 129 @autoreleasepool { 130 NSPasteboard *p = NSPasteboard.generalPasteboard; 131 NSString *content = [p stringForType:NSPasteboardTypeString]; 132 return (__bridge_retained CFTypeRef)content; 133 } 134 } 135 136 CGFloat gio_viewHeight(CFTypeRef viewRef) { 137 NSView *view = (__bridge NSView *)viewRef; 138 return [view bounds].size.height; 139 } 140 141 CGFloat gio_viewWidth(CFTypeRef viewRef) { 142 NSView *view = (__bridge NSView *)viewRef; 143 return [view bounds].size.width; 144 } 145 146 CGFloat gio_getScreenBackingScale(void) { 147 return [NSScreen.mainScreen backingScaleFactor]; 148 } 149 150 CGFloat gio_getViewBackingScale(CFTypeRef viewRef) { 151 NSView *view = (__bridge NSView *)viewRef; 152 return [view.window backingScaleFactor]; 153 } 154 155 void gio_setNeedsDisplay(CFTypeRef viewRef) { 156 NSView *view = (__bridge NSView *)viewRef; 157 [view setNeedsDisplay:YES]; 158 } 159 160 void gio_hideCursor() { 161 @autoreleasepool { 162 [NSCursor hide]; 163 } 164 } 165 166 void gio_showCursor() { 167 @autoreleasepool { 168 [NSCursor unhide]; 169 } 170 } 171 172 void gio_setCursor(NSUInteger curID) { 173 @autoreleasepool { 174 switch (curID) { 175 case 1: 176 [NSCursor.arrowCursor set]; 177 break; 178 case 2: 179 [NSCursor.IBeamCursor set]; 180 break; 181 case 3: 182 [NSCursor.pointingHandCursor set]; 183 break; 184 case 4: 185 [NSCursor.crosshairCursor set]; 186 break; 187 case 5: 188 [NSCursor.resizeLeftRightCursor set]; 189 break; 190 case 6: 191 [NSCursor.resizeUpDownCursor set]; 192 break; 193 case 7: 194 [NSCursor.openHandCursor set]; 195 break; 196 default: 197 [NSCursor.arrowCursor set]; 198 break; 199 } 200 } 201 } 202 203 static CVReturn displayLinkCallback(CVDisplayLinkRef dl, const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime, CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *displayLinkContext) { 204 gio_onFrameCallback(dl); 205 return kCVReturnSuccess; 206 } 207 208 CFTypeRef gio_createDisplayLink(void) { 209 CVDisplayLinkRef dl; 210 CVDisplayLinkCreateWithActiveCGDisplays(&dl); 211 CVDisplayLinkSetOutputCallback(dl, displayLinkCallback, nil); 212 return dl; 213 } 214 215 int gio_startDisplayLink(CFTypeRef dl) { 216 return CVDisplayLinkStart((CVDisplayLinkRef)dl); 217 } 218 219 int gio_stopDisplayLink(CFTypeRef dl) { 220 return CVDisplayLinkStop((CVDisplayLinkRef)dl); 221 } 222 223 void gio_releaseDisplayLink(CFTypeRef dl) { 224 CVDisplayLinkRelease((CVDisplayLinkRef)dl); 225 } 226 227 void gio_setDisplayLinkDisplay(CFTypeRef dl, uint64_t did) { 228 CVDisplayLinkSetCurrentCGDisplay((CVDisplayLinkRef)dl, (CGDirectDisplayID)did); 229 } 230 231 NSPoint gio_cascadeTopLeftFromPoint(CFTypeRef windowRef, NSPoint topLeft) { 232 NSWindow *window = (__bridge NSWindow *)windowRef; 233 return [window cascadeTopLeftFromPoint:topLeft]; 234 } 235 236 void gio_makeKeyAndOrderFront(CFTypeRef windowRef) { 237 NSWindow *window = (__bridge NSWindow *)windowRef; 238 [window makeKeyAndOrderFront:nil]; 239 } 240 241 void gio_toggleFullScreen(CFTypeRef windowRef) { 242 NSWindow *window = (__bridge NSWindow *)windowRef; 243 [window toggleFullScreen:nil]; 244 } 245 246 CFTypeRef gio_createWindow(CFTypeRef viewRef, const char *title, CGFloat width, CGFloat height, CGFloat minWidth, CGFloat minHeight, CGFloat maxWidth, CGFloat maxHeight) { 247 @autoreleasepool { 248 NSRect rect = NSMakeRect(0, 0, width, height); 249 NSUInteger styleMask = NSTitledWindowMask | 250 NSResizableWindowMask | 251 NSMiniaturizableWindowMask | 252 NSClosableWindowMask; 253 254 NSWindow* window = [[NSWindow alloc] initWithContentRect:rect 255 styleMask:styleMask 256 backing:NSBackingStoreBuffered 257 defer:NO]; 258 if (minWidth > 0 || minHeight > 0) { 259 window.contentMinSize = NSMakeSize(minWidth, minHeight); 260 } 261 if (maxWidth > 0 || maxHeight > 0) { 262 window.contentMaxSize = NSMakeSize(maxWidth, maxHeight); 263 } 264 [window setAcceptsMouseMovedEvents:YES]; 265 if (title != nil) { 266 window.title = [NSString stringWithUTF8String: title]; 267 } 268 NSView *view = (__bridge NSView *)viewRef; 269 [window setContentView:view]; 270 [window makeFirstResponder:view]; 271 window.releasedWhenClosed = NO; 272 window.delegate = globalWindowDel; 273 return (__bridge_retained CFTypeRef)window; 274 } 275 } 276 277 void gio_close(CFTypeRef windowRef) { 278 NSWindow* window = (__bridge NSWindow *)windowRef; 279 [window performClose:nil]; 280 } 281 282 void gio_setSize(CFTypeRef windowRef, CGFloat width, CGFloat height) { 283 NSWindow* window = (__bridge NSWindow *)windowRef; 284 NSSize size = NSMakeSize(width, height); 285 [window setContentSize:size]; 286 } 287 288 void gio_setMinSize(CFTypeRef windowRef, CGFloat width, CGFloat height) { 289 NSWindow* window = (__bridge NSWindow *)windowRef; 290 window.contentMinSize = NSMakeSize(width, height); 291 } 292 293 void gio_setMaxSize(CFTypeRef windowRef, CGFloat width, CGFloat height) { 294 NSWindow* window = (__bridge NSWindow *)windowRef; 295 window.contentMaxSize = NSMakeSize(width, height); 296 } 297 298 void gio_setTitle(CFTypeRef windowRef, const char *title) { 299 NSWindow* window = (__bridge NSWindow *)windowRef; 300 window.title = [NSString stringWithUTF8String: title]; 301 } 302 303 CFTypeRef gio_layerForView(CFTypeRef viewRef) { 304 NSView *view = (__bridge NSView *)viewRef; 305 return (__bridge CFTypeRef)view.layer; 306 } 307 308 CFTypeRef gio_createView(void) { 309 @autoreleasepool { 310 NSRect frame = NSMakeRect(0, 0, 0, 0); 311 GioView* view = [[GioView alloc] initWithFrame:frame]; 312 [view setWantsLayer:YES]; 313 return CFBridgingRetain(view); 314 } 315 } 316 317 @implementation GioAppDelegate 318 - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { 319 [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; 320 [NSApp activateIgnoringOtherApps:YES]; 321 gio_onFinishLaunching(); 322 } 323 - (void)applicationDidHide:(NSNotification *)aNotification { 324 gio_onAppHide(); 325 } 326 - (void)applicationWillUnhide:(NSNotification *)notification { 327 gio_onAppShow(); 328 } 329 @end 330 331 void gio_main() { 332 @autoreleasepool { 333 [NSApplication sharedApplication]; 334 GioAppDelegate *del = [[GioAppDelegate alloc] init]; 335 [NSApp setDelegate:del]; 336 337 NSMenuItem *mainMenu = [NSMenuItem new]; 338 339 NSMenu *menu = [NSMenu new]; 340 NSMenuItem *hideMenuItem = [[NSMenuItem alloc] initWithTitle:@"Hide" 341 action:@selector(hide:) 342 keyEquivalent:@"h"]; 343 [menu addItem:hideMenuItem]; 344 NSMenuItem *quitMenuItem = [[NSMenuItem alloc] initWithTitle:@"Quit" 345 action:@selector(terminate:) 346 keyEquivalent:@"q"]; 347 [menu addItem:quitMenuItem]; 348 [mainMenu setSubmenu:menu]; 349 NSMenu *menuBar = [NSMenu new]; 350 [menuBar addItem:mainMenu]; 351 [NSApp setMainMenu:menuBar]; 352 353 globalWindowDel = [[GioWindowDelegate alloc] init]; 354 355 [NSApp run]; 356 } 357 }