48ab1c07a2c51dbf0f9d19f49bdc6cc6e26106aa
[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     tp = &tps[MI_SCREEN(mi)];
361   }
362
363   tp = &tps[MI_SCREEN(mi)];
364
365   if ((tp->glx_context = init_GL(mi)) != NULL) {
366     gl_init(mi);
367     reshape_text (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
368   }
369
370   {
371     double spin_speed   = 0.5;
372     double wander_speed = 0.02;
373     double tilt_speed   = 0.03;
374     double spin_accel   = 0.5;
375
376     char *s = do_spin;
377     while (*s)
378       {
379         if      (*s == 'x' || *s == 'X') tp->spinx = True;
380         else if (*s == 'y' || *s == 'Y') tp->spiny = True;
381         else if (*s == 'z' || *s == 'Z') tp->spinz = True;
382         else if (*s == '0') ;
383         else
384           {
385             fprintf (stderr,
386          "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
387                      progname, do_spin);
388             exit (1);
389           }
390         s++;
391       }
392
393     tp->rot = make_rotator (tp->spinx ? spin_speed : 0,
394                             tp->spiny ? spin_speed : 0,
395                             tp->spinz ? spin_speed : 0,
396                             spin_accel,
397                             do_wander ? wander_speed : 0,
398                             False);
399     tp->rot2 = (face_front_p
400                 ? make_rotator (0, 0, 0, 0, tilt_speed, True)
401                 : 0);
402     tp->trackball = gltrackball_init ();
403   }
404
405   tp->ncolors = 255;
406   tp->colors = (XColor *) calloc(tp->ncolors, sizeof(XColor));
407   make_smooth_colormap (0, 0, 0,
408                         tp->colors, &tp->ncolors,
409                         False, 0, False);
410
411   /* brighter colors, please... */
412   for (i = 0; i < tp->ncolors; i++)
413     {
414       tp->colors[i].red   = (tp->colors[i].red   / 2) + 32767;
415       tp->colors[i].green = (tp->colors[i].green / 2) + 32767;
416       tp->colors[i].blue  = (tp->colors[i].blue  / 2) + 32767;
417     }
418
419   parse_text (mi);
420
421 }
422
423
424 static int
425 fill_character (GLUTstrokeFont font, int c, Bool wire, int *polysP)
426 {
427   GLfloat tube_width = 10;
428
429   const StrokeCharRec *ch;
430   const StrokeRec *stroke;
431   const CoordRec *coord;
432   StrokeFontPtr fontinfo;
433   int i, j;
434
435   fontinfo = (StrokeFontPtr) font;
436
437   if (c < 0 || c >= fontinfo->num_chars)
438     return 0;
439   ch = &(fontinfo->ch[c]);
440   if (ch)
441     {
442       GLfloat lx=0, ly=0;
443       for (i = ch->num_strokes, stroke = ch->stroke;
444            i > 0; i--, stroke++) {
445         for (j = stroke->num_coords, coord = stroke->coord;
446              j > 0; j--, coord++)
447           {
448 # ifdef SMOOTH_TUBE
449             int smooth = True;
450 # else
451             int smooth = False;
452 # endif
453             if (j != stroke->num_coords)
454               *polysP += tube (lx,       ly,       0,
455                                coord->x, coord->y, 0,
456                                tube_width,
457                                tube_width * 0.15,
458                                TUBE_FACES, smooth, True, wire);
459             lx = coord->x;
460             ly = coord->y;
461           }
462       }
463       return (int) (ch->right + tube_width);
464     }
465   return 0;
466 }
467
468
469 static int
470 text_extents (const char *string, int *wP, int *hP)
471 {
472   const char *s, *start;
473   int line_height = GLUT_FONT->top - GLUT_FONT->bottom;
474   int lines = 0;
475   *wP = 0;
476   *hP = 0;
477   start = string;
478   s = start;
479   while (1)
480     if (*s == '\n' || *s == 0)
481       {
482         int w = 0;
483         while (start < s)
484           {
485             w += glutStrokeWidth(GLUT_FONT, *start);
486             start++;
487           }
488         start = s+1;
489
490         if (w > *wP) *wP = w;
491         *hP += line_height;
492         lines++;
493         if (*s == 0) break;
494         s++;
495       }
496     else
497       s++;
498
499   return lines;
500 }
501
502
503 static unsigned long
504 fill_string (const char *string, Bool wire)
505 {
506   int polys = 0;
507   const char *s, *start;
508   int line_height = GLUT_FONT->top - GLUT_FONT->bottom;
509   int off;
510   GLfloat x = 0, y = 0;
511   int lines;
512
513   int ow, oh;
514   lines = text_extents (string, &ow, &oh);
515
516   y = oh / 2 - line_height;
517
518   start = string;
519   s = start;
520   while (1)
521     if (*s == '\n' || *s == 0)
522       {
523         int line_w = 0;
524         const char *s2;
525         const char *lstart = start;
526         const char *lend = s;
527
528         /* strip off whitespace at beginning and end of line
529            (since we're centering.) */
530         while (lend > lstart && isspace(lend[-1]))
531           lend--;
532         while (lstart < lend && isspace(*lstart))
533           lstart++;
534
535         for (s2 = lstart; s2 < lend; s2++)
536           line_w += glutStrokeWidth (GLUT_FONT, *s2);
537
538         x = (-ow/2) + ((ow-line_w)/2);
539         for (s2 = lstart; s2 < lend; s2++)
540           {
541             glPushMatrix();
542             glTranslatef(x, y, 0);
543             off = fill_character (GLUT_FONT, *s2, wire, &polys);
544             x += off;
545             glPopMatrix();
546           }
547
548         start = s+1;
549
550         y -= line_height;
551         if (*s == 0) break;
552         s++;
553       }
554     else
555       s++;
556   return polys;
557 }
558
559
560 ENTRYPOINT void
561 draw_text (ModeInfo *mi)
562 {
563   text_configuration *tp = &tps[MI_SCREEN(mi)];
564   Display *dpy = MI_DISPLAY(mi);
565   Window window = MI_WINDOW(mi);
566   int wire = MI_IS_WIREFRAME(mi);
567
568   GLfloat white[4] = {1.0, 1.0, 1.0, 1.0};
569   GLfloat color[4] = {0.0, 0.0, 0.0, 1.0};
570
571   if (!tp->glx_context)
572     return;
573
574   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(tp->glx_context));
575
576   if (tp->reload)
577     {
578       if (time ((time_t *) 0) >= tp->last_update + tp->reload)
579         {
580           parse_text (mi);
581           tp->last_update = time ((time_t *) 0);
582         }
583     }
584
585   glShadeModel(GL_SMOOTH);
586
587   glEnable(GL_DEPTH_TEST);
588   glEnable(GL_NORMALIZE);
589   glEnable(GL_CULL_FACE);
590
591
592   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
593
594   glPushMatrix ();
595
596   glScalef(1.1, 1.1, 1.1);
597
598   {
599     double x, y, z;
600     get_position (tp->rot, &x, &y, &z, !tp->button_down_p);
601     glTranslatef((x - 0.5) * 8,
602                  (y - 0.5) * 8,
603                  (z - 0.5) * 8);
604
605     gltrackball_rotate (tp->trackball);
606
607     if (face_front_p)
608       {
609         double max = 90;
610         get_position (tp->rot2, &x, &y, &z, !tp->button_down_p);
611         if (tp->spinx) glRotatef (max/2 - x*max, 1, 0, 0);
612         if (tp->spiny) glRotatef (max/2 - y*max, 0, 1, 0);
613         if (tp->spinz) glRotatef (max/2 - z*max, 0, 0, 1);
614       }
615     else
616       {
617         get_rotation (tp->rot, &x, &y, &z, !tp->button_down_p);
618         glRotatef (x * 360, 1, 0, 0);
619         glRotatef (y * 360, 0, 1, 0);
620         glRotatef (z * 360, 0, 0, 1);
621       }
622   }
623
624
625   glColor4fv (white);
626
627   color[0] = tp->colors[tp->ccolor].red   / 65536.0;
628   color[1] = tp->colors[tp->ccolor].green / 65536.0;
629   color[2] = tp->colors[tp->ccolor].blue  / 65536.0;
630   tp->ccolor++;
631   if (tp->ccolor >= tp->ncolors) tp->ccolor = 0;
632
633   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
634
635   glScalef(0.01, 0.01, 0.01);
636
637   mi->polygon_count = fill_string(tp->text, wire);
638
639   glPopMatrix ();
640
641   if (mi->fps_p) do_fps (mi);
642   glFinish();
643
644   glXSwapBuffers(dpy, window);
645 }
646
647 XSCREENSAVER_MODULE_2 ("GLText", gltext, text)
648
649 #endif /* USE_GL */