1 /* xscreensaver, Copyright (c) 1998-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 * 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"
32 #define countof(x) (sizeof((x))/sizeof((*x)))
34 #define SCREEN_COLS 40
35 #define SCREEN_ROWS 24
40 /* Given a bitmask, returns the position and width of the field.
43 decode_mask (unsigned int mask, unsigned int *pos_ret, unsigned int *size_ret)
46 for (i = 0; i < 32; i++)
51 for (; i < 32; i++, j++)
52 if (! (mask & (1L << i)))
60 /* Given a value and a field-width, expands the field to fill out 8 bits.
63 spread_bits (unsigned char value, unsigned char width)
68 case 7: return (value << 1) | (value >> 6);
69 case 6: return (value << 2) | (value >> 4);
70 case 5: return (value << 3) | (value >> 2);
71 case 4: return (value << 4) | (value);
72 case 3: return (value << 5) | (value << 2) | (value >> 2);
73 case 2: return (value << 6) | (value << 4) | (value);
74 default: abort(); break;
79 /* Convert an XImage (of any size/depth/visual) to a 32bpp RGB array.
80 Scales it (without dithering) to WxH.
83 scale_image (Display *dpy, Window window, XImage *in,
84 int fromx, int fromy, int fromw, int fromh,
85 unsigned int *out, int w, int h)
89 unsigned int rpos=0, gpos=0, bpos=0; /* bitfield positions */
90 unsigned int rsiz=0, gsiz=0, bsiz=0;
91 unsigned int rmsk=0, gmsk=0, bmsk=0;
92 unsigned char spread_map[3][256];
93 XWindowAttributes xgwa;
96 if (fromx + fromw > in->width ||
97 fromy + fromh > in->height)
100 XGetWindowAttributes (dpy, window, &xgwa);
102 /* Compute the field offsets for RGB decoding in the XImage,
103 when in TrueColor mode. Otherwise we use the colormap.
105 if (visual_class (xgwa.screen, xgwa.visual) == PseudoColor ||
106 visual_class (xgwa.screen, xgwa.visual) == GrayScale)
108 int ncolors = visual_cells (xgwa.screen, xgwa.visual);
109 colors = (XColor *) calloc (sizeof (*colors), ncolors+1);
110 for (i = 0; i < ncolors; i++)
112 XQueryColors (dpy, xgwa.colormap, colors, ncolors);
116 rmsk = xgwa.visual->red_mask;
117 gmsk = xgwa.visual->green_mask;
118 bmsk = xgwa.visual->blue_mask;
119 decode_mask (rmsk, &rpos, &rsiz);
120 decode_mask (gmsk, &gpos, &gsiz);
121 decode_mask (bmsk, &bpos, &bsiz);
123 for (i = 0; i < 256; i++)
125 spread_map[0][i] = spread_bits (i, rsiz);
126 spread_map[1][i] = spread_bits (i, gsiz);
127 spread_map[2][i] = spread_bits (i, bsiz);
131 scale = (fromw > fromh
133 : (float) fromh / h);
135 /* Scale the pixmap from window size to Apple][ screen size (but 32bpp)
137 for (y = 0; y < h-1; y++) /* iterate over dest pixels */
138 for (x = 0; x < w-1; x++)
141 unsigned int r=0, g=0, b=0;
143 int xx1 = x * scale + fromx;
144 int yy1 = y * scale + fromy;
145 int xx2 = (x+1) * scale + fromx;
146 int yy2 = (y+1) * scale + fromy;
148 /* Iterate over the source pixels contributing to this one, and sum. */
149 for (xx = xx1; xx < xx2; xx++)
150 for (yy = yy1; yy < yy2; yy++)
152 unsigned char rr, gg, bb;
153 unsigned long sp = ((xx > in->width || yy > in->height)
154 ? 0 : XGetPixel (in, xx, yy));
157 rr = colors[sp].red & 0xFF;
158 gg = colors[sp].green & 0xFF;
159 bb = colors[sp].blue & 0xFF;
163 rr = (sp & rmsk) >> rpos;
164 gg = (sp & gmsk) >> gpos;
165 bb = (sp & bmsk) >> bpos;
166 rr = spread_map[0][rr];
167 gg = spread_map[1][gg];
168 bb = spread_map[2][bb];
175 /* Scale summed pixel values down to 8/8/8 range */
176 i = (xx2 - xx1) * (yy2 - yy1);
182 out[y * w + x] = (r << 16) | (g << 8) | b;
187 /* Convert an XImage (of any size/depth/visual) to a 32bpp RGB array.
188 Picks a random sub-image out of the source image, and scales it to WxH.
191 pick_a2_subimage (Display *dpy, Window window, XImage *in,
192 unsigned int *out, int w, int h)
194 int fromx, fromy, fromw, fromh;
195 if (in->width <= w || in->height <= h)
206 double scale = (0.5 + frand(0.7) + frand(0.7) + frand(0.7));
209 } while (fromw > in->width ||
212 dw = (in->width - fromw) / 2; /* near the center! */
213 dh = (in->height - fromh) / 2;
215 fromx = (dw <= 0 ? 0 : (random() % dw) + (dw/2));
216 fromy = (dh <= 0 ? 0 : (random() % dh) + (dh/2));
219 scale_image (dpy, window, in,
220 fromx, fromy, fromw, fromh,
225 /* Floyd-Steinberg dither. Derived from ppmquant.c,
226 Copyright (c) 1989, 1991 by Jef Poskanzer.
229 a2_dither (unsigned int *in, unsigned char *out, int w, int h)
232 Apple ][ color map. Each pixel can only be 1 or 0, but what that
233 means depends on whether it's an odd or even pixel, and whether
234 the high bit in the byte is set or not. If it's 0, it's always
237 static const int a2_cmap[2][2][3] = {
240 {/* odd pixels = blue */ 0x00, 0x80, 0xff},
241 {/* even pixels = red */ 0xff, 0x80, 0x00}
245 {/* even pixels = purple */ 0xa0, 0x40, 0xa0},
246 {/* odd pixels = green */ 0x40, 0xff, 0x40}
251 unsigned int **pixels;
266 FILE *pipe = popen ("xv -", "w");
267 fprintf (pipe, "P6\n%d %d\n%d\n", w, h, 255);
268 for (y = 0; y < h; y++)
269 for (x = 0; x < w; x++)
271 unsigned int p = in[y * w + x];
272 unsigned int r = (p >> 16) & 0xFF;
273 unsigned int g = (p >> 8) & 0xFF;
274 unsigned int b = (p ) & 0xFF;
275 fprintf(pipe, "%c%c%c", r, g, b);
281 /* Initialize Floyd-Steinberg error vectors. */
282 this_rerr = (long *) calloc (w + 2, sizeof(long));
283 next_rerr = (long *) calloc (w + 2, sizeof(long));
284 this_gerr = (long *) calloc (w + 2, sizeof(long));
285 next_gerr = (long *) calloc (w + 2, sizeof(long));
286 this_berr = (long *) calloc (w + 2, sizeof(long));
287 next_berr = (long *) calloc (w + 2, sizeof(long));
290 /* #### do we really need more than one element of "pixels" at once?
292 pixels = (unsigned int **) malloc (h * sizeof (unsigned int *));
293 for (y = 0; y < h; y++)
294 pixels[y] = (unsigned int *) malloc (w * sizeof (unsigned int));
296 for (x = 0; x < w + 2; ++x)
298 this_rerr[x] = random() % (fs_scale * 2) - fs_scale;
299 this_gerr[x] = random() % (fs_scale * 2) - fs_scale;
300 this_berr[x] = random() % (fs_scale * 2) - fs_scale;
301 /* (random errors in [-1 .. 1]) */
304 for (y = 0; y < h; y++)
305 for (x = 0; x < w; x++)
306 pixels[y][x] = in[y * w + x];
308 for (y = 0; y < h; y++)
314 for (x = 0; x < w + 2; x++)
315 next_rerr[x] = next_gerr[x] = next_berr[x] = 0;
317 /* It's too complicated to go back and forth on alternate rows,
318 so we always go left-right here. It doesn't change the result
321 For each group of 7 pixels, we have to try it both with the
322 high bit=0 and =1. For each high bit value, we add up the
323 total error and pick the best one.
325 Because we have to go through each group of bits twice, we
326 don't propagate the error values through this_[rgb]err since
327 it would add them twice. So we keep seperate local_[rgb]err
328 variables for propagating error within the 7-pixel group.
332 for (xbyte=0; xbyte<280; xbyte+=7)
335 int best_error=2000000000;
339 int local_rerr=0, local_gerr=0, local_berr=0;
341 for (hibit=0; hibit<2; hibit++)
346 for (x=xbyte; x<xbyte+7; x++)
350 /* Use Floyd-Steinberg errors to adjust actual color. */
351 sr = ((pP[x] >> 16) & 0xFF) * brightness/256;
352 sg = ((pP[x] >> 8) & 0xFF) * brightness/256;
353 sb = ((pP[x] ) & 0xFF) * brightness/256;
354 sr += (this_rerr[x + 1] + local_rerr) / fs_scale;
355 sg += (this_gerr[x + 1] + local_gerr) / fs_scale;
356 sb += (this_berr[x + 1] + local_berr) / fs_scale;
359 else if (sr > maxval) sr = maxval;
361 else if (sg > maxval) sg = maxval;
363 else if (sb > maxval) sb = maxval;
365 /* This is the color we'd get if we set the bit 1. For 0,
367 r2=a2_cmap[hibit][x&1][0];
368 g2=a2_cmap[hibit][x&1][1];
369 b2=a2_cmap[hibit][x&1][2];
372 dist0 and dist1 are the error (Minkowski 2-metric
373 distances in the color space) for choosing 0 and
374 1 respectively. 0 is black, 1 is the color r2,g2,b2.
376 dist1= (sr-r2)*(sr-r2) + (sg-g2)*(sg-g2) + (sb-b2)*(sb-b2);
377 dist0= sr*sr + sg*sg + sb*sb;
381 byte |= 1 << (x-xbyte);
384 /* Wanted sr but got r2, so propagate sr-r2 */
385 local_rerr = (sr - r2) * fs_scale * 7/16;
386 local_gerr = (sg - g2) * fs_scale * 7/16;
387 local_berr = (sb - b2) * fs_scale * 7/16;
393 /* Wanted sr but got 0, so propagate sr */
394 local_rerr = sr * fs_scale * 7/16;
395 local_gerr = sg * fs_scale * 7/16;
396 local_berr = sb * fs_scale * 7/16;
400 if (tot_error < best_error)
403 best_error = tot_error;
407 /* Avoid alternating 7f and ff in all-white areas, because it makes
408 regular pink vertical lines */
409 if ((best_byte&0x7f)==0x7f && (prev_byte&0x7f)==0x7f)
414 Now that we've chosen values for all 8 bits of the byte, we
415 have to fill in the real pixel values into pP and propagate
416 all the error terms. We end up repeating a lot of the code
420 for (x=xbyte; x<xbyte+7; x++)
422 int bit=(best_byte>>(x-xbyte))&1;
423 hibit=(best_byte>>7)&1;
425 sr = (pP[x] >> 16) & 0xFF;
426 sg = (pP[x] >> 8) & 0xFF;
427 sb = (pP[x] ) & 0xFF;
428 sr += this_rerr[x + 1] / fs_scale;
429 sg += this_gerr[x + 1] / fs_scale;
430 sb += this_berr[x + 1] / fs_scale;
433 else if (sr > maxval) sr = maxval;
435 else if (sg > maxval) sg = maxval;
437 else if (sb > maxval) sb = maxval;
439 r2=a2_cmap[hibit][x&1][0] * bit;
440 g2=a2_cmap[hibit][x&1][1] * bit;
441 b2=a2_cmap[hibit][x&1][2] * bit;
443 pP[x] = (r2<<16) | (g2<<8) | (b2);
445 /* Propagate Floyd-Steinberg error terms. */
446 err = (sr - r2) * fs_scale;
447 this_rerr[x + 2] += (err * 7) / 16;
448 next_rerr[x ] += (err * 3) / 16;
449 next_rerr[x + 1] += (err * 5) / 16;
450 next_rerr[x + 2] += (err ) / 16;
451 err = (sg - g2) * fs_scale;
452 this_gerr[x + 2] += (err * 7) / 16;
453 next_gerr[x ] += (err * 3) / 16;
454 next_gerr[x + 1] += (err * 5) / 16;
455 next_gerr[x + 2] += (err ) / 16;
456 err = (sb - b2) * fs_scale;
457 this_berr[x + 2] += (err * 7) / 16;
458 next_berr[x ] += (err * 3) / 16;
459 next_berr[x + 1] += (err * 5) / 16;
460 next_berr[x + 2] += (err ) / 16;
464 And put the actual byte into out.
467 out[y*(w/7) + xbyte/7] = best_byte;
471 temp_err = this_rerr;
472 this_rerr = next_rerr;
473 next_rerr = temp_err;
474 temp_err = this_gerr;
475 this_gerr = next_gerr;
476 next_gerr = temp_err;
477 temp_err = this_berr;
478 this_berr = next_berr;
479 next_berr = temp_err;
495 /* let's see what we got... */
496 FILE *pipe = popen ("xv -", "w");
497 fprintf (pipe, "P6\n%d %d\n%d\n", w, h, 255);
498 for (y = 0; y < h; y++)
499 for (x = 0; x < w; x++)
501 unsigned int r = (pixels[y][x]>>16)&0xff;
502 unsigned int g = (pixels[y][x]>>8)&0xff;
503 unsigned int b = (pixels[y][x]>>0)&0xff;
504 fprintf(pipe, "%c%c%c", r, g, b);
511 typedef struct slideshow_data_s {
513 int render_img_lineno;
514 unsigned char *render_img;
516 Bool image_loading_p;
521 image_loaded_cb (Screen *screen, Window window, Drawable p,
522 const char *name, XRectangle *geometry,
525 Display *dpy = DisplayOfScreen (screen);
526 apple2_sim_t *sim = (apple2_sim_t *) closure;
527 slideshow_data *mine = (slideshow_data *) sim->controller_data;
528 XWindowAttributes xgwa;
532 unsigned int *buf32 = (unsigned int *) calloc (w, h * 4);
533 unsigned char *buf8 = (unsigned char *) calloc (w/7, h);
537 fprintf (stderr, "%s: out of memory (%dx%d)\n", progname, w, h);
541 XGetWindowAttributes (dpy, window, &xgwa);
543 image = XGetImage (dpy, p, 0, 0, xgwa.width, xgwa.height, ~0, ZPixmap);
544 XFreePixmap (dpy, p);
547 /* Scale the XImage down to Apple][ size, and convert it to a 32bpp
548 image (regardless of whether it started as TrueColor/PseudoColor.)
550 pick_a2_subimage (dpy, window, image, buf32, w, h);
553 XDestroyImage(image);
555 /* Then dither the 32bpp image to a 6-color Apple][ colormap.
557 a2_dither (buf32, buf8, w, h);
561 mine->image_loading_p = False;
562 mine->img_filename = (name ? strdup (name) : 0);
563 mine->render_img = buf8;
568 static const char *apple2_defaults [] = {
569 ".background: black",
570 ".foreground: white",
573 "*program: xscreensaver-text --cols 40",
574 "*metaSendsESC: True",
581 # endif /* !HAVE_FORKPTY */
587 static XrmOptionDescRec apple2_options [] = {
588 { "-mode", ".mode", XrmoptionSepArg, 0 },
589 { "-slideshow", ".mode", XrmoptionNoArg, "slideshow" },
590 { "-basic", ".mode", XrmoptionNoArg, "basic" },
591 { "-text", ".mode", XrmoptionNoArg, "text" },
592 { "-program", ".program", XrmoptionSepArg, 0 },
593 { "-duration", ".duration", XrmoptionSepArg, 0 },
594 { "-pty", ".usePty", XrmoptionNoArg, "True" },
595 { "-pipe", ".usePty", XrmoptionNoArg, "False" },
596 { "-meta", ".metaSendsESC", XrmoptionNoArg, "False" },
597 { "-esc", ".metaSendsESC", XrmoptionNoArg, "True" },
598 { "-bs", ".swapBSDEL", XrmoptionNoArg, "False" },
599 { "-del", ".swapBSDEL", XrmoptionNoArg, "True" },
600 { "-fast", ".fast", XrmoptionNoArg, "True" },
606 TODO: this should load 10 images at startup time, then cycle through them
607 to avoid the pause while it loads.
610 static void slideshow_controller(apple2_sim_t *sim, int *stepno,
611 double *next_actiontime)
613 apple2_state_t *st=sim->st;
615 slideshow_data *mine;
617 if (!sim->controller_data)
618 sim->controller_data = calloc (1, sizeof(*mine));
619 mine = (slideshow_data *) sim->controller_data;
627 sim->typing_rate = 0.3;
628 sim->dec->powerup=0.0;
631 a2_prints(st, "APPLE ][");
640 XWindowAttributes xgwa;
642 XGetWindowAttributes (sim->dpy, sim->window, &xgwa);
643 p = XCreatePixmap (sim->dpy, sim->window, xgwa.width, xgwa.height,
645 mine->image_loading_p = True;
646 load_image_async (xgwa.screen, sim->window, p, image_loaded_cb, sim);
648 /* pause with a blank screen for a bit, while the image loads in the
650 *next_actiontime += 2.0;
656 if (! mine->image_loading_p) { /* image is finally loaded */
662 *next_actiontime += 3.0;
677 st->gr_mode=A2_GR_HIRES;
678 if (mine->img_filename) {
679 char *basename, *tmp;
682 basename = tmp = strdup (mine->img_filename);
685 char *slash = strchr(basename, '/');
686 if (!slash || !slash[1]) break;
690 char *dot=strrchr(basename,'.');
693 if (strlen(basename)>20) basename[20]=0;
694 for (s=basename; *s; s++) {
696 if (*s <= ' ') *s = '_';
698 sprintf(sim->typing_buf, "BLOAD %s\n", basename);
699 sim->typing = sim->typing_buf;
703 sim->typing = "BLOAD IMAGE\n";
705 mine->render_img_lineno=0;
711 *next_actiontime += 0.7;
716 if (mine->render_img_lineno>=192) {
718 sim->typing="POKE 49234,0\n";
723 for (i=0; i<6 && mine->render_img_lineno<192; i++) {
724 a2_display_image_loading(st, mine->render_img,
725 mine->render_img_lineno++);
728 /* The disk would have to seek every 13 sectors == 78 lines.
729 (This ain't no newfangled 16-sector operating system) */
730 if ((mine->render_img_lineno%78)==0) {
731 *next_actiontime += 0.5;
733 *next_actiontime += 0.08;
738 st->gr_mode |= A2_GR_FULL;
740 /* Note that sim->delay is sometimes "infinite" in this controller.
741 These images are kinda dull anyway, so don't leave it on too long. */
742 *next_actiontime += 2;
747 sim->typing="POKE 49235,0\n";
753 st->gr_mode &= ~A2_GR_FULL;
754 if (mine->render_img) {
755 free(mine->render_img);
756 mine->render_img=NULL;
758 if (mine->img_filename) {
759 free(mine->img_filename);
760 mine->img_filename=NULL;
765 case A2CONTROLLER_FREE:
766 free(mine->render_img);
767 free(mine->img_filename);
777 struct terminal_controller_data {
781 double last_emit_time;
787 int cursor_x, cursor_y;
788 int saved_x, saved_y;
791 unsigned int bold : 1;
792 unsigned int blink : 1;
793 unsigned int rev : 1;
802 /* The structure of closure linkage throughout this code is so amazingly
803 baroque that I can't get to the 'struct state' from where I need it. */
804 static const char *global_program;
805 static Bool global_fast_p;
809 terminal_closegen(struct terminal_controller_data *mine)
812 textclient_close (mine->tc);
818 terminal_read(struct terminal_controller_data *mine, unsigned char *buf, int n)
820 if (!mine || !mine->tc) {
824 for (i = 0; i < n; i++) {
825 int c = textclient_getc (mine->tc);
837 terminal_keypress_handler (Display *dpy, XEvent *event, void *data)
839 struct terminal_controller_data *mine =
840 (struct terminal_controller_data *) data;
842 if (event->xany.type == KeyPress && mine->tc)
843 return textclient_putc (mine->tc, &event->xkey);
849 a2_ascii_printc (apple2_state_t *st, unsigned char c,
850 Bool bold_p, Bool blink_p, Bool rev_p,
853 if (c >= 'a' && c <= 'z') /* upcase lower-case chars */
857 else if ((c >= 'A'+128) || /* upcase and blink */
858 (c < ' ' && c != 014 && /* high-bit & ctl chrs */
859 c != '\r' && c != '\n' && c!='\t'))
861 c = (c & 0x1F) | 0x80;
863 else if (c >= 'A' && c <= 'Z') /* invert upper-case chars */
868 if (bold_p) c |= 0xc0;
869 if (blink_p) c = (c & ~0x40) | 0x80;
870 if (rev_p) c |= 0xc0;
875 a2_printc_noscroll(st, c);
880 a2_vt100_printc (apple2_sim_t *sim, struct terminal_controller_data *state,
883 apple2_state_t *st=sim->st;
884 int cols = SCREEN_COLS;
885 int rows = SCREEN_ROWS;
890 switch (state->escstate)
896 /* Dummy case - we don't want the screensaver to beep */
897 /* #### But maybe this should flash the screen? */
900 if (state->cursor_x > 0)
904 if (state->cursor_x < cols - 8)
906 state->cursor_x = (state->cursor_x & ~7) + 8;
911 if (state->cursor_y < rows - 1)
918 # ifndef HAVE_FORKPTY
919 state->cursor_x = 0; /* No ptys on iPhone; assume CRLF. */
923 if (state->cursor_y < rows - 1)
933 /* Dummy case - there is one and only one font. */
937 /* Dummy case - these interrupt escape sequences, so
938 they don't do anything in this state */
944 /* Dummy case - this is supposed to be ignored */
948 for(i = 0; i < NPAR; i++)
949 state->csiparam[i] = 0;
953 /* If the cursor is in column 39 and we print a character, then
954 that character shows up in column 39, and the cursor is no longer
955 visible on the screen (it's in "column 40".) If another character
956 is printed, then that character shows up in column 0, and the
957 cursor moves to column 1.
959 This is empirically what xterm and gnome-terminal do, so that must
960 be the right thing. (In xterm, the cursor vanishes, whereas; in
961 gnome-terminal, the cursor overprints the character in col 39.)
963 if (state->cursor_x >= cols)
966 if (state->cursor_y >= rows - 1)
972 a2_goto(st, state->cursor_y, state->cursor_x); /* clips range */
973 a2_ascii_printc (st, c,
974 state->termattrib.bf.bold,
975 state->termattrib.bf.blink,
976 state->termattrib.bf.rev,
990 case 'c': /* Reset */
994 case 'D': /* Linefeed */
995 if (state->cursor_y < rows - 1)
1001 case 'E': /* Newline */
1002 state->cursor_x = 0;
1003 state->escstate = 0;
1005 case 'M': /* Reverse newline */
1006 if (state->cursor_y > 0)
1008 state->escstate = 0;
1010 case '7': /* Save state */
1011 state->saved_x = state->cursor_x;
1012 state->saved_y = state->cursor_y;
1013 state->escstate = 0;
1015 case '8': /* Restore state */
1016 state->cursor_x = state->saved_x;
1017 state->cursor_y = state->saved_y;
1018 state->escstate = 0;
1021 state->escstate = 2;
1022 for(i = 0; i < NPAR; i++)
1023 state->csiparam[i] = 0;
1024 state->curparam = 0;
1026 case '%': /* Select charset */
1027 /* No, I don't support UTF-8, since the apple2 font
1028 isn't even Unicode anyway. We must still catch the
1029 last byte, though. */
1032 /* I don't support different fonts either - see above
1034 state->escstate = 3;
1037 /* Escape sequences not supported:
1040 * Z - Terminal identification
1042 * = - Other keypad change
1045 state->escstate = 0;
1054 state->escstate = 0;
1056 case '0': case '1': case '2': case '3': case '4':
1057 case '5': case '6': case '7': case '8': case '9':
1058 if (state->curparam < NPAR)
1059 state->csiparam[state->curparam] =
1060 (state->csiparam[state->curparam] * 10) + (c - '0');
1063 state->csiparam[++state->curparam] = 0;
1066 state->escstate = 3;
1069 for (i = 0; i < state->csiparam[0]; i++)
1071 if(++state->cursor_x > cols)
1073 state->cursor_x = 0;
1074 if (state->cursor_y < rows - 1)
1080 state->escstate = 0;
1083 state->cursor_x = 0;
1085 if (state->csiparam[0] == 0)
1086 state->csiparam[0] = 1;
1087 if ((state->cursor_y -= state->csiparam[0]) < 0)
1088 state->cursor_y = 0;
1089 state->escstate = 0;
1092 state->cursor_x = 0;
1095 if (state->csiparam[0] == 0)
1096 state->csiparam[0] = 1;
1097 if ((state->cursor_y += state->csiparam[0]) >= rows)
1098 state->cursor_y = rows - 1;
1099 state->escstate = 0;
1103 if (state->csiparam[0] == 0)
1104 state->csiparam[0] = 1;
1105 if ((state->cursor_x += state->csiparam[0]) >= cols)
1106 state->cursor_x = cols - 1;
1107 state->escstate = 0;
1110 if (state->csiparam[0] == 0)
1111 state->csiparam[0] = 1;
1112 if ((state->cursor_x -= state->csiparam[0]) < 0)
1113 state->cursor_x = 0;
1114 state->escstate = 0;
1117 if ((state->cursor_y = (state->csiparam[0] - 1)) >= rows)
1118 state->cursor_y = rows - 1;
1119 state->escstate = 0;
1123 if ((state->cursor_x = (state->csiparam[0] - 1)) >= cols)
1124 state->cursor_x = cols - 1;
1125 state->escstate = 0;
1129 if ((state->cursor_y = (state->csiparam[0] - 1)) >= rows)
1130 state->cursor_y = rows - 1;
1131 if ((state->cursor_x = (state->csiparam[1] - 1)) >= cols)
1132 state->cursor_x = cols - 1;
1133 if(state->cursor_y < 0)
1134 state->cursor_y = 0;
1135 if(state->cursor_x < 0)
1136 state->cursor_x = 0;
1137 state->escstate = 0;
1142 if (state->csiparam[0] == 0)
1143 start = cols * state->cursor_y + state->cursor_x;
1144 if (state->csiparam[0] == 1)
1145 end = cols * state->cursor_y + state->cursor_x;
1147 a2_goto(st, state->cursor_y, state->cursor_x);
1148 for (i = start; i < end; i++)
1150 a2_ascii_printc(st, ' ', False, False, False, False);
1152 state->escstate = 0;
1157 if (state->csiparam[0] == 0)
1158 start = state->cursor_x;
1159 if (state->csiparam[1] == 1)
1160 end = state->cursor_x;
1162 a2_goto(st, state->cursor_y, state->cursor_x);
1163 for (i = start; i < end; i++)
1165 a2_ascii_printc(st, ' ', False, False, False, False);
1167 state->escstate = 0;
1169 case 'm': /* Set attributes */
1170 for (i = 0; i <= state->curparam; i++)
1172 switch(state->csiparam[i])
1175 state->termattrib.w = 0;
1178 state->termattrib.bf.bold = 1;
1181 state->termattrib.bf.blink = 1;
1184 state->termattrib.bf.rev = 1;
1188 state->termattrib.bf.bold = 0;
1191 state->termattrib.bf.blink = 0;
1194 state->termattrib.bf.rev = 0;
1198 state->escstate = 0;
1200 case 's': /* Save position */
1201 state->saved_x = state->cursor_x;
1202 state->saved_y = state->cursor_y;
1203 state->escstate = 0;
1205 case 'u': /* Restore position */
1206 state->cursor_x = state->saved_x;
1207 state->cursor_y = state->saved_y;
1208 state->escstate = 0;
1210 case '?': /* DEC Private modes */
1211 if ((state->curparam != 0) || (state->csiparam[0] != 0))
1212 state->escstate = 0;
1215 /* Known unsupported CSIs:
1217 * L - Insert blank lines
1218 * M - Delete lines (I don't know what this means...)
1219 * P - Delete characters
1220 * X - Erase characters (difference with P being...?)
1221 * c - Terminal identification
1222 * g - Clear tab stop(s)
1223 * h - Set mode (Mainly due to its complexity and lack of good
1226 * m - Set mode (Phosphor is, per defenition, green on black)
1228 * q - Set keyboard LEDs
1229 * r - Set scrolling region (too exhausting - noone uses this,
1232 state->escstate = 0;
1237 state->escstate = 0;
1240 a2_goto(st, state->cursor_y, state->cursor_x);
1245 It's fun to put things like "gdb" as the command. For one, it's
1246 amusing how the standard mumble (version, no warranty, it's
1247 GNU/Linux dammit) occupies an entire screen on the Apple ][.
1251 terminal_controller(apple2_sim_t *sim, int *stepno, double *next_actiontime)
1253 apple2_state_t *st=sim->st;
1257 struct terminal_controller_data *mine;
1258 if (!sim->controller_data)
1259 sim->controller_data=calloc(sizeof(struct terminal_controller_data),1);
1260 mine=(struct terminal_controller_data *) sim->controller_data;
1261 mine->dpy = sim->dpy;
1263 mine->fast_p = global_fast_p;
1269 st->gr_mode |= A2_GR_FULL; /* Turn on color mode even through it's
1273 a2_prints(st, "APPLE ][");
1278 mine->tc = textclient_open (mine->dpy);
1279 textclient_reshape (mine->tc,
1280 SCREEN_COLS, SCREEN_ROWS,
1281 SCREEN_COLS, SCREEN_ROWS);
1285 *next_actiontime += 4.0;
1291 unsigned char buf[1024];
1295 elapsed=sim->curtime - mine->last_emit_time;
1296 mine->last_emit_time=sim->curtime;
1298 if (elapsed>1.0) nwant=1;
1299 if (nwant<1) nwant=1;
1300 if (nwant>4) nwant=4;
1303 nwant = sizeof(buf)-1;
1305 nr=terminal_read(mine, buf, nwant);
1306 for (i=0; i<nr; i++) {
1310 a2_vt100_printc (sim, mine, c);
1312 a2_ascii_printc (st, c, False, False, False, True);
1317 case A2CONTROLLER_FREE:
1318 terminal_closegen(mine);
1325 struct basic_controller_data {
1328 const char * const * progtext;
1332 double prog_start_time;
1333 char error_buf[256];
1337 Adding more programs is easy. Just add a listing here and to all_programs,
1338 then add the state machine to actually execute it to basic_controller.
1340 static const char * const moire_program[]={
1342 "20 FOR Y = 0 TO 190 STEP 2\n",
1343 "30 HCOLOR=4 : REM BLACK\n",
1344 "40 HPLOT 0,191-Y TO 279,Y\n",
1345 "60 HCOLOR=7 : REM WHITE\n",
1346 "80 HPLOT 0,190-Y TO 279,Y+1\n",
1348 "100 FOR X = 0 TO 278 STEP 3\n",
1350 "120 HPLOT 279-X,0 TO X,191\n",
1352 "150 HPLOT 278-X,0 TO X+1,191\n",
1357 static const char * const sinewave_program[] = {
1360 "30 FOR X = 0 TO 279\n",
1362 "35 HPLOT X,0 TO X,159\n",
1364 "40 Y = 80 + SIN(15*(X-K)/279) * 40\n",
1373 static const char * const dumb_program[]={
1374 "10 PRINT \"APPLE ][ ROOLZ! TRS-80 DROOLZ!\"\n",
1380 static const char * const random_lores_program[]={
1381 "1 REM APPLE ][ SCREEN SAVER\n",
1383 "100 COLOR= RND(1)*16\n",
1385 "110 X=RND(1)*40\n",
1386 "120 Y1=RND(1)*40\n",
1387 "130 Y2=RND(1)*40\n",
1388 "140 FOR Y = Y1 TO Y2\n",
1392 "210 Y=RND(1)*40\n",
1393 "220 X1=RND(1)*40\n",
1394 "230 X2=RND(1)*40\n",
1395 "240 FOR X = X1 TO X2\n",
1403 static char typo_map[256];
1405 static int make_typo(char *out_buf, const char *orig, char *err_buf)
1465 strcpy(out_buf, orig);
1466 for (i=0; out_buf[i]; i++) {
1467 char *p = out_buf+i;
1469 if (i>2 && p[-2]=='R' && p[-1]=='E' && p[0]=='M')
1472 if (isalpha(p[0]) &&
1481 sprintf(err_buf,"?SYNTAX ERROR\n");
1485 if (random()%10==0 && strlen(p)>=4 && (errc=typo_map[(int)(unsigned char)p[0]])) {
1486 int remain=strlen(p);
1487 int past=random()%(remain-2)+1;
1488 memmove(p+past+past, p, remain+1);
1490 for (j=0; j<past; j++) {
1499 static const struct {
1500 const char * const * progtext;
1503 {moire_program, 100},
1504 /*{dumb_program, 200}, */
1505 {sinewave_program, 400},
1506 {random_lores_program, 500},
1510 basic_controller(apple2_sim_t *sim, int *stepno, double *next_actiontime)
1512 apple2_state_t *st=sim->st;
1515 struct basic_controller_data *mine;
1516 if (!sim->controller_data)
1517 sim->controller_data=calloc(sizeof(struct basic_controller_data),1);
1518 mine=(struct basic_controller_data *) sim->controller_data;
1525 a2_prints(st, "APPLE ][");
1528 sim->typing_rate=0.2;
1530 i=random()%countof(all_programs);
1531 mine->progtext=all_programs[i].progtext;
1532 mine->progstep=all_programs[i].progstep;
1535 *next_actiontime += 1.0;
1540 if (st->cursx==0) a2_printc(st,']');
1541 if (mine->progtext[mine->prog_line]) {
1542 if (random()%4==0) {
1543 int err=make_typo(sim->typing_buf,
1544 mine->progtext[mine->prog_line],
1546 sim->typing=sim->typing_buf;
1553 sim->typing=mine->progtext[mine->prog_line++];
1561 sim->printing=mine->error_buf;
1566 if (st->cursx==0) a2_printc(st,']');
1567 *next_actiontime+=1.0;
1572 sim->typing="RUN\n";
1576 mine->prog_start_time=*next_actiontime;
1577 *stepno=mine->progstep;
1582 st->gr_mode=A2_GR_HIRES|A2_GR_FULL;
1583 for (i=0; i<24 && mine->y<192; i++)
1585 a2_hline(st, 4, 0, 191-mine->y, 279, mine->y);
1586 a2_hline(st, 7, 0, 191-mine->y-1, 279, mine->y+1);
1596 for (i=0; i<24 && mine->x<280; i++)
1598 a2_hline(st, 4, 279-mine->x, 0, mine->x, 192);
1599 a2_hline(st, 7, 279-mine->x-1, 0, mine->x+1, 192);
1602 if (mine->x >= 280) *stepno=120;
1606 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1611 mine->rep_str="\nAPPLE ][ ROOLZ! TRS-80 DROOLZ!";
1612 for (i=0; i<30; i++) {
1613 a2_prints(st, mine->rep_str);
1619 i=random()%strlen(mine->rep_str);
1620 while (mine->rep_pos != i) {
1621 a2_printc(st, mine->rep_str[mine->rep_pos]);
1623 if (!mine->rep_str[mine->rep_pos]) mine->rep_pos=0;
1625 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1628 /* sinewave_program */
1630 st->gr_mode=A2_GR_HIRES;
1635 for (i=0; i<48; i++) {
1636 int y=80 + (int)(75.0*sin(15.0*(mine->x-mine->k)/279.0));
1637 a2_hline(st, 0, mine->x, 0, mine->x, 159);
1638 a2_hplot(st, 3, mine->x, y);
1645 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1653 /* random_lores_program */
1655 st->gr_mode=A2_GR_LORES|A2_GR_FULL;
1660 for (i=0; i<10; i++) {
1661 int color,x,y,x1,x2,y1,y2;
1667 for (y=y1; y<y2; y++) a2_plot(st, color, x, y);
1672 for (x=x1; x<x2; x++) a2_plot(st, color, x, y);
1674 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1681 case A2CONTROLLER_FREE:
1689 static void (* const controllers[]) (apple2_sim_t *sim, int *stepno,
1690 double *next_actiontime) = {
1691 slideshow_controller,
1692 terminal_controller,
1700 void (*controller) (apple2_sim_t *sim, int *stepno, double *next_actiontime);
1705 apple2_init (Display *dpy, Window window)
1707 struct state *st = (struct state *) calloc (1, sizeof(*st));
1710 st->duration = get_integer_resource (dpy, "duration", "Integer");
1713 if (st->duration < 1) st->duration = 1;
1715 s = get_string_resource (dpy, "mode", "Mode");
1716 if (!s || !*s || !strcasecmp(s, "random"))
1717 st->random_p = True;
1718 else if (!strcasecmp(s, "text"))
1719 st->controller = terminal_controller;
1720 else if (!strcasecmp(s, "slideshow"))
1721 st->controller = slideshow_controller;
1722 else if (!strcasecmp(s, "basic"))
1723 st->controller = basic_controller;
1726 fprintf (stderr, "%s: mode must be text, slideshow, or random; not %s\n",
1732 global_program = get_string_resource (dpy, "program", "Program");
1733 global_fast_p = get_boolean_resource (dpy, "fast", "Boolean");
1736 /* Kludge for MacOS standalone mode: see OSX/SaverRunner.m. */
1738 const char *s = getenv ("XSCREENSAVER_STANDALONE");
1739 if (s && *s && strcmp(s, "0"))
1741 st->controller = terminal_controller;
1742 st->random_p = False;
1743 global_program = getenv ("SHELL");
1744 global_fast_p = True;
1749 if (! st->random_p) {
1750 if (st->controller == terminal_controller ||
1751 st->controller == slideshow_controller)
1752 st->duration = 999999; /* these run "forever" */
1758 static unsigned long
1759 apple2_draw (Display *dpy, Window window, void *closure)
1761 struct state *st = (struct state *) closure;
1765 st->controller = controllers[random() % (countof(controllers))];
1766 st->sim = apple2_start (dpy, window, st->duration, st->controller);
1769 if (! apple2_one_frame (st->sim)) {
1777 apple2_reshape (Display *dpy, Window window, void *closure,
1778 unsigned int w, unsigned int h)
1780 struct state *st = (struct state *) closure;
1782 analogtv_reconfigure (st->sim->dec);
1786 apple2_event (Display *dpy, Window window, void *closure, XEvent *event)
1788 struct state *st = (struct state *) closure;
1791 st->controller == terminal_controller &&
1792 event->xany.type == KeyPress) {
1793 terminal_keypress_handler (dpy, event, st->sim->controller_data);
1801 apple2_free (Display *dpy, Window window, void *closure)
1803 struct state *st = (struct state *) closure;
1805 st->sim->stepno = A2CONTROLLER_DONE;
1806 if (apple2_one_frame (st->sim))
1807 abort(); /* should have freed! */
1813 XSCREENSAVER_MODULE ("Apple2", apple2)