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