--- /dev/null
+/*
+MiniGLUT - minimal GLUT subset without dependencies
+Copyright (C) 2020-2023 John Tsiombikas <nuclear@member.fsf.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+#import <Cocoa/Cocoa.h>
+#include "miniglut.h"
+
+@interface OpenGLView : NSOpenGLView
+{
+ int foo;
+}
+
+-(id) initWithFrame: (NSRect) frame pixelFormat: (NSOpenGLPixelFormat*) pf;
+
+-(void) drawRect: (NSRect) rect;
+-(void) reshape;
+-(void) keyDown: (NSEvent*) ev;
+-(void) keyUp: (NSEvent*) ev;
+-(void) mouseDown: (NSEvent*) ev;
+-(void) mouseUp: (NSEvent*) ev;
+-(void) rightMouseDown: (NSEvent*) ev;
+-(void) rightMouseUp: (NSEvent*) ev;
+-(void) otherMouseDown: (NSEvent*) ev;
+-(void) otherMouseUp: (NSEvent*) ev;
+-(void) mouseDragged: (NSEvent*) ev;
+-(void) rightMouseDragged: (NSEvent*) ev;
+-(void) otherMouseDragged: (NSEvent*) ev;
+
+-(BOOL) acceptsFirstResponder;
+@end
+
+
+@interface AppDelegate : NSObject
+{
+ int foo;
+}
+
+-(void) applicationWillFinishLaunching: (NSNotification*) notification;
+-(void) applicationDidFinishLaunching: (NSNotification*) notification;
+
+-(BOOL) applicationShouldTerminate: (NSApplication*) app;
+-(BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication*) app;
+-(void) applicationWillTerminate: (NSNotification*) notification;
+@end
+
+struct window;
+
+@interface WinDelegate : NSObject <NSWindowDelegate>
+{
+ @public
+ struct window *win;
+}
+-(id) init;
+-(void) dealloc;
+
+-(void) windowDidExpose: (NSNotification*) notification;
+-(void) windowDidResize: (NSNotification*) notification;
+-(BOOL) windowShouldClose: (id) win;
+-(void) windowWillClose: (NSNotification*) notification;
+@end
+
+
+static int init(void);
+static void shutdown(void);
+
+/* video mode switching */
+static int set_vidmode(int xsz, int ysz);
+static int get_vidmode(int *xsz, int *ysz);
+
+/* create/destroy windows */
+static int create_window(int xsz, int ysz, unsigned int flags);
+static void close_window(void);
+
+/* window management */
+static int set_title(const char *str);
+static void redisplay(void);
+static void swap_buffers(void);
+
+static int get_modifiers(void);
+
+/* event handling and friends */
+static void set_event(int idx, int enable);
+static int process_events(void);
+
+static void select_event_window(NSEvent *ev);
+static void handle_key(NSEvent *ev, int state);
+static void handle_mouse(NSEvent *ev, int state);
+static void handle_motion(NSEvent *ev);
+
+static void fill_attr(NSOpenGLPixelFormatAttribute *attr, unsigned int flags);
+
+
+static glut_cb cb_display;
+static glut_cb cb_idle;
+static glut_cb_reshape cb_reshape;
+static glut_cb_state cb_vis, cb_entry;
+static glut_cb_keyb cb_keydown, cb_keyup;
+static glut_cb_special cb_skeydown, cb_skeyup;
+static glut_cb_mouse cb_mouse;
+static glut_cb_motion cb_motion, cb_passive;
+static glut_cb_sbmotion cb_sball_motion, cb_sball_rotate;
+static glut_cb_sbbutton cb_sball_button;
+
+static int win_width, win_height;
+static NSWindow *glwin;
+static OpenGLView *glview;
+static NSOpenGLContext *glctx;
+static int needs_redisplay;
+static int quit_main_loop;
+
+static NSAutoreleasePool *global_pool;
+
+
+void glutInit(int *argc, char **argv)
+{
+}
+
+
+
+@implementation OpenGLView
+
+-(id) initWithFrame: (NSRect) frame pixelFormat: (NSOpenGLPixelFormat*) pf
+{
+ self = [super initWithFrame: frame pixelFormat: pf];
+ return self;
+}
+
+-(void) drawRect: (NSRect) rect
+{
+ cb_display();
+}
+
+-(void) reshape
+{
+ NSSize sz = [self bounds].size;
+
+ if(cb_reshape && (sz.width != win_width || sz.height != win_height)) {
+ win_width = sz.width;
+ win_height = sz.height;
+ cb_reshape(sz.width, sz.height);
+ }
+}
+
+-(void) keyDown: (NSEvent*) ev
+{
+ handle_key(ev, 1);
+}
+
+-(void) keyUp: (NSEvent*) ev
+{
+ handle_key(ev, 0);
+}
+
+-(void) mouseDown: (NSEvent*) ev
+{
+ handle_mouse(ev, 1);
+}
+
+-(void) mouseUp: (NSEvent*) ev
+{
+ handle_mouse(ev, 0);
+}
+
+-(void) rightMouseDown: (NSEvent*) ev
+{
+ handle_mouse(ev, 1);
+}
+
+-(void) rightMouseUp: (NSEvent*) ev
+{
+ handle_mouse(ev, 0);
+}
+
+-(void) otherMouseDown: (NSEvent*) ev
+{
+ handle_mouse(ev, 1);
+}
+
+-(void) otherMouseUp: (NSEvent*) ev
+{
+ handle_mouse(ev, 0);
+}
+
+-(void) mouseDragged: (NSEvent*) ev
+{
+ handle_motion(ev);
+}
+
+-(void) rightMouseDragged: (NSEvent*) ev
+{
+ handle_motion(ev);
+}
+
+-(void) otherMouseDragged: (NSEvent*) ev
+{
+ handle_motion(ev);
+}
+
+
+-(BOOL) acceptsFirstResponder
+{
+ return YES;
+}
+@end
+
+@implementation AppDelegate
+-(void) applicationWillFinishLaunching: (NSNotification*) notification
+{
+}
+
+-(void) applicationDidFinishLaunching: (NSNotification*) notification
+{
+}
+
+-(BOOL) applicationShouldTerminate: (NSApplication*) app
+{
+ return NSTerminateNow;
+}
+
+-(BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication*) app
+{
+ return YES;
+}
+
+-(void) applicationWillTerminate: (NSNotification*) notification
+{
+ /*[NSApp setDelegate: nil];
+ [global_pool drain];*/
+}
+@end
+
+@implementation WinDelegate
+-(id) init
+{
+ self = [super init];
+ return self;
+}
+
+-(void) dealloc
+{
+ [super dealloc];
+}
+
+-(void) windowDidExpose: (NSNotification*) notification
+{
+}
+
+-(void) windowDidResize: (NSNotification*) notification
+{
+}
+
+-(BOOL) windowShouldClose: (id) win
+{
+ close_window();
+ return YES;
+}
+
+-(void) windowWillClose: (NSNotification*) notification
+{
+ /*[NSApp terminate: nil];*/
+}
+@end
+
+static int init(void)
+{
+ AppDelegate *delegate;
+
+ global_pool = [[NSAutoreleasePool alloc] init];
+
+ [NSApplication sharedApplication];
+
+ delegate = [[AppDelegate alloc] init];
+ [NSApp setDelegate: delegate];
+ return 0;
+}
+
+static void shutdown(void)
+{
+ close_window();
+
+ quit_main_loop = 1;
+ [NSApp terminate: nil];
+}
+
+
+/* create/destroy windows */
+static int create_window(int xsz, int ysz, unsigned int flags)
+{
+ NSAutoreleasePool *pool;
+ WinDelegate *delegate;
+ NSWindow *nswin;
+ NSRect rect;
+ OpenGLView *view;
+ NSOpenGLPixelFormat *pf;
+ NSOpenGLPixelFormatAttribute attr[32];
+ unsigned int style;
+
+ pool = [[NSAutoreleasePool alloc] init];
+
+ /* create the view */
+ fill_attr(attr, flags);
+ pf = [[[NSOpenGLPixelFormat alloc] initWithAttributes: attr] autorelease];
+ view = [[OpenGLView alloc] initWithFrame: rect pixelFormat: pf];
+
+ /* create the window and attach the OpenGL view */
+ rect.origin.x = rect.origin.y = 0;
+ rect.size.width = xsz;
+ rect.size.height = ysz;
+
+ style = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask |
+ NSResizableWindowMask;
+
+ nswin = [[NSWindow alloc] initWithContentRect: rect styleMask: style
+ backing: NSBackingStoreBuffered defer: YES];
+
+ delegate = [[WinDelegate alloc] init];
+
+ [nswin setDelegate: delegate];
+ [nswin setTitle: @"OpenGL/Cocoa"];
+ [nswin setReleasedWhenClosed: YES];
+ [nswin setContentView: view];
+ [nswin makeFirstResponder: view];
+ [nswin makeKeyAndOrderFront: nil];
+ [view release];
+
+ glwin = nswin;
+ glview = view;
+ glctx = [view openGLContext];
+ needs_redisplay = 1;
+
+ delegate->win = glwin;
+
+ [glctx makeCurrentContext];
+ [pool drain];
+ return win->wid;
+}
+
+static void close_window(void)
+{
+ [glwin close];
+ shutdown();
+}
+
+static int set_title(const char *str)
+{
+ NSString *nsstr;
+
+ nsstr = [[NSString alloc] initWithCString: str encoding: NSASCIIStringEncoding];
+ [glwin setTitle: nsstr];
+ [nsstr release];
+ return 0;
+}
+
+static void redisplay(void)
+{
+ needs_redisplay = 1;
+}
+
+static void swap_buffers(void)
+{
+ [glctx flushBuffer];
+}
+
+
+static int get_modifiers(void)
+{
+ unsigned int nsmod = [NSEvent modifierFlags];
+ unsigned int mod = 0;
+
+ if(nsmod & NSShiftKeyMask) {
+ mod |= GLUT_ACTIVE_SHIFT;
+ }
+ if(nsmod & NSControlKeyMask) {
+ mod |= GLUT_ACTIVE_CTRL;
+ }
+ if(nsmod & NSAlternateKeyMask) {
+ mod |= GLUT_ACTIVE_ALT;
+ }
+ return mod;
+}
+
+static int process_events(void)
+{
+ NSAutoreleasePool *pool;
+ NSRunLoop *runloop;
+ NSDate *block, *nonblock, *limdate;
+
+ pool = [[NSAutoreleasePool alloc] init];
+
+ if(needs_redisplay) {
+ needs_redisplay = 0;
+ cb_display();
+ }
+
+ runloop = [[NSRunLoop currentRunLoop] retain];
+ block = [runloop limitDateForMode: NSDefaultRunLoopMode];
+ nonblock = [[NSDate distantPast] retain];
+ limdate = idle ? nonblock : block;
+
+ while(!quit_main_loop) {
+ NSEvent *ev = [NSApp nextEventMatchingMask: NSAnyEventMask untilDate: limdate
+ inMode: NSDefaultRunLoopMode dequeue: YES];
+ if(!ev) break;
+
+ [NSApp sendEvent: ev];
+ if(limdate == block) {
+ limdate = nonblock;
+ }
+ }
+
+ if(cb_idle) {
+ cb_idle();
+ }
+
+ [runloop release];
+ [pool drain];
+
+ return quit_main_loop ? -1 : 0;
+}
+
+static void handle_key(NSEvent *ev, int state)
+{
+ NSPoint pt;
+ NSString *str;
+ unichar c;
+
+ str = [ev characters];
+ if(![str length]) {
+ return;
+ }
+
+ pt = [ev locationInWindow];
+ c = [str characterAtIndex: 0];
+ if(state) {
+ if(c < 256) {
+ if(cb_keydown) cb_keydown(c, pt.x, pt.y);
+ } else {
+ if(cb_skeydown) cb_skeydown(c, pt.x, pt.y);
+ }
+ } else {
+ if(c < 256) {
+ if(cb_keyup) cb_keyup(c, pt.x, pt.y);
+ } else {
+ if(cb_skeyup) cb_skeyup(c, pt.x, pt.y);
+ }
+ }
+}
+
+static void handle_mouse(NSEvent *ev, int state)
+{
+ int bn;
+ NSPoint pt;
+
+ if(cb_mouse) {
+ bn = [ev buttonNumber];
+ if(bn == 2) {
+ bn = 1;
+ } else if(bn == 1) {
+ bn = 2;
+ }
+ pt = [ev locationInWindow];
+
+ cb_mouse(0, bn, state, pt.x, pt.y - 1);
+ }
+}
+
+static void handle_motion(NSEvent *ev)
+{
+ NSPoint pt;
+
+ if(cb_motion) {
+ pt = [ev locationInWindow];
+ cb_motion(0, pt.x, pt.y - 1);
+ }
+}
+
+static void fill_attr(NSOpenGLPixelFormatAttribute *attr, unsigned int flags)
+{
+ int i = 0;
+
+ /* this is very important. makes pixelformat selection behave like GLX
+ * where any non-zero value will denote "choose highest possible". This
+ * is pretty much what we intend, as the user doesn't actually pass any
+ * of these numbers.
+ */
+ attr[i++] = NSOpenGLPFAMaximumPolicy;
+
+ attr[i++] = NSOpenGLPFAColorSize;
+ attr[i++] = 1;
+
+ if(flags & SGL_DOUBLE) {
+ attr[i++] = NSOpenGLPFADoubleBuffer;
+ }
+ if(flags & SGL_DEPTH) {
+ attr[i++] = NSOpenGLPFADepthSize;
+ attr[i++] = 1;
+ }
+ if(flags & SGL_STENCIL) {
+ attr[i++] = NSOpenGLPFAStencilSize;
+ attr[i++] = 8; /* max-policy has no effect on stencil selection */
+ }
+ if(flags & SGL_STEREO) {
+ attr[i++] = NSOpenGLPFAStereo;
+ }
+ if(flags & SGL_MULTISAMPLE) {
+ attr[i++] = NSOpenGLPFASampleBuffers;
+ attr[i++] = 1;
+ attr[i++] = NSOpenGLPFASamples;
+ attr[i++] = 4; /* TODO don't hardcode, query */
+ }
+ attr[i++] = 0;
+}