1 /* xanalogtv, Copyright (c) 2003-2018 Trevor Blackwell <tlb@tlb.org>
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
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.
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.
25 #endif /* HAVE_CONFIG_H */
34 # include <X11/Intrinsic.h> /* for XtDatabase in hack_resources() */
37 #include "screenhack.h"
38 #include "ximage-loader.h"
41 #define USE_TEST_PATTERNS
43 #include "images/gen/logo-180_png.h"
45 #ifdef USE_TEST_PATTERNS
46 # include "images/gen/testcard_rca_png.h"
47 # include "images/gen/testcard_pm5544_png.h"
48 # include "images/gen/testcard_bbcf_png.h"
52 #define countof(x) (sizeof((x))/sizeof((*x)))
55 N_CHANNELS=12, /* Channels 2 through 13 on VHF */
60 typedef struct chansetting_s {
62 analogtv_reception recs[MAX_MULTICHAN];
65 /* char *filename; was only used for diagnostics */
74 analogtv_font ugly_font;
75 struct timeval basetime;
78 analogtv_input *stations[MAX_STATIONS];
80 XImage *logo, *logo_mask;
81 # ifdef USE_TEST_PATTERNS
82 XImage *test_patterns[MAX_STATIONS];
87 chansetting chansettings[N_CHANNELS];
96 update_smpte_colorbars(analogtv_input *input)
98 struct state *st = (struct state *) input->client_data;
104 SMPTE is the society of motion picture and television engineers, and
105 these are the standard color bars in the US. Following the partial spec
106 at http://broadcastengineering.com/ar/broadcasting_inside_color_bars/
107 These are luma, chroma, and phase numbers for each of the 7 bars.
109 double top_cb_table[7][3]={
110 {75, 0, 0.0}, /* gray */
111 {69, 31, 167.0}, /* yellow */
112 {56, 44, 283.5}, /* cyan */
113 {48, 41, 240.5}, /* green */
114 {36, 41, 60.5}, /* magenta */
115 {28, 44, 103.5}, /* red */
116 {15, 31, 347.0} /* blue */
118 double mid_cb_table[7][3]={
119 {15, 31, 347.0}, /* blue */
120 {7, 0, 0}, /* black */
121 {36, 41, 60.5}, /* magenta */
122 {7, 0, 0}, /* black */
123 {56, 44, 283.5}, /* cyan */
124 {7, 0, 0}, /* black */
125 {75, 0, 0.0} /* gray */
128 analogtv_lcp_to_ntsc(0.0, 0.0, 0.0, black_ntsc);
130 analogtv_setup_sync(input, 1, 0);
131 analogtv_setup_teletext(input);
133 for (col=0; col<7; col++) {
134 analogtv_draw_solid_rel_lcp(input, col*(1.0/7.0), (col+1)*(1.0/7.0), 0.00, 0.68,
135 top_cb_table[col][0],
136 top_cb_table[col][1], top_cb_table[col][2]);
138 analogtv_draw_solid_rel_lcp(input, col*(1.0/7.0), (col+1)*(1.0/7.0), 0.68, 0.75,
139 mid_cb_table[col][0],
140 mid_cb_table[col][1], mid_cb_table[col][2]);
143 analogtv_draw_solid_rel_lcp(input, 0.0, 1.0/6.0,
144 0.75, 1.00, 7, 40, 303); /* -I */
145 analogtv_draw_solid_rel_lcp(input, 1.0/6.0, 2.0/6.0,
146 0.75, 1.00, 100, 0, 0); /* white */
147 analogtv_draw_solid_rel_lcp(input, 2.0/6.0, 3.0/6.0,
148 0.75, 1.00, 7, 40, 33); /* +Q */
149 analogtv_draw_solid_rel_lcp(input, 3.0/6.0, 4.0/6.0,
150 0.75, 1.00, 7, 0, 0); /* black */
151 analogtv_draw_solid_rel_lcp(input, 12.0/18.0, 13.0/18.0,
152 0.75, 1.00, 3, 0, 0); /* black -4 */
153 analogtv_draw_solid_rel_lcp(input, 13.0/18.0, 14.0/18.0,
154 0.75, 1.00, 7, 0, 0); /* black */
155 analogtv_draw_solid_rel_lcp(input, 14.0/18.0, 15.0/18.0,
156 0.75, 1.00, 11, 0, 0); /* black +4 */
157 analogtv_draw_solid_rel_lcp(input, 5.0/6.0, 6.0/6.0,
158 0.75, 1.00, 7, 0, 0); /* black */
162 xpos=ANALOGTV_VIS_START + ANALOGTV_VIS_LEN/2;
164 /* if (! st->colorbars_only_p) */
167 if (gethostname (localname, sizeof (localname))==0) {
169 localname[sizeof(localname)-1]=0; /* "The returned name is null-
170 terminated unless insufficient
171 space is provided" */
172 L = strlen(localname);
173 if (L > 6 && !strcmp(".local", localname+L-6))
176 localname[24]=0; /* limit length */
178 analogtv_draw_string_centered(input, &st->ugly_font, localname,
179 xpos, ypos, black_ntsc);
182 ypos += st->ugly_font.char_h*5/2;
186 int w2 = st->tv->xgwa.width * 0.2;
187 int h2 = st->tv->xgwa.height * 0.2;
188 analogtv_load_ximage (st->tv, input, st->logo, st->logo_mask,
189 (st->tv->xgwa.width - w2) / 2,
190 st->tv->xgwa.height * 0.28,
197 analogtv_draw_string_centered(input, &st->ugly_font,
198 "Please Stand By", xpos, ypos);
199 ypos += st->ugly_font.char_h*4;
202 /* if (! st->colorbars_only_p) */
205 time_t t = time ((time_t *) 0);
206 struct tm *tm = localtime (&t);
208 /* Y2K: It is OK for this to use a 2-digit year because it's simulating a
209 TV display and is purely decorative. */
210 strftime(timestamp, sizeof(timestamp)-1, "%y.%m.%d %H:%M:%S ", tm);
211 analogtv_draw_string_centered(input, &st->ugly_font, timestamp,
212 xpos, ypos, black_ntsc);
216 input->next_update_time += 1.0;
220 getticks(struct state *st)
223 gettimeofday(&tv,NULL);
224 return ((tv.tv_sec - st->basetime.tv_sec)*1000 +
225 (tv.tv_usec - st->basetime.tv_usec)/1000);
229 /* The first time we grab an image, do it the default way.
230 The second and subsequent times, add "-no-desktop" to the command.
231 That way we don't have to watch the window un-map 5+ times in a row.
232 Also, we end up with the desktop on only one channel, and pictures
233 on all the others (or colorbars, if no imageDirectory is set.)
236 hack_resources (Display *dpy)
239 static int count = -1;
246 XrmDatabase db = XtDatabase (dpy);
247 char *res = "desktopGrabber";
248 char *val = get_string_resource (dpy, res, "DesktopGrabber");
252 sprintf (buf1, "%.100s.%.100s", progname, res);
253 sprintf (buf2, "%.200s -no-desktop", val);
255 value.size = strlen(buf2);
256 XrmPutResource (&db, buf1, "String", &value);
258 #endif /* HAVE_JWXYZ */
262 static void analogtv_load_random_image(struct state *);
265 static void image_loaded_cb (Screen *screen, Window window, Drawable pixmap,
266 const char *name, XRectangle *geometry,
269 /* When an image has just been loaded, store it into the first available
270 channel. If there are other unloaded channels, then start loading
273 struct state *st = (struct state *) closure;
278 if (!st->image_loading_p) abort(); /* only one at a time... */
279 st->image_loading_p = False;
281 for (i = 0; i < MAX_STATIONS; i++) {
282 if (! st->chansettings[i].image_loaded_p) {
283 if (this == -1) this = i;
284 else if (next == -1) next = i;
287 if (this == -1) abort(); /* no unloaded stations? */
289 /* Load this image into the next channel. */
291 analogtv_input *input = st->stations[this];
292 int width=ANALOGTV_PIC_LEN;
293 int height=width*3/4;
294 XImage *image = XGetImage (st->dpy, pixmap, 0, 0,
295 width, height, ~0L, ZPixmap);
296 XFreePixmap(st->dpy, pixmap);
298 analogtv_setup_sync(input, 1, (random()%20)==0);
299 analogtv_load_ximage(st->tv, input, image, 0, 0, 0, 0, 0);
300 if (image) XDestroyImage(image);
301 st->chansettings[this].image_loaded_p = True;
304 const char *s = strrchr (name, '/');
307 st->chansettings[this].filename = strdup (s);
309 fprintf(stderr, "%s: loaded channel %d, %s\n", progname, this,
310 st->chansettings[this].filename);
314 /* If there are still unloaded stations, fire off another loader. */
316 analogtv_load_random_image (st);
320 /* Queues a single image for loading. Only load one at a time.
321 The image is done loading when st->img_loader is null and
322 it->loaded_image is a pixmap.
325 analogtv_load_random_image(struct state *st)
327 int width=ANALOGTV_PIC_LEN;
328 int height=width*3/4;
331 if (st->image_loading_p) /* a load is already in progress */
334 st->image_loading_p = True;
335 p = XCreatePixmap(st->dpy, st->window, width, height, st->tv->visdepth);
336 hack_resources(st->dpy);
337 load_image_async (st->tv->xgwa.screen, st->window, p, image_loaded_cb, st);
341 static void add_stations(struct state *st)
343 while (st->n_stations < MAX_STATIONS) {
344 analogtv_input *input=analogtv_input_allocate();
345 st->stations[st->n_stations++]=input;
346 input->client_data = st;
351 static void load_station_images(struct state *st)
354 for (i = 0; i < MAX_STATIONS; i++) {
355 analogtv_input *input = st->stations[i];
357 st->chansettings[i].image_loaded_p = True;
358 if (i == 0 || /* station 0 is always colorbars */
359 st->colorbars_only_p) {
360 input->updater = update_smpte_colorbars;
361 input->do_teletext=1;
363 #ifdef USE_TEST_PATTERNS
364 else if (random()%5==0) {
366 for (count = 0; st->test_patterns[count]; count++)
369 analogtv_setup_sync(input, 1, 0);
370 analogtv_load_ximage(st->tv, input, st->test_patterns[j],
372 analogtv_setup_teletext(input);
376 analogtv_load_random_image(st);
377 input->do_teletext=1;
378 st->chansettings[i].image_loaded_p = False;
385 xanalogtv_init (Display *dpy, Window window)
387 struct state *st = (struct state *) calloc (1, sizeof(*st));
390 int delay = get_integer_resource(dpy, "delay", "Integer");
392 if (delay < 1) delay = 1;
394 analogtv_make_font(dpy, window, &st->ugly_font, 7, 10, "6x10");
398 st->tv=analogtv_allocate(dpy, window);
400 st->colorbars_only_p =
401 get_boolean_resource(dpy, "colorbarsOnly", "ColorbarsOnly");
403 /* if (!st->colorbars_only_p) */
407 Pixmap p = image_data_to_pixmap (dpy, window,
408 logo_180_png, sizeof(logo_180_png),
410 st->logo = XGetImage (dpy, p, 0, 0, w, h, ~0L, ZPixmap);
411 XFreePixmap (dpy, p);
414 st->logo_mask = XGetImage (dpy, mask, 0, 0, w, h, ~0L, ZPixmap);
415 XFreePixmap (dpy, mask);
419 # ifdef USE_TEST_PATTERNS
424 p = image_data_to_pixmap (dpy, window,
425 testcard_rca_png, sizeof(testcard_rca_png),
427 st->test_patterns[i++] = XGetImage (dpy, p, 0, 0, w, h, ~0L, ZPixmap);
428 XFreePixmap (dpy, p);
430 p = image_data_to_pixmap (dpy, window,
431 testcard_pm5544_png, sizeof(testcard_pm5544_png),
433 st->test_patterns[i++] = XGetImage (dpy, p, 0, 0, w, h, ~0L, ZPixmap);
434 XFreePixmap (dpy, p);
436 p = image_data_to_pixmap (dpy, window,
437 testcard_bbcf_png, sizeof(testcard_bbcf_png),
439 st->test_patterns[i++] = XGetImage (dpy, p, 0, 0, w, h, ~0L, ZPixmap);
440 XFreePixmap (dpy, p);
442 # endif /* USE_TEST_PATTERNS */
447 analogtv_set_defaults(st->tv, "");
448 st->tv->need_clear=1;
451 st->tv->tint_control += pow(frand(2.0)-1.0, 7) * 180.0;
454 st->tv->color_control += frand(0.3);
457 for (i=0; i<N_CHANNELS; i++) {
458 memset(&st->chansettings[i], 0, sizeof(chansetting));
460 st->chansettings[i].noise_level = 0.06;
461 st->chansettings[i].dur = 1000*delay;
464 st->chansettings[i].dur=600;
468 for (stati=0; stati<MAX_MULTICHAN; stati++) {
469 analogtv_reception *rec=&st->chansettings[i].recs[stati];
472 station=random()%st->n_stations;
473 if (station!=last_station) break;
474 if ((random()%10)==0) break;
476 last_station=station;
477 rec->input = st->stations[station];
478 rec->level = pow(frand(1.0), 3.0) * 2.0 + 0.05;
479 rec->ofs=random()%ANALOGTV_SIGNAL_LEN;
481 rec->multipath = frand(1.0);
486 /* We only set a frequency error for ghosting stations,
487 because it doesn't matter otherwise */
488 rec->freqerr = (frand(2.0)-1.0) * 3.0;
491 if (rec->level > 0.3) break;
492 if (random()%4) break;
497 gettimeofday(&st->basetime,NULL);
500 st->cs = &st->chansettings[st->curinputi];
501 st->change_ticks = st->cs->dur + 1500;
505 load_station_images(st);
511 xanalogtv_draw (Display *dpy, Window window, void *closure)
513 struct state *st = (struct state *) closure;
516 int curticks=getticks(st);
517 double curtime=curticks*0.001;
519 const analogtv_reception *recs[MAX_MULTICHAN];
520 unsigned rec_count = 0;
522 int auto_change = curticks >= st->change_ticks && st->tv->powerup > 10.0 ? 1 : 0;
524 if (st->change_now || auto_change) {
525 st->curinputi=(st->curinputi+st->change_now+auto_change+N_CHANNELS)%N_CHANNELS;
527 st->cs = &st->chansettings[st->curinputi];
529 fprintf (stderr, "%s: channel %d, %s\n", progname, st->curinputi,
532 st->change_ticks = curticks + st->cs->dur;
533 /* Set channel change noise flag */
534 st->tv->channel_change_cycles=200000;
537 for (i=0; i<MAX_MULTICHAN; i++) {
538 analogtv_reception *rec = &st->cs->recs[i];
539 analogtv_input *inp=rec->input;
543 inp->next_update_time = curtime;
546 rec->ofs += rec->freqerr;
549 st->tv->powerup=curtime;
551 for (i=0; i<MAX_MULTICHAN; i++) {
552 analogtv_reception *rec = &st->cs->recs[i];
554 analogtv_reception_update(rec);
555 recs[rec_count] = rec;
559 analogtv_draw(st->tv, st->cs->noise_level, recs, rec_count);
569 xanalogtv_reshape (Display *dpy, Window window, void *closure,
570 unsigned int w, unsigned int h)
572 struct state *st = (struct state *) closure;
573 analogtv_reconfigure(st->tv);
577 xanalogtv_event (Display *dpy, Window window, void *closure, XEvent *event)
579 struct state *st = (struct state *) closure;
581 if (event->type == ButtonPress)
583 unsigned button = event->xbutton.button;
584 st->change_now = button == 2 || button == 3 || button == 5 ? -1 : 1;
587 else if (event->type == KeyPress)
591 XLookupString (&event->xkey, &c, 1, &keysym, 0);
592 if (c == ' ' || c == '\t' || c == '\r' || c == '\n' ||
593 keysym == XK_Up || keysym == XK_Right || keysym == XK_Prior)
598 else if (c == '\b' ||
599 keysym == XK_Down || keysym == XK_Left || keysym == XK_Next)
604 else if (screenhack_event_helper (dpy, window, event))
607 else if (screenhack_event_helper (dpy, window, event))
610 st->change_now = ((random() & 1) ? 1 : -1);
618 xanalogtv_free (Display *dpy, Window window, void *closure)
620 struct state *st = (struct state *) closure;
621 analogtv_release(st->tv);
622 if (st->logo) XDestroyImage (st->logo);
623 if (st->logo_mask) XDestroyImage (st->logo_mask);
624 # ifdef USE_TEST_PATTERNS
627 for (i = 0; i < countof(st->test_patterns); i++)
628 if (st->test_patterns[i]) XDestroyImage (st->test_patterns[i]);
635 static const char *xanalogtv_defaults [] = {
636 ".background: black",
637 ".foreground: white",
639 "*grabDesktopImages: False", /* HAVE_JWXYZ */
640 "*chooseRandomImages: True", /* HAVE_JWXYZ */
641 "*colorbarsOnly: False",
646 static XrmOptionDescRec xanalogtv_options [] = {
647 { "-delay", ".delay", XrmoptionSepArg, 0 },
648 { "-colorbars-only", ".colorbarsOnly", XrmoptionNoArg, "True" },
654 XSCREENSAVER_MODULE ("XAnalogTV", xanalogtv)