initial libvterm integration

This commit is contained in:
cy384 2020-08-28 22:14:43 -04:00
parent c29c1a2935
commit 1351b96ca7
6 changed files with 139 additions and 142 deletions

View File

@ -10,11 +10,11 @@ IF(CMAKE_SYSTEM_NAME MATCHES Retro68)
#add_compile_options(-mcpu=68020) #add_compile_options(-mcpu=68020)
#add_compile_options(-O2 -flto) #add_compile_options(-O2 -flto)
set_target_properties(ssheven PROPERTIES LINK_FLAGS "-Wl,-gc-sections -Wl,--mac-segments -Wl,${CMAKE_CURRENT_SOURCE_DIR}/ssheven.segmap") set_target_properties(ssheven PROPERTIES LINK_FLAGS "-Wl,-gc-sections -Wl,--mac-segments -Wl,${CMAKE_CURRENT_SOURCE_DIR}/ssheven.segmap")
target_link_libraries(ssheven ssh2 mbedtls mbedx509 mbedcrypto OpenTransportApp OpenTransport OpenTptInet) target_link_libraries(ssheven ssh2 mbedtls mbedx509 mbedcrypto OpenTransportApp OpenTransport OpenTptInet vterm)
ELSE() ELSE()
# for PPC # for PPC
add_compile_options(-O2 -flto) add_compile_options(-O2 -flto)
set_target_properties(ssheven PROPERTIES LINK_FLAGS "-Wl,-gc-sections") set_target_properties(ssheven PROPERTIES LINK_FLAGS "-Wl,-gc-sections")
target_link_libraries(ssheven ThreadsLib ssh2 mbedtls mbedx509 mbedcrypto OpenTransportAppPPC OpenTransportLib OpenTptInternetLib) target_link_libraries(ssheven ThreadsLib ssh2 mbedtls mbedx509 mbedcrypto OpenTransportAppPPC OpenTransportLib OpenTptInternetLib vterm)
ENDIF() ENDIF()

View File

