/* * Copyright 2005-2010, Axel Dörfler, axeld@pinc-software.de. * Distributed under the terms of the MIT License. * * Copyright 2001-2002, Travis Geiselbrecht. All rights reserved. * Distributed under the terms of the NewOS License. */ #include #include #include #include #include #include #include #define DEVICE_NAME "console" #define TAB_SIZE 8 #define TAB_MASK 7 #define FMASK 0x0f #define BMASK 0x70 typedef enum { CONSOLE_STATE_NORMAL = 0, CONSOLE_STATE_GOT_ESCAPE, CONSOLE_STATE_SEEN_BRACKET, CONSOLE_STATE_NEW_ARG, CONSOLE_STATE_PARSING_ARG, } console_state; typedef enum { SCREEN_ERASE_WHOLE, SCREEN_ERASE_UP, SCREEN_ERASE_DOWN } erase_screen_mode; typedef enum { LINE_ERASE_WHOLE, LINE_ERASE_LEFT, LINE_ERASE_RIGHT } erase_line_mode; #define MAX_ARGS 8 static struct console_desc { mutex lock; int32 lines; int32 columns; uint8 attr; uint8 saved_attr; bool bright_attr; bool reverse_attr; int32 x; /* current x coordinate */ int32 y; /* current y coordinate */ int32 saved_x; /* used to save x and y */ int32 saved_y; int32 scroll_top; /* top of the scroll region */ int32 scroll_bottom; /* bottom of the scroll region */ /* state machine */ console_state state; int32 arg_count; int32 args[MAX_ARGS]; char module_name[B_FILE_NAME_LENGTH]; console_module_info *module; } sConsole; int32 api_version = B_CUR_DRIVER_API_VERSION; static int32 sOpenMask; static inline void update_cursor(struct console_desc *console, int x, int y) { console->module->move_cursor(x, y); } static void gotoxy(struct console_desc *console, int newX, int newY) { if (newX >= console->columns) newX = console->columns - 1; if (newX < 0) newX = 0; if (newY >= console->lines) newY = console->lines - 1; if (newY < 0) newY = 0; console->x = newX; console->y = newY; } static void reset_console(struct console_desc *console) { console->attr = 0x0f; console->scroll_top = 0; console->scroll_bottom = console->lines - 1; console->bright_attr = true; console->reverse_attr = false; } /*! Scroll from the cursor line up to the top of the scroll region up one line. */ static void scrup(struct console_desc *console) { // see if cursor is outside of scroll region if (console->y < console->scroll_top || console->y > console->scroll_bottom) return; if (console->y - console->scroll_top > 1) { // move the screen up one console->module->blit(0, console->scroll_top + 1, console->columns, console->y - console->scroll_top, 0, console->scroll_top); } // clear the bottom line console->module->fill_glyph(0, console->y, console->columns, 1, ' ', console->attr); } /*! Scroll from the cursor line down to the bottom of the scroll region down one line. */ static void scrdown(struct console_desc *console) { // see if cursor is outside of scroll region if (console->y < console->scroll_top || console->y > console->scroll_bottom) return; if (console->scroll_bottom - console->y > 1) { // move the screen down one console->module->blit(0, console->y, console->columns, console->scroll_bottom - console->y, 0, console->y + 1); } // clear the top line console->module->fill_glyph(0, console->y, console->columns, 1, ' ', console->attr); } static void lf(struct console_desc *console) { //dprintf("lf: y %d x %d scroll_top %d scoll_bottom %d\n", console->y, console->x, console->scroll_top, console->scroll_bottom); if (console->y == console->scroll_bottom ) { // we hit the bottom of our scroll region scrup(console); } else if(console->y < console->scroll_bottom) { console->y++; } } static void rlf(struct console_desc *console) { if (console->y == console->scroll_top) { // we hit the top of our scroll region scrdown(console); } else if (console->y > console->scroll_top) { console->y--; } } static void cr(struct console_desc *console) { console->x = 0; } static void del(struct console_desc *console) { if (console->x > 0) { console->x--; } else if (console->y > 0) { console->y--; console->x = console->columns - 1; } else { //This doesn't work... //scrdown(console); //console->y--; //console->x = console->columns - 1; return; } console->module->put_glyph(console->x, console->y, ' ', console->attr); } static void erase_line(struct console_desc *console, erase_line_mode mode) { switch (mode) { case LINE_ERASE_WHOLE: console->module->fill_glyph(0, console->y, console->columns, 1, ' ', console->attr); break; case LINE_ERASE_LEFT: console->module->fill_glyph(0, console->y, console->x + 1, 1, ' ', console->attr); break; case LINE_ERASE_RIGHT: console->module->fill_glyph(console->x, console->y, console->columns - console->x, 1, ' ', console->attr); break; default: return; } } static void erase_screen(struct console_desc *console, erase_screen_mode mode) { switch (mode) { case SCREEN_ERASE_WHOLE: console->module->clear(console->attr); break; case SCREEN_ERASE_UP: console->module->fill_glyph(0, 0, console->columns, console->y + 1, ' ', console->attr); break; case SCREEN_ERASE_DOWN: console->module->fill_glyph(console->y, 0, console->columns, console->lines - console->y, ' ', console->attr); break; default: return; } } static void save_cur(struct console_desc *console, bool saveAttrs) { console->saved_x = console->x; console->saved_y = console->y; if (saveAttrs) console->saved_attr = console->attr; } static void restore_cur(struct console_desc *console, bool restoreAttrs) { console->x = console->saved_x; console->y = console->saved_y; if (restoreAttrs) console->attr = console->saved_attr; } static char console_putch(struct console_desc *console, const char c) { if (++console->x >= console->columns) { cr(console); lf(console); } console->module->put_glyph(console->x - 1, console->y, c, console->attr); return c; } static void tab(struct console_desc *console) { console->x = (console->x + TAB_SIZE) & ~TAB_MASK; if (console->x >= console->columns) { console->x -= console->columns; lf(console); } } static void set_scroll_region(struct console_desc *console, int top, int bottom) { if (top < 0) top = 0; if (bottom >= console->lines) bottom = console->lines - 1; if (top > bottom) return; console->scroll_top = top; console->scroll_bottom = bottom; } static void set_vt100_attributes(struct console_desc *console, int32 *args, int32 argCount) { if (argCount == 0) { // that's the default (attributes off) argCount++; args[0] = 0; } for (int32 i = 0; i < argCount; i++) { //dprintf("set_vt100_attributes: %ld\n", args[i]); switch (args[i]) { case 0: // reset console->attr = 0x0f; console->bright_attr = true; console->reverse_attr = false; break; case 1: // bright console->bright_attr = true; console->attr |= 0x08; // set the bright bit break; case 2: // dim console->bright_attr = false; console->attr &= ~0x08; // unset the bright bit break; case 4: // underscore we can't do break; case 5: // blink console->attr |= 0x80; // set the blink bit break; case 7: // reverse console->reverse_attr = true; console->attr = ((console->attr & BMASK) >> 4) | ((console->attr & FMASK) << 4); if (console->bright_attr) console->attr |= 0x08; break; case 8: // hidden? break; /* foreground colors */ case 30: console->attr = (console->attr & ~FMASK) | 0 | (console->bright_attr ? 0x08 : 0); break; // black case 31: console->attr = (console->attr & ~FMASK) | 4 | (console->bright_attr ? 0x08 : 0); break; // red case 32: console->attr = (console->attr & ~FMASK) | 2 | (console->bright_attr ? 0x08 : 0); break; // green case 33: console->attr = (console->attr & ~FMASK) | 6 | (console->bright_attr ? 0x08 : 0); break; // yellow case 34: console->attr = (console->attr & ~FMASK) | 1 | (console->bright_attr ? 0x08 : 0); break; // blue case 35: console->attr = (console->attr & ~FMASK) | 5 | (console->bright_attr ? 0x08 : 0); break; // magenta case 36: console->attr = (console->attr & ~FMASK) | 3 | (console->bright_attr ? 0x08 : 0); break; // cyan case 37: console->attr = (console->attr & ~FMASK) | 7 | (console->bright_attr ? 0x08 : 0); break; // white /* background colors */ case 40: console->attr = (console->attr & ~BMASK) | (0 << 4); break; // black case 41: console->attr = (console->attr & ~BMASK) | (4 << 4); break; // red case 42: console->attr = (console->attr & ~BMASK) | (2 << 4); break; // green case 43: console->attr = (console->attr & ~BMASK) | (6 << 4); break; // yellow case 44: console->attr = (console->attr & ~BMASK) | (1 << 4); break; // blue case 45: console->attr = (console->attr & ~BMASK) | (5 << 4); break; // magenta case 46: console->attr = (console->attr & ~BMASK) | (3 << 4); break; // cyan case 47: console->attr = (console->attr & ~BMASK) | (7 << 4); break; // white } } } static bool process_vt100_command(struct console_desc *console, const char c, bool seenBracket, int32 *args, int32 argCount) { bool ret = true; //dprintf("process_vt100_command: c '%c', argCount %ld, arg[0] %ld, arg[1] %ld, seenBracket %d\n", // c, argCount, args[0], args[1], seenBracket); if (seenBracket) { switch(c) { case 'H': // set cursor position case 'f': { int32 row = argCount > 0 ? args[0] : 1; int32 col = argCount > 1 ? args[1] : 1; if (row > 0) row--; if (col > 0) col--; gotoxy(console, col, row); break; } case 'A': // move up { int32 deltaY = argCount > 0 ? -args[0] : -1; if (deltaY == 0) deltaY = -1; gotoxy(console, console->x, console->y + deltaY); break; } case 'e': case 'B': // move down { int32 deltaY = argCount > 0 ? args[0] : 1; if (deltaY == 0) deltaY = 1; gotoxy(console, console->x, console->y + deltaY); break; } case 'D': // move left { int32 deltaX = argCount > 0 ? -args[0] : -1; if (deltaX == 0) deltaX = -1; gotoxy(console, console->x + deltaX, console->y); break; } case 'a': case 'C': // move right { int32 deltaX = argCount > 0 ? args[0] : 1; if (deltaX == 0) deltaX = 1; gotoxy(console, console->x + deltaX, console->y); break; } case '`': case 'G': // set X position { int32 newX = argCount > 0 ? args[0] : 1; if (newX > 0) newX--; gotoxy(console, newX, console->y); break; } case 'd': // set y position { int32 newY = argCount > 0 ? args[0] : 1; if (newY > 0) newY--; gotoxy(console, console->x, newY); break; } case 's': // save current cursor save_cur(console, false); break; case 'u': // restore cursor restore_cur(console, false); break; case 'r': // set scroll region { int32 low = argCount > 0 ? args[0] : 1; int32 high = argCount > 1 ? args[1] : console->lines; if (low <= high) set_scroll_region(console, low - 1, high - 1); break; } case 'L': // scroll virtual down at cursor { int32 lines = argCount > 0 ? args[0] : 1; while (lines > 0) { scrdown(console); lines--; } break; } case 'M': // scroll virtual up at cursor { int32 lines = argCount > 0 ? args[0] : 1; while (lines > 0) { scrup(console); lines--; } break; } case 'K': if (argCount == 0 || args[0] == 0) { // erase to end of line erase_line(console, LINE_ERASE_RIGHT); } else if (argCount > 0) { if (args[0] == 1) erase_line(console, LINE_ERASE_LEFT); else if (args[0] == 2) erase_line(console, LINE_ERASE_WHOLE); } break; case 'J': if (argCount == 0 || args[0] == 0) { // erase to end of screen erase_screen(console, SCREEN_ERASE_DOWN); } else { if (args[0] == 1) erase_screen(console, SCREEN_ERASE_UP); else if (args[0] == 2) erase_screen(console, SCREEN_ERASE_WHOLE); } break; case 'm': if (argCount >= 0) set_vt100_attributes(console, args, argCount); break; default: ret = false; } } else { switch (c) { case 'c': reset_console(console); break; case 'D': rlf(console); break; case 'M': lf(console); break; case '7': save_cur(console, true); break; case '8': restore_cur(console, true); break; default: ret = false; } } return ret; } static ssize_t _console_write(struct console_desc *console, const void *buffer, size_t length) { const char *c; size_t pos = 0; while (pos < length) { c = &((const char *)buffer)[pos++]; switch (console->state) { case CONSOLE_STATE_NORMAL: // just output the stuff switch (*c) { case '\n': lf(console); break; case '\r': cr(console); break; case 0x8: // backspace del(console); break; case '\t': tab(console); break; case '\a': // beep dprintf("\n"); break; case '\0': break; case 0x1b: // escape character console->arg_count = 0; console->state = CONSOLE_STATE_GOT_ESCAPE; break; default: console_putch(console, *c); } break; case CONSOLE_STATE_GOT_ESCAPE: // Look for either commands with no argument, or the '[' // character switch (*c) { case '[': console->state = CONSOLE_STATE_SEEN_BRACKET; break; default: console->args[0] = 0; process_vt100_command(console, *c, false, console->args, 0); console->state = CONSOLE_STATE_NORMAL; break; } break; case CONSOLE_STATE_SEEN_BRACKET: switch (*c) { case '0'...'9': console->arg_count = 0; console->args[console->arg_count] = *c - '0'; console->state = CONSOLE_STATE_PARSING_ARG; break; case '?': // Private DEC mode parameter follows - we ignore those // anyway // TODO: check if it was really used in combination with // a mode command break; default: process_vt100_command(console, *c, true, console->args, 0); console->state = CONSOLE_STATE_NORMAL; break; } break; case CONSOLE_STATE_NEW_ARG: switch (*c) { case '0'...'9': console->arg_count++; if (console->arg_count == MAX_ARGS) { console->state = CONSOLE_STATE_NORMAL; break; } console->args[console->arg_count] = *c - '0'; console->state = CONSOLE_STATE_PARSING_ARG; break; default: process_vt100_command(console, *c, true, console->args, console->arg_count + 1); console->state = CONSOLE_STATE_NORMAL; break; } break; case CONSOLE_STATE_PARSING_ARG: // parse args switch (*c) { case '0'...'9': console->args[console->arg_count] *= 10; console->args[console->arg_count] += *c - '0'; break; case ';': console->state = CONSOLE_STATE_NEW_ARG; break; default: process_vt100_command(console, *c, true, console->args, console->arg_count + 1); console->state = CONSOLE_STATE_NORMAL; break; } } } return pos; } // #pragma mark - static status_t console_open(const char *name, uint32 flags, void **cookie) { if (atomic_or(&sOpenMask, 1) == 1) return B_BUSY; *cookie = &sConsole; status_t status = get_module(sConsole.module_name, (module_info **)&sConsole.module); if (status == B_OK) sConsole.module->clear(0x0f); return status; } static status_t console_freecookie(void *cookie) { if (sConsole.module != NULL) { put_module(sConsole.module_name); sConsole.module = NULL; } atomic_and(&sOpenMask, ~1); return B_OK; } static status_t console_close(void *cookie) { // dprintf("console_close: entry\n"); return 0; } static status_t console_read(void *cookie, off_t pos, void *buffer, size_t *_length) { return B_NOT_ALLOWED; } static status_t console_write(void *cookie, off_t pos, const void *buffer, size_t *_length) { struct console_desc *console = (struct console_desc *)cookie; ssize_t written = 0; status_t status = B_OK; #if 0 { const char *input = (const char *)buffer; dprintf("console_write (%lu bytes): \"", *_length); for (uint32 i = 0; i < *_length; i++) { if (input[i] < ' ') dprintf("(%d:0x%x)", input[i], input[i]); else dprintf("%c", input[i]); } dprintf("\"\n"); } #endif mutex_lock(&console->lock); update_cursor(console, -1, -1); // hide it size_t bytesLeft = *_length; const char *str = (const char*)buffer; while (bytesLeft > 0) { char localBuffer[512]; size_t chunkSize = min_c(sizeof(localBuffer), bytesLeft); if (user_memcpy(localBuffer, str, chunkSize) < B_OK) { status = B_BAD_ADDRESS; break; } written += _console_write(console, localBuffer, chunkSize); str += chunkSize; bytesLeft -= chunkSize; } update_cursor(console, console->x, console->y); mutex_unlock(&console->lock); if (status == B_OK) *_length = written; return status; } static status_t console_ioctl(void *cookie, uint32 op, void *buffer, size_t length) { struct console_desc *console = (struct console_desc *)cookie; if (op == TIOCGWINSZ) { struct winsize size; size.ws_xpixel = size.ws_col = console->columns; size.ws_ypixel = size.ws_row = console->lines; return user_memcpy(buffer, &size, sizeof(struct winsize)); } return B_BAD_VALUE; } // #pragma mark - status_t init_hardware(void) { // iterate through the list of console modules until we find one that accepts the job void *cookie = open_module_list("console"); if (cookie == NULL) return B_ERROR; bool found = false; char buffer[B_FILE_NAME_LENGTH]; size_t bufferSize = sizeof(buffer); while (read_next_module_name(cookie, buffer, &bufferSize) == B_OK) { dprintf("con_init: trying module %s\n", buffer); if (get_module(buffer, (module_info **)&sConsole.module) == B_OK) { strlcpy(sConsole.module_name, buffer, sizeof(sConsole.module_name)); put_module(buffer); found = true; break; } bufferSize = sizeof(buffer); } if (found) { // set up the console structure mutex_init(&sConsole.lock, "console lock"); sConsole.module->get_size(&sConsole.columns, &sConsole.lines); reset_console(&sConsole); gotoxy(&sConsole, 0, 0); save_cur(&sConsole, true); } close_module_list(cookie); return found ? B_OK : B_ERROR; } const char ** publish_devices(void) { static const char *devices[] = { DEVICE_NAME, NULL }; return devices; } device_hooks * find_device(const char *name) { static device_hooks hooks = { &console_open, &console_close, &console_freecookie, &console_ioctl, &console_read, &console_write, /* Leave select/deselect/readv/writev undefined. The kernel will * use its own default implementation. The basic hooks above this * line MUST be defined, however. */ NULL, NULL, NULL, NULL }; if (!strcmp(name, DEVICE_NAME)) return &hooks; return NULL; } status_t init_driver(void) { return B_OK; } void uninit_driver(void) { }