a72478123ca94141925b6f61379603dfb3ab3f6c
[xscreensaver] / hacks / glx / stonerview-view.c
1 /* StonerView: An eccentric visual toy.
2    Copyright 1998-2001 by Andrew Plotkin (erkyrath@eblong.com)
3    http://www.eblong.com/zarf/stonerview.html
4  
5    Permission to use, copy, modify, distribute, and sell this software and its
6    documentation for any purpose is hereby granted without fee, provided that
7    the above copyright notice appear in all copies and that both that
8    copyright notice and this permission notice appear in supporting
9    documentation.  No representations are made about the suitability of this
10    software for any purpose.  It is provided "as is" without express or 
11    implied warranty.
12 */
13
14 /* Ported away from GLUT (so that it can do `-root' and work with xscreensaver)
15    by Jamie Zawinski <jwz@jwz.org>, 22-Jan-2001.
16  */
17
18 #include "config.h"
19
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <math.h>
24
25 #include <X11/Xlib.h>
26 #include <X11/Xutil.h>
27 #include <X11/Xatom.h>
28 #include <X11/keysym.h>
29 #include <GL/gl.h>
30 #include <GL/glx.h>
31
32 #include "vroot.h"      /* handle virtual root windows --- very important */
33
34 #include "version.h"    /* get the xscreensaver package's version number */
35 #include "yarandom.h"   /* use xscreensaver's RNG */
36
37 #include "stonerview-osc.h"
38 #include "stonerview-move.h"
39
40 static char *progclass = "StonerView";
41 static char *progname = 0;
42
43 #define FRAMERATE (50) /* milliseconds per frame */
44
45 static GLfloat view_rotx = -45.0, view_roty = 0.0, view_rotz = 0.0;
46 static GLfloat view_scale = 4.0;
47 /* static GLint prevtime = 0; / * for timing */
48
49 static void setup_window(void);
50
51 void win_draw(void);
52 static void win_reshape(int width, int height);
53 /* static void win_visible(int vis); */
54 /* static void win_idle(void); */
55 static void handle_events(void);
56
57 static Display *dpy;
58 static Window window;
59 static int wireframe = 0;
60
61 static Atom XA_WM_PROTOCOLS, XA_WM_DELETE_WINDOW;
62
63
64 static void
65 usage (void)
66 {
67   fprintf (stderr,
68            "usage: %s [--geom =WxH+X+Y | --fullscreen | --root] [--wire]\n",
69            progname);
70   exit (1);
71 }
72
73
74 /* mostly lifted from xscreensaver/utils/visual.c... */
75 static int
76 visual_depth (Display *dpy, int screen, Visual *visual)
77 {
78   XVisualInfo vi_in, *vi_out;
79   int out_count, d;
80   vi_in.screen = screen;
81   vi_in.visualid = XVisualIDFromVisual (visual);
82   vi_out = XGetVisualInfo (dpy, VisualScreenMask|VisualIDMask,
83                            &vi_in, &out_count);
84   if (! vi_out) abort ();
85   d = vi_out [0].depth;
86   XFree ((char *) vi_out);
87   return d;
88 }
89
90
91 /* mostly lifted from xscreensaver/hacks/screenhack.c... */
92 static char *make_title_string (void)
93 {
94   char version[255];
95   char *v = (char *) strdup(strchr(screensaver_id, ' '));
96   char *s1, *s2, *s3, *s4;
97   s1 = (char *) strchr(v,  ' '); s1++;
98   s2 = (char *) strchr(s1, ' ');
99   s3 = (char *) strchr(v,  '('); s3++;
100   s4 = (char *) strchr(s3, ')');
101   *s2 = 0;
102   *s4 = 0;
103   sprintf (version, "%s: from the XScreenSaver %s distribution (%s.)",
104            progclass, s1, s3);
105   return (strdup(version));
106 }
107
108
109 int init_view(int *argc, char *argv[])
110 {
111   int ix;
112   int fullscreen = 0;
113   int on_root = 0;
114
115   int undef = -65536;
116   int x = undef, y = undef;
117   int w = 500, h = 500;
118   char *dpystr = (char *) getenv ("DISPLAY");
119   char *geom = 0;
120   int screen;
121   Visual *visual;
122   XWindowAttributes xgwa;
123   XSetWindowAttributes xswa;
124   unsigned long xswa_mask = 0;
125   XSizeHints hints;
126   GLXContext glx_context = 0;
127
128   memset (&hints, 0, sizeof(hints));
129
130   for (ix=1; ix<*argc; ix++)
131     {
132       if (argv[ix][0] == '-' && argv[ix][1] == '-')
133         argv[ix]++;
134       if (!strcmp(argv[ix], "-geometry") ||
135           !strcmp(argv[ix], "-geom"))
136         {
137           if (on_root || fullscreen) usage();
138           geom = argv[++ix];
139         }
140       else if (!strcmp(argv[ix], "-display") ||
141                !strcmp(argv[ix], "-disp") ||
142                !strcmp(argv[ix], "-dpy") ||
143                !strcmp(argv[ix], "-d"))
144         dpystr = argv[++ix];
145       else if (!strcmp(argv[ix], "-root"))
146         {
147           if (geom || fullscreen) usage();
148           on_root = 1;
149         }
150       else if (!strcmp(argv[ix], "-fullscreen") ||
151                !strcmp(argv[ix], "-full"))
152         {
153           if (on_root || geom) usage();
154           fullscreen = 1;
155         }
156       else if (!strcmp(argv[ix], "-wireframe") ||
157                !strcmp(argv[ix], "-wire"))
158         {
159           wireframe = 1;
160         }
161       else
162         {
163           usage();
164         }
165     }
166
167   dpy = XOpenDisplay (dpystr);
168   if (!dpy) exit (1);
169
170   screen = DefaultScreen (dpy);
171
172   XA_WM_PROTOCOLS = XInternAtom (dpy, "WM_PROTOCOLS", False);
173   XA_WM_DELETE_WINDOW = XInternAtom (dpy, "WM_DELETE_WINDOW", False);
174
175   if (on_root)
176     {
177       window = RootWindow (dpy, screen);
178       XGetWindowAttributes (dpy, window, &xgwa);
179       visual = xgwa.visual;
180       w = xgwa.width;
181       h = xgwa.height;
182     }
183   else
184     {
185       int ww = WidthOfScreen (DefaultScreenOfDisplay (dpy));
186       int hh = HeightOfScreen (DefaultScreenOfDisplay (dpy));
187
188       if (fullscreen)
189         {
190           w = ww;
191           h = hh;
192         }
193       else if (geom)
194         {
195           /* Since we're not linking against Xt or GLUT, we get to parse
196              the `-geometry' argument ourselves.  YAY.
197            */
198           char c;
199           if      (4 == sscanf (geom, "=%dx%d+%d+%d%c", &w, &h, &x, &y, &c))
200             ;
201           else if (4 == sscanf (geom, "=%dx%d-%d+%d%c", &w, &h, &x, &y, &c))
202             x = ww-w-x;
203           else if (4 == sscanf (geom, "=%dx%d+%d-%d%c", &w, &h, &x, &y, &c))
204             y = hh-h-y;
205           else if (4 == sscanf (geom, "=%dx%d-%d-%d%c", &w, &h, &x, &y, &c))
206             x = ww-w-x, y = hh-h-y;
207           else if (2 == sscanf (geom, "=%dx%d%c", &w, &h, &c))
208             ;
209           else if (2 == sscanf (geom, "+%d+%d%c", &x, &y, &c))
210             ;
211           else if (2 == sscanf (geom, "-%d+%d%c", &x, &y, &c))
212             x = ww-w-x;
213           else if (2 == sscanf (geom, "+%d-%d%c", &x, &y, &c))
214             y = hh-h-y;
215           else if (2 == sscanf (geom, "-%d-%d%c", &x, &y, &c))
216             x = ww-w-x, y = hh-h-y;
217           else
218             {
219               fprintf (stderr, "%s: unparsable geometry: %s\n",
220                        progname, geom);
221               exit (1);
222             }
223
224           hints.flags = USSize;
225           hints.width = w;
226           hints.height = h;
227           if (x != undef && y != undef)
228             {
229               hints.flags |= USPosition;
230               hints.x = x;
231               hints.y = y;
232             }
233         }
234
235       /* Pick a good GL visual */
236       {
237 # define R GLX_RED_SIZE
238 # define G GLX_GREEN_SIZE
239 # define B GLX_BLUE_SIZE
240 # define D GLX_DEPTH_SIZE
241 # define I GLX_BUFFER_SIZE
242 # define DB GLX_DOUBLEBUFFER
243
244         int attrs[][20] = {
245           { GLX_RGBA, R, 8, G, 8, B, 8, D, 8, DB, 0 }, /* rgb double */
246           { GLX_RGBA, R, 4, G, 4, B, 4, D, 4, DB, 0 },
247           { GLX_RGBA, R, 2, G, 2, B, 2, D, 2, DB, 0 },
248           { GLX_RGBA, R, 8, G, 8, B, 8, D, 8,     0 }, /* rgb single */
249           { GLX_RGBA, R, 4, G, 4, B, 4, D, 4,     0 },
250           { GLX_RGBA, R, 2, G, 2, B, 2, D, 2,     0 },
251           { I, 8,                       D, 8, DB, 0 }, /* cmap double */
252           { I, 4,                       D, 4, DB, 0 },
253           { I, 8,                       D, 8,     0 }, /* cmap single */
254           { I, 4,                       D, 4,     0 },
255           { GLX_RGBA, R, 1, G, 1, B, 1, D, 1,     0 }  /* monochrome */
256         };
257
258         int i;
259         for (i = 0; i < sizeof(attrs)/sizeof(*attrs); i++)
260           {
261             XVisualInfo *vi = glXChooseVisual (dpy, screen, attrs[i]);
262             if (vi)
263               {
264                 visual = vi->visual;
265                 XFree (vi);
266                 break;
267               }
268           }
269         if (!visual)
270           {
271             fprintf (stderr, "%s: unable to find a GL visual\n", progname);
272             exit (1);
273           }
274       }
275
276
277       if (x == undef) x = 0;
278       if (y == undef) y = 0;
279
280       xswa_mask = (CWEventMask | CWColormap |
281                    CWBackPixel | CWBackingPixel | CWBorderPixel );
282       xswa.colormap = XCreateColormap (dpy, RootWindow (dpy, screen),
283                                        visual, AllocNone);
284       xswa.background_pixel = BlackPixel (dpy, screen);
285       xswa.backing_pixel = xswa.background_pixel;
286       xswa.border_pixel = xswa.background_pixel;
287       xswa.event_mask = (KeyPressMask | ButtonPressMask | StructureNotifyMask);
288
289       window = XCreateWindow (dpy, RootWindow (dpy, screen),
290                               x, y, w, h, 0,
291                               visual_depth (dpy, screen, visual),
292                               InputOutput, visual,
293                               xswa_mask, &xswa);
294
295       {
296         XWMHints wmhints;
297
298         XTextProperty tp1, tp2;
299         char *v = make_title_string ();
300         XStringListToTextProperty (&v, 1, &tp1);
301         XStringListToTextProperty (&progclass, 1, &tp2);
302         wmhints.flags = InputHint;
303         wmhints.input = True;
304         XSetWMProperties (dpy, window, &tp1, &tp2, argv, *argc, &hints,
305                           &wmhints, 0);
306         free (v);
307       }
308
309       XChangeProperty (dpy, window, XA_WM_PROTOCOLS, XA_ATOM, 32,
310                        PropModeReplace,
311                        (unsigned char *) &XA_WM_DELETE_WINDOW, 1);
312
313       XMapRaised (dpy, window);
314       XSync (dpy, False);
315     }
316
317
318   /* Now hook up to GLX */
319   {
320     XVisualInfo vi_in, *vi_out;
321     int out_count;
322     vi_in.screen = screen;
323     vi_in.visualid = XVisualIDFromVisual (visual);
324       vi_out = XGetVisualInfo (dpy, VisualScreenMask|VisualIDMask,
325                                &vi_in, &out_count);
326       if (! vi_out) abort ();
327
328       glx_context = glXCreateContext (dpy, vi_out, 0, GL_TRUE);
329       XFree((char *) vi_out);
330
331       if (!glx_context)
332         {
333           fprintf(stderr, "%s: couldn't create GL context for root window.\n",
334                   progname);
335           exit(1);
336         }
337
338       glXMakeCurrent (dpy, window, glx_context);
339   }
340
341   setup_window();
342   win_reshape(w, h);
343
344   return 1;
345 }
346
347 static void setup_window()
348 {
349   glEnable(GL_CULL_FACE);
350   glEnable(GL_LIGHTING);
351   glEnable(GL_LIGHT0);
352   glEnable(GL_DEPTH_TEST);
353
354   glEnable(GL_NORMALIZE);
355 }
356
357 /* callback: draw everything */
358 void win_draw(void)
359 {
360   int ix;
361   static GLfloat white[] = { 1.0, 1.0, 1.0, 1.0 };
362
363   glDrawBuffer(GL_BACK);
364
365   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
366
367   glPushMatrix();
368   glScalef(view_scale, view_scale, view_scale);
369   glRotatef(view_rotx, 1.0, 0.0, 0.0);
370   glRotatef(view_roty, 0.0, 1.0, 0.0);
371   glRotatef(view_rotz, 0.0, 0.0, 1.0);
372
373   for (ix=0; ix<NUM_ELS; ix++) {
374     elem_t *el = &elist[ix];
375
376     glPushMatrix();
377
378     glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE,
379                  (wireframe ? white : el->col));
380     glShadeModel(GL_FLAT);
381     glBegin(wireframe ? GL_LINE_LOOP : GL_QUADS);
382
383     glNormal3f(0.0, 0.0, 1.0);
384     glVertex3f(el->pos[0] - el->vervec[0], el->pos[1] - el->vervec[1], 
385       el->pos[2]);
386     glVertex3f(el->pos[0] + el->vervec[1], el->pos[1] - el->vervec[0], 
387       el->pos[2]);
388     glVertex3f(el->pos[0] + el->vervec[0], el->pos[1] + el->vervec[1], 
389       el->pos[2]);
390     glVertex3f(el->pos[0] - el->vervec[1], el->pos[1] + el->vervec[0], 
391       el->pos[2]);
392
393     glEnd();
394
395     glPopMatrix();
396   }
397
398   glPopMatrix();
399
400   glFinish();
401   glXSwapBuffers(dpy, window);
402
403   handle_events();
404 }
405
406
407 /* callback: new window size or exposure */
408 static void win_reshape(int width, int height)
409 {
410   GLfloat h = (GLfloat) height / (GLfloat) width;
411
412   glViewport(0, 0, (GLint) width, (GLint) height);
413   glMatrixMode(GL_PROJECTION);
414   glLoadIdentity();
415   glFrustum(-1.0, 1.0, -h, h, 5.0, 60.0);
416   glMatrixMode(GL_MODELVIEW);
417   glLoadIdentity();
418   glTranslatef(0.0, 0.0, -40.0);
419 }
420
421
422 static void
423 handle_events (void)
424 {
425   while (XPending (dpy))
426     {
427       XEvent E;
428       XEvent *event = &E;
429       XNextEvent (dpy, event);
430       switch (event->xany.type)
431         {
432         case ConfigureNotify:
433           {
434             XWindowAttributes xgwa;
435             XGetWindowAttributes (dpy, window, &xgwa);
436             win_reshape (xgwa.width, xgwa.height);
437             break;
438           }
439         case KeyPress:
440           {
441             KeySym keysym;
442             char c = 0;
443             XLookupString (&event->xkey, &c, 1, &keysym, 0);
444             if (c == 'q' ||
445                 c == 'Q' ||
446                 c == 3 ||       /* ^C */
447                 c == 27)        /* ESC */
448               exit (0);
449             else if (! (keysym >= XK_Shift_L && keysym <= XK_Hyper_R))
450               XBell (dpy, 0);  /* beep for non-chord keys */
451           }
452           break;
453         case ButtonPress:
454           XBell (dpy, 0);
455           break;
456         case ClientMessage:
457           {
458             if (event->xclient.message_type != XA_WM_PROTOCOLS)
459               {
460                 char *s = XGetAtomName(dpy, event->xclient.message_type);
461                 if (!s) s = "(null)";
462                 fprintf (stderr, "%s: unknown ClientMessage %s received!\n",
463                          progname, s);
464               }
465             else if (event->xclient.data.l[0] != XA_WM_DELETE_WINDOW)
466               {
467                 char *s1 = XGetAtomName(dpy, event->xclient.message_type);
468                 char *s2 = XGetAtomName(dpy, event->xclient.data.l[0]);
469                 if (!s1) s1 = "(null)";
470                 if (!s2) s2 = "(null)";
471                 fprintf (stderr,"%s: unknown ClientMessage %s[%s] received!\n",
472                          progname, s1, s2);
473               }
474             else
475               {
476                 exit (0);
477               }
478           }
479           break;
480         }
481     }
482 }