1 /* xscreensaver, Copyright (c) 1999-2014 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"
29 #define MAX(a,b) ((a)>(b)?(a):(b))
30 #define MIN(a,b) ((a)<(b)?(a):(b))
36 #define STATE_MAX FADE
38 #define CURSOR_INDEX 128
45 # include "images/6x10font.xbm"
46 #endif /* BUILTIN_FONT */
54 #endif /* FUZZY_BORDER */
67 XWindowAttributes xgwa;
70 int grid_width, grid_height;
71 int char_width, char_height;
86 #endif /* FUZZY_BORDER */
90 int cursor_x, cursor_y;
91 XtIntervalId cursor_timer;
104 static void capture_font_bits (p_state *state);
105 static p_char *make_character (p_state *state, int c);
106 static void char_to_pixmap (p_state *state, p_char *pc, int c);
109 /* About font metrics:
111 "lbearing" is the distance from the leftmost pixel of a character to
112 the logical origin of that character. That is, it is the number of
113 pixels of the character which are to the left of its logical origin.
115 "rbearing" is the distance from the logical origin of a character to
116 the rightmost pixel of a character. That is, it is the number of
117 pixels of the character to the right of its logical origin.
119 "descent" is the distance from the bottommost pixel of a character to
120 the logical baseline. That is, it is the number of pixels of the
121 character which are below the baseline.
123 "ascent" is the distance from the logical baseline to the topmost pixel.
124 That is, it is the number of pixels of the character above the baseline.
126 Therefore, the bounding box of the "ink" of a character is
127 lbearing + rbearing by ascent + descent;
129 "width" is the distance from the logical origin of this character to
130 the position where the logical orgin of the next character should be
133 For our purposes, we're only interested in the part of the character
134 lying inside the "width" box. If the characters have ink outside of
135 that box (the "charcell" box) then we're going to lose it. Alas.
139 static void clear (p_state *);
140 static void set_cursor (p_state *, Bool on);
142 static unsigned short scale_color_channel (unsigned short ch1, unsigned short ch2)
144 return (ch1 * 100 + ch2 * 156) >> 8;
148 phosphor_init (Display *dpy, Window window)
152 p_state *state = (p_state *) calloc (sizeof(*state), 1);
153 char *fontname = get_string_resource (dpy, "font", "Font");
157 state->window = window;
159 XGetWindowAttributes (dpy, window, &state->xgwa);
160 /* XSelectInput (dpy, window, state->xgwa.your_event_mask | ExposureMask);*/
162 state->delay = get_integer_resource (dpy, "delay", "Integer");
163 state->pty_p = get_boolean_resource (dpy, "usePty", "UsePty");
165 if (!strcasecmp (fontname, "builtin") ||
166 !strcasecmp (fontname, "(builtin)"))
169 fprintf (stderr, "%s: no builtin font\n", progname);
170 state->font = XLoadQueryFont (dpy, "fixed");
171 #endif /* !BUILTIN_FONT */
175 state->font = XLoadQueryFont (dpy, fontname);
179 fprintf(stderr, "couldn't load font \"%s\"\n", fontname);
180 state->font = XLoadQueryFont (dpy, "fixed");
184 fprintf(stderr, "couldn't load font \"fixed\"");
190 state->scale = get_integer_resource (dpy, "scale", "Integer");
191 state->ticks = STATE_MAX + get_integer_resource (dpy, "ticks", "Integer");
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;
210 state->grid_width = state->xgwa.width / (state->char_width * state->scale);
211 state->grid_height = state->xgwa.height /(state->char_height * state->scale);
212 state->cells = (p_cell *) calloc (sizeof(p_cell),
213 state->grid_width * state->grid_height);
214 state->chars = (p_char **) calloc (sizeof(p_char *), 256);
216 state->gcs = (GC *) calloc (sizeof(GC), state->ticks + 1);
219 int ncolors = MAX (0, state->ticks - 3);
220 XColor *colors = (XColor *) calloc (ncolors, sizeof(XColor));
222 double s1, s2, v1, v2;
224 unsigned long fg = get_pixel_resource (state->dpy, state->xgwa.colormap,
225 "foreground", "Foreground");
226 unsigned long bg = get_pixel_resource (state->dpy, state->xgwa.colormap,
227 "background", "Background");
228 unsigned long flare = fg;
230 XColor fg_color, bg_color;
233 XQueryColor (state->dpy, state->xgwa.colormap, &fg_color);
236 XQueryColor (state->dpy, state->xgwa.colormap, &bg_color);
238 /* Now allocate a ramp of colors from the main color to the background. */
239 rgb_to_hsv (scale_color_channel(fg_color.red, bg_color.red),
240 scale_color_channel(fg_color.green, bg_color.green),
241 scale_color_channel(fg_color.blue, bg_color.blue),
243 rgb_to_hsv (bg_color.red, bg_color.green, bg_color.blue, &h2, &s2, &v2);
245 /* Avoid rainbow effects when fading to black/grey/white. */
251 make_color_ramp (state->xgwa.screen, state->xgwa.visual,
252 state->xgwa.colormap,
258 /* Adjust to the number of colors we actually got. */
259 state->ticks = ncolors + STATE_MAX;
261 /* If the foreground is brighter than the background, the flare is white.
262 * Otherwise, the flare is left at the foreground color (i.e. no flare). */
263 rgb_to_hsv (fg_color.red, fg_color.green, fg_color.blue, &h1, &s1, &v1);
267 /* WhitePixel is only for the default visual, which can be overridden
268 * on the command line. */
270 white.green = 0xffff;
272 if (XAllocColor(state->dpy, state->xgwa.colormap, &white))
276 /* Now, GCs all around.
278 state->gcv.font = (font ? font->fid : 0);
279 state->gcv.cap_style = CapRound;
281 state->gcv.line_width = (int) (((long) state->scale) * 1.3);
282 if (state->gcv.line_width == state->scale)
283 state->gcv.line_width++;
284 #else /* !FUZZY_BORDER */
285 state->gcv.line_width = (int) (((long) state->scale) * 0.9);
286 if (state->gcv.line_width >= state->scale)
287 state->gcv.line_width = state->scale - 1;
288 if (state->gcv.line_width < 1)
289 state->gcv.line_width = 1;
290 #endif /* !FUZZY_BORDER */
292 flags = (GCForeground | GCBackground | GCCapStyle | GCLineWidth);
294 state->gcv.background = bg;
295 state->gcv.foreground = bg;
296 state->gcs[BLANK] = XCreateGC (state->dpy, state->window, flags,
299 state->gcv.foreground = flare;
300 state->gcs[FLARE] = XCreateGC (state->dpy, state->window, flags,
303 state->gcv.foreground = fg;
304 state->gcs[NORMAL] = XCreateGC (state->dpy, state->window, flags,
307 for (i = 0; i < ncolors; i++)
309 state->gcv.foreground = colors[i].pixel;
310 state->gcs[STATE_MAX + i] = XCreateGC (state->dpy, state->window,
315 capture_font_bits (state);
317 set_cursor (state, True);
321 state->tc = textclient_open (dpy);
322 textclient_reshape (state->tc,
325 state->grid_width - 1,
326 state->grid_height - 1,
333 /* Re-query the window size and update the internal character grid if changed.
336 resize_grid (p_state *state)
338 int ow = state->grid_width;
339 int oh = state->grid_height;
340 p_cell *ocells = state->cells;
343 XGetWindowAttributes (state->dpy, state->window, &state->xgwa);
345 state->grid_width = state->xgwa.width /(state->char_width * state->scale);
346 state->grid_height = state->xgwa.height /(state->char_height * state->scale);
348 if (ow == state->grid_width &&
349 oh == state->grid_height)
352 state->cells = (p_cell *) calloc (sizeof(p_cell),
353 state->grid_width * state->grid_height);
355 for (y = 0; y < state->grid_height; y++)
357 for (x = 0; x < state->grid_width; x++)
359 p_cell *ncell = &state->cells [state->grid_width * y + x];
360 if (x < ow && y < oh)
361 *ncell = ocells [ow * y + x];
362 ncell->changed = True;
366 if (state->cursor_x >= state->grid_width)
367 state->cursor_x = state->grid_width-1;
368 if (state->cursor_y >= state->grid_height)
369 state->cursor_y = state->grid_height-1;
377 capture_font_bits (p_state *state)
379 XFontStruct *font = state->font;
380 int safe_width, height;
381 unsigned char string[257];
390 safe_width = state->char_width + 1;
391 height = state->char_height;
392 p2 = XCreatePixmapFromBitmapData (state->dpy, state->window,
393 (char *) font6x10_bits,
399 # endif /* BUILTIN_FONT */
401 safe_width = font->max_bounds.rbearing - font->min_bounds.lbearing;
402 height = state->char_height;
405 p = XCreatePixmap (state->dpy, state->window,
406 (safe_width * 256), height, 1);
408 for (i = 0; i < 256; i++)
409 string[i] = (unsigned char) i;
412 state->gcv.foreground = 0;
413 state->gcv.background = 0;
414 state->gc0 = XCreateGC (state->dpy, p,
415 (GCForeground | GCBackground),
418 state->gcv.foreground = 1;
419 state->gc1 = XCreateGC (state->dpy, p,
420 ((font ? GCFont : 0) |
421 GCForeground | GCBackground |
422 GCCapStyle | GCLineWidth),
426 jwxyz_XSetAntiAliasing (state->dpy, state->gc0, False);
427 jwxyz_XSetAntiAliasing (state->dpy, state->gc1, False);
432 state->gcv.line_width = (int) (((long) state->scale) * 0.8);
433 if (state->gcv.line_width >= state->scale)
434 state->gcv.line_width = state->scale - 1;
435 if (state->gcv.line_width < 1)
436 state->gcv.line_width = 1;
437 state->gc2 = XCreateGC (state->dpy, p,
438 ((font ? GCFont : 0) |
439 GCForeground | GCBackground |
440 GCCapStyle | GCLineWidth),
443 #endif /* FUZZY_BORDER */
445 XFillRectangle (state->dpy, p, state->gc0, 0, 0, (safe_width * 256), height);
450 XCopyPlane (state->dpy, p2, p, state->gc1,
451 0, 0, font6x10_width, font6x10_height,
453 XFreePixmap (state->dpy, p2);
456 # endif /* BUILTIN_FONT */
458 for (i = 0; i < 256; i++)
460 if (string[i] < font->min_char_or_byte2 ||
461 string[i] > font->max_char_or_byte2)
463 XDrawString (state->dpy, p, state->gc1,
464 i * safe_width, font->ascent,
465 (char *) (string + i), 1);
469 /* Draw the cursor. */
470 XFillRectangle (state->dpy, p, state->gc1,
471 (CURSOR_INDEX * safe_width), 1,
474 ? font->per_char['n'-font->min_char_or_byte2].width
475 : font->max_bounds.width)
476 : state->char_width),
479 : state->char_height));
481 state->font_bits = XGetImage (state->dpy, p, 0, 0,
482 (safe_width * 256), height, ~0L, XYPixmap);
483 XFreePixmap (state->dpy, p);
485 for (i = 0; i < 256; i++)
486 state->chars[i] = make_character (state, i);
487 state->chars[CURSOR_INDEX] = make_character (state, CURSOR_INDEX);
492 make_character (p_state *state, int c)
494 p_char *pc = (p_char *) malloc (sizeof (*pc));
495 pc->name = (unsigned char) c;
496 pc->width = state->scale * state->char_width;
497 pc->height = state->scale * state->char_height;
498 char_to_pixmap (state, pc, c);
504 char_to_pixmap (p_state *state, p_char *pc, int c)
511 #endif /* FUZZY_BORDER */
515 XFontStruct *font = state->font;
516 int safe_width = (font
517 ? font->max_bounds.rbearing - font->min_bounds.lbearing
518 : state->char_width + 1);
520 int width = state->scale * state->char_width;
521 int height = state->scale * state->char_height;
523 if (font && (c < font->min_char_or_byte2 ||
524 c > font->max_char_or_byte2))
528 p = XCreatePixmap (state->dpy, state->window, width, height, 1);
529 XFillRectangle (state->dpy, p, state->gc0, 0, 0, width, height);
532 p2 = XCreatePixmap (state->dpy, state->window, width, height, 1);
533 XFillRectangle (state->dpy, p2, state->gc0, 0, 0, width, height);
534 #endif /* FUZZY_BORDER */
536 from = safe_width * c;
537 to = safe_width * (c + 1);
540 if (c > 75 && c < 150)
542 printf ("\n=========== %d (%c)\n", c, c);
543 for (y = 0; y < state->char_height; y++)
545 for (x1 = from; x1 < to; x1++)
546 printf (XGetPixel (state->font_bits, x1, y) ? "* " : ". ");
553 for (y = 0; y < state->char_height; y++)
554 for (x1 = from; x1 < to; x1++)
555 if (XGetPixel (state->font_bits, x1, y))
557 int xoff = state->scale / 2;
559 for (x2 = x1; x2 < to; x2++)
560 if (!XGetPixel (state->font_bits, x2, y))
563 XDrawLine (state->dpy, p, gc,
564 (x1 - from) * state->scale + xoff, y * state->scale,
565 (x2 - from) * state->scale + xoff, y * state->scale);
567 XDrawLine (state->dpy, p2, gc2,
568 (x1 - from) * state->scale + xoff, y * state->scale,
569 (x2 - from) * state->scale + xoff, y * state->scale);
570 #endif /* FUZZY_BORDER */
575 /* if (pc->blank_p && c == CURSOR_INDEX)
582 #endif /* FUZZY_BORDER */
586 /* Managing the display.
589 static void cursor_on_timer (XtPointer closure, XtIntervalId *id);
590 static void cursor_off_timer (XtPointer closure, XtIntervalId *id);
593 set_cursor_1 (p_state *state, Bool on)
595 p_cell *cell = &state->cells[state->grid_width * state->cursor_y
597 p_char *cursor = state->chars[CURSOR_INDEX];
598 int new_state = (on ? NORMAL : FADE);
600 if (cell->p_char != cursor)
601 cell->changed = True;
603 if (cell->state != new_state)
604 cell->changed = True;
606 cell->p_char = cursor;
607 cell->state = new_state;
608 return cell->changed;
612 set_cursor (p_state *state, Bool on)
614 if (set_cursor_1 (state, on))
616 if (state->cursor_timer)
617 XtRemoveTimeOut (state->cursor_timer);
618 state->cursor_timer = 0;
619 cursor_on_timer (state, 0);
625 cursor_off_timer (XtPointer closure, XtIntervalId *id)
627 p_state *state = (p_state *) closure;
628 XtAppContext app = XtDisplayToApplicationContext (state->dpy);
629 set_cursor_1 (state, False);
630 state->cursor_timer = XtAppAddTimeOut (app, state->cursor_blink,
631 cursor_on_timer, closure);
635 cursor_on_timer (XtPointer closure, XtIntervalId *id)
637 p_state *state = (p_state *) closure;
638 XtAppContext app = XtDisplayToApplicationContext (state->dpy);
639 set_cursor_1 (state, True);
640 state->cursor_timer = XtAppAddTimeOut (app, 2 * state->cursor_blink,
641 cursor_off_timer, closure);
646 clear (p_state *state)
651 for (y = 0; y < state->grid_height; y++)
652 for (x = 0; x < state->grid_width; x++)
654 p_cell *cell = &state->cells[state->grid_width * y + x];
655 if (cell->state == FLARE || cell->state == NORMAL)
658 cell->changed = True;
661 set_cursor (state, True);
666 decay (p_state *state)
669 for (y = 0; y < state->grid_height; y++)
670 for (x = 0; x < state->grid_width; x++)
672 p_cell *cell = &state->cells[state->grid_width * y + x];
673 if (cell->state == FLARE)
675 cell->state = NORMAL;
676 cell->changed = True;
678 else if (cell->state >= FADE)
681 if (cell->state >= state->ticks)
683 cell->changed = True;
690 scroll (p_state *state)
694 for (x = 0; x < state->grid_width; x++)
696 p_cell *from = 0, *to = 0;
697 for (y = 1; y < state->grid_height; y++)
699 from = &state->cells[state->grid_width * y + x];
700 to = &state->cells[state->grid_width * (y-1) + x];
702 if ((from->state == FLARE || from->state == NORMAL) &&
703 !from->p_char->blank_p)
706 to->state = NORMAL; /* should be FLARE? Looks bad... */
710 if (to->state == FLARE || to->state == NORMAL)
718 if (to && (to->state == FLARE || to->state == NORMAL))
724 set_cursor (state, True);
729 print_char (p_state *state, int c)
731 p_cell *cell = &state->cells[state->grid_width * state->cursor_y
734 /* Start the cursor fading (in case we don't end up overwriting it.) */
735 if (cell->state == FLARE || cell->state == NORMAL)
738 cell->changed = True;
742 if (state->pty_p) /* Only interpret VT100 sequences if running in pty-mode.
743 It would be nice if we could just interpret them all
744 the time, but that would require subprocesses to send
745 CRLF line endings instead of bare LF, so that's no good.
749 switch (state->escstate)
755 /* Dummy case - we don't want the screensaver to beep */
756 /* #### But maybe this should flash the screen? */
759 if (state->cursor_x > 0)
763 if (state->cursor_x < state->grid_width - 8)
765 state->cursor_x = (state->cursor_x & ~7) + 8;
770 if (state->cursor_y < state->grid_height - 1)
779 if(state->last_c == 13)
781 cell->state = NORMAL;
782 cell->p_char = state->chars[state->bk];
783 cell->changed = True;
785 if (state->cursor_y < state->grid_height - 1)
792 cell = &state->cells[state->grid_width * state->cursor_y];
793 if((cell->p_char == NULL) || (cell->p_char->name == CURSOR_INDEX))
796 state->bk = cell->p_char->name;
800 /* Dummy case - I don't want to load several fonts for
801 the maybe two programs world-wide that use that */
805 /* Dummy case - these interrupt escape sequences, so
806 they don't do anything in this state */
812 /* Dummy case - this is supposed to be ignored */
816 for(i = 0; i < NPAR; i++)
817 state->csiparam[i] = 0;
822 cell->p_char = state->chars[c];
823 cell->changed = True;
826 if (c != ' ' && cell->p_char->blank_p)
827 cell->p_char = state->chars[CURSOR_INDEX];
829 if (state->cursor_x >= state->grid_width - 1)
832 if (state->cursor_y >= state->grid_height - 1)
847 case 'c': /* Reset */
851 case 'D': /* Linefeed */
852 if (state->cursor_y < state->grid_height - 1)
858 case 'E': /* Newline */
862 case 'M': /* Reverse newline */
863 if (state->cursor_y > 0)
867 case '7': /* Save state */
868 state->saved_x = state->cursor_x;
869 state->saved_y = state->cursor_y;
872 case '8': /* Restore state */
873 state->cursor_x = state->saved_x;
874 state->cursor_y = state->saved_y;
879 for(i = 0; i < NPAR; i++)
880 state->csiparam[i] = 0;
883 case '%': /* Select charset */
884 /* No, I don't support UTF-8, since the phosphor font
885 isn't even Unicode anyway. We must still catch the
886 last byte, though. */
889 /* I don't support different fonts either - see above
894 /* Escape sequences not supported:
897 * Z - Terminal identification
899 * = - Other keypad change
913 case '0': case '1': case '2': case '3': case '4':
914 case '5': case '6': case '7': case '8': case '9':
915 if (state->curparam < NPAR)
916 state->csiparam[state->curparam] = (state->csiparam[state->curparam] * 10) + (c - '0');
919 state->csiparam[++state->curparam] = 0;
925 for (i = 0; i < state->csiparam[0]; i++)
927 if(++state->cursor_x > state->grid_width)
930 if (state->cursor_y < state->grid_height - 1)
935 cell = &state->cells[state->grid_width * state->cursor_y + state->cursor_x];
936 if (cell->state == FLARE || cell->state == NORMAL)
939 cell->changed = True;
947 if (state->csiparam[0] == 0)
948 state->csiparam[0] = 1;
949 if ((state->cursor_y -= state->csiparam[0]) < 0)
957 if (state->csiparam[0] == 0)
958 state->csiparam[0] = 1;
959 if ((state->cursor_y += state->csiparam[0]) >= state->grid_height - 1)
960 state->cursor_y = state->grid_height - 1;
965 if (state->csiparam[0] == 0)
966 state->csiparam[0] = 1;
967 if ((state->cursor_x += state->csiparam[0]) >= state->grid_width - 1)
968 state->cursor_x = state->grid_width - 1;
972 if (state->csiparam[0] == 0)
973 state->csiparam[0] = 1;
974 if ((state->cursor_x -= state->csiparam[0]) < 0)
979 if ((state->cursor_y = (state->csiparam[0] - 1)) >= state->grid_height - 1)
980 state->cursor_y = state->grid_height - 1;
985 if ((state->cursor_x = (state->csiparam[0] - 1)) >= state->grid_width - 1)
986 state->cursor_x = state->grid_width - 1;
991 if ((state->cursor_y = (state->csiparam[0] - 1)) >= state->grid_height - 1)
992 state->cursor_y = state->grid_height - 1;
993 if ((state->cursor_x = (state->csiparam[1] - 1)) >= state->grid_width - 1)
994 state->cursor_x = state->grid_width - 1;
995 if(state->cursor_y < 0)
997 if(state->cursor_x < 0)
1003 end = state->grid_height * state->grid_width;
1004 if (state->csiparam[0] == 0)
1005 start = state->grid_width * state->cursor_y + state->cursor_x;
1006 if (state->csiparam[0] == 1)
1007 end = state->grid_width * state->cursor_y + state->cursor_x;
1008 for (i = start; i < end; i++)
1010 cell = &state->cells[i];
1011 if (cell->state == FLARE || cell->state == NORMAL)
1014 cell->changed = True;
1017 set_cursor (state, True);
1018 state->escstate = 0;
1022 end = state->grid_width;
1023 if (state->csiparam[0] == 0)
1024 start = state->cursor_x;
1025 if (state->csiparam[1] == 1)
1026 end = state->cursor_x;
1027 for (i = start; i < end; i++)
1029 if (cell->state == FLARE || cell->state == NORMAL)
1032 cell->changed = True;
1036 state->escstate = 0;
1038 case 's': /* Save position */
1039 state->saved_x = state->cursor_x;
1040 state->saved_y = state->cursor_y;
1041 state->escstate = 0;
1043 case 'u': /* Restore position */
1044 state->cursor_x = state->saved_x;
1045 state->cursor_y = state->saved_y;
1046 state->escstate = 0;
1048 case '?': /* DEC Private modes */
1049 if ((state->curparam != 0) || (state->csiparam[0] != 0))
1050 state->escstate = 0;
1053 /* Known unsupported CSIs:
1055 * L - Insert blank lines
1056 * M - Delete lines (I don't know what this means...)
1057 * P - Delete characters
1058 * X - Erase characters (difference with P being...?)
1059 * c - Terminal identification
1060 * g - Clear tab stop(s)
1061 * h - Set mode (Mainly due to its complexity and lack of good
1064 * m - Set mode (Phosphor is, per defenition, green on black)
1066 * q - Set keyboard LEDs
1067 * r - Set scrolling region (too exhausting - noone uses this,
1070 state->escstate = 0;
1075 state->escstate = 0;
1078 set_cursor (state, True);
1081 #endif /* HAVE_FORKPTY */
1083 if (c == '\t') c = ' '; /* blah. */
1085 if (c == '\r' || c == '\n') /* handle CR, LF, or CRLF as "new line". */
1087 if (c == '\n' && state->last_c == '\r')
1088 ; /* CRLF -- do nothing */
1091 state->cursor_x = 0;
1092 if (state->cursor_y == state->grid_height - 1)
1098 else if (c == '\014')
1104 cell->state = FLARE;
1105 cell->p_char = state->chars[c];
1106 cell->changed = True;
1109 if (c != ' ' && cell->p_char->blank_p)
1110 cell->p_char = state->chars[CURSOR_INDEX];
1112 if (state->cursor_x >= state->grid_width - 1)
1114 state->cursor_x = 0;
1115 if (state->cursor_y >= state->grid_height - 1)
1121 set_cursor (state, True);
1129 update_display (p_state *state, Bool changed_only)
1133 for (y = 0; y < state->grid_height; y++)
1134 for (x = 0; x < state->grid_width; x++)
1136 p_cell *cell = &state->cells[state->grid_width * y + x];
1137 int width, height, tx, ty;
1139 if (changed_only && !cell->changed)
1142 width = state->char_width * state->scale;
1143 height = state->char_height * state->scale;
1147 if (cell->state == BLANK || cell->p_char->blank_p)
1149 XFillRectangle (state->dpy, state->window, state->gcs[BLANK],
1150 tx, ty, width, height);
1155 GC gc1 = state->gcs[cell->state];
1156 GC gc2 = ((cell->state + 2) < state->ticks
1157 ? state->gcs[cell->state + 2]
1159 GC gc3 = (gc2 ? gc2 : gc1);
1161 XCopyPlane (state->dpy, cell->p_char->pixmap, state->window, gc3,
1162 0, 0, width, height, tx, ty, 1L);
1165 XSetClipMask (state->dpy, gc1, cell->p_char->pixmap2);
1166 XSetClipOrigin (state->dpy, gc1, tx, ty);
1167 XFillRectangle (state->dpy, state->window, gc1,
1168 tx, ty, width, height);
1169 XSetClipMask (state->dpy, gc1, None);
1171 #else /* !FUZZY_BORDER */
1173 XCopyPlane (state->dpy,
1174 cell->p_char->pixmap, state->window,
1175 state->gcs[cell->state],
1176 0, 0, width, height, tx, ty, 1L);
1178 #endif /* !FUZZY_BORDER */
1181 cell->changed = False;
1186 static unsigned long
1187 phosphor_draw (Display *dpy, Window window, void *closure)
1189 p_state *state = (p_state *) closure;
1191 update_display (state, True);
1194 c = textclient_getc (state->tc);
1196 print_char (state, c);
1198 return state->delay;
1203 phosphor_reshape (Display *dpy, Window window, void *closure,
1204 unsigned int w, unsigned int h)
1206 p_state *state = (p_state *) closure;
1207 Bool changed_p = resize_grid (state);
1209 if (! changed_p) return;
1211 textclient_reshape (state->tc, w, h,
1212 state->grid_width - 1,
1213 state->grid_height - 1,
1219 phosphor_event (Display *dpy, Window window, void *closure, XEvent *event)
1221 p_state *state = (p_state *) closure;
1223 if (event->xany.type == Expose)
1224 update_display (state, False);
1225 else if (event->xany.type == KeyPress)
1226 return textclient_putc (state->tc, &event->xkey);
1231 phosphor_free (Display *dpy, Window window, void *closure)
1233 p_state *state = (p_state *) closure;
1235 textclient_close (state->tc);
1236 if (state->cursor_timer)
1237 XtRemoveTimeOut (state->cursor_timer);
1239 /* #### there's more to free here */
1246 static const char *phosphor_defaults [] = {
1247 ".background: Black",
1248 ".foreground: #00FF00",
1250 #if defined(BUILTIN_FONT)
1252 #elif defined(HAVE_COCOA)
1261 "*program: xscreensaver-text",
1263 "*metaSendsESC: True",
1267 #else /* !HAVE_FORKPTY */
1269 #endif /* !HAVE_FORKPTY */
1273 static XrmOptionDescRec phosphor_options [] = {
1274 { "-font", ".font", XrmoptionSepArg, 0 },
1275 { "-scale", ".scale", XrmoptionSepArg, 0 },
1276 { "-ticks", ".ticks", XrmoptionSepArg, 0 },
1277 { "-delay", ".delay", XrmoptionSepArg, 0 },
1278 { "-program", ".program", XrmoptionSepArg, 0 },
1279 { "-pipe", ".usePty", XrmoptionNoArg, "False" },
1280 { "-pty", ".usePty", XrmoptionNoArg, "True" },
1281 { "-meta", ".metaSendsESC", XrmoptionNoArg, "False" },
1282 { "-esc", ".metaSendsESC", XrmoptionNoArg, "True" },
1283 { "-bs", ".swapBSDEL", XrmoptionNoArg, "False" },
1284 { "-del", ".swapBSDEL", XrmoptionNoArg, "True" },
1289 XSCREENSAVER_MODULE ("Phosphor", phosphor)