@ -4,13 +4,13 @@ ssheven
------- -------
A modern SSH client for Mac OS 7/8/9. A modern SSH client for Mac OS 7/8/9.
Project status: as of 0.3.0 (see github releases), an actual SSH client with a zero-features "vanilla" fixed-size terminal Project status: as of 0.4.0 (see github releases), an actual SSH client with a fixed-size terminal, good enough to run `top`.
![ssheven screenshot](http://www.cy384.com/media/img/ssheven-screenshot.png) ![ssheven screenshot](http://www.cy384.com/media/img/ssheven-screenshot.png)
system requirements system requirements
------------------- -------------------
* CPU: Any PPC processor. Maybe a 33 or 40 MHz 68040 (or 68LC040). 68030 is too slow (for now). * CPU: Any PPC processor, or a 33 MHz 68040 (maybe a 68LC040, maybe 25 MHz).
* RAM: 2MB * RAM: 2MB
* Disk space: 1MB for the fat binary * Disk space: 1MB for the fat binary
* System 7.5 recommended, earlier System 7 versions possible with the Thread Manager extension installed * System 7.5 recommended, earlier System 7 versions possible with the Thread Manager extension installed
@ -18,7 +18,9 @@ system requirements
to do to do
----- -----
* escape codes and related console emulation features (via libvterm) * hook in more libvterm callbacks (output, termprops)
* feed keyboard input to libvterm (esp. for arrow keys)
* bold/underline/italic in character drawing code
* refactor libssh2 usage to handle errors and centralize network ops * refactor libssh2 usage to handle errors and centralize network ops
* terminal window resizing * terminal window resizing
* nicer connection dialog * nicer connection dialog
@ -28,9 +30,9 @@ to do
* key authentication * key authentication
* check server keys/known hosts/keys * check server keys/known hosts/keys
* text selection + copy * text selection + copy
* improve 68k performance (rewrite `mbedtls_mpi_exp_mod` in assembly) * figure out retro68 mcpu issue, improve 68k performance (rewrite `mbedtls_mpi_exp_mod` in assembly)
* font size options * font size options
* color/bold/underline/italic etc. fancy console features * color
build build
----- -----

View File

@ -55,11 +55,15 @@ void draw_screen(Rect* r)
TextSize(9); TextSize(9);
TextFace(normal); TextFace(normal);
VTermScreenCell vtsc;
for(int i = minRow; i < maxRow; i++) for(int i = minRow; i < maxRow; i++)
{ {
for (int j = minCol; j < maxCol; j++) for (int j = minCol; j < maxCol; j++)
{ {
draw_char(j, i, r, con.data[j][i]); vterm_screen_get_cell(con.vts, (VTermPos){.row = i, .col = j}, &vtsc);
char c = (char)vtsc.chars[0];
draw_char(j, i, r, c);
} }
} }
@ -88,89 +92,11 @@ void ruler(Rect* r)
draw_char(x, y, r, itoc[x%10]); draw_char(x, y, r, itoc[x%10]);
} }
void bump_up_line()
{
for (int y = 0; y < 23; y++)
{
for (int x = 0; x < 80; x++)
{
con.data[x][y] = con.data[x][y+1];
}
}
for (int x = 0; x < 80; x++) con.data[x][23] = ' ';
InvalRect(&(con.win->portRect));
}
int is_printable(char c) int is_printable(char c)
{ {
if (c >= 32 && c <= 126) return 1; else return 0; if (c >= 32 && c <= 126) return 1; else return 0;
} }
void print_char(char c)
{
if (con.cursor_state == 1)
{
con.cursor_state = 0;
Rect cursor = cell_rect(con.cursor_x, con.cursor_y, con.win->portRect);
//InvertRect(&cursor);
InvalRect(&cursor);
}
// backspace
if ('\b' == c)
{
// erase current location
con.data[con.cursor_x][con.cursor_y] = ' ';
Rect inval = cell_rect(con.cursor_x, con.cursor_y, (con.win->portRect));
InvalRect(&inval);
// wrap back to the previous line if possible and necessary
if (con.cursor_x == 0 && con.cursor_y != 0)
{
con.cursor_x = 79;
con.cursor_y--;
}
// otherwise just move back a spot
else if (con.cursor_x > 0)
{
con.cursor_x--;
}
return;
}
// got a bell, give em a system beep (value of 30 recommended by docs)
if ('\a' == c) SysBeep(30);
if ('\n' == c)
{
con.cursor_y++;
con.cursor_x = 0;
}
if (is_printable(c))
{
con.data[con.cursor_x][con.cursor_y] = c;
Rect inval = cell_rect(con.cursor_x, con.cursor_y, (con.win->portRect));
InvalRect(&inval);
con.cursor_x++;
}
if (con.cursor_x == 80)
{
con.cursor_x = 0;
con.cursor_y++;
}
if (con.cursor_y == 24)
{
bump_up_line();
con.cursor_y = 23;
}
}
void print_int(int d) void print_int(int d)
{ {
char itoc[] = {'0','1','2','3','4','5','6','7','8','9'}; char itoc[] = {'0','1','2','3','4','5','6','7','8','9'};
@ -204,10 +130,7 @@ void print_int(int d)
void print_string(const char* c) void print_string(const char* c)
{ {
while (*c != '\0') vterm_input_write(con.vterm, c, strlen(c));
{
print_char(*c++);
}
} }
void printf_i(const char* str, ...) void printf_i(const char* str, ...)
@ -227,16 +150,19 @@ void printf_i(const char* str, ...)
break; break;
case 's': case 's':
print_string(va_arg(args, char*)); print_string(va_arg(args, char*));
break;
case '\0':
vterm_input_write(con.vterm, str-1, 1);
break;
default: default:
va_arg(args, int); // ignore va_arg(args, int); // ignore
print_char('%'); vterm_input_write(con.vterm, str-1, 2);
print_char(*str);
break; break;
} }
} }
else else
{ {
print_char(*str); vterm_input_write(con.vterm, str, 1);
} }
str++; str++;
@ -254,6 +180,58 @@ void set_window_title(WindowPtr w, const char* c_name)
SetWTitle(w, pascal_name); SetWTitle(w, pascal_name);
} }
int bell(void* user)
{
SysBeep(30);
return 1;
}
int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user)
{
// if the cursor is dark, invalidate that location
if (con.cursor_state == 1)
{
Rect inval = cell_rect(con.cursor_x, con.cursor_y, (con.win->portRect));
InvalRect(&inval);
}
con.cursor_x = pos.col;
con.cursor_y = pos.row;
return 1;
}
Rect cell_rect(int x, int y, Rect bounds)
{
Rect r = { (short) (bounds.top + y * con.cell_height), (short) (bounds.left + x * con.cell_width + 2),
(short) (bounds.top + (y+1) * con.cell_height), (short) (bounds.left + (x+1) * con.cell_width + 2) };
return r;
}
int damage(VTermRect rect, void *user)
{
Rect topleft = cell_rect(rect.start_col, rect.start_row, (con.win->portRect));
Rect bottomright = cell_rect(rect.end_col, rect.end_row, (con.win->portRect));
UnionRect(&topleft, &bottomright, &topleft);
InvalRect(&topleft);
return 1;
}
const VTermScreenCallbacks vtscrcb =
{
.damage = damage,
.moverect = NULL,
.movecursor = movecursor,
.settermprop = NULL,
.bell = bell,
.resize = NULL,
.sb_pushline = NULL,
.sb_popline = NULL
};
void console_setup(void) void console_setup(void)
{ {
@ -303,12 +281,14 @@ void console_setup(void)
con.cursor_x = 0; con.cursor_x = 0;
con.cursor_y = 0; con.cursor_y = 0;
con.vterm = vterm_new(24, 80);
vterm_set_utf8(con.vterm, 0);
VTermState* vtermstate = vterm_obtain_state(con.vterm);
vterm_state_reset(vtermstate, 1);
con.vts = vterm_obtain_screen(con.vterm);
vterm_screen_reset(con.vts, 1);
vterm_screen_set_callbacks(con.vts, &vtscrcb, NULL);
} }
Rect cell_rect(int x, int y, Rect bounds)
{
Rect r = { (short) (bounds.top + y * con.cell_height), (short) (bounds.left + x * con.cell_width + 2),
(short) (bounds.top + (y+1) * con.cell_height), (short) (bounds.left + (x+1) * con.cell_width + 2) };
return r;
}

