/**********
 * Copyright (c) 2003-2004 Greg Parker.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY GREG PARKER ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 **********/

#include "includes.h"
#include "about.h"
#include "formutils.h"
#include "data/connectionlist.h"
#include "data/prefs.h"
#include "rsrc/rsrc.h"
#include "ssh/ssh.h"
#include "forms/CollapseUtils.h"
#include <PalmChars.h>
#include "SonyChars.h"

#include "terminalform.h"


// DO NOT renumber any of these! They are stored in preferences.
typedef enum {
    topAll = -1, // initial state - everything visible
    topVT100 = 0,
    topABC, 
    top123, 
    topIntl,
    topEtc 
} top_pane_t;

// DO NOT renumber any of these! They are stored in preferences.
typedef enum {
    bottomAll = -1, // initial state - everything visible
    bottomTC = 0,
    bottomFull, 
    bottomCust
} bottom_pane_t;

// DO NOT renumber any of these! They are stored in preferences.
typedef enum {
    rightNone = -1, // initial state - everything visible
    rightScroll = 0,
    rightArrows
} right_pane_t;

typedef enum {
    caseUpper,
    caseLower, 
    caseCaps
} case_t;

static top_pane_t TopPane = -1;
static bottom_pane_t BottomPane = -1;
static right_pane_t RightPane = -1;

static int TerminalWindowActive = 0;

static int devicesChecked = 0;
static int deviceTreo600 = 0;

extern struct ssh_session_t *ss;
extern UInt16 NetLibCount;


static Boolean OpenNetLib(void) FORMS_SEGMENT;
static Boolean StartConnection(void) FORMS_SEGMENT;
static void ConstructTopVT100Pane(void) FORMS_SEGMENT;
static void DestructTopVT100Pane(void) FORMS_SEGMENT;
static void SetBigShiftKey(void) FORMS_SEGMENT;
static void SetSmallShiftKey(void) FORMS_SEGMENT;
static void ConstructTopABCPane(void) FORMS_SEGMENT;
static void DestructTopABCPane(void) FORMS_SEGMENT;
static void SetABCCase(case_t newcase) FORMS_SEGMENT;
static void ConstructTop123Pane(void) FORMS_SEGMENT;
static void DestructTop123Pane(void) FORMS_SEGMENT;
static void ConstructTopIntlPane(void) FORMS_SEGMENT;
static void DestructTopIntlPane(void) FORMS_SEGMENT;
static void ConstructTopEtcPane(void) FORMS_SEGMENT;
static void DestructTopEtcPane(void) FORMS_SEGMENT;
static void ConstructTopPane(top_pane_t newPane) FORMS_SEGMENT;
static void DestructTopPane(top_pane_t newPane) FORMS_SEGMENT;
static void SetTopPane(top_pane_t newPane) FORMS_SEGMENT;
static void ConstructBottomTCPane(void) FORMS_SEGMENT;
static void DestructBottomTCPane(void) FORMS_SEGMENT;
static void ConstructBottomFullPane(void) FORMS_SEGMENT;
static void DestructBottomFullPane(void) FORMS_SEGMENT;
static void ConstructBottomPane(bottom_pane_t newPane) FORMS_SEGMENT;
static void DestructBottomPane(bottom_pane_t newPane) FORMS_SEGMENT;
static void SetBottomPane(bottom_pane_t newPane) FORMS_SEGMENT;
static void ShowABCPanes(void) FORMS_SEGMENT;
static void Show123Panes(void) FORMS_SEGMENT;
static void ShowIntlPanes(void) FORMS_SEGMENT;
static void ShowEtcPanes(void) FORMS_SEGMENT;
static void HideABC123Panes(void) FORMS_SEGMENT;
static unsigned char control_character(unsigned char c) FORMS_SEGMENT;
static Boolean CtrlPressed(void) FORMS_SEGMENT;
static Boolean MetaPressed(void) FORMS_SEGMENT;
static Boolean ShiftPressed(void) FORMS_SEGMENT;
static void CtrlPress(Boolean down, Boolean lockDown) FORMS_SEGMENT;
static void MetaPress(Boolean down, Boolean lockDown) FORMS_SEGMENT;
static void ShiftPress(Boolean down, Boolean lockDown) FORMS_SEGMENT;
static void SendModifiedKey(unsigned char c) FORMS_SEGMENT;
static void ClearModifiers(void) FORMS_SEGMENT;
static void SendUpArrowKey(void) FORMS_SEGMENT;
static void SendDownArrowKey(void) FORMS_SEGMENT;
static void SendRightArrowKey(void) FORMS_SEGMENT;
static void SendLeftArrowKey(void) FORMS_SEGMENT;
static void SendFKey(UInt16 id) FORMS_SEGMENT;
static void SendScrollpadKey(UInt16 id) FORMS_SEGMENT;
static char Backspace(void) FORMS_SEGMENT;
static Boolean HandleCtlSelectEvent(EventPtr e) FORMS_SEGMENT;
static void TrackDrag(EventPtr e) FORMS_SEGMENT;
static void SetScrollBar(FormPtr frmP, SetScrollBarEventType *se) FORMS_SEGMENT;
static Boolean VT100EventHandler(struct FormGadgetTypeInCallback *gadget, UInt16 cmd, void *paramP) FORMS_SEGMENT;
static Boolean HandleCtlRepeatEvent(EventPtr e) FORMS_SEGMENT;
static Boolean TerminalFormResize(FormPtr frmP) FORMS_SEGMENT;
Boolean RefreshNetLib(void); // fixme NOT forms segment (used elsewhere w/o prototype)

static Boolean OpenNetLib(void)
{
    Err err;
    UInt16 badIF = 0;

    if (NetLibCount == 0) {
        err = NetLibOpen(AppNetRefnum, &badIF);
        if (err  &&  err != netErrAlreadyOpen) {
            NetworkError("Could not open network library.", err);
            return false;
        } else if (badIF) {
            NetworkError("Could not open network interface.", badIF);
            return false;
        } else {
            NetLibCount++;
        }
    }

    return RefreshNetLib();
}


Boolean RefreshNetLib(void)
{
    Err err;
    Boolean allUp;
    UInt16 badIF = 0;

    // reconnect if necessary
    err = NetLibConnectionRefresh(AppNetRefnum, true, &allUp, &badIF);
    if (err) {
        NetworkError("Could not open network interface.", err);
        return false;
    } else if (!allUp) {
        NetworkError("Could not open network interface.", badIF);
        return false;
    } else {
        return true;
    }
}


static Boolean StartConnection(void)
{
    MemHandle recordH;
    char *hostname;
    char *portname;
    char *username;
    char *displayname;
    FieldPtr displayField;
    int port;
    int result;
    RectangleType bounds;

    ss = NULL;

    recordH=ConnectionListReadSelectedRecord(&hostname, &portname, &username);
    if (!recordH) return false;

    port = StrAToI(portname);
    if (port == 0) port = 22;

    displayField = PrvGetObjectByID(TerminalHostFieldID);
    displayname = MemPtrNew(1+StrLen(username)+StrLen("@")+StrLen(hostname));
    if (!displayname) {
        PrvSetFieldToValue(displayField, hostname);
    } else {
        StrCopy(displayname, username);
        StrCat(displayname, "@");
        StrCat(displayname, hostname);
        PrvSetFieldToValue(displayField, displayname);
        MemPtrFree(displayname);
    }

    // draw now in case of error during connection
    FrmDrawForm(FrmGetActiveForm());

    if (!OpenNetLib()) goto done;

    PrvGetObjectBoundsByID(TerminalTextGadgetID, &bounds);
    ss = ssh_create(username, bounds);
    result = ssh_open(ss, hostname, port);
    if (result < 0) {
        ssh_free(ss);
        ss = NULL;
    }

 done:
    MemHandleUnlock(recordH);
        
    return (ss != NULL);
}

