diff --git a/src/Home/Net/Programs/Telnet/Art/Examples/2m-invaders.ans b/src/Home/Net/Programs/Telnet/Art/Examples/2m-invaders.ans new file mode 100755 index 00000000..5896353a --- /dev/null +++ b/src/Home/Net/Programs/Telnet/Art/Examples/2m-invaders.ans @@ -0,0 +1,140 @@ +[?33l + + + +  +  +    +       +      +       +    +       +      +     +      +     +      +   +        +  +   +  ۰ +      +       +                             +          +          +            +                 +              +        +         +        +       +    +        +       +        +         +                 +            +                           ۱    ߰                              ۰  mܱ +            +          +     2m          ۱     +       ܱ  +                                                      +         +        +       +                                ۰ ۰ܰ +    +                                            ܰ        ܱ                          ۲                     +                      +                 +              +                +            +                +         +               +               +                 +                +          +            +          +           +        +    -    ۲   +   ޱ       + ޱ    +          +         +      ۲  +      ۲   +    ۲۲  +    ۱۲ +   ۱۲   +  ۰ ߱ +  ۲   +  ۲  + ۲ +  ۱߰ +  ۰  +   m ܰ  +      +   ߲   +      +    ۲ +   ۲ +    ߲ +     +      +         +     ܲ              ܰ        ۰       ܱ۰ +      ۰  +     ۲    +          +  ޲                    ޲ޱ                 ޲                                         ߲                                                                                                                                                         +    ߱   +        ߲  +    ߱   +        +        +  ߲߲  ޲    ݰ  +          +         + ܲ   +   ܲ  +    ߲ ۲߰   +    ߰ ߲߲          ߰                     ߲߱                     ܲ           m                             ߲         ߲            ߲                         ߲۲   +     ߲۱ +          ۱ +          ۱  + ۱۱ + ۱  + ۱ + ۱߱ +۱ + ۱ + ߲߲  +߲  + ۱۱۱۱   + + + + + + + + + + + + + +  matt-yee.tumblr.com : blocktronics.org : 2013 + + \ No newline at end of file diff --git a/src/Home/Net/Programs/Telnet/Art/Examples/AVG-DEMO.ANS b/src/Home/Net/Programs/Telnet/Art/Examples/AVG-DEMO.ANS new file mode 100755 index 00000000..32569fc7 Binary files /dev/null and b/src/Home/Net/Programs/Telnet/Art/Examples/AVG-DEMO.ANS differ diff --git a/src/Home/Net/Programs/Telnet/Art/Examples/DARKMAN-TEMPLAR.ANS b/src/Home/Net/Programs/Telnet/Art/Examples/DARKMAN-TEMPLAR.ANS new file mode 100755 index 00000000..77124ad1 --- /dev/null +++ b/src/Home/Net/Programs/Telnet/Art/Examples/DARKMAN-TEMPLAR.ANS @@ -0,0 +1 @@ +۲۲۲۲۱۲۱۱۲۱۲۰۲۲۲۲۰۰۲۲۲۲۲۱  ۲  ۲ܰ    ܰ    ߲ܰ   ۲     ۲    ܲ  ߲ܰ     ߲ ܱ        ۲߲ܱ۲ ܲ     ߲۲߲ܱ۲ ۲   ߲۲߲                ܱ          ۲       ۲             ۱            ܱ      ۰    ߲       ۱ ۲                  ߲    ۲߲                    ߲       ߲                        ܲ     ߱            ۱                                                                                            ݱ            ۱                      ۰           ۰      ۲    /)        /\                                     ۰                                                ۲    ۲۱     ۲۱   ݲ ۲      ۲۲     ܲ  ܲ ۱  ۲۲۲۲۲۱۲۱۲۱۲۱۱۲۲۱۲۱۱۱۲۲۲۲۲۲۲۲۲۲COMNTThe OG, Crusader and protector. SAUCE00Knight Templar Darkman Almighty USS Excelsior BBS 202207253PCIBM VGA \ No newline at end of file diff --git a/src/Home/Net/Programs/Telnet/Art/Examples/HRV_PrivateJester.ans b/src/Home/Net/Programs/Telnet/Art/Examples/HRV_PrivateJester.ans new file mode 100755 index 00000000..95f7c198 --- /dev/null +++ b/src/Home/Net/Programs/Telnet/Art/Examples/HRV_PrivateJester.ans @@ -0,0 +1,100 @@ + Ŀ +   +   +           +           +          +              +            +           +           +           HRV +   +  ۱   +   ۲  +    ۱   + ۱ ۲ ۲ ۲ ۲  + ۲ ۱ ۰   +  ۲ ۲ ۰ ۱ +  ۰ ۰ ۲۲ ۱۰ +  ۲ ۲۰ ۰ ۰ ۰  +   ۱ ۱ ۰ ۱  +   ۲ ۲ +    ۲ ۲ + ۲     + ۱۱  ۱۱  +  ۱  ۲߰۲   +    ۲۲   +     ۲۲    +     ۲۲   +     ۲۲۲ܱ   +     +        +    ۲  +    ܲ   +    ߲۲۲۲   +            +            +          +            +               +          +               +           +           +           +   ۰    +   ߰  +     + ۲۱  + ܱ۱  + ۲۲۱۱  + ۲۱۲۲۱ + ۲۲۱۲۱۲۱ + ۱۲۱۲۱۱ + ۱۱۲۲۱۲۱۲۲ + ۱۲۲۲۱۲۲۲۱ + ۱۲۲۱۲ + ۱۲۲۱۲ + ۲۱۱۱۲ +۳۱۲۱۲۱۱۲۲۳ +۳۲۱۰۱۲۲۳ +۱۲۱۰۲۱۲ +۲۲۱۲۲۲۲۲۲۱۳ +۱۲۲۱۱۲۱۲۱۲۲۱۲ +۱۲۱۱۲۲۲۲۱۲۲۱۱۲۱۲۲۲۱ + ۲۱۲۰۲۱۱۲۲۲۲۲۱۲۱۱۱ + ۱۲۲۲۰'Investigating was a joke,۲۰۲۳ +  ۱۱۱ but he wasn't ready to be the fool...'۱۲۲۲ + ۱۱۰۰۲۲۰۲۱۱۲۲۲۰ +  ۲۰۲۰۱۱۰۱۲۱۱  +  ۲۱  ۰۰۲۲ ۲۱ ۲۰۲۲۲۳ +  ۱۱۰۰۱۱۲۱۱۰۰۱۱۱ ۲ +   ۲۲۰۱۱۱۲۱۲۲۰۱ ۰۰۲ ۱ +   ۱۰۱۲۲ ۱۱ ۰ ۱۰۲۱  +  ۱  ۱۱۱۲۲ ۰ ۱۰  +  ۲  ۲۲۱      ۲  +     ۲۲۲  ۱ ۰۰  ۰   +      ۱ ۲۲    +      ۲  ۲      +     ۰      +                   +                      +                 +                    +                    +                          +                 +                   +                    +                  +                  +                 +            +          +     +  After long decades of admiring in the sidelines a VERY humble  +  ANSI submission to #blender thanks to everyone in #16c for the  +  inspiration...  +        harvest.MMXXII  + ͼSAUCE00Private Jester harvest.pt 20220427DPdIBM VGA \ No newline at end of file diff --git a/src/Home/Net/Programs/Telnet/Art/Examples/Keyboard.ANS b/src/Home/Net/Programs/Telnet/Art/Examples/Keyboard.ANS new file mode 100755 index 00000000..4071746f Binary files /dev/null and b/src/Home/Net/Programs/Telnet/Art/Examples/Keyboard.ANS differ diff --git a/src/Home/Net/Programs/Telnet/Art/Examples/N-BTFONT.ANS b/src/Home/Net/Programs/Telnet/Art/Examples/N-BTFONT.ANS new file mode 100755 index 00000000..5ef84460 --- /dev/null +++ b/src/Home/Net/Programs/Telnet/Art/Examples/N-BTFONT.ANS @@ -0,0 +1,134 @@ + + +  +nail    +    +    +     +߲  +  +   +   +   +   67 +  + + + fonts originally for use with + ANSI based slides created for +beyond tellerrand Dsseldorf 2016 +by nail/blocktronics + + + + + headline font + + +  + +       +       +  +  +   +  +       +       + ߲ ۲ ߲ ߲ ߲ ߲ +  + +  +  +       +          +       +     +      +      +         +     +    ߲   ߲ + + + +  +      +        +     +      +    +    +        +      +  ߲  ߲ ߲ ߲  ޲ +  + + + +  +   +      +     +     +     +     +     +  ۲  ߲ + + + + + + + plain text font + + + + + +  + +  + + + + + + + +   +  +  + + + + + + + +  +  + + + + + + + small text font + + + + + + + + +  + +   + + + + +SAUCE00beyond tellerrand fonts nail blocktronics 20180420!PIBM VGA \ No newline at end of file diff --git a/src/Home/Net/Programs/Telnet/Art/Examples/SHI-BAKEMONO.ANS b/src/Home/Net/Programs/Telnet/Art/Examples/SHI-BAKEMONO.ANS new file mode 100755 index 00000000..4baeedb6 --- /dev/null +++ b/src/Home/Net/Programs/Telnet/Art/Examples/SHI-BAKEMONO.ANS @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + +۲۲ +߲ +۰ +۰ +۲۰ ۲۰ +۲ ۰ ۲ + ۲۰ +۱  ۲ + ۲ + ۰ +۲۲۰ +۲۰ + +۲ + + + + + + + + + + +  +  + ߱  +ܰ ߱ +ܲ + +߲ +  +  +   ߲߲߱  +ݰ ݰ  ܲ߱ܲ  +ޱ   ܲ  + ݰܰ    +       +      +۱   +ܰ߰   +  ް߲  +  ߱ ܱ +  ް  ܰ  +    ݰ   +   +       +  ܱܲ +ܲܲ +  + ߲ ܲ۱ + ۱ܰ ۰  +  ߰߰  +  ߲  +  +ܰ + +  +  + ݰ +  ߰   +۱  ߰  +   +  ޲   ۲ +   + ߱ +۱    +۰ ܲ +۰ ܰ + ܲ +  +  ߲ ۲  +ܰ  + ޲ +  +߲ܲ +ܲ  + + +  +    +   ܱ +۰  ߰   +    +ܰ + + + + + + + + + + + + + + + + + +޲ +޲ + + + + + + + + + + +۰ +۰۰ + + + + + + +shi + + COMN + + +  ܲ  ۱ +  ۰ ۰    ۲ + ۰  ۰  ۲ ۱    ۰ + ܲ  ۰ܲ ۰       +  ۰    +۰ ۲۰    +۰ ۱    ۰ +۰ ۰   ܲ۰   +۰ ۰    ۰۰ + ߲۱ ۲ +SAUCE00死 死 死 20220328<PIBM VGA \ No newline at end of file diff --git a/src/Home/Net/Programs/Telnet/Art/Examples/US-DS22.ANS b/src/Home/Net/Programs/Telnet/Art/Examples/US-DS22.ANS new file mode 100755 index 00000000..c6d13f50 --- /dev/null +++ b/src/Home/Net/Programs/Telnet/Art/Examples/US-DS22.ANS @@ -0,0 +1,241 @@ +        +       +     ۱    +    ܲܲ۰   +   ܲ   +     ۲ +       +        +        +        +۲         +۲      +۱        +߰          +       +۲     +߱     +     +    +   +     +     ߲  +        +          +           +          +           +          +            +     ܲ         +    ܲ     ۲   +    ܲ  ۱   +     ߰     ߰   +        +          +            +         +        +     +    +         +ܱ     ۲      +          +           +           +    ޲      +              +             +          ۲ ܱ +             +         +           +         +  ۲    ߲  +  ۰       +      +           +        +        +      +       ߲  +       ܲ +           +            +            +              ߲ +              +                +          +            +           +           +            +            +          ޲ +    ۲     ޱ +        ޱ + ݲ       +          +             +               +    ۲       +  ޲      +      ߲      +            +      ܱ +   ܲ  +     ۱    +  ܱ   ߲ +        +     ߱      +     ܰ    +          +           + ܲ        +            +    ܲ      ۲  +      ۲   ۱ +          ۰ +        ޲۲ +                +                  +   ްݲ ݰ    ݱ  +           ۱ ޲ ݰ  +               ݰ   +            +    ܲ       +         ޲    +              ۲  +       ۲۲ ܱ޲  +  ߰        +    ܰ ܰ    +    ۲        + ޲  ۲ ܱ    +      ۲      +         ܲ    +           +             +   ޱ     +           ޲ +     ܲ    +        ܲ  +       ߲ +   ܱ     +۲ ۲۲ ߲  +  ۲  + ܲ  ܲ  +    ۱    +     ܲ    +      +   +      +      +     +       +         + ۲     + ݰ۰     ۲ +      +      ۲ +       + ߲     + ߲۰ ۲    +۲ ۲۲     +۲ ޱ   ۰  + ޱ    ۱۰ +      ۲۱ +     ۲ +    +ܲ ݰ  +      +       +      +  ۰  +   ۰۲۲  +    ۲    +    +     +    +        +  ޲۲    +      ۲   + ߰  ޲   +  ߰   ۰    + ۱ ۱  + ۱   ۲ + ۲ ݰܲ۲  + ܲ۲ ۰  +   ܰ  +    ۲  +  ۲    +     ۲   +      +    ۲  ܲ  +    ۲      +       +  ܰ ߲  +     +     + ߲     +߰ܰ    ۲ +  ߲      +      +۱       +۲    +       +     +    +   + ߲  ۲ +      +      +     +      +      +     +      +   +  ۰ +  +  + +  +  + ߲ ۲ +  +  +  +   +    +߲ + +߱ + +   +   +     +     ܲ          +          +         +       + ۲    ۲۲ +        ۲ +  ۲۲  +  ߲  ۲ +     +       +        +        +        ܰ +       +       +   ۲      +  ۲  +     +  + +Demosplash 2022 + + Tainted [Fire]AVG [Fire] +SAUCE00Demosplash 2022 Promo Tainted / AVG Fire 20221118PIBM VGA \ No newline at end of file diff --git a/src/Home/Net/Programs/Telnet/Art/Examples/rad2m-MAXTRONICS.ans b/src/Home/Net/Programs/Telnet/Art/Examples/rad2m-MAXTRONICS.ans new file mode 100755 index 00000000..84b902a4 Binary files /dev/null and b/src/Home/Net/Programs/Telnet/Art/Examples/rad2m-MAXTRONICS.ans differ diff --git a/src/Home/Net/Programs/Telnet/Art/Examples/rd-theylive.ans b/src/Home/Net/Programs/Telnet/Art/Examples/rd-theylive.ans new file mode 100755 index 00000000..7a6806bd --- /dev/null +++ b/src/Home/Net/Programs/Telnet/Art/Examples/rd-theylive.ans @@ -0,0 +1,26 @@ + +  +  +  +   +    +     +         +      ܰ   + ߰       +                +         ܰ   +         ۰  +              +         +      +     +   + + + +          ۲                                         ۱۲۲     ۲                ߲                                                                          ܱܰ    ܲܲ߱      ߰߰  ۱۲  ߲   ۲۲   ۲   ۲۲     ۲۱    ۲     +        ܲ ۰ +     ۰            ۰ +       ۰     ݰ ۰޲۲      ܰ ۰ޱ     ۰         ܰ ۰ܰ         ۰ܲ     ߲     ۰        ߲ ۰            ۲ ۰ܲܲ                  ۰ ۲     ܰ   ۰         ۰           ۰۲            ۰۲           ۲rd!   ۲ ۲           ܲ۲       ܲ۲ ܲ  +SAUCE00they live realm dweller blocktronics 20180927l4PBIBM VGA \ No newline at end of file diff --git a/src/Home/Net/Programs/Telnet/Art/TelnetSplash.ans b/src/Home/Net/Programs/Telnet/Art/TelnetSplash.ans new file mode 100755 index 00000000..93dcd3fa --- /dev/null +++ b/src/Home/Net/Programs/Telnet/Art/TelnetSplash.ans @@ -0,0 +1,25 @@ + + +  +  ZealOS Telnet client - v1.0 ۲ +  + + ..... .. s + .H8888888h. ~-. x .d88" :8 + 888888888888x `> 5888R u. u. .88 + X~ `?888888hx~ .u '888R x@88k u@88c. .u :888ooo + ' x8.^"*88*" ud8888. 888R ^"8888""8888" ud8888. -*8888888 + `-:- X8888x :888'8888. 888R 8888 888R :888'8888. 8888 + 488888> d888 '88%" 888R 8888 888R d888 '88%" 8888 + .. `"88* 8888.+" 888R 8888 888R 8888.+" 8888 + x88888nX" . 8888L 888R 8888 888R 8888L .8888Lu= + !"*8888888n.. : '8888c. .+ .888B . "*88*" 8888" '8888c. .+ ^%888* + ' "*88888888* "88888% ^*888% "" 'Y" "88888% 'Y" + ^"***"` "YP' "% "YP' + + +  + Alt+n to Connect۲ +  + + @y4my4m \ No newline at end of file diff --git a/src/Home/Net/Programs/Telnet/Extra/MenuIcon.DD b/src/Home/Net/Programs/Telnet/Extra/MenuIcon.DD new file mode 100755 index 00000000..fe9b6103 Binary files /dev/null and b/src/Home/Net/Programs/Telnet/Extra/MenuIcon.DD differ diff --git a/src/Home/Net/Programs/Telnet/Extra/TelnetGr.ZC b/src/Home/Net/Programs/Telnet/Extra/TelnetGr.ZC new file mode 100755 index 00000000..c77ef30f --- /dev/null +++ b/src/Home/Net/Programs/Telnet/Extra/TelnetGr.ZC @@ -0,0 +1,909 @@ +// 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" + +Bool force_disconnect = FALSE; +Bool redraw_needed = FALSE; + +class ScreenCell { + U8 ch; + I64 color; + I64 bgcolor; +} +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 current_bgcolor; + 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)d + 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) { + // DocClear(term.doc); + + // DCFill(DCAlias,BLACK); + // term.cursor_x = 0; + 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); + // DCFill(DCAlias,BLACK); + 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.screen[term.cursor_y][term.cursor_x].bgcolor = term.current_bgcolor; + 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 + } + } +} + + +I64 LoadSplashScreen(U8 *filename) { + CFile *file = FOpen(filename, "rb"); + if (!file) { + PrintErr("Failed to open file"); + return -1; + } + + // Ensure that the file size isn't larger than the buffer + if (file->de.size > BUF_SIZE) { + PrintErr("File is too large for the buffer."); + FClose(file); + return -1; + } + + // Calculate the number of full blocks to read based on file size and block size + I64 full_blocks = file->de.size / BLK_SIZE; + I64 remaining_bytes = file->de.size % BLK_SIZE; + + SysLog("File size: %d, Number of full blocks: %d, Remaining bytes: %d\n", file->de.size, full_blocks, remaining_bytes); + + // Read the full blocks into the buffer + I64 i, blocks_read = 0; + for (i = 0; i < full_blocks; i++) { + blocks_read += FBlkRead(file, term.buffer + i * BLK_SIZE, i, 1); + } + + // Check if there are any remaining bytes in the last block + if (remaining_bytes != 0) { + // Read the remaining bytes + U8 temp_buffer[BLK_SIZE]; + if (FBlkRead(file, temp_buffer, full_blocks, 1)) { + blocks_read++; + MemCopy(term.buffer + full_blocks * BLK_SIZE, temp_buffer, remaining_bytes); + } + } + + FClose(file); + + if (blocks_read != (full_blocks + (remaining_bytes != 0))) { + PrintErr("Failed to read all the blocks"); + return -1; + } + + return file->de.size; // Return the number of bytes read +} + +U0 TerminalDrawIt(CTask *task, CDC *dc) +{ + // Clear the document + // DocClear(term.doc); + // Sleep(100); + DCFill; + + 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; + dc->bkcolor = term.screen[row][col].bgcolor; + // 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 curso + // Note: this draws the cursor as a white rectangle. You may want to customize this. + // dc->color = WHITE; + // GrRect(term.dc, term.cursor_x * 8, term.cursor_y * 8, + // (term.cursor_x + 1) * 8 - 1, (term.cursor_y + 1) * 8 - 1); +} + +U0 ANSIParse() +{ + // 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': + // Set graphics mode (colors) + 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_bgcolor = BLACK; // should be BG FG for full reset + 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_bgcolor = BLACK; + break; + case 41: + term.current_bgcolor = RED; + break; + case 42: + term.current_bgcolor = GREEN; + break; + case 43: + term.current_bgcolor = YELLOW; + break; + case 44: + term.current_bgcolor = BLUE; + break; + case 45: + term.current_bgcolor = PURPLE; + break; + case 46: + term.current_bgcolor = CYAN; + break; + case 47: + term.current_bgcolor = WHITE; + break; + case 49: + // reset + term.current_bgcolor = BLACK; + break; + default: break; + } + } + else { + switch (ansi_code[m]) { + case 100: + case 40: + term.current_bgcolor = DKGRAY; + break; + case 101: + case 41: + term.current_bgcolor = LTRED; + break; + case 102: + case 42: + term.current_bgcolor = LTGREEN; + break; + case 103: + case 43: + term.current_bgcolor = YELLOW; + break; + case 104: + case 44: + term.current_bgcolor = LTBLUE; + break; + case 105: + case 45: + term.current_bgcolor = LTPURPLE; + break; + case 106: + case 46: + term.current_bgcolor = LTCYAN; + break; + case 107: + case 47: + term.current_bgcolor = LTGRAY; + break; + case 49: + // reset + term.current_bgcolor = 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 - 1; + if (col > term.window_width) + col = term.window_width - 1; + // "$$CM,0,0$$"; + term.cursor_x = col-1; + term.cursor_y = 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(term.doc); + DCFill(term.dc,BLACK); + // term.cursor_x = 0; + // term.cursor_y = 0; + // redraw_needed = TRUE; + } + 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); + + // DCFill(,RED); + // term.cursor_x = 0; + // term.cursor_y = 0; + redraw_needed = FALSE; + } +} + +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'; + ANSIParse; + } 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.dc = DCNew(term.window_width, term.window_height); + term.doc = Fs->put_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; + + // SplashScreen + term.buffer_len = LoadSplashScreen("Art/TelnetSplash.ans"); + if (term.buffer_len > 0) { + term.buffer[term.buffer_len] = '\0'; + // parse the buffer + ANSIParse; + } + else { + "Error: Could not load splash screen.\n"; + } + + // PopUp if no host is specified + if (host == NULL) { + try + { + while (host == NULL) { + CHostForm form; + TelnetPrompt(&form); + host = form.host; + port = form.port; + DocClear; + break; + } + } + catch + PutExcept; + } + + "$$BG,GREEN$$$$WHITE$$Connected$$FG$$$$BG$$\n"; + Sleep(1000); + DocClear; + + 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/Net/Programs/Telnet/Extra/TelnetList.DD b/src/Home/Net/Programs/Telnet/Extra/TelnetList.DD new file mode 100755 index 00000000..ef2a8952 --- /dev/null +++ b/src/Home/Net/Programs/Telnet/Extra/TelnetList.DD @@ -0,0 +1,49 @@ +// Telnet("mbrserver.com"); +// Telnet("freechess.org"); +// Telnet("dura-bbs.net", 6359); +// Telnet("darkrealms.ca"); + +// good to test refresh, line feed, etc +// Telnet("20forbeers.com", 1337); + +// view public IP +// Telnet("telnetmyip.com") + +// time +// Telnet("india.colorado.edu", 13); + +// telehack +// Telnet("telehack.com"); + +// star trek game +// Telnet("mtrek.com", 1701); +// Telnet("xmltrek.com", 1701); + +// Telnet("bbs.archaicbinary.net"); // Archaic Binary +// Telnet("ateraan.com", 4002); // New Worlds - Ateraan +// Telnet("avalon-rpg.com"); // Avalon: The Legend Lives +// Telnet("aardmud.org", 4000); // Aardwolf MUD +// Telnet("TextMMOde.com"); // Sands of Time / Deep Space MMO +// Telnet("legendofthereddragon.ca"); // Legend of the Red Dragon (Canada) +// Telnet("lord.stabs.org"); // Legend of the Red Dragon +// Telnet("thehatshop.mudhosting.net", 3000); // Hallowed Halls +// Telnet("batmud.bat.org"); // BatMUD +// Telnet("forgottenkingdoms.org", 4000); // Forgotten Kingdoms +// Telnet("igormud.org", 1701);// Igor MUD +// Telnet("achaea.com"); // Achaea, Dreams of Divine Lands +// Telnet("gcomm.com"); // Galacticomm BBS +// Telnet("1984.ws"); // 1984 + +// Telnet("telnet.holdfastbbs.ca", 2323); + +// Telnet("bbs.fozztexx.com", 23); +// Telnet("bbs.bottomlessabyss.net", 2023); // really modern + +// Telnet("bbs.stormbbs.com"); +// Telnet("conchaos.synchro.net"); +// Telnet("excelsiorbbs.ddns.net",2000); +// Telnet("daemonsgate.com"); +// Telnet("bbs.inktwo.com"); +// Telnet("erb.pw/the-bbs"); + +// bbs.alsgeeklab.com // full of good stuff (irc/gopher browser, etc..) \ No newline at end of file diff --git a/src/Home/Net/Programs/Telnet/Telnet.ZC b/src/Home/Net/Programs/Telnet/Telnet.ZC new file mode 100755 index 00000000..8b7d7a53 --- /dev/null +++ b/src/Home/Net/Programs/Telnet/Telnet.ZC @@ -0,0 +1,491 @@ +// 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" +#include "TelnetANSI" + +// 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 TermBottom() +{ + Bool unlock; + CDoc *doc = term.doc; + + unlock = DocLock(doc); + doc->cur_entry = doc; + doc->cur_col = 0; + if (unlock) + DocUnlock(doc); +} + +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++; // Move past '[' + + U8 seqBuffer[64]; + MemSet(seqBuffer, 0, sizeof(seqBuffer)); // Set all bytes in seqBuffer to 0 + + U8 *seqPtr = seqBuffer; + + while (*ptr && !IsAlpha(*ptr) && (seqPtr - seqBuffer < sizeof(seqBuffer) - 1)) { + *seqPtr++ = *ptr++; + } + *seqPtr++ = *ptr++; // Append the final letter + *seqPtr = '\0'; // Null-terminate + + HandleANSISequence(seqBuffer); + } + } + 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); + + DocMax; + DocCursor(OFF); + + SettingsPush; + MenuPush( + "Telnet {" + " Connect(,SCF_ALT+'n');" + " Exit(,CH_SHIFT_ESC);" + "}" + "Load {" + " ANSIArt(,SCF_ALT);" + "}" + "Config {" + " ToggleDarkMode(,SCF_ALT);" + " SetDarkColors(,SCF_ALT);" + "}" + "About {" + " Info(,SCF_ALT);" + "}" + ); + + 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); + + // this flag will make the term auto-size to the content (ish) + // term.doc->flags |= DOCEG_DONT_EDIT; + +show_splash: + // SplashScreen + Fs->border_attr = WHITE << 4 + LTRED; + DocClear; + // probably should use word wrap? dunno if it actually changes anything in this context... + DocPrint(, "$$WW,1$$"); + // 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; + TermBottom; + // Free(term.buffer); + } + else { + DocClear; + 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; + } + Fs->border_attr = WHITE << 4 + GREEN; + 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); + + // ALT + if (sc & SCF_ALT && !(sc & SCF_CTRL)) + { + switch (key) + { + // case 0: + // switch (sc.u8[0]) + // { + // } + case 'n': + TelnetPrompt(&form); + host = form.host; + port = form.port; + DocClear; + term.waiting_for_input = FALSE; + goto init_connection; + break; + case 'o': + 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 's': + // Save as DD image. + StrCopy(term.doc->filename.name, "::/Home/Wallpapers/1024/Default.DD"); + DocWrite(term.doc, TRUE); + break; + case 'd': + dark_mode = !dark_mode; + if (dark_mode) Fs->text_attr = WHITE << 4 + BLACK; + else Fs->text_attr = BLACK << 4 + WHITE; + break; + case 't': + original_colors = !original_colors; + if (original_colors) PaletteSetStd(FALSE); + else { + GrPaletteSet(original_palette); + LFBFlush; + } + break; + case 'l': + 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; + } + } + else { // 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; + 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; + } + break; + case 9: + switch (sc.u8[0]) + { + case SC_TAB: + if (term.sock_ready) TCPSocketSendString(term.sock, "\x09"); + break; + default: + break; + } + case CH_BACKSPACE: + if (term.sock_ready) TCPSocketSendString(term.sock, "\x08\x7F"); + break; + case CH_SHIFT_ESC: + if (term.sock_ready) + { + term.waiting_for_input = TRUE; + term.sock_ready = 0; + host = NULL; + TCPSocketClose(term.sock); + DocClear; + "Telnet connection closed.\n"; + Sleep(100); + goto show_splash; + } + else + force_disconnect = TRUE; + break; + case CH_ESC: + if (term.sock_ready) TCPSocketSendString(term.sock, "\x1B"); + break; + // send buffer on enter + case '\n': + if (term.sock_ready) TCPSocketSendString(term.sock, "\r\n"); + break; + case CH_CTRLU: + if(term.sock_ready) TCPSocketSendString(term.sock, "\x15"); + 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); + +// ZealOS's official BBS zealos +// Telnet("bbs.zealos.net", 4001); + +Telnet; + + +/* +TODO: +- make all keys send their respective signals (like Ctrl+Key, etc) +- 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. +*/ diff --git a/src/Home/Net/Programs/Telnet/TelnetANSI.ZC b/src/Home/Net/Programs/Telnet/TelnetANSI.ZC new file mode 100644 index 00000000..c62813bd --- /dev/null +++ b/src/Home/Net/Programs/Telnet/TelnetANSI.ZC @@ -0,0 +1,537 @@ +U0 HandleANSISequence(U8 *sequence) +{ + I64 ansi_code[MAX_ANSI_PARAMS]; + MemSet(ansi_code, 0, sizeof(ansi_code)); // Initialize all elements to 0 + I64 ansi_param_count = 0; + + U8 *ptr = sequence; + 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++; + if (ansi_param_count >= MAX_ANSI_PARAMS) { + break; // Avoid going out of bounds + } + ptr++; + } else { + // This should be a letter indicating the end of the sequence + break; + } + } + + // Now ptr should be at the final letter of the sequence + U8 actionCode = *ptr; + // Handle specific ANSI escape sequences + switch (actionCode) { + 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); + } + 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); + + 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; + // if (ansi_param_count >2) + // { + // SysLog("param: %d \n", ansi_param_count); + // SysLog("%d | %d\n", m, ansi_code[m]); + // } + 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$$"); + DocPrint(term.doc, "$$BG,WHITE$$$$BLACK$$"); + 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$$"); + DocPrint(term.doc, "$$WHITE$$"); + 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$$"); + DocPrint(term.doc, "$$BLACK$$"); + break; + case 39: + // if (dark_mode) DocPrint(term.doc, "$$WHITE$$"); + // else DocPrint(term.doc, "$$FG$$"); + DocPrint(term.doc, "$$WHITE$$"); + break; + default: break; + } + } + else { + switch (ansi_code[m]) { + case 90: + case 30: + // if (dark_mode) DocPrint(term.doc, "$$LTGRAY$$"); + // else DocPrint(term.doc, "$$DKGRAY$$"); + DocPrint(term.doc, "$$LTGRAY$$"); + 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$$"); + DocPrint(term.doc, "$$DKGRAY$$"); + break; + case 39: + // if (dark_mode) DocPrint(term.doc, "$$WHITE$$"); + // else DocPrint(term.doc, "$$FG$$"); + DocPrint(term.doc, "$$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: + // if (dark_mode) DocPrint(term.doc, "$$BG,WHITE$$"); + // else DocPrint(term.doc, "$$BG,BLACK$$"); + DocPrint(term.doc, "$$BG,WHITE$$"); + 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$$"); + DocPrint(term.doc, "$$BG,BLACK$$"); + break; + case 49: + // if (dark_mode) DocPrint(term.doc, "$$BG,WHITE$$"); + // else DocPrint(term.doc, "$$BG$$"); + DocPrint(term.doc, "$$BG,WHITE$$"); + 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$$"); + DocPrint(term.doc, "$$BG,LTGRAY$$"); + 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$$"); + DocPrint(term.doc, "$$BG,DKGRAY$$"); + break; + case 49: + // if (dark_mode) DocPrint(term.doc, "$$BG,LTGRAY$$"); + // else DocPrint(term.doc, "$$BG$$"); + DocPrint(term.doc, "$$BG,LTGRAY$$"); + break; + // reset + default: break; + } + } + } + } + + 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]); + + break; + case 'B': + // Cursor Down + SysLog("Cursor Down\n"); + DocPrint(term.doc, "$$CM,0,%d$$", ansi_code[0]); + + 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= 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"); + // + // 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; + // + // 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); + + 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 + // TODO: dont think this is working + // DocDelToNum(Fs->display_doc, Fs->display_doc->cur_entry->line_num); + } else if (ansi_code[0] == 1) { + // Erase from cursor to beginning of display + // TODO: dont think this is working + // 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); + } + + break; + case 'K': + // TODO: I have no idea if this actually works + SysLog("K code, %d %d\n", ansi_param_count, ansi_code[0]); + 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); + // DocDelToEntry(term.doc, term.doc->cur_entry, TRUE); + + // DocDelToNum(term.doc, 80); + // DocRecalc(term.doc); + } 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); + } + + break; + case 'L': + SysLog("L code\n"); + // DocPrint(term.doc, "\n"); + + break; + case 'S': + // TODO: Scroll Up + SysLog("S Scroll Up"); + + break; + case 'T': + // TODO: Scroll Down + SysLog("T Scroll Down"); + + break; + case 'M': + SysLog("Case M\n"); + // TODO: is this correct? cursor should go one line up + DocPrint(term.doc, "$$CM,0,-1$$"); + + break; + case '?': + + I64 code = 0; + + while (IsDigit(*ptr)) { + code = code * 10 + (*ptr - '0'); + + } + switch (code) { + case 25: + // need to specify which doc? + // if (*ptr == 'l') DocCursor(OFF); // Hide cursor + // if (*ptr == 'h') DocCursor(ON); // Show cursor + // 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 + // 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 + // Move past 'l' or 'h' + break; + default: + + break; + } + break; + case 's': + SysLog("SaveCurrentCursorPosition\n"); + + break; + case 'u': + SysLog("RestoreCurrentCursorPosition\n"); + + break; + case 'r': + // self.restoreCursorPosition(); + SysLog("r case \n"); + + break; + case 'h': + case 'l': + // TODO: Handle 'h' (set mode) or 'l' (reset mode) codes + SysLog("h or l case \n"); + // 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); + } + } + + + break; + case '=': + SysLog("ScreenMode attempt\n"); + + break; + default: + // if(!IsDigit(*ptr)) { + if(*ptr > 0x32) { + SysLog("Unknown code: 0x%X\n", *ptr); + } + + break; + } + +} diff --git a/src/Home/Net/Programs/Telnet/TelnetClass.ZC b/src/Home/Net/Programs/Telnet/TelnetClass.ZC new file mode 100755 index 00000000..067ea5e5 --- /dev/null +++ b/src/Home/Net/Programs/Telnet/TelnetClass.ZC @@ -0,0 +1,17 @@ +class Terminal { + I64 sock; + Bool sock_ready; + Bool waiting_for_input; + I64 window_width; + I64 window_height; + CDoc *doc; + CTask *task; + + I64 current_color; + I64 current_bgcolor; + I64 current_row; + I64 current_col; + + U8 buffer[BUF_SIZE]; + I64 buffer_len; +} term; diff --git a/src/Home/Net/Programs/Telnet/TelnetHelpers.ZC b/src/Home/Net/Programs/Telnet/TelnetHelpers.ZC new file mode 100755 index 00000000..937b2a50 --- /dev/null +++ b/src/Home/Net/Programs/Telnet/TelnetHelpers.ZC @@ -0,0 +1,259 @@ +Cd(__DIR__); +#include "TelnetClass" +#include "TelnetNegotiation" + +U8 IsDigit(U8 ch) { + return '0' <= ch <= '9'; +} +U8 IsAlpha(U8 ch) { + return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z'); +} +class CHostForm { + U8 host[256] format "\n\n \t$$DA-P,LEN=255,A=\"Host:%s\"$$\t\n"; + U16 port format "\t$$DA,LEN=255,A=\"Port:%d\"$$\t\n \n\n"; +}; + +U0 TelnetPrompt(CHostForm *form) { + form->host[0] = 0; + form->port = TELNET_PORT; + PopUpForm(form); +} + +I64 ANSIArtLoad(U8 *filename, U8 *buffer) { + CFile *file = FOpen(filename, "rb"); + if (!file) { + PrintErr("Failed to open file"); + return -1; + } + + // Allocate memory for the buffer based on file size + *buffer = MAlloc(file->de.size); + if (!(*buffer)) { + PrintErr("Failed to allocate memory for the buffer"); + FClose(file); + return -1; + } + + // Calculate the number of full blocks to read based on file size and block size + I64 full_blocks = file->de.size / BLK_SIZE; + I64 remaining_bytes = file->de.size % BLK_SIZE; + + // SysLog("File size: %d, Number of full blocks: %d, Remaining bytes: %d\n", file->de.size, full_blocks, remaining_bytes); + + // Read the full blocks into the buffer + I64 i, blocks_read = 0; + for (i = 0; i < full_blocks; i++) { + blocks_read += FBlkRead(file, buffer + i * BLK_SIZE, i, 1); + } + + // Check if there are any remaining bytes in the last block + if (remaining_bytes != 0) { + // Read the remaining bytes + U8 temp_buffer[BLK_SIZE]; + if (FBlkRead(file, temp_buffer, full_blocks, 1)) { + blocks_read++; + MemCopy(buffer + full_blocks * BLK_SIZE, temp_buffer, remaining_bytes); + } + } + + I64 file_size = file->de.size; + FClose(file); + + if (blocks_read != (full_blocks + (remaining_bytes != 0))) { + PrintErr("Failed to read all the blocks"); + return -1; + } + + return file_size; // Return the number of bytes read +} + +public U8 *ANSIArtBrowser() +{ + // would be nice to keep last opened file path in memory... + // U8 *selectedFile = PopUpPickFile(CatPrint(__DIR__,"/Art")) + // FIXME: dont hardcode path + U8 *selectedFile = PopUpPickFile("::/Home/Net/Programs/Telnet/Art"); + if (selectedFile) { + if (StrCompare(selectedFile, "")) { + // SysLog(selectedFile); + StrCopy(Fs->task_title, selectedFile); + return selectedFile; + } + } + // Handle the case where no file was selected, if necessary. + return NULL; +} + +U0 DocCursorPosSet(CDoc *doc, I64 x, I64 y) +{ + Bool unlock; + + unlock = DocLock(doc); + if (doc->doc_signature == DOC_SIGNATURE_VAL) + { + doc->x = x; + doc->y = y; + DocRecalc(doc, RECALCt_FIND_CURSOR); + if (unlock) + DocUnlock(doc); + } +} + +U0 DocRelCursorPosSet(CDoc *doc, I64 dx, I64 dy) { + Bool unlock; + + if (!doc && !(doc = DocPut)) { + return; + } + + unlock = DocLock(doc); + + doc->x += dx; + doc->y += dy; + + DocRecalc(doc, RECALCt_FIND_CURSOR); + + if (unlock) + DocUnlock(doc); +} + +// function to increase/decrease col or row +// U0 SetPos(I64 col, I64 row, Bool relativeCol=TRUE) { +// if (col < 0) { +// if (relativeCol) +// term.current_col += col; +// else +// term.current_col = col; +// if (term.current_col > term.window_width) +// term.current_col = 1; +// } +// if (row < 0) { +// if (relativeCol) +// term.current_row += row; +// else +// term.current_row = row; +// if (term.current_row > term.window_height) +// term.current_row = 1; +// } +// } + +// public I64 ANSIArtBrowser() +// { +// CDirEntry *tmpde1 = NULL, *tmpde2; +// CDoc *doc = DocNew; +// I64 res = 0; +// I64 res2 = 0; + +// DocPrint(doc, "$$LTBLUE$$\n\n"); + +// tmpde1 = FilesFind("Art/", 1); + +// if (tmpde1) +// { +// while (tmpde1) +// { +// tmpde2 = tmpde1->next; +// res++; + +// DocPrint(doc, " $$MU,\"%d.%s\",LE=0x%X$$\n", res, tmpde1->name, tmpde1->name); +// DirEntryDel(tmpde1); + +// tmpde1 = tmpde2; +// } +// } + +// DocPrint(doc, "\n\n$$BT+CX,\"CANCEL\",LE=0$$\n\n"); +// res2 = PopUpMenu(doc); +// DocDel(doc); + + +// return MStrPrint("%Q%Q", "Art/", res2); +// } + +// U0 SetTermColor(CBGR24 *color) { +// DocPrint(term.doc, "$$TC,0x%06X$$\n", color->bgr); +// } + +// U0 SetTermBGColor(CBGR24 *color) { +// DocPrint(term.doc, "$$BC,0x%06X$$\n", color->bgr); +// } + +// U0 SetTermPalette(CBGR24 *palette) { +// I64 i; +// for (i = 0; i < COLORS_NUM; i++) { +// DocPrint(term.doc, "$$PC,%d,%d,%d,0x%06X$$\n", i, palette[i].r, palette[i].g, palette[i].bgr); +// } +// } + +// U0 SetTermDarkMode(Bool dark) { +// if (dark) { +// DocPrint(term.doc, "$$DM,1$$\n"); +// } +// else { +// DocPrint(term.doc, "$$DM,0$$\n"); +// } +// } + +// U0 SetTermOriginalColors(Bool original) { +// if (original) { +// DocPrint(term.doc, "$$OC,1$$\n"); +// } +// else { +// DocPrint(term.doc, "$$OC,0$$\n"); +// } +// } + +// function to clear screen and move cursor to 0,0 +// U0 Reset() { +// DocPrint(term.doc, "$$CM+LX+PRY,LE=0,RE=0$$"); +// DocClear(term.doc); +// } + +// // function to move cursor to 0,0 +// U0 Home() { +// DocPrint(term.doc, "$$CM+LX+PRY,LE=0,RE=0$$"); +// } + +// // function to move cursor to 0,0 and clear screen +// U0 ClearScreen() { +// DocPrint(term.doc, "$$CM+LX+PRY,LE=0,RE=0$$"); +// DocClear(term.doc); +// } + +// // function to move cursor to 0,0 and clear line +// U0 ClearLine() { +// DocPrint(term.doc, "$$CM+LX+PRY,LE=0,RE=1$$"); +// } + +// // function to move cursor to 0,0 and clear line +// U0 ClearLineRight() { +// DocPrint(term.doc, "$$CM+LX+PRY,LE=1,RE=0$$"); +// } + +// // function to move cursor to 0,0 and clear line +// U0 ClearLineLeft() { +// DocPrint(term.doc, "$$CM+LX+PRY,LE=0,RE=1$$"); +// } + +// // function to move cursor to 0,0 and clear line +// U0 ClearLineAll() { +// DocPrint(term.doc, "$$CM+LX+PRY,LE=1,RE=1$$"); + +// Placeholder for the full ANSI text styling +// if (ansi_code[m] <= 10) { +// switch (ansi_code[m]) { +// case 0: "$$BG,BLACK$$$$WHITE$$"; isBright = FALSE; break; // reset +// case 1: isBright = TRUE; break; +// case 2: isBright = FALSE; break; +// // case 0: "$$BG$$$$FG$$"; break; // reset +// // case 1: ""; break; // TODO: bold +// // case 2: ""; break; // TODO: dim +// // case 3: ""; break; // TODO: italic +// // case 4: "$$UL,1$$" + string + "$$UL,0$$"; break; // TODO: underline +// // case 5: "$$"; break; // TODO: blink +// // case 6: ""; break; // TODO: fast blink +// // case 7: "$$IV,1$$" + string + "$$IV,0$$"; break; // TODO: invert +// // case 8: ""; break; // TODO: hide (rare) +// // case 9: ""; break; // TODO: strikethrough +// // case 10: ""; break; // TODO: primary font +// } \ No newline at end of file diff --git a/src/Home/Net/Programs/Telnet/TelnetNegotiation.ZC b/src/Home/Net/Programs/Telnet/TelnetNegotiation.ZC new file mode 100755 index 00000000..40250839 --- /dev/null +++ b/src/Home/Net/Programs/Telnet/TelnetNegotiation.ZC @@ -0,0 +1,202 @@ +#define IAC 0xFF +#define WILL 0xFB +#define WONT 0xFC +#define DO 0xFD +#define DONT 0xFE +#define ECHO 0x01 +#define SUPPRESS_GO_AHEAD 0x03 +#define TERMINAL_TYPE 0x18 +#define LINEMODE 0x22 +#define NAWS 0x1F // (Negotiate About Window Size) +#define IS 0x00 +#define SEND 0x01 +#define SB 0xFA +#define SE 0xF0 +#define CHARSET 0x2A + +// #define TELNET_IAC 255 /* 0xff - Interpret as command */ +// #define TELNET_DONT 254 /* 0xfe - Don't do option */ +// #define TELNET_DO 253 /* 0xfd - Do option */ +// #define TELNET_WONT 252 /* 0xfc - Won't do option */ +// #define TELNET_WILL 251 /* 0xfb - Will do option */ + +// #define TELNET_SB 250 /* 0xfa - sub-negotiation */ +// #define TELNET_GA 249 /* 0xf9 - Go ahead */ +// #define TELNET_EL 248 /* 0xf8 - Erase line */ +// #define TELNET_EC 247 /* 0xf7 - Erase char */ +// #define TELNET_AYT 246 /* 0xf6 - Are you there? */ +// #define TELNET_AO 245 /* 0xf5 - Abort output */ +// #define TELNET_IP 244 /* 0xf4 - Interrupt process */ +// #define TELNET_BRK 243 /* 0xf3 - Break */ +// #define TELNET_SYNC 242 /* 0xf2 - Data mark */ +// #define TELNET_NOP 241 /* 0xf1 - No operation */ +#define SE_END 240 + +// #define TELNET_NAWS 31 +// #define TELNET_TERMINAL_TYPE 24 +#define SEND_LOCATION 23 +#define BINARY_TRANSMISSION 0 + + + +// U0 TelnetRequest(I64 sock, U8 option_code) +// { +// U8 request[3]; +// request[0] = IAC; +// request[1] = WILL; +// request[2] = option_code; + +// TCPSocketSend(sock, request, 3); +// } +U0 SendWindowSize(I64 sock, U16 cols, U16 rows) +{ + U8 buf[9]; + + buf[0] = IAC; + buf[1] = SB; + buf[2] = NAWS; + // if dynamic resolution, make sure to account for 255 AIC (byte stuffing needs to be done) + buf[3] = cols >> 8; // High byte of columns + buf[4] = cols & 0xFF; // Low byte of columns + buf[5] = rows >> 8; // High byte of rows + buf[6] = rows & 0xFF; // Low byte of rows + buf[7] = IAC; + buf[8] = SE; + + SysLog("SendWindowSize: %d x %d\n", cols, rows); + TCPSocketSend(sock, buf, 9); +} + +U0 SendTerminalType(I64 sock) +{ + + U8 response[14]; + + response[0] = IAC; + response[1] = SB; + response[2] = TERMINAL_TYPE; + response[3] = IS; + + response[4] = 'A'; + response[5] = 'N'; + response[6] = 'S'; + response[7] = 'I'; + response[8] = '-'; + response[9] = 'B'; + response[10] = 'B'; + response[11] = 'S'; + + response[12] = IAC; + response[13] = SE; + + SysLog("SendTerminalType: ANSI-BBS\n"); + TCPSocketSend(sock, response, 14); +} + +U0 TelnetNegotiate(I64 sock, U8 *ptr) +{ + U8 negotiation_code = *(ptr + 1); + U8 option_code = *(ptr + 2); + + + SysLog("Negotiation code: %d | Option code: %d\n", negotiation_code, option_code); + + // Check if this is a subnegotiation request + if (negotiation_code == SB) + { + SysLog("SB: %d\n", option_code); + if (option_code == TERMINAL_TYPE && *(ptr + 3) == SEND) + { + SendTerminalType(sock); + } + else if (option_code == NAWS && *(ptr + 3) == SEND) + { + SendWindowSize(sock, 80, 25); + } + return; + } + + // Otherwise, handle it as a normal negotiation... + U8 response[3]; + response[0] = IAC; + if (negotiation_code == DO || negotiation_code == DONT) + { + if (option_code == ECHO) + { + if (negotiation_code == DO || negotiation_code == WILL) + { + response[1] = WILL; + } + else + { + response[1] = WONT; + } + } + else if (option_code == SUPPRESS_GO_AHEAD) + { + if (negotiation_code == DO) + { + response[1] = WILL; + } + else + { + response[1] = WONT; + } + } + else if (option_code == TERMINAL_TYPE) + { + SysLog("TERMINAL_TYPE negotiation\n"); + if (negotiation_code == DO) + { + SysLog("TERMINAL_TYPE WILL\n"); + response[1] = WILL; + } + else + { + SysLog("TERMINAL_TYPE WONT\n"); + response[1] = WONT; + } + } + else if (option_code == NAWS) + { + SysLog("NAWS negotiation\n"); + if (negotiation_code == DO || negotiation_code == WILL) + { + SysLog("NAWS WILL\n"); + response[1] = WILL; + } + else + { + SysLog("NAWS WONT\n"); + response[1] = WONT; + } + } + else if (option_code == LINEMODE) + { + if (negotiation_code == DO) + { + response[1] = WILL; + } + else + { + response[1] = WONT; + } + } + else + { + response[1] = WONT; + } + } + else + { + response[1] = WONT; + } + + response[2] = option_code; + TCPSocketSend(sock, response, 3); + // if (option_code == NAWS && negotiation_code == DO) + // { + // SysLog("Sending NAWS right away\n"); + // SendWindowSize(sock, 80, 25); + // } +} \ No newline at end of file diff --git a/src/Home/Net/Utilities/NetLog.ZC b/src/Home/Net/Utilities/NetLog.ZC index 845f4dac..c16728a6 100755 --- a/src/Home/Net/Utilities/NetLog.ZC +++ b/src/Home/Net/Utilities/NetLog.ZC @@ -1,4 +1,5 @@ CTask *net_log_task = NULL; +Bool net_log_enabled = TRUE; U0 NetLogSave() { @@ -8,6 +9,7 @@ U0 NetLogSave() U0 NetLogInit() { + if (!net_log_enabled) return; net_log_task = Spawn(&ServerCmdLine, NULL, "Network Log"); TaskWait(net_log_task); @@ -27,6 +29,19 @@ U0 NetLogInit() Bts(&net_log_task->win_inhibit, WIf_SELF_FOCUS); } +U0 NetLogToggle() { + if (Fs != sys_task) + { + "\nMust be ran as system"; + return; + } + net_log_enabled = !net_log_enabled; + if(!net_log_enabled) + Kill(net_log_task); + else + NetLogInit; +} + U0 NetLogBottom() {//Move cursor, cur_entry, to bottom. Bool unlock; @@ -41,6 +56,7 @@ U0 NetLogBottom() U0 NetLog(U8 *format, ...) { // Output text to NetLogTask as Log. + if (!net_log_enabled) return; U8 *buf = StrPrintJoin(NULL, format, argc, argv); NetLogBottom; @@ -52,6 +68,7 @@ U0 NetLog(U8 *format, ...) U0 NetWarn(U8 *format, ...) { // Output text to NetLogTask as Warning. + if (!net_log_enabled) return; U8 *buf = StrPrintJoin(NULL, format, argc, argv); NetLogBottom; @@ -63,6 +80,7 @@ U0 NetWarn(U8 *format, ...) U0 NetErr(U8 *format, ...) { // Output text to NetLogTask as Error. + if (!net_log_enabled) return; U8 *buf = StrPrintJoin(NULL, format, argc, argv); NetLogBottom; @@ -74,6 +92,7 @@ U0 NetErr(U8 *format, ...) U0 NetDebug(U8 *format, ...) { // Output text to NetLogTask as Debug. + if (!net_log_enabled) return; U8 *buf = StrPrintJoin(NULL, format, argc, argv); NetLogBottom; diff --git a/src/Home/PaletteEditor/Palettes/Zsnes.ZC b/src/Home/PaletteEditor/Palettes/Zsnes.ZC index 4b639212..1fcec267 100755 --- a/src/Home/PaletteEditor/Palettes/Zsnes.ZC +++ b/src/Home/PaletteEditor/Palettes/Zsnes.ZC @@ -1,5 +1,5 @@ public CBGR24 gr_palette_zsnes[COLORS_NUM] = { -0xE8E8E9, 0x374B88, 0x6C84FC, 0x297582, 0x63D64E, 0x397AFF, 0xB7822F, 0x25256E, 0x712, 0xFF2A76, 0xA1CE97, 0x6DB4BE, 0x9FA6FF, 0xCA94E8, 0xFFCC00, 0x132C +0xE8E8E9, 0x374B88, 0x6C84FC, 0x297582, 0x63D64E, 0x397AFF, 0xB7822F, 0x7F7A7C, 0x66696E, 0xFF2A76, 0xA1CE97, 0x6DB4BE, 0x9FA6FF, 0xCA94E8, 0xFFCC00, 0x132C }; public U0 PaletteSetZsnes(Bool persistent=TRUE) { diff --git a/src/System/Utils/ConversionScript.ZC b/src/System/Utils/ConversionScript.ZC index 91373bf9..8fe8b7b8 100755 --- a/src/System/Utils/ConversionScript.ZC +++ b/src/System/Utils/ConversionScript.ZC @@ -114,6 +114,11 @@ U0 Cvt(U8 *ff_mask="*", U8 *fu_flags="+r+l-i+S") Find("RWF_SCROLL", ff_mask,fu_flags,"RAWF_SCROLL"); Find("RawDr", ff_mask,fu_flags,"RawDumpRegs"); + //Added by y4my4m August 13 2023 + Find("ms_hard", ff_mask,fu_flags,"mouse_hard"); + Find("MsUpdate", ff_mask,fu_flags,"MouseUpdate"); + Find("FlushMsgs", ff_mask,fu_flags,"FlushMessages"); + "\n$$BK,1$$$$LTRED$$Might want to go over these$$FG$$$$BK,0$$\n"; Find("MemSet", ff_mask, fu_flags); }