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