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))
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)
533 for (x = 0; x < state->grid_width; x++)
535 p_cell *from = 0, *to = 0;
536 for (y = 1; y < state->grid_height; y++)
538 from = &state->cells[state->grid_width * y + x];
539 to = &state->cells[state->grid_width * (y-1) + x];
541 if ((from->state == FLARE || from->state == NORMAL) &&
542 !from->p_char->blank_p)
545 to->state = NORMAL; /* should be FLARE? Looks bad... */
549 if (to->state == FLARE || to->state == NORMAL)
557 if (to && (to->state == FLARE || to->state == NORMAL))
563 set_cursor (state, True);
568 print_char (p_state *state, int c)
570 static char last_c = 0;
572 p_cell *cell = &state->cells[state->grid_width * state->cursor_y
575 /* Start the cursor fading (in case we don't end up overwriting it.) */
576 if (cell->state == FLARE || cell->state == NORMAL)
579 cell->changed = True;
582 if (c == '\t') c = ' '; /* blah. */
584 if (c == '\r' || c == '\n')
586 if (c == '\n' && last_c == '\r')
587 ; /* CRLF -- do nothing */
591 if (state->cursor_y == state->grid_height - 1)
597 else if (c == '\014')
604 cell->p_char = state->chars[c];
605 cell->changed = True;
608 if (c != ' ' && cell->p_char->blank_p)
609 cell->p_char = state->chars[CURSOR_INDEX];
611 if (state->cursor_x >= state->grid_width - 1)
614 if (state->cursor_y >= state->grid_height - 1)
620 set_cursor (state, True);
627 update_display (p_state *state, Bool changed_only)
631 for (y = 0; y < state->grid_height; y++)
632 for (x = 0; x < state->grid_width; x++)
634 p_cell *cell = &state->cells[state->grid_width * y + x];
635 int width, height, tx, ty;
637 if (changed_only && !cell->changed)
640 width = state->char_width * state->scale;
641 height = state->char_height * state->scale;
645 if (cell->state == BLANK || cell->p_char->blank_p)
647 XFillRectangle (state->dpy, state->window, state->gcs[BLANK],
648 tx, ty, width, height);
653 GC gc1 = state->gcs[cell->state];
654 GC gc2 = ((cell->state + 2) < state->ticks
655 ? state->gcs[cell->state + 2]
657 GC gc3 = (gc2 ? gc2 : gc1);
659 XCopyPlane (state->dpy, cell->p_char->pixmap, state->window, gc3,
660 0, 0, width, height, tx, ty, 1L);
663 XSetClipMask (state->dpy, gc1, cell->p_char->pixmap2);
664 XSetClipOrigin (state->dpy, gc1, tx, ty);
665 XFillRectangle (state->dpy, state->window, gc1,
666 tx, ty, width, height);
667 XSetClipMask (state->dpy, gc1, None);
669 #else /* !FUZZY_BORDER */
671 XCopyPlane (state->dpy,
672 cell->p_char->pixmap, state->window,
673 state->gcs[cell->state],
674 0, 0, width, height, tx, ty, 1L);
676 #endif /* !FUZZY_BORDER */
679 cell->changed = False;
685 run_phosphor (p_state *state)
687 update_display (state, True);
697 subproc_cb (XtPointer closure, int *source, XtInputId *id)
699 p_state *state = (p_state *) closure;
700 state->input_available_p = True;
705 launch_text_generator (p_state *state)
707 char *oprogram = get_string_resource ("program", "Program");
708 char *program = (char *) malloc (strlen (oprogram) + 10);
710 strcpy (program, "( ");
711 strcat (program, oprogram);
712 strcat (program, " ) 2>&1");
714 if ((state->pipe = popen (program, "r")))
717 XtAppAddInput (app, fileno (state->pipe),
718 (XtPointer) (XtInputReadMask | XtInputExceptMask),
719 subproc_cb, (XtPointer) state);
729 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
731 p_state *state = (p_state *) closure;
732 launch_text_generator (state);
737 drain_input (p_state *state)
739 if (state->input_available_p)
742 int n = read (fileno (state->pipe), (void *) s, 1);
745 print_char (state, s[0]);
749 XtRemoveInput (state->pipe_id);
751 pclose (state->pipe);
754 if (state->cursor_x != 0) /* break line if unbroken */
755 print_char (state, '\n'); /* blank line */
756 print_char (state, '\n');
758 /* Set up a timer to re-launch the subproc in a bit. */
759 XtAppAddTimeOut (app, state->subproc_relaunch_delay,
760 relaunch_generator_timer,
764 state->input_available_p = False;
770 char *progclass = "Phosphor";
772 char *defaults [] = {
773 ".background: Black",
774 ".foreground: Green",
775 "*fadeForeground: DarkGreen",
776 "*flareForeground: White",
782 "*program: " FORTUNE_PROGRAM,
787 XrmOptionDescRec options [] = {
788 { "-font", ".font", XrmoptionSepArg, 0 },
789 { "-scale", ".scale", XrmoptionSepArg, 0 },
790 { "-ticks", ".ticks", XrmoptionSepArg, 0 },
791 { "-delay", ".delay", XrmoptionSepArg, 0 },
792 { "-program", ".program", XrmoptionSepArg, 0 },
798 screenhack (Display *dpy, Window window)
800 int delay = get_integer_resource ("delay", "Integer");
801 p_state *state = init_phosphor (dpy, window);
807 run_phosphor (state);
809 screenhack_handle_events (dpy);
811 if (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
812 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
814 if (delay) usleep (delay);