DVX_GUI/termdemo/termdemo.c

317 lines
9.6 KiB
C

// termdemo.c -- SecLink terminal emulator demo
//
// A standalone DVX GUI application (NOT a DXE app -- this has its own main())
// that combines the ANSI terminal widget with the SecLink encrypted serial
// stack to create a BBS terminal client.
//
// Unlike the DXE apps (progman, notepad, clock, dvxdemo) which run inside
// the DVX Shell, this is a freestanding program that initializes the GUI
// directly and manages its own event loop. It demonstrates how to use the
// DVX widget system outside the shell framework.
//
// Data flow:
// User keystrokes -> terminal widget -> commWrite -> secLink (encrypt) -> UART
// UART -> secLink (decrypt) -> onRecv -> ring buffer -> commRead -> terminal widget
//
// The ring buffer decouples the secLink receive callback (which fires during
// secLinkPoll) from the terminal widget's read cycle (which fires during
// dvxUpdate). This is necessary because the callback can fire at any time
// during polling, but the terminal widget expects to read data synchronously
// during its paint cycle.
//
// Usage: termdemo [com_port] [baud_rate]
// com_port -- 1-4 (default 1)
// baud_rate -- baud rate (default 115200)
#include "dvxApp.h"
#include "dvxWidget.h"
#include "secLink.h"
#include "security.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// ============================================================
// Constants
// ============================================================
#define CMD_FILE_QUIT 100
#define RECV_BUF_SIZE 4096
#define CHANNEL_DATA 0
// ============================================================
// Types
// ============================================================
// Bridges the secLink receive callback (asynchronous, fires during poll)
// with the terminal widget's comm interface (synchronous, called during
// widget paint). The ring buffer allows these two timing domains to
// communicate without blocking.
typedef struct {
SecLinkT *link;
AppContextT *app;
WidgetT *term;
uint8_t recvBuf[RECV_BUF_SIZE];
int32_t recvHead; // written by onRecv callback
int32_t recvTail; // read by commRead during widget paint
} TermContextT;
// ============================================================
// Prototypes
// ============================================================
static int32_t commRead(void *ctx, uint8_t *buf, int32_t maxLen);
static int32_t commWrite(void *ctx, const uint8_t *data, int32_t len);
static void idlePoll(void *ctx);
static void onCloseCb(WindowT *win);
static void onMenuCb(WindowT *win, int32_t menuId);
static void onRecv(void *ctx, const uint8_t *data, int len, uint8_t channel);
// ============================================================
// commRead -- drain receive ring buffer for terminal widget
// ============================================================
static int32_t commRead(void *ctx, uint8_t *buf, int32_t maxLen) {
TermContextT *tc = (TermContextT *)ctx;
int32_t count = 0;
while (count < maxLen && tc->recvTail != tc->recvHead) {
buf[count++] = tc->recvBuf[tc->recvTail];
tc->recvTail = (tc->recvTail + 1) % RECV_BUF_SIZE;
}
return count;
}
// ============================================================
// commWrite -- send keystrokes via secLink (encrypted, channel 0)
// ============================================================
static int32_t commWrite(void *ctx, const uint8_t *data, int32_t len) {
TermContextT *tc = (TermContextT *)ctx;
int rc = secLinkSend(tc->link, data, len, CHANNEL_DATA, true, false);
return (rc == SECLINK_SUCCESS) ? len : 0;
}
// ============================================================
// idlePoll -- poll serial link instead of yielding CPU
// ============================================================
static void idlePoll(void *ctx) {
TermContextT *tc = (TermContextT *)ctx;
secLinkPoll(tc->link);
}
// ============================================================
// onCloseCb -- quit when the terminal window is closed
// ============================================================
static void onCloseCb(WindowT *win) {
AppContextT *ctx = (AppContextT *)win->userData;
if (ctx) {
dvxQuit(ctx);
}
}
// ============================================================
// onMenuCb -- handle menu commands
// ============================================================
static void onMenuCb(WindowT *win, int32_t menuId) {
AppContextT *ctx = (AppContextT *)win->userData;
switch (menuId) {
case CMD_FILE_QUIT:
if (ctx) {
dvxQuit(ctx);
}
break;
}
}
// ============================================================
// onRecv -- secLink receive callback, fills ring buffer
// ============================================================
static void onRecv(void *ctx, const uint8_t *data, int len, uint8_t channel) {
(void)channel;
TermContextT *tc = (TermContextT *)ctx;
for (int i = 0; i < len; i++) {
int32_t next = (tc->recvHead + 1) % RECV_BUF_SIZE;
if (next == tc->recvTail) {
break;
}
tc->recvBuf[tc->recvHead] = data[i];
tc->recvHead = next;
}
}
// ============================================================
// main
// ============================================================
int main(int argc, char *argv[]) {
int comPort = 0;
int32_t baudRate = 115200;
// Parse command line
if (argc >= 2) {
if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0) {
printf("Usage: termdemo [com_port] [baud_rate]\n");
printf(" com_port -- 1-4 (default 1)\n");
printf(" baud_rate -- baud rate (default 115200)\n");
return 0;
}
comPort = atoi(argv[1]) - 1;
if (comPort < 0 || comPort > 3) {
fprintf(stderr, "Invalid COM port (must be 1-4)\n");
return 1;
}
}
if (argc >= 3) {
baudRate = atol(argv[2]);
if (baudRate <= 0) {
fprintf(stderr, "Invalid baud rate\n");
return 1;
}
}
printf("SecLink Terminal Demo\n");
printf("COM%d at %ld 8N1\n\n", comPort + 1, (long)baudRate);
// Seed the RNG with hardware entropy
printf("Gathering entropy...\n");
uint8_t entropy[16];
int got = secRngGatherEntropy(entropy, sizeof(entropy));
secRngSeed(entropy, got);
// Initialize terminal context
TermContextT tc;
memset(&tc, 0, sizeof(tc));
// Open secLink on the specified COM port
printf("Opening SecLink on COM%d...\n", comPort + 1);
tc.link = secLinkOpen(comPort, baudRate, 8, 'N', 1, 0, onRecv, &tc);
if (!tc.link) {
fprintf(stderr, "Failed to open SecLink\n");
return 1;
}
// Perform DH key exchange (blocks until both sides complete)
printf("Performing key exchange (waiting for remote side)...\n");
int rc = secLinkHandshake(tc.link);
if (rc != SECLINK_SUCCESS) {
fprintf(stderr, "Handshake failed (%d)\n", rc);
secLinkClose(tc.link);
return 1;
}
printf("Secure link established.\n\n");
// Initialize DVX GUI
AppContextT ctx;
printf("Initializing video...\n");
if (dvxInit(&ctx, 1024, 768, 16) != 0) {
fprintf(stderr, "Failed to initialize DVX GUI\n");
secLinkClose(tc.link);
return 1;
}
tc.app = &ctx;
// Create the terminal window
WindowT *win = dvxCreateWindow(&ctx, "SecLink Terminal", 40, 30, 680, 460, false);
if (!win) {
dvxShutdown(&ctx);
secLinkClose(tc.link);
return 1;
}
win->userData = &ctx;
win->onClose = onCloseCb;
win->onMenu = onMenuCb;
// File menu
MenuBarT *bar = wmAddMenuBar(win);
if (bar) {
MenuT *fileMenu = wmAddMenu(bar, "File");
if (fileMenu) {
wmAddMenuItem(fileMenu, "Quit", CMD_FILE_QUIT);
}
}
// Widget tree: terminal + status bar
WidgetT *root = wgtInitWindow(&ctx, win);
WidgetT *term = wgtAnsiTerm(root, 80, 25);
term->weight = 100;
wgtAnsiTermSetScrollback(term, 1000);
tc.term = term;
// The comm interface is the glue between the terminal widget and the
// secLink transport. The widget calls commRead during its paint cycle
// to get new data to display, and commWrite when the user types to send
// keystrokes over the encrypted link.
wgtAnsiTermSetComm(term, &tc, commRead, commWrite);
// Status bar showing connection info
WidgetT *sb = wgtStatusBar(root);
char statusText[64];
snprintf(statusText, sizeof(statusText), "COM%d %ld 8N1 [Encrypted]", comPort + 1, (long)baudRate);
wgtLabel(sb, statusText);
// Fit window to widget tree
dvxFitWindow(&ctx, win);
// Register an idle callback so the GUI event loop polls secLink during
// idle time (when there are no mouse/keyboard events to process). This
// ensures incoming data is processed even when the user isn't interacting
// with the terminal. Without this, data would only arrive during the
// fixed-rate dvxUpdate calls.
ctx.idleCallback = idlePoll;
ctx.idleCtx = &tc;
// Main loop: poll secLink for incoming data, then dvxUpdate processes
// input events, repaints dirty widgets (including the terminal, which
// calls commRead to consume the ring buffer), and flushes to the LFB.
while (ctx.running) {
secLinkPoll(tc.link);
if (!dvxUpdate(&ctx)) {
break;
}
}
// Cleanup
dvxShutdown(&ctx);
secLinkClose(tc.link);
printf("SecLink Terminal Demo ended.\n");
return 0;
}