static void GetVT100Bounds(RectangleType *bounds)
{
    PrvGetObjectBoundsByID(TerminalTextGadgetID, bounds);
}

static void SetVT100Bounds(RectangleType bounds)
{
    PrvSetObjectBoundsByID(TerminalTextGadgetID, &bounds);
    WinEraseRectangle(&bounds, 0);
    if (ss) ssh_set_bounds(ss, bounds);
}


static Boolean smallVT100 = false;
static Boolean inputShown = true;
static Boolean titleShown = true;
static int titleShift = 0; // distance vt100 moved when title bar was hidden

static void ShowTitleBar(void)
{
    RectangleType bounds;
    if (titleShown) return;

    // grow vt100 to top of screen
    GetVT100Bounds(&bounds);
    bounds.extent.y -= titleShift;
    bounds.topLeft.y = titleShift;
    SetVT100Bounds(bounds);

    // show hostname field
    PrvShowObjectByID(TerminalHostFieldID);

    // hide close box
    PrvHideObjectByID(TerminalCloseBoxButtonID);

    // set pref
    titleShown = 1;
    PrefsPutInt(prefHideTitleBar, 0);
}


static void HideTitleBar(void)
{
    RectangleType bounds;
    if (!titleShown) return;

    // hide hostname field
    PrvHideObjectByID(TerminalHostFieldID);

    // show close box
    PrvShowObjectByID(TerminalCloseBoxButtonID);

    // grow vt100 to top of screen
    GetVT100Bounds(&bounds);
    titleShift = bounds.topLeft.y;
    bounds.extent.y += titleShift;
    bounds.topLeft.y = 0;
    SetVT100Bounds(bounds);

    // set pref
    titleShown = 0;
    PrefsPutInt(prefHideTitleBar, 1);
}


static void SetTitleBar(int show)
{
    if (show) ShowTitleBar();
    else HideTitleBar();
}


static void SetSmallVT100(void)
{
    RectangleType bounds;

    if (!smallVT100) {
        // shrink vt100 gadget
        GetVT100Bounds(&bounds);
        bounds.extent.y -= 48;
        SetVT100Bounds(bounds);
        smallVT100 = true;
    }
}

static void SetLargeVT100(void)
{
    RectangleType bounds;

    if (smallVT100) {
        // grow vt100 gadget
        GetVT100Bounds(&bounds);
        bounds.extent.y += 48;
        SetVT100Bounds(bounds);
        smallVT100 = false;
    }
}


static void ConstructTopVT100Pane(void)
{
    SetLargeVT100();
}

static void DestructTopVT100Pane(void)
{
    // nothing to do - other constructors will set vt100 size
}


// intl and ABC use a wider shift key than etc.
static void SetBigShiftKey(void)
{
    RectangleType bounds;
    PrvGetObjectBoundsByID(TerminalTABCShiftButtonID, &bounds);
    bounds.extent.x = 29;
    PrvSetObjectBoundsByID(TerminalTABCShiftButtonID, &bounds);
}


static void SetSmallShiftKey(void)
{
    RectangleType bounds;
    PrvGetObjectBoundsByID(TerminalTABCShiftButtonID, &bounds);
    bounds.extent.x = 21;
    PrvSetObjectBoundsByID(TerminalTABCShiftButtonID, &bounds);
}


static void ConstructTopABCPane(void)
{
    int i;
    int start, end;
    Boolean caps, num, autoshift;
    UInt16 shift;

    {
        static int construct = 0;
        debug_printf("construct ABC %d", construct++);
    }

    SetSmallVT100();

    // show bitmap and buttons
    PrvShowObjectByID(TerminalTABCButtonsBitmapID);
    // choose caps based on Graffiti shift state
    GrfGetState(&caps, &num, &shift, &autoshift);
    if (caps) {
        start = TerminalTABCFirstCapscaseButtonID;
        end = TerminalTABCLastCapscaseButtonID;
        PrvSetControlValue(TerminalTABCCapsButtonID, 1);
        PrvSetControlValue(TerminalTABCShiftButtonID, 0);
    } else if (shift) {
        start = TerminalTABCFirstUppercaseButtonID;
        end = TerminalTABCLastUppercaseButtonID;
        PrvSetControlValue(TerminalTABCCapsButtonID, 0);
        PrvSetControlValue(TerminalTABCShiftButtonID, 1);
    } else {
        start = TerminalTABCFirstLowercaseButtonID;
        end = TerminalTABCLastLowercaseButtonID;
        PrvSetControlValue(TerminalTABCCapsButtonID, 0);
        PrvSetControlValue(TerminalTABCShiftButtonID, 0);
    }
    for (i = start; i <= end; i++) {
        PrvShowObjectByID(i);
    }
    PrvShowObjectByID(TerminalTABCTabButtonID);
    PrvShowObjectByID(TerminalTABCSpaceButtonID);
    PrvShowObjectByID(TerminalTABCBackspaceButtonID);
    PrvShowObjectByID(TerminalTABCReturnButtonID);
    PrvShowObjectByID(TerminalTABCCapsButtonID);
    PrvShowObjectByID(TerminalTABCShiftButtonID);
    SetBigShiftKey();
}

static void DestructTopABCPane(void)
{
    int i;
    int count;

    // hide bitmap and buttons
    count = TerminalTABCLastButtonID - TerminalTABCFirstButtonID + 1;
    for (i = 0; i < count; i++) {
        PrvHideObjectByID(TerminalTABCFirstButtonID + i);
    }
    PrvHideObjectByID(TerminalTABCTabButtonID);
    PrvHideObjectByID(TerminalTABCSpaceButtonID);
    PrvHideObjectByID(TerminalTABCBackspaceButtonID);
    PrvHideObjectByID(TerminalTABCReturnButtonID);
    PrvHideObjectByID(TerminalTABCCapsButtonID);
    PrvHideObjectByID(TerminalTABCShiftButtonID);
    PrvHideObjectByID(TerminalTABCButtonsBitmapID);
}


static void SetABCCase(case_t newcase)
{
    Boolean caps = 0, shift = 0;
    if (newcase == caseUpper) shift = 1;
    else if (newcase == caseCaps) caps = 1;

    GrfSetState(caps, 0, shift);
    GsiSetShiftState(caps ? glfCapsLock : 0, shift ? 4 : 0);
    // value 4 should be gsiShiftUpper but isn't, or something

    switch (TopPane) {
    case topABC:
        DestructTopABCPane();
        ConstructTopABCPane();
        break;
    case topIntl:
        DestructTopIntlPane();
        ConstructTopIntlPane();
        break;
    case topEtc:
        DestructTopEtcPane();
        ConstructTopEtcPane();
        break;
    default:
        break;
    }
}


static void ConstructTop123Pane(void)
{
    int i;
    int count;

    SetSmallVT100();

    // show bitmap and buttons
    PrvShowObjectByID(TerminalT123ButtonsBitmapID);
    count = TerminalT123LastButtonID - TerminalT123FirstButtonID + 1;
    for (i = 0; i < count; i++) {
        PrvShowObjectByID(TerminalT123FirstButtonID + i);
    }
    PrvShowObjectByID(TerminalT123TabButtonID);
    PrvShowObjectByID(TerminalT123SpaceButtonID);
    PrvShowObjectByID(TerminalT123BackspaceButtonID);
    PrvShowObjectByID(TerminalT123ReturnButtonID);
}

