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