/*! * \ingroup programs * \defgroup console Console server * * Console server for The Möbius * * - Sets up a server port (named CONSOLE_PORT = L"console") and waits for * connections. * - Starts a new thread for each connection and creates an * on-screen console. * - Receives characters over the connection and displays them on the console. * - Sends any keys pressed over the connection. * - Allows switching between consoles (via Ctrl+Tab/Ctrl+Shift+Tab) and * closing of consoles (via Ctrl+F4). * * Most console I/O for programs is handled by kernelu.dll. */ #include <stdlib.h> #include <stdio.h> #include <wchar.h> #include <ctype.h> #include <os/port.h> #include <os/os.h> #include <os/fs.h> #include <os/devreq.h> #include <os/device.h> #include <os/keyboard.h> #include <os/console.h> #include <os/mouse.h> #include <os/video.h> //@{ //! Colours to be used for console title bars #define CON_TITLE_COLOUR 0x1700 //! Colours to be used for the buttons on console title bars #define CON_BUTTON_COLOUR 0x7900 //! The text-mode VGA device addr_t vgatext; typedef struct console_t console_t; //! Describes a console /*! * The console server maintains one console_t structure for each connection * to its server port. */ struct console_t { //! Connection to the client program addr_t port; //! Base address of the console's video buffer, relative to the start //! of video memory addr_t base; //! Location of the text cursor unsigned x, y; //! Attributes (colour/intensity) currently selected for new text word attrib; //! Variables describing the state of the escape sequence parser int esc, esc1, esc2, esc3; //! Width and height of the console short width, height; //! Console's title wchar_t* title; }; //! Number of consoles currently allocated int num_consoles; console_t *consoles[8], *con_current; int mouse_x, mouse_y; void conWriteToScreen(console_t* con, word* buf, size_t length) { request_t dreq; dreq.code = DEV_WRITE; dreq.params.write.length = length; dreq.params.write.buffer = buf; dreq.params.write.pos = con->base + (con->width * (con->y + 1) + con->x) * 2; con->x += length / sizeof(word); if (devUserRequest(vgatext, &dreq, sizeof(dreq))) thrWaitHandle(&dreq.event, 1, true); devUserFinishRequest(&dreq, true); } void conDrawMouse(bool draw) { request_t dreq; word w = 0x1234; dreq.code = DEV_WRITE; dreq.params.write.length = sizeof(w); dreq.params.write.buffer = &w; dreq.params.write.pos = con_current->base + (con_current->width * mouse_y + mouse_x) * 2; if (devUserRequest(vgatext, &dreq, sizeof(dreq))) thrWaitHandle(&dreq.event, 1, true); devUserFinishRequest(&dreq, true); } void conWriteTitle(console_t* con) { request_t dreq; word* buf; int i; const wchar_t* ch; char n[2]; int index; index = -1; for (i = 0; i < num_consoles; i++) if (consoles[i] == con) { index = i; break; } buf = malloc(con->width * sizeof(word)); for (i = 0; i < con->width; i++) buf[i] = CON_TITLE_COLOUR | 0x20; i = 1; for (ch = L"Ctrl+F1=Help"; *ch && i < con->width; ch++, i++) { wcstombs(n, ch, 1); buf[i] = CON_TITLE_COLOUR | (byte) n[0]; } i = (con->width - wcslen(con->title)) / 2; if (i < 0) i = 0; if (index > 0) buf[max(i - 2, 0)] = CON_TITLE_COLOUR | 27; for (ch = con->title; *ch && i < con->width; ch++, i++) { wcstombs(n, ch, 1); buf[i] = CON_TITLE_COLOUR | (byte) n[0]; } if (index > -1 && index < num_consoles - 1) buf[min(i + 1, con->width - 1)] = CON_TITLE_COLOUR | 26; buf[con->width - 1] = CON_BUTTON_COLOUR | 0xFE; dreq.code = DEV_WRITE; dreq.params.write.length = con->width * sizeof(word); dreq.params.write.buffer = buf; dreq.params.write.pos = con->base; if (devUserRequest(vgatext, &dreq, sizeof(dreq))) thrWaitHandle(&dreq.event, 1, true); devUserFinishRequest(&dreq, true); free(buf); } void conClear(console_t* con) { word *buf; int i; buf = malloc(sizeof(word) * con->width * con->height); for (i = 0; i < con->width * con->height; i++) buf[i] = con->attrib | 0x20; con->x = con->y = 0; conWriteToScreen(con, buf, sizeof(word) * con->width * con->height); con->x = con->y = 0; free(buf); } void conSetAttrib(console_t* con, byte att) { static const char ansi_to_vga[] = { 0, 4, 2, 6, 1, 5, 3, 7 }; unsigned char new_att; new_att = con->attrib >> 8; if(att == 0) new_att &= ~0x08; /* bold off */ else if(att == 1) new_att |= 0x08; /* bold on */ else if(att >= 30 && att <= 37) { att = ansi_to_vga[att - 30]; new_att = (new_att & ~0x07) | att;/* fg color */ } else if(att >= 40 && att <= 47) { att = ansi_to_vga[att - 40] << 4; new_att = (new_att & ~0x70) | att;/* bg color */ } con->attrib = new_att << 8; } void conUpdateBase(console_t* con) { dword params[2]; params[0] = con->base; params[1] = con->base + (con->x + (con->y + 1) * con->width) * 2; devIoCtl(vgatext, 0, params, sizeof(params)); } void conSwitch(console_t* con) { con_current = con; conUpdateBase(con); conWriteTitle(con); } void conClose(console_t* con) { unsigned i; for (i = 0; i < num_consoles; i++) if (consoles[i] == con) consoles[i] = NULL; if (con->port) portClose(con->port); free(con); } void conWriteInternal(console_t* con, const wchar_t* buf, size_t length) { word* cells; const wchar_t *next_ctrl; wchar_t temp[100]; int i, j, k; size_t len; //wprintf(L"conWriteInternal: writing %d...", length); j = 0; while (j < length) { if (con->esc == 0) { next_ctrl = NULL; for (k = j; k < length; k++) if (wcschr(L"\t\r\n\b\x1b", buf[k])) { next_ctrl = buf + k; break; } if (next_ctrl == NULL) next_ctrl = buf + length; if (next_ctrl > buf + j) { wchar_t w; char n[2]; len = next_ctrl - (buf + j); wcsncpy(temp, buf + j, len); temp[len] = '\0'; /*cells = (word*) temp; for (i = 0; i < len; i++) { w = temp[i]; wcstombs(n, &w, 1); cells[i] = con->attrib | (byte) n[0]; } conWriteToScreen(con, cells, len * sizeof(word));*/ dputws(temp); j += len; } else { switch (*next_ctrl) { case '\t': con->x = (con->x + 4) & -3; break; case '\n': con->x = 0; con->y++; break; case '\r': con->x = 0; break; case '\b': if (con->x > 0) { con->x--; conWriteInternal(con, L" ", 1); con->x--; } break; case '\x1b': con->esc = 1; con->esc1 = 0; break; } j++; } } else { wchar_t c; c = buf[j++]; if (con->esc == 1) { if (c == L'[') { con->esc++; con->esc1 = 0; continue; } } else if (con->esc == 2) { if (iswdigit(c)) { con->esc1 = con->esc1 * 10 + c - L'0'; continue; } else if (c == ';') { con->esc++; con->esc2 = 0; continue; } else if (c == 'J') { if (con->esc1 == 2) conClear(con); } else if (c == 'm') conSetAttrib(con, con->esc1); con->esc = 0; continue; } else if (con->esc == 3) { if (iswdigit(c)) { con->esc2 = con->esc2 * 10 + c - '0'; continue; } else if(c == ';') { con->esc++; /* ESC[num1;num2; */ con->esc3 = 0; continue; } /* ESC[num1;num2H -- move cursor to num1,num2 */ else if(c == 'H') { if(con->esc2 < con->width) con->x = con->esc2; if(con->esc1 < con->height) con->y = con->esc1; } /* ESC[num1;num2m -- set attributes num1,num2 */ else if(c == 'm') { conSetAttrib(con, con->esc1); conSetAttrib(con, con->esc2); } con->esc = 0; continue; } /* ESC[num1;num2;num3 */ else if(con->esc == 4) { if (iswdigit(c)) { con->esc3 = con->esc3 * 10 + c - '0'; continue; } /* ESC[num1;num2;num3m -- set attributes num1,num2,num3 */ else if(c == 'm') { conSetAttrib(con, con->esc1); conSetAttrib(con, con->esc2); conSetAttrib(con, con->esc3); } con->esc = 0; continue; } con->esc = 0; } } //wprintf(L"done\n"); if (con == con_current) conUpdateBase(con); } bool conWrite(const wchar_t *str) { conWriteInternal(con_current, str, wcslen(str)); return true; } console_t *conCreate(addr_t port) { console_t *con; wchar_t str[16]; con = malloc(sizeof(console_t)); con->port = port; con->base = num_consoles * 4000; con->attrib = 0x0700; con->esc = 0; con->width = 80; con->height = 24; swprintf(str, L"Console %d", num_consoles + 1); con->title = wcsdup(str); consoles[num_consoles++] = con; conWriteTitle(con); conClear(con); conSwitch(con); return con; } void conClientThread(addr_t param) { console_t* con; console_request_t req; //console_reply_t reply; size_t length, written; wchar_t buf[100]; con = (console_t*) param; //wprintf(L"[%d] conClientThread: port %x\n", thrGetId(), con->port); while (thrWaitHandle(&con->port, 1, true)) { //wprintf(L"[%d] reading...", thrGetId()); length = sizeof(req); if (!fsRead(con->port, &req, &length) || length < sizeof(req)) { _pwerror(L"console"); continue; } //wprintf(L"done\n"); //reply.code = req.code; switch (req.code) { case CON_WRITE: written = 0; while (written < req.params.write.length) { length = min(req.params.write.length, countof(buf)); length *= sizeof(wchar_t); if (!fsRead(con->port, buf, &length)) break; length /= sizeof(wchar_t); conWriteInternal(con, buf, length); written += length; } break; case CON_CLOSE: conClose(con); thrExit(0); default: //reply.code = CON_UNKNOWN; break; } //length = sizeof(reply); //fsWrite(con->port, &reply, &length); } } void conHelpThread(dword param) { console_t *con, *old_current; old_current = con_current; con = conCreate(NULL); conWrite(L"\x1b[30;47m\x1b[2JThe Möbius Help"); thrSleep(1000); conClose(con); conSwitch(old_current); thrExit(0); } void conKeyboardThread(dword param) { addr_t keyboard; request_t req; dword key, old_key; size_t length; unsigned con_current_index = 0; //wchar_t ch; //wprintf(L"Keyboard thread (%d)\n", thrGetId()); keyboard = devOpen(L"keyboard", NULL); while (true) { req.code = DEV_READ; req.params.read.buffer = &key; req.params.read.length = sizeof(key); //wprintf(L"Request..."); if (!devUserRequest(keyboard, &req, sizeof(req))) break; //wprintf(L"done\nWait..."); thrWaitHandle(&req.event, 1, true); //wprintf(L"done\n"); devUserFinishRequest(&req, true); //wprintf(L"Key pressed\n"); old_key = key; if (key & KBD_BUCKY_CTRL) { if (consoles[con_current_index] != con_current) con_current_index = -1; switch (key & ~KBD_BUCKY_ANY) { case KEY_F1: thrCreate(conHelpThread, 0, 16); break; case KEY_F4: conClose(con_current); case '\t': if (key & KBD_BUCKY_SHIFT) do { con_current_index = (con_current_index - 1) % num_consoles; } while (consoles[con_current_index] == NULL); else do { con_current_index = (con_current_index + 1) % num_consoles; } while (consoles[con_current_index] == NULL); conSwitch(consoles[con_current_index]); continue; } } if (con_current) { length = sizeof(old_key); fsWrite(con_current->port, &old_key, &length); //ch = old_key; //if (ch) //conWriteInternal(con_current, &ch, 1); } } devClose(keyboard); thrExit(0); } void conMouseThread(dword param) { addr_t mouse; request_t req; mouse_packet_t packet; mouse = devOpen(L"mouse", NULL); while (true) { req.code = DEV_READ; req.params.read.buffer = &packet; req.params.read.length = sizeof(packet); if (!devUserRequest(mouse, &req, sizeof(req))) break; thrWaitHandle(&req.event, 1, true); devUserFinishRequest(&req, true); conDrawMouse(false); mouse_x += packet.dx; mouse_y += packet.dy; conDrawMouse(true); } devClose(mouse); thrExit(0); } void NtProcessStartup() { addr_t port, client; addr_t video; console_t* con; request_t *req; vid_rect_t *rect; vid_text_t *text; size_t size; int i; vgatext = devOpen(L"vgatext", NULL); video = devOpen(L"vga4", NULL); size = sizeof(*req) - sizeof(union request_params_t) + sizeof(vid_rect_t); req = malloc(size); req->code = VID_FILLRECT; rect = (vid_rect_t*) &req->params; rect->colour = 6; for (i = 0; i < 16; i++) { rect->rect.left = i * 32; rect->rect.top = 0; rect->rect.right = rect->rect.left + 32; rect->rect.bottom = rect->rect.top + 32; rect->colour = i; devUserRequestSync(video, req, size); } free(req); size = sizeof(*req) - sizeof(union request_params_t) + sizeof(vid_text_t); req = malloc(size); req->code = VID_TEXTOUT; text = (vid_text_t*) &req->params; text->buffer = (addr_t) L"Hello, world!"; text->length = wcslen((wchar_t*) text->buffer) * sizeof(wchar_t); text->backColour = (pixel_t) -1; for (i = 0; i < 10; i++) { text->x = rand() % 100; text->y = rand() % 100; text->foreColour = rand() % 16; devUserRequestSync(video, req, size); } free(req); devClose(video); port = portCreate(CONSOLE_PORT); portListen(port); thrCreate(conKeyboardThread, 0, 16); //thrCreate(conMouseThread, 0, 16); wprintf(L"Console server\n"); while (true) { thrWaitHandle(&port, 1, true); client = portAccept(port); con = conCreate(client); thrCreate(conClientThread, (dword) con, 16); } devClose(vgatext); procExit(0); } //@}