* 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 <Drivers.h>
#include <KernelExport.h>
#include <console.h>
#include <lock.h>
#include <string.h>
#include <stdio.h>
#include <termios.h>
#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;
int32 y;
int32 saved_x;
int32 saved_y;
int32 scroll_top;
int32 scroll_bottom;
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;
}
*/
static void
scrup(struct console_desc *console)
{
if (console->y < console->scroll_top || console->y > console->scroll_bottom)
return;
if (console->y - console->scroll_top > 1) {
console->module->blit(0, console->scroll_top + 1, console->columns,
console->y - console->scroll_top, 0, console->scroll_top);
}
console->module->fill_glyph(0, console->y, console->columns, 1, ' ',
console->attr);
}
one line.
*/
static void
scrdown(struct console_desc *console)
{
if (console->y < console->scroll_top || console->y > console->scroll_bottom)
return;
if (console->scroll_bottom - console->y > 1) {
console->module->blit(0, console->y, console->columns,
console->scroll_bottom - console->y, 0, console->y + 1);
}
console->module->fill_glyph(0, console->y, console->columns, 1, ' ',
console->attr);
}
static void
lf(struct console_desc *console)
{
if (console->y == console->scroll_bottom ) {
scrup(console);
} else if(console->y < console->scroll_bottom) {
console->y++;
}
}
static void
rlf(struct console_desc *console)
{
if (console->y == console->scroll_top) {
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 {
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) {
argCount++;
args[0] = 0;
}
for (int32 i = 0; i < argCount; i++) {
switch (args[i]) {
case 0:
console->attr = 0x0f;
console->bright_attr = true;
console->reverse_attr = false;
break;
case 1:
console->bright_attr = true;
console->attr |= 0x08;
break;
case 2:
console->bright_attr = false;
console->attr &= ~0x08;
break;
case 4:
break;
case 5:
console->attr |= 0x80;
break;
case 7:
console->reverse_attr = true;
console->attr = ((console->attr & BMASK) >> 4)
| ((console->attr & FMASK) << 4);
if (console->bright_attr)
console->attr |= 0x08;
break;
case 8:
break;
case 30: console->attr = (console->attr & ~FMASK) | 0 | (console->bright_attr ? 0x08 : 0); break;
case 31: console->attr = (console->attr & ~FMASK) | 4 | (console->bright_attr ? 0x08 : 0); break;
case 32: console->attr = (console->attr & ~FMASK) | 2 | (console->bright_attr ? 0x08 : 0); break;
case 33: console->attr = (console->attr & ~FMASK) | 6 | (console->bright_attr ? 0x08 : 0); break;
case 34: console->attr = (console->attr & ~FMASK) | 1 | (console->bright_attr ? 0x08 : 0); break;
case 35: console->attr = (console->attr & ~FMASK) | 5 | (console->bright_attr ? 0x08 : 0); break;
case 36: console->attr = (console->attr & ~FMASK) | 3 | (console->bright_attr ? 0x08 : 0); break;
case 37: console->attr = (console->attr & ~FMASK) | 7 | (console->bright_attr ? 0x08 : 0); break;
case 40: console->attr = (console->attr & ~BMASK) | (0 << 4); break;
case 41: console->attr = (console->attr & ~BMASK) | (4 << 4); break;
case 42: console->attr = (console->attr & ~BMASK) | (2 << 4); break;
case 43: console->attr = (console->attr & ~BMASK) | (6 << 4); break;
case 44: console->attr = (console->attr & ~BMASK) | (1 << 4); break;
case 45: console->attr = (console->attr & ~BMASK) | (5 << 4); break;
case 46: console->attr = (console->attr & ~BMASK) | (3 << 4); break;
case 47: console->attr = (console->attr & ~BMASK) | (7 << 4); break;
}
}
}
static bool
process_vt100_command(struct console_desc *console, const char c,
bool seenBracket, int32 *args, int32 argCount)
{
bool ret = true;
if (seenBracket) {
switch(c) {
case 'H':
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':
{
int32 deltaY = argCount > 0 ? -args[0] : -1;
if (deltaY == 0)
deltaY = -1;
gotoxy(console, console->x, console->y + deltaY);
break;
}
case 'e':
case 'B':
{
int32 deltaY = argCount > 0 ? args[0] : 1;
if (deltaY == 0)
deltaY = 1;
gotoxy(console, console->x, console->y + deltaY);
break;
}
case 'D':
{
int32 deltaX = argCount > 0 ? -args[0] : -1;
if (deltaX == 0)
deltaX = -1;
gotoxy(console, console->x + deltaX, console->y);
break;
}
case 'a':
case 'C':
{
int32 deltaX = argCount > 0 ? args[0] : 1;
if (deltaX == 0)
deltaX = 1;
gotoxy(console, console->x + deltaX, console->y);
break;
}
case '`':
case 'G':
{
int32 newX = argCount > 0 ? args[0] : 1;
if (newX > 0)
newX--;
gotoxy(console, newX, console->y);
break;
}
case 'd':
{
int32 newY = argCount > 0 ? args[0] : 1;
if (newY > 0)
newY--;
gotoxy(console, console->x, newY);
break;
}
case 's':
save_cur(console, false);
break;
case 'u':
restore_cur(console, false);
break;
case 'r':
{
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':
{
int32 lines = argCount > 0 ? args[0] : 1;
while (lines > 0) {
scrdown(console);
lines--;
}
break;
}
case 'M':
{
int32 lines = argCount > 0 ? args[0] : 1;
while (lines > 0) {
scrup(console);
lines--;
}
break;
}
case 'K':
if (argCount == 0 || args[0] == 0) {
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_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:
switch (*c) {
case '\n':
lf(console);
break;
case '\r':
cr(console);
break;
case 0x8:
del(console);
break;
case '\t':
tab(console);
break;
case '\a':
dprintf("<BEEP>\n");
break;
case '\0':
break;
case 0x1b:
console->arg_count = 0;
console->state = CONSOLE_STATE_GOT_ESCAPE;
break;
default:
console_putch(console, *c);
}
break;
case CONSOLE_STATE_GOT_ESCAPE:
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 '?':
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:
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;
}
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)
{
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);
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;
}
status_t
init_hardware(void)
{
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) {
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,
* 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)
{
}