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