From http://www.jwz.org/xscreensaver/xscreensaver-5.40.tar.gz
[xscreensaver] / hacks / xanalogtv.c
index 9d65586f1cf084819e4913dd7c56d8fa90a77efa..80cc7d91734d8ad29eb6b350e69b3ed3fea0eebd 100644 (file)
@@ -1,4 +1,4 @@
-/* xanalogtv, Copyright (c) 2003 Trevor Blackwell <tlb@tlb.org>
+/* xanalogtv, Copyright (c) 2003-2018 Trevor Blackwell <tlb@tlb.org>
  *
  * Permission to use, copy, modify, distribute, and sell this software and its
  * documentation for any purpose is hereby granted without fee, provided that
  * pictures from your images directory, some show color bars, and some
  * just have static. Some channels receive two stations simultaneously
  * so you see a ghostly, misaligned image.
- *
- * It's easy to add some test patterns by compiling in an XPM, but I
- * can't find any that are clearly freely redistributable.
- *
  */
 
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif /* HAVE_CONFIG_H */
+
 #include <math.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#ifndef HAVE_JWXYZ
+# include <X11/Intrinsic.h> /* for XtDatabase in hack_resources() */
+#endif
+
 #include "screenhack.h"
-#include "xpm-pixmap.h"
+#include "ximage-loader.h"
 #include "analogtv.h"
-#include <stdio.h>
-#include <time.h>
-#include <sys/time.h>
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include <X11/Intrinsic.h>
 
-#include "images/logo-50.xpm"
+#define USE_TEST_PATTERNS
+
+#include "images/gen/logo-180_png.h"
+
+#ifdef USE_TEST_PATTERNS
+# include "images/gen/testcard_rca_png.h"
+# include "images/gen/testcard_pm5544_png.h"
+# include "images/gen/testcard_bbcf_png.h"
+#endif
 
-/* #define DEBUG 1 */
-/* #define USE_TEST_PATTERNS */
 
 #define countof(x) (sizeof((x))/sizeof((*x)))
 
-static analogtv *tv=NULL;
+enum {
+  N_CHANNELS=12, /* Channels 2 through 13 on VHF */
+  MAX_MULTICHAN=2,
+  MAX_STATIONS=6
+}; 
+
+typedef struct chansetting_s {
+
+  analogtv_reception recs[MAX_MULTICHAN];
+  double noise_level;
+  Bool image_loaded_p;
+/*  char *filename;     was only used for diagnostics */
+  int dur;
+} chansetting;
+
+
+struct state {
+  Display *dpy;
+  Window window;
+  analogtv *tv;
+  analogtv_font ugly_font;
+  struct timeval basetime;
+
+  int n_stations;
+  analogtv_input *stations[MAX_STATIONS];
+  Bool image_loading_p;
+  XImage *logo, *logo_mask;
+# ifdef USE_TEST_PATTERNS
+  XImage *test_patterns[MAX_STATIONS];
+# endif
+
+  int curinputi;
+  int change_ticks;
+  chansetting chansettings[N_CHANNELS];
+  chansetting *cs;
+
+  int change_now;
+  int colorbars_only_p;
+};
 
