/* Copyright (C) 1992, 1994, 1998 artofcode LLC. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA, 02111-1307. */ /*$Id: gdev3b1.c,v 1.2.6.1.2.1 2003/01/17 00:49:00 giles Exp $*/ /* * This is a driver for the AT&T 3b1/7300/UnixPC console display. * * The image is built in a buffer the size of the page. Once complete, * a screen-sized subset is copied to the screen, and one can scroll * through the entire image (move with "vi" or arrow keys). * * Written by Andy Fyfe, andy@cs.caltech.edu. * * There are a couple of undesirable "features" that I have found no * way to work around. * * 1) Gs attempts to save the contents of the window before using it, and * then restores the contents afterward. However, if the gs window is * not the current window, and there are small windows present, then * the saved image is incorrect, and thus the screen will not be correctly * restored. This seems to be a bug in the 3b1 window driver. Making * the gs window current before saving its contents is not an acceptable * solution. * * 2) Gs will enable the scrolling/help/cancel icons if the window has * a border. Changing these border icons has the side effect of making * the gs window current. This does circumvent the first problem though. */ /* * About the ATT3B1_PERF flag (notes by Andy Fyfe): * * I am unable to profile gs on the 3b1, so I added ATT3B1_PERF as a * quick way to find out how much time was spent in the 3b1 driver, * through dynamically suppressing parts of the code at run time by * setting environment variables. I can then get the time spent in * those parts by comparing the results of "time gs ....". * * At one point this was very useful, and led to a fairly substantial * speedup of the fill and copy_mono routines. It also showed that I * wasn't going to get too much more, overall, by further attempts to * optimize the 3b1 driver. So those parts of the code controlled by * ATT3B1_PERF have really now outlived their usefulness. */ #include "gx.h" #include "gxdevice.h" #include "gserrors.h" #include #include #include typedef struct gx_device_att3b1_s { gx_device_common; int fd; /* window file descriptor */ uchar *screen; /* pointer to screen image */ ushort line_size; /* size of screen line in bytes */ ulong screen_size; /* size of screen image in bytes */ int page_num; /* page number */ #ifdef ATT3B1_PERF char *no_output, *no_fill, *no_copy; #endif } gx_device_att3b1; #define att3b1dev ((gx_device_att3b1 *)dev) #define XDPI 100 /* to get a more-or-less square aspect ratio */ #define YDPI 72 #define XSIZE (8.5 * XDPI) /* 8.5 x 11 inch page, by default */ #define YSIZE (11 * YDPI) static const ushort masks[] = { 0, 0x0001, 0x0003, 0x0007, 0x000f, 0x001f, 0x003f, 0x007f, 0x00ff, 0x01ff, 0x03ff, 0x07ff, 0x0fff, 0x1fff, 0x3fff, 0x7fff, 0xffff, }; static uchar reverse_bits[256] = { 0, 128, 64, 192, 32, 160, 96, 224, 16, 144, 80, 208, 48, 176, 112, 240, 8, 136, 72, 200, 40, 168, 104, 232, 24, 152, 88, 216, 56, 184, 120, 248, 4, 132, 68, 196, 36, 164, 100, 228, 20, 148, 84, 212, 52, 180, 116, 244, 12, 140, 76, 204, 44, 172, 108, 236, 28, 156, 92, 220, 60, 188, 124, 252, 2, 130, 66, 194, 34, 162, 98, 226, 18, 146, 82, 210, 50, 178, 114, 242, 10, 138, 74, 202, 42, 170, 106, 234, 26, 154, 90, 218, 58, 186, 122, 250, 6, 134, 70, 198, 38, 166, 102, 230, 22, 150, 86, 214, 54, 182, 118, 246, 14, 142, 78, 206, 46, 174, 110, 238, 30, 158, 94, 222, 62, 190, 126, 254, 1, 129, 65, 193, 33, 161, 97, 225, 17, 145, 81, 209, 49, 177, 113, 241, 9, 137, 73, 201, 41, 169, 105, 233, 25, 153, 89, 217, 57, 185, 121, 249, 5, 133, 69, 197, 37, 165, 101, 229, 21, 149, 85, 213, 53, 181, 117, 245, 13, 141, 77, 205, 45, 173, 109, 237, 29, 157, 93, 221, 61, 189, 125, 253, 3, 131, 67, 195, 35, 163, 99, 227, 19, 147, 83, 211, 51, 179, 115, 243, 11, 139, 75, 203, 43, 171, 107, 235, 27, 155, 91, 219, 59, 187, 123, 251, 7, 135, 71, 199, 39, 167, 103, 231, 23, 151, 87, 215, 55, 183, 119, 247, 15, 143, 79, 207, 47, 175, 111, 239, 31, 159, 95, 223, 63, 191, 127, 255 }; dev_proc_open_device(att3b1_open); dev_proc_close_device(att3b1_close); dev_proc_fill_rectangle(att3b1_fill_rectangle); dev_proc_copy_mono(att3b1_copy_mono); dev_proc_output_page(att3b1_output_page); private gx_device_procs att3b1_procs = { att3b1_open, gx_default_get_initial_matrix, gx_default_sync_output, att3b1_output_page, att3b1_close, gx_default_map_rgb_color, gx_default_map_color_rgb, att3b1_fill_rectangle, gx_default_tile_rectangle, att3b1_copy_mono, gx_default_copy_color, gx_default_draw_line, gx_default_get_bits }; gx_device_att3b1 gs_att3b1_device = { std_device_std_body(gx_device_att3b1, &att3b1_procs, "att3b1", XSIZE, YSIZE, XDPI, YDPI), { 0 }, /* std_procs */ -1, 0, 0, /* fd, screen, line_size, */ 0, 0, /* screen size, page */ #ifdef ATT3B1_PERF 0, 0, 0, /* no_output, no_fill, no_copy */ #endif }; int att3b1_open(gx_device *dev) { struct uwdata uw; #ifdef ATT3B1_PERF char *getenv(const char *); #endif if (att3b1dev->fd >= 0) { close(att3b1dev->fd); att3b1dev->fd = -1; } if (att3b1dev->screen != NULL) { gs_free((char *)att3b1dev->screen, att3b1dev->screen_size, 1, "att3b1_open"); att3b1dev->screen = 0; att3b1dev->screen_size = 0; } att3b1dev->fd = open("/dev/tty", 2); if (att3b1dev->fd < 0) { lprintf1("att3b1_open: open /dev/tty failed [%d]\n", errno); return_error(gs_error_ioerror); } /* Verify that /dev/tty is associated with a console window. */ if (ioctl(att3b1dev->fd, WIOCGETD, &uw) < 0) { lprintf1("att3b1_open: can not obtain window data [%d]\n", errno); lprintf("att3b1_open: the att3b1 device requires a console window\n"); att3b1_close(dev); return_error(gs_error_ioerror); } /* we need an even number of bytes per line */ att3b1dev->line_size = ((att3b1dev->width + 15) / 16) * 2; att3b1dev->screen_size = att3b1dev->line_size * att3b1dev->height; att3b1dev->screen = (uchar *)gs_malloc(att3b1dev->screen_size, 1, "att3b1_open"); if (att3b1dev->screen == NULL) { att3b1_close(dev); return_error(gs_error_VMerror); } att3b1dev->page_num = 1; #ifdef ATT3B1_PERF att3b1dev->no_output = getenv("GS_NOOUTPUT"); att3b1dev->no_fill = getenv("GS_NOFILL"); att3b1dev->no_copy = getenv("GS_NOCOPY"); #endif return 0; } int att3b1_close(gx_device *dev) { if (att3b1dev->fd >= 0) { close(att3b1dev->fd); att3b1dev->fd = -1; } if (att3b1dev->screen != NULL) { gs_free((char *)att3b1dev->screen, att3b1dev->screen_size, 1, "att3b1_close"); att3b1dev->screen = 0; att3b1dev->screen_size = 0; } return 0; } int att3b1_fill_rectangle(gx_device *dev, int x, int y, int w, int h, gx_color_index colour) { uint o, b, wl, wr, w2; ushort *p, *q, maskl, maskr; #ifdef ATT3B1_PERF if (att3b1dev->no_fill) return 0; #endif fit_fill(dev, x, y, w, h); /* following fit_fill, we can assume x, y, w, h are unsigned. */ p = (ushort *)&att3b1dev->screen[(ushort)y*att3b1dev->line_size] + (uint)x/16; o = (uint)x % 16; b = 16 - o; wl = ((uint)w < b) ? (uint)w : b; maskl = masks[wl] << o; w -= wl; wr = (uint)w % 16; maskr = masks[wr]; if (colour == 0) { maskl = ~maskl; maskr = ~maskr; while (h-- > 0) { q = p; w2 = w; *q++ &= maskl; while (w2 >= 16) { *q++ = 0; w2 -= 16; } *q &= maskr; p += (att3b1dev->line_size / 2); } } else { while (h-- > 0) { q = p; w2 = w; *q++ |= maskl; while (w2 >= 16) { *q++ = 0xffff; w2 -= 16; } *q |= maskr; p += (att3b1dev->line_size / 2); } } return 0; } #ifdef __GNUC__ #define rotate(value, count) \ asm("ror%.l %2,%0" : "=d" (value) : "0" (value), "d" (count)) #else #define rotate(value, count) \ value = (value >> count) | (value << (32-count)) #endif int att3b1_copy_mono(gx_device *dev, const uchar *data, int data_x, int raster, gx_bitmap_id id, int x, int y, int width, int height, gx_color_index colour0, gx_color_index colour1) { const ushort *src_p, *src_q; ushort *dst_p, *dst_q; ulong bits, mask, *p; uint src_o, src_b, dst_o, dst_b, op; uint w1, w2; #ifdef ATT3B1_PERF if (att3b1dev->no_copy) return 0; #endif if (colour1 == colour0) /* vacuous case */ return att3b1_fill_rectangle(dev, x, y, width, height, colour0); fit_copy(dev, data, data_x, raster, id, x, y, width, height); /* following fit_copy, we can assume x, y, width, height are unsigned. */ /* * In what follows, we're assuming that each row of the input bitmap * is short-aligned, that is, that both "data" and "raster" are even. */ src_p = ((const ushort *)data) + (uint)data_x/16; src_o = (uint)data_x % 16; src_b = 16 - src_o; dst_p = (ushort *)&att3b1dev->screen[(ushort)y*att3b1dev->line_size] + (uint)x/16; dst_o = (uint)x % 16; dst_b = 16 - dst_o; op = (int)colour0 * 3 + (int)colour1 + 4; while (height-- > 0) { w2 = width; src_q = src_p; dst_q = dst_p; while (w2 > 0) { w1 = (w2 < 16) ? w2 : 16; mask = masks[w1]; /* * We are assuming that the bitmap "data" is typically aligned. * Thus the test for this special case is typically a win over * a 16-bit shift. */ if (src_o == 0) bits = *src_q++; else { bits = *((ulong *)src_q) >> src_b; bits &= 0xffff; src_q++; } if (w1 <= 8) bits = reverse_bits[bits>>8]; else bits = (reverse_bits[bits&0xff] << 8) | reverse_bits[bits>>8]; /* * While the input bit map is assumed to be typically aligned, we * assume that the place in the image is not. Thus we don't * separate out the aligned case. Doing so would cost a test, * and only reduce the average shift by about 1. */ p = (ulong *)dst_q; switch(op) { case 1: /* not src and dst */ bits = ~(bits & mask); rotate(bits,dst_b); *p &= bits; break; case 2: /* src or dst */ bits = bits & mask; rotate(bits,dst_b); *p |= bits; break; case 3: /* src and dst */ bits = bits | ~mask; rotate(bits,dst_b); *p &= bits; break; case 5: /* src */ rotate(bits,dst_b); rotate(mask,dst_b); *p = (*p & ~mask) | (bits & mask); break; case 6: /* not src or dst */ bits = ~bits & mask; rotate(bits,dst_b); *p |= bits; break; case 7: /* not src */ rotate(bits,dst_b); rotate(mask,dst_b); *p = (*p & ~mask) | (~bits & mask); break; } dst_q++; w2 -= w1; } src_p += (raster / 2); dst_p += (att3b1dev->line_size / 2); } return 0; } static int getKeyboard(gx_device *); const char *help_msg[] = { "h, j, k, l, UP, DOWN, LEFT, RIGHT move the page (0.25\" h, 0.5\" v)", "H, J, K, L, BEG, END move to far edge of the page", "^U, ^D, ROLL UP, ROLL DOWN scroll up or down (1/2 screen height)", "^F, ^B, PAGE UP, PAGE DOWN scroll up or down (full screen height)", "c, C centre page horizontally, vertically", "<, >, ^, _ fine movements (single pixel)", "^L, ^R, r, HOME move to default position", "=, MARK make current position the default", "I invert the image (black <-> white)", "q, x, ^C, EXIT, CANCL, n, f, NEXT,", " SPACE, RETURN, ENTER end the page", "?, HELP help screen", }; static void do_help(gx_device *dev) { int i; struct utdata ut; /* we would like to save the cursor position, but we can't */ write(att3b1dev->fd, "\033[2J\033[H", 7); /* write help screen */ for (i=0; i < sizeof(help_msg)/sizeof(help_msg[0]); ++i) { write(att3b1dev->fd, help_msg[i], strlen(help_msg[i])); write(att3b1dev->fd, "\n", 1); } ut.ut_num = WTXTSLK1; strcpy(ut.ut_text, "Press any key to continue"); ioctl(att3b1dev->fd, WIOCSETTEXT, &ut); /* wait for keyboard input */ i = getKeyboard(dev); /* clear screen and put cursor at the bottom of the screen */ write(att3b1dev->fd, "\033[2J\033[99;1H", 11); } private int att3b1_do_output_page(gx_device *dev, int num_copies, int flush) { struct urdata ur; struct utdata ut, ut_orig; struct uwdata uw; int uflags; struct termio old, new; int xorigin, yorigin; static int def_xorigin = 0, def_yorigin = 0; int screen_width, screen_height; int inverted = 0; int error = 0; int ch; ushort *p; ushort save_image[WINWIDTH * WINHEIGHT / 16]; #ifdef ATT3B1_PERF if (att3b1dev->no_output) return 0; #endif /* * initialize, and save screen state */ if (ioctl(att3b1dev->fd, WIOCGETD, &uw) < 0) { lprintf1("att3b1_output_page: window WIOCGETD ioctl failed [%d]\n", errno); att3b1_close(dev); return_error(gs_error_ioerror); } /* * we assume, henceforth, that screen ioctl calls will succeed */ write(att3b1dev->fd, "\a\033[=1C", 6); uflags = uw.uw_uflags; if (!(uflags & NBORDER)) { uw.uw_uflags = BORDHSCROLL | BORDVSCROLL | BORDHELP | BORDCANCEL; ioctl(att3b1dev->fd, WIOCSETD, &uw); } ut_orig.ut_num = WTXTSLK1; ioctl(att3b1dev->fd, WIOCGETTEXT, &ut_orig); /* This isn't necessary, but helps a bit when the following attempt to get the current screen image fails (without any indication). */ memset(save_image, '\0', sizeof(save_image)); ur.ur_srcbase = 0; ur.ur_srcwidth = 0; ur.ur_srcx = 0; ur.ur_srcy = 0; ur.ur_dstbase = save_image; ur.ur_dstwidth = WINWIDTH / 8; ur.ur_dstx = 0; ur.ur_dsty = 0; ur.ur_width = uw.uw_width; ur.ur_height = uw.uw_height; ur.ur_srcop = SRCSRC; ur.ur_dstop = DSTSRC; ur.ur_pattern = 0; ioctl(att3b1dev->fd, WIOCRASTOP, &ur); ioctl(att3b1dev->fd, TCGETA, &old); new = old; new.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL); new.c_cc[VMIN] = 1; ioctl(att3b1dev->fd, TCSETAF, &new); screen_width = (uw.uw_width < att3b1dev->width) ? uw.uw_width : att3b1dev->width; screen_height = (uw.uw_height < att3b1dev->height) ? uw.uw_height : att3b1dev->height; write(att3b1dev->fd, "\033[2J", 4); ur.ur_srcwidth = att3b1dev->line_size; ur.ur_width = screen_width; ur.ur_height = screen_height; ur.ur_dstbase = 0; ur.ur_dstwidth = 0; /* * allow one to move the screen window through the entire image */ xorigin = def_xorigin; yorigin = def_yorigin; while (1) { /* Things go bad if ur_srcx >= 2048 */ ur.ur_srcbase = (ushort *)att3b1dev->screen + (xorigin >> 4); ur.ur_srcx = xorigin & 15; ur.ur_srcy = yorigin; if (ioctl(att3b1dev->fd, WIOCRASTOP, &ur) < 0) { lprintf1( "att3b1_output_page: window WIOCRASTOP ioctl failed [%d]\n", errno); error = gs_error_ioerror; } ut.ut_num = WTXTSLK1; sprintf(ut.ut_text, "%s %d, top right (%d,%d), size (%d,%d), press '?' for help.", flush ? "Showpage" : "Copypage", att3b1dev->page_num, xorigin, yorigin, att3b1dev->width, att3b1dev->height); ioctl(att3b1dev->fd, WIOCSETTEXT, &ut); ch = error ? 'q' : getKeyboard(dev); switch(ch) { case 'h': xorigin -= ((uint)(int)att3b1dev->x_pixels_per_inch+3)/4; break; case 'k': yorigin -= ((uint)(int)att3b1dev->y_pixels_per_inch+1)/2; break; case 'l': xorigin += ((uint)(int)att3b1dev->x_pixels_per_inch+3)/4; break; case 'j': yorigin += ((uint)(int)att3b1dev->y_pixels_per_inch+1)/2; break; case 'H': xorigin = 0; break; case 'K': yorigin = 0; break; case 'L': xorigin = att3b1dev->width - screen_width; break; case 'J': yorigin = att3b1dev->height - screen_height; break; case '<': xorigin -= 1; break; case '>': xorigin += 1; break; case '^': yorigin -= 1; break; case '_': yorigin += 1; break; case '\025': /* control-U */ yorigin -= screen_height/2; break; case '\004': /* control-D */ yorigin += screen_height/2; break; case '\002': /* control-B */ yorigin -= screen_height; break; case '\006': /* control-F */ yorigin += screen_height; break; case '\f': case 'r' : case '\022': /* control-R */ xorigin = def_xorigin; yorigin = def_yorigin; break; case 'c': /* centre horizontally */ xorigin = (att3b1dev->width - screen_width) / 2; break; case 'C': /* centre vertically */ yorigin = (att3b1dev->height - screen_height) / 2; break; case '=': def_xorigin = xorigin; def_yorigin = yorigin; break; case 'I': for (p = (ushort *)att3b1dev->screen; p < (ushort *)&att3b1dev->screen[att3b1dev->screen_size]; ++p) *p = ~ *p; inverted = !inverted; break; case '?': do_help(dev); break; case -1: error = gs_error_ioerror; /* fall through, for cleanup */ case 'q': case 'x': case '\003': /* control-C */ case 'n': case 'f': case ' ': case '\n': case '\r': if (flush) att3b1dev->page_num++; else if (inverted) /* restore inverted image for copypage */ for (p = (ushort *)att3b1dev->screen; p < (ushort *)&att3b1dev->screen[att3b1dev->screen_size]; ++p) *p = ~ *p; if (!(uflags & NBORDER)) { ioctl(att3b1dev->fd, WIOCGETD, &uw); /*window may have moved*/ uw.uw_uflags = uflags; ioctl(att3b1dev->fd, WIOCSETD, &uw); } ur.ur_srcbase = save_image; ur.ur_srcwidth = WINWIDTH / 8; ur.ur_width = uw.uw_width; ur.ur_height = uw.uw_height; ur.ur_srcx = 0; ur.ur_srcy = 0; ioctl(att3b1dev->fd, WIOCRASTOP, &ur); ioctl(att3b1dev->fd, WIOCSETTEXT, &ut_orig); ioctl(att3b1dev->fd, TCSETAF, &old); write(att3b1dev->fd, "\033[=0C", 5); if (error) { att3b1_close(dev); return_error(error); } else return 0; } if (xorigin >= att3b1dev->width - screen_width) xorigin = att3b1dev->width - screen_width; if (xorigin < 0) xorigin = 0; if (yorigin >= att3b1dev->height - screen_height) yorigin = att3b1dev->height - screen_height; if (yorigin < 0) yorigin = 0; } } int att3b1_output_page(gx_device *dev, int num_copies, int flush) { int code = att3b1_do_output_page(dev, num_copies, flush); if (code >= 0) code = gx_finish_output_page(dev, num_copies, flush); return code; } static int get_char(gx_device *dev) { char ch; int count; count = read(att3b1dev->fd, &ch, 1); if (count == 0) return 'q'; else if (count < 0) return -1; else return ch; } static int getKeyboard(gx_device *dev) { char ch; ch = get_char(dev); if (ch != '\033') return ch; /* * If the char is escape, interpret the escape sequence and return * an equivalent single character. * * Note that a mouse click on a window border icon is translated * to the corresponding key, for example, the "up" icon generates * roll-up/page-up/beg for the left/middle/right mouse button. */ switch (get_char(dev)) { case '[': switch(get_char(dev)) { case 'A': /* up arrow */ return 'k'; case 'T': /* shift up arrow (roll up) */ return '\025'; case 'B': /* down arrow */ return 'j'; case 'S': /* shift down arrow (roll down) */ return '\004'; case 'C': /* right arrow */ return 'l'; case 'D': /* left arrow */ return 'h'; case 'H': /* home */ return 'r'; case 'U': /* page down */ return '\006'; case 'V': /* page up */ return '\002'; } break; case 'O': switch(get_char(dev)) { case 'm': /* help */ case 'M': /* shift help */ return '?'; case 'k': /* exit */ case 'K': /* shift exit */ case 'w': /* cancl */ case 'W': /* shift cancl */ return 'q'; } break; case 'N': switch(get_char(dev)) { case 'h': /* next */ return 'f'; case 'i': /* mark */ return '='; case 'L': /* shift right arrow */ return 'l'; case 'K': /* shift left arrow */ return 'h'; } break; case '9': /* Beg */ return 'K'; case '0': /* End */ return 'J'; } return '\0'; }