- fixed the detection of EHWM _NET_STATE_FULLSCREEN support
[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 unsigned int evmask;
42
43 static int have_netwm_fullscr(void);
44
45 #elif defined(_WIN32)
46
47 #include <windows.h>
48 #define BUILD_WIN32
49
50 static HRESULT CALLBACK handle_message(HWND win, unsigned int msg, WPARAM wparam, LPARAM lparam);
51
52 static HINSTANCE hinst;
53 static HWND win;
54 static HDC dc;
55 static HGLRC ctx;
56
57 #else
58 #error unsupported platform
59 #endif
60 #include <GL/gl.h>
61 #include "miniglut.h"
62
63 struct ctx_info {
64         int rsize, gsize, bsize, asize;
65         int zsize, ssize;
66         int dblbuf;
67         int samples;
68         int stereo;
69         int srgb;
70 };
71
72 static void create_window(const char *title);
73 static void get_window_pos(int *x, int *y);
74 static void get_window_size(int *w, int *h);
75 static void get_screen_size(int *scrw, int *scrh);
76
77 static long get_msec(void);
78 static void panic(const char *msg);
79 static void sys_exit(int status);
80 static int sys_write(int fd, const void *buf, int count);
81
82
83 static int init_x = -1, init_y, init_width = 256, init_height = 256;
84 static unsigned int init_mode;
85
86 static struct ctx_info ctx_info;
87 static int cur_cursor = GLUT_CURSOR_INHERIT;
88
89 static glut_cb cb_display;
90 static glut_cb cb_idle;
91 static glut_cb_reshape cb_reshape;
92 static glut_cb_state cb_vis, cb_entry;
93 static glut_cb_keyb cb_keydown, cb_keyup;
94 static glut_cb_special cb_skeydown, cb_skeyup;
95 static glut_cb_mouse cb_mouse;
96 static glut_cb_motion cb_motion, cb_passive;
97 static glut_cb_sbmotion cb_sball_motion, cb_sball_rotate;
98 static glut_cb_sbbutton cb_sball_button;
99
100 static int fullscreen;
101 static int prev_win_x, prev_win_y, prev_win_width, prev_win_height;
102
103 static int win_width, win_height;
104 static int mapped;
105 static int quit;
106 static int upd_pending;
107 static int modstate;
108
109
110 void glutInit(int *argc, char **argv)
111 {
112 #ifdef BUILD_X11
113         if(!(dpy = XOpenDisplay(0))) {
114                 panic("Failed to connect to the X server\n");
115         }
116         scr = DefaultScreen(dpy);
117         root = RootWindow(dpy, scr);
118         xa_wm_proto = XInternAtom(dpy, "WM_PROTOCOLS", False);
119         xa_wm_del_win = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
120         xa_motif_wm_hints = XInternAtom(dpy, "_MOTIF_WM_HINTS", False);
121         if(have_netwm_fullscr()) {
122                 xa_net_wm_state = XInternAtom(dpy, "_NET_WM_STATE", False);
123                 xa_net_wm_state_fullscr = XInternAtom(dpy, "_NET_WM_STATE_FULLSCREEN", False);
124         }
125
126         evmask = ExposureMask | StructureNotifyMask;
127
128 #endif
129 #ifdef BUILD_WIN32
130         WNDCLASSEX wc = {0};
131
132         hinst = GetModuleHandle(0);
133
134         wc.cbSize = sizeof wc;
135         wc.hbrBackground = GetStockObject(BLACK_BRUSH);
136         wc.hCursor = LoadCursor(0, IDC_ARROW);
137         wc.hIcon = wc.hIconSm = LoadIcon(0, IDI_APPLICATION);
138         wc.hInstance = hinst;
139         wc.lpfnWndProc = handle_message;
140         wc.lpszClassName = "MiniGLUT";
141         wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
142         if(!RegisterClassEx(&wc)) {
143                 panic("Failed to register \"MiniGLUT\" window class\n");
144         }
145
146         if(init_x == -1) {
147                 get_screen_size(&init_x, &init_y);
148                 init_x >>= 3;
149                 init_y >>= 3;
150         }
151 #endif
152 }
153
154 void glutInitWindowPosition(int x, int y)
155 {
156         init_x = x;
157         init_y = y;
158 }
159
160 void glutInitWindowSize(int xsz, int ysz)
161 {
162         init_width = xsz;
163         init_height = ysz;
164 }
165
166 void glutInitDisplayMode(unsigned int mode)
167 {
168         init_mode = mode;
169 }
170
171 void glutCreateWindow(const char *title)
172 {
173         create_window(title);
174 }
175
176 void glutExit(void)
177 {
178         quit = 1;
179 }
180
181 void glutMainLoop(void)
182 {
183         while(!quit) {
184                 glutMainLoopEvent();
185         }
186 }
187
188 void glutPostRedisplay(void)
189 {
190         upd_pending = 1;
191 }
192
193 #define UPD_EVMASK(x) \
194         do { \
195                 if(func) { \
196                         evmask |= x; \
197                 } else { \
198                         evmask &= ~(x); \
199                 } \
200                 if(win) XSelectInput(dpy, win, evmask); \
201         } while(0)
202
203
204 void glutIdleFunc(glut_cb func)
205 {
206         cb_idle = func;
207 }
208
209 void glutDisplayFunc(glut_cb func)
210 {
211         cb_display = func;
212 }
213
214 void glutReshapeFunc(glut_cb_reshape func)
215 {
216         cb_reshape = func;
217 }
218
219 void glutVisibilityFunc(glut_cb_state func)
220 {
221         cb_vis = func;
222 #ifdef BUILD_X11
223         UPD_EVMASK(VisibilityChangeMask);
224 #endif
225 }
226
227 void glutEntryFunc(glut_cb_state func)
228 {
229         cb_entry = func;
230 #ifdef BUILD_X11
231         UPD_EVMASK(EnterWindowMask | LeaveWindowMask);
232 #endif
233 }
234
235 void glutKeyboardFunc(glut_cb_keyb func)
236 {
237         cb_keydown = func;
238 #ifdef BUILD_X11
239         UPD_EVMASK(KeyPressMask);
240 #endif
241 }
242
243 void glutKeyboardUpFunc(glut_cb_keyb func)
244 {
245         cb_keyup = func;
246 #ifdef BUILD_X11
247         UPD_EVMASK(KeyReleaseMask);
248 #endif
249 }
250
251 void glutSpecialFunc(glut_cb_special func)
252 {
253         cb_skeydown = func;
254 #ifdef BUILD_X11
255         UPD_EVMASK(KeyPressMask);
256 #endif
257 }
258
259 void glutSpecialUpFunc(glut_cb_special func)
260 {
261         cb_skeyup = func;
262 #ifdef BUILD_X11
263         UPD_EVMASK(KeyReleaseMask);
264 #endif
265 }
266
267 void glutMouseFunc(glut_cb_mouse func)
268 {
269         cb_mouse = func;
270 #ifdef BUILD_X11
271         UPD_EVMASK(ButtonPressMask | ButtonReleaseMask);
272 #endif
273 }
274
275 void glutMotionFunc(glut_cb_motion func)
276 {
277         cb_motion = func;
278 #ifdef BUILD_X11
279         UPD_EVMASK(ButtonMotionMask);
280 #endif
281 }
282
283 void glutPassiveMotionFunc(glut_cb_motion func)
284 {
285         cb_passive = func;
286 #ifdef BUILD_X11
287         UPD_EVMASK(PointerMotionMask);
288 #endif
289 }
290
291 void glutSpaceballMotionFunc(glut_cb_sbmotion func)
292 {
293         cb_sball_motion = func;
294 }
295
296 void glutSpaceballRotateFunc(glut_cb_sbmotion func)
297 {
298         cb_sball_rotate = func;
299 }
300
301 void glutSpaceballBittonFunc(glut_cb_sbbutton func)
302 {
303         cb_sball_button = func;
304 }
305
306 int glutGet(unsigned int s)
307 {
308         int x, y;
309         switch(s) {
310         case GLUT_WINDOW_X:
311                 get_window_pos(&x, &y);
312                 return x;
313         case GLUT_WINDOW_Y:
314                 get_window_pos(&x, &y);
315                 return y;
316         case GLUT_WINDOW_WIDTH:
317                 get_window_size(&x, &y);
318                 return x;
319         case GLUT_WINDOW_HEIGHT:
320                 get_window_size(&x, &y);
321                 return y;
322         case GLUT_WINDOW_BUFFER_SIZE:
323                 return ctx_info.rsize + ctx_info.gsize + ctx_info.bsize + ctx_info.asize;
324         case GLUT_WINDOW_STENCIL_SIZE:
325                 return ctx_info.ssize;
326         case GLUT_WINDOW_DEPTH_SIZE:
327                 return ctx_info.zsize;
328         case GLUT_WINDOW_RED_SIZE:
329                 return ctx_info.rsize;
330         case GLUT_WINDOW_GREEN_SIZE:
331                 return ctx_info.gsize;
332         case GLUT_WINDOW_BLUE_SIZE:
333                 return ctx_info.bsize;
334         case GLUT_WINDOW_ALPHA_SIZE:
335                 return ctx_info.asize;
336         case GLUT_WINDOW_DOUBLEBUFFER:
337                 return ctx_info.dblbuf;
338         case GLUT_WINDOW_RGBA:
339                 return 1;
340         case GLUT_WINDOW_NUM_SAMPLES:
341                 return ctx_info.samples;
342         case GLUT_WINDOW_STEREO:
343                 return ctx_info.stereo;
344         case GLUT_WINDOW_SRGB:
345                 return ctx_info.srgb;
346         case GLUT_WINDOW_CURSOR:
347                 return cur_cursor;
348         case GLUT_SCREEN_WIDTH:
349                 get_screen_size(&x, &y);
350                 return x;
351         case GLUT_SCREEN_HEIGHT:
352                 get_screen_size(&x, &y);
353                 return y;
354         case GLUT_INIT_DISPLAY_MODE:
355                 return init_mode;
356         case GLUT_INIT_WINDOW_X:
357                 return init_x;
358         case GLUT_INIT_WINDOW_Y:
359                 return init_y;
360         case GLUT_INIT_WINDOW_WIDTH:
361                 return init_width;
362         case GLUT_INIT_WINDOW_HEIGHT:
363                 return init_height;
364         case GLUT_ELAPSED_TIME:
365                 return get_msec();
366         default:
367                 break;
368         }
369         return 0;
370 }
371
372 int glutGetModifiers(void)
373 {
374         return modstate;
375 }
376
377 static int is_space(int c)
378 {
379         return c == ' ' || c == '\t' || c == '\v' || c == '\n' || c == '\r';
380 }
381
382 static const char *skip_space(const char *s)
383 {
384         while(*s && is_space(*s)) s++;
385         return s;
386 }
387
388 int glutExtensionSupported(char *ext)
389 {
390         const char *str, *eptr;
391
392         if(!(str = (const char*)glGetString(GL_EXTENSIONS))) {
393                 return 0;
394         }
395
396         while(*str) {
397                 str = skip_space(str);
398                 eptr = skip_space(ext);
399                 while(*str && !is_space(*str) && *eptr && *str == *eptr) {
400                         str++;
401                         eptr++;
402                 }
403                 if((!*str || is_space(*str)) && !*eptr) {
404                         return 1;
405                 }
406                 while(*str && !is_space(*str)) str++;
407         }
408
409         return 0;
410 }
411
412
413 /* --------------- UNIX/X11 implementation ----------------- */
414 #ifdef BUILD_X11
415 static void handle_event(XEvent *ev);
416
417 void glutMainLoopEvent(void)
418 {
419         XEvent ev;
420
421         if(!cb_display) {
422                 panic("display callback not set");
423         }
424
425         if(!upd_pending && !cb_idle) {
426                 XNextEvent(dpy, &ev);
427                 handle_event(&ev);
428                 if(quit) return;
429         }
430         while(XPending(dpy)) {
431                 XNextEvent(dpy, &ev);
432                 handle_event(&ev);
433                 if(quit) return;
434         }
435
436         if(cb_idle) {
437                 cb_idle();
438         }
439
440         if(upd_pending && mapped) {
441                 upd_pending = 0;
442                 cb_display();
443         }
444 }
445
446 static KeySym translate_keysym(KeySym sym)
447 {
448         switch(sym) {
449         case XK_Escape:
450                 return 27;
451         case XK_BackSpace:
452                 return '\b';
453         case XK_Linefeed:
454                 return '\r';
455         case XK_Return:
456                 return '\n';
457         case XK_Delete:
458                 return 127;
459         case XK_Tab:
460                 return '\t';
461         default:
462                 break;
463         }
464         return sym;
465 }
466
467 static void handle_event(XEvent *ev)
468 {
469         KeySym sym;
470
471         switch(ev->type) {
472         case MapNotify:
473                 mapped = 1;
474                 break;
475         case UnmapNotify:
476                 mapped = 0;
477                 break;
478         case ConfigureNotify:
479                 if(cb_reshape && (ev->xconfigure.width != win_width || ev->xconfigure.height != win_height)) {
480                         win_width = ev->xconfigure.width;
481                         win_height = ev->xconfigure.height;
482                         cb_reshape(ev->xconfigure.width, ev->xconfigure.height);
483                 }
484                 break;
485
486         case ClientMessage:
487                 if(ev->xclient.message_type == xa_wm_proto) {
488                         if(ev->xclient.data.l[0] == xa_wm_del_win) {
489                                 quit = 1;
490                         }
491                 }
492                 break;
493
494         case Expose:
495                 upd_pending = 1;
496                 break;
497
498         case KeyPress:
499         case KeyRelease:
500                 modstate = ev->xkey.state & (ShiftMask | ControlMask | Mod1Mask);
501                 if(!(sym = XLookupKeysym(&ev->xkey, 0))) {
502                         break;
503                 }
504                 sym = translate_keysym(sym);
505                 if(sym < 256) {
506                         if(ev->type == KeyPress) {
507                                 if(cb_keydown) cb_keydown((unsigned char)sym, ev->xkey.x, ev->xkey.y);
508                         } else {
509                                 if(cb_keyup) cb_keyup((unsigned char)sym, ev->xkey.x, ev->xkey.y);
510                         }
511                 } else {
512                         if(ev->type == KeyPress) {
513                                 if(cb_skeydown) cb_skeydown(sym, ev->xkey.x, ev->xkey.y);
514                         } else {
515                                 if(cb_skeyup) cb_skeyup(sym, ev->xkey.x, ev->xkey.y);
516                         }
517                 }
518                 break;
519
520         case ButtonPress:
521         case ButtonRelease:
522                 modstate = ev->xbutton.state & (ShiftMask | ControlMask | Mod1Mask);
523                 if(cb_mouse) {
524                         int bn = ev->xbutton.button - Button1;
525                         cb_mouse(bn, ev->type == ButtonPress ? GLUT_DOWN : GLUT_UP,
526                                         ev->xbutton.x, ev->xbutton.y);
527                 }
528                 break;
529
530         case MotionNotify:
531                 if(ev->xmotion.state & (Button1Mask | Button2Mask | Button3Mask | Button4Mask | Button5Mask)) {
532                         if(cb_motion) cb_motion(ev->xmotion.x, ev->xmotion.y);
533                 } else {
534                         if(cb_passive) cb_passive(ev->xmotion.x, ev->xmotion.y);
535                 }
536                 break;
537
538         case VisibilityNotify:
539                 if(cb_vis) {
540                         cb_vis(ev->xvisibility.state == VisibilityFullyObscured ? GLUT_NOT_VISIBLE : GLUT_VISIBLE);
541                 }
542                 break;
543         case EnterNotify:
544                 if(cb_entry) cb_entry(GLUT_ENTERED);
545                 break;
546         case LeaveNotify:
547                 if(cb_entry) cb_entry(GLUT_LEFT);
548                 break;
549         }
550 }
551
552 void glutSwapBuffers(void)
553 {
554         glXSwapBuffers(dpy, win);
555 }
556
557 struct mwm_hints {
558         unsigned long flags;
559         unsigned long functions;
560         unsigned long decorations;
561         long input_mode;
562         unsigned long status;
563 };
564
565 #define MWM_HINTS_DECORATIONS   2
566 #define MWM_DECOR_ALL                   1
567
568 static void set_fullscreen_mwm(int fs)
569 {
570         struct mwm_hints hints;
571
572         if(fs) {
573                 hints.decorations = 0;
574                 hints.flags = MWM_HINTS_DECORATIONS;
575                 XChangeProperty(dpy, win, xa_motif_wm_hints, xa_motif_wm_hints, 32,
576                                 PropModeReplace, (unsigned char*)&hints, 5);
577         } else {
578                 XDeleteProperty(dpy, win, xa_motif_wm_hints);
579         }
580 }
581
582 static int have_netwm_fullscr(void)
583 {
584         int fmt;
585         long offs = 0;
586         unsigned long i, count, rem;
587         Atom prop[8], type;
588         Atom xa_net_supported = XInternAtom(dpy, "_NET_SUPPORTED", False);
589
590         do {
591                 XGetWindowProperty(dpy, root, xa_net_supported, offs, 8, False, AnyPropertyType,
592                                 &type, &fmt, &count, &rem, (unsigned char**)prop);
593
594                 for(i=0; i<count; i++) {
595                         if(prop[i] == xa_net_wm_state_fullscr) {
596                                 return 1;
597                         }
598                 }
599                 offs += count;
600         } while(rem > 0);
601
602         return 0;
603 }
604
605 static void set_fullscreen_ewmh(int fs)
606 {
607         XClientMessageEvent msg = {0};
608
609         msg.type = ClientMessage;
610         msg.window = win;
611         msg.message_type = xa_net_wm_state;     /* _NET_WM_STATE */
612         msg.format = 32;
613         msg.data.l[0] = fs ? 1 : 0;
614         msg.data.l[1] = xa_net_wm_state_fullscr;        /* _NET_WM_STATE_FULLSCREEN */
615         msg.data.l[2] = 0;
616         msg.data.l[3] = 1;      /* source regular application */
617         XSendEvent(dpy, root, False, SubstructureNotifyMask | SubstructureRedirectMask, (XEvent*)&msg);
618 }
619
620 static void set_fullscreen(int fs)
621 {
622         if(fullscreen == fs) return;
623
624         if(xa_net_wm_state && xa_net_wm_state_fullscr) {
625                 set_fullscreen_ewmh(fs);
626                 fullscreen = fs;
627         } else if(xa_motif_wm_hints) {
628                 set_fullscreen_mwm(fs);
629                 fullscreen = fs;
630         }
631 }
632
633 void glutPositionWindow(int x, int y)
634 {
635         set_fullscreen(0);
636         XMoveWindow(dpy, win, x, y);
637 }
638
639 void glutReshapeWindow(int xsz, int ysz)
640 {
641         set_fullscreen(0);
642         XResizeWindow(dpy, win, xsz, ysz);
643 }
644
645 void glutFullScreen(void)
646 {
647         set_fullscreen(1);
648 }
649
650 void glutSetWindowTitle(const char *title)
651 {
652         XTextProperty tprop;
653         if(!XStringListToTextProperty((char**)&title, 1, &tprop)) {
654                 return;
655         }
656         XSetWMName(dpy, win, &tprop);
657         XFree(tprop.value);
658 }
659
660 void glutSetIconTitle(const char *title)
661 {
662         XTextProperty tprop;
663         if(!XStringListToTextProperty((char**)&title, 1, &tprop)) {
664                 return;
665         }
666         XSetWMIconName(dpy, win, &tprop);
667         XFree(tprop.value);
668 }
669
670 void glutSetCursor(int cidx)
671 {
672         Cursor cur = None;
673
674         switch(cidx) {
675         case GLUT_CURSOR_LEFT_ARROW:
676                 cur = XCreateFontCursor(dpy, XC_left_ptr);
677                 break;
678         case GLUT_CURSOR_INHERIT:
679                 break;
680         case GLUT_CURSOR_NONE:
681                 /* TODO */
682         default:
683                 return;
684         }
685
686         XDefineCursor(dpy, win, cur);
687         cur_cursor = cidx;
688 }
689
690 static XVisualInfo *choose_visual(unsigned int mode)
691 {
692         XVisualInfo *vi;
693         int attr[32];
694         int *aptr = attr;
695         int *samples = 0;
696
697         if(mode & GLUT_DOUBLE) {
698                 *aptr++ = GLX_DOUBLEBUFFER;
699         }
700
701         if(mode & GLUT_INDEX) {
702                 *aptr++ = GLX_BUFFER_SIZE;
703                 *aptr++ = 1;
704         } else {
705                 *aptr++ = GLX_RGBA;
706                 *aptr++ = GLX_RED_SIZE; *aptr++ = 4;
707                 *aptr++ = GLX_GREEN_SIZE; *aptr++ = 4;
708                 *aptr++ = GLX_BLUE_SIZE; *aptr++ = 4;
709         }
710         if(mode & GLUT_ALPHA) {
711                 *aptr++ = GLX_ALPHA_SIZE;
712                 *aptr++ = 4;
713         }
714         if(mode & GLUT_DEPTH) {
715                 *aptr++ = GLX_DEPTH_SIZE;
716                 *aptr++ = 16;
717         }
718         if(mode & GLUT_STENCIL) {
719                 *aptr++ = GLX_STENCIL_SIZE;
720                 *aptr++ = 1;
721         }
722         if(mode & GLUT_ACCUM) {
723                 *aptr++ = GLX_ACCUM_RED_SIZE; *aptr++ = 1;
724                 *aptr++ = GLX_ACCUM_GREEN_SIZE; *aptr++ = 1;
725                 *aptr++ = GLX_ACCUM_BLUE_SIZE; *aptr++ = 1;
726         }
727         if(mode & GLUT_STEREO) {
728                 *aptr++ = GLX_STEREO;
729         }
730         if(mode & GLUT_SRGB) {
731                 *aptr++ = GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB;
732         }
733         if(mode & GLUT_MULTISAMPLE) {
734                 *aptr++ = GLX_SAMPLE_BUFFERS_ARB;
735                 *aptr++ = 1;
736                 *aptr++ = GLX_SAMPLES_ARB;
737                 samples = aptr;
738                 *aptr++ = 32;
739         }
740         *aptr++ = None;
741
742         if(!samples) {
743                 return glXChooseVisual(dpy, scr, attr);
744         }
745         while(!(vi = glXChooseVisual(dpy, scr, attr)) && *samples) {
746                 *samples >>= 1;
747                 if(!*samples) {
748                         aptr[-3] = None;
749                 }
750         }
751         return vi;
752 }
753
754 static void create_window(const char *title)
755 {
756         XSetWindowAttributes xattr = {0};
757         XVisualInfo *vi;
758         unsigned int xattr_mask;
759         unsigned int mode = init_mode;
760
761         if(!(vi = choose_visual(mode))) {
762                 mode &= ~GLUT_SRGB;
763                 if(!(vi = choose_visual(mode))) {
764                         panic("Failed to find compatible visual\n");
765                 }
766         }
767
768         if(!(ctx = glXCreateContext(dpy, vi, 0, True))) {
769                 XFree(vi);
770                 panic("Failed to create OpenGL context\n");
771         }
772
773         glXGetConfig(dpy, vi, GLX_RED_SIZE, &ctx_info.rsize);
774         glXGetConfig(dpy, vi, GLX_GREEN_SIZE, &ctx_info.gsize);
775         glXGetConfig(dpy, vi, GLX_BLUE_SIZE, &ctx_info.bsize);
776         glXGetConfig(dpy, vi, GLX_ALPHA_SIZE, &ctx_info.asize);
777         glXGetConfig(dpy, vi, GLX_DEPTH_SIZE, &ctx_info.zsize);
778         glXGetConfig(dpy, vi, GLX_STENCIL_SIZE, &ctx_info.ssize);
779         glXGetConfig(dpy, vi, GLX_DOUBLEBUFFER, &ctx_info.dblbuf);
780         glXGetConfig(dpy, vi, GLX_STEREO, &ctx_info.stereo);
781         glXGetConfig(dpy, vi, GLX_SAMPLES_ARB, &ctx_info.samples);
782         glXGetConfig(dpy, vi, GLX_FRAMEBUFFER_SRGB_CAPABLE_ARB, &ctx_info.srgb);
783
784         xattr.background_pixel = BlackPixel(dpy, scr);
785         xattr.colormap = XCreateColormap(dpy, root, vi->visual, AllocNone);
786         xattr_mask = CWBackPixel | CWColormap | CWBackPixmap | CWBorderPixel;
787         if(!(win = XCreateWindow(dpy, root, init_x, init_y, init_width, init_height, 0,
788                         vi->depth, InputOutput, vi->visual, xattr_mask, &xattr))) {
789                 XFree(vi);
790                 glXDestroyContext(dpy, ctx);
791                 panic("Failed to create window\n");
792         }
793         XFree(vi);
794
795         XSelectInput(dpy, win, evmask);
796
797         glutSetWindowTitle(title);
798         glutSetIconTitle(title);
799         XSetWMProtocols(dpy, win, &xa_wm_del_win, 1);
800         XMapWindow(dpy, win);
801
802         glXMakeCurrent(dpy, win, ctx);
803 }
804
805 static void get_window_pos(int *x, int *y)
806 {
807         XWindowAttributes wattr;
808         XGetWindowAttributes(dpy, win, &wattr);
809         *x = wattr.x;
810         *y = wattr.y;
811 }
812
813 static void get_window_size(int *w, int *h)
814 {
815         XWindowAttributes wattr;
816         XGetWindowAttributes(dpy, win, &wattr);
817         *w = wattr.width;
818         *h = wattr.height;
819 }
820
821 static void get_screen_size(int *scrw, int *scrh)
822 {
823         XWindowAttributes wattr;
824         XGetWindowAttributes(dpy, root, &wattr);
825         *scrw = wattr.width;
826         *scrh = wattr.height;
827 }
828 #endif  /* BUILD_X11 */
829
830
831 /* --------------- windows implementation ----------------- */
832 #ifdef BUILD_WIN32
833 static int reshape_pending;
834
835 static void update_modkeys(void);
836 static int translate_vkey(int vkey);
837 static void handle_mbutton(int bn, int st, WPARAM wparam, LPARAM lparam);
838
839 #ifdef MINIGLUT_WINMAIN
840 int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hprev, char *cmdline, int showcmd)
841 {
842         int argc = 1;
843         char *argv[] = { "miniglut.exe", 0 };
844         return main(argc, argv);
845 }
846 #endif
847
848 void glutMainLoopEvent(void)
849 {
850         MSG msg;
851
852         if(!cb_display) {
853                 panic("display callback not set");
854         }
855
856         if(reshape_pending && cb_reshape) {
857                 reshape_pending = 0;
858                 get_window_size(&win_width, &win_height);
859                 cb_reshape(win_width, win_height);
860         }
861
862         if(!upd_pending && !cb_idle) {
863                 GetMessage(&msg, 0, 0, 0);
864                 TranslateMessage(&msg);
865                 DispatchMessage(&msg);
866                 if(quit) return;
867         }
868         while(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
869                 TranslateMessage(&msg);
870                 DispatchMessage(&msg);
871                 if(quit) return;
872         }
873
874         if(cb_idle) {
875                 cb_idle();
876         }
877
878         if(upd_pending && mapped) {
879                 upd_pending = 0;
880                 cb_display();
881         }
882 }
883
884 void glutSwapBuffers(void)
885 {
886         SwapBuffers(dc);
887 }
888
889 void glutPositionWindow(int x, int y)
890 {
891         RECT rect;
892         unsigned int flags = SWP_SHOWWINDOW;
893
894         if(fullscreen) {
895                 rect.left = prev_win_x;
896                 rect.top = prev_win_y;
897                 rect.right = rect.left + prev_win_width;
898                 rect.bottom = rect.top + prev_win_height;
899                 SetWindowLong(win, GWL_STYLE, WS_OVERLAPPEDWINDOW);
900                 fullscreen = 0;
901                 flags |= SWP_FRAMECHANGED;
902         } else {
903                 GetWindowRect(win, &rect);
904         }
905         SetWindowPos(win, HWND_NOTOPMOST, x, y, rect.right - rect.left, rect.bottom - rect.top, flags);
906 }
907
908 void glutReshapeWindow(int xsz, int ysz)
909 {
910         RECT rect;
911         unsigned int flags = SWP_SHOWWINDOW;
912
913         if(fullscreen) {
914                 rect.left = prev_win_x;
915                 rect.top = prev_win_y;
916                 SetWindowLong(win, GWL_STYLE, WS_OVERLAPPEDWINDOW);
917                 fullscreen = 0;
918                 flags |= SWP_FRAMECHANGED;
919         } else {
920                 GetWindowRect(win, &rect);
921         }
922         SetWindowPos(win, HWND_NOTOPMOST, rect.left, rect.top, xsz, ysz, flags);
923 }
924
925 void glutFullScreen(void)
926 {
927         RECT rect;
928         int scr_width, scr_height;
929
930         if(fullscreen) return;
931
932         GetWindowRect(win, &rect);
933         prev_win_x = rect.left;
934         prev_win_y = rect.top;
935         prev_win_width = rect.right - rect.left;
936         prev_win_height = rect.bottom - rect.top;
937
938         get_screen_size(&scr_width, &scr_height);
939
940         SetWindowLong(win, GWL_STYLE, 0);
941         SetWindowPos(win, HWND_TOPMOST, 0, 0, scr_width, scr_height, SWP_SHOWWINDOW);
942
943         fullscreen = 1;
944 }
945
946 void glutSetWindowTitle(const char *title)
947 {
948         SetWindowText(win, title);
949 }
950
951 void glutSetIconTitle(const char *title)
952 {
953 }
954
955 void glutSetCursor(int cidx)
956 {
957         switch(cidx) {
958         case GLUT_CURSOR_NONE:
959                 ShowCursor(0);
960                 break;
961         case GLUT_CURSOR_INHERIT:
962         case GLUT_CURSOR_LEFT_ARROW:
963         default:
964                 SetCursor(LoadCursor(0, IDC_ARROW));
965                 ShowCursor(1);
966         }
967 }
968
969
970 static void create_window(const char *title)
971 {
972         int pixfmt;
973         PIXELFORMATDESCRIPTOR pfd = {0};
974         RECT rect;
975
976         rect.left = init_x;
977         rect.top = init_y;
978         rect.right = init_x + init_width;
979         rect.bottom = init_y + init_height;
980         AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, 0);
981
982         if(!(win = CreateWindow("MiniGLUT", title, WS_OVERLAPPEDWINDOW, rect.left, rect.top,
983                                 rect.right - rect.left, rect.bottom - rect.top, 0, 0, hinst, 0))) {
984                 panic("Failed to create window\n");
985         }
986         dc = GetDC(win);
987
988         pfd.nSize = sizeof pfd;
989         pfd.nVersion = 1;
990         pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
991         if(init_mode & GLUT_STEREO) {
992                 pfd.dwFlags |= PFD_STEREO;
993         }
994         pfd.iPixelType = init_mode & GLUT_INDEX ? PFD_TYPE_COLORINDEX : PFD_TYPE_RGBA;
995         pfd.cColorBits = 24;
996         if(init_mode & GLUT_ALPHA) {
997                 pfd.cAlphaBits = 8;
998         }
999         if(init_mode & GLUT_ACCUM) {
1000                 pfd.cAccumBits = 24;
1001         }
1002         if(init_mode & GLUT_DEPTH) {
1003                 pfd.cDepthBits = 24;
1004         }
1005         if(init_mode & GLUT_STENCIL) {
1006                 pfd.cStencilBits = 8;
1007         }
1008         pfd.iLayerType = PFD_MAIN_PLANE;
1009
1010         if(!(pixfmt = ChoosePixelFormat(dc, &pfd))) {
1011                 panic("Failed to find suitable pixel format\n");
1012         }
1013         if(!SetPixelFormat(dc, pixfmt, &pfd)) {
1014                 panic("Failed to set the selected pixel format\n");
1015         }
1016         if(!(ctx = wglCreateContext(dc))) {
1017                 panic("Failed to create the OpenGL context\n");
1018         }
1019         wglMakeCurrent(dc, ctx);
1020
1021         DescribePixelFormat(dc, pixfmt, sizeof pfd, &pfd);
1022         ctx_info.rsize = pfd.cRedBits;
1023         ctx_info.gsize = pfd.cGreenBits;
1024         ctx_info.bsize = pfd.cBlueBits;
1025         ctx_info.asize = pfd.cAlphaBits;
1026         ctx_info.zsize = pfd.cDepthBits;
1027         ctx_info.ssize = pfd.cStencilBits;
1028         ctx_info.dblbuf = pfd.dwFlags & PFD_DOUBLEBUFFER ? 1 : 0;
1029         ctx_info.samples = 1;   /* TODO */
1030         ctx_info.srgb = 0;              /* TODO */
1031
1032         ShowWindow(win, 1);
1033         SetForegroundWindow(win);
1034         SetFocus(win);
1035         upd_pending = 1;
1036         reshape_pending = 1;
1037 }
1038
1039 static HRESULT CALLBACK handle_message(HWND win, unsigned int msg, WPARAM wparam, LPARAM lparam)
1040 {
1041         static int mouse_x, mouse_y;
1042         int x, y, key;
1043
1044         switch(msg) {
1045         case WM_CLOSE:
1046                 if(win) DestroyWindow(win);
1047                 break;
1048
1049         case WM_DESTROY:
1050                 wglMakeCurrent(dc, 0);
1051                 wglDeleteContext(ctx);
1052                 quit = 1;
1053                 PostQuitMessage(0);
1054                 break;
1055
1056         case WM_PAINT:
1057                 upd_pending = 1;
1058                 ValidateRect(win, 0);
1059                 break;
1060
1061         case WM_SIZE:
1062                 x = lparam & 0xffff;
1063                 y = lparam >> 16;
1064                 if(x != win_width && y != win_height) {
1065                         win_width = x;
1066                         win_height = y;
1067                         if(cb_reshape) {
1068                                 reshape_pending = 0;
1069                                 cb_reshape(win_width, win_height);
1070                         }
1071                 }
1072                 break;
1073
1074         case WM_SHOWWINDOW:
1075                 mapped = wparam;
1076                 if(cb_vis) cb_vis(mapped ? GLUT_VISIBLE : GLUT_NOT_VISIBLE);
1077                 break;
1078
1079         case WM_KEYDOWN:
1080         case WM_SYSKEYDOWN:
1081                 update_modkeys();
1082                 key = translate_vkey(wparam);
1083                 if(key < 256) {
1084                         if(cb_keydown) {
1085                                 cb_keydown((unsigned char)key, mouse_x, mouse_y);
1086                         }
1087                 } else {
1088                         if(cb_skeydown) {
1089                                 cb_skeydown(key, mouse_x, mouse_y);
1090                         }
1091                 }
1092                 break;
1093
1094         case WM_KEYUP:
1095         case WM_SYSKEYUP:
1096                 update_modkeys();
1097                 key = translate_vkey(wparam);
1098                 if(key < 256) {
1099                         if(cb_keyup) {
1100                                 cb_keyup((unsigned char)key, mouse_x, mouse_y);
1101                         }
1102                 } else {
1103                         if(cb_skeyup) {
1104                                 cb_skeyup(key, mouse_x, mouse_y);
1105                         }
1106                 }
1107                 break;
1108
1109         case WM_LBUTTONDOWN:
1110                 handle_mbutton(0, 1, wparam, lparam);
1111                 break;
1112         case WM_MBUTTONDOWN:
1113                 handle_mbutton(1, 1, wparam, lparam);
1114                 break;
1115         case WM_RBUTTONDOWN:
1116                 handle_mbutton(2, 1, wparam, lparam);
1117                 break;
1118         case WM_LBUTTONUP:
1119                 handle_mbutton(0, 0, wparam, lparam);
1120                 break;
1121         case WM_MBUTTONUP:
1122                 handle_mbutton(1, 0, wparam, lparam);
1123                 break;
1124         case WM_RBUTTONUP:
1125                 handle_mbutton(2, 0, wparam, lparam);
1126                 break;
1127
1128         case WM_MOUSEMOVE:
1129                 if(wparam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) {
1130                         if(cb_motion) cb_motion(lparam & 0xffff, lparam >> 16);
1131                 } else {
1132                         if(cb_passive) cb_passive(lparam & 0xffff, lparam >> 16);
1133                 }
1134                 break;
1135
1136         case WM_SYSCOMMAND:
1137                 wparam &= 0xfff0;
1138                 if(wparam == SC_KEYMENU || wparam == SC_SCREENSAVE || wparam == SC_MONITORPOWER) {
1139                         return 0;
1140                 }
1141         default:
1142                 return DefWindowProc(win, msg, wparam, lparam);
1143         }
1144
1145         return 0;
1146 }
1147
1148 static void update_modkeys(void)
1149 {
1150         if(GetKeyState(VK_SHIFT) & 0x8000) {
1151                 modstate |= GLUT_ACTIVE_SHIFT;
1152         } else {
1153                 modstate &= ~GLUT_ACTIVE_SHIFT;
1154         }
1155         if(GetKeyState(VK_CONTROL) & 0x8000) {
1156                 modstate |= GLUT_ACTIVE_CTRL;
1157         } else {
1158                 modstate &= ~GLUT_ACTIVE_CTRL;
1159         }
1160         if(GetKeyState(VK_MENU) & 0x8000) {
1161                 modstate |= GLUT_ACTIVE_ALT;
1162         } else {
1163                 modstate &= ~GLUT_ACTIVE_ALT;
1164         }
1165 }
1166
1167 static int translate_vkey(int vkey)
1168 {
1169         switch(vkey) {
1170         case VK_PRIOR: return GLUT_KEY_PAGE_UP;
1171         case VK_NEXT: return GLUT_KEY_PAGE_DOWN;
1172         case VK_END: return GLUT_KEY_END;
1173         case VK_HOME: return GLUT_KEY_HOME;
1174         case VK_LEFT: return GLUT_KEY_LEFT;
1175         case VK_UP: return GLUT_KEY_UP;
1176         case VK_RIGHT: return GLUT_KEY_RIGHT;
1177         case VK_DOWN: return GLUT_KEY_DOWN;
1178         default:
1179                 break;
1180         }
1181
1182         if(vkey >= 'A' && vkey <= 'Z') {
1183                 vkey += 32;
1184         } else if(vkey >= VK_F1 && vkey <= VK_F12) {
1185                 vkey -= VK_F1 + GLUT_KEY_F1;
1186         }
1187
1188         return vkey;
1189 }
1190
1191 static void handle_mbutton(int bn, int st, WPARAM wparam, LPARAM lparam)
1192 {
1193         int x, y;
1194
1195         update_modkeys();
1196
1197         if(cb_mouse) {
1198                 x = lparam & 0xffff;
1199                 y = lparam >> 16;
1200                 cb_mouse(bn, st ? GLUT_DOWN : GLUT_UP, x, y);
1201         }
1202 }
1203
1204 static void get_window_pos(int *x, int *y)
1205 {
1206         RECT rect;
1207         GetWindowRect(win, &rect);
1208         *x = rect.left;
1209         *y = rect.top;
1210 }
1211
1212 static void get_window_size(int *w, int *h)
1213 {
1214         RECT rect;
1215         GetClientRect(win, &rect);
1216         *w = rect.right - rect.left;
1217         *h = rect.bottom - rect.top;
1218 }
1219
1220 static void get_screen_size(int *scrw, int *scrh)
1221 {
1222         *scrw = GetSystemMetrics(SM_CXSCREEN);
1223         *scrh = GetSystemMetrics(SM_CYSCREEN);
1224 }
1225 #endif  /* BUILD_WIN32 */
1226
1227 #if defined(__unix__) || defined(__APPLE__)
1228 #include <sys/time.h>
1229
1230 #ifdef MINIGLUT_USE_LIBC
1231 #define sys_gettimeofday(tv, tz)        gettimeofday(tv, tz)
1232 #else
1233 static int sys_gettimeofday(struct timeval *tv, struct timezone *tz);
1234 #endif
1235
1236 static long get_msec(void)
1237 {
1238         static struct timeval tv0;
1239         struct timeval tv;
1240
1241         sys_gettimeofday(&tv, 0);
1242         if(tv0.tv_sec == 0 && tv0.tv_usec == 0) {
1243                 tv0 = tv;
1244                 return 0;
1245         }
1246         return (tv.tv_sec - tv0.tv_sec) * 1000 + (tv.tv_usec - tv0.tv_usec) / 1000;
1247 }
1248 #endif  /* UNIX */
1249 #ifdef _WIN32
1250 static long get_msec(void)
1251 {
1252         static long t0;
1253         long tm;
1254
1255 #ifdef MINIGLUT_NO_WINMM
1256         tm = GetTickCount();
1257 #else
1258         tm = timeGetTime();
1259 #endif
1260         if(!t0) {
1261                 t0 = tm;
1262                 return 0;
1263         }
1264         return tm - t0;
1265 }
1266 #endif
1267
1268 static void panic(const char *msg)
1269 {
1270         const char *end = msg;
1271         while(*end) end++;
1272         sys_write(2, msg, end - msg);
1273         sys_exit(1);
1274 }
1275
1276
1277 #ifdef MINIGLUT_USE_LIBC
1278 static void sys_exit(int status)
1279 {
1280         exit(status);
1281 }
1282
1283 static int sys_write(int fd, const void *buf, int count)
1284 {
1285         return write(fd, buf, count);
1286 }
1287
1288 #else   /* !MINIGLUT_USE_LIBC */
1289
1290 #ifdef __linux__
1291 #ifdef __x86_64__
1292 static void sys_exit(int status)
1293 {
1294         asm volatile(
1295                 "syscall\n\t"
1296                 :: "a"(60), "D"(status));
1297 }
1298 static int sys_write(int fd, const void *buf, int count)
1299 {
1300         long res;
1301         asm volatile(
1302                 "syscall\n\t"
1303                 : "=a"(res)
1304                 : "a"(1), "D"(fd), "S"(buf), "d"(count));
1305         return res;
1306 }
1307 static int sys_gettimeofday(struct timeval *tv, struct timezone *tz)
1308 {
1309         int res;
1310         asm volatile(
1311                 "syscall\n\t"
1312                 : "=a"(res)
1313                 : "a"(96), "D"(tv), "S"(tz));
1314         return res;
1315 }
1316 #endif  /* __x86_64__ */
1317 #ifdef __i386__
1318 static void sys_exit(int status)
1319 {
1320         asm volatile(
1321                 "int $0x80\n\t"
1322                 :: "a"(1), "b"(status));
1323 }
1324 static int sys_write(int fd, const void *buf, int count)
1325 {
1326         int res;
1327         asm volatile(
1328                 "int $0x80\n\t"
1329                 : "=a"(res)
1330                 : "a"(4), "b"(fd), "c"(buf), "d"(count));
1331         return res;
1332 }
1333 static int sys_gettimeofday(struct timeval *tv, struct timezone *tz)
1334 {
1335         int res;
1336         asm volatile(
1337                 "int $0x80\n\t"
1338                 : "=a"(res)
1339                 : "a"(78), "b"(tv), "c"(tz));
1340         return res;
1341 }
1342 #endif  /* __i386__ */
1343 #endif  /* __linux__ */
1344
1345 #ifdef _WIN32
1346 static void sys_exit(int status)
1347 {
1348         ExitProcess(status);
1349 }
1350 static int sys_write(int fd, const void *buf, int count)
1351 {
1352         unsigned long wrsz = 0;
1353
1354         HANDLE out = GetStdHandle(fd == 1 ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE);
1355         if(!WriteFile(out, buf, count, &wrsz, 0)) {
1356                 return -1;
1357         }
1358         return wrsz;
1359 }
1360 #endif  /* _WIN32 */
1361 #endif  /* !MINIGLUT_USE_LIBC */
1362
1363
1364 /* ----------------- primitives ------------------ */
1365 #ifdef MINIGLUT_USE_LIBC
1366 #include <stdlib.h>
1367 #include <math.h>
1368
1369 #define mglut_sincos(a, s, c)   \
1370         do { \
1371                 *(s) = sin(a); \
1372                 *(c) = cos(a); \
1373         } while(0)
1374
1375 #define mglut_atan(x)   atan(x)
1376
1377 #else   /* !MINIGLUT_USE_LIBC */
1378
1379 #ifdef __GNUC__
1380 static void mglut_sincos(float angle, float *sptr, float *cptr)
1381 {
1382         asm volatile(
1383                 "flds %2\n\t"
1384                 "fsincos\n\t"
1385                 "fstps %1\n\t"
1386                 "fstps %0\n\t"
1387                 : "=m"(*sptr), "=m"(*cptr)
1388                 : "m"(angle)
1389         );
1390 }
1391
1392 static float mglut_atan(float x)
1393 {
1394         float res;
1395         asm volatile(
1396                 "flds %1\n\t"
1397                 "fld1\n\t"
1398                 "fpatan\n\t"
1399                 "fstps %0\n\t"
1400                 : "=m"(res)
1401                 : "m"(x)
1402         );
1403         return res;
1404 }
1405 #endif
1406
1407 #ifdef _MSC_VER
1408 static void mglut_sincos(float angle, float *sptr, float *cptr)
1409 {
1410         float s, c;
1411         __asm {
1412                 fld angle
1413                 fsincos
1414                 fstp c
1415                 fstp s
1416         }
1417         *sptr = s;
1418         *cptr = c;
1419 }
1420
1421 static float mglut_atan(float x)
1422 {
1423         float res;
1424         __asm {
1425                 fld x
1426                 fld1
1427                 fpatan
1428                 fstp res
1429         }
1430         return res;
1431 }
1432 #endif
1433
1434 #ifdef __WATCOMC__
1435 #pragma aux mglut_sincos = \
1436         "fsincos" \
1437         "fstp dword ptr [edx]" \
1438         "fstp dword ptr [eax]" \
1439         parm[8087][eax][edx]    \
1440         modify[8087];
1441
1442 #pragma aux mglut_atan = \
1443         "fld1" \
1444         "fpatan" \
1445         parm[8087] \
1446         value[8087] \
1447         modify [8087];
1448 #endif  /* __WATCOMC__ */
1449
1450 #endif  /* !MINIGLUT_USE_LIBC */
1451
1452 #define PI      3.1415926536f
1453
1454 void glutSolidSphere(float rad, int slices, int stacks)
1455 {
1456         int i, j, k, gray;
1457         float x, y, z, s, t, u, v, phi, theta, sintheta, costheta, sinphi, cosphi;
1458         float du = 1.0f / (float)slices;
1459         float dv = 1.0f / (float)stacks;
1460
1461         glBegin(GL_QUADS);
1462         for(i=0; i<stacks; i++) {
1463                 v = i * dv;
1464                 for(j=0; j<slices; j++) {
1465                         u = j * du;
1466                         for(k=0; k<4; k++) {
1467                                 gray = k ^ (k >> 1);
1468                                 s = gray & 1 ? u + du : u;
1469                                 t = gray & 2 ? v + dv : v;
1470                                 theta = s * PI * 2.0f;
1471                                 phi = t * PI;
1472                                 mglut_sincos(theta, &sintheta, &costheta);
1473                                 mglut_sincos(phi, &sinphi, &cosphi);
1474                                 x = sintheta * sinphi;
1475                                 y = costheta * sinphi;
1476                                 z = cosphi;
1477
1478                                 glColor3f(s, t, 1);
1479                                 glTexCoord2f(s, t);
1480                                 glNormal3f(x, y, z);
1481                                 glVertex3f(x * rad, y * rad, z * rad);
1482                         }
1483                 }
1484         }
1485         glEnd();
1486 }
1487
1488 void glutWireSphere(float rad, int slices, int stacks)
1489 {
1490         glPushAttrib(GL_POLYGON_BIT);
1491         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
1492         glutSolidSphere(rad, slices, stacks);
1493         glPopAttrib();
1494 }
1495
1496 void glutSolidCube(float sz)
1497 {
1498         int i, j, idx, gray, flip, rotx;
1499         float vpos[3], norm[3];
1500         float rad = sz * 0.5f;
1501
1502         glBegin(GL_QUADS);
1503         for(i=0; i<6; i++) {
1504                 flip = i & 1;
1505                 rotx = i >> 2;
1506                 idx = (~i & 2) - rotx;
1507                 norm[0] = norm[1] = norm[2] = 0.0f;
1508                 norm[idx] = flip ^ ((i >> 1) & 1) ? -1 : 1;
1509                 glNormal3fv(norm);
1510                 vpos[idx] = norm[idx] * rad;
1511                 for(j=0; j<4; j++) {
1512                         gray = j ^ (j >> 1);
1513                         vpos[i & 2] = (gray ^ flip) & 1 ? rad : -rad;
1514                         vpos[rotx + 1] = (gray ^ (rotx << 1)) & 2 ? rad : -rad;
1515                         glTexCoord2f(gray & 1, gray >> 1);
1516                         glVertex3fv(vpos);
1517                 }
1518         }
1519         glEnd();
1520 }
1521
1522 void glutWireCube(float sz)
1523 {
1524         glPushAttrib(GL_POLYGON_BIT);
1525         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
1526         glutSolidCube(sz);
1527         glPopAttrib();
1528 }
1529
1530 static void draw_cylinder(float rbot, float rtop, float height, int slices, int stacks)
1531 {
1532         int i, j, k, gray;
1533         float x, y, z, s, t, u, v, theta, phi, sintheta, costheta, sinphi, cosphi, rad;
1534         float du = 1.0f / (float)slices;
1535         float dv = 1.0f / (float)stacks;
1536
1537         rad = rbot - rtop;
1538         phi = mglut_atan((rad < 0 ? -rad : rad) / height);
1539         mglut_sincos(phi, &sinphi, &cosphi);
1540
1541         glBegin(GL_QUADS);
1542         for(i=0; i<stacks; i++) {
1543                 v = i * dv;
1544                 for(j=0; j<slices; j++) {
1545                         u = j * du;
1546                         for(k=0; k<4; k++) {
1547                                 gray = k ^ (k >> 1);
1548                                 s = gray & 2 ? u + du : u;
1549                                 t = gray & 1 ? v + dv : v;
1550                                 rad = rbot + (rtop - rbot) * t;
1551                                 theta = s * PI * 2.0f;
1552                                 mglut_sincos(theta, &sintheta, &costheta);
1553
1554                                 x = sintheta * cosphi;
1555                                 y = costheta * cosphi;
1556                                 z = sinphi;
1557
1558                                 glColor3f(s, t, 1);
1559                                 glTexCoord2f(s, t);
1560                                 glNormal3f(x, y, z);
1561                                 glVertex3f(sintheta * rad, costheta * rad, t * height);
1562                         }
1563                 }
1564         }
1565         glEnd();
1566 }
1567
1568 void glutSolidCone(float base, float height, int slices, int stacks)
1569 {
1570         draw_cylinder(base, 0, height, slices, stacks);
1571 }
1572
1573 void glutWireCone(float base, float height, int slices, int stacks)
1574 {
1575         glPushAttrib(GL_POLYGON_BIT);
1576         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
1577         glutSolidCone(base, height, slices, stacks);
1578         glPopAttrib();
1579 }
1580
1581 void glutSolidCylinder(float rad, float height, int slices, int stacks)
1582 {
1583         draw_cylinder(rad, rad, height, slices, stacks);
1584 }
1585
1586 void glutWireCylinder(float rad, float height, int slices, int stacks)
1587 {
1588         glPushAttrib(GL_POLYGON_BIT);
1589         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
1590         glutSolidCylinder(rad, height, slices, stacks);
1591         glPopAttrib();
1592 }
1593
1594 void glutSolidTorus(float inner_rad, float outer_rad, int sides, int rings)
1595 {
1596         int i, j, k, gray;
1597         float x, y, z, s, t, u, v, phi, theta, sintheta, costheta, sinphi, cosphi;
1598         float du = 1.0f / (float)rings;
1599         float dv = 1.0f / (float)sides;
1600
1601         glBegin(GL_QUADS);
1602         for(i=0; i<rings; i++) {
1603                 u = i * du;
1604                 for(j=0; j<sides; j++) {
1605                         v = j * dv;
1606                         for(k=0; k<4; k++) {
1607                                 gray = k ^ (k >> 1);
1608                                 s = gray & 1 ? u + du : u;
1609                                 t = gray & 2 ? v + dv : v;
1610                                 theta = s * PI * 2.0f;
1611                                 phi = t * PI * 2.0f;
1612                                 mglut_sincos(theta, &sintheta, &costheta);
1613                                 mglut_sincos(phi, &sinphi, &cosphi);
1614                                 x = sintheta * sinphi;
1615                                 y = costheta * sinphi;
1616                                 z = cosphi;
1617
1618                                 glColor3f(s, t, 1);
1619                                 glTexCoord2f(s, t);
1620                                 glNormal3f(x, y, z);
1621
1622                                 x = x * inner_rad + sintheta * outer_rad;
1623                                 y = y * inner_rad + costheta * outer_rad;
1624                                 z *= inner_rad;
1625                                 glVertex3f(x, y, z);
1626                         }
1627                 }
1628         }
1629         glEnd();
1630 }
1631
1632 void glutWireTorus(float inner_rad, float outer_rad, int sides, int rings)
1633 {
1634         glPushAttrib(GL_POLYGON_BIT);
1635         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
1636         glutSolidTorus(inner_rad, outer_rad, sides, rings);
1637         glPopAttrib();
1638 }
1639
1640 void glutSolidTeapot(float size)
1641 {
1642 }
1643
1644 void glutWireTeapot(float size)
1645 {
1646 }