/* starfish.c * version 0.2 * * Copyright (C) 2004, Andy Goth * http://ioioio.net/ * * This crap, I mean program, is available under the GNU General Public * License, and therefore has no warranty. (As if anyone would ever want to * reuse my code!) * * This program only works in the Linux console. And when I say console, I * mean *text console*. It might work in the framebuffer console, but it'll be * slow (actually it's slow either way). Things probably look best in 80x25. * When you see it running, remind yourself that you are still in text mode. * * $ gcc -o starfish starfish.c -lm -lcurses * $ ./starfish * * To quit the program, press any key. I recommend you do it quickly, before * you fall in and are lost forever. And did I mention there's no warranty!? * * Whatever you do, don't try to run it in the background or change virtual * consoles (unless you're using fbcon). The Linux VGA console really should * add support for different fonts for each virtual console, like fbcon does... * * This program would be a lot faster if it had direct access to VRAM, but as * it is, it doesn't even require root access. Hooray for that. If the Linux * console added some escape sequences to change the VGA font, this program * could run over telnet/ssh. Frightening. * * My code has a bitmapped image in mind that it wants to display, but it must * convert it into a (funky) character-cell pseudo-ASCII-art grid (using * UTF-8 to encode control characters, no less!), which in turn must be * converted into terminal-control escape sequences to pass to the kernel, * which then converts from escape sequences to a character-cell grid which it * hands off to the VGA, which converts from fonts and characters into a * bitmapped image. A more direct approach would be nice... :^) * * So, in search of said direct approach, I tried using /dev/vcsa*, which * allows me to write raw character and attribute tothe screen using lseek() * and write(), but amazingly enough I actually got better performance with * the old code, even though it added four more levels of translation * (character code to UTF-8, color code to SGR; UTF-8 to character code, SGR to * color code). The kernel vcsa driver probably needs optimization. */ #define __KERNEL_STRICT_NAMES #include #include #include #include #include #include #include #include #include #include /* Change this to 8 or 9 as appropriate, or if you're using some horrid laptop * screen with fixed pixels, use 0. */ #define FONT_WIDTH 9 #if FONT_WIDTH == 8 /* 640x400 screen. */ #define ASPECT ( 5. / 12.) #elif FONT_WIDTH == 9 /* 720x400 screen. */ #define ASPECT (10. / 27.) #else /* Assuming square dots. */ #define ASPECT ( 1. / 2.) #endif static struct console_font_op old_font; static struct termios old_term; static void cleanup(int sig) { /* Restore font. */ if (old_font.data != NULL) { old_font.op = KD_FONT_OP_SET; if (ioctl(1, KDFONTOP, &old_font) == -1) { perror("ioctl"); } free(old_font.data); } /* Restore terminal. */ if (isatty(1) && tcsetattr(1, TCSAFLUSH, &old_term) == -1) { perror("tcsetattr"); } /* Disable UTF-8. */ printf("\e%%@"); /* Clear screen. */ printf("\e[0m\e[2J\e[1;1H"); /* Prepare to evacuate soul... */ exit(sig == -1 ? EXIT_FAILURE : EXIT_SUCCESS); } int main(int argc, char** argv) { int i, j, y, x; int xc, yc, h, a, d; int rows, cols; struct console_font_op new_font; struct termios new_term; unsigned char chr, attr, prev_attr; if (isatty(1)) { /* Get old terminal settings. */ if (tcgetattr(1, &old_term) == -1) { perror("tcgetattr"); return EXIT_FAILURE; } /* Set raw mode (all I really need is to disable the line buffer). */ new_term = old_term; cfmakeraw(&new_term); if (tcsetattr(1, TCSANOW, &new_term) == -1) { perror("tcsetattr"); cleanup(-1); } /* Get old font. */ old_font.op = KD_FONT_OP_GET; old_font.flags = 0; old_font.data = malloc(512 * 32); old_font.width = 8; old_font.height = 32; old_font.charcount = 512; if (ioctl(1, KDFONTOP, &old_font) == -1) { perror("ioctl"); free(old_font.data); cleanup(-1); } /* Set new font. */ new_font.op = KD_FONT_OP_SET; new_font.flags = 0; new_font.data = malloc(256 * 32); new_font.width = 8; new_font.height = 2; new_font.charcount = 256; for (i = 0; i < 256; ++i) { new_font.data[i * 32 + 0] = new_font.data[i * 32 + 1] = (char) (((i & 128) >> 7) | ((i & 64) >> 5) | ((i & 32) >> 3) | ((i & 16) >> 1) | ((i & 8) << 1) | ((i & 4) << 3) | ((i & 2) << 5) | ((i & 1) << 7)); } if (ioctl(1, KDFONTOP, &new_font) == -1) { perror("ioctl"); if (tcsetattr(0, TCSANOW, &old_term) == -1) { perror("tcsetattr"); } return EXIT_FAILURE; } free(new_font.data); } else { /* Redirecting to a file, for some weird reason... */ old_font.data = NULL; } /* Arrange to restore sanity to the terminal. */ signal(SIGINT , cleanup); signal(SIGTERM, cleanup); signal(SIGIO , cleanup); /* Configure stdin to use SIGIO. */ fcntl(0, F_SETOWN, getpid()); fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_ASYNC | O_NONBLOCK); /* Get screen size. */ rows = tigetnum("lines"); if (rows <= 0) rows = 200; cols = tigetnum("cols" ); if (cols <= 0) cols = 80; /* Enable UTF-8. */ printf("\e%%G"); /* Main loop. */ prev_attr = 0xff; for (j = 0;; ++j) { /* Start at the upper-lefthand corner of the screen. */ printf("\e[1;1H"); /* Screen parameters (assuming 80x25): * * - Resolution, dots : 640x400 (720x400) * - Resolution, pixels : 640x200 * - Resolution, characters : 80x200 * - Character cell, dots : 8x2 ( 9x2 ) * - Character cell, pixels : 8x1 * - Dots per pixel (WxH) : 1x2 * - Pixel aspect (W:H) : 5:12 ( 10:27 ) * - Screen size, bytes : 32000 = 80 * 200 * 2 * - Total VRAM, bytes : 32768 = b8000 through bffff * * Above, a pixel is the smallest individually-addressable screen * element in this funky screen hack, and a dot is the smallest point * addressable by the VGA (by changing the font). Each character has a * foreground and a background attribute, and all pixels within a * character must be one or the other. Normally there are sixteen * foreground and eight background attributes (which, by the way, can * be remapped if you really want), and if you disable blinking (I wish * the Linux console would add support for this) then you can get * sixteen background attributes. * * For best results, use 8-pixel-wide fonts. The VGA usually uses * 9-pixel-wide fonts, resulting in those dark vertical lines. * SVGATextMode is one way of making this adjustment. Personally, I * simply hacked up arch/i386/boot/video.S. :^) */ for (y = 0; y < rows; ++y) { /* For each pixel row... */ for (x = 0; x < cols; ++x) { /* For each character... */ /* Build the character. */ chr = 0; for (i = 0; i < 8; ++i) { /* For each pixel... */ xc = (x * 8 + i - cols * 4) * ASPECT; /* X coordinate. */ yc = y - rows / 2; /* Y coordinate. */ h = hypot(xc, yc); /* Radius (hypot). */ a = atan2(xc, yc) * 180 / M_PI; /* Angle (arctan). */ if ((h + j + a + 180) % 10 < 5) { /* Create a spiral going one direction. This spiral is * composed of foreground and background *pixels*. */ chr |= 1 << i; } } /* Build the attribute. Create psychedelic starfish. :^) The * starfish determines whether the secondary spiral is drawn in * red, magenta, or blue, and the secondary spiral determines * what colors are used to draw the primary spiral. It's all * connected, man! */ d = (int)(h + j + sin((j - a) * M_PI / 36) * 10 + 10) % 50; if (d < 20) d = 0; else if (d < 25) d = 1; else if (d < 45) d = 2; else if (d < 50) d = 1; /* Create a spiral going the other direction. This * spiral is composed of foreground and background * *attributes*, determining the color of the pixels. */ if ((h - j - a - 360) % 10 > -5) { /* Bright colors. */ switch (d) { case 0: attr = 0x4c; break; /* Blue. */ case 1: attr = 0x5d; break; /* Magenta. */ case 2: attr = 0x19; break; /* Red. */ } } else { /* Dark colors. */ switch (d) { case 0: attr = 0x04; break; /* Blue. */ case 1: attr = 0x05; break; /* Magenta. */ case 2: attr = 0x01; break; /* Red. */ } } if (attr != prev_attr) { /* Change colors. */ printf("\e[%d;3%d;4%dm", (attr >> 3) & 1, (attr >> 0) & 7, (attr >> 4) & 7); prev_attr = attr; } /* Display the character. */ if (chr < 32 || chr > 126) { /* UTF-8 direct-to-font. */ putchar(0xef); putchar(0x80 | ((chr >> 6) & 3)); putchar(0x80 | ((chr >> 0) & 63)); } else { /* ASCII. */ putchar(chr); } } } } } /* vim: set ts=4 sts=4 sw=4 et: */