From 983703190603caa30a8b1a65f70772dd5f9c09d6 Mon Sep 17 00:00:00 2001 From: John Wiseman Date: Tue, 30 Aug 2022 11:28:28 +0100 Subject: [PATCH] First GIT Commit --- AGWCode.cpp | 1546 ++++++++++ AGWConnect.ui | 148 + AGWParams.ui | 395 +++ ColourConfig.ui | 440 +++ KISSConfig.ui | 381 +++ ListenPort.ui | 140 + QtSoundModem.ini | 99 + QtTermTCP.aps | Bin 0 -> 125896 bytes QtTermTCP.cpp | 6146 +++++++++++++++++++++++++++++++++++++ QtTermTCP.h | 260 ++ QtTermTCP.ico | Bin 0 -> 123113 bytes QtTermTCP.ini | 116 + QtTermTCP.pro | 48 + QtTermTCP.pro.bak | 38 + QtTermTCP.qrc | 5 + QtTermTCP.rc | Bin 0 -> 5202 bytes QtTermTCP.sln | 25 + QtTermTCP.ui | 66 + QtTermTCP.vcxproj | 267 ++ QtTermTCP.vcxproj.filters | 131 + QtTermTCP.vcxproj.user | 16 + QtTermTCP52.zip | Bin 0 -> 251141 bytes QtTermTCP_resource.rc | 37 + TCPHostConfig.ui | 85 + TabDialog.cpp | 1127 +++++++ TabDialog.h | 208 ++ TermTCPCommon.cpp | 1091 +++++++ UZ7HOUtils.c | 320 ++ VARA.ui | 148 + VARAConfig.ui | 566 ++++ ax25.c | 2987 ++++++++++++++++++ ax25.h | 304 ++ ax25_l2.c | 1726 +++++++++++ hid.c | 910 ++++++ hidapi.h | 386 +++ icon1.ico | Bin 0 -> 45451 bytes icon2.ico | Bin 0 -> 45451 bytes main.cpp | 26 + makeit | 9 + ui_TCPHostConfig.h | 65 + utf8Routines.cpp | 597 ++++ 41 files changed, 20859 insertions(+) create mode 100644 AGWCode.cpp create mode 100644 AGWConnect.ui create mode 100644 AGWParams.ui create mode 100644 ColourConfig.ui create mode 100644 KISSConfig.ui create mode 100644 ListenPort.ui create mode 100644 QtSoundModem.ini create mode 100644 QtTermTCP.aps create mode 100644 QtTermTCP.cpp create mode 100644 QtTermTCP.h create mode 100644 QtTermTCP.ico create mode 100644 QtTermTCP.ini create mode 100644 QtTermTCP.pro create mode 100644 QtTermTCP.pro.bak create mode 100644 QtTermTCP.qrc create mode 100644 QtTermTCP.rc create mode 100644 QtTermTCP.sln create mode 100644 QtTermTCP.ui create mode 100644 QtTermTCP.vcxproj create mode 100644 QtTermTCP.vcxproj.filters create mode 100644 QtTermTCP.vcxproj.user create mode 100644 QtTermTCP52.zip create mode 100644 QtTermTCP_resource.rc create mode 100644 TCPHostConfig.ui create mode 100644 TabDialog.cpp create mode 100644 TabDialog.h create mode 100644 TermTCPCommon.cpp create mode 100644 UZ7HOUtils.c create mode 100644 VARA.ui create mode 100644 VARAConfig.ui create mode 100644 ax25.c create mode 100644 ax25.h create mode 100644 ax25_l2.c create mode 100644 hid.c create mode 100644 hidapi.h create mode 100644 icon1.ico create mode 100644 icon2.ico create mode 100644 main.cpp create mode 100644 makeit create mode 100644 ui_TCPHostConfig.h create mode 100644 utf8Routines.cpp diff --git a/AGWCode.cpp b/AGWCode.cpp new file mode 100644 index 0000000..42f5f36 --- /dev/null +++ b/AGWCode.cpp @@ -0,0 +1,1546 @@ +// AGW Interface + +#include "QtTermTCP.h" +#include "TabDialog.h" +#include +#include +#include + +#ifndef WIN32 +#define strtok_s strtok_r +#endif + +#define UCHAR unsigned char +#define byte unsigned char + +myTcpSocket * AGWSock; + +extern QColor monRxText; +extern QColor monTxText; + +extern QColor outputText; +extern QColor EchoText; +extern QColor WarningText; + +extern QTabWidget *tabWidget; +extern QWidget * mythis; + +extern int AGWEnable; +extern int AGWMonEnable; +extern char AGWTermCall[12]; +extern char AGWBeaconDest[12]; +extern char AGWBeaconPath[80]; +extern int AGWBeaconInterval; +extern char AGWBeaconPorts[80]; +extern char AGWBeaconMsg[260]; + +extern char AGWHost[128]; +extern int AGWPortNum; +extern int AGWPaclen; + +extern char listenCText[4096]; +extern int ConnectBeep; + +extern QList _sessions; + +extern QLabel * Status1; +extern QLabel * Status2; +extern QLabel * Status3; +extern QLabel * Status4; + +extern QMenu *connectMenu; +extern QAction *discAction; +extern QAction *YAPPSend; + +extern QAction *actHost[17]; + +// Session Type Equates + +#define Term 1 +#define Mon 2 +#define Listen 4 + +extern int TermMode; + +#define Single 0 +#define MDI 1 +#define Tabbed 2 + +extern int singlemodeFormat; + +typedef struct AGWUser_t +{ + QTcpSocket *socket; + unsigned char data_in[8192]; + int data_in_len; + unsigned char AGW_frame_buf[512]; + int Monitor; + int Monitor_raw; + Ui_ListenSession * MonSess; // Window for Monitor info + +} AGWUser; + + +struct AGWHeader +{ + int Port; + unsigned char DataKind; + unsigned char filler2; + unsigned char PID; + unsigned char filler3; + char callfrom[10]; + char callto[10]; + int DataLength; + int reserved; +}; + +#define AGWHDDRRLEN sizeof(struct AGWHeader) + +typedef struct TAGWPort_t +{ + byte PID; + int port; + char corrcall[10]; + char mycall[10]; + int Active; + QTcpSocket * socket; + Ui_ListenSession * Sess; // Terminal Session + +} TAGWPort; + + +int AGWConnected = 0; +int AGWConnecting = 0; + +char * AGWPortList = NULL; + +#define max_sessions 8 + +TAGWPort AGWPort[max_sessions]; + +#define LSB 29 +#define MSB 30 +#define MON_ON '1' +#define MON_OFF '0' +#define MODE_OUR 0 +#define MODE_OTHER 1 +#define MODE_RETRY 2 + +// Don't think we will support more than one, but leave in option + +AGWUser *AGWUsers = NULL; // List of currently connected clients + +void AGW_add_socket(QTcpSocket * socket); +void AGW_Process_Input(AGWUser * AGW); +void Send_AGW_X_Frame(QTcpSocket* socket, char * CallFrom); +void Send_AGW_G_Frame(QTcpSocket* socket); +void Send_AGW_m_Frame(QTcpSocket* socket); + +Ui_ListenSession * FindFreeWindow(); +Ui_ListenSession * newWindow(QObject * parent, int Type, const char * Label = nullptr); +void AGW_frame_header(UCHAR * Msg, char AGWPort, char DataKind, unsigned char PID, const char * CallFrom, const char * CallTo, int Len); + + +void Debugprintf(const char * format, ...) +{ + char Mess[10000]; + va_list(arglist); + + va_start(arglist, format); + vsprintf(Mess, format, arglist); + qDebug() << Mess; + + return; +} + +int ConvToAX25(char * callsign, unsigned char * ax25call) +{ + int i; + + memset(ax25call, 0x40, 6); // in case short + ax25call[6] = 0x60; // default SSID + + for (i = 0; i < 7; i++) + { + if (callsign[i] == '-') + { + // + // process ssid and return + // + i = atoi(&callsign[i + 1]); + + if (i < 16) + { + ax25call[6] |= i << 1; + return (1); + } + return (0); + } + + if (callsign[i] == 0 || callsign[i] == 13 || callsign[i] == ' ' || callsign[i] == ',') + { + // + // End of call - no ssid + // + return (1); + } + + ax25call[i] = callsign[i] << 1; + } + + // + // Too many chars + // + + return (0); +} + + +int ConvFromAX25(unsigned char * incall, char * outcall) +{ + int in, out = 0; + unsigned char chr; + + memset(outcall, 0x20, 10); + + for (in = 0; in < 6; in++) + { + chr = incall[in]; + if (chr == 0x40) + break; + chr >>= 1; + outcall[out++] = chr; + } + + chr = incall[6]; // ssid + + if (chr == 0x42) + { + outcall[out++] = '-'; + outcall[out++] = 'T'; + return out; + } + + if (chr == 0x44) + { + outcall[out++] = '-'; + outcall[out++] = 'R'; + return out; + } + + chr >>= 1; + chr &= 15; + + if (chr > 0) + { + outcall[out++] = '-'; + if (chr > 9) + { + chr -= 10; + outcall[out++] = '1'; + } + chr += 48; + outcall[out++] = chr; + } + return (out); +} + +void doAGWBeacon() +{ + if (AGWBeaconDest[0] && AGWBeaconPorts[0]) + { + // Send as M or V depending on Digis + + UCHAR Msg[512]; + char ports[80]; + + char * ptr, * context; + int DataLen = (int)strlen(AGWBeaconMsg); + + strcpy(ports, AGWBeaconPorts); // strtok changes it + + + // Replace newlines with CR + + while ((ptr = strchr(AGWBeaconMsg, 10))) + *ptr = 13; + + ptr = strtok_s(ports, " ,", &context); + + if (AGWBeaconPath[0]) + { + + } + else + { + while (ptr) + { + AGW_frame_header(Msg, atoi(ptr) - 1, 'M', 240, AGWTermCall, AGWBeaconDest, DataLen); + + memcpy(&Msg[AGWHDDRRLEN], AGWBeaconMsg, DataLen); + DataLen += AGWHDDRRLEN; + AGWSock->write((char *)Msg, DataLen); + + ptr = strtok_s(NULL, " ,", &context); + } + } + } +} + +TAGWPort * get_free_port() +{ + int i = 0; + + while (i < max_sessions) + { + if (AGWPort[i].Active == 0) + return &AGWPort[i]; + + i++; + } + return nullptr; +} + +TAGWPort * findSession(int AGWChan, char * MyCall, char * OtherCall) +{ + int i = 0; + + TAGWPort * Sess = nullptr; + + while (i < max_sessions) + { + Sess = &AGWPort[i]; + + if (Sess->Active && Sess->port == AGWChan && strcmp(Sess->corrcall, OtherCall) == 0 && strcmp(Sess->mycall, MyCall) == 0) + return Sess; + + i++; + } + return nullptr; +} + + + +void QtTermTCP::AGWdisplayError(QAbstractSocket::SocketError socketError) +{ + switch (socketError) + { + case QAbstractSocket::RemoteHostClosedError: + break; + + case QAbstractSocket::HostNotFoundError: + QMessageBox::information(this, tr("QtTermTCP"), + tr("AGW host was not found. Please check the " + "host name and port settings.")); + + Status1->setText("AGW Connection Failed"); + + break; + + case QAbstractSocket::ConnectionRefusedError: + + Status1->setText("AGW Connection Refused"); + break; + + default: + + Status1->setText("AGW Connection Failed"); + } + + AGWConnecting = 0; + AGWConnected = 0; +} + +void QtTermTCP::AGWreadyRead() +{ + int Read; + unsigned char Buffer[4096]; + myTcpSocket* Socket = static_cast(QObject::sender()); + + // read the data from the socket + + Read = Socket->read((char *)Buffer, 4095); + + int AGWHeaderLen = sizeof(struct AGWHeader); + + AGWUser * AGW = NULL; + + AGW = AGWUsers; + + if (AGW == NULL) + return; + + memcpy(&AGW->data_in[AGW->data_in_len], Buffer, Read); + + AGW->data_in_len += Read; + + while (AGW->data_in_len >= AGWHeaderLen) // Make sure have at least header + { + struct AGWHeader * Hddr = (struct AGWHeader *)AGW->data_in; + + int AgwLen = Hddr->DataLength + AGWHeaderLen; + + if (AGW->data_in_len >= AgwLen) + { + // Have frame as well + + AGW_Process_Input(AGW); + + AGW->data_in_len -= AgwLen; + + memmove(AGW->data_in, &AGW->data_in[AgwLen], AGW->data_in_len); + } + else + return; // Wait for the data + } + +} + +void QtTermTCP::onAGWSocketStateChanged(QAbstractSocket::SocketState socketState) +{ + myTcpSocket* sender = static_cast(QObject::sender()); + Ui_ListenSession * Sess = (Ui_ListenSession *)sender->Sess; + int i; + + if (socketState == QAbstractSocket::UnconnectedState) + { + // Close any connections + + Status1->setText("AGW Disconnected"); + actHost[16]->setVisible(0); + + int i = 0; + + TAGWPort * AGW = nullptr; + + for (i = 0; i < max_sessions; i++) + { + AGW = &AGWPort[i]; + + if (AGW->Active && AGW->socket) + { + AGW->Active = 0; + AGW->socket = nullptr; + + Sess = AGW->Sess; + + if (Sess) + { + // Send Disconnected + + char Msg[] = "Disconnected\r"; + + WritetoOutputWindowEx(Sess, (unsigned char *)Msg, (int)strlen(Msg), + Sess->termWindow, &Sess->OutputSaveLen, Sess->OutputSave, WarningText); // Red + + if (TermMode == MDI) + { + if (Sess->SessionType == Mon) // Mon Only + Sess->setWindowTitle("Monitor Session Disconnected"); + else + Sess->setWindowTitle("Disconnected"); + } + else if (TermMode == Tabbed) + { + if (Sess->SessionType == Mon) // Mon Only + tabWidget->setTabText(Sess->Tab, "Monitor"); + else + { + char Label[16]; + sprintf(Label, "Sess %d", Sess->Tab + 1); + tabWidget->setTabText(Sess->Tab, Label); + } + } + else if (TermMode == Single) + { + if (Sess->SessionType == Mon) // Mon Only + mythis->setWindowTitle("Monitor Session Disconnected"); + else + mythis->setWindowTitle("Disconnected"); + } + + setMenus(false); + + // if Single Split or Monitor leave session allocated to AGW + + if (TermMode == Single && (Sess->SessionType & Mon)) + { + return; + } + + Sess->AGWSession = nullptr; + AGW->Sess = nullptr; + } + } + } + + // Free the monitor Window + + if (AGWUsers && AGWUsers->MonSess) + { + Sess = AGWUsers->MonSess; + + char Msg[] = "Disconnected\r"; + + WritetoOutputWindowEx(Sess, (unsigned char *)Msg, (int)strlen(Msg), + Sess->monWindow, &Sess->OutputSaveLen, Sess->OutputSave, WarningText); // Red + + if (TermMode == MDI) + Sess->setWindowTitle("Monitor Session Disconnected"); + + else if (TermMode == Tabbed) + tabWidget->setTabText(Sess->Tab, "Monitor"); + + Sess->AGWSession = nullptr; + AGWUsers->MonSess = nullptr; + } + + AGWConnected = 0; + } + else if (socketState == QAbstractSocket::ConnectedState) + { + AGWConnected = 1; + AGWConnecting = 0; + + Status1->setText("AGW Connected"); + + AGW_add_socket(sender); + + actHost[16]->setVisible(1); // Enable AGW Connect Line + + // Send X frame to register Term Call + + if (AGWTermCall[0]) + Send_AGW_X_Frame(sender, AGWTermCall); + + Send_AGW_G_Frame(sender); // Request Port List + + // Attach a Monitor Window if available + + Ui_ListenSession * Sess = NULL; + Ui_ListenSession * S; + + if (TermMode == MDI) + { + // See if an old session can be reused + + for (int i = 0; i < _sessions.size(); ++i) + { + S = _sessions.at(i); + + // for (Ui_ListenSession * S: _sessions) + // { + if ((S->SessionType == Mon) && S->clientSocket == NULL && S->AGWSession == NULL && S->KISSSession == NULL) + { + Sess = S; + break; + } + } + + // Create a window if none found, else reuse old + + if (Sess == NULL) + { + Sess = newWindow((QObject *)mythis, Mon, ""); + } + } + else if (TermMode == Tabbed) + { + // Tabbed - look for free session + + for (i = 8; i; i--) + { + S = _sessions.at(i); + + if (S->clientSocket == NULL && S->AGWSession == NULL) + { + Sess = S; + break; + } + } + } + else if (TermMode == Single && (singlemodeFormat & Mon)) + { + S = _sessions.at(0); + + if (S->clientSocket == NULL && S->AGWSession == NULL && S->KISSSession == NULL) + Sess = S; + + } + + if (Sess) + { + AGWUsers->MonSess = Sess; + Sess->AGWSession = sender; // Flag as in use + + if (TermMode == MDI) + Sess->setWindowTitle("AGW Monitor Window"); + else if (TermMode == Tabbed) + tabWidget->setTabText(Sess->Tab, "AGW Mon"); + else if (TermMode == Single) + mythis->setWindowTitle("AGW Monitor Window"); + + if (TermMode != Tabbed) // Not ideal, but AGW mon window is unlikely to be active window + { + discAction->setEnabled(false); + YAPPSend->setEnabled(false); + connectMenu->setEnabled(false); + } + + Send_AGW_m_Frame(sender); // Request Monitor Frames + } + } +} + + +void QtTermTCP::ConnecttoAGW() +{ + delete(AGWSock); + + AGWSock = new myTcpSocket(); + + connect(AGWSock, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(AGWdisplayError(QAbstractSocket::SocketError))); + connect(AGWSock, SIGNAL(readyRead()), this, SLOT(AGWreadyRead())); + connect(AGWSock, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onAGWSocketStateChanged(QAbstractSocket::SocketState))); + + AGWSock->connectToHost(AGWHost, AGWPortNum); + + Status1->setText("AGW Connecting"); + + return; +} + +int AGWBeaconTimer = 0; + + +void QtTermTCP::AGWTimer() +{ + // Runs every 10 Seconds + + if (AGWConnected == 0 && AGWConnecting == 0) + { + AGWConnecting = true; + ConnecttoAGW(); + } + + if (AGWBeaconInterval) + { + AGWBeaconTimer++; + + if (AGWBeaconTimer >= AGWBeaconInterval * 6) + { + AGWBeaconTimer = 0; + + if (AGWConnected) + doAGWBeacon(); + } + } +} + + +void AGW_del_socket(void * socket) +{ + if (AGWUsers->socket == socket) + { + free(AGWUsers); + AGWUsers = nullptr; + } +} + + + +void AGW_add_socket(QTcpSocket * socket) +{ + AGWUser * User = (struct AGWUser_t *)malloc(sizeof(struct AGWUser_t)); // One Client + memset(User, 0, sizeof(struct AGWUser_t)); + + User->socket = socket; + + AGWUsers = User; +}; + +void AGWWindowClosing(Ui_ListenSession *Sess) +{ + if (AGWUsers && AGWUsers->MonSess == Sess) + AGWUsers->MonSess = NULL; +} + + + +void AGW_frame_header(UCHAR * Msg, char AGWPort, char DataKind, unsigned char PID, const char * CallFrom, const char * CallTo, int Len) +{ + struct AGWHeader * Hddr = (struct AGWHeader *)Msg; + memset(Hddr, 0, sizeof(struct AGWHeader)); + + Hddr->Port = AGWPort; + Hddr->DataLength = Len; + Hddr->DataKind = DataKind; + Hddr->PID = PID; + strcpy(Hddr->callfrom, CallFrom); + strcpy(Hddr->callto, CallTo); +}; + + +/* + +// AGW to APP frames + +UCHAR * AGW_R_Frame() +{ + UCHAR * Msg = AGW_frame_header(0, 'R', 0, "", "", 8); + +// stringAdd(Msg, (UCHAR *)AGWVersion, 8); + + return Msg; +}; +*/ + + +void Send_AGW_X_Frame(QTcpSocket* socket, char * CallFrom) +{ + UCHAR Msg[512]; + + AGW_frame_header(Msg, 0, 'X', 0, CallFrom, "", 0); + socket->write((char *)Msg, AGWHDDRRLEN); +} + +void Send_AGW_G_Frame(QTcpSocket* socket) +{ + UCHAR Msg[512]; + + AGW_frame_header(Msg, 0, 'G', 0, "", "", 0); + socket->write((char *)Msg, AGWHDDRRLEN); +} + +void Send_AGW_m_Frame(QTcpSocket* socket) +{ + UCHAR Msg[512]; + + AGW_frame_header(Msg, 0, 'm', 0, "", "", 0); + socket->write((char *)Msg, AGWHDDRRLEN); +} + +/* + + +UCHAR * AGW_Y_Frame(int port, char * CallFrom, char *CallTo, int frame_outstanding) +{ + UCHAR * Msg; + + Msg = AGW_frame_header(port, 'Y', 0, CallFrom, CallTo, 4); + + stringAdd(Msg, (UCHAR *)&frame_outstanding, 4); + return Msg; +} + + + +*/ + + +void Send_AGW_C_Frame(Ui_ListenSession * Sess, int Port, char * CallFrom, char * CallTo, char * Data, int DataLen) +{ + UCHAR Msg[512]; + char Title[64]; + + // We should allocate a AGW session record + + TAGWPort * AX25Sess; + + // FIrst check that we don't already have a session between these calla + + if (Port == -1) + return; + + Debugprintf("Send_AGW_C_Frame"); + + AX25Sess = findSession(Port, CallTo, CallFrom); + + if (AX25Sess) + { + // Seems this can be called twice + + if (Sess->AGWSession == nullptr) + QMessageBox::information(mythis, "QtTermTCP", "You already have a conenction to that call"); + + return; + } + + AX25Sess = get_free_port(); + + if (AX25Sess) + { + AX25Sess->Active = 1; + AX25Sess->port = Port; + AX25Sess->Sess = Sess; // Crosslink AGW and Term Sessions + AX25Sess->PID = 240; + + Sess->AGWSession = AX25Sess; + + strcpy(AX25Sess->mycall, CallTo); + strcpy(AX25Sess->corrcall, CallFrom); + + AX25Sess->socket = AGWSock; + + if (DataLen) // Digis so use 'v' frame + { + AGW_frame_header(Msg, Port, 'v', 240, CallFrom, CallTo, DataLen); + memcpy(&Msg[AGWHDDRRLEN], (UCHAR *)Data, DataLen); + } + else + { + AGW_frame_header(Msg, Port, 'C', 240, CallFrom, CallTo, DataLen); + } + + DataLen += AGWHDDRRLEN; + + AGWSock->write((char *)Msg, DataLen); + + sprintf(Title, "Connecting to %s", CallTo); + + if (TermMode == MDI) + Sess->setWindowTitle(Title); + else if (TermMode == Tabbed) + tabWidget->setTabText(Sess->Tab, "Connecting"); + else if (TermMode == Single) + mythis->setWindowTitle(Title); + + } +} + + +void Send_AGW_Ds_Frame(void * Sess) +{ + TAGWPort * AGW = (TAGWPort *)Sess; + UCHAR Msg[512]; + + AGW_frame_header(Msg, AGW->port, 'd', 240, AGW->corrcall, AGW->mycall, 0); + AGW->socket->write((char *)Msg, AGWHDDRRLEN); +} + + + +void Send_AGW_D_Frame(TAGWPort * AGW, UCHAR * Data, int DataLen) +{ + UCHAR Msg[512]; + + AGW_frame_header(Msg, AGW->port, 'D', AGW->PID, AGW->corrcall, AGW->mycall, DataLen); + + memcpy(&Msg[AGWHDDRRLEN], Data, DataLen); + + DataLen += AGWHDDRRLEN; + + AGW->socket->write((char *)Msg, DataLen); +} + + + +/* + +UCHAR * AGW_T_Frame(int port, char * CallFrom, char * CallTo, char * Data) +{ + UCHAR * Msg; + int DataLen; + + DataLen = strlen(Data); + + Msg = AGW_frame_header(port, 'T', 0, CallFrom, CallTo, DataLen); + + stringAdd(Msg, (byte *)Data, DataLen); + + return Msg; +} + +// Raw Monitor +UCHAR * AGW_K_Frame(int port, int PID, char * CallFrom, char * CallTo, UCHAR * Data, int DataLen) +{ + UCHAR Msg[512]; + + DataLen = Data->Length; + + AGW_frame_header(Msg, port, 'K', PID, CallFrom, CallTo, DataLen); + +// stringAdd(Msg, Data->Data, Data->Length); + +// freeString(Data); + + return Msg; +} + +// APP to AGW frames + +void on_AGW_P_frame(AGWUser * AGW) +{ + +} + +*/ + +void on_AGW_G_frame(unsigned char * Data) +{ + if (AGWPortList) + free(AGWPortList); + + AGWPortList = (char *)strdup((char *)Data); +}; + +/* + +void on_AGW_Ms_frame(AGWUser * AGW) +{ + AGW->Monitor ^= 1; // Flip State +} + + +void on_AGW_R_frame(AGWUser * AGW) +{ +// AGW_send_to_app(AGW->socket, AGW_R_Frame()); +} + + + +void on_AGW_Y_frame(void * socket, int snd_ch, char * CallFrom, char * CallTo) +{ + int frames; + TAGWPort * AX25Sess; + +// AX25Sess = get_user_port_by_calls(snd_ch, CallFrom, CallTo); + +// if (AX25Sess) +// { + //frames = AX25port[snd_ch][user_port].in_data_buf.Count; +// frames = AX25Sess->in_data_buf.Count + AX25Sess->I_frame_buf.Count; +// ; +// AGW_send_to_app(socket, AGW_Y_Frame(snd_ch, CallFrom, CallTo, frames)); +// } +} + +// UI Transmit + +void on_AGW_M_frame(int port, byte PID, char * CallFrom, char *CallTo, byte * Msg, int MsgLen) +{ + byte path[80]; + char Calls[80]; + UCHAR * Data = newString(); + + stringAdd(Data, Msg, MsgLen); + + sprintf(Calls, "%s,%s", CallTo, CallFrom); + + get_addr(Calls, 0, 0, path); + + Add(&all_frame_buf[port], + make_frame(Data, path, PID, 0, 0, U_FRM, U_UI, FALSE, SET_F, SET_C)); + +} + +*/ +void on_AGW_C_frame(AGWUser * AGW, struct AGWHeader * Frame, byte * Msg) +{ + int snd_ch = Frame->Port; + char * CallFrom = Frame->callfrom; + char * CallTo = Frame->callto; + char Title[64]; + int i = 0; + + TAGWPort * AX25Sess; + + // Connection received - may be incoming or response to our connect + + // See if incoming + + if (strstr((char *)Msg, (const char *)"CONNECTED To")) + { + // incoming call + + AX25Sess = get_free_port(); + + if (AX25Sess) + { + // Look for/create Terminal Window for connection + + Ui_ListenSession * Sess = NULL; + Ui_ListenSession * S; + + + if (TermMode == MDI) + { + // See if an old session can be reused + + for (int i = 0; i < _sessions.size(); ++i) + { + S = _sessions.at(i); + + // for (Ui_ListenSession * S: _sessions) + // { + if ((S->SessionType & Listen) && S->clientSocket == NULL) + { + Sess = S; + break; + } + } + + // Create a window if none found, else reuse old + + if (Sess == NULL) + { + Sess = newWindow((QObject *)mythis, Listen, ""); + } + } + else + { + // Single or Tabbed - look for free session + + + for (i = 0; i < _sessions.size(); ++i) + { + S = _sessions.at(i); + + if (S->clientSocket == NULL && S->AGWSession == NULL && S->KISSSession == NULL) + { + Sess = S; + break; + } + } + + if (Sess == NULL) + { + // Clear connection + + return; + } + } + + if (Sess) + { + sprintf(Title, "Connected to %s", CallFrom); + + if (TermMode == MDI) + { + Sess->setWindowTitle(Title); + } + else if (TermMode == Tabbed) + { + tabWidget->setTabText(i, CallFrom); + } + else if (TermMode == Single) + mythis->setWindowTitle(Title); + + AX25Sess->Active = 1; + AX25Sess->port = snd_ch; + AX25Sess->Sess = Sess; // Crosslink AGW and Term Sessions + AX25Sess->PID = 240;; + + Sess->AGWSession = AX25Sess; + + strcpy(AX25Sess->mycall, CallFrom); + strcpy(AX25Sess->corrcall, CallTo); + + AX25Sess->socket = AGW->socket; + + setMenus(true); + + if (ConnectBeep) + myBeep(); + + // Send CText if defined + + if (listenCText[0]) + Send_AGW_D_Frame(AX25Sess, (unsigned char *)listenCText, (int)strlen(listenCText)); + + // Send Message to Terminal + + char Msg[80]; + + sprintf(Msg, "Incoming Connect from %s\r\r", CallFrom); + + WritetoOutputWindow(Sess, (unsigned char *)Msg, (int)strlen(Msg)); + return; + } + } + } + + // Reply to out connect request - look for our connection + + AX25Sess = findSession(snd_ch, CallFrom, CallTo); + + if (AX25Sess) + { + Ui_ListenSession * Sess = AX25Sess->Sess; + + sprintf(Title, "Connected to %s", CallFrom); + + if (TermMode == MDI) + Sess->setWindowTitle(Title); + else if (TermMode == Tabbed) + tabWidget->setTabText(Sess->Tab, CallFrom); + else if (TermMode == Single) + mythis->setWindowTitle(Title); + + setMenus(true); + + int Len = sprintf(Title, "*** Connected to %s\r", CallFrom); + + ProcessReceivedData(Sess, (unsigned char *)Title, Len); + } + +} + + + +void on_AGW_D_frame(int snd_ch, char * CallFrom, char * CallTo, byte * Msg, int Len) +{ + TAGWPort * AX25Sess; + + AX25Sess = findSession(snd_ch, CallFrom, CallTo); + + if (AX25Sess) + { + ProcessReceivedData(AX25Sess->Sess, Msg, Len); + } +} + + +void on_AGW_Mon_frame(byte * Msg, int Len, char Type) +{ + if (AGWUsers && AGWUsers->MonSess && AGWUsers->MonSess->monWindow) + { + if (Type == 'T') + WritetoOutputWindowEx(AGWUsers->MonSess, Msg, Len, + AGWUsers->MonSess->monWindow, &AGWUsers->MonSess->OutputSaveLen, AGWUsers->MonSess->OutputSave, monTxText); // Red + else + WritetoOutputWindowEx(AGWUsers->MonSess, Msg, Len, + AGWUsers->MonSess->monWindow, &AGWUsers->MonSess->OutputSaveLen, AGWUsers->MonSess->OutputSave, monRxText); // Blue + } +} + + +void on_AGW_Ds_frame(int AGWChan, char * CallFrom, char * CallTo, struct AGWHeader * Frame) +{ + TAGWPort * AX25Sess; + char * Msg = (char *)Frame; + Msg = &Msg[36]; + + char Reply[128] = ""; + + memcpy(Reply, Msg, Frame->DataLength); + + // Disconnect Sessiom + + AX25Sess = findSession(AGWChan, CallFrom, CallTo); + + if (AX25Sess) + { + Ui_ListenSession * Sess = AX25Sess->Sess; + + WritetoOutputWindowEx(Sess, (unsigned char *)Reply, (int)strlen(Reply), + Sess->termWindow, &Sess->OutputSaveLen, Sess->OutputSave, WarningText); // Red + + if (TermMode == MDI) + { + if (Sess->SessionType == Mon) // Mon Only + Sess->setWindowTitle("Monitor Session Disconnected"); + else + Sess->setWindowTitle("Disconnected"); + } + else if (TermMode == Tabbed) + { + if (Sess->SessionType == Mon) // Mon Only + tabWidget->setTabText(Sess->Tab, "Monitor"); + else + { + char Label[16]; + sprintf(Label, "Sess %d", Sess->Tab + 1); + tabWidget->setTabText(Sess->Tab, Label); + } + } + else if (TermMode == Single) + { + if (Sess->SessionType == Mon) // Mon Only + mythis->setWindowTitle("Monitor Session Disconnected"); + else + mythis->setWindowTitle("Disconnected"); + } + + Sess->AGWSession = nullptr; + AX25Sess->Sess = nullptr; + AX25Sess->Active = 0; + + setMenus(false); + } + else + { + // Session not found + } +} + + +void AGW_AX25_data_in(void * Sess, UCHAR * data, int Len) +{ + TAGWPort * AX25Sess = (TAGWPort *)Sess; + int len = 250, sendlen; + UCHAR pkt[512]; + + while (Len) + { + if (Len > len) + sendlen = len; + else + sendlen = Len; + + memcpy(pkt, data, sendlen); + + Len -= sendlen; + + memmove(data, &data[sendlen], Len); + + Send_AGW_D_Frame(AX25Sess, pkt, sendlen); + } + +} + + +/* + +void AGW_frame_monitor(byte snd_ch, byte * path, UCHAR * data, byte pid, byte nr, byte ns, byte f_type, byte f_id, byte rpt, byte pf, byte cr, byte RX) +{ + char mon_frm[512]; + char AGW_path[256]; + UCHAR * AGW_data; + + const char * frm; + byte * datap = data->Data; + byte _data[512]; + byte * p_data = _data; + int _datalen; + + char agw_port; + char CallFrom[10], CallTo[10], Digi[80]; + + int i; + const char * ctrl; + int len; + + AGWUser * AGW; + + UNUSED(rpt); + + len = data->Length; + + // if (pid == 0xCF) + // data = parse_NETROM(data, f_id); + // IP parsing + // else if (pid == 0xCC) + // data = parse_IP(data); + // ARP parsing + // else if (pid == 0xCD) + // data = parse_ARP(data); + // + + if (len > 0) + { + for (i = 0; i < len; i++) + { + if (datap[i] > 31 || datap[i] == 13 || datap[i] == 9) + *(p_data++) = datap[i]; + } + } + + _datalen = p_data - _data; + + if (_datalen) + { + byte * ptr = _data; + i = 0; + + // remove successive cr or cr on end while (i < _datalen) + + while (i < _datalen) + { + if ((_data[i] == 13) && (_data[i + 1] = 13)) + i++; + else + *(ptr++) = _data[i++]; + } + + if (*(ptr - 1) == 13) + ptr--; + + *ptr = 0; + + _datalen = ptr - _data; + } + + agw_port = snd_ch; + + get_monitor_path(path, CallTo, CallFrom, Digi); + + ctrl = ""; + frm = ""; + + switch (f_id) + { + case I_I: + + frm = "I"; + if (cr == SET_C && pf == SET_P) + ctrl = " P"; + + break; + + case S_RR: + + frm = "RR"; + if (pf == SET_P) + ctrl = " P/F"; + + break; + + case S_RNR: + + frm = "RNR"; + if (pf == SET_P) + ctrl = " P/F"; + + break; + + case S_REJ: + + frm = "REJ"; + if (pf == SET_P) + ctrl = " P/F"; + + break; + + case U_SABM: + + frm = "SABM"; + if (cr == SET_C && pf == SET_P) + ctrl = " P"; + + break; + + case U_DISC: + + frm = "DISC"; + if (cr == SET_C && pf == SET_P) + ctrl = " P"; + break; + + case U_DM: + + frm = "DM"; + if ((cr == SET_R) && (pf == SET_P)) + ctrl = " F "; + break; + + case U_UA: + + frm = "UA"; + if ((cr == SET_R) && (pf == SET_P)) + ctrl = " F "; + + break; + + case U_FRMR: + + frm = "FRMR"; + if ((cr == SET_R) && (pf == SET_P)) + ctrl = " F "; + break; + + case U_UI: + + frm = "UI"; + if ((pf == SET_P)) + ctrl = " P/F"; + } + + if (Digi[0]) + sprintf(AGW_path, " %d:Fm %s To %s Via %s <%s", snd_ch + 1, CallFrom, CallTo, Digi, frm); + else + sprintf(AGW_path, " %d:Fm %s To %s <%s", snd_ch + 1, CallFrom, CallTo, frm); + + + switch (f_type) + { + case I_FRM: + + //mon_frm = AGW_path + ctrl + ' R' + inttostr(nr) + ' S' + inttostr(ns) + ' pid=' + dec2hex(pid) + ' Len=' + inttostr(len) + ' >' + time_now + #13 + _data + #13#13; + sprintf(mon_frm, "%s%s R%d S%d pid=%X Len=%d >[%s]\r%s\r", AGW_path, ctrl, nr, ns, pid, len, ShortDateTime(), _data); + + break; + + case U_FRM: + + if (f_id == U_UI) + { + sprintf(mon_frm, "%s pid=%X Len=%d >[%s]\r%s\r", AGW_path, pid, len, ShortDateTime(), _data); // "= AGW_path + ctrl + '>' + time_now + #13; + } + else if (f_id == U_FRMR) + { + // _data = copy(_data + #0#0#0, 1, 3); + // mon_frm = AGW_path + ctrl + '>' + time_now + #13 + inttohex((byte(data[1]) shl 16) or (byte(data[2]) shl 8) or byte(data[3]), 6) + #13#13; + } + else + sprintf(mon_frm, "%s%s>[%s]\r", AGW_path, ctrl, ShortDateTime()); // "= AGW_path + ctrl + '>' + time_now + #13; + + break; + + case S_FRM: + + // mon_frm = AGW_path + ctrl + ' R' + inttostr(nr) + ' >' + time_now + #13; + sprintf(mon_frm, "%s%s R%d>[%s]\r", AGW_path, ctrl, nr, ShortDateTime()); // "= AGW_path + ctrl + '>' + time_now + #13; + + break; + + } + + // stringAdd(mon_frm, "", 1); // Add 0 at the end of each frame + + // I think we send to all AGW sockets + + for (i = 0; i < AGWConCount; i++) + { + AGW = AGWUsers[i]; + + if (AGW->Monitor) + { + if (RX) + { + + + case f_id of + I_I : AGW_data = AGW_I_frame(agw_port,CallFrom,CallTo,mon_frm); + S_RR : AGW_data = AGW_S_frame(agw_port,CallFrom,CallTo,mon_frm); + S_RNR : AGW_data = AGW_S_frame(agw_port,CallFrom,CallTo,mon_frm); + S_REJ : AGW_data = AGW_S_frame(agw_port,CallFrom,CallTo,mon_frm); + U_SABM: AGW_data = AGW_S_frame(agw_port,CallFrom,CallTo,mon_frm); + U_DISC: AGW_data = AGW_S_frame(agw_port,CallFrom,CallTo,mon_frm); + U_DM : AGW_data = AGW_S_frame(agw_port,CallFrom,CallTo,mon_frm); + U_UA : AGW_data = AGW_S_frame(agw_port,CallFrom,CallTo,mon_frm); + U_FRMR: AGW_data = AGW_S_frame(agw_port,CallFrom,CallTo,mon_frm); + U_UI : AGW_data = AGW_U_frame(agw_port,CallFrom,CallTo,mon_frm); + + AGW_send_to_app(AGW->socket, AGW_data); + + + } + else + { + AGW_data = AGW_T_Frame(agw_port, CallFrom, CallTo, mon_frm); + AGW_send_to_app(AGW->socket, AGW_data); + } + } + } +} + +*/ + + + + +void AGW_Process_Input(AGWUser * AGW) +{ + struct AGWHeader * Frame = (struct AGWHeader *)AGW->data_in; + byte * Data = &AGW->data_in[36]; + + switch (Frame->DataKind) + { + case 'P': + +// on_AGW_P_frame(AGW); +// return; + + case 'X': + + // Registration response - no need to process + + return; + + case 'G': + + on_AGW_G_frame(Data); + return; +/* + + case 'x': + + on_AGW_Xs_frame(Frame->callfrom); + return; + + + + case 'm': + + on_AGW_Ms_frame(AGW); + return; + + case 'R': + + on_AGW_R_frame(AGW); + return; + + + // 'g': on_AGW_Gs_frame(AGW,Frame->Port); + // 'H': on_AGW_H_frame(AGW,Frame->Port); + // 'y': on_AGW_Ys_frame(AGW,Frame->Port); + + case 'Y': + on_AGW_Y_frame(AGW->socket, Frame->Port, Frame->callfrom, Frame->callto); + break; + + case 'M': + + on_AGW_M_frame(Frame->Port, Frame->PID, Frame->callfrom, Frame->callto, Data, Frame->DataLength); + break; +*/ + + case 'C': +// case 'v': // Call with digis + + on_AGW_C_frame(AGW, Frame, Data); + return; + + + case 'D': + + on_AGW_D_frame(Frame->Port, Frame->callfrom, Frame->callto, Data, Frame->DataLength); + return; + + case 'd': + + on_AGW_Ds_frame(Frame->Port, Frame->callfrom, Frame->callto, Frame); + return; + + case 'U': + case 'S': + case 'I': + case 'T': + + // Monitor Data + + if (AGWMonEnable) + on_AGW_Mon_frame(Data, Frame->DataLength, Frame->DataKind); + + return; + +/* + + // 'V': on_AGW_V_frame(AGW,Frame->Port,PID,CallFrom,CallTo,Data); + // 'c': on_AGW_Cs_frame(sAGWocket,Frame->Port,PID,CallFrom,CallTo); + + + case 'K': + + on_AGW_K_frame(Frame); + return; + + case 'k': + on_AGW_Ks_frame(AGW); + return; + */ + default: + Debugprintf("AGW %c", Frame->DataKind); + } +} diff --git a/AGWConnect.ui b/AGWConnect.ui new file mode 100644 index 0000000..c5e4506 --- /dev/null +++ b/AGWConnect.ui @@ -0,0 +1,148 @@ + + + AGWConnectDkg + + + + 0 + 0 + 500 + 400 + + + + Dialog + + + + + 10 + 10 + 480 + 380 + + + + + + + + + Call From + + + + + + + + + + Call To + + + + + + + true + + + QComboBox::NoInsert + + + + + + + Digis + + + + + + + + + + + + Radio Ports + + + + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + OK + + + + + + + Cancel + + + + + + + + + + + + okButton + clicked() + AGWConnectDkg + accept() + + + 278 + 253 + + + 96 + 254 + + + + + cancelButton + clicked() + AGWConnectDkg + reject() + + + 369 + 253 + + + 179 + 282 + + + + + diff --git a/AGWParams.ui b/AGWParams.ui new file mode 100644 index 0000000..7a5fc45 --- /dev/null +++ b/AGWParams.ui @@ -0,0 +1,395 @@ + + + AGWParams + + + + 0 + 0 + 562 + 491 + + + + Dialog + + + + + 120 + 50 + 113 + 20 + + + + + + + 12 + 83 + 537 + 215 + + + + Beacon Setup + + + + + 110 + 24 + 113 + 20 + + + + + + + 110 + 54 + 113 + 20 + + + + + + + 108 + 84 + 45 + 20 + + + + + + + 110 + 114 + 43 + 20 + + + + + + + 14 + 22 + 75 + 21 + + + + Destination + + + + + + 14 + 52 + 75 + 21 + + + + Digipeaters + + + + + + 14 + 82 + 75 + 21 + + + + Interval + + + + + + 14 + 112 + 75 + 21 + + + + Ports + + + + + + 176 + 82 + 75 + 22 + + + + Minutes + + + + + + 176 + 112 + 137 + 21 + + + + (Separate with commas) + + + + + + 14 + 144 + 75 + 21 + + + + Message + + + + + + 110 + 140 + 425 + 63 + + + + + + + 14 + 158 + 95 + 21 + + + + (max 256 chars) + + + + + + + 12 + 300 + 537 + 123 + + + + TNC Setup + + + + + 110 + 24 + 101 + 20 + + + + + + + 110 + 54 + 47 + 20 + + + + + + + 110 + 84 + 47 + 20 + + + + + + + 16 + 24 + 47 + 13 + + + + Host + + + + + + 16 + 54 + 47 + 13 + + + + Port + + + + + + 16 + 84 + 47 + 13 + + + + Paclen + + + + + + + 18 + 48 + 111 + 21 + + + + Terminal Callsign + + + + + + 180 + 440 + 95 + 23 + + + + OK + + + + + + 292 + 440 + 79 + 23 + + + + Cancel + + + + + + 152 + 16 + 23 + 25 + + + + Qt::RightToLeft + + + + + + + + + 20 + 18 + 135 + 21 + + + + Enable AGW Interface + + + + + + 255 + 18 + 216 + 21 + + + + Qt::RightToLeft + + + Enable AGW Monitor Window + + + + + + + cancelButton + clicked() + AGWParams + reject() + + + 369 + 253 + + + 179 + 282 + + + + + okButton + clicked() + AGWParams + accept() + + + 278 + 253 + + + 96 + 254 + + + + + diff --git a/ColourConfig.ui b/ColourConfig.ui new file mode 100644 index 0000000..d980e87 --- /dev/null +++ b/ColourConfig.ui @@ -0,0 +1,440 @@ + + + ColourDialog + + + + 0 + 0 + 342 + 407 + + + + Colour Selection + + + + + 66 + 350 + 199 + 33 + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + OK + + + + + + + Cancel + + + + + + + + + 216 + 15 + 75 + 23 + + + + + 75 + 0 + + + + + 75 + 16777215 + + + + + + + + + + 216 + 247 + 75 + 23 + + + + + 75 + 0 + + + + + 75 + 16777215 + + + + + + + + + + 216 + 276 + 75 + 23 + + + + + 75 + 0 + + + + + 75 + 16777215 + + + + Input + + + + + + 216 + 44 + 75 + 23 + + + + + 75 + 0 + + + + + 75 + 16777215 + + + + Normal + + + + + + 216 + 73 + 75 + 23 + + + + + 75 + 0 + + + + + 75 + 16777215 + + + + Echoed + + + + + + 216 + 102 + 75 + 23 + + + + + 75 + 0 + + + + + 75 + 16777215 + + + + Warning + + + + + + 21 + 16 + 187 + 16 + + + + Terminal Background + + + + + + 21 + 45 + 185 + 16 + + + + Normal Text + + + + + + 21 + 74 + 191 + 16 + + + + Echoed Text + + + + + + 21 + 103 + 185 + 16 + + + + Warning Text + + + + + + 21 + 248 + 189 + 16 + + + + Input Background + + + + + + 21 + 277 + 183 + 16 + + + + Input Text + + + + + + 216 + 189 + 75 + 23 + + + + + 75 + 0 + + + + + 75 + 16777215 + + + + RX Text + + + + + + 216 + 131 + 75 + 23 + + + + + 75 + 0 + + + + + 75 + 16777215 + + + + + + + + + + 216 + 218 + 75 + 23 + + + + + 75 + 0 + + + + + 75 + 16777215 + + + + Other Text + + + + + + 216 + 160 + 75 + 23 + + + + + 75 + 0 + + + + + 75 + 16777215 + + + + TX Text + + + + + + 21 + 132 + 187 + 16 + + + + Monitor Background + + + + + + 21 + 161 + 183 + 16 + + + + TX Text + + + + + + 21 + 190 + 187 + 16 + + + + RX Text + + + + + + 21 + 219 + 189 + 16 + + + + Other Text + + + + + + diff --git a/KISSConfig.ui b/KISSConfig.ui new file mode 100644 index 0000000..183778e --- /dev/null +++ b/KISSConfig.ui @@ -0,0 +1,381 @@ + + + KISSDialog + + + + 0 + 0 + 432 + 286 + + + + KISS Configuration + + + + + 40 + 238 + 351 + 33 + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + OK + + + + + + + Cancel + + + + + + + + + 10 + 96 + 401 + 57 + + + + TCP Setup + + + + + 90 + 20 + 111 + 22 + + + + + + + 330 + 21 + 47 + 22 + + + + + + + 16 + 24 + 47 + 13 + + + + Host + + + + + + 280 + 24 + 47 + 13 + + + + Port + + + + + + 85 + 63 + 47 + 22 + + + + + + + 20 + 66 + 47 + 13 + + + + Port + + + + + + + 0 + 46 + 411 + 51 + + + + Serial TNC + + + + + 150 + 20 + 111 + 22 + + + + + + + 20 + 23 + 131 + 16 + + + + Select Device + + + + + + 280 + 20 + 47 + 22 + + + + Speed + + + + + + 335 + 21 + 51 + 22 + + + + 19200 + + + + + + + 10 + 12 + 161 + 21 + + + + Enable KISS Interface + + + + + + 150 + 10 + 23 + 25 + + + + Qt::RightToLeft + + + + + + + + + 230 + 13 + 61 + 17 + + + + MYCALL + + + + + + 300 + 10 + 91 + 22 + + + + + + + 25 + 168 + 47 + 13 + + + + Paclen + + + + + + 100 + 165 + 47 + 22 + + + + + + + 265 + 168 + 66 + 16 + + + + MaxFrame + + + + + + 340 + 165 + 47 + 22 + + + + + + + 25 + 202 + 47 + 13 + + + + Frack + + + + + + 100 + 199 + 36 + 22 + + + + + + + 265 + 201 + 47 + 13 + + + + Retries + + + + + + 340 + 198 + 36 + 22 + + + + + + + + okButton + clicked() + KISSDialog + accept() + + + 278 + 253 + + + 96 + 254 + + + + + cancelButton + clicked() + KISSDialog + reject() + + + 369 + 253 + + + 179 + 282 + + + + + diff --git a/ListenPort.ui b/ListenPort.ui new file mode 100644 index 0000000..c46838a --- /dev/null +++ b/ListenPort.ui @@ -0,0 +1,140 @@ + + + ListenPort + + + + 0 + 0 + 445 + 654 + + + + Dialog + + + + + 0 + 0 + 431 + 301 + + + + + + + + + + 100 + 30 + + + + + 100 + 30 + + + + Port + + + + + + + + 0 + 30 + + + + + 100 + 30 + + + + + + + + + 0 + 30 + + + + + 16777215 + 30 + + + + CText + + + + + + + + 0 + 150 + + + + + 401 + 150 + + + + + + + + + + + + 0 + 50 + + + + + 16777215 + 50 + + + + Qt::RightToLeft + + + QDialogButtonBox::Save + + + + + + + Qt::LeftToRight + + + Enable Listen + + + + + + + + + + + diff --git a/QtSoundModem.ini b/QtSoundModem.ini new file mode 100644 index 0000000..85fc91e --- /dev/null +++ b/QtSoundModem.ini @@ -0,0 +1,99 @@ +[General] +HostParams0=127.0.0.1|8011|john|password|80001 1 1 0 1 0 0 1\r +HostParams1=|0||| +HostParams2=|0||| +HostParams3=|0||| +HostParams4=|0||| +HostParams5=|0||| +HostParams6=|0||| +HostParams7=|0||| +HostParams8=|0||| +HostParams9=|0||| +HostParams10=|0||| +HostParams11=|0||| +HostParams12=|0||| +HostParams13=|0||| +HostParams14=|0||| +HostParams15=|0||| +Split=50 +ChatMode=1 +Bells=1 +StripLF=1 +AlertBeep=1 +ConnectBeep=1 +CurrentHost=16 +YAPPPath= +listenPort=8015 +listenEnable=1 +listenCText= +convUTF8=0 +PTT=None +PTTBAUD=19200 +PTTMode=19200 +CATHex=1 +PTTOffString= +PTTOnString= +pttGPIOPin=17 +pttGPIOPinR=17 +CM108Addr=0xD8C:0x08 +HamLibPort=4532 +HamLibHost=127.0.0.1 +FLRigPort=12345 +FLRigHost=127.0.0.1 +AGWEnable=0 +AGWMonEnable=0 +AGWTermCall= +AGWBeaconDest= +AGWBeaconPath= +AGWBeaconInterval=0 +AGWBeaconPorts= +AGWBeaconText= +AGWHost=127.0.0.1 +AGWPort=8000 +AGWPaclen=80 +AGWToCalls=GM8BPQ-1, GM8BPQ-2, GM8BPQ, G8BPQ-2, G8B, G8BPQ-9, +KISSEnable=1 +MYCALL=GM8BPQ +KISSHost=127.0.0.1 +KISSPort=8102 +KISSSerialPort=COM54 +KISSBAUD=19200 +VARAEnable=0 +VARATermCall= +VARAHost=127.0.0.1 +VARAPort=8300 +VARAPath=C:\\VARA\\VARA.exe +VARAHostHF=127.0.0.1 +VARAPortHF=8300 +VARAPathHF=C:\\VARA\\VARA.exe +VARAHostFM=127.0.0.1 +VARAPortFM=8300 +VARAPathFM=C:\\VARA\\VARAFM.exe +VARAHostSAT=127.0.0.1 +VARAPortSAT=8300 +VARAPathSAT=C:\\VARA\\VARASAT.exe +VARA500=0 +VARA2300=1 +VARA2750=0 +VARAHF=1 +VARAFM=0 +VARASAT=0 +TabType=1 1 1 1 1 1 1 2 2 0 + +[AX25_A] +Retries=9 +Maxframe=6 +FrackTime=7 +IdleTime=180 +SlotTime=100 +Persist=128 +RespTime=1500 +TXFrmMode=1 +FrameCollector=6 +ExcludeCallsigns= +ExcludeAPRSFrmType= +KISSOptimization=0 +DynamicFrack=0 +BitRecovery=0 +IPOLL=80 +MyDigiCall= diff --git a/QtTermTCP.aps b/QtTermTCP.aps new file mode 100644 index 0000000000000000000000000000000000000000..6fa53fb42847f35f483018afc71a846b80aa3942 GIT binary patch literal 125896 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%0DJ>*HW~lK8ebatBeCB^ zBJPe)J<#G;K8LzD_@$BG*}p&CW8A&VWH4+A{LzT(huFvcp|1%9f;KTu6KW zy)YMyuFj;{h!NKhvGntCUYiR&Z#3co8k$Loi<#Bti-*lc?$HmR%lk^Vk#q2mK0NHx ztUu<9a6NVyjMyz%TlQ$&AlOu7BmSM8&ivpx*Gnw;ocOG5FUugD6B3`bb*~J@J#_Lo z`!9uVz(5J~u8i%*az4(fT{y>PJ{vkAg5irt9I|-KeN)bczA+o;_beB@enj#Sw~YDz z&_|RY=9=MS>g<0Ua0Xn^uO!}dt1i3u9h4I2_{Kt}%A?a9@MkSsuJfi3Xa9CR zMFwK86t3Q=br=J&zDO&1=@l8X|BzJS8V5qRLtG?u5Rr)a&HO-|1F%h(W(2XwUV>+L7g}KIQy>wzkyNDy&ySv-=oWUs8@rt)~a_- z$jB#FX8cH0e&X!?hhqIT z0B2w-^dgr+|Hm@Vx81mR%;%#$59H8%kje1ZW89g){BWn_BQ7BGMTjdi9%tpu$HSL? z>WL4%{KTxKkqisSgi$RFl)Ntm{(u}iSH}pe>$C4`TJE8UcGjh$@zxd#te9rPUXAuwL=xM+8*cbb>^&cOdoqMtw_`ip27wYVcZL<&d zg}!~?0U;C4oHmAGJzNN2+g;b~?AtaJ-))xX3EOevun5Nl-zBh5_B|ITb}0%5hx&Jo z`yl6gj=^J#6oB^}!+C-XTGuBV_UA2lx0!ifKwhP?y>(Zv`@-)#2j7=hJZHU|@SW5C ze!@Cz18r&hY1-!Ux7lcej%P!4;F$H^H2)6xSD>AL_($vAp|)AStMPr*$jsmD((iA4 z*W>SrEm(-(&$>(W??5em*!o>4w!`-^((!Jm-&t|Jc%Gcu??b#h(XN+%96TSO-1H-& z4+eqnm3&X(Z>T)EUSCIl>s?6Kzx17xGGN(b(hqP6;9@{Hp!cH1fS>Ao1j>!;&+-9Q3jWt)5~_+kW$MCa0zE5Xrdnr7hq{7B0d-E=BYvn%vkz}lXK{644+zq<| z+H8@Q`rvXY1~2QdE`c^O#jvfQolgF5UlP~G-$^N+!?W-#mIZsG(nENT29C5sJlBo{ zuePh{s!v;qQF!(lhi9Ntv@sg7PudL8G zABFF_G0$%m#yFeY`LRr5yhZ3-85eFm>W&3%^qP%cy*%p@jJ~w^{)eR+b*W>m!}p-U zGhcu7j8x%!i980c>tp^J{aHQYP*tJ*meYUo(W$kZ`b4Vnd{hU0q~S+Q7`=`ze|Fs$ zMt?Ks!@mU7<@^xeCx(x8>}!y1Veg%JK4ALg`d!2*2H)_*x-(Fhey@0|Qyx_bXa!WL zT2-S8y4rQwes7(-k+oZKSGw&gmKQCu_NdJ85Y>S6 zTW_4(S|#p=}^_te&Y@EJKEaw=AwDkT3*-K zT2pmJQ)@$2V{LOoMOEvx@+r+#s=!r19ZxsCqIT-k@|wyna*@jghRB`_HBAk*Q@r(Y z0hie6jaN>sGS%67-)@-}Tsse9X^BFnHMUNxYH0M-*0g$Rs%tTp(JI7KS<%|qRNmyN zP?;kNveXC_qOXVlPw>&e`jp~|dXB?hO|XvxzeD(m2(M71RSWLdJk^f-*MZO`HzU`A zd^>V2s!`pb<^$iRvGuA6xOxNzYC^dKK_8lMx9foGK>0=+Z=RZqUaelb`>6xp4QdYl z&qm!8RgM0x(=}66rn*Iys$!Le)}{kLPhA7v1!y%zO-A`96*!9!_@ z&C^hzO7K^#+`8NXj_W~Rpxd)#G?MwBu@T|ESC@y{ZIDH&m4KdO`?Am2Hen)pYAU8Q zS5~P)O-;i%*s*TESZlqIx1bmou~-y6ufuDmJZ9*=&w1Q`k93mme>Ss0mu>$&hH}fv5ZnFi-XqD& b5BHC2PT6_7_YaH1S_j)t+j+Xzf#LZ-GI#~A literal 0 HcmV?d00001 diff --git a/QtTermTCP.cpp b/QtTermTCP.cpp new file mode 100644 index 0000000..71c6b33 --- /dev/null +++ b/QtTermTCP.cpp @@ -0,0 +1,6146 @@ +// Qt Version of BPQTermTCP + +#define VersionString "0.0.0.55" + +// .12 Save font weight +// .13 Display incomplete lines (ie without CR) +// .14 Add YAPP and Listen Mode +// .15 Reuse windows in Listen Mode +// .17 MDI Version 7/1/20 +// .18 Fix inpout window losing focus when data arrives on other window +// .19 Fix Scrollback +// .20 WinXP compatibility changes +// .21 Save Window Positions +// .22 Open a window on first start +// .23 Add Tabbed display option +// .24 Fix crash when setting Monitor flags in Tabbed mode +// .25 Add AGW mode +// .26 Add sending CTRL/C CTRL/D and CTRL/Z) +// .27 Limit size of windows to 10000 lines +// Fix YAPP in Tabbed Mode +// .28 Add AGW Beacon +// .29 Fix allocating Listem sessions to tabs connected using AGW +// .30 Fix allocationd AGW Monitor Sessions +// .31 Fix output being written to the wrong place when window is scrolled back +// .32 Fix connect with digis +// .33 Add StripLF option +// .34 Improvements for Andriod +// Option to change Menu Fonts +// Fix receiving part lines when scrolled back +// .35 Fix PE if not in Tabbed Mode +// .36 Improved dialogs mainly for Android +// Try to make sure sessions are closed before exiting +// .37 Replace VT with LF (for pasting from BPQ32 Terminal Window) +// Dont show AGW status line if AGW interface is disabled +// Don't show Window Menu in Single Mode +// .38 Protect against connect to normal Telnet Port. +// Send CTEXT and Beep for inward AGW Connects +// Make sending Idle and Connect beeps configurable +// Change displayed Monitor flags when active window changed. +// Fix duplicate text on long lines +// .39 Add option to Convert non-utf8 charaters +// .40 Prevent crash if AGW monitor Window closed +// .41 Allow AGW Config and Connect dialogs to be resized with scrollbars +// Fix disabling connect flag on current session when AGW connects +// .42 Fix some bugs in AGW session handling +// .43 Include Ross KD5LPB's fixes for MAC +// .44 Ctrl/[ sends ESC (0x1b) +// .45 Add VARA support Nov 2021 +// .46 Fix dialog size on VARA setup +// .47 Move VARA Status indicator. +// Add FLRIG PTT for VARA +// .48 Check that YAPP Receive Directory has been set +// Save separate configs for VARA, VARAFM and VARASAT +// .49 Add BBS Monitor Dec 2021 +// .50 Make Serial port support optional for Android Version +// .51 Add KISS TNC Support May 2022 +// .52 Save partially typed lines on cursor up May 2022 +// .53 Add UI Mode for KISS Sessions May 2022 +// Fix saving KISS Paclen (b) +// Fix Keepalive (c) +// .54 Add option to change screen colours June 2022 +// .55 Build without optimisation + +#define _CRT_SECURE_NO_WARNINGS + +#define UNUSED(x) (void)(x) + +#define USESERIAL + +#include "QtTermTCP.h" +#include "TabDialog.h" +#include +#include +#include +#include +#include +#include "QTreeWidget" +#include +#include +#include +#include +#ifdef USESERIAL +#include +#include +#endif +#ifndef WIN32 +#define strtok_s strtok_r +#endif +#include +#include +#ifndef WIN32 +#include +#endif + +#include "ax25.h" + +#define MAXHOSTS 16 +#define MAXPORTS 32 + +#define UNREFERENCED_PARAMETER(P) (P) + +char Host[MAXHOSTS + 1][100] = { "" }; +int Port[MAXHOSTS + 1] = { 0 }; +char UserName[MAXHOSTS + 1][80] = { "" }; +char Password[MAXHOSTS + 1][80] = { "" }; +char MonParams[MAXHOSTS + 1][80] = { "" }; +int ListenPort = 8015; + +// Session Type Equates + +#define Term 1 +#define Mon 2 +#define Listen 4 + +// Presentation - Single Window, MDI or Tabbed + +int TermMode = 0; + +#define Single 0 +#define MDI 1 +#define Tabbed 2 + +int singlemodeFormat = Mon + Term; + +char monStyleSheet[128] = "background-color: rgb(0,255,255)"; +char termStyleSheet[128] = "background-color: rgb(255,0,255);"; +char inputStyleSheet[128] = "color: rgb(255, 0, 0); background-color: rgb(255,255,0);"; + + +QColor monBackground = qRgb(0, 255, 255); +QColor monRxText = qRgb(0, 0, 255); +QColor monTxText = qRgb(255, 0, 0); +QColor monOtherText = qRgb(0, 0, 0); + +QColor termBackground = qRgb(255, 0, 255); +QColor outputText = qRgb(0, 0, 0); +QColor EchoText = qRgb(0, 0, 255); +QColor WarningText = qRgb(255, 0, 0); +QColor inputBackground = qRgb(255, 255, 0); +QColor inputText = qRgb(0, 0, 255); + + +// There is something odd about this. It doesn't match BPQTERMTCP though it looks the same + +// Chat uses these (+ 10) +//{ 0, 4, 9, 11, 13, 16, 17, 42, 45, 50, 61, 64, 66, 72, 81, 84, 85, 86, 87, 89 }; + +// As we have a white background we need dark colours + +// 0 is black. 23 is normal output (blue) 81 is red (used by AGW Mon) +// for monitor in colour, host sends 17 for RX Text, 81 for TX Text but code converts to monRx/TxText qrgb values + +QRgb Colours[256] = { 0, + qRgb(0,0,0), qRgb(0,0,128), qRgb(0,0,192), qRgb(0,0,255), // 1 - 4 + qRgb(0,64,0), qRgb(0,64,128), qRgb(0,64,192), qRgb(0,64,255), // 5 - 8 + qRgb(0,128,0), qRgb(0,128,128), qRgb(0,128,192), qRgb(0,128,255), // 9 - 12 + qRgb(0,192,0), qRgb(0,192,128), qRgb(0,192,192), qRgb(0,192,255), // 13 - 16 + qRgb(0,255,0), qRgb(0,255,128), qRgb(0,255,192), qRgb(0,255,255), // 17 - 20 + + qRgb(64,0,0), qRgb(64,0,128), qRgb(64,0,192), qRgb(0,0,255), // 21 + qRgb(64,64,0), qRgb(64,64,128), qRgb(64,64,192), qRgb(64,64,255), + qRgb(64,128,0), qRgb(64,128,128), qRgb(64,128,192), qRgb(64,128,255), + qRgb(64,192,0), qRgb(64,192,128), qRgb(64,192,192), qRgb(64,192,255), + qRgb(64,255,0), qRgb(64,255,128), qRgb(64,255,192), qRgb(64,255,255), + + qRgb(128,0,0), qRgb(128,0,128), qRgb(128,0,192), qRgb(128,0,255), // 41 + qRgb(128,64,0), qRgb(128,64,128), qRgb(128,64,192), qRgb(128,64,255), + qRgb(128,128,0), qRgb(128,128,128), qRgb(128,128,192), qRgb(128,128,255), + qRgb(128,192,0), qRgb(128,192,128), qRgb(128,192,192), qRgb(128,192,255), + qRgb(128,255,0), qRgb(128,255,128), qRgb(128,255,192), qRgb(128,255,255), + + qRgb(192,0,0), qRgb(192,0,128), qRgb(192,0,192), qRgb(192,0,255), // 61 - 80 + qRgb(192,64,0), qRgb(192,64,128), qRgb(192,64,192), qRgb(192,64,255), + qRgb(192,128,0), qRgb(192,128,128), qRgb(192,128,192), qRgb(192,128,255), + qRgb(192,192,0), qRgb(192,192,128), qRgb(192,192,192), qRgb(192,192,255), + qRgb(192,255,0), qRgb(192,255,128), qRgb(192,255,192), qRgb(192,255,255), + + qRgb(255,0,0), qRgb(255,0,128), qRgb(255,0,192), qRgb(255,0,255), // 81 - 100 + qRgb(255,64,0), qRgb(255,64,128), qRgb(255,64,192), qRgb(255,64,255), + qRgb(255,128,0), qRgb(255,128,128), qRgb(255,128,192), qRgb(255,128,255), + qRgb(255,192,0), qRgb(255,192,128), qRgb(255,192,192), qRgb(255,192,255), + qRgb(255,255,0), qRgb(255,255,128), qRgb(255,255,192), qRgb(255,255,255) +}; + + + +int SavedHost = 0; // from config + +char * sessionList = NULL; // Saved sessions + +extern int ChatMode; +extern int Bells; +extern int StripLF; +extern int convUTF8; + +extern time_t LastWrite; +extern int AlertInterval; +extern int AlertBeep; +extern int AlertFreq; +extern int AlertDuration; +extern int ConnectBeep; + +// AGW Host Interface stuff + +int AGWEnable = 0; +int AGWMonEnable = 0; +char AGWTermCall[12] = ""; +char AGWBeaconDest[12] = ""; +char AGWBeaconPath[80] = ""; +int AGWBeaconInterval = 0; +char AGWBeaconPorts[80]; +char AGWBeaconMsg[260] = ""; + +char AGWHost[128] = "127.0.0.1"; +int AGWPortNum = 8000; +int AGWPaclen = 80; +extern char * AGWPortList; +extern myTcpSocket * AGWSock; + +QStringList AGWToCalls; + +// KISS Interface + +int KISSEnable = 0; +char SerialPort[80] = ""; +char KISSHost[128] = "127.0.0.1"; +int KISSPortNum = 1000; +int KISSBAUD = 19200; +char MYCALL[32]; + +int KISSMode = 0; // Connected (0) or UI (1) + +myTcpSocket * KISSSock; + +extern "C" void * KISSSockCopy[4]; + +int KISSConnected = 0; +int KISSConnecting = 0; + +Ui_ListenSession * KISSMonSess; + +QSerialPort * m_serial = nullptr; + +// VARA Interface + +int VARAEnable = 0; +char VARAHost[128] = "127.0.0.1"; +char VARAHostHF[128] = "127.0.0.1"; +char VARAHostFM[128] = "127.0.0.1"; +char VARAHostSAT[128] = "127.0.0.1"; +int VARAPortNum = 8000; +int VARAPortHF = 8000; +int VARAPortFM = 8000; +int VARAPortSAT = 8000; +char VARATermCall[12] = ""; +char VARAPath[256] = ""; +char VARAPathHF[256] = ""; +char VARAPathFM[256] = ""; +char VARAPathSAT[256] = ""; + +int VARA500 = 0; +int VARA2300 = 1; +int VARA2750 = 0; + +int VARAHF = 1; +int VARAFM = 0; +int VARASAT = 0; + +myTcpSocket * VARASock; +myTcpSocket * VARADataSock; + +int VARAConnected = 0; +int VARAConnecting = 0; + + +extern char YAPPPath[256]; + +void menuChecked(QAction * Act); + +// PTT Stuff + +#define PTTRTS 1 +#define PTTDTR 2 +#define PTTCAT 4 +#define PTTCM108 8 +#define PTTHAMLIB 16 +#define PTTFLRIG 32 + +#ifdef USESERIAL + +QSerialPort * hPTTDevice = 0; +#else + + +#endif +char PTTPort[80] = ""; // Port for Hardware PTT - may be same as control port. +int PTTBAUD = 19200; +int PTTMode = PTTRTS; // PTT Control Flags. + +char PTTOnString[128] = ""; +char PTTOffString[128] = ""; + +int CATHex = 1; + +unsigned char PTTOnCmd[64]; +int PTTOnCmdLen = 0; + +unsigned char PTTOffCmd[64]; +int PTTOffCmdLen = 0; + +int pttGPIOPin = 17; // Default +int pttGPIOPinR = 17; +bool pttGPIOInvert = false; +bool useGPIO = false; +bool gotGPIO = false; + +int HamLibPort = 4532; +char HamLibHost[32] = "192.168.1.14"; + +int FLRigPort = 12345; +char FLRigHost[32] = "127.0.0.1"; + + +char CM108Addr[80] = ""; + +int VID = 0; +int PID = 0; + +// CM108 Code + +char * CM108Device = NULL; + +QProcess *process = NULL; + +void GetSettings(); + +// These widgets defined here as they are accessed from outside the framework + +QLabel * Status1; +QLabel * Status2; +QLabel * Status3; +QLabel * Status4; + +QAction *actHost[19]; +QAction *actSetup[16]; + +QAction * TabSingle = NULL; +QAction * TabBoth = NULL; +QAction * TabMon = NULL; + +QMenu *monitorMenu; +QMenu * YAPPMenu; +QMenu *connectMenu; +QMenu *disconnectMenu; + +QAction *MonTX; +QAction *MonSup; +QAction *MonNodes; +QAction *MonUI; +QAction *MonColour; +QAction *MonPort[33]; +QAction *actChatMode; +QAction *actBells; +QAction *actStripLF; +QAction *actIntervalBeep; +QAction *actConnectBeep; +QAction *actAuto; +QAction *actnoConv; +QAction *actCP1251; +QAction *actCP1252; +QAction *actCP437; + +QAction *actFonts; +QAction *actmenuFont; +QAction *actColours; +QAction *actmyFonts; +QAction *YAPPSend; +QAction *YAPPSetRX; +QAction *singleAct; +QAction *singleAct2; +QAction *MDIAct; +QAction *tabbedAct; +QAction *discAction; +QFont * menufont; +QMenu *windowMenu; +QMenu *setupMenu; +QAction *ListenAction; + +QTabWidget *tabWidget; +QMdiArea *mdiArea; +QWidget * mythis; +QStatusBar * myStatusBar; + +QTcpServer * _server; + +QMenuBar *mymenuBar; +QToolBar *toolBar; +QToolButton * but1; +QToolButton * but2; +QToolButton * but3; +QToolButton * but4; +QToolButton * but5; + +QList _sessions; + +// Session Type Equates + +#define Term 1 +#define Mon 2 +#define Listen 4 + +int TabType[10] = { Term, Term + Mon, Term, Term, Term, Term, Mon, Mon, Mon }; + +int listenPort = 8015; +extern "C" int listenEnable; +char listenCText[4096] = ""; + +int ConfigHost = 0; + +int Split = 50; // Mon/Term size split + +int termX; + +bool Cascading = false; // Set to stop size being saved when cascading + +QMdiSubWindow * ActiveSubWindow = NULL; +Ui_ListenSession * ActiveSession = NULL; + +Ui_ListenSession * newWindow(QObject * parent, int Type, const char * Label = nullptr); +void Send_AGW_C_Frame(Ui_ListenSession * Sess, int Port, char * CallFrom, char * CallTo, char * Data, int DataLen); +void AGW_AX25_data_in(void * AX25Sess, unsigned char * data, int Len); +void AGWMonWindowClosing(Ui_ListenSession *Sess); +void AGWWindowClosing(Ui_ListenSession *Sess); +extern "C" void KISSDataReceived(void * socket, unsigned char * data, int length); +void closeSerialPort(); + +extern void initUTF8(); +int checkUTF8(unsigned char * Msg, int Len, unsigned char * out); + +#include +#include +#include + + + + +void EncodeSettingsLine(int n, char * String) +{ + sprintf(String, "%s|%d|%s|%s|%s", Host[n], Port[n], UserName[n], Password[n], MonParams[n]); + return; +} + +// This is used for placing discAction in the preference menu +#ifdef __APPLE__ + bool is_mac = true; +#else + bool is_mac = false; +#endif + +QString GetConfPath() +{ + std::string conf_path = "QtTermTCP.ini"; // Default conf file stored alongside application. + +#ifdef __APPLE__ + + // Get configuration path for MacOS. + + // This will usually be placed in /Users/USER/Library/Application Support/QtTermTCP + + std::string directory = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).at(0).toStdString(); + + conf_path = directory + "/QtTermTCP.ini"; + + mkdir(directory.c_str(), 0775); + +#endif + + return QString::fromUtf8(conf_path.c_str()); + +} + + + +void DecodeSettingsLine(int n, char * String) +{ + char * Param = strdup(String); + char * Rest; + char * Save = Param; // for Free + + Rest = strlop(Param, '|'); + + if (Rest == NULL) + return; + + strcpy(Host[n], Param); + Param = Rest; + + Rest = strlop(Param, '|'); + Port[n] = atoi(Param); + Param = Rest; + + Rest = strlop(Param, '|'); + strcpy(UserName[n], Param); + Param = Rest; + + Rest = strlop(Param, '|'); + strcpy(Password[n], Param); + + strcpy(MonParams[n], Rest); + + + free(Save); + return; +} + +//#ifdef ANDROID +//#define inputheight 60 +//#else +#define inputheight 25 +//#endif + +extern "C" void SendtoAX25(void * conn, unsigned char * Msg, int Len); + +bool QtTermTCP::eventFilter(QObject* obj, QEvent *event) +{ + // See if from a Listening Session + + Ui_ListenSession * Sess; + + for (int i = 0; i < _sessions.size(); ++i) + { + Sess = _sessions.at(i); + +// Sess = _sessions.at(i); for (Ui_ListenSession * Sess : _sessions) +// { + if (Sess == NULL) + continue; + + if (Sess == obj) + { + if (event->type() == QEvent::Close) + { + // Window closing + + // This only closed mon sessinos + + if (Sess->AGWMonSession) +// AGWMonWindowClosing(Sess); + AGWWindowClosing(Sess); + + if (Sess->AGWSession) + AGWWindowClosing(Sess); + + if (Sess->clientSocket) + { + int loops = 100; + Sess->clientSocket->disconnectFromHost(); + while (Sess->clientSocket && loops-- && Sess->clientSocket->state() != QAbstractSocket::UnconnectedState) + QThread::msleep(10); + } + _sessions.removeOne(Sess); + return QMainWindow::eventFilter(obj, event); + } + + if (event->type() == QEvent::Resize) + { + QRect r = Sess->rect(); + + int H, Mid, monHeight, termHeight, Width, Border = 3; + + Width = r.width(); + H = r.height(); + + if (TermMode == Tabbed) + { + // H -= 20; + Width -= 7; + } + else if (TermMode == Single) + { + Width -= 7; +// H += 20; + } + + if (Sess->SessionType == Listen || Sess->SessionType == Term) + { + // Term and Input + + // Calc Positions of window + + termHeight = H - (inputheight + 3 * Border); + + Sess->termWindow->setGeometry(QRect(Border, Border, Width, termHeight)); + Sess->inputWindow->setGeometry(QRect(Border, H - (inputheight + Border), Width, inputheight)); + } + else if (Sess->SessionType == (Term | Mon)) + { + // All 3 + + // Calc Positions of window + + Mid = (H * Split) / 100; + + monHeight = Mid - Border; + termX = Mid; + termHeight = H - Mid - (inputheight + 3 * Border); + + Sess->monWindow->setGeometry(QRect(Border, Border, Width, monHeight)); + Sess->termWindow->setGeometry(QRect(Border, Mid + Border, Width, termHeight)); + Sess->inputWindow->setGeometry(QRect(Border, H - (inputheight + Border), Width, inputheight)); + } + else + { + // Should just be Mon only + + Sess->monWindow->setGeometry(QRect(Border, Border, Width, H - 2 * Border)); + } + + } + } + + if (Sess->inputWindow == obj) + { + if (event->type() == QEvent::KeyPress) + { + QKeyEvent* keyEvent = static_cast(event); + + int key = keyEvent->key(); + Qt::KeyboardModifiers modifier = keyEvent->modifiers(); + + if (modifier == Qt::ControlModifier) + { + char Msg[] = "\0\r"; + + if (key == Qt::Key_BracketLeft) + Msg[0] = 0x1b; + if (key == Qt::Key_Z) + Msg[0] = 0x1a; + else if (key == Qt::Key_C) + Msg[0] = 3; + else if (key == Qt::Key_D) + Msg[0] = 4; + + if (Msg[0]) + { + if (Sess->KISSSession) + { + // Send to ax.25 code + + SendtoAX25(Sess->KISSSession, (unsigned char *)Msg, (int)strlen(Msg)); + } + else if (Sess->AGWSession) + { + // Terminal is in AGWPE mode - send as AGW frame + + AGW_AX25_data_in(Sess->AGWSession, (unsigned char *)Msg, (int)strlen(Msg)); + + } + else if (Sess->clientSocket && Sess->clientSocket->state() == QAbstractSocket::ConnectedState) + { + Sess->clientSocket->write(Msg); + } + + return true; + } + } + + if (key == Qt::Key_Up) + { + // Scroll up + + if (Sess->KbdStack[Sess->StackIndex] == NULL) + return true; + + // If anything typed, stack part command + + QByteArray stringData = Sess->inputWindow->text().toUtf8(); + + if (stringData.length() && Sess->StackIndex == 0) + { + if (Sess->KbdStack[49]) + free(Sess->KbdStack[49]); + + for (int i = 48; i >= 0; i--) + { + Sess->KbdStack[i + 1] = Sess->KbdStack[i]; + } + + Sess->KbdStack[0] = qstrdup(stringData.data()); + Sess->StackIndex++; + } + + Sess->inputWindow->setText(Sess->KbdStack[Sess->StackIndex]); + Sess->inputWindow->cursorForward(strlen(Sess->KbdStack[Sess->StackIndex])); + + Sess->StackIndex++; + if (Sess->StackIndex == 50) + Sess->StackIndex = 49; + + return true; + } + else if (key == Qt::Key_Down) + { + // Scroll down + + if (Sess->StackIndex == 0) + { + Sess->inputWindow->setText(""); + return true; + } + + Sess->StackIndex--; + + if (Sess->StackIndex && Sess->KbdStack[Sess->StackIndex - 1]) + { + Sess->inputWindow->setText(Sess->KbdStack[Sess->StackIndex - 1]); + Sess->inputWindow->cursorForward(strlen(Sess->KbdStack[Sess->StackIndex - 1])); + } + else + Sess->inputWindow->setText(""); + + return true; + } + else if (key == Qt::Key_Return || key == Qt::Key_Enter) + { + LreturnPressed(Sess); + return true; + } + + return false; + } + + if (event->type() == QEvent::MouseButtonPress) + { + QMouseEvent *k = static_cast (event); + + // Paste on Right Click + + if (k->button() == Qt::RightButton) + { + Sess->inputWindow->paste(); + return true; + } + return QMainWindow::eventFilter(obj, event); + } + } + } + return QMainWindow::eventFilter(obj, event); +} + +QAction * setupMenuLine(QMenu * Menu, char * Label, QObject * parent, int State) +{ + QAction * Act = new QAction(Label, parent); + if (Menu) + Menu->addAction(Act); + + Act->setCheckable(true); + if (State) + Act->setChecked(true); + + parent->connect(Act, SIGNAL(triggered()), parent, SLOT(menuChecked())); + + Act->setFont(*menufont); + + return Act; +} + +// This now creates all window types - Term, Mon, Combined, Listen + +int xCount = 0; + +Ui_ListenSession * newWindow(QObject * parent, int Type, const char * Label) +{ + Ui_ListenSession * Sess = new(Ui_ListenSession); + + // Need to explicity initialise on Qt4 + + + Sess->termWindow = NULL; + Sess->monWindow = NULL; + Sess->inputWindow = NULL; + + for (int i = 0; i < 50; i++) + Sess->KbdStack[i] = NULL; + + Sess->StackIndex = 0; + Sess->InputMode = 0; + Sess->SlowTimer = 0; + Sess->MonData = 0; + Sess->OutputSaveLen = 0; + Sess->MonSaveLen = 0; + Sess->PortMonString[0] = 0; + Sess->portmask = 0; + Sess->portmask = 1; + Sess->mtxparam = 1; + Sess->mcomparam = 1; + Sess->monUI = 0; + Sess->MonitorNODES = 0; + Sess->MonitorColour = 1; + Sess->CurrentHost = 0; + + Sess->SessionType = Type; + Sess->clientSocket = NULL; + Sess->AGWSession = NULL; + Sess->AGWMonSession = NULL; + Sess->KISSSession = NULL; + Sess->KISSMode = 0; + + _sessions.push_back(Sess); + +// Sess->setObjectName(QString::fromUtf8("Sess")); + + if (TermMode == MDI) + { + Sess->sw = mdiArea->addSubWindow(Sess); +// Sess->installEventFilter(parent); + + Sess->sw->resize(800, 600); + } + else if (TermMode == Tabbed) + { + Sess->Tab = xCount++; + + if (Type == Mon) + tabWidget->addTab(Sess, "Monitor"); + else + tabWidget->addTab(Sess, Label); + } + + Sess->installEventFilter(parent); + + QSettings settings(GetConfPath(), QSettings::IniFormat); + +#ifdef ANDROID + QFont font = QFont(settings->value("FontFamily", "Driod Sans Mono").value(), + settings->value("PointSize", 12).toInt(), + settings->value("Weight", 50).toInt()); +#else + QFont font = QFont(settings.value("FontFamily", "Courier New").value(), + settings.value("PointSize", 10).toInt(), + settings.value("Weight", 50).toInt()); +#endif + + if ((Type & Mon)) + { + Sess->monWindow = new QTextEdit(Sess); + Sess->monWindow->setReadOnly(1); + Sess->monWindow->document()->setMaximumBlockCount(10000); + Sess->monWindow->setFont(font); + Sess->monWindow->setStyleSheet(monStyleSheet); + mythis->connect(Sess->monWindow, SIGNAL(selectionChanged()), parent, SLOT(onTEselectionChanged())); + } + + if ((Type & (Listen | Term))) + { + Sess->termWindow = new QTextEdit(Sess); + Sess->termWindow->setReadOnly(1); + Sess->termWindow->document()->setMaximumBlockCount(10000); + Sess->termWindow->setFont(font); + Sess->termWindow->setStyleSheet(termStyleSheet); + + mythis->connect(Sess->termWindow, SIGNAL(selectionChanged()), parent, SLOT(onTEselectionChanged())); + + Sess->inputWindow = new QLineEdit(Sess); + Sess->inputWindow->installEventFilter(parent); + Sess->inputWindow->setFont(font); + Sess->inputWindow->setStyleSheet(inputStyleSheet); + Sess->inputWindow->setContextMenuPolicy(Qt::PreventContextMenu); + + mythis->connect(Sess->inputWindow, SIGNAL(selectionChanged()), parent, SLOT(onLEselectionChanged())); + } + + if (Type == (Term | Mon)) + { + // Add Custom Menu to set Mon/Term Split with Right Click + + Sess->monWindow->setContextMenuPolicy(Qt::CustomContextMenu); + Sess->termWindow->setContextMenuPolicy(Qt::CustomContextMenu); + + + mythis->connect(Sess->monWindow, SIGNAL(customContextMenuRequested(const QPoint&)), + parent, SLOT(showContextMenuM(const QPoint &))); + + mythis->connect(Sess->termWindow, SIGNAL(customContextMenuRequested(const QPoint&)), + parent, SLOT(showContextMenuT(const QPoint &))); + } + + if (Sess->SessionType == Mon) // Mon Only + Sess->setWindowTitle("Monitor Session Disconnected"); + else if (Sess->SessionType == Listen) // Mon Only + Sess->setWindowTitle("Listen Window Disconnected"); + else + Sess->setWindowTitle("Disconnected"); + + Sess->installEventFilter(mythis); + + Sess->show(); + + int pos = (_sessions.size() - 1) * 20; + + if (TermMode == MDI) + { + Sess->sw->move(pos, pos); + } + + QSize Size(800, 602); // Not actually used, but Event constructor needs it + + QResizeEvent event(Size, Size); + + QApplication::sendEvent(Sess, &event); // Resize Widgets to fix Window + + return Sess; +} + + +QtTermTCP::QtTermTCP(QWidget *parent) : QMainWindow(parent) +{ + int i; + char Title[80]; + + // Get configuration path for MacOS. + std::string directory = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).at(0).toStdString(); + std::string conf_path = directory + "/QtTermTCP.ini"; + +//mkdir(directory.c_str(), 0775); + + _server = new QTcpServer(); + + mythis = this; + + ui.setupUi(this); + + initUTF8(); + + mymenuBar = new QMenuBar(this); + mymenuBar->setGeometry(QRect(0, 0, 781, 26)); + setMenuBar(mymenuBar); + + toolBar = new QToolBar(this); + toolBar->setObjectName("mainToolbar"); + addToolBar(Qt::BottomToolBarArea, toolBar); + + QSettings mysettings(GetConfPath(), QSettings::IniFormat); + + restoreGeometry(mysettings.value("geometry").toByteArray()); + restoreState(mysettings.value("windowState").toByteArray()); + + GetSettings(); + + // Set background colours + + sprintf(monStyleSheet, "background-color: rgb(%d, %d, %d);", + monBackground.red(), monBackground.green(), monBackground.blue()); + + sprintf(termStyleSheet, "background-color: rgb(%d, %d, %d);", + termBackground.red(), termBackground.green(), termBackground.blue()); + + sprintf(inputStyleSheet, "color: rgb(%d, %d, %d); background-color: rgb(%d, %d, %d);", + inputText.red(), inputText.green(), inputText.blue(), + inputBackground.red(), inputBackground.green(), inputBackground.blue()); + + +#ifdef ANDROID + menufont = new QFont(mysettings.value("MFontFamily", "Driod Sans").value(), + mysettings.value("MPointSize", 12).toInt(), + mysettings.value("MWeight", 50).toInt()); +#else + menufont = new QFont(mysettings.value("MFontFamily", "Aerial").value(), + mysettings.value("MPointSize", 10).toInt(), + mysettings.value("MWeight", 50).toInt()); + +#endif + if (TermMode == MDI) + { + mdiArea = new QMdiArea(ui.centralWidget); + + mdiArea->setGeometry(QRect(0, 0, 771, 571)); + + mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + + connect(mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(xon_mdiArea_changed())); + + setCentralWidget(mdiArea); + } + else if (TermMode == Tabbed) + { + tabWidget = new QTabWidget(this); + + ui.verticalLayout->addWidget(tabWidget); + + tabWidget->setTabPosition(QTabWidget::South); + + newWindow(this, TabType[0], "Sess 1"); + newWindow(this, TabType[1], "Sess 2"); + newWindow(this, TabType[2], "Sess 3"); + newWindow(this, TabType[3], "Sess 4"); + newWindow(this, TabType[4], "Sess 5"); + newWindow(this, TabType[5], "Sess 6"); + newWindow(this, TabType[6], "Sess 7"); + newWindow(this, TabType[7], "Monitor"); + newWindow(this, TabType[8], "Monitor"); + + ActiveSession= _sessions.at(0); + + connect(tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabSelected(int))); + + tabWidget->setFont(*menufont); + + } + + sprintf(Title, "QtTermTCP Version %s", VersionString); + + this->setWindowTitle(Title); + +//#ifdef ANDROID +// mymymenuBar->setVisible(false); +//#endif + + if (TermMode == Single) + windowMenu = mymenuBar->addMenu(""); + else + windowMenu = mymenuBar->addMenu(tr("&Window")); + + connect(windowMenu, SIGNAL(aboutToShow()), this, SLOT(updateWindowMenu())); + windowMenu->setFont(*menufont); + + newTermAct = new QAction(tr("New Terminal Window"), this); + connect(newTermAct, SIGNAL(triggered()), this, SLOT(doNewTerm())); + + newMonAct = new QAction(tr("New Monitor Window"), this); + connect(newMonAct, SIGNAL(triggered()), this, SLOT(doNewMon())); + + newCombinedAct = new QAction(tr("New Combined Term/Mon Window"), this); + connect(newCombinedAct, SIGNAL(triggered()), this, SLOT(doNewCombined())); + + if (TermMode == MDI) + { + closeAct = new QAction(tr("Cl&ose"), this); + closeAct->setStatusTip(tr("Close the active window")); + connect(closeAct, SIGNAL(triggered()), mdiArea, SLOT(closeActiveSubWindow())); + + closeAllAct = new QAction(tr("Close &All"), this); + closeAllAct->setStatusTip(tr("Close all the windows")); + connect(closeAllAct, SIGNAL(triggered()), mdiArea, SLOT(closeAllSubWindows())); + + tileAct = new QAction(tr("&Tile"), this); + tileAct->setStatusTip(tr("Tile the windows")); + connect(tileAct, SIGNAL(triggered()), mdiArea, SLOT(tileSubWindows())); + + cascadeAct = new QAction(tr("&Cascade"), this); + cascadeAct->setStatusTip(tr("Cascade the windows")); + connect(cascadeAct, SIGNAL(triggered()), this, SLOT(doCascade())); + + nextAct = new QAction(tr("Ne&xt"), this); + nextAct->setShortcuts(QKeySequence::NextChild); + nextAct->setStatusTip(tr("Move the focus to the next window")); + connect(nextAct, SIGNAL(triggered()), mdiArea, SLOT(activateNextSubWindow())); + + previousAct = new QAction(tr("Pre&vious"), this); + previousAct->setShortcuts(QKeySequence::PreviousChild); + previousAct->setStatusTip(tr("Move the focus to the previous window")); + connect(previousAct, SIGNAL(triggered()), mdiArea, SLOT(activatePreviousSubWindow())); + } + + quitAction = new QAction(tr("&Quit"), this); + connect(quitAction, SIGNAL(triggered()), this, SLOT(doQuit())); + + windowMenuSeparatorAct = new QAction(this); + windowMenuSeparatorAct->setSeparator(true); + + updateWindowMenu(); + + connectMenu = mymenuBar->addMenu(tr("&Connect")); + + actHost[16] = new QAction("AGW Connect", this); + actHost[16]->setFont(*menufont); + actHost[16]->setVisible(0); + connectMenu->addAction(actHost[16]); + + connect(actHost[16], SIGNAL(triggered()), this, SLOT(Connect())); + + actHost[17] = new QAction("VARA Connect", this); + actHost[17]->setFont(*menufont); + actHost[17]->setVisible(0); + connectMenu->addAction(actHost[17]); + + connect(actHost[17], SIGNAL(triggered()), this, SLOT(Connect())); + + actHost[18] = new QAction("KISS Connect", this); + actHost[18]->setFont(*menufont); + actHost[18]->setVisible(KISSEnable); + actHost[18]->setEnabled(0); + connectMenu->addAction(actHost[18]); + + connect(actHost[18], SIGNAL(triggered()), this, SLOT(Connect())); + for (i = 0; i < MAXHOSTS; i++) + { + actHost[i] = new QAction(Host[i], this); + actHost[i]->setFont(*menufont); + connectMenu->addAction(actHost[i]); + connect(actHost[i], SIGNAL(triggered()), this, SLOT(Connect())); + } + + discAction = mymenuBar->addAction("&Disconnect"); + + // Place discAction in mac preferences menu, otherwise it doesn't appear + if (is_mac == true) { + QMenu * app_menu = mymenuBar->addMenu("App Menu"); + discAction->setMenuRole(QAction::ApplicationSpecificRole); + app_menu->addAction(discAction); + + } + + connect(discAction, SIGNAL(triggered()), this, SLOT(Disconnect())); + discAction->setEnabled(false); + + toolBar->setFont(*menufont); + +#ifndef ANDROID + toolBar->setVisible(false); +#endif + but4 = new QToolButton(); + but4->setPopupMode(QToolButton::InstantPopup); + but4->setText("Window"); + but4->addAction(windowMenu->menuAction()); + but4->setFont(*menufont); + toolBar->addWidget(but4); + + but5 = new QToolButton(); + but5->setPopupMode(QToolButton::InstantPopup); + but5->setText("Connect"); + but5->addAction(connectMenu->menuAction()); + but5->setFont(*menufont); + + toolBar->addWidget(but5); + + toolBar->addAction(discAction); + + setupMenu = mymenuBar->addMenu(tr("&Setup")); + hostsubMenu = setupMenu->addMenu("Hosts"); + hostsubMenu->setFont(*menufont); + + for (i = 0; i < MAXHOSTS; i++) + { + if (Host[i][0]) + actSetup[i] = new QAction(Host[i], this); + else + actSetup[i] = new QAction("New Host", this); + + hostsubMenu->addAction(actSetup[i]); + connect(actSetup[i], SIGNAL(triggered()), this, SLOT(SetupHosts())); + + actSetup[i]->setFont(*menufont); + } + + + // Setup Presentation Options + + setupMenu->addSeparator()->setText(tr("Presentation")); + + QActionGroup * termGroup = new QActionGroup(this); + + singleAct = setupMenuLine(nullptr, (char *)"Single Window (Term + Mon)", this, (TermMode == Single) && (singlemodeFormat == Term + Mon)); + singleAct2 = setupMenuLine(nullptr, (char *)"Single Window (Term only)", this, (TermMode == Single) && (singlemodeFormat == Term)); + MDIAct = setupMenuLine(nullptr, (char *)"MDI", this, TermMode == MDI); + tabbedAct = setupMenuLine(nullptr, (char *)"Tabbed", this, TermMode == Tabbed); + + termGroup->addAction(singleAct); + termGroup->addAction(singleAct2); + termGroup->addAction(MDIAct); + termGroup->addAction(tabbedAct); + + setupMenu->addAction(singleAct); + setupMenu->addAction(singleAct2); + setupMenu->addAction(MDIAct); + setupMenu->addAction(tabbedAct); + + setupMenu->addSeparator(); + + actFonts = new QAction("Terminal Font", this); + setupMenu->addAction(actFonts); + connect(actFonts, SIGNAL(triggered()), this, SLOT(doFonts())); + actFonts->setFont(*menufont); + + actmenuFont = new QAction("Menu Font", this); + setupMenu->addAction(actmenuFont); + connect(actmenuFont, SIGNAL(triggered()), this, SLOT(doMFonts())); + actmenuFont->setFont(*menufont); + + actColours = new QAction("Choose Colours", this); + setupMenu->addAction(actColours); + connect(actColours, SIGNAL(triggered()), this, SLOT(doColours())); + actColours->setFont(*menufont); + + AGWAction = new QAction("AGW Setup", this); + setupMenu->addAction(AGWAction); + connect(AGWAction, SIGNAL(triggered()), this, SLOT(AGWSlot())); + AGWAction->setFont(*menufont); + + VARAAction = new QAction("VARA Setup", this); + setupMenu->addAction(VARAAction); + connect(VARAAction, SIGNAL(triggered()), this, SLOT(VARASlot())); + VARAAction->setFont(*menufont); + + KISSAction = new QAction("KISS Setup", this); + setupMenu->addAction(KISSAction); + connect(KISSAction, SIGNAL(triggered()), this, SLOT(KISSSlot())); + KISSAction->setFont(*menufont); + + + actChatMode = setupMenuLine(setupMenu, (char *)"Chat Terminal Mode", this, ChatMode); + actBells = setupMenuLine(setupMenu, (char *)"Enable Bells", this, Bells); + actStripLF = setupMenuLine(setupMenu, (char *)"Strip Line Feeds", this, StripLF); + actIntervalBeep = setupMenuLine(setupMenu, (char *)"Beep after inactivity", this, AlertBeep); + actConnectBeep = setupMenuLine(setupMenu, (char *)"Beep on inbound connect", this, ConnectBeep); + + setupMenu->addAction(new QAction("Interpret non-UTF8 input as:", this)); + setupMenu->setFont(*menufont); + + QActionGroup * cpGroup = new QActionGroup(this); + + actnoConv = setupMenuLine(nullptr, (char *)" Don't Convert (assume is UTF-8)", this, convUTF8 == -1); + actAuto = setupMenuLine(nullptr, (char *)" Auto", this, convUTF8 == 0); + actCP1251 = setupMenuLine(nullptr, (char *)" CP1251 (Cyrillic)", this, convUTF8 == 1251); + actCP1252 = setupMenuLine(nullptr, (char *)" CP1252 (Western Europe)", this, convUTF8 == 1252); + actCP437 = setupMenuLine(nullptr, (char *)" CP437 (Windows Line Draw)", this, convUTF8 == 437); + + cpGroup->addAction(actnoConv); + cpGroup->addAction(actAuto); + cpGroup->addAction(actCP1251); + cpGroup->addAction(actCP1252); + cpGroup->addAction(actCP437); + + setupMenu->addAction(actnoConv); + setupMenu->addAction(actAuto); + setupMenu->addAction(actCP1251); + setupMenu->addAction(actCP1252); + setupMenu->addAction(actCP437); + + monitorMenu = mymenuBar->addMenu(tr("&Monitor")); + + MonTX = setupMenuLine(monitorMenu, (char *)"Monitor TX", this, 1); + MonSup = setupMenuLine(monitorMenu, (char *)"Monitor Supervisory", this, 1); + MonUI = setupMenuLine(monitorMenu, (char *)"Only Monitor UI Frames", this, 0); + MonNodes = setupMenuLine(monitorMenu, (char *)"Monitor NODES Broadcasts", this, 0); + MonColour = setupMenuLine(monitorMenu, (char *)"Enable Colour", this, 1); + + for (i = 0; i < MAXPORTS + 1; i++) + { + MonPort[i] = setupMenuLine(monitorMenu, (char *)"Port", this, 0); + MonPort[i]->setVisible(false); + } + + but1 = new QToolButton(); + but1->setPopupMode(QToolButton::InstantPopup); + but1->setText("Monitor"); + but1->addAction(monitorMenu->menuAction()); + toolBar->addWidget(but1); + + but2 = new QToolButton(); + but2->setPopupMode(QToolButton::InstantPopup); + but2->setText("Setup"); + but2->addAction(setupMenu->menuAction()); + toolBar->addWidget(but2); + + ListenAction = mymenuBar->addAction("&Listen"); + connect(ListenAction, SIGNAL(triggered()), this, SLOT(ListenSlot())); + + toolBar->addAction(ListenAction); + + YAPPMenu = mymenuBar->addMenu(tr("&YAPP")); + + YAPPSend = new QAction("Send File", this); + YAPPSetRX = new QAction("Set Receive Directory", this); + YAPPSend->setFont(*menufont); + YAPPSetRX->setFont(*menufont); + + YAPPMenu->addAction(YAPPSend); + YAPPMenu->addAction(YAPPSetRX); + YAPPSend->setEnabled(false); + + connect(YAPPSend, SIGNAL(triggered()), this, SLOT(doYAPPSend())); + connect(YAPPSetRX, SIGNAL(triggered()), this, SLOT(doYAPPSetRX())); + + + but3 = new QToolButton(); + but3->setPopupMode(QToolButton::InstantPopup); + but3->setText("YAPP"); + but3->addAction(YAPPMenu->menuAction()); + but3->setFont(*menufont); + toolBar->addWidget(but3); + + toolBar->setFont(*menufont); + + // Set up Status Bar + + setStyleSheet("QStatusBar{border-top: 1px outset black;} QStatusBar::item { border: 1px solid black; border-radius: 3px;}"); +// setStyleSheet("QStatusBar{border-top: 1px outset black;}"); + + + Status4 = new QLabel(""); + Status3 = new QLabel(" "); + Status2 = new QLabel(" "); + Status1 = new QLabel(" Disconnected "); + + Status1->setMinimumWidth(100); + Status2->setMinimumWidth(100); + Status3->setMinimumWidth(100); + Status4->setMinimumWidth(100); + + myStatusBar = statusBar(); + + statusBar()->addPermanentWidget(Status4); + statusBar()->addPermanentWidget(Status3); + statusBar()->addPermanentWidget(Status2); + statusBar()->addPermanentWidget(Status1); + + statusBar()->setVisible(AGWEnable | VARAEnable | KISSEnable); + // Restore saved sessions + + if (TermMode == Single) + { + Ui_ListenSession * Sess = newWindow(this, singlemodeFormat); + + ActiveSession = Sess; + ui.verticalLayout->addWidget(Sess); + + connectMenu->setEnabled(true); + discAction->setEnabled(false); + YAPPSend->setEnabled(false); + } + + if (TermMode == MDI) + { + int n = atoi(sessionList); + + char *p, *Context; + char delim[] = "|"; + + int type = 0, host = 0, topx, leftx, bottomx, rightx; + + p = strtok_s((char *)sessionList, delim, &Context); + + while (n--) + { + p = strtok_s(NULL, delim, &Context); + + sscanf(p, "%d,%d,%d,%d,%d,%d", &type, &host, &topx, &leftx, &bottomx, &rightx); + + Ui_ListenSession * Sess = newWindow(this, type); + + Sess->CurrentHost = host; + + QRect r(leftx, topx, rightx - leftx, bottomx - topx); + + Sess->sw->setGeometry(r); + Sess->sw->move(leftx, topx); + + } + } + + QTimer *timer = new QTimer(this); + connect(timer, SIGNAL(timeout()), this, SLOT(MyTimerSlot())); + timer->start(10000); + + QTimer *timer2 = new QTimer(this); + connect(timer2, SIGNAL(timeout()), this, SLOT(KISSTimerSlot())); + timer2->start(100); + + // Run timer now to connect to AGW if configured + + MyTimerSlot(); + + if (listenEnable) + _server->listen(QHostAddress::Any, listenPort); + + connect(_server, SIGNAL(newConnection()), this, SLOT(onNewConnection())); + + setFonts(); + + if (VARAEnable) + OpenPTTPort(); +} + +void QtTermTCP::setFonts() +{ + windowMenu->menuAction()->setFont(*menufont); + connectMenu->menuAction()->setFont(*menufont); + setupMenu->menuAction()->setFont(*menufont); + monitorMenu->menuAction()->setFont(*menufont); + YAPPMenu->menuAction()->setFont(*menufont); + + if (tabWidget) + tabWidget->setFont(*menufont); + mymenuBar->setFont(*menufont); + toolBar->setFont(*menufont); + but1->setFont(*menufont); + but2->setFont(*menufont); + but3->setFont(*menufont); + but4->setFont(*menufont); + but5->setFont(*menufont); + discAction->setFont(*menufont); + ListenAction->setFont(*menufont); + + for (int i = 0; i < MAXHOSTS; i++) + { + actHost[i]->setFont(*menufont); + actSetup[i]->setFont(*menufont); + } + + actHost[16]->setFont(*menufont); // AGW Host +} + +void QtTermTCP::doQuit() +{ + if (_server->isListening()) + _server->close(); + + SaveSettings(); + + QCoreApplication::quit(); +} + +// "Copy on select" Code + + +void QtTermTCP::onTEselectionChanged() +{ + QTextEdit * x = static_cast(QObject::sender()); + x->copy(); +} + +void QtTermTCP::onLEselectionChanged() +{ + QLineEdit * x = static_cast(QObject::sender()); + x->copy(); +} + +int splitY; // Value when menu added + +void QtTermTCP::setSplit() +{ + QAction * sender = static_cast(QObject::sender()); + + QWidget * Parent = sender->parentWidget(); + + if (Parent) + Parent = Parent->parentWidget(); + + int y = Parent->rect().height() - 50; + + // y is height of whole windom. splitX is new split position + // So split = 100 * splitx/x + + Split = (splitY * 100) / y; + + if (Split < 10) + Split = 10; + else if (Split > 90) + Split = 90; + + QSize size(800, 602); + QResizeEvent event(size, size); + + eventFilter(Parent, &event); +} +void QtTermTCP::showContextMenuT(const QPoint &pt) // Term Window +{ + QTextEdit* sender = static_cast(QObject::sender()); + + QMenu *menu = sender->createStandardContextMenu(); + + QString style = "QMenu {border-radius:15px; background-color: white;margin: 2px; border: 1px solid rgb(58, 80, 116); color: rgb(58, 80, 116);}QMenu::separator {height: 2px;background: rgb(58, 80, 116);margin-left: 10px;margin-right: 5px;}"; + menu->setStyleSheet(style); + + + QAction * actSplit = new QAction("Set Monitor/Output Split", sender); + + menu->addAction(actSplit); + + splitY = pt.y() + termX; + + connect(actSplit, SIGNAL(triggered()), this, SLOT(setSplit())); + + menu->exec(sender->mapToGlobal(pt)); + delete menu; +} + +void QtTermTCP::showContextMenuM(const QPoint &pt) // Mon Window +{ + QTextEdit* sender = static_cast(QObject::sender()); + + QMenu *menu = sender->createStandardContextMenu(); + QString style = "QMenu {border-radius:15px; background-color: white;margin: 2px; border: 1px solid rgb(58, 80, 116); color: rgb(58, 80, 116);}"; + menu->setStyleSheet(style); + + QAction * actSplit = new QAction("Set Monitor/Output Split", sender); + + menu->addAction(actSplit); + + splitY = pt.y(); + + connect(actSplit, SIGNAL(triggered()), this, SLOT(setSplit())); + + menu->exec(sender->mapToGlobal(pt)); + delete menu; +} + +extern "C" void setMenus(int State) +{ + // Sets Connect, Disconnect and YAPP Send enable flags to match connection state + + if (State) + { + connectMenu->setEnabled(false); + discAction->setEnabled(true); + YAPPSend->setEnabled(true); + } + else + { + connectMenu->setEnabled(true); + discAction->setEnabled(false); + YAPPSend->setEnabled(false); + } +} + +extern "C" void SetSessLabel(Ui_ListenSession * Sess, char * label) +{ + if (TermMode == MDI) + Sess->setWindowTitle(label); + else if (TermMode == Tabbed) + tabWidget->setTabText(Sess->Tab, label); + else if (TermMode == Single) + mythis->setWindowTitle(label); +} + + +extern "C" void ClearSessLabel(Ui_ListenSession * Sess) +{ + if (TermMode == MDI) + { + if (Sess->SessionType == Mon) // Mon Only + Sess->setWindowTitle("Monitor Session Disconnected"); + else if (Sess->SessionType == Listen) // Mon Only + Sess->setWindowTitle("Listen Window Disconnected"); + else + Sess->setWindowTitle("Disconnected"); + } + else if (TermMode == Tabbed) + { + if (Sess->SessionType == Mon) // Mon Only + tabWidget->setTabText(Sess->Tab, "Monitor"); + else + { + char Label[16]; + sprintf(Label, "Sess %d", Sess->Tab + 1); + tabWidget->setTabText(Sess->Tab, Label); + } + } + else if (TermMode == Single) + { + if (Sess->SessionType == Mon) // Mon Only + mythis->setWindowTitle("Monitor Session Disconnected"); + else + mythis->setWindowTitle("Disconnected"); + } +} + +void QtTermTCP::tabSelected(int Current) +{ + Ui_ListenSession * Sess = NULL; + + if (Current < _sessions.size()) + { + Sess = _sessions.at(Current); + + if (Sess == nullptr) + return; + + ActiveSession = Sess; + + if (Sess->clientSocket || Sess->AGWSession || Sess->KISSSession || Sess->KISSMode) + { + connectMenu->setEnabled(false); + discAction->setEnabled(true); + YAPPSend->setEnabled(true); + } + else + { + connectMenu->setEnabled(true); + discAction->setEnabled(false); + YAPPSend->setEnabled(false); + } + + // If a monitor Window, change Monitor config settings + + if (Sess->PortMonString[0]) + { + char * ptr = (char *)malloc(1024); + memcpy(ptr, Sess->PortMonString, 1024); + + int NumberofPorts = atoi((char *)&ptr[2]); + char *p, *Context; + char msg[80]; + int portnum, m; + char delim[] = "|"; + + // Remove old Monitor menu + + for (int i = 0; i < 32; i++) + { + SetPortMonLine(i, (char *)"", 0, 0); // Set all hidden + } + + p = strtok_s((char *)&ptr[2], delim, &Context); + + while (NumberofPorts--) + { + p = strtok_s(NULL, delim, &Context); + if (p == NULL) + break; + + m = portnum = atoi(p); + + sprintf(msg, "Port %s", p); + + if (m == 0) + m = 33; + + if (Sess->portmask & (1ll << (m - 1))) + SetPortMonLine(portnum, msg, 1, 1); + else + SetPortMonLine(portnum, msg, 1, 0); + } + free(ptr); + + MonTX->setChecked(Sess->mtxparam); + MonSup->setChecked(Sess->mcomparam); + MonUI->setChecked(Sess->monUI); + MonNodes->setChecked(Sess->MonitorNODES); + MonColour->setChecked(Sess->MonitorColour); + } + return; + } +} + +void QtTermTCP::SetupHosts() +{ + QAction * Act = static_cast(QObject::sender()); + int i; + + for (i = 0; i < MAXHOSTS; i++) + { + if (Act == actSetup[i]) + break; + } + + if (i > 15) + return; + + ConfigHost = i; + + TabDialog tabdialog(0); + tabdialog.exec(); +} + + +void QtTermTCP::Connect() +{ + QMdiSubWindow * UI; + Ui_ListenSession * Sess = nullptr; + QAction * Act = static_cast(QObject::sender()); + + int i; + + for (i = 0; i < MAXHOSTS; i++) + { + if (Act == actHost[i]) + break; + } + + SavedHost = i; // Last used + + if (TermMode == MDI) + { + UI = mdiArea->activeSubWindow(); + + for (i = 0; i < _sessions.size(); ++i) + { + Sess = _sessions.at(i); + + if (Sess->sw == UI) + break; + } + + if (i == _sessions.size()) + return; + } + + else if (TermMode == Tabbed) + { + Sess = (Ui_ListenSession *)tabWidget->currentWidget(); + } + + else if (TermMode == Single) + Sess = _sessions.at(0); + + if ((Sess == nullptr || Sess->SessionType & Listen)) + return; + + Sess->CurrentHost = SavedHost; + + if (Act == actHost[16]) + { + // This runs the AGW Connection dialog + + Sess->CurrentHost = 16; // Iast used + AGWConnect dialog(0); + dialog.exec(); + return; + } + + + if (Act == actHost[17]) + { + // This runs the VARA Connection dialog + + Sess->CurrentHost = 17; // Iast used + VARADataSock->Sess = Sess; + VARASock->Sess = Sess; + + VARAConnect dialog(0); + dialog.exec(); + WritetoOutputWindow(Sess, (unsigned char *)"Connecting...\r",14); + + return; + } + + if (Act == actHost[18]) + { + // This runs the KISS Connection dialog + + Sess->CurrentHost = 18; // Iast used + + KISSConnect dialog(0); + dialog.exec(); + return; + } + + // Set Monitor Params for this host + + sscanf(MonParams[Sess->CurrentHost], "%llx %x %x %x %x %x", + &Sess->portmask, &Sess->mtxparam, &Sess->mcomparam, + &Sess->MonitorNODES, &Sess->MonitorColour, &Sess->monUI); + + MonTX->setChecked(Sess->mtxparam); + MonSup->setChecked(Sess->mcomparam); + MonUI->setChecked(Sess->monUI); + MonNodes->setChecked(Sess->MonitorNODES); + MonColour->setChecked(Sess->MonitorColour); + + // Remove old Monitor menu + + for (i = 0; i < 32; i++) + { + SetPortMonLine(i, (char *)"", 0, 0); // Set all hidden + } + + delete(Sess->clientSocket); + + Sess->clientSocket = new myTcpSocket(); + Sess->clientSocket->Sess = Sess; + + connect(Sess->clientSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(displayError(QAbstractSocket::SocketError))); + connect(Sess->clientSocket, SIGNAL(readyRead()), this, SLOT(readyRead())); + connect(Sess->clientSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onSocketStateChanged(QAbstractSocket::SocketState))); + + Sess->clientSocket->connectToHost(&Host[Sess->CurrentHost][0], Port[Sess->CurrentHost]); + return; +} + +extern "C" void rst_timer(TAX25Port * AX25Sess); +extern "C" void set_unlink(TAX25Port * AX25Sess, Byte * path); + +void QtTermTCP::Disconnect() +{ + QMdiSubWindow * UI; + Ui_ListenSession * Sess = nullptr; + + if (TermMode == MDI) + { + int i; + + UI = mdiArea->activeSubWindow(); + + for (i = 0; i < _sessions.size(); ++i) + { + Sess = _sessions.at(i); + + if (Sess->sw == UI) + break; + } + + if (i == _sessions.size()) + return; + } + + else if (TermMode == Tabbed) + { + Sess = (Ui_ListenSession *)tabWidget->currentWidget(); + } + + else if (TermMode == Single) + Sess = _sessions.at(0); + + // if AGW send a d frame else disconnect TCP session + + if (Sess->AGWSession) + Send_AGW_Ds_Frame(Sess->AGWSession); + else if (VARASock && VARASock->Sess == Sess) + VARASock->write("DISCONNECT\r"); + else if (Sess->KISSSession) + { + if (Sess->KISSMode == 0) + { + TAX25Port * Port = (TAX25Port *)Sess->KISSSession; + + rst_timer(Port); + set_unlink(Port, Port->Path); + } + else + { + // KISS UI Mode + + char Msg[128]; + int Len = sprintf(Msg, "Disconnected\r"); + + Sess->KISSMode = 0; + SendtoTerm(Sess, Msg, Len); + ClearSessLabel(Sess); + setMenus(0); + Sess->KISSSession = NULL; + } + } + else + + { + if (Sess && Sess->clientSocket) + Sess->clientSocket->disconnectFromHost(); + } + return; +} + + +void QtTermTCP::doYAPPSend() +{ + QFileDialog dialog(this); + QStringList fileNames; + dialog.setFileMode(QFileDialog::AnyFile); + + QMdiSubWindow * UI; + Ui_ListenSession * Sess = nullptr; + int i; + + if (TermMode == MDI) + { + UI = mdiArea->activeSubWindow(); + + for (i = 0; i < _sessions.size(); ++i) + { + Sess = _sessions.at(i); + + if (Sess->sw == UI) + break; + } + + if (i == _sessions.size()) + return; + } + + else if (TermMode == Tabbed) + { + Sess = (Ui_ListenSession *)tabWidget->currentWidget(); + } + + else if (TermMode == Single) + Sess = _sessions.at(0); + + if (Sess == nullptr) + return; + + if ((Sess->SessionType & Mon)) + { + // Turn off monitoring + + setTraceOff(Sess); + } + + if (dialog.exec()) + { + char FN[256]; + + fileNames = dialog.selectedFiles(); + if (fileNames[0].length() < 256) + { + strcpy(FN, fileNames[0].toUtf8()); + YAPPSendFile(Sess, FN); + } + } +} + +void QtTermTCP::doYAPPSetRX() +{ + QFileDialog dialog(this); + QStringList fileNames; + dialog.setFileMode(QFileDialog::Directory); + dialog.setDirectory(YAPPPath); + + if (dialog.exec()) + { + fileNames = dialog.selectedFiles(); + if (fileNames[0].length() < 256) + strcpy(YAPPPath, fileNames[0].toUtf8()); + + SaveSettings(); + } +} + +void QtTermTCP::menuChecked() +{ + QAction * Act = static_cast(QObject::sender()); + + int state = Act->isChecked(); + int newTermMode = TermMode; + int newSingleMode = singlemodeFormat; + + if (Act == TabSingle || Act == TabBoth || Act == TabMon) + { + // Tabbed Window format had changed + + int i = tabWidget->currentIndex(); + + if (Act == TabSingle) + TabType[i] = Term; + else if (Act == TabBoth) + TabType[i] = Term + Mon; + else + TabType[i] = Mon; + + QMessageBox msgBox; + msgBox.setText("Tab Types will change next time program is started."); + msgBox.exec(); + + } + + if (Act == singleAct || Act == singleAct2 || Act == MDIAct || Act == tabbedAct) + { + // Term Mode had changed + + if (singleAct->isChecked()) + { + newTermMode = Single; + newSingleMode = Mon + Term; + } + else if (singleAct2->isChecked()) + { + newTermMode = Single; + newSingleMode = Term; + } + else if (MDIAct->isChecked()) + newTermMode = MDI; + else if (tabbedAct->isChecked()) + newTermMode = Tabbed; + + if (newTermMode != TermMode || newSingleMode != singlemodeFormat) + { + QSettings settings(GetConfPath(), QSettings::IniFormat); + settings.setValue("TermMode", newTermMode); + settings.setValue("singlemodeFormat", newSingleMode); + QMessageBox msgBox; + msgBox.setText("Presentation Mode will change next time program is started."); + msgBox.exec(); + } + + return; + } + + + if (Act == MonTX) + ActiveSession->mtxparam = state; + else if (Act == MonSup) + ActiveSession->mcomparam = state; + else if (Act == MonUI) + ActiveSession->monUI = state; + else if (Act == MonNodes) + ActiveSession->MonitorNODES = state; + else if (Act == MonColour) + ActiveSession->MonitorColour = state; + else if (Act == actChatMode) + ChatMode = state; + else if (Act == actBells) + Bells = state; + else if (Act == actStripLF) + StripLF = state; + else if (Act == actIntervalBeep) + AlertBeep = state; + else if (Act == actConnectBeep) + ConnectBeep = state; + else if (Act == actnoConv) + convUTF8 = -1; + else if (Act == actAuto) + convUTF8 = 0; + else if (Act == actCP1251) + convUTF8 = 1251; + else if (Act == actCP1252) + convUTF8 = 1252; + else if (Act == actCP437) + convUTF8 = 437; + else + { + // look for port entry + + for (int i = 0; i < MAXPORTS; i++) + { + if (Act == MonPort[i]) + { + unsigned long long mmask; + + if (i == 0) // BBS Mon - use bit 32 + mmask = 1ll << 32; + else + mmask = 1ll << (i - 1); + + if (state) + ActiveSession->portmask |= mmask; + else + ActiveSession->portmask &= ~mmask; + break; + } + } + } + + // Get active Session +/* + + QMdiSubWindow *SW = mdiArea->activeSubWindow(); + + Ui_ListenSession * Sess; + + for (int i = 0; i < _sessions.size(); ++i) + { + Sess = _sessions.at(i); + +// for (Ui_ListenSession * Sess : _sessions) +// { + if (Sess->sw == SW) + { + */ + + if (ActiveSession->clientSocket && ActiveSession->SessionType & Mon) + SendTraceOptions(ActiveSession); + + return; +} + + + + +void QtTermTCP::LDisconnect(Ui_ListenSession * LUI) +{ + if (LUI->clientSocket) + LUI->clientSocket->disconnectFromHost(); +} + +extern QApplication * a; + +void QtTermTCP::LreturnPressed(Ui_ListenSession * Sess) +{ + QByteArray stringData = Sess->inputWindow->text().toUtf8(); + + // if multiline input (eg from copy/paste) replace LF with CR + + char * ptr; + char * Msgptr; + char * Msg; + + QScrollBar *scrollbar = Sess->termWindow->verticalScrollBar(); + bool scrollbarAtBottom = (scrollbar->value() >= (scrollbar->maximum() - 4)); + + if (scrollbarAtBottom) + Sess->termWindow->moveCursor(QTextCursor::End); // So we don't get blank lines + + // Stack it + + Sess->StackIndex = 0; + + if (Sess->KbdStack[49]) + free(Sess->KbdStack[49]); + + for (int i = 48; i >= 0; i--) + { + Sess->KbdStack[i + 1] = Sess->KbdStack[i]; + } + + Sess->KbdStack[0] = qstrdup(stringData.data()); + + stringData.append('\n'); + + Msgptr = stringData.data(); + + while ((ptr = strchr(Msgptr, 11))) // Replace VT with LF + *ptr++ = 10; + + LastWrite = time(NULL); // Stop initial beep + Sess->SlowTimer = 0; + + if (Sess->KISSMode == 1) + { + // UI session. Send as UI Message + + while ((ptr = strchr(Msgptr, '\n'))) + { + *ptr++ = 0; + + Msg = (char *)malloc(strlen(Msgptr) + 10); + strcpy(Msg, Msgptr); + strcat(Msg, "\r"); + + Send_UI(0, 0xF0, MYCALL, Sess->UIDEST, (unsigned char *)Msg, (int)strlen(Msg)); + + WritetoOutputWindowEx(Sess, (unsigned char *)Msg, (int)strlen(Msg), + Sess->termWindow, &Sess->OutputSaveLen, Sess->OutputSave, EchoText); // Black + + free(Msg); + + Msgptr = ptr; + } + } + else if (Sess->KISSSession) + { + // Send to ax.25 code + + while ((ptr = strchr(Msgptr, '\n'))) + { + *ptr++ = 0; + + Msg = (char *)malloc(strlen(Msgptr) + 10); + strcpy(Msg, Msgptr); + strcat(Msg, "\r"); + + SendtoAX25(Sess->KISSSession, (unsigned char *)Msg, (int)strlen(Msg)); + + WritetoOutputWindowEx(Sess, (unsigned char *)Msg, (int)strlen(Msg), + Sess->termWindow, &Sess->OutputSaveLen, Sess->OutputSave, EchoText); // Black + + free(Msg); + + Msgptr = ptr; + } + } + + else if (Sess->AGWSession) + { + // Terminal is in AGWPE mode - send as AGW frame + + while ((ptr = strchr(Msgptr, '\n'))) + { + *ptr++ = 0; + + Msg = (char *)malloc(strlen(Msgptr) + 10); + strcpy(Msg, Msgptr); + strcat(Msg, "\r"); + + AGW_AX25_data_in(Sess->AGWSession, (unsigned char *)Msg, (int)strlen(Msg)); + + WritetoOutputWindowEx(Sess, (unsigned char *)Msg, (int)strlen(Msg), + Sess->termWindow, &Sess->OutputSaveLen, Sess->OutputSave, EchoText); // Black + +// Sess->termWindow->insertPlainText(Msg); + + free(Msg); + + Msgptr = ptr; + } + } + + else if (VARASock && VARASock->Sess == Sess) + { + while ((ptr = strchr(Msgptr, '\n'))) + { + *ptr++ = 0; + + Msg = (char *)malloc(strlen(Msgptr) + 10); + strcpy(Msg, Msgptr); + strcat(Msg, "\r"); + + VARADataSock->write(Msg); + + WritetoOutputWindowEx(Sess, (unsigned char *)Msg, (int)strlen(Msg), + Sess->termWindow, &Sess->OutputSaveLen, Sess->OutputSave, EchoText); // Black + +// Sess->termWindow->insertPlainText(Msg); + + free(Msg); + + Msgptr = ptr; + } + + + } + else if (Sess->clientSocket && Sess->clientSocket->state() == QAbstractSocket::ConnectedState) + { + while ((ptr = strchr(Msgptr, '\n'))) + { + *ptr++ = 0; + + Msg = (char *)malloc(strlen(Msgptr) + 10); + strcpy(Msg, Msgptr); + strcat(Msg, "\r"); + + Sess->clientSocket->write(Msg); + + WritetoOutputWindowEx(Sess, (unsigned char *)Msg, (int)strlen(Msg), + Sess->termWindow, &Sess->OutputSaveLen, Sess->OutputSave, EchoText); // Black + +// Sess->termWindow->insertPlainText(Msg); + + free(Msg); + + Msgptr = ptr; + } + } + else + { + // Not Connected + + if (Sess->SessionType != Listen) + { + if (Sess->CurrentHost < MAXHOSTS) + { + // Last connect was TCP + + char Msg[] = "Connecting....\r"; + + WritetoOutputWindowEx(Sess, (unsigned char *)Msg, (int)strlen(Msg), + Sess->termWindow, &Sess->OutputSaveLen, Sess->OutputSave, WarningText); // Red + + delete(Sess->clientSocket); + + Sess->clientSocket = new myTcpSocket(); + Sess->clientSocket->Sess = Sess; + + connect(Sess->clientSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(displayError(QAbstractSocket::SocketError))); + connect(Sess->clientSocket, SIGNAL(readyRead()), this, SLOT(readyRead())); + connect(Sess->clientSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onSocketStateChanged(QAbstractSocket::SocketState))); + + Sess->clientSocket->connectToHost(&Host[Sess->CurrentHost][0], Port[Sess->CurrentHost]); + } + + else if (Sess->CurrentHost == 16) // AGW + { + // Invoke AGW connect dialog + + AGWConnect dialog(0); + dialog.exec(); + + WritetoOutputWindow(Sess, (unsigned char *)"Connecting...\r", 14); + return; + } + + else if (Sess->CurrentHost == 17) // VARA + { + // Invoke VARA connect dialog + + VARAConnect dialog(0); + dialog.exec(); + + WritetoOutputWindow(Sess, (unsigned char *)"Connecting...\r", 14); + return; + } + + else if (Sess->CurrentHost == 18) // KISS + { + // Do we send as UI or invoke kiss connect dialog ?? + + // Try Connect Dialog for now - may make a menu option + + KISSConnect dialog(0); + dialog.exec(); + + WritetoOutputWindow(Sess, (unsigned char *)"Connecting...\r", 14); + return; + } + } + else + { + char Msg[] = "Incoming Session - Can't Connect\r"; + + WritetoOutputWindowEx(Sess, (unsigned char *)Msg, (int)strlen(Msg), + Sess->termWindow, &Sess->OutputSaveLen, Sess->OutputSave, 81); // Red + + } + } + + if (scrollbarAtBottom) + Sess->termWindow->moveCursor(QTextCursor::End); + + Sess->inputWindow->setText(""); + a->inputMethod()->hide(); +} + + +void QtTermTCP::displayError(QAbstractSocket::SocketError socketError) +{ + myTcpSocket* sender = static_cast(QObject::sender()); + + switch (socketError) + { + case QAbstractSocket::RemoteHostClosedError: + break; + + case QAbstractSocket::HostNotFoundError: + QMessageBox::information(this, tr("QtTermTCP"), + tr("The host was not found. Please check the " + "host name and portsettings->")); + break; + + case QAbstractSocket::ConnectionRefusedError: + QMessageBox::information(this, tr("QtTermTCP"), + tr("The connection was refused by the peer.")); + break; + + default: + QMessageBox::information(this, tr("QtTermTCP"), + tr("The following error occurred: %1.") + .arg(sender->errorString())); + } + + connectMenu->setEnabled(true); +} + +void QtTermTCP::readyRead() +{ + int Read; + unsigned char Buffer[8192]; + myTcpSocket* Socket = static_cast(QObject::sender()); + + Ui_ListenSession * Sess = (Ui_ListenSession *)Socket->Sess; + + // read the data from the socket + + Read = Socket->read((char *)Buffer, 2047); + + while (Read > 0) + { +// if (InputMode == 'Y') // Yapp +// { +// QString myString = QString::fromUtf8((char*)Buffer, Read); +// QByteArray ptr = myString.toLocal8Bit(); +// memcpy(Buffer, ptr.data(), ptr.length()); +// Read = ptr.length(); +// } + + ProcessReceivedData(Sess, Buffer, Read); + + QString myString = QString::fromUtf8((char*)Buffer); +// qDebug() << myString; + Read = Socket->read((char *)Buffer, 2047); + } +} + +extern "C" int SocketSend(Ui_ListenSession * Sess, char * Buffer, int len) +{ + myTcpSocket *Socket = Sess->clientSocket; + + if (Socket && Socket->state() == QAbstractSocket::ConnectedState) + return Socket->write(Buffer, len); + + return 0; +} + +extern "C" int SocketFlush(Ui_ListenSession * Sess) +{ + if (Sess->AGWSession || Sess->KISSSession || (VARASock && VARASock->Sess == Sess)) + return 0; + + myTcpSocket* Socket = Sess->clientSocket; + + if (Socket && Socket->state() == QAbstractSocket::ConnectedState) + return Socket->flush(); + + return 0; +} + +extern "C" void mySleep(int ms) +{ + QThread::msleep(ms); +} + +extern "C" void SetPortMonLine(int i, char * Text, int visible, int enabled) +{ + MonPort[i]->setText(Text); + MonPort[i]->setVisible(visible); + MonPort[i]->setChecked(enabled); +} + +bool scrollbarAtBottom = 0; + +extern "C" void WritetoOutputWindowEx(Ui_ListenSession * Sess, unsigned char * Buffer, int len, QTextEdit * termWindow, int * OutputSaveLen, char * OutputSave, QColor Colour); + +extern "C" void WritetoOutputWindow(Ui_ListenSession * Sess, unsigned char * Buffer, int len) +{ + WritetoOutputWindowEx(Sess, Buffer, len, Sess->termWindow, &Sess->OutputSaveLen, Sess->OutputSave, outputText); +} + + +extern "C" void WritetoOutputWindowEx(Ui_ListenSession * Sess, unsigned char * Buffer, int len, QTextEdit * termWindow, int * OutputSaveLen, char * OutputSave, QColor Colour) +{ + unsigned char Copy[8192]; + unsigned char * ptr1, *ptr2; + unsigned char Line[8192]; + unsigned char out[8192]; + int outlen; + + int num; + + if (termWindow == NULL) + return; + + time_t NOW = time(NULL); + + // Beep if no output for a while + + if (AlertInterval && (NOW - LastWrite) > AlertInterval) + { + if (AlertBeep) + myBeep(); + } + + LastWrite = NOW; + + // Mustn't mess with original buffer + + memcpy(Copy, Buffer, len); + + Copy[len] = 0; + + ptr1 = Copy; + + const QTextCursor old_cursor = termWindow->textCursor(); + const int old_scrollbar_value = termWindow->verticalScrollBar()->value(); + const bool is_scrolled_down = old_scrollbar_value == termWindow->verticalScrollBar()->maximum(); + + if (*OutputSaveLen) + { + // Have part line - append to it + memcpy(&OutputSave[*OutputSaveLen], Copy, len); + *OutputSaveLen += len; + ptr1 = (unsigned char *)OutputSave; + len = *OutputSaveLen; + *OutputSaveLen = 0; + + // part line was written to screen so remove it + +// termWindow->setFocus(); + termWindow->moveCursor(QTextCursor::End, QTextCursor::MoveAnchor); + termWindow->moveCursor(QTextCursor::StartOfBlock, QTextCursor::MoveAnchor); + termWindow->moveCursor(QTextCursor::End, QTextCursor::KeepAnchor); + termWindow->textCursor().removeSelectedText(); + // termWindow->textCursor().deletePreviousChar(); + } + + // Move the cursor to the end of the document. + + termWindow->moveCursor(QTextCursor::End); + + // Insert the text at the position of the cursor (which is the end of the document). + +lineloop: + + if (len <= 0) + { + if (old_cursor.hasSelection() || !is_scrolled_down) + { + // The user has selected text or scrolled away from the bottom: maintain position. + termWindow->setTextCursor(old_cursor); + termWindow->verticalScrollBar()->setValue(old_scrollbar_value); + } + else + { + // The user hasn't selected any text and the scrollbar is at the bottom: scroll to the bottom. + termWindow->moveCursor(QTextCursor::End); + termWindow->verticalScrollBar()->setValue(termWindow->verticalScrollBar()->maximum()); + } + + return; + } + + + // copy text to control a line at a time + + ptr2 = (unsigned char *)memchr(ptr1, 13, len); + + if (ptr2 == 0) // No CR + { + if (len > 8000) + len = 8000; // Should never get lines this long + + memmove(OutputSave, ptr1, len); + *OutputSaveLen = len; + + // Write part line to screen + + memcpy(Line, ptr1, len); + Line[len] = 0; + + // I don't think I need to worry if UTF8 as will be rewritten when rest arrives + + if (Line[0] == 0x1b) // Colour Escape + { + if (Sess->MonitorColour) + termWindow->setTextColor(Colours[Line[1] - 10]); + + termWindow->textCursor().insertText(QString::fromUtf8((char*)&Line[2])); + } + else + { + termWindow->setTextColor(Colour); + termWindow->textCursor().insertText(QString::fromUtf8((char*)Line)); + } + +// *OutputSaveLen = 0; // Test if we need to delete part line + +// if (scrollbarAtBottom) +// termWindow->moveCursor(QTextCursor::End); + + if (old_cursor.hasSelection() || !is_scrolled_down) + { + // The user has selected text or scrolled away from the bottom: maintain position. + termWindow->setTextCursor(old_cursor); + termWindow->verticalScrollBar()->setValue(old_scrollbar_value); + } + else + { + // The user hasn't selected any text and the scrollbar is at the bottom: scroll to the bottom. + termWindow->moveCursor(QTextCursor::End); + termWindow->verticalScrollBar()->setValue(termWindow->verticalScrollBar()->maximum()); + } + return; + } + + *(ptr2++) = 0; + + if (Bells) + { + char * ptr; + + do { + ptr = (char *)memchr(ptr1, 7, len); + + if (ptr) + { + *(ptr) = 32; + myBeep(); + } + } while (ptr); + } + + num = ptr2 - ptr1 - 1; + + // if (LogMonitor) WriteMonitorLine(ptr1, ptr2 - ptr1); + + memcpy(Line, ptr1, num); + Line[num++] = 13; + Line[num] = 0; + + if (Line[0] == 0x1b) // Colour Escape + { + if (Sess->MonitorColour) + termWindow->setTextColor(Colours[Line[1] - 10]); + + outlen = checkUTF8(&Line[2], num - 2, out); + out[outlen] = 0; + termWindow->textCursor().insertText(QString::fromUtf8((char*)out)); +// termWindow->textCursor().insertText(QString::fromUtf8((char*)&Line[2])); + } + else + { + termWindow->setTextColor(Colour); + + outlen = checkUTF8(Line, num, out); + out[outlen] = 0; + termWindow->textCursor().insertText(QString::fromUtf8((char*)out)); +// termWindow->textCursor().insertText(QString::fromUtf8((char*)Line)); + } + + len -= (ptr2 - ptr1); + + ptr1 = ptr2; + + if ((len > 0) && StripLF) + { + if (*ptr1 == 0x0a) // Line Feed + { + ptr1++; + len--; + } + } + + + + goto lineloop; + +} + +extern "C" void WritetoMonWindow(Ui_ListenSession * Sess, unsigned char * Msg, int len) +{ + char * ptr1 = (char *)Msg, * ptr2; + char Line[512]; + int num; + +// QScrollBar *scrollbar = Sess->monWindow->verticalScrollBar(); +// bool scrollbarAtBottom = (scrollbar->value() >= (scrollbar->maximum() - 4)); + + if (Sess->monWindow == nullptr) + return; + + Sess->monWindow->setStyleSheet(monStyleSheet); + + const QTextCursor old_cursor = Sess->monWindow->textCursor(); + const int old_scrollbar_value = Sess->monWindow->verticalScrollBar()->value(); + const bool is_scrolled_down = old_scrollbar_value == Sess->monWindow->verticalScrollBar()->maximum(); + + // Move the cursor to the end of the document. + Sess->monWindow->moveCursor(QTextCursor::End); + + // Insert the text at the position of the cursor (which is the end of the document). + + + // Write a line at a time so we can action colour chars + +// Buffer[Len] = 0; + +// if (scrollbarAtBottom) +// Sess->monWindow->moveCursor(QTextCursor::End); + + if (Sess->MonSaveLen) + { + // Have part line - append to it + memcpy(&Sess->MonSave[Sess->MonSaveLen], Msg, len); + Sess->MonSaveLen += len; + ptr1 = Sess->MonSave; + len = Sess->MonSaveLen; + Sess->MonSaveLen = 0; + } + +lineloop: + + if (len <= 0) + { +// if (scrollbarAtBottom) +// Sess->monWindow->moveCursor(QTextCursor::End); + + + if (old_cursor.hasSelection() || !is_scrolled_down) + { + // The user has selected text or scrolled away from the bottom: maintain position. + Sess->monWindow->setTextCursor(old_cursor); + Sess->monWindow->verticalScrollBar()->setValue(old_scrollbar_value); + } + else + { + // The user hasn't selected any text and the scrollbar is at the bottom: scroll to the bottom. + Sess->monWindow->moveCursor(QTextCursor::End); + Sess->monWindow->verticalScrollBar()->setValue(Sess->monWindow->verticalScrollBar()->maximum()); + } + + return; + } + + // copy text to control a line at a time + + ptr2 = (char *)memchr(ptr1, 13, len); + + if (ptr2 == 0) // No CR + { + memmove(Sess->MonSave, ptr1, len); + Sess->MonSaveLen = len; + return; + } + + *(ptr2++) = 0; + +// if (LogMonitor) WriteMonitorLine(ptr1, ptr2 - ptr1); + + num = ptr2 - ptr1 - 1; + + memcpy(Line, ptr1, num); + Line[num++] = 13; + Line[num] = 0; + + if (Line[0] == 0x1b) // Colour Escape + { + if (Sess->MonitorColour) + { + if (Line[1] == 17) + Sess->monWindow->setTextColor(monRxText); + else + Sess->monWindow->setTextColor(monTxText); + } + else + Sess->monWindow->setTextColor(monOtherText); + + Sess->monWindow->textCursor().insertText(QString::fromUtf8((char*)&Line[2])); + } + else + { + Sess->monWindow->textCursor().insertText(QString::fromUtf8((char*)Line)); + } + len -= (ptr2 - ptr1); + + ptr1 = ptr2; + + goto lineloop; + +} + +int ProcessYAPPMessage(Ui_ListenSession * Sess, UCHAR * Msg, int Len); +void QueueMsg(Ui_ListenSession * Sess, char * Msg, int Len); + +// YAPP stuff + +#define SOH 1 +#define STX 2 +#define ETX 3 +#define EOT 4 +#define ENQ 5 +#define ACK 6 +#define DLE 0x10 +#define NAK 0x15 +#define CAN 0x18 + +extern "C" void SendtoTerm(Ui_ListenSession * Sess, char * Msg, int Len) +{ + Sess->SlowTimer = 0; + + if (Sess->InputMode == 'Y') // Yapp + { + ProcessYAPPMessage(Sess, (unsigned char *)Msg, Len); + return; + } + + // Could be a YAPP Header + + if (Len == 2 && Msg[0] == ENQ && Msg[1] == 1) // YAPP Send_Init + { + char YAPPRR[2]; + + // Turn off monitoring + + Sess->InputMode = 'Y'; + + YAPPRR[0] = ACK; + YAPPRR[1] = 1; + QueueMsg(Sess, YAPPRR, 2); + + return; + } + + WritetoOutputWindow(Sess, (unsigned char *)Msg, Len); + +} + +QSettings* settings; + +// This makes geting settings for more channels easier + +char Prefix[16] = "AX25_A"; + +void GetPortSettings(int Chan); + +QVariant getAX25Param(const char * key, QVariant Default) +{ + char fullKey[64]; + + sprintf(fullKey, "%s/%s", Prefix, key); + return settings->value(fullKey, Default); +} + +void getAX25Params(int chan) +{ + Prefix[5] = chan + 'A'; + GetPortSettings(chan); +} + + +void GetPortSettings(int Chan) +{ + maxframe[Chan] = getAX25Param("Maxframe", 4).toInt(); + fracks[Chan] = getAX25Param("Retries", 10).toInt(); + frack_time[Chan] = getAX25Param("FrackTime", 8).toInt(); + persist[Chan] = getAX25Param("Persist", 128).toInt(); + kisspaclen[Chan] = getAX25Param("Paclen", 128).toInt(); + idletime[Chan] = getAX25Param("IdleTime", 180).toInt(); + slottime[Chan] = getAX25Param("SlotTime", 100).toInt(); + resptime[Chan] = getAX25Param("RespTime", 1500).toInt(); + TXFrmMode[Chan] = getAX25Param("TXFrmMode", 1).toInt(); + max_frame_collector[Chan] = getAX25Param("FrameCollector", 6).toInt(); + //exclude_callsigns[Chan]= getAX25Param("ExcludeCallsigns/"); + //exclude_APRS_frm[Chan]= getAX25Param("ExcludeAPRSFrmType/"); + KISS_opt[Chan] = getAX25Param("KISSOptimization", false).toInt();; + dyn_frack[Chan] = getAX25Param("DynamicFrack", false).toInt();; + IPOLL[Chan] = getAX25Param("IPOLL", 80).toInt(); + +} + + +void GetSettings() +{ + QByteArray qb; + int i; + char Key[16]; + char Param[256]; + + settings = new QSettings(GetConfPath(), QSettings::IniFormat); + + // Get saved session definitions + + sessionList = strdup(settings->value("Sessions", "1|3, 0, 5, 5, 600, 800|").toString().toUtf8()); + + for (i = 0; i < MAXHOSTS; i++) + { + sprintf(Key, "HostParams%d", i); + + qb =settings->value(Key).toByteArray(); + + DecodeSettingsLine(i, qb.data()); + } + + Split =settings->value("Split", 50).toInt(); + ChatMode =settings->value("ChatMode", 1).toInt(); + Bells =settings->value("Bells", 1).toInt(); + StripLF =settings->value("StripLF", 1).toInt(); + AlertBeep =settings->value("AlertBeep", 1).toInt(); + ConnectBeep =settings->value("ConnectBeep", 1).toInt(); + SavedHost =settings->value("CurrentHost", 0).toInt(); + strcpy(YAPPPath,settings->value("YAPPPath", "").toString().toUtf8()); + + listenPort =settings->value("listenPort", 8015).toInt(); + listenEnable =settings->value("listenEnable", false).toBool(); + strcpy(listenCText,settings->value("listenCText", "").toString().toUtf8()); + + TermMode =settings->value("TermMode", 0).toInt(); + singlemodeFormat =settings->value("singlemodeFormat", Term + Mon).toInt(); + + AGWEnable =settings->value("AGWEnable", 0).toInt(); + AGWMonEnable =settings->value("AGWMonEnable", 0).toInt(); + strcpy(AGWTermCall,settings->value("AGWTermCall", "").toString().toUtf8()); + strcpy(AGWBeaconDest,settings->value("AGWBeaconDest", "").toString().toUtf8()); + strcpy(AGWBeaconPath,settings->value("AGWBeaconPath", "").toString().toUtf8()); + AGWBeaconInterval =settings->value("AGWBeaconInterval", 0).toInt(); + strcpy(AGWBeaconPorts,settings->value("AGWBeaconPorts", "").toString().toUtf8()); + strcpy(AGWBeaconMsg,settings->value("AGWBeaconText", "").toString().toUtf8()); + strcpy(AGWHost,settings->value("AGWHost", "127.0.0.1").toString().toUtf8()); + AGWPortNum =settings->value("AGWPort", 8000).toInt(); + AGWPaclen =settings->value("AGWPaclen", 80).toInt(); + AGWToCalls =settings->value("AGWToCalls","").toStringList(); + convUTF8 =settings->value("convUTF8", 0).toInt(); + + KISSEnable =settings->value("KISSEnable", 0).toInt(); + strcpy(MYCALL,settings->value("MYCALL", "").toString().toUtf8()); + strcpy(KISSHost,settings->value("KISSHost", "127.0.0.1").toString().toUtf8()); + KISSPortNum = settings->value("KISSPort", 8100).toInt(); + KISSMode = settings->value("KISSMode", 0).toInt(); + + strcpy(SerialPort,settings->value("KISSSerialPort", "None").toString().toUtf8()); + KISSBAUD =settings->value("KISSBAUD", 19200).toInt(); + getAX25Params(0); + + VARAEnable =settings->value("VARAEnable", 0).toInt(); + strcpy(VARAHost,settings->value("VARAHost", "127.0.0.1").toString().toUtf8()); + strcpy(VARAHostFM,settings->value("VARAHostFM", "127.0.0.1").toString().toUtf8()); + strcpy(VARAHostHF,settings->value("VARAHostHF", "127.0.0.1").toString().toUtf8()); + strcpy(VARAHostSAT,settings->value("VARAHostSAT", "127.0.0.1").toString().toUtf8()); + VARAPortNum =settings->value("VARAPort", 8300).toInt(); + VARAPortHF =settings->value("VARAPortHF", 8300).toInt(); + VARAPortFM =settings->value("VARAPortFM", 8300).toInt(); + VARAPortSAT =settings->value("VARAPortSAT", 8300).toInt(); + strcpy(VARAPath,settings->value("VARAPath", "C:\\VARA\\VARA.exe").toString().toUtf8()); + strcpy(VARAPathHF,settings->value("VARAPathHF", "C:\\VARA\\VARA.exe").toString().toUtf8()); + strcpy(VARAPathFM,settings->value("VARAPathFM", "C:\\VARA\\VARAFM.exe").toString().toUtf8()); + strcpy(VARAPathSAT,settings->value("VARAPathSAT", "C:\\VARA\\VARASAT.exe").toString().toUtf8()); + strcpy(VARATermCall,settings->value("VARATermCall", "").toString().toUtf8()); + VARA500 =settings->value("VARA500", 0).toInt(); + VARA2300 =settings->value("VARA2300", 1).toInt(); + VARA2750 =settings->value("VARA2750", 0).toInt(); + VARAHF =settings->value("VARAHF", 1).toInt(); + VARAFM =settings->value("VARAFM", 0).toInt(); + VARASAT =settings->value("VARASAT", 0).toInt(); + + strcpy(PTTPort,settings->value("PTT", "None").toString().toUtf8()); + PTTMode =settings->value("PTTMode", 19200).toInt(); + PTTBAUD =settings->value("PTTBAUD", 19200).toInt(); + CATHex =settings->value("CATHex", 1).toInt(); + + strcpy(PTTOnString,settings->value("PTTOnString", "").toString().toUtf8()); + strcpy(PTTOffString,settings->value("PTTOffString", "").toString().toUtf8()); + + pttGPIOPin =settings->value("pttGPIOPin", 17).toInt(); + pttGPIOPinR =settings->value("pttGPIOPinR", 17).toInt(); + +#ifdef WIN32 + strcpy(CM108Addr,settings->value("CM108Addr", "0xD8C:0x08").toString().toUtf8()); +#else + strcpy(CM108Addr,settings->value("CM108Addr", "/dev/hidraw0").toString().toUtf8()); +#endif + + HamLibPort =settings->value("HamLibPort", 4532).toInt(); + strcpy(HamLibHost,settings->value("HamLibHost", "127.0.0.1").toString().toUtf8()); + + FLRigPort =settings->value("FLRigPort", 12345).toInt(); + strcpy(FLRigHost,settings->value("FLRigHost", "127.0.0.1").toString().toUtf8()); + + + strcpy(Param,settings->value("TabType", "1 1 1 1 1 1 1 2 2").toString().toUtf8()); + sscanf(Param, "%d %d %d %d %d %d %d %d %d %d", + &TabType[0], &TabType[1], &TabType[2], &TabType[3], &TabType[4], + &TabType[5], &TabType[6], &TabType[7], &TabType[8], &TabType[9]); + + monBackground = settings->value("monBackground", QColor(255,255,255)).value(); + monRxText = settings->value("monRxText", QColor(0, 0, 255)).value(); + monTxText = settings->value("monTxText", QColor(255, 0, 0)).value(); + monOtherText = settings->value("monOtherText", QColor(0, 0, 0)).value(); + + termBackground = settings->value("termBackground", QColor(255, 255, 255)).value(); + outputText = settings->value("outputText", QColor(0, 0, 255)).value(); + EchoText = settings->value("EchoText", QColor(0, 0, 0)).value(); + WarningText = settings->value("WarningText", QColor(255, 0, 0)).value(); + + inputBackground = settings->value("inputBackground", QColor(255, 255, 255)).value(); + inputText = settings->value("inputText", QColor(0, 0, 0)).value(); + + delete(settings); +} + +void SavePortSettings(int Chan); + +void saveAX25Param(const char * key, QVariant Value) +{ + char fullKey[64]; + + sprintf(fullKey, "%s/%s", Prefix, key); + + settings->setValue(fullKey, Value); +} + +void saveAX25Params(int chan) +{ + Prefix[5] = chan + 'A'; + SavePortSettings(chan); +} + +void SavePortSettings(int Chan) +{ + saveAX25Param("Retries", fracks[Chan]); + saveAX25Param("Maxframe", maxframe[Chan]); + saveAX25Param("Paclen", kisspaclen[Chan]); + saveAX25Param("FrackTime", frack_time[Chan]); + saveAX25Param("IdleTime", idletime[Chan]); + saveAX25Param("SlotTime", slottime[Chan]); + saveAX25Param("Persist", persist[Chan]); + saveAX25Param("RespTime", resptime[Chan]); + saveAX25Param("TXFrmMode", TXFrmMode[Chan]); + saveAX25Param("FrameCollector", max_frame_collector[Chan]); + saveAX25Param("ExcludeCallsigns", exclude_callsigns[Chan]); + saveAX25Param("ExcludeAPRSFrmType", exclude_APRS_frm[Chan]); + saveAX25Param("KISSOptimization", KISS_opt[Chan]); + saveAX25Param("DynamicFrack", dyn_frack[Chan]); + saveAX25Param("BitRecovery", recovery[Chan]); + saveAX25Param("IPOLL", IPOLL[Chan]); + saveAX25Param("MyDigiCall", MyDigiCall[Chan]); +} + +extern "C" void SaveSettings() +{ + int i; + char Param[512]; + char Key[16]; + + settings = new QSettings(GetConfPath(), QSettings::IniFormat); + + for (i = 0; i < MAXHOSTS; i++) + { + sprintf(Key, "HostParams%d", i); + EncodeSettingsLine(i, Param); + settings->setValue(Key, Param); + } + + settings->setValue("Split", Split); + settings->setValue("ChatMode", ChatMode); + settings->setValue("Bells", Bells); + settings->setValue("StripLF", StripLF); + settings->setValue("AlertBeep", AlertBeep); + settings->setValue("ConnectBeep", ConnectBeep); + settings->setValue("CurrentHost", SavedHost); + + settings->setValue("YAPPPath", YAPPPath); + settings->setValue("listenPort", listenPort); + settings->setValue("listenEnable", listenEnable); + settings->setValue("listenCText", listenCText); + settings->setValue("convUTF8", convUTF8); + + settings->setValue("PTT", PTTPort); + settings->setValue("PTTBAUD", PTTBAUD); + settings->setValue("PTTMode", PTTMode); + + settings->setValue("CATHex", CATHex); + + settings->setValue("PTTOffString", PTTOffString); + settings->setValue("PTTOnString", PTTOnString); + + settings->setValue("pttGPIOPin", pttGPIOPin); + settings->setValue("pttGPIOPinR", pttGPIOPinR); + + settings->setValue("CM108Addr", CM108Addr); + settings->setValue("HamLibPort", HamLibPort); + settings->setValue("HamLibHost", HamLibHost); + settings->setValue("FLRigPort", FLRigPort); + settings->setValue("FLRigHost", FLRigHost); + + + // Save Sessions + + char SessionString[1024]; + int SessStringLen; + + SessStringLen = sprintf(SessionString, "%d|", _sessions.size()); + + if (TermMode == MDI) + { + for (int i = 0; i < _sessions.size(); ++i) + { + Ui_ListenSession * Sess = _sessions.at(i); + + QRect r = Sess->sw->geometry(); + + SessStringLen += sprintf(&SessionString[SessStringLen], + "%d, %d, %d, %d, %d, %d|", Sess->SessionType, Sess->CurrentHost, r.top(), r.left(), r.bottom(), r.right()); + } + + settings->setValue("Sessions", SessionString); + } + + settings->setValue("AGWEnable", AGWEnable); + settings->setValue("AGWMonEnable", AGWMonEnable); + settings->setValue("AGWTermCall", AGWTermCall); + settings->setValue("AGWBeaconDest", AGWBeaconDest); + settings->setValue("AGWBeaconPath", AGWBeaconPath); + settings->setValue("AGWBeaconInterval", AGWBeaconInterval); + settings->setValue("AGWBeaconPorts", AGWBeaconPorts); + settings->setValue("AGWBeaconText", AGWBeaconMsg); + settings->setValue("AGWHost", AGWHost); + settings->setValue("AGWPort", AGWPortNum); + settings->setValue("AGWPaclen", AGWPaclen); + settings->setValue("AGWToCalls", AGWToCalls); + + + settings->setValue("KISSEnable", KISSEnable); + settings->setValue("MYCALL", MYCALL); + settings->setValue("KISSHost", KISSHost); + settings->setValue("KISSMode", KISSMode); + settings->setValue("KISSPort", KISSPortNum); + settings->setValue("KISSSerialPort", SerialPort); + settings->setValue("KISSBAUD", KISSBAUD); + saveAX25Params(0); + + settings->setValue("VARAEnable", VARAEnable); + settings->setValue("VARATermCall", VARATermCall); + settings->setValue("VARAHost", VARAHost); + settings->setValue("VARAPort", VARAPortNum); + settings->setValue("VARAPath", VARAPath); + settings->setValue("VARAHostHF", VARAHostHF); + settings->setValue("VARAPortHF", VARAPortHF); + settings->setValue("VARAPathHF", VARAPathHF); + settings->setValue("VARAHostFM", VARAHostFM); + settings->setValue("VARAPortFM", VARAPortFM); + settings->setValue("VARAPathFM", VARAPathFM); + settings->setValue("VARAHostSAT", VARAHostSAT); + settings->setValue("VARAPortSAT", VARAPortSAT); + settings->setValue("VARAPathSAT", VARAPathSAT); + settings->setValue("VARA500", VARA500); + settings->setValue("VARA2300", VARA2300); + settings->setValue("VARA2750", VARA2750); + settings->setValue("VARAHF", VARAHF); + settings->setValue("VARAFM", VARAFM); + settings->setValue("VARASAT", VARASAT); + + + sprintf(Param, "%d %d %d %d %d %d %d %d %d %d", + TabType[0], TabType[1], TabType[2], TabType[3], TabType[4], TabType[5], TabType[6], TabType[7], TabType[8], TabType[9]); + + settings->setValue("TabType", Param); + + settings->setValue("monBackground", monBackground); + settings->setValue("monRxText", monRxText); + settings->setValue("monTxText", monTxText); + settings->setValue("monOtherText", monOtherText); + + settings->setValue("termBackground", termBackground); + settings->setValue("outputText", outputText); + settings->setValue("EchoText", EchoText); + settings->setValue("WarningText", WarningText); + + settings->setValue("inputBackground", inputBackground); + settings->setValue("inputText", inputText); + + settings->sync(); + delete(settings); +} + +#include + +void QtTermTCP::closeEvent(QCloseEvent *event) +{ + QMessageBox::StandardButton resBtn = QMessageBox::question(this, "QtTermTCP", + tr("Are you sure?\n"), + QMessageBox::Cancel | QMessageBox::No | QMessageBox::Yes, + QMessageBox::Yes); + if (resBtn != QMessageBox::Yes) { + event->ignore(); + } + else + { + event->accept(); +#ifdef USESERIAL + if (hPTTDevice) + hPTTDevice->close(); +#endif + if (process) + process->close(); + } +} + +QtTermTCP::~QtTermTCP() +{ + Ui_ListenSession * Sess; + + for (int i = 0; i < _sessions.size(); ++i) + { + Sess = _sessions.at(i); + + if (Sess->clientSocket) + { + int loops = 100; + Sess->clientSocket->disconnectFromHost(); + while (Sess->clientSocket && loops-- && Sess->clientSocket->state() != QAbstractSocket::UnconnectedState) + QThread::msleep(10); + } + + } + + if (AGWSock && AGWSock->ConnectedState == QAbstractSocket::ConnectedState) + { + int loops = 100; + AGWSock->disconnectFromHost(); + QThread::msleep(10); + while (AGWSock && loops-- && AGWSock->state() != QAbstractSocket::UnconnectedState) + QThread::msleep(10); + } + + if (_server->isListening()) + _server->close(); + + delete(_server); + + QSettings mysettings(GetConfPath(), QSettings::IniFormat); + mysettings.setValue("geometry", saveGeometry()); + mysettings.setValue("windowState", saveState()); + + SaveSettings(); +} + +extern "C" void timer_event(); + +void QtTermTCP::KISSTimerSlot() +{ + // Runs every 100 mS + + timer_event(); +} + +void QtTermTCP::MyTimerSlot() +{ + // Runs every 10 seconds + + Ui_ListenSession * Sess; + + for (int i = 0; i < _sessions.size(); ++i) + { + Sess = _sessions.at(i); + + // for (Ui_ListenSession * Sess : _sessions) + // { + if (Sess == NULL) + continue; + + if (!ChatMode) + continue; + + if (Sess->KISSSession || + (Sess->clientSocket && Sess->clientSocket->state() == QAbstractSocket::ConnectedState)) + { + Sess->SlowTimer++; + + if (Sess->SlowTimer > 54) // About 9 mins + { + unsigned char Msg[2] = ""; + + Sess->SlowTimer = 0; + + if (Sess->KISSSession) + { + TAX25Port * ax25 = (TAX25Port *)Sess->KISSSession; + + if (ax25->status == STAT_LINK) + SendtoAX25(Sess->KISSSession, Msg, 1); + } + else + Sess->clientSocket->write("\0", 1); + } + } + } + + if (AGWEnable) + AGWTimer(); + + if (VARAEnable) + VARATimer(); + + if (KISSEnable) + KISSTimer(); + +} + +extern "C" void myBeep() +{ + QApplication::beep(); +} + + +void QtTermTCP::ListenSlot() +{ + // This runs the Listen Configuration dialog + + ListenDialog * xx = new ListenDialog(); + xx->exec(); +} + +void QtTermTCP::AGWSlot() +{ + // This runs the AGW Configuration dialog + + AGWDialog dialog(0); + dialog.exec(); +} + +Ui_Dialog * Dev; + +static Ui_KISSDialog * KISS; +static Ui_ColourDialog * COLOURS; + +QDialog * deviceUI; + + +char NewPTTPort[80]; + +int newSoundMode = 0; +int oldSoundMode = 0; + +#ifdef USESERIAL +QList Ports = QSerialPortInfo::availablePorts(); +#endif + + +void QtTermTCP::KISSSlot() +{ + // This runs the KISS Configuration dialog + + KISS = new(Ui_KISSDialog); + + QDialog UI; + + KISS->setupUi(&UI); + + UI.setFont(*menufont); + + deviceUI = &UI; + KISS->KISSEnable->setChecked(KISSEnable); + KISS->MYCALL->setText(MYCALL); + +// connect(KISS->SerialPort, SIGNAL(currentIndexChanged(int)), this, SLOT(PTTPortChanged(int))); + + QStringList items; + +#ifdef USESERIAL + + for (const QSerialPortInfo &info : Ports) + { + items.append(info.portName()); + } + + items.sort(); + items.insert(0, "TCP"); + +#endif + + for (const QString &info : items) + { + KISS->SerialPort->addItem(info); + } + + KISS->SerialPort->setCurrentIndex(KISS->SerialPort->findText(SerialPort, Qt::MatchFixedString)); + KISS->Speed->setText(QString::number(KISSBAUD)); + KISS->Host->setText(KISSHost); + KISS->Port->setText(QString::number(KISSPortNum)); + + KISS->Paclen->setText(QString::number(kisspaclen[0])); + KISS->Maxframe->setText(QString::number(maxframe[0])); + KISS->Frack->setText(QString::number(frack_time[0])); + KISS->Retries->setText(QString::number(fracks[0])); + + QObject::connect(KISS->okButton, SIGNAL(clicked()), this, SLOT(KISSaccept())); + QObject::connect(KISS->cancelButton, SIGNAL(clicked()), this, SLOT(KISSreject())); + + UI.exec(); + +} + + +void QtTermTCP::KISSaccept() +{ + QVariant Q; + int OldEnable = KISSEnable; + char oldSerialPort[64]; + int OldPort = KISSPortNum; + char oldHost[128]; + + strcpy(oldSerialPort, SerialPort); + strcpy(oldHost, KISSHost); + + KISSEnable = KISS->KISSEnable->isChecked(); + actHost[18]->setVisible(KISSEnable); // Show KISS Connect Line + + strcpy(MYCALL, KISS->MYCALL->text().toUtf8().toUpper()); + + Q = KISS->Port->text(); + KISSPortNum = Q.toInt(); + + Q = KISS->Speed->text(); + KISSBAUD = Q.toInt(); + + strcpy(KISSHost, KISS->Host->text().toUtf8().toUpper()); + + Q = KISS->SerialPort->currentText(); + strcpy(SerialPort, Q.toString().toUtf8()); + + Q = KISS->Paclen->text(); + kisspaclen[0] = Q.toInt(); + Q = KISS->Maxframe->text(); + maxframe[0] = Q.toInt(); + Q = KISS->Frack->text(); + frack_time[0] = Q.toInt(); + Q = KISS->Retries->text(); + fracks[0] = Q.toInt(); + + myStatusBar->setVisible(AGWEnable | VARAEnable | KISSEnable); + + + if (KISSEnable != OldEnable || KISSPortNum != OldPort || + strcmp(oldHost, KISSHost) != 0 || + strcmp(oldSerialPort, SerialPort) != 0) + { + // (re)start connection + + if (OldEnable) + { + if (KISSEnable) + Status3->setText("KISS Closed"); + else + Status3->setText("KISS Disabled"); + + KISSConnected = 0; + + if (KISSSock && KISSSock->ConnectedState == QAbstractSocket::ConnectedState) + KISSSock->disconnectFromHost(); + else if (m_serial) + closeSerialPort(); + } + } + + delete(KISS); + SaveSettings(); + deviceUI->accept(); + +// QSize newSize(this->size()); +// QSize oldSize(this->size()); + +// QResizeEvent *myResizeEvent = new QResizeEvent(newSize, oldSize); + +// QCoreApplication::postEvent(this, myResizeEvent); +} + +void QtTermTCP::KISSreject() +{ + delete(KISS); + deviceUI->reject(); +} + + + + + +void QtTermTCP::VARASlot() +{ + // This runs the VARA Configuration dialog + + char valChar[80]; + + Dev = new(Ui_Dialog); + + QDialog UI; + + Dev->setupUi(&UI); + + UI.setFont(*menufont); + + deviceUI = &UI; + + Dev->VARAEnable->setChecked(VARAEnable); + Dev->TermCall->setText(VARATermCall); + + SetVARAParams(); + + connect(Dev->VARAHF, SIGNAL(toggled(bool)), this, SLOT(VARAHFChanged(bool))); + connect(Dev->VARAFM, SIGNAL(toggled(bool)), this, SLOT(VARAFMChanged(bool))); + connect(Dev->VARASAT, SIGNAL(toggled(bool)), this, SLOT(VARASATChanged(bool))); + + if (VARA500) + Dev->VARA500->setChecked(true); + else if (VARA2750) + Dev->VARA2750->setChecked(true); + else + Dev->VARA2300->setChecked(true); + + if (VARAHF) + Dev->VARAHF->setChecked(true); + else if (VARAFM) + Dev->VARAFM->setChecked(true); + else if (VARASAT) + Dev->VARASAT->setChecked(true); + + if (VARAHF == 0) + Dev->HFMode->setVisible(false); + + connect(Dev->CAT, SIGNAL(toggled(bool)), this, SLOT(CATChanged(bool))); + connect(Dev->PTTPort, SIGNAL(currentIndexChanged(int)), this, SLOT(PTTPortChanged(int))); + + if (PTTMode == PTTCAT) + Dev->CAT->setChecked(true); + else + Dev->RTSDTR->setChecked(true); + + if (CATHex) + Dev->CATHex->setChecked(true); + else + Dev->CATText->setChecked(true); + + sprintf(valChar, "%d", pttGPIOPin); + Dev->GPIOLeft->setText(valChar); + sprintf(valChar, "%d", pttGPIOPinR); + Dev->GPIORight->setText(valChar); + + Dev->VIDPID->setText(CM108Addr); + + + QStringList items; + +#ifdef USESERIAL + + for (const QSerialPortInfo &info : Ports) + { + items.append(info.portName()); + } + + items.sort(); + +#endif + + Dev->PTTPort->addItem("None"); + Dev->PTTPort->addItem("CM108"); + +#ifdef __ARM_ARCH + + // Dev->PTTPort->addItem("GPIO"); + +#endif + + Dev->PTTPort->addItem("FLRIG"); + Dev->PTTPort->addItem("HAMLIB"); + + for (const QString &info : items) + { + Dev->PTTPort->addItem(info); + } + + Dev->PTTPort->setCurrentIndex(Dev->PTTPort->findText(PTTPort, Qt::MatchFixedString)); + + PTTPortChanged(0); // Force reevaluation + + QObject::connect(Dev->okButton, SIGNAL(clicked()), this, SLOT(deviceaccept())); + QObject::connect(Dev->cancelButton, SIGNAL(clicked()), this, SLOT(devicereject())); + + UI.exec(); + +} + +extern QProcess *process; + +void ClosePTTPort(); + +void QtTermTCP::deviceaccept() +{ + QVariant Q; + + int OldEnable = VARAEnable; + int OldPort = VARAPortNum; + char oldHost[128]; + char oldPath[256]; + + strcpy(oldHost, VARAHost); + strcpy(oldPath, VARAPath); + + VARAEnable = Dev->VARAEnable->isChecked(); + + strcpy(VARATermCall, Dev->TermCall->text().toUtf8().toUpper()); + + Q = Dev->Port->text(); + + VARAPortNum = Q.toInt(); + strcpy(VARAHost, Dev->Host->text().toUtf8().toUpper()); + strcpy(VARAPath, Dev->Path->text().toUtf8()); + + VARA500 = Dev->VARA500->isChecked(); + VARA2300 = Dev->VARA2300->isChecked(); + VARA2750 = Dev->VARA2750->isChecked(); + + VARAHF = Dev->VARAHF->isChecked(); + VARAFM = Dev->VARAFM->isChecked(); + VARASAT = Dev->VARASAT->isChecked(); + + if (VARAHF) + { + strcpy(VARAHostHF, VARAHost); + strcpy(VARAPathHF, VARAPath); + VARAPortHF = VARAPortNum; + } + else if (VARAFM) + { + strcpy(VARAHostFM, VARAHost); + strcpy(VARAPathFM, VARAPath); + VARAPortFM = VARAPortNum; + } + else if (VARASAT) + { + strcpy(VARAHostSAT, VARAHost); + strcpy(VARAPathSAT, VARAPath); + VARAPortSAT = VARAPortNum; + } + + Q = Dev->PTTPort->currentText(); + strcpy(PTTPort, Q.toString().toUtf8()); + + if (Dev->CAT->isChecked()) + PTTMode = PTTCAT; + else + PTTMode = PTTRTS; + + if (Dev->CATHex->isChecked()) + CATHex = 1; + else + CATHex = 0; + + Q = Dev->PTTOn->text(); + strcpy(PTTOnString, Q.toString().toUtf8()); + Q = Dev->PTTOff->text(); + strcpy(PTTOffString, Q.toString().toUtf8()); + + Q = Dev->CATSpeed->text(); + PTTBAUD = Q.toInt(); + + Q = Dev->GPIOLeft->text(); + pttGPIOPin = Q.toInt(); + + Q = Dev->GPIORight->text(); + pttGPIOPinR = Q.toInt(); + + Q = Dev->VIDPID->text(); + + if (strcmp(PTTPort, "CM108") == 0) + strcpy(CM108Addr, Q.toString().toUtf8()); + else if (strcmp(PTTPort, "HAMLIB") == 0) + { + HamLibPort = Q.toInt(); + Q = Dev->PTTOn->text(); + strcpy(HamLibHost, Q.toString().toUtf8()); + } + + if (VARAEnable != OldEnable || VARAPortNum != OldPort || strcmp(oldHost, VARAHost) != 0) + { + // (re)start connection + + if (OldEnable && VARASock && VARASock->ConnectedState == QAbstractSocket::ConnectedState) + { + VARASock->disconnectFromHost(); + if (VARADataSock) + VARADataSock->disconnectFromHost(); + Status2->setText("VARA Disconnected"); + } + } + + if (process && process->state() == QProcess::Running) + if ((VARAEnable == 0 || strcmp(oldPath, VARAPath) != 0)) + process->close(); + + myStatusBar->setVisible(AGWEnable | VARAEnable | KISSEnable); + + ClosePTTPort(); + OpenPTTPort(); + + delete(Dev); + SaveSettings(); + deviceUI->accept(); + + QSize newSize(this->size()); + QSize oldSize(this->size()); + + QResizeEvent *myResizeEvent = new QResizeEvent(newSize, oldSize); + + QCoreApplication::postEvent(this, myResizeEvent); +} + +void QtTermTCP::devicereject() +{ + delete(Dev); + deviceUI->reject(); +} + +// This handles incoming connections + +void QtTermTCP::onNewConnection() +{ + myTcpSocket *clientSocket = (myTcpSocket *)_server->nextPendingConnection(); + + clientSocket->Sess = NULL; + + Ui_ListenSession * S; + Ui_ListenSession * Sess = NULL; + + char Title[512]; + int i = 0; + + QByteArray Host = clientSocket->peerAddress().toString().toUtf8(); + + if (TermMode == MDI) + { + // See if an old session can be reused + + for (int i = 0; i < _sessions.size(); ++i) + { + S = _sessions.at(i); + + // for (Ui_ListenSession * S: _sessions) + // { + if ((S->SessionType & Listen) && S->clientSocket == NULL) + { + Sess = S; + break; + } + } + + // Create a window if none found, else reuse old + + if (Sess == NULL) + { + Sess = newWindow(this, Listen); + } + + QByteArray Host = clientSocket->peerAddress().toString().toUtf8(); + } + else + { + // Single or Tabbed - look for free session + + for (i = 0; i < _sessions.size(); ++i) + { + S = _sessions.at(i); + + if (S->clientSocket == NULL && S->AGWSession == NULL && S->AGWMonSession == NULL && S->KISSSession == NULL) + { + Sess = S; + break; + } + } + + if (Sess == NULL) + { + // Clear connection + + clientSocket->disconnectFromHost(); + return; + } + } + + _sockets.push_back(clientSocket); + + clientSocket->Sess = Sess; + + // See if any data from host - first msg should be callsign + + clientSocket->waitForReadyRead(1000); + + QByteArray datas = clientSocket->readAll(); + + datas.chop(2); + datas.truncate(10); // Just in case! + + datas.append('\0'); + + sprintf(Title, "Inward Connect from %s:%d Call " + datas, + Host.data(), clientSocket->peerPort()); + + if (TermMode == MDI) + { + Sess->setWindowTitle(Title); + } + else if (TermMode == Tabbed) + { + tabWidget->setTabText(i, datas.data()); + } + else if (TermMode == Single) + this->setWindowTitle(Title); + + connect(clientSocket, SIGNAL(readyRead()), this, SLOT(readyRead())); + connect(clientSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onSocketStateChanged(QAbstractSocket::SocketState))); + + Sess->clientSocket = clientSocket; + + // We need to set Connect and Disconnect if the window is active + + if (TermMode == MDI && Sess->sw == ActiveSubWindow) + setMenus(true); + + if (TermMode == Tabbed && Sess->Tab == tabWidget->currentIndex()) + setMenus(true); + + if (TermMode == Single) + setMenus(true); // Single + + // Send CText if defined + + if (listenCText[0]) + Sess->clientSocket->write(listenCText); + + // Send Message to Terminal + + char Msg[80]; + + sprintf(Msg, "Listen Connect from %s\r\r", datas.data()); + + WritetoOutputWindow(Sess, (unsigned char *)Msg, (int)strlen(Msg)); + + if (ConnectBeep) + myBeep(); +} + +void QtTermTCP::onSocketStateChanged(QAbstractSocket::SocketState socketState) +{ + myTcpSocket* sender = static_cast(QObject::sender()); + Ui_ListenSession * Sess = (Ui_ListenSession *)sender->Sess; + + if (socketState == QAbstractSocket::UnconnectedState) + { + char Msg[] = "Disconnected\r"; + + WritetoOutputWindowEx(Sess, (unsigned char *)Msg, (int)strlen(Msg), + Sess->termWindow, &Sess->OutputSaveLen, Sess->OutputSave, WarningText); // Red + + if (TermMode == MDI) + { + if (Sess->SessionType == Mon) // Mon Only + Sess->setWindowTitle("Monitor Session Disconnected"); + else + Sess->setWindowTitle("Disconnected"); + } + else if (TermMode == Tabbed) + { + if (Sess->SessionType == Mon) // Mon Only + tabWidget->setTabText(Sess->Tab, "Monitor"); + else + { + char Label[16]; + sprintf(Label, "Sess %d", Sess->Tab + 1); + tabWidget->setTabText(Sess->Tab, Label); + } + } + else if (TermMode == Single) + { + if (Sess->AGWMonSession) + mythis->setWindowTitle("AGW Monitor Window"); + else + { + if (Sess->SessionType == Mon) // Mon Only + this->setWindowTitle("Monitor Session Disconnected"); + else + this->setWindowTitle("Disconnected"); + } + } + + Sess->PortMonString[0] = 0; + +// delete(Sess->clientSocket); + Sess->clientSocket = NULL; + + discAction->setEnabled(false); + + if ((Sess->SessionType & Listen)) + _sockets.removeOne(sender); + else + { + connectMenu->setEnabled(true); + YAPPSend->setEnabled(false); + } + } + else if (socketState == QAbstractSocket::ConnectedState) + { + char Signon[256]; + char Title[128]; + + // only seems to be triggered for outward connect + + sprintf(Signon, "%s\r%s\rBPQTERMTCP\r", UserName[Sess->CurrentHost], Password[Sess->CurrentHost]); + + Sess->clientSocket->write(Signon); + + discAction->setEnabled(true); + YAPPSend->setEnabled(true); + connectMenu->setEnabled(false); + + SendTraceOptions(Sess); + + Sess->InputMode = 0; + Sess->SlowTimer = 0; + Sess->MonData = 0; + Sess->OutputSaveLen = 0; // Clear any part line + Sess->MonSaveLen = 0; // Clear any part line + + if (Sess->SessionType == Mon) // Mon Only + sprintf(Title, "Monitor Session Connected to %s", Host[Sess->CurrentHost]); + else + sprintf(Title, "Connected to %s", Host[Sess->CurrentHost]); + + if (TermMode == MDI) + Sess->setWindowTitle(Title); + else if (TermMode == Tabbed) + tabWidget->setTabText(tabWidget->currentIndex(), Host[Sess->CurrentHost]); + else if (TermMode == Single) + this->setWindowTitle(Title); + } +} + +void QtTermTCP::updateWindowMenu() +{ + if (TermMode == MDI) + { + windowMenu->clear(); + windowMenu->addAction(newTermAct); + windowMenu->addAction(newMonAct); + windowMenu->addAction(newCombinedAct); + windowMenu->addSeparator(); + windowMenu->addAction(closeAct); + windowMenu->addAction(closeAllAct); + windowMenu->addSeparator(); + windowMenu->addAction(tileAct); + windowMenu->addAction(cascadeAct); + windowMenu->addSeparator(); + windowMenu->addAction(nextAct); + windowMenu->addAction(previousAct); + windowMenu->addAction(quitAction); + windowMenu->addAction(windowMenuSeparatorAct); + + QList windows = mdiArea->subWindowList(); + windowMenuSeparatorAct->setVisible(!windows.isEmpty()); + + Ui_ListenSession * Sess; + + for (int i = 0; i < _sessions.size(); ++i) + { + Sess = _sessions.at(i); + Sess->actActivate = windowMenu->addAction(Sess->sw->windowTitle()); + QAction::connect(Sess->actActivate, SIGNAL(triggered()), this, SLOT(actActivate())); + Sess->actActivate->setCheckable(true); + Sess->actActivate->setChecked(ActiveSubWindow == Sess->sw); + } + } + else if (TermMode == Tabbed) + { + windowMenu->clear(); + + Ui_ListenSession * Sess = (Ui_ListenSession *)tabWidget->currentWidget(); + + QActionGroup * termGroup = new QActionGroup(this); + + delete(TabSingle); + delete(TabBoth); + delete(TabMon); + + TabSingle = setupMenuLine(nullptr, (char *)"Terminal Only", this, (Sess->SessionType == Term)); + TabBoth = setupMenuLine(nullptr, (char *)"Terminal + Monitor", this, (Sess->SessionType == Term + Mon)); + TabMon = setupMenuLine(nullptr, (char *)"Monitor Only", this, (Sess->SessionType == Mon)); + + termGroup->addAction(TabSingle); + termGroup->addAction(TabBoth); + termGroup->addAction(TabMon); + + windowMenu->addAction(TabSingle); + windowMenu->addAction(TabBoth); + windowMenu->addAction(TabMon); + + } +} + +Ui_ListenSession::~Ui_ListenSession() +{ + if (this->clientSocket) + { + int loops = 100; + this->clientSocket->disconnectFromHost(); + while (loops-- && this->clientSocket->state() != QAbstractSocket::UnconnectedState) + QThread::msleep(10); + } +} + +extern "C" void setTraceOff(Ui_ListenSession * Sess) +{ + if ((Sess->SessionType & Mon) == 0) + return; // Not Monitor + + if (Sess->AGWMonSession) + return; + + char Buffer[80]; + int Len = sprintf(Buffer, "\\\\\\\\0 0 0 0 0 0 0 0\r"); + + SocketFlush(Sess); + SocketSend(Sess, Buffer, Len); + SocketFlush(Sess); +} + + +extern "C" void SendTraceOptions(Ui_ListenSession * Sess) +{ + if ((Sess->SessionType & Mon) == 0) + return; // Not Monitor + + if (Sess->AGWMonSession) + return; + + char Buffer[80]; + int Len = sprintf(Buffer, "\\\\\\\\%llx %x %x %x %x %x %x %x\r", Sess->portmask, Sess->mtxparam, Sess->mcomparam, + Sess->MonitorNODES, Sess->MonitorColour, Sess->monUI, 0, 1); + + strcpy(&MonParams[Sess->CurrentHost][0], &Buffer[4]); + SaveSettings(); + SocketFlush(Sess); + SocketSend(Sess, Buffer, Len); + SocketFlush(Sess); +} + +void QtTermTCP::doNewTerm() +{ + newWindow(this, Term); +} + +void QtTermTCP::doNewMon() +{ + newWindow(this, Mon); +} + +void QtTermTCP::doNewCombined() +{ + newWindow(this, Term + Mon); +} + +void QtTermTCP::doCascade() +{ + // Qt Cascade Minimizes windows so do it ourselves + + int x = 0, y = 0; + + Ui_ListenSession * Sess; + + for (int i = 0; i < _sessions.size(); ++i) + { + Sess = _sessions.at(i); + + Sess->sw->move(x, y); + x += 14; + y += 30; + } +} + +void QtTermTCP::actActivate() +{ + QAction * sender = static_cast(QObject::sender()); + + Ui_ListenSession * Sess; + + for (int i = 0; i < _sessions.size(); ++i) + { + Sess = _sessions.at(i); + + if (Sess->actActivate == sender) + { + mdiArea->setActiveSubWindow(Sess->sw); + return; + } + } +} + + +void QtTermTCP::xon_mdiArea_changed() +{ + // This is triggered when the Active MDI window changes + // and is used to enable/disable Connect, Disconnect and YAPP Send + + + QMdiSubWindow *SW = mdiArea->activeSubWindow(); + + // Dont waste time if not changed + + if (ActiveSubWindow == SW) + return; + + ActiveSubWindow = SW; + + Ui_ListenSession * Sess; + + for (int i = 0; i < _sessions.size(); ++i) + { + Sess = _sessions.at(i); + +// for (Ui_ListenSession * Sess : _sessions) +// { + if (Sess->sw == SW) + { + ActiveSession = Sess; + + if (Sess->clientSocket && Sess->clientSocket->state() == QAbstractSocket::ConnectedState) + { + discAction->setEnabled(true); + YAPPSend->setEnabled(true); + connectMenu->setEnabled(false); + } + else if (Sess->AGWMonSession) + { + // Connected AGW Monitor Session + + discAction->setEnabled(false); + YAPPSend->setEnabled(false); + connectMenu->setEnabled(false); + } + else if (Sess->AGWSession || Sess->KISSSession) + { + // Connected AGW or KISS Terminal Session + + discAction->setEnabled(true); + YAPPSend->setEnabled(true); + connectMenu->setEnabled(false); + } + else + { + // Not connected + + discAction->setEnabled(false); + YAPPSend->setEnabled(false); + + if ((Sess->SessionType & Listen)) // Listen Sessions can't connect + connectMenu->setEnabled(false); + else + connectMenu->setEnabled(true); + } + + // If a monitor Window, change Monitor config settings + + if (Sess->PortMonString[0]) + { + char * ptr = (char *)malloc(1024); + memcpy(ptr, Sess->PortMonString, 1024); + + int NumberofPorts = atoi((char *)&ptr[2]); + char *p, *Context; + char msg[80]; + int portnum; + char delim[] = "|"; + + // Remove old Monitor menu + + for (int i = 0; i < 32; i++) + { + SetPortMonLine(i, (char *)"", 0, 0); // Set all hidden + } + + p = strtok_s((char *)&ptr[2], delim, &Context); + + while (NumberofPorts--) + { + p = strtok_s(NULL, delim, &Context); + if (p == NULL) + break; + + portnum = atoi(p); + + sprintf(msg, "Port %s", p); + + if (portnum == 0) + portnum = 33; + + if (Sess->portmask & (1ll << (portnum - 1))) + SetPortMonLine(portnum, msg, 1, 1); + else + SetPortMonLine(portnum, msg, 1, 0); + } + free(ptr); + + MonTX->setChecked(Sess->mtxparam); + MonSup->setChecked(Sess->mcomparam); + MonUI->setChecked(Sess->monUI); + MonNodes->setChecked(Sess->MonitorNODES); + MonColour->setChecked(Sess->MonitorColour); + + } + + return; + } + } +} + +void QtTermTCP::doFonts() +{ + fontDialog * xx = new fontDialog(0); + xx->exec(); +} + +void QtTermTCP::doMFonts() +{ + fontDialog * xx = new fontDialog(1); + xx->exec(); +} + +QColor TempmonBackground = monBackground; +QColor TemptermBackground = termBackground; +QColor TempinputBackground = inputBackground; + +QColor TempmonRxText = monRxText; +QColor TempmonTxText = monTxText; +QColor TempmonOtherText = monOtherText; + +QColor TempoutputText = outputText; +QColor TempEchoText = EchoText; +QColor TempWarningText = WarningText; + + +QColor TempinputText = inputText; + + +void setDialogColours() +{ + char monStyle[128]; + + sprintf(monStyle, "color: rgb(%d, %d, %d); background-color: rgb(%d, %d, %d);", + TempmonOtherText.red(), TempmonOtherText.green(), TempmonOtherText.blue(), + TempmonBackground.red(), TempmonBackground.green(), TempmonBackground.blue()); + + COLOURS->MonitorBG->setStyleSheet(monStyle); + COLOURS->MonOther->setStyleSheet(monStyle); + + sprintf(monStyle, "color: rgb(%d, %d, %d); background-color: rgb(%d, %d, %d);", + TempmonTxText.red(), TempmonTxText.green(), TempmonTxText.blue(), + TempmonBackground.red(), TempmonBackground.green(), TempmonBackground.blue()); + COLOURS->MonTX->setStyleSheet(monStyle); + + sprintf(monStyle, "color: rgb(%d, %d, %d); background-color: rgb(%d, %d, %d);", + TempmonRxText.red(), TempmonRxText.green(), TempmonRxText.blue(), + TempmonBackground.red(), TempmonBackground.green(), TempmonBackground.blue()); + + COLOURS->MonRX->setStyleSheet(monStyle); + + sprintf(monStyle, "color: rgb(%d, %d, %d); background-color: rgb(%d, %d, %d);", + TempoutputText.red(), TempoutputText.green(), TempoutputText.blue(), + TemptermBackground.red(), TemptermBackground.green(), TemptermBackground.blue()); + + COLOURS->TermBG->setStyleSheet(monStyle); + COLOURS->TermNormal->setStyleSheet(monStyle); + + sprintf(monStyle, "color: rgb(%d, %d, %d); background-color: rgb(%d, %d, %d);", + TempEchoText.red(), TempEchoText.green(), TempEchoText.blue(), + TemptermBackground.red(), TemptermBackground.green(), TemptermBackground.blue()); + + COLOURS->Echoed->setStyleSheet(monStyle); + + sprintf(monStyle, "color: rgb(%d, %d, %d); background-color: rgb(%d, %d, %d);", + TempWarningText.red(), TempWarningText.green(), TempWarningText.blue(), + TemptermBackground.red(), TemptermBackground.green(), TemptermBackground.blue()); + + COLOURS->Warning->setStyleSheet(monStyle); + + sprintf(monStyle, "color: rgb(%d, %d, %d); background-color: rgb(%d, %d, %d);", + TempinputText.red(), TempinputText.green(), TempinputText.blue(), + TempinputBackground.red(), TempinputBackground.green(), TempinputBackground.blue()); + + COLOURS->InputBG->setStyleSheet(monStyle); + COLOURS->InputColour->setStyleSheet(monStyle); + +} + +void QtTermTCP::doColours() +{ + COLOURS = new(Ui_ColourDialog); + + QDialog UI; + + COLOURS->setupUi(&UI); + + UI.setFont(*menufont); + + deviceUI = &UI; + + TempmonBackground = monBackground; + TempmonRxText = monRxText; + TempmonTxText = monTxText; + TempmonOtherText = monOtherText; + + TemptermBackground = termBackground; + TempoutputText = outputText; + TempEchoText = EchoText; + TempWarningText = WarningText; + + TempinputBackground = inputBackground; + TempinputText = inputText; + + setDialogColours(); + + QObject::connect(COLOURS->MonitorBG, SIGNAL(clicked()), this, SLOT(ColourPressed())); + QObject::connect(COLOURS->TermBG, SIGNAL(clicked()), this, SLOT(ColourPressed())); + QObject::connect(COLOURS->InputBG, SIGNAL(clicked()), this, SLOT(ColourPressed())); + QObject::connect(COLOURS->MonRX, SIGNAL(clicked()), this, SLOT(ColourPressed())); + QObject::connect(COLOURS->MonTX, SIGNAL(clicked()), this, SLOT(ColourPressed())); + QObject::connect(COLOURS->MonOther, SIGNAL(clicked()), this, SLOT(ColourPressed())); + QObject::connect(COLOURS->TermNormal, SIGNAL(clicked()), this, SLOT(ColourPressed())); + QObject::connect(COLOURS->Echoed, SIGNAL(clicked()), this, SLOT(ColourPressed())); + QObject::connect(COLOURS->Warning, SIGNAL(clicked()), this, SLOT(ColourPressed())); + QObject::connect(COLOURS->InputColour, SIGNAL(clicked()), this, SLOT(ColourPressed())); + + QObject::connect(COLOURS->okButton, SIGNAL(clicked()), this, SLOT(Colouraccept())); + QObject::connect(COLOURS->cancelButton, SIGNAL(clicked()), this, SLOT(Colourreject())); + + UI.exec(); + +} + +void QtTermTCP::ColourPressed() +{ + char Name[32]; + + strcpy(Name, sender()->objectName().toUtf8()); + + if (strcmp(Name, "MonitorBG") == 0) + TempmonBackground = setColor(TempmonBackground); + + else if (strcmp(Name, "MonTX") == 0) + TempmonTxText = setColor(TempmonTxText); + + else if (strcmp(Name, "MonRX") == 0) + TempmonRxText = setColor(TempmonRxText); + + else if (strcmp(Name, "MonOther") == 0) + TempmonOtherText = setColor(TempmonOtherText); + + else if (strcmp(Name, "TermBG") == 0) + TemptermBackground = setColor(TemptermBackground); + + else if (strcmp(Name, "InputBG") == 0) + TempinputBackground = setColor(TempinputBackground); + + else if (strcmp(Name, "InputColour") == 0) + TempinputText = setColor(TempinputText); + + else if (strcmp(Name, "TermNormal") == 0) + TempoutputText = setColor(TempoutputText); + + else if (strcmp(Name, "Echoed") == 0) + TempEchoText = setColor(TempEchoText); + + else if (strcmp(Name, "Warning") == 0) + TempWarningText = setColor(TempWarningText); + + setDialogColours(); +} + + +void QtTermTCP::Colouraccept() +{ + monBackground = TempmonBackground; + monRxText = TempmonRxText; + monTxText = TempmonTxText; + monOtherText = TempmonOtherText; + + termBackground = TemptermBackground; + EchoText = TempEchoText; + WarningText = TempWarningText; + outputText = TempoutputText; + + inputBackground = TempinputBackground; + inputText = TempinputText; + + // Set background colour for new windows + + sprintf(monStyleSheet, "background-color: rgb(%d, %d, %d);", + monBackground.red(), monBackground.green(), monBackground.blue()); + + sprintf(termStyleSheet, "background-color: rgb(%d, %d, %d);", + termBackground.red(), termBackground.green(), termBackground.blue()); + + sprintf(inputStyleSheet, "color: rgb(%d, %d, %d); background-color: rgb(%d, %d, %d);", + inputText.red(), inputText.green(), inputText.blue(), + inputBackground.red(), inputBackground.green(), inputBackground.blue()); + + // Update existing windows + + for (int i = 0; i < _sessions.size(); ++i) + { + Ui_ListenSession * S = _sessions.at(i); + + if (S->monWindow) + S->monWindow->setStyleSheet(monStyleSheet); + + if (S->termWindow) + S->termWindow->setStyleSheet(termStyleSheet); + + if (S->inputWindow) + S->inputWindow->setStyleSheet(inputStyleSheet); + + } + + + delete(COLOURS); + + SaveSettings(); + deviceUI->accept(); +} + +void QtTermTCP::Colourreject() +{ + delete(COLOURS); + deviceUI->reject(); +} + + +void QtTermTCP::ConnecttoVARA() +{ + + delete(VARASock); + + VARASock = new myTcpSocket(); + + connect(VARASock, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(VARAdisplayError(QAbstractSocket::SocketError))); + connect(VARASock, SIGNAL(readyRead()), this, SLOT(VARAreadyRead())); + connect(VARASock, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onVARASocketStateChanged(QAbstractSocket::SocketState))); + + VARASock->connectToHost(VARAHost, VARAPortNum); + + Status2->setText("VARA Control Connecting"); + + return; +} + + +void QtTermTCP::VARATimer() +{ + // Runs every 10 Seconds + + if (VARAConnected == 0 && VARAConnecting == 0) + { + if (process == nullptr || process->state() == QProcess::NotRunning) + { + if (VARAPath[0]) + { + process = new QProcess(this); + QString file = VARAPath; + process->start(file); + } + } + QThread::msleep(1000); + VARAConnecting = true; + ConnecttoVARA(); + } +} + + +void QtTermTCP::VARAdisplayError(QAbstractSocket::SocketError socketError) +{ + switch (socketError) + { + case QAbstractSocket::RemoteHostClosedError: + break; + + case QAbstractSocket::HostNotFoundError: + QMessageBox::information(this, tr("QtTermTCP"), + tr("VARA host was not found. Please check the " + "host name and portsettings->")); + + Status2->setText("VARA Connection Failed"); + + break; + + case QAbstractSocket::ConnectionRefusedError: + + Status2->setText("VARA Connection Refused"); + break; + + default: + + Status2->setText("VARA Connection Failed"); + } + + VARAConnecting = 0; + VARAConnected = 0; +} + +void QtTermTCP::VARAreadyRead() +{ + int Read; + char Buffer[4096]; + char * ptr; + char * Msg; + myTcpSocket* Socket = static_cast(QObject::sender()); + + // read the data from the socket + + Read = Socket->read((char *)Buffer, 4095); + + Buffer[Read] = 0; + + Msg = Buffer; + + ptr = strchr(Msg, 0x0d); + + while (ptr) + { + *ptr++ = 0; + + if (strcmp(Msg, "IAMALIVE") == 0) + { + } + else if (strcmp(Msg, "PTT ON") == 0) + { + RadioPTT(1); + } + else if (strcmp(Msg, "PTT OFF") == 0) + { + RadioPTT(0); + } + else if (strcmp(Msg, "PENDING") == 0) + { + } + else if (strcmp(Msg, "CANCELPENDING") == 0) + { + } + else if (strcmp(Msg, "OK") == 0) + { + } + else if (memcmp(Msg, "CONNECTED ", 10) == 0) + { + Ui_ListenSession * Sess = (Ui_ListenSession *)VARASock->Sess; + char Title[128] = ""; + char CallFrom[64] = ""; + char CallTo[64] = ""; + char Mode[64] = ""; + char Message[128]; + int n; + + + sscanf(&Msg[10], "%s %s %s", CallFrom, CallTo, Mode); + + if (Sess) + { + if (Mode[0]) + sprintf(Title, "Connected to %s %s Mode", CallTo, Mode); + else + sprintf(Title, "Connected to %s", CallTo); + + n = sprintf(Message, "%s\r\n", Title); + WritetoOutputWindow(Sess, (unsigned char *)Message, n); + + if (TermMode == MDI) + Sess->setWindowTitle(Title); + else if (TermMode == Tabbed) + tabWidget->setTabText(Sess->Tab, CallTo); + else if (TermMode == Single) + mythis->setWindowTitle(Title); + + setMenus(true); + } + else + { + // Incoming Call + + Ui_ListenSession * S; + int i = 0; + + if (TermMode == MDI) + { + // See if an old session can be reused + + for (int i = 0; i < _sessions.size(); ++i) + { + S = _sessions.at(i); + + // for (Ui_ListenSession * S: _sessions) + // { + if ((S->SessionType & Listen) && S->clientSocket == NULL) + { + Sess = S; + break; + } + } + + // Create a window if none found, else reuse old + + if (Sess == NULL) + { + Sess = newWindow(this, Listen); + } + } + else + { + // Single or Tabbed - look for free session + + for (i = 0; i < _sessions.size(); ++i) + { + S = _sessions.at(i); + + if (S->clientSocket == NULL && S->AGWSession == NULL && S->AGWMonSession == NULL && S->KISSSession == NULL) + { + Sess = S; + break; + } + } + } + + if (Sess == NULL) + { + // Clear connection + + VARASock->write("DISCONNECT\r"); + } + else + { + if (Mode[0]) + { + sprintf(Title, "Connected to %s Mode %s", CallFrom, Mode); + n = sprintf(Message, "Incoming Connected from %s %s Mode\r\n", CallFrom, Mode); + } + else + { + sprintf(Title, "Connected to %s", CallFrom); + n = sprintf(Message, "Incoming Connected from %s\r\n", CallFrom); + } + + WritetoOutputWindow(Sess, (unsigned char *)Message, n); + + VARASock->Sess = Sess; + VARADataSock->Sess = Sess; + + if (TermMode == MDI) + Sess->setWindowTitle(Title); + else if (TermMode == Tabbed) + tabWidget->setTabText(Sess->Tab, CallFrom); + else if (TermMode == Single) + mythis->setWindowTitle(Title); + + setMenus(true); + + } + } + } + else if (strcmp(Msg, "DISCONNECTED") == 0) + { + Ui_ListenSession * Sess = (Ui_ListenSession *)VARASock->Sess; + + if (Sess) + { + WritetoOutputWindow(Sess, (unsigned char *)"Disconnected\r\n", 14); + VARASock->Sess = 0; + VARADataSock->Sess = 0; + + if (TermMode == MDI) + { + if (Sess->SessionType == Mon) // Mon Only + Sess->setWindowTitle("Monitor Session Disconnected"); + else + Sess->setWindowTitle("Disconnected"); + } + else if (TermMode == Tabbed) + { + if (Sess->SessionType == Mon) // Mon Only + tabWidget->setTabText(Sess->Tab, "Monitor"); + else + { + char Label[16]; + sprintf(Label, "Sess %d", Sess->Tab + 1); + tabWidget->setTabText(Sess->Tab, Label); + } + } + else if (TermMode == Single) + { + if (Sess->AGWMonSession) + mythis->setWindowTitle("AGW Monitor Window"); + else + { + if (Sess->SessionType == Mon) // Mon Only + this->setWindowTitle("Monitor Session Disconnected"); + else + this->setWindowTitle("Disconnected"); + } + } + + setMenus(false); + } + } + + Msg = ptr; + + ptr = strchr(Msg, 0x0d); + } + + + +} + +void QtTermTCP::onVARASocketStateChanged(QAbstractSocket::SocketState socketState) +{ +// myTcpSocket* sender = static_cast(QObject::sender()); + + if (socketState == QAbstractSocket::UnconnectedState) + { + // Close any connections + + Status2->setText("VARA Disconnected"); + actHost[17]->setVisible(0); + + VARAConnecting = VARAConnected = 0; + } + else if (socketState == QAbstractSocket::ConnectedState) + { + // Connect Data Session. Leave Connecting till that completes + + VARADataSock = new myTcpSocket(); + + connect(VARADataSock, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(VARADatadisplayError(QAbstractSocket::SocketError))); + connect(VARADataSock, SIGNAL(readyRead()), this, SLOT(VARADatareadyRead())); + connect(VARADataSock, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onVARADataSocketStateChanged(QAbstractSocket::SocketState))); + + VARADataSock->connectToHost(VARAHost, VARAPortNum + 1); + Status2->setText("VARA Data Connecting"); + } +} + + +void QtTermTCP::VARADatadisplayError(QAbstractSocket::SocketError socketError) +{ + switch (socketError) + { + case QAbstractSocket::RemoteHostClosedError: + break; + + case QAbstractSocket::HostNotFoundError: + QMessageBox::information(this, tr("QtTermTCP"), + tr("VARA host was not found. Please check the " + "host name and portsettings->")); + + Status2->setText("VARA Connection Failed"); + + break; + + case QAbstractSocket::ConnectionRefusedError: + + Status2->setText("VARA Connection Refused"); + break; + + default: + + Status2->setText("VARA Connection Failed"); + } + + VARAConnecting = 0; + VARAConnected = 0; +} + +void QtTermTCP::VARADatareadyRead() +{ + int Read; + unsigned char Buffer[4096]; + myTcpSocket* Socket = static_cast(QObject::sender()); + + Ui_ListenSession * Sess = (Ui_ListenSession *)Socket->Sess; + + // read the data from the socket + + Read = Socket->read((char *)Buffer, 2047); + + while (Read > 0) + { + // if (InputMode == 'Y') // Yapp + // { + // QString myString = QString::fromUtf8((char*)Buffer, Read); + // QByteArray ptr = myString.toLocal8Bit(); + // memcpy(Buffer, ptr.data(), ptr.length()); + // Read = ptr.length(); + // } + + ProcessReceivedData(Sess, Buffer, Read); + + QString myString = QString::fromUtf8((char*)Buffer); + // qDebug() << myString; + Read = Socket->read((char *)Buffer, 2047); + } +} + + +void QtTermTCP::onVARADataSocketStateChanged(QAbstractSocket::SocketState socketState) +{ +// myTcpSocket* sender = static_cast(QObject::sender()); + + if (socketState == QAbstractSocket::UnconnectedState) + { + // Close any connections + + Status2->setText("VARA Disconnected"); + actHost[17]->setVisible(0); + + VARAConnecting = VARAConnected = 0; + } + else if (socketState == QAbstractSocket::ConnectedState) + { + char MyCall[32]; + + VARAConnected = 1; + VARAConnecting = 0; + + Status2->setText("VARA Connected"); + + actHost[17]->setVisible(1); // Enable VARA Connect Line + + sprintf(MyCall, "MYCALL %s\r", VARATermCall); + VARASock->write(MyCall); + + if (VARA500) + VARASock->write("BW500\r"); + else if (VARA2300) + VARASock->write("BW2300\r"); + else if (VARA2750) + VARASock->write("BW2750\r"); + + VARASock->write("COMPRESSION FILES\r"); + + if (listenEnable) + VARASock->write("LISTEN ON\r"); + } +} + +// PTT Stuff + +#include "hidapi.h" + +// Serial Port Stuff + + +QTcpSocket * HAMLIBsock; +int HAMLIBConnected = 0; +int HAMLIBConnecting = 0; + +void QtTermTCP::HAMLIBdisplayError(QAbstractSocket::SocketError socketError) +{ + switch (socketError) + { + case QAbstractSocket::RemoteHostClosedError: + break; + + case QAbstractSocket::HostNotFoundError: + QMessageBox::information(nullptr, tr("QtSM"), + "HAMLIB host was not found. Please check the " + "host name and portsettings->"); + + break; + + case QAbstractSocket::ConnectionRefusedError: + + qDebug() << "HAMLIB Connection Refused"; + break; + + default: + + qDebug() << "HAMLIB Connection Failed"; + break; + + } + + HAMLIBConnecting = 0; + HAMLIBConnected = 0; +} + +void QtTermTCP::HAMLIBreadyRead() +{ + unsigned char Buffer[4096]; + QTcpSocket* Socket = static_cast(QObject::sender()); + + // read the data from the socket. Don't do anyhing with it at the moment + + Socket->read((char *)Buffer, 4095); +} + +void QtTermTCP::onHAMLIBSocketStateChanged(QAbstractSocket::SocketState socketState) +{ + if (socketState == QAbstractSocket::UnconnectedState) + { + // Close any connections + + HAMLIBConnected = 0; + HAMLIBConnecting = 0; + + // delete (HAMLIBsock); + // HAMLIBsock = 0; + + qDebug() << "HAMLIB Connection Closed"; + + } + else if (socketState == QAbstractSocket::ConnectedState) + { + HAMLIBConnected = 1; + HAMLIBConnecting = 0; + qDebug() << "HAMLIB Connected"; + } +} + + +void QtTermTCP::ConnecttoHAMLIB() +{ + delete(HAMLIBsock); + + HAMLIBConnected = 0; + HAMLIBConnecting = 1; + + HAMLIBsock = new QTcpSocket(); + + connect(HAMLIBsock, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(HAMLIBdisplayError(QAbstractSocket::SocketError))); + connect(HAMLIBsock, SIGNAL(readyRead()), this, SLOT(HAMLIBreadyRead())); + connect(HAMLIBsock, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onHAMLIBSocketStateChanged(QAbstractSocket::SocketState))); + + HAMLIBsock->connectToHost(HamLibHost, HamLibPort); + + return; +} + +void QtTermTCP::HAMLIBSetPTT(int PTTState) +{ + char Msg[16]; + + if (HAMLIBsock == nullptr || HAMLIBsock->state() != QAbstractSocket::ConnectedState) + ConnecttoHAMLIB(); + + if (HAMLIBsock == nullptr || HAMLIBsock->state() != QAbstractSocket::ConnectedState) + return; + + sprintf(Msg, "T %d\r\n", PTTState); + HAMLIBsock->write(Msg); + + HAMLIBsock->waitForBytesWritten(3000); + + QByteArray datas = HAMLIBsock->readAll(); + + qDebug(datas.data()); +} + +QTcpSocket * FLRigsock; +int FLRigConnected = 0; +int FLRigConnecting = 0; + +void QtTermTCP::FLRigdisplayError(QAbstractSocket::SocketError socketError) +{ + switch (socketError) + { + case QAbstractSocket::RemoteHostClosedError: + break; + + case QAbstractSocket::HostNotFoundError: + QMessageBox::information(nullptr, tr("QtSM"), + "FLRig host was not found. Please check the " + "host name and portsettings->"); + + break; + + case QAbstractSocket::ConnectionRefusedError: + + qDebug() << "FLRig Connection Refused"; + break; + + default: + + qDebug() << "FLRig Connection Failed"; + break; + + } + + FLRigConnecting = 0; + FLRigConnected = 0; +} + +void QtTermTCP::FLRigreadyRead() +{ + unsigned char Buffer[4096]; + QTcpSocket* Socket = static_cast(QObject::sender()); + + // read the data from the socket. Don't do anyhing with it at the moment + + Socket->read((char *)Buffer, 4095); +} + +void QtTermTCP::onFLRigSocketStateChanged(QAbstractSocket::SocketState socketState) +{ + if (socketState == QAbstractSocket::UnconnectedState) + { + // Close any connections + + FLRigConnected = 0; + FLRigConnecting = 0; + + // delete (FLRigsock); + // FLRigsock = 0; + + qDebug() << "FLRig Connection Closed"; + + } + else if (socketState == QAbstractSocket::ConnectedState) + { + FLRigConnected = 1; + FLRigConnecting = 0; + qDebug() << "FLRig Connected"; + } +} + + +void QtTermTCP::ConnecttoFLRig() +{ + delete(FLRigsock); + + FLRigConnected = 0; + FLRigConnecting = 1; + + FLRigsock = new QTcpSocket(); + + connect(FLRigsock, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(FLRigdisplayError(QAbstractSocket::SocketError))); + connect(FLRigsock, SIGNAL(readyRead()), this, SLOT(FLRigreadyRead())); + connect(FLRigsock, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onFLRigSocketStateChanged(QAbstractSocket::SocketState))); + + FLRigsock->connectToHost(FLRigHost, FLRigPort); + + return; +} + +static char MsgHddr[] = "POST /RPC2 HTTP/1.1\r\n" +"User-Agent: XMLRPC++ 0.8\r\n" +"Host: 127.0.0.1:7362\r\n" +"Content-Type: text/xml\r\n" +"Content-length: %d\r\n" +"\r\n%s"; + +static char Req[] = "\r\n" + "%s\r\n" + "%s" + "\r\n"; + +void QtTermTCP::FLRigSetPTT(int PTTState) +{ + int Len; + char ReqBuf[512]; + char SendBuff[512]; + char ValueString[256] = ""; + + sprintf(ValueString, "%d", PTTState); + + Len = sprintf(ReqBuf, Req, "rig.set_ptt", ValueString); + Len = sprintf(SendBuff, MsgHddr, Len, ReqBuf); + + if (FLRigsock == nullptr || FLRigsock->state() != QAbstractSocket::ConnectedState) + ConnecttoFLRig(); + + if (FLRigsock == nullptr || FLRigsock->state() != QAbstractSocket::ConnectedState) + return; + + FLRigsock->write(SendBuff); + + FLRigsock->waitForBytesWritten(3000); + + QByteArray datas = FLRigsock->readAll(); + + qDebug(datas.data()); +} + + + +void QtTermTCP::CATChanged(bool State) +{ + UNUSED(State); + PTTPortChanged(0); +} + +void QtTermTCP::VARAHFChanged(bool State) +{ + Dev->HFMode->setVisible(State); + + if (State) + { + Dev->TNCInfo->setTitle("VARA HF Paramters"); + Dev->Host->setText(VARAHostHF); + Dev->Port->setText(QString::number(VARAPortHF)); + Dev->Path->setText(VARAPathHF); + } +} + +void QtTermTCP::VARAFMChanged(bool State) +{ + if (State) + { + Dev->TNCInfo->setTitle("VARA FM Paramters"); + Dev->Host->setText(VARAHostFM); + Dev->Port->setText(QString::number(VARAPortFM)); + Dev->Path->setText(VARAPathFM); + } +} + +void QtTermTCP::VARASATChanged(bool State) +{ + if (State) + { + Dev->TNCInfo->setTitle("VARA SAT Paramters"); + Dev->Host->setText(VARAHostSAT); + Dev->Port->setText(QString::number(VARAPortSAT)); + Dev->Path->setText(VARAPathSAT); + } +} + +void QtTermTCP::SetVARAParams() +{ + Dev->Host->setText(VARAHost); + Dev->Port->setText(QString::number(VARAPortNum)); + Dev->Path->setText(VARAPath); +} + +void QtTermTCP::PTTPortChanged(int Selected) +{ + UNUSED(Selected); + + QVariant Q = Dev->PTTPort->currentText(); + strcpy(NewPTTPort, Q.toString().toUtf8()); + + Dev->RTSDTR->setVisible(false); + Dev->CAT->setVisible(false); + + Dev->PTTOnLab->setVisible(false); + Dev->PTTOn->setVisible(false); + Dev->PTTOff->setVisible(false); + Dev->PTTOffLab->setVisible(false); + Dev->CATLabel->setVisible(false); + Dev->CATSpeed->setVisible(false); + Dev->CATHex->setVisible(false); + Dev->CATText->setVisible(false); + + Dev->GPIOLab->setVisible(false); + Dev->GPIOLeft->setVisible(false); + Dev->GPIORight->setVisible(false); + Dev->GPIOLab2->setVisible(false); + + Dev->CM108Label->setVisible(false); + Dev->VIDPID->setVisible(false); + + if (strcmp(NewPTTPort, "None") == 0) + { + } + else if (strcmp(NewPTTPort, "GPIO") == 0) + { + Dev->GPIOLab->setVisible(true); + Dev->GPIOLeft->setVisible(true); + } + + else if (strcmp(NewPTTPort, "CM108") == 0) + { + Dev->CM108Label->setVisible(true); + #ifdef WIN32 + Dev->CM108Label->setText("CM108 VID/PID"); + #else + Dev->CM108Label->setText("CM108 Device"); + #endif + Dev->VIDPID->setText(CM108Addr); + Dev->VIDPID->setVisible(true); + } + else if (strcmp(NewPTTPort, "HAMLIB") == 0) + { + Dev->CM108Label->setVisible(true); + Dev->CM108Label->setText("rigctrld Port"); + Dev->VIDPID->setText(QString::number(HamLibPort)); + Dev->VIDPID->setVisible(true); + Dev->PTTOnLab->setText("rigctrld Host"); + Dev->PTTOnLab->setVisible(true); + Dev->PTTOn->setText(HamLibHost); + Dev->PTTOn->setVisible(true); + } + else if (strcmp(NewPTTPort, "FLRIG") == 0) + { + Dev->CM108Label->setVisible(true); + Dev->CM108Label->setText("FLRig Port"); + Dev->VIDPID->setText(QString::number(FLRigPort)); + Dev->VIDPID->setVisible(true); + Dev->PTTOnLab->setText("FLRig Host"); + Dev->PTTOnLab->setVisible(true); + Dev->PTTOn->setText(FLRigHost); + Dev->PTTOn->setVisible(true); + } + + else + { + Dev->RTSDTR->setVisible(true); + Dev->CAT->setVisible(true); + + if (Dev->CAT->isChecked()) + { + Dev->CATHex->setVisible(true); + Dev->CATText->setVisible(true); + Dev->PTTOnLab->setVisible(true); + Dev->PTTOnLab->setText("PTT On String"); + Dev->PTTOn->setText(PTTOnString); + Dev->PTTOn->setVisible(true); + Dev->PTTOff->setVisible(true); + Dev->PTTOffLab->setVisible(true); + Dev->PTTOff->setVisible(true); + Dev->PTTOff->setText(PTTOffString); + Dev->CATLabel->setVisible(true); + Dev->CATSpeed->setVisible(true); + Dev->CATSpeed->setText(QString::number(PTTBAUD)); + } + } +} + + + +void DecodeCM108(char * ptr) +{ + // Called if Device Name or PTT = Param is CM108 + +#ifdef WIN32 + + // Next Param is VID and PID - 0xd8c:0x8 or Full device name + // On Windows device name is very long and difficult to find, so + // easier to use VID/PID, but allow device in case more than one needed + + char * next; + long VID = 0, PID = 0; + char product[256] = "Unknown"; + + struct hid_device_info *devs, *cur_dev; + const char *path_to_open = NULL; + hid_device *handle = NULL; + + if (strlen(ptr) > 16) + CM108Device = _strdup(ptr); + else + { + VID = strtol(ptr, &next, 0); + if (next) + PID = strtol(++next, &next, 0); + + // Look for Device + + devs = hid_enumerate((unsigned short)VID, (unsigned short)PID); + cur_dev = devs; + + while (cur_dev) + { + if (cur_dev->product_string) + wcstombs(product, cur_dev->product_string, 255); + + printf("HID Device %s VID %X PID %X", product, cur_dev->vendor_id, cur_dev->product_id); + if (cur_dev->vendor_id == VID && cur_dev->product_id == PID) + { + path_to_open = cur_dev->path; + break; + } + cur_dev = cur_dev->next; + } + + if (path_to_open) + { + handle = hid_open_path(path_to_open); + + if (handle) + { + hid_close(handle); + CM108Device = _strdup(path_to_open); + } + else + { + printf("Unable to open CM108 device %x %x", VID, PID); + } + } + else + printf("Couldn't find CM108 device %x %x", VID, PID); + + hid_free_enumeration(devs); + } +#else + + // Linux - Next Param HID Device, eg /dev/hidraw0 + + CM108Device = strdup(ptr); +#endif +} + + +void QtTermTCP::OpenPTTPort() +{ + PTTMode &= ~PTTCM108; + PTTMode &= ~PTTHAMLIB; + PTTMode &= ~PTTFLRIG; + + if (PTTPort[0] && strcmp(PTTPort, "None") != 0) + { + if (PTTMode == PTTCAT) + { + // convert config strings from Hex + + if (CATHex == 0) // Ascii Strings + { + strcpy((char *)PTTOffCmd, PTTOffString); + PTTOffCmdLen = strlen(PTTOffString); + + strcpy((char *)PTTOnCmd, PTTOnString); + PTTOnCmdLen = strlen(PTTOnString); + } + else + { + char * ptr1 = PTTOffString; + unsigned char * ptr2 = PTTOffCmd; + char c; + int val; + + while ((c = *(ptr1++))) + { + val = c - 0x30; + if (val > 15) val -= 7; + val <<= 4; + c = *(ptr1++) - 0x30; + if (c > 15) c -= 7; + val |= c; + *(ptr2++) = val; + } + + PTTOffCmdLen = ptr2 - PTTOffCmd; + + ptr1 = PTTOnString; + ptr2 = PTTOnCmd; + + while ((c = *(ptr1++))) + { + val = c - 0x30; + if (val > 15) val -= 7; + val <<= 4; + c = *(ptr1++) - 0x30; + if (c > 15) c -= 7; + val |= c; + *(ptr2++) = val; + } + + PTTOnCmdLen = ptr2 - PTTOnCmd; + } + } + + if (strcmp(PTTPort, "GPIO") == 0) + { + // Initialise GPIO for PTT if available + +#ifdef __ARM_ARCH + +// if (gpioInitialise() == 0) +// { +// printf("GPIO interface for PTT available\n"); +// gotGPIO = TRUE; + +// SetupGPIOPTT(); +// } +// else +// printf("Couldn't initialise GPIO interface for PTT\n"); +// +#else + printf("GPIO interface for PTT not available on this platform\n"); +#endif + + } + else if (strcmp(PTTPort, "CM108") == 0) + { + DecodeCM108(CM108Addr); + PTTMode |= PTTCM108; + } + + else if (strcmp(PTTPort, "HAMLIB") == 0) + { + PTTMode |= PTTHAMLIB; + HAMLIBSetPTT(0); // to open port + return; + } + + else if (strcmp(PTTPort, "FLRIG") == 0) + { + PTTMode |= PTTFLRIG; + FLRigSetPTT(0); // to open port + return; + } + + else // Serial Port + { +#ifdef USESERIAL + hPTTDevice = new QSerialPort(this); + hPTTDevice->setPortName(PTTPort); + hPTTDevice->setBaudRate(PTTBAUD ); + hPTTDevice->setDataBits(QSerialPort::Data8); + hPTTDevice->setParity(QSerialPort::NoParity); + hPTTDevice->setStopBits(QSerialPort::OneStop); + hPTTDevice->setFlowControl(QSerialPort::NoFlowControl); + if (hPTTDevice->open(QIODevice::ReadWrite)) + { + qDebug() << "PTT Port Opened"; + } + else + { + QMessageBox msgBox; + msgBox.setText("PTT COM Port Open Failed."); + msgBox.exec(); + qDebug() << "PTT Port Open failed"; + delete(hPTTDevice); + hPTTDevice = 0; + } +#endif + } + } +} + +void ClosePTTPort() +{ +#ifdef USESERIAL + if (hPTTDevice) + hPTTDevice->close(); + hPTTDevice = 0; +#endif +} + + +void CM108_set_ptt(int PTTState) +{ + unsigned char io[5]; + int n; + + io[0] = 0; + io[1] = 0; + io[2] = 1 << (3 - 1); + io[3] = PTTState << (3 - 1); + io[4] = 0; + + if (CM108Device == NULL) + return; + +#ifdef WIN32 + hid_device *handle; + + handle = hid_open_path(CM108Device); + + if (!handle) { + printf("unable to open device\n"); + return; + } + + n = hid_write(handle, io, 5); + if (n < 0) + { + printf("Unable to write()\n"); + printf("Error: %ls\n", hid_error(handle)); + } + + hid_close(handle); + +#else + + int fd; + + fd = open(CM108Device, O_WRONLY); + + if (fd == -1) + { + printf("Could not open %s for write, errno=%d\n", CM108Device, errno); + return; + } + + io[0] = 0; + io[1] = 0; + io[2] = 1 << (3 - 1); + io[3] = PTTState << (3 - 1); + io[4] = 0; + + n = write(fd, io, 5); + if (n != 5) + { + printf("Write to %s failed, n=%d, errno=%d\n", CM108Device, n, errno); + } + + close(fd); +#endif + return; + +} + + + +void QtTermTCP::RadioPTT(bool PTTState) +{ +#ifdef __ARM_ARCH + if (useGPIO) + { +// gpioWrite(pttGPIOPin, (pttGPIOInvert ? (1 - PTTState) : (PTTState))); +// return; + } + +#endif + + if ((PTTMode & PTTCM108)) + { + CM108_set_ptt(PTTState); + return; + } + + if ((PTTMode & PTTHAMLIB)) + { + HAMLIBSetPTT(PTTState); + return; + } + + if ((PTTMode & PTTFLRIG)) + { + FLRigSetPTT(PTTState); + return; + } + +#ifdef USESERIAL + + if (hPTTDevice == 0) + return; + + if ((PTTMode & PTTCAT)) + { + if (PTTState) + hPTTDevice->write((char *)PTTOnCmd, PTTOnCmdLen); + else + hPTTDevice->write((char *)PTTOffCmd, PTTOffCmdLen); + + hPTTDevice->flush(); +// hPTTDevice->error(); + return; + + } + + + if ((PTTMode & PTTRTS)) + { + int n = hPTTDevice->setRequestToSend(PTTState); + n = n; + } + +#endif + +} + + + +extern "C" void WriteDebugLog(char * Mess) +{ + qDebug() << Mess; +} + +void QtTermTCP::ConnecttoKISS() +{ + if (strcmp(SerialPort, "TCP") == 0) + { + delete(KISSSock); + + KISSSock = new myTcpSocket(); + KISSSockCopy[0] = (void *)KISSSock; + + + connect(KISSSock, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(KISSdisplayError(QAbstractSocket::SocketError))); + connect(KISSSock, SIGNAL(readyRead()), this, SLOT(KISSreadyRead())); + connect(KISSSock, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onKISSSocketStateChanged(QAbstractSocket::SocketState))); + + KISSSock->connectToHost(KISSHost, KISSPortNum); + + Status3->setText("KISS Connecting"); + } + else + openSerialPort(); + + return; +} + + +void QtTermTCP::KISSTimer() +{ + // Runs every 10 Seconds + + if (KISSConnected == 0 && KISSConnecting == 0) + { + ConnecttoKISS(); + } + else + { + // Verify Serial port is still ok + + if (m_serial && KISSConnected) + { + m_serial->clearError(); + m_serial->isDataTerminalReady(); + + if (m_serial->error()) + { + Debugprintf("Serial Port Lost - isOpen %d Error %d", m_serial->isOpen(), m_serial->error()); + closeSerialPort(); + } + } + } +} + + + +void QtTermTCP::KISSdisplayError(QAbstractSocket::SocketError socketError) +{ + switch (socketError) + { + case QAbstractSocket::RemoteHostClosedError: + break; + + case QAbstractSocket::HostNotFoundError: + QMessageBox::information(this, tr("QtTermTCP"), + tr("KISS host was not found. Please check the " + "host name and portsettings->")); + + Status3->setText("KISS Connection Failed"); + + break; + + case QAbstractSocket::ConnectionRefusedError: + + Status3->setText("KISS Connection Refused"); + break; + + default: + + Status3->setText("KISS Connection Failed"); + } + + KISSConnecting = 0; + KISSConnected = 0; +} + + +extern "C" void KISSSendtoServer(myTcpSocket* Socket, char * Data, int Length) +{ + if (m_serial) + { + if (m_serial->isOpen()) + { + m_serial->clearError(); + + int n = m_serial->write(Data, Length); + + n = m_serial->flush(); + + if (m_serial->error()) + { + Debugprintf("Serial Flush Error - Requested = %d Actual %d Error %d", Length, n, m_serial->error()); + closeSerialPort(); + } + } + } + else if (Socket) + Socket->write(Data, Length); +} + + + +void QtTermTCP::KISSreadyRead() +{ + int Read; + unsigned char Buffer[4096]; myTcpSocket* Socket = static_cast(QObject::sender()); + + // read the data from the socket + + Read = Socket->read((char *)Buffer, 4095); + + KISSDataReceived(Socket, Buffer, Read); + +} + +extern "C" void KISS_del_socket(void * socket); +extern "C" void KISS_add_stream(void * Socket); + +void QtTermTCP::onKISSSocketStateChanged(QAbstractSocket::SocketState socketState) +{ + // myTcpSocket* sender = static_cast(QObject::sender()); + + QTcpSocket* sender = static_cast(QObject::sender()); + + if (socketState == QAbstractSocket::UnconnectedState) + { + // Close any connections + + Ui_ListenSession * Sess = NULL; + + Status3->setText("KISS Disconnected"); + actHost[18]->setEnabled(0); + + KISSConnecting = KISSConnected = 0; + + // Free the monitor Window + + if (KISSMonSess) + { + Sess = KISSMonSess; + + if (TermMode == MDI) + Sess->setWindowTitle("Monitor Session Disconnected"); + + else if (TermMode == Tabbed) + tabWidget->setTabText(Sess->Tab, "Monitor"); + + KISSMonSess = nullptr; + } + + KISS_del_socket(sender); + KISSSock = NULL; + } + else if (socketState == QAbstractSocket::ConnectedState) + { + int i; + + KISSConnected = 1; + KISSConnecting = 0; + + Status3->setText("KISS Connected"); + actHost[18]->setEnabled(1); // Enable KISS Connect Line + + KISS_add_stream(sender); + + // Attach a Monitor Window if available + + Ui_ListenSession * Sess = NULL; + Ui_ListenSession * S; + + if (TermMode == MDI) + { + // See if an old session can be reused + + for (int i = 0; i < _sessions.size(); ++i) + { + S = _sessions.at(i); + + // for (Ui_ListenSession * S: _sessions) + // { + if ((S->SessionType == Mon) && S->clientSocket == NULL && S->KISSSession == NULL) + { + Sess = S; + break; + } + } + + // Create a window if none found, else reuse old + + if (Sess == NULL) + { + Sess = newWindow((QObject *)mythis, Mon, ""); + } + } + else if (TermMode == Tabbed) + { + // Tabbed - look for free session + + for (i = 8; i; i--) + { + S = _sessions.at(i); + + if (S->clientSocket == NULL && S->KISSSession == NULL) + { + Sess = S; + break; + } + } + } + else if (TermMode == Single && (singlemodeFormat & Mon)) + { + S = _sessions.at(0); + + if (S->clientSocket == NULL && S->KISSSession == NULL) + Sess = S; + + } + + if (Sess) + { + KISSMonSess = Sess; // Flag as in use + + if (TermMode == MDI) + Sess->setWindowTitle("KISS Monitor Window"); + else if (TermMode == Tabbed) + tabWidget->setTabText(Sess->Tab, "KISS Mon"); + else if (TermMode == Single) + mythis->setWindowTitle("KISS Monitor Window"); + +// if (TermMode == Single) +// { +// discAction->setEnabled(false); +// YAPPSend->setEnabled(false); +// connectMenu->setEnabled(false); +// } + } + } +} + + +extern "C" char * frame_monitor(string * frame, char * code, bool tx_stat); +extern "C" char * ShortDateTime(); + +extern "C" void monitor_frame(int snd_ch, string * frame, char * code, int tx, int excluded) +{ + UNUSED(excluded); + UNUSED(snd_ch); + + int Len; + char Msg[1024]; + + if (tx) + sprintf(Msg, "\x1b\x10%s", frame_monitor(frame, code, tx)); + else + sprintf(Msg, "\x1b\x11%s", frame_monitor(frame, code, tx)); + + Len = strlen(Msg); + + if (Msg[Len - 1] != '\r') + { + Msg[Len++] = '\r'; + Msg[Len] = 0; + } + + if (KISSMonSess) + WritetoMonWindow(KISSMonSess, (unsigned char *)Msg, Len); + +} + +extern "C" Ui_ListenSession * ax25IncommingConnect(TAX25Port * AX25Sess) +{ + // Look for/create Terminal Window for connection + + Ui_ListenSession * Sess = NULL; + Ui_ListenSession * S; + char Title[80]; + int i = 0; + + if (TermMode == MDI) + { + // See if an old session can be reused + + for (int i = 0; i < _sessions.size(); ++i) + { + S = _sessions.at(i); + + if ((S->SessionType & Listen) && S->clientSocket == NULL && S->AGWSession == NULL && S->KISSSession == NULL) + { + Sess = S; + break; + } + } + + // Create a window if none found, else reuse old + + if (Sess == NULL) + { + Sess = newWindow((QObject *)mythis, Listen, ""); + } + } + else + { + // Single or Tabbed - look for free session + + + for (i = 0; i < _sessions.size(); ++i) + { + S = _sessions.at(i); + + if (S->clientSocket == NULL && S->AGWSession == NULL && S->KISSSession == NULL) + { + Sess = S; + break; + } + } + + if (Sess == NULL) + { + // Clear connection + + return NULL; + } + } + + if (Sess) + { + sprintf(Title, "Connected to %s", AX25Sess->corrcall); + + if (TermMode == MDI) + { + Sess->setWindowTitle(Title); + } + else if (TermMode == Tabbed) + { + tabWidget->setTabText(i, AX25Sess->corrcall); + } + else if (TermMode == Single) + mythis->setWindowTitle(Title); + + AX25Sess->port = 0; + AX25Sess->Sess = Sess; // Crosslink AGW and Term Sessions + AX25Sess->PID = 240;; + + Sess->KISSSession = AX25Sess; + + setMenus(true); + + if (ConnectBeep) + myBeep(); + + // Send CText if defined + + if (listenCText[0]) + SendtoAX25(Sess->KISSSession, (unsigned char *)listenCText, (int)strlen(listenCText)); + } + return Sess; +} + + +extern "C" void AX25_disc(TAX25Port * AX25Sess, Byte mode) +{ + char Msg[128]; + int Len = 0; + Ui_ListenSession * Sess = (Ui_ListenSession *)AX25Sess->Sess; + + if (AX25Sess->status == STAT_TRY_LINK) + { + // Connect failed + + Len = sprintf(Msg, "Connection to %s failed\r", AX25Sess->corrcall); + } + else + { + switch (mode) + { + case MODE_OTHER: + case MODE_OUR: + + Len = sprintf(Msg, "Disconnected from %s\r", AX25Sess->corrcall); + break; + + case MODE_RETRY: + + Len = sprintf(Msg, "Disconnected from %s - Retry count exceeded\r", AX25Sess->corrcall); + break; + + }; + } + + SendtoTerm(Sess, Msg, Len); + ClearSessLabel(Sess); + Sess->KISSSession = NULL; + AX25Sess->Sess = 0; + + setMenus(0); +}; + +int QtTermTCP::openSerialPort() +{ + if (m_serial && m_serial->isOpen()) + { + m_serial->close(); + } + + m_serial = nullptr; + m_serial = new QSerialPort(this); + + m_serial->setPortName(SerialPort); + m_serial->setBaudRate(KISSBAUD); + + if (m_serial->open(QIODevice::ReadWrite)) + { + int i; + connect(m_serial, &QSerialPort::readyRead, this, &QtTermTCP::readSerialData); +// connect(m_serial, &QSerialPort::errorOccurred, this, &QtTermTCP::handleError); + + KISSConnected = 1; + KISSConnecting = 0; + + Status3->setText("KISS Connected"); + actHost[18]->setEnabled(1); // Enable KISS Connect Line + + KISS_add_stream(m_serial); + + // Attach a Monitor Window if available + + Ui_ListenSession * Sess = NULL; + Ui_ListenSession * S; + + if (TermMode == MDI) + { + // See if an old session can be reused + + for (int i = 0; i < _sessions.size(); ++i) + { + S = _sessions.at(i); + + // for (Ui_ListenSession * S: _sessions) + // { + if ((S->SessionType == Mon) && S->clientSocket == NULL && S->KISSSession == NULL) + { + Sess = S; + break; + } + } + + // Create a window if none found, else reuse old + + if (Sess == NULL) + { + Sess = newWindow((QObject *)mythis, Mon, ""); + } + } + else if (TermMode == Tabbed) + { + // Tabbed - look for free session + + for (i = 8; i; i--) + { + S = _sessions.at(i); + + if (S->clientSocket == NULL && S->KISSSession == NULL) + { + Sess = S; + break; + } + } + } + else if (TermMode == Single && (singlemodeFormat & Mon)) + { + S = _sessions.at(0); + + if (S->clientSocket == NULL && S->KISSSession == NULL) + Sess = S; + + } + + if (Sess) + { + KISSMonSess = Sess; // Flag as in use + + if (TermMode == MDI) + Sess->setWindowTitle("KISS Monitor Window"); + else if (TermMode == Tabbed) + tabWidget->setTabText(Sess->Tab, "KISS Mon"); + else if (TermMode == Single) + mythis->setWindowTitle("KISS Monitor Window"); + + // if (TermMode == Single) + // { + // discAction->setEnabled(false); + // YAPPSend->setEnabled(false); + // connectMenu->setEnabled(false); + // } + } + + + + return 1; + } + else + { + Status3->setText("KISS Open Failed"); + KISSConnected = 0; + KISSConnecting = 0; + return 0; + } +} + + + +void closeSerialPort() +{ + if (m_serial && m_serial->isOpen()) + { + m_serial->close(); + m_serial = nullptr; + } + + m_serial = nullptr; + + KISSConnected = 0; + KISSConnecting = 0; + + Status3->setText("KISS Closed"); + actHost[18]->setEnabled(0); // Enable KISS Connect Line +} + +void QtTermTCP::readSerialData() +{ + int Read; + unsigned char Buffer[8192]; + + // read the data from the socket + + m_serial->clearError(); + + Read = m_serial->read((char *)Buffer, 2047); + + if (m_serial->error()) + { + Debugprintf("Serial Read Error - RC %d Error %d", Read, m_serial->error()); + closeSerialPort(); + return; + } + + + while (Read > 0) + { + KISSDataReceived(m_serial, Buffer, Read); + Read = m_serial->read((char *)Buffer, 2047); + } +} + +void QtTermTCP::handleError(QSerialPort::SerialPortError serialPortError) +{ + Debugprintf("Serial port Error %d", serialPortError); + closeSerialPort(); +} + +extern "C" void CheckUIFrame(unsigned char * path, string * data) +{ + // If we have KISS enabled and dest is UIDEST look for a KISS window in UI Mode + + if (KISSSock == 0) + return; + + char Dest[10]; + char From[10]; + + Dest[ConvFromAX25(path, Dest)] = 0; + From[ConvFromAX25(&path[7], From)] = 0; + + // ok, Find a Kiss Session with this Dest + + Ui_ListenSession * Sess = NULL; + + for (int i = 0; i < _sessions.size(); ++i) + { + Sess = _sessions.at(i); + + if (Sess->KISSMode == 1 && strcmp(Dest, Sess->UIDEST) == 0) + { + char Msg[512]; + int Len; + + data->Data[data->Length] = 0; + + Len = sprintf(Msg, "%s:%s", From, data->Data); + SendtoTerm(Sess, Msg, Len); + return; + } + } + +} + + +QColor QtTermTCP::setColor(QColor Colour) +{ + // const QColorDialog::ColorDialogOptions options = QFlag(colorDialogOptionsWidget->value()); + + + QColor col = Colour; + + QColorDialog dialog; + dialog.setCurrentColor(Colour); + dialog.setOption(QColorDialog::DontUseNativeDialog); + + if (dialog.exec() == QColorDialog::Accepted) + col = QVariant(dialog.currentColor()).toString(); + + +// const QColor color = QColorDialog::getColor(Qt::green, this, "Select Color", 0); + + return col; +} + + + + + + + diff --git a/QtTermTCP.h b/QtTermTCP.h new file mode 100644 index 0000000..dd93eec --- /dev/null +++ b/QtTermTCP.h @@ -0,0 +1,260 @@ +#pragma once + +#include +#include "ui_QtTermTCP.h" +//#include "ui_ListenPort.h" +//#include "ui_AGWParams.h" +//#include "ui_AGWConnect.h" +#include "ui_ColourConfig.h" +#include "ui_VARAConfig.h" +#include "ui_KISSConfig.h" +#include "QTextEdit" +#include "QSplitter" +#include "QLineEdit" +#include "QTcpSocket" +#include +#include +#include "QThread" +#include "QTcpServer" +#include "QMdiArea" +#include +#include "QMessageBox" +#include "QTimer" +#include "QSettings" +#include "QThread" +#include +#include +#include +#include +#include +#include +#include + + + +QT_BEGIN_NAMESPACE +class QComboBox; +class QLabel; +class QLineEdit; +class QPushButton; +class QTcpSocket; +class QNetworkSession; + +class myTcpSocket : public QTcpSocket +{ +public: + QWidget * Sess; +}; + + +class Ui_ListenSession : public QMainWindow +{ + Q_OBJECT + +public: + explicit Ui_ListenSession(QWidget *Parent = 0) : QMainWindow(Parent) {} + ~Ui_ListenSession(); + + int SessionType; // Type Mask - Term, Mon, Listen + int CurrentWidth; + int CurrentHeight; // Saved so can be restored after Cascade + + QTextEdit *termWindow; + QTextEdit *monWindow; + QLineEdit *inputWindow; + + myTcpSocket *clientSocket; + + QAction * actActivate; // From active Windows menu + + char * KbdStack[50]; + int StackIndex; + + QMdiSubWindow *sw; // The MdiSubwindow is the container for this session + + int InputMode; + int SlowTimer; + int MonData; + + int OutputSaveLen; + char OutputSave[16384]; + + int MonSaveLen; + char MonSave[4096]; + + char PortMonString[1024]; // 32 ports 32 Bytes + unsigned long long portmask; + int mtxparam; + int mcomparam; + int monUI; + int MonitorNODES; + int MonitorColour; + int CurrentHost; + int Tab; // Tab Index if Tabbed Mode + void * AGWSession; // Terinal sess - Need to cast to TAGWPort to use it + void * AGWMonSession; + void * KISSSession; + int KISSMode; // Connected or UI + char UIDEST[32]; + char UIPATH[128]; + +protected: + +private slots: + +private: + +}; + + +class QtTermTCP : public QMainWindow +{ + Q_OBJECT + +public: + QtTermTCP(QWidget *parent = NULL); + void closeEvent(QCloseEvent * event); + static void setFonts(); + ~QtTermTCP(); + +private slots: + void Disconnect(); + void doYAPPSend(); + void doYAPPSetRX(); + void menuChecked(); + void Connect(); + void displayError(QAbstractSocket::SocketError socketError); + void readyRead(); + + void LreturnPressed(Ui_ListenSession * LUI); + void LDisconnect(Ui_ListenSession * LUI); + void SetupHosts(); + void MyTimerSlot(); + void KISSTimerSlot(); + void ListenSlot(); + void AGWSlot(); + void VARASlot(); + void KISSSlot(); + void deviceaccept(); + void KISSaccept(); + void KISSreject(); + void devicereject(); + void showContextMenuM(const QPoint &pt); + void showContextMenuT(const QPoint &pt); + void doQuit(); + void onTEselectionChanged(); + void onLEselectionChanged(); + void setSplit(); + void onNewConnection(); + void onSocketStateChanged(QAbstractSocket::SocketState socketState); + void updateWindowMenu(); + void doNewTerm(); + void doNewMon(); + void doNewCombined(); + void doCascade(); + void actActivate(); + void xon_mdiArea_changed(); + void doFonts(); + void doMFonts(); + void ConnecttoVARA(); + void VARATimer(); + void AGWdisplayError(QAbstractSocket::SocketError socketError); + void AGWreadyRead(); + void onAGWSocketStateChanged(QAbstractSocket::SocketState socketState); + void VARAdisplayError(QAbstractSocket::SocketError socketError); + void VARAreadyRead(); + void onVARASocketStateChanged(QAbstractSocket::SocketState socketState); + void KISSdisplayError(QAbstractSocket::SocketError socketError); + void KISSreadyRead(); + void onKISSSocketStateChanged(QAbstractSocket::SocketState socketState); + int openSerialPort(); + void readSerialData(); + void handleError(QSerialPort::SerialPortError serialPortError); + void doColours(); + void ColourPressed(); + void Colouraccept(); + void Colourreject(); + QColor setColor(QColor Colour); + void VARADatadisplayError(QAbstractSocket::SocketError socketError); + void VARADatareadyRead(); + void onVARADataSocketStateChanged(QAbstractSocket::SocketState socketState); + void HAMLIBdisplayError(QAbstractSocket::SocketError socketError); + void HAMLIBreadyRead(); + void onHAMLIBSocketStateChanged(QAbstractSocket::SocketState socketState); + void ConnecttoHAMLIB(); + void HAMLIBSetPTT(int PTTState); + void FLRigdisplayError(QAbstractSocket::SocketError socketError); + void FLRigreadyRead(); + void onFLRigSocketStateChanged(QAbstractSocket::SocketState socketState); + void ConnecttoFLRig(); + void FLRigSetPTT(int PTTState); + void CATChanged(bool State); + void PTTPortChanged(int Selected); + void OpenPTTPort(); + void RadioPTT(bool PTTState); + void tabSelected(int); + void VARAHFChanged(bool state); + void VARAFMChanged(bool State); + void VARASATChanged(bool State); + void SetVARAParams(); + +protected: + bool eventFilter(QObject* obj, QEvent *event); + +private: + + void ConnecttoAGW(); + + void AGWTimer(); + + Ui::QtTermTCPClass ui; + + QMenu *hostsubMenu; + + QAction *closeAct; + QAction *closeAllAct; + QAction *tileAct; + QAction *cascadeAct; + QAction *nextAct; + QAction *previousAct; + QAction *windowMenuSeparatorAct; + QAction *newTermAct; + QAction *newMonAct; + QAction *newCombinedAct; + QAction *AGWAction; + QAction *VARAAction; + QAction *KISSAction; + QAction *quitAction; + + QList _sockets; + + QWidget *centralWidget; + void ConnecttoKISS(); + void KISSTimer(); +}; + +extern "C" +{ + void EncodeSettingsLine(int n, char * String); + void DecodeSettingsLine(int n, char * String); + void WritetoOutputWindow(Ui_ListenSession * Sess, unsigned char * Buffer, int Len); + void WritetoOutputWindowEx(Ui_ListenSession * Sess, unsigned char * Buffer, int len, QTextEdit * termWindow, int *OutputSaveLen, char * OutputSave, QColor Colour); + void WritetoMonWindow(Ui_ListenSession * Sess, unsigned char * Buffer, int Len); + void ProcessReceivedData(Ui_ListenSession * Sess, unsigned char * Buffer, int len); + void SendTraceOptions(Ui_ListenSession * LUI); + void setTraceOff(Ui_ListenSession * Sess); + void SetPortMonLine(int i, char * Text, int visible, int enabled); + void SaveSettings(); + void myBeep(); + void YAPPSendFile(Ui_ListenSession * Sess, char * FN); + int SocketSend(Ui_ListenSession * Sess, char * Buffer, int len); + void SendTraceOptions(Ui_ListenSession * Sess); + int SocketFlush(Ui_ListenSession * Sess); + extern void mySleep(int ms); + extern void setTraceOff(Ui_ListenSession * Sess); +} + + +char * strlop(char * buf, char delim); +extern "C" void setMenus(int State); +void Send_AGW_Ds_Frame(void * AGW); diff --git a/QtTermTCP.ico b/QtTermTCP.ico new file mode 100644 index 0000000000000000000000000000000000000000..4a01e37172a2b552e3284c042772a12c5b8cc9c9 GIT binary patch literal 123113 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 z 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 literal 0 HcmV?d00001 diff --git a/QtTermTCP.ini b/QtTermTCP.ini new file mode 100644 index 0000000..4544206 --- /dev/null +++ b/QtTermTCP.ini @@ -0,0 +1,116 @@ +[General] +HostParams0=127.0.0.1|8011|john|password| +geometry=@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\0\0\0\xf9\0\0\0\x85\0\0\x4\x5\0\0\x3>\0\0\0\xf9\0\0\0\x85\0\0\x4\x5\0\0\x3>\0\0\0\0\0\0\0\0\x5\0\0\0\0\xf9\0\0\0\x85\0\0\x4\x5\0\0\x3>) +HostParams1=|0||| +windowState=@ByteArray(\0\0\0\xff\0\0\0\0\xfd\0\0\0\0\0\0\x3\r\0\0\x2z\0\0\0\x4\0\0\0\x4\0\0\0\b\0\0\0\b\xfc\0\0\0\x1\0\0\0\x3\0\0\0\x1\0\0\0\x16\0m\0\x61\0i\0n\0T\0o\0o\0l\0\x62\0\x61\0r\0\0\0\0\0\xff\xff\xff\xff\0\0\0\0\0\0\0\0) +HostParams2=|0||| +HostParams3=|0||| +HostParams4=|0||| +HostParams5=|0||| +HostParams6=|0||| +HostParams7=|0||| +HostParams8=|0||| +HostParams9=|0||| +HostParams10=|0||| +HostParams11=|0||| +HostParams12=|0||| +HostParams13=|0||| +HostParams14=|0||| +HostParams15=|0||| +Split=50 +ChatMode=1 +Bells=1 +StripLF=1 +AlertBeep=1 +ConnectBeep=1 +CurrentHost=0 +YAPPPath= +listenPort=8015 +listenEnable=1 +listenCText=Hello\r +convUTF8=0 +PTT=None +PTTBAUD=19200 +PTTMode=19200 +CATHex=1 +PTTOffString= +PTTOnString= +pttGPIOPin=17 +pttGPIOPinR=17 +CM108Addr=0xD8C:0x08 +HamLibPort=4532 +HamLibHost=127.0.0.1 +FLRigPort=12345 +FLRigHost=127.0.0.1 +AGWEnable=1 +AGWMonEnable=1 +AGWTermCall= +AGWBeaconDest= +AGWBeaconPath= +AGWBeaconInterval=0 +AGWBeaconPorts= +AGWBeaconText= +AGWHost=127.0.0.1 +AGWPort=8001 +AGWPaclen=80 +AGWToCalls= +KISSEnable=0 +MYCALL= +KISSHost=127.0.0.1 +KISSMode=0 +KISSPort=8100 +KISSSerialPort=None +KISSBAUD=19200 +VARAEnable=0 +VARATermCall= +VARAHost=127.0.0.1 +VARAPort=8300 +VARAPath=C:\\VARA\\VARA.exe +VARAHostHF=127.0.0.1 +VARAPortHF=8300 +VARAPathHF=C:\\VARA\\VARA.exe +VARAHostFM=127.0.0.1 +VARAPortFM=8300 +VARAPathFM=C:\\VARA\\VARAFM.exe +VARAHostSAT=127.0.0.1 +VARAPortSAT=8300 +VARAPathSAT=C:\\VARA\\VARASAT.exe +VARA500=0 +VARA2300=1 +VARA2750=0 +VARAHF=1 +VARAFM=0 +VARASAT=0 +TabType=1 1 1 1 1 1 1 2 2 0 +monBackground=@Variant(\0\0\0\x43\x1\xff\xff\0\0\0\0\0\0\0\0) +monRxText=@Variant(\0\0\0\x43\x1\xff\xff\0\0\xff\xff\0\0\0\0) +monTxText=@Variant(\0\0\0\x43\x1\xff\xff\xff\xff\0\0\0\0\0\0) +monOtherText=@Variant(\0\0\0\x43\x1\xff\xff\0\0\0\0\0\0\0\0) +termBackground=@Variant(\0\0\0\x43\x1\xff\xff\0\0\0\0\0\0\0\0) +outputText=@Variant(\0\0\0\x43\x1\xff\xff\0\0\xff\xff\0\0\0\0) +EchoText=@Variant(\0\0\0\x43\x1\xff\xff\xff\xff\0\0\xff\xff\0\0) +WarningText=@Variant(\0\0\0\x43\x1\xff\xff\xff\xff\0\0\0\0\0\0) +inputBackground=@Variant(\0\0\0\x43\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0) +inputText=@Variant(\0\0\0\x43\x1\xff\xff\0\0\0\0\0\0\0\0) +TermMode=2 +singlemodeFormat=3 +Sessions="1|4, 0, 3, 136, 599, 932|" + +[AX25_A] +Retries=10 +Maxframe=4 +Paclen=128 +FrackTime=8 +IdleTime=180 +SlotTime=100 +Persist=128 +RespTime=1500 +TXFrmMode=1 +FrameCollector=6 +ExcludeCallsigns= +ExcludeAPRSFrmType= +KISSOptimization=0 +DynamicFrack=0 +BitRecovery=0 +IPOLL=80 +MyDigiCall= diff --git a/QtTermTCP.pro b/QtTermTCP.pro new file mode 100644 index 0000000..e802688 --- /dev/null +++ b/QtTermTCP.pro @@ -0,0 +1,48 @@ + +QT += core gui +QT += network +QT += widgets +QT += serialport + + +TARGET = QtTermTCP +TEMPLATE = app + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which as been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. + +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x050000 # disables all the APIs deprecated before Qt 6.0.0 + + +SOURCES += main.cpp\ + QtTermTCP.cpp\ + TermTCPCommon.cpp\ + TabDialog.cpp \ + AGWCode.cpp \ + ax25.c \ + UZ7HOUtils.c \ + ax25_l2.c\ + utf8Routines.cpp + +HEADERS += QtTermTCP.h\ + TabDialog.h + +FORMS += QtTermTCP.ui\ + ListenPort.ui \ + AGWParams.ui \ + VARAConfig.ui \ + KISSConfig.ui \ + AGWConnect.ui + +RESOURCES += QtTermTCP.qrc + +RC_ICONS = QtTermTCP.ico + +QMAKE_LFLAGS += -no-pie diff --git a/QtTermTCP.pro.bak b/QtTermTCP.pro.bak new file mode 100644 index 0000000..d07cc3d --- /dev/null +++ b/QtTermTCP.pro.bak @@ -0,0 +1,38 @@ + +QT += core gui +QT += network +QT += widgets + + +TARGET = QtTermTCP +TEMPLATE = app + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which as been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. + +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x050000 # disables all the APIs deprecated before Qt 6.0.0 + + +SOURCES += main.cpp\ + QtTermTCP.cpp\ + TermTCPCommon.cpp\ + TabDialog.cpp + +HEADERS += QtTermTCP.h\ + TabDialog.h + +FORMS += QtTermTCP.ui\ + ListenPort.ui + +RESOURCES += QtTermTCP.qrc + +RC_ICONS = QtTermTCP.ico + +QMAKE_LFLAGS += -no-pie diff --git a/QtTermTCP.qrc b/QtTermTCP.qrc new file mode 100644 index 0000000..1e71b3b --- /dev/null +++ b/QtTermTCP.qrc @@ -0,0 +1,5 @@ + + + QtTermTCP.ico + + diff --git a/QtTermTCP.rc b/QtTermTCP.rc new file mode 100644 index 0000000000000000000000000000000000000000..b75a88f233c8cef35e6c33ecd5c225cfa9274957 GIT binary patch literal 5202 zcmdUzTTdHD6vxkVrGAI4d1=%XOu|F@7>uDV20QVkt%PJzzy=9yWScgT`q`8I{xd%8 zu6LallLuF;@$8;C_uI_)^QRr#vBXa7#x87X6Pq!HHV4)RHnRsiw_Cny_5)91BfGLO zV+wrEd8YE_hG)61N+>2WQ*9wQrEJ6Xq?TV^(j$z6`jP0{ymKTP0wNax35s z1D$xkwYGJvYdt%#hP7;9@2w8>fcH~t+V4O*{GQl8kS6agUpj2wD+33DF%>f44+xMh+zQJux5i<#l)*VmQJZ%9Ix+c`(aV)lKl&ce(1igy&m3 zB(Gvsb*P{o^;JXmfc$Fmi0|J#$x6yDJl>PV1E@}!Uj(U5p!(`DAB9YHh~qWM<&OPq zFX7z*Zy#xqW*K9^>hY2-t8+jV(#Mixq#pe5xkrf5h1O2+FoHF~O?-8JPDd*n6tmD{ovI`SpH zQkRL3xi#h?LL0#9Ea1uQK@^S2u&k?(ggce!VqX%{WBaKTq4E^H>4NvGJ9qM0FK9HG1 zR>WV~KYZLh)ZyxMogS5v*EQu7IqW{tU(t_e`0C5})eOFND@n~$w9qf|-a|({dkdP> zV_i-)Z8Xg5kgms`na6U;<@K))+rqQ*Oa%?*#9aMgM3io@?21^8(RH#^_2t~WGV)xj z_6BQy3KsprZ@zz|G-dtCSAFgiN1b%!g9>_UpS$NII<}AG-Z@`az_ou=h~XPz*|v8p zB$o4ZyAR-$J;la4aNb>Hw~^8XYl>9)ES{6)t0^l{@@w*V2aUD9zApJ%bRDXktbOc} z)8&3Gw^C8nsbRX%d)asISasW?t=nLKU_3=i6Z}!|iz0PTjFep?&MtHK#3&V4bgrp| zQ^qUo+a+^;#EVsiI{hzjx<>27eubAR?=Hk&r{;Cy-CF465%l;gp4n{rIv!~wZ9@G& za08kbGPWq=Dc;WWeooG$`?zOM5t6U*KHi!>t7qgZy9+l}^^6{*duF!!)WKs%Q3maX z+^8HhojK)yVF8oAvJ6`JB*uFUXeS?km01)g+wObSq5o)`Ty0F5Tc8>}QGn zi3fGt)}8S__)~Xu-P?7i)*dh1Ig|--`CRvSVV{F93jH^bMj;8`S-s+YQ+22+nn4-$ z*6Bgq#Z@vyBd)hR`j6n_V)c$W-cY4+Bb+2^>`SMi;ynk;<_4WYcx7v4unN0Dv9rlO z-{Iu_)u+9$bpzY1%*$#c`?DjSkiK-oE}nO@{5I>|Ys=^7{@0>APVpY$+_Qe%EI(wm Vk(R47eoSSkc8mY46yq*V{{XegYUuy~ literal 0 HcmV?d00001 diff --git a/QtTermTCP.sln b/QtTermTCP.sln new file mode 100644 index 0000000..01fdba7 --- /dev/null +++ b/QtTermTCP.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.102 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "QtTermTCP", "D:\BT Cloud\ActiveSource\QtTermTCP\QtTermTCP.vcxproj", "{B12702AD-ABFB-343A-A199-8E24837244A3}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x86 = Debug|x86 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug|x86.ActiveCfg = Debug|Win32 + {B12702AD-ABFB-343A-A199-8E24837244A3}.Debug|x86.Build.0 = Debug|Win32 + {B12702AD-ABFB-343A-A199-8E24837244A3}.Release|x86.ActiveCfg = Release|Win32 + {B12702AD-ABFB-343A-A199-8E24837244A3}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {DD1F8FD5-5375-4EFC-BBEE-8E35ABA15C82} + EndGlobalSection +EndGlobal diff --git a/QtTermTCP.ui b/QtTermTCP.ui new file mode 100644 index 0000000..a2bfd8c --- /dev/null +++ b/QtTermTCP.ui @@ -0,0 +1,66 @@ + + + QtTermTCPClass + + + + 0 + 0 + 781 + 698 + + + + + Arial + 12 + + + + QtTermTCP + + + + :/QtTermTCP/QtTermTCP.ico:/QtTermTCP/QtTermTCP.ico + + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + 6 + + + + + + + + + Listen + + + + + Disconnect + + + + + + + + + diff --git a/QtTermTCP.vcxproj b/QtTermTCP.vcxproj new file mode 100644 index 0000000..4382cba --- /dev/null +++ b/QtTermTCP.vcxproj @@ -0,0 +1,267 @@ + + + + + Release + Win32 + + + Debug + Win32 + + + + {14F3B24E-473C-324E-A99D-3B679FCF5F67} + QtTermTCP + QtVS_v304 + 10.0.19041.0 + 10.0.19041.0 + $(MSBuildProjectDirectory)\QtMsBuild + + + + v141 + release\ + false + NotSet + Application + release\ + QtTermTCP + + + v141 + debug\ + false + NotSet + Application + debug\ + QtTermTCP + + + + + + + + + + + + + + + + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)Intermed\$(Platform)\$(Configuration)\ + QtTermTCP + true + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)Intermed\$(Platform)\$(Configuration)\ + QtTermTCP + true + false + + + msvc2017 + core;network;gui;widgets;serialport + + + msvc2017 + core;network;gui;widgets;serialport + + + + + + + GeneratedFiles\$(ConfigurationName);GeneratedFiles;.;release;/include;%(AdditionalIncludeDirectories) + -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions) + $(IntDir) + false + None + 4577;4467;%(DisableSpecificWarnings) + Sync + $(IntDir) + MaxSpeed + _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;QT_DEPRECATED_WARNINGS;QT_DISABLE_DEPRECATED_BEFORE=0x050000;NDEBUG;QT_NO_DEBUG;%(PreprocessorDefinitions) + false + + + MultiThreadedDLL + true + true + Level3 + true + + + shell32.lib;setupapi.lib;%(AdditionalDependencies) + C:\opensslx86\lib;C:\Utils\my_sql\mysql-5.7.25-win32\lib;C:\Utils\postgresqlx86\pgsql\lib;%(AdditionalLibraryDirectories) + -no-pie "/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" %(AdditionalOptions) + true + false + true + false + true + $(OutDir)\QtTermTCP.exe + true + Windows + true + + + Unsigned + None + 0 + + + _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;QT_DEPRECATED_WARNINGS;QT_DISABLE_DEPRECATED_BEFORE=0x050000;NDEBUG;QT_NO_DEBUG;QT_WIDGETS_LIB;QT_GUI_LIB;QT_NETWORK_LIB;QT_SERIALPORT_LIB;QT_CORE_LIB;%(PreprocessorDefinitions) + + + msvc + ./$(Configuration)/moc_predefs.h + Moc'ing %(Identity)... + output + $(IntDir) + moc_%(Filename).cpp + + + QtTermTCP + default + Rcc'ing %(Identity)... + $(IntDir) + qrc_%(Filename).cpp + + + Uic'ing %(Identity)... + $(IntDir) + ui_%(Filename).h + + + + + GeneratedFiles\$(ConfigurationName);GeneratedFiles;.;debug;/include;%(AdditionalIncludeDirectories) + -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 %(AdditionalOptions) + $(IntDir) + false + ProgramDatabase + 4577;4467;%(DisableSpecificWarnings) + Sync + $(intdir) + Disabled + _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;QT_DEPRECATED_WARNINGS;QT_DISABLE_DEPRECATED_BEFORE=0x050000;%(PreprocessorDefinitions) + false + MultiThreadedDebugDLL + true + true + Level3 + true + + + shell32.lib;setupapi.lib;%(AdditionalDependencies) + C:\opensslx86\lib;C:\Utils\my_sql\mysql-5.7.25-win32\lib;C:\Utils\postgresqlx86\pgsql\lib;%(AdditionalLibraryDirectories) + -no-pie "/MANIFESTDEPENDENCY:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' publicKeyToken='6595b64144ccf1df' language='*' processorArchitecture='*'" %(AdditionalOptions) + true + true + true + $(OutDir)\QtTermTCP.exe + true + Windows + true + + + Unsigned + None + 0 + + + _WINDOWS;UNICODE;_UNICODE;WIN32;_ENABLE_EXTENDED_ALIGNED_STORAGE;QT_DEPRECATED_WARNINGS;QT_DISABLE_DEPRECATED_BEFORE=0x050000;QT_WIDGETS_LIB;QT_GUI_LIB;QT_NETWORK_LIB;QT_SERIALPORT_LIB;QT_CORE_LIB;_DEBUG;%(PreprocessorDefinitions) + + + msvc + ./$(Configuration)/moc_predefs.h + Moc'ing %(Identity)... + output + $(IntDir) + moc_%(Filename).cpp + + + QtTermTCP + default + Rcc'ing %(Identity)... + $(IntDir) + qrc_%(Filename).cpp + + + Uic'ing %(Identity)... + $(IntDir) + ui_%(Filename).h + + + + + + + + + + + + + + + + + + + + + + + Document + true + $(QTDIR)\mkspecs\features\data\dummy.cpp;%(AdditionalInputs) + cl -Bx"$(QTDIR)\bin\qmake.exe" -nologo -Zc:wchar_t -FS -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -Zi -MDd -W3 -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 -wd4577 -wd4467 -E $(QTDIR)\mkspecs\features\data\dummy.cpp 2>NUL >debug\moc_predefs.h + Generate moc_predefs.h + debug\moc_predefs.h;%(Outputs) + + + Document + $(QTDIR)\mkspecs\features\data\dummy.cpp;%(AdditionalInputs) + cl -Bx"$(QTDIR)\bin\qmake.exe" -nologo -Zc:wchar_t -FS -Zc:rvalueCast -Zc:inline -Zc:strictStrings -Zc:throwingNew -Zc:referenceBinding -Zc:__cplusplus -O2 -MD -W3 -w34100 -w34189 -w44996 -w44456 -w44457 -w44458 -wd4577 -wd4467 -E $(QTDIR)\mkspecs\features\data\dummy.cpp 2>NUL >release\moc_predefs.h + Generate moc_predefs.h + release\moc_predefs.h;%(Outputs) + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/QtTermTCP.vcxproj.filters b/QtTermTCP.vcxproj.filters new file mode 100644 index 0000000..5347f44 --- /dev/null +++ b/QtTermTCP.vcxproj.filters @@ -0,0 +1,131 @@ + + + + + {99349809-55BA-4b9d-BF79-8FDBB0286EB3} + ui + false + + + {99349809-55BA-4b9d-BF79-8FDBB0286EB3} + ui + false + + + {71ED8ED8-ACB9-4CE9-BBE1-E00B30144E11} + cpp;c;cxx;moc;h;def;odl;idl;res; + + + {71ED8ED8-ACB9-4CE9-BBE1-E00B30144E11} + cpp;c;cxx;moc;h;def;odl;idl;res; + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {D9D6E242-F8AF-46E4-B9FD-80ECBC20BA3E} + qrc;* + false + + + {D9D6E242-F8AF-46E4-B9FD-80ECBC20BA3E} + qrc;* + false + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + + + Generated Files + + + Generated Files + + + + + Form Files + + + Form Files + + + Form Files + + + Form Files + + + Form Files + + + Form Files + + + Form Files + + + + + Resource Files + + + Resource Files + + + + + + \ No newline at end of file diff --git a/QtTermTCP.vcxproj.user b/QtTermTCP.vcxproj.user new file mode 100644 index 0000000..594ceec --- /dev/null +++ b/QtTermTCP.vcxproj.user @@ -0,0 +1,16 @@ + + + + + 2022-05-19T07:28:47.9186341Z + + + 2022-05-19T07:28:58.9302359Z + + + 2022-08-30T07:53:00.9638702Z + + + 2022-08-30T07:53:22.4120059Z + + \ No newline at end of file diff --git a/QtTermTCP52.zip b/QtTermTCP52.zip new file mode 100644 index 0000000000000000000000000000000000000000..e36d5dc4936f7438336c9efca994f49d63dc3e89 GIT binary patch literal 251141 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 z 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 G=>GuO-*8a? literal 0 HcmV?d00001 diff --git a/QtTermTCP_resource.rc b/QtTermTCP_resource.rc new file mode 100644 index 0000000..e58cc35 --- /dev/null +++ b/QtTermTCP_resource.rc @@ -0,0 +1,37 @@ +#include + +IDI_ICON1 ICON DISCARDABLE "C:\\Users\\John\\OneDrive\\Dev\\Source\\QT\\QtTermTCP\\QtTermTCP.ico" + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 0,0,0,0 + PRODUCTVERSION 0,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_DLL + FILESUBTYPE 0x0L + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "\0" + VALUE "FileDescription", "\0" + VALUE "FileVersion", "0.0.0.0\0" + VALUE "LegalCopyright", "\0" + VALUE "OriginalFilename", "QtTermTCP.exe\0" + VALUE "ProductName", "QtTermTCP\0" + VALUE "ProductVersion", "0.0.0.0\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 1200 + END + END +/* End of Version info */ + diff --git a/TCPHostConfig.ui b/TCPHostConfig.ui new file mode 100644 index 0000000..e4b706b --- /dev/null +++ b/TCPHostConfig.ui @@ -0,0 +1,85 @@ + + + Dialog + + + + 0 + 0 + 400 + 300 + + + + TCP Host Config + + + + QtTermTCP.icoQtTermTCP.ico + + + + + 30 + 240 + 341 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Save + + + true + + + + + + 184 + 44 + 104 + 23 + + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/TabDialog.cpp b/TabDialog.cpp new file mode 100644 index 0000000..b627f30 --- /dev/null +++ b/TabDialog.cpp @@ -0,0 +1,1127 @@ + +#define _CRT_SECURE_NO_WARNINGS + +#include "TabDialog.h" +#include "QtTermTCP.h" +#include +#include "QSettings" +#include "QLineEdit" +#include "QTabWidget" +#include "QDialogButtonBox" +#include "QVBoxLayout" +#include "QLabel" +#include "QAction" +#include "QGroupBox" +#include "QPlainTextEdit" +#include "QCheckBox" +#include "QFormLayout" +#include "QScrollArea" + +#include "ax25.h" + +#ifndef WIN32 +#define strtok_s strtok_r +#endif + +extern "C" void SaveSettings(); + +extern int screenHeight; +extern int screenWidth; + +extern QList _sessions; +extern QTcpServer * _server; + +extern myTcpSocket * AGWSock; +extern myTcpSocket * KISSSock; +extern QLabel * Status1; +extern QFont * menufont; +extern QStatusBar * myStatusBar; + +QLineEdit *hostEdit; +QLineEdit *portEdit; +QLineEdit *userEdit; +QLineEdit *passEdit; + +extern QAction *actHost[17]; +extern QAction *actSetup[16]; + +extern int ConfigHost; + +#define MAXHOSTS 16 + +extern char Host[MAXHOSTS + 1][100]; +extern int Port[MAXHOSTS + 1]; +extern char UserName[MAXHOSTS + 1][80]; +extern char Password[MAXHOSTS + 1][80]; +extern char MYCALL[32]; + +QLineEdit *TermCall; +QGroupBox *groupBox; +QLineEdit *beaconDest; +QLineEdit *beaconPath; +QLineEdit *beaconInterval; +QLineEdit *beaconPorts; +QLabel *label_2; +QLabel *label_3; +QLabel *label_4; +QLabel *label_5; +QLabel *label_6; +QLabel *label_7; +QLabel *label_11; +QPlainTextEdit *beaconText; +QLabel *label_12; +QGroupBox *groupBox_2; +QLineEdit *AHost; +QLineEdit *APort; +QLineEdit *APath; +QLineEdit *Paclen; +QLabel *label_8; +QLabel *label_9; +QLabel *label_10; +QLabel *label; +QCheckBox *AAGWEnable; +QLabel *label_13; +QCheckBox *AAGWMonEnable; + +QLineEdit *AVARATermCall; +QLineEdit *AVARAHost; +QLineEdit *AVARAPort; +QCheckBox *AVARAEnable; + + +extern int AGWEnable; +extern int VARAEnable; +extern int KISSEnable; +extern int AGWMonEnable; +extern char AGWTermCall[12]; +extern char AGWBeaconDest[12]; +extern char AGWBeaconPath[80]; +extern int AGWBeaconInterval; +extern char AGWBeaconPorts[80]; +extern char AGWBeaconMsg[260]; + +extern char VARATermCall[12]; + +extern char AGWHost[128]; +extern int AGWPortNum; +extern int AGWPaclen; +extern char * AGWPortList; +extern myTcpSocket * AGWSock; +extern char * AGWPortList; +extern QStringList AGWToCalls; + +extern int KISSMode; + +extern Ui_ListenSession * ActiveSession; + +extern int TermMode; + +#define Single 0 +#define MDI 1 +#define Tabbed 2 + +extern int listenPort; +extern "C" int listenEnable; +extern char listenCText[4096]; + +void Send_AGW_C_Frame(Ui_ListenSession * Sess, int Port, char * CallFrom, char * CallTo, char * Data, int DataLen); +extern "C" void SetSessLabel(Ui_ListenSession * Sess, char * label); +extern "C" void SendtoTerm(Ui_ListenSession * Sess, char * Msg, int Len); + +QString GetConfPath(); + +QScrollArea *scrollArea; +QWidget *scrollAreaWidgetContents; + +bool myResize::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::Resize) + { + QResizeEvent *resizeEvent = static_cast(event); + QSize size = resizeEvent->size(); + int h = size.height(); + int w = size.width(); + + scrollArea->setGeometry(QRect(5, 5, w - 10, h - 10)); + return true; + } + return QObject::eventFilter(obj, event); +} + +AGWConnect::AGWConnect(QWidget *parent) : QDialog(parent) +{ + this->setFont(*menufont); + + setWindowTitle(tr("AGW Connection")); + + myResize *resize = new myResize(); + installEventFilter(resize); + + scrollArea = new QScrollArea(this); + scrollArea->setObjectName(QString::fromUtf8("scrollArea")); + scrollArea->setGeometry(QRect(5, 5, 562, 681)); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + scrollArea->setWidgetResizable(false); + scrollAreaWidgetContents = new QWidget(); + scrollAreaWidgetContents->setObjectName(QString::fromUtf8("scrollAreaWidgetContents")); + + QVBoxLayout *layout = new QVBoxLayout(); + layout->setContentsMargins(10, 10, 10, 10); + + setLayout(layout); + + QFormLayout *formLayout2 = new QFormLayout(); + layout->addLayout(formLayout2); + + wCallFrom = new QLineEdit(); + formLayout2->addRow(new QLabel("Call From"), wCallFrom); + + wCallTo = new QComboBox(); + wCallTo->setEditable(true); + wCallTo->setInsertPolicy(QComboBox::NoInsert); + + formLayout2->addRow(new QLabel("Call To"), wCallTo); + + Digis = new QLineEdit(); + formLayout2->addRow(new QLabel("Digis"), Digis); + + layout->addSpacing(2); + layout->addWidget(new QLabel("Radio Ports")); + + RadioPorts = new QListWidget(); + + layout->addWidget(RadioPorts); + + QString str; + int count; + + char * Context; + char * ptr; + + wCallFrom->setText(AGWTermCall); + + wCallTo->addItems(AGWToCalls); + + if (AGWPortList) + { + char * Temp = strdup(AGWPortList); // Need copy as strtok messes with it + + count = atoi(Temp); + + ptr = strtok_s(Temp, ";", &Context); + + for (int n = 0; n < count; n++) + { + ptr = strtok_s(NULL, ";", &Context); + new QListWidgetItem(ptr, RadioPorts); + } + + free(Temp); + + // calculate scrollarea height from count + + scrollAreaWidgetContents->setGeometry(QRect(0, 0, 400, 220 + 22 * count)); + this->resize(420, 240 + 22 * count); + + } + + RadioPorts->setFont(*menufont); + + buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + buttonBox->setFont(*menufont); + layout->addWidget(buttonBox); + + scrollAreaWidgetContents->setLayout(layout); + scrollArea->setWidget(scrollAreaWidgetContents); + + connect(buttonBox, SIGNAL(accepted()), this, SLOT(myaccept())); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(myreject())); +} + +AGWConnect::~AGWConnect() +{ +} + +extern QAction *discAction; + +void AGWConnect::myaccept() +{ + QVariant Q; + + int port = RadioPorts->currentRow(); + char CallFrom[32]; + char CallTo[32]; + char Via[128]; + char DigiMsg[128] = ""; + int DigiLen = 0; + char * digiptr = &DigiMsg[1]; + + strcpy(CallFrom, wCallFrom->text().toUpper().toUtf8()); + strcpy(CallTo, wCallTo->currentText().toUpper().toUtf8()); + strcpy(Via, Digis->text().toUpper().toUtf8()); + + // if digis have to form block with byte count followed by n 10 byte calls + + if (Via[0]) + { + char * context; + char * ptr = strtok_s(Via, ", ", &context); + + while (ptr) + { + DigiMsg[0]++; + strcpy(digiptr, ptr); + digiptr += 10; + + ptr = strtok_s(NULL, ", ", &context); + } + DigiLen = digiptr - DigiMsg; + } + + // Add CallTo if not already in list + + if (AGWToCalls.contains(CallTo)) + AGWToCalls.removeOne(CallTo); + + AGWToCalls.insert(-1, CallTo); + + if (port == -1) + { + // Port not set. If connecting to SWITCH use any, otherwise tell user + + if (strcmp(CallTo, "SWITCH") == 0) + { + port = 0; + } + else + { + QMessageBox msgBox; + msgBox.setText("Select a port to call on"); + msgBox.exec(); + return; + } + } + + Send_AGW_C_Frame(ActiveSession, port, CallFrom, CallTo, DigiMsg, DigiLen); + + discAction->setEnabled(true); + + + AGWConnect::accept(); +} + +void AGWConnect::myreject() +{ + AGWConnect::reject(); +} + + +AGWDialog::AGWDialog(QWidget *parent) : QDialog(parent) +{ + this->setFont(*menufont); + + setWindowTitle(tr("TermTCP AGW Configuration")); + + myResize *resize = new myResize(); + installEventFilter(resize); + + + scrollArea = new QScrollArea(this); + scrollArea->setObjectName(QString::fromUtf8("scrollArea")); + scrollArea->setGeometry(QRect(5, 5, 562, 681)); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + scrollArea->setWidgetResizable(false); + scrollAreaWidgetContents = new QWidget(); + scrollAreaWidgetContents->setObjectName(QString::fromUtf8("scrollAreaWidgetContents")); + scrollAreaWidgetContents->setGeometry(QRect(0, 0, 552, 581)); + + this->resize(572, 601); + + QVBoxLayout *layout = new QVBoxLayout; + QHBoxLayout *hlayout = new QHBoxLayout; + + layout->addLayout(hlayout); + AAGWEnable = new QCheckBox("Enable AGW Interface"); + AAGWEnable->setLayoutDirection(Qt::LeftToRight); + + AAGWMonEnable = new QCheckBox("Enable Monitor"); + AAGWMonEnable->setGeometry(QRect(255, 18, 216, 21)); + AAGWMonEnable->setLayoutDirection(Qt::RightToLeft); + + hlayout->addWidget(AAGWEnable); + hlayout->addWidget(AAGWMonEnable); + + QFormLayout *flayout = new QFormLayout; + layout->addLayout(flayout); + + label = new QLabel("Terminal Call"); + TermCall = new QLineEdit(this); + + flayout->addRow(label, TermCall); + + layout->addWidget(new QLabel("Beacon Setup")); + + QFormLayout *flayout1 = new QFormLayout; + layout->addLayout(flayout1); + + label_2 = new QLabel("Destination"); + beaconDest = new QLineEdit(); + label_3 = new QLabel("Digipeaters"); + beaconPath = new QLineEdit(); + + flayout1->addRow(label_2, beaconDest); + flayout1->addRow(label_3, beaconPath); + + label_4 = new QLabel("Interval"); + beaconInterval = new QLineEdit(); + label_5 = new QLabel("Ports"); + beaconPorts = new QLineEdit(); + + flayout1->addRow(label_4, beaconInterval); + flayout1->addRow(label_5, beaconPorts); + +// label_6 = new QLabel("Minutes", groupBox); + +// label_7 = new QLabel("(Separate with commas)", groupBox); + label_11 = new QLabel("Message"); + beaconText = new QPlainTextEdit(); + + flayout1->addRow(label_11, beaconText); + + + +// label_12 = new QLabel("(max 256 chars)"); +// label_12->setGeometry(QRect(14, 158, 95, 21)); + + layout->addWidget(new QLabel("TNC Setup")); + + QFormLayout *flayout2 = new QFormLayout; + layout->addLayout(flayout2); + + AHost = new QLineEdit(); + APort = new QLineEdit(); + Paclen = new QLineEdit(); + label_8 = new QLabel("host"); + label_9 = new QLabel("Port"); + label_10 = new QLabel("Paclen "); + + flayout2->addRow(label_8, AHost); + flayout2->addRow(label_9, APort); + flayout2->addRow(label_10, Paclen); + + AAGWEnable->setChecked(AGWEnable); + AAGWMonEnable->setChecked(AGWMonEnable); + TermCall->setText(AGWTermCall); + beaconDest->setText(AGWBeaconDest); + beaconPath->setText(AGWBeaconPath); + beaconPorts->setText(AGWBeaconPorts); + beaconText->setPlainText(AGWBeaconMsg); + beaconInterval->setText(QString::number(AGWBeaconInterval)); + AHost->setText(AGWHost); + APort->setText(QString::number(AGWPortNum)); + Paclen->setText(QString::number(AGWPaclen)); + + buttonBox = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Cancel, this); + buttonBox->setFont(*menufont); + layout->addWidget(buttonBox); + scrollAreaWidgetContents->setLayout(layout); + scrollArea->setWidget(scrollAreaWidgetContents); + + connect(buttonBox, SIGNAL(accepted()), this, SLOT(myaccept())); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(myreject())); + +} + + +AGWDialog::~AGWDialog() +{ +} + + +KISSConnect::KISSConnect(QWidget *parent) : QDialog(parent) +{ + this->setFont(*menufont); + + setWindowTitle(tr("KISS Connection")); + + myResize *resize = new myResize(); + installEventFilter(resize); + + scrollArea = new QScrollArea(this); + scrollArea->setObjectName(QString::fromUtf8("scrollArea")); + scrollArea->setGeometry(QRect(5, 5, 250, 200)); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + scrollArea->setWidgetResizable(false); + scrollAreaWidgetContents = new QWidget(); + scrollAreaWidgetContents->setObjectName(QString::fromUtf8("scrollAreaWidgetContents")); + + // layout is top level. + // Add a Horizontal layout for Mode and Verical Layout for Call and Digis + + QVBoxLayout *layout = new QVBoxLayout(); + layout->setContentsMargins(10, 10, 10, 10); + setLayout(layout); + + QHBoxLayout *mylayout = new QHBoxLayout(); + + Connected = new QRadioButton("Sesion"); + UIMode = new QRadioButton("UI"); + + mylayout->addWidget(new QLabel("Connection Mode")); + mylayout->addWidget(Connected); + mylayout->addWidget(UIMode); + + if (KISSMode) // UI = 1 + UIMode->setChecked(1); + else + Connected->setChecked(1); + + QFormLayout *formLayout2 = new QFormLayout(); + layout->addLayout(mylayout); + layout->addSpacing(10); + layout->addLayout(formLayout2); + + wCallTo = new QComboBox(); + wCallTo->setEditable(true); + wCallTo->setInsertPolicy(QComboBox::NoInsert); + + formLayout2->addRow(new QLabel("Call To"), wCallTo); + + Digis = new QLineEdit(); + formLayout2->addRow(new QLabel("Digis"), Digis); + + layout->addSpacing(2); + + buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + buttonBox->setFont(*menufont); + layout->addWidget(buttonBox); + + scrollAreaWidgetContents->setLayout(layout); + scrollArea->setWidget(scrollAreaWidgetContents); + + wCallTo->addItems(AGWToCalls); + + connect(buttonBox, SIGNAL(accepted()), this, SLOT(myaccept())); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(myreject())); +} + +extern "C" void * KISSConnectOut(void * Sess, char * CallFrom, char * CallTo, char * Digis, int Chan, void * Socket); + +KISSConnect::~KISSConnect() +{ +} + +extern QAction * YAPPSend; +extern QMenu * connectMenu; +extern QMenu * disconnectMenu; + +TAX25Port DummyPort; + +void KISSConnect::myaccept() +{ + QVariant Q; + + char CallTo[32]; + char Via[128]; + strcpy(CallTo, wCallTo->currentText().toUpper().toUtf8()); + strcpy(Via, Digis->text().toUpper().toUtf8()); + + KISSMode = UIMode->isChecked(); + + // Add CallTo if not already in list + + if (AGWToCalls.contains(CallTo)) + AGWToCalls.removeOne(CallTo); + + AGWToCalls.insert(-1, CallTo); + + ActiveSession->KISSMode = KISSMode; + + if (KISSMode == 0) + { + ActiveSession->KISSSession = KISSConnectOut(ActiveSession, MYCALL, CallTo, Via, 0, (void *)KISSSock); + WritetoOutputWindow(ActiveSession, (unsigned char *)"Connecting...\r", 14); + discAction->setEnabled(true); + } + else + { + // UI + + char Msg[128]; + int Len = 0; + + memset(&DummyPort, 0, sizeof(DummyPort)); + + ActiveSession->KISSSession = (void *)&DummyPort; // Dummy marker to show session in use + + strcpy(ActiveSession->UIDEST, CallTo); + strcpy(ActiveSession->UIPATH, Via); + + if (TermMode == Tabbed) + Len = sprintf(Msg, "UI %s", CallTo); + else + Len = sprintf(Msg, "UI Session with %s\r", CallTo); + + SetSessLabel(ActiveSession, Msg); + + Len = sprintf(Msg, "UI Session with %s\r", CallTo); + SendtoTerm(ActiveSession, Msg, Len); + connectMenu->setEnabled(false); + discAction->setEnabled(true); + YAPPSend->setEnabled(false); + } + + KISSConnect::accept(); +} + +void KISSConnect::myreject() +{ + KISSConnect::reject(); +} + + + +VARAConnect::VARAConnect(QWidget *parent) : QDialog(parent) +{ + this->setFont(*menufont); + + setWindowTitle(tr("VARA Connection")); + + myResize *resize = new myResize(); + installEventFilter(resize); + + scrollArea = new QScrollArea(this); + scrollArea->setObjectName(QString::fromUtf8("scrollArea")); + scrollArea->setGeometry(QRect(5, 5, 250, 150)); + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + scrollArea->setWidgetResizable(false); + scrollAreaWidgetContents = new QWidget(); + scrollAreaWidgetContents->setObjectName(QString::fromUtf8("scrollAreaWidgetContents")); + + QVBoxLayout *layout = new QVBoxLayout(); + layout->setContentsMargins(10, 10, 10, 10); + + setLayout(layout); + + QFormLayout *formLayout2 = new QFormLayout(); + layout->addLayout(formLayout2); + + wCallFrom = new QLineEdit(); + formLayout2->addRow(new QLabel("Call From"), wCallFrom); + + wCallTo = new QComboBox(); + wCallTo->setEditable(true); + wCallTo->setInsertPolicy(QComboBox::NoInsert); + + formLayout2->addRow(new QLabel("Call To"), wCallTo); + + Digis = new QLineEdit(); + formLayout2->addRow(new QLabel("Digis"), Digis); + + layout->addSpacing(2); + + buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + buttonBox->setFont(*menufont); + layout->addWidget(buttonBox); + + scrollAreaWidgetContents->setLayout(layout); + scrollArea->setWidget(scrollAreaWidgetContents); + + wCallFrom->setText(VARATermCall); + wCallTo->addItems(AGWToCalls); + + connect(buttonBox, SIGNAL(accepted()), this, SLOT(myaccept())); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(myreject())); +} + +VARAConnect::~VARAConnect() +{ +} + +extern myTcpSocket * VARASock; +extern myTcpSocket * VARADataSock; + +void VARAConnect::myaccept() +{ + QVariant Q; + + char CallFrom[32]; + char CallTo[32]; + char Via[128]; + char Msg[256]; + int Len; + + strcpy(CallFrom, wCallFrom->text().toUpper().toUtf8()); + strcpy(CallTo, wCallTo->currentText().toUpper().toUtf8()); + strcpy(Via, Digis->text().toUpper().toUtf8()); + +// if digis have to form block with byte count followed by n 10 byte calls + + +// Add CallTo if not already in list + + if (AGWToCalls.contains(CallTo)) + AGWToCalls.removeOne(CallTo); + + AGWToCalls.insert(-1, CallTo); + + + if (Via[0]) + Len = sprintf(Msg, "CONNECT %s %s VIA %s\r", CallFrom, CallTo, Via); + else + Len = sprintf(Msg, "CONNECT %s %s\r", CallFrom, CallTo); + + VARASock->write(Msg, Len); + + discAction->setEnabled(true); + VARAConnect::accept(); +} + +void VARAConnect::myreject() +{ + VARAConnect::reject(); +} + + +extern QProcess *process; + + + +TabDialog::TabDialog(QWidget *parent) : QDialog(parent) +{ + char portnum[10]; + + buttonBox = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Cancel); + + connect(buttonBox, SIGNAL(accepted()), this, SLOT(myaccept())); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(myreject())); + + QVBoxLayout *layout = new QVBoxLayout; + QLabel *hostLabel = new QLabel(tr("Host Name:")); + hostEdit = new QLineEdit(Host[ConfigHost]); + + QLabel *portLabel = new QLabel(tr("Port:")); + sprintf(portnum, "%d", Port[ConfigHost]); + portEdit = new QLineEdit(portnum); + + QLabel *userLabel = new QLabel(tr("User:")); + userEdit = new QLineEdit(UserName[ConfigHost]); + + QLabel *passLabel = new QLabel(tr("Password:")); + passEdit = new QLineEdit(Password[ConfigHost]); + + layout->addWidget(hostLabel); + layout->addWidget(hostEdit); + layout->addWidget(portLabel); + layout->addWidget(portEdit); + layout->addWidget(userLabel); + layout->addWidget(userEdit); + layout->addWidget(passLabel); + layout->addWidget(passEdit); + + layout->addStretch(1); + layout->addWidget(buttonBox); + setLayout(layout); + + setWindowTitle(tr("TermTCP Host Configuration")); +} + +void AGWDialog::myaccept() +{ + QVariant Q; + + int OldEnable = AGWEnable; + int OldPort = AGWPortNum; + char oldHost[128]; + strcpy(oldHost, AGWHost); + + // QString val = Sess->portNo->text();A + // QByteArray qb = val.toLatin1(); + // char * ptr = qb.data(); + + AGWEnable = AAGWEnable->isChecked(); + AGWMonEnable = AAGWMonEnable->isChecked(); + + strcpy(AGWTermCall, TermCall->text().toUtf8().toUpper()); + strcpy(AGWBeaconDest, beaconDest->text().toUtf8().toUpper()); + strcpy(AGWBeaconPath, beaconPath->text().toUtf8().toUpper()); + strcpy(AGWBeaconPorts, beaconPorts->text().toUtf8().toUpper()); + + if (beaconText->toPlainText().length() > 256) + { + QMessageBox msgBox; + msgBox.setText("Beacon Text Too Long"); + msgBox.exec(); + } + else + strcpy(AGWBeaconMsg, beaconText->toPlainText().toUtf8().toUpper()); + + Q = beaconInterval->text(); + AGWBeaconInterval = Q.toInt(); + + strcpy(AGWHost, AHost->text().toUtf8()); + + Q = APort->text(); + AGWPortNum = Q.toInt(); + + Q = Paclen->text(); + AGWPaclen = Q.toInt(); + + SaveSettings(); + + if (AGWEnable != OldEnable || AGWPortNum != OldPort || strcmp(oldHost, AGWHost) != 0) + { + // (re)start connection + + if (OldEnable && AGWSock && AGWSock->ConnectedState == QAbstractSocket::ConnectedState) + { + AGWSock->disconnectFromHost(); + Status1->setText("AGW Disconnected"); + } + // AGWTimer will reopen connection + } + + myStatusBar->setVisible(AGWEnable | VARAEnable | KISSEnable); + + AGWDialog::accept(); + +} + +void AGWDialog::myreject() +{ + AGWDialog::reject(); +} + +void TabDialog::myaccept() +{ + QString val = hostEdit->text(); + QByteArray qb = val.toLatin1(); + char * ptr = qb.data(); + strcpy(Host[ConfigHost], ptr); + + val = portEdit->text(); + qb = val.toLatin1(); + ptr = qb.data(); + Port[ConfigHost] = atoi(ptr); + + val = userEdit->text(); + qb = val.toLatin1(); + ptr = qb.data(); + strcpy(UserName[ConfigHost], ptr); + + val = passEdit->text(); + qb = val.toLatin1(); + ptr = qb.data(); + strcpy(Password[ConfigHost], ptr); + + actHost[ConfigHost]->setText(Host[ConfigHost]); + actSetup[ConfigHost]->setText(Host[ConfigHost]); + + SaveSettings(); + + + TabDialog::accept(); + +} + +void TabDialog::myreject() +{ + TabDialog::reject(); +} + +TabDialog::~TabDialog() +{ +} + +// Menu dialog + + +fontDialog::fontDialog(int Menu, QWidget *parent) : QDialog(parent) +{ + // Menu is set if setting Menufont, zero for setting terminal font. + + int i; + char valChar[16]; + + QString family; + int csize; + QFont::Weight weight; + +#ifdef ANDROID + this->resize((screenWidth * 7) / 8, 200); + this->setMaximumWidth((screenWidth * 7) / 8); +#endif + this->setFont(*menufont); + + Menuflag = Menu; + + if (Menu) + { + workingFont = *menufont; + + QFontInfo info(*menufont); + family = info.family(); + csize = info.pointSize(); + + setWindowTitle("Menu Font Dialog"); + } + else + { + // get current term font + + QSettings settings(GetConfPath(), QSettings::IniFormat); + +#ifdef ANDROID + family = settings.value("FontFamily", "Driod Sans Mono").toString(); + csize = settings.value("PointSize", 12).toInt(); + weight = (QFont::Weight)settings.value("Weight", 50).toInt(); +#else + family = settings.value("FontFamily", "Courier New").toString(); + csize = settings.value("PointSize", 10).toInt(); + weight = (QFont::Weight)settings.value("Weight", 50).toInt(); +#endif + + workingFont = QFont(family); + workingFont.setPointSize(csize); + workingFont.setWeight(weight); + + setWindowTitle("Terminal Font Dialog"); + } + + QVBoxLayout *layout = new QVBoxLayout(); + layout->setContentsMargins(10, 10, 10, 10); + + setLayout(layout); + + QHBoxLayout *hlayout = new QHBoxLayout(); + layout->addLayout(hlayout); + + font = new QFontComboBox(); + + if (Menu == 0) + font->setFontFilters(QFontComboBox::MonospacedFonts); + + font->setMaximumWidth((screenWidth * 5) / 8); + font->view()->setMaximumWidth((7 * screenWidth) / 8); + + style = new QComboBox(); + style->setMaximumWidth(screenWidth / 4); + size = new QComboBox(); + sample = new QTextEdit(); + sample->setText("ABCDabcd1234"); + sample->setFont(workingFont); + + hlayout->addWidget(font); + hlayout->addWidget(style); + hlayout->addWidget(size); + layout->addWidget(sample); + + QFontDatabase database; + + const QStringList styles = database.styles(family); + + const QList smoothSizes = database.smoothSizes(family, styles[0]); + + for (int points : smoothSizes) + size->addItem(QString::number(points)); + + for (QString xstyle : styles) + style->addItem(xstyle); + + i = font->findText(family, Qt::MatchExactly); + font->setCurrentIndex(i); + + sprintf(valChar, "%d", csize); + i = size->findText(valChar, Qt::MatchExactly); + size->setCurrentIndex(i); + + buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + buttonBox->setFont(*menufont); + layout->addWidget(buttonBox); + setLayout(layout); + + connect(buttonBox, SIGNAL(accepted()), this, SLOT(myaccept())); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(myreject())); + connect(font, SIGNAL(currentFontChanged(QFont)), this, SLOT(fontchanged(QFont))); + connect(style, SIGNAL(currentIndexChanged(int)), this, SLOT(stylechanged())); + connect(size, SIGNAL(currentIndexChanged(int)), this, SLOT(sizechanged())); +} + +void fontDialog::fontchanged(QFont newfont) +{ + QFontDatabase database; + QString family = newfont.family(); + + workingFont = newfont; + + const QStringList styles = database.styles(family); + const QList smoothSizes = database.smoothSizes(family, styles[0]); + + size->clear(); + style->clear(); + + for (int points : smoothSizes) + size->addItem(QString::number(points)); + + for (QString xstyle : styles) + style->addItem(xstyle); + + sample->setFont(workingFont); +} + +void fontDialog::stylechanged() +{ + QFontDatabase database; + + QString family = font->currentFont().family(); + + bool italic = database.italic(family, style->currentText()); + int weight = database.weight(family, style->currentText()); + + if (weight < 0) + weight = 50; // Normal + + workingFont.setItalic(italic); + workingFont.setWeight((QFont::Weight) weight); + + sample->setFont(workingFont); +} + +void fontDialog::sizechanged() +{ + int newsize = size->currentText().toInt(); + workingFont.setPointSize(newsize); + sample->setFont(workingFont); +} +fontDialog::~fontDialog() +{ +} + +void fontDialog::myaccept() +{ + QSettings settings(GetConfPath(), QSettings::IniFormat); + + if (Menuflag) + { + delete menufont; + menufont = new QFont(workingFont); + + QtTermTCP::setFonts(); + + settings.setValue("MFontFamily", workingFont.family()); + settings.setValue("MPointSize", workingFont.pointSize()); + settings.setValue("MWeight", workingFont.weight()); + } + else + { + Ui_ListenSession * Sess; + + for (int i = 0; i < _sessions.size(); ++i) + { + Sess = _sessions.at(i); + + if (Sess->termWindow) + Sess->termWindow->setFont(workingFont); + + if (Sess->inputWindow) + Sess->inputWindow->setFont(workingFont); + + if (Sess->monWindow) + Sess->monWindow->setFont(workingFont); + } + + settings.setValue("FontFamily", workingFont.family()); + settings.setValue("PointSize", workingFont.pointSize()); + settings.setValue("Weight", workingFont.weight()); + } + + fontDialog::accept(); +} + +void fontDialog::myreject() +{ + fontDialog::reject(); +} + + + +ListenDialog::ListenDialog(QWidget *parent) : QDialog(parent) +{ +#ifdef ANDROID + this->resize((screenWidth * 3) / 4 , 500); +#endif + verticalLayout = new QVBoxLayout(); + verticalLayout->setContentsMargins(10, 10, 10, 10); + + setLayout(verticalLayout); + + Enabled = new QCheckBox(); + Enabled->setText(QString::fromUtf8("Enable Listen")); + Enabled->setLayoutDirection(Qt::LeftToRight); + + verticalLayout->addWidget(Enabled); + + formLayout = new QFormLayout(); + + portNo = new QLineEdit(); +// portNo->setMaximumSize(QSize(100, 30)); + + formLayout->addRow(new QLabel("Port"), portNo); + + CText = new QTextEdit(); + CText->setMinimumSize(QSize(0, 150)); + CText->setMaximumSize(QSize(401, 150)); + + formLayout->addRow(new QLabel("CText"), CText); + buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); + buttonBox->setFont(*menufont); + verticalLayout->addWidget(buttonBox); + + connect(buttonBox, SIGNAL(accepted()), this, SLOT(myaccept())); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(myreject())); + + verticalLayout->addLayout(formLayout); + verticalLayout->addWidget(buttonBox); + + portNo->setText(QString::number(listenPort)); + Enabled->setChecked(listenEnable); + CText->setText(listenCText); + +} + +ListenDialog::~ListenDialog() +{ +} + +void ListenDialog::myaccept() +{ + QString val = portNo->text(); + QByteArray qb = val.toLatin1(); + char * ptr = qb.data(); + + listenPort = atoi(ptr); + listenEnable = Enabled->isChecked(); + strcpy(listenCText, CText->toPlainText().toUtf8()); + + while ((ptr = strchr(listenCText, '\n'))) + *ptr = '\r'; + + if (_server->isListening()) + _server->close(); + + SaveSettings(); + + if (listenEnable) + _server->listen(QHostAddress::Any, listenPort); + + ListenDialog::accept(); +} + +void ListenDialog::myreject() +{ + ListenDialog::reject(); +} diff --git a/TabDialog.h b/TabDialog.h new file mode 100644 index 0000000..a7ac986 --- /dev/null +++ b/TabDialog.h @@ -0,0 +1,208 @@ + +#ifndef TABDIALOG_H +#define TABDIALOG_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class QDialogButtonBox; +class QFileInfo; +class QTabWidget; + +namespace Ui { +class TabDialog; +} + +class ListenDialog: public QDialog +{ + Q_OBJECT + +public: + explicit ListenDialog(QWidget *parent = 0); + ~ListenDialog(); + +private slots: + void myaccept(); + void myreject(); + +public: + QVBoxLayout *verticalLayout; + QFormLayout *formLayout; + QLineEdit *portNo; + QTextEdit *CText; + QVBoxLayout *verticalLayout_2; + QDialogButtonBox *buttonBox; + QCheckBox *Enabled; + + +}; + + + +class TabDialog : public QDialog +{ + Q_OBJECT + +public: + explicit TabDialog(QWidget *parent = 0); + ~TabDialog(); + +private slots: + void myaccept(); + void myreject(); + +private: + // Ui::TabDialog *ui; + QDialogButtonBox *buttonBox; +}; + +class AGWDialog : public QDialog +{ + Q_OBJECT + +public: + explicit AGWDialog(QWidget *parent = 0); + ~AGWDialog(); + +public: + QPushButton *okButton; + QPushButton *cancelButton; + +private slots: + void myaccept(); + void myreject(); + +private: + // Ui::TabDialog *ui; + QDialogButtonBox *buttonBox; +}; + +class AGWConnect : public QDialog +{ + Q_OBJECT + +public: + explicit AGWConnect(QWidget *parent = 0); + ~AGWConnect(); + +public: + QLineEdit * wCallFrom; + QComboBox * wCallTo; + QLineEdit * Digis; + QListWidget * RadioPorts; + +private slots: + void myaccept(); + void myreject(); + +private: + // Ui::TabDialog *ui; + QDialogButtonBox *buttonBox; +}; + + + +class VARAConnect : public QDialog +{ + Q_OBJECT + +public: + explicit VARAConnect(QWidget *parent = 0); + ~VARAConnect(); + +public: + QLineEdit * wCallFrom; + QComboBox * wCallTo; + QLineEdit * Digis; + QListWidget * RadioPorts; + +private slots: + void myaccept(); + void myreject(); + +private: + // Ui::TabDialog *ui; + QDialogButtonBox *buttonBox; +}; + + +class KISSConnect : public QDialog +{ + Q_OBJECT + +public: + explicit KISSConnect(QWidget *parent = 0); + ~KISSConnect(); + +public: + QLineEdit * wCallFrom; + QComboBox * wCallTo; + QLineEdit * Digis; + QLineEdit * UIDest; + QListWidget * RadioPorts; + QHBoxLayout *mylayout; + QRadioButton * Connected; + QRadioButton * UIMode; + +private slots: + void myaccept(); + void myreject(); + +private: + // Ui::TabDialog *ui; + QDialogButtonBox *buttonBox; +}; + + + +class fontDialog : public QDialog +{ + Q_OBJECT + +public: + explicit fontDialog(int Menu, QWidget *parent = 0); + ~fontDialog(); + +public: + QFontComboBox *font; + QComboBox *style; + QComboBox *size; + QTextEdit * sample; + QFont workingFont; + int workingSize; + int Menuflag; // Set if menu font + +private slots: + void myaccept(); + void myreject(); + void fontchanged(QFont); + void stylechanged(); + void sizechanged(); + +private: + + QDialogButtonBox *buttonBox; +}; + +#endif + +class myResize : public QObject +{ + Q_OBJECT + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; +}; + + + + diff --git a/TermTCPCommon.cpp b/TermTCPCommon.cpp new file mode 100644 index 0000000..217e122 --- /dev/null +++ b/TermTCPCommon.cpp @@ -0,0 +1,1091 @@ +#include +#include +#include +#include +#include + +#include "QtTermTCP.h" + +#define _CRT_SECURE_NO_WARNINGS + +#define TRUE 1 +#define FALSE 0 +#define UCHAR unsigned char +#define MAX_PATH 256 +#define WCHAR char + +#ifndef WIN32 +#define strtok_s strtok_r +#endif + + +typedef unsigned long DWORD; +typedef int BOOL; +typedef unsigned char BYTE; +typedef unsigned short WORD; + +#define MAXHOSTS 16 + +#define TCHAR char + +void QueueMsg(Ui_ListenSession * Sess, char * Msg, int Len); +int ProcessYAPPMessage(Ui_ListenSession * Sess, UCHAR * Msg, int Len); +void SetPortMonLine(int i, char * Text, int visible, int enabled); +void AGW_AX25_data_in(void * Sess, UCHAR * data, int Len); +int checkUTF8(unsigned char * Msg, int Len, unsigned char * out); + +int Bells = TRUE; +int StripLF = FALSE; +int LogMonitor = FALSE; +int LogOutput = FALSE; +int SendDisconnected = TRUE; +int ChatMode = TRUE; +int MonPorts = 1; +int ListenOn = FALSE; + +time_t LastWrite = 0xffffffff; +int AlertInterval = 300; +int AlertBeep = TRUE; +int AlertFreq = 600; +int AlertDuration = 250; +TCHAR AlertFileName[256] = { 0 }; +int ConnectBeep = TRUE; +int UseKeywords = TRUE; + +char KeyWordsName[MAX_PATH] = "Keywords.sys"; +char ** KeyWords = NULL; +int NumberofKeyWords = 0; + + + +// YAPP stuff + +#define SOH 1 +#define STX 2 +#define ETX 3 +#define EOT 4 +#define ENQ 5 +#define ACK 6 +#define DLE 0x10 +#define NAK 0x15 +#define CAN 0x18 + +#define YAPPTX 32768 // Sending YAPP file + +int MaxRXSize = 100000; +char BaseDir[256] = ""; + +unsigned char InputBuffer[1024]; + +char YAPPPath[MAX_PATH] = ""; // Path for saving YAPP Files + +int paclen = 128; + +int InputLen; // Data we have already = Offset of end of an incomplete packet; + +unsigned char * MailBuffer; // Yapp Message being received +int MailBufferSize; +int YAPPLen; // Bytes sent/received of YAPP Message +long YAPPDate; // Date for received file - if set enables YAPPC +char ARQFilename[200]; // Filename from YAPP Header + +unsigned char SavedData[8192]; // Max receive is 4096 is should never get more that 8k +int SaveLen = 0; + +void YAPPSendData(Ui_ListenSession * Sess); + + +char * strlop(char * buf, char delim) +{ + // Terminate buf at delim, and return rest of string + + char * ptr = strchr(buf, delim); + + if (ptr == NULL) return NULL; + + *(ptr)++ = 0; + + return ptr; +} + +int CheckKeyWord(char * Word, char * Msg) +{ + char * ptr1 = Msg, *ptr2; + int len = (int)strlen(Word); + + while (*ptr1) // Stop at end + { + ptr2 = strstr(ptr1, Word); + + if (ptr2 == NULL) + return FALSE; // OK + + // Only bad if it ia not part of a longer word + + if ((ptr2 == Msg) || !(isalpha(*(ptr2 - 1)))) // No alpha before + if (!(isalpha(*(ptr2 + len)))) // No alpha after + return TRUE; // Bad word + + // Keep searching + + ptr1 = ptr2 + len; + } + + return FALSE; // OK +} + +int CheckKeyWords(UCHAR * Msg, int len) +{ + char dupMsg[2048]; + int i; + + if (UseKeywords == 0 || NumberofKeyWords == 0) + return FALSE; + + memcpy(dupMsg, Msg, len); + dupMsg[len] = 0; + //_strlwr(dupMsg); + + for (i = 1; i <= NumberofKeyWords; i++) + { + if (CheckKeyWord(KeyWords[i], dupMsg)) + { +// Beep(660, 250); + return TRUE; // Alert + } + } + + return FALSE; // OK + +} + + +void ProcessReceivedData(Ui_ListenSession * Sess, unsigned char * Buffer, int len) +{ + int MonLen = 0; + unsigned char * ptr; + unsigned char * Buffptr; + unsigned char * FEptr = 0; + + if (Sess->InputMode == 'Y') // Yapp + { + ProcessYAPPMessage(Sess, Buffer, len); + return; + } + + // mbstowcs(Buffer, BufferB, len); + + // Look for MON delimiters (FF/FE) + + + Buffptr = Buffer; + + if (Sess->MonData) + { + // Already in MON State + + FEptr = (UCHAR *)memchr(Buffptr, 0xfe, len); + + if (!FEptr) + { + // no FE - so send all to monitor + + WritetoMonWindow(Sess, Buffer, len); + return; + } + + Sess->MonData = FALSE; + + MonLen = FEptr - Buffptr; // Mon Data, Excluding the FE + + WritetoMonWindow(Sess, Buffptr, MonLen); + + Buffptr = ++FEptr; // Char following FE + + if (++MonLen < len) + { + len -= MonLen; + goto MonLoop; // See if next in MON or Data + } + + // Nothing Left + + return; + } + +MonLoop: + + ptr = (UCHAR *)memchr(Buffptr, 0xff, len); + + if (ptr) + { + unsigned char telcmd[] = "\xff\xfb\x03\xff\xfb\x01"; + + // Try to trap connect to normal Telnet Port + + if (memcmp(ptr, telcmd, 6) == 0) + return; + + // Buffer contains Mon Data + + if (ptr > Buffptr) + { + // Some Normal Data before the FF + + int NormLen = ptr - Buffptr; // Before the FF + + if (NormLen == 1 && Buffptr[0] == 0) + { + // Keepalive + } + + else + { + CheckKeyWords(Buffptr, NormLen); + WritetoOutputWindow(Sess, Buffptr, NormLen); + } + + len -= NormLen; + Buffptr = ptr; + goto MonLoop; + } + + if (ptr[1] == 0xff) + { + // Port Definition String + + int NumberofPorts = atoi((char *)&ptr[2]); + char *p, *Context; + int i = 1; + TCHAR msg[80]; + int portnum; + char delim[] = "|"; + int m; + + // Save for changes of Window + + if (len < 1024) + memcpy(Sess->PortMonString, ptr, len); + + + // Remove old menu + + for (i = 0; i < 33; i++) + { + SetPortMonLine(i, (char *)"", 0, 0); + } + + p = strtok_s((char *)&ptr[2], delim, &Context); + + while (NumberofPorts--) + { + p = strtok_s(NULL, delim, &Context); + if (p == NULL) + break; + + m = portnum = atoi(p); + sprintf(msg, "Port %s", p); + + if (m == 0) + m = 33; + + if (Sess->portmask & (1ll << (m - 1))) + SetPortMonLine(portnum, msg, 1, 1); + else + SetPortMonLine(portnum, msg, 1, 0); + } + return; + } + + + Sess->MonData = 1; + + FEptr = (UCHAR *)memchr(Buffptr, 0xfe, len); + + if (FEptr) + { + Sess->MonData = 0; + + MonLen = FEptr + 1 - Buffptr; // MonLen includes FF and FE + + WritetoMonWindow(Sess, Buffptr + 1, MonLen - 2); + + len -= MonLen; + Buffptr += MonLen; // Char Following FE + + if (len <= 0) + { + return; + } + goto MonLoop; + } + else + { + // No FE, so rest of buffer is MON Data + + if (MonLen) + WritetoMonWindow(Sess, Buffptr + 1, MonLen - 1); + return; + } + } + + // No FF, so must be session data + + if (Sess->InputMode == 'Y') // Yapp + { + ProcessYAPPMessage(Sess, Buffer, len); + return; + } + + + if (len == 1 && Buffptr[0] == 0) + return; // Keepalive + + // Could be a YAPP Header + + if (len == 2 && Buffptr[0] == ENQ && Buffptr[1] == 1) // YAPP Send_Init + { + char YAPPRR[2]; + + // Turn off monitoring + + setTraceOff(Sess); + + Sess->InputMode = 'Y'; + + YAPPRR[0] = ACK; + YAPPRR[1] = 1; + + SocketFlush(Sess); // To give Monitor Msg time to be sent + mySleep(1000); + QueueMsg(Sess, YAPPRR, 2); + + return; + } + // Check UTF8 + { + CheckKeyWords(Buffptr, len); + WritetoOutputWindow(Sess, Buffptr, len); + } + Sess->SlowTimer = 0; + return; +} + +extern myTcpSocket * VARASock; +extern myTcpSocket * VARADataSock; +extern "C" void SendtoAX25(void * conn, unsigned char * Msg, int Len); + +int SendMsg(Ui_ListenSession * Sess, TCHAR * Buffer, int len) +{ + if (Sess->KISSSession) + { + // Send to ax.25 code + + SendtoAX25(Sess->KISSSession, (unsigned char *)Buffer, len); + return len; + } + else if (Sess->AGWSession) + { + // Terminal is in AGWPE mode - send as AGW frame + + AGW_AX25_data_in(Sess->AGWSession, (unsigned char *)Buffer, len); + return len; + } + else if (VARASock && VARASock->Sess == Sess) + { + VARADataSock->write(Buffer, len); + return len; + } + + return SocketSend(Sess, Buffer, len); +} + + + +void QueueMsg(Ui_ListenSession * Sess, char * Msg, int len) +{ + int Sent = SendMsg(Sess, Msg, len); + + if (Sent != len) + Sent = 0; +} + +int InnerProcessYAPPMessage(Ui_ListenSession * Sess, UCHAR * Msg, int Len); + +int ProcessYAPPMessage(Ui_ListenSession * Sess, UCHAR * Msg, int Len) +{ + // may have saved data + + memcpy(&SavedData[SaveLen], Msg, Len); + + SaveLen += Len; + + while (SaveLen && Sess->InputMode == 'Y') + { + int Used = InnerProcessYAPPMessage(Sess, SavedData, SaveLen); + + if (Used == 0) + return 0; // Waiting for more + + SaveLen -= Used; + + if (SaveLen) + memmove(SavedData, &SavedData[Used], SaveLen); + } + return 0; +} + +extern int VARAEnable; + +int InnerProcessYAPPMessage(Ui_ListenSession * Sess, UCHAR * Msg, int Len) +{ + int pktLen = Msg[1]; + char Reply[2] = { ACK }; + size_t NameLen, SizeLen, OptLen; + char * ptr; + int FileSize; + WCHAR MsgFile[MAX_PATH]; + FILE * hFile; + char Mess[255]; + int len; + UCHAR Buffer[2000]; + struct stat STAT; + + switch (Msg[0]) + { + case ENQ: // YAPP Send_Init + + // Shouldn't occur in session. Reset state and process + + if (MailBuffer) + { + free(MailBuffer); + MailBufferSize = 0; + MailBuffer = 0; + } + + Mess[0] = ACK; + Mess[1] = 1; + + Sess->InputMode = 'Y'; + QueueMsg(Sess, Mess, 2); + + // Turn off monitoring + + mySleep(1000); // To give YAPP Msg time to be sent + + setTraceOff(Sess); + + return Len; + + case SOH: + + // HD Send_Hdr SOH len (Filename) NUL (File Size in ASCII) NUL (Opt) + + // YAPPC has date/time in dos format + + if (Len < Msg[1] + 1) + return 0; // Wait till we have it all + + NameLen = strlen((char *)&Msg[2]); + strcpy(ARQFilename, (char *)&Msg[2]); + + ptr = (char *)&Msg[3 + NameLen]; + SizeLen = strlen(ptr); + FileSize = atoi(ptr); + + OptLen = pktLen - (NameLen + SizeLen + 2); + + YAPPDate = 0; + + if (OptLen >= 8) // We have a Date/Time for YAPPC + { + ptr = ptr + SizeLen + 1; + YAPPDate = strtol(ptr, NULL, 16); + } + + // Check Size + + if (FileSize > MaxRXSize && VARAEnable == 0) + { + Mess[0] = NAK; + Mess[1] = sprintf(&Mess[2], "File %s size %d larger than limit %d\r", ARQFilename, FileSize, MaxRXSize); + mySleep(1000); // To give YAPP Msg tome to be sent + QueueMsg(Sess, Mess, Mess[1] + 2); + + len = sprintf((char *)Buffer, "YAPP File %s size %d larger than limit %d\r", ARQFilename, FileSize, MaxRXSize); + WritetoOutputWindow(Sess, Buffer, len); + + Sess->InputMode = 0; + SendTraceOptions(Sess); + + return Len; + } + + // Check that Path is set + + if (YAPPPath[0] == 0) + { + Mess[0] = NAK; + Mess[1] = sprintf(&Mess[2], "%s", "YAPP Receive directory not set"); + mySleep(1000); // To give YAPP Msg time to be sent + QueueMsg(Sess, Mess, Mess[1] + 2); + len = sprintf((char *)Buffer, "YAPP File Receive Failed - YAPP Receive directory not set\r"); + WritetoOutputWindow(Sess, Buffer, len); + + Sess->InputMode = 0; + SendTraceOptions(Sess); + + return Len; + } + + // Make sure file does not exist + + sprintf(MsgFile, "%s/%s", YAPPPath, ARQFilename); + + if (stat(MsgFile, &STAT) == 0) + { + FileSize = STAT.st_size; + + Mess[0] = NAK; + Mess[1] = sprintf(&Mess[2], "%s", "File Already Exists"); + mySleep(1000); // To give YAPP Msg time to be sent + QueueMsg(Sess, Mess, Mess[1] + 2); + len = sprintf((char *)Buffer, "YAPP File Receive Failed - %s already exists\r", MsgFile); + WritetoOutputWindow(Sess, Buffer, len); + + Sess->InputMode = 0; + SendTraceOptions(Sess); + + return Len; + } + + + MailBufferSize = FileSize; + MailBuffer = (UCHAR *)malloc(FileSize); + YAPPLen = 0; + + if (YAPPDate) // If present use YAPPC + Reply[1] = ACK; //Receive_TPK + else + Reply[1] = 2; //Rcv_File + + QueueMsg(Sess, Reply, 2); + + len = sprintf((char *)Buffer, "YAPP Receving File %s size %d\r", ARQFilename, FileSize); + WritetoOutputWindow(Sess, Buffer, len); + + return Len; + + case STX: + + // Data Packet + + // Check we have it all + + if (YAPPDate) // If present use YAPPC so have checksum + { + if (pktLen > (Len - 3)) // -2 for header and checksum + return 0; // Wait for rest + } + else + { + if (pktLen > (Len - 2)) // -2 for header + return 0; // Wait for rest + } + + // Save data and remove from buffer + + // if YAPPC check checksum + + if (YAPPDate) + { + UCHAR Sum = 0; + int i; + UCHAR * uptr = &Msg[2]; + + i = pktLen; + + while (i--) + Sum += *(uptr++); + + if (Sum != *uptr) + { + // Checksum Error + + Mess[0] = CAN; + Mess[1] = sprintf(&Mess[2], "YAPPC Checksum Error"); + QueueMsg(Sess, Mess, Mess[1] + 2); + + len = sprintf((char *)Buffer, "YAPPC Checksum Error on file %s\r", MsgFile); + WritetoOutputWindow(Sess, Buffer, len); + + Sess->InputMode = 0; + SendTraceOptions(Sess); + return Len; + } + } + + if ((YAPPLen) + pktLen > MailBufferSize) + { + // Too Big ?? + + Mess[0] = CAN; + Mess[1] = sprintf(&Mess[2], "YAPP Too much data received"); + QueueMsg(Sess, Mess, Mess[1] + 2); + + len = sprintf((char *)Buffer, "YAPP Too much data received on file %s\r", MsgFile); + WritetoOutputWindow(Sess, Buffer, len); + + Sess->InputMode = 0; + SendTraceOptions(Sess); + return Len; + } + + + memcpy(&MailBuffer[YAPPLen], &Msg[2], pktLen); + YAPPLen += pktLen; + + if (YAPPDate) + ++pktLen; // Add Checksum + +// if (YAPPLen == MailBufferSize) +// pktLen = pktLen; + + return pktLen + 2; + + case ETX: + + // End Data + + if (YAPPLen == MailBufferSize) + { + // All received + + int Written = 0; + + sprintf(MsgFile, "%s/%s", YAPPPath, ARQFilename); + + hFile = fopen(MsgFile, "wb"); + + if (hFile) + { + Written = (int)fwrite(MailBuffer, 1, YAPPLen, hFile); + fclose(hFile); + + if (YAPPDate) + { +// struct tm TM; +// struct timeval times[2]; + /* + The MS-DOS date. The date is a packed value with the following format. + + cant use DosDateTimeToFileTime on Linux + + Bits Description + 0-4 Day of the month (1–31) + 5-8 Month (1 = January, 2 = February, and so on) + 9-15 Year offset from 1980 (add 1980 to get actual year) + wFatTime + The MS-DOS time. The time is a packed value with the following format. + Bits Description + 0-4 Second divided by 2 + 5-10 Minute (0–59) + 11-15 Hour (0–23 on a 24-hour clock) + */ + /* + + memset(&TM, 0, sizeof(TM)); + + TM.tm_sec = (YAPPDate & 0x1f) << 1; + TM.tm_min = ((YAPPDate >> 5) & 0x3f); + TM.tm_hour = ((YAPPDate >> 11) & 0x1f); + + TM.tm_mday = ((YAPPDate >> 16) & 0x1f); + TM.tm_mon = ((YAPPDate >> 21) & 0xf) - 1; + TM.tm_year = ((YAPPDate >> 25) & 0x7f) + 80; + + Debugprintf("%d %d %d %d %d %d", TM.tm_year, TM.tm_mon, TM.tm_mday, TM.tm_hour, TM.tm_min, TM.tm_sec); + + times[0].tv_sec = times[1].tv_sec = mktime(&TM); + times[0].tv_usec = times[1].tv_usec = 0; + */ + } + } + + + free(MailBuffer); + MailBufferSize = 0; + MailBuffer = 0; + + if (Written != YAPPLen) + { + Mess[0] = CAN; + Mess[1] = sprintf(&Mess[2], "Failed to save YAPP File"); + QueueMsg(Sess, Mess, Mess[1] + 2); + + len = sprintf((char *)Buffer, "Failed to save YAPP File %s\r", MsgFile); + WritetoOutputWindow(Sess, Buffer, len); + + Sess->InputMode = 0; + SendTraceOptions(Sess); + } + } + + Reply[1] = 3; //Ack_EOF + QueueMsg(Sess, Reply, 2); + + len = sprintf((char *)Buffer, "Reception of file %s complete\r", MsgFile); + WritetoOutputWindow(Sess, Buffer, len); + + return Len; + + case EOT: + + // End Session + + Reply[1] = 4; // Ack_EOT + QueueMsg(Sess, Reply, 2); + SocketFlush(Sess); + Sess->InputMode = 0; + + SendTraceOptions(Sess); + return Len; + + case CAN: + + // Abort + + Mess[0] = ACK; + Mess[1] = 5; // CAN Ack + QueueMsg(Sess, Mess, 2); + + if (MailBuffer) + { + free(MailBuffer); + MailBufferSize = 0; + MailBuffer = 0; + } + + // May have an error message + + len = Msg[1]; + + if (len) + { + len = sprintf((char *)Buffer, "YAPP Transfer cancelled - %s\r", &Msg[2]); + } + else + len = sprintf(Mess, "YAPP Transfer cancelled\r"); + + Sess->InputMode = 0; + SendTraceOptions(Sess); + + return Len; + + case ACK: + + switch (Msg[1]) + { + char * ptr; + + case 1: // Rcv_Rdy + + // HD Send_Hdr SOH len (Filename) NUL (File Size in ASCII) NUL (Opt) + + // Remote only needs filename so remove path + + ptr = ARQFilename; + + while (strchr(ptr, '/')) + ptr = strchr(ptr, '/') + 1; + + len = (int)strlen(ptr) + 3; + + strcpy(&Mess[2], ptr); + len += sprintf(&Mess[len], "%d", MailBufferSize); + len++; // include null +// len += sprintf(&Mess[len], "%8X", YAPPDate); +// len++; // include null + Mess[0] = SOH; + Mess[1] = len - 2; + + QueueMsg(Sess, Mess, len); + + return Len; + + case 2: + + YAPPDate = 0; // Switch to Normal (No Checksum) Mode + + case 6: // Send using YAPPC + + // Start sending message + + YAPPSendData(Sess); + return Len; + + case 3: + + // ACK EOF - Send EOT + + Mess[0] = EOT; + Mess[1] = 1; + QueueMsg(Sess, Mess, 2); + + return Len; + + case 4: + + // ACK EOT + + Sess->InputMode = 0; + SendTraceOptions(Sess); + + len = sprintf((char *)Buffer, "File transfer complete\r"); + WritetoOutputWindow(Sess, Buffer, len); + + + return Len; + + default: + return Len; + + } + + case NAK: + + // Either Reject or Restart + + // RE Resume NAK len R NULL (File size in ASCII) NULL + + if (Len > 2 && Msg[2] == 'R' && Msg[3] == 0) + { + int posn = atoi((char *)&Msg[4]); + + YAPPLen += posn; + MailBufferSize -= posn; + + YAPPSendData(Sess); + return Len; + + } + + // May have an error message + + len = Msg[1]; + + if (len) + { + char ws[256]; + + Msg[len + 2] = 0; + + strcpy(ws, (char *)&Msg[2]); + + len = sprintf((char *)Buffer, "File rejected - %s\r", ws); + } + else + len = sprintf((char *)Buffer, "File rejected\r"); + + WritetoOutputWindow(Sess, Buffer, len); + + + Sess->InputMode = 0; + SendTraceOptions(Sess); + + return Len; + + } + + len = sprintf((char *)Buffer, "Unexpected message during YAPP Transfer. Transfer cancelled\r"); + WritetoOutputWindow(Sess, Buffer, len); + + Sess->InputMode = 0; + SendTraceOptions(Sess); + + return Len; + +} + +void YAPPSendFile(Ui_ListenSession * Sess, WCHAR * FN) +{ + int FileSize = 0; + char MsgFile[MAX_PATH]; + FILE * hFile; + struct stat STAT; + UCHAR Buffer[2000]; + int Len; + + strcpy(MsgFile, FN); + + if (MsgFile[0] == 0) + { + Len = sprintf((char *)Buffer, "Filename missing\r"); + WritetoOutputWindow(Sess, Buffer, Len); + + SendTraceOptions(Sess); + + return; + } + + if (stat(MsgFile, &STAT) != -1) + { + FileSize = STAT.st_size; + + hFile = fopen(MsgFile, "rb"); + + if (hFile) + { + char Mess[255]; +// time_t UnixTime = STAT.st_mtime; + +// FILETIME ft; +// long long ll; +// SYSTEMTIME st; +// WORD FatDate; +// WORD FatTime; +// struct tm TM; + + strcpy(ARQFilename, MsgFile); + + if (MailBuffer) + { + free(MailBuffer); + MailBufferSize = 0; + MailBuffer = 0; + } + + MailBuffer = (UCHAR *)malloc(FileSize); + MailBufferSize = FileSize; + YAPPLen = 0; + fread(MailBuffer, 1, FileSize, hFile); + + // Get Date and Time for YAPPC Mode + +/* The MS-DOS date. The date is a packed value with the following format. + + cant use DosDateTimeToFileTime on Linux + + Bits Description + 0-4 Day of the month (1–31) + 5-8 Month (1 = January, 2 = February, and so on) + 9-15 Year offset from 1980 (add 1980 to get actual year) + wFatTime + The MS-DOS time. The time is a packed value with the following format. + Bits Description + 0-4 Second divided by 2 + 5-10 Minute (0–59) + 11-15 Hour (0–23 on a 24-hour clock) + + memset(&TM, 0, sizeof(TM)); + + TM.tm_sec = (YAPPDate & 0x1f) << 1; + TM.tm_min = ((YAPPDate >> 5) & 0x3f); + TM.tm_hour = ((YAPPDate >> 11) & 0x1f); + + TM.tm_mday = ((YAPPDate >> 16) & 0x1f); + TM.tm_mon = ((YAPPDate >> 21) & 0xf) - 1; + TM.tm_year = ((YAPPDate >> 25) & 0x7f) + 80; + + +// Note that LONGLONG is a 64-bit value + + ll = Int32x32To64(UnixTime, 10000000) + 116444736000000000; + ft.dwLowDateTime = (DWORD)ll; + ll >>= 32; + ft.dwHighDateTime = (DWORD)ll; + + FileTimeToSystemTime(&ft, &st); + FileTimeToDosDateTime(&ft, &FatDate, &FatTime); + + YAPPDate = (FatDate << 16) + FatTime; + + memset(&TM, 0, sizeof(TM)); + + TM.tm_sec = (YAPPDate & 0x1f) << 1; + TM.tm_min = ((YAPPDate >> 5) & 0x3f); + TM.tm_hour = ((YAPPDate >> 11) & 0x1f); + + TM.tm_mday = ((YAPPDate >> 16) & 0x1f); + TM.tm_mon = ((YAPPDate >> 21) & 0xf) - 1; + TM.tm_year = ((YAPPDate >> 25) & 0x7f) + 80; +*/ + fclose(hFile); + + Mess[0] = ENQ; + Mess[1] = 1; + + QueueMsg(Sess, Mess, 2); + Sess->InputMode = 'Y'; + + Len = sprintf((char *)Buffer, "Sending File %s ...\r", FN); + WritetoOutputWindow(Sess, Buffer, Len); + + return; + } + } + + Len = sprintf((char *)Buffer, "File %s not found\r", FN); + WritetoOutputWindow(Sess, Buffer, Len); + +} + +void YAPPSendData(Ui_ListenSession * Sess) +{ + char Mess[258]; + + while (1) + { + int Left = MailBufferSize; + + if (Left == 0) + { + // Finished - send End Data + + Mess[0] = ETX; + Mess[1] = 1; + + QueueMsg(Sess, Mess, 2); + + break; + } + + if (Left > paclen - 3) // two bytes header and possible checksum + Left = paclen - 3; + + memcpy(&Mess[2], &MailBuffer[YAPPLen], Left); + + YAPPLen += Left; + MailBufferSize -= Left; + + // if YAPPC add checksum + + if (YAPPDate) + { + UCHAR Sum = 0; + int i; + UCHAR * uptr = (UCHAR *)&Mess[2]; + + i = Left; + + while (i--) + Sum += *(uptr++); + + *(uptr) = Sum; + + Mess[0] = STX; + Mess[1] = Left; + + QueueMsg(Sess, Mess, Left + 3); + } + else + { + Mess[0] = STX; + Mess[1] = Left; + + QueueMsg(Sess, Mess, Left + 2); + } + } +} + diff --git a/UZ7HOUtils.c b/UZ7HOUtils.c new file mode 100644 index 0000000..839bb64 --- /dev/null +++ b/UZ7HOUtils.c @@ -0,0 +1,320 @@ +/* +Copyright (C) 2019-2020 Andrei Kopanchuk UZ7HO + +This file is part of QtSoundModem + +QtSoundModem is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +QtSoundModem is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with QtSoundModem. If not, see http://www.gnu.org/licenses + +*/ + +// UZ7HO Soundmodem Port by John Wiseman G8BPQ +#include "ax25.h" + +// TStringlist And String emulation Functions + +// Dephi seems to mix starting counts at 0 or 1. I'll try making everything +// base zero. + +// Initialise a list + +void CreateStringList(TStringList * List) +{ + List->Count = 0; + List->Items = 0; +} + + +int Count(TStringList * List) +{ + return List->Count; +} + +string * newString() +{ + // Creates and Initialises a string + + UCHAR * ptr = malloc(sizeof(string)); // Malloc Data separately so it can be ralloc'ed + string * New = (string *)ptr; + New->Length = 0; + New->AllocatedLength = 256; + New->Data = malloc(256); + + return New; +} + +void initString(string * S) +{ + S->Length = 0; + S->AllocatedLength = 256; + S->Data = malloc(256); +} + +void initTStringList(TStringList* T) +{ + //string * New = newString(); + + T->Count = 0; + T->Items = NULL; + + //Add(T, New); +} + + + +TStringList * newTStringList() +{ + TStringList * T = (TStringList *) malloc(sizeof(TStringList)); + string * New = newString(); + + T->Count = 0; + T->Items = NULL; + + Add(T, New); + + return T; +} + + +void freeString(string * Msg) +{ + if (Msg->Data) + free(Msg->Data); + + free(Msg); +} + +string * Strings(TStringList * Q, int Index) +{ + if (Index >= Q->Count) + return NULL; + + return Q->Items[Index]; +} + +int Add(TStringList * Q, string * Entry) +{ + Q->Items = realloc(Q->Items,(Q->Count + 1) * sizeof(void *)); + Q->Items[Q->Count++] = Entry; + + return (Q->Count); +} + + +void mydelete(string * Source, int StartChar, int Count) +{ + //Description + //The Delete procedure deletes up to Count characters from the passed parameter Source string starting + //from position StartChar. + + if (StartChar > Source->Length) + return; + + int left = Source->Length - StartChar; + + if (Count > left) + Count = left; + + memmove(&Source->Data[StartChar], &Source->Data[StartChar + Count], left - Count); + + Source->Length -= Count; +} + + +void Delete(TStringList * Q, int Index) +{ + // Remove item at Index and move rest up list + // Index starts at zero + + if (Index >= Q->Count) + return; + + // We should free it, so user must duplicate msg if needed after delete + + freeString(Q->Items[Index]); + + Q->Count--; + + while (Index < Q->Count) + { + Q->Items[Index] = Q->Items[Index + 1]; + Index++; + } +} + +void setlength(string * Msg, int Count) +{ + // Set length, allocating more space if needed + + if (Count > Msg->AllocatedLength) + { + Msg->AllocatedLength = Count + 256; + Msg->Data = realloc(Msg->Data, Msg->AllocatedLength); + } + + Msg->Length = Count; +} + +string * stringAdd(string * Msg, UCHAR * Chars, int Count) +{ + // Add Chars to string + + if (Msg->Length + Count > Msg->AllocatedLength) + { + Msg->AllocatedLength += Count + 256; + Msg->Data = realloc(Msg->Data, Msg->AllocatedLength); + } + + memcpy(&Msg->Data[Msg->Length], Chars, Count); + Msg->Length += Count; + + return Msg; +} + +void Clear(TStringList * Q) +{ + int i = 0; + + if (Q->Items == NULL) + return; + + while (Q->Count) + { + freeString(Q->Items[i++]); + Q->Count--; + } + + free(Q->Items); + + Q->Items = NULL; +} + +// procedure move ( const SourcePointer; var DestinationPointer; CopyCount : Integer ) ; +// Description +// The move procedure is a badly named method of copying a section of memory from one place to another. + +// CopyCount bytes are copied from storage referenced by SourcePointer and written to DestinationPointer + +void move(UCHAR * SourcePointer, UCHAR * DestinationPointer, int CopyCount) +{ + memmove(DestinationPointer, SourcePointer, CopyCount); +} + +void fmove(float * SourcePointer, float * DestinationPointer, int CopyCount) +{ + memmove(DestinationPointer, SourcePointer, CopyCount); +} + + + +//Description +//The copy function has 2 forms. In the first, it creates a new string from part of an existing string. In the second, it creates a new array from part of an existing array. + +//1.String copy + +//The first character of a string has index = 1. + +//Up to Count characters are copied from the StartChar of the Source string to the returned string. +//Less than Count characters if the end of the Source string is encountered before Count characters have been copied. + + +string * copy(string * Source, int StartChar, int Count) +{ + string * NewString = newString(); + int end = StartChar + Count; + + if (end > Source->Length) + Count = Source->Length - StartChar; + + memcpy(NewString->Data, &Source->Data[StartChar], Count); + + NewString->Length = Count; + + return NewString; +} + +// Duplicate from > to + +void Assign(TStringList * to, TStringList * from) +{ + int i; + + Clear(to); + + if (from->Count == 0) + return; + + // Duplicate each item + + for (i = 0; i < from->Count; i++) + { + string * new = newString(); + + stringAdd(new, from->Items[i]->Data, from->Items[i]->Length); + Add(to, new); + } +} + +string * duplicateString(string * in) +{ + string * new = newString(); + + stringAdd(new, in->Data, in->Length); + + return new; +} + + +double pila(double x) +{ + //x : = frac(x); The frac function returns the fractional part of a floating point number. + + double whole; + double rem; + + rem = modf(x, &whole); // returns fraction, writes whole to whole + + if (rem != rem) + rem = 0; + + if (rem > 0.5) + rem = 1 - rem; + + return 2 * rem; +} + +boolean compareStrings(string * a, string * b) +{ + if (a->Length == b->Length && memcmp(a->Data, b->Data, a->Length) == 0) + return TRUE; + + return FALSE; +} + +// This looks for a string in a stringlist. Returns index if found, otherwise -1 + +int my_indexof(TStringList * l, string * s) +{ + int i; + + for (i = 0; i < l->Count; i++) + { + // Need to compare count and data - C doesn't allow struct compare + + if (l->Items[i]->Length == s->Length && memcmp(l->Items[i]->Data, s->Data, s->Length) == 0) + return i; + } + return -1; +} + + \ No newline at end of file diff --git a/VARA.ui b/VARA.ui new file mode 100644 index 0000000..c5e4506 --- /dev/null +++ b/VARA.ui @@ -0,0 +1,148 @@ + + + AGWConnectDkg + + + + 0 + 0 + 500 + 400 + + + + Dialog + + + + + 10 + 10 + 480 + 380 + + + + + + + + + Call From + + + + + + + + + + Call To + + + + + + + true + + + QComboBox::NoInsert + + + + + + + Digis + + + + + + + + + + + + Radio Ports + + + + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + OK + + + + + + + Cancel + + + + + + + + + + + + okButton + clicked() + AGWConnectDkg + accept() + + + 278 + 253 + + + 96 + 254 + + + + + cancelButton + clicked() + AGWConnectDkg + reject() + + + 369 + 253 + + + 179 + 282 + + + + + diff --git a/VARAConfig.ui b/VARAConfig.ui new file mode 100644 index 0000000..7992b6a --- /dev/null +++ b/VARAConfig.ui @@ -0,0 +1,566 @@ + + + Dialog + + + + 0 + 0 + 571 + 517 + + + + VARA Configuration + + + + + 170 + 460 + 211 + 33 + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + OK + + + + + + + Cancel + + + + + + + + + 10 + 120 + 537 + 123 + + + + TNC Setup + + + + + 130 + 22 + 101 + 22 + + + + + + + 130 + 54 + 47 + 22 + + + + + + + 130 + 90 + 391 + 22 + + + + + + + 16 + 24 + 47 + 13 + + + + Host + + + + + + 16 + 53 + 47 + 22 + + + + Port + + + + + + 16 + 89 + 47 + 22 + + + + Path + + + + + + + 8 + 280 + 541 + 181 + + + + PTT Port + + + + + 150 + 20 + 111 + 22 + + + + + + + 20 + 23 + 131 + 16 + + + + Select PTT Port + + + + + + 20 + 80 + 121 + 22 + + + + PTT On String + + + + + + 150 + 83 + 247 + 22 + + + + + + + 150 + 114 + 247 + 22 + + + + + + + 20 + 112 + 121 + 22 + + + + PTT Off String + + + + + + 20 + 52 + 111 + 16 + + + + GPIO Pin Left + + + + + + 150 + 50 + 25 + 20 + + + + + + + 310 + 50 + 25 + 20 + + + + + + + 165 + 50 + 83 + 18 + + + + GPIO Pin Right + + + + + + 18 + 50 + 121 + 18 + + + + CAT Port Speed + + + + + + 150 + 50 + 47 + 20 + + + + + + + 290 + 22 + 131 + 18 + + + + RTS/DTR + + + buttonGroup_2 + + + + + + 420 + 22 + 121 + 18 + + + + CAT + + + buttonGroup_2 + + + + + + 150 + 50 + 121 + 20 + + + + + + + 20 + 50 + 111 + 18 + + + + CM108 VID/PID + + + + + + 290 + 50 + 131 + 18 + + + + Hex Strings + + + buttonGroup + + + + + + 420 + 50 + 131 + 18 + + + + Text Strings + + + buttonGroup + + + + + + + 140 + 44 + 113 + 22 + + + + + + + 16 + 42 + 131 + 22 + + + + Terminal Callsign + + + + + + 18 + 12 + 161 + 21 + + + + Enable VARA Interface + + + + + + 180 + 10 + 23 + 25 + + + + Qt::RightToLeft + + + + + + + + + 270 + 10 + 131 + 101 + + + + VARA Mode + + + + + 10 + 20 + 121 + 20 + + + + VARA HF + + + true + + + buttonGroup_4 + + + + + + 10 + 43 + 121 + 20 + + + + VARA FM + + + buttonGroup_4 + + + + + + 10 + 66 + 121 + 20 + + + + VARA SAT + + + buttonGroup_4 + + + + + + + 410 + 10 + 131 + 101 + + + + VARA HF BW + + + + + 10 + 20 + 101 + 20 + + + + 500 Hz + + + buttonGroup_3 + + + + + + 10 + 43 + 101 + 20 + + + + 2300 Hz + + + true + + + buttonGroup_3 + + + + + + 10 + 66 + 101 + 21 + + + + 2750 Hz + + + buttonGroup_3 + + + + + + + + + + + + + diff --git a/ax25.c b/ax25.c new file mode 100644 index 0000000..c379b0a --- /dev/null +++ b/ax25.c @@ -0,0 +1,2987 @@ +/* +Copyright (C) 2019-2020 Andrei Kopanchuk UZ7HO + +This file is part of QtSoundModem + +QtSoundModem is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +QtSoundModem is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with QtSoundModem. If not, see http://www.gnu.org/licenses + +*/ + +// UZ7HO Soundmodem Port by John Wiseman G8BPQ + +#include "ax25.h" +#include + +#ifdef WIN32 + +__declspec(dllimport) unsigned short __stdcall htons(__in unsigned short hostshort); +__declspec(dllimport) unsigned short __stdcall ntohs(__in unsigned short hostshort); + +#else + +#define strtok_s strtok_r +#include +#endif + +void decode_frame(Byte * frame, int len, Byte * path, string * data, + Byte * pid, Byte * nr, Byte * ns, Byte * f_type, Byte * f_id, + Byte * rpt, Byte * pf, Byte * cr); + +void SetSessLabel(void * Sess, char * label); +void setMenus(int State); + +/* + +unit ax25; + +interface + +uses classes,sysutils,windows; + + procedure get_exclude_list(line: string; var list: TStringList); + procedure get_exclude_frm(line: string; var list: TStringList); + procedure get_monitor_path(path: string; var mycall,corrcall,digi: string); + procedure get_call(src_call: string; var call: string); + procedure get_call_fm_path(path: string; var callto,callfrom: string); + procedure set_corrcall(snd_ch,port: byte; path: string); + procedure set_mycall(snd_ch,port: byte; path: string); + procedure set_digi(snd_ch,port: byte; path: string); + procedure decode_frame(frame: string; var path,data: string; var pid,nr,ns,f_type,f_id: byte; var rpt,pf,cr: boolean); + procedure del_incoming_mycalls(src_call: string); + procedure del_incoming_mycalls_by_sock(socket: integer); + procedure ax25_info_init(snd_ch,port: byte); + procedure write_ax25_info(snd_ch,port: byte); + procedure clr_frm_win(snd_ch,port: byte); + procedure ax25_init; + procedure ax25_free; + function dec2hex(value: byte): string; + function get_corrcall(path: string): string; + function get_mycall(path: string): string; + function get_digi(path: string): string; + function is_excluded_call(snd_ch: byte; path: string): boolean; + function is_excluded_frm(snd_ch,f_id: byte; data: string): boolean; + function is_last_digi(path: string): boolean; + function is_digi(snd_ch,port: byte; path: string): boolean; + function is_corrcall(snd_ch,port: byte; path: string): boolean; + function is_mycall(snd_ch,port: byte; path: string): boolean; + function is_correct_path(path: string; pid: byte): boolean; + function direct_addr(path: string): string; + function reverse_addr(path: string): string; + function reverse_digi(path: string): string; + function number_digi(path: string): byte; + function info_pid(pid: byte): string; + function get_fcs(var data: string; len: word): word; + function set_addr(path: string; rpt,cr: boolean): string; + function set_ctrl(nr,ns,f_type,f_id: byte; pf: boolean): byte; + function make_frame(data,path: string; pid,nr,ns,f_type,f_id: byte; rpt,pf,cr: boolean): string; + function get_incoming_socket_by_call(src_call: string): integer; + function add_incoming_mycalls(socket: integer; src_call: string): boolean; + function in_list_incoming_mycall(path: string): boolean; + function get_UTC_time: string; + function parse_NETROM(data: string; f_id: byte): string; + function parse_IP(data: string): string; + function parse_ARP(data: string): string; + function add_raw_frames(snd_ch: byte; frame: string; var buf: TStringList): boolean; + function scrambler(in_buf: string): string; + function my_indexof(var l: TStringList; s: string): integer; + + +const + port_num=32; + PKT_ERR=17; //Minimum packet size, bytes + I_MAX=7; //Maximum number of packets + B_IDX_MAX=256; + ADDR_MAX_LEN=10; + FRAME_FLAG=126; + // Status flags + STAT_NO_LINK=0; + STAT_LINK=1; + STAT_CHK_LINK=2; + STAT_WAIT_ANS=3; + STAT_TRY_LINK=4; + STAT_TRY_UNLINK=5; + // Ñmd,Resp,Poll,Final,Digipeater flags + SET_P=TRUE; + SET_F=FALSE; + SET_C=TRUE; + SET_R=FALSE; + SET_NO_RPT=FALSE; + SET_RPT=TRUE; + // Frame ID flags + I_FRM=0; + S_FRM=1; + U_FRM=2; + I_I=0; + S_RR=1; + S_RNR=5; + S_REJ=9; + U_SABM=47; + U_DISC=67; + U_DM=15; + U_UA=99; + U_FRMR=135; + U_UI=3; + // PID flags + PID_X25=$01; // 00000001-CCIT X25 PLP + PID_SEGMENT=$08; // 00001000-Segmentation fragment + PID_TEXNET=$C3; // 11000011-TEXNET Datagram Protocol + PID_LQ=$C4; // 11001000-Link Quality Protocol + PID_APPLETALK=$CA; // 11001010-Appletalk + PID_APPLEARP=$CB; // 11001011-Appletalk ARP + PID_IP=$CC; // 11001100-ARPA Internet Protocol + PID_ARP=$CD; // 11001101-ARPA Address Resolution Protocol + PID_NET_ROM=$CF; // 11001111-NET/ROM +*/ + +#define ADDR_MAX_LEN 10 +#define PID_NO_L3 0xF0; // 11110000-No Level 3 Protocol + + +unsigned short CRCTable[256] = { + 0, 4489, 8978, 12955, 17956, 22445, 25910, 29887, + 35912, 40385, 44890, 48851, 51820, 56293, 59774, 63735, + 4225, 264, 13203, 8730, 22181, 18220, 30135, 25662, + 40137, 36160, 49115, 44626, 56045, 52068, 63999, 59510, + 8450, 12427, 528, 5017, 26406, 30383, 17460, 21949, + 44362, 48323, 36440, 40913, 60270, 64231, 51324, 55797, + 12675, 8202, 4753, 792, 30631, 26158, 21685, 17724, + 48587, 44098, 40665, 36688, 64495, 60006, 55549, 51572, + 16900, 21389, 24854, 28831, 1056, 5545, 10034, 14011, + 52812, 57285, 60766, 64727, 34920, 39393, 43898, 47859, + 21125, 17164, 29079, 24606, 5281, 1320, 14259, 9786, + 57037, 53060, 64991, 60502, 39145, 35168, 48123, 43634, + 25350, 29327, 16404, 20893, 9506, 13483, 1584, 6073, + 61262, 65223, 52316, 56789, 43370, 47331, 35448, 39921, + 29575, 25102, 20629, 16668, 13731, 9258, 5809, 1848, + 65487, 60998, 56541, 52564, 47595, 43106, 39673, 35696, + 33800, 38273, 42778, 46739, 49708, 54181, 57662, 61623, + 2112, 6601, 11090, 15067, 20068, 24557, 28022, 31999, + 38025, 34048, 47003, 42514, 53933, 49956, 61887, 57398, + 6337, 2376, 15315, 10842, 24293, 20332, 32247, 27774, + 42250, 46211, 34328, 38801, 58158, 62119, 49212, 53685, + 10562, 14539, 2640, 7129, 28518, 32495, 19572, 24061, + 46475, 41986, 38553, 34576, 62383, 57894, 53437, 49460, + 14787, 10314, 6865, 2904, 32743, 28270, 23797, 19836, + 50700, 55173, 58654, 62615, 32808, 37281, 41786, 45747, + 19012, 23501, 26966, 30943, 3168, 7657, 12146, 16123, + 54925, 50948, 62879, 58390, 37033, 33056, 46011, 41522, + 23237, 19276, 31191, 26718, 7393, 3432, 16371, 11898, + 59150, 63111, 50204, 54677, 41258, 45219, 33336, 37809, + 27462, 31439, 18516, 23005, 11618, 15595, 3696, 8185, + 63375, 58886, 54429, 50452, 45483, 40994, 37561, 33584, + 31687, 27214, 22741, 18780, 15843, 11370, 7921, 3960 }; + + +unsigned short pkt_raw_min_len = 8; +int stat_r_mem = 0; + +struct TKISSMode_t KISS; + + +TAX25Port AX25Port[4][port_num]; + +TStringList KISS_acked[4]; +TStringList KISS_iacked[4]; + +typedef struct registeredCalls_t +{ + UCHAR myCall[7]; // call in ax.25 + void * socket; + +} registeredCalls; + + +TStringList list_incoming_mycalls; // list strings containing a registered call + + +boolean busy = 0; +boolean dcd[5] = { 0 ,0 ,0, 0 }; + +boolean tx = 0; + +int stdtones = 0; +int fullduplex = 0; + +UCHAR diddles = 0; + +word MEMRecovery[5] = { 200,200,200,200 }; +int NonAX25[5] = { 0 }; + +boolean dyn_frack[4] = { FALSE,FALSE,FALSE,FALSE }; +Byte recovery[4] = { 0,0,0,0 }; +Byte users[4] = { 0,0,0,0 }; + +short txtail[5] = { 50, 50, 50, 50, 50 }; +short txdelay[5] = { 400, 400, 400, 400, 400 }; + +short modem_def[5] = { 1, 1, 1, 1, 1 }; + +int emph_db[5] = { 0, 0, 0, 0, 0 }; +UCHAR emph_all[5] = { 0, 0, 0, 0, 0 }; + +boolean KISS_opt[4] = { FALSE, FALSE, FALSE, FALSE }; +int resptime[4] = { 1500,1500,1500,1500 }; +int slottime[4] = { 100,100,100,100 }; +int persist[4] = { 100,100,100,100 }; +int kisspaclen[4] = { 128,128,128,128 }; +int fracks[4] = { 10,10,10,10 }; +int frack_time[4] = { 5,5,5,5 }; +int idletime[4] = { 180,180,180,180 }; +int redtime[4] = { 0,0,0,0 }; +int IPOLL[4] = { 30,30,30,30 }; +int maxframe[4] = { 4,4,4,4 }; +int TXFrmMode[4] = { 1,1,1,1 }; +int max_frame_collector[4] = { 6,6,6,6 }; + + +char MyDigiCall[4][512] = { "","","","" }; +char exclude_callsigns[4][512] = { "","","","" }; +char exclude_APRS_frm[4][512] = { "","","","" }; + +TStringList list_exclude_callsigns[4]; +TStringList list_exclude_APRS_frm[4]; +TStringList list_digi_callsigns[4]; + +Byte xData[256]; +Byte xEncoded[256]; +Byte xDecoded[256]; + +int frame_count = 0; +int single_frame_count = 0; + +/* + mydigi: string; + //// end of user params + addr: string[70]; + ctrl: byte; + pid: byte=PID_NO_L3; + fcs: word; + data: string; + frame: string; + +implementation + +uses ax25_l2,sm_main; + +*/ + +char * strlop(char * buf, char delim) +{ + // Terminate buf at delim, and return rest of string + + char * ptr = strchr(buf, delim); + + if (ptr == NULL) return NULL; + + *(ptr)++ = 0; + return ptr; +} + +void Debugprintf(const char * format, ...) +{ + char Mess[10000]; + va_list(arglist); + + va_start(arglist, format); + vsprintf(Mess, format, arglist); + WriteDebugLog(Mess); + + return; +} + + + +void AX25_conn(TAX25Port * AX25Sess, int snd_ch, Byte mode) +{ + UNUSED(snd_ch); + char Msg[128]; + int Len = 0; + + switch (mode) + { + case MODE_OTHER: + + Len = sprintf(Msg, "Incomming Connection from %s\r", AX25Sess->corrcall); + break; + + case MODE_OUR: + + Len = sprintf(Msg, "Connected To %s\r", AX25Sess->corrcall); + break; + + }; + + SendtoTerm(AX25Sess->Sess, Msg, Len); + SetSessLabel(AX25Sess->Sess, AX25Sess->corrcall); + setMenus(1); +} + +void send_data_buf(TAX25Port * AX25Sess, int nr); + +void SendtoAX25(void * conn, Byte * Msg, int Len) +{ + TAX25Port * AX25Sess; + AX25Sess = (TAX25Port * )conn; + + // Need to enforce PacLen + + if (AX25Sess) + { + int n; + + while (Len) + { + string * data = newString(); + + if (Len > kisspaclen[0]) + n = kisspaclen[0]; + else + n = Len; + + stringAdd(data, Msg, n); + Add(&AX25Sess->in_data_buf, data); + + Len -= n; + Msg += n; + } + send_data_buf(AX25Sess, AX25Sess->vs); + } +} + + + + + +void scrambler(UCHAR * in_buf, int Len) +{ + integer i; + word sreg; + Byte a = 0, k; + + sreg = 0x1ff; + + for (i = 0; i < Len; i++) + { + for (k = 0; k < 8; k++) + { + + // a: = (a shr 1) or (sreg and 1 shl 7); + + a = (a >> 1) | ((sreg & 1) << 7); + + // sreg: = (sreg shl 4 and $200) xor (sreg shl 8 and $200) or (sreg shr 1); + + + sreg = (((sreg << 4) & 0x200) ^ ((sreg << 8) & 0x200)) | (sreg >> 1); + } + in_buf[i] = in_buf[i] ^ a; + } +} + + +/* +function parse_ARP(data: string): string; + +function get_callsign(data: string): string; +var + i: integer; + s: string; + a: byte; +begin + s:=''; + if length(data)=7 then + begin + for i:=1 to 6 do + begin + a:=ord(data[i]) shr 1; + if (a in [$30..$39,$41..$5A]) then s:=s+chr(a); + end; + a:=ord(data[7]) shr 1 and 15; + if a>0 then s:=s+'-'+inttostr(a); + end + else + begin + if length(data)>0 then + begin + for i:=1 to length(data) do + if i=1 then s:=dec2hex(ord(data[i])) else s:=s+':'+dec2hex(ord(data[i])); + end; + end; + if s<>'' then s:=s+' '; + result:=s; +end; + +function get_IP(data: string): string; +var + i: integer; + s: string; +begin + s:=''; + if length(data)>0 then + begin + for i:=1 to length(data) do + if i=1 then s:=inttostr(ord(data[i])) else s:=s+'.'+inttostr(ord(data[i])); + end; + if s<>'' then s:=s+' '; + result:=s; +end; + +const + opcode: array [0..3] of string = ('ARP Request','ARP Response','RARP Request','RARP Response'); +var + oper: word; + hlen,plen: byte; + sha,spa,tha,tpa: string; + s: string; + i: word; +begin + s:=data; + if length(data)>7 then + begin + hlen:=ord(data[5]); + plen:=ord(data[6]); + oper:=(ord(data[7]) shl 8 or ord(data[8])) and 2; + i:=9; sha:=get_callsign(copy(data,i,hlen)); + i:=i+hlen; spa:=get_ip(copy(data,i,plen)); + i:=i+plen; tha:=get_callsign(copy(data,i,hlen)); + i:=i+hlen; tpa:=get_ip(copy(data,i,plen)); + s:=' [ARP] '+opcode[oper]+' from '+sha+spa+'to '+tha+tpa; + end; + result:=s; +end; + +function parse_NETROM(data: string; f_id: byte): string; + + function deshift_AX25(data: string): string; + var + i: byte; + call: string[6]; + ssid: string[2]; + begin + result:=''; + if length(data)<7 then exit; + for i:=1 to 7 do data[i]:=chr(ord(data[i]) shr 1); + call:=trim(copy(data,1,6)); + ssid:=trim(inttostr(ord(data[7]) and 15)); + if ssid='0' then result:=call else result:=call+'-'+ssid; + end; + + function con_req_info(data: string): string; + var + s_call: string; + d_call: string; + w: byte; + t_o: byte; + begin + result:=''; + if length(data)>14 then + begin + w:=ord(data[1]); + s_call:=deshift_AX25(copy(data,2,7)); + d_call:=deshift_AX25(copy(data,9,7)); + result:=' w='+inttostr(w)+' '+s_call+' at '+d_call; + end; + if length(data)>15 then + begin + t_o:=ord(data[16]); + result:=result+' t/o '+inttostr(t_o); + end; + end; + + function con_ack_info(data: string): string; + var + w: byte; + begin + result:=''; + if length(data)>0 then + begin + w:=ord(data[1]); + result:=' w='+inttostr(w); + end; + end; + +const + opcode_arr: array[0..7] of string = ('PE','CON REQ','CON ACK','DISC REQ','DISQ ACK','INFO','INFO ACK','RST'); +var + s: string; + netrom_header: string; + c_idx: byte; + c_ID: byte; + TX_nr: byte; + RX_nr: byte; + opcode: byte; + s_call: string; + s_node: string; + d_call: string; + d_node: string; + b_call: string; + r_s_nr: string; + opc_flags: string; + quality: byte; + ttl: byte; + hops: byte; + rtt: word; + inp3_nr_field: byte; + inp3_field_len: byte; + inp3_ext_fields: boolean; +begin + s:=data; + if length(data)>0 then + begin + if data[1]=#$FF then + begin + delete(data,1,1); + //Nodes broadcasting + if (f_id=U_UI) and (length(data)>5) then + begin + s_node:=copy(data,1,6); + delete(data,1,6); + s:='NODES broadcast from '+s_node+#13#10; + while length(data)>20 do + begin + d_call:=deshift_AX25(copy(data,1,7)); + d_node:=copy(data,8,6); + b_call:=deshift_AX25(copy(data,14,7)); + quality:=ord(data[21]); + delete(data,1,21); + s:=s+' '+d_node+':'+d_call+' via '+b_call+' Q='+inttostr(quality)+#13#10; + end; + end; + // INP3 RIF + if (f_id=I_I) and (length(data)>10) then + begin + s:='[INP3 RIF]'+#13#10; + while length(data)>10 do + begin + d_call:=deshift_AX25(copy(data,1,7)); + hops:=ord(data[8]); + rtt:=(ord(data[9]) shl 8) or ord(data[10]); + delete(data,1,10); + inp3_ext_fields:=TRUE; + inp3_nr_field:=0; + while (length(data)>0) and inp3_ext_fields do + begin + inp3_field_len:=ord(data[1]); + if inp3_field_len>0 then + begin + if (inp3_nr_field=0) and (length(data)>1) then + begin + if data[2]=#0 then d_call:=copy(data,3,inp3_field_len-2)+':'+d_call; // Copy alias + end; + delete(data,1,inp3_field_len); + inc(inp3_nr_field); + end + else inp3_ext_fields:=FALSE; + end; + delete(data,1,1); + s:=s+d_call+' hops='+inttostr(hops)+' rtt='+inttostr(rtt)+#13#10; + end; + end; + end + else + begin + // NETROM frames + if length(data)>19 then + begin + s_call:=deshift_AX25(copy(data,1,7)); + d_call:=deshift_AX25(copy(data,8,7)); + ttl:=ord(data[15]); + netrom_header:=copy(data,16,5); + delete(data,1,20); + c_idx:=ord(netrom_header[1]); + c_ID:=ord(netrom_header[2]); + TX_nr:=ord(netrom_header[3]); + RX_nr:=ord(netrom_header[4]); + opcode:=ord(netrom_header[5]); + // Opcode flags + opc_flags:=''; + if opcode and 128 = 128 then opc_flags:=opc_flags+' C'; + if opcode and 64 = 64 then opc_flags:=opc_flags+' N'; + // + s:=' [NETROM] '+s_call+' to '+d_call+' ttl='+inttostr(ttl)+' cct='+dec2hex(c_idx)+dec2hex(c_ID); + r_s_nr:=' S'+inttostr(TX_nr)+' R'+inttostr(RX_nr); + case (opcode and 7) of + 0 : s:=s+' <'+opcode_arr[opcode and 7]+r_s_nr+'>'+#13#10+data; + 1 : s:=s+' <'+opcode_arr[opcode and 7]+'>'+con_req_info(data); + 2 : s:=s+' <'+opcode_arr[opcode and 7]+'>'+con_ack_info(data)+' my cct='+dec2hex(TX_nr)+dec2hex(RX_nr); + 3 : s:=s+' <'+opcode_arr[opcode and 7]+'>'; + 4 : s:=s+' <'+opcode_arr[opcode and 7]+'>'; + 5 : s:=s+' <'+opcode_arr[opcode and 7]+r_s_nr+'>:'+#13#10+data; + 6 : s:=s+' <'+opcode_arr[opcode and 7]+' R'+inttostr(RX_nr)+'>'+opc_flags; + 7 : s:=s+' <'+opcode_arr[opcode and 7]+r_s_nr+'>'+#13#10+data; + end; + end; + end; + end; + result:=s; +end; + +function parse_IP(data: string): string; + + function parse_ICMP(var data: string): string; + var + ICMP_type: byte; + ICMP_code: byte; + s: string; + begin + result:=''; + if length(data)>3 then + begin + ICMP_type:=ord(data[1]); + ICMP_code:=ord(data[2]); + delete(data,1,4); + s:=' [ICMP] Type='+inttostr(ICMP_type)+' Code='+inttostr(ICMP_code)+#13#10; + result:=s; + end; + end; + + function parse_TCP(var data: string): string; + var + s: string; + src_port: string; + dest_port: string; + wnd: string; + ihl: word; + idl: word; + flags: byte; + seq: string; + ack: string; + s_flags: string; + s_idl: string; + begin + result:=''; + if length(data)>19 then + begin + src_port:=' src_port:'+inttostr((ord(data[1]) shl 8)+ord(data[2])); + dest_port:=' dest_port:'+inttostr((ord(data[3]) shl 8)+ord(data[4])); + seq:=' seq='+dec2hex(ord(data[5]))+dec2hex(ord(data[6]))+dec2hex(ord(data[7]))+dec2hex(ord(data[8])); + ack:=' ack='+dec2hex(ord(data[9]))+dec2hex(ord(data[10]))+dec2hex(ord(data[11]))+dec2hex(ord(data[12])); + ihl:=(ord(data[13]) shr 4)*4; + idl:=length(data)-ihl; + flags:=ord(data[14]); + wnd:=' wnd='+inttostr((ord(data[15]) shl 8)+ord(data[16])); + delete(data,1,ihl); + // + s_flags:=' '; + if (flags and 32)=32 then s_flags:=s_flags+'URG '; + if (flags and 16)=16 then s_flags:=s_flags+'ACK '; + if (flags and 8)=8 then s_flags:=s_flags+'PSH '; + if (flags and 4)=4 then s_flags:=s_flags+'RST '; + if (flags and 2)=2 then s_flags:=s_flags+'SYN '; + if (flags and 1)=1 then s_flags:=s_flags+'FIN '; + // + if idl>0 then s_idl:=' data='+inttostr(idl) else s_idl:=''; + if (flags and 16)<>16 then ack:=''; + // + s:=' [TCP]'+src_port+dest_port+seq+ack+wnd+s_idl+s_flags+#13#10; + result:=s; + end; + end; + + function parse_UDP(var data: string): string; + var + s: string; + src_port: string; + dest_port: string; + idl: word; + len: word; + s_idl: string; + begin + result:=''; + if length(data)>7 then + begin + src_port:=' src_port:'+inttostr((ord(data[1]) shl 8)+ord(data[2])); + dest_port:=' dest_port:'+inttostr((ord(data[3]) shl 8)+ord(data[4])); + len:=(ord(data[5]) shl 8)+ord(data[6]); + idl:=len-8; + delete(data,1,8); + // + if idl>0 then s_idl:=' data='+inttostr(idl) else s_idl:=''; + // + s:=' [UDP]'+src_port+dest_port+' len='+inttostr(len)+s_idl+#13#10; + result:=s; + end; + end; + +const + prot_idx=#1#6#17; + prot_name: array [1..3] of string = ('ICMP','TCP','UDP'); +var + s: string; + src_ip: string; + dest_ip: string; + s_prot: string; + len: string; + c_prot: char; + ttl: string; + offset: string; + ihl: byte; + p: byte; + fragment_offset: word; +begin + s:=data; + if length(data)>19 then + begin + ihl:=(ord(data[1]) and 15)*4; + len:=' len='+inttostr((ord(data[3]) shl 8)+ord(data[4])); + fragment_offset:=((ord(data[7]) shl 8)+ord(data[8])) shl 3; + ttl:=' ttl='+inttostr(ord(data[9])); + c_prot:=data[10]; + src_ip:=' Fm '+inttostr(ord(data[13]))+'.'+inttostr(ord(data[14]))+'.'+inttostr(ord(data[15]))+'.'+inttostr(ord(data[16])); + dest_ip:=' To '+inttostr(ord(data[17]))+'.'+inttostr(ord(data[18]))+'.'+inttostr(ord(data[19]))+'.'+inttostr(ord(data[20])); + delete(data,1,ihl); + // + p:=pos(c_prot,prot_idx); + if p>0 then s_prot:=' prot='+prot_name[p] else s_prot:=' prot=Type'+inttostr(ord(c_prot)); + if fragment_offset>0 then offset:=' offset='+inttostr(fragment_offset) else offset:=''; + s:=' [IP]'+src_ip+dest_ip+s_prot+ttl+len+offset+#13#10; + if fragment_offset=0 then + case p of + 1 : s:=s+parse_ICMP(data); + 2 : s:=s+parse_TCP(data); + 3 : s:=s+parse_UDP(data); + end; + s:=s+data; + end; + result:=s; +end; + +function get_UTC_time: string; +var + st: TSYSTEMTIME; + sec,hour,minute: string; +begin + GetSystemTime(st); + if st.wSecond<10 then sec:='0'+inttostr(st.wSecond) else sec:=inttostr(st.wSecond); + if st.wMinute<10 then minute:='0'+inttostr(st.wMinute) else minute:=inttostr(st.wMinute); + if st.wHour<10 then Hour:='0'+inttostr(st.wHour) else Hour:=inttostr(st.wHour); + result:=hour+':'+minute+':'+sec; +end; + +function dec2hex(value: byte): string; +const + hex='0123456789ABCDEF'; +var + lo,hi: byte; +begin + lo:=value and 15; + hi:=value shr 4; + result:=hex[hi+1]+hex[lo+1]; +end; + +*/ + +unsigned short get_fcs(UCHAR * Data, unsigned short len) +{ + unsigned short i; + unsigned short result; + + result = 0xFFFF; + + if (len == 0) + return result; + + for (i = 0; i < len; i++) + result = (result >> 8) ^ CRCTable[(result ^ Data[i]) & 0xff]; + + + result ^= 0xffff; + + return result; +} + + +unsigned short CRCTAB[256] = { + 0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, + 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, + 0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, + 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, + 0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, + 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, +0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, +0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, +0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, +0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, +0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, +0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, +0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, +0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, +0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, +0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, +0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, +0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, +0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, +0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, +0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, +0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, +0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, +0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, +0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, +0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, +0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, +0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, +0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, +0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, +0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, +0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78 +}; + +unsigned short int compute_crc(unsigned char *buf, int len) +{ + unsigned short fcs = 0xffff; + int i; + + for (i = 0; i < len; i++) + fcs = (fcs >> 8) ^ CRCTAB[(fcs ^ buf[i]) & 0xff]; + + fcs ^= 0xffff; + + return fcs; +} + + + +int get_addr(char * Calls, UCHAR * AXCalls) +{ + // CONVERT CALL + OPTIONAL DIGI STRING (Comma separated) TO AX25, RETURN + // CONVERTED STRING IN AXCALLS. Return FALSE if invalied + + Byte * axptr = AXCalls; + char * ptr, *Context; + int n = 8; // Max digis + + memset(AXCalls, 0, 70); + + ptr = strtok_s(Calls, " ,", &Context); + + if (ptr == NULL) + return FALSE; + + // First field is Call + + if (ConvToAX25(ptr, axptr) == 0) + return FALSE; + + axptr += 7; + + ptr = strtok_s(NULL, " ,", &Context); + + if (ConvToAX25(ptr, axptr) == 0) + return FALSE; + + axptr += 7; + + ptr = strtok_s(NULL, " ,", &Context); + + while (ptr && n--) + { + // NEXT FIELD = COULD BE CALLSIGN, VIA, + + if (memcmp(ptr, "VIA", (int)strlen(ptr)) == 0) + { + } //skip via + else + { + // Convert next digi + + if (ConvToAX25(ptr, axptr) == 0) + return FALSE; + + axptr += 7; + } + + ptr = strtok_s(NULL, " ,", &Context); + } + + axptr[-1] |= 1; // Set end of address + + return axptr - AXCalls; +} + +Byte set_ctrl(Byte nr, Byte ns, Byte f_type, Byte f_id, boolean pf) +{ + Byte pf_bit, ctrl; + + ctrl = 0; + pf_bit = 0; + + if (pf) + pf_bit = 16; + + switch (f_type) + { + case I_FRM: + + ctrl = (nr << 5) + pf_bit + (ns << 1); + break; + + case S_FRM: + + ctrl = (nr << 5) + pf_bit + f_id; + break; + + case U_FRM: + + ctrl = f_id + pf_bit; + } + + return ctrl; +} + +string * make_frame(string * data, Byte * axaddr, Byte pid, Byte nr, Byte ns, Byte f_type, Byte f_id, boolean rpr, boolean pf, boolean cr) +{ + UNUSED(rpr); + + Byte ctrl; + + string * frame = newString(); + int addrlen; + Byte addr[80]; + + frame->Data[0] = 0; // Lower software expects a kiss control byte here + frame->Length = 1; + + ctrl = set_ctrl(nr, ns, f_type, f_id, pf); + + addrlen = strlen((char *)axaddr); + + memcpy(addr, axaddr, addrlen); + + if (cr) + addr[6] |= 0x80; // Set Command Bit + else + addr[13] |= 0x80; // Set Response Bit + + + switch (f_type) + { + case I_FRM: + + stringAdd(frame, addr, addrlen); + stringAdd(frame, (Byte *)&ctrl, 1); + stringAdd(frame, (Byte *)&pid, 1); + stringAdd(frame, data->Data, data->Length); + + break; + + + case S_FRM: + + stringAdd(frame, addr, addrlen); + stringAdd(frame, (Byte *)&ctrl, 1); + + break; + + case U_FRM: + + if (f_id == U_UI) + { + stringAdd(frame, addr, addrlen); + stringAdd(frame, (Byte *)&ctrl, 1); + stringAdd(frame, (Byte *)&pid, 1); + stringAdd(frame, data->Data, data->Length); + } + else if (f_id == U_FRMR) + { + stringAdd(frame, addr, addrlen); + stringAdd(frame, (Byte *)&ctrl, 1); + stringAdd(frame, data->Data, data->Length); + } + else + { + stringAdd(frame, addr, addrlen); + stringAdd(frame, (Byte *)&ctrl, 1); + } + } + + return frame; +} + + +int add_raw_frames(int snd_ch, string * frame, TStringList * buf) +{ + UNUSED(snd_ch); + + string *s_data = newString(); + Byte s_pid, s_nr, s_ns, s_f_type, s_f_id; + Byte s_rpt, s_cr, s_pf; + string *d_data = newString(); + Byte d_pid, d_nr, d_ns, d_f_type, d_f_id; + Byte d_rpt, d_cr, d_pf; + + Byte d_path[80]; + Byte s_path[80]; + + boolean found_I; + int i; + + unsigned char * framecontents; + int Length; + + boolean result = TRUE; + + // Have to be careful as at this point frames have KISS Header and maybe trailer + + if (buf->Count > 0) + { + // Check for duplicate. Ok to just compare as copy will have same header + + if (my_indexof(buf, frame) >= 0) + { +// Debugprintf("KISSOptimise discarding duplicate frame"); + return FALSE; + } + + // Need to adjust for KISS bytes + + // Normally one, but ackmode has 3 on front and sizeof(void *) on end + + framecontents = frame->Data; + Length = frame->Length; + + if ((framecontents[0] & 15) == 12) // Ackmode + { + framecontents += 3; + Length -= (3 + sizeof(void *)); + } + else + { + framecontents++; + Length--; + } + + decode_frame(framecontents, Length, s_path, s_data, &s_pid, &s_nr, &s_ns, &s_f_type, &s_f_id, &s_rpt, &s_pf, &s_cr); + + found_I = FALSE; + + // check for multiple RR (r) + + if (s_f_id == S_FRM && s_cr == SET_R) + { + for (i = 0; i < buf->Count; i++) + { + framecontents = buf->Items[i]->Data; + Length = buf->Items[i]->Length; + + if ((framecontents[0] & 15) == 12) // Ackmode + { + framecontents += 3; + Length -= (3 + sizeof(void *)); + } + else + { + framecontents++; + Length--; + } + + decode_frame(framecontents, Length, d_path, d_data, &d_pid, &d_nr, &d_ns, &d_f_type, &d_f_id, &d_rpt, &d_pf, &d_cr); + + if (d_f_id == S_FRM && d_cr == SET_R && strcmp((char *)s_path, (char *)d_path) == 0) + { + Delete(buf, i); + Debugprintf("KISSOptimise discarding unneeded RR(R%d)", d_nr); + + break; + } + } + } + + + // check for RR after I Frame + + if (s_f_id == S_FRM && s_cr == SET_C) + { + for (i = 0; i < buf->Count; i++) + { + framecontents = buf->Items[i]->Data; + Length = buf->Items[i]->Length; + + if ((framecontents[0] & 15) == 12) // Ackmode + { + framecontents += 3; + Length -= (3 + sizeof(void *)); + } + else + { + framecontents++; + Length--; + } + + decode_frame(framecontents, Length, d_path, d_data, &d_pid, &d_nr, &d_ns, &d_f_type, &d_f_id, &d_rpt, &d_pf, &d_cr); + + if (d_f_id == I_FRM && strcmp((char *)s_path, (char *)d_path) == 0) + { + found_I = TRUE; + break; + } + } + + if (found_I) + { + Debugprintf("KISSOptimise discarding unneeded RR(C %d) after I frame", s_nr); + result = FALSE; + } + } + + // check on I + + if (s_f_id == I_FRM) + { + for (i = 0; i < buf->Count; i++) + { + framecontents = buf->Items[i]->Data; + Length = buf->Items[i]->Length; + + if ((framecontents[0] & 15) == 12) // Ackmode + { + framecontents += 3; + Length -= (3 + sizeof(void *)); + } + else + { + framecontents++; + Length--; + } + + decode_frame(framecontents, Length, d_path, d_data, &d_pid, &d_nr, &d_ns, &d_f_type, &d_f_id, &d_rpt, &d_pf, &d_cr); + + if (strcmp((char *)s_path, (char *)d_path) == 0 && d_f_id == S_FRM && d_cr == SET_C) + { + Delete(buf, i); + Debugprintf("KISSOptimise discarding unneeded RR(C %d)", d_nr); + i--; // i was removed + } + } + } + } + + freeString(d_data); + freeString(s_data); + + return result; +} + +//////////////////////// Register incoming callsign //////////////////////////// + +// I think a call should only be registered on one socket (or we won't know where to send +// incoming calls + +boolean add_incoming_mycalls(void * socket, char * src_call) +{ + registeredCalls * reg = malloc(sizeof(struct registeredCalls_t)); + int i = 0; + + // Build a string containing Call and Socket + + ConvToAX25(src_call, reg->myCall); + reg->socket = socket; + + if (list_incoming_mycalls.Count > 0) + { + for (i = 0; i < list_incoming_mycalls.Count; i++) + { + registeredCalls * check = (registeredCalls *)list_incoming_mycalls.Items[i]; + + if (memcmp(check->myCall, reg->myCall, 7) == 0) + { + // Update socket + + check->socket = socket; + return FALSE; + } + } + } + + Add(&list_incoming_mycalls, (string *)reg); + return TRUE; +} + + + +void del_incoming_mycalls(char * src_call) +{ + int i = 0; + Byte axcall[7]; + registeredCalls * reg; + + ConvToAX25(src_call, axcall); + + while (i < list_incoming_mycalls.Count) + { + reg = (registeredCalls *)list_incoming_mycalls.Items[i]; + { + if (memcmp(reg->myCall, axcall, 7) == 0) + { + // cant use Delete as stringlist doesn't contain strings + + TStringList * Q = &list_incoming_mycalls; + int Index = i; + + free(Q->Items[Index]); + + Q->Count--; + + while (Index < Q->Count) + { + Q->Items[Index] = Q->Items[Index + 1]; + Index++; + } + + return; + } + } + i++; + } +} + + +void del_incoming_mycalls_by_sock(void * socket) +{ + int i = 0, snd_ch, port; + registeredCalls * reg; + + while (i < list_incoming_mycalls.Count) + { + reg = (registeredCalls *)list_incoming_mycalls.Items[i]; + { + if (reg->socket == socket) + { + // cant use Delete as stringlist doesn't contain strings + + TStringList * Q = &list_incoming_mycalls; + int Index = i; + + free(Q->Items[Index]); + + Q->Count--; + + while (Index < Q->Count) + { + Q->Items[Index] = Q->Items[Index + 1]; + Index++; + } + + //Delete(&list_incoming_mycalls, i); + } + else + i++; + } + } + + // Should clear all connections on socket + + for (snd_ch = 0; snd_ch < 4; snd_ch++) + { + for (port = 0; port < port_num; port++) + { + TAX25Port * AX25Sess = &AX25Port[snd_ch][port]; + + if (AX25Sess->socket == socket) + { + if (AX25Sess->status != STAT_NO_LINK) + { + // Shouldn't we send DM? -0 try it + + set_DM(snd_ch, AX25Sess->ReversePath); + + rst_timer(AX25Sess); + rst_values(AX25Sess); + + AX25Sess->status = STAT_NO_LINK; + } + AX25Sess->socket = 0; + } + } + } +} + + +/* +function get_incoming_socket_by_call(src_call: string): integer; +var + i: integer; + found: boolean; + socket: integer; + call,ssid: string; + a_call: array[0..1] of string; +begin + socket:=-1; + i:=0; + found:=FALSE; + try explode(a_call,'-',src_call,2); except end; + call:=trim(a_call[0]); + if a_call[1]<>'' then ssid:=a_call[1] else ssid:='0'; + if list_incoming_mycalls.Count>0 then + repeat + if (call+'-'+ssid)=list_incoming_mycalls.Strings[i] then + begin socket:=strtoint(list_incoming_mycalls_sock.Strings[i]); found:=TRUE; end; + inc(i); + until found or (i=list_incoming_mycalls.Count); + result:=socket; +end; +*/ + + + +void * in_list_incoming_mycall(Byte * path) +{ + // See if to call is in registered calls list + + int i = 0; + registeredCalls * check; // list_incoming_mycalls contains registeredCalls, not Strings + + while (i < list_incoming_mycalls.Count) + { + check = (registeredCalls *)list_incoming_mycalls.Items[i]; + + if (memcmp(check->myCall, path, 7) == 0) + return check->socket; + + i++; + } + + return NULL; +} + +/* +//////////////////////////////////////////////////////////////////////////////// + +function is_corrcall(snd_ch,port: byte; path: string): boolean; +var + call,ssid: string; +begin + call:=trim(copy(path,8,6)); + ssid:=copy(path,14,1); + if ssid<>'' then ssid:=inttostr((ord(ssid[1]) and 15)) else ssid:='0'; + call:=call+'-'+ssid; + if call=AX25Sess->corrcall then result:=TRUE else result:=FALSE; +end; + +function is_mycall(snd_ch,port: byte; path: string): boolean; +var + call,ssid: string; +begin + call:=trim(copy(path,1,6)); + ssid:=copy(path,7,1); + if ssid<>'' then ssid:=inttostr((ord(ssid[1]) and 15)) else ssid:='0'; + call:=call+'-'+ssid; + if call=AX25Sess->mycall then result:=TRUE else result:=FALSE; +end; + +function is_digi(snd_ch,port: byte; path: string): boolean; +var + digi,call,ssid: string; +begin + digi:=''; + if length(path)>14 then + begin + delete(path,1,14); + repeat + call:=trim(copy(path,1,6)); + ssid:=copy(path,7,1); + delete(path,1,7); + if ssid<>'' then ssid:=inttostr((ord(ssid[1]) and 15)) + else ssid:='0'; + if path<>'' then digi:=digi+call+'-'+ssid+',' + else digi:=digi+call+'-'+ssid; + until path=''; + end; + if digi=AX25Sess->digi then result:=TRUE else result:=FALSE; +end; +*/ + +// Check if laast digi used + +boolean is_last_digi(Byte *path) +{ + int len = strlen(path); + + if (len == 14) + return TRUE; + + if ((path[len - 1] & 128) == 128) + return TRUE; + + return FALSE; +} + + + +/* +function get_corrcall(path: string): string; +var + call,ssid: string; +begin + call:=trim(copy(path,8,6)); + ssid:=copy(path,14,1); + if ssid<>'' then ssid:=inttostr((ord(ssid[1]) and 15)) else ssid:='0'; + call:=call+'-'+ssid; + result:=call; +end; + +function get_mycall(path: string): string; +var + call,ssid: string; +begin + call:=trim(copy(path,1,6)); + ssid:=copy(path,7,1); + if ssid<>'' then ssid:=inttostr((ord(ssid[1]) and 15)) else ssid:='0'; + call:=call+'-'+ssid; + result:=call; +end; + +function get_digi(path: string): string; +var + digi,call,ssid: string; +begin + digi:=''; + if length(path)>14 then + begin + delete(path,1,14); + repeat + call:=trim(copy(path,1,6)); + ssid:=copy(path,7,1); + delete(path,1,7); + if ssid<>'' then ssid:=inttostr((ord(ssid[1]) and 15)) + else ssid:='0'; + if path<>'' then digi:=digi+call+'-'+ssid+',' + else digi:=digi+call+'-'+ssid; + until path=''; + end; + result:=digi; +end; +*/ + +boolean is_correct_path(Byte * path, Byte pid) +{ + Byte networks[] = { 6, 7, 8, 0xc4, 0xcc, 0xcd, 0xce, 0xcf, 0xf0 , 0 }; + int i; + + + if (pid == 0 || strchr(networks, pid)) + { + // Validate calls + + // I think checking bottom bit of first 13 bytes is enough + + for (i = 0; i < 13; i++) + { + if ((*(path) & 1)) + return FALSE; + + path++; + } + return TRUE; + } + return FALSE; +} + + +void get_exclude_list(char * line, TStringList * list) +{ + // Convert comma separated list of calls to ax25 format in list + + string axcall; + + char copy[512]; + + char * ptr, *Context; + + if (line[0] == 0) + return; + + strcpy(copy, line); // copy as strtok messes with it + strcat(copy, ","); + + axcall.Length = 8; + axcall.AllocatedLength = 8; + axcall.Data = malloc(8); + + memset(axcall.Data, 0, 8); + + ptr = strtok_s(copy, " ,", &Context); + + while (ptr) + { + if (ConvToAX25(ptr, axcall.Data) == 0) + return; + + Add(list, duplicateString(&axcall)); + + ptr = strtok_s(NULL, " ,", &Context); + } +} + + + +void get_exclude_frm(char * line, TStringList * list) +{ + UNUSED(line); + UNUSED(list); + /* + + s: string; + p: integer; + n: integer; +begin + list.Clear; + if line='' then exit; + repeat + p:=pos(',',line); + if p>0 then + begin + s:=trim(copy(line,1,p-1)); + if s<>'' then + begin + try n:=strtoint(s); except n:=-1; end; + if n in [0..255] then list.Add(chr(n)); + end; + delete(line,1,p); + end + else + begin + s:=trim(line); + if s<>'' then + begin + try n:=strtoint(s); except n:=-1; end; + if n in [0..255] then list.Add(chr(n)); + end; + end; + until p=0; +end; +*/ +} + +/* + +function is_excluded_call(snd_ch: byte; path: string): boolean; +var + excluded: boolean; + call: string; + ssid: string; +begin + excluded:=FALSE; + if (list_exclude_callsigns[snd_ch].Count>0) and (length(path)>13) then + begin + // Copy sender + call:=trim(copy(path,8,6)); + ssid:=copy(path,14,1); + if ssid<>'' then call:=call+'-'+inttostr((ord(ssid[1]) and 15)); + if list_exclude_callsigns[snd_ch].IndexOf(call)>-1 then excluded:=TRUE; + end; + result:=excluded; +end; + +function is_excluded_frm(snd_ch,f_id: byte; data: string): boolean; +var + excluded: boolean; +begin + excluded:=FALSE; + if list_exclude_APRS_frm[snd_ch].Count>0 then + if f_id=U_UI then + if length(data)>0 then + if list_exclude_APRS_frm[snd_ch].IndexOf(data[1])>=0 then excluded:=TRUE; + result:=excluded; +end; + +procedure set_corrcall(snd_ch,port: byte; path: string); +var + call,ssid: string; +begin + call:=trim(copy(path,8,6)); + ssid:=copy(path,14,1); + if ssid<>'' then ssid:=inttostr((ord(ssid[1]) and 15)) + else ssid:='0'; + AX25Sess->corrcall:=call+'-'+ssid; +end; + +procedure set_mycall(snd_ch,port: byte; path: string); +var + call,ssid: string; +begin + call:=trim(copy(path,1,6)); + ssid:=copy(path,7,1); + if ssid<>'' then ssid:=inttostr((ord(ssid[1]) and 15)) + else ssid:='0'; + AX25Sess->mycall:=call+'-'+ssid; +end; + +procedure set_digi(snd_ch,port: byte; path: string); +var + digi,call,ssid: string; +begin + digi:=''; + if length(path)>14 then + begin + delete(path,1,14); + repeat + call:=trim(copy(path,1,6)); + ssid:=copy(path,7,1); + delete(path,1,7); + if ssid<>'' then ssid:=inttostr((ord(ssid[1]) and 15)) + else ssid:='0'; + if path<>'' then digi:=digi+call+'-'+ssid+',' + else digi:=digi+call+'-'+ssid; + until path=''; + end; + AX25Sess->digi:=digi; +end; + +procedure get_call_fm_path(path: string; var callto,callfrom: string); +var + a_path: array [0..ADDR_MAX_LEN-1] of string; + i: byte; +begin + for i:=0 to ADDR_MAX_LEN-1 do a_path[i]:=''; + try explode(a_path,',',path,ADDR_MAX_LEN); except end; + callto:=a_path[0]; + callfrom:=a_path[1]; +end; + +procedure get_call(src_call: string; var call: string); +var + a_call: array[0..1] of string; + ssid: string; +begin + try explode(a_call,'-',src_call,2); except end; + call:=trim(a_call[0]); + if a_call[1]<>'' then ssid:=trim(a_call[1]) else ssid:='0'; + call:=call+'-'+ssid; +end; +*/ + +int number_digi(unsigned char * path) +{ + UNUSED(path); + int n = 0; + + // a_path: array [0..ADDR_MAX_LEN-3] of string; + // for i:=0 to ADDR_MAX_LEN-3 do a_path[i]:=''; + // try explode(a_path,',',path,ADDR_MAX_LEN-2); except end; + // for i:=0 to ADDR_MAX_LEN-3 do if a_path[i]<>'' then inc(n); + + return n; +} + + + +void get_monitor_path(Byte * path, char * mycall, char * corrcall, char * digi) +{ + Byte * digiptr = digi; + + digi[0] = 0; + + mycall[ConvFromAX25(path, mycall)] = 0; + path += 7; + corrcall[ConvFromAX25(path, corrcall)] = 0; + + while ((path[6] & 1) == 0) // End of call bit + { + if (digi != digiptr) + *(digi++) = ','; + + path += 7; + digi += ConvFromAX25(path, digi); + + if (((path[6] & 128) == 128)) // Digi'd + *(digi++) = '*'; + } + *digi = 0; +} + + +/* + +function reverse_digi(path: string): string; +var + digi: string; + a_path: array [0..ADDR_MAX_LEN-3] of string; + i: word; +begin + digi:=''; + for i:=0 to ADDR_MAX_LEN-3 do a_path[i]:=''; + try explode(a_path,',',path,ADDR_MAX_LEN-2); except end; + for i:=0 to ADDR_MAX_LEN-3 do + if a_path[i]<>'' then + begin + if digi='' then digi:=a_path[i]+digi + else digi:=a_path[i]+','+digi; + end; + result:=digi; +end; + +function direct_addr(path: string): string; +var + s,call,ssid: string; +begin + s:=''; + repeat + call:=copy(path,1,6); + delete(path,1,6); + ssid:=copy(path,1,1); + delete(path,1,1); + if ssid<>'' then ssid:=inttostr((ord(ssid[1]) and 15)); + if s='' then s:=call+'-'+ssid else s:=s+','+call+'-'+ssid; + until path=''; + result:=s; +end; +*/ + +void reverse_addr(Byte * path, Byte * revpath, int Len) +{ + Byte * ptr = path; + Byte * copy = revpath; + int endbit = Len - 1; + int numdigis = (Len - 14) / 7; + int i; + + if (Len < 14) + return; + + Byte digis[57]; // 8 * 7 + null terminator + memset(digis, 0, 57); + Byte * digiptr = digis + 49; // Last Digi + + // remove end of address bit + + path[endbit] &= 0xFE; + + // first reverse dest and origin + + memcpy(copy + 7, ptr, 7); + memcpy(copy, ptr + 7, 7); + + Len -= 14; + ptr += 14; + + for (i = 0; i < numdigis; i++) + { + memcpy(digiptr, ptr, 7); + ptr += 7; + digiptr -= 7; + } + + // Digiptr now points to new first digi + + memcpy(©[14], &digiptr[7], 7 * numdigis); + + path[endbit] |= 1; // restore original end bit + + copy[endbit++] |= 1; + copy[endbit] = 0; // Null terminate + + return; +} + + + +void decode_frame(Byte * frame, int len, Byte * path, string * data, + Byte * pid, Byte * nr, Byte * ns, Byte * f_type, Byte * f_id, + Byte * rpt, Byte * pf, Byte * cr) +{ + int i; + int addr_end; + Byte ctrl; + Byte * savepath = path; + + i = 0; + addr_end = FALSE; + + *cr = SET_R; + *pf = SET_F; + data->Length = 0; + ctrl = 0; + *pid = 0; + *nr = 0; + *ns = 0; + *f_type = 0; + *f_id = 0; + *rpt = FALSE; + + if ((frame[6] & 128) == 128 && (frame[13] & 128) == 0) + *cr = SET_C; + + while (len > i && i < ADDR_MAX_LEN * 7) + { + *path++ = frame[i]; + if ((frame[i] & 1) == 1) + { + addr_end = TRUE; + break; + } + i++; + } + + if (addr_end == 0) + return; + + // clear the c and r bits from address + + savepath[6] &= 0x7f; // Mask + savepath[13] &= 0x7f; // Mask + + *path = 0; // add null terminate + + i++; // Points to ctrl byte + + ctrl = frame[i]; + + if ((ctrl & 16) == 16) + *pf = SET_P; + + if ((ctrl & 1) == 0) // I frame + { + *f_type = I_FRM; + *f_id = I_I; + *nr = (ctrl >> 5); + *ns = (ctrl >> 1) & 7; + } + else + { + // Not I + + *f_type = U_FRM; + + *f_id = ctrl & 239; + + switch (ctrl & 15) + { + case S_RR: + case S_RNR: + case S_REJ: + case S_SREJ: + + *f_type = S_FRM; + } + + if (*f_type == S_FRM) + { + *f_id = ctrl & 15; + *nr = ctrl >> 5; + } + } + + + if (*f_id == I_I || *f_id == U_UI) + { + i++; + *pid = frame[i]; + i++; + if (len > i) + stringAdd(data, &frame[i], len - i); + } + else if (*f_id == U_FRMR) + { + *pid = 0; + i++; + if (len > i) + stringAdd(data, &frame[i], len - i); + } +} + +void ax25_info_init(TAX25Port * AX25Sess) +{ + AX25Sess->info.stat_s_pkt = 0; + AX25Sess->info.stat_s_byte = 0; + AX25Sess->info.stat_r_pkt = 0; + AX25Sess->info.stat_r_byte = 0; + AX25Sess->info.stat_r_fc = 0; + AX25Sess->info.stat_fec_count = 0; + AX25Sess->info.stat_l_r_byte = 0; + AX25Sess->info.stat_l_s_byte = 0; + AX25Sess->info.stat_begin_ses = 0; + AX25Sess->info.stat_end_ses = 0; +} + + +void clr_frm_win(TAX25Port * AX25Sess) +{ + int i; + + for (i = 0; i < 8; i++) + initString(&AX25Sess->frm_win[i]); +} + +void ax25_init() +{ + int snd_ch, port, i; + + for (i = 0; i < 4; i++) + { + initTStringList(&list_exclude_callsigns[i]); + initTStringList(&list_exclude_APRS_frm[i]); + initTStringList(&list_digi_callsigns[i]); + initTStringList(&KISS_acked[i]); + + get_exclude_list(MyDigiCall[i], &list_digi_callsigns[i]); + get_exclude_list(exclude_callsigns[i], &list_exclude_callsigns[i]); + get_exclude_frm(exclude_APRS_frm[i], &list_exclude_APRS_frm[i]); + + } + + initTStringList(&list_incoming_mycalls); +// initTStringList(&list_incoming_mycalls_sock); + + for (snd_ch = 0; snd_ch < 4; snd_ch++) + { + for (port = 0; port < port_num; port++) + { + TAX25Port * AX25Sess = &AX25Port[snd_ch][port]; + + AX25Sess->hi_vs = 0; + AX25Sess->vs = 0; + AX25Sess->vr = 0; + AX25Sess->PID = PID_NO_L3; + initTStringList(&AX25Sess->in_data_buf); + initString(&AX25Sess->out_data_buf); + AX25Sess->t1 = 0; + AX25Sess->t2 = 0; + AX25Sess->t3 = 0; + AX25Sess->i_lo = 0; + AX25Sess->i_hi = 0; + AX25Sess->n1 = 0; + AX25Sess->n2 = 0; + AX25Sess->status = 0; + AX25Sess->clk_frack = 0; + initTStringList(&AX25Sess->frame_buf); + initTStringList(&AX25Sess->I_frame_buf); + initTStringList(&AX25Sess->frm_collector); + AX25Sess->corrcall[0] = 0; + AX25Sess->mycall[0] = 0; + AX25Sess->digi[0] = 0; + AX25Sess->Path[0] = 0; + AX25Sess->kind[0] = 0; + AX25Sess->socket = NULL; + ax25_info_init(AX25Sess); + clr_frm_win(AX25Sess); + } + } +} +/* + +procedure ax25_free; +var + snd_ch,port,i: byte; +begin + for snd_ch:=1 to 4 do + for port:=0 to port_num-1 do + begin + AX25Sess->in_data_buf.Free; + AX25Sess->frame_buf.Free; + AX25Sess->I_frame_buf.Free; + AX25Sess->frm_collector.Free; + end; + for i:=1 to 4 do + begin + all_frame_buf[i].Free; + list_exclude_callsigns[i].Free; + list_exclude_APRS_frm[i].Free; + list_digi_callsigns[i].Free; + end; + list_incoming_mycalls.Free; + list_incoming_mycalls_sock.Free; +end; +*/ +void write_ax25_info(TAX25Port * AX25Sess) +{ + UNUSED(AX25Sess); +} + +/*var + new: boolean; + t: text; + s: string; + time_ses: tdatetime; + time_ses_sec: extended; + call,mycall,spkt,sbyte,rpkt,rbyte,rfc,tcps,rcps,acps,startses,timeses: string; +begin + if stat_log then + begin + time_ses:=AX25Sess->info.stat_end_ses-AX25Sess->info.stat_begin_ses; + time_ses_sec:=time_ses*86400; //âðåìÿ ñåññèè â ñåêóíäàõ + if time_ses_sec<1 then exit; + mycall:=copy(AX25Sess->mycall+' ',1,9); + call:=copy(AX25Sess->corrcall+' ',1,9); + spkt:=copy(inttostr(AX25Sess->info.stat_s_pkt)+' ',1,6); + sbyte:=copy(inttostr(AX25Sess->info.stat_s_byte)+' ',1,9); + rpkt:=copy(inttostr(AX25Sess->info.stat_r_pkt)+' ',1,6); + rbyte:=copy(inttostr(AX25Sess->info.stat_r_byte)+' ',1,9); + rfc:=copy(inttostr(AX25Sess->info.stat_r_fc)+' ',1,6); + tcps:=copy(inttostr(round(AX25Sess->info.stat_s_byte/time_ses_sec))+' ',1,5); + rcps:=copy(inttostr(round(AX25Sess->info.stat_r_byte/time_ses_sec))+' ',1,5); + acps:=copy(inttostr(round(AX25Sess->info.stat_s_byte/time_ses_sec+AX25Sess->info.stat_r_byte/time_ses_sec))+' ',1,5); + timeses:=FormatDateTime('hh:mm:ss',time_ses); + startses:=FormatDateTime('dd-mm-yy hh:mm:ss',AX25Sess->info.stat_begin_ses); + s:=mycall+' '+call+' '+spkt+' '+sbyte+' '+rpkt+' '+rbyte+' '+rfc+' '+tcps+' '+rcps+' '+acps+' '+startses+' '+timeses; + assignfile(t,'log.txt'); + if FileSearch('log.txt','')='' then new:=TRUE else new:=FALSE; + if new then + begin + rewrite(t); + writeln(t,'Mycall CorrCall TXPkt TXByte RXPkt RXByte FCPkt TXCPS RXCPS T.CPS Begin session SesTime'); + writeln(t,'-------- --------- ------ --------- ------ --------- ------ ----- ----- ----- ----------------- --------'); + end + else append(t); + if (AX25Sess->info.stat_s_byte>0) or (AX25Sess->info.stat_r_byte>0) then writeln(t,s); + closefile(t); + end; +end; + +end. +*/ + + +/* +Copyright 2001-2018 John Wiseman G8BPQ + +This file is part of LinBPQ/BPQ32. + +LinBPQ/BPQ32 is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +LinBPQ/BPQ32 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses +*/ + + + +// Monitor Code - from moncode.asm + + +#define CMDBIT 4 // CURRENT MESSAGE IS A COMMAND +#define RESP 2 // CURRENT MSG IS RESPONSE +#define VER1 1 // CURRENT MSG IS VERSION 1 + + +#define UI 3 +#define SABM 0x2F +#define DISC 0x43 +#define DM 0x0F +#define UA 0x63 +#define FRMR 0x87 +#define RR 1 +#define RNR 5 +#define REJ 9 + +#define PFBIT 0x10 // POLL/FINAL BIT IN CONTROL BYTE + +#define NETROM_PID 0xCF +#define IP_PID 0xCC +#define ARP_PID 0xCD + +#define NODES_SIG 0xFF + +/* + +DllExport int APIENTRY SetTraceOptions(int mask, int mtxparam, int mcomparam) +{ + + // Sets the tracing options for DecodeFrame. Mask is a bit + // mask of ports to monitor (ie 101 binary will monitor ports + // 1 and 3). MTX enables monitoring on transmitted frames. MCOM + // enables monitoring of protocol control frames (eg SABM, UA, RR), + // as well as info frames. + + MMASK = mask; + MTX = mtxparam; + MCOM = mcomparam; + + return (0); +} + +DllExport int APIENTRY SetTraceOptionsEx(int mask, int mtxparam, int mcomparam, int monUIOnly) +{ + + // Sets the tracing options for DecodeFrame. Mask is a bit + // mask of ports to monitor (ie 101 binary will monitor ports + // 1 and 3). MTX enables monitoring on transmitted frames. MCOM + // enables monitoring of protocol control frames (eg SABM, UA, RR), + // as well as info frames. + + + MMASK = mask; + MTX = mtxparam; + MCOM = mcomparam; + MUIONLY = monUIOnly; + + return 0; +} + +*/ + +#define USHORT unsigned short + +UCHAR MCOM = 1; +UCHAR MTX = 1; +ULONG MMASK = 0xF; +UCHAR MUIONLY = 0; + +#define SREJ 0x0D +#define SABME 0x6F +#define XID 0xAF +#define TEST 0xE3 + +#define L4BUSY 0x80 // BNA - DONT SEND ANY MORE +#define L4NAK 0x40 // NEGATIVE RESPONSE FLAG +#define L4MORE 0x20 // MORE DATA FOLLOWS - FRAGMENTATION FLAG + +#define L4CREQ 1 // CONNECT REQUEST +#define L4CACK 2 // CONNECT ACK +#define L4DREQ 3 // DISCONNECT REQUEST +#define L4DACK 4 // DISCONNECT ACK +#define L4INFO 5 // INFORMATION +#define L4IACK 6 // INFORMATION ACK + +//#pragma pack(1) +#pragma pack(push, 1) + +struct myin_addr { + union { + struct { unsigned char s_b1, s_b2, s_b3, s_b4; } S_un_b; + struct { unsigned short s_w1, s_w2; } S_un_w; + unsigned long addr; + }; +}; + + +typedef struct _IPMSG +{ + // FORMAT OF IP HEADER + // + // NOTE THESE FIELDS ARE STORED HI ORDER BYTE FIRST (NOT NORMAL 8086 FORMAT) + + UCHAR VERLEN; // 4 BITS VERSION, 4 BITS LENGTH + UCHAR TOS; // TYPE OF SERVICE + USHORT IPLENGTH; // DATAGRAM LENGTH + USHORT IPID; // IDENTIFICATION + USHORT FRAGWORD; // 3 BITS FLAGS, 13 BITS OFFSET + UCHAR IPTTL; + UCHAR IPPROTOCOL; // HIGHER LEVEL PROTOCOL + USHORT IPCHECKSUM; // HEADER CHECKSUM + struct myin_addr IPSOURCE; + struct myin_addr IPDEST; + + UCHAR Data; + +} IPMSG, *PIPMSG; + + +typedef struct _TCPMSG +{ + + // FORMAT OF TCP HEADER WITHIN AN IP DATAGRAM + + // NOTE THESE FIELDS ARE STORED HI ORDER BYTE FIRST (NOT NORMAL 8086 FORMAT) + + USHORT SOURCEPORT; + USHORT DESTPORT; + + ULONG SEQNUM; + ULONG ACKNUM; + + UCHAR TCPCONTROL; // 4 BITS DATA OFFSET 4 RESERVED + UCHAR TCPFLAGS; // (2 RESERVED) URG ACK PSH RST SYN FIN + + USHORT WINDOW; + USHORT CHECKSUM; + USHORT URGPTR; + + +} TCPMSG, *PTCPMSG; + +typedef struct _UDPMSG +{ + + // FORMAT OF UDP HEADER WITHIN AN IP DATAGRAM + + // NOTE THESE FIELDS ARE STORED HI ORDER BYTE FIRST (NOT NORMAL 8086 FORMAT) + + USHORT SOURCEPORT; + USHORT DESTPORT; + USHORT LENGTH; + USHORT CHECKSUM; + UCHAR UDPData[0]; + +} UDPMSG, *PUDPMSG; + +// ICMP MESSAGE STRUCTURE + +typedef struct _ICMPMSG +{ + // FORMAT OF ICMP HEADER WITHIN AN IP DATAGRAM + + // NOTE THESE FIELDS ARE STORED HI ORDER BYTE FIRST (NOT NORMAL 8086 FORMAT) + + UCHAR ICMPTYPE; + UCHAR ICMPCODE; + USHORT ICMPCHECKSUM; + + USHORT ICMPID; + USHORT ICMPSEQUENCE; + UCHAR ICMPData[0]; + +} ICMPMSG, *PICMPMSG; + + +typedef struct _L3MESSAGE +{ + // + // NETROM LEVEL 3 MESSAGE - WITHOUT L2 INFO + // + UCHAR L3SRCE[7]; // ORIGIN NODE + UCHAR L3DEST[7]; // DEST NODE + UCHAR L3TTL; // TX MONITOR FIELD - TO PREVENT MESSAGE GOING // ROUND THE NETWORK FOR EVER DUE TO ROUTING LOOP +// +// NETROM LEVEL 4 DATA +// + UCHAR L4INDEX; // TRANSPORT SESSION INDEX + UCHAR L4ID; // TRANSPORT SESSION ID + UCHAR L4TXNO; // TRANSMIT SEQUENCE NUMBER + UCHAR L4RXNO; // RECEIVE (ACK) SEQ NUMBER + UCHAR L4FLAGS; // FRAGMENTATION, ACK/NAK, FLOW CONTROL AND MSG TYPE BITS + + UCHAR L4DATA[236]; //DATA + +} L3MESSAGE, *PL3MESSAGE; + + +typedef struct _MESSAGE +{ + // BASIC LINK LEVEL MESSAGE BUFFER LAYOUT + + struct _MESSAGE * CHAIN; + + UCHAR PORT; + USHORT LENGTH; + + UCHAR DEST[7]; + UCHAR ORIGIN[7]; + + // MAY BE UP TO 56 BYTES OF DIGIS + + UCHAR CTL; + UCHAR PID; + + union + { /* array named screen */ + UCHAR L2DATA[256]; + struct _L3MESSAGE L3MSG; + }; + + +}MESSAGE, *PMESSAGE; + +//#pragma pack() +#pragma pack(pop) + +char * strlop(char * buf, char delim); +UCHAR * DisplayINP3RIF(UCHAR * ptr1, UCHAR * ptr2, unsigned int msglen); +char * DISPLAY_NETROM(MESSAGE * ADJBUFFER, UCHAR * Output, int MsgLen); +UCHAR * DISPLAYIPDATAGRAM(IPMSG * IP, UCHAR * Output, int MsgLen); +char * DISPLAYARPDATAGRAM(UCHAR * Datagram, UCHAR * Output); + +int CountBits(unsigned long in) +{ + int n = 0; + while (in) + { + if (in & 1) n++; + in >>= 1; + } + return n; +} + +BOOL ConvToAX25(char * callsign, unsigned char * ax25call) +{ + int i; + + memset(ax25call, 0x40, 6); // in case short + ax25call[6] = 0x60; // default SSID + + for (i = 0; i < 7; i++) + { + if (callsign[i] == '-') + { + // + // process ssid and return + // + i = atoi(&callsign[i + 1]); + + if (i < 16) + { + ax25call[6] |= i << 1; + return (TRUE); + } + return (FALSE); + } + + if (callsign[i] == 0 || callsign[i] == 13 || callsign[i] == ' ' || callsign[i] == ',') + { + // + // End of call - no ssid + // + return (TRUE); + } + + ax25call[i] = callsign[i] << 1; + } + + // + // Too many chars + // + + return (FALSE); +} + + +int ConvFromAX25(unsigned char * incall, char * outcall) +{ + int in, out = 0; + unsigned char chr; + + memset(outcall, 0x20, 10); + + for (in = 0; in < 6; in++) + { + chr = incall[in]; + if (chr == 0x40) + break; + chr >>= 1; + outcall[out++] = chr; + } + + chr = incall[6]; // ssid + + if (chr == 0x42) + { + outcall[out++] = '-'; + outcall[out++] = 'T'; + return out; + } + + if (chr == 0x44) + { + outcall[out++] = '-'; + outcall[out++] = 'R'; + return out; + } + + chr >>= 1; + chr &= 15; + + if (chr > 0) + { + outcall[out++] = '-'; + if (chr > 9) + { + chr -= 10; + outcall[out++] = '1'; + } + chr += 48; + outcall[out++] = chr; + } + return (out); +} + +TKISSMode ** KissConnections = NULL; +int KISSConCount = 0; + +#define FEND 0xc0 +#define FESC 0xDB +#define TFEND 0xDC +#define TFESC 0xDD +#define KISS_ACKMODE 0x0C +#define KISS_DATA 0 + +int KISS_encode(UCHAR * KISSBuffer, int port, string * frame, int TXMON); + +void KISS_init() +{ + int i; + + KISS.data_in = newString(); + + // initTStringList(KISS.socket); + + for (i = 0; i < 4; i++) + { + initTStringList(&KISS.buffer[i]); + } +} + +void ProcessKISSFrame(void * socket, UCHAR * Msg, int Len); + +void KISSDataReceived(void * socket, unsigned char * data, int length) +{ + int i; + unsigned char * ptr1, *ptr2; + int Length; + + TKISSMode * KISS = NULL; + + if (KISSConCount == 0) + return; + + for (i = 0; i < KISSConCount; i++) + { + if (KissConnections[i]->Socket == socket) + { + KISS = KissConnections[i]; + break; + } + } + + if (KISS == NULL) + return; + + stringAdd(KISS->data_in, data, length); + + if (KISS->data_in->Length > 10000) // Probably AGW Data on KISS Port + { + KISS->data_in->Length = 0; + return; + } + + ptr1 = KISS->data_in->Data; + Length = KISS->data_in->Length; + + + while ((ptr2 = memchr(ptr1, FEND, Length))) + { + int Len = (ptr2 - ptr1); + + if (Len == 0) + { + // Start of frame + + mydelete(KISS->data_in, 0, 1); + + ptr1 = KISS->data_in->Data; + Length = KISS->data_in->Length; + + continue; + } + + // Process Frame + + if (Len < 350) // Drop obviously corrupt frames + ProcessKISSFrame(socket, ptr1, Len); + + mydelete(KISS->data_in, 0, Len + 1); + + ptr1 = KISS->data_in->Data; + Length = KISS->data_in->Length; + + } +} + +void analiz_frame(int snd_ch, string * frame, void * socket, boolean fecflag); + + +void ProcessKISSFrame(void * socket, UCHAR * Msg, int Len) +{ + int n = Len; + UCHAR c; + int ESCFLAG = 0; + UCHAR * ptr1, *ptr2; + int Chan; + int Opcode; + string * TXMSG; + unsigned short CRC; + UCHAR CRCString[2]; + + ptr1 = ptr2 = Msg; + + while (n--) + { + c = *(ptr1++); + + if (ESCFLAG) + { + // + // FESC received - next should be TFESC or TFEND + + ESCFLAG = 0; + + if (c == TFESC) + c = FESC; + + if (c == TFEND) + c = FEND; + + } + else + { + switch (c) + { + case FEND: + + // + // Either start of message or message complete + // + + // npKISSINFO->MSGREADY = TRUE; + return; + + case FESC: + + ESCFLAG = 1; + continue; + + } + } + + // + // Ok, a normal char + // + + *(ptr2++) = c; + + } + Len = ptr2 - Msg; + + Chan = (Msg[0] >> 4); + Opcode = Msg[0] & 0x0f; + + if (Chan > 3) + return; + + switch (Opcode) + { + case KISS_ACKMODE: + + // How best to do ACKMODE?? I think pass whole frame including CMD and ack bytes to all_frame_buf + + // But ack should only be sent to client that sent the message - needs more thought! + + TXMSG = newString(); + stringAdd(TXMSG, &Msg[0], Len); // include Control + + CRC = get_fcs(&Msg[3], Len - 3); // exclude control and ack bytes + + CRCString[0] = CRC & 0xff; + CRCString[1] = CRC >> 8; + + stringAdd(TXMSG, CRCString, 2); + + // Ackmode needs to know where to send ack back to, so save socket on end of data + + stringAdd(TXMSG, (unsigned char *)&socket, sizeof(socket)); + + // if KISS Optimise see if frame is really needed + + if (!KISS_opt[Chan]) + Add(&KISS.buffer[Chan], TXMSG); + else + { + if (add_raw_frames(Chan, TXMSG, &KISS.buffer[Chan])) + Add(&KISS.buffer[Chan], TXMSG); + } + + + return; + + case KISS_DATA: + + TXMSG = newString(); + stringAdd(TXMSG, &Msg[1], Len - 1); // include Control + + analiz_frame(Chan, TXMSG, socket, 0); + + free(TXMSG); + return; + } + + // Still need to process kiss control frames +} + + +void KISS_add_stream(void * Socket) +{ + // Add a new connection. Called when QT accepts an incoming call} + + TKISSMode * KISS; + + KissConnections = realloc(KissConnections, (KISSConCount + 1) * sizeof(void *)); + + KISS = KissConnections[KISSConCount++] = malloc(sizeof(KISS)); + + KISS->Socket = Socket; + KISS->data_in = newString(); + +} + +void KISS_del_socket(void * socket) +{ + int i; + + TKISSMode * KISS = NULL; + + if (KISSConCount == 0) + return; + + for (i = 0; i < KISSConCount; i++) + { + if (KissConnections[i]->Socket == socket) + { + KISS = KissConnections[i]; + break; + } + } + + if (KISS == NULL) + return; + + // Need to remove entry and move others down + + KISSConCount--; + + while (i < KISSConCount) + { + KissConnections[i] = KissConnections[i + 1]; + i++; + } +} + +TAX25Port * get_free_port(int snd_ch); + +TAX25Port * KISSConnectOut(void * Sess, char * CallFrom, char * CallTo, char * Digis, int Chan, void * Socket) +{ + TAX25Port * AX25Sess = 0; + char path[128]; + Byte axpath[80]; + + AX25Sess = get_free_port(Chan); + + if (AX25Sess) + { + AX25Sess->snd_ch = Chan; + AX25Sess->Sess = Sess; + strcpy(AX25Sess->mycall, CallFrom); + strcpy(AX25Sess->corrcall, CallTo); + AX25Sess->PID = 0xf0; + + sprintf(path, "%s,%s", CallTo, CallFrom); + + if (Digis) + { + strcat(path, ","); + strcat(path, Digis); + } + + AX25Sess->digi[0] = 0; + + // rst_timer(snd_ch, free_port); + + strcpy(AX25Sess->kind, "Outgoing"); + AX25Sess->socket = Socket; + + AX25Sess->pathLen = get_addr(path, axpath); + + if (AX25Sess->pathLen == 0) + return AX25Sess; // Invalid Path + + strcpy((char *)AX25Sess->Path, (char *)axpath); + reverse_addr(axpath, AX25Sess->ReversePath, AX25Sess->pathLen); + + set_link(AX25Sess, AX25Sess->Path); + return AX25Sess; + } + return 0; +} + + + +// Monitor Code - from moncode.asm + + +#define CMDBIT 4 // CURRENT MESSAGE IS A COMMAND +#define RESP 2 // CURRENT MSG IS RESPONSE +#define VER1 1 // CURRENT MSG IS VERSION 1 + + +#define UI 3 +#define SABM 0x2F +#define DISC 0x43 +#define DM 0x0F +#define UA 0x63 +#define FRMR 0x87 +#define RR 1 +#define RNR 5 +#define REJ 9 + +#define SREJ 0x0D +#define SABME 0x6F +#define XID 0xAF +#define TEST 0xE3 + + +#define PFBIT 0x10 // POLL/FINAL BIT IN CONTROL BYTE + +#define NETROM_PID 0xCF +#define IP_PID 0xCC +#define ARP_PID 0xCD + +#define NODES_SIG 0xFF + +char ShortDT[] = "HH:MM:SS"; + +char * ShortDateTime() +{ + struct tm * tm; + time_t NOW = time(NULL); + + tm = gmtime(&NOW); + + sprintf(ShortDT, "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec); + return ShortDT; +} + + + +char FrameData[1024] = ""; + +char * frame_monitor(string * frame, char * code, int tx_stat) +{ + char mon_frm[512]; + char Path[256]; + + char * frm = "???"; + Byte * datap; + Byte _data[512] = ""; + Byte * p_data = _data; + int _datalen; +; + char CallFrom[10], CallTo[10], Digi[80]; + + char TR = 'R'; + char codestr[16] = ""; + + integer i; + int len; + + + Byte pid, nr, ns, f_type, f_id; + Byte rpt, cr, pf; + Byte path[80]; + char c; + const char * p; + + string * data = newString(); + + if (code[0] && strlen(code) < 14) + sprintf(codestr, "[%s]", code); + + if (tx_stat) + TR = 'T'; + + if (tx_stat) // TX frame has control byte + + decode_frame(frame->Data +1 , frame->Length - 1, path, data, &pid, &nr, &ns, &f_type, &f_id, &rpt, &pf, &cr); + else + decode_frame(frame->Data, frame->Length, path, data, &pid, &nr, &ns, &f_type, &f_id, &rpt, &pf, &cr); + + datap = data->Data; + + len = data->Length; + + // if (pid == 0xCF) + // data = parse_NETROM(data, f_id); + // IP parsing + // else if (pid == 0xCC) + // data = parse_IP(data); + // ARP parsing + // else if (pid == 0xCD) + // data = parse_ARP(data); + // + + if (len > 0) + { + for (i = 0; i < len; i++) + { + if (datap[i] > 31 || datap[i] == 13 || datap[i] == 9) + *(p_data++) = datap[i]; + } + } + + _datalen = p_data - _data; + + if (_datalen) + { + Byte * ptr = _data; + i = 0; + + // remove successive cr or cr on end while (i < _datalen) + + while (i < _datalen) + { + if ((_data[i] == 13) && (_data[i + 1] == 13)) + i++; + else + *(ptr++) = _data[i++]; + } + + if (*(ptr - 1) == 13) + ptr--; + + *ptr = 0; + + _datalen = ptr - _data; + } + + get_monitor_path(path, CallTo, CallFrom, Digi); + + if (cr) + { + c = 'C'; + if (pf) + p = " P"; + else p = ""; + } + else + { + c = 'R'; + if (pf) + p = " F"; + else + p = ""; + } + + switch (f_id) + { + case I_I: + + frm = "I"; + break; + + case S_RR: + + frm = "RR"; + break; + + case S_RNR: + + frm = "RNR"; + break; + + case S_REJ: + + frm = "REJ"; + break; + + case S_SREJ: + + frm = "SREJ"; + break; + + case U_SABM: + + frm = "SABM"; + break; + + case SABME: + + frm = "SABME"; + break; + + case U_DISC: + + frm = "DISC"; + break; + + case U_DM: + + frm = "DM"; + break; + + case U_UA: + + frm = "UA"; + break; + + case U_FRMR: + + frm = "FRMR"; + break; + + case U_UI: + + frm = "UI"; + } + +// 07:29:42T G8BPQ - 2 > TEST Port = 20 < UI > : +// helllo +// 07:30: 8T G8BPQ - 2 > ID Port = 20 < UI C > : +// Network node(BPQ) + + if (Digi[0]) +// sprintf(Path, "Fm %s To %s Via %s <%s %c%s", CallFrom, CallTo, Digi, frm, c, p); + sprintf(Path, "%s%c %s>%s,%s <%s %c%s", ShortDateTime(), TR, CallFrom, CallTo, Digi, frm, c, p); + else +// sprintf(Path, "Fm %s To %s <%s %c %s", CallFrom, CallTo, frm, c, p); + sprintf(Path, "%s%c %s>%s <%s %c%s", ShortDateTime(), TR, CallFrom, CallTo, frm, c, p); + + + switch (f_type) + { + case I_FRM: + + //mon_frm = Path + ctrl + ' R' + inttostr(nr) + ' S' + inttostr(ns) + ' pid=' + dec2hex(pid) + ' Len=' + inttostr(len) + ' >' + time_now + #13 + _data + #13#13; + sprintf(mon_frm, "%s R%d S%d>%s\r%s\r", Path, nr, ns, codestr, _data); + + break; + + case U_FRM: + + if (f_id == U_UI) + { + sprintf(mon_frm, "%s>:\r%s\r", Path, _data); // "= Path + ctrl + '>' + time_now + #13; + } + else if (f_id == U_FRMR) + { + sprintf(mon_frm, "%s>%02x %02x %02x\r", Path, datap[0], datap[1], datap[2]); // "= Path + ctrl + '>' + time_now + #13; + } + else + sprintf(mon_frm, "%s>%s\r", Path, codestr); // "= Path + ctrl + '>' + time_now + #13; + + break; + + case S_FRM: + + // mon_frm = Path + ctrl + ' R' + inttostr(nr) + ' >' + time_now + #13; + sprintf(mon_frm, "%s R%d>%s\r", Path, nr, codestr); // "= Path + ctrl + '>' + time_now + #13; + + break; + + } + sprintf(FrameData, "%s", mon_frm); + + freeString(data); + return FrameData; +} + + + + + + + + diff --git a/ax25.h b/ax25.h new file mode 100644 index 0000000..8895eee --- /dev/null +++ b/ax25.h @@ -0,0 +1,304 @@ +// ax.25 code for QtTERMTCP KISS Mode + +// based on SoundModem/QtSoundModem ax.25 code + +// The underlying code allows for up to four KISS ports, but at the moment +// the config and user interface only supports 1 + +// The code supports KISS over a Serial port or TCP connection + +#include +#include +#include +#include +#include +#include + +#define UNUSED(x) (void)(x) + +#define single float +#define boolean int +#define Byte unsigned char // 0 to 255 +#define Word unsigned short // 0 to 65,535 +#define SmallInt short // -32,768 to 32,767 +#define LongWord unsigned int // 0 to 4,294,967,295 +// Int6 : Cardinal; // 0 to 4,294,967,295 +#define LongInt int // -2,147,483,648 to 2,147,483,647 +#define Integer int // -2,147,483,648 to 2,147,483,647 +//#define Int64 long long // -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 + +//#define Byte unsigned char // 0 to 255 +#define word unsigned short // 0 to 65,535 +#define smallint short // -32,768 to 32,767 +#define longword unsigned int // 0 to 4,294,967,295 + // Int6 : Cardinal; // 0 to 4,294,967,295 +#define longint int // -2,147,483,648 to 2,147,483,647 +#define integer int // -2,147,483,648 to 2,147,483,647 + +typedef unsigned long ULONG; + +#define UCHAR unsigned char +#define UINT unsigned int +#define BOOL int +#define TRUE 1 +#define FALSE 0 + +#define FEND 0xc0 +#define FESC 0xDB +#define TFEND 0xDC +#define TFESC 0xDD + +#define port_num 32 // ?? Max AGW sessions +#define PKT_ERR 15 // Minimum packet size, bytes +#define I_MAX 7 // Maximum number of packets + + +// Status flags + +#define STAT_NO_LINK 0 +#define STAT_LINK 1 +#define STAT_CHK_LINK 2 +#define STAT_WAIT_ANS 3 +#define STAT_TRY_LINK 4 +#define STAT_TRY_UNLINK 5 + + + // Ñmd,Resp,Poll,Final,Digipeater flags +#define SET_P 1 +#define SET_F 0 +#define SET_C 1 +#define SET_R 0 +#define SET_NO_RPT 0 +#define SET_RPT 1 + // Frame ID flags +#define I_FRM 0 +#define S_FRM 1 +#define U_FRM 2 +#define I_I 0 +#define S_RR 1 +#define S_RNR 5 +#define S_REJ 9 +#define S_SREJ 0x0D +#define U_SABM 47 +#define U_DISC 67 +#define U_DM 15 +#define U_UA 99 +#define U_FRMR 135 +#define U_UI 3 + // PID flags +#define PID_X25 0x01 // 00000001-CCIT X25 PLP +#define PID_SEGMENT 0x08 // 00001000-Segmentation fragment +#define PID_TEXNET 0xC3 // 11000011-TEXNET Datagram Protocol +#define PID_LQ 0xC4 // 11001000-Link Quality Protocol +#define PID_APPLETALK 0xCA // 11001010-Appletalk +#define PID_APPLEARP 0xCB // 11001011-Appletalk ARP +#define PID_IP 0xCC // 11001100-ARPA Internet Protocol +#define PID_ARP 0xCD // 11001101-ARPA Address Resolution Protocol +#define PID_NET_ROM 0xCF // 11001111-NET/ROM + +#define FX25_LOAD 1 + +#define MODE_OUR 0 +#define MODE_OTHER 1 +#define MODE_RETRY 2 + +#define TIMER_FREE 0 +#define TIMER_BUSY 1 +#define TIMER_OFF 2 +#define TIMER_EVENT_ON 3 +#define TIMER_EVENT_OFF 4 + + +typedef struct string_T +{ + unsigned char * Data; + int Length; + int AllocatedLength; // A reasonable sized block is allocated at the start to speed up adding chars + +}string; + +typedef struct TStringList_T +{ + int Count; + string ** Items; + +} TStringList; + + +typedef struct AGWUser_t +{ + void *socket; + string * data_in; + TStringList AGW_frame_buf; + boolean Monitor; + boolean Monitor_raw; + boolean reportFreqAndModem; // Can report modem and frequency to host + +} AGWUser; + +typedef struct TAX25Info_t +{ + longint stat_s_pkt; + longint stat_s_byte; + longint stat_r_pkt; + longint stat_r_byte; + longint stat_r_fc; + longint stat_fec_count; + time_t stat_begin_ses; + time_t stat_end_ses; + longint stat_l_r_byte; + longint stat_l_s_byte; + +} TAX25Info; + +typedef struct TAX25Port_t +{ + Byte hi_vs; + Byte vs; + Byte vr; + Byte PID; + TStringList in_data_buf; + TStringList frm_collector; + string frm_win[8]; + string out_data_buf; + word t1; + word t2; + word t3; + Byte i_lo; + Byte i_hi; + word n1; + word n2; + word IPOLL_cnt; + TStringList frame_buf; //áóôåð êàäðîâ íà ïåðåäà÷ó + TStringList I_frame_buf; + Byte status; + word clk_frack; + char corrcall[10]; + char mycall[10]; + UCHAR digi[56]; + UCHAR Path[80]; // Path in ax25 format - added to save building it each time + UCHAR ReversePath[80]; + int snd_ch; // Simplifies parameter passing + int port; + int pathLen; + void * socket; + void * Sess; + char kind[16]; + TAX25Info info; +} TAX25Port; + +typedef struct TKISSMode_t +{ + string * data_in; + void * Socket; // Used as a key + + // Not sure what rest are used for. Seems to be one per channel + + TStringList buffer[4]; // Outgoing Frames + +} TKISSMode; + +// Dephi emulation functions + +string * Strings(TStringList * Q, int Index); +void Clear(TStringList * Q); +int Count(TStringList * List); + +string * newString(); +string * copy(string * Source, int StartChar, int Count); +TStringList * newTStringList(); + +void freeString(string * Msg); +void initString(string * S); +void initTStringList(TStringList* T); + +// Two delete() This is confusing!! +// Not really - one acts on String, other TStringList + +void Delete(TStringList * Q, int Index); +void mydelete(string * Source, int StartChar, int Count); +void move(UCHAR * SourcePointer, UCHAR * DestinationPointer, int CopyCount); +void fmove(float * SourcePointer, float * DestinationPointer, int CopyCount); +void setlength(string * Msg, int Count); // Set string length +string * stringAdd(string * Msg, UCHAR * Chars, int Count); // Extend string +void Assign(TStringList * to, TStringList * from); // Duplicate from to to +string * duplicateString(string * in); +int my_indexof(TStringList * l, string * s); +boolean compareStrings(string * a, string * b); +int Add(TStringList * Q, string * Entry); +void Debugprintf(const char * format, ...); +void ax25_info_init(TAX25Port * AX25Sess); +void clr_frm_win(TAX25Port * AX25Sess); +void decode_frame(Byte * frame, int len, Byte * path, string * data, + Byte * pid, Byte * nr, Byte * ns, Byte * f_type, Byte * f_id, + Byte * rpt, Byte * pf, Byte * cr); +#ifdef __cplusplus +extern "C" void KISSSendtoServer(myTcpSocket* Socket, char * Data, int Length); +extern "C" void monitor_frame(int snd_ch, string * frame, char * code, int tx, int excluded); +extern "C" void WriteDebugLog(char * Mess); +extern "C" void SendtoTerm(Ui_ListenSession * Sess, char * Msg, int Len); +extern "C" void ClearSessLabel(Ui_ListenSession * Sess); +extern "C" void rst_timer(TAX25Port * AX25Sess); +extern "C" void Send_UI(int port, Byte PID, char * CallFrom, char *CallTo, Byte * Msg, int MsgLen); +#else +void monitor_frame(int snd_ch, string * frame, char * code, int tx, int excluded); +void SendtoTerm(void * Sess, char * Msg, int Len); +void ClearSessLabel(void * Sess); +void WriteDebugLog(char * Mess); +void AX25_disc(TAX25Port * AX25Sess, Byte mode); +void rst_timer(TAX25Port * AX25Sess); +void Send_UI(int port, Byte PID, char * CallFrom, char *CallTo, Byte * Msg, int MsgLen); +#endif + +BOOL ConvToAX25(char * callsign, unsigned char * ax25call); +int ConvFromAX25(unsigned char * incall, char * outcall); +void reverse_addr(Byte * path, Byte * revpath, int Len); +void set_DM(int snd_ch, Byte * path); +void set_link(TAX25Port * AX25Sess, UCHAR * axpath); +boolean is_last_digi(Byte *path); +boolean is_correct_path(Byte * path, Byte pid); +int number_digi(unsigned char * path); +void AX25_conn(TAX25Port * AX25Sess, int snd_ch, Byte mode); +void write_ax25_info(TAX25Port * AX25Sess); +void rst_values(TAX25Port * AX25Sess); + +#ifdef __cplusplus +extern "C" +{ +#endif + +extern boolean dyn_frack[4]; +extern Byte recovery[4]; +extern Byte users[4]; + +extern int resptime[4]; +extern int slottime[4]; +extern int persist[4]; +extern int kisspaclen[4]; +extern int fracks[4]; +extern int frack_time[4]; +extern int idletime[4]; +extern int redtime[4]; +extern int IPOLL[4]; +extern int maxframe[4]; +extern int TXFrmMode[4]; + +extern char MyDigiCall[4][512]; +extern char exclude_callsigns[4][512]; +extern char exclude_APRS_frm[4][512]; + +extern TStringList list_exclude_callsigns[4]; +extern TStringList list_exclude_APRS_frm[4]; +extern TStringList list_digi_callsigns[4]; + +extern int max_frame_collector[4]; +extern boolean KISS_opt[4]; + +extern TAX25Port AX25Port[4][port_num]; + +extern TStringList KISS_acked[]; +extern TStringList KISS_iacked[]; + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/ax25_l2.c b/ax25_l2.c new file mode 100644 index 0000000..60d587c --- /dev/null +++ b/ax25_l2.c @@ -0,0 +1,1726 @@ +/* +Copyright (C) 2019-2020 Andrei Kopanchuk UZ7HO + +This file is part of QtSoundModem + +QtSoundModem is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +QtSoundModem is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with QtSoundModem. If not, see http://www.gnu.org/licenses + +*/ + +// UZ7HO Soundmodem Port by John Wiseman G8BPQ + +#include "ax25.h" + +UCHAR TimerEvent = TIMER_EVENT_OFF; +extern int busy; +int listenEnable; +void * KISSSockCopy[4]; + +string * make_frame(string * data, Byte * path, Byte pid, Byte nr, Byte ns, Byte f_type, Byte f_id, boolean rpt, boolean pf, boolean cr); +void rst_t3(TAX25Port * AX25Sess); +void CheckUIFrame(unsigned char * path, string * data); +TAX25Port * get_user_port(int snd_ch, Byte * path); + +void inc_frack(TAX25Port * AX25Sess) +{ + AX25Sess->clk_frack++; +} + + +void rst_frack(TAX25Port * AX25Sess) +{ + AX25Sess->clk_frack = 0; +} + +void inc_t1(TAX25Port * AX25Sess) +{ + AX25Sess->t1++; +} + +void rst_t1(TAX25Port * AX25Sess) +{ + AX25Sess->t1 = 0; +} + +void inc_t3(TAX25Port * AX25Sess) +{ + AX25Sess->t3++; +} + +void rst_t3(TAX25Port * AX25Sess) +{ + AX25Sess->t3 = 0; +} + + +void rst_values(TAX25Port * AX25Sess) +{ + AX25Sess->IPOLL_cnt = 0; + AX25Sess->hi_vs = 0; + AX25Sess->vs = 0; + AX25Sess->vr = 0; + Clear(&AX25Sess->I_frame_buf); + Clear(&AX25Sess->in_data_buf); + Clear(&AX25Sess->frm_collector); + + ax25_info_init(AX25Sess); + clr_frm_win(AX25Sess); +} + + +void rst_timer(TAX25Port * AX25Sess) +{ + rst_frack(AX25Sess); + rst_t1(AX25Sess); + rst_t3(AX25Sess); +} + +void upd_i_lo(TAX25Port * AX25Sess, int n) //Update the counter of the first frame in the I-frame buffer +{ + AX25Sess->i_lo = n; +} + +void upd_i_hi(TAX25Port * AX25Sess, int n) //Update last frame counter in I-frame buffer +{ + AX25Sess->i_hi = n; +} + +void upd_vs(TAX25Port * AX25Sess, int n) //Update the counter of the next frame to transmit +{ + AX25Sess->vs = ++n & 7; +} + +void upd_vr(TAX25Port * AX25Sess, int n) //Refresh the counter of the next frame at the reception +{ + AX25Sess->vr = ++n & 7; +} + + +void Frame_Optimize(TAX25Port * AX25Sess, TStringList * buf) +{ + // I think this removes redundant frames from the TX Queue (eg repeated RR frames) + + string * frame; + Byte path[80]; + string * data = newString(); + + Byte pid, nr, ns, f_type, f_id, rpt, cr, pf; + boolean curr_req, optimize; + int i, k; + char need_frm[8] = ""; + int index = 0; + boolean PollRR; + boolean PollREJ; + + PollRR = FALSE; + PollREJ = FALSE; + curr_req = FALSE; + + // Check Poll RR and REJ frame + + i = 0; + + while (i < buf->Count && !PollREJ) + { + frame = Strings(buf, i); + // TX frame has kiss control on front + + decode_frame(frame->Data + 1, frame->Length - 1, path, data, &pid, &nr, &ns, &f_type, &f_id, &rpt, &pf, &cr); + + if (cr == SET_R && pf == SET_P) + { + if (f_id == S_REJ) + PollREJ = TRUE; + else if (f_id == S_RR && nr == AX25Sess->vr) + PollRR = TRUE; + } + i++; + } + + // Performance of the REJ Cards: Optional Rej Cards + + i = 0; + + while (i < buf->Count) + { + optimize = TRUE; + frame = Strings(buf, i); + decode_frame(frame->Data + 1, frame->Length - 1, path, data, &pid, &nr, &ns, &f_type, &f_id, &rpt, &pf, &cr); + + if (f_id == S_REJ && cr == SET_R) + { + if ((pf == SET_F && PollREJ) || nr != AX25Sess->vr) + { + Debugprintf("Optimizer dropping REJ nr %d vr %d pf %d PollREJ %d", nr, AX25Sess->vr, pf, PollREJ); + Delete(buf, i); + optimize = FALSE; + } + if (nr == AX25Sess->vr) + curr_req = TRUE; + } + if (optimize) + i++; + } + + // Performance Options + + i = 0; + + while (i < buf->Count) + { + need_frm[0] = 0; + index = 0; + k = AX25Sess->i_lo; + + while (k != AX25Sess->vs) + { + need_frm[index++] = k + 'A'; + k++; + k &= 7; + } + + optimize = TRUE; + + frame = Strings(buf, i); + + decode_frame(frame->Data +1 , frame->Length - 1, path, data, &pid, &nr, &ns, &f_type, &f_id, &rpt, &pf, &cr); + + if (f_id == S_RR) + { + // RR Cards Methods: Optional RR, F Cards + if (cr == SET_R) + { + if (nr != AX25Sess->vr || ((pf == SET_F) && PollRR) || curr_req) + { + Debugprintf("Optimizer dropping RR nr %d vr %d pf %d PollRR %d", nr, AX25Sess->vr, pf, PollRR); + + Delete(buf, i); + optimize = FALSE; + } + } + + + // RR Cards Methods : Optional RR, P Cards + if (cr == SET_C) + { + if (AX25Sess->status == STAT_LINK || AX25Sess->status == STAT_WAIT_ANS) + { + Debugprintf("Optimizer dropping RR nr %d vr %d pf %d PollRR %d", nr, AX25Sess->vr, pf, PollRR); + Delete(buf, i); + optimize = FALSE; + } + } + } + // I - Cards Methods : Options for I - Cards + else if (f_id == I_I) + { + if (strchr(need_frm, ns + 'A') == 0) + { + Delete(buf, i); + optimize = FALSE; + } + else + { + if (nr != AX25Sess->vr) + buf->Items[i] = make_frame(data, path, pid, AX25Sess->vr, ns, f_type, f_id, rpt, pf, cr); + } + } + + // SABM Applications + + if (f_id == U_SABM) + { + if (AX25Sess->status != STAT_TRY_LINK) + { + Delete(buf, i); + optimize = FALSE; + } + } + + if (optimize) + i++; + } +} + +int KISS_encode(UCHAR * KISSBuffer, int port, string * frame) +{ + // Encode frame + + UCHAR * ptr1 = frame->Data; + UCHAR TXCCC = 0; + int Len = frame->Length; + UCHAR * ptr2 = &KISSBuffer[2]; + UCHAR c; + + // TX Frame has control byte on front + + ptr1++; + Len--; + + KISSBuffer[0] = FEND; + KISSBuffer[1] = port << 4; + + TXCCC ^= KISSBuffer[1]; + + while (Len--) + { + c = *(ptr1++); + TXCCC ^= c; + + switch (c) + { + case FEND: + (*ptr2++) = FESC; + (*ptr2++) = TFEND; + break; + + case FESC: + + (*ptr2++) = FESC; + (*ptr2++) = TFESC; + break; + + // Drop through + + default: + + (*ptr2++) = c; + } + } + (*ptr2++) = FEND; + + return (int)(ptr2 - KISSBuffer); +} + +void KISSSendtoServer(void * Socket, char * Data, int Length); + +void add_pkt_buf(TAX25Port * AX25Sess, string * data) +{ +// boolean found = 0; +// int i = 0; + UCHAR KISSBuffer[512]; + int Length; + + // ? Don't we just send to TNC? + + Length = KISS_encode(KISSBuffer, 0, data); + + KISSSendtoServer(AX25Sess->socket, KISSBuffer, Length); + + monitor_frame(0, data, "", 1, 0); // Monitor + freeString(data); + + +// while (i < AX25Sess->frame_buf.Count && !found) +// { +// found = compareStrings(Strings(&AX25Sess->frame_buf, i++), data); +// } + +// if (found) +// freeString(data); +// else +// Add(&AX25Sess->frame_buf, data); +} + +void add_I_FRM(TAX25Port * AX25Sess) +{ + string * data; + int i; + + upd_i_lo(AX25Sess, AX25Sess->vs); + + while (AX25Sess->in_data_buf.Count > 0 && AX25Sess->I_frame_buf.Count != maxframe[AX25Sess->snd_ch]) + { + data = duplicateString(Strings(&AX25Sess->in_data_buf, 0)); + Delete(&AX25Sess->in_data_buf, 0); + Add(&AX25Sess->I_frame_buf, data); + } + if (AX25Sess->I_frame_buf.Count > 0) + { + for (i = 0; i < AX25Sess->I_frame_buf.Count; i++) + { + upd_i_hi(AX25Sess, AX25Sess->vs); + upd_vs(AX25Sess, AX25Sess->vs); + AX25Sess->hi_vs = AX25Sess->vs; // Last transmitted frame + } + } +} + + +void delete_I_FRM(TAX25Port * AX25Sess, int nr) +{ + int i; + + i = AX25Sess->i_lo; + + while (i != nr) + { + if (AX25Sess->I_frame_buf.Count > 0) + { + AX25Sess->info.stat_s_pkt++; + AX25Sess->info.stat_s_byte += Strings(&AX25Sess->I_frame_buf, 0)->Length; + Delete(&AX25Sess->I_frame_buf, 0); + } + + i++; + i &= 7; + } + upd_i_lo(AX25Sess, nr); +} + +void delete_I_FRM_port(TAX25Port * AX25Sess) +{ + string * frame; + char path[80] = ""; + string data= { 0 }; + + Byte pid, nr, ns, f_type, f_id, rpt, cr, pf; + boolean optimize; + int i = 0; + + while (i < AX25Sess->frame_buf.Count) + { + optimize = TRUE; + frame = Strings(&AX25Sess->frame_buf, i); + + decode_frame(frame->Data, frame->Length, path, &data, &pid, &nr, &ns, &f_type, &f_id, &rpt, &pf, &cr); + + if (f_id == I_I) + { + Delete(&AX25Sess->frame_buf, i); + optimize = FALSE; + } + if (optimize) + i++; + } +} + +void send_data_buf(TAX25Port * AX25Sess, int nr) +{ + int i; + boolean new_frames; + boolean PF_bit; + + if (AX25Sess->status != STAT_LINK) + return; + + AX25Sess->IPOLL_cnt = 0; + AX25Sess->vs = nr; + delete_I_FRM(AX25Sess, nr); // ?? free acked frames +// delete_I_FRM_port(AX25Sess); + + if (TXFrmMode[AX25Sess->snd_ch] == 1) + { + new_frames = FALSE; + + if (AX25Sess->I_frame_buf.Count < 2) + { + add_I_FRM(AX25Sess); + AX25Sess->status = STAT_LINK; + new_frames = TRUE; + } + + if (AX25Sess->I_frame_buf.Count > 0) + { + if (new_frames) + { + for (i = 0; i < AX25Sess->I_frame_buf.Count; i++) + { + if (i == AX25Sess->I_frame_buf.Count - 1) + PF_bit = SET_P; + else + PF_bit = SET_F; + + add_pkt_buf(AX25Sess, make_frame(Strings(&AX25Sess->I_frame_buf, i), AX25Sess->Path, AX25Sess->PID, AX25Sess->vr, ((AX25Sess->i_lo + i) & 7), I_FRM, I_I, FALSE, PF_bit, SET_C)); + } + } + if (!new_frames) + { + add_pkt_buf(AX25Sess, make_frame(Strings(&AX25Sess->I_frame_buf, 0), AX25Sess->Path, AX25Sess->PID, AX25Sess->vr, AX25Sess->i_lo, I_FRM, I_I, FALSE, SET_P, SET_C)); //SET_P + upd_vs(AX25Sess, AX25Sess->vs); + } + AX25Sess->status = STAT_WAIT_ANS; + rst_timer(AX25Sess); + } + } + + if (TXFrmMode[AX25Sess->snd_ch] == 0) + { + add_I_FRM(AX25Sess); + AX25Sess->status = STAT_LINK; + + if (AX25Sess->I_frame_buf.Count > 0) + { + for (i = 0; i < AX25Sess->I_frame_buf.Count; i++) + { + if (i == AX25Sess->I_frame_buf.Count - 1) + PF_bit = SET_P; + else + PF_bit = SET_F; + add_pkt_buf(AX25Sess, make_frame(Strings(&AX25Sess->I_frame_buf, i), AX25Sess->Path, AX25Sess->PID, AX25Sess->vr, ((AX25Sess->i_lo + i) & 7), I_FRM, I_I, FALSE, PF_bit, SET_C)); + } + AX25Sess->status = STAT_WAIT_ANS; + rst_timer(AX25Sess); + } + } +} + + +void send_data_buf_srej(TAX25Port * AX25Sess, int nr) +{ + // Similar to send_data_buf but only sends the requested I frame with P set + + int i = 0; + boolean new_frames; + boolean PF_bit; + + if (AX25Sess->status != STAT_LINK) + return; + + AX25Sess->IPOLL_cnt = 0; + AX25Sess->vs = nr; + delete_I_FRM(AX25Sess, nr); // ?? free acked frames + + new_frames = FALSE; + + add_I_FRM(AX25Sess); + AX25Sess->status = STAT_LINK; + new_frames = TRUE; + + if (AX25Sess->I_frame_buf.Count > 0) + { + if (new_frames) + { + PF_bit = SET_P; + + add_pkt_buf(AX25Sess, make_frame(Strings(&AX25Sess->I_frame_buf, i), AX25Sess->Path, AX25Sess->PID, AX25Sess->vr, ((AX25Sess->i_lo + i) & 7), I_FRM, I_I, FALSE, PF_bit, SET_C)); + } + else + { + + add_pkt_buf(AX25Sess, make_frame(Strings(&AX25Sess->I_frame_buf, 0), AX25Sess->Path, AX25Sess->PID, AX25Sess->vr, AX25Sess->i_lo, I_FRM, I_I, FALSE, SET_P, SET_C)); //SET_P + upd_vs(AX25Sess, AX25Sess->vs); + } + } + AX25Sess->status = STAT_WAIT_ANS; + rst_timer(AX25Sess); +} + +void write_frame_collector(TAX25Port * AX25Sess, int ns, string * data) +{ + Byte i; + char frm_ns; + string * frm; + boolean found ; + boolean need_frm; + + if (max_frame_collector[AX25Sess->snd_ch] < 1) + return; + + need_frm = FALSE; + i = 1; + do + { + if (ns == ((AX25Sess->vr + i) & 7)) + need_frm = TRUE; + + i++; + + } while (i <= max_frame_collector[AX25Sess->snd_ch] && !need_frm); + + if (need_frm) + { + frm_ns = ns; + found = FALSE; + i = 0; + + if (AX25Sess->frm_collector.Count > 0) + { + do + { + frm = Strings(&AX25Sess->frm_collector, i); + + if (frm_ns == frm->Data[0]) + found = TRUE; + i++; + } + while (!found && i != AX25Sess->frm_collector.Count); + + } + + if (!found) + { + string * frm = newString(); + + stringAdd(frm, (char *)&frm_ns, 1); + stringAdd(frm, data->Data, data->Length); + Add(&AX25Sess->frm_collector, frm); + } + } +} + +string * read_frame_collector(TAX25Port * AX25Sess, boolean fecflag) +{ + // Look for required frame no in saved frames + + string * frm; + string * data = newString(); + + int i = 0; + Byte frm_ns; + + while (i < AX25Sess->frm_collector.Count) + { + frm = duplicateString(Strings(&AX25Sess->frm_collector, i)); + + frm_ns = frm->Data[0]; + + if (frm_ns == AX25Sess->vr) + { + Delete(&AX25Sess->frm_collector, i); + + upd_vr(AX25Sess, AX25Sess->vr); + + mydelete(frm, 0, 1); // Remove vr from front + + stringAdd(data, frm->Data, frm->Length); + + AX25Sess->info.stat_r_pkt++; + AX25Sess->info.stat_r_fc++; + + if (fecflag) + AX25Sess->info.stat_fec_count++; + + AX25Sess->info.stat_r_byte += frm->Length; + AX25Sess->frm_win[frm_ns].Length = frm->Length; //Save the frame to the window buffer + AX25Sess->frm_win[frm_ns].Data = frm->Data; //Save the frame to the window buffer + } + + i++; + } + return data; +} + + +/////////////////////////// SET-FRAMES ////////////////////////////////// + +void set_chk_link(TAX25Port * AX25Sess, Byte * path) +{ + boolean b_IPOLL; + int len; + + AX25Sess->status = STAT_CHK_LINK; + + // if AX25Sess->digi<>'' then path:=path+','+reverse_digi(AX25Sess->digi); + + b_IPOLL = FALSE; + + if (AX25Sess->I_frame_buf.Count > 0 && AX25Sess->IPOLL_cnt < 2) + { + len = Strings(&AX25Sess->I_frame_buf, 0)->Length; + + if (len > 0 && len <= IPOLL[AX25Sess->snd_ch]) + { + + b_IPOLL = TRUE; + AX25Sess->IPOLL_cnt++; + } + } + if (b_IPOLL) + add_pkt_buf(AX25Sess, make_frame(Strings(&AX25Sess->I_frame_buf, 0), path, AX25Sess->PID, AX25Sess->vr, AX25Sess->i_lo, I_FRM, I_I, FALSE, SET_P, SET_C)); + else + add_pkt_buf(AX25Sess, make_frame(NULL, path, 0xF0, AX25Sess->vr, 0, S_FRM, S_RR, FALSE, SET_P, SET_C)); + + inc_frack(AX25Sess); +} + + + +// This seems to start a connection + +void set_link(TAX25Port * AX25Sess, UCHAR * axpath) +{ + if (AX25Sess->status != STAT_LINK) + { + string nullstring; + nullstring.Length = 0; + + rst_values(AX25Sess); + + AX25Sess->status = STAT_TRY_LINK; + + // if (AX25Sess->digi[0] != 0) + // path: = path + ',' + reverse_digi(AX25Sess->digi); + + add_pkt_buf(AX25Sess, make_frame(&nullstring, axpath, 0, 0, 0, U_FRM, U_SABM, SET_NO_RPT, SET_P, SET_C)); + + inc_frack(AX25Sess); + } +} + +#define MODE_OUR 0 + +void set_try_unlink(TAX25Port * AX25Sess, Byte * path) +{ + string nullstring; + nullstring.Length = 0; + + AX25Sess->status = STAT_TRY_UNLINK; + add_pkt_buf(AX25Sess, make_frame(&nullstring, path, 0, 0, 0, U_FRM, U_DISC, SET_NO_RPT, SET_P, SET_C)); + inc_frack(AX25Sess); +} + + +void set_unlink(TAX25Port * AX25Sess, Byte * path) +{ + if (AX25Sess->status == STAT_TRY_UNLINK + || AX25Sess->status == STAT_TRY_LINK + || AX25Sess->status == STAT_NO_LINK) + { + string nullstring; + nullstring.Length = 0; + + // if (AX25Sess->digi[0] != 0) + // path: = path + ',' + reverse_digi(AX25Sess->digi); + + AX25_disc(AX25Sess, MODE_OUR); + + if (AX25Sess->status != STAT_TRY_UNLINK) + add_pkt_buf(AX25Sess, make_frame(&nullstring, path, 0, 0, 0, U_FRM, U_DISC, SET_NO_RPT, SET_P, SET_C)); + + AX25Sess->info.stat_end_ses = time(NULL); + + write_ax25_info(AX25Sess); + rst_values(AX25Sess); + + memset(AX25Sess->corrcall, 0, 10); + memset(AX25Sess->mycall, 0, 10); + AX25Sess->digi[0] = 0; + AX25Sess->status = STAT_NO_LINK; + + } + else + set_try_unlink(AX25Sess, AX25Sess->Path); +} + +void set_FRMR(int snd_ch, Byte * path, unsigned char frameType) +{ + //We may not have a session when sending FRMR so reverse path and send + + Byte revpath[80]; + string * Data = newString(); + string * Frame; + + UCHAR KISSBuffer[512]; + int Length; + + Data->Data[0] = frameType; + Data->Data[1] = 0; + Data->Data[2] = 1; // Invalid CTL Byte + Data->Length = 3; + + reverse_addr(path, revpath, strlen(path)); + + Frame = make_frame(Data, revpath, 0, 0, 0, U_FRM, U_FRMR, FALSE, SET_P, SET_R); + + // ? Don't we just send to TNC? + + Length = KISS_encode(KISSBuffer, 0, Frame); + + KISSSendtoServer(KISSSockCopy[snd_ch], KISSBuffer, Length); + + monitor_frame(0, Frame, "", 1, 0); // Monitor + freeString(Frame); + freeString(Data); +} + +void set_DM(int snd_ch, Byte * path) +{ + //We may not have a session when sending DM so reverse path and send + + Byte revpath[80]; + + reverse_addr(path, revpath, strlen(path)); + + add_pkt_buf(&AX25Port[snd_ch][0], make_frame(NULL, revpath, 0, 0, 0, U_FRM,U_DM,FALSE,SET_P,SET_R)); +} + +/////////////////////////// S-FRAMES //////////////////////////////////// + + +void on_RR(TAX25Port * AX25Sess, Byte * path, int nr, int pf, int cr) +{ + char need_frame[16] = ""; + int index = 0; + + int i; + + if (AX25Sess->status == STAT_TRY_LINK) + return; + + if (AX25Sess->status == STAT_NO_LINK || AX25Sess->status == STAT_TRY_UNLINK) + { + if (cr == SET_C) + set_DM(AX25Sess->snd_ch, path); + + return; + } + + if (cr == SET_R) + { + // Determine which frames could get into the user’s frame buffer. + i = AX25Sess->vs; + + need_frame[index++] = i + '0'; + +// Debugprintf("RR Rxed vs = %d hi_vs = %d", AX25Sess->vs, AX25Sess->hi_vs); + while (i != AX25Sess->hi_vs) + { + i++; + i &= 7; + + need_frame[index++] = i + '0'; + if (index > 10) + { + Debugprintf("Index corrupt %d need_frame = %s", index, need_frame); + break; + } + } + + //Clear the received frames from the transmission buffer. + + if (AX25Sess->status == STAT_WAIT_ANS) + delete_I_FRM(AX25Sess, nr); + + // We restore the link if the number is valid + + if (AX25Sess->status == STAT_CHK_LINK || strchr(need_frame, nr + '0') != 0) + { + rst_timer(AX25Sess); + AX25Sess->status = STAT_LINK; + send_data_buf(AX25Sess, nr); + } + } + + if (cr == SET_C) + add_pkt_buf(AX25Sess, make_frame(NULL, path, 0, AX25Sess->vr, 0, S_FRM, S_RR, FALSE, pf, SET_R)); + + rst_t3(AX25Sess); +} + + +void on_RNR(TAX25Port * AX25Sess, Byte * path, int nr, int pf, int cr) +{ + UNUSED(pf); + + if (AX25Sess->status == STAT_TRY_LINK) + return; + + if (AX25Sess->status == STAT_NO_LINK || AX25Sess->status == STAT_TRY_UNLINK) + { + if (cr == SET_C) + set_DM(AX25Sess->snd_ch, path); + + return; + } + + if (cr == SET_R) + { + rst_timer(AX25Sess); + + if (AX25Sess->status == STAT_WAIT_ANS) + delete_I_FRM(AX25Sess, nr); + + AX25Sess->status = STAT_CHK_LINK; + } + + if (cr == SET_C) + add_pkt_buf(AX25Sess, make_frame(NULL, path, 0, AX25Sess->vr, 0, S_FRM, S_RR, FALSE, SET_P, SET_R)); + + rst_t3(AX25Sess); +} + + +void on_REJ(TAX25Port * AX25Sess, Byte * path, int nr, int pf, int cr) +{ + UNUSED(pf); + if (AX25Sess->status == STAT_TRY_LINK) + return; + + if (AX25Sess->status == STAT_NO_LINK || AX25Sess->status == STAT_TRY_UNLINK) + { + if (cr == SET_C) + set_DM(AX25Sess->snd_ch, path); + + return; + } + + if (cr == SET_R) + { + rst_timer(AX25Sess); + AX25Sess->status = STAT_LINK; + + send_data_buf(AX25Sess, nr); + } + + if (cr == SET_C) + add_pkt_buf(AX25Sess, make_frame(NULL, path, 0, AX25Sess->vr, 0, S_FRM, S_RR, FALSE, SET_P, SET_R)); +} + + +void on_SREJ(TAX25Port * AX25Sess, Byte * path, int nr, int pf, int cr) +{ + UNUSED(pf); + + if (AX25Sess->status == STAT_TRY_LINK) + return; + + if (AX25Sess->status == STAT_NO_LINK || AX25Sess->status == STAT_TRY_UNLINK) + { + if (cr == SET_C) + set_DM(AX25Sess->snd_ch, path); + + return; + } + + if (cr == SET_R) + { + rst_timer(AX25Sess); + AX25Sess->status = STAT_LINK; + + send_data_buf_srej(AX25Sess, nr); + } + + if (cr == SET_C) + add_pkt_buf(AX25Sess, make_frame(NULL, path, 0, AX25Sess->vr, 0, S_FRM, S_RR, FALSE, SET_P, SET_R)); +} +/////////////////////////// I-FRAMES //////////////////////////////////// + +void on_I(void * socket, TAX25Port * AX25Sess, int PID, Byte * path, string * data, int nr, int ns, int pf, int cr, boolean fecflag) +{ + UNUSED(cr); + UNUSED(socket); + string * collector_data = 0; + int i; + Byte need_frame[16] = ""; + int index = 0; + + + if (AX25Sess->status == STAT_TRY_LINK) + return; + + if (AX25Sess->status == STAT_NO_LINK || AX25Sess->status == STAT_TRY_UNLINK) + { + set_DM(0, path); + return; + } + + if (busy) + { + add_pkt_buf(AX25Sess, make_frame(NULL, path, PID, AX25Sess->vr, 0, S_FRM, S_RNR, FALSE, pf, SET_R)); + return; + } + + // Determine which frames could get into the user’s frame buffer. + + i = AX25Sess->vs; + + need_frame[index++] = i + '0'; + + // Debugprintf("I Rxed vs = %d hi_vs = %d", AX25Sess->vs, AX25Sess->hi_vs); + + while (i != AX25Sess->hi_vs) + { + i++; + i &= 7; + + need_frame[index++] = i + '0'; + if (index > 10) + { + Debugprintf("Index corrupt %d need_frame = %s", index, need_frame); + break; + } + } + + //Clear received frames from the transmission buffer. + + if (AX25Sess->status == STAT_WAIT_ANS) + delete_I_FRM(AX25Sess, nr); + + //We restore the link if the number is valid + + if (AX25Sess->status == STAT_CHK_LINK || strchr(need_frame, nr + '0') != 0) + { + //We restore the link if the number is valid + + rst_timer(AX25Sess); + AX25Sess->status = STAT_LINK; + send_data_buf(AX25Sess, nr); + } + + if (ns == AX25Sess->vr) + { + //If the expected frame, send RR, F + + AX25Sess->info.stat_r_pkt++; + AX25Sess->info.stat_r_byte += data->Length; + + if (fecflag) + AX25Sess->info.stat_fec_count++; + + upd_vr(AX25Sess, AX25Sess->vr); + + AX25Sess->frm_win[ns].Length = data->Length; //Save the frame to the window buffer + AX25Sess->frm_win[ns].Data = data->Data; //Save the frame to the window buffer + + collector_data = read_frame_collector(AX25Sess, fecflag); + + stringAdd(data, collector_data->Data, collector_data->Length); + + SendtoTerm(AX25Sess->Sess, (char *)data->Data, data->Length); + + // Andy's code queues RR immediately and uses frame optimiser to remove + // redundant RR (not P) frames. I can't do that so need a proper resptime + // system. Try setting an RRNeeded timer (t2). + + if (pf) + { + // Poll set, so respond immediately + + add_pkt_buf(AX25Sess, make_frame(NULL, path, 0, AX25Sess->vr, 0, S_FRM, S_RR, FALSE, pf, SET_R)); + AX25Sess->t2 = 0; + } + else + AX25Sess->t2 = resptime[0] / 100; // resptime is in mS + + freeString(collector_data); + } + else + { + // If the frame is not expected, send REJ, F + + if ((AX25Sess->frm_win[ns].Length != data->Length) && + memcmp(&AX25Sess->frm_win[ns].Data, data->Data, data->Length) != 0) + + write_frame_collector(AX25Sess, ns, data); + + add_pkt_buf(AX25Sess, make_frame(NULL, path, 0, AX25Sess->vr, 0, S_FRM, S_REJ, FALSE, pf, SET_R)); + } + rst_t3(AX25Sess); + +} +/////////////////////////// U-FRAMES //////////////////////////////////// + +void * ax25IncommingConnect(TAX25Port * AX25Sess); + +void on_SABM(void * socket, TAX25Port * AX25Sess) +{ + if (AX25Sess->status == STAT_TRY_UNLINK) + { + AX25Sess->info.stat_end_ses = time(NULL); + + write_ax25_info(AX25Sess); + rst_values(AX25Sess); + + memset(AX25Sess->corrcall, 0, 10); + memset(AX25Sess->mycall, 0, 10); + AX25Sess->digi[0] = 0; + + AX25_disc(AX25Sess, MODE_OTHER); + Clear(&AX25Sess->frame_buf); + + AX25Sess->status = STAT_NO_LINK; + } + + if (AX25Sess->status == STAT_TRY_LINK) + { + AX25_disc(AX25Sess, MODE_OTHER); + + rst_timer(AX25Sess); + rst_values(AX25Sess); + Clear(&AX25Sess->frame_buf); + AX25Sess->status = STAT_NO_LINK; + } + + if (AX25Sess->status != STAT_NO_LINK) + { + if ((strcmp(AX25Sess->kind, "Outgoing") == 0) || + AX25Sess->status == STAT_TRY_UNLINK || AX25Sess->info.stat_s_byte > 0 || + AX25Sess->info.stat_r_byte > 0 || AX25Sess->frm_collector.Count > 0) + { + AX25Sess->info.stat_end_ses = time(NULL); + AX25_disc(AX25Sess, MODE_OTHER); + write_ax25_info(AX25Sess); + rst_timer(AX25Sess); + rst_values(AX25Sess); + Clear(&AX25Sess->frame_buf); + AX25Sess->status = STAT_NO_LINK; + } + } + + if (AX25Sess->status == STAT_NO_LINK) + { + AX25Sess->vr = 0; + AX25Sess->vs = 0; + AX25Sess->hi_vs = 0; + + Clear(&AX25Sess->frm_collector); + clr_frm_win(AX25Sess); + AX25Sess->status = STAT_LINK; + AX25Sess->info.stat_begin_ses = time(NULL); + strcpy(AX25Sess->kind, "Incoming"); + AX25Sess->socket = socket; + + // Must send UA before any ctext + + add_pkt_buf(AX25Sess, make_frame(NULL, AX25Sess->Path, 0, 0, 0, U_FRM, U_UA, FALSE, SET_P, SET_R)); + + if (ax25IncommingConnect(AX25Sess)) // Attach to Terminal + AX25_conn(AX25Sess, AX25Sess->snd_ch, MODE_OTHER); + + return; + } + + add_pkt_buf(AX25Sess, make_frame(NULL, AX25Sess->Path, 0, 0, 0, U_FRM, U_UA, FALSE, SET_P, SET_R)); +} + +void on_DISC(TAX25Port * AX25Sess) +{ + if (AX25Sess->status != STAT_NO_LINK) + { + AX25Sess->info.stat_end_ses = time(NULL); + AX25_disc(AX25Sess, MODE_OTHER); + write_ax25_info(AX25Sess); + } + + if (AX25Sess->status == STAT_NO_LINK || AX25Sess->status == STAT_TRY_LINK) + set_DM(AX25Sess->snd_ch, AX25Sess->Path); + else + add_pkt_buf(AX25Sess, make_frame(NULL, AX25Sess->Path, 0, 0, 0, U_FRM, U_UA, FALSE, SET_P, SET_R)); + + rst_values(AX25Sess); + memset(AX25Sess->corrcall, 0, 10); + memset(AX25Sess->mycall, 0, 10); + AX25Sess->digi[0] = 0; + AX25Sess->status = STAT_NO_LINK; +} + +void on_DM(TAX25Port * AX25Sess) +{ + if (AX25Sess->status == STAT_TRY_LINK) + { + + char Msg[128]; + int Len; + + Len = sprintf(Msg, "*** Busy From %s\r", AX25Sess->corrcall); + SendtoTerm(AX25Sess->Sess, Msg, Len); + } + else if (AX25Sess->status != STAT_NO_LINK) + { + AX25Sess->info.stat_end_ses = time(NULL); + AX25_disc(AX25Sess, MODE_OTHER); + write_ax25_info(AX25Sess); + } + + rst_timer(AX25Sess); + rst_values(AX25Sess); + memset(AX25Sess->corrcall, 0, 10); + memset(AX25Sess->mycall, 0, 10); + AX25Sess->digi[0] = 0; + AX25Sess->status = STAT_NO_LINK; +} + + +void on_UA(TAX25Port * AX25Sess) +{ + switch (AX25Sess->status) + { + case STAT_TRY_LINK: + + AX25Sess->info.stat_begin_ses = time(NULL); + AX25Sess->status = STAT_LINK; + AX25_conn(AX25Sess, AX25Sess->snd_ch, MODE_OUR); + break; + + case STAT_TRY_UNLINK: + + AX25Sess->info.stat_end_ses = time(NULL); + AX25_disc(AX25Sess, MODE_OUR); + write_ax25_info(AX25Sess); + + rst_values(AX25Sess); + AX25Sess->status = STAT_NO_LINK; + memset(AX25Sess->corrcall, 0, 10); + memset(AX25Sess->mycall, 0, 10); + AX25Sess->digi[0] = 0; + break; + } + + rst_timer(AX25Sess); +} + +void on_UI(TAX25Port * AX25Sess, int pf, int cr) +{ + UNUSED(AX25Sess); + UNUSED(pf); + UNUSED(cr); + +} + +void on_FRMR(void * socket, TAX25Port * AX25Sess, Byte * path) +{ + if (AX25Sess->status != STAT_NO_LINK) + { + AX25Sess->info.stat_end_ses = time(NULL); + + AX25_disc(socket, MODE_OTHER); + write_ax25_info(AX25Sess); + } + + set_DM(AX25Sess->snd_ch, path); + + rst_values(AX25Sess); + memset(AX25Sess->corrcall, 0, 10); + memset(AX25Sess->mycall, 0, 10); + AX25Sess->digi[0] = 0; + AX25Sess->status = STAT_NO_LINK; +} + + + +void UpdateActiveConnects(int snd_ch) +{ + int port; + + users[snd_ch] = 0; + + for (port = 0; port < port_num; port++) + if (AX25Port[snd_ch][port].status != STAT_NO_LINK) + users[snd_ch]++; +} + + +void timer_event() +{ + int snd_ch, port; + single frack; + Byte active; + TAX25Port * AX25Sess; + + TimerEvent = TIMER_EVENT_OFF; + + for (snd_ch = 0; snd_ch < 4; snd_ch++) + { + //reset the slottime timer + if (dyn_frack[snd_ch]) + { + UpdateActiveConnects(snd_ch); + if (users[snd_ch] > 0) + active = users[snd_ch] - 1; + else + active = 0; + + frack = frack_time[snd_ch] + frack_time[snd_ch] * active * 0.5; + } + else + frack = frack_time[snd_ch]; + + // + for (port = 0; port < port_num; port++) + { + AX25Sess = &AX25Port[snd_ch][port]; + + if (AX25Sess->status == STAT_NO_LINK) + continue; + + if (AX25Sess->t2) + { + AX25Sess->t2--; + if (AX25Sess->t2 == 0) + { + // Expired - send RR (without P) + + add_pkt_buf(AX25Sess, make_frame(NULL, AX25Sess->Path, 0, AX25Sess->vr, 0, S_FRM, S_RR, FALSE, 0, SET_R)); + } + } + + if (AX25Sess->frame_buf.Count == 0) //when the transfer buffer is empty + inc_t1(AX25Sess); // we consider time of the timer of repeated requests + + // Wouldn't it make more sense to keep path in ax.25 struct? + + if (AX25Sess->t1 >= frack * 10 + (number_digi(AX25Sess->digi) * frack * 20)) + { + if (AX25Sess->clk_frack >= fracks[snd_ch]) + { + // This disconnects after retries expires + + rst_frack(AX25Sess); + set_unlink(AX25Sess, AX25Sess->Path); + } + + switch (AX25Sess->status) + { + case STAT_TRY_LINK: + + set_link(AX25Sess, AX25Sess->Path); + break; + + case STAT_TRY_UNLINK: + + set_try_unlink(AX25Sess, AX25Sess->Path); + break; + + case STAT_WAIT_ANS: + + set_chk_link(AX25Sess, AX25Sess->Path); + break; + + case STAT_CHK_LINK: + + set_chk_link(AX25Sess, AX25Sess->Path); + break; + } + + rst_t1(AX25Sess); + } + + + if (AX25Sess->t3 >= idletime[snd_ch] * 10) + { + set_chk_link(AX25Sess, AX25Sess->Path); + rst_t1(AX25Sess); + rst_t3(AX25Sess); + } + + if (AX25Sess->status == STAT_LINK) + inc_t3(AX25Sess); + + } + } + // KISS ACKMODE + //if (snd_status[snd_ch]<>SND_TX) and KISSServ then KISS_send_ack1(snd_ch); +} + +TAX25Port * get_free_port(int snd_ch) +{ + int i = 0; + + while (i < port_num) + { + if (AX25Port[snd_ch][i].status == STAT_NO_LINK) + return &AX25Port[snd_ch][i]; + + i++; + } + return FALSE; +} + + +TAX25Port * get_user_port(int snd_ch, Byte * path) + { + TAX25Port * AX25Sess = NULL; + + int i = 0; + + + while (i < port_num) + { + AX25Sess = &AX25Port[snd_ch][i]; + + if (AX25Sess->status != STAT_NO_LINK && memcmp(AX25Sess->ReversePath, path, AX25Sess->pathLen) == 0) + return AX25Sess; + + i++; + } + + return FALSE; +} + +boolean get_user_dupe(int snd_ch, Byte * path) +{ + int i = 0; + TAX25Port * AX25Sess; + + while (i < port_num) + { + AX25Sess = &AX25Port[snd_ch][i]; + + if (AX25Sess->status != STAT_NO_LINK && memcmp(AX25Sess->ReversePath, path, AX25Sess->pathLen) == 0) + return TRUE; + + i++; + } + + return FALSE; +} + +TAX25Port * get_user_port_by_calls(int snd_ch, char * CallFrom, char * CallTo) +{ + int i = 0; + TAX25Port * AX25Sess = NULL; + + while (i < port_num) + { + AX25Sess = &AX25Port[snd_ch][i]; + + if (AX25Sess->status != STAT_NO_LINK && + strcmp(CallFrom, AX25Sess->mycall) == 0 && strcmp(CallTo, AX25Sess->corrcall) == 0) + + return AX25Sess; + + i++; + } + + return NULL; +} + +void * get_sock_by_port(TAX25Port * AX25Sess) +{ + void * socket = (void *)-1; + + if (AX25Sess) + socket = AX25Sess->socket; + + return socket; +} + +void Digipeater(int snd_ch, string * frame) +{ + char call[16]; + Byte * addr = &frame->Data[7]; // Origin + string * frameCopy; + + int n = 8; // Max digis + + if (list_digi_callsigns[snd_ch].Count == 0) + return; + + // Look for first unused digi + + while ((addr[6] & 1) == 0 && n--) // until end of address + { + addr += 7; + + if ((addr[6] & 128) == 0) + { + // unused digi - is it addressed to us? + + memcpy(call, addr, 7); + call[6] &= 0x7E; // Mask end of call + + // See if in digi list + + int i; + + for (i = 0; i < list_digi_callsigns->Count; i++) + { + if (memcmp(list_digi_callsigns->Items[i]->Data, call, 7) == 0) + { + UCHAR KISSBuffer[512]; + int Length; + + // for us + + addr[6] |= 128; // set digi'ed + + // TX Frames need a KISS control on front + + frameCopy = newString(); + + frameCopy->Data[0] = 0; + frameCopy->Length = 1; + + stringAdd(frameCopy, frame->Data, frame->Length); // Exclude CRC + + addr[6] &= 0x7f; // clear digi'ed from original; + + // Send to TNC + + // ? Don't we just send to TNC? + + Length = KISS_encode(KISSBuffer, 0, frameCopy); + + KISSSendtoServer(KISSSockCopy[snd_ch], KISSBuffer, Length); + + monitor_frame(0, frameCopy, "", 1, 0); // Monitor + freeString(frameCopy); + + return; + } + } + } + } +} + +void analiz_frame(int snd_ch, string * frame, void * socket, boolean fecflag) +{ + Byte path[80]; + string * data = 0; + Byte pid, nr, ns, f_type, f_id, rpt, cr, pf; + Byte * ptr; + + int excluded = 0; + int len; + + TAX25Port * AX25Sess; + + // mod_icon_status = mod_rx; + + len = frame->Length; + + if (len < PKT_ERR) + return; + + data = newString(); + + decode_frame(frame->Data, frame->Length, path, data, &pid, &nr, &ns, &f_type, &f_id, &rpt, &pf, &cr); + + // if is_excluded_call(snd_ch,path) then excluded:=TRUE; + // if is_excluded_frm(snd_ch,f_id,data) then excluded:=TRUE; + + // CRC Collision Check + + if (!is_correct_path(path, pid)) + { + // Duff path - if Non-AX25 filter active log and discard + + } + + monitor_frame(snd_ch, frame, "", 0, excluded); // Monitor + + // Digipeat frame + + Digipeater(snd_ch, frame); + + if (!is_last_digi(path)) + { + freeString(data); + return; // Don't process if still unused digis + } + + // Clear reapeated bits from digi path + + ptr = &path[13]; + + while ((*ptr & 1) == 0) // end of address + { + ptr += 7; + *(ptr) &= 0x7f; // Clear digi'd bit + } + + // search for port of correspondent + + AX25Sess = get_user_port(snd_ch, path); + + // if not an active session, AX25Sess will be NULL + +// if (AX25Sess == NULL) +// socket = in_list_incoming_mycall(path); +// else +// socket = get_sock_by_port(AX25Sess); + + // link analysis + + if (AX25Sess == NULL) + { + if (f_id == U_UI) + { + CheckUIFrame(path, data); + freeString(data); + return; // Don't precess UI at the moment + } + + // No Session. If socket is set (so call is in incoming calls list) and SABM set up session + + if (listenEnable == 0) + { + set_DM(snd_ch, path); + freeString(data); + return; + } + + if (f_id != U_SABM) // Not SABM + { + // // send DM if P set + + if (cr == SET_C) + { + switch (f_id) + { + case U_DISC: + case S_RR: + case S_REJ: + case S_RNR: + case I_I: + + set_DM(snd_ch, path); + break; + + case U_UI: + break; + + default: + set_FRMR(snd_ch, path, f_id); + } + + + } + freeString(data); + return; + } + + // Must be SABM. See if it would duplicate an existing session (but could it - wouldn't that be found earlier ?? + + if (get_user_dupe(snd_ch, path)) // Not SABM or a duplicate call pair + { + freeString(data); + return; + } + + AX25Sess = get_free_port(snd_ch); + + if (AX25Sess == NULL) + { + // if there are no free ports for connection - reject + + Byte Rev[80]; + + reverse_addr(path, Rev, strlen(path)); + set_DM(snd_ch, Rev); + freeString(data); + return; + } + + // initialise new session + + AX25Sess->snd_ch = snd_ch; + + AX25Sess->corrcall[ConvFromAX25(&path[7], AX25Sess->corrcall)] = 0; + AX25Sess->mycall[ConvFromAX25(path, AX25Sess->mycall)] = 0; + AX25Sess->digi[0] = 0; + + // rst_timer(snd_ch, free_port); + + strcpy(AX25Sess->kind, "Incoming"); + AX25Sess->socket = socket; + + Debugprintf("incoming call socket = %x", socket); + + // I think we need to reverse the path + + AX25Sess->pathLen = strlen(path); + strcpy(AX25Sess->ReversePath, path); + reverse_addr(path, AX25Sess->Path, strlen(path)); + } + + // we process a packet on the necessary port + + memcpy(path, AX25Sess->Path, AX25Sess->pathLen); + + switch (f_id) + { + case I_I: + + on_I(socket, AX25Sess, pid, path, data, nr, ns, pf, cr, fecflag); + break; + + case S_RR: + + on_RR(AX25Sess, path, nr, pf, cr); + break; + + case S_RNR: + + on_RNR(AX25Sess, path, nr, pf, cr); + break; + + case S_REJ: + + on_REJ(AX25Sess, path, nr, pf, cr); + break; + + case S_SREJ: + + on_SREJ(AX25Sess, path, nr, pf, cr); + break; + + case U_SABM: + + on_SABM(socket, AX25Sess); + break; + + case U_DISC: + + on_DISC(AX25Sess); + break; + + case U_UA: + + on_UA(AX25Sess); + break; + + case U_DM: + + on_DM(AX25Sess); + break; + + case U_UI: + + on_UI(AX25Sess, pf, cr); + break; + + case U_FRMR: + + on_FRMR(socket, AX25Sess, path); + break; + } + freeString(data); +} + +int get_addr(char * Calls, UCHAR * AXCalls); + +void Send_UI(int port, Byte PID, char * CallFrom, char *CallTo, Byte * Msg, int MsgLen) +{ + Byte path[80]; + char Calls[80]; + string * Data = newString(); + string * Frame; + + UCHAR KISSBuffer[512]; + int Length; + + stringAdd(Data, Msg, MsgLen); + + sprintf(Calls, "%s,%s", CallTo, CallFrom); + + get_addr(Calls, path); + + Frame = make_frame(Data, path, PID, 0, 0, U_FRM, U_UI, FALSE, SET_F, SET_C); + + // ? Don't we just send to TNC? + + Length = KISS_encode(KISSBuffer, 0, Frame); + + KISSSendtoServer(KISSSockCopy[port], KISSBuffer, Length); + + monitor_frame(0, Frame, "", 1, 0); // Monitor + freeString(Frame); + +} + + + + diff --git a/hid.c b/hid.c new file mode 100644 index 0000000..88ab1bc --- /dev/null +++ b/hid.c @@ -0,0 +1,910 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + Alan Ott + Signal 11 Software + + 8/22/2009 + + Copyright 2009, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + http://github.com/signal11/hidapi . +********************************************************/ + +// Hacked about a bit for BPQ32. John Wiseman G8BPQ April 2018 + + + +#include + +#ifndef _NTDEF_ +typedef LONG NTSTATUS; +#endif + +#ifdef __MINGW32__ +#include +#include +#endif + +#ifdef __CYGWIN__ +#include +#define _wcsdup wcsdup +#endif + +//#define HIDAPI_USE_DDK + +#ifdef __cplusplus +extern "C" { +#endif + #include + #include + #ifdef HIDAPI_USE_DDK + #include + #endif + + // Copied from inc/ddk/hidclass.h, part of the Windows DDK. + #define HID_OUT_CTL_CODE(id) \ + CTL_CODE(FILE_DEVICE_KEYBOARD, (id), METHOD_OUT_DIRECT, FILE_ANY_ACCESS) + #define IOCTL_HID_GET_FEATURE HID_OUT_CTL_CODE(100) + +#ifdef __cplusplus +} // extern "C" +#endif + +#include +#include + + +#include "hidapi.h" + +#ifdef _MSC_VER + // Thanks Microsoft, but I know how to use strncpy(). + #pragma warning(disable:4996) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef HIDAPI_USE_DDK + // Since we're not building with the DDK, and the HID header + // files aren't part of the SDK, we have to define all this + // stuff here. In lookup_functions(), the function pointers + // defined below are set. + typedef struct _HIDD_ATTRIBUTES{ + ULONG Size; + USHORT VendorID; + USHORT ProductID; + USHORT VersionNumber; + } HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES; + + typedef USHORT USAGE; + typedef struct _HIDP_CAPS { + USAGE Usage; + USAGE UsagePage; + USHORT InputReportByteLength; + USHORT OutputReportByteLength; + USHORT FeatureReportByteLength; + USHORT Reserved[17]; + USHORT fields_not_used_by_hidapi[10]; + } HIDP_CAPS, *PHIDP_CAPS; + typedef char* HIDP_PREPARSED_DATA; + #define HIDP_STATUS_SUCCESS 0x0 + + typedef BOOLEAN(__stdcall *HidD_GetAttributes_)(HANDLE device, PHIDD_ATTRIBUTES attrib); + typedef BOOLEAN(__stdcall *HidD_GetSerialNumberString_)(HANDLE device, PVOID buffer, ULONG buffer_len); + typedef BOOLEAN(__stdcall *HidD_GetManufacturerString_)(HANDLE handle, PVOID buffer, ULONG buffer_len); + typedef BOOLEAN(__stdcall *HidD_GetProductString_)(HANDLE handle, PVOID buffer, ULONG buffer_len); + typedef BOOLEAN(__stdcall *HidD_SetFeature_)(HANDLE handle, PVOID data, ULONG length); + typedef BOOLEAN(__stdcall *HidD_GetFeature_)(HANDLE handle, PVOID data, ULONG length); + typedef BOOLEAN(__stdcall *HidD_GetIndexedString_)(HANDLE handle, ULONG string_index, PVOID buffer, ULONG buffer_len); + typedef BOOLEAN(__stdcall *HidD_GetPreparsedData_)(HANDLE handle, HIDP_PREPARSED_DATA **preparsed_data); + typedef BOOLEAN(__stdcall *HidD_FreePreparsedData_)(HIDP_PREPARSED_DATA *preparsed_data); + typedef BOOLEAN(__stdcall *HidP_GetCaps_)(HIDP_PREPARSED_DATA *preparsed_data, HIDP_CAPS *caps); + + static HidD_GetAttributes_ HidD_GetAttributes; + static HidD_GetSerialNumberString_ HidD_GetSerialNumberString; + static HidD_GetManufacturerString_ HidD_GetManufacturerString; + static HidD_GetProductString_ HidD_GetProductString; + static HidD_SetFeature_ HidD_SetFeature; + static HidD_GetFeature_ HidD_GetFeature; + static HidD_GetIndexedString_ HidD_GetIndexedString; + static HidD_GetPreparsedData_ HidD_GetPreparsedData; + static HidD_FreePreparsedData_ HidD_FreePreparsedData; + static HidP_GetCaps_ HidP_GetCaps; + + static HMODULE lib_handle = NULL; + static BOOLEAN initialized = FALSE; +#endif // HIDAPI_USE_DDK + +struct hid_device_ { + HANDLE device_handle; + BOOLEAN blocking; + int input_report_length; + void *last_error_str; + DWORD last_error_num; + BOOLEAN read_pending; + char *read_buf; + OVERLAPPED ol; +}; + +static hid_device *new_hid_device() +{ + hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device)); + dev->device_handle = INVALID_HANDLE_VALUE; + dev->blocking = TRUE; + dev->input_report_length = 0; + dev->last_error_str = NULL; + dev->last_error_num = 0; + dev->read_pending = FALSE; + dev->read_buf = NULL; + memset(&dev->ol, 0, sizeof(dev->ol)); + dev->ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*inital state f=nonsignaled*/, NULL); + + return dev; +} + + +static void register_error(hid_device *device, const char *op) +{ + WCHAR *ptr, *msg; + + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPWSTR)&msg, 0/*sz*/, + NULL); + + // Get rid of the CR and LF that FormatMessage() sticks at the + // end of the message. Thanks Microsoft! + ptr = msg; + while (*ptr) { + if (*ptr == '\r') { + *ptr = 0x0000; + break; + } + ptr++; + } + + // Store the message off in the Device entry so that + // the hid_error() function can pick it up. + LocalFree(device->last_error_str); + device->last_error_str = msg; +} + +#ifndef HIDAPI_USE_DDK +static int lookup_functions() +{ + lib_handle = LoadLibraryA("hid.dll"); + if (lib_handle) { +#define RESOLVE(x) x = (x##_)GetProcAddress(lib_handle, #x); if (!x) return -1; + RESOLVE(HidD_GetAttributes); + RESOLVE(HidD_GetSerialNumberString); + RESOLVE(HidD_GetManufacturerString); + RESOLVE(HidD_GetProductString); + RESOLVE(HidD_SetFeature); + RESOLVE(HidD_GetFeature); + RESOLVE(HidD_GetIndexedString); + RESOLVE(HidD_GetPreparsedData); + RESOLVE(HidD_FreePreparsedData); + RESOLVE(HidP_GetCaps); +#undef RESOLVE + } + else + return -1; + + return 0; +} +#endif + +static HANDLE open_device(const char *path) +{ + HANDLE handle; + + /* First, try to open with sharing mode turned off. This will make it so + that a HID device can only be opened once. This is to be consistent + with the behavior on the other platforms. */ + handle = CreateFileA(path, + GENERIC_WRITE |GENERIC_READ, + 0, /*share mode*/ + NULL, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED,//FILE_ATTRIBUTE_NORMAL, + 0); + + if (handle == INVALID_HANDLE_VALUE) { + /* Couldn't open the device. Some devices must be opened + with sharing enabled (even though they are only opened once), + so try it here. */ + handle = CreateFileA(path, + GENERIC_WRITE |GENERIC_READ, + FILE_SHARE_READ|FILE_SHARE_WRITE, /*share mode*/ + NULL, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED,//FILE_ATTRIBUTE_NORMAL, + 0); + } + + return handle; +} + +int HID_API_EXPORT hid_init(void) +{ +#ifndef HIDAPI_USE_DDK + if (!initialized) { + if (lookup_functions() < 0) { + hid_exit(); + return -1; + } + initialized = TRUE; + } +#endif + return 0; +} + +int HID_API_EXPORT hid_exit(void) +{ +#ifndef HIDAPI_USE_DDK + if (lib_handle) + FreeLibrary(lib_handle); + lib_handle = NULL; + initialized = FALSE; +#endif + return 0; +} + +struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id) +{ + BOOLEAN res; + struct hid_device_info *root = NULL; // return object + struct hid_device_info *cur_dev = NULL; + + // Windows objects for interacting with the driver. + GUID InterfaceClassGuid = {0x4d1e55b2, 0xf16f, 0x11cf, {0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30} }; + SP_DEVINFO_DATA devinfo_data; + SP_DEVICE_INTERFACE_DATA device_interface_data; + SP_DEVICE_INTERFACE_DETAIL_DATA_A *device_interface_detail_data = NULL; + HDEVINFO device_info_set = INVALID_HANDLE_VALUE; + int device_index = 0; + + if (hid_init() < 0) + return NULL; + + // Initialize the Windows objects. + devinfo_data.cbSize = sizeof(SP_DEVINFO_DATA); + device_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + + // Get information for all the devices belonging to the HID class. + device_info_set = SetupDiGetClassDevsA(&InterfaceClassGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + + // Iterate over each device in the HID class, looking for the right one. + + for (;;) { + HANDLE write_handle = INVALID_HANDLE_VALUE; + DWORD required_size = 0; + HIDD_ATTRIBUTES attrib; + + res = SetupDiEnumDeviceInterfaces(device_info_set, + NULL, + &InterfaceClassGuid, + device_index, + &device_interface_data); + + if (!res) { + // A return of FALSE from this function means that + // there are no more devices. + break; + } + + // Call with 0-sized detail size, and let the function + // tell us how long the detail struct needs to be. The + // size is put in &required_size. + res = SetupDiGetDeviceInterfaceDetailA(device_info_set, + &device_interface_data, + NULL, + 0, + &required_size, + NULL); + + // Allocate a long enough structure for device_interface_detail_data. + device_interface_detail_data = (SP_DEVICE_INTERFACE_DETAIL_DATA_A*) malloc(required_size); + device_interface_detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A); + + // Get the detailed data for this device. The detail data gives us + // the device path for this device, which is then passed into + // CreateFile() to get a handle to the device. + res = SetupDiGetDeviceInterfaceDetailA(device_info_set, + &device_interface_data, + device_interface_detail_data, + required_size, + NULL, + NULL); + + if (!res) { + //register_error(dev, "Unable to call SetupDiGetDeviceInterfaceDetail"); + // Continue to the next device. + goto cont; + } + + //wprintf(L"HandleName: %s\n", device_interface_detail_data->DevicePath); + + // Open a handle to the device + write_handle = open_device(device_interface_detail_data->DevicePath); + + // Check validity of write_handle. + if (write_handle == INVALID_HANDLE_VALUE) { + // Unable to open the device. + //register_error(dev, "CreateFile"); + goto cont_close; + } + + + // Get the Vendor ID and Product ID for this device. + attrib.Size = sizeof(HIDD_ATTRIBUTES); + HidD_GetAttributes(write_handle, &attrib); + //wprintf(L"Product/Vendor: %x %x\n", attrib.ProductID, attrib.VendorID); + + // Check the VID/PID to see if we should add this + // device to the enumeration list. + if ((vendor_id == 0x0 && product_id == 0x0) || + (attrib.VendorID == vendor_id && attrib.ProductID == product_id)) + { + #define WSTR_LEN 512 + const char *str; + struct hid_device_info *tmp; + HIDP_PREPARSED_DATA *pp_data = NULL; + HIDP_CAPS caps; + BOOLEAN res; + NTSTATUS nt_res; + wchar_t wstr[WSTR_LEN]; // TODO: Determine Size + int len; + + /* VID/PID match. Create the record. */ + tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info)); + if (cur_dev) { + cur_dev->next = tmp; + } + else { + root = tmp; + } + cur_dev = tmp; + + // Get the Usage Page and Usage for this device. + res = HidD_GetPreparsedData(write_handle, &pp_data); + if (res) { + nt_res = HidP_GetCaps(pp_data, &caps); + if (nt_res == HIDP_STATUS_SUCCESS) { + cur_dev->usage_page = caps.UsagePage; + cur_dev->usage = caps.Usage; + } + + HidD_FreePreparsedData(pp_data); + } + + /* Fill out the record */ + cur_dev->next = NULL; + str = device_interface_detail_data->DevicePath; + if (str) { + len = (int)strlen(str); + cur_dev->path = (char*) calloc(len+1, sizeof(char)); + strncpy(cur_dev->path, str, len+1); + cur_dev->path[len] = '\0'; + } + else + cur_dev->path = NULL; + + /* Serial Number */ + res = HidD_GetSerialNumberString(write_handle, wstr, sizeof(wstr)); + wstr[WSTR_LEN-1] = 0x0000; + if (res) { + cur_dev->serial_number = _wcsdup(wstr); + } + + /* Manufacturer String */ + res = HidD_GetManufacturerString(write_handle, wstr, sizeof(wstr)); + wstr[WSTR_LEN-1] = 0x0000; + if (res) { + cur_dev->manufacturer_string = _wcsdup(wstr); + } + + /* Product String */ + res = HidD_GetProductString(write_handle, wstr, sizeof(wstr)); + wstr[WSTR_LEN-1] = 0x0000; + if (res) { + cur_dev->product_string = _wcsdup(wstr); + } + + /* VID/PID */ + cur_dev->vendor_id = attrib.VendorID; + cur_dev->product_id = attrib.ProductID; + + /* Release Number */ + cur_dev->release_number = attrib.VersionNumber; + + /* Interface Number. It can sometimes be parsed out of the path + on Windows if a device has multiple interfaces. See + http://msdn.microsoft.com/en-us/windows/hardware/gg487473 or + search for "Hardware IDs for HID Devices" at MSDN. If it's not + in the path, it's set to -1. */ + cur_dev->interface_number = -1; + if (cur_dev->path) { + char *interface_component = strstr(cur_dev->path, "&mi_"); + if (interface_component) { + char *hex_str = interface_component + 4; + char *endptr = NULL; + cur_dev->interface_number = strtol(hex_str, &endptr, 16); + if (endptr == hex_str) { + /* The parsing failed. Set interface_number to -1. */ + cur_dev->interface_number = -1; + } + } + } + } + +cont_close: + CloseHandle(write_handle); +cont: + // We no longer need the detail data. It can be freed + free(device_interface_detail_data); + + device_index++; + + } + + // Close the device information handle. + SetupDiDestroyDeviceInfoList(device_info_set); + + return root; + +} + +void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs) +{ + // TODO: Merge this with the Linux version. This function is platform-independent. + struct hid_device_info *d = devs; + while (d) { + struct hid_device_info *next = d->next; + free(d->path); + free(d->serial_number); + free(d->manufacturer_string); + free(d->product_string); + free(d); + d = next; + } +} + + +HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, wchar_t *serial_number) +{ + // TODO: Merge this functions with the Linux version. This function should be platform independent. + struct hid_device_info *devs, *cur_dev; + const char *path_to_open = NULL; + hid_device *handle = NULL; + + devs = hid_enumerate(vendor_id, product_id); + cur_dev = devs; + while (cur_dev) { + if (cur_dev->vendor_id == vendor_id && + cur_dev->product_id == product_id) { + if (serial_number) { + if (wcscmp(serial_number, cur_dev->serial_number) == 0) { + path_to_open = cur_dev->path; + break; + } + } + else { + path_to_open = cur_dev->path; + break; + } + } + cur_dev = cur_dev->next; + } + + if (path_to_open) { + /* Open the device */ + handle = hid_open_path(path_to_open); + } + + hid_free_enumeration(devs); + + return handle; +} + +HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path) +{ + hid_device *dev; + HIDP_CAPS caps; + HIDP_PREPARSED_DATA *pp_data = NULL; + BOOLEAN res; + NTSTATUS nt_res; + + if (hid_init() < 0) { + return NULL; + } + + dev = new_hid_device(); + + // Open a handle to the device + dev->device_handle = open_device(path); + + // Check validity of write_handle. + if (dev->device_handle == INVALID_HANDLE_VALUE) { + // Unable to open the device. + register_error(dev, "CreateFile"); + goto err; + } + + // Get the Input Report length for the device. + res = HidD_GetPreparsedData(dev->device_handle, &pp_data); + if (!res) { + register_error(dev, "HidD_GetPreparsedData"); + goto err; + } + nt_res = HidP_GetCaps(pp_data, &caps); + if (nt_res != HIDP_STATUS_SUCCESS) { + register_error(dev, "HidP_GetCaps"); + goto err_pp_data; + } + dev->input_report_length = caps.InputReportByteLength; + HidD_FreePreparsedData(pp_data); + + dev->read_buf = (char*) malloc(dev->input_report_length); + + return dev; + +err_pp_data: + HidD_FreePreparsedData(pp_data); +err: + CloseHandle(dev->device_handle); + free(dev); + return NULL; +} + +int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length) +{ + DWORD bytes_written; + BOOLEAN res; + + OVERLAPPED ol; + memset(&ol, 0, sizeof(ol)); + + res = WriteFile(dev->device_handle, data, length, NULL, &ol); + + if (!res) { + if (GetLastError() != ERROR_IO_PENDING) { + // WriteFile() failed. Return error. + register_error(dev, "WriteFile"); + return -1; + } + } + + // Wait here until the write is done. This makes + // hid_write() synchronous. + res = GetOverlappedResult(dev->device_handle, &ol, &bytes_written, TRUE/*wait*/); + if (!res) { + // The Write operation failed. + register_error(dev, "WriteFile"); + return -1; + } + + return bytes_written; +} + +int HID_API_EXPORT HID_API_CALL hid_set_ptt(int state) +{ + int res; + hid_device *handle; + unsigned char buf[16]; + + handle = hid_open(0xd8c, 0x8, NULL); + if (!handle) { + printf("unable to open device\n"); + return 1; + } + + + // Toggle PTT + + buf[0] = 0; + buf[1] = 0; + buf[2]= 1 << (3 - 1); + buf[3] = state << (3 - 1); + buf[4] = 0; + + + res = hid_write(handle, buf, 5); + if (res < 0) + { + printf("Unable to write()\n"); + printf("Error: %ls\n", hid_error(handle)); + } + + hid_close(handle); + return res; +} + + +int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds) +{ + DWORD bytes_read = 0; + BOOLEAN res; + + // Copy the handle for convenience. + HANDLE ev = dev->ol.hEvent; + + if (!dev->read_pending) { + // Start an Overlapped I/O read. + dev->read_pending = TRUE; + ResetEvent(ev); + res = ReadFile(dev->device_handle, dev->read_buf, dev->input_report_length, &bytes_read, &dev->ol); + + if (!res) { + if (GetLastError() != ERROR_IO_PENDING) { + // ReadFile() has failed. + // Clean up and return error. + CancelIo(dev->device_handle); + dev->read_pending = FALSE; + goto end_of_function; + } + } + } + + if (milliseconds >= 0) { + // See if there is any data yet. + res = WaitForSingleObject(ev, milliseconds); + if (res != WAIT_OBJECT_0) { + // There was no data this time. Return zero bytes available, + // but leave the Overlapped I/O running. + return 0; + } + } + + // Either WaitForSingleObject() told us that ReadFile has completed, or + // we are in non-blocking mode. Get the number of bytes read. The actual + // data has been copied to the data[] array which was passed to ReadFile(). + res = GetOverlappedResult(dev->device_handle, &dev->ol, &bytes_read, TRUE/*wait*/); + + // Set pending back to false, even if GetOverlappedResult() returned error. + dev->read_pending = FALSE; + + if (res && bytes_read > 0) { + if (dev->read_buf[0] == 0x0) { + /* If report numbers aren't being used, but Windows sticks a report + number (0x0) on the beginning of the report anyway. To make this + work like the other platforms, and to make it work more like the + HID spec, we'll skip over this byte. */ + bytes_read--; + memcpy(data, dev->read_buf+1, length); + } + else { + /* Copy the whole buffer, report number and all. */ + memcpy(data, dev->read_buf, length); + } + } + +end_of_function: + if (!res) { + register_error(dev, "GetOverlappedResult"); + return -1; + } + + return bytes_read; +} + +int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length) +{ + return hid_read_timeout(dev, data, length, (dev->blocking)? -1: 0); +} + +int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock) +{ + dev->blocking = !nonblock; + return 0; /* Success */ +} + +int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length) +{ + BOOLEAN res = HidD_SetFeature(dev->device_handle, (PVOID)data, length); + if (!res) { + register_error(dev, "HidD_SetFeature"); + return -1; + } + + return length; +} + + +int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length) +{ + BOOLEAN res; +#if 0 + res = HidD_GetFeature(dev->device_handle, data, length); + if (!res) { + register_error(dev, "HidD_GetFeature"); + return -1; + } + return 0; /* HidD_GetFeature() doesn't give us an actual length, unfortunately */ +#else + DWORD bytes_returned; + + OVERLAPPED ol; + memset(&ol, 0, sizeof(ol)); + + res = DeviceIoControl(dev->device_handle, + IOCTL_HID_GET_FEATURE, + data, length, + data, length, + &bytes_returned, &ol); + + if (!res) { + if (GetLastError() != ERROR_IO_PENDING) { + // DeviceIoControl() failed. Return error. + register_error(dev, "Send Feature Report DeviceIoControl"); + return -1; + } + } + + // Wait here until the write is done. This makes + // hid_get_feature_report() synchronous. + res = GetOverlappedResult(dev->device_handle, &ol, &bytes_returned, TRUE/*wait*/); + if (!res) { + // The operation failed. + register_error(dev, "Send Feature Report GetOverLappedResult"); + return -1; + } + return bytes_returned; +#endif +} + +void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev) +{ + if (!dev) + return; + CancelIo(dev->device_handle); + CloseHandle(dev->ol.hEvent); + CloseHandle(dev->device_handle); + LocalFree(dev->last_error_str); + free(dev->read_buf); + free(dev); +} + +int HID_API_EXPORT_CALL HID_API_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + BOOLEAN res; + + res = HidD_GetManufacturerString(dev->device_handle, string, 2 * maxlen); + if (!res) { + register_error(dev, "HidD_GetManufacturerString"); + return -1; + } + + return 0; +} + +int HID_API_EXPORT_CALL HID_API_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + BOOLEAN res; + + res = HidD_GetProductString(dev->device_handle, string, 2 * maxlen); + if (!res) { + register_error(dev, "HidD_GetProductString"); + return -1; + } + + return 0; +} + +int HID_API_EXPORT_CALL HID_API_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen) +{ + BOOLEAN res; + + res = HidD_GetSerialNumberString(dev->device_handle, string, 2 * maxlen); + if (!res) { + register_error(dev, "HidD_GetSerialNumberString"); + return -1; + } + + return 0; +} + +int HID_API_EXPORT_CALL HID_API_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen) +{ + BOOLEAN res; + + res = HidD_GetIndexedString(dev->device_handle, string_index, string, 2 * maxlen); + if (!res) { + register_error(dev, "HidD_GetIndexedString"); + return -1; + } + + return 0; +} + + +HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev) +{ + return (wchar_t*)dev->last_error_str; +} + + +//#define PICPGM +//#define S11 +#define P32 +#ifdef S11 + unsigned short VendorID = 0xa0a0; + unsigned short ProductID = 0x0001; +#endif + +#ifdef P32 + unsigned short VendorID = 0x04d8; + unsigned short ProductID = 0x3f; +#endif + + +#ifdef PICPGM + unsigned short VendorID = 0x04d8; + unsigned short ProductID = 0x0033; +#endif + + +#if 0 +int __cdecl main(int argc, char* argv[]) +{ + int res; + unsigned char buf[65]; + + UNREFERENCED_PARAMETER(argc); + UNREFERENCED_PARAMETER(argv); + + // Set up the command buffer. + memset(buf,0x00,sizeof(buf)); + buf[0] = 0; + buf[1] = 0x81; + + + // Open the device. + int handle = open(VendorID, ProductID, L"12345"); + if (handle < 0) + printf("unable to open device\n"); + + + // Toggle LED (cmd 0x80) + buf[1] = 0x80; + res = write(handle, buf, 65); + if (res < 0) + printf("Unable to write()\n"); + + // Request state (cmd 0x81) + buf[1] = 0x81; + write(handle, buf, 65); + if (res < 0) + printf("Unable to write() (2)\n"); + + // Read requested state + read(handle, buf, 65); + if (res < 0) + printf("Unable to read()\n"); + + // Print out the returned buffer. + for (int i = 0; i < 4; i++) + printf("buf[%d]: %d\n", i, buf[i]); + + return 0; +} +#endif + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/hidapi.h b/hidapi.h new file mode 100644 index 0000000..e8ad2ca --- /dev/null +++ b/hidapi.h @@ -0,0 +1,386 @@ +/******************************************************* + HIDAPI - Multi-Platform library for + communication with HID devices. + + Alan Ott + Signal 11 Software + + 8/22/2009 + + Copyright 2009, All Rights Reserved. + + At the discretion of the user of this library, + this software may be licensed under the terms of the + GNU Public License v3, a BSD-Style license, or the + original HIDAPI license as outlined in the LICENSE.txt, + LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt + files located at the root of the source distribution. + These files may also be found in the public source + code repository located at: + http://github.com/signal11/hidapi . +********************************************************/ + +/** @file + * @defgroup API hidapi API + */ + +#ifndef HIDAPI_H__ +#define HIDAPI_H__ + +#include + +#ifdef _WIN32 +// #define HID_API_EXPORT __declspec(dllexport) // BPQ + #define HID_API_EXPORT + #define HID_API_CALL +#else + #define HID_API_EXPORT /**< API export macro */ + #define HID_API_CALL /**< API call macro */ +#endif + +#define HID_API_EXPORT_CALL HID_API_EXPORT HID_API_CALL /**< API export and call macro*/ + +#ifdef __cplusplus +extern "C" { +#endif + struct hid_device_; + typedef struct hid_device_ hid_device; /**< opaque hidapi structure */ + + /** hidapi info structure */ + struct hid_device_info { + /** Platform-specific device path */ + char *path; + /** Device Vendor ID */ + unsigned short vendor_id; + /** Device Product ID */ + unsigned short product_id; + /** Serial Number */ + wchar_t *serial_number; + /** Device Release Number in binary-coded decimal, + also known as Device Version Number */ + unsigned short release_number; + /** Manufacturer String */ + wchar_t *manufacturer_string; + /** Product string */ + wchar_t *product_string; + /** Usage Page for this Device/Interface + (Windows/Mac only). */ + unsigned short usage_page; + /** Usage for this Device/Interface + (Windows/Mac only).*/ + unsigned short usage; + /** The USB interface which this logical device + represents. Valid on both Linux implementations + in all cases, and valid on the Windows implementation + only if the device contains more than one interface. */ + int interface_number; + + /** Pointer to the next device */ + struct hid_device_info *next; + }; + + + /** @brief Initialize the HIDAPI library. + + This function initializes the HIDAPI library. Calling it is not + strictly necessary, as it will be called automatically by + hid_enumerate() and any of the hid_open_*() functions if it is + needed. This function should be called at the beginning of + execution however, if there is a chance of HIDAPI handles + being opened by different threads simultaneously. + + @ingroup API + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_init(void); + + /** @brief Finalize the HIDAPI library. + + This function frees all of the static data associated with + HIDAPI. It should be called at the end of execution to avoid + memory leaks. + + @ingroup API + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_exit(void); + + /** @brief Enumerate the HID Devices. + + This function returns a linked list of all the HID devices + attached to the system which match vendor_id and product_id. + If @p vendor_id and @p product_id are both set to 0, then + all HID devices will be returned. + + @ingroup API + @param vendor_id The Vendor ID (VID) of the types of device + to open. + @param product_id The Product ID (PID) of the types of + device to open. + + @returns + This function returns a pointer to a linked list of type + struct #hid_device, containing information about the HID devices + attached to the system, or NULL in the case of failure. Free + this linked list by calling hid_free_enumeration(). + */ + struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id); + + /** @brief Free an enumeration Linked List + + This function frees a linked list created by hid_enumerate(). + + @ingroup API + @param devs Pointer to a list of struct_device returned from + hid_enumerate(). + */ + void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs); + + /** @brief Open a HID device using a Vendor ID (VID), Product ID + (PID) and optionally a serial number. + + If @p serial_number is NULL, the first device with the + specified VID and PID is opened. + + @ingroup API + @param vendor_id The Vendor ID (VID) of the device to open. + @param product_id The Product ID (PID) of the device to open. + @param serial_number The Serial Number of the device to open + (Optionally NULL). + + @returns + This function returns a pointer to a #hid_device object on + success or NULL on failure. + */ + HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, wchar_t *serial_number); + + /** @brief Open a HID device by its path name. + + The path name be determined by calling hid_enumerate(), or a + platform-specific path name can be used (eg: /dev/hidraw0 on + Linux). + + @ingroup API + @param path The path name of the device to open + + @returns + This function returns a pointer to a #hid_device object on + success or NULL on failure. + */ + HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path); + + /** @brief Write an Output report to a HID device. + + The first byte of @p data[] must contain the Report ID. For + devices which only support a single report, this must be set + to 0x0. The remaining bytes contain the report data. Since + the Report ID is mandatory, calls to hid_write() will always + contain one more byte than the report contains. For example, + if a hid report is 16 bytes long, 17 bytes must be passed to + hid_write(), the Report ID (or 0x0, for devices with a + single report), followed by the report data (16 bytes). In + this example, the length passed in would be 17. + + hid_write() will send the data on the first OUT endpoint, if + one exists. If it does not, it will send the data through + the Control Endpoint (Endpoint 0). + + @ingroup API + @param device A device handle returned from hid_open(). + @param data The data to send, including the report number as + the first byte. + @param length The length in bytes of the data to send. + + @returns + This function returns the actual number of bytes written and + -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_write(hid_device *device, const unsigned char *data, size_t length); + + /** @brief Read an Input report from a HID device with timeout. + + Input reports are returned + to the host through the INTERRUPT IN endpoint. The first byte will + contain the Report number if the device uses numbered reports. + + @ingroup API + @param device A device handle returned from hid_open(). + @param data A buffer to put the read data into. + @param length The number of bytes to read. For devices with + multiple reports, make sure to read an extra byte for + the report number. + @param milliseconds timeout in milliseconds or -1 for blocking wait. + + @returns + This function returns the actual number of bytes read and + -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds); + + /** @brief Read an Input report from a HID device. + + Input reports are returned + to the host through the INTERRUPT IN endpoint. The first byte will + contain the Report number if the device uses numbered reports. + + @ingroup API + @param device A device handle returned from hid_open(). + @param data A buffer to put the read data into. + @param length The number of bytes to read. For devices with + multiple reports, make sure to read an extra byte for + the report number. + + @returns + This function returns the actual number of bytes read and + -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_read(hid_device *device, unsigned char *data, size_t length); + + /** @brief Set the device handle to be non-blocking. + + In non-blocking mode calls to hid_read() will return + immediately with a value of 0 if there is no data to be + read. In blocking mode, hid_read() will wait (block) until + there is data to read before returning. + + Nonblocking can be turned on and off at any time. + + @ingroup API + @param device A device handle returned from hid_open(). + @param nonblock enable or not the nonblocking reads + - 1 to enable nonblocking + - 0 to disable nonblocking. + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *device, int nonblock); + + /** @brief Send a Feature report to the device. + + Feature reports are sent over the Control endpoint as a + Set_Report transfer. The first byte of @p data[] must + contain the Report ID. For devices which only support a + single report, this must be set to 0x0. The remaining bytes + contain the report data. Since the Report ID is mandatory, + calls to hid_send_feature_report() will always contain one + more byte than the report contains. For example, if a hid + report is 16 bytes long, 17 bytes must be passed to + hid_send_feature_report(): the Report ID (or 0x0, for + devices which do not use numbered reports), followed by the + report data (16 bytes). In this example, the length passed + in would be 17. + + @ingroup API + @param device A device handle returned from hid_open(). + @param data The data to send, including the report number as + the first byte. + @param length The length in bytes of the data to send, including + the report number. + + @returns + This function returns the actual number of bytes written and + -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *device, const unsigned char *data, size_t length); + + /** @brief Get a feature report from a HID device. + + Make sure to set the first byte of @p data[] to the Report + ID of the report to be read. Make sure to allow space for + this extra byte in @p data[]. + + @ingroup API + @param device A device handle returned from hid_open(). + @param data A buffer to put the read data into, including + the Report ID. Set the first byte of @p data[] to the + Report ID of the report to be read. + @param length The number of bytes to read, including an + extra byte for the report ID. The buffer can be longer + than the actual report. + + @returns + This function returns the number of bytes read and + -1 on error. + */ + int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *device, unsigned char *data, size_t length); + + /** @brief Close a HID device. + + @ingroup API + @param device A device handle returned from hid_open(). + */ + void HID_API_EXPORT HID_API_CALL hid_close(hid_device *device); + + /** @brief Get The Manufacturer String from a HID device. + + @ingroup API + @param device A device handle returned from hid_open(). + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *device, wchar_t *string, size_t maxlen); + + /** @brief Get The Product String from a HID device. + + @ingroup API + @param device A device handle returned from hid_open(). + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT_CALL hid_get_product_string(hid_device *device, wchar_t *string, size_t maxlen); + + /** @brief Get The Serial Number String from a HID device. + + @ingroup API + @param device A device handle returned from hid_open(). + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *device, wchar_t *string, size_t maxlen); + + /** @brief Get a string from a HID device, based on its string index. + + @ingroup API + @param device A device handle returned from hid_open(). + @param string_index The index of the string to get. + @param string A wide string buffer to put the data into. + @param maxlen The length of the buffer in multiples of wchar_t. + + @returns + This function returns 0 on success and -1 on error. + */ + int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *device, int string_index, wchar_t *string, size_t maxlen); + + /** @brief Get a string describing the last error which occurred. + + @ingroup API + @param device A device handle returned from hid_open(). + + @returns + This function returns a string containing the last error + which occurred or NULL if none has occurred. + */ + HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *device); + + int HID_API_EXPORT HID_API_CALL hid_set_ptt(int state); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/icon1.ico b/icon1.ico new file mode 100644 index 0000000000000000000000000000000000000000..8aa98e515dae1fd85d0df9997de54ed10730c400 GIT binary patch literal 45451 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 I@_Fh10YBW>hyVZp literal 0 HcmV?d00001 diff --git a/icon2.ico b/icon2.ico new file mode 100644 index 0000000000000000000000000000000000000000..5d06b9f2857b39f0b5d3395e3e7999ba6bcc3a38 GIT binary patch literal 45451 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 literal 0 HcmV?d00001 diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..270cffe --- /dev/null +++ b/main.cpp @@ -0,0 +1,26 @@ +#include "QtTermTCP.h" +#include +#include + +QApplication * a; + +int screenHeight = 0; // used to control dialog sizes on Android +int screenWidth = 0; + +int main(int argc, char *argv[]) +{ + a = new QApplication (argc, argv); + + QSize r = a->screens()[0]->size(); + + screenHeight = r.height(); + screenWidth = r.width(); + + QtTermTCP w; + +//#ifdef ANDROID +// w.setWindowFlags(Qt::Window | Qt::CustomizeWindowHint); // Qt::FramelessWindowHint); +//#endif + w.show(); + return a->exec(); +} diff --git a/makeit b/makeit new file mode 100644 index 0000000..f008177 --- /dev/null +++ b/makeit @@ -0,0 +1,9 @@ +cp --preserve /mnt/Source/QT/QtTermTCP/*.cpp ./ +cp --preserve /mnt/Source/QT/QtTermTCP/*.c ./ +cp --preserve /mnt/Source/QT/QtTermTCP/*.h ./ +cp --preserve /mnt/Source/QT/QtTermTCP/*.ui ./ + +qmake +make -j4 +cp QtTermTCP /mnt/Source + diff --git a/ui_TCPHostConfig.h b/ui_TCPHostConfig.h new file mode 100644 index 0000000..1f232a7 --- /dev/null +++ b/ui_TCPHostConfig.h @@ -0,0 +1,65 @@ +/******************************************************************************** +** Form generated from reading UI file 'TCPHostConfig.ui' +** +** Created by: Qt User Interface Compiler version 5.14.2 +** +** WARNING! All changes made in this file will be lost when recompiling UI file! +********************************************************************************/ + +#ifndef UI_TCPHOSTCONFIG_H +#define UI_TCPHOSTCONFIG_H + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class Ui_Dialog +{ +public: + QDialogButtonBox *buttonBox; + QTextEdit *Host; + + void setupUi(QDialog *Dialog) + { + if (Dialog->objectName().isEmpty()) + Dialog->setObjectName(QString::fromUtf8("Dialog")); + Dialog->resize(400, 300); + QIcon icon; + icon.addFile(QString::fromUtf8("QtTermTCP.ico"), QSize(), QIcon::Normal, QIcon::Off); + Dialog->setWindowIcon(icon); + buttonBox = new QDialogButtonBox(Dialog); + buttonBox->setObjectName(QString::fromUtf8("buttonBox")); + buttonBox->setGeometry(QRect(30, 240, 341, 32)); + buttonBox->setOrientation(Qt::Horizontal); + buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Save); + buttonBox->setCenterButtons(true); + Host = new QTextEdit(Dialog); + Host->setObjectName(QString::fromUtf8("Host")); + Host->setGeometry(QRect(184, 44, 104, 23)); + + retranslateUi(Dialog); + QObject::connect(buttonBox, SIGNAL(accepted()), Dialog, SLOT(accept())); + QObject::connect(buttonBox, SIGNAL(rejected()), Dialog, SLOT(reject())); + + QMetaObject::connectSlotsByName(Dialog); + } // setupUi + + void retranslateUi(QDialog *Dialog) + { + Dialog->setWindowTitle(QCoreApplication::translate("Dialog", "TCP Host Config", nullptr)); + } // retranslateUi + +}; + +namespace Ui { + class Dialog: public Ui_Dialog {}; +} // namespace Ui + +QT_END_NAMESPACE + +#endif // UI_TCPHOSTCONFIG_H diff --git a/utf8Routines.cpp b/utf8Routines.cpp new file mode 100644 index 0000000..59b1e23 --- /dev/null +++ b/utf8Routines.cpp @@ -0,0 +1,597 @@ +/* +Copyright 2001-2018 John Wiseman G8BPQ + +This file is part of LinBPQ/BPQ32. + +LinBPQ/BPQ32 is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +LinBPQ/BPQ32 is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with LinBPQ/BPQ32. If not, see http://www.gnu.org/licenses +*/ + +// Routines to convert to and from UTF8 + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers + +#define _CRT_SECURE_NO_DEPRECATE + + +#ifndef WIN32 + +#define VOID void +#define BOOL int +#define TRUE 1 +#define FALSE 0 + +#include + + +#else +#include +#endif + +int convUTF8 = 0; + +unsigned int CP437toUTF8Data[128] = { + 34755, 48323, 43459, 41667, + 42179, 41155, 42435, 42947, + 43715, 43971, 43203, 44995, + 44739, 44227, 33987, 34243, + 35267, 42691, 34499, 46275, // 90 + 46787, 45763, 48067, 47555, + 49091, 38595, 40131, 41666, + 41922, 42434, 10978018, 37574, + 41411, 44483, 46019, 47811, // A0 + 45507, 37315, 43714, 47810, + 49090, 9473250, 44226, 48578, + 48322, 41410, 43970, 48066, + 9541346, 9606882, 9672418, 8557794, //B0 + 10786018, 10589666, 10655202, 9868770, + 9803234, 10720738, 9541090, 9934306, + 10327522, 10261986, 10196450, 9475298, + 9737442, 11834594, 11310306, 10261730, //C0 + 8426722, 12358882, 10393058, 10458594, + 10130914, 9737698, 11113954, 10917346, + 10524130, 9475554, 11310562, 10982882, + 11048418, 10786274, 10851810, 10065378, //D0 + 9999842, 9606626, 9672162, 11245026, + 11179490, 9999586, 9213154, 8951522, + 8689378, 9213666, 9475810, 8427234, + 45518, 40899, 37838, 32975, // E0 + 41934, 33743, 46530, 33999, + 42702, 39118, 43470, 46286, + 10389730, 34511, 46542, 11110626, + 10586594, 45506, 10848738, 10783202, // F0 + 10521826, 10587362, 47043, 8948194, + 45250, 10062050, 47042, 10127586, + 12550626, 45762, 10524386, 41154, +}; +unsigned int CP437toUTF8DataLen[128] = { + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 3, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 3, 2, 2, + 2, 2, 2, 2, + 3, 3, 3, 3, + 3, 3, 3, 3, + 3, 3, 3, 3, + 3, 3, 3, 3, + 3, 3, 3, 3, + 3, 3, 3, 3, + 3, 3, 3, 3, + 3, 3, 3, 3, + 3, 3, 3, 3, + 3, 3, 3, 3, + 3, 3, 3, 3, + 3, 3, 3, 3, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 3, 2, 2, 3, + 3, 2, 3, 3, + 3, 3, 2, 3, + 2, 3, 2, 3, + 3, 2, 3, 2, +}; + +unsigned int CP1251toUTF8Data[128] = { + 33488, 33744, 10125538, 37841, + 10387682, 10911970, 10518754, 10584290, + 11305698, 11567330, 35280, 12157154, + 35536, 36048, 35792, 36816, + 37585, 9994466, 10060002, 10256610, + 10322146, 10649826, 9666786, 9732322, + 39106, 10650850, 39377, 12222690, + 39633, 40145, 39889, 40913, + 41154, 36560, 40657, 35024, + 42178, 37074, 42690, 42946, + 33232, 43458, 34000, 43970, + 44226, 44482, 44738, 34768, + 45250, 45506, 34512, 38609, + 37330, 46530, 46786, 47042, + 37329, 9864418, 38097, 48066, + 39121, 34256, 38353, 38865, + 37072, 37328, 37584, 37840, + 38096, 38352, 38608, 38864, + 39120, 39376, 39632, 39888, + 40144, 40400, 40656, 40912, + 41168, 41424, 41680, 41936, + 42192, 42448, 42704, 42960, + 43216, 43472, 43728, 43984, + 44240, 44496, 44752, 45008, + 45264, 45520, 45776, 46032, + 46288, 46544, 46800, 47056, + 47312, 47568, 47824, 48080, + 48336, 48592, 48848, 49104, + 32977, 33233, 33489, 33745, + 34001, 34257, 34513, 34769, + 35025, 35281, 35537, 35793, + 36049, 36305, 36561, 36817, +}; +unsigned int CP1251toUTF8DataLen[128] = { + 2, 2, 3, 2, + 3, 3, 3, 3, + 3, 3, 2, 3, + 2, 2, 2, 2, + 2, 3, 3, 3, + 3, 3, 3, 3, + 2, 3, 2, 3, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 3, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, +}; + + +unsigned int CP1252toUTF8Data[128] = { + 11305698, 33218, 10125538, 37574, + 10387682, 10911970, 10518754, 10584290, + 34507, 11567330, 41157, 12157154, + 37573, 36290, 48581, 36802, + 37058, 9994466, 10060002, 10256610, + 10322146, 10649826, 9666786, 9732322, + 40139, 10650850, 41413, 12222690, + 37829, 40386, 48837, 47301, + 41154, 41410, 41666, 41922, + 42178, 42434, 42690, 42946, + 43202, 43458, 43714, 43970, + 44226, 44482, 44738, 44994, + 45250, 45506, 45762, 46018, + 46274, 46530, 46786, 47042, + 47298, 47554, 47810, 48066, + 48322, 48578, 48834, 49090, + 32963, 33219, 33475, 33731, + 33987, 34243, 34499, 34755, + 35011, 35267, 35523, 35779, + 36035, 36291, 36547, 36803, + 37059, 37315, 37571, 37827, + 38083, 38339, 38595, 38851, + 39107, 39363, 39619, 39875, + 40131, 40387, 40643, 40899, + 41155, 41411, 41667, 41923, + 42179, 42435, 42691, 42947, + 43203, 43459, 43715, 43971, + 44227, 44483, 44739, 44995, + 45251, 45507, 45763, 46019, + 46275, 46531, 46787, 47043, + 47299, 47555, 47811, 48067, + 48323, 48579, 48835, 49091, +}; +unsigned int CP1252toUTF8DataLen[128] = { + 3, 2, 3, 2, + 3, 3, 3, 3, + 2, 3, 2, 3, + 2, 2, 2, 2, + 2, 3, 3, 3, + 3, 3, 3, 3, + 2, 3, 2, 3, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, +}; + +#ifdef __BIG_ENDIAN__ +BOOL initUTF8Done = FALSE; +#else +BOOL initUTF8Done = TRUE; +#endif + +VOID initUTF8() +{ + // Swap bytes of UTF-8 COde on Big-endian systems + + int n; + char temp[4]; + char rev[4]; + + if (initUTF8Done) + return; + + for (n = 0; n <128; n++) + { + memcpy(temp, &CP437toUTF8Data[n], 4); + rev[0] = temp[3]; + rev[1] = temp[2]; + rev[2] = temp[1]; + rev[3] = temp[0]; + + memcpy(&CP437toUTF8Data[n], rev, 4); + + memcpy(temp, &CP1251toUTF8Data[n], 4); + rev[0] = temp[3]; + rev[1] = temp[2]; + rev[2] = temp[1]; + rev[3] = temp[0]; + + memcpy(&CP1251toUTF8Data[n], rev, 4); + + memcpy(temp, &CP1252toUTF8Data[n], 4); + rev[0] = temp[3]; + rev[1] = temp[2]; + rev[2] = temp[1]; + rev[3] = temp[0]; + + memcpy(&CP1252toUTF8Data[n], rev, 4); + } + + initUTF8Done = TRUE; +} + + + +int Is8Bit(unsigned char *cpt, int len) +{ + int n; + + cpt--; + + for (n = 0; n < len; n++) + { + cpt++; + + if (*cpt > 127) + return TRUE; + } + + return FALSE; +} + + +int IsUTF8(unsigned char *ptr, int len) +{ + int n; + unsigned char * cpt = ptr; + + // This has to be a bit loose, as UTF8 sequences may split over packets + + memcpy(&ptr[len], "\x80\x80\x80", 3); // in case trailing bytes are in next packet + + // Don't check first 3 if could be part of sequence + + if ((*(cpt) & 0xC0) == 0x80) // Valid continuation + { + cpt++; + len--; + if ((*(cpt) & 0xC0) == 0x80) // Valid continuation + { + cpt++; + len--; + if ((*(cpt) & 0xC0) == 0x80) // Valid continuation + { + cpt++; + len--; + } + } + } + + cpt--; + + for (n = 0; n < len; n++) + { + cpt++; + + if (*cpt < 128) + continue; + + if ((*cpt & 0xF8) == 0xF0) + { // start of 4-byte sequence + if (((*(cpt + 1) & 0xC0) == 0x80) + && ((*(cpt + 2) & 0xC0) == 0x80) + && ((*(cpt + 3) & 0xC0) == 0x80)) + { + cpt += 3; + n += 3; + continue; + } + return FALSE; + } + else if ((*cpt & 0xF0) == 0xE0) + { // start of 3-byte sequence + if (((*(cpt + 1) & 0xC0) == 0x80) + && ((*(cpt + 2) & 0xC0) == 0x80)) + { + cpt += 2; + n += 2; + continue; + } + return FALSE; + } + else if ((*cpt & 0xE0) == 0xC0) + { // start of 2-byte sequence + if ((*(cpt + 1) & 0xC0) == 0x80) + { + cpt++; + n++; + continue; + } + return FALSE; + } + return FALSE; + } + + return TRUE; +} + +int WebIsUTF8(unsigned char *ptr, int len) +{ + int n; + unsigned char * cpt = ptr; + + // This is simpler than the Term version, as it only handles complete lines of text, so cant get split sequences + + cpt--; + + for (n = 0; n < len; n++) + { + cpt++; + + if (*cpt < 128) + continue; + + if ((*cpt & 0xF8) == 0xF0) + { // start of 4-byte sequence + if (((*(cpt + 1) & 0xC0) == 0x80) + && ((*(cpt + 2) & 0xC0) == 0x80) + && ((*(cpt + 3) & 0xC0) == 0x80)) + { + cpt += 3; + n += 3; + continue; + } + return FALSE; + } + else if ((*cpt & 0xF0) == 0xE0) + { // start of 3-byte sequence + if (((*(cpt + 1) & 0xC0) == 0x80) + && ((*(cpt + 2) & 0xC0) == 0x80)) + { + cpt += 2; + n += 2; + continue; + } + return FALSE; + } + else if ((*cpt & 0xE0) == 0xC0) + { // start of 2-byte sequence + if ((*(cpt + 1) & 0xC0) == 0x80) + { + cpt++; + n++; + continue; + } + return FALSE; + } + return FALSE; + } + + return TRUE; +} + + + +int Convert437toUTF8(unsigned char * MsgPtr, int len, unsigned char * UTF) +{ + unsigned char * ptr1 = MsgPtr; + unsigned char * ptr2 = UTF; + int n; + unsigned int c; + + for (n = 0; n < len; n++) + { + c = *(ptr1++); + + if (c < 128) + { + *(ptr2++) = c; + continue; + } + + memcpy(ptr2, &CP437toUTF8Data[c - 128], CP437toUTF8DataLen[c - 128]); + ptr2 += CP437toUTF8DataLen[c - 128]; + } + + return (int)(ptr2 - UTF); +} + +int Convert1251toUTF8(unsigned char * MsgPtr, int len, unsigned char * UTF) +{ + unsigned char * ptr1 = MsgPtr; + unsigned char * ptr2 = UTF; + int n; + unsigned int c; + + for (n = 0; n < len; n++) + { + c = *(ptr1++); + + if (c < 128) + { + *(ptr2++) = c; + continue; + } + + memcpy(ptr2, &CP1251toUTF8Data[c - 128], CP1251toUTF8DataLen[c - 128]); + ptr2 += CP1251toUTF8DataLen[c - 128]; + } + + return (int)(ptr2 - UTF); +} + +int Convert1252toUTF8(unsigned char * MsgPtr, int len, unsigned char * UTF) +{ + unsigned char * ptr1 = MsgPtr; + unsigned char * ptr2 = UTF; + int n; + unsigned int c; + + for (n = 0; n < len; n++) + { + c = *(ptr1++); + + if (c < 128) + { + *(ptr2++) = c; + continue; + } + + memcpy(ptr2, &CP1252toUTF8Data[c - 128], CP1252toUTF8DataLen[c - 128]); + ptr2 += CP1252toUTF8DataLen[c - 128]; + } + + return (int)(ptr2 - UTF); +} + +int TrytoGuessCode(unsigned char * Char, int Len) +{ + int Above127 = 0; + int LineDraw = 0; + int NumericAndSpaces = 0; + int n; + + for (n = 0; n < Len; n++) + { + if (Char[n] < 65) + { + NumericAndSpaces++; + } + else + { + if (Char[n] > 127) + { + Above127++; + if (Char[n] == 0xF8 || (Char[n] > 178 && Char[n] < 224)) + { + LineDraw++; + } + } + } + } + + if (Above127 == 0) // Doesn't really matter! + return 1252; + + if (Above127 == LineDraw) + return 437; // If only Line Draw chars, assume line draw + + // If mainly below 128, it is probably Latin if mainly above, probably Cyrillic + + if ((Len - (NumericAndSpaces + Above127)) < Above127) + return 1251; + else + return 1252; +} + +unsigned char outbuffer[16384]; // I don't think this needs to be thread safe + +int checkUTF8(unsigned char * in, int len, unsigned char * out) +{ + // We mustn't mess with input string + + unsigned char Msg[8192]; + int u, code = convUTF8; + + if (convUTF8 == -1 || !Is8Bit(in, len)) + { + // just copy to output + + memcpy(out, in, len); + return len; + } + + // Convert + + memcpy(Msg, in, len); + Msg[len] = 0; + + if (convUTF8 == 0) // Auto - Try to guess encoding + code = TrytoGuessCode(Msg, len); + + if (code == 437) + u = Convert437toUTF8(Msg, len, out); + else if (code == 1251) + u = Convert1251toUTF8(Msg, len, out); + else + u = Convert1252toUTF8(Msg, len, out); + + return u; + +} +