1 /* filmleader, Copyright (c) 2018 Jamie Zawinski <jwz@jwz.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
11 * Simulate an SMPTE Universal Film Leader playing on an analog television.
16 #endif /* HAVE_CONFIG_H */
18 #include "xft.h" /* before screenhack.h */
20 #include "screenhack.h"
26 #define countof(x) (sizeof((x))/sizeof((*x)))
31 XWindowAttributes xgwa;
33 unsigned long bg, text_color, ring_color, trace_color;
34 XftColor xft_text_color_1, xft_text_color_2;
36 XftFont *font, *font2, *font3;
40 double start, last_time;
46 analogtv_reception rec;
52 filmleader_init (Display *dpy, Window window)
54 struct state *st = (struct state *) calloc (1, sizeof(*st));
59 st->tv = analogtv_allocate (st->dpy, st->window);
60 analogtv_set_defaults (st->tv, "");
61 st->tv->need_clear = 1;
62 st->inp = analogtv_input_allocate();
63 analogtv_setup_sync (st->inp, 1, 0);
64 st->rec.input = st->inp;
66 st->tv->use_color = 1;
67 st->rec.level = pow(frand(1.0), 3.0) * 2.0 + 0.05;
68 st->rec.ofs = random() % ANALOGTV_SIGNAL_LEN;
71 st->rec.multipath = 0.0;
72 st->tv->color_control += frand(0.3);
73 st->noise = get_float_resource (st->dpy, "noise", "Float");
74 st->value = 18; /* Leave time for powerup */
75 st->stop = 2 + (random() % 5);
76 XGetWindowAttributes (dpy, window, &st->xgwa);
78 /* Let's render it into a 16:9 pixmap, since that's what most screens are
79 these days. That means the circle will be squashed on 4:3 screens. */
84 /* analogtv.c always fills whole screen on mobile, so use screen aspect. */
85 r = st->xgwa.width / (double) st->xgwa.height;
93 if (st->xgwa.width < st->xgwa.height)
100 st->pix = XCreatePixmap (dpy, window,
101 st->w > st->h ? st->w : st->h,
102 st->w > st->h ? st->w : st->h,
104 st->gc = XCreateGC (dpy, st->pix, 0, &gcv);
106 st->xftdraw = XftDrawCreate (dpy, st->pix, st->xgwa.visual,
108 st->font = load_xft_font_retry (dpy, screen_number (st->xgwa.screen),
109 get_string_resource (dpy, "numberFont",
111 st->font2 = load_xft_font_retry (dpy, screen_number (st->xgwa.screen),
112 get_string_resource (dpy, "numberFont2",
114 st->font3 = load_xft_font_retry (dpy, screen_number (st->xgwa.screen),
115 get_string_resource (dpy, "numberFont3",
118 st->bg = get_pixel_resource (dpy, st->xgwa.colormap,
119 "textBackground", "Background");
120 st->text_color = get_pixel_resource (dpy, st->xgwa.colormap,
121 "textColor", "Foreground");
122 st->ring_color = get_pixel_resource (dpy, st->xgwa.colormap,
123 "ringColor", "Foreground");
124 st->trace_color = get_pixel_resource (dpy, st->xgwa.colormap,
125 "traceColor", "Foreground");
127 XftColorAllocName (dpy, st->xgwa.visual, st->xgwa.colormap,
128 get_string_resource (dpy, "textColor", "Foreground"),
129 &st->xft_text_color_1);
130 XftColorAllocName (dpy, st->xgwa.visual, st->xgwa.colormap,
131 get_string_resource (dpy, "textBackground", "Background"),
132 &st->xft_text_color_2);
142 # ifdef GETTIMEOFDAY_TWO_ARGS
144 gettimeofday(&now, &tzp);
149 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
154 filmleader_draw (Display *dpy, Window window, void *closure)
156 struct state *st = (struct state *) closure;
157 const analogtv_reception *rec = &st->rec;
158 double then = double_time(), now, timedelta;
162 int lbearing, rbearing, ascent, descent;
164 double r = 1 - (st->value - (int) st->value);
165 int ivalue = st->value;
169 /* You may ask, why use Xft for this instead of the much simpler XDrawString?
170 Well, for some reason, XLoadQueryFont is giving me horribly-scaled bitmap
171 fonts, but Xft works properly. So perhaps in This Modern World, if one
172 expects large fonts to work, one must use Xft instead of Xlib?
174 Everything is terrible.
177 const struct { double t; int k, f; const char * const s[4]; } blurbs[] = {
178 { 9.1, 3, 1, { "PICTURE", " START", 0, 0 }},
179 { 10.0, 2, 1, { " 16", "SOUND", "START", 0 }},
180 { 10.5, 2, 1, { " 32", "SOUND", "START", 0 }},
181 { 11.6, 2, 0, { "PICTURE", "COMPANY", "SERIES", 0 }},
182 { 11.7, 2, 0, { "XSCRNSAVER", 0, 0, 0 }},
183 { 11.9, 2, 0, { "REEL No.", "PROD No.", "PLAY DATE", 0 }},
184 { 12.2, 0, 0, { " SMPTE ", "UNIVERSAL", " LEADER", 0 }},
185 { 12.3, 0, 1, { "X ", "X", "X", "X" }},
186 { 12.4, 0, 0, { " SMPTE ", "UNIVERSAL", " LEADER", 0 }},
187 { 12.5, 3, 1, { "PICTURE", 0, 0, 0 }},
188 { 12.7, 3, 1, { "HEAD", 0, 0, 0 }},
189 { 12.8, 2, 1, { "OOOO", 0, "ASPECT", "TYPE OF" }},
190 { 12.9, 2, 0, { "SOUND", 0, "RATIO", 0 }},
191 { 13.2, 1, 1, { " ", "PICTURE", 0, 0 }},
192 { 13.3, 1, 0, { "REEL No. ", "COLOR", 0, 0 }},
193 { 13.4, 1, 0, { "LENGTH ", 0, 0, "ROLL" }},
194 { 13.5, 1, 0, { "SUBJECT", 0, 0, 0 }},
195 { 13.9, 1, 1, { " \342\206\221", "SPLICE", " HERE", 0 }},
198 for (i = 0; i < countof(blurbs); i++)
200 if (st->value >= blurbs[i].t && st->value <= blurbs[i].t + 1/15.0)
204 xftfont = (blurbs[i].f == 1 ? st->font2 :
205 blurbs[i].f == 2 ? st->font : st->font3);
207 XSetForeground (dpy, st->gc,
208 blurbs[i].k == 3 ? st->bg : st->text_color);
209 XFillRectangle (dpy, st->pix, st->gc, 0, 0, st->w, st->h);
210 XSetForeground (dpy, st->gc,
211 blurbs[i].k == 3 ? st->text_color : st->bg);
212 xftcolor = (blurbs[i].k == 3 ?
213 &st->xft_text_color_1 : &st->xft_text_color_2);
215 /* The height of a string of spaces is 0... */
216 XftTextExtentsUtf8 (dpy, xftfont, (FcChar8 *) "My", 2, &extents);
217 line_height = extents.height;
219 XftTextExtentsUtf8 (dpy, xftfont, (FcChar8 *)
220 blurbs[i].s[0], strlen(blurbs[i].s[0]),
222 lbearing = -extents.x;
223 rbearing = extents.width - extents.x;
225 descent = extents.height - extents.y;
227 x = (st->w - rbearing) / 2;
228 y = st->h * 0.1 + ascent;
230 for (j = 0; j < countof(blurbs[i].s); j++)
233 XftDrawStringUtf8 (st->xftdraw, xftcolor, xftfont, x, y,
234 (FcChar8 *) blurbs[i].s[j],
235 strlen(blurbs[i].s[j]));
237 y += line_height * 1.5;
241 XftTextExtentsUtf8 (dpy, xftfont, (FcChar8 *)
242 blurbs[i].s[0], strlen(blurbs[i].s[j]),
244 lbearing = -extents.x;
245 rbearing = extents.width - extents.x;
247 descent = extents.height - extents.y;
251 if (blurbs[i].k == 2) /* Rotate clockwise and flip */
253 int wh = st->w < st->h ? st->w : st->h;
254 XImage *img1 = XGetImage (dpy, st->pix,
257 wh, wh, ~0L, ZPixmap);
258 XImage *img2 = XCreateImage (dpy, st->xgwa.visual,
259 st->xgwa.depth, ZPixmap, 0, 0,
261 img2->data = malloc (img2->bytes_per_line * img2->height);
262 for (y = 0; y < wh; y++)
263 for (x = 0; x < wh; x++)
264 XPutPixel (img2, y, x, XGetPixel (img1, x, y));
265 XSetForeground (dpy, st->gc,
266 blurbs[i].k == 3 ? st->bg : st->text_color);
267 XFillRectangle (dpy, st->pix, st->gc, 0, 0, st->w, st->h);
268 XPutImage (dpy, st->pix, st->gc, img2,
273 XDestroyImage (img1);
274 XDestroyImage (img2);
276 else if (blurbs[i].k == 1) /* Flip vertically */
278 XImage *img1 = XGetImage (dpy, st->pix, 0, 0,
279 st->w, st->h, ~0L, ZPixmap);
280 XImage *img2 = XCreateImage (dpy, st->xgwa.visual,
281 st->xgwa.depth, ZPixmap, 0, 0,
282 st->w, st->h, 32, 0);
283 img2->data = malloc (img2->bytes_per_line * img2->height);
284 for (y = 0; y < img2->height; y++)
285 for (x = 0; x < img2->width; x++)
286 XPutPixel (img2, x, img2->height-y-1,
287 XGetPixel (img1, x, y));
288 XPutImage (dpy, st->pix, st->gc, img2, 0, 0, 0, 0, st->w, st->h);
289 XDestroyImage (img1);
290 XDestroyImage (img2);
297 if (st->value < 2.0 || st->value >= 9.0) /* Black screen */
299 XSetForeground (dpy, st->gc, st->text_color);
300 XFillRectangle (dpy, st->pix, st->gc, 0, 0, st->w, st->h);
304 XSetForeground (dpy, st->gc, st->bg);
305 XFillRectangle (dpy, st->pix, st->gc, 0, 0, st->w, st->h);
307 if (r > 1/30.0) /* Sweep line and background */
309 x = st->w/2 + st->w * cos (M_PI * 2 * r - M_PI/2);
310 y = st->h/2 + st->h * sin (M_PI * 2 * r - M_PI/2);
312 XSetForeground (dpy, st->gc, st->trace_color);
313 XFillArc (dpy, st->pix, st->gc,
314 -st->w, -st->h, st->w*3, st->h*3,
316 90*64 - ((r + 0.25) * 360*64));
318 XSetForeground (dpy, st->gc, st->text_color);
319 XSetLineAttributes (dpy, st->gc, 1, LineSolid, CapRound, JoinRound);
320 XDrawLine (dpy, st->pix, st->gc, st->w/2, st->h/2, x, y);
322 XSetForeground (dpy, st->gc, st->text_color);
323 XSetLineAttributes (dpy, st->gc, 2, LineSolid, CapRound, JoinRound);
324 XDrawLine (dpy, st->pix, st->gc, st->w/2, 0, st->w/2, st->h);
325 XDrawLine (dpy, st->pix, st->gc, 0, st->h/2, st->w, st->h/2);
330 s[0] = (char) (ivalue + '0');
332 xftcolor = &st->xft_text_color_1;
333 XftTextExtentsUtf8 (dpy, xftfont, (FcChar8 *) s, 1, &extents);
334 lbearing = -extents.x;
335 rbearing = extents.width - extents.x;
337 descent = extents.height - extents.y;
339 x = (st->w - (rbearing + lbearing)) / 2;
340 y = (st->h + (ascent - descent)) / 2;
341 XftDrawStringUtf8 (st->xftdraw, xftcolor, xftfont, x, y, (FcChar8 *) s, 1);
343 /* Annotations on 7 and 4 */
345 if ((st->value >= 7.75 && st->value <= 7.85) ||
346 (st->value >= 4.00 && st->value <= 4.25))
348 XSetForeground (dpy, st->gc, st->ring_color);
349 xftcolor = &st->xft_text_color_2;
352 s[0] = (ivalue == 4 ? 'C' : 'M');
355 XftTextExtentsUtf8 (dpy, xftfont, (FcChar8 *) s, strlen(s), &extents);
356 lbearing = -extents.x;
357 rbearing = extents.width - extents.x;
359 descent = extents.height - extents.y;
362 y = st->h * 0.1 + ascent;
363 XftDrawStringUtf8 (st->xftdraw, xftcolor, xftfont, x, y,
364 (FcChar8 *) s, strlen(s));
365 x = st->w * 0.9 - (rbearing + lbearing);
366 XftDrawStringUtf8 (st->xftdraw, xftcolor, xftfont, x, y,
367 (FcChar8 *) s, strlen(s));
369 s[0] = (ivalue == 4 ? 'F' : '3');
370 s[1] = (ivalue == 4 ? 0 : '5');
373 XftTextExtentsUtf8 (dpy, xftfont, (FcChar8 *) s, strlen(s), &extents);
374 lbearing = -extents.x;
375 rbearing = extents.width - extents.x;
377 descent = extents.height - extents.y;
381 XftDrawStringUtf8 (st->xftdraw, xftcolor, xftfont, x, y,
382 (FcChar8 *) s, strlen(s));
383 x = st->w * 0.9 - (rbearing + lbearing);
384 XftDrawStringUtf8 (st->xftdraw, xftcolor, xftfont, x, y,
385 (FcChar8 *) s, strlen(s));
388 if (r > 1/30.0) /* Two rings around number */
390 double r2 = st->w / (double) st->h;
393 if (st->xgwa.width < st->xgwa.height)
396 XSetForeground (dpy, st->gc, st->ring_color);
397 XSetLineAttributes (dpy, st->gc, st->w * 0.025,
398 LineSolid, CapRound, JoinRound);
400 w2 = st->w * 0.8 * ss / r2;
401 h2 = st->h * 0.8 * ss;
402 x = (st->w - w2) / 2;
403 y = (st->h - h2) / 2;
404 XDrawArc (dpy, st->pix, st->gc, x, y, w2, h2, 0, 360*64);
408 x = (st->w - w2) / 2;
409 y = (st->h - h2) / 2;
410 XDrawArc (dpy, st->pix, st->gc, x, y, w2, h2, 0, 360*64);
415 img = XGetImage (dpy, st->pix, 0, 0, st->w, st->h, ~0L, ZPixmap);
417 analogtv_load_ximage (st->tv, st->rec.input, img, 0, 0, 0, 0, 0);
418 analogtv_reception_update (&st->rec);
419 analogtv_draw (st->tv, st->noise, &rec, 1);
424 timedelta = (1 / 29.97) - (now - then);
426 if (! st->button_down_p)
428 if (st->last_time == 0)
431 st->value -= then - st->last_time;
433 if (st->value <= 0 ||
434 (r > 0.9 && st->value <= st->stop))
436 st->value = (random() % 20) ? 8.9 : 15;
437 st->stop = ((random() % 50) ? 2 : 1) + (random() % 5);
439 if (st->value > 9) /* Spin the knobs again */
441 st->rec.level = pow(frand(1.0), 3.0) * 2.0 + 0.05;
442 st->rec.ofs = random() % ANALOGTV_SIGNAL_LEN;
443 st->tv->color_control += frand(0.3) - 0.15;
448 st->tv->powerup = then - st->start;
449 st->last_time = then;
451 return timedelta > 0 ? timedelta * 1000000 : 0;
456 filmleader_reshape (Display *dpy, Window window, void *closure,
457 unsigned int w, unsigned int h)
459 struct state *st = (struct state *) closure;
460 analogtv_reconfigure (st->tv);
461 XGetWindowAttributes (dpy, window, &st->xgwa);
463 if ((st->w > st->h) != (st->xgwa.width > st->xgwa.height))
473 filmleader_event (Display *dpy, Window window, void *closure, XEvent *event)
475 struct state *st = (struct state *) closure;
476 if (event->xany.type == ButtonPress)
478 st->button_down_p = True;
481 else if (event->xany.type == ButtonRelease)
483 st->button_down_p = False;
486 else if (screenhack_event_helper (dpy, window, event))
489 st->rec.level = pow(frand(1.0), 3.0) * 2.0 + 0.05;
490 st->rec.ofs = random() % ANALOGTV_SIGNAL_LEN;
491 st->tv->color_control += frand(0.3) - 0.15;
494 else if (event->xany.type == KeyPress)
498 XLookupString (&event->xkey, &c, 1, &keysym, 0);
499 if (c >= '2' && c <= '8')
501 st->value = (c - '0') + (st->value - (int) st->value);
511 filmleader_free (Display *dpy, Window window, void *closure)
513 struct state *st = (struct state *) closure;
514 analogtv_release (st->tv);
515 XftDrawDestroy (st->xftdraw);
516 XftColorFree(dpy, st->xgwa.visual, st->xgwa.colormap, &st->xft_text_color_1);
517 XftColorFree(dpy, st->xgwa.visual, st->xgwa.colormap, &st->xft_text_color_2);
518 XFreePixmap (dpy, st->pix);
519 XFreeGC (dpy, st->gc);
524 static const char *filmleader_defaults [] = {
526 ".background: #000000",
530 "*textBackground: #444488", /* Need much higher contrast for some reason */
531 "*textColor: #000033",
532 "*ringColor: #DDDDFF",
533 "*traceColor: #222244",
535 # else /* X11 or Cocoa */
537 "*textBackground: #9999DD",
538 "*textColor: #000015",
539 "*ringColor: #DDDDFF",
540 "*traceColor: #555577",
546 "*numberFont: Helvetica Bold 120",
547 "*numberFont2: Helvetica 36",
548 "*numberFont3: Helvetica 28",
550 # else /* X11, Cocoa or Android */
552 "*numberFont: -*-helvetica-bold-r-*-*-*-1700-*-*-*-*-*-*",
553 "*numberFont2: -*-helvetica-medium-r-*-*-*-500-*-*-*-*-*-*",
554 "*numberFont3: -*-helvetica-medium-r-*-*-*-360-*-*-*-*-*-*",
561 "*geometry: 1280x720",
565 static XrmOptionDescRec filmleader_options [] = {
566 { "-noise", ".noise", XrmoptionSepArg, 0 },
571 XSCREENSAVER_MODULE ("FilmLeader", filmleader)