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