win32 support for multisampling and sRGB
[miniglut] / miniglut.c
1 /*
2 MiniGLUT - minimal GLUT subset without dependencies
3 Copyright (C) 2020  John Tsiombikas <nuclear@member.fsf.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <https://www.gnu.org/licenses/>.
17  */
18 #if defined(__unix__)
19
20 #include <X11/Xlib.h>
21 #include <X11/keysym.h>
22 #include <X11/cursorfont.h>
23 #include <GL/glx.h>
24 #define BUILD_X11
25
26 #ifndef GLX_SAMPLE_BUFFERS_ARB
27 #define GLX_SAMPLE_BUFFERS_ARB  100000
28 #define GLX_SAMPLES_ARB                 100001
29 #endif
30 #ifndef GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB
31 #define GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB        0x20b2
32 #endif
33
34 static Display *dpy;
35 static Window win, root;
36 static int scr;
37 static GLXContext ctx;
38 static Atom xa_wm_proto, xa_wm_del_win;
39 static Atom xa_net_wm_state, xa_net_wm_state_fullscr;
40 static Atom xa_motif_wm_hints;
41 static Atom xa_motion_event, xa_button_press_event, xa_button_release_event, xa_command_event;
42 static unsigned int evmask;
43
44 static int have_netwm_fullscr(void);
45
46 #elif defined(_WIN32)
47
48 #include <windows.h>
49 #define BUILD_WIN32
50
51 static HRESULT CALLBACK handle_message(HWND win, unsigned int msg, WPARAM wparam, LPARAM lparam);
52
53 static HINSTANCE hinst;
54 static HWND win;
55 static HDC dc;
56 static HGLRC ctx;
57
58 #else
59 #error unsupported platform
60 #endif
61 #include <GL/gl.h>
62 #include "miniglut.h"
63
64 struct ctx_info {
65         int rsize, gsize, bsize, asize;
66         int zsize, ssize;
67         int dblbuf;
68         int samples;
69         int stereo;
70         int srgb;
71 };
72
73 static void cleanup(void);
74 static void create_window(const char *title);
75 static void get_window_pos(int *x, int *y);
76 static void get_window_size(int *w, int *h);
77 static void get_screen_size(int *scrw, int *scrh);
78
79 static long get_msec(void);
80 static void panic(const char *msg);
81 static void sys_exit(int status);
82 static int sys_write(int fd, const void *buf, int count);
83
84
85 static int init_x = -1, init_y, init_width = 256, init_height = 256;
86 static unsigned int init_mode;
87
88 static struct ctx_info ctx_info;
89 static int cur_cursor = GLUT_CURSOR_INHERIT;
90
91 static glut_cb cb_display;
92 static glut_cb cb_idle;
93 static glut_cb_reshape cb_reshape;
94 static glut_cb_state cb_vis, cb_entry;
95 static glut_cb_keyb cb_keydown, cb_keyup;
96 static glut_cb_special cb_skeydown, cb_skeyup;
97 static glut_cb_mouse cb_mouse;
98 static glut_cb_motion cb_motion, cb_passive;
99 static glut_cb_sbmotion cb_sball_motion, cb_sball_rotate;
100 static glut_cb_sbbutton cb_sball_button;
101
102 static int fullscreen;
103 static int prev_win_x, prev_win_y, prev_win_width, prev_win_height;
104
105 static int win_width, win_height;
106 static int mapped;
107 static int quit;
108 static int upd_pending;
109 static int modstate;
110
111 void glutInit(int *argc, char **argv)
112 {
113 #ifdef BUILD_X11
114         if(!(dpy = XOpenDisplay(0))) {
115                 panic("Failed to connect to the X server\n");
116         }
117         scr = DefaultScreen(dpy);
118         root = RootWindow(dpy, scr);
119         xa_wm_proto = XInternAtom(dpy, "WM_PROTOCOLS", False);
120         xa_wm_del_win = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
121         xa_motif_wm_hints = XInternAtom(dpy, "_MOTIF_WM_HINTS", False);
122         if(have_netwm_fullscr()) {
123                 xa_net_wm_state = XInternAtom(dpy, "_NET_WM_STATE", False);
124                 xa_net_wm_state_fullscr = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
125         }
126
127         xa_motion_event = XInternAtom(dpy, "MotionEvent", True);
128         xa_button_press_event = XInternAtom(dpy, "ButtonPressEvent", True);
129         xa_button_release_event = XInternAtom(dpy, "ButtonReleaseEvent", True);
130         xa_command_event = XInternAtom(dpy, "CommandEvent", True);
131
132         evmask = ExposureMask | StructureNotifyMask;
133
134 #endif
135 #ifdef BUILD_WIN32
136         WNDCLASSEX wc = {0};
137
138         hinst = GetModuleHandle(0);
139
140         wc.cbSize = sizeof wc;
141         wc.hbrBackground = GetStockObject(BLACK_BRUSH);
142         wc.hCursor = LoadCursor(0, IDC_ARROW);
143         wc.hIcon = wc.hIconSm = LoadIcon(0, IDI_APPLICATION);
144         wc.hInstance = hinst;
145         wc.lpfnWndProc = handle_message;
146         wc.lpszClassName = "MiniGLUT";
147         wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
148         if(!RegisterClassEx(&wc)) {
149                 panic("Failed to register \"MiniGLUT\" window class\n");
150         }
151
152         if(init_x == -1) {
153                 get_screen_size(&init_x, &init_y);
154                 init_x >>= 3;
155                 init_y >>= 3;
156         }
157 #endif
158 }
159
160 void glutInitWindowPosition(int x, int y)
161 {
162         init_x = x;
163         init_y = y;
164 }
165
166 void glutInitWindowSize(int xsz, int ysz)
167 {
168         init_width = xsz;
169         init_height = ysz;
170 }
171
172 void glutInitDisplayMode(unsigned int mode)
173 {
174         init_mode = mode;
175 }
176
177 void glutCreateWindow(const char *title)
178 {
179         create_window(title);
180 }
181
182 void glutExit(void)
183 {
184         quit = 1;
185 }
186
187 void glutMainLoop(void)
188 {
189         while(!quit) {
190                 glutMainLoopEvent();
191         }
192 }
193
194 void glutPostRedisplay(void)
195 {
196         upd_pending = 1;
197 }
198
199 #define UPD_EVMASK(x) \
200         do { \
201                 if(func) { \
202                         evmask |= x; \
203                 } else { \
204                         evmask &= ~(x); \
205                 } \
206                 if(win) XSelectInput(dpy, win, evmask); \
207         } while(0)
208
209
210 void glutIdleFunc(glut_cb func)
211 {
212         cb_idle = func;
213 }
214
215 void glutDisplayFunc(glut_cb func)
216 {
217         cb_display = func;
218 }
219
220 void glutReshapeFunc(glut_cb_reshape func)
221 {
222         cb_reshape = func;
223 }
224
225 void glutVisibilityFunc(glut_cb_state func)
226 {
227         cb_vis = func;
228 #ifdef BUILD_X11
229         UPD_EVMASK(VisibilityChangeMask);
230 #endif
231 }
232
233 void glutEntryFunc(glut_cb_state func)
234 {
235         cb_entry = func;
236 #ifdef BUILD_X11
237         UPD_EVMASK(EnterWindowMask | LeaveWindowMask);
238 #endif
239 }
240
241 void glutKeyboardFunc(glut_cb_keyb func)
242 {
243         cb_keydown = func;
244 #ifdef BUILD_X11
245         UPD_EVMASK(KeyPressMask);
246 #endif
247 }
248
249 void glutKeyboardUpFunc(glut_cb_keyb func)
250 {
251         cb_keyup = func;
252 #ifdef BUILD_X11
253         UPD_EVMASK(KeyReleaseMask);
254 #endif
255 }
256
257 void glutSpecialFunc(glut_cb_special func)
258 {
259         cb_skeydown = func;
260 #ifdef BUILD_X11
261         UPD_EVMASK(KeyPressMask);
262 #endif
263 }
264
265 void glutSpecialUpFunc(glut_cb_special func)
266 {
267         cb_skeyup = func;
268 #ifdef BUILD_X11
269         UPD_EVMASK(KeyReleaseMask);
270 #endif
271 }
272
273 void glutMouseFunc(glut_cb_mouse func)
274 {
275         cb_mouse = func;
276 #ifdef BUILD_X11
277         UPD_EVMASK(ButtonPressMask | ButtonReleaseMask);
278 #endif
279 }
280
281 void glutMotionFunc(glut_cb_motion func)
282 {
283         cb_motion = func;
284 #ifdef BUILD_X11
285         UPD_EVMASK(ButtonMotionMask);
286 #endif
287 }
288
289 void glutPassiveMotionFunc(glut_cb_motion func)
290 {
291         cb_passive = func;
292 #ifdef BUILD_X11
293         UPD_EVMASK(PointerMotionMask);
294 #endif
295 }
296
297 void glutSpaceballMotionFunc(glut_cb_sbmotion func)
298 {
299         cb_sball_motion = func;
300 }
301
302 void glutSpaceballRotateFunc(glut_cb_sbmotion func)
303 {
304         cb_sball_rotate = func;
305 }
306
307 void glutSpaceballButtonFunc(glut_cb_sbbutton func)
308 {
309         cb_sball_button = func;
310 }
311
312 int glutGet(unsigned int s)
313 {
314         int x, y;
315         switch(s) {
316         case GLUT_WINDOW_X:
317                 get_window_pos(&x, &y);
318                 return x;
319         case GLUT_WINDOW_Y:
320                 get_window_pos(&x, &y);
321                 return y;
322         case GLUT_WINDOW_WIDTH:
323                 get_window_size(&x, &y);
324                 return x;
325         case GLUT_WINDOW_HEIGHT:
326                 get_window_size(&x, &y);
327                 return y;
328         case GLUT_WINDOW_BUFFER_SIZE:
329                 return ctx_info.rsize + ctx_info.gsize + ctx_info.bsize + ctx_info.asize;
330         case GLUT_WINDOW_STENCIL_SIZE:
331                 return ctx_info.ssize;
332         case GLUT_WINDOW_DEPTH_SIZE:
333                 return ctx_info.zsize;
334         case GLUT_WINDOW_RED_SIZE:
335                 return ctx_info.rsize;
336         case GLUT_WINDOW_GREEN_SIZE:
337                 return ctx_info.gsize;
338         case GLUT_WINDOW_BLUE_SIZE:
339                 return ctx_info.bsize;
340         case GLUT_WINDOW_ALPHA_SIZE:
341                 return ctx_info.asize;
342         case GLUT_WINDOW_DOUBLEBUFFER:
343                 return ctx_info.dblbuf;
344         case GLUT_WINDOW_RGBA:
345                 return 1;
346         case GLUT_WINDOW_NUM_SAMPLES:
347                 return ctx_info.samples;
348         case GLUT_WINDOW_STEREO:
349                 return ctx_info.stereo;
350         case GLUT_WINDOW_SRGB:
351                 return ctx_info.srgb;
352         case GLUT_WINDOW_CURSOR:
353                 return cur_cursor;
354         case GLUT_SCREEN_WIDTH:
355                 get_screen_size(&x, &y);
356                 return x;
357         case GLUT_SCREEN_HEIGHT:
358                 get_screen_size(&x, &y);
359                 return y;
360         case GLUT_INIT_DISPLAY_MODE:
361                 return init_mode;
362         case GLUT_INIT_WINDOW_X:
363                 return init_x;
364         case GLUT_INIT_WINDOW_Y:
365                 return init_y;
366         case GLUT_INIT_WINDOW_WIDTH:
367                 return init_width;
368         case GLUT_INIT_WINDOW_HEIGHT:
369                 return init_height;
370         case GLUT_ELAPSED_TIME:
371                 return get_msec();
372         default:
373                 break;
374         }
375         return 0;
376 }
377
378 int glutGetModifiers(void)
379 {
380         return modstate;
381 }
382
383 static int is_space(int c)
384 {
385         return c == ' ' || c == '\t' || c == '\v' || c == '\n' || c == '\r';
386 }
387
388 static const char *skip_space(const char *s)
389 {
390         while(*s && is_space(*s)) s++;
391         return s;
392 }
393
394 int glutExtensionSupported(char *ext)
395 {
396         const char *str, *eptr;
397
398         if(!(str = (const char*)glGetString(GL_EXTENSIONS))) {
399                 return 0;
400         }
401
402         while(*str) {
403                 str = skip_space(str);
404                 eptr = skip_space(ext);
405                 while(*str && !is_space(*str) && *eptr && *str == *eptr) {
406                         str++;
407                         eptr++;
408                 }
409                 if((!*str || is_space(*str)) && !*eptr) {
410                         return 1;
411                 }
412                 while(*str && !is_space(*str)) str++;
413         }
414
415         return 0;
416 }
417
418
419 /* --------------- UNIX/X11 implementation ----------------- */
420 #ifdef BUILD_X11
421 enum {
422     SPNAV_EVENT_ANY,  /* used by spnav_remove_events() */
423     SPNAV_EVENT_MOTION,
424     SPNAV_EVENT_BUTTON  /* includes both press and release */
425 };
426
427 struct spnav_event_motion {
428     int type;
429     int x, y, z;
430     int rx, ry, rz;
431     unsigned int period;
432     int *data;
433 };
434
435 struct spnav_event_button {
436     int type;
437     int press;
438     int bnum;
439 };
440
441 union spnav_event {
442     int type;
443     struct spnav_event_motion motion;
444     struct spnav_event_button button;
445 };
446
447
448 static void handle_event(XEvent *ev);
449
450 static int spnav_window(Window win);
451 static int spnav_event(const XEvent *xev, union spnav_event *event);
452 static int spnav_remove_events(int type);
453
454
455 void glutMainLoopEvent(void)
456 {
457         XEvent ev;
458
459         if(!cb_display) {
460                 panic("display callback not set");
461         }
462
463         if(!upd_pending && !cb_idle) {
464                 XNextEvent(dpy, &ev);
465                 handle_event(&ev);
466                 if(quit) goto end;
467         }
468         while(XPending(dpy)) {
469                 XNextEvent(dpy, &ev);
470                 handle_event(&ev);
471                 if(quit) goto end;
472         }
473
474         if(cb_idle) {
475                 cb_idle();
476         }
477
478         if(upd_pending && mapped) {
479                 upd_pending = 0;
480                 cb_display();
481         }
482
483 end:
484         if(quit) {
485                 cleanup();
486         }
487 }
488
489 static void cleanup(void)
490 {
491         if(win) {
492                 spnav_window(root);
493                 glXMakeCurrent(dpy, 0, 0);
494                 XDestroyWindow(dpy, win);
495         }
496 }
497
498 static KeySym translate_keysym(KeySym sym)
499 {
500         switch(sym) {
501         case XK_Escape:
502                 return 27;
503         case XK_BackSpace:
504                 return '\b';
505         case XK_Linefeed:
506                 return '\r';
507         case XK_Return:
508                 return '\n';
509         case XK_Delete:
510                 return 127;
511         case XK_Tab:
512                 return '\t';
513         default:
514                 break;
515         }
516         return sym;
517 }
518
519 static void handle_event(XEvent *ev)
520 {
521         KeySym sym;
522         union spnav_event sev;
523
524         switch(ev->type) {
525         case MapNotify:
526                 mapped = 1;
527                 break;
528         case UnmapNotify:
529                 mapped = 0;
530                 break;
531         case ConfigureNotify:
532                 if(cb_reshape && (ev->xconfigure.width != win_width || ev->xconfigure.height != win_height)) {
533                         win_width = ev->xconfigure.width;
534                         win_height = ev->xconfigure.height;
535                         cb_reshape(ev->xconfigure.width, ev->xconfigure.height);
536                 }
537                 break;
538
539         case ClientMessage:
540                 if(ev->xclient.message_type == xa_wm_proto) {
541                         if(ev->xclient.data.l[0] == xa_wm_del_win) {
542                                 quit = 1;
543                         }
544                 }
545                 if(spnav_event(ev, &sev)) {
546                         switch(sev.type) {
547                         case SPNAV_EVENT_MOTION:
548                                 if(cb_sball_motion) {
549                                         cb_sball_motion(sev.motion.x, sev.motion.y, sev.motion.z);
550                                 }
551                                 if(cb_sball_rotate) {
552                                         cb_sball_rotate(sev.motion.rx, sev.motion.ry, sev.motion.rz);
553                                 }
554                                 spnav_remove_events(SPNAV_EVENT_MOTION);
555                                 break;
556
557                         case SPNAV_EVENT_BUTTON:
558                                 if(cb_sball_button) {
559                                         cb_sball_button(sev.button.bnum + 1, sev.button.press ? GLUT_DOWN : GLUT_UP);
560                                 }
561                                 break;
562
563                         default:
564                                 break;
565                         }
566                 }
567                 break;
568
569         case Expose:
570                 upd_pending = 1;
571                 break;
572
573         case KeyPress:
574         case KeyRelease:
575                 modstate = ev->xkey.state & (ShiftMask | ControlMask | Mod1Mask);
576                 if(!(sym = XLookupKeysym(&ev->xkey, 0))) {
577                         break;
578                 }
579                 sym = translate_keysym(sym);
580                 if(sym < 256) {
581                         if(ev->type == KeyPress) {
582                                 if(cb_keydown) cb_keydown((unsigned char)sym, ev->xkey.x, ev->xkey.y);
583                         } else {
584                                 if(cb_keyup) cb_keyup((unsigned char)sym, ev->xkey.x, ev->xkey.y);
585                         }
586                 } else {
587                         if(ev->type == KeyPress) {
588                                 if(cb_skeydown) cb_skeydown(sym, ev->xkey.x, ev->xkey.y);
589                         } else {
590                                 if(cb_skeyup) cb_skeyup(sym, ev->xkey.x, ev->xkey.y);
591                         }
592                 }
593                 break;
594
595         case ButtonPress:
596         case ButtonRelease:
597                 modstate = ev->xbutton.state & (ShiftMask | ControlMask | Mod1Mask);
598                 if(cb_mouse) {
599                         int bn = ev->xbutton.button - Button1;
600                         cb_mouse(bn, ev->type == ButtonPress ? GLUT_DOWN : GLUT_UP,
601                                         ev->xbutton.x, ev->xbutton.y);
602                 }
603                 break;
604
605         case MotionNotify:
606                 if(ev->xmotion.state & (Button1Mask | Button2Mask | Button3Mask | Button4Mask | Button5Mask)) {
607                         if(cb_motion) cb_motion(ev->xmotion.x, ev->xmotion.y);
608                 } else {
609                         if(cb_passive) cb_passive(ev->xmotion.x, ev->xmotion.y);
610                 }
611                 break;
612
613         case VisibilityNotify:
614                 if(cb_vis) {
615                         cb_vis(ev->xvisibility.state == VisibilityFullyObscured ? GLUT_NOT_VISIBLE : GLUT_VISIBLE);
616                 }
617                 break;
618         case EnterNotify:
619                 if(cb_entry) cb_entry(GLUT_ENTERED);
620                 break;
621         case LeaveNotify:
622                 if(cb_entry) cb_entry(GLUT_LEFT);
623                 break;
624         }
625 }
626
627 void glutSwapBuffers(void)
628 {
629         glXSwapBuffers(dpy, win);
630 }
631
632 /* BUG:
633  * set_fullscreen_mwm removes the decorations with MotifWM hints, and then it
634  * needs to resize the window to make it fullscreen. The way it does this is by
635  * querying the size of the root window (see get_screen_size), which in the
636  * case of multi-monitor setups will be the combined size of all monitors.
637  * This is problematic; the way to solve it is to use the XRandR extension, or
638  * the Xinerama extension, to figure out the dimensions of the correct video
639  * output, which would add potentially two extension support libraries to our
640  * dependencies list.
641  * Moreover, any X installation modern enough to support XR&R will almost
642  * certainly be running a window manager supporting the EHWM
643  * _NET_WM_STATE_FULLSCREEN method (set_fullscreen_ewmh), which does not rely
644  * on manual resizing, and is used in preference if available, making this
645  * whole endeavor pointless.
646  * So I'll just leave it with set_fullscreen_mwm covering the entire
647  * multi-monitor area for now.
648  */
649
650 struct mwm_hints {
651         unsigned long flags;
652         unsigned long functions;
653         unsigned long decorations;
654         long input_mode;
655         unsigned long status;
656 };
657
658 #define MWM_HINTS_DECORATIONS   2
659 #define MWM_DECOR_ALL                   1
660
661 static void set_fullscreen_mwm(int fs)
662 {
663         struct mwm_hints hints;
664         int scr_width, scr_height;
665
666         if(fs) {
667                 get_window_pos(&prev_win_x, &prev_win_y);
668                 get_window_size(&prev_win_width, &prev_win_height);
669                 get_screen_size(&scr_width, &scr_height);
670
671                 hints.decorations = 0;
672                 hints.flags = MWM_HINTS_DECORATIONS;
673                 XChangeProperty(dpy, win, xa_motif_wm_hints, xa_motif_wm_hints, 32,
674                                 PropModeReplace, (unsigned char*)&hints, 5);
675
676                 XMoveResizeWindow(dpy, win, 0, 0, scr_width, scr_height);
677         } else {
678                 XDeleteProperty(dpy, win, xa_motif_wm_hints);
679                 XMoveResizeWindow(dpy, win, prev_win_x, prev_win_y, prev_win_width, prev_win_height);
680         }
681 }
682
683 static int have_netwm_fullscr(void)
684 {
685         int fmt;
686         long offs = 0;
687         unsigned long i, count, rem;
688         Atom prop[8], type;
689         Atom xa_net_supported = XInternAtom(dpy, "_NET_SUPPORTED", False);
690
691         do {
692                 XGetWindowProperty(dpy, root, xa_net_supported, offs, 8, False, AnyPropertyType,
693                                 &type, &fmt, &count, &rem, (unsigned char**)prop);
694
695                 for(i=0; i<count; i++) {
696                         if(prop[i] == xa_net_wm_state_fullscr) {
697                                 return 1;
698                         }
699                 }
700                 offs += count;
701         } while(rem > 0);
702
703         return 0;
704 }
705
706 static void set_fullscreen_ewmh(int fs)
707 {
708         XClientMessageEvent msg = {0};
709
710         msg.type = ClientMessage;
711         msg.window = win;
712         msg.message_type = xa_net_wm_state;     /* _NET_WM_STATE */
713         msg.format = 32;
714         msg.data.l[0] = fs ? 1 : 0;
715         msg.data.l[1] = xa_net_wm_state_fullscr;        /* _NET_WM_STATE_FULLSCREEN */
716         msg.data.l[2] = 0;
717         msg.data.l[3] = 1;      /* source regular application */
718         XSendEvent(dpy, root, False, SubstructureNotifyMask | SubstructureRedirectMask, (XEvent*)&msg);
719 }
720
721 static void set_fullscreen(int fs)
722 {
723         if(fullscreen == fs) return;
724
725         if(xa_net_wm_state && xa_net_wm_state_fullscr) {
726                 set_fullscreen_ewmh(fs);
727                 fullscreen = fs;
728         } else if(xa_motif_wm_hints) {
729                 set_fullscreen_mwm(fs);
730                 fullscreen = fs;
731         }
732 }
733
734 void glutPositionWindow(int x, int y)
735 {
736         set_fullscreen(0);
737         XMoveWindow(dpy, win, x, y);
738 }
739
740 void glutReshapeWindow(int xsz, int ysz)
741 {
742         set_fullscreen(0);
743         XResizeWindow(dpy, win, xsz, ysz);
744 }
745
746 void glutFullScreen(void)
747 {
748         set_fullscreen(1);
749 }
750
751 void glutSetWindowTitle(const char *title)
752 {
753         XTextProperty tprop;
754         if(!XStringListToTextProperty((char**)&title, 1, &tprop)) {
755                 return;
756         }
757         XSetWMName(dpy, win, &tprop);
758         XFree(tprop.value);
759 }
760
761 void glutSetIconTitle(const char *title)
762 {
763         XTextProperty tprop;
764         if(!XStringListToTextProperty((char**)&title, 1, &tprop)) {
765                 return;
766         }
767         XSetWMIconName(dpy, win, &tprop);
768         XFree(tprop.value);
769 }
770
771 void glutSetCursor(int cidx)
772 {
773         Cursor cur = None;
774
775         switch(cidx) {
776         case GLUT_CURSOR_LEFT_ARROW:
777                 cur = XCreateFontCursor(dpy, XC_left_ptr);
778                 break;
779         case GLUT_CURSOR_INHERIT:
780                 break;
781         case GLUT_CURSOR_NONE:
782                 /* TODO */
783         default:
784                 return;
785         }
786
787         XDefineCursor(dpy, win, cur);
788         cur_cursor = cidx;
789 }
790
791 static XVisualInfo *choose_visual(unsigned int mode)
792 {
793         XVisualInfo *vi;
794         int attr[32];
795         int *aptr = attr;
796         int *samples = 0;
797
798         if(mode & GLUT_DOUBLE) {
799                 *aptr++ = GLX_DOUBLEBUFFER;
800         }
801
802         if(mode & GLUT_INDEX) {
803                 *aptr++ = GLX_BUFFER_SIZE;
804                 *aptr++ = 1;
805         } else {
806                 *aptr++ = GLX_RGBA;
807                 *aptr++ = GLX_RED_SIZE; *aptr++ = 4;
808                 *aptr++ = GLX_GREEN_SIZE; *aptr++ = 4;
809                 *aptr++ = GLX_BLUE_SIZE; *aptr++ = 4;
810         }
811         if(mode & GLUT_ALPHA) {
812                 *aptr++ = GLX_ALPHA_SIZE;
813                 *aptr++ = 4;
814         }
815         if(mode & GLUT_DEPTH) {
816                 *aptr++ = GLX_DEPTH_SIZE;
817                 *aptr++ = 16;
818         }
819         if(mode & GLUT_STENCIL) {
820                 *aptr++ = GLX_STENCIL_SIZE;
821                 *aptr++ = 1;
822         }
823         if(mode & GLUT_ACCUM) {
824                 *aptr++ = GLX_ACCUM_RED_SIZE; *aptr++ = 1;
825                 *aptr++ = GLX_ACCUM_GREEN_SIZE; *aptr++ = 1;
826                 *aptr++ = GLX_ACCUM_BLUE_SIZE; *aptr++ = 1;
827         }
828         if(mode & GLUT_STEREO) {
829                 *aptr++ = GLX_STEREO;
830         }
831         if(mode & GLUT_SRGB) {
832                 *aptr++ = GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB;
833         }
834         if(mode & GLUT_MULTISAMPLE) {
835                 *aptr++ = GLX_SAMPLE_BUFFERS_ARB;
836                 *aptr++ = 1;
837                 *aptr++ = GLX_SAMPLES_ARB;
838                 samples = aptr;
839                 *aptr++ = 32;
840         }
841         *aptr++ = None;
842
843         if(!samples) {
844                 return glXChooseVisual(dpy, scr, attr);
845         }
846         while(!(vi = glXChooseVisual(dpy, scr, attr)) && *samples) {
847                 *samples >>= 1;
848                 if(!*samples) {
849                         aptr[-3] = None;
850                 }
851         }
852         return vi;
853 }
854
855 static void create_window(const char *title)
856 {
857         XSetWindowAttributes xattr = {0};
858         XVisualInfo *vi;
859         unsigned int xattr_mask;
860         unsigned int mode = init_mode;
861
862         if(!(vi = choose_visual(mode))) {
863                 mode &= ~GLUT_SRGB;
864                 if(!(vi = choose_visual(mode))) {
865                         panic("Failed to find compatible visual\n");
866                 }
867         }
868
869         if(!(ctx = glXCreateContext(dpy, vi, 0, True))) {
870                 XFree(vi);
871                 panic("Failed to create OpenGL context\n");
872         }
873
874         glXGetConfig(dpy, vi, GLX_RED_SIZE, &ctx_info.rsize);
875         glXGetConfig(dpy, vi, GLX_GREEN_SIZE, &ctx_info.gsize);
876         glXGetConfig(dpy, vi, GLX_BLUE_SIZE, &ctx_info.bsize);
877         glXGetConfig(dpy, vi, GLX_ALPHA_SIZE, &ctx_info.asize);
878         glXGetConfig(dpy, vi, GLX_DEPTH_SIZE, &ctx_info.zsize);
879         glXGetConfig(dpy, vi, GLX_STENCIL_SIZE, &ctx_info.ssize);
880         glXGetConfig(dpy, vi, GLX_DOUBLEBUFFER, &ctx_info.dblbuf);
881         glXGetConfig(dpy, vi, GLX_STEREO, &ctx_info.stereo);
882         glXGetConfig(dpy, vi, GLX_SAMPLES_ARB, &ctx_info.samples);
883         glXGetConfig(dpy, vi, GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB, &ctx_info.srgb);
884
885         xattr.background_pixel = BlackPixel(dpy, scr);
886         xattr.colormap = XCreateColormap(dpy, root, vi->visual, AllocNone);
887         xattr_mask = CWBackPixel | CWColormap | CWBackPixmap | CWBorderPixel;
888         if(!(win = XCreateWindow(dpy, root, init_x, init_y, init_width, init_height, 0,
889                         vi->depth, InputOutput, vi->visual, xattr_mask, &xattr))) {
890                 XFree(vi);
891                 glXDestroyContext(dpy, ctx);
892                 panic("Failed to create window\n");
893         }
894         XFree(vi);
895
896         XSelectInput(dpy, win, evmask);
897
898         spnav_window(win);
899
900         glutSetWindowTitle(title);
901         glutSetIconTitle(title);
902         XSetWMProtocols(dpy, win, &xa_wm_del_win, 1);
903         XMapWindow(dpy, win);
904
905         glXMakeCurrent(dpy, win, ctx);
906 }
907
908 static void get_window_pos(int *x, int *y)
909 {
910         Window child;
911         XTranslateCoordinates(dpy, win, root, 0, 0, x, y, &child);
912 }
913
914 static void get_window_size(int *w, int *h)
915 {
916         XWindowAttributes wattr;
917         XGetWindowAttributes(dpy, win, &wattr);
918         *w = wattr.width;
919         *h = wattr.height;
920 }
921
922 static void get_screen_size(int *scrw, int *scrh)
923 {
924         XWindowAttributes wattr;
925         XGetWindowAttributes(dpy, root, &wattr);
926         *scrw = wattr.width;
927         *scrh = wattr.height;
928 }
929
930
931 /* spaceball */
932 enum {
933   CMD_APP_WINDOW = 27695,
934   CMD_APP_SENS
935 };
936
937 static Window get_daemon_window(Display *dpy);
938 static int catch_badwin(Display *dpy, XErrorEvent *err);
939
940 #define SPNAV_INITIALIZED       (xa_motion_event)
941
942 static int spnav_window(Window win)
943 {
944         int (*prev_xerr_handler)(Display*, XErrorEvent*);
945         XEvent xev;
946         Window daemon_win;
947
948         if(!SPNAV_INITIALIZED) {
949                 return -1;
950         }
951
952         if(!(daemon_win = get_daemon_window(dpy))) {
953                 return -1;
954         }
955
956         prev_xerr_handler = XSetErrorHandler(catch_badwin);
957
958         xev.type = ClientMessage;
959         xev.xclient.send_event = False;
960         xev.xclient.display = dpy;
961         xev.xclient.window = win;
962         xev.xclient.message_type = xa_command_event;
963         xev.xclient.format = 16;
964         xev.xclient.data.s[0] = ((unsigned int)win & 0xffff0000) >> 16;
965         xev.xclient.data.s[1] = (unsigned int)win & 0xffff;
966         xev.xclient.data.s[2] = CMD_APP_WINDOW;
967
968         XSendEvent(dpy, daemon_win, False, 0, &xev);
969         XSync(dpy, False);
970
971         XSetErrorHandler(prev_xerr_handler);
972         return 0;
973 }
974
975 static Bool match_events(Display *dpy, XEvent *xev, char *arg)
976 {
977         int evtype = *(int*)arg;
978
979         if(xev->type != ClientMessage) {
980                 return False;
981         }
982
983         if(xev->xclient.message_type == xa_motion_event) {
984                 return !evtype || evtype == SPNAV_EVENT_MOTION ? True : False;
985         }
986         if(xev->xclient.message_type == xa_button_press_event ||
987                         xev->xclient.message_type == xa_button_release_event) {
988                 return !evtype || evtype == SPNAV_EVENT_BUTTON ? True : False;
989         }
990         return False;
991 }
992
993 static int spnav_remove_events(int type)
994 {
995         int rm_count = 0;
996         XEvent xev;
997         while(XCheckIfEvent(dpy, &xev, match_events, (char*)&type)) {
998                 rm_count++;
999         }
1000         return rm_count;
1001 }
1002
1003 static int spnav_event(const XEvent *xev, union spnav_event *event)
1004 {
1005         int i;
1006         int xmsg_type;
1007
1008         xmsg_type = xev->xclient.message_type;
1009
1010         if(xmsg_type != xa_motion_event && xmsg_type != xa_button_press_event &&
1011                         xmsg_type != xa_button_release_event) {
1012                 return 0;
1013         }
1014
1015         if(xmsg_type == xa_motion_event) {
1016                 event->type = SPNAV_EVENT_MOTION;
1017                 event->motion.data = &event->motion.x;
1018
1019                 for(i=0; i<6; i++) {
1020                         event->motion.data[i] = xev->xclient.data.s[i + 2];
1021                 }
1022                 event->motion.period = xev->xclient.data.s[8];
1023         } else {
1024                 event->type = SPNAV_EVENT_BUTTON;
1025                 event->button.press = xmsg_type == xa_button_press_event ? 1 : 0;
1026                 event->button.bnum = xev->xclient.data.s[2];
1027         }
1028         return event->type;
1029 }
1030
1031 static int mglut_strcmp(const char *s1, const char *s2)
1032 {
1033         while(*s1 && *s1 == *s2) {
1034                 s1++;
1035                 s2++;
1036         }
1037         return *s1 - *s2;
1038 }
1039
1040 static Window get_daemon_window(Display *dpy)
1041 {
1042         Window win;
1043         XTextProperty wname;
1044         Atom type;
1045         int fmt;
1046         unsigned long nitems, bytes_after;
1047         unsigned char *prop;
1048
1049         XGetWindowProperty(dpy, root, xa_command_event, 0, 1, False, AnyPropertyType,
1050                         &type, &fmt, &nitems, &bytes_after, &prop);
1051         if(!prop) {
1052                 return 0;
1053         }
1054
1055         win = *(Window*)prop;
1056         XFree(prop);
1057
1058         if(!XGetWMName(dpy, win, &wname) || mglut_strcmp("Magellan Window", (char*)wname.value) != 0) {
1059                 return 0;
1060         }
1061
1062         return win;
1063 }
1064
1065 static int catch_badwin(Display *dpy, XErrorEvent *err)
1066 {
1067         return 0;
1068 }
1069
1070
1071
1072 #endif  /* BUILD_X11 */
1073
1074
1075 /* --------------- windows implementation ----------------- */
1076 #ifdef BUILD_WIN32
1077 static int reshape_pending;
1078
1079 static void update_modkeys(void);
1080 static int translate_vkey(int vkey);
1081 static void handle_mbutton(int bn, int st, WPARAM wparam, LPARAM lparam);
1082
1083 #ifdef MINIGLUT_WINMAIN
1084 int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hprev, char *cmdline, int showcmd)
1085 {
1086         int argc = 1;
1087         char *argv[] = { "miniglut.exe", 0 };
1088         return main(argc, argv);
1089 }
1090 #endif
1091
1092 void glutMainLoopEvent(void)
1093 {
1094         MSG msg;
1095
1096         if(!cb_display) {
1097                 panic("display callback not set");
1098         }
1099
1100         if(reshape_pending && cb_reshape) {
1101                 reshape_pending = 0;
1102                 get_window_size(&win_width, &win_height);
1103                 cb_reshape(win_width, win_height);
1104         }
1105
1106         if(!upd_pending && !cb_idle) {
1107                 GetMessage(&msg, 0, 0, 0);
1108                 TranslateMessage(&msg);
1109                 DispatchMessage(&msg);
1110                 if(quit) return;
1111         }
1112         while(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
1113                 TranslateMessage(&msg);
1114                 DispatchMessage(&msg);
1115                 if(quit) return;
1116         }
1117
1118         if(cb_idle) {
1119                 cb_idle();
1120         }
1121
1122         if(upd_pending && mapped) {
1123                 upd_pending = 0;
1124                 cb_display();
1125         }
1126 }
1127
1128 static void cleanup(void)
1129 {
1130         if(win) {
1131                 wglMakeCurrent(dc, 0);
1132                 wglDeleteContext(ctx);
1133                 UnregisterClass("MiniGLUT", hinst);
1134         }
1135 }
1136
1137 void glutSwapBuffers(void)
1138 {
1139         SwapBuffers(dc);
1140 }
1141
1142 void glutPositionWindow(int x, int y)
1143 {
1144         RECT rect;
1145         unsigned int flags = SWP_SHOWWINDOW;
1146
1147         if(fullscreen) {
1148                 rect.left = prev_win_x;
1149                 rect.top = prev_win_y;
1150                 rect.right = rect.left + prev_win_width;
1151                 rect.bottom = rect.top + prev_win_height;
1152                 SetWindowLong(win, GWL_STYLE, WS_OVERLAPPEDWINDOW);
1153                 fullscreen = 0;
1154                 flags |= SWP_FRAMECHANGED;
1155         } else {
1156                 GetWindowRect(win, &rect);
1157         }
1158         SetWindowPos(win, HWND_NOTOPMOST, x, y, rect.right - rect.left, rect.bottom - rect.top, flags);
1159 }
1160
1161 void glutReshapeWindow(int xsz, int ysz)
1162 {
1163         RECT rect;
1164         unsigned int flags = SWP_SHOWWINDOW;
1165
1166         if(fullscreen) {
1167                 rect.left = prev_win_x;
1168                 rect.top = prev_win_y;
1169                 SetWindowLong(win, GWL_STYLE, WS_OVERLAPPEDWINDOW);
1170                 fullscreen = 0;
1171                 flags |= SWP_FRAMECHANGED;
1172         } else {
1173                 GetWindowRect(win, &rect);
1174         }
1175         SetWindowPos(win, HWND_NOTOPMOST, rect.left, rect.top, xsz, ysz, flags);
1176 }
1177
1178 void glutFullScreen(void)
1179 {
1180         RECT rect;
1181         int scr_width, scr_height;
1182
1183         if(fullscreen) return;
1184
1185         GetWindowRect(win, &rect);
1186         prev_win_x = rect.left;
1187         prev_win_y = rect.top;
1188         prev_win_width = rect.right - rect.left;
1189         prev_win_height = rect.bottom - rect.top;
1190
1191         get_screen_size(&scr_width, &scr_height);
1192
1193         SetWindowLong(win, GWL_STYLE, 0);
1194         SetWindowPos(win, HWND_TOPMOST, 0, 0, scr_width, scr_height, SWP_SHOWWINDOW);
1195
1196         fullscreen = 1;
1197 }
1198
1199 void glutSetWindowTitle(const char *title)
1200 {
1201         SetWindowText(win, title);
1202 }
1203
1204 void glutSetIconTitle(const char *title)
1205 {
1206 }
1207
1208 void glutSetCursor(int cidx)
1209 {
1210         switch(cidx) {
1211         case GLUT_CURSOR_NONE:
1212                 ShowCursor(0);
1213                 break;
1214         case GLUT_CURSOR_INHERIT:
1215         case GLUT_CURSOR_LEFT_ARROW:
1216         default:
1217                 SetCursor(LoadCursor(0, IDC_ARROW));
1218                 ShowCursor(1);
1219         }
1220 }
1221
1222 #define WGL_DRAW_TO_WINDOW      0x2001
1223 #define WGL_SUPPORT_OPENGL      0x2010
1224 #define WGL_DOUBLE_BUFFER       0x2011
1225 #define WGL_STEREO                      0x2012
1226 #define WGL_PIXEL_TYPE          0x2013
1227 #define WGL_COLOR_BITS          0x2014
1228 #define WGL_RED_BITS            0x2015
1229 #define WGL_GREEN_BITS          0x2017
1230 #define WGL_BLUE_BITS           0x2019
1231 #define WGL_ALPHA_BITS          0x201b
1232 #define WGL_ACCUM_BITS          0x201d
1233 #define WGL_DEPTH_BITS          0x2022
1234 #define WGL_STENCIL_BITS        0x2023
1235
1236 #define WGL_TYPE_RGBA           0x202b
1237 #define WGL_TYPE_COLORINDEX     0x202c
1238
1239 #define WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB        0x20a9
1240 #define WGL_SAMPLE_BUFFERS_ARB                          0x2041
1241 #define WGL_SAMPLES_ARB                                         0x2042
1242
1243 static PROC wglChoosePixelFormat;
1244 static PROC wglGetPixelFormatAttribiv;
1245
1246 #define ATTR(a, v) \
1247         do { *aptr++ = (a); *aptr++ = (v); } while(0)
1248
1249 static unsigned int choose_pixfmt(unsigned int mode)
1250 {
1251         unsigned int num_pixfmt, pixfmt = 0;
1252         int attr[32] = { WGL_DRAW_TO_WINDOW, 1, WGL_SUPPORT_OPENGL, 1 };
1253
1254         int *aptr = attr;
1255         int *samples = 0;
1256
1257         if(mode & GLUT_DOUBLE) {
1258                 ATTR(WGL_DOUBLE_BUFFER, 1);
1259         }
1260
1261         ATTR(WGL_PIXEL_TYPE, mode & GLUT_INDEX ? WGL_TYPE_COLORINDEX : WGL_TYPE_RGBA);
1262         ATTR(WGL_COLOR_BITS, 8);
1263         if(mode & GLUT_ALPHA) {
1264                 ATTR(WGL_ALPHA_BITS, 4);
1265         }
1266         if(mode & GLUT_DEPTH) {
1267                 ATTR(WGL_DEPTH_BITS, 16);
1268         }
1269         if(mode & GLUT_STENCIL) {
1270                 ATTR(WGL_STENCIL_BITS, 1);
1271         }
1272         if(mode & GLUT_ACCUM) {
1273                 ATTR(WGL_ACCUM_BITS, 1);
1274         }
1275         if(mode & GLUT_STEREO) {
1276                 ATTR(WGL_STEREO, 1);
1277         }
1278         if(mode & GLUT_SRGB) {
1279                 ATTR(WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB, 1);
1280         }
1281         if(mode & GLUT_MULTISAMPLE) {
1282                 ATTR(WGL_SAMPLE_BUFFERS_ARB, 1);
1283                 *aptr++ = WGL_SAMPLES_ARB;
1284                 samples = aptr;
1285                 *aptr++ = 32;
1286         }
1287         *aptr++ = 0;
1288
1289         while(!wglChoosePixelFormat(dc, attr, 0, 1, &pixfmt, &num_pixfmt) && samples && *samples) {
1290                 *samples >>= 1;
1291                 if(!*samples) {
1292                         aptr[-3] = 0;
1293                 }
1294         }
1295         return pixfmt;
1296 }
1297
1298 static PIXELFORMATDESCRIPTOR tmppfd = {
1299         sizeof tmppfd, 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
1300         PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 8, 0,
1301         PFD_MAIN_PLANE, 0, 0, 0, 0
1302 };
1303 #define TMPCLASS        "TempMiniGLUT"
1304
1305 #define GETATTR(attr, vptr) \
1306         do { \
1307                 int gattr = attr; \
1308                 wglGetPixelFormatAttribiv(dc, pixfmt, 0, 1, &gattr, vptr); \
1309         } while(0)
1310
1311 static int create_window_wglext(const char *title, int width, int height)
1312 {
1313         WNDCLASSEX wc = {0};
1314         HWND tmpwin = 0;
1315         HDC tmpdc = 0;
1316         HGLRC tmpctx = 0;
1317         int pixfmt;
1318
1319         /* create a temporary window and GL context, just to query and retrieve
1320          * the wglChoosePixelFormatEXT function
1321          */
1322         wc.cbSize = sizeof wc;
1323         wc.hbrBackground = GetStockObject(BLACK_BRUSH);
1324         wc.hCursor = LoadCursor(0, IDC_ARROW);
1325         wc.hIcon = wc.hIconSm = LoadIcon(0, IDI_APPLICATION);
1326         wc.hInstance = hinst;
1327         wc.lpfnWndProc = DefWindowProc;
1328         wc.lpszClassName = TMPCLASS;
1329         wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
1330         if(!RegisterClassEx(&wc)) {
1331                 return 0;
1332         }
1333         if(!(tmpwin = CreateWindow(TMPCLASS, "temp", WS_OVERLAPPEDWINDOW, 0, 0,
1334                                         width, height, 0, 0, hinst, 0))) {
1335                 goto fail;
1336         }
1337         tmpdc = GetDC(tmpwin);
1338
1339         if(!(pixfmt = ChoosePixelFormat(dc, &tmppfd)) ||
1340                         !SetPixelFormat(dc, pixfmt, &tmppfd) ||
1341                         !(tmpctx = wglCreateContext(tmpdc))) {
1342                 goto fail;
1343         }
1344         wglMakeCurrent(tmpdc, tmpctx);
1345
1346         if(!(wglChoosePixelFormat = wglGetProcAddress("wglChoosePixelFormatARB"))) {
1347                 if(!(wglChoosePixelFormat = wglGetProcAddress("wglChoosePixelFormatEXT"))) {
1348                         goto fail;
1349                 }
1350                 if(!(wglGetPixelFormatAttribiv = wglGetProcAddress("wglGetPixelFormatAttribivEXT"))) {
1351                         goto fail;
1352                 }
1353         } else {
1354                 if(!(wglGetPixelFormatAttribiv = wglGetProcAddress("wglGetPixelFormatAttribivARB"))) {
1355                         goto fail;
1356                 }
1357         }
1358         wglMakeCurrent(0, 0);
1359         wglDeleteContext(tmpctx);
1360         DestroyWindow(tmpwin);
1361         UnregisterClass(TMPCLASS, hinst);
1362
1363         /* create the real window and context */
1364         if(!(win = CreateWindow("MiniGLUT", title, WS_OVERLAPPEDWINDOW, init_x,
1365                                         init_y, width, height, 0, 0, hinst, 0))) {
1366                 panic("Failed to create window\n");
1367         }
1368         dc = GetDC(win);
1369
1370         if(!(pixfmt = choose_pixfmt(init_mode))) {
1371                 panic("Failed to find suitable pixel format\n");
1372         }
1373         if(!SetPixelFormat(dc, pixfmt, &tmppfd)) {
1374                 panic("Failed to set the selected pixel format\n");
1375         }
1376         if(!(ctx = wglCreateContext(dc))) {
1377                 panic("Failed to create the OpenGL context\n");
1378         }
1379         wglMakeCurrent(dc, ctx);
1380
1381         GETATTR(WGL_RED_BITS, &ctx_info.rsize);
1382         GETATTR(WGL_GREEN_BITS, &ctx_info.gsize);
1383         GETATTR(WGL_BLUE_BITS, &ctx_info.bsize);
1384         GETATTR(WGL_ALPHA_BITS, &ctx_info.asize);
1385         GETATTR(WGL_DEPTH_BITS, &ctx_info.zsize);
1386         GETATTR(WGL_STENCIL_BITS, &ctx_info.ssize);
1387         GETATTR(WGL_DOUBLE_BUFFER, &ctx_info.dblbuf);
1388         GETATTR(WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB, &ctx_info.srgb);
1389         GETATTR(WGL_SAMPLES_ARB, &ctx_info.samples);
1390         return 0;
1391
1392 fail:
1393         if(tmpctx) {
1394                 wglMakeCurrent(0, 0);
1395                 wglDeleteContext(tmpctx);
1396         }
1397         if(tmpwin) {
1398                 DestroyWindow(tmpwin);
1399         }
1400         UnregisterClass(TMPCLASS, hinst);
1401         return -1;
1402 }
1403
1404
1405 static void create_window(const char *title)
1406 {
1407         int pixfmt;
1408         PIXELFORMATDESCRIPTOR pfd = {0};
1409         RECT rect;
1410         int width, height;
1411
1412         rect.left = init_x;
1413         rect.top = init_y;
1414         rect.right = init_x + init_width;
1415         rect.bottom = init_y + init_height;
1416         AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, 0);
1417         width = rect.right - rect.left;
1418         height = rect.bottom - rect.top;
1419
1420         if(create_window_wglext(title, width, height) == -1) {
1421
1422                 if(!(win = CreateWindow("MiniGLUT", title, WS_OVERLAPPEDWINDOW,
1423                                         rect.left, rect.top, width, height, 0, 0, hinst, 0))) {
1424                         panic("Failed to create window\n");
1425                 }
1426                 dc = GetDC(win);
1427
1428                 pfd.nSize = sizeof pfd;
1429                 pfd.nVersion = 1;
1430                 pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
1431                 if(init_mode & GLUT_STEREO) {
1432                         pfd.dwFlags |= PFD_STEREO;
1433                 }
1434                 pfd.iPixelType = init_mode & GLUT_INDEX ? PFD_TYPE_COLORINDEX : PFD_TYPE_RGBA;
1435                 pfd.cColorBits = 24;
1436                 if(init_mode & GLUT_ALPHA) {
1437                         pfd.cAlphaBits = 8;
1438                 }
1439                 if(init_mode & GLUT_ACCUM) {
1440                         pfd.cAccumBits = 24;
1441                 }
1442                 if(init_mode & GLUT_DEPTH) {
1443                         pfd.cDepthBits = 24;
1444                 }
1445                 if(init_mode & GLUT_STENCIL) {
1446                         pfd.cStencilBits = 8;
1447                 }
1448                 pfd.iLayerType = PFD_MAIN_PLANE;
1449
1450                 if(!(pixfmt = ChoosePixelFormat(dc, &pfd))) {
1451                         panic("Failed to find suitable pixel format\n");
1452                 }
1453                 if(!SetPixelFormat(dc, pixfmt, &pfd)) {
1454                         panic("Failed to set the selected pixel format\n");
1455                 }
1456                 if(!(ctx = wglCreateContext(dc))) {
1457                         panic("Failed to create the OpenGL context\n");
1458                 }
1459                 wglMakeCurrent(dc, ctx);
1460
1461                 DescribePixelFormat(dc, pixfmt, sizeof pfd, &pfd);
1462                 ctx_info.rsize = pfd.cRedBits;
1463                 ctx_info.gsize = pfd.cGreenBits;
1464                 ctx_info.bsize = pfd.cBlueBits;
1465                 ctx_info.asize = pfd.cAlphaBits;
1466                 ctx_info.zsize = pfd.cDepthBits;
1467                 ctx_info.ssize = pfd.cStencilBits;
1468                 ctx_info.dblbuf = pfd.dwFlags & PFD_DOUBLEBUFFER ? 1 : 0;
1469                 ctx_info.samples = 0;
1470                 ctx_info.srgb = 0;
1471         }
1472
1473         ShowWindow(win, 1);
1474         SetForegroundWindow(win);
1475         SetFocus(win);
1476         upd_pending = 1;
1477         reshape_pending = 1;
1478 }
1479
1480 static HRESULT CALLBACK handle_message(HWND win, unsigned int msg, WPARAM wparam, LPARAM lparam)
1481 {
1482         static int mouse_x, mouse_y;
1483         int x, y, key;
1484
1485         switch(msg) {
1486         case WM_CLOSE:
1487                 if(win) DestroyWindow(win);
1488                 break;
1489
1490         case WM_DESTROY:
1491                 cleanup();
1492                 quit = 1;
1493                 PostQuitMessage(0);
1494                 break;
1495
1496         case WM_PAINT:
1497                 upd_pending = 1;
1498                 ValidateRect(win, 0);
1499                 break;
1500
1501         case WM_SIZE:
1502                 x = lparam & 0xffff;
1503                 y = lparam >> 16;
1504                 if(x != win_width && y != win_height) {
1505                         win_width = x;
1506                         win_height = y;
1507                         if(cb_reshape) {
1508                                 reshape_pending = 0;
1509                                 cb_reshape(win_width, win_height);
1510                         }
1511                 }
1512                 break;
1513
1514         case WM_SHOWWINDOW:
1515                 mapped = wparam;
1516                 if(cb_vis) cb_vis(mapped ? GLUT_VISIBLE : GLUT_NOT_VISIBLE);
1517                 break;
1518
1519         case WM_KEYDOWN:
1520         case WM_SYSKEYDOWN:
1521                 update_modkeys();
1522                 key = translate_vkey(wparam);
1523                 if(key < 256) {
1524                         if(cb_keydown) {
1525                                 cb_keydown((unsigned char)key, mouse_x, mouse_y);
1526                         }
1527                 } else {
1528                         if(cb_skeydown) {
1529                                 cb_skeydown(key, mouse_x, mouse_y);
1530                         }
1531                 }
1532                 break;
1533
1534         case WM_KEYUP:
1535         case WM_SYSKEYUP:
1536                 update_modkeys();
1537                 key = translate_vkey(wparam);
1538                 if(key < 256) {
1539                         if(cb_keyup) {
1540                                 cb_keyup((unsigned char)key, mouse_x, mouse_y);
1541                         }
1542                 } else {
1543                         if(cb_skeyup) {
1544                                 cb_skeyup(key, mouse_x, mouse_y);
1545                         }
1546                 }
1547                 break;
1548
1549         case WM_LBUTTONDOWN:
1550                 handle_mbutton(0, 1, wparam, lparam);
1551                 break;
1552         case WM_MBUTTONDOWN:
1553                 handle_mbutton(1, 1, wparam, lparam);
1554                 break;
1555         case WM_RBUTTONDOWN:
1556                 handle_mbutton(2, 1, wparam, lparam);
1557                 break;
1558         case WM_LBUTTONUP:
1559                 handle_mbutton(0, 0, wparam, lparam);
1560                 break;
1561         case WM_MBUTTONUP:
1562                 handle_mbutton(1, 0, wparam, lparam);
1563                 break;
1564         case WM_RBUTTONUP:
1565                 handle_mbutton(2, 0, wparam, lparam);
1566                 break;
1567
1568         case WM_MOUSEMOVE:
1569                 if(wparam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) {
1570                         if(cb_motion) cb_motion(lparam & 0xffff, lparam >> 16);
1571                 } else {
1572                         if(cb_passive) cb_passive(lparam & 0xffff, lparam >> 16);
1573                 }
1574                 break;
1575
1576         case WM_SYSCOMMAND:
1577                 wparam &= 0xfff0;
1578                 if(wparam == SC_KEYMENU || wparam == SC_SCREENSAVE || wparam == SC_MONITORPOWER) {
1579                         return 0;
1580                 }
1581         default:
1582                 return DefWindowProc(win, msg, wparam, lparam);
1583         }
1584
1585         return 0;
1586 }
1587
1588 static void update_modkeys(void)
1589 {
1590         if(GetKeyState(VK_SHIFT) & 0x8000) {
1591                 modstate |= GLUT_ACTIVE_SHIFT;
1592         } else {
1593                 modstate &= ~GLUT_ACTIVE_SHIFT;
1594         }
1595         if(GetKeyState(VK_CONTROL) & 0x8000) {
1596                 modstate |= GLUT_ACTIVE_CTRL;
1597         } else {
1598                 modstate &= ~GLUT_ACTIVE_CTRL;
1599         }
1600         if(GetKeyState(VK_MENU) & 0x8000) {
1601                 modstate |= GLUT_ACTIVE_ALT;
1602         } else {
1603                 modstate &= ~GLUT_ACTIVE_ALT;
1604         }
1605 }
1606
1607 static int translate_vkey(int vkey)
1608 {
1609         switch(vkey) {
1610         case VK_PRIOR: return GLUT_KEY_PAGE_UP;
1611         case VK_NEXT: return GLUT_KEY_PAGE_DOWN;
1612         case VK_END: return GLUT_KEY_END;
1613         case VK_HOME: return GLUT_KEY_HOME;
1614         case VK_LEFT: return GLUT_KEY_LEFT;
1615         case VK_UP: return GLUT_KEY_UP;
1616         case VK_RIGHT: return GLUT_KEY_RIGHT;
1617         case VK_DOWN: return GLUT_KEY_DOWN;
1618         default:
1619                 break;
1620         }
1621
1622         if(vkey >= 'A' && vkey <= 'Z') {
1623                 vkey += 32;
1624         } else if(vkey >= VK_F1 && vkey <= VK_F12) {
1625                 vkey -= VK_F1 + GLUT_KEY_F1;
1626         }
1627
1628         return vkey;
1629 }
1630
1631 static void handle_mbutton(int bn, int st, WPARAM wparam, LPARAM lparam)
1632 {
1633         int x, y;
1634
1635         update_modkeys();
1636
1637         if(cb_mouse) {
1638                 x = lparam & 0xffff;
1639                 y = lparam >> 16;
1640                 cb_mouse(bn, st ? GLUT_DOWN : GLUT_UP, x, y);
1641         }
1642 }
1643
1644 static void get_window_pos(int *x, int *y)
1645 {
1646         RECT rect;
1647         GetWindowRect(win, &rect);
1648         *x = rect.left;
1649         *y = rect.top;
1650 }
1651
1652 static void get_window_size(int *w, int *h)
1653 {
1654         RECT rect;
1655         GetClientRect(win, &rect);
1656         *w = rect.right - rect.left;
1657         *h = rect.bottom - rect.top;
1658 }
1659
1660 static void get_screen_size(int *scrw, int *scrh)
1661 {
1662         *scrw = GetSystemMetrics(SM_CXSCREEN);
1663         *scrh = GetSystemMetrics(SM_CYSCREEN);
1664 }
1665 #endif  /* BUILD_WIN32 */
1666
1667 #if defined(__unix__) || defined(__APPLE__)
1668 #include <sys/time.h>
1669
1670 #ifdef MINIGLUT_USE_LIBC
1671 #define sys_gettimeofday(tv, tz)        gettimeofday(tv, tz)
1672 #else
1673 static int sys_gettimeofday(struct timeval *tv, struct timezone *tz);
1674 #endif
1675
1676 static long get_msec(void)
1677 {
1678         static struct timeval tv0;
1679         struct timeval tv;
1680
1681         sys_gettimeofday(&tv, 0);
1682         if(tv0.tv_sec == 0 && tv0.tv_usec == 0) {
1683                 tv0 = tv;
1684                 return 0;
1685         }
1686         return (tv.tv_sec - tv0.tv_sec) * 1000 + (tv.tv_usec - tv0.tv_usec) / 1000;
1687 }
1688 #endif  /* UNIX */
1689 #ifdef _WIN32
1690 static long get_msec(void)
1691 {
1692         static long t0;
1693         long tm;
1694
1695 #ifdef MINIGLUT_NO_WINMM
1696         tm = GetTickCount();
1697 #else
1698         tm = timeGetTime();
1699 #endif
1700         if(!t0) {
1701                 t0 = tm;
1702                 return 0;
1703         }
1704         return tm - t0;
1705 }
1706 #endif
1707
1708 static void panic(const char *msg)
1709 {
1710         const char *end = msg;
1711         while(*end) end++;
1712         sys_write(2, msg, end - msg);
1713         sys_exit(1);
1714 }
1715
1716
1717 #ifdef MINIGLUT_USE_LIBC
1718 static void sys_exit(int status)
1719 {
1720         exit(status);
1721 }
1722
1723 static int sys_write(int fd, const void *buf, int count)
1724 {
1725         return write(fd, buf, count);
1726 }
1727
1728 #else   /* !MINIGLUT_USE_LIBC */
1729
1730 #ifdef __linux__
1731 #ifdef __x86_64__
1732 static void sys_exit(int status)
1733 {
1734         asm volatile(
1735                 "syscall\n\t"
1736                 :: "a"(60), "D"(status));
1737 }
1738 static int sys_write(int fd, const void *buf, int count)
1739 {
1740         long res;
1741         asm volatile(
1742                 "syscall\n\t"
1743                 : "=a"(res)
1744                 : "a"(1), "D"(fd), "S"(buf), "d"(count));
1745         return res;
1746 }
1747 static int sys_gettimeofday(struct timeval *tv, struct timezone *tz)
1748 {
1749         int res;
1750         asm volatile(
1751                 "syscall\n\t"
1752                 : "=a"(res)
1753                 : "a"(96), "D"(tv), "S"(tz));
1754         return res;
1755 }
1756 #endif  /* __x86_64__ */
1757 #ifdef __i386__
1758 static void sys_exit(int status)
1759 {
1760         asm volatile(
1761                 "int $0x80\n\t"
1762                 :: "a"(1), "b"(status));
1763 }
1764 static int sys_write(int fd, const void *buf, int count)
1765 {
1766         int res;
1767         asm volatile(
1768                 "int $0x80\n\t"
1769                 : "=a"(res)
1770                 : "a"(4), "b"(fd), "c"(buf), "d"(count));
1771         return res;
1772 }
1773 static int sys_gettimeofday(struct timeval *tv, struct timezone *tz)
1774 {
1775         int res;
1776         asm volatile(
1777                 "int $0x80\n\t"
1778                 : "=a"(res)
1779                 : "a"(78), "b"(tv), "c"(tz));
1780         return res;
1781 }
1782 #endif  /* __i386__ */
1783 #endif  /* __linux__ */
1784
1785 #ifdef _WIN32
1786 static void sys_exit(int status)
1787 {
1788         ExitProcess(status);
1789 }
1790 static int sys_write(int fd, const void *buf, int count)
1791 {
1792         unsigned long wrsz = 0;
1793
1794         HANDLE out = GetStdHandle(fd == 1 ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE);
1795         if(!WriteFile(out, buf, count, &wrsz, 0)) {
1796                 return -1;
1797         }
1798         return wrsz;
1799 }
1800 #endif  /* _WIN32 */
1801 #endif  /* !MINIGLUT_USE_LIBC */
1802
1803
1804 /* ----------------- primitives ------------------ */
1805 #ifdef MINIGLUT_USE_LIBC
1806 #include <stdlib.h>
1807 #include <math.h>
1808
1809 void mglut_sincos(float angle, float *sptr, float *cptr)
1810 {
1811         *sptr = sin(angle);
1812         *cptr = cos(angle);
1813 }
1814
1815 float mglut_atan(float x)
1816 {
1817         return atan(x);
1818 }
1819
1820 #else   /* !MINIGLUT_USE_LIBC */
1821
1822 #ifdef __GNUC__
1823 void mglut_sincos(float angle, float *sptr, float *cptr)
1824 {
1825         asm volatile(
1826                 "flds %2\n\t"
1827                 "fsincos\n\t"
1828                 "fstps %1\n\t"
1829                 "fstps %0\n\t"
1830                 : "=m"(*sptr), "=m"(*cptr)
1831                 : "m"(angle)
1832         );
1833 }
1834
1835 float mglut_atan(float x)
1836 {
1837         float res;
1838         asm volatile(
1839                 "flds %1\n\t"
1840                 "fld1\n\t"
1841                 "fpatan\n\t"
1842                 "fstps %0\n\t"
1843                 : "=m"(res)
1844                 : "m"(x)
1845         );
1846         return res;
1847 }
1848 #endif
1849
1850 #ifdef _MSC_VER
1851 void mglut_sincos(float angle, float *sptr, float *cptr)
1852 {
1853         float s, c;
1854         __asm {
1855                 fld angle
1856                 fsincos
1857                 fstp c
1858                 fstp s
1859         }
1860         *sptr = s;
1861         *cptr = c;
1862 }
1863
1864 float mglut_atan(float x)
1865 {
1866         float res;
1867         __asm {
1868                 fld x
1869                 fld1
1870                 fpatan
1871                 fstp res
1872         }
1873         return res;
1874 }
1875 #endif
1876
1877 #ifdef __WATCOMC__
1878 #pragma aux mglut_sincos = \
1879         "fsincos" \
1880         "fstp dword ptr [edx]" \
1881         "fstp dword ptr [eax]" \
1882         parm[8087][eax][edx]    \
1883         modify[8087];
1884
1885 #pragma aux mglut_atan = \
1886         "fld1" \
1887         "fpatan" \
1888         parm[8087] \
1889         value[8087] \
1890         modify [8087];
1891 #endif  /* __WATCOMC__ */
1892
1893 #endif  /* !MINIGLUT_USE_LIBC */
1894
1895 #define PI      3.1415926536f
1896
1897 void glutSolidSphere(float rad, int slices, int stacks)
1898 {
1899         int i, j, k, gray;
1900         float x, y, z, s, t, u, v, phi, theta, sintheta, costheta, sinphi, cosphi;
1901         float du = 1.0f / (float)slices;
1902         float dv = 1.0f / (float)stacks;
1903
1904         glBegin(GL_QUADS);
1905         for(i=0; i<stacks; i++) {
1906                 v = i * dv;
1907                 for(j=0; j<slices; j++) {
1908                         u = j * du;
1909                         for(k=0; k<4; k++) {
1910                                 gray = k ^ (k >> 1);
1911                                 s = gray & 1 ? u + du : u;
1912                                 t = gray & 2 ? v + dv : v;
1913                                 theta = s * PI * 2.0f;
1914                                 phi = t * PI;
1915                                 mglut_sincos(theta, &sintheta, &costheta);
1916                                 mglut_sincos(phi, &sinphi, &cosphi);
1917                                 x = sintheta * sinphi;
1918                                 y = costheta * sinphi;
1919                                 z = cosphi;
1920
1921                                 glColor3f(s, t, 1);
1922                                 glTexCoord2f(s, t);
1923                                 glNormal3f(x, y, z);
1924                                 glVertex3f(x * rad, y * rad, z * rad);
1925                         }
1926                 }
1927         }
1928         glEnd();
1929 }
1930
1931 void glutWireSphere(float rad, int slices, int stacks)
1932 {
1933         glPushAttrib(GL_POLYGON_BIT);
1934         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
1935         glutSolidSphere(rad, slices, stacks);
1936         glPopAttrib();
1937 }
1938
1939 void glutSolidCube(float sz)
1940 {
1941         int i, j, idx, gray, flip, rotx;
1942         float vpos[3], norm[3];
1943         float rad = sz * 0.5f;
1944
1945         glBegin(GL_QUADS);
1946         for(i=0; i<6; i++) {
1947                 flip = i & 1;
1948                 rotx = i >> 2;
1949                 idx = (~i & 2) - rotx;
1950                 norm[0] = norm[1] = norm[2] = 0.0f;
1951                 norm[idx] = flip ^ ((i >> 1) & 1) ? -1 : 1;
1952                 glNormal3fv(norm);
1953                 vpos[idx] = norm[idx] * rad;
1954                 for(j=0; j<4; j++) {
1955                         gray = j ^ (j >> 1);
1956                         vpos[i & 2] = (gray ^ flip) & 1 ? rad : -rad;
1957                         vpos[rotx + 1] = (gray ^ (rotx << 1)) & 2 ? rad : -rad;
1958                         glTexCoord2f(gray & 1, gray >> 1);
1959                         glVertex3fv(vpos);
1960                 }
1961         }
1962         glEnd();
1963 }
1964
1965 void glutWireCube(float sz)
1966 {
1967         glPushAttrib(GL_POLYGON_BIT);
1968         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
1969         glutSolidCube(sz);
1970         glPopAttrib();
1971 }
1972
1973 static void draw_cylinder(float rbot, float rtop, float height, int slices, int stacks)
1974 {
1975         int i, j, k, gray;
1976         float x, y, z, s, t, u, v, theta, phi, sintheta, costheta, sinphi, cosphi, rad;
1977         float du = 1.0f / (float)slices;
1978         float dv = 1.0f / (float)stacks;
1979
1980         rad = rbot - rtop;
1981         phi = mglut_atan((rad < 0 ? -rad : rad) / height);
1982         mglut_sincos(phi, &sinphi, &cosphi);
1983
1984         glBegin(GL_QUADS);
1985         for(i=0; i<stacks; i++) {
1986                 v = i * dv;
1987                 for(j=0; j<slices; j++) {
1988                         u = j * du;
1989                         for(k=0; k<4; k++) {
1990                                 gray = k ^ (k >> 1);
1991                                 s = gray & 2 ? u + du : u;
1992                                 t = gray & 1 ? v + dv : v;
1993                                 rad = rbot + (rtop - rbot) * t;
1994                                 theta = s * PI * 2.0f;
1995                                 mglut_sincos(theta, &sintheta, &costheta);
1996
1997                                 x = sintheta * cosphi;
1998                                 y = costheta * cosphi;
1999                                 z = sinphi;
2000
2001                                 glColor3f(s, t, 1);
2002                                 glTexCoord2f(s, t);
2003                                 glNormal3f(x, y, z);
2004                                 glVertex3f(sintheta * rad, costheta * rad, t * height);
2005                         }
2006                 }
2007         }
2008         glEnd();
2009 }
2010
2011 void glutSolidCone(float base, float height, int slices, int stacks)
2012 {
2013         draw_cylinder(base, 0, height, slices, stacks);
2014 }
2015
2016 void glutWireCone(float base, float height, int slices, int stacks)
2017 {
2018         glPushAttrib(GL_POLYGON_BIT);
2019         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
2020         glutSolidCone(base, height, slices, stacks);
2021         glPopAttrib();
2022 }
2023
2024 void glutSolidCylinder(float rad, float height, int slices, int stacks)
2025 {
2026         draw_cylinder(rad, rad, height, slices, stacks);
2027 }
2028
2029 void glutWireCylinder(float rad, float height, int slices, int stacks)
2030 {
2031         glPushAttrib(GL_POLYGON_BIT);
2032         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
2033         glutSolidCylinder(rad, height, slices, stacks);
2034         glPopAttrib();
2035 }
2036
2037 void glutSolidTorus(float inner_rad, float outer_rad, int sides, int rings)
2038 {
2039         int i, j, k, gray;
2040         float x, y, z, s, t, u, v, phi, theta, sintheta, costheta, sinphi, cosphi;
2041         float du = 1.0f / (float)rings;
2042         float dv = 1.0f / (float)sides;
2043
2044         glBegin(GL_QUADS);
2045         for(i=0; i<rings; i++) {
2046                 u = i * du;
2047                 for(j=0; j<sides; j++) {
2048                         v = j * dv;
2049                         for(k=0; k<4; k++) {
2050                                 gray = k ^ (k >> 1);
2051                                 s = gray & 1 ? u + du : u;
2052                                 t = gray & 2 ? v + dv : v;
2053                                 theta = s * PI * 2.0f;
2054                                 phi = t * PI * 2.0f;
2055                                 mglut_sincos(theta, &sintheta, &costheta);
2056                                 mglut_sincos(phi, &sinphi, &cosphi);
2057                                 x = sintheta * sinphi;
2058                                 y = costheta * sinphi;
2059                                 z = cosphi;
2060
2061                                 glColor3f(s, t, 1);
2062                                 glTexCoord2f(s, t);
2063                                 glNormal3f(x, y, z);
2064
2065                                 x = x * inner_rad + sintheta * outer_rad;
2066                                 y = y * inner_rad + costheta * outer_rad;
2067                                 z *= inner_rad;
2068                                 glVertex3f(x, y, z);
2069                         }
2070                 }
2071         }
2072         glEnd();
2073 }
2074
2075 void glutWireTorus(float inner_rad, float outer_rad, int sides, int rings)
2076 {
2077         glPushAttrib(GL_POLYGON_BIT);
2078         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
2079         glutSolidTorus(inner_rad, outer_rad, sides, rings);
2080         glPopAttrib();
2081 }
2082
2083 void glutSolidTeapot(float size)
2084 {
2085 }
2086
2087 void glutWireTeapot(float size)
2088 {
2089 }