mirror of
https://github.com/Zeal-Operating-System/ZealOS.git
synced 2025-06-07 08:14:48 +00:00
969 lines
33 KiB
HolyC
Executable File
969 lines
33 KiB
HolyC
Executable File
// Telnet client for ZealOS by y4my4m
|
||
// Public Domain
|
||
|
||
Cd(__DIR__);;
|
||
|
||
#define TELNET_PORT 23
|
||
// #define BUF_SIZE 8192 // way too big?
|
||
#define BUF_SIZE 819200 // way too big?
|
||
#define INPUT_BUF_SIZE 32
|
||
#define TIMEOUT_DURATION 500000
|
||
|
||
#define NEGOTIATE 0xFF
|
||
|
||
#define ANSI_ESC 0x1B
|
||
#define ANSI_CSI 0x5B // [
|
||
|
||
#define MAX_ANSI_PARAMS 32
|
||
|
||
#include "TelnetClass"
|
||
#include "TelnetHelpers"
|
||
|
||
// If you're using a custom palette, the colors might not seem right.
|
||
CBGR24 original_palette[COLORS_NUM];
|
||
Bool dark_mode = TRUE; // since ZealOS is dark by default
|
||
Bool original_colors = FALSE;
|
||
Bool force_disconnect = FALSE;
|
||
|
||
I64 TelnetOpen(U8 *host, U16 port) {
|
||
I64 sock;
|
||
|
||
if (host == NULL) {
|
||
return -1;
|
||
}
|
||
DocPrint(term.doc, "$$GREEN$$Connecting to %s:%d.$$FG$$$$BG$$\n", host, port);
|
||
sock = TCPConnectionCreate(host, port);
|
||
if (sock <= 0) {
|
||
// PrintErr("Failed to connect to %s:%d\n", host, port);
|
||
PopUpOk("\n\n\tFailed to connect\n\n");
|
||
return sock;
|
||
}
|
||
|
||
// sock(CTCPSocket *)->timeout = 0;
|
||
// 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)
|
||
DocPrint(term.doc, "$$CM,-1,0$$");
|
||
break;
|
||
case 9: // HT (Horizontal Tab)
|
||
DocPrint(term.doc, "$$CM,8,0$$");
|
||
break;
|
||
case 10: // LF (Line Feed)
|
||
// DocPrint(term.doc, "\n");
|
||
break;
|
||
case 11: // VT (Vertical Tab)
|
||
SysLog("Vertical Tab\n");
|
||
break;
|
||
case 12: // FF (Form Feed)
|
||
// SysLog("form feed\n");
|
||
DocClear(term.doc);
|
||
// DocPrint(term.doc, "\f");
|
||
break;
|
||
case 13: // CR (Carriage Return)
|
||
DocPrint(term.doc, "\r");
|
||
// DocPrint(term.doc, "$$CM+LX+PRY,LE=0,RE=1$$");
|
||
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:
|
||
// some ch make Zeal crash or behave weird because they're commands?
|
||
// SysLog("CC %c happened\n", ch);
|
||
SysLog("CC 0x%X happened\n", ch);
|
||
break;
|
||
}
|
||
}
|
||
else {
|
||
if (ch == 127) {
|
||
SysLog("case 127");
|
||
}
|
||
if (ch == 0x24) {
|
||
DocPrint(term.doc, "$$$$");
|
||
}
|
||
if (ch >= 32 && ch < 256) // ZealOS's ASCII is up to 255
|
||
{
|
||
DocPrint(term.doc, "%c", ch);
|
||
}
|
||
else {
|
||
DocPrint(term.doc, "?"); // unrecognized character
|
||
}
|
||
}
|
||
}
|
||
|
||
U0 ANSIParse()
|
||
{
|
||
// Basic Telnet protocol parser
|
||
U8 *ptr = term.buffer;
|
||
while (ptr < term.buffer + term.buffer_len) {
|
||
// disable all SAUCE00 art signature? dsnt work
|
||
// if (StrNCompare(ptr, "\033SAUCE", 6) == 0)
|
||
// {
|
||
// SysLog("SAUCE found\n");
|
||
// term.buffer_len = ptr - term.buffer;
|
||
// }
|
||
// Telnet negotiation sequence
|
||
if (*ptr == NEGOTIATE) {
|
||
/*telnet negotiation seems proper...however i don't really see any BBS systems relying on this too much...
|
||
for instance, the screen size tends to be reported using the Curser Report and not Telnet's NAWS */
|
||
if (term.sock_ready) 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 (*ptr != '\0') {
|
||
if (IsDigit(*ptr)) {
|
||
ansi_code[ansi_param_count] = ansi_code[ansi_param_count] * 10 + (*ptr - '0');
|
||
ptr++;
|
||
}
|
||
else if (*ptr == ';') {
|
||
ansi_param_count++;
|
||
ptr++;
|
||
if (!IsDigit(*ptr) && *ptr != ';') {
|
||
break;
|
||
}
|
||
}
|
||
else {
|
||
// If the next character is not a digit or semicolon, break out
|
||
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
|
||
if (term.sock_ready) TCPSocketSend(term.sock, deviceStatusResponse, 4);
|
||
}
|
||
else if (ansi_code[0] == 6) {
|
||
// Respond with cursor position
|
||
SysLog("reported cursor position\n");
|
||
// TODO: position 24rows x 80cols is hardcoded, should actually report the real cursor position
|
||
// U8 cursorResponse[9] = "\x1B[24;80R";
|
||
U8 cursorResponse[9];
|
||
cursorResponse[0] = ANSI_ESC;
|
||
cursorResponse[1] = '['; // Start of CSI
|
||
cursorResponse[2] = '2'; // First digit of "24"
|
||
cursorResponse[3] = '4'; // Second digit of "24"
|
||
cursorResponse[4] = ';'; // Separator
|
||
cursorResponse[5] = '8'; // First digit of "80"
|
||
cursorResponse[6] = '0'; // Second digit of "80"
|
||
cursorResponse[7] = 'R'; // End of CPR
|
||
cursorResponse[8] = 0x00; // Null-terminator
|
||
if (term.sock_ready) TCPSocketSend(term.sock, cursorResponse, 9);
|
||
}
|
||
else if (ansi_code[0] == 255) {
|
||
// https://github.com/NuSkooler/enigma-bbs/blob/97cd0c3063b0c9f93a0fa4a44a85318ca81aef43/core/ansi_term.js#L140
|
||
SysLog("TODO: reported screensize?\n");
|
||
// SendWindowSize(term.sock, 80, 25);
|
||
}
|
||
ptr++;
|
||
break;
|
||
case 'c':
|
||
// Respond with device attributes
|
||
SysLog("reported device attributes\n");
|
||
// 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
|
||
if (term.sock_ready) TCPSocketSend(term.sock, deviceAttributesResponse, 7);
|
||
ptr++;
|
||
break;
|
||
case 'm':
|
||
// this is where colors are being set
|
||
// TODO: what happens in this case??? --> [0;1;34;44m
|
||
I64 m;
|
||
Bool isBright = FALSE;
|
||
for (m = 0; m <= ansi_param_count; m++) {
|
||
if (ansi_code[m] <= 10) {
|
||
switch (ansi_code[m]) {
|
||
case 0:
|
||
if (dark_mode)
|
||
DocPrint(term.doc, "$$BG,WHITE$$$$BLACK$$");
|
||
else
|
||
DocPrint(term.doc, "$$BG$$$$FG$$");
|
||
isBright = FALSE;
|
||
break; // reset
|
||
case 1: isBright = TRUE; break;
|
||
case 2: isBright = FALSE; break;
|
||
default: 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:
|
||
if (dark_mode) DocPrint(term.doc, "$$WHITE$$");
|
||
else DocPrint(term.doc, "$$BLACK$$");
|
||
break;
|
||
case 31:
|
||
DocPrint(term.doc, "$$RED$$");
|
||
break;
|
||
case 32:
|
||
DocPrint(term.doc, "$$GREEN$$");
|
||
break;
|
||
case 33:
|
||
DocPrint(term.doc, "$$YELLOW$$");
|
||
break;
|
||
case 34:
|
||
DocPrint(term.doc, "$$BLUE$$");
|
||
break;
|
||
case 35:
|
||
DocPrint(term.doc, "$$PURPLE$$");
|
||
break;
|
||
case 36:
|
||
DocPrint(term.doc, "$$CYAN$$");
|
||
break;
|
||
case 37:
|
||
if (dark_mode) DocPrint(term.doc, "$$BLACK$$");
|
||
else DocPrint(term.doc, "$$WHITE$$");
|
||
break;
|
||
case 39:
|
||
if (dark_mode) DocPrint(term.doc, "$$WHITE$$");
|
||
else DocPrint(term.doc, "$$FG$$");
|
||
break;
|
||
default: break;
|
||
}
|
||
}
|
||
else {
|
||
switch (ansi_code[m]) {
|
||
case 90:
|
||
case 30:
|
||
if (dark_mode) DocPrint(term.doc, "$$LTGRAY$$");
|
||
else DocPrint(term.doc, "$$DKGRAY$$");
|
||
break;
|
||
case 91:
|
||
case 31:
|
||
DocPrint(term.doc, "$$LTRED$$");
|
||
break;
|
||
case 92:
|
||
case 32:
|
||
DocPrint(term.doc, "$$LTGREEN$$");
|
||
break;
|
||
case 93:
|
||
case 33:
|
||
DocPrint(term.doc, "$$YELLOW$$");
|
||
break;
|
||
case 94:
|
||
case 34:
|
||
DocPrint(term.doc, "$$LTBLUE$$");
|
||
break;
|
||
case 95:
|
||
case 35:
|
||
DocPrint(term.doc, "$$LTPURPLE$$");
|
||
break;
|
||
case 96:
|
||
case 36:
|
||
DocPrint(term.doc, "$$LTCYAN$$");
|
||
break;
|
||
case 97:
|
||
case 37:
|
||
if (dark_mode) DocPrint(term.doc, "$$DKGRAY$$");
|
||
else DocPrint(term.doc, "$$LTGRAY$$");
|
||
break;
|
||
case 39:
|
||
if (dark_mode) DocPrint(term.doc, "$$WHITE$$");
|
||
else DocPrint(term.doc, "$$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:
|
||
if (dark_mode) DocPrint(term.doc, "$$BG,WHITE$$");
|
||
else DocPrint(term.doc, "$$BG,BLACK$$");
|
||
break;
|
||
case 41:
|
||
DocPrint(term.doc,"$$BG,RED$$");
|
||
break;
|
||
case 42:
|
||
DocPrint(term.doc,"$$BG,GREEN$$");
|
||
break;
|
||
case 43:
|
||
DocPrint(term.doc,"$$BG,YELLOW$$");
|
||
break;
|
||
case 44:
|
||
DocPrint(term.doc,"$$BG,BLUE$$");
|
||
break;
|
||
case 45:
|
||
DocPrint(term.doc,"$$BG,PURPLE$$");
|
||
break;
|
||
case 46:
|
||
DocPrint(term.doc,"$$BG,CYAN$$");
|
||
break;
|
||
case 47:
|
||
if (dark_mode) DocPrint(term.doc, "$$BG,BLACK$$");
|
||
else DocPrint(term.doc, "$$BG,WHITE$$");
|
||
break;
|
||
case 49:
|
||
if (dark_mode) DocPrint(term.doc, "$$BG,WHITE$$");
|
||
else DocPrint(term.doc, "$$BG$$");
|
||
break;
|
||
default: break;
|
||
}
|
||
}
|
||
else {
|
||
switch (ansi_code[m]) {
|
||
case 100:
|
||
case 40:
|
||
if (dark_mode) DocPrint(term.doc, "$$BG,LTGRAY$$");
|
||
else DocPrint(term.doc, "$$BG,DKGRAY$$");
|
||
break;
|
||
case 101:
|
||
case 41:
|
||
DocPrint(term.doc,"$$BG,LTRED$$");
|
||
break;
|
||
case 102:
|
||
case 42:
|
||
DocPrint(term.doc,"$$BG,LTGREEN$$");
|
||
break;
|
||
case 103:
|
||
case 43:
|
||
DocPrint(term.doc,"$$BG,YELLOW$$");
|
||
break;
|
||
case 104:
|
||
case 44:
|
||
DocPrint(term.doc,"$$BG,LTBLUE$$");
|
||
break;
|
||
case 105:
|
||
case 45:
|
||
DocPrint(term.doc,"$$BG,LTPURPLE$$");
|
||
break;
|
||
case 106:
|
||
case 46:
|
||
DocPrint(term.doc,"$$BG,LTCYAN$$");
|
||
break;
|
||
case 107:
|
||
case 47:
|
||
if (dark_mode) DocPrint(term.doc, "$$BG,DKGRAY$$");
|
||
else DocPrint(term.doc, "$$BG,LTGRAY$$");
|
||
break;
|
||
case 49:
|
||
if (dark_mode) DocPrint(term.doc, "$$BG,LTGRAY$$");
|
||
else DocPrint(term.doc, "$$BG$$");
|
||
break;
|
||
// reset
|
||
default: break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
ptr++;
|
||
break;
|
||
case 'A':
|
||
// Cursor Up
|
||
SysLog("Cursor Up\n");
|
||
// "$$CM+TY,0,-%d$$", ansi_code[0];
|
||
DocPrint(term.doc, "$$CM,0,-%d$$", ansi_code[0]);
|
||
ptr++;
|
||
break;
|
||
case 'B':
|
||
// Cursor Down
|
||
SysLog("Cursor Down\n");
|
||
DocPrint(term.doc, "$$CM,0,%d$$", ansi_code[0]);
|
||
ptr++;
|
||
break;
|
||
case 'C':
|
||
// Cursor Right
|
||
// SysLog("Cursor Right %d %d\n", ansi_param_count, ansi_code[0]);
|
||
// DocPrint(term.doc, "$$CM,%d,0$$", ansi_code[0]);
|
||
// seems to be less prone to bugs?
|
||
I64 cr;
|
||
for (cr=0; cr<ansi_code[0]; cr++){
|
||
DocPrint(term.doc, " ");
|
||
}
|
||
ptr++;
|
||
break;
|
||
case 'D':
|
||
// Cursor Left
|
||
SysLog("Cursor Left\n");
|
||
DocPrint(term.doc, "$$CM,-%d,0$$", ansi_code[0]);
|
||
ptr++;
|
||
break;
|
||
case 'E':
|
||
// Cursor Next Line
|
||
SysLog("Cursor Next Line\n");
|
||
DocPrint(term.doc, "\n");
|
||
ptr++;
|
||
break;
|
||
case 'F':
|
||
// Cursor Previous Line
|
||
SysLog("Cursor Previous Line\n");
|
||
DocPrint(term.doc, "$$CM+LY,0,-%d$$", ansi_code[0]);
|
||
ptr++;
|
||
break;
|
||
case 'G':
|
||
// Cursor Horizontal Absolute
|
||
SysLog("Cursor Horizontal Absolute\n");
|
||
DocPrint(term.doc, "$$CM,%d,0$$", 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];
|
||
|
||
// If we're already at the right position, no need to move
|
||
// if (row == term.current_row && col == term.current_col) {
|
||
// ptr++;
|
||
// break;
|
||
// }
|
||
|
||
// SysLog("H or f row:%d, col:%d, cnt:%d\n", row, col, ansi_param_count);
|
||
|
||
|
||
// Adjust the position based on the window size
|
||
if (row >= term.window_height) {
|
||
row = term.window_height - 1;
|
||
} else if (row < 1) {
|
||
row = 1;
|
||
}
|
||
|
||
if (col >= term.window_width) {
|
||
col = term.window_width - 1;
|
||
} else if (col < 1) {
|
||
col = 1;
|
||
}
|
||
|
||
// if (row == term.window_height) {
|
||
// term.current_row = row;
|
||
// term.current_col = col;
|
||
// DocPrint(term.doc, "$$CM+LX+TY,LE=%d,RE=%d$$", term.current_col-1, term.current_row-1);
|
||
// DocPrint(term.doc, "\n");
|
||
// ptr++;
|
||
// break;
|
||
// }
|
||
|
||
// If row or col are at their max value, reset the current position to 1
|
||
// if (row == term.window_height || col == term.window_width) {
|
||
// if (row == term.window_height) term.current_row = 1;
|
||
// if (col == term.window_width) term.current_col = 1;
|
||
// ptr++;
|
||
// break;
|
||
// }
|
||
|
||
term.current_row = row;
|
||
term.current_col = col;
|
||
|
||
DocPrint(term.doc, "$$CM+LX+TY,LE=%d,RE=%d$$", term.current_col-1, term.current_row-1);
|
||
//DocCursorPosSet(term.doc, col, 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);
|
||
// Clear the buffer
|
||
// term.buffer_len = 0;
|
||
// MemSet(term.buffer, 0, BUF_SIZE);
|
||
// Sleep(100);
|
||
}
|
||
ptr++;
|
||
break;
|
||
case 'K':
|
||
// TODO: I have no idea if this actually works
|
||
|
||
// if (ansi_param_count == 0 || ansi_code[0] == 0) {
|
||
// // Erase from cursor to end of line
|
||
// LineDeleteToEnd(term.doc->cur_entry, doc.term->cur_col);
|
||
// } else if (ansi_code[0] == 1) {
|
||
// // Erase from cursor to beginning of line
|
||
// LineDeleteToStart(term.doc->cur_entry, doc.term->cur_col);
|
||
// } else if (ansi_code[0] == 2) {
|
||
// // Erase entire line
|
||
// LineDeleteEntire(term.doc->cur_entry);
|
||
// }
|
||
ptr++;
|
||
break;
|
||
case 'L':
|
||
SysLog("L code\n");
|
||
// DocPrint(term.doc, "\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
|
||
DocPrint(term.doc, "$$CM,0,-1$$");
|
||
ptr++;
|
||
break;
|
||
case '?':
|
||
ptr++;
|
||
I64 code = 0;
|
||
|
||
while (IsDigit(*ptr)) {
|
||
code = code * 10 + (*ptr - '0');
|
||
ptr++;
|
||
}
|
||
switch (code) {
|
||
case 25:
|
||
// need to specify which doc?
|
||
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 't':
|
||
// for (m = 0; m <= ansi_param_count; m++) {
|
||
SysLog("ansi_code[%d]: %d\n", m, ansi_code[m]);
|
||
// }
|
||
if (ansi_param_count == 3) {
|
||
if (ansi_code[0] == 8) {
|
||
term.window_width = ansi_code[1];
|
||
term.window_height = ansi_code[2];
|
||
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);
|
||
}
|
||
}
|
||
|
||
ptr++;
|
||
break;
|
||
case '=':
|
||
SysLog("ScreenMode attempt\n");
|
||
ptr++;
|
||
break;
|
||
default:
|
||
// if(!IsDigit(*ptr)) {
|
||
if(*ptr > 32) {
|
||
SysLog("Unknown code: 0x%X\n", *ptr);
|
||
}
|
||
ptr++;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
else {
|
||
// Print the received character
|
||
HandleControlCodes(*ptr);
|
||
ptr++;
|
||
}
|
||
}
|
||
}
|
||
|
||
U0 TerminalTask() {
|
||
while (!term.sock_ready) {
|
||
Sleep(100); // Avoid busy waiting
|
||
}
|
||
|
||
while (term.sock_ready && !force_disconnect) {
|
||
receive_data:
|
||
term.buffer_len = TCPSocketReceive(term.sock, term.buffer, BUF_SIZE - 1);
|
||
if (term.buffer_len > 0) {
|
||
term.buffer[term.buffer_len] = '\0';
|
||
// parse the buffer
|
||
ANSIParse;
|
||
} else {
|
||
//SysLog("BUF_SIZE: %d\n", BUF_SIZE);
|
||
if (!term.sock_ready || force_disconnect)
|
||
DocPrint(term.doc, "Error: Connection closed by the remote host.\n");
|
||
else {
|
||
SysLog("goto received_data\n");
|
||
goto receive_data;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
U0 Telnet(U8 *host=NULL, U16 port=TELNET_PORT) {
|
||
CHostForm form;
|
||
term.window_width = 80;
|
||
term.window_height = 25;
|
||
term.doc = Fs->display_doc;
|
||
term.waiting_for_input = TRUE;
|
||
term.sock_ready = 0;
|
||
I64 art_path = "::/Home/Net/Programs/Telnet/Art/TelnetSplash.ans";
|
||
I64 message_code, arg1, arg2;
|
||
GrPaletteGet(original_palette);
|
||
AutoComplete(OFF);
|
||
|
||
SettingsPush;
|
||
MenuPush(
|
||
"Telnet {"
|
||
" Connect(,CH_CTRLN);"
|
||
" Exit(,CH_SHIFT_ESC);"
|
||
"}"
|
||
"Load {"
|
||
" ANSIArt(,CH_CTRLO);"
|
||
"}"
|
||
"Config {"
|
||
" ToggleDarkMode(,CH_CTRLD);"
|
||
" SetDarkColors(,CH_CTRLT);"
|
||
"}"
|
||
"About {"
|
||
" Info(,CH_CTRLL);"
|
||
"}"
|
||
);
|
||
|
||
StrCopy(Fs->task_title, "TELNET");
|
||
Fs->border_src = BDS_CONST;
|
||
Fs->border_attr = LTGREEN << 4 + DriveTextAttrGet(':') & 15;
|
||
if (dark_mode) Fs->text_attr = WHITE << 4 + BLACK;
|
||
else Fs->text_attr = BLACK << 4 + WHITE;
|
||
Fs->title_src = TTS_LOCKED_CONST;
|
||
DocClear(Fs->border_doc, TRUE);
|
||
|
||
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);
|
||
|
||
|
||
show_splash:
|
||
// SplashScreen
|
||
DocMax;
|
||
DocClear;
|
||
// probably should use word wrap?
|
||
DocPrint(, "$$WW,1$$");
|
||
DocCursor(OFF);
|
||
// Load the file into the buffer and get its size
|
||
term.buffer_len = ANSIArtLoad(art_path, term.buffer);
|
||
if (term.buffer_len > 0) {
|
||
term.buffer[term.buffer_len] = '\0';
|
||
// parse the buffer
|
||
ANSIParse;
|
||
// Free(term.buffer);
|
||
}
|
||
else {
|
||
Print("Error: Could not load splash screen.\n");
|
||
}
|
||
|
||
init_connection:
|
||
while (!term.waiting_for_input || host != NULL)
|
||
{
|
||
// if(term.sock != NULL) TCPSocketClose(term.sock);
|
||
// TODO: should probably kill the task if it already exists too?
|
||
|
||
// Spawn a task to receive data from the socket
|
||
term.task = Spawn(&TerminalTask, NULL, "Telnet");
|
||
|
||
|
||
term.sock = TelnetOpen(host, port);
|
||
if (term.sock <= 0) {
|
||
// return;
|
||
term.waiting_for_input = TRUE;
|
||
host = NULL;
|
||
goto show_splash;
|
||
}
|
||
term.sock_ready = 1; // Signal that the socket is ready
|
||
term.waiting_for_input = FALSE;
|
||
|
||
"$$BG,GREEN$$$$WHITE$$Connected$$FG$$$$BG$$\n";
|
||
Sleep(1000);
|
||
DocClear;
|
||
break;
|
||
}
|
||
|
||
I64 sc;
|
||
try
|
||
{
|
||
while (!force_disconnect) {
|
||
U8 key = KeyGet(&sc);
|
||
// fix the bug of holding Ctrl triggers all the CH_CTRL, needs to be handled like in Demo/Graphics/FontEd.ZC:104
|
||
switch (key)
|
||
{
|
||
case 0:
|
||
switch (sc.u8[0])
|
||
{
|
||
case SC_CURSOR_LEFT:
|
||
if (term.sock_ready) TCPSocketSendString(term.sock, "\x1B[D");
|
||
break;
|
||
|
||
case SC_CURSOR_RIGHT:
|
||
if (term.sock_ready) TCPSocketSendString(term.sock, "\x1B[C");
|
||
break;
|
||
|
||
case SC_CURSOR_UP:
|
||
if (term.sock_ready) TCPSocketSendString(term.sock, "\x1B[A");
|
||
break;
|
||
|
||
case SC_CURSOR_DOWN:
|
||
if (term.sock_ready) TCPSocketSendString(term.sock, "\x1B[B");
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
break;
|
||
case 9:
|
||
switch (sc.u8[0])
|
||
{
|
||
case SC_TAB:
|
||
if (term.sock_ready) TCPSocketSendString(term.sock, "\x09");
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
case 255:
|
||
switch (sc.u8[0])
|
||
{
|
||
case SC_PAGE_UP: if (term.sock_ready) TCPSocketSendString(term.sock, "\033[V"); break;
|
||
case SC_PAGE_DOWN:if (term.sock_ready) TCPSocketSendString(term.sock, "\033[U"); break;
|
||
case SC_HOME: if (term.sock_ready) TCPSocketSendString(term.sock, "\033[H"); break;
|
||
case SC_END: if (term.sock_ready) TCPSocketSendString(term.sock, "\033[K"); break;
|
||
case SC_DELETE: if (term.sock_ready) TCPSocketSendString(term.sock, "\x7F"); break;
|
||
case SC_F1: if (term.sock_ready) TCPSocketSendString(term.sock, "\033OP"); break;
|
||
case SC_F2: if (term.sock_ready) TCPSocketSendString(term.sock, "\033OQ"); break;
|
||
case SC_F3: if (term.sock_ready) TCPSocketSendString(term.sock, "\033OR"); break;
|
||
case SC_F4: if (term.sock_ready) TCPSocketSendString(term.sock, "\033OS"); break;
|
||
case SC_F5: if (term.sock_ready) TCPSocketSendString(term.sock, "\033Ot"); break;
|
||
case SC_F6: if (term.sock_ready) TCPSocketSendString(term.sock, "\033[17~"); break;
|
||
case SC_F7: if (term.sock_ready) TCPSocketSendString(term.sock, "\033[18~"); break;
|
||
case SC_F8: if (term.sock_ready) TCPSocketSendString(term.sock, "\033[19~"); break;
|
||
case SC_F9: if (term.sock_ready) TCPSocketSendString(term.sock, "\033[20~"); break;
|
||
case SC_F10: if (term.sock_ready) TCPSocketSendString(term.sock, "\033[21~"); break;
|
||
case SC_F11: if (term.sock_ready) TCPSocketSendString(term.sock, "\033[23~"); break;
|
||
case SC_F12: if (term.sock_ready) TCPSocketSendString(term.sock, "\033[24~"); break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
case CH_BACKSPACE:
|
||
if (term.sock_ready) TCPSocketSendString(term.sock, "\x08\x7F");
|
||
break;
|
||
case CH_ESC:
|
||
if (term.sock_ready) TCPSocketSendString(term.sock, "\x1B");
|
||
break;
|
||
case CH_SHIFT_ESC:
|
||
if (term.sock_ready)
|
||
{
|
||
if (term.sock_ready) TCPSocketClose(term.sock);
|
||
host = NULL;
|
||
term.sock_ready = 0;
|
||
term.waiting_for_input = TRUE;
|
||
"Telnet connection closed.\n";
|
||
Sleep(100);
|
||
goto show_splash;
|
||
}
|
||
else
|
||
force_disconnect = TRUE;
|
||
break;
|
||
// send buffer on enter
|
||
case '\n':
|
||
if (term.sock_ready) TCPSocketSendString(term.sock, "\r\n");
|
||
break;
|
||
case CH_CTRLN:
|
||
TelnetPrompt(&form);
|
||
host = form.host;
|
||
port = form.port;
|
||
DocClear;
|
||
term.waiting_for_input = FALSE;
|
||
goto init_connection;
|
||
break;
|
||
case CH_CTRLO:
|
||
art_path = ANSIArtBrowser;
|
||
if (art_path != NULL) {
|
||
term.sock_ready = 0;
|
||
term.waiting_for_input = TRUE;
|
||
// SysLog("%s\n", art_path);
|
||
goto show_splash;
|
||
}
|
||
else {
|
||
"Error: Could not load art.\n";
|
||
}
|
||
break;
|
||
case CH_CTRLS:
|
||
// Save as DD image.
|
||
StrCopy(term.doc->filename.name, "::/Home/Wallpapers/1024/Default.DD");
|
||
DocWrite(term.doc, TRUE);
|
||
break;
|
||
case CH_CTRLD:
|
||
dark_mode = !dark_mode;
|
||
if (dark_mode) Fs->text_attr = WHITE << 4 + BLACK;
|
||
else Fs->text_attr = BLACK << 4 + WHITE;
|
||
break;
|
||
case CH_CTRLT:
|
||
original_colors = !original_colors;
|
||
if (original_colors) PaletteSetStd(FALSE);
|
||
else {
|
||
GrPaletteSet(original_palette);
|
||
LFBFlush;
|
||
}
|
||
break;
|
||
case CH_CTRLU:
|
||
if(term.sock_ready) TCPSocketSendString(term.sock, "\x15");
|
||
break;
|
||
// case CH_CTRLP:
|
||
// DocFlagsToggle(doc, DOCF_PLAIN_TEXT);
|
||
case CH_CTRLL:
|
||
PopUpOk("\n\n Not all BBS will work perfectly (yet). \n\n You can load ANSi artwork with Ctrl+O","\n\n\n\t\tMade by y4my4m\n\n");
|
||
break;
|
||
default:
|
||
if (key >= ' ' && key <= '~') {
|
||
// Handle regular keys
|
||
U8 input_buf[2];
|
||
input_buf[0] = key;
|
||
input_buf[1] = '\0';
|
||
if (term.sock_ready) TCPSocketSend(term.sock, input_buf, 1);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
catch
|
||
PutExcept;
|
||
|
||
// ideally go back to the splashscreen and wait for another input?
|
||
// term.sock_ready = 0;
|
||
// term.waiting_for_input = TRUE;
|
||
// goto show_splash;
|
||
|
||
MenuPop;
|
||
SettingsPop;
|
||
GrPaletteSet(original_palette);
|
||
LFBFlush;
|
||
}
|
||
|
||
// dev server
|
||
// Telnet("localhost", 8888);
|
||
//Telnet;
|
||
|
||
|
||
/*
|
||
TODO:
|
||
- refactor ANSIParser's TCPSocketSends to send everything to a "TelnetSend" function
|
||
- TelnetSend will handle whether there's a connection or not
|
||
- This will make easier to transform this software into an ANSI viewer / Telnet client/ general purpose Term.
|
||
- There's a lot more to fix, but this is a good start.
|
||
*/
|