static void DestructTop123Pane(void)
{
    int i;
    int count;

    // hide bitmap and buttons
    count = TerminalT123LastButtonID - TerminalT123FirstButtonID + 1;
    for (i = 0; i < count; i++) {
        PrvHideObjectByID(TerminalT123FirstButtonID + i);
    }
    PrvHideObjectByID(TerminalT123TabButtonID);
    PrvHideObjectByID(TerminalT123SpaceButtonID);
    PrvHideObjectByID(TerminalT123BackspaceButtonID);
    PrvHideObjectByID(TerminalT123ReturnButtonID);
    PrvHideObjectByID(TerminalT123ButtonsBitmapID);
}


static void ConstructTopIntlPane(void)
{
    int i;
    int start, end;
    Boolean caps, num, autoshift;
    UInt16 shift;

    SetSmallVT100();

    // show bitmap and buttons
    PrvShowObjectByID(TerminalTIntlButtonsBitmapID);
    // choose caps based on Graffiti shift state
    GrfGetState(&caps, &num, &shift, &autoshift);
    if (caps) {
        start = TerminalTIntlFirstUppercaseButtonID;
        end = TerminalTIntlLastUppercaseButtonID;
        PrvSetControlValue(TerminalTIntlCapsButtonID, 1);
        PrvSetControlValue(TerminalTIntlShiftButtonID, 0);
    } else if (shift) {
        start = TerminalTIntlFirstUppercaseButtonID;
        end = TerminalTIntlLastUppercaseButtonID;
        PrvSetControlValue(TerminalTIntlCapsButtonID, 0);
        PrvSetControlValue(TerminalTIntlShiftButtonID, 1);
    } else {
        start = TerminalTIntlFirstLowercaseButtonID;
        end = TerminalTIntlLastLowercaseButtonID;
        PrvSetControlValue(TerminalTIntlCapsButtonID, 0);
        PrvSetControlValue(TerminalTIntlShiftButtonID, 0);
    }
    for (i = start; i <= end; i++) {
        PrvShowObjectByID(i);
    }
    PrvShowObjectByID(TerminalTIntlTabButtonID);
    PrvShowObjectByID(TerminalTIntlSpaceButtonID);
    PrvShowObjectByID(TerminalTIntlBackspaceButtonID);
    PrvShowObjectByID(TerminalTIntlReturnButtonID);
    PrvShowObjectByID(TerminalTIntlCapsButtonID);
    PrvShowObjectByID(TerminalTIntlShiftButtonID);
    SetBigShiftKey();
}


static void DestructTopIntlPane(void)
{
    int i;
    int count;

    // hide bitmap and buttons
    count = TerminalTIntlLastButtonID - TerminalTIntlFirstButtonID + 1;
    for (i = 0; i < count; i++) {
        PrvHideObjectByID(TerminalTIntlFirstButtonID + i);
    }
    PrvHideObjectByID(TerminalTIntlTabButtonID);
    PrvHideObjectByID(TerminalTIntlSpaceButtonID);
    PrvHideObjectByID(TerminalTIntlBackspaceButtonID);
    PrvHideObjectByID(TerminalTIntlReturnButtonID);
    PrvHideObjectByID(TerminalTIntlCapsButtonID);
    PrvHideObjectByID(TerminalTIntlShiftButtonID);
    PrvHideObjectByID(TerminalTIntlButtonsBitmapID);
}


static void ConstructTopEtcPane(void)
{
    int i;
    int start, end;
    int start2, end2;
    Boolean caps, num, autoshift;
    UInt16 shift;

    SetSmallVT100();

    // show bitmap and buttons
    PrvShowObjectByID(TerminalTEtcButtonsBitmapID);
    // choose caps based on Graffiti shift state
    GrfGetState(&caps, &num, &shift, &autoshift);
    if (caps) {
        // no caps in etc, just shift
        start = TerminalTEtcFirstUppercaseLatin1ButtonID;
        end = TerminalTEtcLastUppercaseLatin1ButtonID;
        start2 = TerminalTEtcFirstUppercaseFKeyButtonID;
        end2 = TerminalTEtcLastUppercaseFKeyButtonID;
        PrvSetControlValue(TerminalTABCCapsButtonID, 0);
        PrvSetControlValue(TerminalTEtcShiftButtonID, 1);
    } else if (shift) {
        start = TerminalTEtcFirstUppercaseLatin1ButtonID;
        end = TerminalTEtcLastUppercaseLatin1ButtonID;
        start2 = TerminalTEtcFirstUppercaseFKeyButtonID;
        end2 = TerminalTEtcLastUppercaseFKeyButtonID;
        PrvSetControlValue(TerminalTABCCapsButtonID, 0);
        PrvSetControlValue(TerminalTEtcShiftButtonID, 1);
    } else {
        start = TerminalTEtcFirstLowercaseLatin1ButtonID;
        end = TerminalTEtcLastLowercaseLatin1ButtonID;
        start2 = TerminalTEtcFirstLowercaseFKeyButtonID;
        end2 = TerminalTEtcLastLowercaseFKeyButtonID;
        PrvSetControlValue(TerminalTABCCapsButtonID, 0);
        PrvSetControlValue(TerminalTEtcShiftButtonID, 0);
    }
    for (i = start; i <= end; i++) {
        PrvShowObjectByID(i);
    }
    for (i = start2; i <= end2; i++) {
        PrvShowObjectByID(i);
    }
    PrvShowObjectByID(TerminalTEtcInsButtonID);
    PrvShowObjectByID(TerminalTEtcDelButtonID);
    PrvShowObjectByID(TerminalTEtcHomeButtonID);
    PrvShowObjectByID(TerminalTEtcEndButtonID);
    PrvShowObjectByID(TerminalTEtcPgUpButtonID);
    PrvShowObjectByID(TerminalTEtcPgDnButtonID);
    PrvShowObjectByID(TerminalTEtcShiftButtonID);
    SetSmallShiftKey();
}


static void DestructTopEtcPane(void)
{
    int i;
    int count;

    // hide bitmap and buttons
    count = TerminalTEtcLastButtonID - TerminalTEtcFirstButtonID + 1;
    for (i = 0; i < count; i++) {
        PrvHideObjectByID(TerminalTEtcFirstButtonID + i);
    }
    PrvHideObjectByID(TerminalTEtcInsButtonID);
    PrvHideObjectByID(TerminalTEtcDelButtonID);
    PrvHideObjectByID(TerminalTEtcHomeButtonID);
    PrvHideObjectByID(TerminalTEtcEndButtonID);
    PrvHideObjectByID(TerminalTEtcPgUpButtonID);
    PrvHideObjectByID(TerminalTEtcPgDnButtonID);
    PrvHideObjectByID(TerminalTEtcShiftButtonID);
    PrvHideObjectByID(TerminalTEtcButtonsBitmapID);
}


static void ConstructTopPane(top_pane_t newPane)
{
    switch (newPane) {
    case topVT100: ConstructTopVT100Pane(); return;
    case topABC:   ConstructTopABCPane();   return;
    case top123:   ConstructTop123Pane();   return;
    case topIntl:  ConstructTopIntlPane();  return;
    case topEtc:   ConstructTopEtcPane();   return;
    default:       ConstructTopVT100Pane(); return;
    }
}


static void DestructTopPane(top_pane_t newPane)
{
    switch (newPane) {
    case topVT100: DestructTopVT100Pane(); return;
    case topABC:   DestructTopABCPane();   return;
    case top123:   DestructTop123Pane();   return;
    case topIntl:  DestructTopIntlPane();  return;
    case topEtc:   DestructTopEtcPane();   return;
    default: return;
    }
}


