http://www.jwz.org/xscreensaver/xscreensaver-5.12.tar.gz
[xscreensaver] / hacks / glx / gltext.c
1 /* gltext, Copyright (c) 2001-2008 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  */
11
12 #define DEFAULTS        "*delay:        20000        \n" \
13                         "*showFPS:      False        \n" \
14                         "*wireframe:    False        \n" \
15
16 # define refresh_text 0
17 # define release_text 0
18 #define SMOOTH_TUBE       /* whether to have smooth or faceted tubes */
19
20 #ifdef SMOOTH_TUBE
21 # define TUBE_FACES  12   /* how densely to render tubes */
22 #else
23 # define TUBE_FACES  8
24 #endif
25
26
27 #undef countof
28 #define countof(x) (sizeof((x))/sizeof((*x)))
29
30 #include "xlockmore.h"
31 #include "colors.h"
32 #include "tube.h"
33 #include "rotator.h"
34 #include "gltrackball.h"
35
36 #include <ctype.h>
37
38 #ifdef USE_GL /* whole file */
39
40 #define DEF_TEXT        "(default)"
41 #define DEF_PROGRAM     "(default)"
42 #define DEF_SPIN        "XYZ"
43 #define DEF_WANDER      "True"
44 #define DEF_FACE_FRONT  "True"
45
46 #ifdef HAVE_UNAME
47 # include <sys/utsname.h>
48 #endif /* HAVE_UNAME */
49
50 #include "glutstroke.h"
51 #include "glut_roman.h"
52 #define GLUT_FONT (&glutStrokeRoman)
53
54
55 typedef struct {
56   GLXContext *glx_context;
57   rotator *rot, *rot2;
58   trackball_state *trackball;
59   Bool button_down_p;
60   Bool spinx, spiny, spinz;
61
62   GLuint text_list;
63
64   int ncolors;
65   XColor *colors;
66   int ccolor;
67
68   char *text;
69   int reload;
70
71   time_t last_update;
72
73 } text_configuration;
74
75 static text_configuration *tps = NULL;
76
77 static char *text_fmt;
78 static char *program_str;
79 static char *do_spin;
80 static Bool do_wander;
81 static Bool face_front_p;
82
83 static XrmOptionDescRec opts[] = {
84   { "-text",    ".text",      XrmoptionSepArg, 0 },
85   { "-program", ".program",   XrmoptionSepArg, 0 },
86   { "-spin",    ".spin",      XrmoptionSepArg, 0 },
87   { "+spin",    ".spin",      XrmoptionNoArg, "" },
88   { "-wander",  ".wander",    XrmoptionNoArg, "True" },
89   { "+wander",  ".wander",    XrmoptionNoArg, "False" },
90   { "-front",   ".faceFront", XrmoptionNoArg, "True" },
91   { "+front",   ".faceFront", XrmoptionNoArg, "False" }
92 };
93
94 static argtype vars[] = {
95   {&text_fmt,     "text",      "Text",      DEF_TEXT,       t_String},
96   {&program_str,  "program",   "Program",   DEF_PROGRAM,    t_String},
97   {&do_spin,      "spin",      "Spin",      DEF_SPIN,       t_String},
98   {&do_wander,    "wander",    "Wander",    DEF_WANDER,     t_Bool},
99   {&face_front_p, "faceFront", "FaceFront", DEF_FACE_FRONT, t_Bool},
100 };
101
102 ENTRYPOINT ModeSpecOpt text_opts = {countof(opts), opts, countof(vars), vars, NULL};
103
104
105 /* Window management, etc
106  */
107 ENTRYPOINT void
108 reshape_text (ModeInfo *mi, int width, int height)
109 {
110   GLfloat h = (GLfloat) height / (GLfloat) width;
111
112   glViewport (0, 0, (GLint) width, (GLint) height);
113
114   glMatrixMode(GL_PROJECTION);
115   glLoadIdentity();
116   gluPerspective (30.0, 1/h, 1.0, 100.0);
117
118   glMatrixMode(GL_MODELVIEW);
119   glLoadIdentity();
120   gluLookAt( 0.0, 0.0, 30.0,
121              0.0, 0.0, 0.0,
122              0.0, 1.0, 0.0);
123
124   glClear(GL_COLOR_BUFFER_BIT);
125 }
126
127
128 static void
129 gl_init (ModeInfo *mi)
130 {
131   text_configuration *tp = &tps[MI_SCREEN(mi)];
132   int wire = MI_IS_WIREFRAME(mi);
133
134   static const GLfloat pos[4] = {5.0, 5.0, 10.0, 1.0};
135
136   if (!wire)
137     {
138       glLightfv(GL_LIGHT0, GL_POSITION, pos);
139       glEnable(GL_CULL_FACE);
140       glEnable(GL_LIGHTING);
141       glEnable(GL_LIGHT0);
142       glEnable(GL_DEPTH_TEST);
143     }
144
145   tp->text_list = glGenLists (1);
146   glNewList (tp->text_list, GL_COMPILE);
147   glEndList ();
148 }
149
150
151 /* The GLUT font only has ASCII characters in them, so do what we can to
152    convert Latin1 characters to the nearest ASCII equivalent... 
153  */
154 static void
155 latin1_to_ascii (char *s)
156 {
157   unsigned char *us = (unsigned char *) s;
158   const unsigned char ascii[95] = {
159     '!', 'C', '#', '#', 'Y', '|', 'S', '_', 'C', '?', '<', '=', '-', 'R', '_',
160     '?', '?', '2', '3', '\'','u', 'P', '.', ',', '1', 'o', '>', '?', '?', '?',
161     '?', 'A', 'A', 'A', 'A', 'A', 'A', 'E', 'C', 'E', 'E', 'E', 'E', 'I', 'I',
162     'I', 'I', 'D', 'N', 'O', 'O', 'O', 'O', 'O', 'x', '0', 'U', 'U', 'U', 'U',
163     'Y', 'p', 'S', 'a', 'a', 'a', 'a', 'a', 'a', 'e', 'c', 'e', 'e', 'e', 'e',
164     'i', 'i', 'i', 'i', 'o', 'n', 'o', 'o', 'o', 'o', 'o', '/', 'o', 'u', 'u',
165     'u', 'u', 'y', 'p', 'y' };
166   while (*us)
167     {
168       if (*us >= 161)
169         *us = ascii[*us - 161];
170       else if (*us > 127)
171         *us = '?';
172       us++;
173     }
174 }
175
176
177 static void
178 parse_text (ModeInfo *mi)
179 {
180   text_configuration *tp = &tps[MI_SCREEN(mi)];
181
182   if (tp->text) free (tp->text);
183
184   if (program_str && *program_str && !!strcmp(program_str, "(default)"))
185     {
186       FILE *p;
187       int i;
188       char buf[1024];
189       sprintf (buf, "( %.900s ) 2>&1", program_str);
190       p = popen (buf, "r");
191       if (! p)
192         sprintf (buf, "error running '%.900s'", program_str);
193       else
194         {
195           char *out = buf;
196           char *end = out + sizeof(buf) - 1;
197           int n;
198           do {
199             n = fread (out, 1, end - out, p);
200             if (n > 0)
201               out += n;
202             *out = 0;
203           } while (n > 0);
204           fclose (p);
205         }
206
207       /* Truncate it to 10 lines */
208       {
209         char *s = buf;
210         for (i = 0; i < 10; i++)
211           if (s && (s = strchr (s, '\n')))
212             s++;
213         if (s) *s = 0;
214       }
215
216       tp->text = strdup (buf);
217       tp->reload = 5;
218     }
219   else if (!text_fmt || !*text_fmt || !strcmp(text_fmt, "(default)"))
220     {
221 # ifdef HAVE_UNAME
222       struct utsname uts;
223
224       if (uname (&uts) < 0)
225         {
226           tp->text = strdup("uname() failed");
227         }
228       else
229         {
230           char *s;
231           if ((s = strchr(uts.nodename, '.')))
232             *s = 0;
233           tp->text = (char *) malloc(strlen(uts.nodename) +
234                                      strlen(uts.sysname) +
235                                      strlen(uts.version) +
236                                      strlen(uts.release) + 10);
237 #  if defined(_AIX)
238           sprintf(tp->text, "%s\n%s %s.%s",
239                   uts.nodename, uts.sysname, uts.version, uts.release);
240 #  elif defined(__APPLE__)  /* MacOS X + XDarwin */
241           {
242             const char *file = 
243               "/System/Library/CoreServices/SystemVersion.plist";
244             FILE *f = fopen (file, "r");
245             char *pbv = 0, *pn = 0, *puvv = 0;
246             if (f) {
247               char *s, buf[255];
248
249               while (fgets (buf, sizeof(buf)-1, f)) {
250 #               define GRAB(S,V)                                        \
251                 if (strstr(buf, S)) {                                   \
252                   fgets (buf, sizeof(buf)-1, f);                        \
253                   if ((s = strchr (buf, '>'))) V = strdup(s+1);         \
254                   if ((s = strchr (V, '<'))) *s = 0;                    \
255                 }
256                 GRAB ("ProductName", pn)
257                 GRAB ("ProductBuildVersion", pbv)
258                 GRAB ("ProductUserVisibleVersion", puvv)
259 #               undef GRAB
260               }
261             }
262             if (pbv)
263               sprintf (tp->text, "%s\n%s\n%s\n%s", 
264                        uts.nodename, pn, puvv, uts.machine);
265             else
266               sprintf(tp->text, "%s\n%s %s\n%s",
267                       uts.nodename, uts.sysname, uts.release, uts.machine);
268           }
269 #  else
270           sprintf(tp->text, "%s\n%s %s",
271                   uts.nodename, uts.sysname, uts.release);
272 #  endif /* special system types */
273         }
274 # else  /* !HAVE_UNAME */
275 #  ifdef VMS
276       tp->text = strdup(getenv("SYS$NODE"));
277 #  else
278       tp->text = strdup("*  *\n*  *  *\nxscreensaver\n*  *  *\n*  *");
279 #  endif
280 # endif /* !HAVE_UNAME */
281     }
282   else if (!strchr (text_fmt, '%'))
283     {
284       tp->text = strdup (text_fmt);
285     }
286   else
287     {
288       time_t now = time ((time_t *) 0);
289       struct tm *tm = localtime (&now);
290       int L = strlen(text_fmt) + 100;
291       tp->text = (char *) malloc (L);
292       *tp->text = 0;
293       strftime (tp->text, L-1, text_fmt, tm);
294       if (!*tp->text)
295         sprintf (tp->text, "strftime error:\n%s", text_fmt);
296       tp->reload = 1;
297     }
298
299   latin1_to_ascii (tp->text);
300 }
301
302
303 ENTRYPOINT Bool
304 text_handle_event (ModeInfo *mi, XEvent *event)
305 {
306   text_configuration *tp = &tps[MI_SCREEN(mi)];
307
308   if (event->xany.type == ButtonPress &&
309       event->xbutton.button == Button1)
310     {
311       tp->button_down_p = True;
312       gltrackball_start (tp->trackball,
313                          event->xbutton.x, event->xbutton.y,
314                          MI_WIDTH (mi), MI_HEIGHT (mi));
315       return True;
316     }
317   else if (event->xany.type == ButtonRelease &&
318            event->xbutton.button == Button1)
319     {
320       tp->button_down_p = False;
321       return True;
322     }
323   else if (event->xany.type == ButtonPress &&
324            (event->xbutton.button == Button4 ||
325             event->xbutton.button == Button5 ||
326             event->xbutton.button == Button6 ||
327             event->xbutton.button == Button7))
328     {
329       gltrackball_mousewheel (tp->trackball, event->xbutton.button, 10,
330                               !!event->xbutton.state);
331       return True;
332     }
333   else if (event->xany.type == MotionNotify &&
334            tp->button_down_p)
335     {
336       gltrackball_track (tp->trackball,
337                          event->xmotion.x, event->xmotion.y,
338                          MI_WIDTH (mi), MI_HEIGHT (mi));
339       return True;
340     }
341
342   return False;
343 }
344
345
346 ENTRYPOINT void 
347 init_text (ModeInfo *mi)
348 {
349   text_configuration *tp;
350   int i;
351
352   if (!tps) {
353     tps = (text_configuration *)
354       calloc (MI_NUM_SCREENS(mi), sizeof (text_configuration));
355     if (!tps) {
356       fprintf(stderr, "%s: out of memory\n", progname);
357       exit(1);
358     }
359   }
360
361   tp = &tps[MI_SCREEN(mi)];
362
363   if ((tp->glx_context = init_GL(mi)) != NULL) {
364     gl_init(mi);
365     reshape_text (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
366   }
367
368   {
369     double spin_speed   = 0.5;
370     double wander_speed = 0.02;
371     double tilt_speed   = 0.03;
372     double spin_accel   = 0.5;
373
374     char *s = do_spin;
375     while (*s)
376       {
377         if      (*s == 'x' || *s == 'X') tp->spinx = True;
378         else if (*s == 'y' || *s == 'Y') tp->spiny = True;
379         else if (*s == 'z' || *s == 'Z') tp->spinz = True;
380         else if (*s == '0') ;
381         else
382           {
383             fprintf (stderr,
384          "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
385                      progname, do_spin);
386             exit (1);
387           }
388         s++;
389       }
390
391     tp->rot = make_rotator (tp->spinx ? spin_speed : 0,
392                             tp->spiny ? spin_speed : 0,
393                             tp->spinz ? spin_speed : 0,
394                             spin_accel,
395                             do_wander ? wander_speed : 0,
396                             False);
397     tp->rot2 = (face_front_p
398                 ? make_rotator (0, 0, 0, 0, tilt_speed, True)
399                 : 0);
400     tp->trackball = gltrackball_init ();
401   }
402
403   tp->ncolors = 255;
404   tp->colors = (XColor *) calloc(tp->ncolors, sizeof(XColor));
405   make_smooth_colormap (0, 0, 0,
406                         tp->colors, &tp->ncolors,
407                         False, 0, False);
408
409   /* brighter colors, please... */
410   for (i = 0; i < tp->ncolors; i++)
411     {
412       tp->colors[i].red   = (tp->colors[i].red   / 2) + 32767;
413       tp->colors[i].green = (tp->colors[i].green / 2) + 32767;
414       tp->colors[i].blue  = (tp->colors[i].blue  / 2) + 32767;
415     }
416
417   parse_text (mi);
418
419 }
420
421
422 static int
423 fill_character (GLUTstrokeFont font, int c, Bool wire, int *polysP)
424 {
425   GLfloat tube_width = 10;
426
427   const StrokeCharRec *ch;
428   const StrokeRec *stroke;
429   const CoordRec *coord;
430   StrokeFontPtr fontinfo;
431   int i, j;
432
433   fontinfo = (StrokeFontPtr) font;
434
435   if (c < 0 || c >= fontinfo->num_chars)
436     return 0;
437   ch = &(fontinfo->ch[c]);
438   if (ch)
439     {
440       GLfloat lx=0, ly=0;
441       for (i = ch->num_strokes, stroke = ch->stroke;
442            i > 0; i--, stroke++) {
443         for (j = stroke->num_coords, coord = stroke->coord;
444              j > 0; j--, coord++)
445           {
446 # ifdef SMOOTH_TUBE
447             int smooth = True;
448 # else
449             int smooth = False;
450 # endif
451             if (j != stroke->num_coords)
452               *polysP += tube (lx,       ly,       0,
453                                coord->x, coord->y, 0,
454                                tube_width,
455                                tube_width * 0.15,
456                                TUBE_FACES, smooth, True, wire);
457             lx = coord->x;
458             ly = coord->y;
459           }
460       }
461       return (int) (ch->right + tube_width);
462     }
463   return 0;
464 }
465
466
467 static int
468 text_extents (const char *string, int *wP, int *hP)
469 {
470   const char *s, *start;
471   int line_height = GLUT_FONT->top - GLUT_FONT->bottom;
472   int lines = 0;
473   *wP = 0;
474   *hP = 0;
475   start = string;
476   s = start;
477   while (1)
478     if (*s == '\n' || *s == 0)
479       {
480         int w = 0;
481         while (start < s)
482           {
483             w += glutStrokeWidth(GLUT_FONT, *start);
484             start++;
485           }
486         start = s+1;
487
488         if (w > *wP) *wP = w;
489         *hP += line_height;
490         lines++;
491         if (*s == 0) break;
492         s++;
493       }
494     else
495       s++;
496
497   return lines;
498 }
499
500
501 static unsigned long
502 fill_string (const char *string, Bool wire)
503 {
504   int polys = 0;
505   const char *s, *start;
506   int line_height = GLUT_FONT->top - GLUT_FONT->bottom;
507   int off;
508   GLfloat x = 0, y = 0;
509
510   int ow, oh;
511   text_extents (string, &ow, &oh);
512
513   y = oh / 2 - line_height;
514
515   start = string;
516   s = start;
517   while (1)
518     if (*s == '\n' || *s == 0)
519       {
520         int line_w = 0;
521         const char *s2;
522         const char *lstart = start;
523         const char *lend = s;
524
525         /* strip off whitespace at beginning and end of line
526            (since we're centering.) */
527         while (lend > lstart && isspace(lend[-1]))
528           lend--;
529         while (lstart < lend && isspace(*lstart))
530           lstart++;
531
532         for (s2 = lstart; s2 < lend; s2++)
533           line_w += glutStrokeWidth (GLUT_FONT, *s2);
534
535         x = (-ow/2) + ((ow-line_w)/2);
536         for (s2 = lstart; s2 < lend; s2++)
537           {
538             glPushMatrix();
539             glTranslatef(x, y, 0);
540             off = fill_character (GLUT_FONT, *s2, wire, &polys);
541             x += off;
542             glPopMatrix();
543           }
544
545         start = s+1;
546
547         y -= line_height;
548         if (*s == 0) break;
549         s++;
550       }
551     else
552       s++;
553   return polys;
554 }
555
556
557 ENTRYPOINT void
558 draw_text (ModeInfo *mi)
559 {
560   text_configuration *tp = &tps[MI_SCREEN(mi)];
561   Display *dpy = MI_DISPLAY(mi);
562   Window window = MI_WINDOW(mi);
563   int wire = MI_IS_WIREFRAME(mi);
564
565   GLfloat white[4] = {1.0, 1.0, 1.0, 1.0};
566   GLfloat color[4] = {0.0, 0.0, 0.0, 1.0};
567
568   if (!tp->glx_context)
569     return;
570
571   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(tp->glx_context));
572
573   if (tp->reload)
574     {
575       if (time ((time_t *) 0) >= tp->last_update + tp->reload)
576         {
577           parse_text (mi);
578           tp->last_update = time ((time_t *) 0);
579         }
580     }
581
582   glShadeModel(GL_SMOOTH);
583
584   glEnable(GL_DEPTH_TEST);
585   glEnable(GL_NORMALIZE);
586   glEnable(GL_CULL_FACE);
587
588
589   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
590
591   glPushMatrix ();
592
593   glScalef(1.1, 1.1, 1.1);
594
595   {
596     double x, y, z;
597     get_position (tp->rot, &x, &y, &z, !tp->button_down_p);
598     glTranslatef((x - 0.5) * 8,
599                  (y - 0.5) * 8,
600                  (z - 0.5) * 8);
601
602     gltrackball_rotate (tp->trackball);
603
604     if (face_front_p)
605       {
606         double max = 90;
607         get_position (tp->rot2, &x, &y, &z, !tp->button_down_p);
608         if (tp->spinx) glRotatef (max/2 - x*max, 1, 0, 0);
609         if (tp->spiny) glRotatef (max/2 - y*max, 0, 1, 0);
610         if (tp->spinz) glRotatef (max/2 - z*max, 0, 0, 1);
611       }
612     else
613       {
614         get_rotation (tp->rot, &x, &y, &z, !tp->button_down_p);
615         glRotatef (x * 360, 1, 0, 0);
616         glRotatef (y * 360, 0, 1, 0);
617         glRotatef (z * 360, 0, 0, 1);
618       }
619   }
620
621
622   glColor4fv (white);
623
624   color[0] = tp->colors[tp->ccolor].red   / 65536.0;
625   color[1] = tp->colors[tp->ccolor].green / 65536.0;
626   color[2] = tp->colors[tp->ccolor].blue  / 65536.0;
627   tp->ccolor++;
628   if (tp->ccolor >= tp->ncolors) tp->ccolor = 0;
629
630   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
631
632   glScalef(0.01, 0.01, 0.01);
633
634   mi->polygon_count = fill_string(tp->text, wire);
635
636   glPopMatrix ();
637
638   if (mi->fps_p) do_fps (mi);
639   glFinish();
640
641   glXSwapBuffers(dpy, window);
642 }
643
644 XSCREENSAVER_MODULE_2 ("GLText", gltext, text)
645
646 #endif /* USE_GL */