summaryrefslogtreecommitdiffstats
path: root/hacks/xanalogtv.c
diff options
context:
space:
mode:
Diffstat (limited to 'hacks/xanalogtv.c')
-rw-r--r--hacks/xanalogtv.c654
1 files changed, 654 insertions, 0 deletions
diff --git a/hacks/xanalogtv.c b/hacks/xanalogtv.c
new file mode 100644
index 0000000..80cc7d9
--- /dev/null
+++ b/hacks/xanalogtv.c
@@ -0,0 +1,654 @@
+/* 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
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ *
+ *
+ * Simulate test patterns on an analog TV. Concept similar to xteevee
+ * in this distribution, but a totally different implementation based
+ * on the simulation of an analog TV set in utils/analogtv.c. Much
+ * more realistic, but needs more video card bandwidth.
+ *
+ * It flips around through simulated channels 2 through 13. Some show
+ * 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.
+ */
+
+#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 "ximage-loader.h"
+#include "analogtv.h"
+
+#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 countof(x) (sizeof((x))/sizeof((*x)))
+
+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;
+};
+
+
+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];
+
+ /*
+ SMPTE is the society of motion picture and television engineers, and
+ these are the standard color bars in the US. Following the partial spec
+ at http://broadcastengineering.com/ar/broadcasting_inside_color_bars/
+ These are luma, chroma, and phase numbers for each of the 7 bars.
+ */
+ double top_cb_table[7][3]={
+ {75, 0, 0.0}, /* gray */
+ {69, 31, 167.0}, /* yellow */
+ {56, 44, 283.5}, /* cyan */
+ {48, 41, 240.5}, /* green */
+ {36, 41, 60.5}, /* magenta */
+ {28, 44, 103.5}, /* red */
+ {15, 31, 347.0} /* blue */
+ };
+ double mid_cb_table[7][3]={
+ {15, 31, 347.0}, /* blue */
+ {7, 0, 0}, /* black */
+ {36, 41, 60.5}, /* magenta */
+ {7, 0, 0}, /* black */
+ {56, 44, 283.5}, /* cyan */
+ {7, 0, 0}, /* black */
+ {75, 0, 0.0} /* gray */
+ };
+
+ analogtv_lcp_to_ntsc(0.0, 0.0, 0.0, black_ntsc);
+
+ analogtv_setup_sync(input, 1, 0);
+ analogtv_setup_teletext(input);
+
+ for (col=0; col<7; col++) {
+ analogtv_draw_solid_rel_lcp(input, col*(1.0/7.0), (col+1)*(1.0/7.0), 0.00, 0.68,
+ top_cb_table[col][0],
+ top_cb_table[col][1], top_cb_table[col][2]);
+
+ analogtv_draw_solid_rel_lcp(input, col*(1.0/7.0), (col+1)*(1.0/7.0), 0.68, 0.75,
+ mid_cb_table[col][0],
+ mid_cb_table[col][1], mid_cb_table[col][2]);
+ }
+
+ analogtv_draw_solid_rel_lcp(input, 0.0, 1.0/6.0,
+ 0.75, 1.00, 7, 40, 303); /* -I */
+ analogtv_draw_solid_rel_lcp(input, 1.0/6.0, 2.0/6.0,
+ 0.75, 1.00, 100, 0, 0); /* white */
+ analogtv_draw_solid_rel_lcp(input, 2.0/6.0, 3.0/6.0,
+ 0.75, 1.00, 7, 40, 33); /* +Q */
+ analogtv_draw_solid_rel_lcp(input, 3.0/6.0, 4.0/6.0,
+ 0.75, 1.00, 7, 0, 0); /* black */
+ analogtv_draw_solid_rel_lcp(input, 12.0/18.0, 13.0/18.0,
+ 0.75, 1.00, 3, 0, 0); /* black -4 */
+ analogtv_draw_solid_rel_lcp(input, 13.0/18.0, 14.0/18.0,
+ 0.75, 1.00, 7, 0, 0); /* black */
+ analogtv_draw_solid_rel_lcp(input, 14.0/18.0, 15.0/18.0,
+ 0.75, 1.00, 11, 0, 0); /* black +4 */
+ analogtv_draw_solid_rel_lcp(input, 5.0/6.0, 6.0/6.0,
+ 0.75, 1.00, 7, 0, 0); /* black */
+
+
+ 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, &st->ugly_font, localname,
+ xpos, ypos, black_ntsc);
+ }
+ }
+ ypos += st->ugly_font.char_h*5/2;
+
+ 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, &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);
+ struct tm *tm = localtime (&t);
+
+ /* 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, &st->ugly_font, timestamp,
+ xpos, ypos, black_ntsc);
+ }
+
+
+ input->next_update_time += 1.0;
+}
+
+static int
+getticks(struct state *st)
+{
+ struct timeval tv;
+ gettimeofday(&tv,NULL);
+ return ((tv.tv_sec - st->basetime.tv_sec)*1000 +
+ (tv.tv_usec - st->basetime.tv_usec)/1000);
+}
+
+
+/* The first time we grab an image, do it the default way.
+ The second and subsequent times, add "-no-desktop" to the command.
+ That way we don't have to watch the window un-map 5+ times in a row.
+ Also, we end up with the desktop on only one channel, and pictures
+ on all the others (or colorbars, if no imageDirectory is set.)
+ */
+static void
+hack_resources (Display *dpy)
+{
+#ifndef HAVE_JWXYZ
+ static int count = -1;
+ count++;
+
+ if (count == 0)
+ return;
+ else if (count == 1)
+ {
+ XrmDatabase db = XtDatabase (dpy);
+ char *res = "desktopGrabber";
+ char *val = get_string_resource (dpy, res, "DesktopGrabber");
+ char buf1[255];
+ char buf2[255];
+ XrmValue value;
+ 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 */
+}
+
+
+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)
+{
+ int width=ANALOGTV_PIC_LEN;
+ int height=width*3/4;
+ 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);
+}
+
+
+static void add_stations(struct state *st)
+{
+ while (st->n_stations < MAX_STATIONS) {
+ analogtv_input *input=analogtv_input_allocate();
+ st->stations[st->n_stations++]=input;
+ input->client_data = st;
+ }
+}
+
+
+static void load_station_images(struct state *st)
+{
+ int i;
+ for (i = 0; i < MAX_STATIONS; i++) {
+ analogtv_input *input = st->stations[i];
+
+ 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) {
+ 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(st);
+ input->do_teletext=1;
+ st->chansettings[i].image_loaded_p = False;
+ }
+ }
+}
+
+
+static void *
+xanalogtv_init (Display *dpy, Window window)
+{
+ struct state *st = (struct state *) calloc (1, sizeof(*st));
+ int i;
+ int last_station=42;
+ int delay = get_integer_resource(dpy, "delay", "Integer");
+
+ if (delay < 1) delay = 1;
+
+ analogtv_make_font(dpy, window, &st->ugly_font, 7, 10, "6x10");
+
+ st->dpy = dpy;
+ st->window = window;
+ st->tv=analogtv_allocate(dpy, window);
+
+ st->colorbars_only_p =
+ get_boolean_resource(dpy, "colorbarsOnly", "ColorbarsOnly");
+
+ /* 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) {
+ st->tv->tint_control += pow(frand(2.0)-1.0, 7) * 180.0;
+ }
+ if (1) {
+ st->tv->color_control += frand(0.3);
+ }
+
+ for (i=0; i<N_CHANNELS; i++) {
+ memset(&st->chansettings[i], 0, sizeof(chansetting));
+
+ st->chansettings[i].noise_level = 0.06;
+ st->chansettings[i].dur = 1000*delay;
+
+ if (random()%6==0) {
+ st->chansettings[i].dur=600;
+ }
+ else {
+ int stati;
+ for (stati=0; stati<MAX_MULTICHAN; stati++) {
+ analogtv_reception *rec=&st->chansettings[i].recs[stati];
+ int station;
+ while (1) {
+ station=random()%st->n_stations;
+ if (station!=last_station) break;
+ if ((random()%10)==0) break;
+ }
+ last_station=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) {
+ rec->multipath = frand(1.0);
+ } else {
+ rec->multipath=0.0;
+ }
+ if (stati) {
+ /* We only set a frequency error for ghosting stations,
+ because it doesn't matter otherwise */
+ rec->freqerr = (frand(2.0)-1.0) * 3.0;
+ }
+
+ if (rec->level > 0.3) break;
+ if (random()%4) break;
+ }
+ }
+ }
+
+ gettimeofday(&st->basetime,NULL);
+
+ st->curinputi=0;
+ st->cs = &st->chansettings[st->curinputi];
+ st->change_ticks = st->cs->dur + 1500;
+
+ st->tv->powerup=0.0;
+
+ load_station_images(st);
+
+ return st;
+}
+
+static unsigned long
+xanalogtv_draw (Display *dpy, Window window, void *closure)
+{
+ struct state *st = (struct state *) closure;
+ int i;
+
+ int curticks=getticks(st);
+ double curtime=curticks*0.001;
+
+ 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);
+ recs[rec_count] = rec;
+ ++rec_count;
+ }
+ }
+ analogtv_draw(st->tv, st->cs->noise_level, recs, rec_count);
+
+#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)