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