diff --git a/macssh/source/Headers/Preferences.h b/macssh/source/Headers/Preferences.h index 95a7539..0a368a8 100755 --- a/macssh/source/Headers/Preferences.h +++ b/macssh/source/Headers/Preferences.h @@ -278,6 +278,9 @@ typedef struct { cacheDelay; // delay in seconds before passphrase expires /* NONO */ + short + commandHistoryLines; // RAB MacSSH 2.2a1 + short padding[96]; // Memory is getting cheap too.... (used to be [96]) } ApplicationPrefs; diff --git a/macssh/source/Linemode/linemode.c b/macssh/source/Linemode/linemode.c index e80fd97..797ae5f 100755 --- a/macssh/source/Linemode/linemode.c +++ b/macssh/source/Linemode/linemode.c @@ -52,8 +52,12 @@ static unsigned char default_slc[] = static char munger[255]; #endif +#define ESCAPED(ascii) (!(ascii > 31 && ascii < 127)) + void LinemodeUnload(void) {} +/* every connection setup needs to call this now (2.2a1) since it's needed for plain echo + (aka kludge-linemode) and not just traditional linemode */ void initLinemode(struct WindRec *tw) { short temp; @@ -68,18 +72,170 @@ void initLinemode(struct WindRec *tw) tw->lmodeBits = 0; tw->litNext = 0; tw->lmode = 0; + /* RAB 2.2a1 - command line editing */ + tw->editPos = 0; + tw->commandHistory = 0; + tw->chEditPos = 0; + tw->commandHistoryLength = 0; } -void process_key(unsigned char ascii,struct WindRec *tw) +// of course there's a new function to dispose of the stuff... we don't like memory leaks! +void disposeHistory(WindRec *tw) { + CommandHistoryEntry *histptr = tw->commandHistory; + CommandHistoryEntry *tempptr; + + while (tw->commandHistoryLength) { + tempptr = histptr; + histptr = histptr->next; + DisposePtr((char *)tempptr); + tw->commandHistoryLength--; + } +} + +void addToHistory(WindRec *tw) +{ + CommandHistoryEntry *histptr; + short commandHistoryLines = gApplicationPrefs->commandHistoryLines; + + if (commandHistoryLines == -1) return; + if (!commandHistoryLines) + commandHistoryLines = DEFAULT_HISTORY_LINES; + + if (tw->commandHistoryLength >= commandHistoryLines) { // we're at the limit here... + histptr = tw->commandHistory; + histptr = histptr->next; // so re-use what we have + tw->commandHistory = histptr; + } else if (!tw->commandHistory) { // or maybe we haven't even started yet... + histptr = (CommandHistoryEntry *) myNewPtr(sizeof(CommandHistoryEntry)); + if (!histptr) return; // bad bad bad + histptr->next = histptr; + histptr->prev = histptr; + tw->commandHistory = histptr; + tw->commandHistoryLength = 1; + } else { + histptr = (CommandHistoryEntry *) myNewPtr(sizeof(CommandHistoryEntry)); + if (!histptr) return; // bad bad bad + histptr->next = tw->commandHistory->next; + histptr->prev = tw->commandHistory; + tw->commandHistory->next->prev = histptr; + tw->commandHistory->next = histptr; + tw->commandHistory = histptr; + tw->commandHistoryLength++; + } + + BlockMoveData(tw->kbbuf, tw->commandHistory->kbbuf, sizeof(tw->kbbuf)); + tw->commandHistory->kblen = tw->kblen; + tw->chEditPos = 0; +} + +void linemode_kbwrite(struct WindRec *tw, unsigned char ascii) { + if (tw->kblen < (MAXKB - 1)) { /* Add to buffer if not full */ + if (tw->editPos == tw->kblen + 1) tw->editPos = 0; + if (!tw->editPos) + tw->kbbuf[tw->kblen++] = ascii; + else { + BlockMoveData(&tw->kbbuf[tw->editPos-1], &tw->kbbuf[tw->editPos], + tw->kblen - tw->editPos + 1); + tw->kbbuf[tw->editPos-1] = ascii; + tw->kblen++; tw->editPos++; + } + } + else + { + kbflush(tw); + tw->kbbuf[0]=ascii; + tw->kblen=1; + } +} + +void delLinemodeChar(struct WindRec *tw) { + if (!tw->kblen) return; // sanity check + if (!tw->editPos) + tw->kblen--; + else { + if (tw->editPos == 1) return; // another sanity check + BlockMoveData(&tw->kbbuf[tw->editPos-1], &tw->kbbuf[tw->editPos-2], + tw->kblen - tw->editPos + 1); + tw->kblen--; tw->editPos--; + } +} + +void displayHistory(WindRec *tw) +{ + static char tempkbbuf[MAXKB*2+1]; + short kblen; + char *tempkbptr, *kbptr; + +// get rid of the old text first + while (tw->kblen >0) + { + if (tw->echo) { + if (tw->editPos && tw->editPos <= tw->kblen) { + parse(tw,(unsigned char *) "\033[P",3); + if (ESCAPED(tw->kbbuf[tw->editPos - 2])) + parse(tw,(unsigned char *) "\033[P",3); + } else { + parse(tw,(unsigned char *) "\010 \010",3); + if (ESCAPED(tw->kbbuf[tw->kblen - 1])) + parse(tw,(unsigned char *) "\010\033[P", 4); + } + } + tw->kblen--; + } + tw->editPos = 0; + +// now find the new text to put in + if (!tw->chEditPos) { + tw->kblen = tw->oldkblen; + BlockMove(tw->oldkbbuf, tw->kbbuf, sizeof(tw->kbbuf)); + } else { + BlockMove(tw->chEditPos->kbbuf, tw->kbbuf, sizeof(tw->kbbuf)); + tw->kblen = tw->chEditPos->kblen; + } + kblen = tw->kblen; + tempkbptr = tempkbbuf; + kbptr = tw->kbbuf; + +// now draw it - but first, escape controls + while (kblen) { + if (ESCAPED(*kbptr)) { + *tempkbptr = '^'; + tempkbptr++; + *tempkbptr = '@'+*kbptr; + tempkbptr++; + } else { + *tempkbptr = *kbptr; + tempkbptr++; + } + kbptr++; + kblen--; + } + parse(tw, (unsigned char *)tempkbbuf, tempkbptr-tempkbbuf); +} + +// NB: this function can now get called if linemode is OFF (for kludge-pseudo-linemode) +// so make sure no assumptions are made about whether linemode itself is on +// (don't send linemode subnegotations without checking tw->lmode, for example) +void process_key(unsigned char ascii, unsigned char keycode, struct WindRec *tw) +{ + if (!tw->lmode) tw->lmodeBits = L_EDIT; // edit, no trapsig if linemode is off +/* signals? what signals? if you are actually telnetted into a un*x box, you should + be using real linemode, or else. kludge-linemode is designed for other services. + we never did flush the keybuffer on ^Z, etc., and we never will, unless you're + using REAL linemode. So there. */ + if (tw->litNext) { //do no processing on next key tw->litNext = FALSE; - kbwrite(tw, &ascii, 1); + linemode_kbwrite(tw, ascii); + if (tw->editPos) tw->editPos++; if (tw->echo) { - if (ascii>31 && ascii <127) { + if (!ESCAPED(ascii)) { + if (tw->editPos) /* make some room if we're not at the end */ + parse(tw, (unsigned char*)"\033[@", 3); // insert char /* add these chars to buffer */ parse(tw, &ascii, 1); } @@ -87,6 +243,10 @@ void process_key(unsigned char ascii,struct WindRec *tw) } } + /* make sure editing keys don't get confused for control codes */ + if ((keycode == 0x75 || keycode == 0x7b || keycode == 0x7c) + && (tw->lmodeBits & L_EDIT)) ascii = 64; + if (tw->lmodeBits & 2) { // TRAPSIG mode active unsigned char toSend[2] = {IAC,0}; @@ -116,6 +276,7 @@ void process_key(unsigned char ascii,struct WindRec *tw) // if (tw->echo) // parse(tw, &ascii, 1); // echo if we should tw->kblen=0; //zero out the buffer + tw->editPos = 0; netpush(tw->port); netwrite(tw->port,toSend,2); //send IAC whatever if (tw->slcLevel[whichSignal] & SLC_FLUSHIN) @@ -139,12 +300,13 @@ void process_key(unsigned char ascii,struct WindRec *tw) if ((tw->lmodeBits & L_SOFT_TAB)&&(ascii == 0x09)) // SOFT_TAB mode active; expand tab into spaces { short numSpaces = VSIgetNextTabDistance(); - unsigned char spacechar = ' '; while (numSpaces-- > 0) { - kbwrite(tw, &spacechar, 1); + linemode_kbwrite(tw, ' '); } - if (tw->echo) + if (tw->echo) { + if (tw->editPos) parse(tw, (unsigned char*)"\033[@", 3); parse(tw, &ascii, 1); + } return; } @@ -152,10 +314,73 @@ void process_key(unsigned char ascii,struct WindRec *tw) if (tw->lmodeBits & L_EDIT) //handle editing functions { - + if (keycode == 0x7b) { // left arrow + if (tw->editPos == 1 || !tw->kblen) return; // blah + if (!tw->editPos) + tw->editPos = tw->kblen; + else tw->editPos--; + parse(tw, (unsigned char *)"\033[D", 3); + if (ESCAPED(tw->kbbuf[tw->editPos - 1])) + parse(tw, (unsigned char *)"\033[D", 3); + return; + } + + if (keycode == 0x7c) { // right arrow + short times = 1; + + if (!tw->editPos) return; // blah + if (ESCAPED(tw->kbbuf[tw->editPos - 1])) + times = 2; + tw->editPos++; + if (tw->editPos > tw->kblen) tw->editPos = 0; + parse(tw, (unsigned char *) "\033[C\033[C", times*3); + return; + } + if (keycode == 0x7e) { // up arrow + if (!tw->commandHistory) return; // blah + if (!tw->chEditPos) { + tw->chEditPos = tw->commandHistory; + tw->oldkblen = tw->kblen; + BlockMoveData(tw->kbbuf, tw->oldkbbuf, sizeof(tw->kbbuf)); + // save it + } else { + tw->chEditPos = tw->chEditPos->prev; + // we "insert" the entry defined by tw->chEditPos = 0 + // (ie, the current line) between the bottom and top of the chain + if (tw->chEditPos == tw->commandHistory) tw->chEditPos = 0; + } + displayHistory(tw); + return; + } + if (keycode == 0x7d) { // down arrow + if (!tw->commandHistory) return; + if (tw->chEditPos == tw->commandHistory) tw->chEditPos = 0; + else { + if (!tw->chEditPos) { + BlockMoveData(tw->kbbuf, tw->oldkbbuf, sizeof(tw->kbbuf)); // save it + tw->oldkblen = tw->kblen; + tw->chEditPos = tw->commandHistory; + } + tw->chEditPos = tw->chEditPos->next; + } + displayHistory(tw); + return; + } + if (keycode == 0x75) { // forward delete + if (!tw->editPos) return; // blah + tw->editPos++; + if (tw->editPos > tw->kblen) tw->editPos = 0; + parse(tw, (unsigned char *) "\033[P", 3); + if (ESCAPED(tw->kbbuf[tw->editPos ? tw->editPos - 2 : tw->kblen - 1])) + parse(tw,(unsigned char *) "\033[P",3); + delLinemodeChar(tw); + return; + } if (ascii == '\015') //CR { //since we are in edit, send the buffer and CR-LF + addToHistory(tw); /* since we're sending it, save it */ + tw->chEditPos = 0; kbflush(tw); netpush(tw->port); netwrite(tw->port,"\015\012",2); @@ -166,29 +391,34 @@ void process_key(unsigned char ascii,struct WindRec *tw) if (ascii == tw->slc[SLC_EC]) //kill the character { - if (tw->echo) - parse(tw,(unsigned char *) "\010 \010",3); - tw->kblen--; - return; - } - else if (ascii == tw->slc[SLC_AO]) //kill the line - { - while (tw->kblen >0) - { - if (tw->echo) - parse(tw,(unsigned char *) "\010 \010",3); - tw->kblen--; + if (tw->kblen && tw->editPos != 1) { + if (tw->echo) { + parse(tw,(unsigned char *) "\010\033[P", 4); + if (ESCAPED(tw->kbbuf[tw->editPos ? tw->editPos - 2 : tw->kblen - 1])) + parse(tw,(unsigned char *) "\010\033[P",4); + } + delLinemodeChar(tw); } return; } - else if (ascii == tw->slc[SLC_EL]) //kill the line + else if (ascii == tw->slc[SLC_AO] || ascii == tw->slc[SLC_EL]) //kill the line { while (tw->kblen >0) { - if (tw->echo) - parse(tw,(unsigned char *) "\010 \010",3); + if (tw->echo) { + if (tw->editPos && tw->editPos <= tw->kblen) { + parse(tw,(unsigned char *) "\033[P",3); + if (ESCAPED(tw->kbbuf[tw->editPos - 2])) + parse(tw,(unsigned char *) "\033[P",3); + } else { + parse(tw,(unsigned char *) "\010 \010",3); + if (ESCAPED(tw->kbbuf[tw->kblen - 1])) + parse(tw,(unsigned char *) "\010\033[P",4); + } + } tw->kblen--; } + tw->editPos = 0; return; } else if ((ascii == tw->slc[SLC_EOF]) && (tw->lmodeBits & 2)) @@ -224,8 +454,8 @@ void process_key(unsigned char ascii,struct WindRec *tw) while ((tw->kbbuf[tw->kblen-1] != 0x20)&&(tw->kblen >= 0)) //while its not a space { if (tw->echo) - parse(tw,(unsigned char *)"\010 \010",3); - tw->kblen--; + parse(tw,(unsigned char *)"\010\033[P",4); + delLinemodeChar(tw); } } else if (ascii == tw->slc[SLC_RP]) @@ -264,7 +494,7 @@ void process_key(unsigned char ascii,struct WindRec *tw) //ok, at this point, we are past all local editing functions. Now, add the character to the buffer. else { - kbwrite(tw, &ascii, 1); + linemode_kbwrite(tw, ascii); } } @@ -285,10 +515,10 @@ void process_key(unsigned char ascii,struct WindRec *tw) if (tw->echo) /* Handle local ECHOs */ { - if (ascii>31 && ascii <127) /* add these chars to buffer */ + if (!ESCAPED(ascii)) { /* add these chars to buffer */ parse(tw, &ascii, 1); - else /* not printable char */ - { + if (tw->editPos) parse(tw, (unsigned char *) "\033[@", 3); + } else { /* not printable char */ if (!(tw->lmodeBits & L_LIT_ECHO)) //don't echo if this is set { ascii='@'+ascii; @@ -595,8 +825,8 @@ void doLinemode(struct WindRec *tw) unsigned char subBeginSeq[4] = {IAC,TEL_SB,N_LINEMODE,L_SLC}; unsigned char subEndSeq[2] = {IAC,TEL_SE}; unsigned char toSend[3]; - - + + tw->editPos = 0; tw->lmodeBits = 0; tw->lmode = TRUE; tw->litNext = FALSE; diff --git a/macssh/source/Linemode/linemode.proto.h b/macssh/source/Linemode/linemode.proto.h index d7ee7aa..4089a1a 100755 --- a/macssh/source/Linemode/linemode.proto.h +++ b/macssh/source/Linemode/linemode.proto.h @@ -1,6 +1,11 @@ void linemode_suboption(struct WindRec *tw); void DemangleLineModeShort(char *s, short mode); void doLinemode(struct WindRec *tw); -void process_key(unsigned char ascii,struct WindRec *tw); +void process_key(unsigned char ascii, unsigned char keycode, struct WindRec *tw); void initLinemode(struct WindRec *tw); -void LinemodeUnload(void); \ No newline at end of file +void LinemodeUnload(void); +void disposeHistory(WindRec *tw); +void addToHistory(WindRec *tw); +void linemode_kbwrite(struct WindRec *tw, unsigned char ascii); +void delLinemodeChar(struct WindRec *tw); +void displayHistory(WindRec *tw); diff --git a/macssh/source/Screens/wind.h b/macssh/source/Screens/wind.h index a77841a..22d18d7 100755 --- a/macssh/source/Screens/wind.h +++ b/macssh/source/Screens/wind.h @@ -39,6 +39,17 @@ typedef struct sshinfo { void *context; } sshinfo; +typedef struct CommandHistoryEntry CommandHistoryEntry; + +struct CommandHistoryEntry { // a doubly linked list to make removing off the top easier + CommandHistoryEntry *prev; + CommandHistoryEntry *next; + char kbbuf[MAXKB]; + short kblen; +}; + +#define DEFAULT_HISTORY_LINES 10 + struct WindRec { short vs, // virtual screen number @@ -98,7 +109,8 @@ short realbold, ignoreBeeps, // ignore beeps: RAB BetterTelnet 1.0fc7 inversebold, // use inverse for bold: RAB BetterTelnet 1.0fc9 - kblen, // Pointer to next char in buffer to be used + kblen, // Pointer to next char in buffer to be used + oldkblen, // same thing if we're stuck in history clientflags, // BYU mod - boolean flags for ftp client jsNoFlush, // RAB BetterTelnet 2.0b4 - don't flush jump scroller at all realBlink; @@ -202,7 +214,10 @@ short lmodeBits, // Current linemode MODE. Currently support EDIT and TRAPSIG lmode, // linemode is active forwardMask, // should we forward on certain characters - numForwardKeys; // how many keys to forward on + numForwardKeys, // how many keys to forward on + editPos; /* if we're editing the text stream, location past the insertion pt; + otherwise, 0 */ + unsigned char slcLevel[SLC_ARRAY_SIZE+1], //levelBits forwardKeys[32],// which keys to forward on @@ -212,7 +227,8 @@ char TELstop, // Character for scrolling to stop TELgo, // Character for scrolling to go TELip, // Character for interrupt process - kbbuf[MAXKB]; // The keyboard buffer (echo mode ) + kbbuf[MAXKB], // The keyboard buffer (echo mode ) + oldkbbuf[MAXKB]; // the real keyboard buffer if we're stuck in history GrafPtr wind; @@ -239,6 +255,9 @@ Boolean NewMacroInfo sessmacros; +CommandHistoryEntry *commandHistory, *chEditPos; +short commandHistoryLength; + /* NONO */ short authentication, diff --git a/macssh/source/ae/ae.c b/macssh/source/ae/ae.c index 2dc326e..481b83d 100755 --- a/macssh/source/ae/ae.c +++ b/macssh/source/ae/ae.c @@ -189,7 +189,7 @@ pascal OSErr MyHandleSendCR (AppleEvent *theAppleEvent, AppleEvent* reply, long if (TelInfo->numwindows < 1) return noErr; if (screens[scrn].lmode) { - process_key('\015', &screens[scrn]); + process_key('\015', 0, &screens[scrn]); return noErr; } diff --git a/macssh/source/config/configure.c b/macssh/source/config/configure.c index 3fe5487..e812816 100755 --- a/macssh/source/config/configure.c +++ b/macssh/source/config/configure.c @@ -190,6 +190,14 @@ void Cenviron( void) NumToString(scratchlong,scratchPstring); SetTEText(dptr,PrefSendTime, scratchPstring); + if (gApplicationPrefs->commandHistoryLines != -1) { + if (!gApplicationPrefs->commandHistoryLines) + NumToString(DEFAULT_HISTORY_LINES, scratchPstring); + else + NumToString(gApplicationPrefs->commandHistoryLines, scratchPstring); + SetTEText(dptr, 44, scratchPstring); + } + ShowWindow(dptr); ditem=0; /* initially no hits */ while((ditem>2) || (ditem==0)) { @@ -308,7 +316,12 @@ void Cenviron( void) GetTEText(dptr,PrefSendTime, scratchPstring); StringToNum(scratchPstring, &scratchlong); gApplicationPrefs->SendTimeout = (short) scratchlong; - + + GetTEText(dptr, 44, scratchPstring); + StringToNum(scratchPstring, &scratchlong); + if (!scratchlong) gApplicationPrefs->commandHistoryLines = -1; + else gApplicationPrefs->commandHistoryLines = scratchlong; + GetTEText(dptr, PrefCaptTE, scratchPstring); BlockMoveData(&scratchPstring[1], &(gApplicationPrefs->CaptureFileCreator), sizeof(OSType)); GetTEText(dptr,PrefStaggerOffset, scratchPstring); diff --git a/macssh/source/main/Connections.c b/macssh/source/main/Connections.c index 7ee3673..462f385 100755 --- a/macssh/source/main/Connections.c +++ b/macssh/source/main/Connections.c @@ -802,7 +802,9 @@ Boolean CreateConnectionFromParams( ConnInitParams **Params) theScreen->lmode = 0; // RAB BetterTelnet 2.0b1 - fix for a really bizarre bug theScreen->lineAllow = SessPtr->linemode; - if (SessPtr->linemode) //we allow linemode + + /* Other stuff uses the linemode code now, so we need to set it up all the time... + if (SessPtr->linemode) //we allow linemode */ initLinemode(&screens[cur]); theScreen->launchurlesc = SessPtr->launchurlesc; diff --git a/macssh/source/main/event.c b/macssh/source/main/event.c index c2c503b..2eddb80 100755 --- a/macssh/source/main/event.c +++ b/macssh/source/main/event.c @@ -436,6 +436,14 @@ void HandleKeyDown(EventRecord theEvent,struct WindRec *tw) ObscureCursor(); +// command line editing for linemode is a special case here + if (((tw->lmode && (tw->lmodeBits & 1)) || + (tw->echo && !tw->halfdup)) && (code == 0x7b || code == 0x7c || code == 0x75 + || code == 0x7d || code == 0x7e)) { + process_key(ascii, code, tw); + return; + } + if ( tw->emacsmeta == 2 && optiondown ) { // option key as emacs meta key: keep shift and control translation ascii = translatekeycode( code, (theEvent.modifiers & (shiftKey|controlKey)) ); @@ -561,8 +569,11 @@ emacsHack: } if ( code == BScode ) { + // if linemode, give it what it wants + if ((tw->lmode || (tw->echo && !tw->halfdup)) && !optiondown) + ascii = tw->slc[10]; // handle mapping BS to DEL, flipping on option - if ( tw->bsdel ) { + else if ( tw->bsdel ) { if ( (optiondown && tw->emacsmeta != 2) || commanddown ) ascii = BS; else @@ -697,7 +708,7 @@ emacsHack: } else if ( tw->lmode ) { for (res = 0; res < trlen; ++res) { // Some form of linemode is active; we dont touch it after them - process_key( pbuf[res], tw ); + process_key( pbuf[res], 0, tw ); } return; } @@ -731,7 +742,8 @@ emacsHack: } if ( tw->echo && !tw->halfdup ) { - // Handle klude-linemode + // Handle kludge-linemode +#if 0 if ( ascii > 31 && ascii < 127 && code < KPlowest ) { // printable key kbwrite( tw, pbuf, trlen); @@ -758,7 +770,10 @@ emacsHack: parse( tw, &sendch, 1 ); } } - } //end if klude-linemode +#endif + process_key(ascii, code, tw); // let "linemode" handle this + return; + } //end if kludge-linemode if (ascii == '\015') { diff --git a/macssh/source/parse/parse.c b/macssh/source/parse/parse.c index ab54600..7ba8747 100755 --- a/macssh/source/parse/parse.c +++ b/macssh/source/parse/parse.c @@ -80,7 +80,8 @@ void kbflush(struct WindRec *tw) { if ( tw->kblen ) { netwrite( tw->port, tw->kbbuf, tw->kblen); - tw->kblen = 0; + tw->kblen = 0; + tw->editPos = 0; } } diff --git a/macssh/source/telnet.rsrc b/macssh/source/telnet.rsrc index f52a52f..2dfba35 100755 Binary files a/macssh/source/telnet.rsrc and b/macssh/source/telnet.rsrc differ