1/*
2 * Copyright (c) 2012-2015 Hypertriton, Inc. <http://hypertriton.com/>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
18 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
23 * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26/*
27 * Driver for Cocoa framework. This is a multiple display driver (one
28 * Cocoa window is created for each Agar window).
29 */
30
31#include <agar/core/core.h>
32#include <agar/core/config.h>
33
34#include <agar/gui/gui.h>
35#include <agar/gui/window.h>
36#include <agar/gui/gui_math.h>
37#include <agar/gui/text.h>
38#include <agar/gui/cursors.h>
39
40#include <ApplicationServices/ApplicationServices.h>
41#include <Cocoa/Cocoa.h>
42#include <OpenGL/CGLTypes.h>
43#include <OpenGL/OpenGL.h>
44#include <OpenGL/CGLRenderers.h>
45#include <OpenGL/gl.h>
46
47#include <agar/gui/drv_gl_common.h>
48#include <agar/gui/drv_cocoa_keymap.h>
49
50static int nDrivers = 0;		/* Drivers open */
51static AG_DriverEventQ cocEventQ;	/* Private event queue */
52static AG_EventSink   *cocEventSpinner = NULL;  /* Standard event sink */
53static AG_EventSink   *cocEventEpilogue = NULL; /* Standard event epilogue */
54struct ag_driver_cocoa;
55
56/*
57 * Application delegate
58 */
59
60@interface AG_AppDelegate : NSObject
61- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender;
62@end
63
64@implementation AG_AppDelegate : NSObject
65- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender;
66{
67	AG_QuitGUI();
68	return (NSTerminateCancel);
69}
70@end
71
72/*
73 * Window interface
74 */
75@interface AG_CocoaWindow : NSWindow {
76@public
77	AG_Window *_agarWindow;		/* Corresponding Agar window */
78}
79
80- (BOOL)canBecomeKeyWindow;
81- (BOOL)canBecomeMainWindow;
82@end
83
84@implementation AG_CocoaWindow
85- (BOOL)canBecomeKeyWindow
86{
87	return (_agarWindow->flags & AG_WINDOW_DENYFOCUS) ? NO : YES;
88}
89
90- (BOOL)canBecomeMainWindow
91{
92	return (_agarWindow->flags & AG_WINDOW_MAIN) ? YES : NO;
93}
94@end
95
96/*
97 * View interface
98 */
99@interface AG_CocoaView : NSView {
100@public
101	AG_Window *_agarWindow;		/* Corresponding Agar window */
102}
103- (void)rightMouseDown:(NSEvent *)theEvent;
104- (BOOL)preservesContentDuringLiveResize;
105- (void)viewWillStartLiveResize;
106- (void)viewDidEndLiveResize;
107@end
108
109@implementation AG_CocoaView
110- (void)rightMouseDown:(NSEvent *)theEvent
111{
112	[[self nextResponder] rightMouseDown:theEvent];
113}
114
115- (BOOL)preservesContentDuringLiveResize
116{
117	return (YES);
118}
119
120- (void)viewWillStartLiveResize
121{
122	[super viewWillStartLiveResize];
123}
124
125- (void)viewDidEndLiveResize
126{
127	[super viewDidEndLiveResize];
128}
129@end
130
131/*
132 * Event listener interface
133 */
134#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
135@interface AG_CocoaListener : NSResponder <NSWindowDelegate> {
136#else
137@interface AG_CocoaListener : NSResponder {
138#endif
139	AG_Window *_window;
140	struct ag_driver_cocoa *_driver;
141}
142
143-(void) listen:(struct ag_driver_cocoa *) driver;
144-(void) close;
145
146/* Window delegate functionality */
147-(BOOL) windowShouldClose:(id) sender;
148-(void) windowDidExpose:(NSNotification *) aNotification;
149-(void) windowDidMove:(NSNotification *) aNotification;
150-(void) windowDidResize:(NSNotification *) aNotification;
151-(void) windowDidMiniaturize:(NSNotification *) aNotification;
152-(void) windowDidDeminiaturize:(NSNotification *) aNotification;
153-(void) windowDidBecomeKey:(NSNotification *) aNotification;
154-(void) windowDidResignKey:(NSNotification *) aNotification;
155
156@end
157
158/* Driver instance data */
159typedef struct ag_driver_cocoa {
160	struct ag_driver_mw _inherit;
161	AG_CocoaWindow   *win;		/* Cocoa window */
162	AG_CocoaListener *evListener;	/* Cocoa event listener */
163	NSOpenGLContext *glCtx;
164	AG_GL_Context    gl;
165	AG_Mutex         lock;		/* Protect Cocoa calls */
166	Uint             modFlags;	/* Last modifier state */
167#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
168	NSTrackingArea  *trackArea;	/* For mouse motion events */
169#endif
170} AG_DriverCocoa;
171
172AG_DriverMwClass agDriverCocoa;
173
174#define AGDRIVER_IS_COCOA(drv) \
175	(AGDRIVER_CLASS(drv) == (AG_DriverClass *)&agDriverCocoa)
176
177
178static int  COCOA_PendingEvents(void *);
179static int  COCOA_GetNextEvent(void *, AG_DriverEvent *);
180static int  COCOA_ProcessEvent(void *, AG_DriverEvent *);
181static void COCOA_PostResizeCallback(AG_Window *, AG_SizeAlloc *);
182static void COCOA_PostMoveCallback(AG_Window *, AG_SizeAlloc *);
183static int  COCOA_RaiseWindow(AG_Window *);
184static int  COCOA_SetInputFocus(AG_Window *);
185#if 0
186static void COCOA_FreeWidgetResources(AG_Widget *);
187#endif
188
189static __inline__ void
190ConvertNSRect(NSRect *r)
191{
192	r->origin.y = CGDisplayPixelsHigh(kCGDirectMainDisplay) -
193	              r->origin.y - r->size.height;
194}
195
196@implementation AG_CocoaListener
197
198- (void) listen:(AG_DriverCocoa *)co
199{
200	NSNotificationCenter *nc;
201	NSWindow *win = co->win;
202	NSView *view = [win contentView];
203
204	_driver = co;
205	_window = AGDRIVER_MW(co)->win;
206
207	nc = [NSNotificationCenter defaultCenter];
208
209	if ([win delegate] != nil) {
210		[nc addObserver:self selector:@selector(windowDidExpose:) name:NSWindowDidExposeNotification object:win];
211		[nc addObserver:self selector:@selector(windowDidMove:) name:NSWindowDidMoveNotification object:win];
212		[nc addObserver:self selector:@selector(windowDidResize:) name:NSWindowDidResizeNotification object:win];
213		[nc addObserver:self selector:@selector(windowDidMiniaturize:) name:NSWindowDidMiniaturizeNotification object:win];
214		[nc addObserver:self selector:@selector(windowDidDeminiaturize:) name:NSWindowDidDeminiaturizeNotification object:win];
215		[nc addObserver:self selector:@selector(windowDidBecomeKey:) name:NSWindowDidBecomeKeyNotification object:win];
216		[nc addObserver:self selector:@selector(windowDidResignKey:) name:NSWindowDidResignKeyNotification object:win];
217	} else {
218		[win setDelegate:self];
219	}
220
221	[win setNextResponder:self];
222#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050
223	[win setAcceptsMouseMovedEvents:YES];
224#endif
225	[view setNextResponder:self];
226
227#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
228	if ([view respondsToSelector:@selector(setAcceptsTouchEvents:)])
229		[view setAcceptsTouchEvents:YES];
230#endif
231}
232
233- (void)close
234{
235	NSNotificationCenter *nc;
236	AG_DriverCocoa *co = _driver;
237	NSWindow *win = co->win;
238	NSView *view = [win contentView];
239
240	nc = [NSNotificationCenter defaultCenter];
241
242	if ([win delegate] != self) {
243		[nc removeObserver:self name:NSWindowDidExposeNotification object:win];
244		[nc removeObserver:self name:NSWindowDidMoveNotification object:win];
245		[nc removeObserver:self name:NSWindowDidResizeNotification object:win];
246		[nc removeObserver:self name:NSWindowDidMiniaturizeNotification object:win];
247		[nc removeObserver:self name:NSWindowDidDeminiaturizeNotification object:win];
248		[nc removeObserver:self name:NSWindowDidBecomeKeyNotification object:win];
249		[nc removeObserver:self name:NSWindowDidResignKeyNotification object:win];
250	} else {
251		[win setDelegate:nil];
252	}
253
254	if ([win nextResponder] == self) {
255		[win setNextResponder:nil];
256	}
257	if ([view nextResponder] == self) {
258		[view setNextResponder:nil];
259	}
260}
261
262- (BOOL)windowShouldClose:(id)sender
263{
264	AG_DriverEvent *dev;
265
266	if ((dev = TryMalloc(sizeof(AG_DriverEvent))) != NULL) {
267		dev->type = AG_DRIVER_CLOSE;
268		dev->win = _window;
269		TAILQ_INSERT_TAIL(&cocEventQ, dev, events);
270	}
271	return NO;
272}
273
274- (void)windowDidExpose:(NSNotification *)aNotification
275{
276	AG_DriverEvent *dev;
277
278	if ((dev = TryMalloc(sizeof(AG_DriverEvent))) != NULL) {
279		dev->type = AG_DRIVER_EXPOSE;
280		dev->win = _window;
281		TAILQ_INSERT_TAIL(&cocEventQ, dev, events);
282	}
283}
284
285- (void)windowDidMove:(NSNotification *)aNotification
286{
287	AG_DriverCocoa *co = _driver;
288	AG_Window *win = _window;
289	NSRect rect;
290	AG_SizeAlloc a;
291
292	rect = [co->win contentRectForFrameRect:[co->win frame]];
293	ConvertNSRect(&rect);
294
295	a.x = (int)rect.origin.x;
296	a.y = (int)rect.origin.y;
297	a.w = WIDGET(win)->w;
298	a.h = WIDGET(win)->h;
299	COCOA_PostMoveCallback(win, &a);
300}
301
302- (void)windowDidResize:(NSNotification *)aNotification
303{
304	AG_Window *win = _window;
305	AG_DriverCocoa *co = _driver;
306	AG_SizeAlloc a;
307	NSRect rect;
308
309	rect = [co->win contentRectForFrameRect:[co->win frame]];
310	ConvertNSRect(&rect);
311
312	/*
313	 * Since the event loop will not run during a live resize
314	 * operation, we can't use AG_DRIVER_VIDEORESIZE, and we
315	 * must redraw the window immediately.
316	 */
317	a.x = (int)rect.origin.x;
318	a.y = (int)rect.origin.y;
319	a.w = (int)rect.size.width;
320	a.h = (int)rect.size.height;
321
322	AG_MutexLock(&co->lock);
323	AG_ObjectLock(win);
324
325	if (a.w != WIDTH(win) || a.h != HEIGHT(win)) {
326		COCOA_PostResizeCallback(win, &a);
327	} else {
328		COCOA_PostMoveCallback(win, &a);
329	}
330	if (win->visible) {
331		AG_BeginRendering(_driver);
332		AG_WindowDraw(win);
333		AG_EndRendering(_driver);
334	}
335
336	AG_ObjectUnlock(win);
337	AG_MutexUnlock(&co->lock);
338}
339
340- (void)windowDidMiniaturize:(NSNotification *)aNotification
341{
342	_window->flags |= AG_WINDOW_MINIMIZED;
343}
344
345- (void)windowDidDeminiaturize:(NSNotification *)aNotification
346{
347	_window->flags &= ~(AG_WINDOW_MINIMIZED);
348}
349
350- (void)windowDidBecomeKey:(NSNotification *)aNotification
351{
352	AG_DriverEvent *dev;
353
354	agWindowFocused = _window;
355
356	if ((dev = TryMalloc(sizeof(AG_DriverEvent))) != NULL) {
357		dev->type = AG_DRIVER_FOCUS_IN;
358		dev->win = _window;
359		TAILQ_INSERT_TAIL(&cocEventQ, dev, events);
360	}
361}
362
363- (void)windowDidResignKey:(NSNotification *)aNotification
364{
365	AG_DriverEvent *dev;
366
367	if (agWindowFocused != _window) {
368		return;
369	}
370	agWindowFocused = NULL;
371
372	if ((dev = TryMalloc(sizeof(AG_DriverEvent))) != NULL) {
373		dev->type = AG_DRIVER_FOCUS_OUT;
374		dev->win = _window;
375		TAILQ_INSERT_TAIL(&cocEventQ, dev, events);
376	}
377}
378
379@end
380
381static void
382Init(void *obj)
383{
384	AG_DriverCocoa *co = obj;
385
386	co->win = NULL;
387	co->glCtx = NULL;
388	co->modFlags = 0;
389#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
390	co->trackArea = nil;
391#endif
392	AG_MutexInitRecursive(&co->lock);
393}
394
395static void
396Destroy(void *obj)
397{
398	AG_DriverCocoa *co = obj;
399
400	AG_MutexDestroy(&co->lock);
401}
402
403/*
404 * Standard AG_EventLoop() event sink.
405 */
406static int
407COCOA_EventSink(AG_EventSink *es, AG_Event *event)
408{
409	AG_DriverEvent dev;
410
411	if (COCOA_GetNextEvent(NULL, &dev) == 1) {
412		return COCOA_ProcessEvent(NULL, &dev);
413	}
414	return (0);
415}
416static int
417COCOA_EventEpilogue(AG_EventSink *es, AG_Event *event)
418{
419	AG_WindowDrawQueued();
420	AG_WindowProcessQueued();
421	return (0);
422}
423
424static int
425COCOA_Open(void *obj, const char *spec)
426{
427	AG_Driver *drv = obj;
428	AG_DriverCocoa *co = obj;
429	NSAutoreleasePool *pool;
430
431	/* Driver manages rendering of window background. */
432	drv->flags |= AG_DRIVER_WINDOW_BG;
433
434	/* Initialize NSApp if needed. */
435	pool = [[NSAutoreleasePool alloc] init];
436	if (NSApp == nil) {
437		NSApp = [NSApplication sharedApplication];
438		[NSApp finishLaunching];
439	}
440	if ([NSApp delegate] == nil) {
441		[NSApp setDelegate:[[AG_AppDelegate alloc] init]];
442	}
443	[pool release];
444
445	/* Initialize the core mouse and keyboard */
446	/* TODO: touch handling */
447	if ((drv->mouse = AG_MouseNew(co, "X mouse")) == NULL ||
448	    (drv->kbd = AG_KeyboardNew(co, "X keyboard")) == NULL) {
449		goto fail;
450	}
451	if (nDrivers == 0) {
452		TAILQ_INIT(&cocEventQ);
453
454		/* Set up event filters for standard AG_EventLoop(). */
455		if ((cocEventSpinner = AG_AddEventSpinner(COCOA_EventSink, NULL)) == NULL ||
456		    (cocEventEpilogue = AG_AddEventEpilogue(COCOA_EventEpilogue, NULL)) == NULL)
457			goto fail;
458	}
459	nDrivers++;
460	return (0);
461fail:
462	if (cocEventSpinner != NULL) { AG_DelEventSpinner(cocEventSpinner); cocEventSpinner = NULL; }
463	if (cocEventEpilogue != NULL) { AG_DelEventEpilogue(cocEventEpilogue); cocEventEpilogue = NULL; }
464	if (drv->kbd != NULL) { AG_ObjectDelete(drv->kbd); drv->kbd = NULL; }
465	if (drv->mouse != NULL) { AG_ObjectDelete(drv->mouse); drv->mouse = NULL; }
466	return (-1);
467}
468
469static void
470COCOA_Close(void *obj)
471{
472	AG_Driver *drv = obj;
473
474#ifdef AG_DEBUG
475	if (nDrivers == 0) { AG_FatalError("Driver close without open"); }
476#endif
477	if (--nDrivers == 0) {
478		AG_DriverEvent *dev, *devNext;
479
480		AG_DelEventSink(cocEventSpinner); cocEventSpinner = NULL;
481		AG_DelEventEpilogue(cocEventEpilogue); cocEventEpilogue = NULL;
482
483		for (dev = TAILQ_FIRST(&cocEventQ);
484		     dev != TAILQ_LAST(&cocEventQ, ag_driver_eventq);
485		     dev = devNext) {
486			devNext = TAILQ_NEXT(dev, events);
487			Free(dev);
488		}
489		TAILQ_INIT(&cocEventQ);
490	}
491
492	AG_ObjectDelete(drv->mouse); drv->mouse = NULL;
493	AG_ObjectDelete(drv->kbd); drv->kbd = NULL;
494}
495
496static int
497COCOA_GetDisplaySize(Uint *w, Uint *h)
498{
499	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
500	NSScreen *screen;
501	NSRect sr;
502
503	/* XXX this is probably wrong */
504	screen = [NSScreen mainScreen];
505	sr = [screen frame];
506	*w = sr.size.width;
507	*h = sr.size.height;
508	[pool release];
509	return (0);
510}
511
512static int
513COCOA_PendingEvents(void *drvCaller)
514{
515	NSAutoreleasePool *pool;
516	NSEvent *ev;
517	int rv;
518
519	if (!TAILQ_EMPTY(&cocEventQ))
520		return (1);
521
522	pool = [[NSAutoreleasePool alloc] init];
523	ev = [NSApp nextEventMatchingMask:NSAnyEventMask
524	                                  untilDate:[NSDate distantPast]
525					  inMode:NSDefaultRunLoopMode
526					  dequeue:NO];
527	rv = (ev != nil);
528
529	[pool release];
530	return (rv);
531}
532
533/* Convert a NSEvent mouse button number to AG_MouseButton. */
534static AG_MouseButton
535GetMouseButton(int which)
536{
537	switch (which) {
538	case 0:		return (AG_MOUSE_LEFT);
539	case 1:		return (AG_MOUSE_RIGHT);
540	case 2:		return (AG_MOUSE_MIDDLE);
541	default:	return (which+1);
542	}
543}
544
545static AG_MouseButton
546GetScrollWheelButton(float x, float y)
547{
548	if (x > 0) {
549		return (AG_MOUSE_X1);
550	} else if (x < 0) {
551		return (AG_MOUSE_X2);
552	}
553	if (y > 0) {
554		return (AG_MOUSE_WHEELUP);
555	} else if (y < 0) {
556		return (AG_MOUSE_WHEELDOWN);
557	}
558	return (AG_MOUSE_NONE);
559}
560
561/* Add a keyboard event to the queue. */
562static void
563QueueKeyEvent(AG_DriverCocoa *co, enum ag_driver_event_type type,
564    AG_KeySym ks, Uint32 ucs)
565{
566	AG_DriverEvent *dev;
567
568	if ((dev = TryMalloc(sizeof(AG_DriverEvent))) == NULL) {
569		AG_Verbose("Out of memory for keymod event\n");
570		return;
571	}
572	dev->type = type;
573	dev->win = AGDRIVER_MW(co)->win;
574	dev->data.key.ks = ks;
575	dev->data.key.ucs = ucs;
576	TAILQ_INSERT_TAIL(&cocEventQ, dev, events);
577}
578
579static int
580COCOA_GetNextEvent(void *drvCaller, AG_DriverEvent *dev)
581{
582	NSAutoreleasePool *pool;
583	AG_CocoaWindow *coWin;
584	AG_Window *win;
585	AG_Driver *drv;
586	AG_DriverCocoa *co;
587	AG_DriverEvent *devFirst;
588	NSEvent *event;
589	int rv = 0;
590
591	if (!TAILQ_EMPTY(&cocEventQ))
592		goto out_dequeue;
593
594	pool = [[NSAutoreleasePool alloc] init];
595	event = [NSApp nextEventMatchingMask:NSAnyEventMask
596	         untilDate:[NSDate distantPast]
597	         inMode:NSDefaultRunLoopMode
598	         dequeue:YES];
599
600	if (event == nil) {
601		goto out;
602	}
603
604	if ((coWin = (AG_CocoaWindow *)[event window]) == nil) {
605		[NSApp sendEvent:event];
606		goto out;
607	}
608	win = coWin->_agarWindow;
609
610	AG_LockVFS(&agDrivers);
611	drv = WIDGET(win)->drv;
612	co = (AG_DriverCocoa *)drv;
613
614	switch ([event type]) {
615	case NSMouseMoved:
616	case NSLeftMouseDragged:
617	case NSRightMouseDragged:
618	case NSOtherMouseDragged:
619		{
620			NSPoint point = [event locationInWindow];
621			int x = (int)point.x;
622			int y = (int)(WIDGET(win)->h - point.y);
623
624			AG_MouseMotionUpdate(drv->mouse, x, y);
625			dev->type = AG_DRIVER_MOUSE_MOTION;
626			dev->win = win;
627			dev->data.motion.x = x;
628			dev->data.motion.y = y;
629			rv = 1;
630			break;
631		}
632	case NSScrollWheel:
633		{
634			float x = [event deltaX];
635			float y = [event deltaY];
636			AG_MouseButton btn = GetScrollWheelButton(x, y);
637
638			if (btn == AG_MOUSE_NONE) {
639				break;
640			}
641			AG_MouseButtonUpdate(drv->mouse, AG_BUTTON_PRESSED, btn);
642			dev->type = AG_DRIVER_MOUSE_BUTTON_DOWN;
643			dev->win = win;
644			dev->data.button.which = btn;
645			dev->data.button.x = drv->mouse->x;
646			dev->data.button.y = drv->mouse->y;
647			rv = 1;
648			break;
649		}
650	case NSLeftMouseDown:
651	case NSOtherMouseDown:
652	case NSRightMouseDown:
653		{
654			AG_MouseButton btn = GetMouseButton([event buttonNumber]);
655
656			AG_MouseButtonUpdate(drv->mouse, AG_BUTTON_PRESSED, btn);
657			dev->type = AG_DRIVER_MOUSE_BUTTON_DOWN;
658			dev->win = win;
659			dev->data.button.which = btn;
660			dev->data.button.x = drv->mouse->x;
661			dev->data.button.y = drv->mouse->y;
662			rv = 1;
663			break;
664		}
665	case NSLeftMouseUp:
666	case NSOtherMouseUp:
667	case NSRightMouseUp:
668		{
669			AG_MouseButton btn = GetMouseButton([event buttonNumber]);
670
671			AG_MouseButtonUpdate(drv->mouse, AG_BUTTON_RELEASED, btn);
672			dev->type = AG_DRIVER_MOUSE_BUTTON_UP;
673			dev->win = win;
674			dev->data.button.which = btn;
675			dev->data.button.x = drv->mouse->x;
676			dev->data.button.y = drv->mouse->y;
677			rv = 1;
678			break;
679		}
680	case NSMouseEntered:
681		if ([co->win isKeyWindow]) {
682			dev->type = AG_DRIVER_MOUSE_ENTER;
683			dev->win = win;
684			rv = 1;
685		} else {
686			rv = 0;
687		}
688		break;
689	case NSMouseExited:
690		if ([co->win isKeyWindow]) {
691			dev->type = AG_DRIVER_MOUSE_LEAVE;
692			dev->win = win;
693			rv = 1;
694		} else {
695			rv = 0;
696		}
697		break;
698	case NSKeyDown:
699		if ([event isARepeat]) {
700			/* Agar implements its own key repeat */
701			goto out;
702		}
703		/* FALLTHROUGH */
704	case NSKeyUp:
705		{
706			NSString *characters = [event characters];
707			enum ag_driver_event_type evType;
708			enum ag_keyboard_action kbdAction;
709			AG_KeySym ks;
710			unichar c;
711			NSUInteger i;
712
713			if ([characters length] == 0)
714				goto out;
715
716			if ([event type] == NSKeyDown) {
717				evType = AG_DRIVER_KEY_DOWN;
718				kbdAction = AG_KEY_PRESSED;
719			} else {
720				evType = AG_DRIVER_KEY_UP;
721				kbdAction = AG_KEY_RELEASED;
722			}
723
724			/* Look for matching function keys first. */
725			c = [characters characterAtIndex: 0];
726			ks = AG_KEY_NONE;
727			for (i = 0; i < agCocoaFunctionKeysSize; i++) {
728				const struct ag_cocoa_function_key *fnKey =
729				    &agCocoaFunctionKeys[i];
730
731				if (fnKey->uc == c) {
732					ks = fnKey->keySym;
733					break;
734				}
735			}
736			if (ks != AG_KEY_NONE) {
737				AG_KeyboardUpdate(drv->kbd, kbdAction, ks, 0);
738				dev->type = evType;
739				dev->win = win;
740				dev->data.key.ks = ks;
741				if (ks == AG_KEY_RETURN) {
742					dev->data.key.ucs = '\n';
743				} else {
744					dev->data.key.ucs = 0;
745				}
746				rv = 1;
747				goto out;
748			}
749
750			/* Process as a character sequence. */
751			for (i = 0; i < [characters length]; i++) {
752				AG_KeySym ks;
753
754				c = [characters characterAtIndex: i];
755				ks = (c <= AG_KEY_ASCII_END) ?
756				    (AG_KeySym)c : AG_KEY_NONE;
757				AG_KeyboardUpdate(drv->kbd, kbdAction, ks,
758				    (Uint32)c);
759
760				if (i == 0) {
761					dev->type = evType;
762					dev->win = win;
763					dev->data.key.ks = ks;
764					dev->data.key.ucs = (Uint32)c;
765					rv = 1;
766				} else {
767					QueueKeyEvent(co, evType, ks,
768					    (Uint32)c);
769				}
770			}
771			if (rv == 1)
772				goto out;
773		}
774	case NSFlagsChanged:
775		{
776			Uint modFlags = [event modifierFlags];
777			int i, nChanged = 0;
778
779			for (i = 0; i < agCocoaKeymodSize; i++) {
780				const struct ag_cocoa_keymod_entry *kmEnt =
781				    &agCocoaKeymod[i];
782
783				if ((modFlags & kmEnt->keyMask) &&
784				     !(co->modFlags & kmEnt->keyMask)) {
785					AG_KeyboardUpdate(drv->kbd, AG_KEY_PRESSED, kmEnt->keySym, 0);
786					QueueKeyEvent(co, AG_DRIVER_KEY_DOWN, kmEnt->keySym, 0);
787					nChanged++;
788				} else if (!(modFlags & kmEnt->keyMask) &&
789				    (co->modFlags & kmEnt->keyMask)) {
790					AG_KeyboardUpdate(drv->kbd, AG_KEY_RELEASED, kmEnt->keySym, 0);
791					QueueKeyEvent(co, AG_DRIVER_KEY_UP, kmEnt->keySym, 0);
792					nChanged++;
793				}
794			}
795			co->modFlags = modFlags;
796
797			if (nChanged > 0) {
798				goto out_dequeue;
799			}
800			break;
801		}
802	default:
803		break;
804	}
805	[NSApp sendEvent:event];
806out:
807	[pool release];
808	return (rv);
809out_dequeue:
810	devFirst = TAILQ_FIRST(&cocEventQ);
811	TAILQ_REMOVE(&cocEventQ, devFirst, events);
812	memcpy(dev, devFirst, sizeof(AG_DriverEvent));
813	Free(devFirst);
814	return (1);
815}
816
817static int
818COCOA_ProcessEvent(void *drvCaller, AG_DriverEvent *dev)
819{
820	AG_Driver *drv;
821	int rv = 1;
822
823	if (dev->win == NULL ||
824	    dev->win->flags & AG_WINDOW_DETACHING)
825		return (0);
826
827	AG_LockVFS(&agDrivers);
828	drv = WIDGET(dev->win)->drv;
829
830	switch (dev->type) {
831	case AG_DRIVER_MOUSE_MOTION:
832		AG_ProcessMouseMotion(dev->win,
833		    dev->data.motion.x, dev->data.motion.y,
834		    drv->mouse->xRel, drv->mouse->yRel,
835		    drv->mouse->btnState);
836		AG_MouseCursorUpdate(dev->win,
837		     dev->data.motion.x, dev->data.motion.y);
838		break;
839	case AG_DRIVER_MOUSE_BUTTON_DOWN:
840		AG_ProcessMouseButtonDown(dev->win,
841		    dev->data.button.x, dev->data.button.y,
842		    dev->data.button.which);
843		break;
844	case AG_DRIVER_MOUSE_BUTTON_UP:
845		AG_ProcessMouseButtonUp(dev->win,
846		    dev->data.button.x, dev->data.button.y,
847		    dev->data.button.which);
848		break;
849	case AG_DRIVER_KEY_UP:
850		AG_ProcessKey(drv->kbd, dev->win, AG_KEY_RELEASED,
851		    dev->data.key.ks, dev->data.key.ucs);
852		break;
853	case AG_DRIVER_KEY_DOWN:
854		AG_ProcessKey(drv->kbd, dev->win, AG_KEY_PRESSED,
855		    dev->data.key.ks, dev->data.key.ucs);
856		break;
857	case AG_DRIVER_MOUSE_ENTER:
858		AG_PostEvent(NULL, dev->win, "window-enter", NULL);
859		break;
860	case AG_DRIVER_MOUSE_LEAVE:
861		AG_PostEvent(NULL, dev->win, "window-leave", NULL);
862		break;
863	case AG_DRIVER_FOCUS_IN:
864		agWindowFocused = dev->win;
865		AG_PostEvent(NULL, dev->win, "window-gainfocus", NULL);
866		break;
867	case AG_DRIVER_FOCUS_OUT:
868		AG_PostEvent(NULL, dev->win, "window-lostfocus", NULL);
869		break;
870	case AG_DRIVER_CLOSE:
871		AG_PostEvent(NULL, dev->win, "window-close", NULL);
872		break;
873	case AG_DRIVER_EXPOSE:
874		dev->win->dirty = 1;
875		break;
876	default:
877		rv = 0;
878		break;
879	}
880	AG_UnlockVFS(&agDrivers);
881	return (rv);
882}
883
884/* Select the window's OpenGL context. */
885static __inline__ void
886COCOA_GL_MakeCurrent(AG_DriverCocoa *co, AG_Window *win)
887{
888	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
889
890	[co->glCtx setView:[co->win contentView]];
891	[co->glCtx update];
892	[co->glCtx makeCurrentContext];
893
894	[pool release];
895}
896
897static void
898COCOA_BeginRendering(void *obj)
899{
900	AG_DriverCocoa *co = obj;
901
902	AG_MutexLock(&co->lock);
903	COCOA_GL_MakeCurrent(co, AGDRIVER_MW(co)->win);
904}
905
906static void
907COCOA_RenderWindow(AG_Window *win)
908{
909	AG_DriverCocoa *co = (AG_DriverCocoa *)WIDGET(win)->drv;
910	AG_GL_Context *gl = &co->gl;
911	AG_Color c = WCOLOR(win,0);
912
913	gl->clipStates[0] = glIsEnabled(GL_CLIP_PLANE0); glEnable(GL_CLIP_PLANE0);
914	gl->clipStates[1] = glIsEnabled(GL_CLIP_PLANE1); glEnable(GL_CLIP_PLANE1);
915	gl->clipStates[2] = glIsEnabled(GL_CLIP_PLANE2); glEnable(GL_CLIP_PLANE2);
916	gl->clipStates[3] = glIsEnabled(GL_CLIP_PLANE3); glEnable(GL_CLIP_PLANE3);
917
918	glClearColor(c.r/255.0,
919	             c.g/255.0,
920		     c.b/255.0, 1.0);
921	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
922
923	AG_WidgetDraw(win);
924}
925
926static void
927COCOA_EndRendering(void *obj)
928{
929	AG_DriverCocoa *co = obj;
930	AG_GL_Context *gl = &co->gl;
931	Uint i;
932
933	[co->glCtx flushBuffer];
934
935	/* Remove textures and display lists queued for deletion. */
936	glDeleteTextures(gl->nTextureGC, (const GLuint *)gl->textureGC);
937	for (i = 0; i < gl->nListGC; i++) {
938		glDeleteLists(gl->listGC[i], 1);
939	}
940	gl->nTextureGC = 0;
941	gl->nListGC = 0;
942	AG_MutexUnlock(&co->lock);
943}
944
945/*
946 * Window operations
947 */
948
949static void
950SetBackgroundColor(AG_DriverCocoa *co, AG_Color C)
951{
952	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
953	NSColor *bgColor;
954	CGFloat r, g, b, a;
955
956	r = (CGFloat)(C.r / 255.0);
957	g = (CGFloat)(C.g / 255.0);
958	b = (CGFloat)(C.b / 255.0);
959	a = (CGFloat)(C.a / 255.0);
960	bgColor = [NSColor colorWithCalibratedRed:r green:g blue:b alpha:a];
961	[co->win setBackgroundColor:bgColor];
962
963	[pool release];
964}
965
966static int
967COCOA_OpenWindow(AG_Window *win, AG_Rect r, int depthReq, Uint mwFlags)
968{
969	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
970	AG_DriverCocoa *co = (AG_DriverCocoa *)WIDGET(win)->drv;
971	AG_Driver *drv = WIDGET(win)->drv;
972	Uint winStyle = 0;
973	NSRect winRect, scrRect;
974	NSArray *screens;
975	NSScreen *screen, *selScreen = nil;
976	AG_CocoaView *contentView;
977	NSOpenGLPixelFormatAttribute pfAttr[32];
978	NSOpenGLPixelFormat *pf;
979	int i, count;
980	AG_SizeAlloc a;
981
982	AG_MutexLock(&co->lock);
983
984	/* Set the window style. */
985	if (win->flags & AG_WINDOW_NOBORDERS) {
986		winStyle = NSBorderlessWindowMask;
987	} else {
988		if (!(win->flags & AG_WINDOW_NOTITLE)) { winStyle |= NSTitledWindowMask; }
989		if (!(win->flags & AG_WINDOW_NOCLOSE)) { winStyle |= NSClosableWindowMask; }
990		if (!(win->flags & AG_WINDOW_NOMINIMIZE)) { winStyle |= NSMiniaturizableWindowMask; }
991		if (!(win->flags & AG_WINDOW_NORESIZE)) { winStyle |= NSResizableWindowMask; }
992	}
993
994	/* Set the window coordinates. */
995	winRect.origin.x = r.x;
996	winRect.origin.y = r.y;
997	winRect.size.width = r.w;
998	winRect.size.height = r.h;
999	ConvertNSRect(&winRect);
1000
1001	/* Select a screen. */
1002	screens = [NSScreen screens];
1003	count = [screens count];
1004	for (i = 0; i < count; i++) {
1005		screen = [screens objectAtIndex:i];
1006		scrRect = [screen frame];
1007
1008		if (winRect.origin.x >= scrRect.origin.x &&
1009		    winRect.origin.y >= scrRect.origin.y &&
1010		    winRect.origin.x < scrRect.origin.x + scrRect.size.width &&
1011		    winRect.origin.y < scrRect.origin.y + scrRect.size.height) {
1012		    	selScreen = screen;
1013			winRect.origin.x -= scrRect.origin.x;
1014			winRect.origin.y -= scrRect.origin.y;
1015			break;
1016		}
1017	}
1018
1019	/* Create the window. */
1020	if (selScreen == nil) {
1021		co->win = [[AG_CocoaWindow alloc] initWithContentRect:winRect
1022		                                  styleMask:winStyle
1023					          backing:NSBackingStoreBuffered
1024					          defer:YES];
1025	} else {
1026		co->win = [[AG_CocoaWindow alloc] initWithContentRect:winRect
1027		                                  styleMask:winStyle
1028					          backing:NSBackingStoreBuffered
1029					          defer:YES
1030					          screen:selScreen];
1031	}
1032	co->win->_agarWindow = win;
1033	SetBackgroundColor(co, WCOLOR(win,0));
1034
1035	if (win->flags & AG_WINDOW_MAIN)
1036		[co->win makeMainWindow];
1037
1038	/* Create an event listener. */
1039	co->evListener = [[AG_CocoaListener alloc] init];
1040
1041	/* Obtain the effective window coordinates; set up our contentView. */
1042	winRect = [co->win contentRectForFrameRect:[co->win frame]];
1043	contentView = [[AG_CocoaView alloc] initWithFrame:winRect];
1044	contentView->_agarWindow = win;
1045	[co->win setContentView: contentView];
1046	[contentView release];
1047
1048	/* Set our event listener. */
1049	[co->evListener listen:co];
1050
1051	/* Retrieve the effective style flags. */
1052	winStyle = [co->win styleMask];
1053	if (winStyle == NSBorderlessWindowMask) {
1054		win->flags |= AG_WINDOW_NOBORDERS;
1055	} else {
1056		if (!(winStyle & NSTitledWindowMask)) { win->flags |= AG_WINDOW_NOTITLE; }
1057		if (!(winStyle & NSClosableWindowMask)) { win->flags |= AG_WINDOW_NOCLOSE; }
1058		if (!(winStyle & NSMiniaturizableWindowMask)) { win->flags |= AG_WINDOW_NOMINIMIZE; }
1059		if (!(winStyle & NSResizableWindowMask)) { win->flags |= AG_WINDOW_NORESIZE; }
1060	}
1061
1062	/* Retrieve the effective maximize/minimize and focus state. */
1063	if (!(win->flags & AG_WINDOW_NORESIZE) && [co->win isZoomed]) {
1064		win->flags |= AG_WINDOW_MAXIMIZED;
1065	} else {
1066		win->flags &= ~(AG_WINDOW_MAXIMIZED);
1067	}
1068	if ([co->win isMiniaturized]) {
1069		win->flags |= AG_WINDOW_MINIMIZED;
1070	} else {
1071		win->flags &= ~(AG_WINDOW_MINIMIZED);
1072	}
1073	if ([co->win isKeyWindow])
1074		agWindowFocused = win;
1075
1076	/* Create an OpenGL rendering context. */
1077	i = 0;
1078	if (depthReq != 0) {
1079		pfAttr[i++] = NSOpenGLPFADepthSize;
1080		pfAttr[i++] = depthReq;
1081	}
1082	pfAttr[i++] = NSOpenGLPFADoubleBuffer;
1083	if (agStereo) { pfAttr[i++] = NSOpenGLPFAStereo; }
1084	pfAttr[i] = 0;
1085	pf = [[NSOpenGLPixelFormat alloc] initWithAttributes:pfAttr];
1086	if (pf == nil) {
1087		AG_SetError("Cannot create NSOpenGLPixelFormat");
1088		goto fail;
1089	}
1090	co->glCtx = [[NSOpenGLContext alloc] initWithFormat:pf shareContext:nil];
1091	[pf release];
1092	if (co->glCtx == nil) {
1093		AG_SetError("Cannot create NSOpenGLContext");
1094		goto fail;
1095	}
1096	[co->glCtx update];
1097	[co->glCtx makeCurrentContext];
1098	if (AG_GL_InitContext(co, &co->gl) == -1)
1099		goto fail;
1100
1101	/* Set the preferred Agar pixel formats. */
1102	/* XXX XXX XXX: retrieve effective depth */
1103	drv->videoFmt = AG_PixelFormatRGB(depthReq != 0 ? depthReq : 16,
1104#if AG_BYTEORDER == AG_BIG_ENDIAN
1105		0xff000000, 0x00ff0000, 0x0000ff00
1106#else
1107		0x000000ff, 0x0000ff00, 0x00ff0000
1108#endif
1109	);
1110	if (drv->videoFmt == NULL)
1111		goto fail_ctx;
1112
1113	/*
1114	 * Set the effective window geometry, initialize the viewport
1115	 * and tracking rectangle.
1116	 */
1117	ConvertNSRect(&winRect);
1118	a.x = winRect.origin.x;
1119	a.y = winRect.origin.y;
1120	a.w = winRect.size.width;
1121	a.h = winRect.size.height;
1122	COCOA_PostResizeCallback(win, &a);
1123
1124	AG_MutexUnlock(&co->lock);
1125	[pool release];
1126	return (0);
1127fail_ctx:
1128	AG_GL_DestroyContext(co);
1129fail:
1130	[NSOpenGLContext clearCurrentContext];
1131	[co->evListener close];
1132	[co->evListener release];
1133	[co->win close];
1134	if (drv->videoFmt) {
1135		AG_PixelFormatFree(drv->videoFmt);
1136		drv->videoFmt = NULL;
1137	}
1138	AG_MutexUnlock(&co->lock);
1139	[pool release];
1140	return (-1);
1141}
1142
1143static void
1144COCOA_CloseWindow(AG_Window *win)
1145{
1146	AG_Driver *drv = WIDGET(win)->drv;
1147	AG_DriverCocoa *co = (AG_DriverCocoa *)drv;
1148	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1149
1150	AG_MutexLock(&co->lock);
1151
1152#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
1153	if (co->trackArea != nil) {
1154		[[co->win contentView] removeTrackingArea:co->trackArea];
1155		[co->trackArea release];
1156		co->trackArea = nil;
1157	}
1158#endif
1159	/* Destroy our OpenGL rendering context. */
1160	COCOA_GL_MakeCurrent(co, win);
1161	AG_GL_DestroyContext(drv);
1162	[co->glCtx clearDrawable];
1163	[co->glCtx release];
1164
1165	/* Close the window. */
1166	[co->evListener close];
1167	[co->evListener release];
1168	[co->win close];
1169
1170	AG_PixelFormatFree(drv->videoFmt);
1171	drv->videoFmt = NULL;
1172
1173	AG_MutexUnlock(&co->lock);
1174	[pool release];
1175}
1176
1177static int
1178COCOA_MapWindow(AG_Window *win)
1179{
1180	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1181	AG_DriverCocoa *co = (AG_DriverCocoa *)WIDGET(win)->drv;
1182
1183	AG_MutexLock(&co->lock);
1184	if (![co->win isMiniaturized]) {
1185		[co->win makeKeyAndOrderFront:nil];
1186	}
1187	AG_MutexUnlock(&co->lock);
1188
1189	[pool release];
1190	return (0);
1191}
1192
1193static int
1194COCOA_UnmapWindow(AG_Window *win)
1195{
1196	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1197	AG_DriverCocoa *co = (AG_DriverCocoa *)WIDGET(win)->drv;
1198
1199	AG_MutexLock(&co->lock);
1200	[co->win orderOut:nil];
1201	AG_MutexUnlock(&co->lock);
1202
1203	[pool release];
1204	return (0);
1205}
1206
1207static int
1208COCOA_RaiseWindow(AG_Window *win)
1209{
1210	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1211	AG_DriverCocoa *co = (AG_DriverCocoa *)WIDGET(win)->drv;
1212
1213	AG_MutexLock(&co->lock);
1214	[co->win makeKeyAndOrderFront:nil];
1215	AG_MutexUnlock(&co->lock);
1216
1217	[pool release];
1218	return (0);
1219}
1220
1221static int
1222COCOA_LowerWindow(AG_Window *win)
1223{
1224	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1225	AG_DriverCocoa *co = (AG_DriverCocoa *)WIDGET(win)->drv;
1226
1227	AG_MutexLock(&co->lock);
1228	if (![co->win isMiniaturized]) {
1229		[co->win orderBack:nil];
1230	}
1231	AG_MutexUnlock(&co->lock);
1232
1233	[pool release];
1234	return (0);
1235}
1236
1237static int
1238COCOA_ReparentWindow(AG_Window *win, AG_Window *winParent, int x, int y)
1239{
1240	/* TODO */
1241	AG_SetError("Reparent window not implemented");
1242	return (-1);
1243}
1244
1245static int
1246COCOA_GetInputFocus(AG_Window **rv)
1247{
1248	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1249	AG_DriverCocoa *co = NULL;
1250
1251	AGOBJECT_FOREACH_CHILD(co, &agDrivers, ag_driver_cocoa) {
1252		if (!AGDRIVER_IS_COCOA(co)) {
1253			continue;
1254		}
1255		AG_MutexLock(&co->lock);
1256		if ([co->win isKeyWindow]) {
1257			AG_MutexUnlock(&co->lock);
1258			break;
1259		}
1260		AG_MutexUnlock(&co->lock);
1261	}
1262	if (co == NULL) {
1263		AG_SetError("Input focus is external to this application");
1264		goto fail;
1265	}
1266	*rv = AGDRIVER_MW(co)->win;
1267	[pool release];
1268	return (0);
1269fail:
1270	[pool release];
1271	return (-1);
1272}
1273
1274static int
1275COCOA_SetInputFocus(AG_Window *win)
1276{
1277	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1278	AG_DriverCocoa *co = (AG_DriverCocoa *)WIDGET(win)->drv;
1279
1280	AG_MutexLock(&co->lock);
1281	/* XXX use makeKeyWindow? */
1282	[co->win makeKeyAndOrderFront:nil];
1283	AG_MutexUnlock(&co->lock);
1284
1285	[pool release];
1286	return (0);
1287}
1288
1289static void
1290COCOA_PreResizeCallback(AG_Window *win)
1291{
1292#if 0
1293	AG_DriverCocoa *co = (AG_DriverCocoa *)WIDGET(win)->drv;
1294
1295	/*
1296	 * Backup all GL resources since it is not portable to assume that a
1297	 * display resize will not cause a change in GL contexts
1298	 * (XXX TODO test for platforms where this is unnecessary)
1299	 * (XXX is this correctly done?)
1300	 */
1301	COCOA_GL_MakeCurrent(co, win);
1302	COCOA_FreeWidgetResources(WIDGET(win));
1303	AG_TextClearGlyphCache(co);
1304#endif
1305}
1306
1307static void
1308COCOA_PostResizeCallback(AG_Window *win, AG_SizeAlloc *a)
1309{
1310	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1311	AG_Driver *drv = WIDGET(win)->drv;
1312	AG_DriverCocoa *co = (AG_DriverCocoa *)drv;
1313	int x = a->x;
1314	int y = a->y;
1315	NSRect trackRect;
1316
1317	AG_MutexLock(&co->lock);
1318
1319	/* Update per-widget coordinate information. */
1320	a->x = 0;
1321	a->y = 0;
1322	(void)AG_WidgetSizeAlloc(win, a);
1323	AG_WidgetUpdateCoords(win, 0, 0);
1324
1325	/* The viewport coordinates have changed. */
1326	[co->glCtx makeCurrentContext];
1327	AG_GL_SetViewport(&co->gl, AG_RECT(0, 0, WIDTH(win), HEIGHT(win)));
1328
1329#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
1330	/* Update the tracking rectangle. */
1331	if (co->trackArea != nil) {
1332		[[co->win contentView] removeTrackingArea:co->trackArea];
1333		[co->trackArea release];
1334		co->trackArea = nil;
1335	}
1336	trackRect.origin.x = 0;
1337	trackRect.origin.y = 0;
1338	trackRect.size.width = WIDTH(win);
1339	trackRect.size.height = HEIGHT(win);
1340	co->trackArea = [[NSTrackingArea alloc] initWithRect:trackRect
1341	                 options: (NSTrackingMouseEnteredAndExited|
1342			           NSTrackingMouseMoved|
1343				   NSTrackingActiveAlways)
1344			 owner:co->win userInfo:nil];
1345	[[co->win contentView] addTrackingArea:co->trackArea];
1346#endif /* >= 10.5 */
1347
1348	AG_MutexUnlock(&co->lock);
1349
1350	/* Save the new effective window position. */
1351	WIDGET(win)->x = a->x = x;
1352	WIDGET(win)->y = a->y = y;
1353
1354	[pool release];
1355}
1356
1357static void
1358COCOA_PostMoveCallback(AG_Window *win, AG_SizeAlloc *a)
1359{
1360	AG_Driver *drv = WIDGET(win)->drv;
1361	AG_DriverCocoa *co = (AG_DriverCocoa *)drv;
1362	AG_SizeAlloc aNew;
1363	int xRel, yRel;
1364
1365	AG_MutexLock(&co->lock);
1366
1367	xRel = a->x - WIDGET(win)->x;
1368	yRel = a->y - WIDGET(win)->y;
1369
1370	/* Update the window coordinates. */
1371	aNew.x = 0;
1372	aNew.y = 0;
1373	aNew.w = a->w;
1374	aNew.h = a->h;
1375	AG_WidgetSizeAlloc(win, &aNew);
1376	AG_WidgetUpdateCoords(win, 0, 0);
1377	WIDGET(win)->x = a->x;
1378	WIDGET(win)->y = a->y;
1379	win->dirty = 1;
1380
1381	/* Move other windows pinned to this one. */
1382	AG_WindowMovePinned(win, xRel, yRel);
1383
1384	AG_MutexUnlock(&co->lock);
1385}
1386
1387static int
1388COCOA_MoveWindow(AG_Window *win, int x, int y)
1389{
1390	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1391	AG_DriverCocoa *co = (AG_DriverCocoa *)WIDGET(win)->drv;
1392	NSScreen *screen = [co->win screen];
1393	NSRect scrRect = [screen frame];
1394	NSPoint pt;
1395
1396	pt.x = x;
1397	pt.y = scrRect.size.height - y;
1398
1399	AG_MutexLock(&co->lock);
1400	[co->win setFrameTopLeftPoint:pt];
1401	AG_MutexUnlock(&co->lock);
1402
1403	[pool release];
1404	return (0);
1405}
1406
1407#if 0
1408/* Save/restore associated widget GL resources (for GL context changes). */
1409static void
1410COCOA_FreeWidgetResources(AG_Widget *wid)
1411{
1412	AG_Widget *chld;
1413
1414	OBJECT_FOREACH_CHILD(chld, wid, ag_widget) {
1415		COCOA_FreeWidgetResources(chld);
1416	}
1417	AG_WidgetFreeResourcesGL(wid);
1418}
1419#endif
1420
1421static int
1422COCOA_ResizeWindow(AG_Window *win, Uint w, Uint h)
1423{
1424	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1425	AG_DriverCocoa *co = (AG_DriverCocoa *)WIDGET(win)->drv;
1426	NSSize sz;
1427
1428	sz.width = w;
1429	sz.height = h;
1430
1431	AG_MutexLock(&co->lock);
1432	[co->win setContentSize:sz];
1433	AG_MutexUnlock(&co->lock);
1434
1435	[pool release];
1436	return (0);
1437}
1438
1439static int
1440COCOA_MoveResizeWindow(AG_Window *win, AG_SizeAlloc *a)
1441{
1442	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1443	AG_DriverCocoa *co = (AG_DriverCocoa *)WIDGET(win)->drv;
1444	NSScreen *screen = [co->win screen];
1445	NSRect scrRect = [screen frame];
1446	NSPoint pt;
1447	NSSize sz;
1448
1449	pt.x = a->x;
1450	pt.y = scrRect.size.height - a->y;
1451	sz.width = a->w;
1452	sz.height = a->h;
1453
1454	AG_MutexLock(&co->lock);
1455	[co->win setFrameTopLeftPoint:pt];
1456	[co->win setContentSize:sz];
1457	AG_MutexUnlock(&co->lock);
1458
1459	[pool release];
1460	return (0);
1461}
1462
1463static int
1464COCOA_SetBorderWidth(AG_Window *win, Uint width)
1465{
1466	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1467	AG_DriverCocoa *co = (AG_DriverCocoa *)WIDGET(win)->drv;
1468
1469	AG_MutexLock(&co->lock);
1470	if (([co->win styleMask] & NSTexturedBackgroundWindowMask) == 0) {
1471		[co->win setContentBorderThickness:width forEdge:NSMaxYEdge];
1472	}
1473	[co->win setContentBorderThickness:width forEdge:NSMinYEdge];
1474	AG_MutexUnlock(&co->lock);
1475
1476	[pool release];
1477	return (0);
1478}
1479
1480static int
1481COCOA_SetWindowCaption(AG_Window *win, const char *s)
1482{
1483	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1484	AG_DriverCocoa *co = (AG_DriverCocoa *)WIDGET(win)->drv;
1485	NSString *string;
1486
1487	if (win->caption[0] != '\0') {
1488		string = [[NSString alloc] initWithUTF8String:win->caption];
1489	} else {
1490		string = [[NSString alloc] init];
1491	}
1492
1493	AG_MutexLock(&co->lock);
1494	[co->win setTitle:string];
1495	AG_MutexUnlock(&co->lock);
1496
1497	[string release];
1498	[pool release];
1499	return (0);
1500}
1501
1502static int
1503COCOA_SetOpacity(AG_Window *win, float f)
1504{
1505	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1506	AG_DriverCocoa *co = (AG_DriverCocoa *)WIDGET(win)->drv;
1507
1508	AG_MutexLock(&co->lock);
1509	if (f > 0.99) {
1510		[co->win setOpaque:YES];
1511		[co->win setAlphaValue:1.0];
1512	} else {
1513		[co->win setOpaque:NO];
1514		[co->win setAlphaValue:f];
1515	}
1516	AG_MutexUnlock(&co->lock);
1517
1518	[pool release];
1519	return (0);
1520}
1521
1522static void
1523COCOA_TweakAlignment(AG_Window *win, AG_SizeAlloc *a, Uint wMax, Uint hMax)
1524{
1525	/* XXX TODO */
1526	switch (win->alignment) {
1527	case AG_WINDOW_TL:
1528	case AG_WINDOW_TC:
1529	case AG_WINDOW_TR:
1530		a->y += 50;
1531		if (a->y+a->h > hMax) { a->y = 0; }
1532		break;
1533	case AG_WINDOW_BL:
1534	case AG_WINDOW_BC:
1535	case AG_WINDOW_BR:
1536		a->y -= 100;
1537		if (a->y < 0) { a->y = 0; }
1538		break;
1539	default:
1540		break;
1541	}
1542}
1543
1544AG_DriverMwClass agDriverCocoa = {
1545	{
1546		{
1547			"AG_Driver:AG_DriverMw:AG_DriverCocoa",
1548			sizeof(AG_DriverCocoa),
1549			{ 1,5 },
1550			Init,
1551			NULL,	/* reinit */
1552			Destroy,
1553			NULL,	/* load */
1554			NULL,	/* save */
1555			NULL,	/* edit */
1556		},
1557		"cocoa",
1558		AG_VECTOR,
1559		AG_WM_MULTIPLE,
1560		AG_DRIVER_OPENGL|AG_DRIVER_TEXTURES,
1561		COCOA_Open,
1562		COCOA_Close,
1563		COCOA_GetDisplaySize,
1564		NULL,			/* beginEventProcessing */
1565		COCOA_PendingEvents,
1566		COCOA_GetNextEvent,
1567		COCOA_ProcessEvent,
1568		NULL,			/* genericEventLoop */
1569		NULL,			/* endEventProcessing */
1570		NULL,			/* terminate */
1571		COCOA_BeginRendering,
1572		COCOA_RenderWindow,
1573		COCOA_EndRendering,
1574		AG_GL_FillRect,
1575		NULL,			/* updateRegion */
1576		AG_GL_StdUploadTexture,
1577		AG_GL_StdUpdateTexture,
1578		AG_GL_StdDeleteTexture,
1579		NULL,			/* setRefreshRate */
1580		AG_GL_StdPushClipRect,
1581		AG_GL_StdPopClipRect,
1582		AG_GL_StdPushBlendingMode,
1583		AG_GL_StdPopBlendingMode,
1584		NULL,			/* createCursor */
1585		NULL,			/* freeCursor */
1586		NULL,			/* setCursor */
1587		NULL,			/* unsetCursor */
1588		NULL,			/* getCursorVisibility */
1589		NULL,			/* setCursorVisibility */
1590		AG_GL_BlitSurface,
1591		AG_GL_BlitSurfaceFrom,
1592		AG_GL_BlitSurfaceGL,
1593		AG_GL_BlitSurfaceFromGL,
1594		AG_GL_BlitSurfaceFlippedGL,
1595		AG_GL_BackupSurfaces,
1596		AG_GL_RestoreSurfaces,
1597		AG_GL_RenderToSurface,
1598		AG_GL_PutPixel,
1599		AG_GL_PutPixel32,
1600		AG_GL_PutPixelRGB,
1601		AG_GL_BlendPixel,
1602		AG_GL_DrawLine,
1603		AG_GL_DrawLineH,
1604		AG_GL_DrawLineV,
1605		AG_GL_DrawLineBlended,
1606		AG_GL_DrawArrowUp,
1607		AG_GL_DrawArrowDown,
1608		AG_GL_DrawArrowLeft,
1609		AG_GL_DrawArrowRight,
1610		AG_GL_DrawBoxRounded,
1611		AG_GL_DrawBoxRoundedTop,
1612		AG_GL_DrawCircle,
1613		AG_GL_DrawCircleFilled,
1614		AG_GL_DrawRectFilled,
1615		AG_GL_DrawRectBlended,
1616		AG_GL_DrawRectDithered,
1617		AG_GL_UpdateGlyph,
1618		AG_GL_DrawGlyph,
1619		AG_GL_StdDeleteList
1620	},
1621	COCOA_OpenWindow,
1622	COCOA_CloseWindow,
1623	COCOA_MapWindow,
1624	COCOA_UnmapWindow,
1625	COCOA_RaiseWindow,
1626	COCOA_LowerWindow,
1627	COCOA_ReparentWindow,
1628	COCOA_GetInputFocus,
1629	COCOA_SetInputFocus,
1630	COCOA_MoveWindow,
1631	COCOA_ResizeWindow,
1632	COCOA_MoveResizeWindow,
1633	COCOA_PreResizeCallback,
1634	COCOA_PostResizeCallback,
1635	NULL,				/* captureWindow */
1636	COCOA_SetBorderWidth,
1637	COCOA_SetWindowCaption,
1638	NULL,				/* setTransientFor */
1639	COCOA_SetOpacity,
1640	COCOA_TweakAlignment
1641};
1642