diff --git a/src/Home/Telnet/Art/MATRIX.ANS b/src/Home/Telnet/Art/MATRIX.ANS deleted file mode 100755 index 0a598544..00000000 Binary files a/src/Home/Telnet/Art/MATRIX.ANS and /dev/null differ diff --git a/src/Home/Telnet/Art/MATRIX2.ANS b/src/Home/Telnet/Art/MATRIX2.ANS deleted file mode 100644 index 35688e66..00000000 Binary files a/src/Home/Telnet/Art/MATRIX2.ANS and /dev/null differ diff --git a/src/Home/Telnet/Art/TelnetSplash.ans b/src/Home/Telnet/Art/TelnetSplash.ans new file mode 100644 index 00000000..16c20ce1 Binary files /dev/null and b/src/Home/Telnet/Art/TelnetSplash.ans differ diff --git a/src/Home/Telnet/Art/ZealBBS.ans b/src/Home/Telnet/Art/ZealBBS.ans new file mode 100644 index 00000000..2cae0550 Binary files /dev/null and b/src/Home/Telnet/Art/ZealBBS.ans differ diff --git a/src/Home/Telnet/Telnet.ZC b/src/Home/Telnet/Telnet.ZC old mode 100755 new mode 100644 index 1911702f..627ae0ca --- a/src/Home/Telnet/Telnet.ZC +++ b/src/Home/Telnet/Telnet.ZC @@ -39,7 +39,88 @@ I64 TelnetOpen(U8 *host, U16 port) { // sock(CTCPSocket *)->timeout = TCP_TIMEOUT; return sock; } - +U0 HandleControlCodes(U8 ch) { + if (ch < 32) { // ASCII code below 32 (control character) + switch (ch) { + case 0: // NUL (Null) - Typically ignored + break; + case 7: // BEL (Bell) + Beep; + break; + case 8: // BS (Backspace) + term.cursor_x--; + break; + case 9: // HT (Horizontal Tab) + term.cursor_x += 8; + break; + case 10: // LF (Line Feed) + term.cursor_y++; + break; + case 11: // VT (Vertical Tab) + SysLog("Vertical Tab\n"); + break; + case 12: // FF (Form Feed) + DocClear(term.doc); + break; + case 13: // CR (Carriage Return) + term.cursor_y++; + break; + case 14: // SO (Shift Out) - Switch to an alternate character set + case 15: // SI (Shift In) - Switch back to the default character set + SysLog("Shift In/Out\n"); + break; + case 22: + SysLog("Synchronous Idle\n"); + break; + case 23: + SysLog("End of Transmission Block\n"); + break; + case 24: + SysLog("Cancel\n"); + break; + case 25: + SysLog("End of Medium\n"); + break; + case 26: + SysLog("Sub\n"); + break; + case 27: + SysLog("Esc\n"); + break; + case 28: + SysLog("Fs\n"); + break; + case 29: + SysLog("Gs\n"); + break; + case 30: + SysLog("Rs\n"); + break; + case 31: + SysLog("Unit Separator\n"); + break; + default: + // SysLog("CC %c happened\n", ch); + SysLog("CC happened\n"); + break; + } + } + else { + if (ch == 127) { + SysLog("case 127"); + } + if (ch == 0x24) { + // ch = "//$$$$"; + } + if (ch >= 32 && ch < 256) // ZealOS's ASCII is up to 255 + { + "%c", ch; + } + else { + "%c", '?'; // unrecognized character + } + } +} U0 InputTask(U0 *args) { I64 sock = *args; I64 sc; diff --git a/src/Home/Telnet/Telnet32.ZC b/src/Home/Telnet/Telnet32.ZC new file mode 100755 index 00000000..4afc3a2a --- /dev/null +++ b/src/Home/Telnet/Telnet32.ZC @@ -0,0 +1,821 @@ +// Telnet client for ZealOS by y4my4m +// Public Domain +Cd(__DIR__);; + +#define TELNET_PORT 23 +#define BUF_SIZE 8192 // way too big? +#define INPUT_BUF_SIZE 32 +#define TIMEOUT_DURATION 500000 + +#define NEGOTIATE 0xFF + +#define ANSI_ESC 0x1B +#define ANSI_CSI 0x5B // [ + +#define CHAR_HEIGHT 8 +#define CHAR_WIDTH 8 + +#define MAX_ANSI_PARAMS 32 + +#include "TelnetNegotiation" +#include "TelnetHelpers" + +CTask *input_task = NULL; +Bool force_disconnect = FALSE; +Bool redraw_needed = FALSE; + +class ScreenCell { + U8 ch; + I64 color; +} +class Terminal { + I64 sock; + Bool sock_ready; + I64 window_width; + I64 window_height; + CDoc *doc; + CTask *task; + CDC *dc; + + ScreenCell screen[25][80]; + + I64 current_color; + I64 cursor_x; + I64 cursor_y; + + U8 buffer[BUF_SIZE]; + I64 buffer_len; +} term; + + +I64 TelnetOpen(U8 *host, U16 port) { + I64 socket; + + if (host == NULL) { + return -1; + } + + socket = TCPConnectionCreate(host, port); + "$$GREEN$$Conecting to %s:%d.$$FG$$$$BG$$\n", host, port; + if (socket <= 0) { + PrintErr("Failed to connect to %s:%d\n", host, port); + return socket; + } + + // sock(CTCPSocket *)->timeout = 0; + // sock(CTCPSocket *)->timeout = TCP_TIMEOUT; + return socket; +} + +U0 HandleControlCodes(U8 ch) { + if (ch < 32) { // ASCII code below 32 (control character) + switch (ch) { + case 0: // NUL (Null) - Typically ignored + break; + case 7: // BEL (Bell) + Beep; + break; + case 8: // BS (Backspace) + term.cursor_x--; + break; + case 9: // HT (Horizontal Tab) + term.cursor_x += 8; + break; + case 10: // LF (Line Feed) + term.cursor_y++; + // if (term.cursor_y >= term.window_height) { + // term.cursor_y = 0; // reset Y position to 0 when it exceeds window height + // } + // If the next character is CR, ignore it + // TODO: dont directly manipulate the buffer + // if (*(term.buffer + 1) == 13) term.buffer++; + break; + case 11: // VT (Vertical Tab) + SysLog("Vertical Tab\n"); + break; + case 12: // FF (Form Feed) + DocClear(term.doc); + // term.cursor_x = 0; + // term.cursor_y = 0; + break; + case 13: // CR (Carriage Return) + term.cursor_x = 0; + // If the next character is LF, ignore it + // if (*(term.buffer + 1) == 10) term.buffer++; + break; + case 14: // SO (Shift Out) - Switch to an alternate character set + case 15: // SI (Shift In) - Switch back to the default character set + SysLog("Shift In/Out\n"); + break; + case 22: + SysLog("Synchronous Idle\n"); + break; + case 23: + SysLog("End of Transmission Block\n"); + break; + case 24: + SysLog("Cancel\n"); + break; + case 25: + SysLog("End of Medium\n"); + break; + case 26: + SysLog("Sub\n"); + break; + case 27: + SysLog("Esc\n"); + break; + case 28: + SysLog("Fs\n"); + break; + case 29: + SysLog("Gs\n"); + break; + case 30: + SysLog("Rs\n"); + break; + case 31: + SysLog("Unit Separator\n"); + break; + default: + // SysLog("CC %c happened\n", ch); + SysLog("CC happened\n"); + break; + } + } + else { + if (ch == 127) { + SysLog("case 127"); + } + if (ch == 0x24) { + // ch = "//$$$$"; + } + if (ch >= 32 && ch < 256) // ZealOS's ASCII is up to 255 + { + term.screen[term.cursor_y][term.cursor_x].ch = ch; + term.screen[term.cursor_y][term.cursor_x].color = term.current_color; + term.cursor_x++; + if (term.cursor_x >= term.window_width) { + term.cursor_x = 0; + term.cursor_y++; + if (term.cursor_y >= term.window_height) { + term.cursor_y = 0; + } + } + } + else { + // "%c", '?'; // unrecognized character + } + } +} + + +U0 TerminalDrawIt(CTask *task, CDC *dc) +{ + // Clear the document + // DocClear; + + I64 row, col; + // Loop over the screen array and draw each character + for (row = 0; row < term.window_height; row++) { + for (col = 0; col < term.window_width; col++) { + // Get the character and color from the screen array + U8 ch = term.screen[row][col].ch; + + // Set the color + dc->color = term.screen[row][col].color; + // term.dc->color = WHITE; + + // Draw the character + // GrPutChar(term.dc, col * CHAR_WIDTH, row * CHAR_HEIGHT, ch); + GrPutChar(dc, col * 8, row * 8, ch); + } + } + + // Draw the cursor + // Note: this draws the cursor as a white rectangle. You may want to customize this. + // term.dc->color = WHITE; + // GrRect(term.dc, term.cursor_x * CHAR_WIDTH, term.cursor_y * CHAR_HEIGHT, + // (term.cursor_x + 1) * CHAR_WIDTH - 1, (term.cursor_y + 1) * CHAR_HEIGHT - 1); +} + +U0 TerminalTask() { + while (!term.sock_ready) { + Sleep(100); // Avoid busy waiting + } + // This task receives data from the socket and fills the buffer + while (!force_disconnect) + { + term.buffer_len = TCPSocketReceive(term.sock, term.buffer, BUF_SIZE - 1); + if (term.buffer_len > 0) { + redraw_needed = TRUE; + term.buffer[term.buffer_len] = '\0'; + // Parse the buffer and draw the contents + U8 *ptr = term.buffer; + while (ptr < term.buffer + term.buffer_len) { + // Telnet negotiation sequence + if (*ptr == NEGOTIATE) { + // FIXME: i don't think the telnet negotiation is actually working properly? + TelnetNegotiate(term.sock, *ptr); + ptr += 3; + } + else if (*ptr == ANSI_ESC) { + // ANSI escape sequence + ptr++; + if (*ptr == ANSI_CSI) { + ptr++; + I64 ansi_code[MAX_ANSI_PARAMS], counter; + for (counter = 0; counter < MAX_ANSI_PARAMS; counter++) { + ansi_code[counter] = 0; // Initialize all elements to 0 + } + I64 ansi_param_count = 0; + while (IsDigit(*ptr) || *ptr == ';') { + if (IsDigit(*ptr)) { + ansi_code[ansi_param_count] = ansi_code[ansi_param_count] * 10 + (*ptr - '0'); + ptr++; + } + else if (*ptr == ';') { + ansi_param_count++; + if (ansi_param_count >= MAX_ANSI_PARAMS) { + // Error handling: too many parameters + break; + } + ptr++; + if(!IsDigit(*ptr) || *ptr == ';'){ + break; + } + } + } + + // Handle specific ANSI escape sequences + switch (*ptr) { + case 'n': + SysLog("Case n, %d\n",ansi_code[0]); + if (ansi_code[0] == 5) { + // Respond with terminal readiness + SysLog("reported terminal readiness\n"); + U8 deviceStatusResponse[5]; + deviceStatusResponse[0] = ANSI_ESC; + deviceStatusResponse[1] = ANSI_CSI; + deviceStatusResponse[2] = 0x30; // '0' + deviceStatusResponse[3] = 0x6E; // 'n' + deviceStatusResponse[4] = 0x00; // Null-terminator + TCPSocketSend(term.sock, deviceStatusResponse, 4); + // TCPSocketSendString(term.sock, "\x1B[0n"); + } + else if (ansi_code[0] == 6) { + // Respond with cursor position + // U8 response[32] = "\x1B[%d;%dR", window_width, term.window_height; + SysLog("reported cursor position\n"); + U8 cursorResponse[8]; + cursorResponse[0] = ANSI_ESC; + cursorResponse[1] = ANSI_CSI; + cursorResponse[2] = 0x32; + cursorResponse[3] = 0x35; + cursorResponse[4] = 0x3B; + cursorResponse[5] = 0x38; + cursorResponse[6] = 0x30; + cursorResponse[6] = 0x52; + cursorResponse[7] = 0x00; + TCPSocketSend(term.sock, cursorResponse, 7); + // TCPSocketSendString(term.sock, "\x1B\[25;80R"); + } + else if (ansi_code[0] == 255) { + // https://github.com/NuSkooler/enigma-bbs/blob/97cd0c3063b0c9f93a0fa4a44a85318ca81aef43/core/ansi_term.js#L140 + SysLog("reported screensize?\n"); + SendWindowSize(term.sock, 25, 80); + } + ptr++; + break; + case 'c': + // Respond with device attributes + SysLog("reported device attributes\n"); + // TCPSocketSendString(term.sock, "\x1B[?1;0c"); + // Reports at VT101 (not sure why though) + U8 deviceAttributesResponse[8]; + deviceAttributesResponse[0] = ANSI_ESC; + deviceAttributesResponse[1] = ANSI_CSI; + deviceAttributesResponse[2] = 0x3F; // '?' + deviceAttributesResponse[3] = 0x31; // '1' + deviceAttributesResponse[4] = 0x3B; // ';' + deviceAttributesResponse[5] = 0x32; // '0' + deviceAttributesResponse[6] = 0x63; // 'c' + deviceAttributesResponse[7] = 0x00; // Null-terminator + TCPSocketSend(term.sock, deviceAttributesResponse, 7); + ptr++; + break; + case 'm': + // colors might be printed in the wrong order? + // like, [1;40m and now [40m;1m + I64 m; + Bool isBright = FALSE; + for (m = 0; m <= ansi_param_count; m++) { + if (ansi_code[m] <= 10) { + switch (ansi_code[m]) { + case 0: + term.current_color = WHITE; // should be BG FG for full reset + isBright = FALSE; + break; // reset + case 1: isBright = TRUE; break; + case 2: isBright = FALSE; break; + } + } + else if ((ansi_code[m] >= 30 && ansi_code[m] <= 39) || (ansi_code[m] >= 90 && ansi_code[m] <= 97)) { + // Set foreground color + // SysLog("ansi_code[%d] = %d\n", m, ansi_code[m]); + if(!isBright){ + switch (ansi_code[m]) { + case 30: + term.current_color = BLACK; + break; + case 31: + term.current_color = RED; + break; + case 32: + term.current_color = GREEN; + break; + case 33: + term.current_color = YELLOW; + break; + case 34: + term.current_color = BLUE; + break; + case 35: + term.current_color = PURPLE; + break; + case 36: + term.current_color = CYAN; + break; + case 37: + term.current_color = WHITE; + break; + case 39: + term.current_color = WHITE; + break; + default: break; + } + } + else { + switch (ansi_code[m]) { + case 90: + case 30: + term.current_color = DKGRAY; + break; + case 91: + case 31: + term.current_color = LTRED; + break; + case 92: + case 32: + term.current_color = LTGREEN; + break; + case 93: + case 33: + term.current_color = YELLOW; + break; + case 94: + case 34: + term.current_color = LTBLUE; + break; + case 95: + case 35: + term.current_color = LTPURPLE; + break; + case 96: + case 36: + term.current_color = LTCYAN; + break; + case 97: + case 37: + term.current_color = LTGRAY; + break; + case 39: + term.current_color = WHITE; + break; + default: break; + } + } + } + // this is a dumb approach, just do a CatPrint or something + // until we properly catch the `;` it will stay buggy + else if ((ansi_code[m] >= 40 && ansi_code[m] <= 49) || (ansi_code[m] >= 100 && ansi_code[m] <= 107)) { + // Set background color + // SysLog("ansi_code[%d] = %d\n", m, ansi_code[m]); + if(!isBright){ + switch (ansi_code[m]) { + case 40: + term.current_color = BLACK; + break; + case 41: + term.current_color = RED; + break; + case 42: + term.current_color = GREEN; + break; + case 43: + term.current_color = YELLOW; + break; + case 44: + term.current_color = BLUE; + break; + case 45: + term.current_color = PURPLE; + break; + case 46: + term.current_color = CYAN; + break; + case 47: + term.current_color = WHITE; + break; + case 49: + // reset + term.current_color = BLACK; + break; + default: break; + } + } + else { + switch (ansi_code[m]) { + case 100: + case 40: + term.current_color = DKGRAY; + break; + case 101: + case 41: + term.current_color = LTRED; + break; + case 102: + case 42: + term.current_color = LTGREEN; + break; + case 103: + case 43: + term.current_color = YELLOW; + break; + case 104: + case 44: + term.current_color = LTBLUE; + break; + case 105: + case 45: + term.current_color = LTPURPLE; + break; + case 106: + case 46: + term.current_color = LTCYAN; + break; + case 107: + case 47: + term.current_color = LTGRAY; + break; + case 49: + // reset + term.current_color = BLACK; + default: break; + } + } + } + } + ptr++; + break; + case 'A': + // Cursor Up + SysLog("Cursor Up\n"); + term.cursor_y -= ansi_code[0]; + ptr++; + break; + case 'B': + // Cursor Down + SysLog("Cursor Down\n"); + term.cursor_y += ansi_code[0]; + ptr++; + break; + case 'C': + // Cursor Right + // SysLog("Cursor Right %d %d\n", ansi_param_count, ansi_code[0]); + term.cursor_x += ansi_code[0]; + // NOTE: this has been "fixed" since we now change the window's background color + // if we just move the cursor, + // you dont get the colored background since we skip over it directly + // I64 C; + // for (C = 0; C < ansi_code[0]; C++) { + // " "; + // } + ptr++; + break; + case 'D': + // Cursor Left + SysLog("Cursor Left\n"); + term.cursor_x -= ansi_code[0]; + ptr++; + break; + case 'E': + // Cursor Next Line + SysLog("Cursor Next Line\n"); + term.cursor_x = 0; + term.cursor_y++; + if (term.cursor_y >= term.window_height) { + // scroll + } + ptr++; + break; + case 'F': + // Cursor Previous Line + SysLog("Cursor Previous Line\n"); + term.cursor_x = 0; + term.cursor_y -= ansi_code[0]; + if (term.cursor_y < 0) { + term.cursor_y = 0; // prevent y from going below 0 + } + ptr++; + break; + case 'G': + // Cursor Horizontal Absolute + SysLog("Cursor Horizontal Absolute\n"); + term.cursor_x = ansi_code[0]; + ptr++; + break; + case 'H': + case 'f': + I64 row = 1, col = 1; // default values + // Parse the row number + if(ansi_code[0] != 1) + row = ansi_code[0]; + if(ansi_code[1] != 1) + col = ansi_code[1]; + + // TODO: This is a hack, dont skip row 0, col 0 (maybe?) + if (row == 0 && col == 0) { + ptr++; + break; + } + // SysLog("H or f AFTER row:%d, col:%d, cnt:%d\n", row, col, ansi_param_count); + + if (row >= term.window_height) + row = term.window_height; + if (col >= term.window_width) + col = term.window_width; + // "$$CM,0,0$$"; + // term.cursor_x = col-1; + // term.cursor_y = row-1; + term.cursor_x = col; + term.cursor_y = row; + ptr++; + break; + case 'J': + // SysLog("J code, %d %d\n", ansi_param_count, ansi_code[0]); + // Erase in Display + if (ansi_code[0] == 0) { + // Erase from cursor to end of display + // DocDelToNum(Fs->display_doc, Fs->display_doc->cur_entry->line_num); + } else if (ansi_code[0] == 1) { + // Erase from cursor to beginning of display + // DocDelToEntry(Fs->display_doc, Fs->display_doc->cur_entry, FALSE); + } else if (ansi_code[0] == 2) { + // Erase entire display + DocClear(term.doc); + term.cursor_x = 0; + term.cursor_y = 0; + } + ptr++; + break; + case 'K': + // TODO: I have no idea if this actually works + SysLog("K code\n"); + // Erase in Line + // CDocEntry *cur_entry = Fs->display_doc->cur_entry; + // CDocEntry *next_entry = cur_entry->next; + + // // Delete the current entry + // if (!(cur_entry->de_flags & (DOCEF_HOLD | DOCEF_FILTER_SKIP))) { + // Fs->display_doc->cur_entry = next_entry; + // Fs->display_doc->cur_col = next_entry->min_col; + // DocEntryDel(Fs->display_doc, cur_entry); + // } + + // // Create a new entry (line) in its place + // CDocEntry *new_entry = DocEntryNewTag(Fs->display_doc, cur_entry, ""); + // DocInsEntry(Fs->display_doc, new_entry); + + ptr++; + break; + case 'L': + SysLog("L code\n"); + ptr++; + break; + case 'S': + // TODO: Scroll Up + SysLog("Scroll Up"); + ptr++; + break; + case 'T': + // TODO: Scroll Down + SysLog("Scroll Down"); + ptr++; + break; + case 'M': + SysLog("Case M\n"); + term.cursor_y--; + ptr++; + break; + case '?': + ptr++; + I64 code = 0; + + while (IsDigit(*ptr)) { + code = code * 10 + (*ptr - '0'); + ptr++; + } + switch (code) { + case 25: + if (*ptr == 'l') DocCursor(OFF); // Hide cursor + if (*ptr == 'h') DocCursor(ON); // Show cursor + ptr++; // Move past 'l' or 'h' + break; + case 47: + if (*ptr == 'l') SysLog("code 47l\n"); // restore screen + if (*ptr == 'h') SysLog("code 47h\n"); // save screen + ptr++; // Move past 'l' or 'h' + break; + case 1049: + if (*ptr == 'l') SysLog("code 1049l\n"); // enables the alternative buffer + if (*ptr == 'h') SysLog("code 1049h\n"); // disables the alternative buffer + ptr++; // Move past 'l' or 'h' + break; + default: + ptr++; + break; + } + break; + case 's': + SysLog("SaveCurrentCursorPosition\n"); + ptr++; + break; + case 'u': + SysLog("RestoreCurrentCursorPosition\n"); + ptr++; + break; + case 'r': + // self.restoreCursorPosition(); + SysLog("r case \n"); + ptr++; + break; + case 'h': + case 'l': + // TODO: Handle 'h' (set mode) or 'l' (reset mode) codes + SysLog("h or l case \n"); + ptr++; // Skip 'h' or 'l' + break; + case '=': + SysLog("ScreenMode attempt\n"); + ptr++; + break; + default: + if(!IsDigit(*ptr)) { + SysLog("Unknown code: %c\n", *ptr); + } + ptr++; + break; + } + } + } + else { + // Print the received character + HandleControlCodes(*ptr); + ptr++; + } + } + if (redraw_needed) { + // TerminalDrawIt(term.task, term.task); + DocClear(term.doc); + term.cursor_x = 0; + term.cursor_y = 0; + redraw_needed = TRUE; + } + } else { + "Error: Connection closed by the remote host.\n"; + break; + } + } +} + +U0 Telnet(U8 *host, U16 port=TELNET_PORT) { + + term.sock_ready = 0; // Initialize the semaphore + term.sock = TelnetOpen(host, port); + term.window_width = 80; + term.window_height = 25; + if (term.sock <= 0) { + return; + } + term.sock_ready = 1; // Signal that the socket is ready + term.doc = Fs->display_doc; + + // Spawn a task to receive data from the socket + term.task = Spawn(&TerminalTask, NULL, "Telnet"); + // term.task->win_inhibit = WIG_USER_TASK_DEFAULT; + // LBts(&term.task->display_flags, DISPLAYf_SHOW); + + Fs->draw_it = &TerminalDrawIt; + + StrCopy(Fs->task_title, "TELNET"); + Fs->border_src = BDS_CONST; + Fs->border_attr = LTGREEN << 4 + DriveTextAttrGet(':') & 15; + Fs->text_attr = BLACK << 4 + WHITE; + Fs->title_src = TTS_LOCKED_CONST; + + Fs->win_width = term.window_width; + WinHorz((TEXT_COLS / 2) - (Fs->win_width / 2), + (TEXT_COLS / 2) - (Fs->win_width / 2) + + (Fs->win_width - 1), + Fs); + Fs->win_height = term.window_height; + WinVert((TEXT_ROWS / 2) - (Fs->win_height / 2), + (TEXT_ROWS / 2) - (Fs->win_height / 2) + + (Fs->win_height - 1), + Fs); + DocClear; + // WinFocus(term.task); + // DocClear(Fs->border_doc, TRUE); + + // probably should use word wrap? + // DocPrint(, "$$WW,1$$"); + // DocCursor(OFF); + // term.dc = DCAlias; + "$$BG,GREEN$$$$WHITE$$Connected$$FG$$$$BG$$\n"; + + I64 sc; + // https://theasciicode.com.ar/ascii-control-characters/escape-ascii-code-27.html + try + { + while (!force_disconnect) { + U8 key = KeyGet(&sc); + switch (key) + { + case 0: + switch (sc.u8[0]) + { + case SC_CURSOR_LEFT: + TCPSocketSendString(term.sock, "\x1B[D"); + break; + + case SC_CURSOR_RIGHT: + TCPSocketSendString(term.sock, "\x1B[C"); + break; + + case SC_CURSOR_UP: + TCPSocketSendString(term.sock, "\x1B[A"); + break; + + case SC_CURSOR_DOWN: + TCPSocketSendString(term.sock, "\x1B[B"); + break; + default: + break; + } + break; + case 9: + switch (sc.u8[0]) + { + case SC_TAB: + TCPSocketSendString(term.sock, "\x09"); + break; + default: + break; + } + case CH_BACKSPACE: + TCPSocketSendString(term.sock, "\x08"); + break; + case CH_ESC: + TCPSocketSendString(term.sock, "\x1B"); + break; + case CH_SHIFT_ESC: + force_disconnect = TRUE; + break; + // send buffer on enter + case '\n': + TCPSocketSendString(term.sock, "\r\n"); + + break; + default: + if (key >= ' ' && key <= '~') { + // Handle regular keys + U8 input_buf[2]; + input_buf[0] = key; + input_buf[1] = '\0'; + TCPSocketSend(term.sock, input_buf, 1); + } + break; + } + } + } + catch + PutExcept; + + Kill(term.task); + TCPSocketClose(term.sock); + "Telnet connection closed.\n"; +} + +// dev server +Telnet("localhost", 8888); \ No newline at end of file diff --git a/src/Home/Telnet/TelnetHelpers.ZC b/src/Home/Telnet/TelnetHelpers.ZC index 242fb055..3fb7efab 100644 --- a/src/Home/Telnet/TelnetHelpers.ZC +++ b/src/Home/Telnet/TelnetHelpers.ZC @@ -17,89 +17,392 @@ U0 TelnetPrompt() { } } -U0 HandleControlCodes(U8 ch) { - if (ch < 32) { // ASCII code below 32 (control character) - switch (ch) { - case 0: // NUL (Null) - Typically ignored - break; - case 7: // BEL (Bell) - Beep; - break; - case 8: // BS (Backspace) - // "%c%c%c", 8, ' ', 8; // Move cursor back, erase character, move cursor back again - "$$CM,-8,0$$"; - break; - case 9: // HT (Horizontal Tab) - // " "; // 8 spaces - "$$CM,8,0$$"; - break; - case 10: // LF (Line Feed) - "\n"; - break; - case 11: // VT (Vertical Tab) - SysLog("Vertical Tab\n"); - break; - case 12: // FF (Form Feed) - DocClear; - break; - case 13: // CR (Carriage Return) - "\r"; - break; - case 14: // SO (Shift Out) - Switch to an alternate character set - case 15: // SI (Shift In) - Switch back to the default character set - SysLog("Shift In/Out\n"); - break; - case 22: - SysLog("Synchronous Idle\n"); - break; - case 23: - SysLog("End of Transmission Block\n"); - break; - case 24: - SysLog("Cancel\n"); - break; - case 25: - SysLog("End of Medium\n"); - break; - case 26: - SysLog("Sub\n"); - break; - case 27: - SysLog("Esc\n"); - break; - case 28: - SysLog("Fs\n"); - break; - case 29: - SysLog("Gs\n"); - break; - case 30: - SysLog("Rs\n"); - break; - case 31: - SysLog("Unit Separator\n"); - break; - default: - SysLog("CC happened\n", ch); - break; - } - } - else { - if (ch == 127) { - SysLog("case 127"); - } - if (ch == 0x24) { - ch = "//$$$$"; - } - if (ch >= 32 && ch < 256) // ZealOS's ASCII is up to 255 - { - "%c", ch; - } - else { - "%c", '?'; // unrecognized character - } - } -} + +// U0 SplashScreen(U8 ch) { +// +// } + +// Eventually would like to parse it here: + +// U0 TelnetANSIParser(I64 sock, U8 *ptr) { +// U8 buffer[BUF_SIZE]; + +// I64 window_width = 80; +// I64 window_height = 25; +// if (*ptr == ANSI_CSI) { +// ptr++; +// I64 ansi_code[MAX_ANSI_PARAMS], counter; +// for (counter = 0; counter < MAX_ANSI_PARAMS; counter++) { +// ansi_code[counter] = 0; // Initialize all elements to 0 +// } +// I64 ansi_param_count = 0; +// while (IsDigit(*ptr) || *ptr == ';') { +// if (IsDigit(*ptr)) { +// ansi_code[ansi_param_count] = ansi_code[ansi_param_count] * 10 + (*ptr - '0'); +// ptr++; +// } +// else if (*ptr == ';') { +// ansi_param_count++; +// if (ansi_param_count >= MAX_ANSI_PARAMS) { +// // Error handling: too many parameters +// break; +// } +// ptr++; +// if(!IsDigit(*ptr) || *ptr == ';'){ +// break; +// } +// } +// } + +// // Handle specific ANSI escape sequences +// switch (*ptr) { +// case 'n': +// SysLog("Case n, %d\n",ansi_code[0]); +// if (ansi_code[0] == 5) { +// // Respond with terminal readiness +// SysLog("reported terminal readiness\n"); +// U8 deviceStatusResponse[5]; +// deviceStatusResponse[0] = ANSI_ESC; +// deviceStatusResponse[1] = ANSI_CSI; +// deviceStatusResponse[2] = 0x30; // '0' +// deviceStatusResponse[3] = 0x6E; // 'n' +// deviceStatusResponse[4] = 0x00; // Null-terminator +// TCPSocketSend(sock, deviceStatusResponse, 4); +// // TCPSocketSendString(sock, "\x1B[0n"); +// } +// else if (ansi_code[0] == 6) { +// // Respond with cursor position +// // U8 response[32] = "\x1B[%d;%dR", window_width, window_height; +// SysLog("reported cursor position\n"); +// U8 cursorResponse[8]; +// cursorResponse[0] = ANSI_ESC; +// cursorResponse[1] = ANSI_CSI; +// cursorResponse[2] = 0x32; +// cursorResponse[3] = 0x35; +// cursorResponse[4] = 0x3B; +// cursorResponse[5] = 0x38; +// cursorResponse[6] = 0x30; +// cursorResponse[6] = 0x52; +// cursorResponse[7] = 0x00; +// TCPSocketSend(sock, cursorResponse, 7); +// // TCPSocketSendString(sock, "\x1B\[25;80R"); +// } +// else if (ansi_code[0] == 255) { +// // https://github.com/NuSkooler/enigma-bbs/blob/97cd0c3063b0c9f93a0fa4a44a85318ca81aef43/core/ansi_term.js#L140 +// SysLog("reported screensize?\n"); +// SendWindowSize(sock, 25, 80); +// } +// ptr++; +// break; +// case 'c': +// // Respond with device attributes +// SysLog("reported device attributes\n"); +// // TCPSocketSendString(sock, "\x1B[?1;0c"); +// // Reports at VT101 (not sure why though) +// U8 deviceAttributesResponse[8]; +// deviceAttributesResponse[0] = ANSI_ESC; +// deviceAttributesResponse[1] = ANSI_CSI; +// deviceAttributesResponse[2] = 0x3F; // '?' +// deviceAttributesResponse[3] = 0x31; // '1' +// deviceAttributesResponse[4] = 0x3B; // ';' +// deviceAttributesResponse[5] = 0x32; // '0' +// deviceAttributesResponse[6] = 0x63; // 'c' +// deviceAttributesResponse[7] = 0x00; // Null-terminator +// TCPSocketSend(sock, deviceAttributesResponse, 7); +// ptr++; +// break; +// case 'm': +// // colors might be printed in the wrong order? +// // like, [1;40m and now [40m;1m +// I64 m; +// Bool isBright = FALSE; +// for (m = 0; m <= ansi_param_count; m++) { +// if (ansi_code[m] <= 10) { +// switch (ansi_code[m]) { +// case 0: "$$BG$$$$FG$$"; isBright = FALSE; break; // reset +// case 1: isBright = TRUE; break; +// case 2: isBright = FALSE; break; +// } +// } +// else if ((ansi_code[m] >= 30 && ansi_code[m] <= 39) || (ansi_code[m] >= 90 && ansi_code[m] <= 97)) { +// // Set foreground color +// // SysLog("ansi_code[%d] = %d\n", m, ansi_code[m]); +// if(!isBright){ +// switch (ansi_code[m]) { +// case 30: "$$BLACK$$"; break; +// case 31: "$$RED$$"; break; +// case 32: "$$GREEN$$"; break; +// case 33: "$$YELLOW$$"; break; +// case 34: "$$BLUE$$"; break; +// case 35: "$$PURPLE$$"; break; +// case 36: "$$CYAN$$"; break; +// case 37: "$$WHITE$$"; break; +// case 39: "$$FG$$"; break; +// default: break; +// } +// } +// else { +// switch (ansi_code[m]) { +// case 90: +// case 30: "$$DKGRAY$$"; break; +// case 91: +// case 31: "$$LTRED$$"; break; +// case 92: +// case 32: "$$LTGREEN$$"; break; +// case 93: +// case 33: "$$YELLOW$$"; break; +// case 94: +// case 34: "$$LTBLUE$$"; break; +// case 95: +// case 35: "$$LTPURPLE$$"; break; +// case 96: +// case 36: "$$LTCYAN$$"; break; +// case 97: +// case 37: "$$LTGRAY$$"; break; +// case 39: "$$FG$$"; break; +// default: break; +// } +// } +// } +// // this is a dumb approach, just do a CatPrint or something +// // until we properly catch the `;` it will stay buggy +// else if ((ansi_code[m] >= 40 && ansi_code[m] <= 49) || (ansi_code[m] >= 100 && ansi_code[m] <= 107)) { +// // Set background color +// // SysLog("ansi_code[%d] = %d\n", m, ansi_code[m]); +// if(!isBright){ +// switch (ansi_code[m]) { +// case 40: "$$BG,BLACK$$"; break; +// case 41: "$$BG,RED$$"; break; +// case 42: "$$BG,GREEN$$"; break; +// case 43: "$$BG,YELLOW$$"; break; +// case 44: "$$BG,BLUE$$"; break; +// case 45: "$$BG,PURPLE$$"; break; +// case 46: "$$BG,CYAN$$"; break; +// case 47: "$$BG,WHITE$$"; break; +// case 49: "$$BG$$"; break; // reset +// default: break; +// } +// } +// else { +// switch (ansi_code[m]) { +// case 100: +// case 40: "$$BG,DKGRAY$$"; break; +// case 101: +// case 41: "$$BG,LTRED$$"; break; +// case 102: +// case 42: "$$BG,LTGREEN$$"; break; +// case 103: +// case 43: "$$BG,YELLOW$$"; break; +// case 104: +// case 44: "$$BG,LTBLUE$$"; break; +// case 105: +// case 45: "$$BG,LTPURPLE$$"; break; +// case 106: +// case 46: "$$BG,LTCYAN$$"; break; +// case 107: +// case 47: "$$BG,LTGRAY$$"; break; +// case 49: "$$BG$$"; break; // reset +// default: break; +// } +// } +// } +// } +// ptr++; +// break; +// case 'A': +// // Cursor Up +// SysLog("Cursor Up\n"); +// // "$$CM+TY,0,-%d$$", ansi_code[0]; +// "$$CM,0,-%d$$", ansi_code[0]; +// ptr++; +// break; +// case 'B': +// // Cursor Down +// SysLog("Cursor Down\n"); +// "$$CM,0,%d$$", ansi_code[0]; +// ptr++; +// break; +// case 'C': +// // Cursor Right +// // SysLog("Cursor Right %d %d\n", ansi_param_count, ansi_code[0]); +// "$$CM,%d,0$$", ansi_code[0]; +// // NOTE: this has been "fixed" since we now change the window's background color +// // if we just move the cursor, +// // you dont get the colored background since we skip over it directly +// // I64 C; +// // for (C = 0; C < ansi_code[0]; C++) { +// // " "; +// // } +// ptr++; +// break; +// case 'D': +// // Cursor Left +// SysLog("Cursor Left\n"); +// "$$CM,-%d,0$$", ansi_code[0]; +// ptr++; +// break; +// case 'E': +// // Cursor Next Line +// SysLog("Cursor Next Line\n"); +// // "$$CM+TY,0,+%d$$", ansi_code[0]; +// "\n"; +// ptr++; +// break; +// case 'F': +// // Cursor Previous Line +// SysLog("Cursor Previous Line\n"); +// "$$CM+LY,0,-%d$$", ansi_code[0]; +// // "\n"; +// ptr++; +// break; +// case 'G': +// // Cursor Horizontal Absolute +// SysLog("Cursor Horizontal Absolute\n"); +// "$$CM,%d,0$$", ansi_code[0]; +// // "\n"; +// ptr++; +// break; +// case 'H': +// case 'f': +// I64 row = 1, col = 1; // default values +// // Parse the row number +// if(ansi_code[0] != 1) +// row = ansi_code[0]; +// if(ansi_code[1] != 1) +// col = ansi_code[1]; + +// // TODO: This is a hack, dont skip row 0, col 0 (maybe?) +// if (row == 0 && col == 0) { +// ptr++; +// break; +// } +// // SysLog("H or f AFTER row:%d, col:%d, cnt:%d\n", row, col, ansi_param_count); + +// if (row > window_height) +// row = window_height-1; +// if (col > window_width) +// col = window_width-1; +// // "$$CM,0,0$$"; +// "$$CM+LX+TY,LE=%d,RE=%d$$", col-1, row-1; +// ptr++; +// break; +// case 'J': +// // SysLog("J code, %d %d\n", ansi_param_count, ansi_code[0]); +// // Erase in Display +// if (ansi_code[0] == 0) { +// // Erase from cursor to end of display +// // DocDelToNum(Fs->display_doc, Fs->display_doc->cur_entry->line_num); +// } else if (ansi_code[0] == 1) { +// // Erase from cursor to beginning of display +// // DocDelToEntry(Fs->display_doc, Fs->display_doc->cur_entry, FALSE); +// } else if (ansi_code[0] == 2) { +// // Erase entire display +// DocClear; +// } +// ptr++; +// break; +// case 'K': +// // TODO: I have no idea if this actually works +// SysLog("K code\n"); +// // Erase in Line +// // CDocEntry *cur_entry = Fs->display_doc->cur_entry; +// // CDocEntry *next_entry = cur_entry->next; + +// // // Delete the current entry +// // if (!(cur_entry->de_flags & (DOCEF_HOLD | DOCEF_FILTER_SKIP))) { +// // Fs->display_doc->cur_entry = next_entry; +// // Fs->display_doc->cur_col = next_entry->min_col; +// // DocEntryDel(Fs->display_doc, cur_entry); +// // } + +// // // Create a new entry (line) in its place +// // CDocEntry *new_entry = DocEntryNewTag(Fs->display_doc, cur_entry, ""); +// // DocInsEntry(Fs->display_doc, new_entry); + +// ptr++; +// break; +// case 'L': +// SysLog("L code\n"); +// ptr++; +// break; +// case 'S': +// // TODO: Scroll Up +// SysLog("Scroll Up"); +// ptr++; +// break; +// case 'T': +// // TODO: Scroll Down +// SysLog("Scroll Down"); +// ptr++; +// break; +// case 'M': +// SysLog("Case M\n"); +// // TODO: is this correct? cursor should go one line up +// "$$CM,0,-1$$"; +// ptr++; +// break; +// case '?': +// ptr++; +// I64 code = 0; + +// while (IsDigit(*ptr)) { +// code = code * 10 + (*ptr - '0'); +// ptr++; +// } +// switch (code) { +// case 25: +// if (*ptr == 'l') DocCursor(OFF); // Hide cursor +// if (*ptr == 'h') DocCursor(ON); // Show cursor +// ptr++; // Move past 'l' or 'h' +// break; +// case 47: +// if (*ptr == 'l') SysLog("code 47l\n"); // restore screen +// if (*ptr == 'h') SysLog("code 47h\n"); // save screen +// ptr++; // Move past 'l' or 'h' +// break; +// case 1049: +// if (*ptr == 'l') SysLog("code 1049l\n"); // enables the alternative buffer +// if (*ptr == 'h') SysLog("code 1049h\n"); // disables the alternative buffer +// ptr++; // Move past 'l' or 'h' +// break; +// default: +// ptr++; +// break; +// } +// break; +// case 's': +// SysLog("SaveCurrentCursorPosition\n"); +// ptr++; +// break; +// case 'u': +// SysLog("RestoreCurrentCursorPosition\n"); +// ptr++; +// break; +// case 'r': +// // self.restoreCursorPosition(); +// SysLog("r case \n"); +// ptr++; +// break; +// case 'h': +// case 'l': +// // TODO: Handle 'h' (set mode) or 'l' (reset mode) codes +// SysLog("h or l case \n"); +// ptr++; // Skip 'h' or 'l' +// break; +// case '=': +// SysLog("ScreenMode attempt\n"); +// ptr++; +// break; +// default: +// if(!IsDigit(*ptr)) { +// SysLog("Unknown code: %c\n", *ptr); +// } +// ptr++; +// break; +// } +// } +// } + // Placeholder for the full ANSI text styling // if (ansi_code[m] <= 10) { diff --git a/src/Home/Telnet/TelnetNegotiation.ZC b/src/Home/Telnet/TelnetNegotiation.ZC index 8a034da5..88915b10 100644 --- a/src/Home/Telnet/TelnetNegotiation.ZC +++ b/src/Home/Telnet/TelnetNegotiation.ZC @@ -47,7 +47,7 @@ U0 SendTerminalType(I64 sock, U8 *terminal_type) { TCPSocketSendString(sock, response); } -U0 TelnetNegotiate(I64 sock, U8 *ptr) +U0 TelnetNegotiate(I64 sock, U8 ptr) { U8 negotiation_code = *(ptr + 1); U8 option_code = *(ptr + 2);