1 /* xscreensaver, Copyright (c) 1999-2018 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.
12 * Written by Jamie Zawinski <jwz@jwz.org>
13 * Pty and vt100 emulation by Fredrik Tolf <fredrik@dolda2000.com>
18 #endif /* HAVE_CONFIG_H */
21 # include <X11/Intrinsic.h>
24 #include "screenhack.h"
25 #include "textclient.h"
26 #include "ximage-loader.h"
31 #define MAX(a,b) ((a)>(b)?(a):(b))
32 #define MIN(a,b) ((a)<(b)?(a):(b))
38 #define STATE_MAX FADE
40 #define CURSOR_INDEX 128
47 # include "images/gen/6x10font_png.h"
48 #endif /* BUILTIN_FONT */
56 #endif /* FUZZY_BORDER */
69 XWindowAttributes xgwa;
72 int grid_width, grid_height;
73 int char_width, char_height;
83 int unicruds; unsigned char unicrud[7];
92 #endif /* FUZZY_BORDER */
96 int cursor_x, cursor_y;
97 XtIntervalId cursor_timer;
110 static void capture_font_bits (p_state *state);
111 static p_char *make_character (p_state *state, int c);
112 static void char_to_pixmap (p_state *state, p_char *pc, int c);
115 /* About font metrics:
117 "lbearing" is the distance from the leftmost pixel of a character to
118 the logical origin of that character. That is, it is the number of
119 pixels of the character which are to the left of its logical origin.
121 "rbearing" is the distance from the logical origin of a character to
122 the rightmost pixel of a character. That is, it is the number of
123 pixels of the character to the right of its logical origin.
125 "descent" is the distance from the bottommost pixel of a character to
126 the logical baseline. That is, it is the number of pixels of the
127 character which are below the baseline.
129 "ascent" is the distance from the logical baseline to the topmost pixel.
130 That is, it is the number of pixels of the character above the baseline.
132 Therefore, the bounding box of the "ink" of a character is
133 lbearing + rbearing by ascent + descent;
135 "width" is the distance from the logical origin of this character to
136 the position where the logical orgin of the next character should be
139 For our purposes, we're only interested in the part of the character
140 lying inside the "width" box. If the characters have ink outside of
141 that box (the "charcell" box) then we're going to lose it. Alas.
145 static void clear (p_state *);
146 static void set_cursor (p_state *, Bool on);
148 static unsigned short scale_color_channel (unsigned short ch1, unsigned short ch2)
150 return (ch1 * 100 + ch2 * 156) >> 8;
153 #define FONT6x10_WIDTH (256*7)
154 #define FONT6x10_HEIGHT 10
157 phosphor_init (Display *dpy, Window window)
161 p_state *state = (p_state *) calloc (sizeof(*state), 1);
162 char *fontname = get_string_resource (dpy, "font", "Font");
166 state->window = window;
168 XGetWindowAttributes (dpy, window, &state->xgwa);
169 /* XSelectInput (dpy, window, state->xgwa.your_event_mask | ExposureMask);*/
171 state->delay = get_integer_resource (dpy, "delay", "Integer");
172 state->pty_p = get_boolean_resource (dpy, "usePty", "UsePty");
174 if (!strcasecmp (fontname, "builtin") ||
175 !strcasecmp (fontname, "(builtin)"))
178 fprintf (stderr, "%s: no builtin font\n", progname);
179 state->font = load_font_retry (dpy, "fixed");
180 #endif /* !BUILTIN_FONT */
184 state->font = load_font_retry (dpy, fontname);
185 if (!state->font) abort();
189 state->scale = get_integer_resource (dpy, "scale", "Integer");
190 state->ticks = STATE_MAX + get_integer_resource (dpy, "ticks", "Integer");
193 if (state->xgwa.width > 2560) state->scale *= 2; /* Retina displays */
195 state->cursor_blink = get_integer_resource (dpy, "cursor", "Time");
200 state->char_width = (FONT6x10_WIDTH / 256) - 1;
201 state->char_height = FONT6x10_HEIGHT;
204 # endif /* BUILTIN_FONT */
206 state->char_width = font->max_bounds.width;
207 state->char_height = font->max_bounds.ascent + font->max_bounds.descent;
211 /* Stupid iPhone X bezel.
212 #### This is the worst of all possible ways to do this! But how else?
214 if (state->xgwa.width == 2436 || state->xgwa.height == 2436) {
216 state->ymargin = state->xmargin;
220 state->grid_width = ((state->xgwa.width - state->xmargin * 2) /
221 (state->char_width * state->scale));
222 state->grid_height = ((state->xgwa.height - state->ymargin * 2) /
223 (state->char_height * state->scale));
224 state->cells = (p_cell *) calloc (sizeof(p_cell),
225 state->grid_width * state->grid_height);
226 state->chars = (p_char **) calloc (sizeof(p_char *), 256);
228 state->gcs = (GC *) calloc (sizeof(GC), state->ticks + 1);
231 int ncolors = MAX (1, state->ticks - 3);
232 XColor *colors = (XColor *) calloc (ncolors, sizeof(XColor));
234 double s1, s2, v1, v2;
236 unsigned long fg = get_pixel_resource (state->dpy, state->xgwa.colormap,
237 "foreground", "Foreground");
238 unsigned long bg = get_pixel_resource (state->dpy, state->xgwa.colormap,
239 "background", "Background");
240 unsigned long flare = fg;
242 XColor fg_color, bg_color;
245 XQueryColor (state->dpy, state->xgwa.colormap, &fg_color);
248 XQueryColor (state->dpy, state->xgwa.colormap, &bg_color);
250 /* Now allocate a ramp of colors from the main color to the background. */
251 rgb_to_hsv (scale_color_channel(fg_color.red, bg_color.red),
252 scale_color_channel(fg_color.green, bg_color.green),
253 scale_color_channel(fg_color.blue, bg_color.blue),
255 rgb_to_hsv (bg_color.red, bg_color.green, bg_color.blue, &h2, &s2, &v2);
257 /* Avoid rainbow effects when fading to black/grey/white. */
263 make_color_ramp (state->xgwa.screen, state->xgwa.visual,
264 state->xgwa.colormap,
270 /* Adjust to the number of colors we actually got. */
271 state->ticks = ncolors + STATE_MAX;
273 /* If the foreground is brighter than the background, the flare is white.
274 * Otherwise, the flare is left at the foreground color (i.e. no flare). */
275 rgb_to_hsv (fg_color.red, fg_color.green, fg_color.blue, &h1, &s1, &v1);
279 /* WhitePixel is only for the default visual, which can be overridden
280 * on the command line. */
282 white.green = 0xffff;
284 if (XAllocColor(state->dpy, state->xgwa.colormap, &white))
288 /* Now, GCs all around.
290 state->gcv.font = (font ? font->fid : 0);
291 state->gcv.cap_style = CapRound;
293 state->gcv.line_width = (int) (((long) state->scale) * 1.3);
294 if (state->gcv.line_width == state->scale)
295 state->gcv.line_width++;
296 #else /* !FUZZY_BORDER */
297 state->gcv.line_width = (int) (((long) state->scale) * 0.9);
298 if (state->gcv.line_width >= state->scale)
299 state->gcv.line_width = state->scale - 1;
300 if (state->gcv.line_width < 1)
301 state->gcv.line_width = 1;
302 #endif /* !FUZZY_BORDER */
304 flags = (GCForeground | GCBackground | GCCapStyle | GCLineWidth);
306 state->gcv.background = bg;
307 state->gcv.foreground = bg;
308 state->gcs[BLANK] = XCreateGC (state->dpy, state->window, flags,
311 state->gcv.foreground = flare;
312 state->gcs[FLARE] = XCreateGC (state->dpy, state->window, flags,
315 state->gcv.foreground = fg;
316 state->gcs[NORMAL] = XCreateGC (state->dpy, state->window, flags,
319 for (i = 0; i < ncolors; i++)
321 state->gcv.foreground = colors[i].pixel;
322 state->gcs[STATE_MAX + i] = XCreateGC (state->dpy, state->window,
327 capture_font_bits (state);
329 set_cursor (state, True);
333 state->tc = textclient_open (dpy);
334 textclient_reshape (state->tc,
335 state->xgwa.width - state->xmargin * 2,
336 state->xgwa.height - state->ymargin * 2,
337 state->grid_width - 1,
338 state->grid_height - 1,
345 /* Re-query the window size and update the internal character grid if changed.
348 resize_grid (p_state *state)
350 int ow = state->grid_width;
351 int oh = state->grid_height;
352 p_cell *ocells = state->cells;
355 XGetWindowAttributes (state->dpy, state->window, &state->xgwa);
357 /* Would like to ensure here that
358 state->char_height * state->scale <= state->xgwa.height
359 but changing scale requires regenerating the bitmaps. */
361 state->grid_width = ((state->xgwa.width - state->xmargin * 2) /
362 (state->char_width * state->scale));
363 state->grid_height = ((state->xgwa.height - state->ymargin * 2) /
364 (state->char_height * state->scale));
366 if (state->grid_width < 2) state->grid_width = 2;
367 if (state->grid_height < 2) state->grid_height = 2;
369 if (ow == state->grid_width &&
370 oh == state->grid_height)
373 state->cells = (p_cell *) calloc (sizeof(p_cell),
374 state->grid_width * state->grid_height);
376 for (y = 0; y < state->grid_height; y++)
378 for (x = 0; x < state->grid_width; x++)
380 p_cell *ncell = &state->cells [state->grid_width * y + x];
381 if (x < ow && y < oh)
382 *ncell = ocells [ow * y + x];
383 ncell->changed = True;
387 if (state->cursor_x >= state->grid_width)
388 state->cursor_x = state->grid_width-1;
389 if (state->cursor_y >= state->grid_height)
390 state->cursor_y = state->grid_height-1;
398 capture_font_bits (p_state *state)
400 XFontStruct *font = state->font;
401 int safe_width, height;
402 unsigned char string[257];
411 safe_width = state->char_width + 1;
412 height = state->char_height;
415 XWindowAttributes xgwa;
417 Pixmap p = image_data_to_pixmap (state->dpy, state->window,
418 _6x10font_png, sizeof(_6x10font_png),
420 XImage *im = XGetImage (state->dpy, p, 0, 0, pix_w, pix_h, ~0L, ZPixmap);
421 XImage *mm = XGetImage (state->dpy, m, 0, 0, pix_w, pix_h, 1, XYPixmap);
426 unsigned long black =
427 BlackPixelOfScreen (DefaultScreenOfDisplay (state->dpy));
429 XFreePixmap (state->dpy, p);
430 XFreePixmap (state->dpy, m);
431 if (pix_w != 256*7) abort();
432 if (pix_h != 10) abort();
433 if (pix_w != FONT6x10_WIDTH) abort();
434 if (pix_h != FONT6x10_HEIGHT) abort();
436 XGetWindowAttributes (state->dpy, state->window, &xgwa);
437 im2 = XCreateImage (state->dpy, xgwa.visual, 1, XYBitmap, 0, 0,
439 im2->data = malloc (im2->bytes_per_line * im2->height);
441 /* Convert deep image to 1 bit */
442 for (y = 0; y < pix_h; y++)
443 for (x = 0; x < pix_w; x++)
444 XPutPixel (im2, x, y,
445 (XGetPixel (mm, x, y)
446 ? (XGetPixel (im, x, y) == black)
453 p2 = XCreatePixmap (state->dpy, state->window,
454 im2->width, im2->height, im2->depth);
457 gc = XCreateGC (state->dpy, p2, GCForeground|GCBackground, &gcv);
458 XPutImage (state->dpy, p2, gc, im2, 0, 0, 0, 0, im2->width, im2->height);
459 XFreeGC (state->dpy, gc);
463 # endif /* BUILTIN_FONT */
465 safe_width = font->max_bounds.rbearing - font->min_bounds.lbearing;
466 height = state->char_height;
469 p = XCreatePixmap (state->dpy, state->window,
470 (safe_width * 256), height, 1);
472 for (i = 0; i < 256; i++)
473 string[i] = (unsigned char) i;
476 state->gcv.foreground = 0;
477 state->gcv.background = 0;
478 state->gc0 = XCreateGC (state->dpy, p,
479 (GCForeground | GCBackground),
482 state->gcv.foreground = 1;
483 state->gc1 = XCreateGC (state->dpy, p,
484 ((font ? GCFont : 0) |
485 GCForeground | GCBackground |
486 GCCapStyle | GCLineWidth),
490 jwxyz_XSetAntiAliasing (state->dpy, state->gc0, False);
491 jwxyz_XSetAntiAliasing (state->dpy, state->gc1, False);
496 state->gcv.line_width = (int) (((long) state->scale) * 0.8);
497 if (state->gcv.line_width >= state->scale)
498 state->gcv.line_width = state->scale - 1;
499 if (state->gcv.line_width < 1)
500 state->gcv.line_width = 1;
501 state->gc2 = XCreateGC (state->dpy, p,
502 ((font ? GCFont : 0) |
503 GCForeground | GCBackground |
504 GCCapStyle | GCLineWidth),
507 #endif /* FUZZY_BORDER */
509 XFillRectangle (state->dpy, p, state->gc0, 0, 0, (safe_width * 256), height);
514 XCopyPlane (state->dpy, p2, p, state->gc1,
515 0, 0, FONT6x10_WIDTH, FONT6x10_HEIGHT,
517 XFreePixmap (state->dpy, p2);
520 # endif /* BUILTIN_FONT */
522 for (i = 0; i < 256; i++)
524 if (string[i] < font->min_char_or_byte2 ||
525 string[i] > font->max_char_or_byte2)
527 XDrawString (state->dpy, p, state->gc1,
528 i * safe_width, font->ascent,
529 (char *) (string + i), 1);
533 /* Draw the cursor. */
534 XFillRectangle (state->dpy, p, state->gc1,
535 (CURSOR_INDEX * safe_width), 1,
538 ? font->per_char['n'-font->min_char_or_byte2].width
539 : font->max_bounds.width)
540 : state->char_width),
543 : state->char_height));
545 state->font_bits = XGetImage (state->dpy, p, 0, 0,
546 (safe_width * 256), height, ~0L, XYPixmap);
547 XFreePixmap (state->dpy, p);
549 for (i = 0; i < 256; i++)
550 state->chars[i] = make_character (state, i);
551 state->chars[CURSOR_INDEX] = make_character (state, CURSOR_INDEX);
556 make_character (p_state *state, int c)
558 p_char *pc = (p_char *) malloc (sizeof (*pc));
559 pc->name = (unsigned char) c;
560 pc->width = state->scale * state->char_width;
561 pc->height = state->scale * state->char_height;
562 char_to_pixmap (state, pc, c);
568 char_to_pixmap (p_state *state, p_char *pc, int c)
575 #endif /* FUZZY_BORDER */
579 XFontStruct *font = state->font;
580 int safe_width = (font
581 ? font->max_bounds.rbearing - font->min_bounds.lbearing
582 : state->char_width + 1);
584 int width = state->scale * state->char_width;
585 int height = state->scale * state->char_height;
587 if (font && (c < font->min_char_or_byte2 ||
588 c > font->max_char_or_byte2))
592 p = XCreatePixmap (state->dpy, state->window, width, height, 1);
593 XFillRectangle (state->dpy, p, state->gc0, 0, 0, width, height);
596 p2 = XCreatePixmap (state->dpy, state->window, width, height, 1);
597 XFillRectangle (state->dpy, p2, state->gc0, 0, 0, width, height);
598 #endif /* FUZZY_BORDER */
600 from = safe_width * c;
601 to = safe_width * (c + 1);
604 if (c > 75 && c < 150)
606 printf ("\n=========== %d (%c)\n", c, c);
607 for (y = 0; y < state->char_height; y++)
609 for (x1 = from; x1 < to; x1++)
610 printf (XGetPixel (state->font_bits, x1, y) ? "* " : ". ");
617 for (y = 0; y < state->char_height; y++)
618 for (x1 = from; x1 < to; x1++)
619 if (XGetPixel (state->font_bits, x1, y))
621 int xoff = state->scale / 2;
623 for (x2 = x1; x2 < to; x2++)
624 if (!XGetPixel (state->font_bits, x2, y))
627 XDrawLine (state->dpy, p, gc,
628 (x1 - from) * state->scale + xoff, y * state->scale,
629 (x2 - from) * state->scale + xoff, y * state->scale);
631 XDrawLine (state->dpy, p2, gc2,
632 (x1 - from) * state->scale + xoff, y * state->scale,
633 (x2 - from) * state->scale + xoff, y * state->scale);
634 #endif /* FUZZY_BORDER */
639 /* if (pc->blank_p && c == CURSOR_INDEX)
646 #endif /* FUZZY_BORDER */
650 /* Managing the display.
653 static void cursor_on_timer (XtPointer closure, XtIntervalId *id);
654 static void cursor_off_timer (XtPointer closure, XtIntervalId *id);
657 set_cursor_1 (p_state *state, Bool on)
659 p_cell *cell = &state->cells[state->grid_width * state->cursor_y
661 p_char *cursor = state->chars[CURSOR_INDEX];
662 int new_state = (on ? NORMAL : FADE);
664 if (cell->p_char != cursor)
665 cell->changed = True;
667 if (cell->state != new_state)
668 cell->changed = True;
670 cell->p_char = cursor;
671 cell->state = new_state;
672 return cell->changed;
676 set_cursor (p_state *state, Bool on)
678 if (set_cursor_1 (state, on))
680 if (state->cursor_timer)
681 XtRemoveTimeOut (state->cursor_timer);
682 state->cursor_timer = 0;
683 cursor_on_timer (state, 0);
689 cursor_off_timer (XtPointer closure, XtIntervalId *id)
691 p_state *state = (p_state *) closure;
692 XtAppContext app = XtDisplayToApplicationContext (state->dpy);
693 set_cursor_1 (state, False);
694 state->cursor_timer = XtAppAddTimeOut (app, state->cursor_blink,
695 cursor_on_timer, closure);
699 cursor_on_timer (XtPointer closure, XtIntervalId *id)
701 p_state *state = (p_state *) closure;
702 XtAppContext app = XtDisplayToApplicationContext (state->dpy);
703 set_cursor_1 (state, True);
704 state->cursor_timer = XtAppAddTimeOut (app, 2 * state->cursor_blink,
705 cursor_off_timer, closure);
710 clear (p_state *state)
715 for (y = 0; y < state->grid_height; y++)
716 for (x = 0; x < state->grid_width; x++)
718 p_cell *cell = &state->cells[state->grid_width * y + x];
719 if (cell->state == FLARE || cell->state == NORMAL)
722 cell->changed = True;
725 set_cursor (state, True);
730 decay (p_state *state)
733 for (y = 0; y < state->grid_height; y++)
734 for (x = 0; x < state->grid_width; x++)
736 p_cell *cell = &state->cells[state->grid_width * y + x];
737 if (cell->state == FLARE)
739 cell->state = NORMAL;
740 cell->changed = True;
742 else if (cell->state >= FADE)
745 if (cell->state >= state->ticks)
747 cell->changed = True;
754 scroll (p_state *state)
758 for (x = 0; x < state->grid_width; x++)
760 p_cell *from = 0, *to = 0;
761 for (y = 1; y < state->grid_height; y++)
763 from = &state->cells[state->grid_width * y + x];
764 to = &state->cells[state->grid_width * (y-1) + x];
766 if ((from->state == FLARE || from->state == NORMAL) &&
767 !from->p_char->blank_p)
770 to->state = NORMAL; /* should be FLARE? Looks bad... */
774 if (to->state == FLARE || to->state == NORMAL)
782 if (to && (to->state == FLARE || to->state == NORMAL))
788 set_cursor (state, True);
793 process_unicrud (p_state *state, int c)
795 if ((c & 0xE0) == 0xC0) { /* 110xxxxx: 11 bits, 2 bytes */
797 state->unicrud[0] = c;
798 state->escstate = 102;
799 } else if ((c & 0xF0) == 0xE0) { /* 1110xxxx: 16 bits, 3 bytes */
801 state->unicrud[0] = c;
802 state->escstate = 103;
803 } else if ((c & 0xF8) == 0xF0) { /* 11110xxx: 21 bits, 4 bytes */
805 state->unicrud[0] = c;
806 state->escstate = 104;
807 } else if ((c & 0xFC) == 0xF8) { /* 111110xx: 26 bits, 5 bytes */
809 state->unicrud[0] = c;
810 state->escstate = 105;
811 } else if ((c & 0xFE) == 0xFC) { /* 1111110x: 31 bits, 6 bytes */
813 state->unicrud[0] = c;
814 state->escstate = 106;
815 } else if (state->unicruds == 0) {
818 int total = state->escstate - 100; /* see what I did there */
819 if (state->unicruds < total) {
820 /* Buffer more bytes of the UTF-8 sequence */
821 state->unicrud[state->unicruds++] = c;
824 if (state->unicruds >= total) {
825 /* Done! Convert it to Latin1 and print that. */
827 state->unicrud[state->unicruds] = 0;
828 s = utf8_to_latin1 ((const char *) state->unicrud, False);
832 c = (unsigned char) s[0];
843 print_char (p_state *state, int c)
845 int cols = state->grid_width;
846 int rows = state->grid_height;
847 p_cell *cell = &state->cells[state->grid_width * state->cursor_y
850 /* Start the cursor fading (in case we don't end up overwriting it.) */
851 if (cell->state == FLARE || cell->state == NORMAL)
854 cell->changed = True;
858 if (state->pty_p) /* Only interpret VT100 sequences if running in pty-mode.
859 It would be nice if we could just interpret them all
860 the time, but that would require subprocesses to send
861 CRLF line endings instead of bare LF, so that's no good.
867 /* Mostly duplicated in apple2-main.c */
869 switch (state->escstate)
875 /* Dummy case - we don't want the screensaver to beep */
876 /* #### But maybe this should flash the screen? */
879 if (state->cursor_x > 0)
883 if (state->cursor_x < cols - 8)
885 state->cursor_x = (state->cursor_x & ~7) + 8;
890 if (state->cursor_y < rows - 1)
897 # ifndef HAVE_FORKPTY
898 state->cursor_x = 0; /* No ptys on iPhone; assume CRLF. */
902 if(state->last_c == 13)
904 cell->state = NORMAL;
905 cell->p_char = state->chars[state->bk];
906 cell->changed = True;
908 if (state->cursor_y < rows - 1)
915 cell = &state->cells[cols * state->cursor_y];
916 if((cell->p_char == NULL) || (cell->p_char->name == CURSOR_INDEX))
919 state->bk = cell->p_char->name;
923 /* Dummy case - there is one and only one font. */
927 /* Dummy case - these interrupt escape sequences, so
928 they don't do anything in this state */
934 /* Dummy case - this is supposed to be ignored */
938 for(i = 0; i < NPAR; i++)
939 state->csiparam[i] = 0;
944 PRINT: /* Come from states 102-106 */
945 c = process_unicrud (state, c);
949 /* If the cursor is in column 39 and we print a character, then
950 that character shows up in column 39, and the cursor is no
951 longer visible on the screen (it's in "column 40".) If
952 another character is printed, then that character shows up in
953 column 0, and the cursor moves to column 1.
955 This is empirically what xterm and gnome-terminal do, so that
956 must be the right thing. (In xterm, the cursor vanishes,
957 whereas; in gnome-terminal, the cursor overprints the
958 character in col 39.)
961 cell->p_char = state->chars[c];
962 cell->changed = True;
965 if (c != ' ' && cell->p_char->blank_p)
966 cell->p_char = state->chars[CURSOR_INDEX];
968 if (state->cursor_x >= cols - 1 /*####*/)
971 if (state->cursor_y >= rows - 1)
986 case 'c': /* Reset */
990 case 'D': /* Linefeed */
991 if (state->cursor_y < rows - 1)
997 case 'E': /* Newline */
1001 case 'M': /* Reverse newline */
1002 if (state->cursor_y > 0)
1004 state->escstate = 0;
1006 case '7': /* Save state */
1007 state->saved_x = state->cursor_x;
1008 state->saved_y = state->cursor_y;
1009 state->escstate = 0;
1011 case '8': /* Restore state */
1012 state->cursor_x = state->saved_x;
1013 state->cursor_y = state->saved_y;
1014 state->escstate = 0;
1017 state->escstate = 2;
1018 for(i = 0; i < NPAR; i++)
1019 state->csiparam[i] = 0;
1020 state->curparam = 0;
1022 case '%': /* Select charset */
1023 /* @: Select default (ISO 646 / ISO 8859-1)
1025 8: Select UTF-8 (obsolete)
1027 We can just ignore this and always process UTF-8, I think?
1028 We must still catch the last byte, though.
1032 /* I don't support different fonts either - see above
1034 state->escstate = 3;
1037 /* Escape sequences not supported:
1040 * Z - Terminal identification
1042 * = - Other keypad change
1045 state->escstate = 0;
1054 state->escstate = 0;
1056 case '0': case '1': case '2': case '3': case '4':
1057 case '5': case '6': case '7': case '8': case '9':
1058 if (state->curparam < NPAR)
1059 state->csiparam[state->curparam] = (state->csiparam[state->curparam] * 10) + (c - '0');
1062 state->csiparam[++state->curparam] = 0;
1065 state->escstate = 3;
1068 for (i = 0; i < state->csiparam[0]; i++)
1070 if(++state->cursor_x > cols)
1072 state->cursor_x = 0;
1073 if (state->cursor_y < rows - 1)
1078 cell = &state->cells[cols * state->cursor_y + state->cursor_x];
1079 if (cell->state == FLARE || cell->state == NORMAL)
1082 cell->changed = True;
1085 state->escstate = 0;
1088 state->cursor_x = 0;
1090 if (state->csiparam[0] == 0)
1091 state->csiparam[0] = 1;
1092 if ((state->cursor_y -= state->csiparam[0]) < 0)
1093 state->cursor_y = 0;
1094 state->escstate = 0;
1097 state->cursor_x = 0;
1100 if (state->csiparam[0] == 0)
1101 state->csiparam[0] = 1;
1102 if ((state->cursor_y += state->csiparam[0]) >= rows - 1 /*####*/)
1103 state->cursor_y = rows - 1;
1104 state->escstate = 0;
1108 if (state->csiparam[0] == 0)
1109 state->csiparam[0] = 1;
1110 if ((state->cursor_x += state->csiparam[0]) >= cols - 1 /*####*/)
1111 state->cursor_x = cols - 1;
1112 state->escstate = 0;
1115 if (state->csiparam[0] == 0)
1116 state->csiparam[0] = 1;
1117 if ((state->cursor_x -= state->csiparam[0]) < 0)
1118 state->cursor_x = 0;
1119 state->escstate = 0;
1122 if ((state->cursor_y = (state->csiparam[0] - 1)) >= rows - 1 /*####*/)
1123 state->cursor_y = rows - 1;
1124 state->escstate = 0;
1128 if ((state->cursor_x = (state->csiparam[0] - 1)) >= cols - 1 /*####*/)
1129 state->cursor_x = cols - 1;
1130 state->escstate = 0;
1134 if ((state->cursor_y = (state->csiparam[0] - 1)) >= rows - 1 /*####*/)
1135 state->cursor_y = rows - 1;
1136 if ((state->cursor_x = (state->csiparam[1] - 1)) >= cols - 1 /*####*/)
1137 state->cursor_x = cols - 1;
1138 if(state->cursor_y < 0)
1139 state->cursor_y = 0;
1140 if(state->cursor_x < 0)
1141 state->cursor_x = 0;
1142 state->escstate = 0;
1147 if (state->csiparam[0] == 0)
1148 start = cols * state->cursor_y + state->cursor_x;
1149 if (state->csiparam[0] == 1)
1150 end = cols * state->cursor_y + state->cursor_x;
1151 for (i = start; i < end; i++)
1153 cell = &state->cells[i];
1154 if (cell->state == FLARE || cell->state == NORMAL)
1157 cell->changed = True;
1160 set_cursor (state, True);
1161 state->escstate = 0;
1166 if (state->csiparam[0] == 0)
1167 start = state->cursor_x;
1168 if (state->csiparam[1] == 1)
1169 end = state->cursor_x;
1170 for (i = start; i < end; i++)
1172 if (cell->state == FLARE || cell->state == NORMAL)
1175 cell->changed = True;
1179 state->escstate = 0;
1181 case 'm': /* Set attributes unimplemented (bold, blink, rev) */
1182 state->escstate = 0;
1184 case 's': /* Save position */
1185 state->saved_x = state->cursor_x;
1186 state->saved_y = state->cursor_y;
1187 state->escstate = 0;
1189 case 'u': /* Restore position */
1190 state->cursor_x = state->saved_x;
1191 state->cursor_y = state->saved_y;
1192 state->escstate = 0;
1194 case '?': /* DEC Private modes */
1195 if ((state->curparam != 0) || (state->csiparam[0] != 0))
1196 state->escstate = 0;
1199 /* Known unsupported CSIs:
1201 * L - Insert blank lines
1202 * M - Delete lines (I don't know what this means...)
1203 * P - Delete characters
1204 * X - Erase characters (difference with P being...?)
1205 * c - Terminal identification
1206 * g - Clear tab stop(s)
1207 * h - Set mode (Mainly due to its complexity and lack of good
1210 * m - Set mode (Phosphor is, per defenition, green on black)
1212 * q - Set keyboard LEDs
1213 * r - Set scrolling region (too exhausting - noone uses this,
1216 state->escstate = 0;
1221 state->escstate = 0;
1224 case 102: /* states 102-106 are for UTF-8 decoding */
1234 set_cursor (state, True);
1237 #endif /* HAVE_FORKPTY */
1239 if (c == '\t') c = ' '; /* blah. */
1241 if (c == '\r' || c == '\n') /* handle CR, LF, or CRLF as "new line". */
1243 if (c == '\n' && state->last_c == '\r')
1244 ; /* CRLF -- do nothing */
1247 state->cursor_x = 0;
1248 if (state->cursor_y == rows - 1)
1254 else if (c == '\014')
1260 c = process_unicrud (state, c);
1263 cell->state = FLARE;
1264 cell->p_char = state->chars[c];
1265 cell->changed = True;
1268 if (c != ' ' && cell->p_char->blank_p)
1269 cell->p_char = state->chars[CURSOR_INDEX];
1271 if (state->cursor_x >= cols - 1)
1273 state->cursor_x = 0;
1274 if (state->cursor_y >= rows - 1)
1280 set_cursor (state, True);
1288 update_display (p_state *state, Bool changed_only)
1292 for (y = 0; y < state->grid_height; y++)
1293 for (x = 0; x < state->grid_width; x++)
1295 p_cell *cell = &state->cells[state->grid_width * y + x];
1296 int width, height, tx, ty;
1298 if (changed_only && !cell->changed)
1301 width = state->char_width * state->scale;
1302 height = state->char_height * state->scale;
1303 tx = x * width + state->xmargin;
1304 ty = y * height + state->ymargin;
1306 if (cell->state == BLANK || cell->p_char->blank_p)
1308 XFillRectangle (state->dpy, state->window, state->gcs[BLANK],
1309 tx, ty, width, height);
1314 GC gc1 = state->gcs[cell->state];
1315 GC gc2 = ((cell->state + 2) < state->ticks
1316 ? state->gcs[cell->state + 2]
1318 GC gc3 = (gc2 ? gc2 : gc1);
1320 XCopyPlane (state->dpy, cell->p_char->pixmap, state->window, gc3,
1321 0, 0, width, height, tx, ty, 1L);
1324 XSetClipMask (state->dpy, gc1, cell->p_char->pixmap2);
1325 XSetClipOrigin (state->dpy, gc1, tx, ty);
1326 XFillRectangle (state->dpy, state->window, gc1,
1327 tx, ty, width, height);
1328 XSetClipMask (state->dpy, gc1, None);
1330 #else /* !FUZZY_BORDER */
1332 XCopyPlane (state->dpy,
1333 cell->p_char->pixmap, state->window,
1334 state->gcs[cell->state],
1335 0, 0, width, height, tx, ty, 1L);
1337 #endif /* !FUZZY_BORDER */
1340 cell->changed = False;
1345 static unsigned long
1346 phosphor_draw (Display *dpy, Window window, void *closure)
1348 p_state *state = (p_state *) closure;
1350 update_display (state, True);
1353 c = textclient_getc (state->tc);
1355 print_char (state, c);
1357 return state->delay;
1362 phosphor_reshape (Display *dpy, Window window, void *closure,
1363 unsigned int w, unsigned int h)
1365 p_state *state = (p_state *) closure;
1366 Bool changed_p = resize_grid (state);
1368 if (! changed_p) return;
1370 textclient_reshape (state->tc,
1371 w - state->xmargin * 2,
1372 h - state->ymargin * 2,
1373 state->grid_width - 1,
1374 state->grid_height - 1,
1380 phosphor_event (Display *dpy, Window window, void *closure, XEvent *event)
1382 p_state *state = (p_state *) closure;
1384 if (event->xany.type == Expose)
1385 update_display (state, False);
1386 else if (event->xany.type == KeyPress)
1387 return textclient_putc (state->tc, &event->xkey);
1392 phosphor_free (Display *dpy, Window window, void *closure)
1394 p_state *state = (p_state *) closure;
1396 textclient_close (state->tc);
1397 if (state->cursor_timer)
1398 XtRemoveTimeOut (state->cursor_timer);
1400 /* #### there's more to free here */
1407 static const char *phosphor_defaults [] = {
1408 /* ".lowrez: true",*/
1409 ".background: Black",
1410 ".foreground: #00FF00",
1412 #if defined(BUILTIN_FONT)
1414 #elif defined(HAVE_COCOA)
1423 "*program: xscreensaver-text",
1425 "*metaSendsESC: True",
1429 #else /* !HAVE_FORKPTY */
1431 #endif /* !HAVE_FORKPTY */
1435 static XrmOptionDescRec phosphor_options [] = {
1436 { "-font", ".font", XrmoptionSepArg, 0 },
1437 { "-scale", ".scale", XrmoptionSepArg, 0 },
1438 { "-ticks", ".ticks", XrmoptionSepArg, 0 },
1439 { "-delay", ".delay", XrmoptionSepArg, 0 },
1440 { "-program", ".program", XrmoptionSepArg, 0 },
1441 { "-pipe", ".usePty", XrmoptionNoArg, "False" },
1442 { "-pty", ".usePty", XrmoptionNoArg, "True" },
1443 { "-meta", ".metaSendsESC", XrmoptionNoArg, "False" },
1444 { "-esc", ".metaSendsESC", XrmoptionNoArg, "True" },
1445 { "-bs", ".swapBSDEL", XrmoptionNoArg, "False" },
1446 { "-del", ".swapBSDEL", XrmoptionNoArg, "True" },
1451 XSCREENSAVER_MODULE ("Phosphor", phosphor)