static void SetTopPane(top_pane_t newPane)
{
    if (TopPane == newPane) {
        // nothing to do
    } else if (TopPane == topAll) {
        // Everything currently visible. Hide everything and show newPane.
        DestructTopPane(topVT100);
        DestructTopPane(topABC);
        DestructTopPane(top123);
        DestructTopPane(topIntl);
        DestructTopPane(topEtc);
        TopPane = newPane;
        PrefsPutInt(prefTopPane, TopPane);
        ConstructTopPane(TopPane);
    } else {
        // Hide current pane and show newPane.
        DestructTopPane(TopPane);
        TopPane = newPane;
        PrefsPutInt(prefTopPane, TopPane);
        ConstructTopPane(TopPane);
    }
}



static void ConstructBottomTCPane(void)
{
    int i;
    int count;

    // T|C:      hardware has no '&'
    // Treo 600: hardware has no '_' but does have '&'
    if (deviceTreo600) {
        PrvSetControlLabel(TerminalBTCAmpersandButtonID, "_");
    }

    PrvShowObjectByID(TerminalBTCButtonsBitmapID);
    count = TerminalBTCLastButtonID - TerminalBTCFirstButtonID + 1;
    for (i = 0; i < count; i++) {
        PrvShowObjectByID(TerminalBTCFirstButtonID + i);
    }
}

static void DestructBottomTCPane(void)
{
    int i;
    int count;

    PrvHideObjectByID(TerminalBTCButtonsBitmapID);
    count = TerminalBTCLastButtonID - TerminalBTCFirstButtonID + 1;
    for (i = 0; i < count; i++) {
        PrvHideObjectByID(TerminalBTCFirstButtonID + i);
    }
}


static void ConstructBottomFullPane(void)
{
    PrvShowObjectByID(TerminalBFullABCButtonID);
    PrvShowObjectByID(TerminalBFull123ButtonID);
    PrvShowObjectByID(TerminalBFullIntlButtonID);
    PrvShowObjectByID(TerminalBFullEtcButtonID);

    PrvShowObjectByID(TerminalBFullUpButtonID);
    PrvShowObjectByID(TerminalBFullDownButtonID);
    PrvShowObjectByID(TerminalBFullLeftButtonID);
    PrvShowObjectByID(TerminalBFullRightButtonID);
}

static void DestructBottomFullPane(void)
{
    PrvHideObjectByID(TerminalBFullABCButtonID);
    PrvHideObjectByID(TerminalBFull123ButtonID);
    PrvHideObjectByID(TerminalBFullIntlButtonID);
    PrvHideObjectByID(TerminalBFullEtcButtonID);

    PrvHideObjectByID(TerminalBFullUpButtonID);
    PrvHideObjectByID(TerminalBFullDownButtonID);
    PrvHideObjectByID(TerminalBFullLeftButtonID);
    PrvHideObjectByID(TerminalBFullRightButtonID);
}


static void ConstructBottomPane(bottom_pane_t newPane)
{
    switch (newPane) {
    case bottomTC:   ConstructBottomTCPane();   return;
    case bottomFull: ConstructBottomFullPane(); return;
        // case bottomCust: ConstructBottomCustPane(); return;
    default:         ConstructBottomFullPane(); return;
    }
}


static void DestructBottomPane(bottom_pane_t newPane)
{
    switch (newPane) {
    case bottomTC:   DestructBottomTCPane();   return;
    case bottomFull: DestructBottomFullPane(); return;
        // case bottomCust: DestructBottomCustPane(); return;
    default: return;
    }
}


static void SetBottomPane(bottom_pane_t newPane)
{
    if (BottomPane == newPane) {
        // nothing to do
    } else if (BottomPane == bottomAll) {
        // Everything currently visible. Hide everything and show newPane
        DestructBottomPane(bottomTC);
        DestructBottomPane(bottomFull);
        DestructBottomPane(bottomCust);
        BottomPane = newPane;
        PrefsPutInt(prefBottomPane, BottomPane);
        ConstructBottomPane(BottomPane);
    } else {
        // Hide current pane and show newPane
        DestructBottomPane(BottomPane);
        BottomPane = newPane;
        PrefsPutInt(prefBottomPane, BottomPane);
        ConstructBottomPane(BottomPane);
    }
}


static void ShowABCPanes(void)
{
    SetTopPane(topABC);
    SetBottomPane(bottomFull);
    
    PrvSetControlValue(TerminalBFullABCButtonID, 1);
    PrvSetControlValue(TerminalBFull123ButtonID, 0);
    PrvSetControlValue(TerminalBFullIntlButtonID, 0);
    PrvSetControlValue(TerminalBFullEtcButtonID, 0);

    FrmDrawForm(FrmGetActiveForm());
}

static void Show123Panes(void)
{
    SetTopPane(top123);
    SetBottomPane(bottomFull);
    
    PrvSetControlValue(TerminalBFullABCButtonID, 0);
    PrvSetControlValue(TerminalBFull123ButtonID, 1);
    PrvSetControlValue(TerminalBFullIntlButtonID, 0);
    PrvSetControlValue(TerminalBFullEtcButtonID, 0);

    FrmDrawForm(FrmGetActiveForm());
}


static void ShowIntlPanes(void)
{
    SetTopPane(topIntl);
    SetBottomPane(bottomFull);
    
    PrvSetControlValue(TerminalBFullABCButtonID, 0);
    PrvSetControlValue(TerminalBFull123ButtonID, 0);
    PrvSetControlValue(TerminalBFullIntlButtonID, 1);
    PrvSetControlValue(TerminalBFullEtcButtonID, 0);

    FrmDrawForm(FrmGetActiveForm());
}


static void ShowEtcPanes(void)
{
    SetTopPane(topEtc);
    SetBottomPane(bottomFull);
    
    PrvSetControlValue(TerminalBFullABCButtonID, 0);
    PrvSetControlValue(TerminalBFull123ButtonID, 0);
    PrvSetControlValue(TerminalBFullIntlButtonID, 0);
    PrvSetControlValue(TerminalBFullEtcButtonID, 1);

    FrmDrawForm(FrmGetActiveForm());
}


// Hide ABC, 123, Intl, etc
// but leave bottom full intact
static void HideABC123Panes(void)
{
    SetTopPane(topVT100);
    SetBottomPane(bottomFull);
    
    PrvSetControlValue(TerminalBFullABCButtonID, 0);
    PrvSetControlValue(TerminalBFull123ButtonID, 0);
    PrvSetControlValue(TerminalBFullIntlButtonID, 0);
    PrvSetControlValue(TerminalBFullEtcButtonID, 0);

    FrmDrawForm(FrmGetActiveForm());
}




static unsigned char control_character(unsigned char c)
{
    if (c == ' '  ||  c == '@') return '\0';        // C-space, C-@
    if (c >= 'a'  &&  c <= 'z') return c - 'a' + 1; // C-a .. C-z
    if (c >= 'A'  &&  c <= 'Z') return c - 'A' + 1; // C-A .. C-Z
    if (c >= '['  &&  c <= '_') return c - 'A' + 1; // C-[ .. C-_
    if (c >= '{'  &&  c <= '}') return c - 'a' + 1; // C-{ .. C-} (no C-~)
    return c;
}

// ctrl or shift locked down (i.e. don't release after sending a key)
static Boolean ctrlLocked = 0;
static Boolean shiftLocked = 0;

static Boolean jogPressed = 0;
static Boolean jogPressedCtrl = 0;
static Boolean jogPressedMeta = 0;
static Boolean keyPressedDuringJogPress = 0;
static Boolean jogMovedDuringJogPress = 0;


static Boolean CtrlPressed(void)
{
    return PrvGetControlValue(TerminalCtrlButtonID);
}

