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