-analogtv_font ugly_font;
 
 static void
 update_smpte_colorbars(analogtv_input *input)
 {
+  struct state *st = (struct state *) input->client_data;
   int col;
   int xpos, ypos;
   int black_ntsc[4];
@@ -114,30 +161,45 @@ update_smpte_colorbars(analogtv_input *input)
   ypos=ANALOGTV_V/5;
   xpos=ANALOGTV_VIS_START + ANALOGTV_VIS_LEN/2;
 
+  /* if (! st->colorbars_only_p) */
   {
     char localname[256];
     if (gethostname (localname, sizeof (localname))==0) {
+      int L;
       localname[sizeof(localname)-1]=0; /* "The returned name is null-
                                            terminated unless insufficient 
                                            space is provided" */
+      L = strlen(localname);
+      if (L > 6 && !strcmp(".local", localname+L-6))
+        localname[L-6] = 0;
+
       localname[24]=0; /* limit length */
 
-      analogtv_draw_string_centered(input, &ugly_font, localname,
+      analogtv_draw_string_centered(input, &st->ugly_font, localname,
                                     xpos, ypos, black_ntsc);
     }
   }
-  ypos += ugly_font.char_h*5/2;
+  ypos += st->ugly_font.char_h*5/2;
 
-  analogtv_draw_xpm(tv, input,
-                    logo_50_xpm, xpos - 100, ypos);
+  if (st->logo)
+    {
+      int w2 = st->tv->xgwa.width  * 0.2;
+      int h2 = st->tv->xgwa.height * 0.2;
+      analogtv_load_ximage (st->tv, input, st->logo, st->logo_mask,
+                            (st->tv->xgwa.width - w2) / 2,
+                            st->tv->xgwa.height * 0.28,
+                            w2, h2);
+    }
 
   ypos += 58;
 
 #if 0
-  analogtv_draw_string_centered(input, &ugly_font, "Please Stand By", xpos, ypos);
-  ypos += ugly_font.char_h*4;
+  analogtv_draw_string_centered(input, &st->ugly_font,
+                                "Please Stand By", xpos, ypos);
+  ypos += st->ugly_font.char_h*4;
 #endif
 
+  /* if (! st->colorbars_only_p) */
   {
     char timestamp[256];
     time_t t = time ((time_t *) 0);
@@ -146,7 +208,7 @@ update_smpte_colorbars(analogtv_input *input)
     /* Y2K: It is OK for this to use a 2-digit year because it's simulating a
        TV display and is purely decorative. */
     strftime(timestamp, sizeof(timestamp)-1, "%y.%m.%d %H:%M:%S ", tm);
-    analogtv_draw_string_centered(input, &ugly_font, timestamp,
+    analogtv_draw_string_centered(input, &st->ugly_font, timestamp,
                                   xpos, ypos, black_ntsc);
   }
 
@@ -154,94 +216,13 @@ update_smpte_colorbars(analogtv_input *input)
   input->next_update_time += 1.0;
 }
 
-#if 0
-static void
-draw_color_square(analogtv_input *input)
-{
-  double xs,ys;
-
-  analogtv_draw_solid_rel_lcp(input, 0.0, 1.0, 0.0, 1.0,
-                              30.0, 0.0, 0.0);
-  
-  for (xs=0.0; xs<0.9999; xs+=1.0/15.0) {
-    analogtv_draw_solid_rel_lcp(input, xs, xs, 0.0, 1.0,
-                                100.0, 0.0, 0.0);
-  }
-
-  for (ys=0.0; ys<0.9999; ys+=1.0/11.0) {
-    analogtv_draw_solid_rel_lcp(input, 0.0, 1.0, ys, ys,
-                                100.0, 0.0, 0.0);
-  }
-
-  for (ys=0.0; ys<0.9999; ys+=0.01) {
-    
-    analogtv_draw_solid_rel_lcp(input, 0.0/15, 1.0/15, ys, ys+0.01,
-                                40.0, 45.0, 103.5*(1.0-ys) + 347.0*ys);
-
-    analogtv_draw_solid_rel_lcp(input, 14.0/15, 15.0/15, ys, ys+0.01,
-                                40.0, 45.0, 103.5*(ys) + 347.0*(1.0-ys));
-  }
-
-  for (ys=0.0; ys<0.9999; ys+=0.02) {
-    analogtv_draw_solid_rel_lcp(input, 1.0/15, 2.0/15, ys*2.0/11.0+1.0/11.0, 
-                                (ys+0.01)*2.0/11.0+1.0/11.0,
-                                100.0*(1.0-ys), 0.0, 0.0);
-  }
-
-
-}
-#endif
-
-char *progclass = "XAnalogTV";
-
-char *defaults [] = {
-  ".background:             black",
-  ".foreground:             white",
-  "*delay:          5",
-  ANALOGTV_DEFAULTS
-  0,
-};
-
-XrmOptionDescRec options [] = {
-  { "-delay",          ".delay",               XrmoptionSepArg, 0 },
-  ANALOGTV_OPTIONS
-  { 0, 0, 0, 0 }
-};
-
-
-#ifdef USE_TEST_PATTERNS
-
-#include "images/earth.xpm"
-
-char **test_patterns[] = {
-  earth_xpm,
-};
-
-#endif
-
-
-enum {
-  N_CHANNELS=12, /* Channels 2 through 13 on VHF */
-  MAX_MULTICHAN=2
-}; 
-
-typedef struct chansetting_s {
-
-  analogtv_reception recs[MAX_MULTICHAN];
-  double noise_level;
-
-  int dur;
-} chansetting;
-
-static struct timeval basetime;
-
 static int
-getticks(void)
+getticks(struct state *st)
 {
   struct timeval tv;
   gettimeofday(&tv,NULL);
-  return ((tv.tv_sec - basetime.tv_sec)*1000 +
-          (tv.tv_usec - basetime.tv_usec)/1000);
+  return ((tv.tv_sec - st->basetime.tv_sec)*1000 +
+          (tv.tv_usec - st->basetime.tv_usec)/1000);
 }
 
 
@@ -252,8 +233,9 @@ getticks(void)
    on all the others (or colorbars, if no imageDirectory is set.)
  */
 static void
-hack_resources (void)
+hack_resources (Display *dpy)
 {
+#ifndef HAVE_JWXYZ
   static int count = -1;
   count++;
 
@@ -261,151 +243,238 @@ hack_resources (void)
     return;
   else if (count == 1)
     {
+      XrmDatabase db = XtDatabase (dpy);
       char *res = "desktopGrabber";
-      char *val = get_string_resource (res, "DesktopGrabber");
+      char *val = get_string_resource (dpy, res, "DesktopGrabber");
       char buf1[255];
       char buf2[255];
       XrmValue value;
-      sprintf (buf1, "%.100s.%.100s", progclass, res);
+      sprintf (buf1, "%.100s.%.100s", progname, res);
       sprintf (buf2, "%.200s -no-desktop", val);
       value.addr = buf2;
       value.size = strlen(buf2);
       XrmPutResource (&db, buf1, "String", &value);
     }
+#endif /* HAVE_JWXYZ */
 }
 
 
-int
-analogtv_load_random_image(analogtv *it, analogtv_input *input)
+static void analogtv_load_random_image(struct state *);
+
+
+static void image_loaded_cb (Screen *screen, Window window, Drawable pixmap,
+                             const char *name, XRectangle *geometry,
+                             void *closure)
+{
+  /* When an image has just been loaded, store it into the first available
+     channel.  If there are other unloaded channels, then start loading
+     another image.
+  */
+  struct state *st = (struct state *) closure;
+  int i;
+  int this = -1;
+  int next = -1;
+
+  if (!st->image_loading_p) abort();  /* only one at a time... */
+  st->image_loading_p = False;
+
+  for (i = 0; i < MAX_STATIONS; i++) {
+    if (! st->chansettings[i].image_loaded_p) {
+      if (this == -1) this = i;
+      else if (next == -1) next = i;
+    }
+  }
+  if (this == -1) abort();  /* no unloaded stations? */
+
+  /* Load this image into the next channel. */
+  {
+    analogtv_input *input = st->stations[this];
+    int width=ANALOGTV_PIC_LEN;
+    int height=width*3/4;
+    XImage *image = XGetImage (st->dpy, pixmap, 0, 0,
+                               width, height, ~0L, ZPixmap);
+    XFreePixmap(st->dpy, pixmap);
+
+    analogtv_setup_sync(input, 1, (random()%20)==0);
+    analogtv_load_ximage(st->tv, input, image, 0, 0, 0, 0, 0);
+    if (image) XDestroyImage(image);
+    st->chansettings[this].image_loaded_p = True;
+#if 0
+    if (name) {
+      const char *s = strrchr (name, '/');
+      if (s) s++;
+      else s = name;
+      st->chansettings[this].filename = strdup (s);
+    }
+    fprintf(stderr, "%s: loaded channel %d, %s\n", progname, this, 
+            st->chansettings[this].filename);
+#endif
+  }
+
+  /* If there are still unloaded stations, fire off another loader. */
+  if (next != -1)
+    analogtv_load_random_image (st);
+}
+
+
+/* Queues a single image for loading.  Only load one at a time.
+   The image is done loading when st->img_loader is null and
+   it->loaded_image is a pixmap.
+ */
+static void
+analogtv_load_random_image(struct state *st)
 {
-  Pixmap pixmap;
-  XImage *image=NULL;
   int width=ANALOGTV_PIC_LEN;
   int height=width*3/4;
-  int rc;
-
-  pixmap=XCreatePixmap(it->dpy, it->window, width, height, it->visdepth);
-  XSync(it->dpy, False);
-  hack_resources();
-  load_random_image(it->screen, it->window, pixmap, NULL, NULL);
-  image = XGetImage(it->dpy, pixmap, 0, 0, width, height, ~0L, ZPixmap);
-  XFreePixmap(it->dpy, pixmap);
-
-  /* Make sure the window's background is not set to None, and get the
-     grabbed bits (if any) off it as soon as possible. */
-  XSetWindowBackground (it->dpy, it->window,
-                        get_pixel_resource ("background", "Background",
-                                            it->dpy, it->xgwa.colormap));
-  XClearWindow (it->dpy, it->window);
-
-  analogtv_setup_sync(input, 1, (random()%20)==0);
-  rc=analogtv_load_ximage(it, input, image);
-  if (image) XDestroyImage(image);
-  XSync(it->dpy, False);
-  return rc;
+  Pixmap p;
+
+  if (st->image_loading_p)  /* a load is already in progress */
+    return;
+
+  st->image_loading_p = True;
+  p = XCreatePixmap(st->dpy, st->window, width, height, st->tv->visdepth);
+  hack_resources(st->dpy);
+  load_image_async (st->tv->xgwa.screen, st->window, p, image_loaded_cb, st);
 }
 
-int
-analogtv_load_xpm(analogtv *it, analogtv_input *input, char **xpm)
+
+static void add_stations(struct state *st)
 {
-  Pixmap pixmap;
-  XImage *image;
-  int width,height;
-  int rc;
-
-  pixmap=xpm_data_to_pixmap (it->dpy, it->window, xpm,
-                             &width, &height, NULL);
-  image = XGetImage(it->dpy, pixmap, 0, 0, width, height, ~0L, ZPixmap);
-  XFreePixmap(it->dpy, pixmap);
-  rc=analogtv_load_ximage(it, input, image);
-  if (image) XDestroyImage(image);
-  XSync(it->dpy, False);
-  return rc;
+  while (st->n_stations < MAX_STATIONS) {
+    analogtv_input *input=analogtv_input_allocate();
+    st->stations[st->n_stations++]=input;
+    input->client_data = st;
+  }
 }
 
-enum { MAX_STATIONS = 6 };
-static int n_stations;
-static analogtv_input *stations[MAX_STATIONS];
 
-
-void add_stations(void)
+static void load_station_images(struct state *st)
 {
-  while (n_stations < MAX_STATIONS) {
-    analogtv_input *input=analogtv_input_allocate();
-    stations[n_stations++]=input;
+  int i;
+  for (i = 0; i < MAX_STATIONS; i++) {
+    analogtv_input *input = st->stations[i];
 
-    if (n_stations==1) {
+    st->chansettings[i].image_loaded_p = True;
+    if (i == 0 ||   /* station 0 is always colorbars */
+        st->colorbars_only_p) {
       input->updater = update_smpte_colorbars;
       input->do_teletext=1;
     }
 #ifdef USE_TEST_PATTERNS
     else if (random()%5==0) {
-      j=random()%countof(test_patterns);
-      analogtv_setup_sync(input);
-      analogtv_load_xpm(tv, input, test_patterns[j]);
+      int count = 0, j;
+      for (count = 0; st->test_patterns[count]; count++)
+        ;
+      j=random()%count;
+      analogtv_setup_sync(input, 1, 0);
+      analogtv_load_ximage(st->tv, input, st->test_patterns[j],
+                           0, 0, 0, 0, 0);
       analogtv_setup_teletext(input);
     }
 #endif
     else {
-      analogtv_load_random_image(tv, input);
+      analogtv_load_random_image(st);
       input->do_teletext=1;
+      st->chansettings[i].image_loaded_p = False;
     }
   }
 }
 
 
-void
-screenhack (Display *dpy, Window window)
+static void *
+xanalogtv_init (Display *dpy, Window window)
 {
+  struct state *st = (struct state *) calloc (1, sizeof(*st));
   int i;
-  int curinputi;
-  int change_ticks;
-  int using_mouse=0;
-  int change_now;
-  chansetting chansettings[N_CHANNELS];
-  chansetting *cs;
   int last_station=42;
-  int delay = get_integer_resource("delay", "Integer");
+  int delay = get_integer_resource(dpy, "delay", "Integer");
+
   if (delay < 1) delay = 1;
 
-  analogtv_make_font(dpy, window, &ugly_font, 7, 10, "6x10");
+  analogtv_make_font(dpy, window, &st->ugly_font, 7, 10, "6x10");
   
-  tv=analogtv_allocate(dpy, window);
-  tv->event_handler = screenhack_handle_event;
+  st->dpy = dpy;
+  st->window = window;
+  st->tv=analogtv_allocate(dpy, window);
 
-  add_stations();
+  st->colorbars_only_p =
+    get_boolean_resource(dpy, "colorbarsOnly", "ColorbarsOnly");
 
-  analogtv_set_defaults(tv, "");
-  tv->need_clear=1;
+  /* if (!st->colorbars_only_p) */
+    {
+      int w, h;
+      Pixmap mask = 0;
+      Pixmap p = image_data_to_pixmap (dpy, window,
+                                       logo_180_png, sizeof(logo_180_png),
+                                       &w, &h, &mask);
+      st->logo = XGetImage (dpy, p, 0, 0, w, h, ~0L, ZPixmap);
+      XFreePixmap (dpy, p);
+      if (mask)
+        {
+          st->logo_mask = XGetImage (dpy, mask, 0, 0, w, h, ~0L, ZPixmap);
+          XFreePixmap (dpy, mask);
+        }
+    }
+
+# ifdef USE_TEST_PATTERNS
+  {
+    int i = 0;
+    int w, h;
+    Pixmap p;
+    p = image_data_to_pixmap (dpy, window,
+                              testcard_rca_png, sizeof(testcard_rca_png),
+                              &w, &h, 0);
+    st->test_patterns[i++] = XGetImage (dpy, p, 0, 0, w, h, ~0L, ZPixmap);
+    XFreePixmap (dpy, p);
+
+    p = image_data_to_pixmap (dpy, window,
+                              testcard_pm5544_png, sizeof(testcard_pm5544_png),
+                              &w, &h, 0);
+    st->test_patterns[i++] = XGetImage (dpy, p, 0, 0, w, h, ~0L, ZPixmap);
+    XFreePixmap (dpy, p);
+
+    p = image_data_to_pixmap (dpy, window,
+                              testcard_bbcf_png, sizeof(testcard_bbcf_png),
+                              &w, &h, 0);
+    st->test_patterns[i++] = XGetImage (dpy, p, 0, 0, w, h, ~0L, ZPixmap);
+    XFreePixmap (dpy, p);
+  }
+# endif /* USE_TEST_PATTERNS */
+
+
+  add_stations(st);
+
+  analogtv_set_defaults(st->tv, "");
+  st->tv->need_clear=1;
 
   if (random()%4==0) {
-    tv->tint_control += pow(frand(2.0)-1.0, 7) * 180.0;
+    st->tv->tint_control += pow(frand(2.0)-1.0, 7) * 180.0;
   }
   if (1) {
-    tv->color_control += frand(0.3);
+    st->tv->color_control += frand(0.3);
   }
 
   for (i=0; i<N_CHANNELS; i++) {
-    memset(&chansettings[i], 0, sizeof(chansetting));
+    memset(&st->chansettings[i], 0, sizeof(chansetting));
 
-    chansettings[i].noise_level = 0.06;
-    chansettings[i].dur = 1000*delay;
+    st->chansettings[i].noise_level = 0.06;
+    st->chansettings[i].dur = 1000*delay;
 
     if (random()%6==0) {
-      chansettings[i].dur=600;
+      st->chansettings[i].dur=600;
     }
     else {
       int stati;
       for (stati=0; stati<MAX_MULTICHAN; stati++) {
-        analogtv_reception *rec=&chansettings[i].recs[stati];
+        analogtv_reception *rec=&st->chansettings[i].recs[stati];
         int station;
         while (1) {
-          station=random()%n_stations;
+          station=random()%st->n_stations;
           if (station!=last_station) break;
           if ((random()%10)==0) break;
         }
         last_station=station;
-        rec->input = stations[station];
+        rec->input = st->stations[station];
         rec->level = pow(frand(1.0), 3.0) * 2.0 + 0.05;
         rec->ofs=random()%ANALOGTV_SIGNAL_LEN;
         if (random()%3) {
@@ -425,60 +494,161 @@ screenhack (Display *dpy, Window window)
     }
   }
 
-  gettimeofday(&basetime,NULL);
+  gettimeofday(&st->basetime,NULL);
 
-  curinputi=0;
-  cs=&chansettings[curinputi];
-  change_ticks = cs->dur + 1500;
+  st->curinputi=0;
+  st->cs = &st->chansettings[st->curinputi];
+  st->change_ticks = st->cs->dur + 1500;
 
-  tv->powerup=0.0;
-  while (1) {
-    int curticks=getticks();
-    double curtime=curticks*0.001;
+  st->tv->powerup=0.0;
 
-    change_now=0;
-    if (analogtv_handle_events(tv)) {
-      using_mouse=1;
-      change_now=1;
-    }
-    if (change_now || (!using_mouse && curticks>=change_ticks 
-                       && tv->powerup > 10.0)) {
-      curinputi=(curinputi+1)%N_CHANNELS;
-      cs=&chansettings[curinputi];
-      change_ticks = curticks + cs->dur;
-      /* Set channel change noise flag */
-      tv->channel_change_cycles=200000;
-    }
+  load_station_images(st);
 
-    for (i=0; i<MAX_MULTICHAN; i++) {
-      analogtv_reception *rec=&cs->recs[i];
-      analogtv_input *inp=rec->input;
-      if (!inp) continue;
+  return st;
+}
 
-      if (inp->updater) {
-        inp->next_update_time = curtime;
-        (inp->updater)(inp);
-      }
-      rec->ofs += rec->freqerr;
-    }
+static unsigned long
+xanalogtv_draw (Display *dpy, Window window, void *closure)
+{
+  struct state *st = (struct state *) closure;
+  int i;
 
-    tv->powerup=curtime;
+  int curticks=getticks(st);
+  double curtime=curticks*0.001;
 
-    analogtv_init_signal(tv, cs->noise_level);
-    for (i=0; i<MAX_MULTICHAN; i++) {
-      analogtv_reception *rec=&cs->recs[i];
-      analogtv_input *inp=rec->input;
-      if (!inp) continue;
+  const analogtv_reception *recs[MAX_MULTICHAN];
+  unsigned rec_count = 0;
 
+  int auto_change = curticks >= st->change_ticks && st->tv->powerup > 10.0 ? 1 : 0;
+
+  if (st->change_now || auto_change) {
+    st->curinputi=(st->curinputi+st->change_now+auto_change+N_CHANNELS)%N_CHANNELS;
+    st->change_now = 0;
+    st->cs = &st->chansettings[st->curinputi];
+#if 0
+    fprintf (stderr, "%s: channel %d, %s\n", progname, st->curinputi,
+             st->cs->filename);
+#endif
+    st->change_ticks = curticks + st->cs->dur;
+    /* Set channel change noise flag */
+    st->tv->channel_change_cycles=200000;
+  }
+
+  for (i=0; i<MAX_MULTICHAN; i++) {
+    analogtv_reception *rec = &st->cs->recs[i];
+    analogtv_input *inp=rec->input;
+    if (!inp) continue;
+
+    if (inp->updater) {
+      inp->next_update_time = curtime;
+      (inp->updater)(inp);
+    }
+    rec->ofs += rec->freqerr;
+  }
+
+  st->tv->powerup=curtime;
+
+  for (i=0; i<MAX_MULTICHAN; i++) {
+    analogtv_reception *rec = &st->cs->recs[i];
+    if (rec->input) {
       analogtv_reception_update(rec);
-      analogtv_add_signal(tv, rec);
+      recs[rec_count] = rec;
+      ++rec_count;
     }
-    analogtv_draw(tv);
   }
+  analogtv_draw(st->tv, st->cs->noise_level, recs, rec_count);
 
-  XSync(dpy, False);
-  XClearWindow(dpy, window);
-  
-  if (tv) analogtv_release(tv);
+#ifdef HAVE_MOBILE
+  return 0;
+#else
+  return 5000;
+#endif
+}
+
+static void
+xanalogtv_reshape (Display *dpy, Window window, void *closure, 
+                 unsigned int w, unsigned int h)
+{
+  struct state *st = (struct state *) closure;
+  analogtv_reconfigure(st->tv);
+}
+
+static Bool
+xanalogtv_event (Display *dpy, Window window, void *closure, XEvent *event)
+{
+  struct state *st = (struct state *) closure;
+
+  if (event->type == ButtonPress)
+    {
+      unsigned button = event->xbutton.button;
+      st->change_now = button == 2 || button == 3 || button == 5 ? -1 : 1;
+      return True;
+    }
+  else if (event->type == KeyPress)
+    {
+      KeySym keysym;
+      char c = 0;
+      XLookupString (&event->xkey, &c, 1, &keysym, 0);
+      if (c == ' ' || c == '\t' || c == '\r' || c == '\n' ||
+          keysym == XK_Up || keysym == XK_Right || keysym == XK_Prior)
+        {
+          st->change_now = 1;
+          return True;
+        }
+      else if (c == '\b' ||
+               keysym == XK_Down || keysym == XK_Left || keysym == XK_Next)
+        {
+          st->change_now = -1;
+          return True;
+        }
+      else if (screenhack_event_helper (dpy, window, event))
+        goto DEF;
+    }
+  else if (screenhack_event_helper (dpy, window, event))
+    {
+    DEF:
+      st->change_now = ((random() & 1) ? 1 : -1);
+      return True;
+    }
+
+  return False;
+}
+
+static void
+xanalogtv_free (Display *dpy, Window window, void *closure)
+{
+  struct state *st = (struct state *) closure;
+  analogtv_release(st->tv);
+  if (st->logo) XDestroyImage (st->logo);
+  if (st->logo_mask) XDestroyImage (st->logo_mask);
+# ifdef USE_TEST_PATTERNS
+  {
+    int i;
+    for (i = 0; i < countof(st->test_patterns); i++)
+      if (st->test_patterns[i]) XDestroyImage (st->test_patterns[i]);
+  }
+# endif
+  free (st);
 }
 
+
+static const char *xanalogtv_defaults [] = {
+  ".background:                black",
+  ".foreground:                white",
+  "*delay:             5",
+  "*grabDesktopImages:  False",   /* HAVE_JWXYZ */
+  "*chooseRandomImages: True",    /* HAVE_JWXYZ */
+  "*colorbarsOnly:      False",
+  ANALOGTV_DEFAULTS
+  0,
+};
+
+static XrmOptionDescRec xanalogtv_options [] = {
+  { "-delay",          ".delay",               XrmoptionSepArg, 0 },
+  { "-colorbars-only", ".colorbarsOnly",       XrmoptionNoArg, "True" },
+  ANALOGTV_OPTIONS
+  { 0, 0, 0, 0 }
+};
+
+
+XSCREENSAVER_MODULE ("XAnalogTV", xanalogtv)