View File

@ -23,8 +23,8 @@
#define SSHEVEN_BUFFER_SIZE 4096 #define SSHEVEN_BUFFER_SIZE 4096
/* terminal type to send over ssh, determines features etc. /* terminal type to send over ssh, determines features etc.
* "vanilla" supports basically nothing, which is good for us here */ * "vanilla" supports basically nothing, "vt100" as per the... vt100 */
#define SSHEVEN_TERMINAL_TYPE "vanilla" #define SSHEVEN_TERMINAL_TYPE "vt100"
/* dialog for getting connection info */ /* dialog for getting connection info */
#define DLOG_CONNECT 128 #define DLOG_CONNECT 128

View File

@ -12,11 +12,11 @@
#include "ssheven-debug.c" #include "ssheven-debug.c"
// error checking convenience macros // error checking convenience macros
#define OT_CHECK(X) err = (X); if (err != noErr) { printf_i("" #X " failed\n"); return 0; }; #define OT_CHECK(X) err = (X); if (err != noErr) { printf_i("" #X " failed\r\n"); return 0; };
#define SSH_CHECK(X) rc = (X); if (rc != LIBSSH2_ERROR_NONE) { printf_i("" #X " failed: %s\n", libssh2_error_string(rc)); return 0;}; #define SSH_CHECK(X) rc = (X); if (rc != LIBSSH2_ERROR_NONE) { printf_i("" #X " failed: %s\r\n", libssh2_error_string(rc)); return 0;};
// sinful globals // sinful globals
struct ssheven_console con = { NULL, {0}, 0, 0, 0, 0, 0, 0}; struct ssheven_console con = { NULL, {0}, 0, 0, 0, 0, 0, 0, NULL};
struct ssheven_ssh_connection ssh_con = { NULL, NULL, kOTInvalidEndpointRef, NULL, NULL }; struct ssheven_ssh_connection ssh_con = { NULL, NULL, kOTInvalidEndpointRef, NULL, NULL };
enum { WAIT, READ, EXIT } read_thread_command = WAIT; enum { WAIT, READ, EXIT } read_thread_command = WAIT;
@ -57,17 +57,18 @@ static pascal void yield_notifier(void* contextPtr, OTEventCode code, OTResult r
// read from the channel and print to console // read from the channel and print to console
void ssh_read(void) void ssh_read(void)
{ {
int rc = libssh2_channel_read(ssh_con.channel, ssh_con.recv_buffer, SSHEVEN_BUFFER_SIZE); size_t rc = libssh2_channel_read(ssh_con.channel, ssh_con.recv_buffer, SSHEVEN_BUFFER_SIZE);
if (rc == 0) return; if (rc == 0) return;
if (rc > 0) if (rc <= 0)
{ {
for(int i = 0; i < rc; ++i) print_char(ssh_con.recv_buffer[i]); printf_i("channel read error: %s\r\n", libssh2_error_string(rc));
} }
else
while (rc > 0)
{ {
printf_i("channel read error: %s\n", libssh2_error_string(rc)); rc -= vterm_input_write(con.vterm, ssh_con.recv_buffer, rc);
} }
} }
@ -119,16 +120,16 @@ int end_connection(void)
break; break;
default: default:
printf_i("unexpected OTLook result while closing: %s\n", OT_event_string(result)); printf_i("unexpected OTLook result while closing: %s\r\n", OT_event_string(result));
break; break;
} }
// release endpoint // release endpoint
err = OTUnbind(ssh_con.endpoint); err = OTUnbind(ssh_con.endpoint);
if (err != noErr) printf_i("OTUnbind failed\n"); if (err != noErr) printf_i("OTUnbind failed\r\n");
err = OTCloseProvider(ssh_con.endpoint); err = OTCloseProvider(ssh_con.endpoint);
if (err != noErr) printf_i("OTCloseProvider failed\n"); if (err != noErr) printf_i("OTCloseProvider failed\r\n");
} }
read_thread_state = DONE; read_thread_state = DONE;
@ -198,8 +199,8 @@ void ssh_write(char* buf, int len)
int r = libssh2_channel_write(ssh_con.channel, buf, len); int r = libssh2_channel_write(ssh_con.channel, buf, len);
if (r < 1) if (r < 1)
{ {
printf_i("failed to write to channel!\n"); printf_i("failed to write to channel!\r\n");
printf_i("closing connection!\n"); printf_i("closing connection!\r\n");
read_thread_command = EXIT; read_thread_command = EXIT;
} }
} }
@ -383,7 +384,7 @@ int init_connection(char* hostname)
if (err != noErr || ssh_con.endpoint == kOTInvalidEndpointRef) if (err != noErr || ssh_con.endpoint == kOTInvalidEndpointRef)
{ {
printf_i("failed to open OT TCP endpoint\n"); printf_i("failed to open OT TCP endpoint\r\n");
return 0; return 0;
} }
@ -396,7 +397,7 @@ int init_connection(char* hostname)
OT_CHECK(OTSetNonBlocking(ssh_con.endpoint)); OT_CHECK(OTSetNonBlocking(ssh_con.endpoint));
printf_i("done.\n"); YieldToAnyThread(); printf_i("done.\r\n"); YieldToAnyThread();
// set up address struct, do the DNS lookup, and connect // set up address struct, do the DNS lookup, and connect
OTMemzero(&sndCall, sizeof(TCall)); OTMemzero(&sndCall, sizeof(TCall));
@ -407,28 +408,28 @@ int init_connection(char* hostname)
printf_i("connecting endpoint... "); YieldToAnyThread(); printf_i("connecting endpoint... "); YieldToAnyThread();
OT_CHECK(OTConnect(ssh_con.endpoint, &sndCall, nil)); OT_CHECK(OTConnect(ssh_con.endpoint, &sndCall, nil));
printf_i("done.\n"); YieldToAnyThread(); printf_i("done.\r\n"); YieldToAnyThread();
printf_i("initializing SSH... "); YieldToAnyThread(); printf_i("initializing SSH... "); YieldToAnyThread();
// init libssh2 // init libssh2
SSH_CHECK(libssh2_init(0)); SSH_CHECK(libssh2_init(0));
printf_i("done.\n"); YieldToAnyThread(); printf_i("done.\r\n"); YieldToAnyThread();
printf_i("opening SSH session... "); YieldToAnyThread(); printf_i("opening SSH session... "); YieldToAnyThread();
ssh_con.session = libssh2_session_init(); ssh_con.session = libssh2_session_init();
if (ssh_con.session == 0) if (ssh_con.session == 0)
{ {
printf_i("failed to initialize SSH library\n"); printf_i("failed to initialize SSH library\r\n");
return 0; return 0;
} }
printf_i("done.\n"); YieldToAnyThread(); printf_i("done.\r\n"); YieldToAnyThread();
long s = TickCount(); long s = TickCount();
printf_i("beginning SSH session handshake... "); YieldToAnyThread(); printf_i("beginning SSH session handshake... "); YieldToAnyThread();
SSH_CHECK(libssh2_session_handshake(ssh_con.session, ssh_con.endpoint)); SSH_CHECK(libssh2_session_handshake(ssh_con.session, ssh_con.endpoint));
printf_i("done. (%d ticks)\n", TickCount() - s); YieldToAnyThread(); printf_i("done. (%d ticks)\r\n", TickCount() - s); YieldToAnyThread();
read_thread_state = OPEN; read_thread_state = OPEN;
@ -532,7 +533,7 @@ void* read_thread(void* arg)
{ {
printf_i("authenticating... "); YieldToAnyThread(); printf_i("authenticating... "); YieldToAnyThread();
ok = ssh_password_auth(username+1, password+1); ok = ssh_password_auth(username+1, password+1);
printf_i("done.\n"); YieldToAnyThread(); printf_i("done.\r\n"); YieldToAnyThread();
} }
if (ok) if (ok)
@ -565,7 +566,7 @@ void* read_thread(void* arg)
int safety_checks(void) int safety_checks(void)
{ {
OSStatus err; OSStatus err = noErr;
// check for thread manager // check for thread manager
long int thread_manager_gestalt = 0; long int thread_manager_gestalt = 0;
@ -575,7 +576,7 @@ int safety_checks(void)
if (err != noErr || (thread_manager_gestalt & (1 << gestaltThreadMgrPresent)) == 0) if (err != noErr || (thread_manager_gestalt & (1 << gestaltThreadMgrPresent)) == 0)
{ {
StopAlert(ALRT_TM, nil); StopAlert(ALRT_TM, nil);
printf_i("Thread Manager not available!\n"); printf_i("Thread Manager not available!\r\n");
return 0; return 0;
} }
@ -591,7 +592,7 @@ int safety_checks(void)
if (err != noErr) if (err != noErr)
{ {
printf_i("Failed to check for Open Transport!\n"); printf_i("Failed to check for Open Transport!\r\n");
return 0; return 0;
} }
@ -599,13 +600,13 @@ int safety_checks(void)
if (err != noErr) if (err != noErr)
{ {
printf_i("Failed to check for Open Transport!\n"); printf_i("Failed to check for Open Transport!\r\n");
return 0; return 0;
} }
if (open_transport_any_version == 0 && open_transport_new_version == 0) if (open_transport_any_version == 0 && open_transport_new_version == 0)
{ {
printf_i("Open Transport required but not found!\n"); printf_i("Open Transport required but not found!\r\n");
StopAlert(ALRT_OT, nil); StopAlert(ALRT_OT, nil);
return 0; return 0;
} }
@ -613,12 +614,12 @@ int safety_checks(void)
if (open_transport_any_version != 0 && open_transport_new_version == 0) if (open_transport_any_version != 0 && open_transport_new_version == 0)
{ {
printf_i("Early version of Open Transport detected!"); printf_i("Early version of Open Transport detected!");
printf_i(" Attempting to continue anyway.\n"); printf_i(" Attempting to continue anyway.\r\n");
} }
NumVersion* ot_version = (NumVersion*) &open_transport_new_version; NumVersion* ot_version = (NumVersion*) &open_transport_new_version;
printf_i("Detected Open Transport version: %d.%d.%d\n", printf_i("Detected Open Transport version: %d.%d.%d\r\n",
(int)ot_version->majorRev, (int)ot_version->majorRev,
(int)((ot_version->minorAndBugRev & 0xF0) >> 4), (int)((ot_version->minorAndBugRev & 0xF0) >> 4),
(int)(ot_version->minorAndBugRev & 0x0F)); (int)(ot_version->minorAndBugRev & 0x0F));
@ -635,7 +636,7 @@ int safety_checks(void)
if (err != noErr || cpu_type == 0) if (err != noErr || cpu_type == 0)
{ {
cpu_slow = 1; cpu_slow = 1;
printf_i("Failed to detect CPU type, continuing anyway.\n"); printf_i("Failed to detect CPU type, continuing anyway.\r\n");
} }
else else
{ {
@ -702,15 +703,21 @@ int main(int argc, char** argv)
console_setup(); console_setup();
char* logo = " _____ _____ _ _\n" char* logo = " _____ _____ _ _\r\n"
" / ____/ ____| | | |\n" " / ____/ ____| | | |\r\n"
" | (___| (___ | |__| | _____ _____ _ __\n" " | (___| (___ | |__| | _____ _____ _ __\r\n"
" \\___ \\\\___ \\| __ |/ _ \\ \\ / / _ \\ '_ \\\n" " \\___ \\\\___ \\| __ |/ _ \\ \\ / / _ \\ '_ \\\r\n"
" ____) |___) | | | | __/\\ V / __/ | | |\n" " ____) |___) | | | | __/\\ V / __/ | | |\r\n"
" |_____/_____/|_| |_|\\___| \\_/ \\___|_| |_|\n"; " |_____/_____/|_| |_|\\___| \\_/ \\___|_| |_|]\r\n";
printf_i(logo); printf_i(logo);
printf_i("by cy384, version " SSHEVEN_VERSION "\n"); printf_i("by cy384, version " SSHEVEN_VERSION "\r\n");
#if defined(__ppc__)
printf_i("Running in PPC mode.\r\n");
#else
printf_i("Running in 68k mode.\r\n");
#endif
BeginUpdate(con.win); BeginUpdate(con.win);
draw_screen(&(con.win->portRect)); draw_screen(&(con.win->portRect));
@ -725,13 +732,13 @@ int main(int argc, char** argv)
EndUpdate(con.win); EndUpdate(con.win);
if (!intro_dialog(hostname, username, password)) ok = 0; if (!intro_dialog(hostname, username, password)) ok = 0;
if (!ok) printf_i("Cancelled, not connecting.\n"); if (!ok) printf_i("Cancelled, not connecting.\r\n");
if (ok) if (ok)
{ {
if (InitOpenTransport() != noErr) if (InitOpenTransport() != noErr)
{ {
printf_i("failed to initialize OT\n"); printf_i("failed to initialize OT\r\n");
ok = 0; ok = 0;
} }
} }
@ -743,7 +750,7 @@ int main(int argc, char** argv)
if (ssh_con.recv_buffer == NULL || ssh_con.send_buffer == NULL) if (ssh_con.recv_buffer == NULL || ssh_con.send_buffer == NULL)
{ {
printf_i("failed to allocate network buffers\n"); printf_i("failed to allocate network buffers\r\n");
ok = 0; ok = 0;
} }
} }
@ -760,7 +767,7 @@ int main(int argc, char** argv)
if (err < 0) if (err < 0)
{ {
ok = 0; ok = 0;
printf_i("failed to create network read thread\n"); printf_i("failed to create network read thread\r\n");
} }
} }
@ -788,6 +795,8 @@ int main(int argc, char** argv)
if (ssh_con.recv_buffer != NULL) OTFreeMem(ssh_con.recv_buffer); if (ssh_con.recv_buffer != NULL) OTFreeMem(ssh_con.recv_buffer);
if (ssh_con.send_buffer != NULL) OTFreeMem(ssh_con.send_buffer); if (ssh_con.send_buffer != NULL) OTFreeMem(ssh_con.send_buffer);
if (con.vterm != NULL) vterm_free(con.vterm);
if (ok) if (ok)
{ {
err = OTCancelSynchronousCalls(ssh_con.endpoint, kOTCanceledErr); err = OTCancelSynchronousCalls(ssh_con.endpoint, kOTCanceledErr);

View File

@ -28,6 +28,9 @@
// ssheven constants // ssheven constants
#include "ssheven-constants.r" #include "ssheven-constants.r"
#include <vterm.h>
#include <vterm_keycodes.h>
// sinful globals // sinful globals
struct ssheven_console struct ssheven_console
{ {
@ -43,6 +46,9 @@ struct ssheven_console
int cursor_state; int cursor_state;
long int last_cursor_blink; long int last_cursor_blink;
VTerm* vterm;
VTermScreen* vts;
}; };
extern struct ssheven_console con; extern struct ssheven_console con;