1 /* xscreensaver, Copyright (c) 1998-2012 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)
920 if (state->cursor_y < rows - 1)
930 /* Dummy case - there is one and only one font. */
934 /* Dummy case - these interrupt escape sequences, so
935 they don't do anything in this state */
941 /* Dummy case - this is supposed to be ignored */
945 for(i = 0; i < NPAR; i++)
946 state->csiparam[i] = 0;
950 /* If the cursor is in column 39 and we print a character, then
951 that character shows up in column 39, and the cursor is no longer
952 visible on the screen (it's in "column 40".) If another character
953 is printed, then that character shows up in column 0, and the
954 cursor moves to column 1.
956 This is empirically what xterm and gnome-terminal do, so that must
957 be the right thing. (In xterm, the cursor vanishes, whereas; in
958 gnome-terminal, the cursor overprints the character in col 39.)
960 if (state->cursor_x >= cols)
963 if (state->cursor_y >= rows - 1)
969 a2_goto(st, state->cursor_y, state->cursor_x); /* clips range */
970 a2_ascii_printc (st, c,
971 state->termattrib.bf.bold,
972 state->termattrib.bf.blink,
973 state->termattrib.bf.rev,
987 case 'c': /* Reset */
991 case 'D': /* Linefeed */
992 if (state->cursor_y < rows - 1)
998 case 'E': /* Newline */
1000 state->escstate = 0;
1002 case 'M': /* Reverse newline */
1003 if (state->cursor_y > 0)
1005 state->escstate = 0;
1007 case '7': /* Save state */
1008 state->saved_x = state->cursor_x;
1009 state->saved_y = state->cursor_y;
1010 state->escstate = 0;
1012 case '8': /* Restore state */
1013 state->cursor_x = state->saved_x;
1014 state->cursor_y = state->saved_y;
1015 state->escstate = 0;
1018 state->escstate = 2;
1019 for(i = 0; i < NPAR; i++)
1020 state->csiparam[i] = 0;
1021 state->curparam = 0;
1023 case '%': /* Select charset */
1024 /* No, I don't support UTF-8, since the apple2 font
1025 isn't even Unicode anyway. We must still catch the
1026 last byte, though. */
1029 /* I don't support different fonts either - see above
1031 state->escstate = 3;
1034 /* Escape sequences not supported:
1037 * Z - Terminal identification
1039 * = - Other keypad change
1042 state->escstate = 0;
1051 state->escstate = 0;
1053 case '0': case '1': case '2': case '3': case '4':
1054 case '5': case '6': case '7': case '8': case '9':
1055 if (state->curparam < NPAR)
1056 state->csiparam[state->curparam] =
1057 (state->csiparam[state->curparam] * 10) + (c - '0');
1060 state->csiparam[++state->curparam] = 0;
1063 state->escstate = 3;
1066 for (i = 0; i < state->csiparam[0]; i++)
1068 if(++state->cursor_x > cols)
1070 state->cursor_x = 0;
1071 if (state->cursor_y < rows - 1)
1077 state->escstate = 0;
1080 state->cursor_x = 0;
1082 if (state->csiparam[0] == 0)
1083 state->csiparam[0] = 1;
1084 if ((state->cursor_y -= state->csiparam[0]) < 0)
1085 state->cursor_y = 0;
1086 state->escstate = 0;
1089 state->cursor_x = 0;
1092 if (state->csiparam[0] == 0)
1093 state->csiparam[0] = 1;
1094 if ((state->cursor_y += state->csiparam[0]) >= rows)
1095 state->cursor_y = rows - 1;
1096 state->escstate = 0;
1100 if (state->csiparam[0] == 0)
1101 state->csiparam[0] = 1;
1102 if ((state->cursor_x += state->csiparam[0]) >= cols)
1103 state->cursor_x = cols - 1;
1104 state->escstate = 0;
1107 if (state->csiparam[0] == 0)
1108 state->csiparam[0] = 1;
1109 if ((state->cursor_x -= state->csiparam[0]) < 0)
1110 state->cursor_x = 0;
1111 state->escstate = 0;
1114 if ((state->cursor_y = (state->csiparam[0] - 1)) >= rows)
1115 state->cursor_y = rows - 1;
1116 state->escstate = 0;
1120 if ((state->cursor_x = (state->csiparam[0] - 1)) >= cols)
1121 state->cursor_x = cols - 1;
1122 state->escstate = 0;
1126 if ((state->cursor_y = (state->csiparam[0] - 1)) >= rows)
1127 state->cursor_y = rows - 1;
1128 if ((state->cursor_x = (state->csiparam[1] - 1)) >= cols)
1129 state->cursor_x = cols - 1;
1130 if(state->cursor_y < 0)
1131 state->cursor_y = 0;
1132 if(state->cursor_x < 0)
1133 state->cursor_x = 0;
1134 state->escstate = 0;
1139 if (state->csiparam[0] == 0)
1140 start = cols * state->cursor_y + state->cursor_x;
1141 if (state->csiparam[0] == 1)
1142 end = cols * state->cursor_y + state->cursor_x;
1144 a2_goto(st, state->cursor_y, state->cursor_x);
1145 for (i = start; i < end; i++)
1147 a2_ascii_printc(st, ' ', False, False, False, False);
1149 state->escstate = 0;
1154 if (state->csiparam[0] == 0)
1155 start = state->cursor_x;
1156 if (state->csiparam[1] == 1)
1157 end = state->cursor_x;
1159 a2_goto(st, state->cursor_y, state->cursor_x);
1160 for (i = start; i < end; i++)
1162 a2_ascii_printc(st, ' ', False, False, False, False);
1164 state->escstate = 0;
1166 case 'm': /* Set attributes */
1167 for (i = 0; i <= state->curparam; i++)
1169 switch(state->csiparam[i])
1172 state->termattrib.w = 0;
1175 state->termattrib.bf.bold = 1;
1178 state->termattrib.bf.blink = 1;
1181 state->termattrib.bf.rev = 1;
1185 state->termattrib.bf.bold = 0;
1188 state->termattrib.bf.blink = 0;
1191 state->termattrib.bf.rev = 0;
1195 state->escstate = 0;
1197 case 's': /* Save position */
1198 state->saved_x = state->cursor_x;
1199 state->saved_y = state->cursor_y;
1200 state->escstate = 0;
1202 case 'u': /* Restore position */
1203 state->cursor_x = state->saved_x;
1204 state->cursor_y = state->saved_y;
1205 state->escstate = 0;
1207 case '?': /* DEC Private modes */
1208 if ((state->curparam != 0) || (state->csiparam[0] != 0))
1209 state->escstate = 0;
1212 /* Known unsupported CSIs:
1214 * L - Insert blank lines
1215 * M - Delete lines (I don't know what this means...)
1216 * P - Delete characters
1217 * X - Erase characters (difference with P being...?)
1218 * c - Terminal identification
1219 * g - Clear tab stop(s)
1220 * h - Set mode (Mainly due to its complexity and lack of good
1223 * m - Set mode (Phosphor is, per defenition, green on black)
1225 * q - Set keyboard LEDs
1226 * r - Set scrolling region (too exhausting - noone uses this,
1229 state->escstate = 0;
1234 state->escstate = 0;
1237 a2_goto(st, state->cursor_y, state->cursor_x);
1242 It's fun to put things like "gdb" as the command. For one, it's
1243 amusing how the standard mumble (version, no warranty, it's
1244 GNU/Linux dammit) occupies an entire screen on the Apple ][.
1248 terminal_controller(apple2_sim_t *sim, int *stepno, double *next_actiontime)
1250 apple2_state_t *st=sim->st;
1254 struct terminal_controller_data *mine;
1255 if (!sim->controller_data)
1256 sim->controller_data=calloc(sizeof(struct terminal_controller_data),1);
1257 mine=(struct terminal_controller_data *) sim->controller_data;
1258 mine->dpy = sim->dpy;
1260 mine->fast_p = global_fast_p;
1266 st->gr_mode |= A2_GR_FULL; /* Turn on color mode even through it's
1270 a2_prints(st, "APPLE ][");
1275 mine->tc = textclient_open (mine->dpy);
1276 textclient_reshape (mine->tc,
1277 SCREEN_COLS, SCREEN_ROWS,
1278 SCREEN_COLS, SCREEN_ROWS);
1282 *next_actiontime += 4.0;
1288 unsigned char buf[1024];
1292 elapsed=sim->curtime - mine->last_emit_time;
1293 mine->last_emit_time=sim->curtime;
1295 if (elapsed>1.0) nwant=1;
1296 if (nwant<1) nwant=1;
1297 if (nwant>4) nwant=4;
1300 nwant = sizeof(buf)-1;
1302 nr=terminal_read(mine, buf, nwant);
1303 for (i=0; i<nr; i++) {
1307 a2_vt100_printc (sim, mine, c);
1309 a2_ascii_printc (st, c, False, False, False, True);
1314 case A2CONTROLLER_FREE:
1315 terminal_closegen(mine);
1322 struct basic_controller_data {
1325 const char * const * progtext;
1329 double prog_start_time;
1330 char error_buf[256];
1334 Adding more programs is easy. Just add a listing here and to all_programs,
1335 then add the state machine to actually execute it to basic_controller.
1337 static const char * const moire_program[]={
1339 "20 FOR Y = 0 TO 190 STEP 2\n",
1340 "30 HCOLOR=4 : REM BLACK\n",
1341 "40 HPLOT 0,191-Y TO 279,Y\n",
1342 "60 HCOLOR=7 : REM WHITE\n",
1343 "80 HPLOT 0,190-Y TO 279,Y+1\n",
1345 "100 FOR X = 0 TO 278 STEP 3\n",
1347 "120 HPLOT 279-X,0 TO X,191\n",
1349 "150 HPLOT 278-X,0 TO X+1,191\n",
1354 static const char * const sinewave_program[] = {
1357 "30 FOR X = 0 TO 279\n",
1359 "35 HPLOT X,0 TO X,159\n",
1361 "40 Y = 80 + SIN(15*(X-K)/279) * 40\n",
1370 static const char * const dumb_program[]={
1371 "10 PRINT \"APPLE ][ ROOLZ! TRS-80 DROOLZ!\"\n",
1377 static const char * const random_lores_program[]={
1378 "1 REM APPLE ][ SCREEN SAVER\n",
1380 "100 COLOR= RND(1)*16\n",
1382 "110 X=RND(1)*40\n",
1383 "120 Y1=RND(1)*40\n",
1384 "130 Y2=RND(1)*40\n",
1385 "140 FOR Y = Y1 TO Y2\n",
1389 "210 Y=RND(1)*40\n",
1390 "220 X1=RND(1)*40\n",
1391 "230 X2=RND(1)*40\n",
1392 "240 FOR X = X1 TO X2\n",
1400 static char typo_map[256];
1402 static int make_typo(char *out_buf, const char *orig, char *err_buf)
1462 strcpy(out_buf, orig);
1463 for (i=0; out_buf[i]; i++) {
1464 char *p = out_buf+i;
1466 if (i>2 && p[-2]=='R' && p[-1]=='E' && p[0]=='M')
1469 if (isalpha(p[0]) &&
1478 sprintf(err_buf,"?SYNTAX ERROR\n");
1482 if (random()%10==0 && strlen(p)>=4 && (errc=typo_map[(int)(unsigned char)p[0]])) {
1483 int remain=strlen(p);
1484 int past=random()%(remain-2)+1;
1485 memmove(p+past+past, p, remain+1);
1487 for (j=0; j<past; j++) {
1496 static const struct {
1497 const char * const * progtext;
1500 {moire_program, 100},
1501 /*{dumb_program, 200}, */
1502 {sinewave_program, 400},
1503 {random_lores_program, 500},
1507 basic_controller(apple2_sim_t *sim, int *stepno, double *next_actiontime)
1509 apple2_state_t *st=sim->st;
1512 struct basic_controller_data *mine;
1513 if (!sim->controller_data)
1514 sim->controller_data=calloc(sizeof(struct basic_controller_data),1);
1515 mine=(struct basic_controller_data *) sim->controller_data;
1522 a2_prints(st, "APPLE ][");
1525 sim->typing_rate=0.2;
1527 i=random()%countof(all_programs);
1528 mine->progtext=all_programs[i].progtext;
1529 mine->progstep=all_programs[i].progstep;
1532 *next_actiontime += 1.0;
1537 if (st->cursx==0) a2_printc(st,']');
1538 if (mine->progtext[mine->prog_line]) {
1539 if (random()%4==0) {
1540 int err=make_typo(sim->typing_buf,
1541 mine->progtext[mine->prog_line],
1543 sim->typing=sim->typing_buf;
1550 sim->typing=mine->progtext[mine->prog_line++];
1558 sim->printing=mine->error_buf;
1563 if (st->cursx==0) a2_printc(st,']');
1564 *next_actiontime+=1.0;
1569 sim->typing="RUN\n";
1573 mine->prog_start_time=*next_actiontime;
1574 *stepno=mine->progstep;
1579 st->gr_mode=A2_GR_HIRES|A2_GR_FULL;
1580 for (i=0; i<24 && mine->y<192; i++)
1582 a2_hline(st, 4, 0, 191-mine->y, 279, mine->y);
1583 a2_hline(st, 7, 0, 191-mine->y-1, 279, mine->y+1);
1593 for (i=0; i<24 && mine->x<280; i++)
1595 a2_hline(st, 4, 279-mine->x, 0, mine->x, 192);
1596 a2_hline(st, 7, 279-mine->x-1, 0, mine->x+1, 192);
1599 if (mine->x >= 280) *stepno=120;
1603 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1608 mine->rep_str="\nAPPLE ][ ROOLZ! TRS-80 DROOLZ!";
1609 for (i=0; i<30; i++) {
1610 a2_prints(st, mine->rep_str);
1616 i=random()%strlen(mine->rep_str);
1617 while (mine->rep_pos != i) {
1618 a2_printc(st, mine->rep_str[mine->rep_pos]);
1620 if (!mine->rep_str[mine->rep_pos]) mine->rep_pos=0;
1622 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1625 /* sinewave_program */
1627 st->gr_mode=A2_GR_HIRES;
1632 for (i=0; i<48; i++) {
1633 int y=80 + (int)(75.0*sin(15.0*(mine->x-mine->k)/279.0));
1634 a2_hline(st, 0, mine->x, 0, mine->x, 159);
1635 a2_hplot(st, 3, mine->x, y);
1642 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1650 /* random_lores_program */
1652 st->gr_mode=A2_GR_LORES|A2_GR_FULL;
1657 for (i=0; i<10; i++) {
1658 int color,x,y,x1,x2,y1,y2;
1664 for (y=y1; y<y2; y++) a2_plot(st, color, x, y);
1669 for (x=x1; x<x2; x++) a2_plot(st, color, x, y);
1671 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1678 case A2CONTROLLER_FREE:
1686 static void (* const controllers[]) (apple2_sim_t *sim, int *stepno,
1687 double *next_actiontime) = {
1688 slideshow_controller,
1689 terminal_controller,
1697 void (*controller) (apple2_sim_t *sim, int *stepno, double *next_actiontime);
1702 apple2_init (Display *dpy, Window window)
1704 struct state *st = (struct state *) calloc (1, sizeof(*st));
1707 st->duration = get_integer_resource (dpy, "duration", "Integer");
1710 if (st->duration < 1) st->duration = 1;
1712 s = get_string_resource (dpy, "mode", "Mode");
1713 if (!s || !*s || !strcasecmp(s, "random"))
1714 st->random_p = True;
1715 else if (!strcasecmp(s, "text"))
1716 st->controller = terminal_controller;
1717 else if (!strcasecmp(s, "slideshow"))
1718 st->controller = slideshow_controller;
1719 else if (!strcasecmp(s, "basic"))
1720 st->controller = basic_controller;
1723 fprintf (stderr, "%s: mode must be text, slideshow, or random; not %s\n",
1729 global_program = get_string_resource (dpy, "program", "Program");
1730 global_fast_p = get_boolean_resource (dpy, "fast", "Boolean");
1733 /* Kludge for MacOS standalone mode: see OSX/SaverRunner.m. */
1735 const char *s = getenv ("XSCREENSAVER_STANDALONE");
1736 if (s && *s && strcmp(s, "0"))
1738 st->controller = terminal_controller;
1739 st->random_p = False;
1740 global_program = getenv ("SHELL");
1741 global_fast_p = True;
1746 if (! st->random_p) {
1747 if (st->controller == terminal_controller ||
1748 st->controller == slideshow_controller)
1749 st->duration = 999999; /* these run "forever" */
1755 static unsigned long
1756 apple2_draw (Display *dpy, Window window, void *closure)
1758 struct state *st = (struct state *) closure;
1762 st->controller = controllers[random() % (countof(controllers))];
1763 st->sim = apple2_start (dpy, window, st->duration, st->controller);
1766 if (! apple2_one_frame (st->sim)) {
1774 apple2_reshape (Display *dpy, Window window, void *closure,
1775 unsigned int w, unsigned int h)
1777 struct state *st = (struct state *) closure;
1778 analogtv_reconfigure (st->sim->dec);
1782 apple2_event (Display *dpy, Window window, void *closure, XEvent *event)
1784 struct state *st = (struct state *) closure;
1786 if (st->controller == terminal_controller &&
1787 event->xany.type == KeyPress) {
1788 terminal_keypress_handler (dpy, event, st->sim->controller_data);
1796 apple2_free (Display *dpy, Window window, void *closure)
1798 struct state *st = (struct state *) closure;
1800 st->sim->stepno = A2CONTROLLER_DONE;
1801 if (apple2_one_frame (st->sim))
1802 abort(); /* should have freed! */
1808 XSCREENSAVER_MODULE ("Apple2", apple2)