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