1 /* xscreensaver, Copyright (c) 1999 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 * Phosphor -- simulate a glass tty with long-sustain phosphor.
14 #include "screenhack.h"
16 #include <X11/Xutil.h>
17 #include <X11/Xatom.h>
18 #include <X11/Intrinsic.h>
20 extern XtAppContext app;
24 #define MAX(a,b) ((a)>(b)?(a):(b))
25 #define MIN(a,b) ((a)<(b)?(a):(b))
32 #define CURSOR_INDEX 128
40 #endif /* FUZZY_BORDER */
53 XWindowAttributes xgwa;
55 int grid_width, grid_height;
56 int char_width, char_height;
66 #endif /* FUZZY_BORDER */
70 int cursor_x, cursor_y;
71 XtIntervalId cursor_timer;
76 Bool input_available_p;
77 Time subproc_relaunch_delay;
82 static void capture_font_bits (p_state *state);
83 static p_char *make_character (p_state *state, int c);
84 static void drain_input (p_state *state);
85 static void char_to_pixmap (p_state *state, p_char *pc, int c);
86 static void launch_text_generator (p_state *state);
89 /* About font metrics:
91 "lbearing" is the distance from the leftmost pixel of a character to
92 the logical origin of that character. That is, it is the number of
93 pixels of the character which are to the left of its logical origin.
95 "rbearing" is the distance from the logical origin of a character to
96 the rightmost pixel of a character. That is, it is the number of
97 pixels of the character to the right of its logical origin.
99 "descent" is the distance from the bottommost pixel of a character to
100 the logical baseline. That is, it is the number of pixels of the
101 character which are below the baseline.
103 "ascent" is the distance from the logical baseline to the topmost pixel.
104 That is, it is the number of pixels of the character above the baseline.
106 Therefore, the bounding box of the "ink" of a character is
107 lbearing + rbearing by ascent + descent;
109 "width" is the distance from the logical origin of this character to
110 the position where the logical orgin of the next character should be
113 For our purposes, we're only interested in the part of the character
114 lying inside the "width" box. If the characters have ink outside of
115 that box (the "charcell" box) then we're going to lose it. Alas.
119 init_phosphor (Display *dpy, Window window)
123 p_state *state = (p_state *) calloc (sizeof(*state), 1);
124 char *fontname = get_string_resource ("font", "Font");
128 state->window = window;
130 XGetWindowAttributes (dpy, window, &state->xgwa);
132 state->font = XLoadQueryFont (dpy, fontname);
136 fprintf(stderr, "couldn't load font \"%s\"\n", fontname);
137 state->font = XLoadQueryFont (dpy, "fixed");
141 fprintf(stderr, "couldn't load font \"fixed\"");
146 state->scale = get_integer_resource ("scale", "Integer");
147 state->ticks = 3 + get_integer_resource ("ticks", "Integer");
150 for (i = 0; i < font->n_properties; i++)
151 if (font->properties[i].name == XA_FONT)
152 printf ("font: %s\n", XGetAtomName(dpy, font->properties[i].card32));
155 state->cursor_blink = get_integer_resource ("cursor", "Time");
156 state->subproc_relaunch_delay =
157 (1000 * get_integer_resource ("relaunch", "Time"));
159 state->char_width = font->max_bounds.width;
160 state->char_height = font->max_bounds.ascent + font->max_bounds.descent;
162 state->grid_width = state->xgwa.width / (state->char_width * state->scale);
163 state->grid_height = state->xgwa.height /(state->char_height * state->scale);
164 state->cells = (p_cell *) calloc (sizeof(p_cell),
165 state->grid_width * state->grid_height);
166 state->chars = (p_char **) calloc (sizeof(p_char *), 256);
168 state->gcs = (GC *) calloc (sizeof(GC), state->ticks + 1);
171 int ncolors = MAX (0, state->ticks - 3);
172 XColor *colors = (XColor *) calloc (ncolors, sizeof(XColor));
174 double s1, s2, v1, v2;
176 unsigned long fg = get_pixel_resource ("foreground", "Foreground",
177 state->dpy, state->xgwa.colormap);
178 unsigned long bg = get_pixel_resource ("background", "Background",
179 state->dpy, state->xgwa.colormap);
180 unsigned long flare = get_pixel_resource ("flareForeground", "Foreground",
181 state->dpy,state->xgwa.colormap);
182 unsigned long fade = get_pixel_resource ("fadeForeground", "Foreground",
183 state->dpy,state->xgwa.colormap);
188 XQueryColor (state->dpy, state->xgwa.colormap, &start);
191 XQueryColor (state->dpy, state->xgwa.colormap, &end);
193 /* Now allocate a ramp of colors from the main color to the background. */
194 rgb_to_hsv (start.red, start.green, start.blue, &h1, &s1, &v1);
195 rgb_to_hsv (end.red, end.green, end.blue, &h2, &s2, &v2);
196 make_color_ramp (state->dpy, state->xgwa.colormap,
202 /* Now, GCs all around.
204 state->gcv.font = font->fid;
205 state->gcv.cap_style = CapRound;
207 state->gcv.line_width = (int) (((long) state->scale) * 1.3);
208 if (state->gcv.line_width == state->scale)
209 state->gcv.line_width++;
210 #else /* !FUZZY_BORDER */
211 state->gcv.line_width = (int) (((long) state->scale) * 0.9);
212 if (state->gcv.line_width >= state->scale)
213 state->gcv.line_width = state->scale - 1;
214 if (state->gcv.line_width < 1)
215 state->gcv.line_width = 1;
216 #endif /* !FUZZY_BORDER */
218 flags = (GCForeground | GCBackground | GCCapStyle | GCLineWidth);
220 state->gcv.background = bg;
221 state->gcv.foreground = bg;
222 state->gcs[BLANK] = XCreateGC (state->dpy, state->window, flags,
225 state->gcv.foreground = flare;
226 state->gcs[FLARE] = XCreateGC (state->dpy, state->window, flags,
229 state->gcv.foreground = fg;
230 state->gcs[NORMAL] = XCreateGC (state->dpy, state->window, flags,
233 for (i = 0; i < ncolors; i++)
235 state->gcv.foreground = colors[i].pixel;
236 state->gcs[FADE + i] = XCreateGC (state->dpy, state->window,
241 capture_font_bits (state);
243 launch_text_generator (state);
250 capture_font_bits (p_state *state)
252 XFontStruct *font = state->font;
253 int safe_width = font->max_bounds.rbearing - font->min_bounds.lbearing;
254 int height = state->char_height;
255 unsigned char string[257];
257 Pixmap p = XCreatePixmap (state->dpy, state->window,
258 (safe_width * 256), height, 1);
260 for (i = 0; i < 256; i++)
261 string[i] = (unsigned char) i;
264 state->gcv.foreground = 0;
265 state->gcv.background = 0;
266 state->gc0 = XCreateGC (state->dpy, p,
267 (GCForeground | GCBackground),
270 state->gcv.foreground = 1;
271 state->gc1 = XCreateGC (state->dpy, p,
272 (GCFont | GCForeground | GCBackground |
273 GCCapStyle | GCLineWidth),
278 state->gcv.line_width = (int) (((long) state->scale) * 0.8);
279 if (state->gcv.line_width >= state->scale)
280 state->gcv.line_width = state->scale - 1;
281 if (state->gcv.line_width < 1)
282 state->gcv.line_width = 1;
283 state->gc2 = XCreateGC (state->dpy, p,
284 (GCFont | GCForeground | GCBackground |
285 GCCapStyle | GCLineWidth),
288 #endif /* FUZZY_BORDER */
290 XFillRectangle (state->dpy, p, state->gc0, 0, 0, (safe_width * 256), height);
292 for (i = 0; i < 256; i++)
294 if (string[i] < font->min_char_or_byte2 ||
295 string[i] > font->max_char_or_byte2)
297 XDrawString (state->dpy, p, state->gc1,
298 i * safe_width, font->ascent,
302 /* Draw the cursor. */
303 XFillRectangle (state->dpy, p, state->gc1,
304 (CURSOR_INDEX * safe_width), 1,
306 ? font->per_char['n'-font->min_char_or_byte2].width
307 : font->max_bounds.width),
311 XCopyPlane (state->dpy, p, state->window, state->gcs[FLARE],
312 0, 0, (safe_width * 256), height, 0, 0, 1L);
313 XSync(state->dpy, False);
316 XSync (state->dpy, False);
317 state->font_bits = XGetImage (state->dpy, p, 0, 0,
318 (safe_width * 256), height, ~0L, XYPixmap);
319 XFreePixmap (state->dpy, p);
321 for (i = 0; i < 256; i++)
322 state->chars[i] = make_character (state, i);
323 state->chars[CURSOR_INDEX] = make_character (state, CURSOR_INDEX);
328 make_character (p_state *state, int c)
330 p_char *pc = (p_char *) malloc (sizeof (*pc));
331 pc->name = (unsigned char) c;
332 pc->width = state->scale * state->char_width;
333 pc->height = state->scale * state->char_height;
334 char_to_pixmap (state, pc, c);
340 char_to_pixmap (p_state *state, p_char *pc, int c)
347 #endif /* FUZZY_BORDER */
351 XFontStruct *font = state->font;
352 int safe_width = font->max_bounds.rbearing - font->min_bounds.lbearing;
354 int width = state->scale * state->char_width;
355 int height = state->scale * state->char_height;
357 if (c < font->min_char_or_byte2 ||
358 c > font->max_char_or_byte2)
362 p = XCreatePixmap (state->dpy, state->window, width, height, 1);
363 XFillRectangle (state->dpy, p, state->gc0, 0, 0, width, height);
366 p2 = XCreatePixmap (state->dpy, state->window, width, height, 1);
367 XFillRectangle (state->dpy, p2, state->gc0, 0, 0, width, height);
368 #endif /* FUZZY_BORDER */
370 from = safe_width * c;
371 to = safe_width * (c + 1);
374 if (c > 75 && c < 150)
376 printf ("\n=========== %d (%c)\n", c, c);
377 for (y = 0; y < state->char_height; y++)
379 for (x1 = from; x1 < to; x1++)
380 printf (XGetPixel (state->font_bits, x1, y) ? "* " : ". ");
387 for (y = 0; y < state->char_height; y++)
388 for (x1 = from; x1 < to; x1++)
389 if (XGetPixel (state->font_bits, x1, y))
391 int xoff = state->scale / 2;
393 for (x2 = x1; x2 < to; x2++)
394 if (!XGetPixel (state->font_bits, x2, y))
397 XDrawLine (state->dpy, p, gc,
398 (x1 - from) * state->scale + xoff, y * state->scale,
399 (x2 - from) * state->scale + xoff, y * state->scale);
401 XDrawLine (state->dpy, p2, gc2,
402 (x1 - from) * state->scale + xoff, y * state->scale,
403 (x2 - from) * state->scale + xoff, y * state->scale);
404 #endif /* FUZZY_BORDER */
409 /* if (pc->blank_p && c == CURSOR_INDEX)
416 #endif /* FUZZY_BORDER */
420 /* Managing the display.
423 static void cursor_on_timer (XtPointer closure, XtIntervalId *id);
424 static void cursor_off_timer (XtPointer closure, XtIntervalId *id);
427 set_cursor_1 (p_state *state, Bool on)
429 p_cell *cell = &state->cells[state->grid_width * state->cursor_y
431 p_char *cursor = state->chars[CURSOR_INDEX];
432 int new_state = (on ? NORMAL : FADE);
434 if (cell->p_char != cursor)
435 cell->changed = True;
437 if (cell->state != new_state)
438 cell->changed = True;
440 cell->p_char = cursor;
441 cell->state = new_state;
442 return cell->changed;
446 set_cursor (p_state *state, Bool on)
448 if (set_cursor_1 (state, on))
451 if (state->cursor_timer)
452 XtRemoveTimeOut (state->cursor_timer);
453 state->cursor_timer = 0;
454 cursor_on_timer (state, 0);
462 cursor_off_timer (XtPointer closure, XtIntervalId *id)
464 p_state *state = (p_state *) closure;
465 set_cursor_1 (state, False);
466 state->cursor_timer = XtAppAddTimeOut (app, state->cursor_blink,
467 cursor_on_timer, closure);
471 cursor_on_timer (XtPointer closure, XtIntervalId *id)
473 p_state *state = (p_state *) closure;
474 set_cursor_1 (state, True);
475 state->cursor_timer = XtAppAddTimeOut (app, 2 * state->cursor_blink,
476 cursor_off_timer, closure);
481 clear (p_state *state)
486 for (y = 0; y < state->grid_height; y++)
487 for (x = 0; x < state->grid_width; x++)
489 p_cell *cell = &state->cells[state->grid_width * y + x];
490 if (cell->state == FLARE || cell->state == NORMAL)
493 cell->changed = True;
496 set_cursor (state, True);
501 decay (p_state *state)
504 for (y = 0; y < state->grid_height; y++)
505 for (x = 0; x < state->grid_width; x++)
507 p_cell *cell = &state->cells[state->grid_width * y + x];
508 if (cell->state == FLARE)
510 cell->state = NORMAL;
511 cell->changed = True;
513 else if (cell->state >= FADE)
516 if (cell->state >= state->ticks)
518 cell->changed = True;
525 scroll (p_state *state)
528 for (x = 0; x < state->grid_width; x++)
531 for (y = 1; y < state->grid_height; y++)
533 from = &state->cells[state->grid_width * y + x];
534 to = &state->cells[state->grid_width * (y-1) + x];
536 if ((from->state == FLARE || from->state == NORMAL) &&
537 !from->p_char->blank_p)
540 to->state = NORMAL; /* should be FLARE? Looks bad... */
544 if (to->state == FLARE || to->state == NORMAL)
552 if (to->state == FLARE || to->state == NORMAL)
558 set_cursor (state, True);
563 print_char (p_state *state, int c)
565 p_cell *cell = &state->cells[state->grid_width * state->cursor_y
568 /* Start the cursor fading (in case we don't end up overwriting it.) */
569 if (cell->state == FLARE || cell->state == NORMAL)
572 cell->changed = True;
575 if (c == '\t') c = ' '; /* blah. */
577 if (c == '\r' || c == '\n')
580 if (state->cursor_y == state->grid_height - 1)
585 else if (c == '\014')
592 cell->p_char = state->chars[c];
593 cell->changed = True;
596 if (c != ' ' && cell->p_char->blank_p)
597 cell->p_char = state->chars[CURSOR_INDEX];
599 if (state->cursor_x >= state->grid_width - 1)
602 if (state->cursor_y >= state->grid_height - 1)
608 set_cursor (state, True);
613 update_display (p_state *state, Bool changed_only)
617 for (y = 0; y < state->grid_height; y++)
618 for (x = 0; x < state->grid_width; x++)
620 p_cell *cell = &state->cells[state->grid_width * y + x];
621 int width, height, tx, ty;
623 if (changed_only && !cell->changed)
626 width = state->char_width * state->scale;
627 height = state->char_height * state->scale;
631 if (cell->state == BLANK || cell->p_char->blank_p)
633 XFillRectangle (state->dpy, state->window, state->gcs[BLANK],
634 tx, ty, width, height);
639 GC gc1 = state->gcs[cell->state];
640 GC gc2 = ((cell->state + 2) < state->ticks
641 ? state->gcs[cell->state + 2]
643 XCopyPlane (state->dpy, cell->p_char->pixmap, state->window,
645 0, 0, width, height, tx, ty, 1L);
648 XSetClipMask (state->dpy, gc1, cell->p_char->pixmap2);
649 XSetClipOrigin (state->dpy, gc1, tx, ty);
650 XFillRectangle (state->dpy, state->window, gc1,
651 tx, ty, width, height);
652 XSetClipMask (state->dpy, gc1, None);
654 #else /* !FUZZY_BORDER */
656 XCopyPlane (state->dpy,
657 cell->p_char->pixmap, state->window,
658 state->gcs[cell->state],
659 0, 0, width, height, tx, ty, 1L);
661 #endif /* !FUZZY_BORDER */
664 cell->changed = False;
670 run_phosphor (p_state *state)
672 update_display (state, True);
682 subproc_cb (XtPointer closure, int *source, XtInputId *id)
684 p_state *state = (p_state *) closure;
685 state->input_available_p = True;
690 launch_text_generator (p_state *state)
692 char *oprogram = get_string_resource ("program", "Program");
693 char *program = (char *) malloc (strlen (oprogram) + 10);
695 strcpy (program, "( ");
696 strcat (program, oprogram);
697 strcat (program, " ) 2>&1");
699 if ((state->pipe = popen (program, "r")))
702 XtAppAddInput (app, fileno (state->pipe),
703 (XtPointer) (XtInputReadMask | XtInputExceptMask),
704 subproc_cb, (XtPointer) state);
714 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
716 p_state *state = (p_state *) closure;
717 launch_text_generator (state);
722 drain_input (p_state *state)
724 if (state->input_available_p)
727 int n = read (fileno (state->pipe), (void *) s, 1);
730 print_char (state, s[0]);
734 XtRemoveInput (state->pipe_id);
736 pclose (state->pipe);
739 if (state->cursor_x != 0) /* break line if unbroken */
740 print_char (state, '\n'); /* blank line */
741 print_char (state, '\n');
743 /* Set up a timer to re-launch the subproc in a bit. */
744 XtAppAddTimeOut (app, state->subproc_relaunch_delay,
745 relaunch_generator_timer,
749 state->input_available_p = False;
755 char *progclass = "Phosphor";
757 char *defaults [] = {
758 ".background: Black",
759 ".foreground: Green",
760 "*fadeForeground: DarkGreen",
761 "*flareForeground: White",
767 "*program: " ZIPPY_PROGRAM,
772 XrmOptionDescRec options [] = {
773 { "-font", ".font", XrmoptionSepArg, 0 },
774 { "-scale", ".scale", XrmoptionSepArg, 0 },
775 { "-ticks", ".ticks", XrmoptionSepArg, 0 },
776 { "-delay", ".delay", XrmoptionSepArg, 0 },
777 { "-program", ".program", XrmoptionSepArg, 0 },
783 screenhack (Display *dpy, Window window)
785 int delay = get_integer_resource ("delay", "Integer");
786 p_state *state = init_phosphor (dpy, window);
792 run_phosphor (state);
794 screenhack_handle_events (dpy);
796 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput|XtIMSignal))
797 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput|XtIMSignal);
799 if (delay) usleep (delay);