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))
31 #define STATE_MAX FADE
33 #define CURSOR_INDEX 128
41 #endif /* FUZZY_BORDER */
54 XWindowAttributes xgwa;
56 int grid_width, grid_height;
57 int char_width, char_height;
67 #endif /* FUZZY_BORDER */
71 int cursor_x, cursor_y;
72 XtIntervalId cursor_timer;
77 Bool input_available_p;
78 Time subproc_relaunch_delay;
83 static void capture_font_bits (p_state *state);
84 static p_char *make_character (p_state *state, int c);
85 static void drain_input (p_state *state);
86 static void char_to_pixmap (p_state *state, p_char *pc, int c);
87 static void launch_text_generator (p_state *state);
90 /* About font metrics:
92 "lbearing" is the distance from the leftmost pixel of a character to
93 the logical origin of that character. That is, it is the number of
94 pixels of the character which are to the left of its logical origin.
96 "rbearing" is the distance from the logical origin of a character to
97 the rightmost pixel of a character. That is, it is the number of
98 pixels of the character to the right of its logical origin.
100 "descent" is the distance from the bottommost pixel of a character to
101 the logical baseline. That is, it is the number of pixels of the
102 character which are below the baseline.
104 "ascent" is the distance from the logical baseline to the topmost pixel.
105 That is, it is the number of pixels of the character above the baseline.
107 Therefore, the bounding box of the "ink" of a character is
108 lbearing + rbearing by ascent + descent;
110 "width" is the distance from the logical origin of this character to
111 the position where the logical orgin of the next character should be
114 For our purposes, we're only interested in the part of the character
115 lying inside the "width" box. If the characters have ink outside of
116 that box (the "charcell" box) then we're going to lose it. Alas.
120 init_phosphor (Display *dpy, Window window)
124 p_state *state = (p_state *) calloc (sizeof(*state), 1);
125 char *fontname = get_string_resource ("font", "Font");
129 state->window = window;
131 XGetWindowAttributes (dpy, window, &state->xgwa);
133 state->font = XLoadQueryFont (dpy, fontname);
137 fprintf(stderr, "couldn't load font \"%s\"\n", fontname);
138 state->font = XLoadQueryFont (dpy, "fixed");
142 fprintf(stderr, "couldn't load font \"fixed\"");
147 state->scale = get_integer_resource ("scale", "Integer");
148 state->ticks = STATE_MAX + get_integer_resource ("ticks", "Integer");
151 for (i = 0; i < font->n_properties; i++)
152 if (font->properties[i].name == XA_FONT)
153 printf ("font: %s\n", XGetAtomName(dpy, font->properties[i].card32));
156 state->cursor_blink = get_integer_resource ("cursor", "Time");
157 state->subproc_relaunch_delay =
158 (1000 * get_integer_resource ("relaunch", "Time"));
160 state->char_width = font->max_bounds.width;
161 state->char_height = font->max_bounds.ascent + font->max_bounds.descent;
163 state->grid_width = state->xgwa.width / (state->char_width * state->scale);
164 state->grid_height = state->xgwa.height /(state->char_height * state->scale);
165 state->cells = (p_cell *) calloc (sizeof(p_cell),
166 state->grid_width * state->grid_height);
167 state->chars = (p_char **) calloc (sizeof(p_char *), 256);
169 state->gcs = (GC *) calloc (sizeof(GC), state->ticks + 1);
172 int ncolors = MAX (0, state->ticks - 3);
173 XColor *colors = (XColor *) calloc (ncolors, sizeof(XColor));
175 double s1, s2, v1, v2;
177 unsigned long fg = get_pixel_resource ("foreground", "Foreground",
178 state->dpy, state->xgwa.colormap);
179 unsigned long bg = get_pixel_resource ("background", "Background",
180 state->dpy, state->xgwa.colormap);
181 unsigned long flare = get_pixel_resource ("flareForeground", "Foreground",
182 state->dpy,state->xgwa.colormap);
183 unsigned long fade = get_pixel_resource ("fadeForeground", "Foreground",
184 state->dpy,state->xgwa.colormap);
189 XQueryColor (state->dpy, state->xgwa.colormap, &start);
192 XQueryColor (state->dpy, state->xgwa.colormap, &end);
194 /* Now allocate a ramp of colors from the main color to the background. */
195 rgb_to_hsv (start.red, start.green, start.blue, &h1, &s1, &v1);
196 rgb_to_hsv (end.red, end.green, end.blue, &h2, &s2, &v2);
197 make_color_ramp (state->dpy, state->xgwa.colormap,
203 /* Adjust to the number of colors we actually got. */
204 state->ticks = ncolors + STATE_MAX;
206 /* Now, GCs all around.
208 state->gcv.font = font->fid;
209 state->gcv.cap_style = CapRound;
211 state->gcv.line_width = (int) (((long) state->scale) * 1.3);
212 if (state->gcv.line_width == state->scale)
213 state->gcv.line_width++;
214 #else /* !FUZZY_BORDER */
215 state->gcv.line_width = (int) (((long) state->scale) * 0.9);
216 if (state->gcv.line_width >= state->scale)
217 state->gcv.line_width = state->scale - 1;
218 if (state->gcv.line_width < 1)
219 state->gcv.line_width = 1;
220 #endif /* !FUZZY_BORDER */
222 flags = (GCForeground | GCBackground | GCCapStyle | GCLineWidth);
224 state->gcv.background = bg;
225 state->gcv.foreground = bg;
226 state->gcs[BLANK] = XCreateGC (state->dpy, state->window, flags,
229 state->gcv.foreground = flare;
230 state->gcs[FLARE] = XCreateGC (state->dpy, state->window, flags,
233 state->gcv.foreground = fg;
234 state->gcs[NORMAL] = XCreateGC (state->dpy, state->window, flags,
237 for (i = 0; i < ncolors; i++)
239 state->gcv.foreground = colors[i].pixel;
240 state->gcs[STATE_MAX + i] = XCreateGC (state->dpy, state->window,
245 capture_font_bits (state);
247 launch_text_generator (state);
254 capture_font_bits (p_state *state)
256 XFontStruct *font = state->font;
257 int safe_width = font->max_bounds.rbearing - font->min_bounds.lbearing;
258 int height = state->char_height;
259 unsigned char string[257];
261 Pixmap p = XCreatePixmap (state->dpy, state->window,
262 (safe_width * 256), height, 1);
264 for (i = 0; i < 256; i++)
265 string[i] = (unsigned char) i;
268 state->gcv.foreground = 0;
269 state->gcv.background = 0;
270 state->gc0 = XCreateGC (state->dpy, p,
271 (GCForeground | GCBackground),
274 state->gcv.foreground = 1;
275 state->gc1 = XCreateGC (state->dpy, p,
276 (GCFont | GCForeground | GCBackground |
277 GCCapStyle | GCLineWidth),
282 state->gcv.line_width = (int) (((long) state->scale) * 0.8);
283 if (state->gcv.line_width >= state->scale)
284 state->gcv.line_width = state->scale - 1;
285 if (state->gcv.line_width < 1)
286 state->gcv.line_width = 1;
287 state->gc2 = XCreateGC (state->dpy, p,
288 (GCFont | GCForeground | GCBackground |
289 GCCapStyle | GCLineWidth),
292 #endif /* FUZZY_BORDER */
294 XFillRectangle (state->dpy, p, state->gc0, 0, 0, (safe_width * 256), height);
296 for (i = 0; i < 256; i++)
298 if (string[i] < font->min_char_or_byte2 ||
299 string[i] > font->max_char_or_byte2)
301 XDrawString (state->dpy, p, state->gc1,
302 i * safe_width, font->ascent,
303 (char *) (string + i), 1);
306 /* Draw the cursor. */
307 XFillRectangle (state->dpy, p, state->gc1,
308 (CURSOR_INDEX * safe_width), 1,
310 ? font->per_char['n'-font->min_char_or_byte2].width
311 : font->max_bounds.width),
315 XCopyPlane (state->dpy, p, state->window, state->gcs[FLARE],
316 0, 0, (safe_width * 256), height, 0, 0, 1L);
317 XSync(state->dpy, False);
320 XSync (state->dpy, False);
321 state->font_bits = XGetImage (state->dpy, p, 0, 0,
322 (safe_width * 256), height, ~0L, XYPixmap);
323 XFreePixmap (state->dpy, p);
325 for (i = 0; i < 256; i++)
326 state->chars[i] = make_character (state, i);
327 state->chars[CURSOR_INDEX] = make_character (state, CURSOR_INDEX);
332 make_character (p_state *state, int c)
334 p_char *pc = (p_char *) malloc (sizeof (*pc));
335 pc->name = (unsigned char) c;
336 pc->width = state->scale * state->char_width;
337 pc->height = state->scale * state->char_height;
338 char_to_pixmap (state, pc, c);
344 char_to_pixmap (p_state *state, p_char *pc, int c)
351 #endif /* FUZZY_BORDER */
355 XFontStruct *font = state->font;
356 int safe_width = font->max_bounds.rbearing - font->min_bounds.lbearing;
358 int width = state->scale * state->char_width;
359 int height = state->scale * state->char_height;
361 if (c < font->min_char_or_byte2 ||
362 c > font->max_char_or_byte2)
366 p = XCreatePixmap (state->dpy, state->window, width, height, 1);
367 XFillRectangle (state->dpy, p, state->gc0, 0, 0, width, height);
370 p2 = XCreatePixmap (state->dpy, state->window, width, height, 1);
371 XFillRectangle (state->dpy, p2, state->gc0, 0, 0, width, height);
372 #endif /* FUZZY_BORDER */
374 from = safe_width * c;
375 to = safe_width * (c + 1);
378 if (c > 75 && c < 150)
380 printf ("\n=========== %d (%c)\n", c, c);
381 for (y = 0; y < state->char_height; y++)
383 for (x1 = from; x1 < to; x1++)
384 printf (XGetPixel (state->font_bits, x1, y) ? "* " : ". ");
391 for (y = 0; y < state->char_height; y++)
392 for (x1 = from; x1 < to; x1++)
393 if (XGetPixel (state->font_bits, x1, y))
395 int xoff = state->scale / 2;
397 for (x2 = x1; x2 < to; x2++)
398 if (!XGetPixel (state->font_bits, x2, y))
401 XDrawLine (state->dpy, p, gc,
402 (x1 - from) * state->scale + xoff, y * state->scale,
403 (x2 - from) * state->scale + xoff, y * state->scale);
405 XDrawLine (state->dpy, p2, gc2,
406 (x1 - from) * state->scale + xoff, y * state->scale,
407 (x2 - from) * state->scale + xoff, y * state->scale);
408 #endif /* FUZZY_BORDER */
413 /* if (pc->blank_p && c == CURSOR_INDEX)
420 #endif /* FUZZY_BORDER */
424 /* Managing the display.
427 static void cursor_on_timer (XtPointer closure, XtIntervalId *id);
428 static void cursor_off_timer (XtPointer closure, XtIntervalId *id);
431 set_cursor_1 (p_state *state, Bool on)
433 p_cell *cell = &state->cells[state->grid_width * state->cursor_y
435 p_char *cursor = state->chars[CURSOR_INDEX];
436 int new_state = (on ? NORMAL : FADE);
438 if (cell->p_char != cursor)
439 cell->changed = True;
441 if (cell->state != new_state)
442 cell->changed = True;
444 cell->p_char = cursor;
445 cell->state = new_state;
446 return cell->changed;
450 set_cursor (p_state *state, Bool on)
452 if (set_cursor_1 (state, on))
455 if (state->cursor_timer)
456 XtRemoveTimeOut (state->cursor_timer);
457 state->cursor_timer = 0;
458 cursor_on_timer (state, 0);
466 cursor_off_timer (XtPointer closure, XtIntervalId *id)
468 p_state *state = (p_state *) closure;
469 set_cursor_1 (state, False);
470 state->cursor_timer = XtAppAddTimeOut (app, state->cursor_blink,
471 cursor_on_timer, closure);
475 cursor_on_timer (XtPointer closure, XtIntervalId *id)
477 p_state *state = (p_state *) closure;
478 set_cursor_1 (state, True);
479 state->cursor_timer = XtAppAddTimeOut (app, 2 * state->cursor_blink,
480 cursor_off_timer, closure);
485 clear (p_state *state)
490 for (y = 0; y < state->grid_height; y++)
491 for (x = 0; x < state->grid_width; x++)
493 p_cell *cell = &state->cells[state->grid_width * y + x];
494 if (cell->state == FLARE || cell->state == NORMAL)
497 cell->changed = True;
500 set_cursor (state, True);
505 decay (p_state *state)
508 for (y = 0; y < state->grid_height; y++)
509 for (x = 0; x < state->grid_width; x++)
511 p_cell *cell = &state->cells[state->grid_width * y + x];
512 if (cell->state == FLARE)
514 cell->state = NORMAL;
515 cell->changed = True;
517 else if (cell->state >= FADE)
520 if (cell->state >= state->ticks)
522 cell->changed = True;
529 scroll (p_state *state)
532 for (x = 0; x < state->grid_width; x++)
534 p_cell *from = 0, *to = 0;
535 for (y = 1; y < state->grid_height; y++)
537 from = &state->cells[state->grid_width * y + x];
538 to = &state->cells[state->grid_width * (y-1) + x];
540 if ((from->state == FLARE || from->state == NORMAL) &&
541 !from->p_char->blank_p)
544 to->state = NORMAL; /* should be FLARE? Looks bad... */
548 if (to->state == FLARE || to->state == NORMAL)
556 if (to->state == FLARE || to->state == NORMAL)
562 set_cursor (state, True);
567 print_char (p_state *state, int c)
569 p_cell *cell = &state->cells[state->grid_width * state->cursor_y
572 /* Start the cursor fading (in case we don't end up overwriting it.) */
573 if (cell->state == FLARE || cell->state == NORMAL)
576 cell->changed = True;
579 if (c == '\t') c = ' '; /* blah. */
581 if (c == '\r' || c == '\n')
584 if (state->cursor_y == state->grid_height - 1)
589 else if (c == '\014')
596 cell->p_char = state->chars[c];
597 cell->changed = True;
600 if (c != ' ' && cell->p_char->blank_p)
601 cell->p_char = state->chars[CURSOR_INDEX];
603 if (state->cursor_x >= state->grid_width - 1)
606 if (state->cursor_y >= state->grid_height - 1)
612 set_cursor (state, True);
617 update_display (p_state *state, Bool changed_only)
621 for (y = 0; y < state->grid_height; y++)
622 for (x = 0; x < state->grid_width; x++)
624 p_cell *cell = &state->cells[state->grid_width * y + x];
625 int width, height, tx, ty;
627 if (changed_only && !cell->changed)
630 width = state->char_width * state->scale;
631 height = state->char_height * state->scale;
635 if (cell->state == BLANK || cell->p_char->blank_p)
637 XFillRectangle (state->dpy, state->window, state->gcs[BLANK],
638 tx, ty, width, height);
643 GC gc1 = state->gcs[cell->state];
644 GC gc2 = ((cell->state + 2) < state->ticks
645 ? state->gcs[cell->state + 2]
647 GC gc3 = (gc2 ? gc2 : gc1);
649 XCopyPlane (state->dpy, cell->p_char->pixmap, state->window, gc3,
650 0, 0, width, height, tx, ty, 1L);
653 XSetClipMask (state->dpy, gc1, cell->p_char->pixmap2);
654 XSetClipOrigin (state->dpy, gc1, tx, ty);
655 XFillRectangle (state->dpy, state->window, gc1,
656 tx, ty, width, height);
657 XSetClipMask (state->dpy, gc1, None);
659 #else /* !FUZZY_BORDER */
661 XCopyPlane (state->dpy,
662 cell->p_char->pixmap, state->window,
663 state->gcs[cell->state],
664 0, 0, width, height, tx, ty, 1L);
666 #endif /* !FUZZY_BORDER */
669 cell->changed = False;
675 run_phosphor (p_state *state)
677 update_display (state, True);
687 subproc_cb (XtPointer closure, int *source, XtInputId *id)
689 p_state *state = (p_state *) closure;
690 state->input_available_p = True;
695 launch_text_generator (p_state *state)
697 char *oprogram = get_string_resource ("program", "Program");
698 char *program = (char *) malloc (strlen (oprogram) + 10);
700 strcpy (program, "( ");
701 strcat (program, oprogram);
702 strcat (program, " ) 2>&1");
704 if ((state->pipe = popen (program, "r")))
707 XtAppAddInput (app, fileno (state->pipe),
708 (XtPointer) (XtInputReadMask | XtInputExceptMask),
709 subproc_cb, (XtPointer) state);
719 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
721 p_state *state = (p_state *) closure;
722 launch_text_generator (state);
727 drain_input (p_state *state)
729 if (state->input_available_p)
732 int n = read (fileno (state->pipe), (void *) s, 1);
735 print_char (state, s[0]);
739 XtRemoveInput (state->pipe_id);
741 pclose (state->pipe);
744 if (state->cursor_x != 0) /* break line if unbroken */
745 print_char (state, '\n'); /* blank line */
746 print_char (state, '\n');
748 /* Set up a timer to re-launch the subproc in a bit. */
749 XtAppAddTimeOut (app, state->subproc_relaunch_delay,
750 relaunch_generator_timer,
754 state->input_available_p = False;
760 char *progclass = "Phosphor";
762 char *defaults [] = {
763 ".background: Black",
764 ".foreground: Green",
765 "*fadeForeground: DarkGreen",
766 "*flareForeground: White",
772 "*program: " ZIPPY_PROGRAM,
777 XrmOptionDescRec options [] = {
778 { "-font", ".font", XrmoptionSepArg, 0 },
779 { "-scale", ".scale", XrmoptionSepArg, 0 },
780 { "-ticks", ".ticks", XrmoptionSepArg, 0 },
781 { "-delay", ".delay", XrmoptionSepArg, 0 },
782 { "-program", ".program", XrmoptionSepArg, 0 },
788 screenhack (Display *dpy, Window window)
790 int delay = get_integer_resource ("delay", "Integer");
791 p_state *state = init_phosphor (dpy, window);
797 run_phosphor (state);
799 screenhack_handle_events (dpy);
801 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
802 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
804 if (delay) usleep (delay);