/* * Copyright (c) 2012-2023 Julien Nadeau Carriere * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Driver for Cocoa framework. This is a multiple display driver (one * Cocoa window is created for each Agar window). */ #include #include #include #include #include #include #include #include #include #define GL_SILENCE_DEPRECATION #include #include #include #include #include #include static int nDrivers = 0; /* Drivers open */ static AG_DriverEventQ cocEventQ; /* Private event queue */ static AG_EventSink *cocEventSpinner = NULL; /* Standard event sink */ static AG_EventSink *cocEventEpilogue = NULL; /* Standard event epilogue */ struct ag_driver_cocoa; /* * Application delegate */ @interface AG_AppDelegate : NSObject - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender; @end @implementation AG_AppDelegate : NSObject - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender; { AG_QuitGUI(); return (NSTerminateCancel); } @end /* * Window interface */ @interface AG_CocoaWindow : NSWindow { @public AG_Window *_agarWindow; /* Corresponding Agar window */ } - (BOOL)canBecomeKeyWindow; - (BOOL)canBecomeMainWindow; @end @implementation AG_CocoaWindow - (BOOL)canBecomeKeyWindow { return (_agarWindow->flags & AG_WINDOW_DENYFOCUS) ? NO : YES; } - (BOOL)canBecomeMainWindow { return (_agarWindow->flags & AG_WINDOW_MAIN) ? YES : NO; } @end /* * View interface */ @interface AG_CocoaView : NSView { @public AG_Window *_agarWindow; /* Corresponding Agar window */ } - (void)rightMouseDown:(NSEvent *)theEvent; - (BOOL)preservesContentDuringLiveResize; - (void)viewWillStartLiveResize; - (void)viewDidEndLiveResize; @end @implementation AG_CocoaView - (void)rightMouseDown:(NSEvent *)theEvent { [[self nextResponder] rightMouseDown:theEvent]; } - (BOOL)preservesContentDuringLiveResize { return (YES); } - (void)viewWillStartLiveResize { [super viewWillStartLiveResize]; } - (void)viewDidEndLiveResize { [super viewDidEndLiveResize]; } @end /* * Event listener interface */ #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 @interface AG_CocoaListener : NSResponder { #else @interface AG_CocoaListener : NSResponder { #endif AG_Window *_window; struct ag_driver_cocoa *_driver; } -(void) listen:(struct ag_driver_cocoa *) driver; -(void) close; /* Window delegate functionality */ -(BOOL) windowShouldClose:(id) sender; -(void) windowDidExpose:(NSNotification *) aNotification; -(void) windowDidMove:(NSNotification *) aNotification; -(void) windowDidResize:(NSNotification *) aNotification; -(void) windowDidMiniaturize:(NSNotification *) aNotification; -(void) windowDidDeminiaturize:(NSNotification *) aNotification; -(void) windowDidBecomeKey:(NSNotification *) aNotification; -(void) windowDidResignKey:(NSNotification *) aNotification; @end /* Driver instance data */ typedef struct ag_driver_cocoa { struct ag_driver_mw _inherit; AG_CocoaWindow *win; /* Cocoa window */ AG_CocoaListener *evListener; /* Cocoa event listener */ NSOpenGLContext *glCtx; AG_GL_Context gl; AG_Mutex lock; /* Protect Cocoa calls */ Uint modFlags; /* Last modifier state */ #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 NSTrackingArea *trackArea; /* For mouse motion events */ #endif } AG_DriverCocoa; AG_DriverMwClass agDriverCocoa; #define AGDRIVER_IS_COCOA(drv) \ (AGDRIVER_CLASS(drv) == (AG_DriverClass *)&agDriverCocoa) static int COCOA_PendingEvents(void *_Nonnull); static int COCOA_GetNextEvent(void *_Nonnull, AG_DriverEvent *_Nonnull); static int COCOA_ProcessEvent(void *_Nonnull, AG_DriverEvent *_Nonnull); static void COCOA_PostResizeCallback(AG_Window *_Nonnull, AG_SizeAlloc *_Nonnull); static void COCOA_PostMoveCallback(AG_Window *_Nonnull, AG_SizeAlloc *_Nonnull); static int COCOA_RaiseWindow(AG_Window *_Nonnull); static int COCOA_SetInputFocus(AG_Window *_Nonnull); #if 0 static void COCOA_FreeWidgetResources(AG_Widget *_Nonnull); #endif static __inline__ void ConvertNSRect(NSRect *_Nonnull r) { r->origin.y = CGDisplayPixelsHigh(kCGDirectMainDisplay) - r->origin.y - r->size.height; } @implementation AG_CocoaListener - (void) listen:(AG_DriverCocoa *)co { NSNotificationCenter *nc; NSWindow *win = co->win; NSView *view = [win contentView]; _driver = co; _window = AGDRIVERMW(co)->win; nc = [NSNotificationCenter defaultCenter]; if ([win delegate] != nil) { [nc addObserver:self selector:@selector(windowDidExpose:) name:NSWindowDidExposeNotification object:win]; [nc addObserver:self selector:@selector(windowDidMove:) name:NSWindowDidMoveNotification object:win]; [nc addObserver:self selector:@selector(windowDidResize:) name:NSWindowDidResizeNotification object:win]; [nc addObserver:self selector:@selector(windowDidMiniaturize:) name:NSWindowDidMiniaturizeNotification object:win]; [nc addObserver:self selector:@selector(windowDidDeminiaturize:) name:NSWindowDidDeminiaturizeNotification object:win]; [nc addObserver:self selector:@selector(windowDidBecomeKey:) name:NSWindowDidBecomeKeyNotification object:win]; [nc addObserver:self selector:@selector(windowDidResignKey:) name:NSWindowDidResignKeyNotification object:win]; } else { [win setDelegate:self]; } [win setNextResponder:self]; #if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 [win setAcceptsMouseMovedEvents:YES]; #endif [view setNextResponder:self]; #if MAC_OS_X_VERSION_MAX_ALLOWED >= 1060 if ([view respondsToSelector:@selector(setAcceptsTouchEvents:)]) [view setAcceptsTouchEvents:YES]; #endif } - (void)close { NSNotificationCenter *nc; AG_DriverCocoa *co = _driver; NSWindow *win = co->win; NSView *view = [win contentView]; nc = [NSNotificationCenter defaultCenter]; if ([win delegate] != self) { [nc removeObserver:self name:NSWindowDidExposeNotification object:win]; [nc removeObserver:self name:NSWindowDidMoveNotification object:win]; [nc removeObserver:self name:NSWindowDidResizeNotification object:win]; [nc removeObserver:self name:NSWindowDidMiniaturizeNotification object:win]; [nc removeObserver:self name:NSWindowDidDeminiaturizeNotification object:win]; [nc removeObserver:self name:NSWindowDidBecomeKeyNotification object:win]; [nc removeObserver:self name:NSWindowDidResignKeyNotification object:win]; } else { [win setDelegate:nil]; } if ([win nextResponder] == self) { [win setNextResponder:nil]; } if ([view nextResponder] == self) { [view setNextResponder:nil]; } } - (BOOL)windowShouldClose:(id)sender { AG_DriverEvent *dev; if ((dev = TryMalloc(sizeof(AG_DriverEvent))) != NULL) { dev->type = AG_DRIVER_CLOSE; dev->win = _window; TAILQ_INSERT_TAIL(&cocEventQ, dev, events); } return NO; } - (void)windowDidExpose:(NSNotification *)aNotification { AG_DriverEvent *dev; if ((dev = TryMalloc(sizeof(AG_DriverEvent))) != NULL) { dev->type = AG_DRIVER_EXPOSE; dev->win = _window; TAILQ_INSERT_TAIL(&cocEventQ, dev, events); } } - (void)windowDidMove:(NSNotification *)aNotification { AG_DriverCocoa *co = _driver; AG_Window *win = _window; NSRect rect; AG_SizeAlloc a; rect = [co->win contentRectForFrameRect:[co->win frame]]; ConvertNSRect(&rect); a.x = (int)rect.origin.x; a.y = (int)rect.origin.y; a.w = WIDGET(win)->w; a.h = WIDGET(win)->h; COCOA_PostMoveCallback(win, &a); } - (void)windowDidResize:(NSNotification *)aNotification { AG_Window *win = _window; AG_DriverCocoa *co = _driver; AG_SizeAlloc a; NSRect rect; rect = [co->win contentRectForFrameRect:[co->win frame]]; ConvertNSRect(&rect); /* * Since the event loop will not run during a live resize * operation, we can't use AG_DRIVER_VIDEORESIZE, and we * must redraw the window immediately. */ a.x = (int)rect.origin.x; a.y = (int)rect.origin.y; a.w = (int)rect.size.width; a.h = (int)rect.size.height; AG_MutexLock(&co->lock); AG_ObjectLock(win); if (a.w != WIDTH(win) || a.h != HEIGHT(win)) { COCOA_PostResizeCallback(win, &a); } else { COCOA_PostMoveCallback(win, &a); } if (win->visible) { AG_BeginRendering(_driver); AG_WindowDraw(win); AG_EndRendering(_driver); } AG_ObjectUnlock(win); AG_MutexUnlock(&co->lock); } - (void)windowDidMiniaturize:(NSNotification *)aNotification { _window->flags |= AG_WINDOW_MINIMIZED; } - (void)windowDidDeminiaturize:(NSNotification *)aNotification { _window->flags &= ~(AG_WINDOW_MINIMIZED); } - (void)windowDidBecomeKey:(NSNotification *)aNotification { AG_DriverEvent *dev; agWindowFocused = _window; if ((dev = TryMalloc(sizeof(AG_DriverEvent))) != NULL) { dev->type = AG_DRIVER_FOCUS_IN; dev->win = _window; TAILQ_INSERT_TAIL(&cocEventQ, dev, events); } } - (void)windowDidResignKey:(NSNotification *)aNotification { AG_DriverEvent *dev; if (agWindowFocused != _window) { return; } agWindowFocused = NULL; if ((dev = TryMalloc(sizeof(AG_DriverEvent))) != NULL) { dev->type = AG_DRIVER_FOCUS_OUT; dev->win = _window; TAILQ_INSERT_TAIL(&cocEventQ, dev, events); } } @end static void Init(void *_Nonnull obj) { AG_DriverCocoa *co = obj; co->win = NULL; co->glCtx = NULL; co->modFlags = 0; #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 co->trackArea = nil; #endif AG_MutexInitRecursive(&co->lock); } static void Destroy(void *_Nonnull obj) { AG_DriverCocoa *co = obj; AG_MutexDestroy(&co->lock); } /* * Standard AG_EventLoop() event sink. */ static int COCOA_EventSink(AG_EventSink *_Nonnull es, AG_Event *_Nonnull event) { AG_DriverEvent dev; if (COCOA_GetNextEvent(NULL, &dev) == 1) { return COCOA_ProcessEvent(NULL, &dev); } return (0); } static int COCOA_EventEpilogue(AG_EventSink *_Nonnull es, AG_Event *_Nonnull event) { AG_WindowDrawQueued(); AG_WindowProcessQueued(); return (0); } static int COCOA_Open(void *_Nonnull obj, const char *_Nullable spec) { AG_Driver *drv = obj; AG_DriverCocoa *co = obj; NSAutoreleasePool *pool; /* Driver manages rendering of window background. */ drv->flags |= AG_DRIVER_WINDOW_BG; /* Initialize NSApp if needed. */ pool = [[NSAutoreleasePool alloc] init]; if (NSApp == nil) { NSApp = [NSApplication sharedApplication]; [NSApp finishLaunching]; } if ([NSApp delegate] == nil) { [NSApp setDelegate:[[AG_AppDelegate alloc] init]]; } [pool release]; /* Initialize the core mouse and keyboard */ /* TODO: touch handling */ if ((drv->mouse = AG_MouseNew(co, "X mouse")) == NULL || (drv->kbd = AG_KeyboardNew(co, "X keyboard")) == NULL) { goto fail; } if (nDrivers == 0) { TAILQ_INIT(&cocEventQ); /* Set up event filters for standard AG_EventLoop(). */ if ((cocEventSpinner = AG_AddEventSpinner(COCOA_EventSink, NULL)) == NULL || (cocEventEpilogue = AG_AddEventEpilogue(COCOA_EventEpilogue, NULL)) == NULL) goto fail; } nDrivers++; return (0); fail: if (cocEventSpinner != NULL) { AG_DelEventSpinner(cocEventSpinner); cocEventSpinner = NULL; } if (cocEventEpilogue != NULL) { AG_DelEventEpilogue(cocEventEpilogue); cocEventEpilogue = NULL; } if (drv->kbd != NULL) { AG_ObjectDelete(drv->kbd); drv->kbd = NULL; } if (drv->mouse != NULL) { AG_ObjectDelete(drv->mouse); drv->mouse = NULL; } return (-1); } static void COCOA_Close(void *_Nonnull obj) { AG_Driver *drv = obj; #ifdef AG_DEBUG if (nDrivers == 0) { AG_FatalError("Driver close without open"); } #endif if (--nDrivers == 0) { AG_DriverEvent *dev, *devNext; AG_DelEventSink(cocEventSpinner); cocEventSpinner = NULL; AG_DelEventEpilogue(cocEventEpilogue); cocEventEpilogue = NULL; for (dev = TAILQ_FIRST(&cocEventQ); dev != TAILQ_LAST(&cocEventQ, ag_driver_eventq); dev = devNext) { devNext = TAILQ_NEXT(dev, events); Free(dev); } TAILQ_INIT(&cocEventQ); } AG_ObjectDelete(drv->mouse); drv->mouse = NULL; AG_ObjectDelete(drv->kbd); drv->kbd = NULL; } static int COCOA_GetDisplaySize(Uint *_Nonnull w, Uint *_Nonnull h) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSScreen *screen; NSRect sr; /* XXX this is probably wrong */ screen = [NSScreen mainScreen]; sr = [screen frame]; *w = sr.size.width; *h = sr.size.height; [pool release]; return (0); } static int COCOA_PendingEvents(void *_Nonnull drvCaller) { NSAutoreleasePool *pool; NSEvent *ev; int rv; if (!TAILQ_EMPTY(&cocEventQ)) return (1); pool = [[NSAutoreleasePool alloc] init]; ev = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:NO]; rv = (ev != nil); [pool release]; return (rv); } /* Convert a NSEvent mouse button number to AG_MouseButton. */ static AG_MouseButton _Const_Attribute GetMouseButton(int which) { switch (which) { case 0: return (AG_MOUSE_LEFT); case 1: return (AG_MOUSE_RIGHT); case 2: return (AG_MOUSE_MIDDLE); default: return (which+1); } } static AG_MouseButton _Const_Attribute GetScrollWheelButton(float x, float y) { if (x > 0) { return (AG_MOUSE_X1); } else if (x < 0) { return (AG_MOUSE_X2); } if (y > 0) { return (AG_MOUSE_WHEELUP); } else if (y < 0) { return (AG_MOUSE_WHEELDOWN); } return (AG_MOUSE_NONE); } /* Add a keyboard event to the queue. */ static void QueueKeyEvent(AG_DriverCocoa *_Nonnull co, enum ag_driver_event_type type, AG_KeySym ks, Uint32 ucs) { AG_DriverEvent *dev; if ((dev = TryMalloc(sizeof(AG_DriverEvent))) == NULL) { AG_Verbose("Out of memory for keymod event\n"); return; } dev->type = type; dev->win = AGDRIVERMW(co)->win; dev->key.ks = ks; dev->key.ucs = ucs; TAILQ_INSERT_TAIL(&cocEventQ, dev, events); } static int COCOA_GetNextEvent(void *_Nullable drvCaller, AG_DriverEvent *_Nonnull dev) { NSAutoreleasePool *pool; AG_CocoaWindow *coWin; AG_Window *win; AG_Driver *drv; AG_DriverCocoa *co; AG_DriverEvent *devFirst; NSEvent *event; AG_Mouse *ms; int rv = 0; if (!TAILQ_EMPTY(&cocEventQ)) goto out_dequeue; pool = [[NSAutoreleasePool alloc] init]; event = [NSApp nextEventMatchingMask:NSAnyEventMask untilDate:[NSDate distantPast] inMode:NSDefaultRunLoopMode dequeue:YES]; if (event == nil) { goto out; } if ((coWin = (AG_CocoaWindow *)[event window]) == nil) { [NSApp sendEvent:event]; goto out; } win = coWin->_agarWindow; AG_LockVFS(&agDrivers); drv = WIDGET(win)->drv; co = (AG_DriverCocoa *)drv; ms = drv->mouse; switch ([event type]) { case NSMouseMoved: case NSLeftMouseDragged: case NSRightMouseDragged: case NSOtherMouseDragged: { NSPoint point = [event locationInWindow]; const int x = (int)point.x; const int y = (int)(WIDGET(win)->h - point.y); ms->xRel = x - ms->x; /* Update state */ ms->yRel = y - ms->y; ms->x = x; ms->y = y; dev->type = AG_DRIVER_MOUSE_MOTION; dev->win = win; dev->motion.x = x; dev->motion.y = y; rv = 1; break; } case NSScrollWheel: { float x = [event deltaX]; float y = [event deltaY]; AG_MouseButton btn = GetScrollWheelButton(x, y); if (btn == AG_MOUSE_NONE) { break; } ms->btnState |= AG_MOUSE_BUTTON(btn); dev->type = AG_DRIVER_MOUSE_BUTTON_DOWN; dev->win = win; dev->button.which = btn; dev->button.x = drv->mouse->x; dev->button.y = drv->mouse->y; rv = 1; break; } case NSLeftMouseDown: case NSOtherMouseDown: case NSRightMouseDown: { AG_MouseButton btn = GetMouseButton([event buttonNumber]); ms->btnState |= AG_MOUSE_BUTTON(btn); dev->type = AG_DRIVER_MOUSE_BUTTON_DOWN; dev->win = win; dev->button.which = btn; dev->button.x = drv->mouse->x; dev->button.y = drv->mouse->y; rv = 1; break; } case NSLeftMouseUp: case NSOtherMouseUp: case NSRightMouseUp: { AG_MouseButton btn = GetMouseButton([event buttonNumber]); ms->btnState &= ~( AG_MOUSE_BUTTON(btn) ); dev->type = AG_DRIVER_MOUSE_BUTTON_UP; dev->win = win; dev->button.which = btn; dev->button.x = drv->mouse->x; dev->button.y = drv->mouse->y; rv = 1; break; } case NSMouseEntered: if ([co->win isKeyWindow]) { dev->type = AG_DRIVER_MOUSE_ENTER; dev->win = win; rv = 1; } else { rv = 0; } break; case NSMouseExited: if ([co->win isKeyWindow]) { dev->type = AG_DRIVER_MOUSE_LEAVE; dev->win = win; rv = 1; } else { rv = 0; } break; case NSKeyDown: if ([event isARepeat]) { /* Agar implements its own key repeat */ goto out; } /* FALLTHROUGH */ case NSKeyUp: { NSString *characters = [event characters]; enum ag_driver_event_type evType; enum ag_keyboard_action kbdAction; AG_KeySym ks; unichar c; NSUInteger i; if ([characters length] == 0) goto out; if ([event type] == NSKeyDown) { evType = AG_DRIVER_KEY_DOWN; kbdAction = AG_KEY_PRESSED; } else { evType = AG_DRIVER_KEY_UP; kbdAction = AG_KEY_RELEASED; } /* Look for matching function keys first. */ c = [characters characterAtIndex: 0]; ks = AG_KEY_NONE; for (i = 0; i < agCocoaFunctionKeysSize; i++) { const struct ag_cocoa_function_key *fnKey = &agCocoaFunctionKeys[i]; if (fnKey->uc == c) { ks = fnKey->keySym; break; } } if (ks != AG_KEY_NONE) { AG_KeyboardUpdate(drv->kbd, kbdAction, ks); dev->type = evType; dev->win = win; dev->key.ks = ks; if (ks == AG_KEY_RETURN) { dev->key.ucs = '\n'; } else { dev->key.ucs = 0; } rv = 1; goto out; } /* Process as a character sequence. */ for (i = 0; i < [characters length]; i++) { AG_KeySym ks; c = [characters characterAtIndex: i]; ks = (c <= AG_KEY_ASCII_END) ? (AG_KeySym)c : AG_KEY_NONE; AG_KeyboardUpdate(drv->kbd, kbdAction, ks); if (i == 0) { dev->type = evType; dev->win = win; dev->key.ks = ks; dev->key.ucs = (Uint32)c; rv = 1; } else { QueueKeyEvent(co, evType, ks, (Uint32)c); } } if (rv == 1) goto out; } case NSFlagsChanged: { Uint modFlags = [event modifierFlags]; int i, nChanged = 0; for (i = 0; i < agCocoaKeymodSize; i++) { const struct ag_cocoa_keymod_entry *kmEnt = &agCocoaKeymod[i]; if ((modFlags & kmEnt->keyMask) && !(co->modFlags & kmEnt->keyMask)) { AG_KeyboardUpdate(drv->kbd, AG_KEY_PRESSED, kmEnt->keySym); QueueKeyEvent(co, AG_DRIVER_KEY_DOWN, kmEnt->keySym, 0); nChanged++; } else if (!(modFlags & kmEnt->keyMask) && (co->modFlags & kmEnt->keyMask)) { AG_KeyboardUpdate(drv->kbd, AG_KEY_RELEASED, kmEnt->keySym); QueueKeyEvent(co, AG_DRIVER_KEY_UP, kmEnt->keySym, 0); nChanged++; } } co->modFlags = modFlags; if (nChanged > 0) { goto out_dequeue; } break; } default: break; } [NSApp sendEvent:event]; out: [pool release]; return (rv); out_dequeue: devFirst = TAILQ_FIRST(&cocEventQ); TAILQ_REMOVE(&cocEventQ, devFirst, events); memcpy(dev, devFirst, sizeof(AG_DriverEvent)); Free(devFirst); return (1); } static int COCOA_ProcessEvent(void *_Nullable drvCaller, AG_DriverEvent *_Nonnull dev) { AG_Driver *drv; AG_Window *win; int rv=1, useText; if ((win = dev->win) == NULL || win->flags & AG_WINDOW_DETACHING) return (0); AG_LockVFS(&agDrivers); drv = WIDGET(win)->drv; if ((useText = (win->flags & AG_WINDOW_USE_TEXT))) { AG_PushTextState(); AG_TextFont(WIDGET(win)->font); AG_TextColor(&WIDGET(win)->pal.c[WIDGET(win)->state] [AG_TEXT_COLOR]); } switch (dev->type) { case AG_DRIVER_MOUSE_MOTION: AG_ProcessMouseMotion(win, dev->motion.x, dev->motion.y, drv->mouse->xRel, drv->mouse->yRel); AG_MouseCursorUpdate(win, dev->motion.x, dev->motion.y); break; case AG_DRIVER_MOUSE_BUTTON_DOWN: AG_ProcessMouseButtonDown(win, dev->button.x, dev->button.y, dev->button.which); break; case AG_DRIVER_MOUSE_BUTTON_UP: AG_ProcessMouseButtonUp(win, dev->button.x, dev->button.y, dev->button.which); break; case AG_DRIVER_KEY_UP: AG_ProcessKey(drv->kbd, win, AG_KEY_RELEASED, dev->key.ks, dev->key.ucs); break; case AG_DRIVER_KEY_DOWN: AG_ProcessKey(drv->kbd, win, AG_KEY_PRESSED, dev->key.ks, dev->key.ucs); break; case AG_DRIVER_MOUSE_ENTER: AG_PostEvent(win, "window-enter", NULL); break; case AG_DRIVER_MOUSE_LEAVE: AG_PostEvent(win, "window-leave", NULL); break; case AG_DRIVER_FOCUS_IN: agWindowFocused = win; AG_PostEvent(win, "window-gainfocus", NULL); break; case AG_DRIVER_FOCUS_OUT: AG_PostEvent(win, "window-lostfocus", NULL); break; case AG_DRIVER_CLOSE: AG_PostEvent(win, "window-close", NULL); break; case AG_DRIVER_EXPOSE: win->dirty = 1; break; default: rv = 0; break; } if (useText) { AG_PopTextState(); } AG_UnlockVFS(&agDrivers); return (rv); } /* Select the window's OpenGL context. */ static __inline__ void COCOA_GL_MakeCurrent(AG_DriverCocoa *_Nonnull co) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; [co->glCtx setView:[co->win contentView]]; [co->glCtx update]; [co->glCtx makeCurrentContext]; [pool release]; } static void COCOA_BeginRendering(void *_Nonnull obj) { AG_DriverCocoa *co = obj; AG_MutexLock(&co->lock); COCOA_GL_MakeCurrent(co); } static void COCOA_RenderWindow(AG_Window *_Nonnull win) { const AG_Color *cBg = &WCOLOR(win, BG_COLOR); AG_PushClipRect(win, &WIDGET(win)->r); glClearColor((float)cBg->r / AG_COLOR_LASTF, (float)cBg->g / AG_COLOR_LASTF, (float)cBg->b / AG_COLOR_LASTF, 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); AG_WidgetDraw(win); AG_PopClipRect(win); } static void COCOA_EndRendering(void *_Nonnull obj) { AG_DriverCocoa *co = obj; AG_GL_Context *gl = &co->gl; Uint i; [co->glCtx flushBuffer]; /* Remove textures and display lists queued for deletion. */ glDeleteTextures(gl->nTextureGC, (const GLuint *)gl->textureGC); for (i = 0; i < gl->nListGC; i++) { glDeleteLists(gl->listGC[i], 1); } gl->nTextureGC = 0; gl->nListGC = 0; AG_MutexUnlock(&co->lock); } /* * Window operations */ static void SetBackgroundColor(AG_DriverCocoa *_Nonnull co, const AG_Color *_Nonnull c) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; CGFloat r = (CGFloat)(c->r / AG_COLOR_LASTF); CGFloat g = (CGFloat)(c->g / AG_COLOR_LASTF); CGFloat b = (CGFloat)(c->b / AG_COLOR_LASTF); CGFloat a = (CGFloat)(c->a / AG_COLOR_LASTF); NSColor *bgColor; bgColor = [NSColor colorWithCalibratedRed:r green:g blue:b alpha:a]; [co->win setBackgroundColor:bgColor]; [pool release]; } static int COCOA_OpenWindow(AG_Window *_Nonnull win, const AG_Rect *_Nonnull r, int depthReq, Uint mwFlags) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; AG_DriverCocoa *co = (AG_DriverCocoa *)WIDGET(win)->drv; AG_Driver *drv = WIDGET(win)->drv; Uint winStyle = 0; NSRect winRect, scrRect; NSArray *screens; NSScreen *screen, *selScreen = nil; AG_CocoaView *contentView; NSOpenGLPixelFormatAttribute pfAttr[32]; NSOpenGLPixelFormat *pf; int i, count; AG_SizeAlloc a; if (depthReq == 0) depthReq = 24; AG_MutexLock(&co->lock); /* Set the window style. */ if (win->flags & AG_WINDOW_NOBORDERS) { winStyle = NSBorderlessWindowMask; } else { if (!(win->flags & AG_WINDOW_NOTITLE)) { winStyle |= NSTitledWindowMask; } if (!(win->flags & AG_WINDOW_NOCLOSE)) { winStyle |= NSClosableWindowMask; } if (!(win->flags & AG_WINDOW_NOMINIMIZE)) { winStyle |= NSMiniaturizableWindowMask; } if (!(win->flags & AG_WINDOW_NORESIZE)) { winStyle |= NSResizableWindowMask; } } /* Set the window coordinates. */ winRect.origin.x = r->x; winRect.origin.y = r->y; winRect.size.width = r->w; winRect.size.height = r->h; ConvertNSRect(&winRect); /* Select a screen. */ screens = [NSScreen screens]; count = [screens count]; for (i = 0; i < count; i++) { screen = [screens objectAtIndex:i]; scrRect = [screen frame]; if (winRect.origin.x >= scrRect.origin.x && winRect.origin.y >= scrRect.origin.y && winRect.origin.x < scrRect.origin.x + scrRect.size.width && winRect.origin.y < scrRect.origin.y + scrRect.size.height) { selScreen = screen; winRect.origin.x -= scrRect.origin.x; winRect.origin.y -= scrRect.origin.y; break; } } /* Create the window. */ if (selScreen == nil) { co->win = [[AG_CocoaWindow alloc] initWithContentRect:winRect styleMask:winStyle backing:NSBackingStoreBuffered defer:YES]; } else { co->win = [[AG_CocoaWindow alloc] initWithContentRect:winRect styleMask:winStyle backing:NSBackingStoreBuffered defer:YES screen:selScreen]; } co->win->_agarWindow = win; SetBackgroundColor(co, &WCOLOR(win, BG_COLOR)); if (win->flags & AG_WINDOW_MAIN) [co->win makeMainWindow]; /* Create an event listener. */ co->evListener = [[AG_CocoaListener alloc] init]; /* Obtain the effective window coordinates; set up our contentView. */ winRect = [co->win contentRectForFrameRect:[co->win frame]]; contentView = [[AG_CocoaView alloc] initWithFrame:winRect]; contentView->_agarWindow = win; [co->win setContentView: contentView]; [contentView release]; /* Set our event listener. */ [co->evListener listen:co]; /* Retrieve the effective style flags. */ winStyle = [co->win styleMask]; if (winStyle == NSBorderlessWindowMask) { win->flags |= AG_WINDOW_NOBORDERS; } else { if (!(winStyle & NSTitledWindowMask)) { win->flags |= AG_WINDOW_NOTITLE; } if (!(winStyle & NSClosableWindowMask)) { win->flags |= AG_WINDOW_NOCLOSE; } if (!(winStyle & NSMiniaturizableWindowMask)) { win->flags |= AG_WINDOW_NOMINIMIZE; } if (!(winStyle & NSResizableWindowMask)) { win->flags |= AG_WINDOW_NORESIZE; } } /* Retrieve the effective maximize/minimize and focus state. */ if (!(win->flags & AG_WINDOW_NORESIZE) && [co->win isZoomed]) { win->flags |= AG_WINDOW_MAXIMIZED; } else { win->flags &= ~(AG_WINDOW_MAXIMIZED); } if ([co->win isMiniaturized]) { win->flags |= AG_WINDOW_MINIMIZED; } else { win->flags &= ~(AG_WINDOW_MINIMIZED); } if ([co->win isKeyWindow]) agWindowFocused = win; /* Create an OpenGL rendering context. */ i = 0; pfAttr[i++] = NSOpenGLPFADepthSize; pfAttr[i++] = depthReq; pfAttr[i++] = NSOpenGLPFADoubleBuffer; if (agStereo) { pfAttr[i++] = NSOpenGLPFAStereo; } pfAttr[i] = 0; pf = [[NSOpenGLPixelFormat alloc] initWithAttributes:pfAttr]; if (pf == nil) { AG_SetError("Cannot create NSOpenGLPixelFormat"); goto fail; } co->glCtx = [[NSOpenGLContext alloc] initWithFormat:pf shareContext:nil]; [pf release]; if (co->glCtx == nil) { AG_SetError("Cannot create NSOpenGLContext"); goto fail; } [co->glCtx update]; [co->glCtx makeCurrentContext]; AG_GL_InitContext(co, &co->gl); /* XXX TODO: how to check effective depth? */ /* Set the pixel formats. */ if ((drv->videoFmt = TryMalloc(sizeof(AG_PixelFormat))) == NULL) { goto fail_ctx; } #if AG_MODEL == AG_LARGE if (depthReq == 48) { /* Deep color */ # if AG_BYTEORDER == AG_BIG_ENDIAN AG_PixelFormatRGB(drv->videoFmt, depthReq, 0xffff000000000000, 0x0000ffff00000000, 0x00000000ffff0000); # else AG_PixelFormatRGB(drv->videoFmt, depthReq, 0x000000000000ffff, 0x00000000ffff0000, 0x0000ffff00000000); # endif } else #endif /* AG_LARGE */ { /* True Color */ #if AG_BYTEORDER == AG_BIG_ENDIAN AG_PixelFormatRGB(drv->videoFmt, depthReq, 0xff000000, 0x00ff0000, 0x0000ff00); #else AG_PixelFormatRGB(drv->videoFmt, depthReq, 0x000000ff, 0x0000ff00, 0x00ff0000); #endif } /* * Set the effective window geometry, initialize the viewport * and tracking rectangle. */ ConvertNSRect(&winRect); a.x = winRect.origin.x; a.y = winRect.origin.y; a.w = winRect.size.width; a.h = winRect.size.height; COCOA_PostResizeCallback(win, &a); AG_MutexUnlock(&co->lock); [pool release]; return (0); fail_ctx: AG_GL_DestroyContext(co); fail: [NSOpenGLContext clearCurrentContext]; [co->evListener close]; [co->evListener release]; [co->win close]; if (drv->videoFmt) { AG_PixelFormatFree(drv->videoFmt); free(drv->videoFmt); drv->videoFmt = NULL; } AG_MutexUnlock(&co->lock); [pool release]; return (-1); } static void COCOA_CloseWindow(AG_Window *_Nonnull win) { AG_Driver *drv = WIDGET(win)->drv; AG_DriverCocoa *co = (AG_DriverCocoa *)drv; NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; AG_MutexLock(&co->lock); #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 if (co->trackArea != nil) { [[co->win contentView] removeTrackingArea:co->trackArea]; [co->trackArea release]; co->trackArea = nil; } #endif /* Destroy our OpenGL rendering context. */ COCOA_GL_MakeCurrent(co); AG_GL_DestroyContext(drv); [co->glCtx clearDrawable]; [co->glCtx release]; /* Close the window. */ [co->evListener close]; [co->evListener release]; [co->win close]; AG_PixelFormatFree(drv->videoFmt); free(drv->videoFmt); drv->videoFmt = NULL; AG_MutexUnlock(&co->lock); [pool release]; } static int COCOA_MapWindow(AG_Window *_Nonnull win) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; AG_DriverCocoa *co = (AG_DriverCocoa *)WIDGET(win)->drv; AG_MutexLock(&co->lock); if (![co->win isMiniaturized]) { [co->win makeKeyAndOrderFront:nil]; } AG_MutexUnlock(&co->lock); [pool release]; return (0); } static int COCOA_UnmapWindow(AG_Window *_Nonnull win) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; AG_DriverCocoa *co = (AG_DriverCocoa *)WIDGET(win)->drv; AG_MutexLock(&co->lock); [co->win orderOut:nil]; AG_MutexUnlock(&co->lock); [pool release]; return (0); } static int COCOA_RaiseWindow(AG_Window *_Nonnull win) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; AG_DriverCocoa *co = (AG_DriverCocoa *)WIDGET(win)->drv; AG_MutexLock(&co->lock); [co->win makeKeyAndOrderFront:nil]; AG_MutexUnlock(&co->lock); [pool release]; return (0); } static int COCOA_LowerWindow(AG_Window *_Nonnull win) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; AG_DriverCocoa *co = (AG_DriverCocoa *)WIDGET(win)->drv; AG_MutexLock(&co->lock); if (![co->win isMiniaturized]) { [co->win orderBack:nil]; } AG_MutexUnlock(&co->lock); [pool release]; return (0); } static int COCOA_ReparentWindow(AG_Window *_Nonnull win, AG_Window *_Nonnull winParent, int x, int y) { /* TODO */ AG_SetError("Reparent window not implemented"); return (-1); } static int COCOA_GetInputFocus(AG_Window *_Nonnull *_Nonnull rv) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; AG_DriverCocoa *co = NULL; AGOBJECT_FOREACH_CHILD(co, &agDrivers, ag_driver_cocoa) { if (!AGDRIVER_IS_COCOA(co)) { continue; } AG_MutexLock(&co->lock); if ([co->win isKeyWindow]) { AG_MutexUnlock(&co->lock); break; } AG_MutexUnlock(&co->lock); } if (co == NULL) { AG_SetError("Input focus is external to this application"); goto fail; } *rv = AGDRIVERMW(co)->win; [pool release]; return (0); fail: [pool release]; return (-1); } static int COCOA_SetInputFocus(AG_Window *_Nonnull win) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; AG_DriverCocoa *co = (AG_DriverCocoa *)WIDGET(win)->drv; AG_MutexLock(&co->lock); /* XXX use makeKeyWindow? */ [co->win makeKeyAndOrderFront:nil]; AG_MutexUnlock(&co->lock); [pool release]; return (0); } static void COCOA_PreResizeCallback(AG_Window *_Nonnull win) { #if 0 AG_DriverCocoa *co = (AG_DriverCocoa *)WIDGET(win)->drv; /* * Backup all GL resources since it is not portable to assume that a * display resize will not cause a change in GL contexts * (XXX TODO test for platforms where this is unnecessary) * (XXX is this correctly done?) */ COCOA_GL_MakeCurrent(co); COCOA_FreeWidgetResources(WIDGET(win)); AG_TextClearGlyphCache(co); #endif } static void COCOA_PostResizeCallback(AG_Window *_Nonnull win, AG_SizeAlloc *_Nonnull a) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; AG_Driver *drv = WIDGET(win)->drv; AG_DriverCocoa *co = (AG_DriverCocoa *)drv; AG_Rect r; NSRect trackRect; int x = a->x; int y = a->y; AG_MutexLock(&co->lock); a->x = 0; a->y = 0; AG_WidgetSizeAlloc(win, a); AG_WidgetUpdateCoords(win, 0,0); /* The viewport coordinates have changed. */ [co->glCtx makeCurrentContext]; r.x = 0; r.y = 0; r.w = WIDTH(win); r.h = HEIGHT(win); AG_GL_SetViewport(&co->gl, &r); #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050 /* Update the tracking rectangle. */ if (co->trackArea != nil) { [[co->win contentView] removeTrackingArea:co->trackArea]; [co->trackArea release]; co->trackArea = nil; } trackRect.origin.x = 0; trackRect.origin.y = 0; trackRect.size.width = WIDTH(win); trackRect.size.height = HEIGHT(win); co->trackArea = [[NSTrackingArea alloc] initWithRect:trackRect options: (NSTrackingMouseEnteredAndExited| NSTrackingMouseMoved| NSTrackingActiveAlways) owner:co->win userInfo:nil]; [[co->win contentView] addTrackingArea:co->trackArea]; #endif /* >= 10.5 */ AG_MutexUnlock(&co->lock); /* Save the new effective window position. */ WIDGET(win)->x = a->x = x; WIDGET(win)->y = a->y = y; [pool release]; } static void COCOA_PostMoveCallback(AG_Window *_Nonnull win, AG_SizeAlloc *_Nonnull a) { AG_Driver *drv = WIDGET(win)->drv; AG_DriverCocoa *co = (AG_DriverCocoa *)drv; AG_SizeAlloc aNew; int xRel, yRel; AG_MutexLock(&co->lock); xRel = a->x - WIDGET(win)->x; yRel = a->y - WIDGET(win)->y; /* Update the window coordinates. */ aNew.x = 0; aNew.y = 0; aNew.w = a->w; aNew.h = a->h; AG_WidgetSizeAlloc(win, &aNew); AG_WidgetUpdateCoords(win, 0,0); WIDGET(win)->x = a->x; WIDGET(win)->y = a->y; win->dirty = 1; if (agWindowPinnedCount > 0) AG_WindowMovePinned(win, xRel, yRel); AG_MutexUnlock(&co->lock); } static int COCOA_MoveWindow(AG_Window *_Nonnull win, int x, int y) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; AG_DriverCocoa *co = (AG_DriverCocoa *)WIDGET(win)->drv; NSScreen *screen = [co->win screen]; NSRect scrRect = [screen frame]; NSPoint pt; pt.x = x; pt.y = scrRect.size.height - y; AG_MutexLock(&co->lock); [co->win setFrameTopLeftPoint:pt]; AG_MutexUnlock(&co->lock); [pool release]; return (0); } #if 0 /* Save/restore associated widget GL resources (for GL context changes). */ static void COCOA_FreeWidgetResources(AG_Widget *_Nonnull wid) { AG_Widget *chld; OBJECT_FOREACH_CHILD(chld, wid, ag_widget) { COCOA_FreeWidgetResources(chld); } AG_WidgetFreeResourcesGL(wid); } #endif static int COCOA_ResizeWindow(AG_Window *_Nonnull win, Uint w, Uint h) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; AG_DriverCocoa *co = (AG_DriverCocoa *)WIDGET(win)->drv; NSSize sz; sz.width = w; sz.height = h; AG_MutexLock(&co->lock); [co->win setContentSize:sz]; AG_MutexUnlock(&co->lock); [pool release]; return (0); } static int COCOA_MoveResizeWindow(AG_Window *_Nonnull win, AG_SizeAlloc *_Nonnull a) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; AG_DriverCocoa *co = (AG_DriverCocoa *)WIDGET(win)->drv; NSScreen *screen = [co->win screen]; NSRect scrRect = [screen frame]; NSPoint pt; NSSize sz; pt.x = a->x; pt.y = scrRect.size.height - a->y; sz.width = a->w; sz.height = a->h; AG_MutexLock(&co->lock); [co->win setFrameTopLeftPoint:pt]; [co->win setContentSize:sz]; AG_MutexUnlock(&co->lock); [pool release]; return (0); } static int COCOA_SetBorderWidth(AG_Window *_Nonnull win, Uint width) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; AG_DriverCocoa *co = (AG_DriverCocoa *)WIDGET(win)->drv; AG_MutexLock(&co->lock); if (([co->win styleMask] & NSTexturedBackgroundWindowMask) == 0) { [co->win setContentBorderThickness:width forEdge:NSMaxYEdge]; } [co->win setContentBorderThickness:width forEdge:NSMinYEdge]; AG_MutexUnlock(&co->lock); [pool release]; return (0); } static int COCOA_SetWindowCaption(AG_Window *_Nonnull win, const char *_Nonnull s) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; AG_DriverCocoa *co = (AG_DriverCocoa *)WIDGET(win)->drv; NSString *string; if (win->caption[0] != '\0') { string = [[NSString alloc] initWithUTF8String:win->caption]; } else { string = [[NSString alloc] init]; } AG_MutexLock(&co->lock); [co->win setTitle:string]; AG_MutexUnlock(&co->lock); [string release]; [pool release]; return (0); } static int COCOA_SetOpacity(AG_Window *_Nonnull win, float f) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; AG_DriverCocoa *co = (AG_DriverCocoa *)WIDGET(win)->drv; AG_MutexLock(&co->lock); if (f > 0.99) { [co->win setOpaque:YES]; [co->win setAlphaValue:1.0]; } else { [co->win setOpaque:NO]; [co->win setAlphaValue:f]; } AG_MutexUnlock(&co->lock); [pool release]; return (0); } static void COCOA_TweakAlignment(AG_Window *_Nonnull win, AG_SizeAlloc *_Nonnull a, Uint wMax, Uint hMax) { /* XXX TODO */ switch (win->alignment) { case AG_WINDOW_TL: case AG_WINDOW_TC: case AG_WINDOW_TR: a->y += 50; if (a->y+a->h > hMax) { a->y = 0; } break; case AG_WINDOW_BL: case AG_WINDOW_BC: case AG_WINDOW_BR: a->y -= 100; if (a->y < 0) { a->y = 0; } break; default: break; } } static AG_Cursor * COCOA_CreateCursor(void *_Nonnull obj, Uint w, Uint h, const Uint8 *_Nonnull data, const Uint8 *_Nonnull mask, int xHot, int yHot) { AG_Cursor *ac; Uint size = w*h; if ((ac = TryMalloc(sizeof(AG_Cursor))) == NULL) return (NULL); if ((ac->data = TryMalloc(size)) == NULL) goto fail; if ((ac->mask = TryMalloc(size)) == NULL) { free(ac->data); goto fail; } memcpy(ac->data, data, size); memcpy(ac->mask, mask, size); ac->w = w; ac->h = h; ac->xHot = xHot; ac->yHot = yHot; /* TODO COCOA stuff here */ return (NULL); fail: free(ac); return (NULL); } static void COCOA_FreeCursor(void *_Nonnull obj, AG_Cursor *_Nonnull ac) { AG_Driver *drv = obj; if (ac == drv->activeCursor) { drv->activeCursor = NULL; /* TODO COCOA stuff here */ } free(ac->data); free(ac->mask); free(ac); } static int COCOA_SetCursor(void *_Nonnull obj, AG_Cursor *_Nonnull ac) { AG_Driver *drv = obj; drv->activeCursor = ac; /* TODO COCOA stuff here */ return (0); } static void COCOA_UnsetCursor(void *_Nonnull obj) { AG_Driver *drv = obj; if (drv->activeCursor == TAILQ_FIRST(&drv->cursors)) return; /* TODO COCOA stuff here */ drv->activeCursor = TAILQ_FIRST(&drv->cursors); /* Default */ } static int COCOA_GetCursorVisibility(void *_Nonnull obj) { /* TODO */ return (1); } static void COCOA_SetCursorVisibility(void *_Nonnull obj, int flag) { /* TODO */ } AG_DriverMwClass agDriverCocoa = { { { "AG_Driver:AG_DriverMw:AG_DriverCocoa", sizeof(AG_DriverCocoa), { 1,7, AGC_DRIVER_COCOA, 0xE098 }, Init, NULL, /* reset */ Destroy, NULL, /* load */ NULL, /* save */ NULL, /* edit */ }, "cocoa", AG_VECTOR, AG_WM_MULTIPLE, AG_DRIVER_OPENGL | AG_DRIVER_TEXTURES, COCOA_Open, COCOA_Close, COCOA_GetDisplaySize, NULL, /* beginEventProcessing */ COCOA_PendingEvents, COCOA_GetNextEvent, COCOA_ProcessEvent, NULL, /* genericEventLoop */ NULL, /* endEventProcessing */ NULL, /* terminate */ COCOA_BeginRendering, COCOA_RenderWindow, COCOA_EndRendering, AG_GL_FillRect, NULL, /* updateRegion */ AG_GL_StdUploadTexture, AG_GL_StdUpdateTexture, AG_GL_StdDeleteTexture, NULL, /* setRefreshRate */ AG_GL_StdPushClipRect, AG_GL_StdPopClipRect, AG_GL_StdPushBlendingMode, AG_GL_StdPopBlendingMode, COCOA_CreateCursor, COCOA_FreeCursor, COCOA_SetCursor, COCOA_UnsetCursor, COCOA_GetCursorVisibility, COCOA_SetCursorVisibility, AG_GL_BlitSurface, AG_GL_BlitSurfaceFrom, #ifdef HAVE_OPENGL AG_GL_BlitSurfaceGL, AG_GL_BlitSurfaceFromGL, AG_GL_BlitSurfaceFlippedGL, AG_GL_BackupSurfaces, AG_GL_RestoreSurfaces, #else NULL, /* backupSurfaces */ NULL, /* restoreSurfaces */ #endif AG_GL_RenderToSurface, AG_GL_PutPixel, AG_GL_PutPixel32, AG_GL_PutPixelRGB8, #if AG_MODEL == AG_LARGE AG_GL_PutPixel64, AG_GL_PutPixelRGB16, #endif AG_GL_BlendPixel, AG_GL_DrawLine, AG_GL_DrawLineH, AG_GL_DrawLineV, AG_GL_DrawLineBlended, AG_GL_DrawLineW, AG_GL_DrawLineW_Sti16, AG_GL_DrawTriangle, AG_GL_DrawPolygon, AG_GL_DrawPolygon_Sti32, AG_GL_DrawArrow, AG_GL_DrawBoxRounded, AG_GL_DrawBoxRoundedTop, AG_GL_DrawCircle, AG_GL_DrawCircleFilled, AG_GL_DrawRectFilled, AG_GL_DrawRectBlended, AG_GL_DrawRectDithered, AG_GL_UpdateGlyph, AG_GL_DrawGlyph, AG_GL_StdDeleteList, NULL, /* getClipboardText */ NULL, /* setClipboardText */ NULL /* setMouseAutoCapture */ }, COCOA_OpenWindow, COCOA_CloseWindow, COCOA_MapWindow, COCOA_UnmapWindow, COCOA_RaiseWindow, COCOA_LowerWindow, COCOA_ReparentWindow, COCOA_GetInputFocus, COCOA_SetInputFocus, COCOA_MoveWindow, COCOA_ResizeWindow, COCOA_MoveResizeWindow, COCOA_PreResizeCallback, COCOA_PostResizeCallback, COCOA_SetBorderWidth, COCOA_SetWindowCaption, NULL, /* setTransientFor */ COCOA_SetOpacity, COCOA_TweakAlignment, NULL, /* setWindowMinSize */ NULL /* setWindowMaxSize */ };