1 /* xscreensaver, Copyright (c) 1999-2013 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);
332 /* Re-query the window size and update the internal character grid if changed.
335 resize_grid (p_state *state)
337 int ow = state->grid_width;
338 int oh = state->grid_height;
339 p_cell *ocells = state->cells;
342 XGetWindowAttributes (state->dpy, state->window, &state->xgwa);
344 state->grid_width = state->xgwa.width /(state->char_width * state->scale);
345 state->grid_height = state->xgwa.height /(state->char_height * state->scale);
347 if (ow == state->grid_width &&
348 oh == state->grid_height)
351 state->cells = (p_cell *) calloc (sizeof(p_cell),
352 state->grid_width * state->grid_height);
354 for (y = 0; y < state->grid_height; y++)
356 for (x = 0; x < state->grid_width; x++)
358 p_cell *ncell = &state->cells [state->grid_width * y + x];
359 if (x < ow && y < oh)
360 *ncell = ocells [ow * y + x];
361 ncell->changed = True;
365 if (state->cursor_x >= state->grid_width)
366 state->cursor_x = state->grid_width-1;
367 if (state->cursor_y >= state->grid_height)
368 state->cursor_y = state->grid_height-1;
376 capture_font_bits (p_state *state)
378 XFontStruct *font = state->font;
379 int safe_width, height;
380 unsigned char string[257];
389 safe_width = state->char_width + 1;
390 height = state->char_height;
391 p2 = XCreatePixmapFromBitmapData (state->dpy, state->window,
392 (char *) font6x10_bits,
398 # endif /* BUILTIN_FONT */
400 safe_width = font->max_bounds.rbearing - font->min_bounds.lbearing;
401 height = state->char_height;
404 p = XCreatePixmap (state->dpy, state->window,
405 (safe_width * 256), height, 1);
407 for (i = 0; i < 256; i++)
408 string[i] = (unsigned char) i;
411 state->gcv.foreground = 0;
412 state->gcv.background = 0;
413 state->gc0 = XCreateGC (state->dpy, p,
414 (GCForeground | GCBackground),
417 state->gcv.foreground = 1;
418 state->gc1 = XCreateGC (state->dpy, p,
419 ((font ? GCFont : 0) |
420 GCForeground | GCBackground |
421 GCCapStyle | GCLineWidth),
425 jwxyz_XSetAntiAliasing (state->dpy, state->gc0, False);
426 jwxyz_XSetAntiAliasing (state->dpy, state->gc1, False);
431 state->gcv.line_width = (int) (((long) state->scale) * 0.8);
432 if (state->gcv.line_width >= state->scale)
433 state->gcv.line_width = state->scale - 1;
434 if (state->gcv.line_width < 1)
435 state->gcv.line_width = 1;
436 state->gc2 = XCreateGC (state->dpy, p,
437 ((font ? GCFont : 0) |
438 GCForeground | GCBackground |
439 GCCapStyle | GCLineWidth),
442 #endif /* FUZZY_BORDER */
444 XFillRectangle (state->dpy, p, state->gc0, 0, 0, (safe_width * 256), height);
449 XCopyPlane (state->dpy, p2, p, state->gc1,
450 0, 0, font6x10_width, font6x10_height,
452 XFreePixmap (state->dpy, p2);
455 # endif /* BUILTIN_FONT */
457 for (i = 0; i < 256; i++)
459 if (string[i] < font->min_char_or_byte2 ||
460 string[i] > font->max_char_or_byte2)
462 XDrawString (state->dpy, p, state->gc1,
463 i * safe_width, font->ascent,
464 (char *) (string + i), 1);
468 /* Draw the cursor. */
469 XFillRectangle (state->dpy, p, state->gc1,
470 (CURSOR_INDEX * safe_width), 1,
473 ? font->per_char['n'-font->min_char_or_byte2].width
474 : font->max_bounds.width)
475 : state->char_width),
478 : state->char_height));
480 state->font_bits = XGetImage (state->dpy, p, 0, 0,
481 (safe_width * 256), height, ~0L, XYPixmap);
482 XFreePixmap (state->dpy, p);
484 for (i = 0; i < 256; i++)
485 state->chars[i] = make_character (state, i);
486 state->chars[CURSOR_INDEX] = make_character (state, CURSOR_INDEX);
491 make_character (p_state *state, int c)
493 p_char *pc = (p_char *) malloc (sizeof (*pc));
494 pc->name = (unsigned char) c;
495 pc->width = state->scale * state->char_width;
496 pc->height = state->scale * state->char_height;
497 char_to_pixmap (state, pc, c);
503 char_to_pixmap (p_state *state, p_char *pc, int c)
510 #endif /* FUZZY_BORDER */
514 XFontStruct *font = state->font;
515 int safe_width = (font
516 ? font->max_bounds.rbearing - font->min_bounds.lbearing
517 : state->char_width + 1);
519 int width = state->scale * state->char_width;
520 int height = state->scale * state->char_height;
522 if (font && (c < font->min_char_or_byte2 ||
523 c > font->max_char_or_byte2))
527 p = XCreatePixmap (state->dpy, state->window, width, height, 1);
528 XFillRectangle (state->dpy, p, state->gc0, 0, 0, width, height);
531 p2 = XCreatePixmap (state->dpy, state->window, width, height, 1);
532 XFillRectangle (state->dpy, p2, state->gc0, 0, 0, width, height);
533 #endif /* FUZZY_BORDER */
535 from = safe_width * c;
536 to = safe_width * (c + 1);
539 if (c > 75 && c < 150)
541 printf ("\n=========== %d (%c)\n", c, c);
542 for (y = 0; y < state->char_height; y++)
544 for (x1 = from; x1 < to; x1++)
545 printf (XGetPixel (state->font_bits, x1, y) ? "* " : ". ");
552 for (y = 0; y < state->char_height; y++)
553 for (x1 = from; x1 < to; x1++)
554 if (XGetPixel (state->font_bits, x1, y))
556 int xoff = state->scale / 2;
558 for (x2 = x1; x2 < to; x2++)
559 if (!XGetPixel (state->font_bits, x2, y))
562 XDrawLine (state->dpy, p, gc,
563 (x1 - from) * state->scale + xoff, y * state->scale,
564 (x2 - from) * state->scale + xoff, y * state->scale);
566 XDrawLine (state->dpy, p2, gc2,
567 (x1 - from) * state->scale + xoff, y * state->scale,
568 (x2 - from) * state->scale + xoff, y * state->scale);
569 #endif /* FUZZY_BORDER */
574 /* if (pc->blank_p && c == CURSOR_INDEX)
581 #endif /* FUZZY_BORDER */
585 /* Managing the display.
588 static void cursor_on_timer (XtPointer closure, XtIntervalId *id);
589 static void cursor_off_timer (XtPointer closure, XtIntervalId *id);
592 set_cursor_1 (p_state *state, Bool on)
594 p_cell *cell = &state->cells[state->grid_width * state->cursor_y
596 p_char *cursor = state->chars[CURSOR_INDEX];
597 int new_state = (on ? NORMAL : FADE);
599 if (cell->p_char != cursor)
600 cell->changed = True;
602 if (cell->state != new_state)
603 cell->changed = True;
605 cell->p_char = cursor;
606 cell->state = new_state;
607 return cell->changed;
611 set_cursor (p_state *state, Bool on)
613 if (set_cursor_1 (state, on))
615 if (state->cursor_timer)
616 XtRemoveTimeOut (state->cursor_timer);
617 state->cursor_timer = 0;
618 cursor_on_timer (state, 0);
624 cursor_off_timer (XtPointer closure, XtIntervalId *id)
626 p_state *state = (p_state *) closure;
627 XtAppContext app = XtDisplayToApplicationContext (state->dpy);
628 set_cursor_1 (state, False);
629 state->cursor_timer = XtAppAddTimeOut (app, state->cursor_blink,
630 cursor_on_timer, closure);
634 cursor_on_timer (XtPointer closure, XtIntervalId *id)
636 p_state *state = (p_state *) closure;
637 XtAppContext app = XtDisplayToApplicationContext (state->dpy);
638 set_cursor_1 (state, True);
639 state->cursor_timer = XtAppAddTimeOut (app, 2 * state->cursor_blink,
640 cursor_off_timer, closure);
645 clear (p_state *state)
650 for (y = 0; y < state->grid_height; y++)
651 for (x = 0; x < state->grid_width; x++)
653 p_cell *cell = &state->cells[state->grid_width * y + x];
654 if (cell->state == FLARE || cell->state == NORMAL)
657 cell->changed = True;
660 set_cursor (state, True);
665 decay (p_state *state)
668 for (y = 0; y < state->grid_height; y++)
669 for (x = 0; x < state->grid_width; x++)
671 p_cell *cell = &state->cells[state->grid_width * y + x];
672 if (cell->state == FLARE)
674 cell->state = NORMAL;
675 cell->changed = True;
677 else if (cell->state >= FADE)
680 if (cell->state >= state->ticks)
682 cell->changed = True;
689 scroll (p_state *state)
693 for (x = 0; x < state->grid_width; x++)
695 p_cell *from = 0, *to = 0;
696 for (y = 1; y < state->grid_height; y++)
698 from = &state->cells[state->grid_width * y + x];
699 to = &state->cells[state->grid_width * (y-1) + x];
701 if ((from->state == FLARE || from->state == NORMAL) &&
702 !from->p_char->blank_p)
705 to->state = NORMAL; /* should be FLARE? Looks bad... */
709 if (to->state == FLARE || to->state == NORMAL)
717 if (to && (to->state == FLARE || to->state == NORMAL))
723 set_cursor (state, True);
728 print_char (p_state *state, int c)
730 p_cell *cell = &state->cells[state->grid_width * state->cursor_y
733 /* Start the cursor fading (in case we don't end up overwriting it.) */
734 if (cell->state == FLARE || cell->state == NORMAL)
737 cell->changed = True;
741 if (state->pty_p) /* Only interpret VT100 sequences if running in pty-mode.
742 It would be nice if we could just interpret them all
743 the time, but that would require subprocesses to send
744 CRLF line endings instead of bare LF, so that's no good.
748 switch (state->escstate)
754 /* Dummy case - we don't want the screensaver to beep */
755 /* #### But maybe this should flash the screen? */
758 if (state->cursor_x > 0)
762 if (state->cursor_x < state->grid_width - 8)
764 state->cursor_x = (state->cursor_x & ~7) + 8;
769 if (state->cursor_y < state->grid_height - 1)
778 if(state->last_c == 13)
780 cell->state = NORMAL;
781 cell->p_char = state->chars[state->bk];
782 cell->changed = True;
784 if (state->cursor_y < state->grid_height - 1)
791 cell = &state->cells[state->grid_width * state->cursor_y];
792 if((cell->p_char == NULL) || (cell->p_char->name == CURSOR_INDEX))
795 state->bk = cell->p_char->name;
799 /* Dummy case - I don't want to load several fonts for
800 the maybe two programs world-wide that use that */
804 /* Dummy case - these interrupt escape sequences, so
805 they don't do anything in this state */
811 /* Dummy case - this is supposed to be ignored */
815 for(i = 0; i < NPAR; i++)
816 state->csiparam[i] = 0;
821 cell->p_char = state->chars[c];
822 cell->changed = True;
825 if (c != ' ' && cell->p_char->blank_p)
826 cell->p_char = state->chars[CURSOR_INDEX];
828 if (state->cursor_x >= state->grid_width - 1)
831 if (state->cursor_y >= state->grid_height - 1)
846 case 'c': /* Reset */
850 case 'D': /* Linefeed */
851 if (state->cursor_y < state->grid_height - 1)
857 case 'E': /* Newline */
861 case 'M': /* Reverse newline */
862 if (state->cursor_y > 0)
866 case '7': /* Save state */
867 state->saved_x = state->cursor_x;
868 state->saved_y = state->cursor_y;
871 case '8': /* Restore state */
872 state->cursor_x = state->saved_x;
873 state->cursor_y = state->saved_y;
878 for(i = 0; i < NPAR; i++)
879 state->csiparam[i] = 0;
882 case '%': /* Select charset */
883 /* No, I don't support UTF-8, since the phosphor font
884 isn't even Unicode anyway. We must still catch the
885 last byte, though. */
888 /* I don't support different fonts either - see above
893 /* Escape sequences not supported:
896 * Z - Terminal identification
898 * = - Other keypad change
912 case '0': case '1': case '2': case '3': case '4':
913 case '5': case '6': case '7': case '8': case '9':
914 if (state->curparam < NPAR)
915 state->csiparam[state->curparam] = (state->csiparam[state->curparam] * 10) + (c - '0');
918 state->csiparam[++state->curparam] = 0;
924 for (i = 0; i < state->csiparam[0]; i++)
926 if(++state->cursor_x > state->grid_width)
929 if (state->cursor_y < state->grid_height - 1)
934 cell = &state->cells[state->grid_width * state->cursor_y + state->cursor_x];
935 if (cell->state == FLARE || cell->state == NORMAL)
938 cell->changed = True;
946 if (state->csiparam[0] == 0)
947 state->csiparam[0] = 1;
948 if ((state->cursor_y -= state->csiparam[0]) < 0)
956 if (state->csiparam[0] == 0)
957 state->csiparam[0] = 1;
958 if ((state->cursor_y += state->csiparam[0]) >= state->grid_height - 1)
959 state->cursor_y = state->grid_height - 1;
964 if (state->csiparam[0] == 0)
965 state->csiparam[0] = 1;
966 if ((state->cursor_x += state->csiparam[0]) >= state->grid_width - 1)
967 state->cursor_x = state->grid_width - 1;
971 if (state->csiparam[0] == 0)
972 state->csiparam[0] = 1;
973 if ((state->cursor_x -= state->csiparam[0]) < 0)
978 if ((state->cursor_y = (state->csiparam[0] - 1)) >= state->grid_height - 1)
979 state->cursor_y = state->grid_height - 1;
984 if ((state->cursor_x = (state->csiparam[0] - 1)) >= state->grid_width - 1)
985 state->cursor_x = state->grid_width - 1;
990 if ((state->cursor_y = (state->csiparam[0] - 1)) >= state->grid_height - 1)
991 state->cursor_y = state->grid_height - 1;
992 if ((state->cursor_x = (state->csiparam[1] - 1)) >= state->grid_width - 1)
993 state->cursor_x = state->grid_width - 1;
994 if(state->cursor_y < 0)
996 if(state->cursor_x < 0)
1002 end = state->grid_height * state->grid_width;
1003 if (state->csiparam[0] == 0)
1004 start = state->grid_width * state->cursor_y + state->cursor_x;
1005 if (state->csiparam[0] == 1)
1006 end = state->grid_width * state->cursor_y + state->cursor_x;
1007 for (i = start; i < end; i++)
1009 cell = &state->cells[i];
1010 if (cell->state == FLARE || cell->state == NORMAL)
1013 cell->changed = True;
1016 set_cursor (state, True);
1017 state->escstate = 0;
1021 end = state->grid_width;
1022 if (state->csiparam[0] == 0)
1023 start = state->cursor_x;
1024 if (state->csiparam[1] == 1)
1025 end = state->cursor_x;
1026 for (i = start; i < end; i++)
1028 if (cell->state == FLARE || cell->state == NORMAL)
1031 cell->changed = True;
1035 state->escstate = 0;
1037 case 's': /* Save position */
1038 state->saved_x = state->cursor_x;
1039 state->saved_y = state->cursor_y;
1040 state->escstate = 0;
1042 case 'u': /* Restore position */
1043 state->cursor_x = state->saved_x;
1044 state->cursor_y = state->saved_y;
1045 state->escstate = 0;
1047 case '?': /* DEC Private modes */
1048 if ((state->curparam != 0) || (state->csiparam[0] != 0))
1049 state->escstate = 0;
1052 /* Known unsupported CSIs:
1054 * L - Insert blank lines
1055 * M - Delete lines (I don't know what this means...)
1056 * P - Delete characters
1057 * X - Erase characters (difference with P being...?)
1058 * c - Terminal identification
1059 * g - Clear tab stop(s)
1060 * h - Set mode (Mainly due to its complexity and lack of good
1063 * m - Set mode (Phosphor is, per defenition, green on black)
1065 * q - Set keyboard LEDs
1066 * r - Set scrolling region (too exhausting - noone uses this,
1069 state->escstate = 0;
1074 state->escstate = 0;
1077 set_cursor (state, True);
1080 #endif /* HAVE_FORKPTY */
1082 if (c == '\t') c = ' '; /* blah. */
1084 if (c == '\r' || c == '\n') /* handle CR, LF, or CRLF as "new line". */
1086 if (c == '\n' && state->last_c == '\r')
1087 ; /* CRLF -- do nothing */
1090 state->cursor_x = 0;
1091 if (state->cursor_y == state->grid_height - 1)
1097 else if (c == '\014')
1103 cell->state = FLARE;
1104 cell->p_char = state->chars[c];
1105 cell->changed = True;
1108 if (c != ' ' && cell->p_char->blank_p)
1109 cell->p_char = state->chars[CURSOR_INDEX];
1111 if (state->cursor_x >= state->grid_width - 1)
1113 state->cursor_x = 0;
1114 if (state->cursor_y >= state->grid_height - 1)
1120 set_cursor (state, True);
1128 update_display (p_state *state, Bool changed_only)
1132 for (y = 0; y < state->grid_height; y++)
1133 for (x = 0; x < state->grid_width; x++)
1135 p_cell *cell = &state->cells[state->grid_width * y + x];
1136 int width, height, tx, ty;
1138 if (changed_only && !cell->changed)
1141 width = state->char_width * state->scale;
1142 height = state->char_height * state->scale;
1146 if (cell->state == BLANK || cell->p_char->blank_p)
1148 XFillRectangle (state->dpy, state->window, state->gcs[BLANK],
1149 tx, ty, width, height);
1154 GC gc1 = state->gcs[cell->state];
1155 GC gc2 = ((cell->state + 2) < state->ticks
1156 ? state->gcs[cell->state + 2]
1158 GC gc3 = (gc2 ? gc2 : gc1);
1160 XCopyPlane (state->dpy, cell->p_char->pixmap, state->window, gc3,
1161 0, 0, width, height, tx, ty, 1L);
1164 XSetClipMask (state->dpy, gc1, cell->p_char->pixmap2);
1165 XSetClipOrigin (state->dpy, gc1, tx, ty);
1166 XFillRectangle (state->dpy, state->window, gc1,
1167 tx, ty, width, height);
1168 XSetClipMask (state->dpy, gc1, None);
1170 #else /* !FUZZY_BORDER */
1172 XCopyPlane (state->dpy,
1173 cell->p_char->pixmap, state->window,
1174 state->gcs[cell->state],
1175 0, 0, width, height, tx, ty, 1L);
1177 #endif /* !FUZZY_BORDER */
1180 cell->changed = False;
1185 static unsigned long
1186 phosphor_draw (Display *dpy, Window window, void *closure)
1188 p_state *state = (p_state *) closure;
1190 update_display (state, True);
1193 c = textclient_getc (state->tc);
1195 print_char (state, c);
1197 return state->delay;
1202 phosphor_reshape (Display *dpy, Window window, void *closure,
1203 unsigned int w, unsigned int h)
1205 p_state *state = (p_state *) closure;
1206 Bool changed_p = resize_grid (state);
1208 if (! changed_p) return;
1210 textclient_reshape (state->tc, w, h,
1211 state->grid_width - 1,
1212 state->grid_height - 1);
1217 phosphor_event (Display *dpy, Window window, void *closure, XEvent *event)
1219 p_state *state = (p_state *) closure;
1221 if (event->xany.type == Expose)
1222 update_display (state, False);
1223 else if (event->xany.type == KeyPress)
1224 return textclient_putc (state->tc, &event->xkey);
1229 phosphor_free (Display *dpy, Window window, void *closure)
1231 p_state *state = (p_state *) closure;
1233 textclient_close (state->tc);
1234 if (state->cursor_timer)
1235 XtRemoveTimeOut (state->cursor_timer);
1237 /* #### there's more to free here */
1244 static const char *phosphor_defaults [] = {
1245 ".background: Black",
1246 ".foreground: #00FF00",
1248 #if defined(BUILTIN_FONT)
1250 #elif defined(HAVE_COCOA)
1259 "*program: xscreensaver-text",
1261 "*metaSendsESC: True",
1265 #else /* !HAVE_FORKPTY */
1267 #endif /* !HAVE_FORKPTY */
1271 static XrmOptionDescRec phosphor_options [] = {
1272 { "-font", ".font", XrmoptionSepArg, 0 },
1273 { "-scale", ".scale", XrmoptionSepArg, 0 },
1274 { "-ticks", ".ticks", XrmoptionSepArg, 0 },
1275 { "-delay", ".delay", XrmoptionSepArg, 0 },
1276 { "-program", ".program", XrmoptionSepArg, 0 },
1277 { "-pipe", ".usePty", XrmoptionNoArg, "False" },
1278 { "-pty", ".usePty", XrmoptionNoArg, "True" },
1279 { "-meta", ".metaSendsESC", XrmoptionNoArg, "False" },
1280 { "-esc", ".metaSendsESC", XrmoptionNoArg, "True" },
1281 { "-bs", ".swapBSDEL", XrmoptionNoArg, "False" },
1282 { "-del", ".swapBSDEL", XrmoptionNoArg, "True" },
1287 XSCREENSAVER_MODULE ("Phosphor", phosphor)