1 /* xscreensaver, Copyright (c) 1998-2014 Jamie Zawinski <jwz@jwz.org>
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
11 * Apple ][ CRT simulator, by Trevor Blackwell <tlb@tlb.org>
12 * with additional work by Jamie Zawinski <jwz@jwz.org>
13 * Pty and vt100 emulation by Fredrik Tolf <fredrik@dolda2000.com>
18 #endif /* HAVE_CONFIG_H */
27 #include "screenhack.h"
29 #include "textclient.h"
33 #define countof(x) (sizeof((x))/sizeof((*x)))
35 #define SCREEN_COLS 40
36 #define SCREEN_ROWS 24
39 /* Given a bitmask, returns the position and width of the field.
42 decode_mask (unsigned int mask, unsigned int *pos_ret, unsigned int *size_ret)
45 for (i = 0; i < 32; i++)
50 for (; i < 32; i++, j++)
51 if (! (mask & (1L << i)))
59 /* Given a value and a field-width, expands the field to fill out 8 bits.
62 spread_bits (unsigned char value, unsigned char width)
67 case 7: return (value << 1) | (value >> 6);
68 case 6: return (value << 2) | (value >> 4);
69 case 5: return (value << 3) | (value >> 2);
70 case 4: return (value << 4) | (value);
71 case 3: return (value << 5) | (value << 2) | (value >> 2);
72 case 2: return (value << 6) | (value << 4) | (value);
73 default: abort(); break;
78 /* Convert an XImage (of any size/depth/visual) to a 32bpp RGB array.
79 Scales it (without dithering) to WxH.
82 scale_image (Display *dpy, Window window, XImage *in,
83 int fromx, int fromy, int fromw, int fromh,
84 unsigned int *out, int w, int h)
88 unsigned int rpos=0, gpos=0, bpos=0; /* bitfield positions */
89 unsigned int rsiz=0, gsiz=0, bsiz=0;
90 unsigned long rmsk=0, gmsk=0, bmsk=0;
91 unsigned char spread_map[3][256];
92 XWindowAttributes xgwa;
95 if (fromx + fromw > in->width ||
96 fromy + fromh > in->height)
99 XGetWindowAttributes (dpy, window, &xgwa);
101 /* Compute the field offsets for RGB decoding in the XImage,
102 when in TrueColor mode. Otherwise we use the colormap.
104 if (visual_class (xgwa.screen, xgwa.visual) == PseudoColor ||
105 visual_class (xgwa.screen, xgwa.visual) == GrayScale)
107 int ncolors = visual_cells (xgwa.screen, xgwa.visual);
108 colors = (XColor *) calloc (sizeof (*colors), ncolors+1);
109 for (i = 0; i < ncolors; i++)
111 XQueryColors (dpy, xgwa.colormap, colors, ncolors);
115 visual_rgb_masks (xgwa.screen, xgwa.visual, &rmsk, &gmsk, &bmsk);
116 decode_mask (rmsk, &rpos, &rsiz);
117 decode_mask (gmsk, &gpos, &gsiz);
118 decode_mask (bmsk, &bpos, &bsiz);
120 for (i = 0; i < 256; i++)
122 spread_map[0][i] = spread_bits (i, rsiz);
123 spread_map[1][i] = spread_bits (i, gsiz);
124 spread_map[2][i] = spread_bits (i, bsiz);
128 scale = (fromw > fromh
130 : (float) fromh / h);
132 /* Scale the pixmap from window size to Apple][ screen size (but 32bpp)
134 for (y = 0; y < h-1; y++) /* iterate over dest pixels */
135 for (x = 0; x < w-1; x++)
138 unsigned int r=0, g=0, b=0;
140 int xx1 = x * scale + fromx;
141 int yy1 = y * scale + fromy;
142 int xx2 = (x+1) * scale + fromx;
143 int yy2 = (y+1) * scale + fromy;
145 /* Iterate over the source pixels contributing to this one, and sum. */
146 for (xx = xx1; xx < xx2; xx++)
147 for (yy = yy1; yy < yy2; yy++)
149 unsigned char rr, gg, bb;
150 unsigned long sp = ((xx > in->width || yy > in->height)
151 ? 0 : XGetPixel (in, xx, yy));
154 rr = colors[sp].red & 0xFF;
155 gg = colors[sp].green & 0xFF;
156 bb = colors[sp].blue & 0xFF;
160 rr = (sp & rmsk) >> rpos;
161 gg = (sp & gmsk) >> gpos;
162 bb = (sp & bmsk) >> bpos;
163 rr = spread_map[0][rr];
164 gg = spread_map[1][gg];
165 bb = spread_map[2][bb];
172 /* Scale summed pixel values down to 8/8/8 range */
173 i = (xx2 - xx1) * (yy2 - yy1);
179 out[y * w + x] = (r << 16) | (g << 8) | b;
184 /* Convert an XImage (of any size/depth/visual) to a 32bpp RGB array.
185 Picks a random sub-image out of the source image, and scales it to WxH.
188 pick_a2_subimage (Display *dpy, Window window, XImage *in,
189 unsigned int *out, int w, int h)
191 int fromx, fromy, fromw, fromh;
192 if (in->width <= w || in->height <= h)
203 double scale = (0.5 + frand(0.7) + frand(0.7) + frand(0.7));
206 } while (fromw > in->width ||
209 dw = (in->width - fromw) / 2; /* near the center! */
210 dh = (in->height - fromh) / 2;
212 fromx = (dw <= 0 ? 0 : (random() % dw) + (dw/2));
213 fromy = (dh <= 0 ? 0 : (random() % dh) + (dh/2));
216 scale_image (dpy, window, in,
217 fromx, fromy, fromw, fromh,
222 /* Floyd-Steinberg dither. Derived from ppmquant.c,
223 Copyright (c) 1989, 1991 by Jef Poskanzer.
226 a2_dither (unsigned int *in, unsigned char *out, int w, int h)
229 Apple ][ color map. Each pixel can only be 1 or 0, but what that
230 means depends on whether it's an odd or even pixel, and whether
231 the high bit in the byte is set or not. If it's 0, it's always
234 static const int a2_cmap[2][2][3] = {
237 {/* odd pixels = blue */ 0x00, 0x80, 0xff},
238 {/* even pixels = red */ 0xff, 0x80, 0x00}
242 {/* even pixels = purple */ 0xa0, 0x40, 0xa0},
243 {/* odd pixels = green */ 0x40, 0xff, 0x40}
248 unsigned int **pixels;
263 FILE *pipe = popen ("xv -", "w");
264 fprintf (pipe, "P6\n%d %d\n%d\n", w, h, 255);
265 for (y = 0; y < h; y++)
266 for (x = 0; x < w; x++)
268 unsigned int p = in[y * w + x];
269 unsigned int r = (p >> 16) & 0xFF;
270 unsigned int g = (p >> 8) & 0xFF;
271 unsigned int b = (p ) & 0xFF;
272 fprintf(pipe, "%c%c%c", r, g, b);
278 /* Initialize Floyd-Steinberg error vectors. */
279 this_rerr = (long *) calloc (w + 2, sizeof(long));
280 next_rerr = (long *) calloc (w + 2, sizeof(long));
281 this_gerr = (long *) calloc (w + 2, sizeof(long));
282 next_gerr = (long *) calloc (w + 2, sizeof(long));
283 this_berr = (long *) calloc (w + 2, sizeof(long));
284 next_berr = (long *) calloc (w + 2, sizeof(long));
287 /* #### do we really need more than one element of "pixels" at once?
289 pixels = (unsigned int **) malloc (h * sizeof (unsigned int *));
290 for (y = 0; y < h; y++)
291 pixels[y] = (unsigned int *) malloc (w * sizeof (unsigned int));
293 for (x = 0; x < w + 2; ++x)
295 this_rerr[x] = random() % (fs_scale * 2) - fs_scale;
296 this_gerr[x] = random() % (fs_scale * 2) - fs_scale;
297 this_berr[x] = random() % (fs_scale * 2) - fs_scale;
298 /* (random errors in [-1 .. 1]) */
301 for (y = 0; y < h; y++)
302 for (x = 0; x < w; x++)
303 pixels[y][x] = in[y * w + x];
305 for (y = 0; y < h; y++)
311 for (x = 0; x < w + 2; x++)
312 next_rerr[x] = next_gerr[x] = next_berr[x] = 0;
314 /* It's too complicated to go back and forth on alternate rows,
315 so we always go left-right here. It doesn't change the result
318 For each group of 7 pixels, we have to try it both with the
319 high bit=0 and =1. For each high bit value, we add up the
320 total error and pick the best one.
322 Because we have to go through each group of bits twice, we
323 don't propagate the error values through this_[rgb]err since
324 it would add them twice. So we keep seperate local_[rgb]err
325 variables for propagating error within the 7-pixel group.
329 for (xbyte=0; xbyte<280; xbyte+=7)
332 int best_error=2000000000;
336 int local_rerr=0, local_gerr=0, local_berr=0;
338 for (hibit=0; hibit<2; hibit++)
343 for (x=xbyte; x<xbyte+7; x++)
347 /* Use Floyd-Steinberg errors to adjust actual color. */
348 sr = ((pP[x] >> 16) & 0xFF) * brightness/256;
349 sg = ((pP[x] >> 8) & 0xFF) * brightness/256;
350 sb = ((pP[x] ) & 0xFF) * brightness/256;
351 sr += (this_rerr[x + 1] + local_rerr) / fs_scale;
352 sg += (this_gerr[x + 1] + local_gerr) / fs_scale;
353 sb += (this_berr[x + 1] + local_berr) / fs_scale;
356 else if (sr > maxval) sr = maxval;
358 else if (sg > maxval) sg = maxval;
360 else if (sb > maxval) sb = maxval;
362 /* This is the color we'd get if we set the bit 1. For 0,
364 r2=a2_cmap[hibit][x&1][0];
365 g2=a2_cmap[hibit][x&1][1];
366 b2=a2_cmap[hibit][x&1][2];
369 dist0 and dist1 are the error (Minkowski 2-metric
370 distances in the color space) for choosing 0 and
371 1 respectively. 0 is black, 1 is the color r2,g2,b2.
373 dist1= (sr-r2)*(sr-r2) + (sg-g2)*(sg-g2) + (sb-b2)*(sb-b2);
374 dist0= sr*sr + sg*sg + sb*sb;
378 byte |= 1 << (x-xbyte);
381 /* Wanted sr but got r2, so propagate sr-r2 */
382 local_rerr = (sr - r2) * fs_scale * 7/16;
383 local_gerr = (sg - g2) * fs_scale * 7/16;
384 local_berr = (sb - b2) * fs_scale * 7/16;
390 /* Wanted sr but got 0, so propagate sr */
391 local_rerr = sr * fs_scale * 7/16;
392 local_gerr = sg * fs_scale * 7/16;
393 local_berr = sb * fs_scale * 7/16;
397 if (tot_error < best_error)
400 best_error = tot_error;
404 /* Avoid alternating 7f and ff in all-white areas, because it makes
405 regular pink vertical lines */
406 if ((best_byte&0x7f)==0x7f && (prev_byte&0x7f)==0x7f)
411 Now that we've chosen values for all 8 bits of the byte, we
412 have to fill in the real pixel values into pP and propagate
413 all the error terms. We end up repeating a lot of the code
417 for (x=xbyte; x<xbyte+7; x++)
419 int bit=(best_byte>>(x-xbyte))&1;
420 hibit=(best_byte>>7)&1;
422 sr = (pP[x] >> 16) & 0xFF;
423 sg = (pP[x] >> 8) & 0xFF;
424 sb = (pP[x] ) & 0xFF;
425 sr += this_rerr[x + 1] / fs_scale;
426 sg += this_gerr[x + 1] / fs_scale;
427 sb += this_berr[x + 1] / fs_scale;
430 else if (sr > maxval) sr = maxval;
432 else if (sg > maxval) sg = maxval;
434 else if (sb > maxval) sb = maxval;
436 r2=a2_cmap[hibit][x&1][0] * bit;
437 g2=a2_cmap[hibit][x&1][1] * bit;
438 b2=a2_cmap[hibit][x&1][2] * bit;
440 pP[x] = (r2<<16) | (g2<<8) | (b2);
442 /* Propagate Floyd-Steinberg error terms. */
443 err = (sr - r2) * fs_scale;
444 this_rerr[x + 2] += (err * 7) / 16;
445 next_rerr[x ] += (err * 3) / 16;
446 next_rerr[x + 1] += (err * 5) / 16;
447 next_rerr[x + 2] += (err ) / 16;
448 err = (sg - g2) * fs_scale;
449 this_gerr[x + 2] += (err * 7) / 16;
450 next_gerr[x ] += (err * 3) / 16;
451 next_gerr[x + 1] += (err * 5) / 16;
452 next_gerr[x + 2] += (err ) / 16;
453 err = (sb - b2) * fs_scale;
454 this_berr[x + 2] += (err * 7) / 16;
455 next_berr[x ] += (err * 3) / 16;
456 next_berr[x + 1] += (err * 5) / 16;
457 next_berr[x + 2] += (err ) / 16;
461 And put the actual byte into out.
464 out[y*(w/7) + xbyte/7] = best_byte;
468 temp_err = this_rerr;
469 this_rerr = next_rerr;
470 next_rerr = temp_err;
471 temp_err = this_gerr;
472 this_gerr = next_gerr;
473 next_gerr = temp_err;
474 temp_err = this_berr;
475 this_berr = next_berr;
476 next_berr = temp_err;
492 /* let's see what we got... */
493 FILE *pipe = popen ("xv -", "w");
494 fprintf (pipe, "P6\n%d %d\n%d\n", w, h, 255);
495 for (y = 0; y < h; y++)
496 for (x = 0; x < w; x++)
498 unsigned int r = (pixels[y][x]>>16)&0xff;
499 unsigned int g = (pixels[y][x]>>8)&0xff;
500 unsigned int b = (pixels[y][x]>>0)&0xff;
501 fprintf(pipe, "%c%c%c", r, g, b);
508 typedef struct slideshow_data_s {
510 int render_img_lineno;
511 unsigned char *render_img;
513 Bool image_loading_p;
518 image_loaded_cb (Screen *screen, Window window, Drawable p,
519 const char *name, XRectangle *geometry,
522 Display *dpy = DisplayOfScreen (screen);
523 apple2_sim_t *sim = (apple2_sim_t *) closure;
524 slideshow_data *mine = (slideshow_data *) sim->controller_data;
525 XWindowAttributes xgwa;
529 unsigned int *buf32 = (unsigned int *) calloc (w, h * 4);
530 unsigned char *buf8 = (unsigned char *) calloc (w/7, h);
534 fprintf (stderr, "%s: out of memory (%dx%d)\n", progname, w, h);
538 XGetWindowAttributes (dpy, window, &xgwa);
540 image = XGetImage (dpy, p, 0, 0, xgwa.width, xgwa.height, ~0, ZPixmap);
541 XFreePixmap (dpy, p);
544 /* Scale the XImage down to Apple][ size, and convert it to a 32bpp
545 image (regardless of whether it started as TrueColor/PseudoColor.)
547 pick_a2_subimage (dpy, window, image, buf32, w, h);
550 XDestroyImage(image);
552 /* Then dither the 32bpp image to a 6-color Apple][ colormap.
554 a2_dither (buf32, buf8, w, h);
558 mine->image_loading_p = False;
559 mine->img_filename = (name ? strdup (name) : 0);
560 mine->render_img = buf8;
565 static const char *apple2_defaults [] = {
566 ".background: black",
567 ".foreground: white",
570 "*program: xscreensaver-text --cols 40",
571 "*metaSendsESC: True",
578 # endif /* !HAVE_FORKPTY */
584 static XrmOptionDescRec apple2_options [] = {
585 { "-mode", ".mode", XrmoptionSepArg, 0 },
586 { "-slideshow", ".mode", XrmoptionNoArg, "slideshow" },
587 { "-basic", ".mode", XrmoptionNoArg, "basic" },
588 { "-text", ".mode", XrmoptionNoArg, "text" },
589 { "-program", ".program", XrmoptionSepArg, 0 },
590 { "-duration", ".duration", XrmoptionSepArg, 0 },
591 { "-pty", ".usePty", XrmoptionNoArg, "True" },
592 { "-pipe", ".usePty", XrmoptionNoArg, "False" },
593 { "-meta", ".metaSendsESC", XrmoptionNoArg, "False" },
594 { "-esc", ".metaSendsESC", XrmoptionNoArg, "True" },
595 { "-bs", ".swapBSDEL", XrmoptionNoArg, "False" },
596 { "-del", ".swapBSDEL", XrmoptionNoArg, "True" },
597 { "-fast", ".fast", XrmoptionNoArg, "True" },
603 TODO: this should load 10 images at startup time, then cycle through them
604 to avoid the pause while it loads.
607 static void slideshow_controller(apple2_sim_t *sim, int *stepno,
608 double *next_actiontime)
610 apple2_state_t *st=sim->st;
612 slideshow_data *mine;
614 if (!sim->controller_data)
615 sim->controller_data = calloc (1, sizeof(*mine));
616 mine = (slideshow_data *) sim->controller_data;
624 sim->typing_rate = 0.3;
625 sim->dec->powerup=0.0;
628 a2_prints(st, "APPLE ][");
637 XWindowAttributes xgwa;
639 XGetWindowAttributes (sim->dpy, sim->window, &xgwa);
640 p = XCreatePixmap (sim->dpy, sim->window, xgwa.width, xgwa.height,
642 mine->image_loading_p = True;
643 load_image_async (xgwa.screen, sim->window, p, image_loaded_cb, sim);
645 /* pause with a blank screen for a bit, while the image loads in the
647 *next_actiontime += 2.0;
653 if (! mine->image_loading_p) { /* image is finally loaded */
659 *next_actiontime += 3.0;
674 st->gr_mode=A2_GR_HIRES;
675 if (mine->img_filename) {
676 char *basename, *tmp;
679 basename = tmp = strdup (mine->img_filename);
682 char *slash = strchr(basename, '/');
683 if (!slash || !slash[1]) break;
687 char *dot=strrchr(basename,'.');
690 if (strlen(basename)>20) basename[20]=0;
691 for (s=basename; *s; s++) {
693 if (*s <= ' ') *s = '_';
695 sprintf(sim->typing_buf, "BLOAD %s\n", basename);
696 sim->typing = sim->typing_buf;
700 sim->typing = "BLOAD IMAGE\n";
702 mine->render_img_lineno=0;
708 *next_actiontime += 0.7;
713 if (mine->render_img_lineno>=192) {
715 sim->typing="POKE 49234,0\n";
720 for (i=0; i<6 && mine->render_img_lineno<192; i++) {
721 a2_display_image_loading(st, mine->render_img,
722 mine->render_img_lineno++);
725 /* The disk would have to seek every 13 sectors == 78 lines.
726 (This ain't no newfangled 16-sector operating system) */
727 if ((mine->render_img_lineno%78)==0) {
728 *next_actiontime += 0.5;
730 *next_actiontime += 0.08;
735 st->gr_mode |= A2_GR_FULL;
737 /* Note that sim->delay is sometimes "infinite" in this controller.
738 These images are kinda dull anyway, so don't leave it on too long. */
739 *next_actiontime += 2;
744 sim->typing="POKE 49235,0\n";
750 st->gr_mode &= ~A2_GR_FULL;
751 if (mine->render_img) {
752 free(mine->render_img);
753 mine->render_img=NULL;
755 if (mine->img_filename) {
756 free(mine->img_filename);
757 mine->img_filename=NULL;
763 /* Do nothing, just wait */
764 *next_actiontime += 2.0;
765 *stepno = A2CONTROLLER_FREE;
768 case A2CONTROLLER_FREE:
769 /* It is possible that still image is being loaded,
770 in that case mine cannot be freed, because
771 callback function tries to use it, so wait.
773 if (mine->image_loading_p) {
777 free(mine->render_img);
778 free(mine->img_filename);
788 struct terminal_controller_data {
792 double last_emit_time;
798 int cursor_x, cursor_y;
799 int saved_x, saved_y;
800 int unicruds; char unicrud[7];
803 unsigned int bold : 1;
804 unsigned int blink : 1;
805 unsigned int rev : 1;
814 /* The structure of closure linkage throughout this code is so amazingly
815 baroque that I can't get to the 'struct state' from where I need it. */
816 static const char *global_program;
817 static Bool global_fast_p;
821 terminal_closegen(struct terminal_controller_data *mine)
824 textclient_close (mine->tc);
830 terminal_read(struct terminal_controller_data *mine, unsigned char *buf, int n)
832 if (!mine || !mine->tc) {
836 for (i = 0; i < n; i++) {
837 int c = textclient_getc (mine->tc);
849 terminal_keypress_handler (Display *dpy, XEvent *event, void *data)
851 struct terminal_controller_data *mine =
852 (struct terminal_controller_data *) data;
854 if (event->xany.type == KeyPress && mine->tc)
855 return textclient_putc (mine->tc, &event->xkey);
861 a2_ascii_printc (apple2_state_t *st, unsigned char c,
862 Bool bold_p, Bool blink_p, Bool rev_p,
865 if (c >= 'a' && c <= 'z') /* upcase lower-case chars */
869 else if ((c >= 'A'+128) || /* upcase and blink */
870 (c < ' ' && c != 014 && /* high-bit & ctl chrs */
871 c != '\r' && c != '\n' && c!='\t'))
873 c = (c & 0x1F) | 0x80;
875 else if (c >= 'A' && c <= 'Z') /* invert upper-case chars */
880 if (bold_p) c |= 0xc0;
881 if (blink_p) c = (c & ~0x40) | 0x80;
882 if (rev_p) c |= 0xc0;
887 a2_printc_noscroll(st, c);
892 a2_vt100_printc (apple2_sim_t *sim, struct terminal_controller_data *state,
895 apple2_state_t *st=sim->st;
896 int cols = SCREEN_COLS;
897 int rows = SCREEN_ROWS;
902 /* Mostly duplicated in phosphor.c */
904 switch (state->escstate)
910 /* Dummy case - we don't want the screensaver to beep */
911 /* #### But maybe this should flash the screen? */
914 if (state->cursor_x > 0)
918 if (state->cursor_x < cols - 8)
920 state->cursor_x = (state->cursor_x & ~7) + 8;
925 if (state->cursor_y < rows - 1)
932 # ifndef HAVE_FORKPTY
933 state->cursor_x = 0; /* No ptys on iPhone; assume CRLF. */
937 if (state->cursor_y < rows - 1)
947 /* Dummy case - there is one and only one font. */
951 /* Dummy case - these interrupt escape sequences, so
952 they don't do anything in this state */
958 /* Dummy case - this is supposed to be ignored */
962 for(i = 0; i < NPAR; i++)
963 state->csiparam[i] = 0;
968 /* states 102-106 are for UTF-8 decoding */
970 if ((c & 0xE0) == 0xC0) { /* 110xxxxx - 11 bits, 2 bytes */
972 state->unicrud[0] = c;
973 state->escstate = 102;
975 } else if ((c & 0xF0) == 0xE0) { /* 1110xxxx - 16 bits, 3 bytes */
977 state->unicrud[0] = c;
978 state->escstate = 103;
980 } else if ((c & 0xF8) == 0xF0) { /* 11110xxx - 21 bits, 4 bytes */
982 state->unicrud[0] = c;
983 state->escstate = 104;
985 } else if ((c & 0xFC) == 0xF8) { /* 111110xx - 26 bits, 5 bytes */
987 state->unicrud[0] = c;
988 state->escstate = 105;
990 } else if ((c & 0xFE) == 0xFC) { /* 1111110x - 31 bits, 6 bytes */
992 state->unicrud[0] = c;
993 state->escstate = 106;
999 /* If the cursor is in column 39 and we print a character, then
1000 that character shows up in column 39, and the cursor is no longer
1001 visible on the screen (it's in "column 40".) If another character
1002 is printed, then that character shows up in column 0, and the
1003 cursor moves to column 1.
1005 This is empirically what xterm and gnome-terminal do, so that must
1006 be the right thing. (In xterm, the cursor vanishes, whereas; in
1007 gnome-terminal, the cursor overprints the character in col 39.)
1009 if (state->cursor_x >= cols)
1011 state->cursor_x = 0;
1012 if (state->cursor_y >= rows - 1)
1018 a2_goto(st, state->cursor_y, state->cursor_x); /* clips range */
1019 a2_ascii_printc (st, c,
1020 state->termattrib.bf.bold,
1021 state->termattrib.bf.blink,
1022 state->termattrib.bf.rev,
1034 state->escstate = 0;
1036 case 'c': /* Reset */
1038 state->escstate = 0;
1040 case 'D': /* Linefeed */
1041 if (state->cursor_y < rows - 1)
1045 state->escstate = 0;
1047 case 'E': /* Newline */
1048 state->cursor_x = 0;
1049 state->escstate = 0;
1051 case 'M': /* Reverse newline */
1052 if (state->cursor_y > 0)
1054 state->escstate = 0;
1056 case '7': /* Save state */
1057 state->saved_x = state->cursor_x;
1058 state->saved_y = state->cursor_y;
1059 state->escstate = 0;
1061 case '8': /* Restore state */
1062 state->cursor_x = state->saved_x;
1063 state->cursor_y = state->saved_y;
1064 state->escstate = 0;
1067 state->escstate = 2;
1068 for(i = 0; i < NPAR; i++)
1069 state->csiparam[i] = 0;
1070 state->curparam = 0;
1072 case '%': /* Select charset */
1073 /* @: Select default (ISO 646 / ISO 8859-1)
1075 8: Select UTF-8 (obsolete)
1077 We can just ignore this and always process UTF-8, I think?
1078 We must still catch the last byte, though.
1082 /* I don't support different fonts either - see above
1084 state->escstate = 3;
1087 /* Escape sequences not supported:
1090 * Z - Terminal identification
1092 * = - Other keypad change
1095 state->escstate = 0;
1104 state->escstate = 0;
1106 case '0': case '1': case '2': case '3': case '4':
1107 case '5': case '6': case '7': case '8': case '9':
1108 if (state->curparam < NPAR)
1109 state->csiparam[state->curparam] =
1110 (state->csiparam[state->curparam] * 10) + (c - '0');
1113 state->csiparam[++state->curparam] = 0;
1116 state->escstate = 3;
1119 for (i = 0; i < state->csiparam[0]; i++)
1121 if(++state->cursor_x > cols)
1123 state->cursor_x = 0;
1124 if (state->cursor_y < rows - 1)
1130 state->escstate = 0;
1133 state->cursor_x = 0;
1135 if (state->csiparam[0] == 0)
1136 state->csiparam[0] = 1;
1137 if ((state->cursor_y -= state->csiparam[0]) < 0)
1138 state->cursor_y = 0;
1139 state->escstate = 0;
1142 state->cursor_x = 0;
1145 if (state->csiparam[0] == 0)
1146 state->csiparam[0] = 1;
1147 if ((state->cursor_y += state->csiparam[0]) >= rows)
1148 state->cursor_y = rows - 1;
1149 state->escstate = 0;
1153 if (state->csiparam[0] == 0)
1154 state->csiparam[0] = 1;
1155 if ((state->cursor_x += state->csiparam[0]) >= cols)
1156 state->cursor_x = cols - 1;
1157 state->escstate = 0;
1160 if (state->csiparam[0] == 0)
1161 state->csiparam[0] = 1;
1162 if ((state->cursor_x -= state->csiparam[0]) < 0)
1163 state->cursor_x = 0;
1164 state->escstate = 0;
1167 if ((state->cursor_y = (state->csiparam[0] - 1)) >= rows)
1168 state->cursor_y = rows - 1;
1169 state->escstate = 0;
1173 if ((state->cursor_x = (state->csiparam[0] - 1)) >= cols)
1174 state->cursor_x = cols - 1;
1175 state->escstate = 0;
1179 if ((state->cursor_y = (state->csiparam[0] - 1)) >= rows)
1180 state->cursor_y = rows - 1;
1181 if ((state->cursor_x = (state->csiparam[1] - 1)) >= cols)
1182 state->cursor_x = cols - 1;
1183 if(state->cursor_y < 0)
1184 state->cursor_y = 0;
1185 if(state->cursor_x < 0)
1186 state->cursor_x = 0;
1187 state->escstate = 0;
1192 if (state->csiparam[0] == 0)
1193 start = cols * state->cursor_y + state->cursor_x;
1194 if (state->csiparam[0] == 1)
1195 end = cols * state->cursor_y + state->cursor_x;
1197 a2_goto(st, state->cursor_y, state->cursor_x);
1198 for (i = start; i < end; i++)
1200 a2_ascii_printc(st, ' ', False, False, False, False);
1202 state->escstate = 0;
1207 if (state->csiparam[0] == 0)
1208 start = state->cursor_x;
1209 if (state->csiparam[1] == 1)
1210 end = state->cursor_x;
1212 a2_goto(st, state->cursor_y, state->cursor_x);
1213 for (i = start; i < end; i++)
1215 a2_ascii_printc(st, ' ', False, False, False, False);
1217 state->escstate = 0;
1219 case 'm': /* Set attributes */
1220 for (i = 0; i <= state->curparam; i++)
1222 switch(state->csiparam[i])
1225 state->termattrib.w = 0;
1228 state->termattrib.bf.bold = 1;
1231 state->termattrib.bf.blink = 1;
1234 state->termattrib.bf.rev = 1;
1238 state->termattrib.bf.bold = 0;
1241 state->termattrib.bf.blink = 0;
1244 state->termattrib.bf.rev = 0;
1248 state->escstate = 0;
1250 case 's': /* Save position */
1251 state->saved_x = state->cursor_x;
1252 state->saved_y = state->cursor_y;
1253 state->escstate = 0;
1255 case 'u': /* Restore position */
1256 state->cursor_x = state->saved_x;
1257 state->cursor_y = state->saved_y;
1258 state->escstate = 0;
1260 case '?': /* DEC Private modes */
1261 if ((state->curparam != 0) || (state->csiparam[0] != 0))
1262 state->escstate = 0;
1265 /* Known unsupported CSIs:
1267 * L - Insert blank lines
1268 * M - Delete lines (I don't know what this means...)
1269 * P - Delete characters
1270 * X - Erase characters (difference with P being...?)
1271 * c - Terminal identification
1272 * g - Clear tab stop(s)
1273 * h - Set mode (Mainly due to its complexity and lack of good
1276 * m - Set mode (Phosphor is, per defenition, green on black)
1278 * q - Set keyboard LEDs
1279 * r - Set scrolling region (too exhausting - noone uses this,
1282 state->escstate = 0;
1287 state->escstate = 0;
1296 int total = state->escstate - 100; /* see what I did there */
1297 if (state->unicruds < total) {
1298 /* Buffer more bytes of the UTF-8 sequence */
1299 state->unicrud[state->unicruds++] = c;
1302 if (state->unicruds >= total) {
1303 /* Done! Convert it to ASCII and print that. */
1305 state->unicrud[state->unicruds] = 0;
1306 s = utf8_to_latin1 ((const char *) state->unicrud, True);
1307 state->unicruds = 0;
1308 state->escstate = 0;
1323 a2_goto(st, state->cursor_y, state->cursor_x);
1328 It's fun to put things like "gdb" as the command. For one, it's
1329 amusing how the standard mumble (version, no warranty, it's
1330 GNU/Linux dammit) occupies an entire screen on the Apple ][.
1334 terminal_controller(apple2_sim_t *sim, int *stepno, double *next_actiontime)
1336 apple2_state_t *st=sim->st;
1340 struct terminal_controller_data *mine;
1341 if (!sim->controller_data)
1342 sim->controller_data=calloc(sizeof(struct terminal_controller_data),1);
1343 mine=(struct terminal_controller_data *) sim->controller_data;
1344 mine->dpy = sim->dpy;
1346 mine->fast_p = global_fast_p;
1352 st->gr_mode |= A2_GR_FULL; /* Turn on color mode even through it's
1356 a2_prints(st, "APPLE ][");
1361 mine->tc = textclient_open (mine->dpy);
1362 textclient_reshape (mine->tc,
1363 SCREEN_COLS, SCREEN_ROWS,
1364 SCREEN_COLS, SCREEN_ROWS,
1369 *next_actiontime += 4.0;
1372 mine->last_emit_time = sim->curtime;
1378 Bool first_line_p = (*stepno == 10);
1379 unsigned char buf[1024];
1383 elapsed=sim->curtime - mine->last_emit_time;
1385 nwant = elapsed * 25.0; /* characters per second */
1392 if (nwant > 40) nwant = 40;
1395 nwant = sizeof(buf)-1;
1397 if (nwant <= 0) break;
1399 mine->last_emit_time = sim->curtime;
1401 nr=terminal_read(mine, buf, nwant);
1402 for (i=0; i<nr; i++) {
1406 a2_vt100_printc (sim, mine, c);
1408 a2_ascii_printc (st, c, False, False, False, True);
1413 case A2CONTROLLER_FREE:
1414 terminal_closegen(mine);
1421 struct basic_controller_data {
1424 const char * const * progtext;
1428 double prog_start_time;
1429 char error_buf[256];
1433 Adding more programs is easy. Just add a listing here and to all_programs,
1434 then add the state machine to actually execute it to basic_controller.
1436 static const char * const moire_program[]={
1438 "20 FOR Y = 0 TO 190 STEP 2\n",
1439 "30 HCOLOR=4 : REM BLACK\n",
1440 "40 HPLOT 0,191-Y TO 279,Y\n",
1441 "60 HCOLOR=7 : REM WHITE\n",
1442 "80 HPLOT 0,190-Y TO 279,Y+1\n",
1444 "100 FOR X = 0 TO 278 STEP 3\n",
1446 "120 HPLOT 279-X,0 TO X,191\n",
1448 "150 HPLOT 278-X,0 TO X+1,191\n",
1453 static const char * const sinewave_program[] = {
1456 "30 FOR X = 0 TO 279\n",
1458 "35 HPLOT X,0 TO X,159\n",
1460 "40 Y = 80 + SIN(15*(X-K)/279) * 40\n",
1469 static const char * const dumb_program[]={
1470 "10 PRINT \"APPLE ][ ROOLZ! TRS-80 DROOLZ!\"\n",
1476 static const char * const random_lores_program[]={
1477 "1 REM APPLE ][ SCREEN SAVER\n",
1479 "100 COLOR= RND(1)*16\n",
1481 "110 X=RND(1)*40\n",
1482 "120 Y1=RND(1)*40\n",
1483 "130 Y2=RND(1)*40\n",
1484 "140 FOR Y = Y1 TO Y2\n",
1488 "210 Y=RND(1)*40\n",
1489 "220 X1=RND(1)*40\n",
1490 "230 X2=RND(1)*40\n",
1491 "240 FOR X = X1 TO X2\n",
1499 static char typo_map[256];
1501 static int make_typo(char *out_buf, const char *orig, char *err_buf)
1561 strcpy(out_buf, orig);
1562 for (i=0; out_buf[i]; i++) {
1563 char *p = out_buf+i;
1565 if (i>2 && p[-2]=='R' && p[-1]=='E' && p[0]=='M')
1568 if (isalpha(p[0]) &&
1577 sprintf(err_buf,"?SYNTAX ERROR\n");
1581 if (random()%10==0 && strlen(p)>=4 && (errc=typo_map[(int)(unsigned char)p[0]])) {
1582 int remain=strlen(p);
1583 int past=random()%(remain-2)+1;
1584 memmove(p+past+past, p, remain+1);
1586 for (j=0; j<past; j++) {
1595 static const struct {
1596 const char * const * progtext;
1599 {moire_program, 100},
1600 /*{dumb_program, 200}, */
1601 {sinewave_program, 400},
1602 {random_lores_program, 500},
1606 basic_controller(apple2_sim_t *sim, int *stepno, double *next_actiontime)
1608 apple2_state_t *st=sim->st;
1611 struct basic_controller_data *mine;
1612 if (!sim->controller_data)
1613 sim->controller_data=calloc(sizeof(struct basic_controller_data),1);
1614 mine=(struct basic_controller_data *) sim->controller_data;
1621 a2_prints(st, "APPLE ][");
1624 sim->typing_rate=0.2;
1626 i=random()%countof(all_programs);
1627 mine->progtext=all_programs[i].progtext;
1628 mine->progstep=all_programs[i].progstep;
1631 *next_actiontime += 1.0;
1636 if (st->cursx==0) a2_printc(st,']');
1637 if (mine->progtext[mine->prog_line]) {
1638 if (random()%4==0) {
1639 int err=make_typo(sim->typing_buf,
1640 mine->progtext[mine->prog_line],
1642 sim->typing=sim->typing_buf;
1649 sim->typing=mine->progtext[mine->prog_line++];
1657 sim->printing=mine->error_buf;
1662 if (st->cursx==0) a2_printc(st,']');
1663 *next_actiontime+=1.0;
1668 sim->typing="RUN\n";
1672 mine->prog_start_time=*next_actiontime;
1673 *stepno=mine->progstep;
1678 st->gr_mode=A2_GR_HIRES|A2_GR_FULL;
1679 for (i=0; i<24 && mine->y<192; i++)
1681 a2_hline(st, 4, 0, 191-mine->y, 279, mine->y);
1682 a2_hline(st, 7, 0, 191-mine->y-1, 279, mine->y+1);
1692 for (i=0; i<24 && mine->x<280; i++)
1694 a2_hline(st, 4, 279-mine->x, 0, mine->x, 192);
1695 a2_hline(st, 7, 279-mine->x-1, 0, mine->x+1, 192);
1698 if (mine->x >= 280) *stepno=120;
1702 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1707 mine->rep_str="\nAPPLE ][ ROOLZ! TRS-80 DROOLZ!";
1708 for (i=0; i<30; i++) {
1709 a2_prints(st, mine->rep_str);
1715 i=random()%strlen(mine->rep_str);
1716 while (mine->rep_pos != i) {
1717 a2_printc(st, mine->rep_str[mine->rep_pos]);
1719 if (!mine->rep_str[mine->rep_pos]) mine->rep_pos=0;
1721 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1724 /* sinewave_program */
1726 st->gr_mode=A2_GR_HIRES;
1731 for (i=0; i<48; i++) {
1732 int y=80 + (int)(75.0*sin(15.0*(mine->x-mine->k)/279.0));
1733 a2_hline(st, 0, mine->x, 0, mine->x, 159);
1734 a2_hplot(st, 3, mine->x, y);
1741 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1749 /* random_lores_program */
1751 st->gr_mode=A2_GR_LORES|A2_GR_FULL;
1756 for (i=0; i<10; i++) {
1757 int color,x,y,x1,x2,y1,y2;
1763 for (y=y1; y<y2; y++) a2_plot(st, color, x, y);
1768 for (x=x1; x<x2; x++) a2_plot(st, color, x, y);
1770 if (*next_actiontime > mine->prog_start_time+sim->delay) *stepno=999;
1777 case A2CONTROLLER_FREE:
1785 static void (* const controllers[]) (apple2_sim_t *sim, int *stepno,
1786 double *next_actiontime) = {
1787 slideshow_controller,
1788 terminal_controller,
1796 void (*controller) (apple2_sim_t *sim, int *stepno, double *next_actiontime);
1801 apple2_init (Display *dpy, Window window)
1803 struct state *st = (struct state *) calloc (1, sizeof(*st));
1806 st->duration = get_integer_resource (dpy, "duration", "Integer");
1809 if (st->duration < 1) st->duration = 1;
1811 s = get_string_resource (dpy, "mode", "Mode");
1812 if (!s || !*s || !strcasecmp(s, "random"))
1813 st->random_p = True;
1814 else if (!strcasecmp(s, "text"))
1815 st->controller = terminal_controller;
1816 else if (!strcasecmp(s, "slideshow"))
1817 st->controller = slideshow_controller;
1818 else if (!strcasecmp(s, "basic"))
1819 st->controller = basic_controller;
1822 fprintf (stderr, "%s: mode must be text, slideshow, or random; not %s\n",
1828 global_program = get_string_resource (dpy, "program", "Program");
1829 global_fast_p = get_boolean_resource (dpy, "fast", "Boolean");
1832 /* Kludge for MacOS standalone mode: see OSX/SaverRunner.m. */
1834 const char *s = getenv ("XSCREENSAVER_STANDALONE");
1835 if (s && *s && strcmp(s, "0"))
1837 st->controller = terminal_controller;
1838 st->random_p = False;
1839 global_program = getenv ("SHELL");
1840 global_fast_p = True;
1845 if (! st->random_p) {
1846 if (st->controller == terminal_controller ||
1847 st->controller == slideshow_controller)
1848 st->duration = 999999; /* these run "forever" */
1854 static unsigned long
1855 apple2_draw (Display *dpy, Window window, void *closure)
1857 struct state *st = (struct state *) closure;
1861 st->controller = controllers[random() % (countof(controllers))];
1862 st->sim = apple2_start (dpy, window, st->duration, st->controller);
1865 if (! apple2_one_frame (st->sim)) {
1877 apple2_reshape (Display *dpy, Window window, void *closure,
1878 unsigned int w, unsigned int h)
1880 struct state *st = (struct state *) closure;
1882 analogtv_reconfigure (st->sim->dec);
1886 apple2_event (Display *dpy, Window window, void *closure, XEvent *event)
1888 struct state *st = (struct state *) closure;
1891 st->controller == terminal_controller &&
1892 event->xany.type == KeyPress) {
1893 terminal_keypress_handler (dpy, event, st->sim->controller_data);
1901 apple2_free (Display *dpy, Window window, void *closure)
1903 struct state *st = (struct state *) closure;
1905 st->sim->stepno = A2CONTROLLER_DONE;
1906 if (apple2_one_frame (st->sim))
1907 abort(); /* should have freed! */
1913 XSCREENSAVER_MODULE ("Apple2", apple2)