From d3a98cf6cbc3bd0b9efc570f58e8812c03931c18 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 16 Oct 2018 10:08:48 +0200 Subject: Original 5.40 --- hacks/phosphor.c | 1451 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1451 insertions(+) create mode 100644 hacks/phosphor.c (limited to 'hacks/phosphor.c') 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 + * + * 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 + * Pty and vt100 emulation by Fredrik Tolf + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif /* HAVE_CONFIG_H */ + +#ifndef HAVE_JWXYZ +# include +#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) -- cgit v1.2.3-55-g7522