static Boolean ShiftPressed(void)
{
    if (TopPane == topABC) {
        return PrvGetControlValue(TerminalTABCShiftButtonID);
    } else {
        return shiftLocked;
    }
}

static Boolean MetaPressed(void)
{
    // nothing here yet
    return 0;
}

static void CtrlPress(Boolean down, Boolean lockDown)
{
    PrvSetControlValue(TerminalCtrlButtonID, down);
    if (down) ctrlLocked = lockDown;
    else ctrlLocked = 0;
}

static void ShiftPress(Boolean down, Boolean lockDown)
{
    static Boolean oldDown = 0;
    static Boolean oldLock = 0;

    // prevent frequent SetABCCase() which causes redraw blinking
    if (down == oldDown  &&  lockDown == oldLock) return;
    oldDown = down;
    oldLock = lockDown;

    if (TopPane == topABC  ||  TopPane == topIntl  ||  TopPane == topEtc) {
        PrvSetControlValue(TerminalTABCShiftButtonID, down);
        SetABCCase(down ? caseUpper : caseLower);
    }
    if (down) shiftLocked = lockDown;
    else shiftLocked = 0;
}

static void MetaPress(Boolean down, Boolean lockDown)
{
    // nothing here yet
}

static void SendModifiedKey(unsigned char c)
{
    Boolean ctrlPressed = CtrlPressed();

    if (ctrlPressed) {
        c = control_character(c);
    }

    ClearModifiers();
    if (jogPressed) keyPressedDuringJogPress = true;
    ssh_key(ss, c);
}


static void ClearModifiers(void)
{
    if (!ctrlLocked) CtrlPress(0, 0);
    if (!shiftLocked && !PrvGetControlValue(TerminalTABCCapsButtonID)) {
        ShiftPress(0, 0);
    }
}


static void SendUpArrowKey(void)
{
    ClearModifiers();
    if (ssh_app_cursor_keys(ss)) 
        ssh_keys(ss, "\eOA", 3);
    else 
        ssh_keys(ss, "\e[A", 3);
}

static void SendDownArrowKey(void)
{
    ClearModifiers();
    if (ssh_app_cursor_keys(ss)) 
        ssh_keys(ss, "\eOB", 3);
    else 
        ssh_keys(ss, "\e[B", 3);
}

static void SendRightArrowKey(void)
{
    ClearModifiers();
    if (ssh_app_cursor_keys(ss)) 
        ssh_keys(ss, "\eOC", 3);
    else 
        ssh_keys(ss, "\e[C", 3);
}

static void SendLeftArrowKey(void)
{
    ClearModifiers();
    if (ssh_app_cursor_keys(ss)) 
        ssh_keys(ss, "\eOD", 3);
    else 
        ssh_keys(ss, "\e[D", 3);
}


static void SendFKey(UInt16 id)
{
    char buf[6];
    char i = (char)(id - TerminalTEtcFirstFKeyButtonID + 1);
    // i is 1..20

    // fixme other fkey modes would go here
    // this implementation is PuTTY's "default" mode

    // F5 => ESC [ 15 ~  ..  F20 => ESC [ 34 ~
    // but there are gaps in the middle
    switch (i) {
    case 1:  i = 11; break;
    case 2:  i = 12; break;
    case 3:  i = 13; break;
    case 4:  i = 14; break;
    case 5:  i = 15; break;
        
    case 6:  i = 17; break;
    case 7:  i = 18; break;
    case 8:  i = 19; break;
    case 9:  i = 20; break;
    case 10: i = 21; break;
        
    case 11: i = 23; break;
    case 12: i = 24; break;
    case 13: i = 25; break;
    case 14: i = 26; break;
        
    case 15: i = 28; break;
    case 16: i = 29; break;
        
    case 17: i = 31; break;
    case 18: i = 32; break;
    case 19: i = 33; break;
    case 20: i = 34; break;
    }
    snprintf(buf, sizeof(buf), "\e[%d~", i);

    ClearModifiers();
    ssh_keys(ss, buf, strlen(buf));
}


static void SendScrollpadKey(UInt16 id)
{
    char buf[5];
    // unaffected by application mode
    snprintf(buf, sizeof(buf), "\e[%d~", id - TerminalTEtcHomeButtonID + 1);
    ClearModifiers();
    ssh_keys(ss, buf, strlen(buf));
}


static char Backspace(void)
{
    if (PrefsGetInt(prefBackspace, defaultBackspace) == backspaceBS) {
        return 8;    // ascii BS
    } else {
        return 127;  // ascii DEL
    }
}


static Boolean HandleCtlSelectEvent(EventPtr e)
{
    UInt16 id = e->data.ctlSelect.controlID;

    // Check for self-labeled controls
    if ((id >= TerminalBTCFirstButtonID  && id <= TerminalBTCLastButtonID)   ||
        (id >= TerminalTABCFirstButtonID && id <= TerminalTABCLastButtonID)  ||
        (id >= TerminalT123FirstButtonID && id <= TerminalT123LastButtonID)  ||
        (id >= TerminalTIntlFirstButtonID && id <= TerminalTIntlLastButtonID)||
        (id >= TerminalTEtcFirstLatin1ButtonID && 
         id <= TerminalTEtcLastLatin1ButtonID))
    {
        const char *text = CtlGetLabel(e->data.ctlSelect.pControl);
        if (text && text[0]) SendModifiedKey(text[0]);
        return false;
    }

    // Check for FKeys
    if ((id >= TerminalTEtcFirstFKeyButtonID && 
         id <= TerminalTEtcLastFKeyButtonID))
    {
        SendFKey(id);
        return false;
    }

    switch (id) {
    case TerminalCtrlButtonID:
        // nothing to do
        return false;

    case TerminalMetaButtonID:
        // send esc immediately
        ClearModifiers();
        ssh_key(ss, '\e');
        return false;

    case TerminalTEtcInsButtonID:
    case TerminalTEtcDelButtonID:
    case TerminalTEtcHomeButtonID:
    case TerminalTEtcEndButtonID:
    case TerminalTEtcPgUpButtonID:
    case TerminalTEtcPgDnButtonID:
        SendScrollpadKey(id);
        return false;

    case TerminalTABCTabButtonID:
    case TerminalT123TabButtonID:
        SendModifiedKey('\t');
        return false;

    case TerminalTABCSpaceButtonID:
    case TerminalT123SpaceButtonID:
        SendModifiedKey(' ');
        return false;

    case TerminalTABCBackspaceButtonID:
    case TerminalT123BackspaceButtonID:
        SendModifiedKey(Backspace());
        return false;

    case TerminalTABCReturnButtonID:
    case TerminalT123ReturnButtonID:
        SendModifiedKey(13); // CR not LF
        return false;

    case TerminalTABCShiftButtonID:
        if (PrvGetControlValue(TerminalTABCShiftButtonID)) {
            SetABCCase(caseUpper);
        } else {
            SetABCCase(caseLower);
        }
        return false;

    case TerminalTABCCapsButtonID:
        if (PrvGetControlValue(TerminalTABCCapsButtonID)) {
            SetABCCase(caseCaps);
        } else {
            SetABCCase(caseLower);
        }
        return false;

    case TerminalBFullABCButtonID:
        if (PrvGetControlValue(TerminalBFullABCButtonID)) {
            ShowABCPanes();
        } else {
            HideABC123Panes();
        }
        return false;

    case TerminalBFull123ButtonID:
        if (PrvGetControlValue(TerminalBFull123ButtonID)) {
            Show123Panes();
        } else {
            HideABC123Panes();
        }
        return false;

    case TerminalBFullIntlButtonID:
        if (PrvGetControlValue(TerminalBFullIntlButtonID)) {
            ShowIntlPanes();
        } else {
            HideABC123Panes();
        }
        return false;

    case TerminalBFullEtcButtonID:
        if (PrvGetControlValue(TerminalBFullEtcButtonID)) {
            ShowEtcPanes();
        } else {
            HideABC123Panes();
        }
        return false;

    case TerminalCloseBoxButtonID:
        SetTitleBar(!titleShown);
        FrmDrawForm(FrmGetActiveForm());
        return false;

    default:
        return false;
    }
}


