From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / hacks / xanalogtv.c
1 /* xanalogtv, Copyright (c) 2003 Trevor Blackwell <tlb@tlb.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  * Simulate test patterns on an analog TV. Concept similar to xteevee
13  * in this distribution, but a totally different implementation based
14  * on the simulation of an analog TV set in utils/analogtv.c. Much
15  * more realistic, but needs more video card bandwidth.
16  *
17  * It flips around through simulated channels 2 through 13. Some show
18  * pictures from your images directory, some show color bars, and some
19  * just have static. Some channels receive two stations simultaneously
20  * so you see a ghostly, misaligned image.
21  *
22  * It's easy to add some test patterns by compiling in an XPM, but I
23  * can't find any that are clearly freely redistributable.
24  *
25  */
26
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif /* HAVE_CONFIG_H */
30
31 #include <math.h>
32
33 #ifdef HAVE_UNISTD_H
34 # include <unistd.h>
35 #endif
36
37 #ifndef HAVE_JWXYZ
38 # include <X11/Intrinsic.h> /* for XtDatabase in hack_resources() */
39 #endif
40
41 #include "screenhack.h"
42 #include "xpm-pixmap.h"
43 #include "analogtv.h"
44
45 #include "images/logo-50.xpm"
46
47 /* #define DEBUG 1 */
48 /* #define USE_TEST_PATTERNS */
49
50 #define countof(x) (sizeof((x))/sizeof((*x)))
51
52 enum {
53   N_CHANNELS=12, /* Channels 2 through 13 on VHF */
54   MAX_MULTICHAN=2,
55   MAX_STATIONS=6
56 }; 
57
58 typedef struct chansetting_s {
59
60   analogtv_reception recs[MAX_MULTICHAN];
61   double noise_level;
62   Bool image_loaded_p;
63 /*  char *filename;     was only used for diagnostics */
64   int dur;
65 } chansetting;
66
67
68 struct state {
69   Display *dpy;
70   Window window;
71   analogtv *tv;
72   analogtv_font ugly_font;
73   struct timeval basetime;
74
75   int n_stations;
76   analogtv_input *stations[MAX_STATIONS];
77   Bool image_loading_p;
78
79   int curinputi;
80   int change_ticks;
81   chansetting chansettings[N_CHANNELS];
82   chansetting *cs;
83
84   int change_now;
85   int colorbars_only_p;
86 };
87
88
89 static void
90 update_smpte_colorbars(analogtv_input *input)
91 {
92   struct state *st = (struct state *) input->client_data;
93   int col;
94   int xpos, ypos;
95   int black_ntsc[4];
96
97   /* 
98      SMPTE is the society of motion picture and television engineers, and
99      these are the standard color bars in the US. Following the partial spec
100      at http://broadcastengineering.com/ar/broadcasting_inside_color_bars/
101      These are luma, chroma, and phase numbers for each of the 7 bars.
102   */
103   double top_cb_table[7][3]={
104     {75, 0, 0.0},    /* gray */
105     {69, 31, 167.0}, /* yellow */
106     {56, 44, 283.5}, /* cyan */
107     {48, 41, 240.5}, /* green */
108     {36, 41, 60.5},  /* magenta */
109     {28, 44, 103.5}, /* red */
110     {15, 31, 347.0}  /* blue */
111   };
112   double mid_cb_table[7][3]={
113     {15, 31, 347.0}, /* blue */
114     {7, 0, 0},       /* black */
115     {36, 41, 60.5},  /* magenta */
116     {7, 0, 0},       /* black */
117     {56, 44, 283.5}, /* cyan */
118     {7, 0, 0},       /* black */
119     {75, 0, 0.0}     /* gray */
120   };
121
122   analogtv_lcp_to_ntsc(0.0, 0.0, 0.0, black_ntsc);
123
124   analogtv_setup_sync(input, 1, 0);
125   analogtv_setup_teletext(input);
126
127   for (col=0; col<7; col++) {
128     analogtv_draw_solid_rel_lcp(input, col*(1.0/7.0), (col+1)*(1.0/7.0), 0.00, 0.68, 
129                                 top_cb_table[col][0], 
130                                 top_cb_table[col][1], top_cb_table[col][2]);
131     
132     analogtv_draw_solid_rel_lcp(input, col*(1.0/7.0), (col+1)*(1.0/7.0), 0.68, 0.75, 
133                                 mid_cb_table[col][0], 
134                                 mid_cb_table[col][1], mid_cb_table[col][2]);
135   }
136
137   analogtv_draw_solid_rel_lcp(input, 0.0, 1.0/6.0,
138                               0.75, 1.00, 7, 40, 303);   /* -I */
139   analogtv_draw_solid_rel_lcp(input, 1.0/6.0, 2.0/6.0,
140                               0.75, 1.00, 100, 0, 0);    /* white */
141   analogtv_draw_solid_rel_lcp(input, 2.0/6.0, 3.0/6.0,
142                               0.75, 1.00, 7, 40, 33);    /* +Q */
143   analogtv_draw_solid_rel_lcp(input, 3.0/6.0, 4.0/6.0,
144                               0.75, 1.00, 7, 0, 0);      /* black */
145   analogtv_draw_solid_rel_lcp(input, 12.0/18.0, 13.0/18.0,
146                               0.75, 1.00, 3, 0, 0);      /* black -4 */
147   analogtv_draw_solid_rel_lcp(input, 13.0/18.0, 14.0/18.0,
148                               0.75, 1.00, 7, 0, 0);      /* black */
149   analogtv_draw_solid_rel_lcp(input, 14.0/18.0, 15.0/18.0,
150                               0.75, 1.00, 11, 0, 0);     /* black +4 */
151   analogtv_draw_solid_rel_lcp(input, 5.0/6.0, 6.0/6.0,
152                               0.75, 1.00, 7, 0, 0);      /* black */
153
154
155   ypos=ANALOGTV_V/5;
156   xpos=ANALOGTV_VIS_START + ANALOGTV_VIS_LEN/2;
157
158   if (! st->colorbars_only_p)
159   {
160     char localname[256];
161     if (gethostname (localname, sizeof (localname))==0) {
162       localname[sizeof(localname)-1]=0; /* "The returned name is null-
163                                            terminated unless insufficient 
164                                            space is provided" */
165       localname[24]=0; /* limit length */
166
167       analogtv_draw_string_centered(input, &st->ugly_font, localname,
168                                     xpos, ypos, black_ntsc);
169     }
170   }
171   ypos += st->ugly_font.char_h*5/2;
172
173   if (! st->colorbars_only_p)
174     analogtv_draw_xpm(st->tv, input,
175                       logo_50_xpm, xpos - 100, ypos);
176
177   ypos += 58;
178
179 #if 0
180   analogtv_draw_string_centered(input, &st->ugly_font,
181                                 "Please Stand By", xpos, ypos);
182   ypos += st->ugly_font.char_h*4;
183 #endif
184
185   if (! st->colorbars_only_p)
186   {
187     char timestamp[256];
188     time_t t = time ((time_t *) 0);
189     struct tm *tm = localtime (&t);
190
191     /* Y2K: It is OK for this to use a 2-digit year because it's simulating a
192        TV display and is purely decorative. */
193     strftime(timestamp, sizeof(timestamp)-1, "%y.%m.%d %H:%M:%S ", tm);
194     analogtv_draw_string_centered(input, &st->ugly_font, timestamp,
195                                   xpos, ypos, black_ntsc);
196   }
197
198   
199   input->next_update_time += 1.0;
200 }
201
202 #if 0
203 static void
204 draw_color_square(analogtv_input *input)
205 {
206   double xs,ys;
207
208   analogtv_draw_solid_rel_lcp(input, 0.0, 1.0, 0.0, 1.0,
209                               30.0, 0.0, 0.0);
210   
211   for (xs=0.0; xs<0.9999; xs+=1.0/15.0) {
212     analogtv_draw_solid_rel_lcp(input, xs, xs, 0.0, 1.0,
213                                 100.0, 0.0, 0.0);
214   }
215
216   for (ys=0.0; ys<0.9999; ys+=1.0/11.0) {
217     analogtv_draw_solid_rel_lcp(input, 0.0, 1.0, ys, ys,
218                                 100.0, 0.0, 0.0);
219   }
220
221   for (ys=0.0; ys<0.9999; ys+=0.01) {
222     
223     analogtv_draw_solid_rel_lcp(input, 0.0/15, 1.0/15, ys, ys+0.01,
224                                 40.0, 45.0, 103.5*(1.0-ys) + 347.0*ys);
225
226     analogtv_draw_solid_rel_lcp(input, 14.0/15, 15.0/15, ys, ys+0.01,
227                                 40.0, 45.0, 103.5*(ys) + 347.0*(1.0-ys));
228   }
229
230   for (ys=0.0; ys<0.9999; ys+=0.02) {
231     analogtv_draw_solid_rel_lcp(input, 1.0/15, 2.0/15, ys*2.0/11.0+1.0/11.0, 
232                                 (ys+0.01)*2.0/11.0+1.0/11.0,
233                                 100.0*(1.0-ys), 0.0, 0.0);
234   }
235
236
237 }
238 #endif
239
240 static const char *xanalogtv_defaults [] = {
241   ".background:         black",
242   ".foreground:         white",
243   "*delay:              5",
244   "*grabDesktopImages:  False",   /* HAVE_JWXYZ */
245   "*chooseRandomImages: True",    /* HAVE_JWXYZ */
246   ANALOGTV_DEFAULTS
247   0,
248 };
249
250 static XrmOptionDescRec xanalogtv_options [] = {
251   { "-delay",           ".delay",               XrmoptionSepArg, 0 },
252   { "-colorbars-only",  ".colorbarsOnly",       XrmoptionNoArg, "True" },
253   ANALOGTV_OPTIONS
254   { 0, 0, 0, 0 }
255 };
256
257
258 #ifdef USE_TEST_PATTERNS
259
260 #include "images/earth.xpm"
261
262 char **test_patterns[] = {
263   earth_xpm,
264 };
265
266 #endif
267
268
269 static int
270 getticks(struct state *st)
271 {
272   struct timeval tv;
273   gettimeofday(&tv,NULL);
274   return ((tv.tv_sec - st->basetime.tv_sec)*1000 +
275           (tv.tv_usec - st->basetime.tv_usec)/1000);
276 }
277
278
279 /* The first time we grab an image, do it the default way.
280    The second and subsequent times, add "-no-desktop" to the command.
281    That way we don't have to watch the window un-map 5+ times in a row.
282    Also, we end up with the desktop on only one channel, and pictures
283    on all the others (or colorbars, if no imageDirectory is set.)
284  */
285 static void
286 hack_resources (Display *dpy)
287 {
288 #ifndef HAVE_JWXYZ
289   static int count = -1;
290   count++;
291
292   if (count == 0)
293     return;
294   else if (count == 1)
295     {
296       XrmDatabase db = XtDatabase (dpy);
297       char *res = "desktopGrabber";
298       char *val = get_string_resource (dpy, res, "DesktopGrabber");
299       char buf1[255];
300       char buf2[255];
301       XrmValue value;
302       sprintf (buf1, "%.100s.%.100s", progname, res);
303       sprintf (buf2, "%.200s -no-desktop", val);
304       value.addr = buf2;
305       value.size = strlen(buf2);
306       XrmPutResource (&db, buf1, "String", &value);
307     }
308 #endif /* HAVE_JWXYZ */
309 }
310
311
312 static void analogtv_load_random_image(struct state *);
313
314
315 static void image_loaded_cb (Screen *screen, Window window, Drawable pixmap,
316                              const char *name, XRectangle *geometry,
317                              void *closure)
318 {
319   /* When an image has just been loaded, store it into the first available
320      channel.  If there are other unloaded channels, then start loading
321      another image.
322   */
323   struct state *st = (struct state *) closure;
324   int i;
325   int this = -1;
326   int next = -1;
327
328   if (!st->image_loading_p) abort();  /* only one at a time... */
329   st->image_loading_p = False;
330
331   for (i = 0; i < MAX_STATIONS; i++) {
332     if (! st->chansettings[i].image_loaded_p) {
333       if (this == -1) this = i;
334       else if (next == -1) next = i;
335     }
336   }
337   if (this == -1) abort();  /* no unloaded stations? */
338
339   /* Load this image into the next channel. */
340   {
341     analogtv_input *input = st->stations[this];
342     int width=ANALOGTV_PIC_LEN;
343     int height=width*3/4;
344     XImage *image = XGetImage (st->dpy, pixmap, 0, 0,
345                                width, height, ~0L, ZPixmap);
346     XFreePixmap(st->dpy, pixmap);
347
348     analogtv_setup_sync(input, 1, (random()%20)==0);
349     analogtv_load_ximage(st->tv, input, image);
350     if (image) XDestroyImage(image);
351     st->chansettings[this].image_loaded_p = True;
352 #if 0
353     if (name) {
354       const char *s = strrchr (name, '/');
355       if (s) s++;
356       else s = name;
357       st->chansettings[this].filename = strdup (s);
358     }
359     fprintf(stderr, "%s: loaded channel %d, %s\n", progname, this, 
360             st->chansettings[this].filename);
361 #endif
362   }
363
364   /* If there are still unloaded stations, fire off another loader. */
365   if (next != -1)
366     analogtv_load_random_image (st);
367 }
368
369
370 /* Queues a single image for loading.  Only load one at a time.
371    The image is done loading when st->img_loader is null and
372    it->loaded_image is a pixmap.
373  */
374 static void
375 analogtv_load_random_image(struct state *st)
376 {
377   int width=ANALOGTV_PIC_LEN;
378   int height=width*3/4;
379   Pixmap p;
380
381   if (st->image_loading_p)  /* a load is already in progress */
382     return;
383
384   st->image_loading_p = True;
385   p = XCreatePixmap(st->dpy, st->window, width, height, st->tv->visdepth);
386   hack_resources(st->dpy);
387   load_image_async (st->tv->xgwa.screen, st->window, p, image_loaded_cb, st);
388 }
389
390
391 #if 0
392 static int
393 analogtv_load_xpm(analogtv *it, analogtv_input *input, char **xpm)
394 {
395   Pixmap pixmap;
396   XImage *image;
397   int width,height;
398   int rc;
399
400   pixmap=xpm_data_to_pixmap (it->dpy, it->window, xpm,
401                              &width, &height, NULL);
402   image = XGetImage(it->dpy, pixmap, 0, 0, width, height, ~0L, ZPixmap);
403   XFreePixmap(it->dpy, pixmap);
404   rc=analogtv_load_ximage(it, input, image);
405   if (image) XDestroyImage(image);
406   return rc;
407 }
408 #endif
409
410
411 static void add_stations(struct state *st)
412 {
413   while (st->n_stations < MAX_STATIONS) {
414     analogtv_input *input=analogtv_input_allocate();
415     st->stations[st->n_stations++]=input;
416     input->client_data = st;
417   }
418 }
419
420
421 static void load_station_images(struct state *st)
422 {
423   int i;
424   for (i = 0; i < MAX_STATIONS; i++) {
425     analogtv_input *input = st->stations[i];
426
427     st->chansettings[i].image_loaded_p = True;
428     if (i == 0 ||   /* station 0 is always colorbars */
429         st->colorbars_only_p) {
430       input->updater = update_smpte_colorbars;
431       input->do_teletext=1;
432     }
433 #ifdef USE_TEST_PATTERNS
434     else if (random()%5==0) {
435       j=random()%countof(test_patterns);
436       analogtv_setup_sync(input);
437       analogtv_load_xpm(tv, input, test_patterns[j]);
438       analogtv_setup_teletext(input);
439     }
440 #endif
441     else {
442       analogtv_load_random_image(st);
443       input->do_teletext=1;
444       st->chansettings[i].image_loaded_p = False;
445     }
446   }
447 }
448
449
450 static void *
451 xanalogtv_init (Display *dpy, Window window)
452 {
453   struct state *st = (struct state *) calloc (1, sizeof(*st));
454   int i;
455   int last_station=42;
456   int delay = get_integer_resource(dpy, "delay", "Integer");
457   if (delay < 1) delay = 1;
458
459   analogtv_make_font(dpy, window, &st->ugly_font, 7, 10, "6x10");
460   
461   st->dpy = dpy;
462   st->window = window;
463   st->tv=analogtv_allocate(dpy, window);
464
465   st->colorbars_only_p =
466     get_boolean_resource(dpy, "colorbarsOnly", "ColorbarsOnly");
467
468   add_stations(st);
469
470   analogtv_set_defaults(st->tv, "");
471   st->tv->need_clear=1;
472
473   if (random()%4==0) {
474     st->tv->tint_control += pow(frand(2.0)-1.0, 7) * 180.0;
475   }
476   if (1) {
477     st->tv->color_control += frand(0.3);
478   }
479
480   for (i=0; i<N_CHANNELS; i++) {
481     memset(&st->chansettings[i], 0, sizeof(chansetting));
482
483     st->chansettings[i].noise_level = 0.06;
484     st->chansettings[i].dur = 1000*delay;
485
486     if (random()%6==0) {
487       st->chansettings[i].dur=600;
488     }
489     else {
490       int stati;
491       for (stati=0; stati<MAX_MULTICHAN; stati++) {
492         analogtv_reception *rec=&st->chansettings[i].recs[stati];
493         int station;
494         while (1) {
495           station=random()%st->n_stations;
496           if (station!=last_station) break;
497           if ((random()%10)==0) break;
498         }
499         last_station=station;
500         rec->input = st->stations[station];
501         rec->level = pow(frand(1.0), 3.0) * 2.0 + 0.05;
502         rec->ofs=random()%ANALOGTV_SIGNAL_LEN;
503         if (random()%3) {
504           rec->multipath = frand(1.0);
505         } else {
506           rec->multipath=0.0;
507         }
508         if (stati) {
509           /* We only set a frequency error for ghosting stations,
510              because it doesn't matter otherwise */
511           rec->freqerr = (frand(2.0)-1.0) * 3.0;
512         }
513
514         if (rec->level > 0.3) break;
515         if (random()%4) break;
516       }
517     }
518   }
519
520   gettimeofday(&st->basetime,NULL);
521
522   st->curinputi=0;
523   st->cs = &st->chansettings[st->curinputi];
524   st->change_ticks = st->cs->dur + 1500;
525
526   st->tv->powerup=0.0;
527
528   load_station_images(st);
529
530   return st;
531 }
532
533 static unsigned long
534 xanalogtv_draw (Display *dpy, Window window, void *closure)
535 {
536   struct state *st = (struct state *) closure;
537   int i;
538
539   int curticks=getticks(st);
540   double curtime=curticks*0.001;
541
542   const analogtv_reception *recs[MAX_MULTICHAN];
543   unsigned rec_count = 0;
544
545   int auto_change = curticks >= st->change_ticks && st->tv->powerup > 10.0 ? 1 : 0;
546
547   if (st->change_now || auto_change) {
548     st->curinputi=(st->curinputi+st->change_now+auto_change+N_CHANNELS)%N_CHANNELS;
549     st->change_now = 0;
550     st->cs = &st->chansettings[st->curinputi];
551 #if 0
552     fprintf (stderr, "%s: channel %d, %s\n", progname, st->curinputi,
553              st->cs->filename);
554 #endif
555     st->change_ticks = curticks + st->cs->dur;
556     /* Set channel change noise flag */
557     st->tv->channel_change_cycles=200000;
558   }
559
560   for (i=0; i<MAX_MULTICHAN; i++) {
561     analogtv_reception *rec = &st->cs->recs[i];
562     analogtv_input *inp=rec->input;
563     if (!inp) continue;
564
565     if (inp->updater) {
566       inp->next_update_time = curtime;
567       (inp->updater)(inp);
568     }
569     rec->ofs += rec->freqerr;
570   }
571
572   st->tv->powerup=curtime;
573
574   for (i=0; i<MAX_MULTICHAN; i++) {
575     analogtv_reception *rec = &st->cs->recs[i];
576     if (rec->input) {
577       analogtv_reception_update(rec);
578       recs[rec_count] = rec;
579       ++rec_count;
580     }
581   }
582   analogtv_draw(st->tv, st->cs->noise_level, recs, rec_count);
583
584 #ifdef HAVE_MOBILE
585   return 0;
586 #else
587   return 5000;
588 #endif
589 }
590
591 static void
592 xanalogtv_reshape (Display *dpy, Window window, void *closure, 
593                  unsigned int w, unsigned int h)
594 {
595   struct state *st = (struct state *) closure;
596   analogtv_reconfigure(st->tv);
597 }
598
599 static Bool
600 xanalogtv_event (Display *dpy, Window window, void *closure, XEvent *event)
601 {
602   struct state *st = (struct state *) closure;
603
604   if (event->type == ButtonPress)
605     {
606       unsigned button = event->xbutton.button;
607       st->change_now = button == 2 || button == 3 || button == 5 ? -1 : 1;
608       return True;
609     }
610   else if (event->type == KeyPress)
611     {
612       KeySym keysym;
613       char c = 0;
614       XLookupString (&event->xkey, &c, 1, &keysym, 0);
615       if (c == ' ' || c == '\t' || c == '\r' || c == '\n' ||
616           keysym == XK_Up || keysym == XK_Right || keysym == XK_Prior)
617         {
618           st->change_now = 1;
619           return True;
620         }
621       else if (c == '\b' ||
622                keysym == XK_Down || keysym == XK_Left || keysym == XK_Next)
623         {
624           st->change_now = -1;
625           return True;
626         }
627       else if (screenhack_event_helper (dpy, window, event))
628         goto DEF;
629     }
630   else if (screenhack_event_helper (dpy, window, event))
631     {
632     DEF:
633       st->change_now = ((random() & 1) ? 1 : -1);
634       return True;
635     }
636
637   return False;
638 }
639
640 static void
641 xanalogtv_free (Display *dpy, Window window, void *closure)
642 {
643   struct state *st = (struct state *) closure;
644   analogtv_release(st->tv);
645   free (st);
646 }
647
648
649 XSCREENSAVER_MODULE ("XAnalogTV", xanalogtv)