addes special key enumerations and translation
[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 #ifdef MINIGLUT_WINMAIN
950 int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hprev, char *cmdline, int showcmd)
951 {
952         int argc = 1;
953         char *argv[] = { "miniglut.exe", 0 };
954         return main(argc, argv);
955 }
956 #endif
957
958 void glutMainLoopEvent(void)
959 {
960         MSG msg;
961
962         if(!cb_display) {
963                 panic("display callback not set");
964         }
965
966         if(reshape_pending && cb_reshape) {
967                 reshape_pending = 0;
968                 get_window_size(&win_width, &win_height);
969                 cb_reshape(win_width, win_height);
970         }
971
972         if(!upd_pending && !cb_idle) {
973                 GetMessage(&msg, 0, 0, 0);
974                 TranslateMessage(&msg);
975                 DispatchMessage(&msg);
976                 if(quit) return;
977         }
978         while(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
979                 TranslateMessage(&msg);
980                 DispatchMessage(&msg);
981                 if(quit) return;
982         }
983
984         if(cb_idle) {
985                 cb_idle();
986         }
987
988         if(upd_pending && mapped) {
989                 upd_pending = 0;
990                 cb_display();
991         }
992 }
993
994 void glutSwapBuffers(void)
995 {
996         SwapBuffers(dc);
997 }
998
999 void glutPositionWindow(int x, int y)
1000 {
1001         RECT rect;
1002         GetWindowRect(win, &rect);
1003         MoveWindow(win, x, y, rect.right - rect.left, rect.bottom - rect.top, 1);
1004 }
1005
1006 void glutReshapeWindow(int xsz, int ysz)
1007 {
1008         RECT rect;
1009         GetWindowRect(win, &rect);
1010         MoveWindow(win, rect.left, rect.top, xsz, ysz, 1);
1011 }
1012
1013 void glutFullScreen(void)
1014 {
1015         /* TODO */
1016 }
1017
1018 void glutSetWindowTitle(const char *title)
1019 {
1020         SetWindowText(win, title);
1021 }
1022
1023 void glutSetIconTitle(const char *title)
1024 {
1025 }
1026
1027 void glutSetCursor(int cidx)
1028 {
1029         switch(cidx) {
1030         case GLUT_CURSOR_NONE:
1031                 ShowCursor(0);
1032                 break;
1033         case GLUT_CURSOR_INHERIT:
1034         case GLUT_CURSOR_LEFT_ARROW:
1035         default:
1036                 SetCursor(LoadCursor(0, IDC_ARROW));
1037                 ShowCursor(1);
1038         }
1039 }
1040
1041
1042 static void create_window(const char *title)
1043 {
1044         int pixfmt;
1045         PIXELFORMATDESCRIPTOR pfd = {0};
1046
1047         if(!(win = CreateWindow("MiniGLUT", title, WS_OVERLAPPEDWINDOW, init_x, init_y,
1048                                 init_width, init_height, 0, 0, hinst, 0))) {
1049                 panic("Failed to create window\n");
1050         }
1051         dc = GetDC(win);
1052
1053         pfd.nSize = sizeof pfd;
1054         pfd.nVersion = 1;
1055         pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
1056         if(init_mode & GLUT_STEREO) {
1057                 pfd.dwFlags |= PFD_STEREO;
1058         }
1059         pfd.iPixelType = init_mode & GLUT_INDEX ? PFD_TYPE_COLORINDEX : PFD_TYPE_RGBA;
1060         pfd.cColorBits = 24;
1061         if(init_mode & GLUT_ALPHA) {
1062                 pfd.cAlphaBits = 8;
1063         }
1064         if(init_mode & GLUT_ACCUM) {
1065                 pfd.cAccumBits = 24;
1066         }
1067         if(init_mode & GLUT_DEPTH) {
1068                 pfd.cDepthBits = 24;
1069         }
1070         if(init_mode & GLUT_STENCIL) {
1071                 pfd.cStencilBits = 8;
1072         }
1073         pfd.iLayerType = PFD_MAIN_PLANE;
1074
1075         if(!(pixfmt = ChoosePixelFormat(dc, &pfd))) {
1076                 panic("Failed to find suitable pixel format\n");
1077         }
1078         if(!SetPixelFormat(dc, pixfmt, &pfd)) {
1079                 panic("Failed to set the selected pixel format\n");
1080         }
1081         if(!(ctx = wglCreateContext(dc))) {
1082                 panic("Failed to create the OpenGL context\n");
1083         }
1084         wglMakeCurrent(dc, ctx);
1085
1086         DescribePixelFormat(dc, pixfmt, sizeof pfd, &pfd);
1087         ctx_info.rsize = pfd.cRedBits;
1088         ctx_info.gsize = pfd.cGreenBits;
1089         ctx_info.bsize = pfd.cBlueBits;
1090         ctx_info.asize = pfd.cAlphaBits;
1091         ctx_info.zsize = pfd.cDepthBits;
1092         ctx_info.ssize = pfd.cStencilBits;
1093         ctx_info.dblbuf = pfd.dwFlags & PFD_DOUBLEBUFFER ? 1 : 0;
1094         ctx_info.samples = 1;   /* TODO */
1095         ctx_info.srgb = 0;              /* TODO */
1096
1097         ShowWindow(win, 1);
1098         upd_pending = 1;
1099         reshape_pending = 1;
1100 }
1101
1102 static HRESULT CALLBACK handle_message(HWND win, unsigned int msg, WPARAM wparam, LPARAM lparam)
1103 {
1104         static int mouse_x, mouse_y;
1105         int key;
1106
1107         switch(msg) {
1108         case WM_CLOSE:
1109                 if(win) DestroyWindow(win);
1110                 break;
1111
1112         case WM_DESTROY:
1113                 wglMakeCurrent(dc, 0);
1114                 wglDeleteContext(ctx);
1115                 quit = 1;
1116                 PostQuitMessage(0);
1117                 break;
1118
1119         case WM_PAINT:
1120                 upd_pending = 1;
1121                 ValidateRect(win, 0);
1122                 break;
1123
1124         case WM_SIZE:
1125                 win_width = lparam & 0xffff;
1126                 win_height = lparam >> 16;
1127                 if(cb_reshape) {
1128                         reshape_pending = 0;
1129                         cb_reshape(win_width, win_height);
1130                 }
1131                 break;
1132
1133         case WM_SHOWWINDOW:
1134                 mapped = wparam;
1135                 if(cb_vis) cb_vis(mapped ? GLUT_VISIBLE : GLUT_NOT_VISIBLE);
1136                 break;
1137
1138         case WM_KEYDOWN:
1139                 update_modkeys();
1140                 key = translate_vkey(wparam);
1141                 if(key < 256) {
1142                         if(cb_keydown) {
1143                                 cb_keydown((unsigned char)key, mouse_x, mouse_y);
1144                         }
1145                 } else {
1146                         if(cb_skeydown) {
1147                                 cb_skeydown(key, mouse_x, mouse_y);
1148                         }
1149                 }
1150                 break;
1151
1152         case WM_KEYUP:
1153                 update_modkeys();
1154                 key = translate_vkey(wparam);
1155                 if(key < 256) {
1156                         if(cb_keyup) {
1157                                 cb_keyup((unsigned char)key, mouse_x, mouse_y);
1158                         }
1159                 } else {
1160                         if(cb_skeyup) {
1161                                 cb_skeyup(key, mouse_x, mouse_y);
1162                         }
1163                 }
1164                 break;
1165
1166         case WM_LBUTTONDOWN:
1167                 handle_mbutton(0, 1, wparam, lparam);
1168                 break;
1169         case WM_MBUTTONDOWN:
1170                 handle_mbutton(1, 1, wparam, lparam);
1171                 break;
1172         case WM_RBUTTONDOWN:
1173                 handle_mbutton(2, 1, wparam, lparam);
1174                 break;
1175         case WM_LBUTTONUP:
1176                 handle_mbutton(0, 0, wparam, lparam);
1177                 break;
1178         case WM_MBUTTONUP:
1179                 handle_mbutton(1, 0, wparam, lparam);
1180                 break;
1181         case WM_RBUTTONUP:
1182                 handle_mbutton(2, 0, wparam, lparam);
1183                 break;
1184
1185         case WM_MOUSEMOVE:
1186                 if(wparam & (MK_LBUTTON | MK_MBUTTON | MK_RBUTTON)) {
1187                         if(cb_motion) cb_motion(lparam & 0xffff, lparam >> 16);
1188                 } else {
1189                         if(cb_passive) cb_passive(lparam & 0xffff, lparam >> 16);
1190                 }
1191                 break;
1192
1193         default:
1194                 return DefWindowProc(win, msg, wparam, lparam);
1195         }
1196
1197         return 0;
1198 }
1199
1200 static void update_modkeys(void)
1201 {
1202         if(GetKeyState(VK_SHIFT)) {
1203                 modstate |= GLUT_ACTIVE_SHIFT;
1204         } else {
1205                 modstate &= ~GLUT_ACTIVE_SHIFT;
1206         }
1207         if(GetKeyState(VK_CONTROL)) {
1208                 modstate |= GLUT_ACTIVE_CTRL;
1209         } else {
1210                 modstate &= ~GLUT_ACTIVE_CTRL;
1211         }
1212         if(GetKeyState(VK_MENU)) {
1213                 modstate |= GLUT_ACTIVE_ALT;
1214         } else {
1215                 modstate &= ~GLUT_ACTIVE_ALT;
1216         }
1217 }
1218
1219 static int translate_vkey(int vkey)
1220 {
1221         switch(vkey) {
1222         case VK_PRIOR: return GLUT_KEY_PAGE_UP;
1223         case VK_NEXT: return GLUT_KEY_PAGE_DOWN;
1224         case VK_END: return GLUT_KEY_END;
1225         case VK_HOME: return GLUT_KEY_HOME;
1226         case VK_LEFT: return GLUT_KEY_LEFT;
1227         case VK_UP: return GLUT_KEY_UP;
1228         case VK_RIGHT: return GLUT_KEY_RIGHT;
1229         case VK_DOWN: return GLUT_KEY_DOWN;
1230         default:
1231                 if(vkey >= VK_F1 && vkey <= VK_F12) {
1232                         return vkey - VK_F1 + GLUT_KEY_F1;
1233                 }
1234         }
1235         return vkey;
1236 }
1237
1238 static void handle_mbutton(int bn, int st, WPARAM wparam, LPARAM lparam)
1239 {
1240         int x, y;
1241
1242         update_modkeys();
1243
1244         if(cb_mouse) {
1245                 x = lparam & 0xffff;
1246                 y = lparam >> 16;
1247                 cb_mouse(bn, st ? GLUT_DOWN : GLUT_UP, x, y);
1248         }
1249 }
1250
1251 static void get_window_pos(int *x, int *y)
1252 {
1253         RECT rect;
1254         GetWindowRect(win, &rect);
1255         *x = rect.left;
1256         *y = rect.top;
1257 }
1258
1259 static void get_window_size(int *w, int *h)
1260 {
1261         RECT rect;
1262         GetClientRect(win, &rect);
1263         *w = rect.right - rect.left;
1264         *h = rect.bottom - rect.top;
1265 }
1266
1267 static void get_screen_size(int *scrw, int *scrh)
1268 {
1269         *scrw = GetSystemMetrics(SM_CXSCREEN);
1270         *scrh = GetSystemMetrics(SM_CYSCREEN);
1271 }
1272 #endif  /* BUILD_WIN32 */
1273
1274 #if defined(__unix__) || defined(__APPLE__)
1275 #include <sys/time.h>
1276
1277 #ifdef MINIGLUT_USE_LIBC
1278 #define sys_gettimeofday(tv, tz)        gettimeofday(tv, tz)
1279 #else
1280 static int sys_gettimeofday(struct timeval *tv, struct timezone *tz);
1281 #endif
1282
1283 static long get_msec(void)
1284 {
1285         static struct timeval tv0;
1286         struct timeval tv;
1287
1288         sys_gettimeofday(&tv, 0);
1289         if(tv0.tv_sec == 0 && tv0.tv_usec == 0) {
1290                 tv0 = tv;
1291                 return 0;
1292         }
1293         return (tv.tv_sec - tv0.tv_sec) * 1000 + (tv.tv_usec - tv0.tv_usec) / 1000;
1294 }
1295 #endif
1296 #ifdef _WIN32
1297 static long get_msec(void)
1298 {
1299         static long t0;
1300         long tm;
1301
1302 #ifdef MINIGLUT_NO_WINMM
1303         tm = GetTickCount();
1304 #else
1305         tm = timeGetTime();
1306 #endif
1307         if(!t0) {
1308                 t0 = tm;
1309                 return 0;
1310         }
1311         return tm - t0;
1312 }
1313 #endif
1314
1315 static void panic(const char *msg)
1316 {
1317         const char *end = msg;
1318         while(*end) end++;
1319         sys_write(2, msg, end - msg);
1320         sys_exit(1);
1321 }
1322
1323
1324 #ifdef MINIGLUT_USE_LIBC
1325 static void sys_exit(int status)
1326 {
1327         exit(status);
1328 }
1329
1330 static int sys_write(int fd, const void *buf, int count)
1331 {
1332         return write(fd, buf, count);
1333 }
1334
1335 static int sys_gettimeofday(struct timeval *tv, struct timezone *tz)
1336 {
1337         return gettimeofday(tv, tz);
1338 }
1339
1340 #else   /* !MINIGLUT_USE_LIBC */
1341
1342 #ifdef __GNUC__
1343 static void mglut_sincosf(float angle, float *sptr, float *cptr)
1344 {
1345         asm volatile(
1346                 "flds %2\n\t"
1347                 "fsincos\n\t"
1348                 "fstps %1\n\t"
1349                 "fstps %0\n\t"
1350                 : "=m"(*sptr), "=m"(*cptr)
1351                 : "m"(angle)
1352         );
1353 }
1354
1355 static float mglut_atan(float x)
1356 {
1357         float res;
1358         asm volatile(
1359                 "flds %1\n\t"
1360                 "fld1\n\t"
1361                 "fpatan\n\t"
1362                 "fstps %0\n\t"
1363                 : "=m"(res)
1364                 : "m"(x)
1365         );
1366         return res;
1367 }
1368 #endif
1369
1370 #ifdef _MSC_VER
1371 static void mglut_sincosf(float angle, float *sptr, float *cptr)
1372 {
1373         float s, c;
1374         __asm {
1375                 fld angle
1376                 fsincos
1377                 fstp c
1378                 fstp s
1379         }
1380         *sptr = s;
1381         *cptr = c;
1382 }
1383
1384 static float mglut_atan(float x)
1385 {
1386         float res;
1387         __asm {
1388                 fld x
1389                 fld1
1390                 fpatan
1391                 fstp res
1392         }
1393         return res;
1394 }
1395 #endif
1396
1397 #ifdef __WATCOMC__
1398 #pragma aux mglut_sincosf = \
1399         "fsincos" \
1400         "fstp dword ptr [edx]" \
1401         "fstp dword ptr [eax]" \
1402         parm[8087][eax][edx]    \
1403         modify[8087];
1404
1405 #pragma aux mglut_atan = \
1406         "fld1" \
1407         "fpatan" \
1408         parm[8087] \
1409         value[8087] \
1410         modify [8087];
1411 #endif
1412
1413 #ifdef __linux__
1414
1415 #ifdef __x86_64__
1416 static void sys_exit(int status)
1417 {
1418         asm volatile(
1419                 "syscall\n\t"
1420                 :: "a"(60), "D"(status));
1421 }
1422 static int sys_write(int fd, const void *buf, int count)
1423 {
1424         long res;
1425         asm volatile(
1426                 "syscall\n\t"
1427                 : "=a"(res)
1428                 : "a"(1), "D"(fd), "S"(buf), "d"(count));
1429         return res;
1430 }
1431 static int sys_gettimeofday(struct timeval *tv, struct timezone *tz)
1432 {
1433         int res;
1434         asm volatile(
1435                 "syscall\n\t"
1436                 : "=a"(res)
1437                 : "a"(96), "D"(tv), "S"(tz));
1438         return res;
1439 }
1440 #endif
1441 #ifdef __i386__
1442 static void sys_exit(int status)
1443 {
1444         asm volatile(
1445                 "int $0x80\n\t"
1446                 :: "a"(1), "b"(status));
1447 }
1448 static int sys_write(int fd, const void *buf, int count)
1449 {
1450         int res;
1451         asm volatile(
1452                 "int $0x80\n\t"
1453                 : "=a"(res)
1454                 : "a"(4), "b"(fd), "c"(buf), "d"(count));
1455         return res;
1456 }
1457 static int sys_gettimeofday(struct timeval *tv, struct timezone *tz)
1458 {
1459         int res;
1460         asm volatile(
1461                 "int $0x80\n\t"
1462                 : "=a"(res)
1463                 : "a"(78), "b"(tv), "c"(tz));
1464         return res;
1465 }
1466 #endif
1467
1468 #endif  /* __linux__ */
1469
1470 #ifdef _WIN32
1471 static void sys_exit(int status)
1472 {
1473         ExitProcess(status);
1474 }
1475 static int sys_write(int fd, const void *buf, int count)
1476 {
1477         unsigned long wrsz = 0;
1478
1479         HANDLE out = GetStdHandle(fd == 1 ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE);
1480         if(!WriteFile(out, buf, count, &wrsz, 0)) {
1481                 return -1;
1482         }
1483         return wrsz;
1484 }
1485 #endif  /* _WIN32 */
1486
1487 #endif  /* !MINIGLUT_USE_LIBC */