static void TrackDrag(EventPtr e)
{
    Boolean moved = 0;
    Boolean stillDown;
    Coord x, y;
    static RectangleType slopRect = {{0, 0}, {0, 0}};
    static int clickCount = 0;
    static UInt32 lastClickTime = 0;
    UInt32 now;

    // fixme drag stops all network events

    x = e->screenX;
    y = e->screenY;

    // See whether this click counts as a multi-click
    now = TimGetTicks();
    if (RctPtInRectangle(x, y, &slopRect)  &&  
        now - lastClickTime < SysTicksPerSecond() / 3)
    {
        clickCount++;
        // keep same slop rect
    } else {
        clickCount = 1;
        slopRect.topLeft.x = x - 1;
        slopRect.topLeft.y = y - 1;
        slopRect.extent.x = 3;
        slopRect.extent.y = 3;
    }
    lastClickTime = now;

    // click
    ssh_click(ss, x, y, clickCount);
    ssh_click(ss, x, y, -1);

    while (true) {
        SysTaskDelay(SysTicksPerSecond() / 30); // don't spin too fast
        PenGetPoint(&x, &y, &stillDown);
        if (!stillDown) break;

        if (!moved) moved = !RctPtInRectangle(x, y, &slopRect);
        if (moved) {
            // drag
            ssh_click(ss, x, y, -1);
        }
    }

    // release
    ssh_click(ss, x, y, 0);
}


static void SetScrollBar(FormPtr frmP, SetScrollBarEventType *se)
{
    int total = se->data.sbar.total;
    int start = se->data.sbar.start;
    int page = se->data.sbar.page;
    Boolean newEnabled;
    Boolean oldEnabled;
    
    // enable or disable up arrows
    newEnabled = (start != 0);
    oldEnabled = PrvGetControlEnabled(TerminalRScrollLineUpButtonID);
    if (newEnabled != oldEnabled) {
        PrvSetControlEnabled(TerminalRScrollLineUpButtonID, newEnabled);
        PrvSetControlEnabled(TerminalRScrollPageUpButtonID, newEnabled);
        if (newEnabled) {
            PrvSetControlGraphics(TerminalRScrollLineUpButtonID, TerminalRScrollLineUpBitmapID, NULL);
            PrvSetControlGraphics(TerminalRScrollPageUpButtonID, TerminalRScrollPageUpBitmapID, NULL);
        } else {
            PrvSetControlGraphics(TerminalRScrollLineUpButtonID, TerminalRScrollLineUpDisabledBitmapID, NULL);
            PrvSetControlGraphics(TerminalRScrollPageUpButtonID, TerminalRScrollPageUpDisabledBitmapID, NULL);
        }
    }

    // enable or disable down arrows
    newEnabled = (total > start + page);
    oldEnabled = PrvGetControlEnabled(TerminalRScrollLineDownButtonID);
    if (newEnabled != oldEnabled) {
        PrvSetControlEnabled(TerminalRScrollLineDownButtonID, newEnabled);
        PrvSetControlEnabled(TerminalRScrollPageDownButtonID, newEnabled);
        if (newEnabled) {
            PrvSetControlGraphics(TerminalRScrollLineDownButtonID, TerminalRScrollLineDownBitmapID, 0);
            PrvSetControlGraphics(TerminalRScrollPageDownButtonID, TerminalRScrollPageDownBitmapID, 0);
        } else {
            PrvSetControlGraphics(TerminalRScrollLineDownButtonID, TerminalRScrollLineDownDisabledBitmapID, 0);
            PrvSetControlGraphics(TerminalRScrollPageDownButtonID, TerminalRScrollPageDownDisabledBitmapID, 0);
        }
    }
}


static Boolean VT100EventHandler(struct FormGadgetTypeInCallback *gadget, 
                                 UInt16 cmd, void *paramP)
{
    Boolean handled = false; 

    switch (cmd) { 
    case formGadgetDrawCmd: 
        // Sent to active gadgets any time form is  
        // drawn or redrawn. 
        if (ss) {
            ssh_activate(ss);
            ssh_update(ss);
        }
        
        gadget->attr.visible = true;
        handled = true; 
        break; 

    case formGadgetHandleEventCmd: {
        EventPtr e = (EventPtr)paramP;
        // Sent when form receives a gadget event.  
        // paramP points to EventType structure.  
        if (e->eType == frmGadgetEnterEvent) { 
            // penDown in gadget's bounds.  
            UInt16 index;
            FormPtr frmP = FrmGetActiveForm();
            if ((index = FrmGetFocus(frmP)) != noFocus) {
                // selection in vt100 disables selection in real fields
                // fixme this might fail if there were a focused table
                FldSetSelection((FieldPtr)FrmGetObjectPtr(frmP, index), 0, 0);
                FrmSetFocus(frmP, noFocus);
            }
            TrackDrag(e);
            handled = true; 
        } else if (e->eType == frmGadgetMiscEvent) { 
            // This event is sent by your application 
            // when it needs to send info to the gadget 
        } 
        break; 
    }

    case formGadgetDeleteCmd:  
        // Perform any cleanup prior to deletion. 
        break; 

    case formGadgetEraseCmd:  
        // FrmHideObject takes care of this if you  
        // return false.  
        handled = false;
        break; 

    default:
        handled = false;
        break;
    } 

    return handled; 
} 


static Boolean HandleCtlRepeatEvent(EventPtr e)
{
    switch (e->data.ctlSelect.controlID) {
    case TerminalRScrollPageUpButtonID:
        if (ss) ssh_scroll(ss, -(ssh_visible_height(ss)-1));
        return false;

    case TerminalRScrollPageDownButtonID:
        if (ss) ssh_scroll(ss, ssh_visible_height(ss)-1);
        return false;
        
    case TerminalRScrollLineUpButtonID:
        if (ss) ssh_scroll(ss, -1);
        return false;
        
    case TerminalRScrollLineDownButtonID:
        if (ss) ssh_scroll(ss, 1);
        return false;

        // fixme these repeating buttons don't work as smoothly as the 5-way
    case TerminalBFullUpButtonID:
        // case TerminalRArrowsUpButtonID:
        SendUpArrowKey();
        return false;

    case TerminalBFullDownButtonID:
        // case TerminalRArrowsDownButtonID:
        SendDownArrowKey();
        return false;

    case TerminalBFullLeftButtonID:
        // case TerminalRArrowsLeftButtonID:
        SendLeftArrowKey();
        return false;

    case TerminalBFullRightButtonID:
        // case TerminalRArrowsRightButtonID:
        SendRightArrowKey();
        return false;

    default: 
        return false;
    }
}


