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