summaryrefslogtreecommitdiffstats
path: root/hacks/phosphor.c
diff options
context:
space:
mode:
Diffstat (limited to 'hacks/phosphor.c')
-rw-r--r--hacks/phosphor.c1451
1 files changed, 1451 insertions, 0 deletions
diff --git a/hacks/phosphor.c b/hacks/phosphor.c
new file mode 100644
index 0000000..2e57b86
--- /dev/null
+++ b/hacks/phosphor.c
@@ -0,0 +1,1451 @@
+/* xscreensaver, Copyright (c) 1999-2018 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ *
+ * Phosphor -- simulate a glass tty with long-sustain phosphor.
+ * Written by Jamie Zawinski <jwz@jwz.org>
+ * Pty and vt100 emulation by Fredrik Tolf <fredrik@dolda2000.com>
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#ifndef HAVE_JWXYZ
+# include <X11/Intrinsic.h>
+#endif
+
+#include "screenhack.h"
+#include "textclient.h"
+#include "ximage-loader.h"
+#include "utf8wc.h"
+
+#define FUZZY_BORDER
+
+#define MAX(a,b) ((a)>(b)?(a):(b))
+#define MIN(a,b) ((a)<(b)?(a):(b))
+
+#define BLANK 0
+#define FLARE 1
+#define NORMAL 2
+#define FADE 3
+#define STATE_MAX FADE
+
+#define CURSOR_INDEX 128
+
+#define NPAR 16
+
+#define BUILTIN_FONT
+
+#ifdef BUILTIN_FONT
+# include "images/gen/6x10font_png.h"
+#endif /* BUILTIN_FONT */
+
+typedef struct {
+ unsigned char name;
+ int width, height;
+ Pixmap pixmap;
+#ifdef FUZZY_BORDER
+ Pixmap pixmap2;
+#endif /* FUZZY_BORDER */
+ Bool blank_p;
+} p_char;
+
+typedef struct {
+ p_char *p_char;
+ int state;
+ Bool changed;
+} p_cell;
+
+typedef struct {
+ Display *dpy;
+ Window window;
+ XWindowAttributes xgwa;
+ XFontStruct *font;
+ const char *program;
+ int grid_width, grid_height;
+ int char_width, char_height;
+ int xmargin, ymargin;
+ int saved_x, saved_y;
+ int scale;
+ int ticks;
+ int mode;
+
+ int escstate;
+ int csiparam[NPAR];
+ int curparam;
+ int unicruds; unsigned char unicrud[7];
+
+ p_char **chars;
+ p_cell *cells;
+ XGCValues gcv;
+ GC gc0;
+ GC gc1;
+#ifdef FUZZY_BORDER
+ GC gc2;
+#endif /* FUZZY_BORDER */
+ GC *gcs;
+ XImage *font_bits;
+
+ int cursor_x, cursor_y;
+ XtIntervalId cursor_timer;
+ Time cursor_blink;
+ int delay;
+ Bool pty_p;
+
+ text_data *tc;
+
+ char last_c;
+ int bk;
+
+} p_state;
+
+
+static void capture_font_bits (p_state *state);
+static p_char *make_character (p_state *state, int c);
+static void char_to_pixmap (p_state *state, p_char *pc, int c);
+
+
+/* About font metrics:
+
+ "lbearing" is the distance from the leftmost pixel of a character to
+ the logical origin of that character. That is, it is the number of
+ pixels of the character which are to the left of its logical origin.
+
+ "rbearing" is the distance from the logical origin of a character to
+ the rightmost pixel of a character. That is, it is the number of
+ pixels of the character to the right of its logical origin.
+
+ "descent" is the distance from the bottommost pixel of a character to
+ the logical baseline. That is, it is the number of pixels of the
+ character which are below the baseline.
+
+ "ascent" is the distance from the logical baseline to the topmost pixel.
+ That is, it is the number of pixels of the character above the baseline.
+
+ Therefore, the bounding box of the "ink" of a character is
+ lbearing + rbearing by ascent + descent;
+
+ "width" is the distance from the logical origin of this character to
+ the position where the logical orgin of the next character should be
+ placed.
+
+ For our purposes, we're only interested in the part of the character
+ lying inside the "width" box. If the characters have ink outside of
+ that box (the "charcell" box) then we're going to lose it. Alas.
+ */
+
+
+static void clear (p_state *);
+static void set_cursor (p_state *, Bool on);
+
+static unsigned short scale_color_channel (unsigned short ch1, unsigned short ch2)
+{
+ return (ch1 * 100 + ch2 * 156) >> 8;
+}
+
+#define FONT6x10_WIDTH (256*7)
+#define FONT6x10_HEIGHT 10
+
+static void *
+phosphor_init (Display *dpy, Window window)
+{
+ int i;
+ unsigned long flags;
+ p_state *state = (p_state *) calloc (sizeof(*state), 1);
+ char *fontname = get_string_resource (dpy, "font", "Font");
+ XFontStruct *font;
+
+ state->dpy = dpy;
+ state->window = window;
+
+ XGetWindowAttributes (dpy, window, &state->xgwa);
+/* XSelectInput (dpy, window, state->xgwa.your_event_mask | ExposureMask);*/
+
+ state->delay = get_integer_resource (dpy, "delay", "Integer");
+ state->pty_p = get_boolean_resource (dpy, "usePty", "UsePty");
+
+ if (!strcasecmp (fontname, "builtin") ||
+ !strcasecmp (fontname, "(builtin)"))
+ {
+#ifndef BUILTIN_FONT
+ fprintf (stderr, "%s: no builtin font\n", progname);
+ state->font = load_font_retry (dpy, "fixed");
+#endif /* !BUILTIN_FONT */
+ }
+ else
+ {
+ state->font = load_font_retry (dpy, fontname);
+ if (!state->font) abort();
+ }
+
+ font = state->font;
+ state->scale = get_integer_resource (dpy, "scale", "Integer");
+ state->ticks = STATE_MAX + get_integer_resource (dpy, "ticks", "Integer");
+ state->escstate = 0;
+
+ if (state->xgwa.width > 2560) state->scale *= 2; /* Retina displays */
+
+ state->cursor_blink = get_integer_resource (dpy, "cursor", "Time");
+
+# ifdef BUILTIN_FONT
+ if (! font)
+ {
+ state->char_width = (FONT6x10_WIDTH / 256) - 1;
+ state->char_height = FONT6x10_HEIGHT;
+ }
+ else
+# endif /* BUILTIN_FONT */
+ {
+ state->char_width = font->max_bounds.width;
+ state->char_height = font->max_bounds.ascent + font->max_bounds.descent;
+ }
+
+# ifdef USE_IPHONE
+ /* Stupid iPhone X bezel.
+ #### This is the worst of all possible ways to do this! But how else?
+ */
+ if (state->xgwa.width == 2436 || state->xgwa.height == 2436) {
+ state->xmargin = 96;
+ state->ymargin = state->xmargin;
+ }
+# endif
+
+ state->grid_width = ((state->xgwa.width - state->xmargin * 2) /
+ (state->char_width * state->scale));
+ state->grid_height = ((state->xgwa.height - state->ymargin * 2) /
+ (state->char_height * state->scale));
+ state->cells = (p_cell *) calloc (sizeof(p_cell),
+ state->grid_width * state->grid_height);
+ state->chars = (p_char **) calloc (sizeof(p_char *), 256);
+
+ state->gcs = (GC *) calloc (sizeof(GC), state->ticks + 1);
+
+ {
+ int ncolors = MAX (1, state->ticks - 3);
+ XColor *colors = (XColor *) calloc (ncolors, sizeof(XColor));
+ int h1, h2;
+ double s1, s2, v1, v2;
+
+ unsigned long fg = get_pixel_resource (state->dpy, state->xgwa.colormap,
+ "foreground", "Foreground");
+ unsigned long bg = get_pixel_resource (state->dpy, state->xgwa.colormap,
+ "background", "Background");
+ unsigned long flare = fg;
+
+ XColor fg_color, bg_color;
+
+ fg_color.pixel = fg;
+ XQueryColor (state->dpy, state->xgwa.colormap, &fg_color);
+
+ bg_color.pixel = bg;
+ XQueryColor (state->dpy, state->xgwa.colormap, &bg_color);
+
+ /* Now allocate a ramp of colors from the main color to the background. */
+ rgb_to_hsv (scale_color_channel(fg_color.red, bg_color.red),
+ scale_color_channel(fg_color.green, bg_color.green),
+ scale_color_channel(fg_color.blue, bg_color.blue),
+ &h1, &s1, &v1);
+ rgb_to_hsv (bg_color.red, bg_color.green, bg_color.blue, &h2, &s2, &v2);
+
+ /* Avoid rainbow effects when fading to black/grey/white. */
+ if (s2 < 0.003)
+ h2 = h1;
+ if (s1 < 0.003)
+ h1 = h2;
+
+ make_color_ramp (state->xgwa.screen, state->xgwa.visual,
+ state->xgwa.colormap,
+ h1, s1, v1,
+ h2, s2, v2,
+ colors, &ncolors,
+ False, True, False);
+
+ /* Adjust to the number of colors we actually got. */
+ state->ticks = ncolors + STATE_MAX;
+
+ /* If the foreground is brighter than the background, the flare is white.
+ * Otherwise, the flare is left at the foreground color (i.e. no flare). */
+ rgb_to_hsv (fg_color.red, fg_color.green, fg_color.blue, &h1, &s1, &v1);
+ if (v2 <= v1)
+ {
+ XColor white;
+ /* WhitePixel is only for the default visual, which can be overridden
+ * on the command line. */
+ white.red = 0xffff;
+ white.green = 0xffff;
+ white.blue = 0xffff;
+ if (XAllocColor(state->dpy, state->xgwa.colormap, &white))
+ flare = white.pixel;
+ }
+
+ /* Now, GCs all around.
+ */
+ state->gcv.font = (font ? font->fid : 0);
+ state->gcv.cap_style = CapRound;
+#ifdef FUZZY_BORDER
+ state->gcv.line_width = (int) (((long) state->scale) * 1.3);
+ if (state->gcv.line_width == state->scale)
+ state->gcv.line_width++;
+#else /* !FUZZY_BORDER */
+ state->gcv.line_width = (int) (((long) state->scale) * 0.9);
+ if (state->gcv.line_width >= state->scale)
+ state->gcv.line_width = state->scale - 1;
+ if (state->gcv.line_width < 1)
+ state->gcv.line_width = 1;
+#endif /* !FUZZY_BORDER */
+
+ flags = (GCForeground | GCBackground | GCCapStyle | GCLineWidth);
+
+ state->gcv.background = bg;
+ state->gcv.foreground = bg;
+ state->gcs[BLANK] = XCreateGC (state->dpy, state->window, flags,
+ &state->gcv);
+
+ state->gcv.foreground = flare;
+ state->gcs[FLARE] = XCreateGC (state->dpy, state->window, flags,
+ &state->gcv);
+
+ state->gcv.foreground = fg;
+ state->gcs[NORMAL] = XCreateGC (state->dpy, state->window, flags,
+ &state->gcv);
+
+ for (i = 0; i < ncolors; i++)
+ {
+ state->gcv.foreground = colors[i].pixel;
+ state->gcs[STATE_MAX + i] = XCreateGC (state->dpy, state->window,
+ flags, &state->gcv);
+ }
+ }
+
+ capture_font_bits (state);
+
+ set_cursor (state, True);
+
+/* clear (state);*/
+
+ state->tc = textclient_open (dpy);
+ textclient_reshape (state->tc,
+ state->xgwa.width - state->xmargin * 2,
+ state->xgwa.height - state->ymargin * 2,
+ state->grid_width - 1,
+ state->grid_height - 1,
+ 0);
+
+ return state;
+}
+
+
+/* Re-query the window size and update the internal character grid if changed.
+ */
+static Bool
+resize_grid (p_state *state)
+{
+ int ow = state->grid_width;
+ int oh = state->grid_height;
+ p_cell *ocells = state->cells;
+ int x, y;
+
+ XGetWindowAttributes (state->dpy, state->window, &state->xgwa);
+
+ /* Would like to ensure here that
+ state->char_height * state->scale <= state->xgwa.height
+ but changing scale requires regenerating the bitmaps. */
+
+ state->grid_width = ((state->xgwa.width - state->xmargin * 2) /
+ (state->char_width * state->scale));
+ state->grid_height = ((state->xgwa.height - state->ymargin * 2) /
+ (state->char_height * state->scale));
+
+ if (state->grid_width < 2) state->grid_width = 2;
+ if (state->grid_height < 2) state->grid_height = 2;
+
+ if (ow == state->grid_width &&
+ oh == state->grid_height)
+ return False;
+
+ state->cells = (p_cell *) calloc (sizeof(p_cell),
+ state->grid_width * state->grid_height);
+
+ for (y = 0; y < state->grid_height; y++)
+ {
+ for (x = 0; x < state->grid_width; x++)
+ {
+ p_cell *ncell = &state->cells [state->grid_width * y + x];
+ if (x < ow && y < oh)
+ *ncell = ocells [ow * y + x];
+ ncell->changed = True;
+ }
+ }
+
+ if (state->cursor_x >= state->grid_width)
+ state->cursor_x = state->grid_width-1;
+ if (state->cursor_y >= state->grid_height)
+ state->cursor_y = state->grid_height-1;
+
+ free (ocells);
+ return True;
+}
+
+
+static void
+capture_font_bits (p_state *state)
+{
+ XFontStruct *font = state->font;
+ int safe_width, height;
+ unsigned char string[257];
+ int i;
+ Pixmap p;
+
+# ifdef BUILTIN_FONT
+ Pixmap p2 = 0;
+
+ if (!font)
+ {
+ safe_width = state->char_width + 1;
+ height = state->char_height;
+
+ int pix_w, pix_h;
+ XWindowAttributes xgwa;
+ Pixmap m = 0;
+ Pixmap p = image_data_to_pixmap (state->dpy, state->window,
+ _6x10font_png, sizeof(_6x10font_png),
+ &pix_w, &pix_h, &m);
+ XImage *im = XGetImage (state->dpy, p, 0, 0, pix_w, pix_h, ~0L, ZPixmap);
+ XImage *mm = XGetImage (state->dpy, m, 0, 0, pix_w, pix_h, 1, XYPixmap);
+ XImage *im2;
+ int x, y;
+ XGCValues gcv;
+ GC gc;
+ unsigned long black =
+ BlackPixelOfScreen (DefaultScreenOfDisplay (state->dpy));
+
+ XFreePixmap (state->dpy, p);
+ XFreePixmap (state->dpy, m);
+ if (pix_w != 256*7) abort();
+ if (pix_h != 10) abort();
+ if (pix_w != FONT6x10_WIDTH) abort();
+ if (pix_h != FONT6x10_HEIGHT) abort();
+
+ XGetWindowAttributes (state->dpy, state->window, &xgwa);
+ im2 = XCreateImage (state->dpy, xgwa.visual, 1, XYBitmap, 0, 0,
+ pix_w, pix_h, 8, 0);
+ im2->data = malloc (im2->bytes_per_line * im2->height);
+
+ /* Convert deep image to 1 bit */
+ for (y = 0; y < pix_h; y++)
+ for (x = 0; x < pix_w; x++)
+ XPutPixel (im2, x, y,
+ (XGetPixel (mm, x, y)
+ ? (XGetPixel (im, x, y) == black)
+ : 0));
+
+ XDestroyImage (im);
+ XDestroyImage (mm);
+ im = 0;
+
+ p2 = XCreatePixmap (state->dpy, state->window,
+ im2->width, im2->height, im2->depth);
+ gcv.foreground = 1;
+ gcv.background = 0;
+ gc = XCreateGC (state->dpy, p2, GCForeground|GCBackground, &gcv);
+ XPutImage (state->dpy, p2, gc, im2, 0, 0, 0, 0, im2->width, im2->height);
+ XFreeGC (state->dpy, gc);
+ XDestroyImage (im2);
+ }
+ else
+# endif /* BUILTIN_FONT */
+ {
+ safe_width = font->max_bounds.rbearing - font->min_bounds.lbearing;
+ height = state->char_height;
+ }
+
+ p = XCreatePixmap (state->dpy, state->window,
+ (safe_width * 256), height, 1);
+
+ for (i = 0; i < 256; i++)
+ string[i] = (unsigned char) i;
+ string[256] = 0;
+
+ state->gcv.foreground = 0;
+ state->gcv.background = 0;
+ state->gc0 = XCreateGC (state->dpy, p,
+ (GCForeground | GCBackground),
+ &state->gcv);
+
+ state->gcv.foreground = 1;
+ state->gc1 = XCreateGC (state->dpy, p,
+ ((font ? GCFont : 0) |
+ GCForeground | GCBackground |
+ GCCapStyle | GCLineWidth),
+ &state->gcv);
+
+#ifdef HAVE_JWXYZ
+ jwxyz_XSetAntiAliasing (state->dpy, state->gc0, False);
+ jwxyz_XSetAntiAliasing (state->dpy, state->gc1, False);
+#endif
+
+#ifdef FUZZY_BORDER
+ {
+ state->gcv.line_width = (int) (((long) state->scale) * 0.8);
+ if (state->gcv.line_width >= state->scale)
+ state->gcv.line_width = state->scale - 1;
+ if (state->gcv.line_width < 1)
+ state->gcv.line_width = 1;
+ state->gc2 = XCreateGC (state->dpy, p,
+ ((font ? GCFont : 0) |
+ GCForeground | GCBackground |
+ GCCapStyle | GCLineWidth),
+ &state->gcv);
+ }
+#endif /* FUZZY_BORDER */
+
+ XFillRectangle (state->dpy, p, state->gc0, 0, 0, (safe_width * 256), height);
+
+# ifdef BUILTIN_FONT
+ if (p2)
+ {
+ XCopyPlane (state->dpy, p2, p, state->gc1,
+ 0, 0, FONT6x10_WIDTH, FONT6x10_HEIGHT,
+ 0, 0, 1);
+ XFreePixmap (state->dpy, p2);
+ }
+ else
+# endif /* BUILTIN_FONT */
+ {
+ for (i = 0; i < 256; i++)
+ {
+ if (string[i] < font->min_char_or_byte2 ||
+ string[i] > font->max_char_or_byte2)
+ continue;
+ XDrawString (state->dpy, p, state->gc1,
+ i * safe_width, font->ascent,
+ (char *) (string + i), 1);
+ }
+ }
+
+ /* Draw the cursor. */
+ XFillRectangle (state->dpy, p, state->gc1,
+ (CURSOR_INDEX * safe_width), 1,
+ (font
+ ? (font->per_char
+ ? font->per_char['n'-font->min_char_or_byte2].width
+ : font->max_bounds.width)
+ : state->char_width),
+ (font
+ ? font->ascent - 1
+ : state->char_height));
+
+ state->font_bits = XGetImage (state->dpy, p, 0, 0,
+ (safe_width * 256), height, ~0L, XYPixmap);
+ XFreePixmap (state->dpy, p);
+
+ for (i = 0; i < 256; i++)
+ state->chars[i] = make_character (state, i);
+ state->chars[CURSOR_INDEX] = make_character (state, CURSOR_INDEX);
+}
+
+
+static p_char *
+make_character (p_state *state, int c)
+{
+ p_char *pc = (p_char *) malloc (sizeof (*pc));
+ pc->name = (unsigned char) c;
+ pc->width = state->scale * state->char_width;
+ pc->height = state->scale * state->char_height;
+ char_to_pixmap (state, pc, c);
+ return pc;
+}
+
+
+static void
+char_to_pixmap (p_state *state, p_char *pc, int c)
+{
+ Pixmap p = 0;
+ GC gc;
+#ifdef FUZZY_BORDER
+ Pixmap p2 = 0;
+ GC gc2;
+#endif /* FUZZY_BORDER */
+ int from, to;
+ int x1, y;
+
+ XFontStruct *font = state->font;
+ int safe_width = (font
+ ? font->max_bounds.rbearing - font->min_bounds.lbearing
+ : state->char_width + 1);
+
+ int width = state->scale * state->char_width;
+ int height = state->scale * state->char_height;
+
+ if (font && (c < font->min_char_or_byte2 ||
+ c > font->max_char_or_byte2))
+ goto DONE;
+
+ gc = state->gc1;
+ p = XCreatePixmap (state->dpy, state->window, width, height, 1);
+ XFillRectangle (state->dpy, p, state->gc0, 0, 0, width, height);
+#ifdef FUZZY_BORDER
+ gc2 = state->gc2;
+ p2 = XCreatePixmap (state->dpy, state->window, width, height, 1);
+ XFillRectangle (state->dpy, p2, state->gc0, 0, 0, width, height);
+#endif /* FUZZY_BORDER */
+
+ from = safe_width * c;
+ to = safe_width * (c + 1);
+
+#if 0
+ if (c > 75 && c < 150)
+ {
+ printf ("\n=========== %d (%c)\n", c, c);
+ for (y = 0; y < state->char_height; y++)
+ {
+ for (x1 = from; x1 < to; x1++)
+ printf (XGetPixel (state->font_bits, x1, y) ? "* " : ". ");
+ printf ("\n");
+ }
+ }
+#endif
+
+ pc->blank_p = True;
+ for (y = 0; y < state->char_height; y++)
+ for (x1 = from; x1 < to; x1++)
+ if (XGetPixel (state->font_bits, x1, y))
+ {
+ int xoff = state->scale / 2;
+ int x2;
+ for (x2 = x1; x2 < to; x2++)
+ if (!XGetPixel (state->font_bits, x2, y))
+ break;
+ x2--;
+ XDrawLine (state->dpy, p, gc,
+ (x1 - from) * state->scale + xoff, y * state->scale,
+ (x2 - from) * state->scale + xoff, y * state->scale);
+#ifdef FUZZY_BORDER
+ XDrawLine (state->dpy, p2, gc2,
+ (x1 - from) * state->scale + xoff, y * state->scale,
+ (x2 - from) * state->scale + xoff, y * state->scale);
+#endif /* FUZZY_BORDER */
+ x1 = x2;
+ pc->blank_p = False;
+ }
+
+ /* if (pc->blank_p && c == CURSOR_INDEX)
+ abort();*/
+
+ DONE:
+ pc->pixmap = p;
+#ifdef FUZZY_BORDER
+ pc->pixmap2 = p2;
+#endif /* FUZZY_BORDER */
+}
+
+
+/* Managing the display.
+ */
+
+static void cursor_on_timer (XtPointer closure, XtIntervalId *id);
+static void cursor_off_timer (XtPointer closure, XtIntervalId *id);
+
+static Bool
+set_cursor_1 (p_state *state, Bool on)
+{
+ p_cell *cell = &state->cells[state->grid_width * state->cursor_y
+ + state->cursor_x];
+ p_char *cursor = state->chars[CURSOR_INDEX];
+ int new_state = (on ? NORMAL : FADE);
+
+ if (cell->p_char != cursor)
+ cell->changed = True;
+
+ if (cell->state != new_state)
+ cell->changed = True;
+
+ cell->p_char = cursor;
+ cell->state = new_state;
+ return cell->changed;
+}
+
+static void
+set_cursor (p_state *state, Bool on)
+{
+ if (set_cursor_1 (state, on))
+ {
+ if (state->cursor_timer)
+ XtRemoveTimeOut (state->cursor_timer);
+ state->cursor_timer = 0;
+ cursor_on_timer (state, 0);
+ }
+}
+
+
+static void
+cursor_off_timer (XtPointer closure, XtIntervalId *id)
+{
+ p_state *state = (p_state *) closure;
+ XtAppContext app = XtDisplayToApplicationContext (state->dpy);
+ set_cursor_1 (state, False);
+ state->cursor_timer = XtAppAddTimeOut (app, state->cursor_blink,
+ cursor_on_timer, closure);
+}
+
+static void
+cursor_on_timer (XtPointer closure, XtIntervalId *id)
+{
+ p_state *state = (p_state *) closure;
+ XtAppContext app = XtDisplayToApplicationContext (state->dpy);
+ set_cursor_1 (state, True);
+ state->cursor_timer = XtAppAddTimeOut (app, 2 * state->cursor_blink,
+ cursor_off_timer, closure);
+}
+
+
+static void
+clear (p_state *state)
+{
+ int x, y;
+ state->cursor_x = 0;
+ state->cursor_y = 0;
+ for (y = 0; y < state->grid_height; y++)
+ for (x = 0; x < state->grid_width; x++)
+ {
+ p_cell *cell = &state->cells[state->grid_width * y + x];
+ if (cell->state == FLARE || cell->state == NORMAL)
+ {
+ cell->state = FADE;
+ cell->changed = True;
+ }
+ }
+ set_cursor (state, True);
+}
+
+
+static void
+decay (p_state *state)
+{
+ int x, y;
+ for (y = 0; y < state->grid_height; y++)
+ for (x = 0; x < state->grid_width; x++)
+ {
+ p_cell *cell = &state->cells[state->grid_width * y + x];
+ if (cell->state == FLARE)
+ {
+ cell->state = NORMAL;
+ cell->changed = True;
+ }
+ else if (cell->state >= FADE)
+ {
+ cell->state++;
+ if (cell->state >= state->ticks)
+ cell->state = BLANK;
+ cell->changed = True;
+ }
+ }
+}
+
+
+static void
+scroll (p_state *state)
+{
+ int x, y;
+
+ for (x = 0; x < state->grid_width; x++)
+ {
+ p_cell *from = 0, *to = 0;
+ for (y = 1; y < state->grid_height; y++)
+ {
+ from = &state->cells[state->grid_width * y + x];
+ to = &state->cells[state->grid_width * (y-1) + x];
+
+ if ((from->state == FLARE || from->state == NORMAL) &&
+ !from->p_char->blank_p)
+ {
+ *to = *from;
+ to->state = NORMAL; /* should be FLARE? Looks bad... */
+ }
+ else
+ {
+ if (to->state == FLARE || to->state == NORMAL)
+ to->state = FADE;
+ }
+
+ to->changed = True;
+ }
+
+ to = from;
+ if (to && (to->state == FLARE || to->state == NORMAL))
+ {
+ to->state = FADE;
+ to->changed = True;
+ }
+ }
+ set_cursor (state, True);
+}
+
+
+static int
+process_unicrud (p_state *state, int c)
+{
+ if ((c & 0xE0) == 0xC0) { /* 110xxxxx: 11 bits, 2 bytes */
+ state->unicruds = 1;
+ state->unicrud[0] = c;
+ state->escstate = 102;
+ } else if ((c & 0xF0) == 0xE0) { /* 1110xxxx: 16 bits, 3 bytes */
+ state->unicruds = 1;
+ state->unicrud[0] = c;
+ state->escstate = 103;
+ } else if ((c & 0xF8) == 0xF0) { /* 11110xxx: 21 bits, 4 bytes */
+ state->unicruds = 1;
+ state->unicrud[0] = c;
+ state->escstate = 104;
+ } else if ((c & 0xFC) == 0xF8) { /* 111110xx: 26 bits, 5 bytes */
+ state->unicruds = 1;
+ state->unicrud[0] = c;
+ state->escstate = 105;
+ } else if ((c & 0xFE) == 0xFC) { /* 1111110x: 31 bits, 6 bytes */
+ state->unicruds = 1;
+ state->unicrud[0] = c;
+ state->escstate = 106;
+ } else if (state->unicruds == 0) {
+ return c;
+ } else {
+ int total = state->escstate - 100; /* see what I did there */
+ if (state->unicruds < total) {
+ /* Buffer more bytes of the UTF-8 sequence */
+ state->unicrud[state->unicruds++] = c;
+ }
+
+ if (state->unicruds >= total) {
+ /* Done! Convert it to Latin1 and print that. */
+ char *s;
+ state->unicrud[state->unicruds] = 0;
+ s = utf8_to_latin1 ((const char *) state->unicrud, False);
+ state->unicruds = 0;
+ state->escstate = 0;
+ if (s) {
+ c = (unsigned char) s[0];
+ free (s);
+ return c;
+ }
+ }
+ }
+ return 0;
+}
+
+
+static void
+print_char (p_state *state, int c)
+{
+ int cols = state->grid_width;
+ int rows = state->grid_height;
+ p_cell *cell = &state->cells[state->grid_width * state->cursor_y
+ + state->cursor_x];
+
+ /* Start the cursor fading (in case we don't end up overwriting it.) */
+ if (cell->state == FLARE || cell->state == NORMAL)
+ {
+ cell->state = FADE;
+ cell->changed = True;
+ }
+
+#ifdef HAVE_FORKPTY
+ if (state->pty_p) /* Only interpret VT100 sequences if running in pty-mode.
+ It would be nice if we could just interpret them all
+ the time, but that would require subprocesses to send
+ CRLF line endings instead of bare LF, so that's no good.
+ */
+ {
+ int i;
+ int start, end;
+
+ /* Mostly duplicated in apple2-main.c */
+
+ switch (state->escstate)
+ {
+ case 0:
+ switch (c)
+ {
+ case 7: /* BEL */
+ /* Dummy case - we don't want the screensaver to beep */
+ /* #### But maybe this should flash the screen? */
+ break;
+ case 8: /* BS */
+ if (state->cursor_x > 0)
+ state->cursor_x--;
+ break;
+ case 9: /* HT */
+ if (state->cursor_x < cols - 8)
+ {
+ state->cursor_x = (state->cursor_x & ~7) + 8;
+ }
+ else
+ {
+ state->cursor_x = 0;
+ if (state->cursor_y < rows - 1)
+ state->cursor_y++;
+ else
+ scroll (state);
+ }
+ break;
+ case 10: /* LF */
+# ifndef HAVE_FORKPTY
+ state->cursor_x = 0; /* No ptys on iPhone; assume CRLF. */
+# endif
+ case 11: /* VT */
+ case 12: /* FF */
+ if(state->last_c == 13)
+ {
+ cell->state = NORMAL;
+ cell->p_char = state->chars[state->bk];
+ cell->changed = True;
+ }
+ if (state->cursor_y < rows - 1)
+ state->cursor_y++;
+ else
+ scroll (state);
+ break;
+ case 13: /* CR */
+ state->cursor_x = 0;
+ cell = &state->cells[cols * state->cursor_y];
+ if((cell->p_char == NULL) || (cell->p_char->name == CURSOR_INDEX))
+ state->bk = ' ';
+ else
+ state->bk = cell->p_char->name;
+ break;
+ case 14: /* SO */
+ case 15: /* SI */
+ /* Dummy case - there is one and only one font. */
+ break;
+ case 24: /* CAN */
+ case 26: /* SUB */
+ /* Dummy case - these interrupt escape sequences, so
+ they don't do anything in this state */
+ break;
+ case 27: /* ESC */
+ state->escstate = 1;
+ break;
+ case 127: /* DEL */
+ /* Dummy case - this is supposed to be ignored */
+ break;
+ case 155: /* CSI */
+ state->escstate = 2;
+ for(i = 0; i < NPAR; i++)
+ state->csiparam[i] = 0;
+ state->curparam = 0;
+ break;
+ default:
+
+ PRINT: /* Come from states 102-106 */
+ c = process_unicrud (state, c);
+ if (! c)
+ break;
+
+ /* If the cursor is in column 39 and we print a character, then
+ that character shows up in column 39, and the cursor is no
+ longer visible on the screen (it's in "column 40".) If
+ another character is printed, then that character shows up in
+ column 0, and the cursor moves to column 1.
+
+ This is empirically what xterm and gnome-terminal do, so that
+ must be the right thing. (In xterm, the cursor vanishes,
+ whereas; in gnome-terminal, the cursor overprints the
+ character in col 39.)
+ */
+ cell->state = FLARE;
+ cell->p_char = state->chars[c];
+ cell->changed = True;
+ state->cursor_x++;
+
+ if (c != ' ' && cell->p_char->blank_p)
+ cell->p_char = state->chars[CURSOR_INDEX];
+
+ if (state->cursor_x >= cols - 1 /*####*/)
+ {
+ state->cursor_x = 0;
+ if (state->cursor_y >= rows - 1)
+ scroll (state);
+ else
+ state->cursor_y++;
+ }
+ break;
+ }
+ break;
+ case 1:
+ switch (c)
+ {
+ case 24: /* CAN */
+ case 26: /* SUB */
+ state->escstate = 0;
+ break;
+ case 'c': /* Reset */
+ clear (state);
+ state->escstate = 0;
+ break;
+ case 'D': /* Linefeed */
+ if (state->cursor_y < rows - 1)
+ state->cursor_y++;
+ else
+ scroll (state);
+ state->escstate = 0;
+ break;
+ case 'E': /* Newline */
+ state->cursor_x = 0;
+ state->escstate = 0;
+ break;
+ case 'M': /* Reverse newline */
+ if (state->cursor_y > 0)
+ state->cursor_y--;
+ state->escstate = 0;
+ break;
+ case '7': /* Save state */
+ state->saved_x = state->cursor_x;
+ state->saved_y = state->cursor_y;
+ state->escstate = 0;
+ break;
+ case '8': /* Restore state */
+ state->cursor_x = state->saved_x;
+ state->cursor_y = state->saved_y;
+ state->escstate = 0;
+ break;
+ case '[': /* CSI */
+ state->escstate = 2;
+ for(i = 0; i < NPAR; i++)
+ state->csiparam[i] = 0;
+ state->curparam = 0;
+ break;
+ case '%': /* Select charset */
+ /* @: Select default (ISO 646 / ISO 8859-1)
+ G: Select UTF-8
+ 8: Select UTF-8 (obsolete)
+
+ We can just ignore this and always process UTF-8, I think?
+ We must still catch the last byte, though.
+ */
+ case '(':
+ case ')':
+ /* I don't support different fonts either - see above
+ for SO and SI */
+ state->escstate = 3;
+ break;
+ default:
+ /* Escape sequences not supported:
+ *
+ * H - Set tab stop
+ * Z - Terminal identification
+ * > - Keypad change
+ * = - Other keypad change
+ * ] - OS command
+ */
+ state->escstate = 0;
+ break;
+ }
+ break;
+ case 2:
+ switch (c)
+ {
+ case 24: /* CAN */
+ case 26: /* SUB */
+ state->escstate = 0;
+ break;
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ if (state->curparam < NPAR)
+ state->csiparam[state->curparam] = (state->csiparam[state->curparam] * 10) + (c - '0');
+ break;
+ case ';':
+ state->csiparam[++state->curparam] = 0;
+ break;
+ case '[':
+ state->escstate = 3;
+ break;
+ case '@':
+ for (i = 0; i < state->csiparam[0]; i++)
+ {
+ if(++state->cursor_x > cols)
+ {
+ state->cursor_x = 0;
+ if (state->cursor_y < rows - 1)
+ state->cursor_y++;
+ else
+ scroll (state);
+ }
+ cell = &state->cells[cols * state->cursor_y + state->cursor_x];
+ if (cell->state == FLARE || cell->state == NORMAL)
+ {
+ cell->state = FADE;
+ cell->changed = True;
+ }
+ }
+ state->escstate = 0;
+ break;
+ case 'F':
+ state->cursor_x = 0;
+ case 'A':
+ if (state->csiparam[0] == 0)
+ state->csiparam[0] = 1;
+ if ((state->cursor_y -= state->csiparam[0]) < 0)
+ state->cursor_y = 0;
+ state->escstate = 0;
+ break;
+ case 'E':
+ state->cursor_x = 0;
+ case 'e':
+ case 'B':
+ if (state->csiparam[0] == 0)
+ state->csiparam[0] = 1;
+ if ((state->cursor_y += state->csiparam[0]) >= rows - 1 /*####*/)
+ state->cursor_y = rows - 1;
+ state->escstate = 0;
+ break;
+ case 'a':
+ case 'C':
+ if (state->csiparam[0] == 0)
+ state->csiparam[0] = 1;
+ if ((state->cursor_x += state->csiparam[0]) >= cols - 1 /*####*/)
+ state->cursor_x = cols - 1;
+ state->escstate = 0;
+ break;
+ case 'D':
+ if (state->csiparam[0] == 0)
+ state->csiparam[0] = 1;
+ if ((state->cursor_x -= state->csiparam[0]) < 0)
+ state->cursor_x = 0;
+ state->escstate = 0;
+ break;
+ case 'd':
+ if ((state->cursor_y = (state->csiparam[0] - 1)) >= rows - 1 /*####*/)
+ state->cursor_y = rows - 1;
+ state->escstate = 0;
+ break;
+ case '`':
+ case 'G':
+ if ((state->cursor_x = (state->csiparam[0] - 1)) >= cols - 1 /*####*/)
+ state->cursor_x = cols - 1;
+ state->escstate = 0;
+ break;
+ case 'f':
+ case 'H':
+ if ((state->cursor_y = (state->csiparam[0] - 1)) >= rows - 1 /*####*/)
+ state->cursor_y = rows - 1;
+ if ((state->cursor_x = (state->csiparam[1] - 1)) >= cols - 1 /*####*/)
+ state->cursor_x = cols - 1;
+ if(state->cursor_y < 0)
+ state->cursor_y = 0;
+ if(state->cursor_x < 0)
+ state->cursor_x = 0;
+ state->escstate = 0;
+ break;
+ case 'J':
+ start = 0;
+ end = rows * cols;
+ if (state->csiparam[0] == 0)
+ start = cols * state->cursor_y + state->cursor_x;
+ if (state->csiparam[0] == 1)
+ end = cols * state->cursor_y + state->cursor_x;
+ for (i = start; i < end; i++)
+ {
+ cell = &state->cells[i];
+ if (cell->state == FLARE || cell->state == NORMAL)
+ {
+ cell->state = FADE;
+ cell->changed = True;
+ }
+ }
+ set_cursor (state, True);
+ state->escstate = 0;
+ break;
+ case 'K':
+ start = 0;
+ end = cols;
+ if (state->csiparam[0] == 0)
+ start = state->cursor_x;
+ if (state->csiparam[1] == 1)
+ end = state->cursor_x;
+ for (i = start; i < end; i++)
+ {
+ if (cell->state == FLARE || cell->state == NORMAL)
+ {
+ cell->state = FADE;
+ cell->changed = True;
+ }
+ cell++;
+ }
+ state->escstate = 0;
+ break;
+ case 'm': /* Set attributes unimplemented (bold, blink, rev) */
+ state->escstate = 0;
+ break;
+ case 's': /* Save position */
+ state->saved_x = state->cursor_x;
+ state->saved_y = state->cursor_y;
+ state->escstate = 0;
+ break;
+ case 'u': /* Restore position */
+ state->cursor_x = state->saved_x;
+ state->cursor_y = state->saved_y;
+ state->escstate = 0;
+ break;
+ case '?': /* DEC Private modes */
+ if ((state->curparam != 0) || (state->csiparam[0] != 0))
+ state->escstate = 0;
+ break;
+ default:
+ /* Known unsupported CSIs:
+ *
+ * L - Insert blank lines
+ * M - Delete lines (I don't know what this means...)
+ * P - Delete characters
+ * X - Erase characters (difference with P being...?)
+ * c - Terminal identification
+ * g - Clear tab stop(s)
+ * h - Set mode (Mainly due to its complexity and lack of good
+ docs)
+ * l - Clear mode
+ * m - Set mode (Phosphor is, per defenition, green on black)
+ * n - Status report
+ * q - Set keyboard LEDs
+ * r - Set scrolling region (too exhausting - noone uses this,
+ right?)
+ */
+ state->escstate = 0;
+ break;
+ }
+ break;
+ case 3:
+ state->escstate = 0;
+ break;
+
+ case 102: /* states 102-106 are for UTF-8 decoding */
+ case 103:
+ case 104:
+ case 105:
+ case 106:
+ goto PRINT;
+
+ default:
+ abort();
+ }
+ set_cursor (state, True);
+ }
+ else
+#endif /* HAVE_FORKPTY */
+ {
+ if (c == '\t') c = ' '; /* blah. */
+
+ if (c == '\r' || c == '\n') /* handle CR, LF, or CRLF as "new line". */
+ {
+ if (c == '\n' && state->last_c == '\r')
+ ; /* CRLF -- do nothing */
+ else
+ {
+ state->cursor_x = 0;
+ if (state->cursor_y == rows - 1)
+ scroll (state);
+ else
+ state->cursor_y++;
+ }
+ }
+ else if (c == '\014')
+ {
+ clear (state);
+ }
+ else
+ {
+ c = process_unicrud (state, c);
+ if (!c) return;
+
+ cell->state = FLARE;
+ cell->p_char = state->chars[c];
+ cell->changed = True;
+ state->cursor_x++;
+
+ if (c != ' ' && cell->p_char->blank_p)
+ cell->p_char = state->chars[CURSOR_INDEX];
+
+ if (state->cursor_x >= cols - 1)
+ {
+ state->cursor_x = 0;
+ if (state->cursor_y >= rows - 1)
+ scroll (state);
+ else
+ state->cursor_y++;
+ }
+ }
+ set_cursor (state, True);
+ }
+
+ state->last_c = c;
+}
+
+
+static void
+update_display (p_state *state, Bool changed_only)
+{
+ int x, y;
+
+ for (y = 0; y < state->grid_height; y++)
+ for (x = 0; x < state->grid_width; x++)
+ {
+ p_cell *cell = &state->cells[state->grid_width * y + x];
+ int width, height, tx, ty;
+
+ if (changed_only && !cell->changed)
+ continue;
+
+ width = state->char_width * state->scale;
+ height = state->char_height * state->scale;
+ tx = x * width + state->xmargin;
+ ty = y * height + state->ymargin;
+
+ if (cell->state == BLANK || cell->p_char->blank_p)
+ {
+ XFillRectangle (state->dpy, state->window, state->gcs[BLANK],
+ tx, ty, width, height);
+ }
+ else
+ {
+#ifdef FUZZY_BORDER
+ GC gc1 = state->gcs[cell->state];
+ GC gc2 = ((cell->state + 2) < state->ticks
+ ? state->gcs[cell->state + 2]
+ : 0);
+ GC gc3 = (gc2 ? gc2 : gc1);
+ if (gc3)
+ XCopyPlane (state->dpy, cell->p_char->pixmap, state->window, gc3,
+ 0, 0, width, height, tx, ty, 1L);
+ if (gc2)
+ {
+ XSetClipMask (state->dpy, gc1, cell->p_char->pixmap2);
+ XSetClipOrigin (state->dpy, gc1, tx, ty);
+ XFillRectangle (state->dpy, state->window, gc1,
+ tx, ty, width, height);
+ XSetClipMask (state->dpy, gc1, None);
+ }
+#else /* !FUZZY_BORDER */
+
+ XCopyPlane (state->dpy,
+ cell->p_char->pixmap, state->window,
+ state->gcs[cell->state],
+ 0, 0, width, height, tx, ty, 1L);
+
+#endif /* !FUZZY_BORDER */
+ }
+
+ cell->changed = False;
+ }
+}
+
+
+static unsigned long
+phosphor_draw (Display *dpy, Window window, void *closure)
+{
+ p_state *state = (p_state *) closure;
+ int c;
+ update_display (state, True);
+ decay (state);
+
+ c = textclient_getc (state->tc);
+ if (c > 0)
+ print_char (state, c);
+
+ return state->delay;
+}
+
+
+static void
+phosphor_reshape (Display *dpy, Window window, void *closure,
+ unsigned int w, unsigned int h)
+{
+ p_state *state = (p_state *) closure;
+ Bool changed_p = resize_grid (state);
+
+ if (! changed_p) return;
+
+ textclient_reshape (state->tc,
+ w - state->xmargin * 2,
+ h - state->ymargin * 2,
+ state->grid_width - 1,
+ state->grid_height - 1,
+ 0);
+}
+
+
+static Bool
+phosphor_event (Display *dpy, Window window, void *closure, XEvent *event)
+{
+ p_state *state = (p_state *) closure;
+
+ if (event->xany.type == Expose)
+ update_display (state, False);
+ else if (event->xany.type == KeyPress)
+ return textclient_putc (state->tc, &event->xkey);
+ return False;
+}
+
+static void
+phosphor_free (Display *dpy, Window window, void *closure)
+{
+ p_state *state = (p_state *) closure;
+
+ textclient_close (state->tc);
+ if (state->cursor_timer)
+ XtRemoveTimeOut (state->cursor_timer);
+
+ /* #### there's more to free here */
+
+ free (state);
+}
+
+
+
+static const char *phosphor_defaults [] = {
+/* ".lowrez: true",*/
+ ".background: Black",
+ ".foreground: #00FF00",
+ "*fpsSolid: true",
+#if defined(BUILTIN_FONT)
+ "*font: (builtin)",
+#elif defined(HAVE_COCOA)
+ "*font: Monaco 15",
+#else
+ "*font: fixed",
+#endif
+ "*scale: 6",
+ "*ticks: 20",
+ "*delay: 50000",
+ "*cursor: 333",
+ "*program: xscreensaver-text",
+ "*relaunch: 5",
+ "*metaSendsESC: True",
+ "*swapBSDEL: True",
+#ifdef HAVE_FORKPTY
+ "*usePty: True",
+#else /* !HAVE_FORKPTY */
+ "*usePty: False",
+#endif /* !HAVE_FORKPTY */
+ 0
+};
+
+static XrmOptionDescRec phosphor_options [] = {
+ { "-font", ".font", XrmoptionSepArg, 0 },
+ { "-scale", ".scale", XrmoptionSepArg, 0 },
+ { "-ticks", ".ticks", XrmoptionSepArg, 0 },
+ { "-delay", ".delay", XrmoptionSepArg, 0 },
+ { "-program", ".program", XrmoptionSepArg, 0 },
+ { "-pipe", ".usePty", XrmoptionNoArg, "False" },
+ { "-pty", ".usePty", XrmoptionNoArg, "True" },
+ { "-meta", ".metaSendsESC", XrmoptionNoArg, "False" },
+ { "-esc", ".metaSendsESC", XrmoptionNoArg, "True" },
+ { "-bs", ".swapBSDEL", XrmoptionNoArg, "False" },
+ { "-del", ".swapBSDEL", XrmoptionNoArg, "True" },
+ { 0, 0, 0, 0 }
+};
+
+
+XSCREENSAVER_MODULE ("Phosphor", phosphor)