1 /* xscreensaver, Copyright (c) 1999, 2000 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))
454 if (state->cursor_timer)
455 XtRemoveTimeOut (state->cursor_timer);
456 state->cursor_timer = 0;
457 cursor_on_timer (state, 0);
465 cursor_off_timer (XtPointer closure, XtIntervalId *id)
467 p_state *state = (p_state *) closure;
468 set_cursor_1 (state, False);
469 state->cursor_timer = XtAppAddTimeOut (app, state->cursor_blink,
470 cursor_on_timer, closure);
474 cursor_on_timer (XtPointer closure, XtIntervalId *id)
476 p_state *state = (p_state *) closure;
477 set_cursor_1 (state, True);
478 state->cursor_timer = XtAppAddTimeOut (app, 2 * state->cursor_blink,
479 cursor_off_timer, closure);
484 clear (p_state *state)
489 for (y = 0; y < state->grid_height; y++)
490 for (x = 0; x < state->grid_width; x++)
492 p_cell *cell = &state->cells[state->grid_width * y + x];
493 if (cell->state == FLARE || cell->state == NORMAL)
496 cell->changed = True;
499 set_cursor (state, True);
504 decay (p_state *state)
507 for (y = 0; y < state->grid_height; y++)
508 for (x = 0; x < state->grid_width; x++)
510 p_cell *cell = &state->cells[state->grid_width * y + x];
511 if (cell->state == FLARE)
513 cell->state = NORMAL;
514 cell->changed = True;
516 else if (cell->state >= FADE)
519 if (cell->state >= state->ticks)
521 cell->changed = True;
528 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 && (to->state == FLARE || to->state == NORMAL))
562 set_cursor (state, True);
567 print_char (p_state *state, int c)
569 static char last_c = 0;
571 p_cell *cell = &state->cells[state->grid_width * state->cursor_y
574 /* Start the cursor fading (in case we don't end up overwriting it.) */
575 if (cell->state == FLARE || cell->state == NORMAL)
578 cell->changed = True;
581 if (c == '\t') c = ' '; /* blah. */
583 if (c == '\r' || c == '\n')
585 if (c == '\n' && last_c == '\r')
586 ; /* CRLF -- do nothing */
590 if (state->cursor_y == state->grid_height - 1)
596 else if (c == '\014')
603 cell->p_char = state->chars[c];
604 cell->changed = True;
607 if (c != ' ' && cell->p_char->blank_p)
608 cell->p_char = state->chars[CURSOR_INDEX];
610 if (state->cursor_x >= state->grid_width - 1)
613 if (state->cursor_y >= state->grid_height - 1)
619 set_cursor (state, True);
626 update_display (p_state *state, Bool changed_only)
630 for (y = 0; y < state->grid_height; y++)
631 for (x = 0; x < state->grid_width; x++)
633 p_cell *cell = &state->cells[state->grid_width * y + x];
634 int width, height, tx, ty;
636 if (changed_only && !cell->changed)
639 width = state->char_width * state->scale;
640 height = state->char_height * state->scale;
644 if (cell->state == BLANK || cell->p_char->blank_p)
646 XFillRectangle (state->dpy, state->window, state->gcs[BLANK],
647 tx, ty, width, height);
652 GC gc1 = state->gcs[cell->state];
653 GC gc2 = ((cell->state + 2) < state->ticks
654 ? state->gcs[cell->state + 2]
656 GC gc3 = (gc2 ? gc2 : gc1);
658 XCopyPlane (state->dpy, cell->p_char->pixmap, state->window, gc3,
659 0, 0, width, height, tx, ty, 1L);
662 XSetClipMask (state->dpy, gc1, cell->p_char->pixmap2);
663 XSetClipOrigin (state->dpy, gc1, tx, ty);
664 XFillRectangle (state->dpy, state->window, gc1,
665 tx, ty, width, height);
666 XSetClipMask (state->dpy, gc1, None);
668 #else /* !FUZZY_BORDER */
670 XCopyPlane (state->dpy,
671 cell->p_char->pixmap, state->window,
672 state->gcs[cell->state],
673 0, 0, width, height, tx, ty, 1L);
675 #endif /* !FUZZY_BORDER */
678 cell->changed = False;
684 run_phosphor (p_state *state)
686 update_display (state, True);
696 subproc_cb (XtPointer closure, int *source, XtInputId *id)
698 p_state *state = (p_state *) closure;
699 state->input_available_p = True;
704 launch_text_generator (p_state *state)
706 char *oprogram = get_string_resource ("program", "Program");
707 char *program = (char *) malloc (strlen (oprogram) + 10);
709 strcpy (program, "( ");
710 strcat (program, oprogram);
711 strcat (program, " ) 2>&1");
713 if ((state->pipe = popen (program, "r")))
716 XtAppAddInput (app, fileno (state->pipe),
717 (XtPointer) (XtInputReadMask | XtInputExceptMask),
718 subproc_cb, (XtPointer) state);
728 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
730 p_state *state = (p_state *) closure;
731 launch_text_generator (state);
736 drain_input (p_state *state)
738 if (state->input_available_p)
741 int n = read (fileno (state->pipe), (void *) s, 1);
744 print_char (state, s[0]);
748 XtRemoveInput (state->pipe_id);
750 pclose (state->pipe);
753 if (state->cursor_x != 0) /* break line if unbroken */
754 print_char (state, '\n'); /* blank line */
755 print_char (state, '\n');
757 /* Set up a timer to re-launch the subproc in a bit. */
758 XtAppAddTimeOut (app, state->subproc_relaunch_delay,
759 relaunch_generator_timer,
763 state->input_available_p = False;
769 char *progclass = "Phosphor";
771 char *defaults [] = {
772 ".background: Black",
773 ".foreground: Green",
774 "*fadeForeground: DarkGreen",
775 "*flareForeground: White",
781 "*program: " FORTUNE_PROGRAM,
786 XrmOptionDescRec options [] = {
787 { "-font", ".font", XrmoptionSepArg, 0 },
788 { "-scale", ".scale", XrmoptionSepArg, 0 },
789 { "-ticks", ".ticks", XrmoptionSepArg, 0 },
790 { "-delay", ".delay", XrmoptionSepArg, 0 },
791 { "-program", ".program", XrmoptionSepArg, 0 },
797 screenhack (Display *dpy, Window window)
799 int delay = get_integer_resource ("delay", "Integer");
800 p_state *state = init_phosphor (dpy, window);
806 run_phosphor (state);
808 screenhack_handle_events (dpy);
810 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
811 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
813 if (delay) usleep (delay);