1 /* xscreensaver, Copyright (c) 1998-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 * Apple ][ CRT simulator, by Trevor Blackwell <tlb@tlb.org>
12 * with additional work by Jamie Zawinski <jwz@jwz.org>
13 * Pty and vt100 emulation by Fredrik Tolf <fredrik@dolda2000.com>
18 #endif /* HAVE_CONFIG_H */
27 #include "screenhack.h"
29 #include "textclient.h"
33 #define countof(x) (sizeof((x))/sizeof((*x)))
35 #define SCREEN_COLS 40
36 #define SCREEN_ROWS 24
41 /* Given a bitmask, returns the position and width of the field.
44 decode_mask (unsigned int mask, unsigned int *pos_ret, unsigned int *size_ret)
47 for (i = 0; i < 32; i++)
52 for (; i < 32; i++, j++)
53 if (! (mask & (1L << i)))
61 /* Given a value and a field-width, expands the field to fill out 8 bits.
64 spread_bits (unsigned char value, unsigned char width)
69 case 7: return (value << 1) | (value >> 6);
70 case 6: return (value << 2) | (value >> 4);
71 case 5: return (value << 3) | (value >> 2);
72 case 4: return (value << 4) | (value);
73 case 3: return (value << 5) | (value << 2) | (value >> 2);
74 case 2: return (value << 6) | (value << 4) | (value);
75 default: abort(); break;
80 /* Convert an XImage (of any size/depth/visual) to a 32bpp RGB array.
81 Scales it (without dithering) to WxH.
84 scale_image (Display *dpy, Window window, XImage *in,
85 int fromx, int fromy, int fromw, int fromh,
86 unsigned int *out, int w, int h)
90 unsigned int rpos=0, gpos=0, bpos=0; /* bitfield positions */
91 unsigned int rsiz=0, gsiz=0, bsiz=0;
92 unsigned int rmsk=0, gmsk=0, bmsk=0;
93 unsigned char spread_map[3][256];
94 XWindowAttributes xgwa;
97 if (fromx + fromw > in->width ||
98 fromy + fromh > in->height)
101 XGetWindowAttributes (dpy, window, &xgwa);
103 /* Compute the field offsets for RGB decoding in the XImage,
104 when in TrueColor mode. Otherwise we use the colormap.
106 if (visual_class (xgwa.screen, xgwa.visual) == PseudoColor ||
107 visual_class (xgwa.screen, xgwa.visual) == GrayScale)
109 int ncolors = visual_cells (xgwa.screen, xgwa.visual);
110 colors = (XColor *) calloc (sizeof (*colors), ncolors+1);
111 for (i = 0; i < ncolors; i++)
113 XQueryColors (dpy, xgwa.colormap, colors, ncolors);
117 rmsk = xgwa.visual->red_mask;
118 gmsk = xgwa.visual->green_mask;
119 bmsk = xgwa.visual->blue_mask;
120 decode_mask (rmsk, &rpos, &rsiz);
121 decode_mask (gmsk, &gpos, &gsiz);
122 decode_mask (bmsk, &bpos, &bsiz);
124 for (i = 0; i < 256; i++)
126 spread_map[0][i] = spread_bits (i, rsiz);
127 spread_map[1][i] = spread_bits (i, gsiz);
128 spread_map[2][i] = spread_bits (i, bsiz);
132 scale = (fromw > fromh
134 : (float) fromh / h);
136 /* Scale the pixmap from window size to Apple][ screen size (but 32bpp)
138 for (y = 0; y < h-1; y++) /* iterate over dest pixels */
139 for (x = 0; x < w-1; x++)
142 unsigned int r=0, g=0, b=0;
144 int xx1 = x * scale + fromx;
145 int yy1 = y * scale + fromy;
146 int xx2 = (x+1) * scale + fromx;
147 int yy2 = (y+1) * scale + fromy;
149 /* Iterate over the source pixels contributing to this one, and sum. */
150 for (xx = xx1; xx < xx2; xx++)
151 for (yy = yy1; yy < yy2; yy++)
153 unsigned char rr, gg, bb;
154 unsigned long sp = ((xx > in->width || yy > in->height)
155 ? 0 : XGetPixel (in, xx, yy));
158 rr = colors[sp].red & 0xFF;
159 gg = colors[sp].green & 0xFF;
160 bb = colors[sp].blue & 0xFF;
164 rr = (sp & rmsk) >> rpos;
165 gg = (sp & gmsk) >> gpos;
166 bb = (sp & bmsk) >> bpos;
167 rr = spread_map[0][rr];
168 gg = spread_map[1][gg];
169 bb = spread_map[2][bb];
176 /* Scale summed pixel values down to 8/8/8 range */
177 i = (xx2 - xx1) * (yy2 - yy1);
183 out[y * w + x] = (r << 16) | (g << 8) | b;
188 /* Convert an XImage (of any size/depth/visual) to a 32bpp RGB array.
189 Picks a random sub-image out of the source image, and scales it to WxH.
192 pick_a2_subimage (Display *dpy, Window window, XImage *in,
193 unsigned int *out, int w, int h)
195 int fromx, fromy, fromw, fromh;
196 if (in->width <= w || in->height <= h)
207 double scale = (0.5 + frand(0.7) + frand(0.7) + frand(0.7));
210 } while (fromw > in->width ||
213 dw = (in->width - fromw) / 2; /* near the center! */
214 dh = (in->height - fromh) / 2;
216 fromx = (dw <= 0 ? 0 : (random() % dw) + (dw/2));
217 fromy = (dh <= 0 ? 0 : (random() % dh) + (dh/2));
220 scale_image (dpy, window, in,
221 fromx, fromy, fromw, fromh,
226 /* Floyd-Steinberg dither. Derived from ppmquant.c,
227 Copyright (c) 1989, 1991 by Jef Poskanzer.
230 a2_dither (unsigned int *in, unsigned char *out, int w, int h)
233 Apple ][ color map. Each pixel can only be 1 or 0, but what that
234 means depends on whether it's an odd or even pixel, and whether
235 the high bit in the byte is set or not. If it's 0, it's always
238 static const int a2_cmap[2][2][3] = {
241 {/* odd pixels = blue */ 0x00, 0x80, 0xff},
242 {/* even pixels = red */ 0xff, 0x80, 0x00}
246 {/* even pixels = purple */ 0xa0, 0x40, 0xa0},
247 {/* odd pixels = green */ 0x40, 0xff, 0x40}
252 unsigned int **pixels;
267 FILE *pipe = popen ("xv -", "w");
268 fprintf (pipe, "P6\n%d %d\n%d\n", w, h, 255);
269 for (y = 0; y < h; y++)
270 for (x = 0; x < w; x++)
272 unsigned int p = in[y * w + x];
273 unsigned int r = (p >> 16) & 0xFF;
274 unsigned int g = (p >> 8) & 0xFF;
275 unsigned int b = (p ) & 0xFF;
276 fprintf(pipe, "%c%c%c", r, g, b);
282 /* Initialize Floyd-Steinberg error vectors. */
283 this_rerr = (long *) calloc (w + 2, sizeof(long));
284 next_rerr = (long *) calloc (w + 2, sizeof(long));
285 this_gerr = (long *) calloc (w + 2, sizeof(long));
286 next_gerr = (long *) calloc (w + 2, sizeof(long));
287 this_berr = (long *) calloc (w + 2, sizeof(long));
288 next_berr = (long *) calloc (w + 2, sizeof(long));
291 /* #### do we really need more than one element of "pixels" at once?
293 pixels = (unsigned int **) malloc (h * sizeof (unsigned int *));
294 for (y = 0; y < h; y++)
295 pixels[y] = (unsigned int *) malloc (w * sizeof (unsigned int));
297 for (x = 0; x < w + 2; ++x)
299 this_rerr[x] = random() % (fs_scale * 2) - fs_scale;
300 this_gerr[x] = random() % (fs_scale * 2) - fs_scale;
301 this_berr[x] = random() % (fs_scale * 2) - fs_scale;
302 /* (random errors in [-1 .. 1]) */
305 for (y = 0; y < h; y++)
306 for (x = 0; x < w; x++)
307 pixels[y][x] = in[y * w + x];
309 for (y = 0; y < h; y++)
315 for (x = 0; x < w + 2; x++)
316 next_rerr[x] = next_gerr[x] = next_berr[x] = 0;
318 /* It's too complicated to go back and forth on alternate rows,
319 so we always go left-right here. It doesn't change the result
322 For each group of 7 pixels, we have to try it both with the
323 high bit=0 and =1. For each high bit value, we add up the
324 total error and pick the best one.
326 Because we have to go through each group of bits twice, we
327 don't propagate the error values through this_[rgb]err since
328 it would add them twice. So we keep seperate local_[rgb]err
329 variables for propagating error within the 7-pixel group.
333 for (xbyte=0; xbyte<280; xbyte+=7)
336 int best_error=2000000000;
340 int local_rerr=0, local_gerr=0, local_berr=0;
342 for (hibit=0; hibit<2; hibit++)
347 for (x=xbyte; x<xbyte+7; x++)
351 /* Use Floyd-Steinberg errors to adjust actual color. */
352 sr = ((pP[x] >> 16) & 0xFF) * brightness/256;
353 sg = ((pP[x] >> 8) & 0xFF) * brightness/256;
354 sb = ((pP[x] ) & 0xFF) * brightness/256;
355 sr += (this_rerr[x + 1] + local_rerr) / fs_scale;
356 sg += (this_gerr[x + 1] + local_gerr) / fs_scale;
357 sb += (this_berr[x + 1] + local_berr) / fs_scale;
360 else if (sr > maxval) sr = maxval;
362 else if (sg > maxval) sg = maxval;
364 else if (sb > maxval) sb = maxval;
366 /* This is the color we'd get if we set the bit 1. For 0,
368 r2=a2_cmap[hibit][x&1][0];
369 g2=a2_cmap[hibit][x&1][1];
370 b2=a2_cmap[hibit][x&1][2];
373 dist0 and dist1 are the error (Minkowski 2-metric
374 distances in the color space) for choosing 0 and
375 1 respectively. 0 is black, 1 is the color r2,g2,b2.
377 dist1= (sr-r2)*(sr-r2) + (sg-g2)*(sg-g2) + (sb-b2)*(sb-b2);
378 dist0= sr*sr + sg*sg + sb*sb;
382 byte |= 1 << (x-xbyte);
385 /* Wanted sr but got r2, so propagate sr-r2 */
386 local_rerr = (sr - r2) * fs_scale * 7/16;
387 local_gerr = (sg - g2) * fs_scale * 7/16;
388 local_berr = (sb - b2) * fs_scale * 7/16;
394 /* Wanted sr but got 0, so propagate sr */
395 local_rerr = sr * fs_scale * 7/16;
396 local_gerr = sg * fs_scale * 7/16;
397 local_berr = sb * fs_scale * 7/16;
401 if (tot_error < best_error)
404 best_error = tot_error;
408 /* Avoid alternating 7f and ff in all-white areas, because it makes
409 regular pink vertical lines */
410 if ((best_byte&0x7f)==0x7f && (prev_byte&0x7f)==0x7f)
415 Now that we've chosen values for all 8 bits of the byte, we
416 have to fill in the real pixel values into pP and propagate
417 all the error terms. We end up repeating a lot of the code
421 for (x=xbyte; x<xbyte+7; x++)
423 int bit=(best_byte>>(x-xbyte))&1;
424 hibit=(best_byte>>7)&1;
426 sr = (pP[x] >> 16) & 0xFF;
427 sg = (pP[x] >> 8) & 0xFF;
428 sb = (pP[x] ) & 0xFF;
429 sr += this_rerr[x + 1] / fs_scale;
430 sg += this_gerr[x + 1] / fs_scale;
431 sb += this_berr[x + 1] / fs_scale;
434 else if (sr > maxval) sr = maxval;
436 else if (sg > maxval) sg = maxval;
438 else if (sb > maxval) sb = maxval;
440 r2=a2_cmap[hibit][x&1][0] * bit;
441 g2=a2_cmap[hibit][x&1][1] * bit;
442 b2=a2_cmap[hibit][x&1][2] * bit;
444 pP[x] = (r2<<16) | (g2<<8) | (b2);
446 /* Propagate Floyd-Steinberg error terms. */
447 err = (sr - r2) * fs_scale;
448 this_rerr[x + 2] += (err * 7) / 16;
449 next_rerr[x ] += (err * 3) / 16;
450 next_rerr[x + 1] += (err * 5) / 16;
451 next_rerr[x + 2] += (err ) / 16;
452 err = (sg - g2) * fs_scale;
453 this_gerr[x + 2] += (err * 7) / 16;
454 next_gerr[x ] += (err * 3) / 16;
455 next_gerr[x + 1] += (err * 5) / 16;
456 next_gerr[x + 2] += (err ) / 16;
457 err = (sb - b2) * fs_scale;
458 this_berr[x + 2] += (err * 7) / 16;
459 next_berr[x ] += (err * 3) / 16;
460 next_berr[x + 1] += (err * 5) / 16;
461 next_berr[x + 2] += (err ) / 16;
465 And put the actual byte into out.
468 out[y*(w/7) + xbyte/7] = best_byte;
472 temp_err = this_rerr;
473 this_rerr = next_rerr;
474 next_rerr = temp_err;
475 temp_err = this_gerr;
476 this_gerr = next_gerr;
477 next_gerr = temp_err;
478 temp_err = this_berr;
479 this_berr = next_berr;
480 next_berr = temp_err;
496 /* let's see what we got... */
497 FILE *pipe = popen ("xv -", "w");
498 fprintf (pipe, "P6\n%d %d\n%d\n", w, h, 255);
499 for (y = 0; y < h; y++)
500 for (x = 0; x < w; x++)
502 unsigned int r = (pixels[y][x]>>16)&0xff;
503 unsigned int g = (pixels[y][x]>>8)&0xff;
504 unsigned int b = (pixels[y][x]>>0)&0xff;
505 fprintf(pipe, "%c%c%c", r, g, b);
512 typedef struct slideshow_data_s {
514 int render_img_lineno;
515 unsigned char *render_img;
517 Bool image_loading_p;
522 image_loaded_cb (Screen *screen, Window window, Drawable p,
523 const char *name, XRectangle *geometry,
526 Display *dpy = DisplayOfScreen (screen);
527 apple2_sim_t *sim = (apple2_sim_t *) closure;
528 slideshow_data *mine = (slideshow_data *) sim->controller_data;
529 XWindowAttributes xgwa;
533 unsigned int *buf32 = (unsigned int *) calloc (w, h * 4);
534 unsigned char *buf8 = (unsigned char *) calloc (w/7, h);
538 fprintf (stderr, "%s: out of memory (%dx%d)\n", progname, w, h);
542 XGetWindowAttributes (dpy, window, &xgwa);
544 image = XGetImage (dpy, p, 0, 0, xgwa.width, xgwa.height, ~0, ZPixmap);
545 XFreePixmap (dpy, p);
548 /* Scale the XImage down to Apple][ size, and convert it to a 32bpp
549 image (regardless of whether it started as TrueColor/PseudoColor.)
551 pick_a2_subimage (dpy, window, image, buf32, w, h);
554 XDestroyImage(image);
556 /* Then dither the 32bpp image to a 6-color Apple][ colormap.
558 a2_dither (buf32, buf8, w, h);
562 mine->image_loading_p = False;
563 mine->img_filename = (name ? strdup (name) : 0);
564 mine->render_img = buf8;
569 static const char *apple2_defaults [] = {
570 ".background: black",
571 ".foreground: white",
574 "*program: xscreensaver-text --cols 40",
575 "*metaSendsESC: True",
582 # endif /* !HAVE_FORKPTY */
588 static XrmOptionDescRec apple2_options [] = {
589 { "-mode", ".mode", XrmoptionSepArg, 0 },
590 { "-slideshow", ".mode", XrmoptionNoArg, "slideshow" },
591 { "-basic", ".mode", XrmoptionNoArg, "basic" },
592 { "-text", ".mode", XrmoptionNoArg, "text" },
593 { "-program", ".program", XrmoptionSepArg, 0 },
594 { "-duration", ".duration", XrmoptionSepArg, 0 },
595 { "-pty", ".usePty", XrmoptionNoArg, "True" },
596 { "-pipe", ".usePty", XrmoptionNoArg, "False" },
597 { "-meta", ".metaSendsESC", XrmoptionNoArg, "False" },
598 { "-esc", ".metaSendsESC", XrmoptionNoArg, "True" },
599 { "-bs", ".swapBSDEL", XrmoptionNoArg, "False" },
600 { "-del", ".swapBSDEL", XrmoptionNoArg, "True" },
601 { "-fast", ".fast", XrmoptionNoArg, "True" },
607 TODO: this should load 10 images at startup time, then cycle through them
608 to avoid the pause while it loads.
611 static void slideshow_controller(apple2_sim_t *sim, int *stepno,
612 double *next_actiontime)
614 apple2_state_t *st=sim->st;
616 slideshow_data *mine;
618 if (!sim->controller_data)
619 sim->controller_data = calloc (1, sizeof(*mine));
620 mine = (slideshow_data *) sim->controller_data;
628 sim->typing_rate = 0.3;
629 sim->dec->powerup=0.0;
632 a2_prints(st, "APPLE ][");
641 XWindowAttributes xgwa;
643 XGetWindowAttributes (sim->dpy, sim->window, &xgwa);
644 p = XCreatePixmap (sim->dpy, sim->window, xgwa.width, xgwa.height,
646 mine->image_loading_p = True;
647 load_image_async (xgwa.screen, sim->window, p, image_loaded_cb, sim);
649 /* pause with a blank screen for a bit, while the image loads in the
651 *next_actiontime += 2.0;
657 if (! mine->image_loading_p) { /* image is finally loaded */
663 *next_actiontime += 3.0;
678 st->gr_mode=A2_GR_HIRES;
679 if (mine->img_filename) {
680 char *basename, *tmp;
683 basename = tmp = strdup (mine->img_filename);
686 char *slash = strchr(basename, '/');
687 if (!slash || !slash[1]) break;
691 char *dot=strrchr(basename,'.');
694 if (strlen(basename)>20) basename[20]=0;
695 for (s=basename; *s; s++) {
697 if (*s <= ' ') *s = '_';
699 sprintf(sim->typing_buf, "BLOAD %s\n", basename);
700 sim->typing = sim->typing_buf;
704 sim->typing = "BLOAD IMAGE\n";
706 mine->render_img_lineno=0;
712 *next_actiontime += 0.7;
717 if (mine->render_img_lineno>=192) {
719 sim->typing="POKE 49234,0\n";
724 for (i=0; i<6 && mine->render_img_lineno<192; i++) {
725 a2_display_image_loading(st, mine->render_img,
726 mine->render_img_lineno++);
729 /* The disk would have to seek every 13 sectors == 78 lines.
730 (This ain't no newfangled 16-sector operating system) */
731 if ((mine->render_img_lineno%78)==0) {
732 *next_actiontime += 0.5;
734 *next_actiontime += 0.08;
739 st->gr_mode |= A2_GR_FULL;
741 /* Note that sim->delay is sometimes "infinite" in this controller.
742 These images are kinda dull anyway, so don't leave it on too long. */
743 *next_actiontime += 2;
748 sim->typing="POKE 49235,0\n";
754 st->gr_mode &= ~A2_GR_FULL;
755 if (mine->render_img) {
756 free(mine->render_img);
757 mine->render_img=NULL;
759 if (mine->img_filename) {
760 free(mine->img_filename);
761 mine->img_filename=NULL;
767 /* Do nothing, just wait */
768 *next_actiontime += 2.0;
769 *stepno = A2CONTROLLER_FREE;
772 case A2CONTROLLER_FREE:
773 /* It is possible that still image is being loaded,
774 in that case mine cannot be freed, because
775 callback function tries to use it, so wait.
777 if (mine->image_loading_p) {
781 free(mine->render_img);
782 free(mine->img_filename);
792 struct terminal_controller_data {
796 double last_emit_time;
802 int cursor_x, cursor_y;
803 int saved_x, saved_y;
804 int unicruds; char unicrud[7];
807 unsigned int bold : 1;
808 unsigned int blink : 1;
809 unsigned int rev : 1;
818 /* The structure of closure linkage throughout this code is so amazingly
819 baroque that I can't get to the 'struct state' from where I need it. */
820 static const char *global_program;
821 static Bool global_fast_p;
825 terminal_closegen(struct terminal_controller_data *mine)
828 textclient_close (mine->tc);
834 terminal_read(struct terminal_controller_data *mine, unsigned char *buf, int n)
836 if (!mine || !mine->tc) {
840 for (i = 0; i < n; i++) {
841 int c = textclient_getc (mine->tc);
853 terminal_keypress_handler (Display *dpy, XEvent *event, void *data)
855 struct terminal_controller_data *mine =
856 (struct terminal_controller_data *) data;
858 if (event->xany.type == KeyPress && mine->tc)
859 return textclient_putc (mine->tc, &event->xkey);
865 a2_ascii_printc (apple2_state_t *st, unsigned char c,
866 Bool bold_p, Bool blink_p, Bool rev_p,
869 if (c >= 'a' && c <= 'z') /* upcase lower-case chars */
873 else if ((c >= 'A'+128) || /* upcase and blink */
874 (c < ' ' && c != 014 && /* high-bit & ctl chrs */
875 c != '\r' && c != '\n' && c!='\t'))
877 c = (c & 0x1F) | 0x80;
879 else if (c >= 'A' && c <= 'Z') /* invert upper-case chars */
884 if (bold_p) c |= 0xc0;
885 if (blink_p) c = (c & ~0x40) | 0x80;
886 if (rev_p) c |= 0xc0;
891 a2_printc_noscroll(st, c);
896 a2_vt100_printc (apple2_sim_t *sim, struct terminal_controller_data *state,
899 apple2_state_t *st=sim->st;
900 int cols = SCREEN_COLS;
901 int rows = SCREEN_ROWS;
906 /* Mostly duplicated in phosphor.c */
908 switch (state->escstate)
914 /* Dummy case - we don't want the screensaver to beep */
915 /* #### But maybe this should flash the screen? */
918 if (state->cursor_x > 0)
922 if (state->cursor_x < cols - 8)
924 state->cursor_x = (state->cursor_x & ~7) + 8;
929 if (state->cursor_y < rows - 1)
936 # ifndef HAVE_FORKPTY
937 state->cursor_x = 0; /* No ptys on iPhone; assume CRLF. */
941 if (state->cursor_y < rows - 1)
951 /* Dummy case - there is one and only one font. */
955 /* Dummy case - these interrupt escape sequences, so
956 they don't do anything in this state */
962 /* Dummy case - this is supposed to be ignored */
966 for(i = 0; i < NPAR; i++)
967 state->csiparam[i] = 0;
972 /* states 102-106 are for UTF-8 decoding */
974 if ((c & 0xE0) == 0xC0) { /* 110xxxxx - 11 bits, 2 bytes */
976 state->unicrud[0] = c;
977 state->escstate = 102;
979 } else if ((c & 0xF0) == 0xE0) { /* 1110xxxx - 16 bits, 3 bytes */
981 state->unicrud[0] = c;
982 state->escstate = 103;
984 } else if ((c & 0xF8) == 0xF0) { /* 11110xxx - 21 bits, 4 bytes */
986 state->unicrud[0] = c;
987 state->escstate = 104;
989 } else if ((c & 0xFC) == 0xF8) { /* 111110xx - 26 bits, 5 bytes */
991 state->unicrud[0] = c;
992 state->escstate = 105;
994 } else if ((c & 0xFE) == 0xFC) { /* 1111110x - 31 bits, 6 bytes */
996 state->unicrud[0] = c;
997 state->escstate = 106;
1003 /* If the cursor is in column 39 and we print a character, then
1004 that character shows up in column 39, and the cursor is no longer
1005 visible on the screen (it's in "column 40".) If another character
1006 is printed, then that character shows up in column 0, and the
1007 cursor moves to column 1.
1009 This is empirically what xterm and gnome-terminal do, so that must
1010 be the right thing. (In xterm, the cursor vanishes, whereas; in
1011 gnome-terminal, the cursor overprints the character in col 39.)
1013 if (state->cursor_x >= cols)
1015 state->cursor_x = 0;
1016 if (state->cursor_y >= rows - 1)
1022 a2_goto(st, state->cursor_y, state->cursor_x); /* clips range */
1023 a2_ascii_printc (st, c,
1024 state->termattrib.bf.bold,
1025 state->termattrib.bf.blink,
1026 state->termattrib.bf.rev,
1038 state->escstate = 0;
1040 case 'c': /* Reset */
1042 state->escstate = 0;
1044 case 'D': /* Linefeed */
1045 if (state->cursor_y < rows - 1)
1049 state->escstate = 0;
1051 case 'E': /* Newline */
1052 state->cursor_x = 0;
1053 state->escstate = 0;
1055 case 'M': /* Reverse newline */
1056 if (state->cursor_y > 0)
1058 state->escstate = 0;
1060 case '7': /* Save state */
1061 state->saved_x = state->cursor_x;
1062 state->saved_y = state->cursor_y;
1063 state->escstate = 0;
1065 case '8': /* Restore state */
1066 state->cursor_x = state->saved_x;
1067 state->cursor_y = state->saved_y;
1068 state->escstate = 0;
1071 state->escstate = 2;
1072 for(i = 0; i < NPAR; i++)
1073 state->csiparam[i] = 0;
1074 state->curparam = 0;
1076 case '%': /* Select charset */
1077 /* @: Select default (ISO 646 / ISO 8859-1)
1079 8: Select UTF-8 (obsolete)
1081 We can just ignore this and always process UTF-8, I think?
1082 We must still catch the last byte, though.
1086 /* I don't support different fonts either - see above
1088 state->escstate = 3;
1091 /* Escape sequences not supported:
1094 * Z - Terminal identification
1096 * = - Other keypad change
1099 state->escstate = 0;
1108 state->escstate = 0;
1110 case '0': case '1': case '2': case '3': case '4':
1111 case '5': case '6': case '7': case '8': case '9':
1112 if (state->curparam < NPAR)
1113 state->csiparam[state->curparam] =
1114 (state->csiparam[state->curparam] * 10) + (c - '0');
1117 state->csiparam[++state->curparam] = 0;
1120 state->escstate = 3;
1123 for (i = 0; i < state->csiparam[0]; i++)
1125 if(++state->cursor_x > cols)
1127 state->cursor_x = 0;
1128 if (state->cursor_y < rows - 1)
1134 state->escstate = 0;
1137 state->cursor_x = 0;
1139 if (state->csiparam[0] == 0)
1140 state->csiparam[0] = 1;
1141 if ((state->cursor_y -= state->csiparam[0]) < 0)
1142 state->cursor_y = 0;
1143 state->escstate = 0;
1146 state->cursor_x = 0;
1149 if (state->csiparam[0] == 0)
1150 state->csiparam[0] = 1;
1151 if ((state->cursor_y += state->csiparam[0]) >= rows)
1152 state->cursor_y = rows - 1;
1153 state->escstate = 0;
1157 if (state->csiparam[0] == 0)
1158 state->csiparam[0] = 1;
1159 if ((state->cursor_x += state->csiparam[0]) >= cols)
1160 state->cursor_x = cols - 1;
1161 state->escstate = 0;
1164 if (state->csiparam[0] == 0)
1165 state->csiparam[0] = 1;
1166 if ((state->cursor_x -= state->csiparam[0]) < 0)
1167 state->cursor_x = 0;
1168 state->escstate = 0;
1171 if ((state->cursor_y = (state->csiparam[0] - 1)) >= rows)
1172 state->cursor_y = rows - 1;
1173 state->escstate = 0;
1177 if ((state->cursor_x = (state->csiparam[0] - 1)) >= cols)
1178 state->cursor_x = cols - 1;
1179 state->escstate = 0;
1183 if ((state->cursor_y = (state->csiparam[0] - 1)) >= rows)
1184 state->cursor_y = rows - 1;
1185 if ((state->cursor_x = (state->csiparam[1] - 1)) >= cols)
1186 state->cursor_x = cols - 1;
1187 if(state->cursor_y < 0)
1188 state->cursor_y = 0;
1189 if(state->cursor_x < 0)
1190 state->cursor_x = 0;
1191 state->escstate = 0;
1196 if (state->csiparam[0] == 0)
1197 start = cols * state->cursor_y + state->cursor_x;
1198 if (state->csiparam[0] == 1)
1199 end = cols * state->cursor_y + state->cursor_x;
1201 a2_goto(st, state->cursor_y, state->cursor_x);
1202 for (i = start; i < end; i++)
1204 a2_ascii_printc(st, ' ', False, False, False, False);
1206 state->escstate = 0;
1211 if (state->csiparam[0] == 0)
1212 start = state->cursor_x;
1213 if (state->csiparam[1] == 1)
1214 end = state->cursor_x;
1216 a2_goto(st, state->cursor_y, state->cursor_x);
1217 for (i = start; i < end; i++)
1219 a2_ascii_printc(st, ' ', False, False, False, False);
1221 state->escstate = 0;
1223 case 'm': /* Set attributes */
1224 for (i = 0; i <= state->curparam; i++)
1226 switch(state->csiparam[i])
1229 state->termattrib.w = 0;
1232 state->termattrib.bf.bold = 1;
1235 state->termattrib.bf.blink = 1;
1238 state->termattrib.bf.rev = 1;
1242 state->termattrib.bf.bold = 0;
1245 state->termattrib.bf.blink = 0;
1248 state->termattrib.bf.rev = 0;
1252 state->escstate = 0;
1254 case 's': /* Save position */
1255 state->saved_x = state->cursor_x;
1256 state->saved_y = state->cursor_y;
1257 state->escstate = 0;
1259 case 'u': /* Restore position */
1260 state->cursor_x = state->saved_x;
1261 state->cursor_y = state->saved_y;
1262 state->escstate = 0;
1264 case '?': /* DEC Private modes */
1265 if ((state->curparam != 0) || (state->csiparam[0] != 0))
1266 state->escstate = 0;
1269 /* Known unsupported CSIs:
1271 * L - Insert blank lines
1272 * M - Delete lines (I don't know what this means...)
1273 * P - Delete characters
1274 * X - Erase characters (difference with P being...?)
1275 * c - Terminal identification
1276 * g - Clear tab stop(s)
1277 * h - Set mode (Mainly due to its complexity and lack of good
1280 * m - Set mode (Phosphor is, per defenition, green on black)
1282 * q - Set keyboard LEDs
1283 * r - Set scrolling region (too exhausting - noone uses this,
1286 state->escstate = 0;
1291 state->escstate = 0;
1300 int total = state->escstate - 100; /* see what I did there */
1301 if (state->unicruds < total) {
1302 /* Buffer more bytes of the UTF-8 sequence */
1303 state->unicrud[state->unicruds++] = c;
1306 if (state->unicruds >= total) {
1307 /* Done! Convert it to ASCII and print that. */
1309 state->unicrud[state->unicruds] = 0;
1310 s = utf8_to_latin1 ((const char *) state->unicrud, True);
1311 state->unicruds = 0;
1312 state->escstate = 0;
1327 a2_goto(st, state->cursor_y, state->cursor_x);
1332 It's fun to put things like "gdb" as the command. For one, it's
1333 amusing how the standard mumble (version, no warranty, it's
1334 GNU/Linux dammit) occupies an entire screen on the Apple ][.
1338 terminal_controller(apple2_sim_t *sim, int *stepno, double *next_actiontime)
1340 apple2_state_t *st=sim->st;
1344 struct terminal_controller_data *mine;
1345 if (!sim->controller_data)
1346 sim->controller_data=calloc(sizeof(struct terminal_controller_data),1);
1347 mine=(struct terminal_controller_data *) sim->controller_data;
1348 mine->dpy = sim->dpy;
1350 mine->fast_p = global_fast_p;
1356 st->gr_mode |= A2_GR_FULL; /* Turn on color mode even through it's
1360 a2_prints(st, "APPLE ][");
1365 mine->tc = textclient_open (mine->dpy);
1366 textclient_reshape (mine->tc,
1367 SCREEN_COLS, SCREEN_ROWS,
1368 SCREEN_COLS, SCREEN_ROWS,
1373 *next_actiontime += 4.0;
1376 mine->last_emit_time = sim->curtime;
1382 Bool first_line_p = (*stepno == 10);
1383 unsigned char buf[1024];
1387 elapsed=sim->curtime - mine->last_emit_time;
1389 nwant = elapsed * 25.0; /* characters per second */
1396 if (nwant > 40) nwant = 40;
1399 nwant = sizeof(buf)-1;
1401 if (nwant <= 0) break;
1403 mine->last_emit_time = sim->curtime;
1405 nr=terminal_read(mine, buf, nwant);
1406 for (i=0; i<nr; i++) {
1410 a2_vt100_printc (sim, mine, c);
1412 a2_ascii_printc (st, c, False, False, False, True);
1417 case A2CONTROLLER_FREE:
1418 terminal_closegen(mine);
1425 struct basic_controller_data {
1428 const char * const * progtext;
1432 double prog_start_time;
1433 char error_buf[256];
1437 Adding more programs is easy. Just add a listing here and to all_programs,
1438 then add the state machine to actually execute it to basic_controller.
1440 static const char * const moire_program[]={
1442 "20 FOR Y = 0 TO 190 STEP 2\n",
1443 "30 HCOLOR=4 : REM BLACK\n",
1444 "40 HPLOT 0,191-Y TO 279,Y\n",
1445 "60 HCOLOR=7 : REM WHITE\n",
1446 "80 HPLOT 0,190-Y TO 279,Y+1\n",
1448 "100 FOR X = 0 TO 278 STEP 3\n",
1450 "120 HPLOT 279-X,0 TO X,191\n",
1452 "150 HPLOT 278-X,0 TO X+1,191\n",
1457 static const char * const sinewave_program[] = {
1460 "30 FOR X = 0 TO 279\n",
1462 "35 HPLOT X,0 TO X,159\n",
1464 "40 Y = 80 + SIN(15*(X-K)/279) * 40\n",
1473 static const char * const dumb_program[]={
1474 "10 PRINT \"APPLE ][ ROOLZ! TRS-80 DROOLZ!\"\n",
1480 static const char * const random_lores_program[]={
1481 "1 REM APPLE ][ SCREEN SAVER\n",
1483 "100 COLOR= RND(1)*16\n",
1485 "110 X=RND(1)*40\n",
1486 "120 Y1=RND(1)*40\n",
1487 "130 Y2=RND(1)*40\n",
1488 "140 FOR Y = Y1 TO Y2\n",
1492 "210 Y=RND(1)*40\n",
1493 "220 X1=RND(1)*40\n",
1494 "230 X2=RND(1)*40\n",
1495 "240 FOR X = X1 TO X2\n",
1503 static char typo_map[256];
1505 static int make_typo(char *out_buf, const char *orig, char *err_buf)
1565 strcpy(out_buf, orig);
1566 for (i=0; out_buf[i]; i++) {
1567 char *p = out_buf+i;
1569 if (i>2 && p[-2]=='R' && p[-1]=='E' && p[0]=='M')
1572 if (isalpha(p[0]) &&
1581 sprintf(err_buf,"?SYNTAX ERROR\n");
1585 if (random()%10==0 && strlen(p)>=4 && (errc=typo_map[(int)(unsigned char)p[0]])) {
1586 int remain=strlen(p);
1587 int past=random()%(remain-2)+1;
1588 memmove(p+past+past, p, remain+1);
1590 for (j=0; j<past; j++) {
1599 static const struct {
1600 const char * const * progtext;
1603 {moire_program, 100},
1604 /*{dumb_program, 200}, */
1605 {sinewave_program, 400},
1606 {random_lores_program, 500},
1610 basic_controller(apple2_sim_t *sim, int *stepno, double *next_actiontime)
1612 apple2_state_t *st=sim->st;
1615 struct basic_controller_data *mine;
1616 if (!sim->controller_data)
1617 sim->controller_data=calloc(sizeof(struct basic_controller_data),1);
1618 mine=(struct basic_controller_data *) sim->controller_data;
1625 a2_prints(st, "APPLE ][");
1628 sim->typing_rate=0.2;
1630 i=random()%countof(all_programs);
1631 mine->progtext=all_programs[i].progtext;
1632 mine->progstep=all_programs[i].progstep;
1635 *next_actiontime += 1.0;
1640 if (st->cursx==0) a2_printc(st,']');
1641 if (mine->progtext[mine->prog_line]) {
1642 if (random()%4==0) {
1643 int err=make_typo(sim->typing_buf,
1644 mine->progtext[mine->prog_line],
1646 sim->typing=sim->typing_buf;
1653 sim->typing=mine->progtext[mine->prog_line++];
1661 sim->printing=mine->error_buf;
1666 if (st->cursx==0) a2_printc(st,']');
1667 *next_actiontime+=1.0;
1672 sim->typing="RUN\n";
1676 mine->prog_start_time=*next_actiontime;
1677 *stepno=mine->progstep;
1682 st->gr_mode=A2_GR_HIRES|A2_GR_FULL;
1683 for (i=0; i<24 && mine->y<192; i++)
1685 a2_hline(st, 4, 0, 191-mine->y, 279, mine->y);
1686 a2_hline(st, 7, 0, 191-mine->y-1, 279, mine->y+1);
1696 for (i=0; i<24 && mine->x<280; i++)
1698 a2_hline(st, 4, 279-mine->x, 0, mine->x, 192);
1699 a2_hline(st, 7, 279-mine->x-1, 0, mine->x+1, 192);
1702 if (mine->x >= 280) *stepno=120;
1706 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1711 mine->rep_str="\nAPPLE ][ ROOLZ! TRS-80 DROOLZ!";
1712 for (i=0; i<30; i++) {
1713 a2_prints(st, mine->rep_str);
1719 i=random()%strlen(mine->rep_str);
1720 while (mine->rep_pos != i) {
1721 a2_printc(st, mine->rep_str[mine->rep_pos]);
1723 if (!mine->rep_str[mine->rep_pos]) mine->rep_pos=0;
1725 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1728 /* sinewave_program */
1730 st->gr_mode=A2_GR_HIRES;
1735 for (i=0; i<48; i++) {
1736 int y=80 + (int)(75.0*sin(15.0*(mine->x-mine->k)/279.0));
1737 a2_hline(st, 0, mine->x, 0, mine->x, 159);
1738 a2_hplot(st, 3, mine->x, y);
1745 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1753 /* random_lores_program */
1755 st->gr_mode=A2_GR_LORES|A2_GR_FULL;
1760 for (i=0; i<10; i++) {
1761 int color,x,y,x1,x2,y1,y2;
1767 for (y=y1; y<y2; y++) a2_plot(st, color, x, y);
1772 for (x=x1; x<x2; x++) a2_plot(st, color, x, y);
1774 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1781 case A2CONTROLLER_FREE:
1789 static void (* const controllers[]) (apple2_sim_t *sim, int *stepno,
1790 double *next_actiontime) = {
1791 slideshow_controller,
1792 terminal_controller,
1800 void (*controller) (apple2_sim_t *sim, int *stepno, double *next_actiontime);
1805 apple2_init (Display *dpy, Window window)
1807 struct state *st = (struct state *) calloc (1, sizeof(*st));
1810 st->duration = get_integer_resource (dpy, "duration", "Integer");
1813 if (st->duration < 1) st->duration = 1;
1815 s = get_string_resource (dpy, "mode", "Mode");
1816 if (!s || !*s || !strcasecmp(s, "random"))
1817 st->random_p = True;
1818 else if (!strcasecmp(s, "text"))
1819 st->controller = terminal_controller;
1820 else if (!strcasecmp(s, "slideshow"))
1821 st->controller = slideshow_controller;
1822 else if (!strcasecmp(s, "basic"))
1823 st->controller = basic_controller;
1826 fprintf (stderr, "%s: mode must be text, slideshow, or random; not %s\n",
1832 global_program = get_string_resource (dpy, "program", "Program");
1833 global_fast_p = get_boolean_resource (dpy, "fast", "Boolean");
1836 /* Kludge for MacOS standalone mode: see OSX/SaverRunner.m. */
1838 const char *s = getenv ("XSCREENSAVER_STANDALONE");
1839 if (s && *s && strcmp(s, "0"))
1841 st->controller = terminal_controller;
1842 st->random_p = False;
1843 global_program = getenv ("SHELL");
1844 global_fast_p = True;
1849 if (! st->random_p) {
1850 if (st->controller == terminal_controller ||
1851 st->controller == slideshow_controller)
1852 st->duration = 999999; /* these run "forever" */
1858 static unsigned long
1859 apple2_draw (Display *dpy, Window window, void *closure)
1861 struct state *st = (struct state *) closure;
1865 st->controller = controllers[random() % (countof(controllers))];
1866 st->sim = apple2_start (dpy, window, st->duration, st->controller);
1869 if (! apple2_one_frame (st->sim)) {
1881 apple2_reshape (Display *dpy, Window window, void *closure,
1882 unsigned int w, unsigned int h)
1884 struct state *st = (struct state *) closure;
1886 analogtv_reconfigure (st->sim->dec);
1890 apple2_event (Display *dpy, Window window, void *closure, XEvent *event)
1892 struct state *st = (struct state *) closure;
1895 st->controller == terminal_controller &&
1896 event->xany.type == KeyPress) {
1897 terminal_keypress_handler (dpy, event, st->sim->controller_data);
1905 apple2_free (Display *dpy, Window window, void *closure)
1907 struct state *st = (struct state *) closure;
1909 st->sim->stepno = A2CONTROLLER_DONE;
1910 if (apple2_one_frame (st->sim))
1911 abort(); /* should have freed! */
1917 XSCREENSAVER_MODULE ("Apple2", apple2)