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