1 /* xscreensaver, Copyright (c) 1999-2009 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 */
25 # define XK_MISCELLANY
26 # include <X11/keysymdef.h>
27 # include <X11/Xatom.h>
28 # include <X11/Intrinsic.h>
33 # include <fcntl.h> /* for O_RDWR */
37 # include <sys/ioctl.h>
44 #endif /* HAVE_FORKPTY */
46 #include "screenhack.h"
50 #define MAX(a,b) ((a)>(b)?(a):(b))
51 #define MIN(a,b) ((a)<(b)?(a):(b))
57 #define STATE_MAX FADE
59 #define CURSOR_INDEX 128
66 # include "images/6x10font.xbm"
67 #endif /* BUILTIN_FONT */
75 #endif /* FUZZY_BORDER */
88 XWindowAttributes xgwa;
90 int grid_width, grid_height;
91 int char_width, char_height;
107 #endif /* FUZZY_BORDER */
111 int cursor_x, cursor_y;
112 XtIntervalId cursor_timer;
113 XtIntervalId pipe_timer;
118 Bool input_available_p;
119 Time subproc_relaunch_delay;
120 XComposeStatus compose;
121 Bool meta_sends_esc_p;
129 unsigned int meta_mask;
134 static void capture_font_bits (p_state *state);
135 static p_char *make_character (p_state *state, int c);
136 static void drain_input (p_state *state);
137 static void char_to_pixmap (p_state *state, p_char *pc, int c);
138 static void launch_text_generator (p_state *state);
141 /* About font metrics:
143 "lbearing" is the distance from the leftmost pixel of a character to
144 the logical origin of that character. That is, it is the number of
145 pixels of the character which are to the left of its logical origin.
147 "rbearing" is the distance from the logical origin of a character to
148 the rightmost pixel of a character. That is, it is the number of
149 pixels of the character to the right of its logical origin.
151 "descent" is the distance from the bottommost pixel of a character to
152 the logical baseline. That is, it is the number of pixels of the
153 character which are below the baseline.
155 "ascent" is the distance from the logical baseline to the topmost pixel.
156 That is, it is the number of pixels of the character above the baseline.
158 Therefore, the bounding box of the "ink" of a character is
159 lbearing + rbearing by ascent + descent;
161 "width" is the distance from the logical origin of this character to
162 the position where the logical orgin of the next character should be
165 For our purposes, we're only interested in the part of the character
166 lying inside the "width" box. If the characters have ink outside of
167 that box (the "charcell" box) then we're going to lose it. Alas.
171 static void clear (p_state *);
172 static void set_cursor (p_state *, Bool on);
175 phosphor_init (Display *dpy, Window window)
179 p_state *state = (p_state *) calloc (sizeof(*state), 1);
180 char *fontname = get_string_resource (dpy, "font", "Font");
184 state->window = window;
186 XGetWindowAttributes (dpy, window, &state->xgwa);
187 /* XSelectInput (dpy, window, state->xgwa.your_event_mask | ExposureMask);*/
189 state->delay = get_integer_resource (dpy, "delay", "Integer");
190 state->meta_sends_esc_p = get_boolean_resource (dpy, "metaSendsESC", "Boolean");
191 state->swap_bs_del_p = get_boolean_resource (dpy, "swapBSDEL", "Boolean");
193 if (!strcasecmp (fontname, "builtin") ||
194 !strcasecmp (fontname, "(builtin)"))
197 fprintf (stderr, "%s: no builtin font\n", progname);
198 state->font = XLoadQueryFont (dpy, "fixed");
199 #endif /* !BUILTIN_FONT */
203 state->font = XLoadQueryFont (dpy, fontname);
207 fprintf(stderr, "couldn't load font \"%s\"\n", fontname);
208 state->font = XLoadQueryFont (dpy, "fixed");
212 fprintf(stderr, "couldn't load font \"fixed\"");
218 state->scale = get_integer_resource (dpy, "scale", "Integer");
219 state->ticks = STATE_MAX + get_integer_resource (dpy, "ticks", "Integer");
223 char *s = get_string_resource (dpy, "mode", "Integer");
225 if (!s || !*s || !strcasecmp (s, "pipe"))
227 else if (!strcasecmp (s, "pty"))
230 fprintf (stderr, "%s: mode must be either `pipe' or `pty', not `%s'\n",
234 fprintf (stderr, "%s: no pty support on this system; using -pipe mode.\n",
237 #endif /* HAVE_FORKPTY */
241 for (i = 0; i < font->n_properties; i++)
242 if (font->properties[i].name == XA_FONT)
243 printf ("font: %s\n", XGetAtomName(dpy, font->properties[i].card32));
246 state->cursor_blink = get_integer_resource (dpy, "cursor", "Time");
247 state->subproc_relaunch_delay =
248 (1000 * get_integer_resource (dpy, "relaunch", "Time"));
253 state->char_width = (font6x10_width / 256) - 1;
254 state->char_height = font6x10_height;
257 # endif /* BUILTIN_FONT */
259 state->char_width = font->max_bounds.width;
260 state->char_height = font->max_bounds.ascent + font->max_bounds.descent;
263 state->grid_width = state->xgwa.width / (state->char_width * state->scale);
264 state->grid_height = state->xgwa.height /(state->char_height * state->scale);
265 state->cells = (p_cell *) calloc (sizeof(p_cell),
266 state->grid_width * state->grid_height);
267 state->chars = (p_char **) calloc (sizeof(p_char *), 256);
269 state->gcs = (GC *) calloc (sizeof(GC), state->ticks + 1);
272 int ncolors = MAX (0, state->ticks - 3);
273 XColor *colors = (XColor *) calloc (ncolors, sizeof(XColor));
275 double s1, s2, v1, v2;
277 unsigned long fg = get_pixel_resource (state->dpy, state->xgwa.colormap,
278 "foreground", "Foreground");
279 unsigned long bg = get_pixel_resource (state->dpy, state->xgwa.colormap,
280 "background", "Background");
281 unsigned long flare = get_pixel_resource (state->dpy,state->xgwa.colormap,
282 "flareForeground", "Foreground");
283 unsigned long fade = get_pixel_resource (state->dpy,state->xgwa.colormap,
284 "fadeForeground", "Foreground");
289 XQueryColor (state->dpy, state->xgwa.colormap, &start);
292 XQueryColor (state->dpy, state->xgwa.colormap, &end);
294 /* Now allocate a ramp of colors from the main color to the background. */
295 rgb_to_hsv (start.red, start.green, start.blue, &h1, &s1, &v1);
296 rgb_to_hsv (end.red, end.green, end.blue, &h2, &s2, &v2);
297 make_color_ramp (state->dpy, state->xgwa.colormap,
303 /* Adjust to the number of colors we actually got. */
304 state->ticks = ncolors + STATE_MAX;
306 /* Now, GCs all around.
308 state->gcv.font = (font ? font->fid : 0);
309 state->gcv.cap_style = CapRound;
311 state->gcv.line_width = (int) (((long) state->scale) * 1.3);
312 if (state->gcv.line_width == state->scale)
313 state->gcv.line_width++;
314 #else /* !FUZZY_BORDER */
315 state->gcv.line_width = (int) (((long) state->scale) * 0.9);
316 if (state->gcv.line_width >= state->scale)
317 state->gcv.line_width = state->scale - 1;
318 if (state->gcv.line_width < 1)
319 state->gcv.line_width = 1;
320 #endif /* !FUZZY_BORDER */
322 flags = (GCForeground | GCBackground | GCCapStyle | GCLineWidth);
324 state->gcv.background = bg;
325 state->gcv.foreground = bg;
326 state->gcs[BLANK] = XCreateGC (state->dpy, state->window, flags,
329 state->gcv.foreground = flare;
330 state->gcs[FLARE] = XCreateGC (state->dpy, state->window, flags,
333 state->gcv.foreground = fg;
334 state->gcs[NORMAL] = XCreateGC (state->dpy, state->window, flags,
337 for (i = 0; i < ncolors; i++)
339 state->gcv.foreground = colors[i].pixel;
340 state->gcs[STATE_MAX + i] = XCreateGC (state->dpy, state->window,
345 capture_font_bits (state);
347 set_cursor (state, True);
349 launch_text_generator (state);
356 /* Re-query the window size and update the internal character grid if changed.
359 resize_grid (p_state *state)
361 int ow = state->grid_width;
362 int oh = state->grid_height;
363 p_cell *ocells = state->cells;
366 XGetWindowAttributes (state->dpy, state->window, &state->xgwa);
368 state->grid_width = state->xgwa.width /(state->char_width * state->scale);
369 state->grid_height = state->xgwa.height /(state->char_height * state->scale);
371 if (ow == state->grid_width &&
372 oh == state->grid_height)
375 state->cells = (p_cell *) calloc (sizeof(p_cell),
376 state->grid_width * state->grid_height);
378 for (y = 0; y < state->grid_height; y++)
380 for (x = 0; x < state->grid_width; x++)
382 p_cell *ncell = &state->cells [state->grid_width * y + x];
383 if (x < ow && y < oh)
384 *ncell = ocells [ow * y + x];
385 ncell->changed = True;
389 if (state->cursor_x >= state->grid_width)
390 state->cursor_x = state->grid_width-1;
391 if (state->cursor_y >= state->grid_height)
392 state->cursor_y = state->grid_height-1;
399 capture_font_bits (p_state *state)
401 XFontStruct *font = state->font;
402 int safe_width, height;
403 unsigned char string[257];
412 safe_width = state->char_width + 1;
413 height = state->char_height;
414 p2 = XCreatePixmapFromBitmapData (state->dpy, state->window,
415 (char *) font6x10_bits,
421 # endif /* BUILTIN_FONT */
423 safe_width = font->max_bounds.rbearing - font->min_bounds.lbearing;
424 height = state->char_height;
427 p = XCreatePixmap (state->dpy, state->window,
428 (safe_width * 256), height, 1);
430 for (i = 0; i < 256; i++)
431 string[i] = (unsigned char) i;
434 state->gcv.foreground = 0;
435 state->gcv.background = 0;
436 state->gc0 = XCreateGC (state->dpy, p,
437 (GCForeground | GCBackground),
440 state->gcv.foreground = 1;
441 state->gc1 = XCreateGC (state->dpy, p,
442 ((font ? GCFont : 0) |
443 GCForeground | GCBackground |
444 GCCapStyle | GCLineWidth),
448 jwxyz_XSetAntiAliasing (state->dpy, state->gc0, False);
449 jwxyz_XSetAntiAliasing (state->dpy, state->gc1, False);
454 state->gcv.line_width = (int) (((long) state->scale) * 0.8);
455 if (state->gcv.line_width >= state->scale)
456 state->gcv.line_width = state->scale - 1;
457 if (state->gcv.line_width < 1)
458 state->gcv.line_width = 1;
459 state->gc2 = XCreateGC (state->dpy, p,
460 ((font ? GCFont : 0) |
461 GCForeground | GCBackground |
462 GCCapStyle | GCLineWidth),
465 #endif /* FUZZY_BORDER */
467 XFillRectangle (state->dpy, p, state->gc0, 0, 0, (safe_width * 256), height);
472 XCopyPlane (state->dpy, p2, p, state->gc1,
473 0, 0, font6x10_width, font6x10_height,
475 XFreePixmap (state->dpy, p2);
478 # endif /* BUILTIN_FONT */
480 for (i = 0; i < 256; i++)
482 if (string[i] < font->min_char_or_byte2 ||
483 string[i] > font->max_char_or_byte2)
485 XDrawString (state->dpy, p, state->gc1,
486 i * safe_width, font->ascent,
487 (char *) (string + i), 1);
491 /* Draw the cursor. */
492 XFillRectangle (state->dpy, p, state->gc1,
493 (CURSOR_INDEX * safe_width), 1,
496 ? font->per_char['n'-font->min_char_or_byte2].width
497 : font->max_bounds.width)
498 : state->char_width),
501 : state->char_height));
503 state->font_bits = XGetImage (state->dpy, p, 0, 0,
504 (safe_width * 256), height, ~0L, XYPixmap);
505 XFreePixmap (state->dpy, p);
507 for (i = 0; i < 256; i++)
508 state->chars[i] = make_character (state, i);
509 state->chars[CURSOR_INDEX] = make_character (state, CURSOR_INDEX);
514 make_character (p_state *state, int c)
516 p_char *pc = (p_char *) malloc (sizeof (*pc));
517 pc->name = (unsigned char) c;
518 pc->width = state->scale * state->char_width;
519 pc->height = state->scale * state->char_height;
520 char_to_pixmap (state, pc, c);
526 char_to_pixmap (p_state *state, p_char *pc, int c)
533 #endif /* FUZZY_BORDER */
537 XFontStruct *font = state->font;
538 int safe_width = (font
539 ? font->max_bounds.rbearing - font->min_bounds.lbearing
540 : state->char_width + 1);
542 int width = state->scale * state->char_width;
543 int height = state->scale * state->char_height;
545 if (font && (c < font->min_char_or_byte2 ||
546 c > font->max_char_or_byte2))
550 p = XCreatePixmap (state->dpy, state->window, width, height, 1);
551 XFillRectangle (state->dpy, p, state->gc0, 0, 0, width, height);
554 p2 = XCreatePixmap (state->dpy, state->window, width, height, 1);
555 XFillRectangle (state->dpy, p2, state->gc0, 0, 0, width, height);
556 #endif /* FUZZY_BORDER */
558 from = safe_width * c;
559 to = safe_width * (c + 1);
562 if (c > 75 && c < 150)
564 printf ("\n=========== %d (%c)\n", c, c);
565 for (y = 0; y < state->char_height; y++)
567 for (x1 = from; x1 < to; x1++)
568 printf (XGetPixel (state->font_bits, x1, y) ? "* " : ". ");
575 for (y = 0; y < state->char_height; y++)
576 for (x1 = from; x1 < to; x1++)
577 if (XGetPixel (state->font_bits, x1, y))
579 int xoff = state->scale / 2;
581 for (x2 = x1; x2 < to; x2++)
582 if (!XGetPixel (state->font_bits, x2, y))
585 XDrawLine (state->dpy, p, gc,
586 (x1 - from) * state->scale + xoff, y * state->scale,
587 (x2 - from) * state->scale + xoff, y * state->scale);
589 XDrawLine (state->dpy, p2, gc2,
590 (x1 - from) * state->scale + xoff, y * state->scale,
591 (x2 - from) * state->scale + xoff, y * state->scale);
592 #endif /* FUZZY_BORDER */
597 /* if (pc->blank_p && c == CURSOR_INDEX)
604 #endif /* FUZZY_BORDER */
608 /* Managing the display.
611 static void cursor_on_timer (XtPointer closure, XtIntervalId *id);
612 static void cursor_off_timer (XtPointer closure, XtIntervalId *id);
615 set_cursor_1 (p_state *state, Bool on)
617 p_cell *cell = &state->cells[state->grid_width * state->cursor_y
619 p_char *cursor = state->chars[CURSOR_INDEX];
620 int new_state = (on ? NORMAL : FADE);
622 if (cell->p_char != cursor)
623 cell->changed = True;
625 if (cell->state != new_state)
626 cell->changed = True;
628 cell->p_char = cursor;
629 cell->state = new_state;
630 return cell->changed;
634 set_cursor (p_state *state, Bool on)
636 if (set_cursor_1 (state, on))
638 if (state->cursor_timer)
639 XtRemoveTimeOut (state->cursor_timer);
640 state->cursor_timer = 0;
641 cursor_on_timer (state, 0);
649 cursor_off_timer (XtPointer closure, XtIntervalId *id)
651 p_state *state = (p_state *) closure;
652 XtAppContext app = XtDisplayToApplicationContext (state->dpy);
653 set_cursor_1 (state, False);
654 state->cursor_timer = XtAppAddTimeOut (app, state->cursor_blink,
655 cursor_on_timer, closure);
659 cursor_on_timer (XtPointer closure, XtIntervalId *id)
661 p_state *state = (p_state *) closure;
662 XtAppContext app = XtDisplayToApplicationContext (state->dpy);
663 set_cursor_1 (state, True);
664 state->cursor_timer = XtAppAddTimeOut (app, 2 * state->cursor_blink,
665 cursor_off_timer, closure);
670 clear (p_state *state)
675 for (y = 0; y < state->grid_height; y++)
676 for (x = 0; x < state->grid_width; x++)
678 p_cell *cell = &state->cells[state->grid_width * y + x];
679 if (cell->state == FLARE || cell->state == NORMAL)
682 cell->changed = True;
685 set_cursor (state, True);
690 decay (p_state *state)
693 for (y = 0; y < state->grid_height; y++)
694 for (x = 0; x < state->grid_width; x++)
696 p_cell *cell = &state->cells[state->grid_width * y + x];
697 if (cell->state == FLARE)
699 cell->state = NORMAL;
700 cell->changed = True;
702 else if (cell->state >= FADE)
705 if (cell->state >= state->ticks)
707 cell->changed = True;
714 scroll (p_state *state)
718 for (x = 0; x < state->grid_width; x++)
720 p_cell *from = 0, *to = 0;
721 for (y = 1; y < state->grid_height; y++)
723 from = &state->cells[state->grid_width * y + x];
724 to = &state->cells[state->grid_width * (y-1) + x];
726 if ((from->state == FLARE || from->state == NORMAL) &&
727 !from->p_char->blank_p)
730 to->state = NORMAL; /* should be FLARE? Looks bad... */
734 if (to->state == FLARE || to->state == NORMAL)
742 if (to && (to->state == FLARE || to->state == NORMAL))
748 set_cursor (state, True);
753 print_char (p_state *state, int c)
755 p_cell *cell = &state->cells[state->grid_width * state->cursor_y
759 /* Start the cursor fading (in case we don't end up overwriting it.) */
760 if (cell->state == FLARE || cell->state == NORMAL)
763 cell->changed = True;
766 if (state->pid) /* Only interpret VT100 sequences if running in pty-mode.
767 It would be nice if we could just interpret them all
768 the time, but that would require subprocesses to send
769 CRLF line endings instead of bare LF, so that's no good.
772 switch (state->escstate)
778 /* Dummy case - we don't want the screensaver to beep */
779 /* #### But maybe this should flash the screen? */
782 if (state->cursor_x > 0)
786 if (state->cursor_x < state->grid_width - 8)
788 state->cursor_x = (state->cursor_x & ~7) + 8;
793 if (state->cursor_y < state->grid_height - 1)
802 if(state->last_c == 13)
804 cell->state = NORMAL;
805 cell->p_char = state->chars[state->bk];
806 cell->changed = True;
808 if (state->cursor_y < state->grid_height - 1)
815 cell = &state->cells[state->grid_width * state->cursor_y];
816 if((cell->p_char == NULL) || (cell->p_char->name == CURSOR_INDEX))
819 state->bk = cell->p_char->name;
823 /* Dummy case - I don't want to load several fonts for
824 the maybe two programs world-wide that use that */
828 /* Dummy case - these interrupt escape sequences, so
829 they don't do anything in this state */
835 /* Dummy case - this is supposed to be ignored */
839 for(i = 0; i < NPAR; i++)
840 state->csiparam[i] = 0;
845 cell->p_char = state->chars[c];
846 cell->changed = True;
849 if (c != ' ' && cell->p_char->blank_p)
850 cell->p_char = state->chars[CURSOR_INDEX];
852 if (state->cursor_x >= state->grid_width - 1)
855 if (state->cursor_y >= state->grid_height - 1)
870 case 'c': /* Reset */
874 case 'D': /* Linefeed */
875 if (state->cursor_y < state->grid_height - 1)
881 case 'E': /* Newline */
885 case 'M': /* Reverse newline */
886 if (state->cursor_y > 0)
890 case '7': /* Save state */
891 state->saved_x = state->cursor_x;
892 state->saved_y = state->cursor_y;
895 case '8': /* Restore state */
896 state->cursor_x = state->saved_x;
897 state->cursor_y = state->saved_y;
902 for(i = 0; i < NPAR; i++)
903 state->csiparam[i] = 0;
906 case '%': /* Select charset */
907 /* No, I don't support UTF-8, since the phosphor font
908 isn't even Unicode anyway. We must still catch the
909 last byte, though. */
912 /* I don't support different fonts either - see above
917 /* Escape sequences not supported:
920 * Z - Terminal identification
922 * = - Other keypad change
936 case '0': case '1': case '2': case '3': case '4':
937 case '5': case '6': case '7': case '8': case '9':
938 if (state->curparam < NPAR)
939 state->csiparam[state->curparam] = (state->csiparam[state->curparam] * 10) + (c - '0');
942 state->csiparam[++state->curparam] = 0;
948 for (i = 0; i < state->csiparam[0]; i++)
950 if(++state->cursor_x > state->grid_width)
953 if (state->cursor_y < state->grid_height - 1)
958 cell = &state->cells[state->grid_width * state->cursor_y + state->cursor_x];
959 if (cell->state == FLARE || cell->state == NORMAL)
962 cell->changed = True;
970 if (state->csiparam[0] == 0)
971 state->csiparam[0] = 1;
972 if ((state->cursor_y -= state->csiparam[0]) < 0)
980 if (state->csiparam[0] == 0)
981 state->csiparam[0] = 1;
982 if ((state->cursor_y += state->csiparam[0]) >= state->grid_height - 1)
983 state->cursor_y = state->grid_height - 1;
988 if (state->csiparam[0] == 0)
989 state->csiparam[0] = 1;
990 if ((state->cursor_x += state->csiparam[0]) >= state->grid_width - 1)
991 state->cursor_x = state->grid_width - 1;
995 if (state->csiparam[0] == 0)
996 state->csiparam[0] = 1;
997 if ((state->cursor_x -= state->csiparam[0]) < 0)
1002 if ((state->cursor_y = (state->csiparam[0] - 1)) >= state->grid_height - 1)
1003 state->cursor_y = state->grid_height - 1;
1004 state->escstate = 0;
1008 if ((state->cursor_x = (state->csiparam[0] - 1)) >= state->grid_width - 1)
1009 state->cursor_x = state->grid_width - 1;
1010 state->escstate = 0;
1014 if ((state->cursor_y = (state->csiparam[0] - 1)) >= state->grid_height - 1)
1015 state->cursor_y = state->grid_height - 1;
1016 if ((state->cursor_x = (state->csiparam[1] - 1)) >= state->grid_width - 1)
1017 state->cursor_x = state->grid_width - 1;
1018 if(state->cursor_y < 0)
1019 state->cursor_y = 0;
1020 if(state->cursor_x < 0)
1021 state->cursor_x = 0;
1022 state->escstate = 0;
1026 end = state->grid_height * state->grid_width;
1027 if (state->csiparam[0] == 0)
1028 start = state->grid_width * state->cursor_y + state->cursor_x;
1029 if (state->csiparam[0] == 1)
1030 end = state->grid_width * state->cursor_y + state->cursor_x;
1031 for (i = start; i < end; i++)
1033 cell = &state->cells[i];
1034 if (cell->state == FLARE || cell->state == NORMAL)
1037 cell->changed = True;
1040 set_cursor (state, True);
1041 state->escstate = 0;
1045 end = state->grid_width;
1046 if (state->csiparam[0] == 0)
1047 start = state->cursor_x;
1048 if (state->csiparam[1] == 1)
1049 end = state->cursor_x;
1050 for (i = start; i < end; i++)
1052 if (cell->state == FLARE || cell->state == NORMAL)
1055 cell->changed = True;
1059 state->escstate = 0;
1061 case 's': /* Save position */
1062 state->saved_x = state->cursor_x;
1063 state->saved_y = state->cursor_y;
1064 state->escstate = 0;
1066 case 'u': /* Restore position */
1067 state->cursor_x = state->saved_x;
1068 state->cursor_y = state->saved_y;
1069 state->escstate = 0;
1071 case '?': /* DEC Private modes */
1072 if ((state->curparam != 0) || (state->csiparam[0] != 0))
1073 state->escstate = 0;
1076 /* Known unsupported CSIs:
1078 * L - Insert blank lines
1079 * M - Delete lines (I don't know what this means...)
1080 * P - Delete characters
1081 * X - Erase characters (difference with P being...?)
1082 * c - Terminal identification
1083 * g - Clear tab stop(s)
1084 * h - Set mode (Mainly due to its complexity and lack of good
1087 * m - Set mode (Phosphor is, per defenition, green on black)
1089 * q - Set keyboard LEDs
1090 * r - Set scrolling region (too exhausting - noone uses this,
1093 state->escstate = 0;
1098 state->escstate = 0;
1101 set_cursor (state, True);
1105 if (c == '\t') c = ' '; /* blah. */
1107 if (c == '\r' || c == '\n') /* handle CR, LF, or CRLF as "new line". */
1109 if (c == '\n' && state->last_c == '\r')
1110 ; /* CRLF -- do nothing */
1113 state->cursor_x = 0;
1114 if (state->cursor_y == state->grid_height - 1)
1120 else if (c == '\014')
1126 cell->state = FLARE;
1127 cell->p_char = state->chars[c];
1128 cell->changed = True;
1131 if (c != ' ' && cell->p_char->blank_p)
1132 cell->p_char = state->chars[CURSOR_INDEX];
1134 if (state->cursor_x >= state->grid_width - 1)
1136 state->cursor_x = 0;
1137 if (state->cursor_y >= state->grid_height - 1)
1143 set_cursor (state, True);
1151 update_display (p_state *state, Bool changed_only)
1155 for (y = 0; y < state->grid_height; y++)
1156 for (x = 0; x < state->grid_width; x++)
1158 p_cell *cell = &state->cells[state->grid_width * y + x];
1159 int width, height, tx, ty;
1161 if (changed_only && !cell->changed)
1164 width = state->char_width * state->scale;
1165 height = state->char_height * state->scale;
1169 if (cell->state == BLANK || cell->p_char->blank_p)
1171 XFillRectangle (state->dpy, state->window, state->gcs[BLANK],
1172 tx, ty, width, height);
1177 GC gc1 = state->gcs[cell->state];
1178 GC gc2 = ((cell->state + 2) < state->ticks
1179 ? state->gcs[cell->state + 2]
1181 GC gc3 = (gc2 ? gc2 : gc1);
1183 XCopyPlane (state->dpy, cell->p_char->pixmap, state->window, gc3,
1184 0, 0, width, height, tx, ty, 1L);
1187 XSetClipMask (state->dpy, gc1, cell->p_char->pixmap2);
1188 XSetClipOrigin (state->dpy, gc1, tx, ty);
1189 XFillRectangle (state->dpy, state->window, gc1,
1190 tx, ty, width, height);
1191 XSetClipMask (state->dpy, gc1, None);
1193 #else /* !FUZZY_BORDER */
1195 XCopyPlane (state->dpy,
1196 cell->p_char->pixmap, state->window,
1197 state->gcs[cell->state],
1198 0, 0, width, height, tx, ty, 1L);
1200 #endif /* !FUZZY_BORDER */
1203 cell->changed = False;
1208 static unsigned long
1209 phosphor_draw (Display *dpy, Window window, void *closure)
1211 p_state *state = (p_state *) closure;
1212 update_display (state, True);
1214 drain_input (state);
1215 return state->delay;
1223 subproc_cb (XtPointer closure, int *source, XtInputId *id)
1225 p_state *state = (p_state *) closure;
1226 state->input_available_p = True;
1231 launch_text_generator (p_state *state)
1233 XtAppContext app = XtDisplayToApplicationContext (state->dpy);
1234 Display *dpy = state->dpy;
1236 char *oprogram = get_string_resource (dpy, "program", "Program");
1237 char *program = (char *) malloc (strlen (oprogram) + 50);
1239 strcpy (program, "( ");
1240 strcat (program, oprogram);
1242 /* Kludge! Special-case "xscreensaver-text" to tell it how wide
1243 the screen is. We used to do this by just always feeding
1244 `program' through sprintf() and setting the default value to
1245 "xscreensaver-text --cols %d", but that makes things blow up
1246 if someone ever uses a --program that includes a % anywhere.
1248 if (!strcmp (oprogram, "xscreensaver-text"))
1249 sprintf (program + strlen(program), " --cols %d", state->grid_width-1);
1251 strcat (program, " ) 2>&1");
1254 if(state->mode == 1)
1259 ws.ws_row = state->grid_height - 1;
1260 ws.ws_col = state->grid_width - 2;
1261 ws.ws_xpixel = state->xgwa.width;
1262 ws.ws_ypixel = state->xgwa.height;
1265 if((state->pid = forkpty(&fd, NULL, NULL, &ws)) < 0)
1267 /* Unable to fork */
1268 sprintf (buf, "%.100s: forkpty", progname);
1271 else if(!state->pid)
1273 /* This is the child fork. */
1276 if (putenv("TERM=vt100"))
1278 av[i++] = "/bin/sh";
1283 sprintf (buf, "%.100s: %.100s", progname, oprogram);
1289 /* This is the parent fork. */
1290 state->pipe = fdopen(fd, "r+");
1292 XtAppAddInput (app, fileno (state->pipe),
1293 (XtPointer) (XtInputReadMask | XtInputExceptMask),
1294 subproc_cb, (XtPointer) state);
1298 #endif /* HAVE_FORKPTY */
1300 /* don't mess up controlling terminal if someone dumbly does
1301 "-pipe -program tcsh". */
1302 static int protected_stdin_p = 0;
1303 if (! protected_stdin_p) {
1305 open ("/dev/null", O_RDWR); /* re-allocate fd 0 */
1306 protected_stdin_p = 1;
1309 if ((state->pipe = popen (program, "r")))
1312 XtAppAddInput (app, fileno (state->pipe),
1313 (XtPointer) (XtInputReadMask | XtInputExceptMask),
1314 subproc_cb, (XtPointer) state);
1318 sprintf (buf, "%.100s: %.100s", progname, program);
1328 relaunch_generator_timer (XtPointer closure, XtIntervalId *id)
1330 p_state *state = (p_state *) closure;
1331 if (!state->pipe_timer) abort();
1332 state->pipe_timer = 0;
1333 launch_text_generator (state);
1338 drain_input (p_state *state)
1340 XtAppContext app = XtDisplayToApplicationContext (state->dpy);
1341 if (state->input_available_p && state->pipe)
1344 int n = read (fileno (state->pipe), (void *) s, 1);
1347 print_char (state, s[0]);
1351 XtRemoveInput (state->pipe_id);
1355 waitpid(state->pid, NULL, 0);
1356 fclose (state->pipe);
1361 pclose (state->pipe);
1365 if (state->cursor_x != 0) { /* break line if unbroken */
1366 print_char (state, '\r');
1367 print_char (state, '\n');
1369 print_char (state, '\r'); /* blank line */
1370 print_char (state, '\n');
1372 /* Set up a timer to re-launch the subproc in a bit. */
1374 XtAppAddTimeOut (app, state->subproc_relaunch_delay,
1375 relaunch_generator_timer,
1379 state->input_available_p = False;
1384 /* The interpretation of the ModN modifiers is dependent on what keys
1385 are bound to them: Mod1 does not necessarily mean "meta". It only
1386 means "meta" if Meta_L or Meta_R are bound to it. If Meta_L is on
1387 Mod5, then Mod5 is the one that means Meta. Oh, and Meta and Alt
1388 aren't necessarily the same thing. Icepicks in my forehead!
1391 do_icccm_meta_key_stupidity (Display *dpy)
1393 unsigned int modbits = 0;
1396 XModifierKeymap *modmap = XGetModifierMapping (dpy);
1397 for (i = 3; i < 8; i++)
1398 for (j = 0; j < modmap->max_keypermod; j++)
1400 int code = modmap->modifiermap[i * modmap->max_keypermod + j];
1403 if (code == 0) continue;
1404 syms = XGetKeyboardMapping (dpy, code, 1, &nsyms);
1405 for (k = 0; k < nsyms; k++)
1406 if (syms[k] == XK_Meta_L || syms[k] == XK_Meta_R ||
1407 syms[k] == XK_Alt_L || syms[k] == XK_Alt_R)
1408 modbits |= (1 << i);
1411 XFreeModifiermap (modmap);
1412 # endif /* HAVE_COCOA */
1416 /* Returns a mask of the bit or bits of a KeyPress event that mean "meta".
1419 meta_modifier (p_state *state)
1421 if (!state->meta_done_once)
1423 /* Really, we are supposed to recompute this if a KeymapNotify
1424 event comes in, but fuck it. */
1425 state->meta_done_once = True;
1426 state->meta_mask = do_icccm_meta_key_stupidity (state->dpy);
1428 return state->meta_mask;
1433 phosphor_reshape (Display *dpy, Window window, void *closure,
1434 unsigned int w, unsigned int h)
1436 p_state *state = (p_state *) closure;
1437 resize_grid (state);
1439 # if defined(HAVE_FORKPTY) && defined(TIOCSWINSZ)
1440 if (state->pid && state->pipe)
1442 /* Tell the sub-process that the screen size has changed. */
1444 ws.ws_row = state->grid_height - 1;
1445 ws.ws_col = state->grid_width - 2;
1446 ws.ws_xpixel = state->xgwa.width;
1447 ws.ws_ypixel = state->xgwa.height;
1448 ioctl (fileno (state->pipe), TIOCSWINSZ, &ws);
1449 kill (state->pid, SIGWINCH);
1451 # endif /* HAVE_FORKPTY && TIOCSWINSZ */
1455 phosphor_event (Display *dpy, Window window, void *closure, XEvent *event)
1457 p_state *state = (p_state *) closure;
1459 if (event->xany.type == Expose)
1460 update_display (state, False);
1461 else if (event->xany.type == KeyPress)
1464 unsigned char c = 0;
1465 XLookupString (&event->xkey, (char *) &c, 1, &keysym,
1467 if (c != 0 && state->pipe)
1469 if (!state->swap_bs_del_p) ;
1470 else if (c == 127) c = 8;
1471 else if (c == 8) c = 127;
1473 /* If meta was held down, send ESC, or turn on the high bit. */
1474 if (event->xkey.state & meta_modifier (state))
1476 if (state->meta_sends_esc_p)
1477 fputc ('\033', state->pipe);
1482 fputc (c, state->pipe);
1483 fflush (state->pipe);
1484 event->xany.type = 0; /* don't interpret this event defaultly. */
1493 phosphor_free (Display *dpy, Window window, void *closure)
1495 p_state *state = (p_state *) closure;
1498 XtRemoveInput (state->pipe_id);
1500 pclose (state->pipe);
1501 if (state->cursor_timer)
1502 XtRemoveTimeOut (state->cursor_timer);
1503 if (state->pipe_timer)
1504 XtRemoveTimeOut (state->pipe_timer);
1506 /* #### there's more to free here */
1513 static const char *phosphor_defaults [] = {
1514 ".background: Black",
1515 ".foreground: #00FF00",
1517 "*fadeForeground: #006400",
1518 "*flareForeground: #FFFFFF",
1519 #if defined(BUILTIN_FONT)
1521 #elif defined(HAVE_COCOA)
1530 "*program: xscreensaver-text",
1532 "*metaSendsESC: True",
1536 #else /* !HAVE_FORKPTY */
1538 #endif /* !HAVE_FORKPTY */
1542 static XrmOptionDescRec phosphor_options [] = {
1543 { "-font", ".font", XrmoptionSepArg, 0 },
1544 { "-scale", ".scale", XrmoptionSepArg, 0 },
1545 { "-ticks", ".ticks", XrmoptionSepArg, 0 },
1546 { "-delay", ".delay", XrmoptionSepArg, 0 },
1547 { "-program", ".program", XrmoptionSepArg, 0 },
1548 { "-pty", ".mode", XrmoptionNoArg, "pty" },
1549 { "-pipe", ".mode", XrmoptionNoArg, "pipe" },
1550 { "-meta", ".metaSendsESC", XrmoptionNoArg, "False" },
1551 { "-esc", ".metaSendsESC", XrmoptionNoArg, "True" },
1552 { "-bs", ".swapBSDEL", XrmoptionNoArg, "False" },
1553 { "-del", ".swapBSDEL", XrmoptionNoArg, "True" },
1558 XSCREENSAVER_MODULE ("Phosphor", phosphor)