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 | #include <math.h> |
---|
28 | #include "screenhack.h" |
---|
29 | #include "xpm-pixmap.h" |
---|
30 | #include "analogtv.h" |
---|
31 | #include <stdio.h> |
---|
32 | #include <time.h> |
---|
33 | #include <sys/time.h> |
---|
34 | #include <X11/Xutil.h> |
---|
35 | #include <X11/Intrinsic.h> |
---|
36 | |
---|
37 | #include "images/logo-50.xpm" |
---|
38 | |
---|
39 | /* #define DEBUG 1 */ |
---|
40 | /* #define USE_TEST_PATTERNS */ |
---|
41 | |
---|
42 | #define countof(x) (sizeof((x))/sizeof((*x))) |
---|
43 | |
---|
44 | static analogtv *tv=NULL; |
---|
45 | |
---|
46 | analogtv_font ugly_font; |
---|
47 | |
---|
48 | static void |
---|
49 | update_smpte_colorbars(analogtv_input *input) |
---|
50 | { |
---|
51 | int col; |
---|
52 | int xpos, ypos; |
---|
53 | int black_ntsc[4]; |
---|
54 | |
---|
55 | /* |
---|
56 | SMPTE is the society of motion picture and television engineers, and |
---|
57 | these are the standard color bars in the US. Following the partial spec |
---|
58 | at http://broadcastengineering.com/ar/broadcasting_inside_color_bars/ |
---|
59 | These are luma, chroma, and phase numbers for each of the 7 bars. |
---|
60 | */ |
---|
61 | double top_cb_table[7][3]={ |
---|
62 | {75, 0, 0.0}, /* gray */ |
---|
63 | {69, 31, 167.0}, /* yellow */ |
---|
64 | {56, 44, 283.5}, /* cyan */ |
---|
65 | {48, 41, 240.5}, /* green */ |
---|
66 | {36, 41, 60.5}, /* magenta */ |
---|
67 | {28, 44, 103.5}, /* red */ |
---|
68 | {15, 31, 347.0} /* blue */ |
---|
69 | }; |
---|
70 | double mid_cb_table[7][3]={ |
---|
71 | {15, 31, 347.0}, /* blue */ |
---|
72 | {7, 0, 0}, /* black */ |
---|
73 | {36, 41, 60.5}, /* magenta */ |
---|
74 | {7, 0, 0}, /* black */ |
---|
75 | {56, 44, 283.5}, /* cyan */ |
---|
76 | {7, 0, 0}, /* black */ |
---|
77 | {75, 0, 0.0} /* gray */ |
---|
78 | }; |
---|
79 | |
---|
80 | analogtv_lcp_to_ntsc(0.0, 0.0, 0.0, black_ntsc); |
---|
81 | |
---|
82 | analogtv_setup_sync(input, 1, 0); |
---|
83 | analogtv_setup_teletext(input); |
---|
84 | |
---|
85 | for (col=0; col<7; col++) { |
---|
86 | analogtv_draw_solid_rel_lcp(input, col*(1.0/7.0), (col+1)*(1.0/7.0), 0.00, 0.68, |
---|
87 | top_cb_table[col][0], |
---|
88 | top_cb_table[col][1], top_cb_table[col][2]); |
---|
89 | |
---|
90 | analogtv_draw_solid_rel_lcp(input, col*(1.0/7.0), (col+1)*(1.0/7.0), 0.68, 0.75, |
---|
91 | mid_cb_table[col][0], |
---|
92 | mid_cb_table[col][1], mid_cb_table[col][2]); |
---|
93 | } |
---|
94 | |
---|
95 | analogtv_draw_solid_rel_lcp(input, 0.0, 1.0/6.0, |
---|
96 | 0.75, 1.00, 7, 40, 303); /* -I */ |
---|
97 | analogtv_draw_solid_rel_lcp(input, 1.0/6.0, 2.0/6.0, |
---|
98 | 0.75, 1.00, 100, 0, 0); /* white */ |
---|
99 | analogtv_draw_solid_rel_lcp(input, 2.0/6.0, 3.0/6.0, |
---|
100 | 0.75, 1.00, 7, 40, 33); /* +Q */ |
---|
101 | analogtv_draw_solid_rel_lcp(input, 3.0/6.0, 4.0/6.0, |
---|
102 | 0.75, 1.00, 7, 0, 0); /* black */ |
---|
103 | analogtv_draw_solid_rel_lcp(input, 12.0/18.0, 13.0/18.0, |
---|
104 | 0.75, 1.00, 3, 0, 0); /* black -4 */ |
---|
105 | analogtv_draw_solid_rel_lcp(input, 13.0/18.0, 14.0/18.0, |
---|
106 | 0.75, 1.00, 7, 0, 0); /* black */ |
---|
107 | analogtv_draw_solid_rel_lcp(input, 14.0/18.0, 15.0/18.0, |
---|
108 | 0.75, 1.00, 11, 0, 0); /* black +4 */ |
---|
109 | analogtv_draw_solid_rel_lcp(input, 5.0/6.0, 6.0/6.0, |
---|
110 | 0.75, 1.00, 7, 0, 0); /* black */ |
---|
111 | |
---|
112 | |
---|
113 | ypos=ANALOGTV_V/5; |
---|
114 | xpos=ANALOGTV_VIS_START + ANALOGTV_VIS_LEN/2; |
---|
115 | |
---|
116 | { |
---|
117 | char localname[256]; |
---|
118 | if (gethostname (localname, sizeof (localname))==0) { |
---|
119 | localname[sizeof(localname)-1]=0; /* "The returned name is null- |
---|
120 | terminated unless insufficient |
---|
121 | space is provided" */ |
---|
122 | localname[24]=0; /* limit length */ |
---|
123 | |
---|
124 | analogtv_draw_string_centered(input, &ugly_font, localname, |
---|
125 | xpos, ypos, black_ntsc); |
---|
126 | } |
---|
127 | } |
---|
128 | ypos += ugly_font.char_h*5/2; |
---|
129 | |
---|
130 | analogtv_draw_xpm(tv, input, |
---|
131 | logo_50_xpm, xpos - 100, ypos); |
---|
132 | |
---|
133 | ypos += 58; |
---|
134 | |
---|
135 | #if 0 |
---|
136 | analogtv_draw_string_centered(input, &ugly_font, "Please Stand By", xpos, ypos); |
---|
137 | ypos += ugly_font.char_h*4; |
---|
138 | #endif |
---|
139 | |
---|
140 | { |
---|
141 | char timestamp[256]; |
---|
142 | time_t t = time ((time_t *) 0); |
---|
143 | struct tm *tm = localtime (&t); |
---|
144 | |
---|
145 | /* Y2K: It is OK for this to use a 2-digit year because it's simulating a |
---|
146 | TV display and is purely decorative. */ |
---|
147 | strftime(timestamp, sizeof(timestamp)-1, "%y.%m.%d %H:%M:%S ", tm); |
---|
148 | analogtv_draw_string_centered(input, &ugly_font, timestamp, |
---|
149 | xpos, ypos, black_ntsc); |
---|
150 | } |
---|
151 | |
---|
152 | |
---|
153 | input->next_update_time += 1.0; |
---|
154 | } |
---|
155 | |
---|
156 | #if 0 |
---|
157 | static void |
---|
158 | draw_color_square(analogtv_input *input) |
---|
159 | { |
---|
160 | double xs,ys; |
---|
161 | |
---|
162 | analogtv_draw_solid_rel_lcp(input, 0.0, 1.0, 0.0, 1.0, |
---|
163 | 30.0, 0.0, 0.0); |
---|
164 | |
---|
165 | for (xs=0.0; xs<0.9999; xs+=1.0/15.0) { |
---|
166 | analogtv_draw_solid_rel_lcp(input, xs, xs, 0.0, 1.0, |
---|
167 | 100.0, 0.0, 0.0); |
---|
168 | } |
---|
169 | |
---|
170 | for (ys=0.0; ys<0.9999; ys+=1.0/11.0) { |
---|
171 | analogtv_draw_solid_rel_lcp(input, 0.0, 1.0, ys, ys, |
---|
172 | 100.0, 0.0, 0.0); |
---|
173 | } |
---|
174 | |
---|
175 | for (ys=0.0; ys<0.9999; ys+=0.01) { |
---|
176 | |
---|
177 | analogtv_draw_solid_rel_lcp(input, 0.0/15, 1.0/15, ys, ys+0.01, |
---|
178 | 40.0, 45.0, 103.5*(1.0-ys) + 347.0*ys); |
---|
179 | |
---|
180 | analogtv_draw_solid_rel_lcp(input, 14.0/15, 15.0/15, ys, ys+0.01, |
---|
181 | 40.0, 45.0, 103.5*(ys) + 347.0*(1.0-ys)); |
---|
182 | } |
---|
183 | |
---|
184 | for (ys=0.0; ys<0.9999; ys+=0.02) { |
---|
185 | analogtv_draw_solid_rel_lcp(input, 1.0/15, 2.0/15, ys*2.0/11.0+1.0/11.0, |
---|
186 | (ys+0.01)*2.0/11.0+1.0/11.0, |
---|
187 | 100.0*(1.0-ys), 0.0, 0.0); |
---|
188 | } |
---|
189 | |
---|
190 | |
---|
191 | } |
---|
192 | #endif |
---|
193 | |
---|
194 | char *progclass = "XAnalogTV"; |
---|
195 | |
---|
196 | char *defaults [] = { |
---|
197 | "*delay: 5", |
---|
198 | ANALOGTV_DEFAULTS |
---|
199 | 0, |
---|
200 | }; |
---|
201 | |
---|
202 | XrmOptionDescRec options [] = { |
---|
203 | { "-delay", ".delay", XrmoptionSepArg, 0 }, |
---|
204 | ANALOGTV_OPTIONS |
---|
205 | { 0, 0, 0, 0 } |
---|
206 | }; |
---|
207 | |
---|
208 | |
---|
209 | #ifdef USE_TEST_PATTERNS |
---|
210 | |
---|
211 | #include "images/earth.xpm" |
---|
212 | |
---|
213 | char **test_patterns[] = { |
---|
214 | earth_xpm, |
---|
215 | }; |
---|
216 | |
---|
217 | #endif |
---|
218 | |
---|
219 | |
---|
220 | enum { |
---|
221 | N_CHANNELS=12, /* Channels 2 through 13 on VHF */ |
---|
222 | MAX_MULTICHAN=2 |
---|
223 | }; |
---|
224 | |
---|
225 | typedef struct chansetting_s { |
---|
226 | |
---|
227 | analogtv_reception recs[MAX_MULTICHAN]; |
---|
228 | double noise_level; |
---|
229 | |
---|
230 | int dur; |
---|
231 | } chansetting; |
---|
232 | |
---|
233 | static struct timeval basetime; |
---|
234 | |
---|
235 | static int |
---|
236 | getticks(void) |
---|
237 | { |
---|
238 | struct timeval tv; |
---|
239 | gettimeofday(&tv,NULL); |
---|
240 | return ((tv.tv_sec - basetime.tv_sec)*1000 + |
---|
241 | (tv.tv_usec - basetime.tv_usec)/1000); |
---|
242 | } |
---|
243 | |
---|
244 | int |
---|
245 | analogtv_load_random_image(analogtv *it, analogtv_input *input) |
---|
246 | { |
---|
247 | Pixmap pixmap; |
---|
248 | XImage *image=NULL; |
---|
249 | int width=ANALOGTV_PIC_LEN; |
---|
250 | int height=width*3/4; |
---|
251 | int rc; |
---|
252 | |
---|
253 | pixmap=XCreatePixmap(it->dpy, it->window, width, height, it->visdepth); |
---|
254 | XSync(it->dpy, False); |
---|
255 | load_random_image(it->screen, it->window, pixmap, NULL); |
---|
256 | image = XGetImage(it->dpy, pixmap, 0, 0, width, height, ~0L, ZPixmap); |
---|
257 | XFreePixmap(it->dpy, pixmap); |
---|
258 | |
---|
259 | /* Make sure the window's background is not set to None, and get the |
---|
260 | grabbed bits (if any) off it as soon as possible. */ |
---|
261 | XSetWindowBackground (it->dpy, it->window, |
---|
262 | get_pixel_resource ("background", "Background", |
---|
263 | it->dpy, it->xgwa.colormap)); |
---|
264 | XClearWindow (it->dpy, it->window); |
---|
265 | |
---|
266 | analogtv_setup_sync(input, 1, (random()%20)==0); |
---|
267 | rc=analogtv_load_ximage(it, input, image); |
---|
268 | if (image) XDestroyImage(image); |
---|
269 | XSync(it->dpy, False); |
---|
270 | return rc; |
---|
271 | } |
---|
272 | |
---|
273 | int |
---|
274 | analogtv_load_xpm(analogtv *it, analogtv_input *input, char **xpm) |
---|
275 | { |
---|
276 | Pixmap pixmap; |
---|
277 | XImage *image; |
---|
278 | int width,height; |
---|
279 | int rc; |
---|
280 | |
---|
281 | pixmap=xpm_data_to_pixmap (it->dpy, it->window, xpm, |
---|
282 | &width, &height, NULL); |
---|
283 | image = XGetImage(it->dpy, pixmap, 0, 0, width, height, ~0L, ZPixmap); |
---|
284 | XFreePixmap(it->dpy, pixmap); |
---|
285 | rc=analogtv_load_ximage(it, input, image); |
---|
286 | if (image) XDestroyImage(image); |
---|
287 | XSync(it->dpy, False); |
---|
288 | return rc; |
---|
289 | } |
---|
290 | |
---|
291 | enum { MAX_STATIONS = 6 }; |
---|
292 | static int n_stations; |
---|
293 | static analogtv_input *stations[MAX_STATIONS]; |
---|
294 | |
---|
295 | |
---|
296 | void add_stations(void) |
---|
297 | { |
---|
298 | while (n_stations < MAX_STATIONS) { |
---|
299 | analogtv_input *input=analogtv_input_allocate(); |
---|
300 | stations[n_stations++]=input; |
---|
301 | |
---|
302 | if (n_stations==1) { |
---|
303 | input->updater = update_smpte_colorbars; |
---|
304 | input->do_teletext=1; |
---|
305 | } |
---|
306 | #ifdef USE_TEST_PATTERNS |
---|
307 | else if (random()%5==0) { |
---|
308 | j=random()%countof(test_patterns); |
---|
309 | analogtv_setup_sync(input); |
---|
310 | analogtv_load_xpm(tv, input, test_patterns[j]); |
---|
311 | analogtv_setup_teletext(input); |
---|
312 | } |
---|
313 | #endif |
---|
314 | else { |
---|
315 | analogtv_load_random_image(tv, input); |
---|
316 | input->do_teletext=1; |
---|
317 | } |
---|
318 | } |
---|
319 | } |
---|
320 | |
---|
321 | void |
---|
322 | screenhack (Display *dpy, Window window) |
---|
323 | { |
---|
324 | int i; |
---|
325 | int curinputi; |
---|
326 | int change_ticks; |
---|
327 | int using_mouse=0; |
---|
328 | int change_now; |
---|
329 | chansetting chansettings[N_CHANNELS]; |
---|
330 | chansetting *cs; |
---|
331 | int last_station=42; |
---|
332 | int delay = get_integer_resource("delay", "Integer"); |
---|
333 | if (delay < 1) delay = 1; |
---|
334 | |
---|
335 | analogtv_make_font(dpy, window, &ugly_font, 7, 10, "6x10"); |
---|
336 | |
---|
337 | tv=analogtv_allocate(dpy, window); |
---|
338 | tv->event_handler = screenhack_handle_event; |
---|
339 | |
---|
340 | add_stations(); |
---|
341 | |
---|
342 | analogtv_set_defaults(tv, ""); |
---|
343 | tv->need_clear=1; |
---|
344 | |
---|
345 | if (random()%4==0) { |
---|
346 | tv->tint_control += pow(frand(2.0)-1.0, 7) * 180.0; |
---|
347 | } |
---|
348 | if (1) { |
---|
349 | tv->color_control += frand(0.3); |
---|
350 | } |
---|
351 | |
---|
352 | for (i=0; i<N_CHANNELS; i++) { |
---|
353 | memset(&chansettings[i], 0, sizeof(chansetting)); |
---|
354 | |
---|
355 | chansettings[i].noise_level = 0.06; |
---|
356 | chansettings[i].dur = 1000*delay; |
---|
357 | |
---|
358 | if (random()%6==0) { |
---|
359 | chansettings[i].dur=600; |
---|
360 | } |
---|
361 | else { |
---|
362 | int stati; |
---|
363 | for (stati=0; stati<MAX_MULTICHAN; stati++) { |
---|
364 | analogtv_reception *rec=&chansettings[i].recs[stati]; |
---|
365 | int station; |
---|
366 | while (1) { |
---|
367 | station=random()%n_stations; |
---|
368 | if (station!=last_station) break; |
---|
369 | if ((random()%10)==0) break; |
---|
370 | } |
---|
371 | last_station=station; |
---|
372 | rec->input = stations[station]; |
---|
373 | rec->level = pow(frand(1.0), 3.0) * 2.0 + 0.05; |
---|
374 | rec->ofs=random()%ANALOGTV_SIGNAL_LEN; |
---|
375 | if (random()%3) { |
---|
376 | rec->multipath = frand(1.0); |
---|
377 | } else { |
---|
378 | rec->multipath=0.0; |
---|
379 | } |
---|
380 | if (stati) { |
---|
381 | /* We only set a frequency error for ghosting stations, |
---|
382 | because it doesn't matter otherwise */ |
---|
383 | rec->freqerr = (frand(2.0)-1.0) * 3.0; |
---|
384 | } |
---|
385 | |
---|
386 | if (rec->level > 0.3) break; |
---|
387 | if (random()%4) break; |
---|
388 | } |
---|
389 | } |
---|
390 | } |
---|
391 | |
---|
392 | gettimeofday(&basetime,NULL); |
---|
393 | |
---|
394 | curinputi=0; |
---|
395 | cs=&chansettings[curinputi]; |
---|
396 | change_ticks = cs->dur + 1500; |
---|
397 | |
---|
398 | tv->powerup=0.0; |
---|
399 | while (1) { |
---|
400 | int curticks=getticks(); |
---|
401 | double curtime=curticks*0.001; |
---|
402 | |
---|
403 | change_now=0; |
---|
404 | if (analogtv_handle_events(tv)) { |
---|
405 | using_mouse=1; |
---|
406 | change_now=1; |
---|
407 | } |
---|
408 | if (change_now || (!using_mouse && curticks>=change_ticks |
---|
409 | && tv->powerup > 10.0)) { |
---|
410 | curinputi=(curinputi+1)%N_CHANNELS; |
---|
411 | cs=&chansettings[curinputi]; |
---|
412 | change_ticks = curticks + cs->dur; |
---|
413 | /* Set channel change noise flag */ |
---|
414 | tv->channel_change_cycles=200000; |
---|
415 | } |
---|
416 | |
---|
417 | for (i=0; i<MAX_MULTICHAN; i++) { |
---|
418 | analogtv_reception *rec=&cs->recs[i]; |
---|
419 | analogtv_input *inp=rec->input; |
---|
420 | if (!inp) continue; |
---|
421 | |
---|
422 | if (inp->updater) { |
---|
423 | inp->next_update_time = curtime; |
---|
424 | (inp->updater)(inp); |
---|
425 | } |
---|
426 | rec->ofs += rec->freqerr; |
---|
427 | } |
---|
428 | |
---|
429 | tv->powerup=curtime; |
---|
430 | |
---|
431 | analogtv_init_signal(tv, cs->noise_level); |
---|
432 | for (i=0; i<MAX_MULTICHAN; i++) { |
---|
433 | analogtv_reception *rec=&cs->recs[i]; |
---|
434 | analogtv_input *inp=rec->input; |
---|
435 | if (!inp) continue; |
---|
436 | |
---|
437 | analogtv_reception_update(rec); |
---|
438 | analogtv_add_signal(tv, rec); |
---|
439 | } |
---|
440 | analogtv_draw(tv); |
---|
441 | } |
---|
442 | |
---|
443 | XSync(dpy, False); |
---|
444 | XClearWindow(dpy, window); |
---|
445 | |
---|
446 | if (tv) analogtv_release(tv); |
---|
447 | } |
---|
448 | |
---|