From d3a98cf6cbc3bd0b9efc570f58e8812c03931c18 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Tue, 16 Oct 2018 10:08:48 +0200 Subject: Original 5.40 --- hacks/apple2-main.c | 1913 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1913 insertions(+) create mode 100644 hacks/apple2-main.c (limited to 'hacks/apple2-main.c') diff --git a/hacks/apple2-main.c b/hacks/apple2-main.c new file mode 100644 index 0000000..3063b58 --- /dev/null +++ b/hacks/apple2-main.c @@ -0,0 +1,1913 @@ +/* xscreensaver, Copyright (c) 1998-2014 Jamie Zawinski + * + * Permission to use, copy, modify, distribute, and sell this software and its + * documentation for any purpose is hereby granted without fee, provided that + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + * + * Apple ][ CRT simulator, by Trevor Blackwell + * with additional work by Jamie Zawinski + * Pty and vt100 emulation by Fredrik Tolf + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include +#include + +#ifdef HAVE_UNISTD_H +# include +#endif + +#include "screenhack.h" +#include "apple2.h" +#include "textclient.h" +#include "utf8wc.h" + +#undef countof +#define countof(x) (sizeof((x))/sizeof((*x))) + +#define SCREEN_COLS 40 +#define SCREEN_ROWS 24 + + +/* Given a bitmask, returns the position and width of the field. + */ +static void +decode_mask (unsigned int mask, unsigned int *pos_ret, unsigned int *size_ret) +{ + int i; + for (i = 0; i < 32; i++) + if (mask & (1L << i)) + { + int j = 0; + *pos_ret = i; + for (; i < 32; i++, j++) + if (! (mask & (1L << i))) + break; + *size_ret = j; + return; + } +} + + +/* Given a value and a field-width, expands the field to fill out 8 bits. + */ +static unsigned char +spread_bits (unsigned char value, unsigned char width) +{ + switch (width) + { + case 8: return value; + case 7: return (value << 1) | (value >> 6); + case 6: return (value << 2) | (value >> 4); + case 5: return (value << 3) | (value >> 2); + case 4: return (value << 4) | (value); + case 3: return (value << 5) | (value << 2) | (value >> 2); + case 2: return (value << 6) | (value << 4) | (value); + default: abort(); break; + } +} + + +/* Convert an XImage (of any size/depth/visual) to a 32bpp RGB array. + Scales it (without dithering) to WxH. + */ +static void +scale_image (Display *dpy, Window window, XImage *in, + int fromx, int fromy, int fromw, int fromh, + unsigned int *out, int w, int h) +{ + float scale; + int x, y, i; + unsigned int rpos=0, gpos=0, bpos=0; /* bitfield positions */ + unsigned int rsiz=0, gsiz=0, bsiz=0; + unsigned long rmsk=0, gmsk=0, bmsk=0; + unsigned char spread_map[3][256]; + XWindowAttributes xgwa; + XColor *colors = 0; + + if (fromx + fromw > in->width || + fromy + fromh > in->height) + abort(); + + XGetWindowAttributes (dpy, window, &xgwa); + + /* Compute the field offsets for RGB decoding in the XImage, + when in TrueColor mode. Otherwise we use the colormap. + */ + if (visual_class (xgwa.screen, xgwa.visual) == PseudoColor || + visual_class (xgwa.screen, xgwa.visual) == GrayScale) + { + int ncolors = visual_cells (xgwa.screen, xgwa.visual); + colors = (XColor *) calloc (sizeof (*colors), ncolors+1); + for (i = 0; i < ncolors; i++) + colors[i].pixel = i; + XQueryColors (dpy, xgwa.colormap, colors, ncolors); + } + else + { + visual_rgb_masks (xgwa.screen, xgwa.visual, &rmsk, &gmsk, &bmsk); + decode_mask (rmsk, &rpos, &rsiz); + decode_mask (gmsk, &gpos, &gsiz); + decode_mask (bmsk, &bpos, &bsiz); + + for (i = 0; i < 256; i++) + { + spread_map[0][i] = spread_bits (i, rsiz); + spread_map[1][i] = spread_bits (i, gsiz); + spread_map[2][i] = spread_bits (i, bsiz); + } + } + + scale = (fromw > fromh + ? (float) fromw / w + : (float) fromh / h); + + /* Scale the pixmap from window size to Apple][ screen size (but 32bpp) + */ + for (y = 0; y < h-1; y++) /* iterate over dest pixels */ + for (x = 0; x < w-1; x++) + { + int xx, yy; + unsigned int r=0, g=0, b=0; + + int xx1 = x * scale + fromx; + int yy1 = y * scale + fromy; + int xx2 = (x+1) * scale + fromx; + int yy2 = (y+1) * scale + fromy; + + /* Iterate over the source pixels contributing to this one, and sum. */ + for (xx = xx1; xx < xx2; xx++) + for (yy = yy1; yy < yy2; yy++) + { + unsigned char rr, gg, bb; + unsigned long sp = ((xx > in->width || yy > in->height) + ? 0 : XGetPixel (in, xx, yy)); + if (colors) + { + rr = colors[sp].red & 0xFF; + gg = colors[sp].green & 0xFF; + bb = colors[sp].blue & 0xFF; + } + else + { + rr = (sp & rmsk) >> rpos; + gg = (sp & gmsk) >> gpos; + bb = (sp & bmsk) >> bpos; + rr = spread_map[0][rr]; + gg = spread_map[1][gg]; + bb = spread_map[2][bb]; + } + r += rr; + g += gg; + b += bb; + } + + /* Scale summed pixel values down to 8/8/8 range */ + i = (xx2 - xx1) * (yy2 - yy1); + if (i < 1) i = 1; + r /= i; + g /= i; + b /= i; + + out[y * w + x] = (r << 16) | (g << 8) | b; + } +} + + +/* Convert an XImage (of any size/depth/visual) to a 32bpp RGB array. + Picks a random sub-image out of the source image, and scales it to WxH. + */ +static void +pick_a2_subimage (Display *dpy, Window window, XImage *in, + unsigned int *out, int w, int h) +{ + int fromx, fromy, fromw, fromh; + if (in->width <= w || in->height <= h) + { + fromx = 0; + fromy = 0; + fromw = in->width; + fromh = in->height; + } + else + { + int dw, dh; + do { + double scale = (0.5 + frand(0.7) + frand(0.7) + frand(0.7)); + fromw = w * scale; + fromh = h * scale; + } while (fromw > in->width || + fromh > in->height); + + dw = (in->width - fromw) / 2; /* near the center! */ + dh = (in->height - fromh) / 2; + + fromx = (dw <= 0 ? 0 : (random() % dw) + (dw/2)); + fromy = (dh <= 0 ? 0 : (random() % dh) + (dh/2)); + } + + scale_image (dpy, window, in, + fromx, fromy, fromw, fromh, + out, w, h); +} + + +/* Floyd-Steinberg dither. Derived from ppmquant.c, + Copyright (c) 1989, 1991 by Jef Poskanzer. + */ +static void +a2_dither (unsigned int *in, unsigned char *out, int w, int h) +{ + /* + Apple ][ color map. Each pixel can only be 1 or 0, but what that + means depends on whether it's an odd or even pixel, and whether + the high bit in the byte is set or not. If it's 0, it's always + black. + */ + static const int a2_cmap[2][2][3] = { + { + /* hibit=0 */ + {/* odd pixels = blue */ 0x00, 0x80, 0xff}, + {/* even pixels = red */ 0xff, 0x80, 0x00} + }, + { + /* hibit=1 */ + {/* even pixels = purple */ 0xa0, 0x40, 0xa0}, + {/* odd pixels = green */ 0x40, 0xff, 0x40} + } + }; + + int x, y; + unsigned int **pixels; + unsigned int *pP; + int maxval = 255; + long *this_rerr; + long *next_rerr; + long *this_gerr; + long *next_gerr; + long *this_berr; + long *next_berr; + long *temp_err; + int fs_scale = 1024; + int brightness = 75; + +#if 0 + { + FILE *pipe = popen ("xv -", "w"); + fprintf (pipe, "P6\n%d %d\n%d\n", w, h, 255); + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + { + unsigned int p = in[y * w + x]; + unsigned int r = (p >> 16) & 0xFF; + unsigned int g = (p >> 8) & 0xFF; + unsigned int b = (p ) & 0xFF; + fprintf(pipe, "%c%c%c", r, g, b); + } + fclose (pipe); + } +#endif + + /* Initialize Floyd-Steinberg error vectors. */ + this_rerr = (long *) calloc (w + 2, sizeof(long)); + next_rerr = (long *) calloc (w + 2, sizeof(long)); + this_gerr = (long *) calloc (w + 2, sizeof(long)); + next_gerr = (long *) calloc (w + 2, sizeof(long)); + this_berr = (long *) calloc (w + 2, sizeof(long)); + next_berr = (long *) calloc (w + 2, sizeof(long)); + + + /* #### do we really need more than one element of "pixels" at once? + */ + pixels = (unsigned int **) malloc (h * sizeof (unsigned int *)); + for (y = 0; y < h; y++) + pixels[y] = (unsigned int *) malloc (w * sizeof (unsigned int)); + + for (x = 0; x < w + 2; ++x) + { + this_rerr[x] = random() % (fs_scale * 2) - fs_scale; + this_gerr[x] = random() % (fs_scale * 2) - fs_scale; + this_berr[x] = random() % (fs_scale * 2) - fs_scale; + /* (random errors in [-1 .. 1]) */ + } + + for (y = 0; y < h; y++) + for (x = 0; x < w; x++) + pixels[y][x] = in[y * w + x]; + + for (y = 0; y < h; y++) + { + int xbyte; + int err; + int prev_byte=0; + + for (x = 0; x < w + 2; x++) + next_rerr[x] = next_gerr[x] = next_berr[x] = 0; + + /* It's too complicated to go back and forth on alternate rows, + so we always go left-right here. It doesn't change the result + very much. + + For each group of 7 pixels, we have to try it both with the + high bit=0 and =1. For each high bit value, we add up the + total error and pick the best one. + + Because we have to go through each group of bits twice, we + don't propagate the error values through this_[rgb]err since + it would add them twice. So we keep seperate local_[rgb]err + variables for propagating error within the 7-pixel group. + */ + + pP = pixels[y]; + for (xbyte=0; xbyte<280; xbyte+=7) + { + int best_byte=0; + int best_error=2000000000; + int hibit; + int sr, sg, sb; + int r2, g2, b2; + int local_rerr=0, local_gerr=0, local_berr=0; + + for (hibit=0; hibit<2; hibit++) + { + int byte = hibit<<7; + int tot_error=0; + + for (x=xbyte; x> 16) & 0xFF) * brightness/256; + sg = ((pP[x] >> 8) & 0xFF) * brightness/256; + sb = ((pP[x] ) & 0xFF) * brightness/256; + sr += (this_rerr[x + 1] + local_rerr) / fs_scale; + sg += (this_gerr[x + 1] + local_gerr) / fs_scale; + sb += (this_berr[x + 1] + local_berr) / fs_scale; + + if (sr < 0) sr = 0; + else if (sr > maxval) sr = maxval; + if (sg < 0) sg = 0; + else if (sg > maxval) sg = maxval; + if (sb < 0) sb = 0; + else if (sb > maxval) sb = maxval; + + /* This is the color we'd get if we set the bit 1. For 0, + we get black */ + r2=a2_cmap[hibit][x&1][0]; + g2=a2_cmap[hibit][x&1][1]; + b2=a2_cmap[hibit][x&1][2]; + + /* + dist0 and dist1 are the error (Minkowski 2-metric + distances in the color space) for choosing 0 and + 1 respectively. 0 is black, 1 is the color r2,g2,b2. + */ + dist1= (sr-r2)*(sr-r2) + (sg-g2)*(sg-g2) + (sb-b2)*(sb-b2); + dist0= sr*sr + sg*sg + sb*sb; + + if (dist1>(x-xbyte))&1; + hibit=(best_byte>>7)&1; + + sr = (pP[x] >> 16) & 0xFF; + sg = (pP[x] >> 8) & 0xFF; + sb = (pP[x] ) & 0xFF; + sr += this_rerr[x + 1] / fs_scale; + sg += this_gerr[x + 1] / fs_scale; + sb += this_berr[x + 1] / fs_scale; + + if (sr < 0) sr = 0; + else if (sr > maxval) sr = maxval; + if (sg < 0) sg = 0; + else if (sg > maxval) sg = maxval; + if (sb < 0) sb = 0; + else if (sb > maxval) sb = maxval; + + r2=a2_cmap[hibit][x&1][0] * bit; + g2=a2_cmap[hibit][x&1][1] * bit; + b2=a2_cmap[hibit][x&1][2] * bit; + + pP[x] = (r2<<16) | (g2<<8) | (b2); + + /* Propagate Floyd-Steinberg error terms. */ + err = (sr - r2) * fs_scale; + this_rerr[x + 2] += (err * 7) / 16; + next_rerr[x ] += (err * 3) / 16; + next_rerr[x + 1] += (err * 5) / 16; + next_rerr[x + 2] += (err ) / 16; + err = (sg - g2) * fs_scale; + this_gerr[x + 2] += (err * 7) / 16; + next_gerr[x ] += (err * 3) / 16; + next_gerr[x + 1] += (err * 5) / 16; + next_gerr[x + 2] += (err ) / 16; + err = (sb - b2) * fs_scale; + this_berr[x + 2] += (err * 7) / 16; + next_berr[x ] += (err * 3) / 16; + next_berr[x + 1] += (err * 5) / 16; + next_berr[x + 2] += (err ) / 16; + } + + /* + And put the actual byte into out. + */ + + out[y*(w/7) + xbyte/7] = best_byte; + + } + + temp_err = this_rerr; + this_rerr = next_rerr; + next_rerr = temp_err; + temp_err = this_gerr; + this_gerr = next_gerr; + next_gerr = temp_err; + temp_err = this_berr; + this_berr = next_berr; + next_berr = temp_err; + } + + free (this_rerr); + free (next_rerr); + free (this_gerr); + free (next_gerr); + free (this_berr); + free (next_berr); + + for (y=0; y>16)&0xff; + unsigned int g = (pixels[y][x]>>8)&0xff; + unsigned int b = (pixels[y][x]>>0)&0xff; + fprintf(pipe, "%c%c%c", r, g, b); + } + fclose (pipe); + } +#endif +} + +typedef struct slideshow_data_s { + int slideno; + int render_img_lineno; + unsigned char *render_img; + char *img_filename; + Bool image_loading_p; +} slideshow_data; + + +static void +image_loaded_cb (Screen *screen, Window window, Drawable p, + const char *name, XRectangle *geometry, + void *closure) +{ + Display *dpy = DisplayOfScreen (screen); + apple2_sim_t *sim = (apple2_sim_t *) closure; + slideshow_data *mine = (slideshow_data *) sim->controller_data; + XWindowAttributes xgwa; + int w = 280; + int h = 192; + XImage *image; + unsigned int *buf32 = (unsigned int *) calloc (w, h * 4); + unsigned char *buf8 = (unsigned char *) calloc (w/7, h); + + if (!buf32 || !buf8) + { + fprintf (stderr, "%s: out of memory (%dx%d)\n", progname, w, h); + exit (1); + } + + XGetWindowAttributes (dpy, window, &xgwa); + + image = XGetImage (dpy, p, 0, 0, xgwa.width, xgwa.height, ~0, ZPixmap); + XFreePixmap (dpy, p); + p = 0; + + /* Scale the XImage down to Apple][ size, and convert it to a 32bpp + image (regardless of whether it started as TrueColor/PseudoColor.) + */ + pick_a2_subimage (dpy, window, image, buf32, w, h); + free(image->data); + image->data = 0; + XDestroyImage(image); + + /* Then dither the 32bpp image to a 6-color Apple][ colormap. + */ + a2_dither (buf32, buf8, w, h); + + free (buf32); + + mine->image_loading_p = False; + mine->img_filename = (name ? strdup (name) : 0); + mine->render_img = buf8; +} + + + +static const char *apple2_defaults [] = { + ".background: black", + ".foreground: white", + "*mode: random", + "*duration: 60", + "*program: xscreensaver-text --cols 40", + "*metaSendsESC: True", + "*swapBSDEL: True", + "*fast: False", +# ifdef HAVE_FORKPTY + "*usePty: True", +#else + "*usePty: False", +# endif /* !HAVE_FORKPTY */ + + ANALOGTV_DEFAULTS + 0 +}; + +static XrmOptionDescRec apple2_options [] = { + { "-mode", ".mode", XrmoptionSepArg, 0 }, + { "-slideshow", ".mode", XrmoptionNoArg, "slideshow" }, + { "-basic", ".mode", XrmoptionNoArg, "basic" }, + { "-text", ".mode", XrmoptionNoArg, "text" }, + { "-program", ".program", XrmoptionSepArg, 0 }, + { "-duration", ".duration", XrmoptionSepArg, 0 }, + { "-pty", ".usePty", XrmoptionNoArg, "True" }, + { "-pipe", ".usePty", XrmoptionNoArg, "False" }, + { "-meta", ".metaSendsESC", XrmoptionNoArg, "False" }, + { "-esc", ".metaSendsESC", XrmoptionNoArg, "True" }, + { "-bs", ".swapBSDEL", XrmoptionNoArg, "False" }, + { "-del", ".swapBSDEL", XrmoptionNoArg, "True" }, + { "-fast", ".fast", XrmoptionNoArg, "True" }, + ANALOGTV_OPTIONS + { 0, 0, 0, 0 } +}; + +/* + TODO: this should load 10 images at startup time, then cycle through them + to avoid the pause while it loads. + */ + +static void slideshow_controller(apple2_sim_t *sim, int *stepno, + double *next_actiontime) +{ + apple2_state_t *st=sim->st; + int i; + slideshow_data *mine; + + if (!sim->controller_data) + sim->controller_data = calloc (1, sizeof(*mine)); + mine = (slideshow_data *) sim->controller_data; + + switch(*stepno) { + + case 0: + a2_invalidate(st); + a2_clear_hgr(st); + a2_cls(st); + sim->typing_rate = 0.3; + sim->dec->powerup=0.0; + + a2_goto(st, 0, 16); + a2_prints(st, "APPLE ]["); + a2_goto(st,23,0); + a2_printc(st,']'); + + *stepno=10; + break; + + case 10: + { + XWindowAttributes xgwa; + Pixmap p; + XGetWindowAttributes (sim->dpy, sim->window, &xgwa); + p = XCreatePixmap (sim->dpy, sim->window, xgwa.width, xgwa.height, + xgwa.depth); + mine->image_loading_p = True; + load_image_async (xgwa.screen, sim->window, p, image_loaded_cb, sim); + + /* pause with a blank screen for a bit, while the image loads in the + background. */ + *next_actiontime += 2.0; + *stepno=11; + } + break; + + case 11: + if (! mine->image_loading_p) { /* image is finally loaded */ + if (st->gr_mode) { + *stepno=30; + } else { + *stepno=20; + } + *next_actiontime += 3.0; + } + break; + + case 20: + sim->typing="HGR\n"; + *stepno=29; + break; + + case 29: + sim->printing="]"; + *stepno=30; + break; + + case 30: + st->gr_mode=A2_GR_HIRES; + if (mine->img_filename) { + char *basename, *tmp; + char *s; + + basename = tmp = strdup (mine->img_filename); + while (1) + { + char *slash = strchr(basename, '/'); + if (!slash || !slash[1]) break; + basename = slash+1; + } + { + char *dot=strrchr(basename,'.'); + if (dot) *dot=0; + } + if (strlen(basename)>20) basename[20]=0; + for (s=basename; *s; s++) { + *s = toupper (*s); + if (*s <= ' ') *s = '_'; + } + sprintf(sim->typing_buf, "BLOAD %s\n", basename); + sim->typing = sim->typing_buf; + + free(tmp); + } else { + sim->typing = "BLOAD IMAGE\n"; + } + mine->render_img_lineno=0; + + *stepno=35; + break; + + case 35: + *next_actiontime += 0.7; + *stepno=40; + break; + + case 40: + if (mine->render_img_lineno>=192) { + sim->printing="]"; + sim->typing="POKE 49234,0\n"; + *stepno=50; + return; + } + + for (i=0; i<6 && mine->render_img_lineno<192; i++) { + a2_display_image_loading(st, mine->render_img, + mine->render_img_lineno++); + } + + /* The disk would have to seek every 13 sectors == 78 lines. + (This ain't no newfangled 16-sector operating system) */ + if ((mine->render_img_lineno%78)==0) { + *next_actiontime += 0.5; + } else { + *next_actiontime += 0.08; + } + break; + + case 50: + st->gr_mode |= A2_GR_FULL; + *stepno=60; + /* Note that sim->delay is sometimes "infinite" in this controller. + These images are kinda dull anyway, so don't leave it on too long. */ + *next_actiontime += 2; + break; + + case 60: + sim->printing="]"; + sim->typing="POKE 49235,0\n"; + *stepno=70; + break; + + case 70: + sim->printing="]"; + st->gr_mode &= ~A2_GR_FULL; + if (mine->render_img) { + free(mine->render_img); + mine->render_img=NULL; + } + if (mine->img_filename) { + free(mine->img_filename); + mine->img_filename=NULL; + } + *stepno=10; + break; + + case 80: + /* Do nothing, just wait */ + *next_actiontime += 2.0; + *stepno = A2CONTROLLER_FREE; + break; + + case A2CONTROLLER_FREE: + /* It is possible that still image is being loaded, + in that case mine cannot be freed, because + callback function tries to use it, so wait. + */ + if (mine->image_loading_p) { + *stepno = 80; + break; + } + free(mine->render_img); + free(mine->img_filename); + free(mine); + mine = 0; + return; + + } +} + +#define NPAR 16 + +struct terminal_controller_data { + Display *dpy; + char curword[256]; + unsigned char lastc; + double last_emit_time; + text_data *tc; + + int escstate; + int csiparam[NPAR]; + int curparam; + int cursor_x, cursor_y; + int saved_x, saved_y; + int unicruds; char unicrud[7]; + union { + struct { + unsigned int bold : 1; + unsigned int blink : 1; + unsigned int rev : 1; + } bf; + int w; + } termattrib; + Bool fast_p; + +}; + + +/* The structure of closure linkage throughout this code is so amazingly + baroque that I can't get to the 'struct state' from where I need it. */ +static const char *global_program; +static Bool global_fast_p; + + +static void +terminal_closegen(struct terminal_controller_data *mine) +{ + if (mine->tc) { + textclient_close (mine->tc); + mine->tc = 0; + } +} + +static int +terminal_read(struct terminal_controller_data *mine, unsigned char *buf, int n) +{ + if (!mine || !mine->tc) { + return 0; + } else { + int i, count = 0; + for (i = 0; i < n; i++) { + int c = textclient_getc (mine->tc); + if (c <= 0) break; + buf[i] = c; + mine->lastc = c; + count++; + } + return count; + } +} + + +static int +terminal_keypress_handler (Display *dpy, XEvent *event, void *data) +{ + struct terminal_controller_data *mine = + (struct terminal_controller_data *) data; + mine->dpy = dpy; + if (event->xany.type == KeyPress && mine->tc) + return textclient_putc (mine->tc, &event->xkey); + return 0; +} + + +static void +a2_ascii_printc (apple2_state_t *st, unsigned char c, + Bool bold_p, Bool blink_p, Bool rev_p, + Bool scroll_p) +{ + if (c >= 'a' && c <= 'z') /* upcase lower-case chars */ + { + c &= 0xDF; + } + else if ((c >= 'A'+128) || /* upcase and blink */ + (c < ' ' && c != 014 && /* high-bit & ctl chrs */ + c != '\r' && c != '\n' && c!='\t')) + { + c = (c & 0x1F) | 0x80; + } + else if (c >= 'A' && c <= 'Z') /* invert upper-case chars */ + { + c |= 0x80; + } + + if (bold_p) c |= 0xc0; + if (blink_p) c = (c & ~0x40) | 0x80; + if (rev_p) c |= 0xc0; + + if (scroll_p) + a2_printc(st, c); + else + a2_printc_noscroll(st, c); +} + + +static void +a2_vt100_printc (apple2_sim_t *sim, struct terminal_controller_data *state, + unsigned char c) +{ + apple2_state_t *st=sim->st; + int cols = SCREEN_COLS; + int rows = SCREEN_ROWS; + + int i; + int start, end; + + /* Mostly duplicated in phosphor.c */ + + switch (state->escstate) + { + case 0: + switch (c) + { + case 7: /* BEL */ + /* Dummy case - we don't want the screensaver to beep */ + /* #### But maybe this should flash the screen? */ + break; + case 8: /* BS */ + if (state->cursor_x > 0) + state->cursor_x--; + break; + case 9: /* HT */ + if (state->cursor_x < cols - 8) + { + state->cursor_x = (state->cursor_x & ~7) + 8; + } + else + { + state->cursor_x = 0; + if (state->cursor_y < rows - 1) + state->cursor_y++; + else + a2_scroll (st); + } + break; + case 10: /* LF */ +# ifndef HAVE_FORKPTY + state->cursor_x = 0; /* No ptys on iPhone; assume CRLF. */ +# endif + case 11: /* VT */ + case 12: /* FF */ + if (state->cursor_y < rows - 1) + state->cursor_y++; + else + a2_scroll (st); + break; + case 13: /* CR */ + state->cursor_x = 0; + break; + case 14: /* SO */ + case 15: /* SI */ + /* Dummy case - there is one and only one font. */ + break; + case 24: /* CAN */ + case 26: /* SUB */ + /* Dummy case - these interrupt escape sequences, so + they don't do anything in this state */ + break; + case 27: /* ESC */ + state->escstate = 1; + break; + case 127: /* DEL */ + /* Dummy case - this is supposed to be ignored */ + break; + case 155: /* CSI */ + state->escstate = 2; + for(i = 0; i < NPAR; i++) + state->csiparam[i] = 0; + state->curparam = 0; + break; + default: + + /* states 102-106 are for UTF-8 decoding */ + + if ((c & 0xE0) == 0xC0) { /* 110xxxxx - 11 bits, 2 bytes */ + state->unicruds = 1; + state->unicrud[0] = c; + state->escstate = 102; + break; + } else if ((c & 0xF0) == 0xE0) { /* 1110xxxx - 16 bits, 3 bytes */ + state->unicruds = 1; + state->unicrud[0] = c; + state->escstate = 103; + break; + } else if ((c & 0xF8) == 0xF0) { /* 11110xxx - 21 bits, 4 bytes */ + state->unicruds = 1; + state->unicrud[0] = c; + state->escstate = 104; + break; + } else if ((c & 0xFC) == 0xF8) { /* 111110xx - 26 bits, 5 bytes */ + state->unicruds = 1; + state->unicrud[0] = c; + state->escstate = 105; + break; + } else if ((c & 0xFE) == 0xFC) { /* 1111110x - 31 bits, 6 bytes */ + state->unicruds = 1; + state->unicrud[0] = c; + state->escstate = 106; + break; + } + + PRINT: + + /* If the cursor is in column 39 and we print a character, then + that character shows up in column 39, and the cursor is no longer + visible on the screen (it's in "column 40".) If another character + is printed, then that character shows up in column 0, and the + cursor moves to column 1. + + This is empirically what xterm and gnome-terminal do, so that must + be the right thing. (In xterm, the cursor vanishes, whereas; in + gnome-terminal, the cursor overprints the character in col 39.) + */ + if (state->cursor_x >= cols) + { + state->cursor_x = 0; + if (state->cursor_y >= rows - 1) + a2_scroll (st); + else + state->cursor_y++; + } + + a2_goto(st, state->cursor_y, state->cursor_x); /* clips range */ + a2_ascii_printc (st, c, + state->termattrib.bf.bold, + state->termattrib.bf.blink, + state->termattrib.bf.rev, + False); + state->cursor_x++; + + break; + } + break; + case 1: + switch (c) + { + case 24: /* CAN */ + case 26: /* SUB */ + state->escstate = 0; + break; + case 'c': /* Reset */ + a2_cls(st); + state->escstate = 0; + break; + case 'D': /* Linefeed */ + if (state->cursor_y < rows - 1) + state->cursor_y++; + else + a2_scroll (st); + state->escstate = 0; + break; + case 'E': /* Newline */ + state->cursor_x = 0; + state->escstate = 0; + break; + case 'M': /* Reverse newline */ + if (state->cursor_y > 0) + state->cursor_y--; + state->escstate = 0; + break; + case '7': /* Save state */ + state->saved_x = state->cursor_x; + state->saved_y = state->cursor_y; + state->escstate = 0; + break; + case '8': /* Restore state */ + state->cursor_x = state->saved_x; + state->cursor_y = state->saved_y; + state->escstate = 0; + break; + case '[': /* CSI */ + state->escstate = 2; + for(i = 0; i < NPAR; i++) + state->csiparam[i] = 0; + state->curparam = 0; + break; + case '%': /* Select charset */ + /* @: Select default (ISO 646 / ISO 8859-1) + G: Select UTF-8 + 8: Select UTF-8 (obsolete) + + We can just ignore this and always process UTF-8, I think? + We must still catch the last byte, though. + */ + case '(': + case ')': + /* I don't support different fonts either - see above + for SO and SI */ + state->escstate = 3; + break; + default: + /* Escape sequences not supported: + * + * H - Set tab stop + * Z - Terminal identification + * > - Keypad change + * = - Other keypad change + * ] - OS command + */ + state->escstate = 0; + break; + } + break; + case 2: + switch (c) + { + case 24: /* CAN */ + case 26: /* SUB */ + state->escstate = 0; + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + if (state->curparam < NPAR) + state->csiparam[state->curparam] = + (state->csiparam[state->curparam] * 10) + (c - '0'); + break; + case ';': + state->csiparam[++state->curparam] = 0; + break; + case '[': + state->escstate = 3; + break; + case '@': + for (i = 0; i < state->csiparam[0]; i++) + { + if(++state->cursor_x > cols) + { + state->cursor_x = 0; + if (state->cursor_y < rows - 1) + state->cursor_y++; + else + a2_scroll (st); + } + } + state->escstate = 0; + break; + case 'F': + state->cursor_x = 0; + case 'A': + if (state->csiparam[0] == 0) + state->csiparam[0] = 1; + if ((state->cursor_y -= state->csiparam[0]) < 0) + state->cursor_y = 0; + state->escstate = 0; + break; + case 'E': + state->cursor_x = 0; + case 'e': + case 'B': + if (state->csiparam[0] == 0) + state->csiparam[0] = 1; + if ((state->cursor_y += state->csiparam[0]) >= rows) + state->cursor_y = rows - 1; + state->escstate = 0; + break; + case 'a': + case 'C': + if (state->csiparam[0] == 0) + state->csiparam[0] = 1; + if ((state->cursor_x += state->csiparam[0]) >= cols) + state->cursor_x = cols - 1; + state->escstate = 0; + break; + case 'D': + if (state->csiparam[0] == 0) + state->csiparam[0] = 1; + if ((state->cursor_x -= state->csiparam[0]) < 0) + state->cursor_x = 0; + state->escstate = 0; + break; + case 'd': + if ((state->cursor_y = (state->csiparam[0] - 1)) >= rows) + state->cursor_y = rows - 1; + state->escstate = 0; + break; + case '`': + case 'G': + if ((state->cursor_x = (state->csiparam[0] - 1)) >= cols) + state->cursor_x = cols - 1; + state->escstate = 0; + break; + case 'f': + case 'H': + if ((state->cursor_y = (state->csiparam[0] - 1)) >= rows) + state->cursor_y = rows - 1; + if ((state->cursor_x = (state->csiparam[1] - 1)) >= cols) + state->cursor_x = cols - 1; + if(state->cursor_y < 0) + state->cursor_y = 0; + if(state->cursor_x < 0) + state->cursor_x = 0; + state->escstate = 0; + break; + case 'J': + start = 0; + end = rows * cols; + if (state->csiparam[0] == 0) + start = cols * state->cursor_y + state->cursor_x; + if (state->csiparam[0] == 1) + end = cols * state->cursor_y + state->cursor_x; + + a2_goto(st, state->cursor_y, state->cursor_x); + for (i = start; i < end; i++) + { + a2_ascii_printc(st, ' ', False, False, False, False); + } + state->escstate = 0; + break; + case 'K': + start = 0; + end = cols; + if (state->csiparam[0] == 0) + start = state->cursor_x; + if (state->csiparam[1] == 1) + end = state->cursor_x; + + a2_goto(st, state->cursor_y, state->cursor_x); + for (i = start; i < end; i++) + { + a2_ascii_printc(st, ' ', False, False, False, False); + } + state->escstate = 0; + break; + case 'm': /* Set attributes */ + for (i = 0; i <= state->curparam; i++) + { + switch(state->csiparam[i]) + { + case 0: + state->termattrib.w = 0; + break; + case 1: + state->termattrib.bf.bold = 1; + break; + case 5: + state->termattrib.bf.blink = 1; + break; + case 7: + state->termattrib.bf.rev = 1; + break; + case 21: + case 22: + state->termattrib.bf.bold = 0; + break; + case 25: + state->termattrib.bf.blink = 0; + break; + case 27: + state->termattrib.bf.rev = 0; + break; + } + } + state->escstate = 0; + break; + case 's': /* Save position */ + state->saved_x = state->cursor_x; + state->saved_y = state->cursor_y; + state->escstate = 0; + break; + case 'u': /* Restore position */ + state->cursor_x = state->saved_x; + state->cursor_y = state->saved_y; + state->escstate = 0; + break; + case '?': /* DEC Private modes */ + if ((state->curparam != 0) || (state->csiparam[0] != 0)) + state->escstate = 0; + break; + default: + /* Known unsupported CSIs: + * + * L - Insert blank lines + * M - Delete lines (I don't know what this means...) + * P - Delete characters + * X - Erase characters (difference with P being...?) + * c - Terminal identification + * g - Clear tab stop(s) + * h - Set mode (Mainly due to its complexity and lack of good + docs) + * l - Clear mode + * m - Set mode (Phosphor is, per defenition, green on black) + * n - Status report + * q - Set keyboard LEDs + * r - Set scrolling region (too exhausting - noone uses this, + right?) + */ + state->escstate = 0; + break; + } + break; + case 3: + state->escstate = 0; + break; + + case 102: + case 103: + case 104: + case 105: + case 106: + { + int total = state->escstate - 100; /* see what I did there */ + if (state->unicruds < total) { + /* Buffer more bytes of the UTF-8 sequence */ + state->unicrud[state->unicruds++] = c; + } + + if (state->unicruds >= total) { + /* Done! Convert it to ASCII and print that. */ + char *s; + state->unicrud[state->unicruds] = 0; + s = utf8_to_latin1 ((const char *) state->unicrud, True); + state->unicruds = 0; + state->escstate = 0; + if (s) { + c = s[0]; + free (s); + goto PRINT; + } else { + /* c = 0; */ + } + } + } + break; + + default: + abort(); + } + a2_goto(st, state->cursor_y, state->cursor_x); +} + + +/* + It's fun to put things like "gdb" as the command. For one, it's + amusing how the standard mumble (version, no warranty, it's + GNU/Linux dammit) occupies an entire screen on the Apple ][. +*/ + +static void +terminal_controller(apple2_sim_t *sim, int *stepno, double *next_actiontime) +{ + apple2_state_t *st=sim->st; + int c; + int i; + + struct terminal_controller_data *mine; + if (!sim->controller_data) + sim->controller_data=calloc(sizeof(struct terminal_controller_data),1); + mine=(struct terminal_controller_data *) sim->controller_data; + mine->dpy = sim->dpy; + + mine->fast_p = global_fast_p; + + switch(*stepno) { + + case 0: + if (random()%2) + st->gr_mode |= A2_GR_FULL; /* Turn on color mode even through it's + showing text */ + a2_cls(st); + a2_goto(st,0,16); + a2_prints(st, "APPLE ]["); + a2_goto(st,2,0); + mine->cursor_y = 2; + + if (! mine->tc) { + mine->tc = textclient_open (mine->dpy); + textclient_reshape (mine->tc, + SCREEN_COLS, SCREEN_ROWS, + SCREEN_COLS, SCREEN_ROWS, + 0); + } + + if (! mine->fast_p) + *next_actiontime += 4.0; + *stepno = 10; + + mine->last_emit_time = sim->curtime; + break; + + case 10: + case 11: + { + Bool first_line_p = (*stepno == 10); + unsigned char buf[1024]; + int nr,nwant; + double elapsed; + + elapsed=sim->curtime - mine->last_emit_time; + + nwant = elapsed * 25.0; /* characters per second */ + + if (first_line_p) { + *stepno = 11; + nwant = 1; + } + + if (nwant > 40) nwant = 40; + + if (mine->fast_p) + nwant = sizeof(buf)-1; + + if (nwant <= 0) break; + + mine->last_emit_time = sim->curtime; + + nr=terminal_read(mine, buf, nwant); + for (i=0; itc) + a2_vt100_printc (sim, mine, c); + else + a2_ascii_printc (st, c, False, False, False, True); + } + } + break; + + case A2CONTROLLER_FREE: + terminal_closegen(mine); + free(mine); + mine = 0; + return; + } +} + +struct basic_controller_data { + int prog_line; + int x,y,k; + const char * const * progtext; + int progstep; + char *rep_str; + int rep_pos; + double prog_start_time; + char error_buf[256]; +}; + +/* + Adding more programs is easy. Just add a listing here and to all_programs, + then add the state machine to actually execute it to basic_controller. + */ +static const char * const moire_program[]={ + "10 HGR2\n", + "20 FOR Y = 0 TO 190 STEP 2\n", + "30 HCOLOR=4 : REM BLACK\n", + "40 HPLOT 0,191-Y TO 279,Y\n", + "60 HCOLOR=7 : REM WHITE\n", + "80 HPLOT 0,190-Y TO 279,Y+1\n", + "90 NEXT Y\n", + "100 FOR X = 0 TO 278 STEP 3\n", + "110 HCOLOR=4\n", + "120 HPLOT 279-X,0 TO X,191\n", + "140 HCOLOR=7\n", + "150 HPLOT 278-X,0 TO X+1,191\n", + "160 NEXT X\n", + NULL +}; + +static const char * const sinewave_program[] = { + "10 HGR\n", + "25 K=0\n", + "30 FOR X = 0 TO 279\n", + "32 HCOLOR= 0\n", + "35 HPLOT X,0 TO X,159\n", + "38 HCOLOR= 3\n", + "40 Y = 80 + SIN(15*(X-K)/279) * 40\n", + "50 HPLOT X,Y\n", + "60 NEXT X\n", + "70 K=K+4\n", + "80 GOTO 30\n", + NULL +}; + +#if 0 +static const char * const dumb_program[]={ + "10 PRINT \"APPLE ][ ROOLZ! TRS-80 DROOLZ!\"\n", + "20 GOTO 10\n", + NULL +}; +#endif + +static const char * const random_lores_program[]={ + "1 REM APPLE ][ SCREEN SAVER\n", + "10 GR\n", + "100 COLOR= RND(1)*16\n", + + "110 X=RND(1)*40\n", + "120 Y1=RND(1)*40\n", + "130 Y2=RND(1)*40\n", + "140 FOR Y = Y1 TO Y2\n", + "150 PLOT X,Y\n", + "160 NEXT Y\n", + + "210 Y=RND(1)*40\n", + "220 X1=RND(1)*40\n", + "230 X2=RND(1)*40\n", + "240 FOR X = X1 TO X2\n", + "250 PLOT X,Y\n", + "260 NEXT X\n", + "300 GOTO 100\n", + + NULL +}; + +static char typo_map[256]; + +static int make_typo(char *out_buf, const char *orig, char *err_buf) +{ + int i,j; + int errc; + int success=0; + err_buf[0]=0; + + typo_map['A']='Q'; + typo_map['S']='A'; + typo_map['D']='S'; + typo_map['F']='G'; + typo_map['G']='H'; + typo_map['H']='J'; + typo_map['J']='H'; + typo_map['K']='L'; + typo_map['L']=';'; + + typo_map['Q']='1'; + typo_map['W']='Q'; + typo_map['E']='3'; + typo_map['R']='T'; + typo_map['T']='Y'; + typo_map['Y']='U'; + typo_map['U']='Y'; + typo_map['I']='O'; + typo_map['O']='P'; + typo_map['P']='['; + + typo_map['Z']='X'; + typo_map['X']='C'; + typo_map['C']='V'; + typo_map['V']='C'; + typo_map['B']='N'; + typo_map['N']='B'; + typo_map['M']='N'; + typo_map[',']='.'; + typo_map['.']=','; + + typo_map['!']='1'; + typo_map['@']='2'; + typo_map['#']='3'; + typo_map['$']='4'; + typo_map['%']='5'; + typo_map['^']='6'; + typo_map['&']='7'; + typo_map['*']='8'; + typo_map['(']='9'; + typo_map[')']='0'; + + typo_map['1']='Q'; + typo_map['2']='W'; + typo_map['3']='E'; + typo_map['4']='R'; + typo_map['5']='T'; + typo_map['6']='Y'; + typo_map['7']='U'; + typo_map['8']='I'; + typo_map['9']='O'; + typo_map['0']='-'; + + strcpy(out_buf, orig); + for (i=0; out_buf[i]; i++) { + char *p = out_buf+i; + + if (i>2 && p[-2]=='R' && p[-1]=='E' && p[0]=='M') + break; + + if (isalpha(p[0]) && + isalpha(p[1]) && + p[0] != p[1] && + random()%15==0) + { + int tmp=p[1]; + p[1]=p[0]; + p[0]=tmp; + success=1; + sprintf(err_buf,"?SYNTAX ERROR\n"); + break; + } + + if (random()%10==0 && strlen(p)>=4 && (errc=typo_map[(int)(unsigned char)p[0]])) { + int remain=strlen(p); + int past=random()%(remain-2)+1; + memmove(p+past+past, p, remain+1); + p[0]=errc; + for (j=0; jst; + int i; + + struct basic_controller_data *mine; + if (!sim->controller_data) + sim->controller_data=calloc(sizeof(struct basic_controller_data),1); + mine=(struct basic_controller_data *) sim->controller_data; + + switch (*stepno) { + case 0: + st->gr_mode=0; + a2_cls(st); + a2_goto(st,0,16); + a2_prints(st, "APPLE ]["); + a2_goto(st,23,0); + a2_printc(st,']'); + sim->typing_rate=0.2; + + i=random()%countof(all_programs); + mine->progtext=all_programs[i].progtext; + mine->progstep=all_programs[i].progstep; + mine->prog_line=0; + + *next_actiontime += 1.0; + *stepno=10; + break; + + case 10: + if (st->cursx==0) a2_printc(st,']'); + if (mine->progtext[mine->prog_line]) { + if (random()%4==0) { + int err=make_typo(sim->typing_buf, + mine->progtext[mine->prog_line], + mine->error_buf); + sim->typing=sim->typing_buf; + if (err) { + *stepno=11; + } else { + mine->prog_line++; + } + } else { + sim->typing=mine->progtext[mine->prog_line++]; + } + } else { + *stepno=15; + } + break; + + case 11: + sim->printing=mine->error_buf; + *stepno=12; + break; + + case 12: + if (st->cursx==0) a2_printc(st,']'); + *next_actiontime+=1.0; + *stepno=10; + break; + + case 15: + sim->typing="RUN\n"; + mine->y=0; + mine->x=0; + mine->k=0; + mine->prog_start_time=*next_actiontime; + *stepno=mine->progstep; + break; + + /* moire_program */ + case 100: + st->gr_mode=A2_GR_HIRES|A2_GR_FULL; + for (i=0; i<24 && mine->y<192; i++) + { + a2_hline(st, 4, 0, 191-mine->y, 279, mine->y); + a2_hline(st, 7, 0, 191-mine->y-1, 279, mine->y+1); + mine->y += 2; + } + if (mine->y>=192) { + mine->x = 0; + *stepno = 110; + } + break; + + case 110: + for (i=0; i<24 && mine->x<280; i++) + { + a2_hline(st, 4, 279-mine->x, 0, mine->x, 192); + a2_hline(st, 7, 279-mine->x-1, 0, mine->x+1, 192); + mine->x+=3; + } + if (mine->x >= 280) *stepno=120; + break; + + case 120: + if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999; + break; + + /* dumb_program */ + case 200: + mine->rep_str="\nAPPLE ][ ROOLZ! TRS-80 DROOLZ!"; + for (i=0; i<30; i++) { + a2_prints(st, mine->rep_str); + } + *stepno=210; + break; + + case 210: + i=random()%strlen(mine->rep_str); + while (mine->rep_pos != i) { + a2_printc(st, mine->rep_str[mine->rep_pos]); + mine->rep_pos++; + if (!mine->rep_str[mine->rep_pos]) mine->rep_pos=0; + } + if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999; + break; + + /* sinewave_program */ + case 400: + st->gr_mode=A2_GR_HIRES; + *stepno=410; + break; + + case 410: + for (i=0; i<48; i++) { + int y=80 + (int)(75.0*sin(15.0*(mine->x-mine->k)/279.0)); + a2_hline(st, 0, mine->x, 0, mine->x, 159); + a2_hplot(st, 3, mine->x, y); + mine->x += 1; + if (mine->x>=279) { + mine->x=0; + mine->k+=4; + } + } + if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999; + break; + + case 420: + a2_prints(st, "]"); + *stepno=999; + break; + + /* random_lores_program */ + case 500: + st->gr_mode=A2_GR_LORES|A2_GR_FULL; + a2_clear_gr(st); + *stepno=510; + + case 510: + for (i=0; i<10; i++) { + int color,x,y,x1,x2,y1,y2; + + color=random()%15; + x=random()%40; + y1=random()%48; + y2=random()%48; + for (y=y1; y mine->prog_start_time+sim->delay) *stepno=999; + break; + + case 999: + *stepno=0; + break; + + case A2CONTROLLER_FREE: + free(mine); + mine = 0; + break; + } + +} + +static void (* const controllers[]) (apple2_sim_t *sim, int *stepno, + double *next_actiontime) = { + slideshow_controller, + terminal_controller, + basic_controller +}; + +struct state { + int duration; + Bool random_p; + apple2_sim_t *sim; + void (*controller) (apple2_sim_t *sim, int *stepno, double *next_actiontime); +}; + + +static void * +apple2_init (Display *dpy, Window window) +{ + struct state *st = (struct state *) calloc (1, sizeof(*st)); + char *s; + + st->duration = get_integer_resource (dpy, "duration", "Integer"); + + st->controller = 0; + if (st->duration < 1) st->duration = 1; + + s = get_string_resource (dpy, "mode", "Mode"); + if (!s || !*s || !strcasecmp(s, "random")) + st->random_p = True; + else if (!strcasecmp(s, "text")) + st->controller = terminal_controller; + else if (!strcasecmp(s, "slideshow")) + st->controller = slideshow_controller; + else if (!strcasecmp(s, "basic")) + st->controller = basic_controller; + else + { + fprintf (stderr, "%s: mode must be text, slideshow, or random; not %s\n", + progname, s); + exit (1); + } + if (s) free (s); + + global_program = get_string_resource (dpy, "program", "Program"); + global_fast_p = get_boolean_resource (dpy, "fast", "Boolean"); + + + /* Kludge for MacOS standalone mode: see OSX/SaverRunner.m. */ + { + const char *s = getenv ("XSCREENSAVER_STANDALONE"); + if (s && *s && strcmp(s, "0")) + { + st->controller = terminal_controller; + st->random_p = False; + global_program = getenv ("SHELL"); + global_fast_p = True; + } + } + + + if (! st->random_p) { + if (st->controller == terminal_controller || + st->controller == slideshow_controller) + st->duration = 999999; /* these run "forever" */ + } + + return st; +} + +static unsigned long +apple2_draw (Display *dpy, Window window, void *closure) +{ + struct state *st = (struct state *) closure; + + if (! st->sim) { + if (st->random_p) + st->controller = controllers[random() % (countof(controllers))]; + st->sim = apple2_start (dpy, window, st->duration, st->controller); + } + + if (! apple2_one_frame (st->sim)) { + st->sim = 0; + } + +#ifdef HAVE_MOBILE + return 0; +#else + return 5000; +#endif +} + +static void +apple2_reshape (Display *dpy, Window window, void *closure, + unsigned int w, unsigned int h) +{ + struct state *st = (struct state *) closure; + if (st->sim) + analogtv_reconfigure (st->sim->dec); +} + +static Bool +apple2_event (Display *dpy, Window window, void *closure, XEvent *event) +{ + struct state *st = (struct state *) closure; + + if (st->sim && + st->controller == terminal_controller && + event->xany.type == KeyPress) { + terminal_keypress_handler (dpy, event, st->sim->controller_data); + return True; + } + + return False; +} + +static void +apple2_free (Display *dpy, Window window, void *closure) +{ + struct state *st = (struct state *) closure; + if (st->sim) { + st->sim->stepno = A2CONTROLLER_DONE; + if (apple2_one_frame (st->sim)) + abort(); /* should have freed! */ + } + free (st); +} + + +XSCREENSAVER_MODULE ("Apple2", apple2) -- cgit v1.2.3-55-g7522