Boolean TerminalFormHandleEvent(EventPtr e)
{
    FormPtr frmP = FrmGetActiveForm();

    switch (e->eType) {
    case frmOpenEvent:
        // reset display states to default
        TopPane = -1;
        BottomPane = -1;
        RightPane = -1;
        inputShown = true;
        titleShown = true;
        smallVT100 = false;
        ctrlLocked = 0;
        shiftLocked = 0;
        jogPressed = 0;
        jogPressedCtrl = 0;
        jogPressedMeta = 0;
        keyPressedDuringJogPress = 0;
        jogMovedDuringJogPress = 0;

        // check for device-specific hacks
        if (!devicesChecked) {
            UInt32 deviceID;
            UInt32 companyID;
            if (FtrGet(sysFtrCreator, sysFtrNumOEMDeviceID, &deviceID) == 0  &&
                FtrGet(sysFtrCreator, sysFtrNumOEMCompanyID, &companyID) == 0)
            {
                deviceTreo600 = (companyID == 'hspr'  &&  deviceID == 'H101');
            }
            devicesChecked = true;
        }

        // set up virtual graffiti support and initial keyboard state
        CollapseSetState(frmP, collapseStateUser);
        FrmSetGadgetHandler(FrmGetActiveForm(), 
                            PrvGetObjectIndexByID(TerminalTextGadgetID), 
                            VT100EventHandler);
        TerminalFormResize(frmP);
        SetTopPane(PrefsGetInt(prefTopPane, topVT100));
        SetBottomPane(PrefsGetInt(prefBottomPane, bottomFull));
        SetTitleBar(! PrefsGetInt(prefHideTitleBar, 0));
        if (TopPane == topABC) {
            PrvSetControlValue(TerminalBFullABCButtonID, 1);
        } else if (TopPane == top123) {
            PrvSetControlValue(TerminalBFull123ButtonID, 1);
        } else if (TopPane == topIntl) {
            PrvSetControlValue(TerminalBFullIntlButtonID, 1);
        } else if (TopPane == topEtc) {
            PrvSetControlValue(TerminalBFullEtcButtonID, 1);
        }

        //SetRightPane(rightScroll);
        FrmDrawForm(FrmGetActiveForm());

        if (!StartConnection()) {
            FrmGotoForm(MainFormID);
            return true;
        }

        FrmDrawForm(FrmGetActiveForm());
        return true;

    case menuEvent:
        switch (e->data.menu.itemID) {
        case MenuDisconnect:
            FrmGotoForm(MainFormID);
            return true;
        case MenuAboutPalmSSH:
            FrmPopupForm(AboutFormID);
            return true;
        case MenuCredits:
            FrmPopupForm(CreditsFormID);
            return true;
        case MenuDisplaySettings:
            FrmPopupForm(DisplayPrefsFormID);
            return true;
        case MenuKeyboardSettings:
            FrmPopupForm(KeyboardPrefsFormID);
            return true;
        case MenuCtrl:
            if (CtrlPressed()) CtrlPress(0, 0);
            else CtrlPress(1, 0);
            return true;
        case MenuEsc:
            ClearModifiers();
            ssh_key(ss, '\e');
            return true;
        case sysEditMenuUndoCmd:
            // no undo in vt100 - send to fields
            return false;
        case sysEditMenuCutCmd:
            // no cut in vt100 - copy will do
            // FALL-THROUGH to copy
        case sysEditMenuCopyCmd:
            if (FrmGetFocus(FrmGetActiveForm()) == noFocus) {
                ssh_copy(ss);
                // don't send to other fields
                return true; 
            } else {
                return false;
            }
        case sysEditMenuPasteCmd:
            if (FrmGetFocus(FrmGetActiveForm()) == noFocus) {
                ssh_paste(ss);
                // don't send to other fields
                return true;
            } else {
                // let real fields handle it
                return false;
            }
        case sysEditMenuKeyboardCmd:
            if (TopPane != topABC) {
                ShowABCPanes();
            } else {
                HideABC123Panes();
            }
            return true;
        case sysEditMenuGraffitiCmd:
            SysGraffitiReferenceDialog(referenceDefault);
            return true;
        case MenuTCKeyboard:
            SetTopPane(topVT100);
            SetBottomPane(bottomTC);
            FrmDrawForm(FrmGetActiveForm());
            return true;
        case MenuToggleTitle:
            SetTitleBar(!titleShown);
            FrmDrawForm(FrmGetActiveForm());
            return true;

        default:
            return false;
        }

    case menuCmdBarOpenEvent: {
        UInt16 len;
        // fixme buttons get added in wrong order if hostname field has 
        // a selection (need to override default field buttons)
        // add "paste" button if there is clipboard content
        if (ClipboardGetItem(clipboardText, &len)) {
            MenuCmdBarAddButton(menuCmdBarOnLeft, BarPasteBitmap, menuCmdBarResultMenuItem, sysEditMenuPasteCmd, NULL);
        }
        // add "copy" button if there is a terminal selection
        if (ssh_selection_exists(ss)) {
            MenuCmdBarAddButton(menuCmdBarOnLeft, BarCopyBitmap, menuCmdBarResultMenuItem, sysEditMenuCopyCmd, NULL);
        }
        return false;
    }

    case frmCloseEvent:
        if (ss) {
            ssh_close(ss);
            ssh_free(ss);
            ss = NULL;
        }
        return false;

    case fldEnterEvent:
        // selection in real field disables selection in vt100
        if (ss) {
            ssh_deselect(ss);
        }
        return false;

    case ctlRepeatEvent:
        return HandleCtlRepeatEvent(e);
        
    case ctlSelectEvent:
        return HandleCtlSelectEvent(e);

    case keyDownEvent: {
        WChar chr = e->data.keyDown.chr;

        if (!EvtKeydownIsVirtual(e)) {
            unsigned char c = (unsigned char)chr;
            
            // send CR rather than LF
            if (c == 10) c = 13;
            // remap DEL or BS based on backspace configuration
            if (c == 8  ||  c == 127) c = Backspace();

            // send arrow key control codes rather than ASCII values
            if (c == 28)      SendLeftArrowKey();
            else if (c == 29) SendRightArrowKey();
            else if (c == 30) SendUpArrowKey();
            else if (c == 31) SendDownArrowKey();

            // handle ctrl modifier on external keyboard
            else if (e->data.keyDown.modifiers & controlKeyMask) SendModifiedKey(control_character(c));

            else SendModifiedKey(c);

            return true;
        } 

        // Treo 600 5-way hack: translate PageUp/Down into RockerUp/Down
        if (deviceTreo600  &&  chr == vchrPageUp) {
            chr = vchrRockerUp;
        }
        if (deviceTreo600  &&  chr == vchrPageDown) {
            chr = vchrRockerDown;
        }


        // Handle interesting vchrs

        if (chr == vchrKeyboard  ||  chr == vchrKeyboardAlpha)
        {
            if (TopPane != topABC) {
                ShowABCPanes();
            } else {
                HideABC123Panes();
            }
            return true;
        }
        else if (chr == vchrKeyboardNumeric)
        {
            if (TopPane != top123) {
                Show123Panes();
            } else {
                HideABC123Panes();
            }
            return true;
        }
        else if (chr == 0x51d)
        {
            // Tungsten|C fn-space (usually runs sys keyboard in symbol pane)
            // Here toggle between full(123) and T|C
            if (BottomPane != bottomFull) {
                Show123Panes();
            } else {
                SetTopPane(topVT100);
                SetBottomPane(bottomTC);
                FrmDrawForm(FrmGetActiveForm());
            }
            return true;
        }
        else if (chr == vchrThumbWheelUp  ||  chr == vchrJogUp) {
            // scroll up (fixme how far?)
            if (ss) ssh_scroll(ss, -1);
            return true;
        }
        else if (chr == vchrThumbWheelDown  ||  chr == vchrJogDown) {
            // scroll down (fixme how far?)
            if (ss) ssh_scroll(ss, +1);
            return true;
        }
        else if (chr == vchrJogPushedUp) {
            // scroll up
            if (ss) ssh_scroll(ss, -(ssh_visible_height(ss)-1));
            if (CtrlPressed()) CtrlPress(0, 0); // set in JogPush
            if (MetaPressed()) MetaPress(0, 0);
            jogMovedDuringJogPress = 1;
            return true;
        }
        else if (chr == vchrJogPushedDown) {
            // scroll down (fixme how far?)
            if (ss) ssh_scroll(ss, ssh_visible_height(ss)-1);
            if (CtrlPressed()) CtrlPress(0, 0); // set in JogPush
            if (MetaPressed()) MetaPress(0, 0);
            jogMovedDuringJogPress = 1;
            return true;
        }
        else if (NavSelectPressed(e)  ||  
                 chr == vchrRockerCenter  ||  
                 chr == vchrThumbWheelPush)
        {
            // Palm 5-way / thumb wheel push == once for ctrl, twice for esc
            int ctrlWasPressed = CtrlPressed();

            if (ctrlWasPressed) {
                // clear ctrl and send esc
                ClearModifiers();
                ssh_key(ss, '\e');
            } else if (chr == vchrJogPress) {
                CtrlPress(1, 1);  // lock ctrl until jog release
            } else {
                CtrlPress(1, 0);
            }
            return true;
        }
        else if (chr == vchrJogPress) 
        {
            // jog wheel push = ctrl/esc and page scroll
            jogPressed = 1;
            keyPressedDuringJogPress = 0;
            jogMovedDuringJogPress = 0;
            if (!CtrlPressed()) {
                CtrlPress(1, 1); // lock until further notice
                jogPressedCtrl = 1;
                jogPressedMeta = 0;
            } else {
                MetaPress(1, 1); // lock until further notice
                jogPressedCtrl = 0;
                jogPressedMeta = 1;
            }
        }
        else if (chr == vchrJogRelease) 
        {
            if (jogPressedCtrl  &&  CtrlPressed()) {
                if (jogMovedDuringJogPress  ||  keyPressedDuringJogPress) {
                    // scroll or ctrl-key sent - release ctrl
                    CtrlPress(0, 0);
                } else {
                    // jog press and release - unlock ctrl but leave it set
                    CtrlPress(1, 0);
                }
            }
            else if (jogPressedMeta  /* &&  MetaPressed() */) {
                if (jogMovedDuringJogPress  ||  keyPressedDuringJogPress) {
                    // scroll or meta-key sent - release meta
                    MetaPress(0, 0);
                } else {
                    // jog press and release - release meta and send esc
                    MetaPress(0, 0);
                    ssh_key(ss, '\e');
                }
            }
            return true;
        }
        else if (NavDirectionPressed(e, Up)  ||  chr == vchrRockerUp) 
        {
            ClearModifiers();
            SendUpArrowKey();
            return true;
        } 
        else if (NavDirectionPressed(e, Down)  ||  chr == vchrRockerDown) {
            ClearModifiers();
            SendDownArrowKey();
            return true;
        } 
        else if (NavDirectionPressed(e, Right)  ||  chr == vchrRockerRight) {
            ClearModifiers();
            SendRightArrowKey();
            return true;
        } 
        else if (NavDirectionPressed(e, Left)  ||  chr == vchrRockerLeft) {
            ClearModifiers();
            SendLeftArrowKey();
            return true;
        }
        return false;
    }

    case winExitEvent:
        if ((FormPtr)e->data.winExit.exitWindow == 
            FrmGetFormPtr(TerminalFormID))
        {
            TerminalWindowActive = false;
            if (ss) ssh_deactivate(ss);
            return true;
        }
        return false;

    case winEnterEvent:
        if ((FormPtr)e->data.winEnter.enterWindow == 
            FrmGetFormPtr(TerminalFormID))
        {
            if (ss) ssh_activate(ss);
            TerminalWindowActive = true;
            return true;
        }
        return false;

    case winDisplayChangedEvent:
        PrvHandleDisplayChange(frmP, TerminalFormResize(frmP));
        return false;

    case usrDrawVT100Event:
        if (ss) ssh_update(ss);
        return true;

    case usrDrawCloseBoxEvent:
        // draw close box if it should be visible
        // fixme test for status bar too?
        if (!titleShown) {
            PrvDrawControl(TerminalCloseBoxButtonID);
        }
        return true;

    case usrSetScrollBarEvent: {
        SetScrollBarEventType *se = (SetScrollBarEventType *)e;
        SetScrollBar(frmP, se);
        return true;
    }


    default:
        return false;
    }
}


