ftp://ftp.krokus.ru/pub/OpenBSD/distfiles/xscreensaver-4.21.tar.gz
[xscreensaver] / hacks / glx / gltext.c
1 /* gltext, Copyright (c) 2001-2005 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 #include <X11/Intrinsic.h>
13
14 extern XtAppContext app;
15
16 #define PROGCLASS       "GLText"
17 #define HACK_INIT       init_text
18 #define HACK_DRAW       draw_text
19 #define HACK_RESHAPE    reshape_text
20 #define HACK_HANDLE_EVENT text_handle_event
21 #define EVENT_MASK      PointerMotionMask
22 #define sws_opts        xlockmore_opts
23
24 #define DEF_TEXT        "(default)"
25 #define DEF_PROGRAM     "(default)"
26 #define DEF_SPIN        "XYZ"
27 #define DEF_WANDER      "True"
28 #define DEF_FRONT       "False"
29
30 #define DEFAULTS        "*delay:        20000        \n" \
31                         "*showFPS:      False        \n" \
32                         "*wireframe:    False        \n" \
33
34 #define SMOOTH_TUBE       /* whether to have smooth or faceted tubes */
35
36 #ifdef SMOOTH_TUBE
37 # define TUBE_FACES  12   /* how densely to render tubes */
38 #else
39 # define TUBE_FACES  8
40 #endif
41
42
43 #undef countof
44 #define countof(x) (sizeof((x))/sizeof((*x)))
45
46 #include "xlockmore.h"
47 #include "colors.h"
48 #include "tube.h"
49 #include "rotator.h"
50 #include "gltrackball.h"
51 #include <time.h>
52 #include <sys/time.h>
53 #include <ctype.h>
54
55 #ifdef HAVE_LOCALE_H
56 # include <locale.h>
57 #endif /* HAVE_LOCALE_H */
58
59 #ifdef USE_GL /* whole file */
60
61 #ifdef HAVE_UNAME
62 # include <sys/utsname.h>
63 #endif /* HAVE_UNAME */
64
65
66 #include <GL/glu.h>
67 #include "glutstroke.h"
68 #include "glut_roman.h"
69 #define GLUT_FONT (&glutStrokeRoman)
70
71
72 typedef struct {
73   GLXContext *glx_context;
74   rotator *rot, *rot2;
75   trackball_state *trackball;
76   Bool button_down_p;
77   Bool spinx, spiny, spinz;
78
79   GLuint text_list;
80
81   int ncolors;
82   XColor *colors;
83   int ccolor;
84
85   char *text;
86   int reload;
87
88 } text_configuration;
89
90 static text_configuration *tps = NULL;
91
92 static char *text_fmt;
93 static char *program_str;
94 static char *do_spin;
95 static Bool do_wander;
96 static Bool face_front_p;
97
98 static XrmOptionDescRec opts[] = {
99   { "-text",    ".text",      XrmoptionSepArg, 0 },
100   { "-program", ".program",   XrmoptionSepArg, 0 },
101   { "-spin",    ".spin",      XrmoptionSepArg, 0 },
102   { "+spin",    ".spin",      XrmoptionNoArg, "" },
103   { "-wander",  ".wander",    XrmoptionNoArg, "True" },
104   { "+wander",  ".wander",    XrmoptionNoArg, "False" },
105   { "-front",   ".faceFront", XrmoptionNoArg, "True" },
106   { "+front",   ".faceFront", XrmoptionNoArg, "False" }
107 };
108
109 static argtype vars[] = {
110   {&text_fmt,     "text",      "Text",      DEF_TEXT,    t_String},
111   {&program_str,  "program",   "Program",   DEF_PROGRAM, t_String},
112   {&do_spin,      "spin",      "Spin",      DEF_SPIN,    t_String},
113   {&do_wander,    "wander",    "Wander",    DEF_WANDER,  t_Bool},
114   {&face_front_p, "faceFront", "FaceFront", DEF_FRONT,   t_Bool},
115 };
116
117 ModeSpecOpt sws_opts = {countof(opts), opts, countof(vars), vars, NULL};
118
119
120 /* Window management, etc
121  */
122 void
123 reshape_text (ModeInfo *mi, int width, int height)
124 {
125   GLfloat h = (GLfloat) height / (GLfloat) width;
126
127   glViewport (0, 0, (GLint) width, (GLint) height);
128
129   glMatrixMode(GL_PROJECTION);
130   glLoadIdentity();
131   gluPerspective (30.0, 1/h, 1.0, 100.0);
132
133   glMatrixMode(GL_MODELVIEW);
134   glLoadIdentity();
135   gluLookAt( 0.0, 0.0, 30.0,
136              0.0, 0.0, 0.0,
137              0.0, 1.0, 0.0);
138
139   glClear(GL_COLOR_BUFFER_BIT);
140 }
141
142
143 static void
144 gl_init (ModeInfo *mi)
145 {
146   text_configuration *tp = &tps[MI_SCREEN(mi)];
147   int wire = MI_IS_WIREFRAME(mi);
148
149   static GLfloat pos[4] = {5.0, 5.0, 10.0, 1.0};
150
151   if (!wire)
152     {
153       glLightfv(GL_LIGHT0, GL_POSITION, pos);
154       glEnable(GL_CULL_FACE);
155       glEnable(GL_LIGHTING);
156       glEnable(GL_LIGHT0);
157       glEnable(GL_DEPTH_TEST);
158     }
159
160   tp->text_list = glGenLists (1);
161   glNewList (tp->text_list, GL_COMPILE);
162   glEndList ();
163 }
164
165
166 /* The GLUT font only has ASCII characters in them, so do what we can to
167    convert Latin1 characters to the nearest ASCII equivalent... 
168  */
169 static void
170 latin1_to_ascii (char *s)
171 {
172   unsigned char *us = (unsigned char *) s;
173   const unsigned char ascii[95] = {
174     '!', 'C', '#', '#', 'Y', '|', 'S', '_', 'C', '?', '<', '=', '-', 'R', '_',
175     '?', '?', '2', '3', '\'','u', 'P', '.', ',', '1', 'o', '>', '?', '?', '?',
176     '?', 'A', 'A', 'A', 'A', 'A', 'A', 'E', 'C', 'E', 'E', 'E', 'E', 'I', 'I',
177     'I', 'I', 'D', 'N', 'O', 'O', 'O', 'O', 'O', 'x', '0', 'U', 'U', 'U', 'U',
178     'Y', 'p', 'S', 'a', 'a', 'a', 'a', 'a', 'a', 'e', 'c', 'e', 'e', 'e', 'e',
179     'i', 'i', 'i', 'i', 'o', 'n', 'o', 'o', 'o', 'o', 'o', '/', 'o', 'u', 'u',
180     'u', 'u', 'y', 'p', 'y' };
181   while (*us)
182     {
183       if (*us >= 161)
184         *us = ascii[*us - 161];
185       else if (*us > 127)
186         *us = '?';
187       us++;
188     }
189 }
190
191
192 static void
193 parse_text (ModeInfo *mi)
194 {
195   text_configuration *tp = &tps[MI_SCREEN(mi)];
196
197   if (tp->text) free (tp->text);
198
199   if (program_str && *program_str && !!strcmp(program_str, "(default)"))
200     {
201       FILE *p;
202       int i;
203       char buf[1024];
204       sprintf (buf, "( %.900s ) 2>&1", program_str);
205       p = popen (buf, "r");
206       if (! p)
207         sprintf (buf, "error running '%.900s'", program_str);
208       else
209         {
210           char *out = buf;
211           char *end = out + sizeof(buf) - 1;
212           int n;
213           do {
214             n = fread (out, 1, end - out, p);
215             if (n > 0)
216               out += n;
217             *out = 0;
218           } while (n > 0);
219           fclose (p);
220         }
221
222       /* Truncate it to 10 lines */
223       {
224         char *s = buf;
225         for (i = 0; i < 10; i++)
226           if (s && (s = strchr (s, '\n')))
227             s++;
228         if (s) *s = 0;
229       }
230
231       tp->text = strdup (buf);
232       tp->reload = 5;
233     }
234   else if (!text_fmt || !*text_fmt || !strcmp(text_fmt, "(default)"))
235     {
236 # ifdef HAVE_UNAME
237       struct utsname uts;
238
239       if (uname (&uts) < 0)
240         {
241           tp->text = strdup("uname() failed");
242         }
243       else
244         {
245           char *s;
246           if ((s = strchr(uts.nodename, '.')))
247             *s = 0;
248           tp->text = (char *) malloc(strlen(uts.nodename) +
249                                      strlen(uts.sysname) +
250                                      strlen(uts.version) +
251                                      strlen(uts.release) + 10);
252 #  if defined(_AIX)
253           sprintf(tp->text, "%s\n%s %s.%s",
254                   uts.nodename, uts.sysname, uts.version, uts.release);
255 #  elif defined(__APPLE__)  /* MacOS X + XDarwin */
256           sprintf(tp->text, "%s\n%s %s\n%s",
257                   uts.nodename, uts.sysname, uts.release, uts.machine);
258 #  else
259           sprintf(tp->text, "%s\n%s %s",
260                   uts.nodename, uts.sysname, uts.release);
261 #  endif /* special system types */
262         }
263 # else  /* !HAVE_UNAME */
264 #  ifdef VMS
265       tp->text = strdup(getenv("SYS$NODE"));
266 #  else
267       tp->text = strdup("*  *\n*  *  *\nxscreensaver\n*  *  *\n*  *");
268 #  endif
269 # endif /* !HAVE_UNAME */
270     }
271   else if (!strchr (text_fmt, '%'))
272     {
273       tp->text = strdup (text_fmt);
274     }
275   else
276     {
277       time_t now = time ((time_t *) 0);
278       struct tm *tm = localtime (&now);
279       int L = strlen(text_fmt) + 100;
280       tp->text = (char *) malloc (L);
281       *tp->text = 0;
282       strftime (tp->text, L-1, text_fmt, tm);
283       if (!*tp->text)
284         sprintf (tp->text, "strftime error:\n%s", text_fmt);
285       tp->reload = 1;
286     }
287
288   latin1_to_ascii (tp->text);
289 }
290
291
292 Bool
293 text_handle_event (ModeInfo *mi, XEvent *event)
294 {
295   text_configuration *tp = &tps[MI_SCREEN(mi)];
296
297   if (event->xany.type == ButtonPress &&
298       event->xbutton.button == Button1)
299     {
300       tp->button_down_p = True;
301       gltrackball_start (tp->trackball,
302                          event->xbutton.x, event->xbutton.y,
303                          MI_WIDTH (mi), MI_HEIGHT (mi));
304       return True;
305     }
306   else if (event->xany.type == ButtonRelease &&
307            event->xbutton.button == Button1)
308     {
309       tp->button_down_p = False;
310       return True;
311     }
312   else if (event->xany.type == ButtonPress &&
313            (event->xbutton.button == Button4 ||
314             event->xbutton.button == Button5))
315     {
316       gltrackball_mousewheel (tp->trackball, event->xbutton.button, 10,
317                               !!event->xbutton.state);
318       return True;
319     }
320   else if (event->xany.type == MotionNotify &&
321            tp->button_down_p)
322     {
323       gltrackball_track (tp->trackball,
324                          event->xmotion.x, event->xmotion.y,
325                          MI_WIDTH (mi), MI_HEIGHT (mi));
326       return True;
327     }
328
329   return False;
330 }
331
332
333 void 
334 init_text (ModeInfo *mi)
335 {
336   text_configuration *tp;
337   int i;
338
339 # ifdef HAVE_SETLOCALE
340   setlocale (LC_TIME, "");      /* for strftime() calls */
341 # endif
342
343   if (!tps) {
344     tps = (text_configuration *)
345       calloc (MI_NUM_SCREENS(mi), sizeof (text_configuration));
346     if (!tps) {
347       fprintf(stderr, "%s: out of memory\n", progname);
348       exit(1);
349     }
350
351     tp = &tps[MI_SCREEN(mi)];
352   }
353
354   tp = &tps[MI_SCREEN(mi)];
355
356   if ((tp->glx_context = init_GL(mi)) != NULL) {
357     gl_init(mi);
358     reshape_text (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
359   }
360
361   {
362     double spin_speed   = 0.5;
363     double wander_speed = 0.02;
364     double tilt_speed   = 0.03;
365     double spin_accel   = 0.5;
366
367     char *s = do_spin;
368     while (*s)
369       {
370         if      (*s == 'x' || *s == 'X') tp->spinx = True;
371         else if (*s == 'y' || *s == 'Y') tp->spiny = True;
372         else if (*s == 'z' || *s == 'Z') tp->spinz = True;
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 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   static GLfloat color[4] = {0.0, 0.0, 0.0, 1.0};
557   static GLfloat white[4] = {1.0, 1.0, 1.0, 1.0};
558
559   if (!tp->glx_context)
560     return;
561
562   if (tp->reload)
563     {
564       static time_t last_update = 0;
565       if (time ((time_t *) 0) >= last_update + tp->reload)
566         {
567           parse_text (mi);
568           last_update = time ((time_t *) 0);
569         }
570     }
571
572   glShadeModel(GL_SMOOTH);
573
574   glEnable(GL_DEPTH_TEST);
575   glEnable(GL_NORMALIZE);
576   glEnable(GL_CULL_FACE);
577
578   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
579
580   glPushMatrix ();
581
582   glScalef(1.1, 1.1, 1.1);
583
584   {
585     double x, y, z;
586     get_position (tp->rot, &x, &y, &z, !tp->button_down_p);
587     glTranslatef((x - 0.5) * 8,
588                  (y - 0.5) * 8,
589                  (z - 0.5) * 8);
590
591     gltrackball_rotate (tp->trackball);
592
593     if (face_front_p)
594       {
595         double max = 90;
596         get_position (tp->rot2, &x, &y, &z, !tp->button_down_p);
597         if (tp->spinx) glRotatef (max/2 - x*max, 1, 0, 0);
598         if (tp->spiny) glRotatef (max/2 - y*max, 0, 1, 0);
599         if (tp->spinz) glRotatef (max/2 - z*max, 0, 0, 1);
600       }
601     else
602       {
603         get_rotation (tp->rot, &x, &y, &z, !tp->button_down_p);
604         glRotatef (x * 360, 1, 0, 0);
605         glRotatef (y * 360, 0, 1, 0);
606         glRotatef (z * 360, 0, 0, 1);
607       }
608   }
609
610
611   glColor4fv (white);
612
613   color[0] = tp->colors[tp->ccolor].red   / 65536.0;
614   color[1] = tp->colors[tp->ccolor].green / 65536.0;
615   color[2] = tp->colors[tp->ccolor].blue  / 65536.0;
616   tp->ccolor++;
617   if (tp->ccolor >= tp->ncolors) tp->ccolor = 0;
618
619   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
620
621   glScalef(0.01, 0.01, 0.01);
622
623   fill_string(tp->text, wire);
624
625   glPopMatrix ();
626
627   if (mi->fps_p) do_fps (mi);
628   glFinish();
629
630   glXSwapBuffers(dpy, window);
631 }
632
633 #endif /* USE_GL */