dict/nda.c
Kelvin Sherlock 1155573111 buffer allocation was wrong which caused excess allocations
it also may have written past the end of the buffer and lost
characters (including formatting characters, resulting in display glitches).
2021-07-07 18:43:53 -04:00

712 lines
15 KiB
C

#pragma nda NDAOpen NDAClose NDAAction NDAInit 15 0xffff "--Dictionary\\H**"
#pragma lint - 1
#pragma optimize - 1
#include <Control.h>
#include <Desk.h>
#include <Event.h>
#include <GSOS.h>
#include <Loader.h>
#include <Locator.h>
#include <Memory.h>
#include <Resources.h>
#include <TCPIP.h>
#include <TextEdit.h>
#include <Types.h>
#include <Window.h>
#include <intmath.h>
#include <misctool.h>
#include <quickdraw.h>
#include <lineedit.h>
#include <ctype.h>
#include <stdio.h>
#include "connection.h"
#include "nda.h"
#define TBBlack "\x01" "C" "\x00\x00"
#define TBBlue "\x01" "C" "\x11\x11"
#define TBRed "\x01" "C" "\x44\x44"
unsigned NDAStartUpTools(Word memID, StartStopRecord *ssRef);
void NDAShutDownTools(StartStopRecord *ssRef);
typedef struct NDAResourceCookie {
Word oldPrefs;
Word oldRApp;
Word resFileID;
} NDAResourceCookie;
void NDAResourceRestore(NDAResourceCookie *cookie);
void NDAResourceShutDown(NDAResourceCookie *cookie);
Word NDAResourceStartUp(Word memID, Word access, NDAResourceCookie *cookie);
Word MyID;
Word FlagTCP;
Word ToolsLoaded;
GrafPortPtr MyWindow;
Handle TECtrlHandle;
Handle TextHandle;
LongWord TextHandleSize;
LongWord TextHandleUsed;
static StartStopRecord ss = {0,
0,
0,
0,
4,
{
0x12, 0x0000, /* QD Aux */
0x1b, 0x0000, /* Font Manager */
0x22, 0x0000, /* Text Edit */
0x36, 0x0300, /* TCP */
}
};
static NDAResourceCookie resInfo;
static Connection connection;
enum {
st_none,
st_idle,
st_connect,
st_login,
st_client,
st_define1,
st_define2,
st_define3,
st_quit,
st_force_quit,
st_disconnect,
};
static unsigned st = 0;
static LongWord qtick = 0;
const char *ReqName = "\pTCP/IP~kelvin~dict~";
int define(Word ipid, const char *dict);
pascal void MarinettiCallback(char *str);
void EnableControls(void);
void DisableControls(void);
void AppendText(word length, char *cp) {
Handle h = TextHandle;
LongWord size;
size = TextHandleUsed + length;
if (size > TextHandleSize) {
size += 4095;
size &= ~4095;
if (h) {
HUnlock(h);
SetHandleSize(size, h);
if (_toolErr) return;
HLock(h);
TextHandleSize = size;
} else {
TextHandle = h = NewHandle(size, MyID, attrLocked|attrNoSpec, 0);
if (_toolErr) return;
TextHandleSize = size;
}
}
BlockMove(cp, *h + TextHandleUsed, length);
TextHandleUsed += length;
}
void AppendText2(word length, char *cp) {
unsigned i;
unsigned char c;
unsigned start = 0;
for (i = 0; i < length; ++i) {
c = cp[i];
if (c == '{' || c == '}' || c == 0x01) {
/* flush any pending data */
if (start < i)
AppendText(i - start, cp + start);
if (c == '{') AppendText(4, TBBlue);
if (c == '}') AppendText(4, TBBlack);
start = i + 1;
}
}
if (start < length)
AppendText(length - start, cp + start);
}
void SetText(void) {
longword oldStart, oldEnd;
TESetSelection((Pointer)-1, (Pointer)-1, TECtrlHandle);
TESetText(teDataIsTextBox2|teTextIsPtr, (Ref)*TextHandle, TextHandleUsed, 0, NULL, TECtrlHandle);
TextHandleUsed = 0;
}
static char buffer[512];
void TCPLoop(void) {
static srBuff sr;
static rlrBuff rlr;
Word terr;
unsigned x;
int status;
Word ipid = connection.ipid;
switch (st) {
case st_idle:
if (GetTick() < qtick) break;
case st_force_quit:
TCPIPWriteTCP(ipid, "QUIT\r\n", 6, 1, 0);
qtick = GetTick() + 60 * 30;
st = st_quit;
return;
break;
case st_connect:
x = ConnectionPoll(&connection);
switch (connection.state) {
case kConnectionStateConnected:
MarinettiCallback("\pConnected");
++st;
break;
case kConnectionStateDisconnected:
MarinettiCallback("\pDisconnected");
EnableControls();
st = st_none;
break;
case kConnectionStateError:
MarinettiCallback("\pConnection Error");
EnableControls();
st = st_none;
break;
}
return;
case st_disconnect:
x = ConnectionPoll(&connection);
switch (connection.state) {
case kConnectionStateDisconnected:
case kConnectionStateError:
MarinettiCallback("\pDisconnected");
EnableControls();
st = st_none;
break;
}
return;
redo:
case st_login:
case st_client:
case st_define1:
case st_define2:
case st_define3:
case st_quit:
TCPIPPoll();
sr.srRcvQueued = 0;
terr = TCPIPStatusTCP(ipid, &sr);
if (sr.srRcvQueued == 0) {
if (GetTick() >= qtick) {
/* timeout */
MarinettiCallback("\pTimed out");
ConnectionClose(&connection);
st = st_disconnect;
}
return;
}
/* all data should be \r\n delimited. we want to keep the \r */
terr = TCPIPReadLineTCP(ipid, "\p\n", 0x0000, (Ref)&buffer,
sizeof(buffer) - 2, &rlr);
if (terr == tcperrConClosing) terr = 0;
if (terr) return;
if (!rlr.rlrIsDataFlag)
return;
qtick += 60 * 15; /* bump timeout */
/* ensure a trailing \r and 0 */
x = rlr.rlrBuffCount;
if (x) {
--x;
if (buffer[x] != '\r') {
buffer[x] = '\r';
++x;
}
++x;
}
buffer[x] = 0;
rlr.rlrBuffCount = x;
if (st != st_define3) {
unsigned i;
status = 0;
for (i = 0;; ++i) {
unsigned c = buffer[i];
if (isdigit(c)) {
status *= 10;
status += c - '0';
continue;
}
if (isspace(c) || c == 0)
break;
status = -1;
break;
}
if (i == 0)
status = -1;
}
break;
}
switch (st) {
case st_login:
/* expect a 220 status */
if (status == 220) {
++st;
terr = TCPIPWriteTCP(ipid, "CLIENT dict-nda-iigs\r\n", 22, 1, 0);
} else {
MarinettiCallback("\pLogin Error");
st = st_force_quit;
}
break;
case st_client:
/* expect 250 status */
if (status == 250) {
++st;
/* send define string... */
define(ipid, NULL);
} else {
MarinettiCallback("\pClient Error");
AppendText(4, TBRed);
AppendText(rlr.rlrBuffCount, buffer);
SetText();
st = st_force_quit;
}
break;
case st_define1:
/* expect 550, 552, or 150 status */
if (status == 150) {
++st;
MarinettiCallback("\pReceiving...");
break;
}
if (status == 552) {
MarinettiCallback("\pNo match");
st = st_idle;
} else {
MarinettiCallback("\pServer Error");
AppendText(4, TBRed);
AppendText(rlr.rlrBuffCount, buffer);
SetText();
st = st_idle;
}
EnableControls();
qtick = GetTick() + 60 * 60 * 2; /* 2-minute timeout */
break;
case st_define2:
/* expect 151 */
if (status == 151) {
++st;
} else if (status == 250) {
st = st_idle;
qtick = GetTick() + 60 * 60 * 2; /* 2-minute timeout */
SetText();
EnableControls();
MarinettiCallback("\pConnected");
} else {
MarinettiCallback("\pServer Error");
AppendText(4, TBRed);
AppendText(rlr.rlrBuffCount, buffer);
SetText();
st = st_idle;
}
break;
case st_define3:
/* expect definition text. '.' terminates. */
if (buffer[0] == '.') {
AppendText(rlr.rlrBuffCount - 1, buffer + 1);
if (buffer[1] == '\r') {
--st;
}
} else {
AppendText2(rlr.rlrBuffCount, buffer);
}
goto redo;
break;
case st_quit:
/* expect 221 but doesn't really matter... */
ConnectionClose(&connection);
st = st_disconnect;
MarinettiCallback("\pDisconnecting...");
break;
}
}
// activate/inactivate controls based on Marinetti status
void UpdateStatus(Boolean redraw) {
if (FlagTCP) {
SetInfoRefCon((LongWord) "\pNetwork Connected", MyWindow);
} else {
SetInfoRefCon((LongWord) "\pNetwork Disconnected", MyWindow);
}
if (redraw)
DrawInfoBar(MyWindow);
}
#pragma databank 1
/*
* Watch for TCP status updates.
*/
pascal word HandleRequest(word request, longword dataIn, longword dataOut) {
Word oldRApp;
oldRApp = GetCurResourceApp();
SetCurResourceApp(MyID);
if (request == TCPIPSaysNetworkUp) {
FlagTCP = true;
UpdateStatus(true);
}
if (request == TCPIPSaysNetworkDown) {
FlagTCP = false;
UpdateStatus(true);
}
SetCurResourceApp(oldRApp);
}
pascal void MarinettiCallback(char *str) {
if (MyWindow) {
SetInfoRefCon((LongWord)str, MyWindow);
DrawInfoBar(MyWindow);
}
}
pascal void DrawInfo(Rect *rect, const char *str, GrafPortPtr w) {
SetForeColor(0x00);
SetBackColor(0x0f);
EraseRect(rect);
if (str) {
MoveTo(/*8, 22*/ rect->h1 + 10, rect->v1 + 9);
DrawString(str);
}
}
void DrawWindow(void) { DrawControls(GetPort()); }
#pragma databank 0
void MakeCtlTargetByID(GrafPortPtr window, Long resID) {
CtlRecHndl h = GetCtlHandleFromID(window, resID);
if (h) MakeThisCtlTarget(h);
}
void NDAInit(Word code) {
if (code) {
MyWindow = NULL;
FlagTCP = false;
ToolsLoaded = false;
MyID = MMStartUp();
st = st_none;
} else {
if (ToolsLoaded)
NDAShutDownTools(&ss);
ToolsLoaded = false;
}
}
void NDAClose(void) {
// if running, shut down.
AcceptRequests(ReqName, MyID, NULL);
ConnectionAbort(&connection);
st = st_none;
CloseWindow(MyWindow);
MyWindow = NULL;
TECtrlHandle = NULL;
NDAResourceShutDown(&resInfo);
if (TextHandle) {
DisposeHandle(TextHandle);
TextHandle = NULL;
TextHandleUsed = 0;
TextHandleSize = 0;
}
}
GrafPortPtr NDAOpen(void) {
MyWindow = NULL;
TextHandle = NULL;
TextHandleSize = 0;
TextHandleSize = 0;
st = st_none;
if (!ToolsLoaded) {
if (NDAStartUpTools(MyID, &ss)) {
NDAShutDownTools(&ss);
return NULL;
}
ToolsLoaded = true;
}
if (TCPIPLongVersion() < 0x03006011) {
AlertWindow(awCString, NULL,
(Ref) "24~Marinetti 3.0b11 or newer required.~^Ok");
return NULL;
}
// Check if Marinetti Active.
FlagTCP = TCPIPGetConnectStatus();
if (NDAResourceStartUp(MyID, readEnable, &resInfo)) {
MyWindow = NewWindow2(NULL, 0, DrawWindow, NULL, refIsResource, rWindow,
rWindParam1);
SetInfoDraw(DrawInfo, MyWindow);
UpdateStatus(0);
AcceptRequests(ReqName, MyID, &HandleRequest);
MakeCtlTargetByID(MyWindow, rCtrlLE);
SetSysWindow(MyWindow);
ShowWindow(MyWindow);
SelectWindow(MyWindow);
TECtrlHandle = (Handle)GetCtlHandleFromID(MyWindow, rCtrlTE);
ConnectionInit(&connection, MyID, MarinettiCallback);
NDAResourceRestore(&resInfo);
return MyWindow;
}
return NULL;
}
static char word_to_define[256];
int define(Word ipid, const char *dict) {
word terr;
int ok;
unsigned x;
static char buffer[512];
if (!dict || !*dict)
dict = "!";
x = sprintf(buffer, "DEFINE %s \"%b\"\r\n", dict, word_to_define);
terr = TCPIPWriteTCP(ipid, buffer, x, 1, 0);
return 0;
}
void DoDefine(void) {
unsigned i;
Handle handle;
handle = (Handle)GetCtlHandleFromID(MyWindow, rCtrlTE);
TESetText(teDataIsTextBlock|teTextIsPtr, (Ref)"", 0, 0, NULL, handle);
GetLETextByID(MyWindow, rCtrlLE, (StringPtr)word_to_define);
i = word_to_define[0];
while (i && isspace(word_to_define[i])) --i;
if (!i) return;
word_to_define[0] = i;
word_to_define[i+1] = 0;
/* considerations:
1. is the network connected?
2. is a tcp connection already established?
*/
if (!FlagTCP) {
MarinettiCallback("\pConnecting to network...");
TCPIPConnect(MarinettiCallback);
if (!FlagTCP) return;
}
qtick = GetTick() + 30 * 60;
DisableControls();
switch(st) {
case st_idle:
define(connection.ipid, NULL);
st = st_define1;
break;
default:
ConnectionAbort(&connection);
case st_none:
ConnectionOpenC(&connection, "dict.org", 2628);
st = st_connect;
break;
}
}
void EnableControls(void) {
HiliteCtlByID(noHilite, MyWindow, rCtrlLE);
HiliteCtlByID(noHilite, MyWindow, rCtrlDefine);
}
void DisableControls(void) {
HiliteCtlByID(inactiveHilite, MyWindow, rCtrlLE);
HiliteCtlByID(inactiveHilite, MyWindow, rCtrlDefine);
}
void EditKeys(Word key, Handle target, LongWord targetID) {
LERecHndl le;
if (!target) {
target = (Handle)FindTargetCtl();
if (!target) return;
targetID = GetCtlID((CtlRecHndl)target);
}
le = (LERecHndl)(*(CtlRecHndl)target)->ctlData;
switch (key) {
case 'a':
case 'A':
if (targetID == rCtrlTE) {
LongWord start = 0;
LongWord end = 0;
TEGetTextInfo((Pointer)&end, 1, TECtrlHandle);
TESetSelection((Pointer)start, (Pointer)end, TECtrlHandle);
} else if (targetID == rCtrlLE) {
Word len = LEGetTextLen(le);
LESetSelect(0, len, le);
}
break;
case 'c':
case 'C':
if (targetID == rCtrlTE) {
TECopy(target);
}
if (targetID == rCtrlLE) {
LECopy(le);
}
break;
case 'v':
case 'V':
if (targetID == rCtrlLE)
LEPaste(le);
break;
case 'x':
case 'X':
if (targetID == rCtrlLE)
LECut(le);
break;
case 'k':
if (targetID == rCtrlLE)
LEDelete(le);
break;
}
}
word NDAAction(void *param, int code) {
word eventCode;
static EventRecord event = {0};
Handle target = NULL;
if (code == runAction) {
if (st)
TCPLoop();
return 1;
}
else if (code == eventAction) {
BlockMove((Pointer)param, (Pointer)&event, 16);
event.wmTaskMask = 0x001FFFFF;
eventCode = TaskMasterDA(0, &event);
switch (eventCode) {
case updateEvt:
BeginUpdate(MyWindow);
DrawWindow();
EndUpdate(MyWindow);
break;
case wInControl:
target = (Handle)event.wmTaskData2;
switch ((Word)event.wmTaskData4) {
case rCtrlDefine:
DoDefine();
break;
case rCtrlTE:
case rCtrlLE:
/* check for control-C, control-A */
if (event.what != keyDownEvt) break;
if (!(event.modifiers & 0x0100)) break;
EditKeys(event.message, (Handle)event.wmTaskData2, event.wmTaskData4);
break;
}
break;
case keyDownEvt:
/* line-edit needs to be handled here */
if (!(event.modifiers & 0x0100)) break;
EditKeys(event.message, 0, 0);
break;
}
} else if (code == copyAction) {
EditKeys('c', 0, 0);
return 1; // yes we handled it.
} else if (code == pasteAction) {
EditKeys('v', 0, 0);
return 1; // yes we handled it.
} else if (code == cutAction) {
EditKeys('x', 0, 0);
return 1; // yes we handled it.
} else if (code == clearAction) {
EditKeys('k', 0, 0);
}
return 0;
}