static Boolean IsStealableVchr(EventPtr e)
{
    if (e->data.keyDown.chr == vchrKeyboard) return true;
    if (e->data.keyDown.chr == vchrKeyboardAlpha) return true;
    if (e->data.keyDown.chr == vchrKeyboardNumeric) return true;
    if (e->data.keyDown.chr == 0x51d) return true;
    if (e->data.keyDown.chr == vchrJogPushedDown  &&  TerminalWindowActive) return true;
    if (e->data.keyDown.chr == vchrJogPushedUp  &&  TerminalWindowActive) return true;
    if (e->data.keyDown.chr == vchrRockerDown  &&  TerminalWindowActive) return true;
    if (e->data.keyDown.chr == vchrRockerUp  &&  TerminalWindowActive) return true;
    if (e->data.keyDown.chr == vchrJogDown  &&  TerminalWindowActive) return true;
    if (e->data.keyDown.chr == vchrJogUp  &&  TerminalWindowActive) return true;

    return false;
}

// Terminal form wants to catch some key events before the system uses them:
// - keyboard vchrs to display its own keyboard
// - Sony jog wheel and Treo 5-way vchrs
Boolean TerminalFormStealEvent(EventPtr e)
{
    // don't steal if terminal is not active, or if event is not key down
    if (FrmGetActiveFormID() != TerminalFormID) return false;
    if (e->eType != keyDownEvent) return false;

    // steal some virtual chars anytime
    if (EvtKeydownIsVirtual(e)  &&  IsStealableVchr(e)) return true;

    // don't steal
    return false;
}


// Resize the terminal form 
static Boolean TerminalFormResize(FormPtr frmP) 
{
    Int16 dh, dv;
    UInt16 index;
    Boolean changed = CollapseResizeForm(frmP, false, &dh, &dv);

    if (!changed) return false;

    // fixme only handles vertical well
        
    // hostname field: resize to width
    // close box: move horizontally
    // vt100 gadget: resize to width and height
    // everything else: move vertically
    
    for (index = 0; index < FrmGetNumberOfObjects(frmP); index++) {
        UInt16 id = FrmGetObjectId(frmP, index);
        FormObjectKind type = FrmGetObjectType(frmP, index);
        if (id == frmInvalidObjectId && type != frmGraffitiStateObj) continue;
        
        if (id == TerminalHostFieldID) {
            // fixme
        } else if (id == TerminalTextGadgetID) {
            RectangleType bounds;
            FrmGetObjectBounds(frmP, index, &bounds);
            bounds.extent.x += dh;
            bounds.extent.y += dv;
            SetVT100Bounds(bounds);
        } else if (id == TerminalCloseBoxButtonID) {
            CollapseMoveResizeFormObject(frmP, id, dh, 0, 0, 0);
        } else {
            switch (type) {
            case frmBitmapObj:
                CollapseMoveFormBitmapObject(frmP, id, 0, dv);
                break;
            case frmGraffitiStateObj: {
                // FrmSetObjectPosition is insufficient for Gsi
                Coord x, y;
                GsiEnable(false);
                FrmGetObjectPosition(frmP, index, &x, &y);
                y += dv;
                FrmSetObjectPosition(frmP, index, x, y);
                GsiSetLocation(x, y);
                GsiEnable(true);
                break;
            }
            default:
                CollapseMoveResizeFormObject(frmP, id, 0, dv, 0, 0);
                break;
            }
        }
    }

    return true;
}
