From 14657a895ce46ef2ae8c9ccf7774a6f4b5231d7f Mon Sep 17 00:00:00 2001 From: Dave Hibberd Date: Fri, 8 Dec 2023 15:48:31 +0000 Subject: [PATCH] New upstream version 0.0.0.73 --- .gitattributes | 63 + .gitignore | 261 + 250x1000Hz.wav | Bin 0 -> 6044 bytes 250x600Hz.wav | Bin 0 -> 6044 bytes AGWCode.cpp | 3172 +++---- AGWConnect.ui | 296 +- AGWParams.ui | 790 +- AlertSetup.ui | 545 ++ ColourConfig.ui | 880 +- DialogButtonBottom.ui | 200 +- KISSConfig.ui | 791 +- ListenPort.ui | 280 +- PCBeep.wav | Bin 0 -> 35152 bytes QtTermTCP.cpp | 15002 +++++++++++++++++---------------- QtTermTCP.h | 590 +- QtTermTCP.icns | Bin 0 -> 358400 bytes QtTermTCP.ini | 121 - QtTermTCP.pro | 5 +- QtTermTCP.pro.user | 714 +- QtTermTCP.qrc | 13 +- QtTermTCP.sln | 50 +- QtTermTCP.ui | 132 +- QtTermTCP.vcxproj | 922 +- QtTermTCP.vcxproj.filters | 266 +- QtTermTCP.vcxproj.user | 49 +- QtTermTCP52.zip | Bin 251141 -> 0 bytes Ring.wav | Bin 0 -> 63760 bytes TCPHostConfig.ui | 170 +- TabDialog.cpp | 2348 +++--- TabDialog.h | 416 +- TermTCPCommon.cpp | 2383 +++--- UZ7HOUtils.c | 638 +- VARA.ui | 296 +- VARAConfig.ui | 1216 +-- TeleText.ui => YAPPRxSize.ui | 222 +- ax25.c | 5993 ++++++------- ax25.h | 595 +- ax25_l2.c | 3472 ++++---- debug/moc_predefs.h | 12 + ding.wav | Bin 0 -> 72972 bytes hid.c | 1820 ++-- main.cpp | 52 +- makeit | 15 +- release/moc_predefs.h | 11 + ui_TCPHostConfig.h | 65 - utf8Routines.cpp | 1194 +-- 46 files changed, 23872 insertions(+), 22188 deletions(-) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 250x1000Hz.wav create mode 100644 250x600Hz.wav create mode 100644 AlertSetup.ui create mode 100644 PCBeep.wav create mode 100644 QtTermTCP.icns delete mode 100644 QtTermTCP.ini delete mode 100644 QtTermTCP52.zip create mode 100644 Ring.wav rename TeleText.ui => YAPPRxSize.ui (74%) create mode 100644 debug/moc_predefs.h create mode 100644 ding.wav create mode 100644 release/moc_predefs.h delete mode 100644 ui_TCPHostConfig.h diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c4efe2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,261 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc \ No newline at end of file diff --git a/250x1000Hz.wav b/250x1000Hz.wav new file mode 100644 index 0000000000000000000000000000000000000000..0079795415083f1335fb96212be173cbedb91fd8 GIT binary patch literal 6044 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 Bo3;P| literal 0 HcmV?d00001 diff --git a/AGWCode.cpp b/AGWCode.cpp index 9539b07..00e319f 100644 --- a/AGWCode.cpp +++ b/AGWCode.cpp @@ -1,1567 +1,1605 @@ -// 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 QColor newTabText; - -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; - } - - if (TermMode == Single && (singlemodeFormat & Mon)) - { - //Re-renable TCP connects - - for (int i = 0; i < MAXHOSTS; i++) - actHost[i]->setVisible(1); - } - - - 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 == Single && (singlemodeFormat & Mon)) - { - // Can't be connected, so leave state alone, but disable TCP connects - - for (int i = 0; i < MAXHOSTS; i++) - actHost[i]->setVisible(0); - - } - else 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); - tabWidget->tabBar()->setTabTextColor(i, newTabText); - } - - 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); - } -} +// 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 QColor newTabText; + +extern QTabWidget *tabWidget; +extern QWidget * mythis; + +extern int AGWEnable; +extern int AGWMonEnable; +extern int AGWLocalTime; +extern int AGWMonNodes; +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 Ui_ListenSession * KISSMonSess; + +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); +void Send_AGW_R_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[32]; + 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; + } + + if (TermMode == Single && (singlemodeFormat & Mon)) + { + //Re-renable TCP connects + + for (int i = 0; i < MAXHOSTS; i++) + actHost[i]->setVisible(1); + } + + + 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 && (AGWUsers == NULL || (S != AGWUsers->MonSess)) && S != KISSMonSess) + { + 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 && S->AGWSession == NULL && (AGWUsers == NULL || (S != AGWUsers->MonSess)) && S != KISSMonSess) + { + Sess = S; + break; + } + } + } + else if (TermMode == Single && (singlemodeFormat & Mon)) + { + S = _sessions.at(0); + + if (S->clientSocket == NULL && S->AGWSession == NULL && S->KISSSession == NULL && (AGWUsers == NULL || (S != AGWUsers->MonSess)) && S != KISSMonSess) + 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 == Single && (singlemodeFormat & Mon)) + { + // Can't be connected, so leave state alone, but disable TCP connects + + for (int i = 0; i < MAXHOSTS; i++) + actHost[i]->setVisible(0); + + } + else if (TermMode != Tabbed) // Not ideal, but AGW mon window is unlikely to be active window + { + discAction->setEnabled(false); + YAPPSend->setEnabled(false); + connectMenu->setEnabled(false); + } + + Sess->mlocaltime = AGWLocalTime; + Sess->MonitorNODES = AGWMonNodes; + + Send_AGW_R_Frame(sender); // Request Version + 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]; + + // Request Monitoring. Add Extended form if connected to BPQ + + AGW_frame_header(Msg, 0, 'm', 0, "", "", 12); + + Msg[AGWHDDRRLEN] = AGWLocalTime; + Msg[AGWHDDRRLEN + 1] = AGWMonNodes; + Msg[AGWHDDRRLEN + 2] = AGWMonEnable; + Msg[AGWHDDRRLEN + 3] = 0; + + memcpy(&Msg[AGWHDDRRLEN + 4], (void *)&AGWUsers->MonSess->portmask, 8); + + socket->write((char *)Msg, AGWHDDRRLEN + 12); +} + +void Send_AGW_R_Frame(QTcpSocket* socket) +{ + UCHAR Msg[512]; + + // Request Version + + AGW_frame_header(Msg, 0, 'R', 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); + + QApplication::alert(mythis, 0); + + if (TermMode == MDI) + { + Sess->setWindowTitle(Title); + } + else if (TermMode == Tabbed) + { + tabWidget->setTabText(i, CallFrom); + tabWidget->tabBar()->setTabTextColor(i, newTabText); + } + + 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(&ConnectWAV); + + // 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 AGW Connection 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); + } +} + +extern "C" void WritetoMonWindow(Ui_ListenSession * Sess, unsigned char * Msg, int len); + +void on_AGW_Mon_frame(byte * Msg, int Len, char Type) +{ + if (AGWUsers && AGWUsers->MonSess && AGWUsers->MonSess->monWindow) + { + unsigned char copy[512]; + + copy[0] = 0x1b; + if (Type == 'T') + copy[1] = 91; + else + copy[1] = 17; + + memcpy(©[2], Msg, Len); + WritetoMonWindow(AGWUsers->MonSess, copy, Len + 2); + } +} + + + +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[32]; + 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 index c5e4506..7e436e4 100644 --- a/AGWConnect.ui +++ b/AGWConnect.ui @@ -1,148 +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 - - - - - + + + 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 index 7a5fc45..a72b545 100644 --- a/AGWParams.ui +++ b/AGWParams.ui @@ -1,395 +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 - - - - - + + + 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/AlertSetup.ui b/AlertSetup.ui new file mode 100644 index 0000000..6addb82 --- /dev/null +++ b/AlertSetup.ui @@ -0,0 +1,545 @@ + + + AlertDialog + + + + 0 + 0 + 554 + 433 + + + + Dialog + + + + + 94 + 376 + 351 + 33 + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + OK + + + + + + + Cancel + + + + + + + + + 24 + 40 + 201 + 20 + + + + Beep on Inbound connect + + + + + + 24 + 65 + 221 + 21 + + + + Beep on message after inactivity + + + + + + 24 + 90 + 201 + 20 + + + + Beep on keyword + + + + + + 350 + 65 + 51 + 20 + + + + + + + 350 + 95 + 137 + 20 + + + + + + + 24 + 204 + 82 + 20 + + + + Use Beep + + + buttonGroup + + + + + + 148 + 204 + 193 + 20 + + + + Use Audio Files + + + buttonGroup + + + + + + 258 + 65 + 47 + 20 + + + + Interval + + + + + + 24 + 263 + 107 + 20 + + + + Interval .wav File + + + + + + 24 + 288 + 169 + 20 + + + + Inbound Connect .wav File + + + + + + 24 + 313 + 165 + 20 + + + + Keyword Alert .wav File + + + + + + 258 + 95 + 89 + 20 + + + + Keyword File + + + + + + 20 + 134 + 465 + 65 + + + + The System Beep function doesn't work on all platform, so you can configure QtTermTCP to play a sound file instead of using Beep. You can set different files for each event if you wish. Select from the supplied sounds or choose your own .wav files. + + + true + + + + + + 416 + 263 + 61 + 20 + + + + Choose + + + + + + 416 + 288 + 61 + 20 + + + + Choose + + + + + + 416 + 313 + 61 + 20 + + + + Choose + + + + + + 24 + 15 + 289 + 20 + + + + Use Bells (ascii BELL char makes a sound) + + + + + + 416 + 238 + 61 + 20 + + + + Choose + + + + + + 24 + 238 + 107 + 20 + + + + Bells .wav File + + + + + + 484 + 288 + 47 + 20 + + + + Test + + + + + + 484 + 263 + 47 + 20 + + + + Test + + + + + + 484 + 313 + 47 + 20 + + + + Test + + + + + + 484 + 238 + 47 + 20 + + + + Test + + + + + + 36 + 336 + 471 + 25 + + + + It may take a second or two for sound to play after pressing Test button + + + Qt::AlignCenter + + + + + + 200 + 238 + 207 + 20 + + + + true + + + QComboBox::InsertAtTop + + + + :/PCBeep + + + + + :/LowTone + + + + + :/HighTone + + + + + :/Ring + + + + + :/Ding + + + + + + + 200 + 264 + 207 + 20 + + + + true + + + QComboBox::InsertAtTop + + + + :/PCBeep + + + + + :/LowTone + + + + + :/HighTone + + + + + :/Ring + + + + + :/Ding + + + + + + + 200 + 288 + 207 + 20 + + + + true + + + QComboBox::InsertAtTop + + + + :/PCBeep + + + + + :/LowTone + + + + + :/HighTone + + + + + :/Ring + + + + + :/Ding + + + + + + + 200 + 314 + 207 + 20 + + + + true + + + QComboBox::InsertAtTop + + + + :/PCBeep + + + + + :/LowTone + + + + + :/HighTone + + + + + :/Ring + + + + + :/Ding + + + + + + + + + + diff --git a/ColourConfig.ui b/ColourConfig.ui index d980e87..882ca6e 100644 --- a/ColourConfig.ui +++ b/ColourConfig.ui @@ -1,440 +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 - - - - - - + + + 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/DialogButtonBottom.ui b/DialogButtonBottom.ui index 5159f82..45832a0 100644 --- a/DialogButtonBottom.ui +++ b/DialogButtonBottom.ui @@ -1,100 +1,100 @@ - - - - - Dialog - - - - 0 - 0 - 400 - 300 - - - - Dialog - - - - - 20 - 250 - 351 - 33 - - - - - 0 - - - 6 - - - - - Qt::Horizontal - - - - 131 - 31 - - - - - - - - OK - - - - - - - Cancel - - - - - - - - - - - okButton - clicked() - Dialog - accept() - - - 278 - 253 - - - 96 - 254 - - - - - cancelButton - clicked() - Dialog - reject() - - - 369 - 253 - - - 179 - 282 - - - - - + + + + + Dialog + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + + 20 + 250 + 351 + 33 + + + + + 0 + + + 6 + + + + + Qt::Horizontal + + + + 131 + 31 + + + + + + + + OK + + + + + + + Cancel + + + + + + + + + + + okButton + clicked() + Dialog + accept() + + + 278 + 253 + + + 96 + 254 + + + + + cancelButton + clicked() + Dialog + reject() + + + 369 + 253 + + + 179 + 282 + + + + + diff --git a/KISSConfig.ui b/KISSConfig.ui index 183778e..b0c1ead 100644 --- a/KISSConfig.ui +++ b/KISSConfig.ui @@ -1,381 +1,410 @@ - - - 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 - - - - - + + + KISSDialog + + + + 0 + 0 + 432 + 319 + + + + KISS Configuration + + + + + 40 + 262 + 351 + 33 + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + OK + + + + + + + Cancel + + + + + + + + + 10 + 120 + 401 + 57 + + + + TCP Setup + + + + + 78 + 20 + 111 + 22 + + + + + + + 290 + 21 + 47 + 22 + + + + + + + 16 + 24 + 47 + 13 + + + + Host + + + + + + 222 + 24 + 47 + 13 + + + + Port + + + + + + 85 + 63 + 47 + 22 + + + + + + + 20 + 66 + 47 + 13 + + + + Port + + + + + + + 10 + 70 + 411 + 51 + + + + Serial TNC + + + + + 138 + 20 + 111 + 22 + + + + + + + 20 + 23 + 131 + 16 + + + + Select Device + + + + + + 276 + 20 + 47 + 22 + + + + Speed + + + + + + 328 + 21 + 51 + 22 + + + + 19200 + + + + + + + 12 + 12 + 149 + 21 + + + + Enable KISS Interface + + + + + + 158 + 10 + 23 + 25 + + + + Qt::RightToLeft + + + + + + + + + 214 + 13 + 61 + 17 + + + + MYCALL + + + + + + 286 + 10 + 91 + 22 + + + + + + + 25 + 192 + 47 + 13 + + + + Paclen + + + + + + 98 + 189 + 47 + 22 + + + + + + + 213 + 192 + 66 + 16 + + + + MaxFrame + + + + + + 288 + 189 + 47 + 22 + + + + + + + 25 + 226 + 47 + 13 + + + + Frack + + + + + + 98 + 223 + 36 + 22 + + + + + + + 213 + 225 + 47 + 13 + + + + Retries + + + + + + 288 + 222 + 36 + 22 + + + + + + + 12 + 38 + 149 + 21 + + + + Allow incoming connects + + + + + + 158 + 38 + 23 + 21 + + + + Qt::RightToLeft + + + + + + + + + + okButton + clicked() + KISSDialog + accept() + + + 278 + 253 + + + 96 + 254 + + + + + cancelButton + clicked() + KISSDialog + reject() + + + 369 + 253 + + + 179 + 282 + + + + + diff --git a/ListenPort.ui b/ListenPort.ui index c46838a..e78574f 100644 --- a/ListenPort.ui +++ b/ListenPort.ui @@ -1,140 +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 - - - - - - - - - - - + + + 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/PCBeep.wav b/PCBeep.wav new file mode 100644 index 0000000000000000000000000000000000000000..8ff2dd9009a1cd1de23e972cea123b6559b81f60 GIT binary patch literal 35152 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

DuaRJrF%!Kc&+=vAi_;Ij-MN1^{n4nGm7mM9 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�$Wm%1zv0lWq#fIPSqdJFN87On$#hWo(P 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 literal 0 HcmV?d00001 diff --git a/QtTermTCP.cpp b/QtTermTCP.cpp index b029b7e..6328adb 100644 --- a/QtTermTCP.cpp +++ b/QtTermTCP.cpp @@ -1,7138 +1,7864 @@ -// Qt Version of BPQTermTCP - -#define VersionString "0.0.0.66" - -// .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 input 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 -// .56 Add Teletext mode Nov 2022 -// .57 Fix KISS mode incoming call handling -// .58 Add method to toggle Normal/Teletext Mode -// Fix KISS multiple session protection -// .59 Add Teletext double height mode -// .60 Add option to name sessions Jan 2023 -// .61 Add VARA Init Script Feb 2023 -// .62 Fix running AGW in single session mode Feb 2023 -// .63 Fix handling split monitor frame (no fe in buffer) Apr 2023 -// .64 Add Clear Screen command to context menu Apr 2023 -// .65 Fixes for 63 port version of BPQ May 2023 -// .66 Colour Tab of incoming calls June 2023 - -#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 -#include -#ifdef USESERIAL -#include -#include -#endif -#ifndef WIN32 -#define strtok_s strtok_r -#endif -#include -#include -#ifndef WIN32 -#include -#endif - -#include "ax25.h" - -#define UNREFERENCED_PARAMETER(P) (P) - -void DecodeTeleText(Ui_ListenSession * Sess, char * text); - -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] = { "" }; -char SessName[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); - -QColor newTabText = qRgb(255, 0, 0); // Red -QColor oldTabText = qRgb(0, 0, 0); // Black - - -// 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; - -extern int AutoTeletext; - -// 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]; -extern "C" UCHAR axMYCALL[7]; // Mycall in ax.25 - -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 VARAInit[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[65]; -QAction *actChatMode; -QAction *actAutoTeletext; -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|%s", Host[n], Port[n], UserName[n], Password[n], MonParams[n], SessName[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); - Param = Rest; - - Rest = strlop(Param, '|'); - strcpy(MonParams[n], Param); - - if (Rest) - strcpy(SessName[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); - -void DoTermResize(Ui_ListenSession * Sess) -{ - - 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)); - - if (Sess->TTActive) - { - Sess->TTLabel->setGeometry(QRect(Border, Border, 600, 475)); -// Sess->termWindow->setVisible(false); - Sess->TTLabel->setVisible(true); - } - else - { -// Sess->termWindow->setVisible(true); - Sess->TTLabel->setVisible(false); - } - - 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)); - - if (Sess->TTActive) - { - Sess->TTLabel->setGeometry(QRect(3, Mid + Border, 600, 475)); -// Sess->termWindow->setVisible(false); - Sess->TTLabel->setVisible(true); - } - else - { -// Sess->termWindow->setVisible(true); - Sess->TTLabel->setVisible(false); - } - - 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)); - } -} - -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) - { - DoTermResize(Sess); - } - } - - 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; - Sess->TTActive = 0; - Sess->TTFlashToggle = 0; - Sess->pageBuffer[0] = 0; - - _sessions.push_back(Sess); - -// Sess->TT = new Ui_TeleTextDialog(); - -// Sess->TT->setupUi(&Sess->TTUI); - -// Sess->TTUI.show(); -// Sess->TTUI.raise(); -// Sess->TTUI.activateWindow(); - - // 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); - - Sess->TTLabel = new QLabel(Sess); - Sess->TTLabel->setGeometry(QRect(0,0 ,500, 500)); - Sess->TTLabel->setVisible(false); - - Sess->TTBitmap = new QImage(40 * 15, 25 * 19, QImage::Format_RGB32); - Sess->TTBitmap->fill(Qt::black); - -/* - - char Page[4096]; - - QFile file("/savepage.txt"); - file.open(QIODevice::ReadOnly); - file.read(Page, 4096); - file.close(); - - Sess->TTActive = 1; - strcpy(Sess->pageBuffer, Page); - DecodeTeleText(Sess, Sess->pageBuffer); - -*/ - - Sess->TTLabel->setPixmap(QPixmap::fromImage(*Sess->TTBitmap)); - - Sess->TTLabel->setContextMenuPolicy(Qt::CustomContextMenu); - mythis->connect(Sess->TTLabel, SIGNAL(customContextMenuRequested(const QPoint&)), - parent, SLOT(showContextMenuL())); - - 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 || Type == Listen) - { - // Term Only or Listen Only - - Sess->termWindow->setContextMenuPolicy(Qt::CustomContextMenu); - - mythis->connect(Sess->termWindow, SIGNAL(customContextMenuRequested(const QPoint&)), - parent, SLOT(showContextMenuT(const QPoint &))); - - } - - if (Type == Mon) - { - // Monitor Only - - Sess->monWindow->setContextMenuPolicy(Qt::CustomContextMenu); - - mythis->connect(Sess->monWindow, SIGNAL(customContextMenuRequested(const QPoint&)), - parent, SLOT(showContextMenuMOnly(const QPoint &))); - - } - - if (Type == (Term | Mon)) - { - // Combined Term and 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(showContextMenuMT(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++) - { - if (SessName[i][0]) - { - char Lable[256]; - sprintf(Lable, "%s(%s)", Host[i], SessName[i]); - actHost[i] = new QAction(Lable, this); - } - else - 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]) - { - char Label[256]; - - if (SessName[i][0]) - sprintf(Label, "%s(%s)", Host[i], SessName[i]); - else - strcpy(Label, Host[i]); - - actSetup[i] = new QAction(Label, 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); - actAutoTeletext = setupMenuLine(setupMenu, (char *)"Auto switch to Teletext", this, AutoTeletext); - 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(); - - memset(axMYCALL, 0, 7); - ConvToAX25(MYCALL, axMYCALL); -} - -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(); -} - -void QtTermTCP::setVDMode() -{ - QAction * sender = static_cast(QObject::sender()); - - QTextEdit * window = static_cast(sender->parentWidget()); - - // Need to find session with this window as owner - - Ui_ListenSession * Sess = NULL; - - - for (int i = 0; i < _sessions.size(); ++i) - { - Sess = _sessions.at(i); - - if (Sess->termWindow == window) - { - Sess->TTActive ^= 1; - DoTermResize(Sess); - break; - } - } -} - -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::ClearScreen() -{ - QAction * sender = static_cast(QObject::sender()); - QTextEdit * window = static_cast(sender->parentWidget()); - window->clear(); -} - - -void QtTermTCP::showContextMenuMT(const QPoint &pt) // Term Window -{ - // Monitor and Terminal (Term Half) - - 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); - QAction * actVDMode = new QAction("Toggle Viewdata Mode", sender); - QAction * actClear = new QAction("Clear Screen Buffer", sender); - - menu->addAction(actSplit); - menu->addAction(actVDMode); - menu->addAction(actClear); - - splitY = pt.y() + termX; - - connect(actSplit, SIGNAL(triggered()), this, SLOT(setSplit())); - connect(actVDMode, SIGNAL(triggered()), this, SLOT(setVDMode())); - connect(actClear, SIGNAL(triggered()), this, SLOT(ClearScreen())); - - menu->exec(sender->mapToGlobal(pt)); - delete menu; -} - -void QtTermTCP::showContextMenuMOnly(const QPoint &pt) -{ - // Monitor only - - 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 * actClear = new QAction("Clear Screen Buffer", sender); - - menu->addAction(actClear); - connect(actClear, SIGNAL(triggered()), this, SLOT(ClearScreen())); - - menu->exec(sender->mapToGlobal(pt)); - delete menu; -} - - -void QtTermTCP::showContextMenuT(const QPoint &pt) // Term Window -{ - // Just Terminal - - 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 * actVDMode = new QAction("Toggle Viewdata Mode", sender); - QAction * actClear = new QAction("Clear Screen Buffer", sender); - - menu->addAction(actVDMode); - menu->addAction(actClear); - - connect(actVDMode, SIGNAL(triggered()), this, SLOT(setVDMode())); - connect(actClear, SIGNAL(triggered()), this, SLOT(ClearScreen())); - - menu->exec(sender->mapToGlobal(pt)); - delete menu; -} - - -void QtTermTCP::showContextMenuL() // Term Window -{ - // Teletext Label Right Clicked - cancel TT Mode - - QLabel * sender = static_cast(QObject::sender()); - - // Need to find session with this label as owner - - Ui_ListenSession * Sess = NULL; - - for (int i = 0; i < _sessions.size(); ++i) - { - Sess = _sessions.at(i); - - if (Sess->TTLabel == sender) - { - Sess->TTActive ^= 1; - - DoTermResize(Sess); - break; - } - } -} - - -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); - QAction * actClear = new QAction("Clear Screen Buffer", sender); - - menu->addAction(actSplit); - menu->addAction(actClear); - - splitY = pt.y(); - - connect(actSplit, SIGNAL(triggered()), this, SLOT(setSplit())); - connect(actClear, SIGNAL(triggered()), this, SLOT(ClearScreen())); - - 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; - - tabWidget->tabBar()->setTabTextColor(tabWidget->currentIndex(), oldTabText); - - 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 == actAutoTeletext) - AutoTeletext = 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 + 1; i++) - { - if (Act == MonPort[i]) - { - unsigned long long mmask; - - if (i == 0) // BBS Mon - use bit 63 (Port 64) - mmask = 1ll << 63; - 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(); - - if (Sess->TTActive) // Teletext uses 0x5F for # - while ((ptr = strchr(Msgptr, 0x23))) // Replace VT with LF - *ptr++ = 0x5f; - - - while ((ptr = strchr(Msgptr, 11))) // Replace VT with LF - *ptr++ = 10; - - LastWrite = time(NULL); // Stop initial beep - Sess->SlowTimer = 0; - - Sess->pageBuffer[0] = 0; // Reset Teletext mode screen buffer - - 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(); - // } - - - Buffer[Read] = 0; - - 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(); - } - - // if tabbed and not active tab set tab label red - - if (TermMode == Tabbed) - { - if (Sess->monWindow != termWindow) // Not if Monitor - { - if (ActiveSession != Sess) - { - tabWidget->tabBar()->setTabTextColor(Sess->Tab, newTabText); - } - } - } - - 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) -{ - if (Sess == 0) - return; - - Sess->SlowTimer = 0; - Msg[Len] = 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; - } - - if (AutoTeletext && (Msg[0] == 0x1e || Msg[0] == 0x0c)) - { - if (Sess->TTActive == 0) - { - Sess->TTActive = 1; - DoTermResize(Sess); - } - } - - if (Sess->TTActive) - { - // Feed to Teletext code - - // We need to decode a whole page. There is no obvious delimiter so process till data stops. - // Buffer is cleared when next input is sent - - if (strlen(&Sess->pageBuffer[0] + Len) > 4090) - Sess->pageBuffer[0] = 0; // Protect buffer - - strcat(Sess->pageBuffer, (char *)Msg); - - DecodeTeleText(Sess, (char *)Sess->pageBuffer); // Re-decode same data until we get the end - 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(); - AutoTeletext = settings->value("AutoTeletext", 0).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(VARAInit, settings->value("VARAInit", "").toString().toUtf8()); - - 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("AutoTeletext", AutoTeletext); - 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("VARAInit", VARAInit); - 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()); - - memset(axMYCALL, 0, 7); - ConvToAX25(MYCALL, axMYCALL); - - 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()); - strcpy(VARAInit, Dev->InitCommands->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()); - tabWidget->tabBar()->setTabTextColor(i, newTabText); - } - - 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"); - } - } - - if (Sess->TTActive) - { - Sess->TTActive = 0; - DoTermResize(Sess); - } - - 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 - { - char Label[256]; - - if (SessName[Sess->CurrentHost][0]) - sprintf(Label, "%s(%s)", Host[Sess->CurrentHost], SessName[Sess->CurrentHost]); - else - strcpy(Label, Host[Sess->CurrentHost]); - - sprintf(Title, "Connected to %s", Label); - } - - if (TermMode == MDI) - Sess->setWindowTitle(Title); - else if (TermMode == Tabbed) - { - if (SessName[Sess->CurrentHost][0]) - tabWidget->setTabText(tabWidget->currentIndex(), SessName[Sess->CurrentHost]); - else - 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); - tabWidget->tabBar()->setTabTextColor(Sess->Tab, newTabText); - } - 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 VARACommand[256]; - - VARAConnected = 1; - VARAConnecting = 0; - - Status2->setText("VARA Connected"); - - actHost[17]->setVisible(1); // Enable VARA Connect Line - - sprintf(VARACommand, "MYCALL %s\r", VARATermCall); - VARASock->write(VARACommand); - - 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 (VARAInit[0]) - { - char Copy[512]; - char * param, *context; - - strcpy(Copy, VARAInit); - - param = strtok_s(Copy, ",", &context); - - while (param && param[0]) - { - sprintf(VARACommand, "%s\r", param); - VARASock->write(VARACommand); - param = strtok_s(nullptr, ",", &context); - } - } - - 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); - Dev->InitCommands->setText(VARAInit); -} - -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); - tabWidget->tabBar()->setTabTextColor(i, newTabText); - } - 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; -} - -// Experimental Viewdata/Teletext/Prestel/CEEFAX Mode - -// Uses ideas and some code from - -/*************************************************************** - * Name: wxTEDMain.cpp - * Purpose: Teletext editor Application Frame - * Author: Peter Kwan (peterk.vt80@gmail.com) - * Created: 2014-10-30 - * Copyright: Peter Kwan - * License: - * - * Copyright (C) 2014-2022, Peter Kwan - * - * Permission to use, copy, modify, and distribute this software - * and its documentation for any purpose and without fee is hereby - * granted, provided that the above copyright notice appear in all - * copies and that both that the copyright notice and this - * permission notice and warranty disclaimer appear in supporting - * documentation, and that the name of the author not be used in - * advertising or publicity pertaining to distribution of the - * software without specific, written prior permission. - * - * The author disclaims all warranties with regard to this - * software, including all implied warranties of merchantability - * and fitness. In no event shall the author be liable for any - * special, indirect or consequential damages or any damages - * whatsoever resulting from loss of use, data or profits, whether - * in an action of contract, negligence or other tortious action, - * arising out of or in connection with the use or performance of - * this software. - *************************************************************************** **/ - - - -int FontWidth = 16; -int FontHeight = 16; - -bool isMosaic(char ch) -{ - ch &= 0x7f; - return (ch >= 0x20 && ch < 0x40) || ch >= 0x60; -} - - -void DecodeTeleText(Ui_ListenSession * Sess, char * page) -{ - - // redraw the whole teletext page - - QColor fg = Qt::white; - QColor bg = Qt::black; - QColor holdColour; - QColor holdfg = fg; // Colour for held graphic - int usehold = 0; - int sep = 0; - - QPainter p(Sess->TTBitmap); - - QSettings settings(GetConfPath(), QSettings::IniFormat); - - //p.setFont(QFont("Courier", 14, QFont::Bold)); - - p.setFont(QFont(settings.value("FontFamily", "Courier New").value(), 14)); - - p.setPen(QPen(fg)); - - bool graphicsMode = false; - bool separated = false; - bool doubleHeight = false; - int skipnextrow = 99; - bool flashing = false; - bool hold = false; - char holdChar = 0; - char holdMode = 0; - Sess->timer.stop(); - - bool concealed = false; - char c; - - fg = Qt::white; - bg = Qt::black; - - char * ptr = page; - - int col = 0; - int line = 0; - - // XXXXXXXXTEEFAX %%# %%a %d %%b C %H:%M.%S - - //p.drawText(0, 19, "P199 HAMFAX"); - - - // interpret data, for now, line by line - - - while ((c = *(ptr++))) - { - char ch = c; - - if (c == 0x11) // Curson On ?? - continue; - - if (c == 0x14) // Curson Off ?? - continue; - - if (c == 9) // Curson On ?? - { - col++; - continue; - } - - if (c == 0x0a) - { - line++; - -// if (doubleHeight) -// line++; - - if (line > 24) - line = 0; - - continue; - } - - if (c == 0x1e) // Cursor Home - { - line = col = 0; - c = 13; // So we reset page flags below - } - - if (c == 12) - { - // Clear the page - - Sess->TTBitmap->fill(Qt::black); - Sess->TTLabel->setPixmap(QPixmap::fromImage(*Sess->TTBitmap)); - - line = col = 0; - - c = 13; // So we reset page flags below - } - - if (c == 13) - { - col = 0; - - graphicsMode = false; - separated = false; - doubleHeight = false; - flashing = false; - hold = false; - holdChar = 0; - concealed = false; - - fg = Qt::white; - bg = Qt::black; - - continue; // Next line - } - - if (c == 0x1b) // Esc - { - // I think the control char is displayed as it was before being actioned, sa save current state - - holdfg = holdColour; // Colour if using held - may be changed before displaying - usehold = hold; // May be changed - sep = holdMode; - - char echar = *(ptr++); - - if (echar == 0) - { - // Esc pair spilt - wait for rest - - Sess->TTLabel->setPixmap(QPixmap::fromImage(*Sess->TTBitmap)); - return; - } - - switch (echar) - { - case '@': - fg = Qt::black; - concealed = false; // Side effect of colour. It cancels a conceal. - graphicsMode = false; -// hold = false; - - break; - case 'A': - fg = Qt::red; - concealed = false; - graphicsMode = false; - hold = false; - - break; - case 'B': - fg = Qt::green; - concealed = false; - graphicsMode = false; - hold = false; - break; - case 'C': - fg = Qt::yellow; - concealed = false; - graphicsMode = false; - hold = false; - break; - case 'D': - fg = Qt::blue; - concealed = false; - graphicsMode = false; - hold = false; - break; - case 'E': - fg = Qt::magenta; - concealed = false; - graphicsMode = false; - hold = false; - break; - case 'F': - fg = Qt::cyan; - concealed = false; - graphicsMode = false; - hold = false; - break; - case 'G': - fg = Qt::white; - concealed = false; - graphicsMode = false; - hold = false; - break; - case 'H': // Flash - flashing = true; - Sess->timer.start(1000, Sess); - concealed = false; - graphicsMode = false; - hold = false; - - break; - - case 'I': // Steady - flashing = false; - concealed = false; - graphicsMode = false; - hold = false; - - break; - // - case 'J': //ttxCodeEndBox: - case 'K': //ttxCodeStartBox: - - concealed = false; - graphicsMode = false; - hold = false; - - break; - case 'L': // Normal height - doubleHeight = false; - hold = false; - break; - - case 'M': // Double height - doubleHeight = true; - skipnextrow = line + 1; // ETSI: row to ignore - hold = false; - holdChar = 0; - break; - case 'P': // Graphics black -// concealed = false; - graphicsMode = true; - holdColour = fg = Qt::black; - - break; - case 'Q': // Graphics red -// concealed = false; - graphicsMode = true; - holdColour = fg = Qt::red; - - break; - case 'R': // Graphics green -// concealed = false; - graphicsMode = true; - holdColour = fg = Qt::green; - - break; - case 'S': // Graphics yellow -// concealed = false; - graphicsMode = true; - holdColour = fg = Qt::yellow; - - break; - case 'T': // Graphics blue -// concealed = false; - graphicsMode = true; - holdColour = fg = Qt::blue; - - break; - case 'U': // Graphics magenta -// concealed = false; - graphicsMode = true; - holdColour = fg = Qt::magenta; - - break; - case 'V': // Graphics cyan -// concealed = false; - graphicsMode = true; - holdColour = fg = Qt::cyan; - - break; - case 'W': // Graphics white -// concealed = false; - graphicsMode = true; - holdColour = fg = Qt::white; - - break; - - case 'X': // Conceal display - - concealed = 1; - break; - - case 'Y': // Contiguous graphics - - separated = false; - break; - - case 'Z': // Separated gfx - - separated = true; - break; - - case 0x5c: // Background black - bg = Qt::black; - break; - - case 0x5d: // New background - - bg = fg; - break; - - case 0x5e: // Hold gfx - - if (hold == 0) - { - hold = true; - holdColour = fg; - holdMode = separated; - } - break; - - case 0x5f: // Non-hold gfx - hold = false; - break; - - default: - echar++; // For testign - break; - - } //end of esc case processing - - if (usehold) - { - // We set sep and color for held char earlier - - ch = holdChar; // Repeat held char - } - else - ch = 0x0; // Default is space - - - if (line > skipnextrow) - skipnextrow = 99; - - if (line == skipnextrow) - { - line = line; - } - else - { - if (concealed == 0 && (flashing == 0 || Sess->TTFlashToggle == 0)) - { - p.fillRect(col * 15, line * 19, 15, 19, bg); - - // if double height also draw background for next row - - if (doubleHeight) - p.fillRect(col * 15, line * 19 + 19, 15, 19, bg); - - - if (sep) - { - if (ch & 1) - p.fillRect(col * 15 + 2, line * 19 + 2, 5, 4, holdfg); - if (ch & 2) - p.fillRect(col * 15 + 9, line * 19 + 2, 6, 4, holdfg); - if (ch & 4) - p.fillRect(col * 15 + 2, line * 19 + 8, 5, 4, holdfg); - if (ch & 8) - p.fillRect(col * 15 + 9, line * 19 + 8, 6, 4, holdfg); - if (ch & 16) - p.fillRect(col * 15 + 2, line * 19 + 14, 5, 5, holdfg); - if (ch & 32) - p.fillRect(col * 15 + 9, line * 19 + 14, 6, 5, holdfg); - - } - else - { - if (ch & 1) - p.fillRect(col * 15, line * 19, 7, 6, holdfg); - if (ch & 2) - p.fillRect(col * 15 + 7, line * 19, 8, 6, holdfg); - if (ch & 4) - p.fillRect(col * 15, line * 19 + 6, 7, 6, holdfg); - if (ch & 8) - p.fillRect(col * 15 + 7, line * 19 + 6, 8, 6, holdfg); - if (ch & 16) - p.fillRect(col * 15, line * 19 + 12, 7, 7, holdfg); - if (ch & 32) - p.fillRect(col * 15 + 7, line * 19 + 12, 8, 7, holdfg); - } - } - } - col++; - } - else - { - // Not esc - so normal or graphics - - if (ch < 0x20) - continue; - - if (line > skipnextrow) - skipnextrow = 99; - - if (line == skipnextrow) - { - line = line; - } - else - { - if (concealed == 0 && (flashing == 0 || Sess->TTFlashToggle == 0)) - { - p.fillRect(col * 15, line * 19, 15, 19, bg); - - if (doubleHeight) - p.fillRect(col * 15, line * 19 + 19, 15, 19, bg); - - p.setPen(QPen(fg));; - - if (graphicsMode) - { - if (ch < 0x40) - ch -= 0x20; - else - if (ch >= 0x60) - ch -= 0x40; - else - goto isText; - - holdChar = ch; - - // Now have 00 - 3f - - - // C is bit mask bit posns are - // 01 - // 23 - // 45 - // Char cell is 15 * 19 which is a bit asymetrical - - // Chars are 20 - 3f and 60 to 7f but I cant see a logic to the mapping - - - if (separated) - { - if (ch & 1) - p.fillRect(col * 15 + 2, line * 19 + 2, 5, 4, fg); - if (ch & 2) - p.fillRect(col * 15 + 9, line * 19 + 2, 6, 4, fg); - if (ch & 4) - p.fillRect(col * 15 + 2, line * 19 + 8, 5, 4, fg); - if (ch & 8) - p.fillRect(col * 15 + 9, line * 19 + 8, 6, 4, fg); - if (ch & 16) - p.fillRect(col * 15 + 2, line * 19 + 14, 5, 5, fg); - if (ch & 32) - p.fillRect(col * 15 + 9, line * 19 + 14, 6, 5, fg); - - } - else - { - if (ch & 1) - p.fillRect(col * 15, line * 19, 7, 6, fg); - if (ch & 2) - p.fillRect(col * 15 + 7, line * 19, 8, 6, fg); - if (ch & 4) - p.fillRect(col * 15, line * 19 + 6, 7, 6, fg); - if (ch & 8) - p.fillRect(col * 15 + 7, line * 19 + 6, 8, 6, fg); - if (ch & 16) - p.fillRect(col * 15, line * 19 + 12, 7, 7, fg); - if (ch & 32) - p.fillRect(col * 15 + 7, line * 19 + 12, 8, 7, fg); - } - } - else - { - // Just write char at current col and line - - isText: - char s[5] = " "; - - // Some chars are in wrong char set - - s[0] = ch; - - if (ch == '_') - s[0] = '#'; - - else if (ch == 0x7e) // division - { - s[0] = 0xC3; - s[1] = 0xB7; - } - else if (ch == 0x5e) // up arrow - { - s[0] = 0xF0; - s[1] = 0x9F; - s[2] = 0xA0; - s[3] = 0x95; - } - else if (ch == 0x7f) // up arrow - { - s[0] = 0xE2; - s[1] = 0x96; - s[2] = 0x88; - } - -// if (doubleHeight) - // p.drawText(col * 15, line * 19 + 25, s); - // else - // p.drawText(col * 15, line * 19 + 15, s); - - // if double height draw normally then copy pixels each row of pixels to two scanlines (starting at the bottom) - - if (doubleHeight) - { - int inscanline = line * 19 + 18; - int outscanline = line * 19 + 35; - unsigned char * inptr = Sess->TTBitmap->scanLine(inscanline); - unsigned char * outptr = Sess->TTBitmap->scanLine(outscanline); - int linelen = Sess->TTBitmap->bytesPerLine(); - int charlen = linelen / 40; // bytes per char position - - p.drawText(col * 15, line * 19 + 16, s); - - inptr += col * charlen; - outptr += col * charlen; - - for (int i = 0; i < 18; i++) - { - memcpy(outptr, inptr, charlen); - outptr -= linelen; - memcpy(outptr, inptr, charlen); - - inptr -= linelen; - outptr -= linelen; - - } - } - else - p.drawText(col * 15, line * 19 + 15, s); - - } - } - } - col++; - } - - if (col > 39) - { - col = 0; - line++; - if (line > 24) - line = 0; - - graphicsMode = false; - separated = false; - doubleHeight = false; - flashing = false; - hold = false; - holdChar = 0; - concealed = false; - - fg = Qt::white; - bg = Qt::black; - } - } - Sess->TTLabel->setPixmap(QPixmap::fromImage(*Sess->TTBitmap)); - return; - - QFile file("D:/savepage.txt"); - file.open(QIODevice::WriteOnly); - file.write(Sess->pageBuffer); - file.close(); -} - - -void Ui_ListenSession::timerEvent(QTimerEvent *event) -{ - Ui_ListenSession * Sess = NULL; - - // Used for flashing - resfresh window - - for (int i = 0; i < _sessions.size(); ++i) - { - Sess = _sessions.at(i); - - if (Sess->timer.timerId() == event->timerId()) - { - if (Sess->TTActive) - { - Sess->TTFlashToggle ^= 1; - - // if in Tabbed mode only refresh active tab - - if (TermMode == Tabbed && Sess != ActiveSession) - return; - - DecodeTeleText(Sess, (char *)Sess->pageBuffer); // Re-decode same data until we get the end - } - else - Sess->timer.stop(); - - return; - } - } - QWidget::timerEvent(event); -} - +// Qt Version of BPQTermTCP + +#define VersionString "0.0.0.73" + + +// .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 input 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 +// .56 Add Teletext mode Nov 2022 +// .57 Fix KISS mode incoming call handling +// .58 Add method to toggle Normal/Teletext Mode +// Fix KISS multiple session protection +// .59 Add Teletext double height mode +// .60 Add option to name sessions Jan 2023 +// .61 Add VARA Init Script Feb 2023 +// .62 Fix running AGW in single session mode Feb 2023 +// .63 Fix handling split monitor frame (no fe in buffer) Apr 2023 +// .64 Add Clear Screen command to context menu Apr 2023 +// .65 Fixes for 63 port version of BPQ May 2023 +// .66 Colour Tab of incoming calls June 2023 +// .67 Add config Yapp RX Size dialog July 2023 +// Fix 63 port montoring + +// .68 Sept 2023 + +// Remember last used host on restart +// Add AutoConnect in Tabbed Mode +// Fix auto copy when QtTerm not active window + +// .69 October 2023 + +// Options related to sound alerts moved to a separte menu in Setup +// Allow use of WAV files instead of Beep sound for sound alerts +// Enable an alarm to be sounded when one of a list of words or phrases is received. + +// .70 October 2023 + +// Include some .wav files in resources +// Add Test button to Alert setup dialog + +// .71 October 2023 + +// Add option to use local time +// Fixes for Mac OS + +// .72 November 2023 +// Don't display "Enable Monitor" on startup + +// .73 November 2023 +// Raise RTS on KISS serial port + + + + +#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 +#include +#ifdef USESERIAL +#include +#include +#endif +#ifndef WIN32 +#define strtok_s strtok_r +#endif +#include +#include +#ifndef WIN32 +#include +#endif + +#include "ax25.h" + +#define UNREFERENCED_PARAMETER(P) (P) + +void DecodeTeleText(Ui_ListenSession * Sess, char * text); + +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] = { "" }; +char SessName[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); + +QColor newTabText = qRgb(255, 0, 0); // Red +QColor oldTabText = qRgb(0, 0, 0); // Black + + +// 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; + +bool useBeep; // use wav files if not set + +extern int UseKeywords; +extern QString KeyWordsFile; + +QString ConnectWAV(""); +QString AlertWAV(""); +QString BellWAV(""); +QString IntervalWAV(""); + +extern int MaxRXSize; + +extern int AutoTeletext; + +// AGW Host Interface stuff + +int AGWEnable = 0; +int AGWMonEnable = 0; +int AGWLocalTime = 0; +int AGWMonNodes = 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; + +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; + +extern AGWUser *AGWUsers; // List of currently connected clients +void Send_AGW_m_Frame(QTcpSocket* socket); + + +QStringList AGWToCalls; + +// KISS Interface + +int KISSEnable = 0; +extern "C" int KISSMonEnable; +extern "C" int KISSLocalTime; +extern "C" int KISSMonNodes; +extern "C" int KISSListen; + +char SerialPort[80] = ""; +char KISSHost[128] = "127.0.0.1"; +int KISSPortNum = 1000; +int KISSBAUD = 19200; +char MYCALL[32]; +extern "C" UCHAR axMYCALL[7]; // Mycall in ax.25 + +int KISSMode = 0; // Connected (0) or UI (1) + +myTcpSocket * KISSSock; + +extern "C" void * KISSSockCopy[4]; + +int KISSConnected = 0; +int KISSConnecting = 0; + +Ui_ListenSession * KISSMonSess = nullptr; + +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 VARAInit[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 *EnableMonitor; +QAction *EnableMonLog; +QAction *MonLocalTime; +QAction *MonTX; +QAction *MonSup; +QAction *MonNodes; +QAction *MonUI; +QAction *MonColour; +QAction *MonPort[65]; +QAction *actChatMode; +QAction *actAutoTeletext; +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 *YAPPSetSize; +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 AutoConnect[10] = {0, 0 ,0, 0, 0, 0, 0, 0, 0, 0}; + +int currentHost[10] = {0, 0 ,0, 0, 0, 0, 0, 0, 0, 0}; + +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); + +QDialog * deviceUI; + +#include +#include +#include + + + + +void EncodeSettingsLine(int n, char * String) +{ + sprintf(String, "%s|%d|%s|%s|%s|%s", Host[n], Port[n], UserName[n], Password[n], MonParams[n], SessName[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); + Param = Rest; + + Rest = strlop(Param, '|'); + strcpy(MonParams[n], Param); + + if (Rest) + strcpy(SessName[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); + +void DoTermResize(Ui_ListenSession * Sess) +{ + + 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)); + + if (Sess->TTActive) + { + Sess->TTLabel->setGeometry(QRect(Border, Border, 600, 475)); +// Sess->termWindow->setVisible(false); + Sess->TTLabel->setVisible(true); + } + else + { +// Sess->termWindow->setVisible(true); + Sess->TTLabel->setVisible(false); + } + + 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)); + + if (Sess->TTActive) + { + Sess->TTLabel->setGeometry(QRect(3, Mid + Border, 600, 475)); +// Sess->termWindow->setVisible(false); + Sess->TTLabel->setVisible(true); + } + else + { +// Sess->termWindow->setVisible(true); + Sess->TTLabel->setVisible(false); + } + + 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)); + } +} + +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) + { + DoTermResize(Sess); + } + } + + 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; +int sessNo = 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->mlocaltime = 0; + 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; + Sess->TTActive = 0; + Sess->TTFlashToggle = 0; + Sess->pageBuffer[0] = 0; + Sess->Tab = 0; + + Sess->LogMonitor = false; + Sess->monSpan = (char *) ""; + Sess->monLogfile = nullptr; + Sess->sessNo = sessNo++; + + _sessions.append(Sess); + +// Sess->TT = new Ui_TeleTextDialog(); + +// Sess->TT->setupUi(&Sess->TTUI); + +// Sess->TTUI.show(); +// Sess->TTUI.raise(); +// Sess->TTUI.activateWindow(); + + // 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); + + Sess->TTLabel = new QLabel(Sess); + Sess->TTLabel->setGeometry(QRect(0,0 ,500, 500)); + Sess->TTLabel->setVisible(false); + + Sess->TTBitmap = new QImage(40 * 15, 25 * 19, QImage::Format_RGB32); + Sess->TTBitmap->fill(Qt::black); + +/* + + char Page[4096]; + + QFile file("/savepage.txt"); + file.open(QIODevice::ReadOnly); + file.read(Page, 4096); + file.close(); + + Sess->TTActive = 1; + strcpy(Sess->pageBuffer, Page); + DecodeTeleText(Sess, Sess->pageBuffer); + +*/ + + Sess->TTLabel->setPixmap(QPixmap::fromImage(*Sess->TTBitmap)); + + Sess->TTLabel->setContextMenuPolicy(Qt::CustomContextMenu); + mythis->connect(Sess->TTLabel, SIGNAL(customContextMenuRequested(const QPoint&)), + parent, SLOT(showContextMenuL())); + + 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 || Type == Listen) + { + // Term Only or Listen Only + + Sess->termWindow->setContextMenuPolicy(Qt::CustomContextMenu); + + mythis->connect(Sess->termWindow, SIGNAL(customContextMenuRequested(const QPoint&)), + parent, SLOT(showContextMenuT(const QPoint &))); + + } + + if (Type == Mon) + { + // Monitor Only + + Sess->monWindow->setContextMenuPolicy(Qt::CustomContextMenu); + + mythis->connect(Sess->monWindow, SIGNAL(customContextMenuRequested(const QPoint&)), + parent, SLOT(showContextMenuMOnly(const QPoint &))); + + } + + if (Type == (Term | Mon)) + { + // Combined Term and 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(showContextMenuMT(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; +} + +QByteArray timeLoaded = QDateTime::currentDateTime().toString("yymmdd_hhmmss").toUtf8(); + +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(); + + GetKeyWordFile(); + + // 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) + { + Ui_ListenSession * Sess; + int index = 0; + + tabWidget = new QTabWidget(this); + QTabBar* tabBar = tabWidget->tabBar(); + +// QString style1 = "QTabWidget::tab-bar{left:0;}"; // for Mac +// tabWidget->setStyleSheet(style1); + + QString style = "QTabBar::tab:selected{background-color: #d0d0d0;} QTabBar::tab:!selected{background-color: #f0f0f0;}"; + + tabBar->setStyleSheet(style); + + tabWidget->setContextMenuPolicy(Qt::CustomContextMenu); + + connect(tabWidget, SIGNAL(customContextMenuRequested(const QPoint &)), SLOT(showContextMenu(const QPoint &))); + + ui.verticalLayout->addWidget(tabWidget); + tabWidget->setTabPosition(QTabWidget::South); + + Sess = newWindow(this, TabType[0], "Sess 1"); + Sess->CurrentHost = currentHost[index++]; + Sess = newWindow(this, TabType[1], "Sess 2"); + Sess->CurrentHost = currentHost[index++]; + Sess = newWindow(this, TabType[2], "Sess 3"); + Sess->CurrentHost = currentHost[index++]; + Sess = newWindow(this, TabType[3], "Sess 4"); + Sess->CurrentHost = currentHost[index++]; + Sess = newWindow(this, TabType[4], "Sess 5"); + Sess->CurrentHost = currentHost[index++]; + Sess = newWindow(this, TabType[5], "Sess 6"); + Sess->CurrentHost = currentHost[index++]; + Sess = newWindow(this, TabType[6], "Sess 7"); + Sess->CurrentHost = currentHost[index++]; + Sess = newWindow(this, TabType[7], "Monitor"); + Sess->CurrentHost = currentHost[index++]; + Sess = newWindow(this, TabType[8], "Monitor"); + Sess->CurrentHost = currentHost[index++]; + + 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++) + { + if (SessName[i][0]) + { + char Lable[256]; + sprintf(Lable, "%s(%s)", Host[i], SessName[i]); + actHost[i] = new QAction(Lable, this); + } + else + 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 app 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]) + { + char Label[256]; + + if (SessName[i][0]) + sprintf(Label, "%s(%s)", Host[i], SessName[i]); + else + strcpy(Label, Host[i]); + + actSetup[i] = new QAction(Label, 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(); + AlertAction = new QAction("Sound Alerts Setup", this); + + setupMenu->addAction(AlertAction); + connect(AlertAction, SIGNAL(triggered()), this, SLOT(AlertSlot())); + AlertAction->setFont(*menufont); + 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); + + setupMenu->addSeparator(); + + 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); + + setupMenu->addSeparator(); + + actChatMode = setupMenuLine(setupMenu, (char *)"Chat Terminal Mode (Send Keepalives)", this, ChatMode); + actAutoTeletext = setupMenuLine(setupMenu, (char *)"Auto switch to Teletext", this, AutoTeletext); + actStripLF = setupMenuLine(setupMenu, (char *)"Strip Line Feeds", this, StripLF); + + setupMenu->addSeparator(); + + 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")); + + EnableMonitor = setupMenuLine(monitorMenu, (char *)"Enable Monitoring", this, 0); + EnableMonitor->setVisible(0); + EnableMonLog = setupMenuLine(monitorMenu, (char *)"Log to File", this, 0); + MonLocalTime = setupMenuLine(monitorMenu, (char *)"Use local time", this, 0); + 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())); + + + // Place Listen Action in mac app menu, otherwise it doesn't appear + if (is_mac == true) + { + QMenu * app_menu = mymenuBar->addMenu("App Menu"); + ListenAction->setMenuRole(QAction::ApplicationSpecificRole); + app_menu->addAction(ListenAction); + } + + toolBar->addAction(ListenAction); + + YAPPMenu = mymenuBar->addMenu(tr("&YAPP")); + + YAPPSend = new QAction("Send File", this); + YAPPSetRX = new QAction("Set Receive Directory", this); + YAPPSetSize = new QAction("Set Max Size", this); + YAPPSend->setFont(*menufont); + YAPPSetRX->setFont(*menufont); + YAPPSetSize->setFont(*menufont); + + YAPPMenu->addAction(YAPPSend); + YAPPMenu->addAction(YAPPSetRX); + YAPPMenu->addAction(YAPPSetSize); + YAPPSend->setEnabled(false); + + connect(YAPPSend, SIGNAL(triggered()), this, SLOT(doYAPPSend())); + connect(YAPPSetRX, SIGNAL(triggered()), this, SLOT(doYAPPSetRX())); + connect(YAPPSetSize, SIGNAL(triggered()), this, SLOT(doYAPPSetSize())); + + 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; + Sess->CurrentHost = currentHost[0]; + + ui.verticalLayout->addWidget(Sess); + + connectMenu->setEnabled(true); + discAction->setEnabled(false); + YAPPSend->setEnabled(false); + } + + if (TermMode == MDI) + { + int n = atoi(sessionList); + int index = 0; + + 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 = currentHost[index++];; + + 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(); + + memset(axMYCALL, 0, 7); + ConvToAX25(MYCALL, axMYCALL); + + // Do any autoconnects + + for (int i = 0; i < _sessions.size(); ++i) + { + if (AutoConnect[i] > 0) + { + Ui_ListenSession * Sess = _sessions.at(i); + + 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]); + } + } + +} + +void QtTermTCP::showContextMenu(const QPoint &point) +{ + if (point.isNull()) + return; + + QTabBar* tabBar = tabWidget->tabBar(); + QRect Wrect = tabWidget->rect(); + QRect Brect = tabBar->rect(); + QPoint myPoint = point; + + // Get x coordinate of first tab (on Mac tabs are centre aligned) + + QRect rect = tabWidget->tabBar()->geometry(); + int left = rect.left(); + + + int n = myPoint.y() - (Wrect.height() - Brect.height()); + myPoint.setY(n); + n = myPoint.x() - left; + + myPoint.setX(n); + + int tabIndex = tabBar->tabAt(myPoint); + + QMenu menu(this); + + QAction * Act = new QAction("AutoConnect on load", this); + Act->setObjectName(QString::number(tabIndex)); + + menu.addAction(Act); + + Act->setCheckable(true); + + if (AutoConnect[tabIndex] <= 0) + Act->setChecked(false); + else + Act->setChecked(true); + + connect(Act, SIGNAL(triggered()), this, SLOT(autoConnectChecked())); + + menu.exec(tabWidget->mapToGlobal(point)); + +} + +void QtTermTCP::autoConnectChecked() +{ + QAction * Act = static_cast(QObject::sender()); + QString Tab = Act->objectName(); + int tabNo = Tab.toInt(); + + tabWidget->setCurrentIndex(tabNo); + + Ui_ListenSession *Sess = (Ui_ListenSession *)tabWidget->currentWidget(); + int state = Act->isChecked(); + + if (state == 0) + AutoConnect[tabNo] = 0; + else + { + if (Sess->clientSocket) // Connected + AutoConnect[tabNo] = 1; + else + AutoConnect[tabNo] = 0; + } +} + + +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(); +} + +void FlashifNotActive() +{ + if (!mythis->isActiveWindow()) + QApplication::alert(mythis, 0); +} + + +// "Copy on select" Code + + +void QtTermTCP::onTEselectionChanged() +{ + QTextEdit * x = static_cast(QObject::sender()); + + if (isActiveWindow()) + x->copy(); +} + +void QtTermTCP::onLEselectionChanged() +{ + QLineEdit * x = static_cast(QObject::sender()); + if (isActiveWindow()) + x->copy(); +} + +void QtTermTCP::setVDMode() +{ + QAction * sender = static_cast(QObject::sender()); + + QTextEdit * window = static_cast(sender->parentWidget()); + + // Need to find session with this window as owner + + Ui_ListenSession * Sess = NULL; + + + for (int i = 0; i < _sessions.size(); ++i) + { + Sess = _sessions.at(i); + + if (Sess->termWindow == window) + { + Sess->TTActive ^= 1; + DoTermResize(Sess); + break; + } + } +} + +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::ClearScreen() +{ + QAction * sender = static_cast(QObject::sender()); + QTextEdit * window = static_cast(sender->parentWidget()); + window->clear(); +} + + +void QtTermTCP::showContextMenuMT(const QPoint &pt) // Term Window +{ + // Monitor and Terminal (Term Half) + + 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); + QAction * actVDMode = new QAction("Toggle Viewdata Mode", sender); + QAction * actClear = new QAction("Clear Screen Buffer", sender); + + menu->addAction(actSplit); + menu->addAction(actVDMode); + menu->addAction(actClear); + + splitY = pt.y() + termX; + + connect(actSplit, SIGNAL(triggered()), this, SLOT(setSplit())); + connect(actVDMode, SIGNAL(triggered()), this, SLOT(setVDMode())); + connect(actClear, SIGNAL(triggered()), this, SLOT(ClearScreen())); + + menu->exec(sender->mapToGlobal(pt)); + delete menu; +} + +void QtTermTCP::showContextMenuMOnly(const QPoint &pt) +{ + // Monitor only + + 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 * actClear = new QAction("Clear Screen Buffer", sender); + + menu->addAction(actClear); + connect(actClear, SIGNAL(triggered()), this, SLOT(ClearScreen())); + + menu->exec(sender->mapToGlobal(pt)); + delete menu; +} + + +void QtTermTCP::showContextMenuT(const QPoint &pt) // Term Window +{ + // Just Terminal + + 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 * actVDMode = new QAction("Toggle Viewdata Mode", sender); + QAction * actClear = new QAction("Clear Screen Buffer", sender); + + menu->addAction(actVDMode); + menu->addAction(actClear); + + connect(actVDMode, SIGNAL(triggered()), this, SLOT(setVDMode())); + connect(actClear, SIGNAL(triggered()), this, SLOT(ClearScreen())); + + menu->exec(sender->mapToGlobal(pt)); + delete menu; +} + + +void QtTermTCP::showContextMenuL() // Term Window +{ + // Teletext Label Right Clicked - cancel TT Mode + + QLabel * sender = static_cast(QObject::sender()); + + // Need to find session with this label as owner + + Ui_ListenSession * Sess = NULL; + + for (int i = 0; i < _sessions.size(); ++i) + { + Sess = _sessions.at(i); + + if (Sess->TTLabel == sender) + { + Sess->TTActive ^= 1; + + DoTermResize(Sess); + break; + } + } +} + + +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); + QAction * actClear = new QAction("Clear Screen Buffer", sender); + + menu->addAction(actSplit); + menu->addAction(actClear); + + splitY = pt.y(); + + connect(actSplit, SIGNAL(triggered()), this, SLOT(setSplit())); + connect(actClear, SIGNAL(triggered()), this, SLOT(ClearScreen())); + + 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; + + tabWidget->tabBar()->setTabTextColor(tabWidget->currentIndex(), oldTabText); + + 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 + + EnableMonLog->setChecked(Sess->LogMonitor); + + if (AGWUsers && Sess == AGWUsers->MonSess) // AGW Monitor + { + for (int i = 0; i < 64; i++) + SetPortMonLine(i, (char *)"", 0, 0); // Set all hidden + + connectMenu->setEnabled(false); + MonTX->setVisible(0); + MonSup->setVisible(0); + MonUI->setVisible(0); + MonColour->setVisible(0); + + EnableMonitor->setVisible(1); + EnableMonitor->setChecked(AGWMonEnable); + MonLocalTime->setChecked(Sess->mlocaltime); + MonNodes->setChecked(Sess->MonitorNODES); + } + else if (Sess == KISSMonSess) // KISS Monitor + { + for (int i = 0; i < 64; i++) + SetPortMonLine(i, (char *)"", 0, 0); // Set all hidden + + connectMenu->setEnabled(false); + MonTX->setVisible(0); + MonSup->setVisible(0); + MonUI->setVisible(0); + MonColour->setVisible(0); + + EnableMonitor->setVisible(1); + EnableMonitor->setChecked(KISSMonEnable); + MonLocalTime->setChecked(Sess->mlocaltime); + MonNodes->setChecked(Sess->MonitorNODES); + } + else + { + EnableMonitor->setVisible(0); + MonTX->setVisible(1); + MonSup->setVisible(1); + MonUI->setVisible(1); + MonColour->setVisible(1); + } + + if (Sess->PortMonString[0]) + { + char * ptr = (char *)malloc(2048); + memcpy(ptr, Sess->PortMonString, 2048); + + 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 < 64; 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 = 64; + + if (Sess->portmask & ((uint64_t)1 << (m - 1))) + SetPortMonLine(portnum, msg, 1, 1); + else + SetPortMonLine(portnum, msg, 1, 0); + } + free(ptr); + + MonLocalTime->setChecked(Sess->mlocaltime); + 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; + + currentHost[i] = SavedHost; + } + + 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; + + currentHost[Sess->Tab] = 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); + + Sess->mlocaltime = (Sess->mtxparam >> 7); + Sess->mtxparam &= 1; + + MonLocalTime->setChecked(Sess->mlocaltime); + 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 < 64; 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(); + } +} + +Ui_SizeDialog * YappSize; + +void QtTermTCP::doYAPPSetSize() +{ + // This runs the VARA Configuration dialog + + char valChar[80]; + + YappSize = new(Ui_SizeDialog); + + QDialog UI; + + YappSize->setupUi(&UI); + + UI.setFont(*menufont); + deviceUI = &UI; + + sprintf(valChar, "%d", MaxRXSize); + + YappSize->maxSize->setText(valChar); + + QObject::connect(YappSize->okButton, SIGNAL(clicked()), this, SLOT(sizeaccept())); + QObject::connect(YappSize->cancelButton, SIGNAL(clicked()), this, SLOT(sizereject())); + + UI.exec(); +} + +void QtTermTCP::sizeaccept() +{ + QVariant Q; + Q = YappSize->maxSize->text(); + + MaxRXSize = Q.toInt(); + SaveSettings(); + delete(YappSize); +} + + +void QtTermTCP::sizereject() +{ + delete(YappSize); + deviceUI->reject(); +} + + +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 == EnableMonitor) + ActiveSession->EnableMonitor = state; + else if (Act == EnableMonLog) + ActiveSession->LogMonitor = state; + else if (Act == MonLocalTime) + ActiveSession->mlocaltime = state; + else 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 == actAutoTeletext) + AutoTeletext = 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 + 1; i++) + { + if (Act == MonPort[i]) + { + uint64_t mmask; + + if (i == 0) // BBS Mon - use bit 63 (Port 64) + mmask = (uint64_t)1 << 63; + else + mmask = (uint64_t)1 << (i - 1); + + if (state) + ActiveSession->portmask |= mmask; + else + ActiveSession->portmask &= ~mmask; + break; + } + } + } + + + if (ActiveSession->clientSocket && ActiveSession->SessionType & Mon) + SendTraceOptions(ActiveSession); + + else if (AGWUsers && ActiveSession == AGWUsers->MonSess) + { + AGWMonEnable = ActiveSession->EnableMonitor; + AGWLocalTime = ActiveSession->mlocaltime; + AGWMonNodes = ActiveSession->MonitorNODES; + Send_AGW_m_Frame((QTcpSocket*)ActiveSession->AGWSession); + } + + else if (ActiveSession == KISSMonSess) + { + KISSLocalTime = ActiveSession->mlocaltime; + KISSMonEnable = ActiveSession->EnableMonitor; + KISSMonNodes = ActiveSession->MonitorNODES; + } + 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(); + + if (Sess->TTActive) // Teletext uses 0x5F for # + while ((ptr = strchr(Msgptr, 0x23))) // Replace VT with LF + *ptr++ = 0x5f; + + + while ((ptr = strchr(Msgptr, 11))) // Replace VT with LF + *ptr++ = 10; + + LastWrite = time(NULL); // Stop initial beep + Sess->SlowTimer = 0; + + Sess->pageBuffer[0] = 0; // Reset Teletext mode screen buffer + + 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(); + // } + + + Buffer[Read] = 0; + + 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(&IntervalWAV); + + } + + // Alert if QtTerm not active window (unless Mon window) + + if((Sess->SessionType & Mon) == 0) + FlashifNotActive(); + + // if tabbed and not active tab set tab label red + + if (TermMode == Tabbed) + { + if (Sess->monWindow != termWindow) // Not if Monitor + { + if (ActiveSession != Sess) + { + tabWidget->tabBar()->setTabTextColor(Sess->Tab, newTabText); + } + } + } + + 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(&BellWAV); + } + } 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; + +} + +void WriteMonitorLog(Ui_ListenSession * Sess, char * Msg); + +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 (Sess->LogMonitor) + WriteMonitorLog(Sess, 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) +{ + if (Sess == 0) + return; + + Sess->SlowTimer = 0; + Msg[Len] = 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; + } + + if (AutoTeletext && (Msg[0] == 0x1e || Msg[0] == 0x0c)) + { + if (Sess->TTActive == 0) + { + Sess->TTActive = 1; + DoTermResize(Sess); + } + } + + if (Sess->TTActive) + { + // Feed to Teletext code + + // We need to decode a whole page. There is no obvious delimiter so process till data stops. + // Buffer is cleared when next input is sent + + if (strlen(&Sess->pageBuffer[0] + Len) > 4090) + Sess->pageBuffer[0] = 0; // Protect buffer + + strcat(Sess->pageBuffer, (char *)Msg); + + DecodeTeleText(Sess, (char *)Sess->pageBuffer); // Re-decode same data until we get the end + 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(); + AutoTeletext = settings->value("AutoTeletext", 0).toInt(); + Bells = settings->value("Bells", 1).toInt(); + StripLF = settings->value("StripLF", 1).toInt(); + useBeep = settings->value("useBeep", true).toBool(); + AlertBeep = settings->value("AlertBeep", 1).toInt(); + AlertInterval = settings->value("AlertInterval", 300).toInt(); + ConnectBeep = settings->value("ConnectBeep", 1).toInt(); + ConnectWAV = settings->value("ConnectWAV", ":/PCBeep").toString().toUtf8(); + AlertWAV = settings->value("AlertWAV", ":/PCBeep").toString().toUtf8(); + BellWAV = settings->value("BellWAV", ":/PCBeep").toString().toUtf8(); + IntervalWAV = settings->value("IntervalWAV", ":/PCBeep").toString().toUtf8(); + + UseKeywords = settings->value("UseKeywords", 0).toInt(); + KeyWordsFile = settings->value("KeyWordsFile", "keywords.sys").toString().toUtf8(); + + SavedHost = settings->value("CurrentHost", 0).toInt(); + strcpy(YAPPPath, settings->value("YAPPPath", "").toString().toUtf8()); + MaxRXSize = settings->value("MaxRXSize", 100000).toInt(); + + 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(); + AGWLocalTime = settings->value("AGWLocalTime", 0).toInt(); + AGWMonNodes = settings->value("AGWMonNodes", 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(); + KISSMonEnable = settings->value("KISSMonEnable", 1).toInt(); + KISSLocalTime = settings->value("KISSLocalTime", 0).toInt(); + KISSMonNodes = settings->value("KISSMonNodes", 0).toInt(); + + KISSListen = settings->value("KISSListen", 1).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(VARAInit, settings->value("VARAInit", "").toString().toUtf8()); + + 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]); + + strcpy(Param, settings->value("AutoConnect", "0, 0 ,0, 0, 0, 0, 0, 0, 0, 0").toString().toUtf8()); + sscanf(Param, "%d %d %d %d %d %d %d %d %d %d", + &AutoConnect[0], &AutoConnect[1], &AutoConnect[2], &AutoConnect[3], &AutoConnect[4], + &AutoConnect[5], &AutoConnect[6], &AutoConnect[7], &AutoConnect[8], &AutoConnect[9]); + + strcpy(Param, settings->value("currentHost", "0, 0 ,0, 0, 0, 0, 0, 0, 0, 0").toString().toUtf8()); + sscanf(Param, "%d %d %d %d %d %d %d %d %d %d", + ¤tHost[0], ¤tHost[1], ¤tHost[2], ¤tHost[3], ¤tHost[4], + ¤tHost[5], ¤tHost[6], ¤tHost[7], ¤tHost[8], ¤tHost[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("AutoTeletext", AutoTeletext); + settings->setValue("Bells", Bells); + settings->setValue("StripLF", StripLF); + settings->setValue("AlertBeep", AlertBeep); + settings->setValue("useBeep", useBeep); + settings->setValue("ConnectBeep", ConnectBeep); + settings->setValue("BellWAV", BellWAV); + settings->setValue("AlertWAV", AlertWAV); + settings->setValue("IntervalWAV", IntervalWAV); + settings->setValue("ConnectWAV", ConnectWAV); + + settings->setValue("UseKeywords", UseKeywords); + settings->setValue("KeyWordsFile", KeyWordsFile); + + settings->setValue("AlertInterval", AlertInterval); + settings->setValue("CurrentHost", SavedHost); + + settings->setValue("YAPPPath", YAPPPath); + settings->setValue("MaxRXSize", MaxRXSize); + + 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("AGWLocalTime", AGWLocalTime); + settings->setValue("AGWMonNodes", AGWMonNodes); + 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("KISSMonEnable", KISSMonEnable); + settings->setValue("KISSLocalTime", KISSLocalTime); + settings->setValue("KISSMonNodes", KISSMonNodes); + + settings->setValue("KISSListen", KISSListen); + 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("VARAInit", VARAInit); + 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); + + sprintf(Param, "%d %d %d %d %d %d %d %d %d %d", + AutoConnect[0], AutoConnect[1], AutoConnect[2], AutoConnect[3], AutoConnect[4], AutoConnect[5], AutoConnect[6], AutoConnect[7], AutoConnect[8], AutoConnect[9]); + + settings->setValue("AutoConnect", Param); + + sprintf(Param, "%d %d %d %d %d %d %d %d %d %d", + currentHost[0], currentHost[1], currentHost[2], currentHost[3], currentHost[4], currentHost[5], currentHost[6], currentHost[7], currentHost[8], currentHost[9]); + + settings->setValue("currentHost", 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->monLogfile) + Sess->monLogfile->close(); + + 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(QString * WAV) +{ + if (useBeep) + { + QApplication::beep(); + return; + } + + // Using .wav files + +// QSoundEffect effect; +// effect.setSource(QUrl::fromLocalFile(*WAV)); +// effect.setLoopCount(1); +// effect.setVolume(1.0f); +// effect.play(); + + QSound::play(*WAV); +} + +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; +Ui_AlertDialog * Alert; + +static Ui_KISSDialog * KISS; +static Ui_ColourDialog * COLOURS; + + +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->KISSListen->setChecked(KISSListen); + 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(); + KISSListen = KISS->KISSListen->isChecked(); + actHost[18]->setVisible(KISSEnable); // Show KISS Connect Line + + strcpy(MYCALL, KISS->MYCALL->text().toUtf8().toUpper()); + + memset(axMYCALL, 0, 7); + ConvToAX25(MYCALL, axMYCALL); + + 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::AlertSlot() +{ + // This runs the VARA Configuration dialog + + Alert = new(Ui_AlertDialog); + + QDialog UI; + + Alert->setupUi(&UI); + + UI.setFont(*menufont); + + deviceUI = &UI; + + Alert->Bells->setChecked(Bells); + Alert->InboundBeep->setChecked(ConnectBeep); + Alert->InactivityBeep->setChecked(AlertBeep); + Alert->Interval->setText(QString::number(AlertInterval)); + Alert->KeywordBeep->setChecked(UseKeywords); + Alert->keywordFile->setText(KeyWordsFile); + + Alert->useBeep->setChecked(useBeep); + Alert->useFiles->setChecked(!useBeep); + + Alert->connectFile->setCurrentText(ConnectWAV); + Alert->bellsFile->setCurrentText(BellWAV); + Alert->intervalFile->setCurrentText(IntervalWAV); + Alert->keywordWAV->setCurrentText(AlertWAV); + + QObject::connect(Alert->chooseInbound, SIGNAL(clicked()), this, SLOT(chooseInboundWAV())); + QObject::connect(Alert->chooseBells, SIGNAL(clicked()), this, SLOT(chooseBellsWAV())); + QObject::connect(Alert->chooseInterval, SIGNAL(clicked()), this, SLOT(chooseIntervalWAV())); + QObject::connect(Alert->chooseKeyAlert, SIGNAL(clicked()), this, SLOT(chooseAlertWAV())); + + QObject::connect(Alert->testBells, SIGNAL(clicked()), this, SLOT(testBellsWAV())); + QObject::connect(Alert->TestInbound, SIGNAL(clicked()), this, SLOT(testInboundWAV())); + QObject::connect(Alert->testInterval, SIGNAL(clicked()), this, SLOT(testIntervalWAV())); + QObject::connect(Alert->TestKeyAlert, SIGNAL(clicked()), this, SLOT(testAlertWAV())); + + QObject::connect(Alert->okButton, SIGNAL(clicked()), this, SLOT(alertAccept())); + QObject::connect(Alert->cancelButton, SIGNAL(clicked()), this, SLOT(alertReject())); + + UI.exec(); +} + + + + + + +void QtTermTCP::chooseInboundWAV() +{ + ConnectWAV = QFileDialog::getOpenFileName(this, + tr("Select Wav"), "", tr("Sound Files (*.wav)")); + + Alert->connectFile->setCurrentText(ConnectWAV); + +} + +void QtTermTCP::chooseBellsWAV() +{ + BellWAV = QFileDialog::getOpenFileName(this, + tr("Select Wav"), "", tr("Sound Files (*.wav)")); + + Alert->bellsFile->setCurrentText(BellWAV); +} + +void QtTermTCP::chooseIntervalWAV() +{ + IntervalWAV = QFileDialog::getOpenFileName(this, + tr("Select Wav"), "", tr("Sound Files (*.wav)")); + + Alert->intervalFile->setCurrentText(IntervalWAV); +} + +void QtTermTCP::chooseAlertWAV() +{ + AlertWAV = QFileDialog::getOpenFileName(this, + tr("Select Wav"), "", tr("Sound Files (*.wav)")); + + Alert->keywordWAV->setCurrentText(AlertWAV); +} + +void QtTermTCP::testInboundWAV() +{ + QSound::play(Alert->connectFile->currentText()); +} + +void QtTermTCP::testBellsWAV() +{ + QSound::play(Alert->bellsFile->currentText()); +} + +void QtTermTCP::testIntervalWAV() +{ + QSound::play(Alert->intervalFile->currentText()); +} + +void QtTermTCP::testAlertWAV() +{ + QSound::play(Alert->keywordWAV->currentText()); +} + + + +void QtTermTCP::alertAccept() +{ + QVariant Q; + + useBeep = Alert->useBeep->isChecked(); + + Bells = Alert->Bells->isChecked(); + ConnectBeep = Alert->InboundBeep->isChecked(); + AlertBeep = Alert->InactivityBeep->isChecked(); + AlertInterval = Alert->Interval->text().toInt(); + + UseKeywords = Alert->KeywordBeep->isChecked(); + KeyWordsFile = Alert->keywordFile->text(); + + ConnectWAV = Alert->connectFile->currentText(); + BellWAV = Alert->bellsFile->currentText(); + IntervalWAV = Alert->intervalFile->currentText(); + AlertWAV = Alert->keywordWAV->currentText(); + + delete(Alert); + SaveSettings(); + deviceUI->accept(); +} + +void QtTermTCP::alertReject() +{ + delete(Alert); + 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()); + strcpy(VARAInit, Dev->InitCommands->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()); + tabWidget->tabBar()->setTabTextColor(i, newTabText); + } + + 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(&ConnectWAV); + + QApplication::alert(mythis, 0); +} + +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"); + } + } + + if (Sess->TTActive) + { + Sess->TTActive = 0; + DoTermResize(Sess); + } + + 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 + { + char Label[256]; + + if (SessName[Sess->CurrentHost][0]) + sprintf(Label, "%s(%s)", Host[Sess->CurrentHost], SessName[Sess->CurrentHost]); + else + strcpy(Label, Host[Sess->CurrentHost]); + + sprintf(Title, "Connected to %s", Label); + } + + if (TermMode == MDI) + Sess->setWindowTitle(Title); + else if (TermMode == Tabbed) + { + if (SessName[Sess->CurrentHost][0]) + tabWidget->setTabText(Sess->Tab, SessName[Sess->CurrentHost]); + else + tabWidget->setTabText(Sess->Tab, 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->mlocaltime << 7), + 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 + + EnableMonLog->setChecked(Sess->LogMonitor); + + if (AGWUsers && Sess == AGWUsers->MonSess) // AGW Monitor + { + for (int i = 0; i < 64; i++) + SetPortMonLine(i, (char *)"", 0, 0); // Set all hidden + + connectMenu->setEnabled(false); + MonTX->setVisible(0); + MonSup->setVisible(0); + MonUI->setVisible(0); + MonColour->setVisible(0); + + EnableMonitor->setVisible(1); + EnableMonitor->setChecked(AGWMonEnable); + + MonLocalTime->setChecked(Sess->mlocaltime); + MonNodes->setChecked(Sess->MonitorNODES); + } + else if (Sess == KISSMonSess) // KISS Monitor + { + for (int i = 0; i < 64; i++) + SetPortMonLine(i, (char *)"", 0, 0); // Set all hidden + + connectMenu->setEnabled(false); + MonTX->setVisible(0); + MonSup->setVisible(0); + MonUI->setVisible(0); + MonColour->setVisible(0); + + EnableMonitor->setVisible(1); + EnableMonitor->setChecked(KISSMonEnable); + MonLocalTime->setChecked(Sess->mlocaltime); + MonNodes->setChecked(Sess->MonitorNODES); + } + + else + { + EnableMonitor->setVisible(0); + MonTX->setVisible(1); + MonSup->setVisible(1); + MonUI->setVisible(1); + MonColour->setVisible(1); + } + + if (Sess->PortMonString[0]) + { + char * ptr = (char *)malloc(2048); + memcpy(ptr, Sess->PortMonString, 2048); + + 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 < 64; 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 = 64; + + if (Sess->portmask & (1ll << (portnum - 1))) + SetPortMonLine(portnum, msg, 1, 1); + else + SetPortMonLine(portnum, msg, 1, 0); + } + free(ptr); + + MonLocalTime->setChecked(Sess->mlocaltime); + 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 VARA Connect from %s %s Mode\r\n", CallFrom, Mode); + } + else + { + sprintf(Title, "Connected to %s", CallFrom); + n = sprintf(Message, "Incoming VARA Connect 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); + tabWidget->tabBar()->setTabTextColor(Sess->Tab, newTabText); + } + else if (TermMode == Single) + mythis->setWindowTitle(Title); + + setMenus(true); + + if (ConnectBeep) + myBeep(&ConnectWAV); + + if (listenCText[0]) + VARADataSock->write(listenCText); + + QApplication::alert(mythis, 0); + + } + } + } + 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 VARACommand[256]; + + VARAConnected = 1; + VARAConnecting = 0; + + Status2->setText("VARA Connected"); + + actHost[17]->setVisible(1); // Enable VARA Connect Line + + sprintf(VARACommand, "MYCALL %s\r", VARATermCall); + VARASock->write(VARACommand); + + 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 (VARAInit[0]) + { + char Copy[512]; + char * param, *context; + + strcpy(Copy, VARAInit); + + param = strtok_s(Copy, ",", &context); + + while (param && param[0]) + { + sprintf(VARACommand, "%s\r", param); + VARASock->write(VARACommand); + param = strtok_s(nullptr, ",", &context); + } + } + + 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); + Dev->InitCommands->setText(VARAInit); +} + +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(); + boolean rc = 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 && (AGWUsers == NULL || (S != AGWUsers->MonSess)) && S != KISSMonSess) + { + 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 && S->AGWSession == NULL && (AGWUsers == NULL || (S != AGWUsers->MonSess)) && S != KISSMonSess) + { + Sess = S; + break; + } + } + } + else if (TermMode == Single && (singlemodeFormat & Mon)) + { + S = _sessions.at(0); + + if (S->clientSocket == NULL && S->AGWSession == NULL && S->KISSSession == NULL && (AGWUsers == NULL || (S != AGWUsers->MonSess)) && S != KISSMonSess) + 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"); + + Sess->mlocaltime = KISSLocalTime; + Sess->MonitorNODES = KISSMonNodes; + + // 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 (Len < 10) // Suppressed NODES + return; + + if (Msg[Len - 1] != '\r') + { + Msg[Len++] = '\r'; + Msg[Len] = 0; + } + + if (KISSMonSess) + WritetoMonWindow(KISSMonSess, (unsigned char *)Msg, Len); + +} + +extern "C" Ui_ListenSession * ax25IncomingConnect(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); + tabWidget->tabBar()->setTabTextColor(i, newTabText); + } + else if (TermMode == Single) + mythis->setWindowTitle(Title); + + AX25Sess->port = 0; + AX25Sess->Sess = Sess; // Crosslink KISS and Term Sessions + AX25Sess->PID = 240;; + + Sess->KISSSession = AX25Sess; + + setMenus(true); + + if (ConnectBeep) + myBeep(&ConnectWAV); + + QApplication::alert(mythis, 0); + + // 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); + boolean ok = m_serial->setBaudRate(KISSBAUD); + + if (m_serial->open(QIODevice::ReadWrite)) + { + int i; + + ok = m_serial->setRequestToSend(true); + + 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; +} + +// Experimental Viewdata/Teletext/Prestel/CEEFAX Mode + +// Uses ideas and some code from + +/*************************************************************** + * Name: wxTEDMain.cpp + * Purpose: Teletext editor Application Frame + * Author: Peter Kwan (peterk.vt80@gmail.com) + * Created: 2014-10-30 + * Copyright: Peter Kwan + * License: + * + * Copyright (C) 2014-2022, Peter Kwan + * + * Permission to use, copy, modify, and distribute this software + * and its documentation for any purpose and without fee is hereby + * granted, provided that the above copyright notice appear in all + * copies and that both that the copyright notice and this + * permission notice and warranty disclaimer appear in supporting + * documentation, and that the name of the author not be used in + * advertising or publicity pertaining to distribution of the + * software without specific, written prior permission. + * + * The author disclaims all warranties with regard to this + * software, including all implied warranties of merchantability + * and fitness. In no event shall the author be liable for any + * special, indirect or consequential damages or any damages + * whatsoever resulting from loss of use, data or profits, whether + * in an action of contract, negligence or other tortious action, + * arising out of or in connection with the use or performance of + * this software. + *************************************************************************** **/ + + + +int FontWidth = 16; +int FontHeight = 16; + +bool isMosaic(char ch) +{ + ch &= 0x7f; + return (ch >= 0x20 && ch < 0x40) || ch >= 0x60; +} + + +void DecodeTeleText(Ui_ListenSession * Sess, char * page) +{ + + // redraw the whole teletext page + + QColor fg = Qt::white; + QColor bg = Qt::black; + QColor holdColour; + QColor holdfg = fg; // Colour for held graphic + int usehold = 0; + int sep = 0; + + QPainter p(Sess->TTBitmap); + + QSettings settings(GetConfPath(), QSettings::IniFormat); + + //p.setFont(QFont("Courier", 14, QFont::Bold)); + + p.setFont(QFont(settings.value("FontFamily", "Courier New").value(), 14)); + + p.setPen(QPen(fg)); + + bool graphicsMode = false; + bool separated = false; + bool doubleHeight = false; + int skipnextrow = 99; + bool flashing = false; + bool hold = false; + char holdChar = 0; + char holdMode = 0; + Sess->timer.stop(); + + bool concealed = false; + char c; + + fg = Qt::white; + bg = Qt::black; + + char * ptr = page; + + int col = 0; + int line = 0; + + // XXXXXXXXTEEFAX %%# %%a %d %%b C %H:%M.%S + + //p.drawText(0, 19, "P199 HAMFAX"); + + + // interpret data, for now, line by line + + + while ((c = *(ptr++))) + { + char ch = c; + + if (c == 0x11) // Curson On ?? + continue; + + if (c == 0x14) // Curson Off ?? + continue; + + if (c == 9) // Curson On ?? + { + col++; + continue; + } + + if (c == 0x0a) + { + line++; + +// if (doubleHeight) +// line++; + + if (line > 24) + line = 0; + + continue; + } + + if (c == 0x1e) // Cursor Home + { + line = col = 0; + c = 13; // So we reset page flags below + } + + if (c == 12) + { + // Clear the page + + Sess->TTBitmap->fill(Qt::black); + Sess->TTLabel->setPixmap(QPixmap::fromImage(*Sess->TTBitmap)); + + line = col = 0; + + c = 13; // So we reset page flags below + } + + if (c == 13) + { + col = 0; + + graphicsMode = false; + separated = false; + doubleHeight = false; + flashing = false; + hold = false; + holdChar = 0; + concealed = false; + + fg = Qt::white; + bg = Qt::black; + + continue; // Next line + } + + if (c == 0x1b) // Esc + { + // I think the control char is displayed as it was before being actioned, sa save current state + + holdfg = holdColour; // Colour if using held - may be changed before displaying + usehold = hold; // May be changed + sep = holdMode; + + char echar = *(ptr++); + + if (echar == 0) + { + // Esc pair spilt - wait for rest + + Sess->TTLabel->setPixmap(QPixmap::fromImage(*Sess->TTBitmap)); + return; + } + + switch (echar) + { + case '@': + fg = Qt::black; + concealed = false; // Side effect of colour. It cancels a conceal. + graphicsMode = false; +// hold = false; + + break; + case 'A': + fg = Qt::red; + concealed = false; + graphicsMode = false; + hold = false; + + break; + case 'B': + fg = Qt::green; + concealed = false; + graphicsMode = false; + hold = false; + break; + case 'C': + fg = Qt::yellow; + concealed = false; + graphicsMode = false; + hold = false; + break; + case 'D': + fg = Qt::blue; + concealed = false; + graphicsMode = false; + hold = false; + break; + case 'E': + fg = Qt::magenta; + concealed = false; + graphicsMode = false; + hold = false; + break; + case 'F': + fg = Qt::cyan; + concealed = false; + graphicsMode = false; + hold = false; + break; + case 'G': + fg = Qt::white; + concealed = false; + graphicsMode = false; + hold = false; + break; + case 'H': // Flash + flashing = true; + Sess->timer.start(1000, Sess); + concealed = false; + graphicsMode = false; + hold = false; + + break; + + case 'I': // Steady + flashing = false; + concealed = false; + graphicsMode = false; + hold = false; + + break; + // + case 'J': //ttxCodeEndBox: + case 'K': //ttxCodeStartBox: + + concealed = false; + graphicsMode = false; + hold = false; + + break; + case 'L': // Normal height + doubleHeight = false; + hold = false; + break; + + case 'M': // Double height + doubleHeight = true; + skipnextrow = line + 1; // ETSI: row to ignore + hold = false; + holdChar = 0; + break; + case 'P': // Graphics black +// concealed = false; + graphicsMode = true; + holdColour = fg = Qt::black; + + break; + case 'Q': // Graphics red +// concealed = false; + graphicsMode = true; + holdColour = fg = Qt::red; + + break; + case 'R': // Graphics green +// concealed = false; + graphicsMode = true; + holdColour = fg = Qt::green; + + break; + case 'S': // Graphics yellow +// concealed = false; + graphicsMode = true; + holdColour = fg = Qt::yellow; + + break; + case 'T': // Graphics blue +// concealed = false; + graphicsMode = true; + holdColour = fg = Qt::blue; + + break; + case 'U': // Graphics magenta +// concealed = false; + graphicsMode = true; + holdColour = fg = Qt::magenta; + + break; + case 'V': // Graphics cyan +// concealed = false; + graphicsMode = true; + holdColour = fg = Qt::cyan; + + break; + case 'W': // Graphics white +// concealed = false; + graphicsMode = true; + holdColour = fg = Qt::white; + + break; + + case 'X': // Conceal display + + concealed = 1; + break; + + case 'Y': // Contiguous graphics + + separated = false; + break; + + case 'Z': // Separated gfx + + separated = true; + break; + + case 0x5c: // Background black + bg = Qt::black; + break; + + case 0x5d: // New background + + bg = fg; + break; + + case 0x5e: // Hold gfx + + if (hold == 0) + { + hold = true; + holdColour = fg; + holdMode = separated; + } + break; + + case 0x5f: // Non-hold gfx + hold = false; + break; + + default: + echar++; // For testign + break; + + } //end of esc case processing + + if (usehold) + { + // We set sep and color for held char earlier + + ch = holdChar; // Repeat held char + } + else + ch = 0x0; // Default is space + + + if (line > skipnextrow) + skipnextrow = 99; + + if (line == skipnextrow) + { + line = line; + } + else + { + if (concealed == 0 && (flashing == 0 || Sess->TTFlashToggle == 0)) + { + p.fillRect(col * 15, line * 19, 15, 19, bg); + + // if double height also draw background for next row + + if (doubleHeight) + p.fillRect(col * 15, line * 19 + 19, 15, 19, bg); + + + if (sep) + { + if (ch & 1) + p.fillRect(col * 15 + 2, line * 19 + 2, 5, 4, holdfg); + if (ch & 2) + p.fillRect(col * 15 + 9, line * 19 + 2, 6, 4, holdfg); + if (ch & 4) + p.fillRect(col * 15 + 2, line * 19 + 8, 5, 4, holdfg); + if (ch & 8) + p.fillRect(col * 15 + 9, line * 19 + 8, 6, 4, holdfg); + if (ch & 16) + p.fillRect(col * 15 + 2, line * 19 + 14, 5, 5, holdfg); + if (ch & 32) + p.fillRect(col * 15 + 9, line * 19 + 14, 6, 5, holdfg); + + } + else + { + if (ch & 1) + p.fillRect(col * 15, line * 19, 7, 6, holdfg); + if (ch & 2) + p.fillRect(col * 15 + 7, line * 19, 8, 6, holdfg); + if (ch & 4) + p.fillRect(col * 15, line * 19 + 6, 7, 6, holdfg); + if (ch & 8) + p.fillRect(col * 15 + 7, line * 19 + 6, 8, 6, holdfg); + if (ch & 16) + p.fillRect(col * 15, line * 19 + 12, 7, 7, holdfg); + if (ch & 32) + p.fillRect(col * 15 + 7, line * 19 + 12, 8, 7, holdfg); + } + } + } + col++; + } + else + { + // Not esc - so normal or graphics + + if (ch < 0x20) + continue; + + if (line > skipnextrow) + skipnextrow = 99; + + if (line == skipnextrow) + { + line = line; + } + else + { + if (concealed == 0 && (flashing == 0 || Sess->TTFlashToggle == 0)) + { + p.fillRect(col * 15, line * 19, 15, 19, bg); + + if (doubleHeight) + p.fillRect(col * 15, line * 19 + 19, 15, 19, bg); + + p.setPen(QPen(fg));; + + if (graphicsMode) + { + if (ch < 0x40) + ch -= 0x20; + else + if (ch >= 0x60) + ch -= 0x40; + else + goto isText; + + holdChar = ch; + + // Now have 00 - 3f + + + // C is bit mask bit posns are + // 01 + // 23 + // 45 + // Char cell is 15 * 19 which is a bit asymetrical + + // Chars are 20 - 3f and 60 to 7f but I cant see a logic to the mapping + + + if (separated) + { + if (ch & 1) + p.fillRect(col * 15 + 2, line * 19 + 2, 5, 4, fg); + if (ch & 2) + p.fillRect(col * 15 + 9, line * 19 + 2, 6, 4, fg); + if (ch & 4) + p.fillRect(col * 15 + 2, line * 19 + 8, 5, 4, fg); + if (ch & 8) + p.fillRect(col * 15 + 9, line * 19 + 8, 6, 4, fg); + if (ch & 16) + p.fillRect(col * 15 + 2, line * 19 + 14, 5, 5, fg); + if (ch & 32) + p.fillRect(col * 15 + 9, line * 19 + 14, 6, 5, fg); + + } + else + { + if (ch & 1) + p.fillRect(col * 15, line * 19, 7, 6, fg); + if (ch & 2) + p.fillRect(col * 15 + 7, line * 19, 8, 6, fg); + if (ch & 4) + p.fillRect(col * 15, line * 19 + 6, 7, 6, fg); + if (ch & 8) + p.fillRect(col * 15 + 7, line * 19 + 6, 8, 6, fg); + if (ch & 16) + p.fillRect(col * 15, line * 19 + 12, 7, 7, fg); + if (ch & 32) + p.fillRect(col * 15 + 7, line * 19 + 12, 8, 7, fg); + } + } + else + { + // Just write char at current col and line + + isText: + char s[5]; + unsigned char su[5] = ""; + + // Some chars are in wrong char set + + su[0] = ch; + + if (ch == '_') + su[0] = '#'; + + else if (ch == 0x7e) // division + { + su[0] = 0xC3; + su[1] = 0xB7; + } + else if (ch == 0x5e) // up arrow + { + su[0] = 0xF0; + su[1] = 0x9F; + su[2] = 0xA0; + su[3] = 0x95; + } + else if (ch == 0x7f) // up arrow + { + su[0] = 0xE2; + su[1] = 0x96; + su[2] = 0x88; + } + + memcpy(s, su, 5); + +// if (doubleHeight) + // p.drawText(col * 15, line * 19 + 25, s); + // else + // p.drawText(col * 15, line * 19 + 15, s); + + // if double height draw normally then copy pixels each row of pixels to two scanlines (starting at the bottom) + + if (doubleHeight) + { + int inscanline = line * 19 + 18; + int outscanline = line * 19 + 35; + unsigned char * inptr = Sess->TTBitmap->scanLine(inscanline); + unsigned char * outptr = Sess->TTBitmap->scanLine(outscanline); + int linelen = Sess->TTBitmap->bytesPerLine(); + int charlen = linelen / 40; // bytes per char position + + p.drawText(col * 15, line * 19 + 16, s); + + inptr += col * charlen; + outptr += col * charlen; + + for (int i = 0; i < 18; i++) + { + memcpy(outptr, inptr, charlen); + outptr -= linelen; + memcpy(outptr, inptr, charlen); + + inptr -= linelen; + outptr -= linelen; + + } + } + else + p.drawText(col * 15, line * 19 + 15, s); + + } + } + } + col++; + } + + if (col > 39) + { + col = 0; + line++; + if (line > 24) + line = 0; + + graphicsMode = false; + separated = false; + doubleHeight = false; + flashing = false; + hold = false; + holdChar = 0; + concealed = false; + + fg = Qt::white; + bg = Qt::black; + } + } + Sess->TTLabel->setPixmap(QPixmap::fromImage(*Sess->TTBitmap)); + return; + + QFile file("D:/savepage.txt"); + file.open(QIODevice::WriteOnly); + file.write(Sess->pageBuffer); + file.close(); +} + + +void Ui_ListenSession::timerEvent(QTimerEvent *event) +{ + Ui_ListenSession * Sess = NULL; + + // Used for flashing - resfresh window + + for (int i = 0; i < _sessions.size(); ++i) + { + Sess = _sessions.at(i); + + if (Sess->timer.timerId() == event->timerId()) + { + if (Sess->TTActive) + { + Sess->TTFlashToggle ^= 1; + + // if in Tabbed mode only refresh active tab + + if (TermMode == Tabbed && Sess != ActiveSession) + return; + + DecodeTeleText(Sess, (char *)Sess->pageBuffer); // Re-decode same data until we get the end + } + else + Sess->timer.stop(); + + return; + } + } + QWidget::timerEvent(event); +} + +// Monitor Log FIle routines + +char * doXMLTransparency(char * string) +{ + // Make sure string doesn't contain forbidden XML chars (<>"'&) + + char * newstring = (char *)malloc(5 * strlen(string) + 1); // If len is zero still need null terminator + + char * in = string; + char * out = newstring; + char c; + + c = *(in++); + + while (c) + { + switch (c) + { + case '<': + + strcpy(out, "<"); + out += 4; + break; + + case '>': + + strcpy(out, ">"); + out += 4; + break; + + case '"': + + strcpy(out, """); + out += 6; + break; + + case '\'': + + strcpy(out, "'"); + out += 6; + break; + + case '&': + + strcpy(out, "&"); + out += 5; + break; + + default: + + *(out++) = c; + } + c = *(in++); + } + + *(out++) = 0; + return newstring; +} + +void WriteMonitorLog(Ui_ListenSession * Sess, char * Msg) +{ + // Write as HTML to preserve formatting + + char Line[512]; + char * HTMLText; + + if (Sess->monLogfile == nullptr) + { + QString FN = "QTTermMonLog" + timeLoaded + "_" + QString::number(Sess->sessNo) + ".html"; + Sess->monLogfile = new QFile(FN); + + if (Sess->monLogfile) + Sess->monLogfile->open(QIODevice::WriteOnly | QIODevice::Text); + else + return; + } + + if (Msg[0] == 0x1b) + { + // Colour Escape + + if (Msg[1] == 17) + Sess->monSpan = (char *)"";// , monRxColour.data()); + else + Sess->monSpan = (char *)"";// , monTxColour.data()); + + HTMLText = doXMLTransparency(&Msg[2]); + } + else + { + // Leave colour at last set value + + HTMLText = doXMLTransparency(Msg); + } + + sprintf(Line, "%s%s
\r\n", Sess->monSpan, HTMLText); + + Sess->monLogfile->write(Line); + + free(HTMLText); + +} + diff --git a/QtTermTCP.h b/QtTermTCP.h index 89e1524..7f477c3 100644 --- a/QtTermTCP.h +++ b/QtTermTCP.h @@ -1,280 +1,310 @@ -#pragma once - -#include -#include "ui_QtTermTCP.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 - -#define MAXHOSTS 16 -#define MAXPORTS 64 - -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; - QLabel * TTLabel; - - 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]; - - // For Teletext Emulator - - QImage * TTBitmap; // TT Image buffer - QDialog TTUI; - - int TTActive; - int TTFlashToggle; - char pageBuffer[4096]; - QBasicTimer timer; - -protected: - - void timerEvent(QTimerEvent *event) override; - -private: - - - -private slots: - -}; - - -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 showContextMenuL(); - void doQuit(); - void onTEselectionChanged(); - void onLEselectionChanged(); - void setSplit(); - void ClearScreen(); - void setVDMode(); - void showContextMenuMT(const QPoint & pt); - void showContextMenuMOnly(const QPoint & pt); - 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); +#pragma once + +#include +#include "ui_QtTermTCP.h" +#include "ui_AlertSetup.h" +#include "ui_YAPPRxSize.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 +#include +#include + +#define MAXHOSTS 16 +#define MAXPORTS 64 + +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; + QLabel * TTLabel; + + 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[2048]; // 64 ports 32 Bytes + uint64_t portmask; + int EnableMonitor; + int mlocaltime; + 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]; + + // For Teletext Emulator + + QImage * TTBitmap; // TT Image buffer + QDialog TTUI; + + int TTActive; + int TTFlashToggle; + char pageBuffer[4096]; + QBasicTimer timer; + + int sessNo; // Used to create unique log filename; + bool LogMonitor; + QFile * monLogfile; + char * monSpan; + +protected: + + void timerEvent(QTimerEvent *event) override; + +private: + + + +private slots: + +}; + + +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 doYAPPSetSize(); + void sizeaccept(); + void sizereject(); + void menuChecked(); + void Connect(); + void displayError(QAbstractSocket::SocketError socketError); + void readyRead(); + void showContextMenu(const QPoint & point); + void autoConnectChecked(); + void LreturnPressed(Ui_ListenSession * LUI); + void LDisconnect(Ui_ListenSession * LUI); + void SetupHosts(); + void MyTimerSlot(); + void KISSTimerSlot(); + void ListenSlot(); + void AGWSlot(); + void AlertSlot(); + void chooseInboundWAV(); + void chooseBellsWAV(); + void chooseIntervalWAV(); + void chooseAlertWAV(); + void testInboundWAV(); + void testBellsWAV(); + void testIntervalWAV(); + void testAlertWAV(); + void alertAccept(); + void alertReject(); + void VARASlot(); + void KISSSlot(); + void deviceaccept(); + void KISSaccept(); + void KISSreject(); + void devicereject(); + void showContextMenuM(const QPoint &pt); + void showContextMenuT(const QPoint &pt); + void showContextMenuL(); + void doQuit(); + void onTEselectionChanged(); + void onLEselectionChanged(); + void setSplit(); + void ClearScreen(); + void setVDMode(); + void showContextMenuMT(const QPoint & pt); + void showContextMenuMOnly(const QPoint & pt); + 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 *AlertAction; + 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(QString * WAV); + 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); + void GetKeyWordFile(); +} + +extern QString ConnectWAV; +extern QString BellWAV; +extern QString AlertWAV; + +char * strlop(char * buf, char delim); +extern "C" void setMenus(int State); +void Send_AGW_Ds_Frame(void * AGW); diff --git a/QtTermTCP.icns b/QtTermTCP.icns new file mode 100644 index 0000000000000000000000000000000000000000..07d43457e5afea09c2b566685b896073217c1d62 GIT binary patch literal 358400 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 NMSj8ooiE$({{xyw0c8LH literal 0 HcmV?d00001 diff --git a/QtTermTCP.ini b/QtTermTCP.ini deleted file mode 100644 index 9d58bbb..0000000 --- a/QtTermTCP.ini +++ /dev/null @@ -1,121 +0,0 @@ -[General] -HostParams0=nottm.g8bpq.net|8011|gm8bpq|password|5 1 1 0 1 0 0 1\r|Pogo4 -geometry=@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\0\0\0\xea\0\0\0\xaf\0\0\x3\xf6\0\0\x3h\0\0\0\xea\0\0\0\xaf\0\0\x3\xf6\0\0\x3h\0\0\0\0\0\0\0\0\a\x80\0\0\0\xea\0\0\0\xaf\0\0\x3\xf6\0\0\x3h) -HostParams1=192.168.1.63|8011|john|password||Pogo2 -windowState=@ByteArray(\0\0\0\xff\0\0\0\0\xfd\0\0\0\0\0\0\x3\r\0\0\x2\x8c\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=127.0.0.1|8011|gm8bpq|password|c000000100000000 1 1 0 1 0 0 1\r| -HostParams3=192.168.1.131|8011|john|password|1 1 1 0 1 0 0 1\r|Slack -HostParams4=127.0.0.1|8021|john|password|c000000100000006 1 1 0 1 0 0 1\r|LinBPQ -HostParams5=192.168.1.18|8011|john|password|c00000010000002e 1 1 0 1 0 0 1\r| -HostParams6=|0|||| -HostParams7=|0|||| -HostParams8=|0|||| -HostParams9=|0|||| -HostParams10=|0|||| -HostParams11=|0|||| -HostParams12=|0|||| -HostParams13=|0|||| -HostParams14=|0|||| -HostParams15=|0|||| -Split=41 -ChatMode=1 -AutoTeletext=1 -Bells=1 -StripLF=1 -AlertBeep=1 -ConnectBeep=1 -CurrentHost=0 -YAPPPath= -listenPort=8015 -listenEnable=1 -listenCText=[PMS-2.3-C]\rHello>\r -convUTF8=0 -PTT=None -PTTBAUD=0 -PTTMode=1 -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=1 -AGWTermCall=G8BPQ -AGWBeaconDest= -AGWBeaconPath= -AGWBeaconInterval=0 -AGWBeaconPorts= -AGWBeaconText= -AGWHost=127.0.0.1 -AGWPort=8001 -AGWPaclen=80 -AGWToCalls=G8BPQ-2, SWITCH, -KISSEnable=0 -MYCALL=GM8BPQ -KISSHost=127.0.0.1 -KISSMode=0 -KISSPort=8110 -KISSSerialPort=TCP -KISSBAUD=19200 -VARAEnable=0 -VARATermCall=G8BPQ -VARAHost=127.0.0.1 -VARAPort=8310 -VARAPath=C:\\VARA\\VARA.exe -VARAHostHF=127.0.0.1 -VARAPortHF=8310 -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 3 1 1 1 1 1 2 2 0 -monBackground=@Variant(\0\0\0\x43\x1\xff\xff\xff\xff\xff\xff\xff\xff\0\0) -monRxText=@Variant(\0\0\0\x43\x1\xff\xff\0\0\0\0\xff\xff\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\xff\xff\xff\xff\xff\xff\0\0) -outputText=@Variant(\0\0\0\x43\x1\xff\xff\0\0\0\0\xff\xff\0\0) -EchoText=@Variant(\0\0\0\x43\x1\xff\xff\0\0\0\0\0\0\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 -FontFamily=Courier -PointSize=10 -Weight=50 -Sessions="1|1, 17, 40, 205, 625, 990|" -VARAInit="P2P SESSION,WINLINK SESSION" - -[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 index da12fb3..aeb5e0b 100644 --- a/QtTermTCP.pro +++ b/QtTermTCP.pro @@ -3,6 +3,7 @@ QT += core gui QT += network QT += widgets QT += serialport +QT += multimedia TARGET = QtTermTCP @@ -40,7 +41,9 @@ FORMS += QtTermTCP.ui\ VARAConfig.ui \ KISSConfig.ui \ ColourConfig.ui \ - AGWConnect.ui + YAPPRxSize.ui \ + AGWConnect.ui \ + AlertSetup.ui RESOURCES += QtTermTCP.qrc diff --git a/QtTermTCP.pro.user b/QtTermTCP.pro.user index 4e70805..4bbf667 100644 --- a/QtTermTCP.pro.user +++ b/QtTermTCP.pro.user @@ -1,357 +1,357 @@ - - - - - - EnvironmentId - {49a2cf38-2251-47e3-97df-4ecaa9d41931} - - - ProjectExplorer.Project.ActiveTarget - 0 - - - ProjectExplorer.Project.EditorSettings - - true - false - true - - Cpp - - CppGlobal - - - - QmlJS - - QmlJSGlobal - - - 2 - UTF-8 - false - 4 - false - 80 - true - true - 1 - true - false - 0 - true - true - 0 - 8 - true - 1 - true - true - true - *.md, *.MD, Makefile - false - true - - - - ProjectExplorer.Project.PluginSettings - - - true - true - true - true - true - - - 0 - true - - -fno-delayed-template-parsing - - true - Builtin.Questionable - - true - Builtin.DefaultTidyAndClazy - 3 - - - - true - - - - - ProjectExplorer.Project.Target.0 - - Desktop - Desktop Qt 5.14.2 MSVC2015 64bit - Desktop Qt 5.14.2 MSVC2015 64bit - qt.qt5.5142.win64_msvc2015_64_kit - 0 - 0 - 0 - - true - 0 - C:\Users\John\OneDrive\Dev\Source\QT\build-QtTermTCP-Desktop_Qt_5_14_2_MSVC2015_64bit-Debug - C:/Users/John/OneDrive/Dev/Source/QT/build-QtTermTCP-Desktop_Qt_5_14_2_MSVC2015_64bit-Debug - - - true - QtProjectManager.QMakeBuildStep - - false - - - - true - Qt4ProjectManager.MakeStep - - false - - - false - - 2 - Build - Build - ProjectExplorer.BuildSteps.Build - - - - true - Qt4ProjectManager.MakeStep - - true - clean - - false - - 1 - Clean - Clean - ProjectExplorer.BuildSteps.Clean - - 2 - false - - - Debug - Qt4ProjectManager.Qt4BuildConfiguration - 2 - 2 - 2 - - - true - 2 - C:\Users\John\OneDrive\Dev\Source\QT\build-QtTermTCP-Desktop_Qt_5_14_2_MSVC2015_64bit-Release - C:/Users/John/OneDrive/Dev/Source/QT/build-QtTermTCP-Desktop_Qt_5_14_2_MSVC2015_64bit-Release - - - true - QtProjectManager.QMakeBuildStep - - false - - - - true - Qt4ProjectManager.MakeStep - - false - - - false - - 2 - Build - Build - ProjectExplorer.BuildSteps.Build - - - - true - Qt4ProjectManager.MakeStep - - true - clean - - false - - 1 - Clean - Clean - ProjectExplorer.BuildSteps.Clean - - 2 - false - - - Release - Qt4ProjectManager.Qt4BuildConfiguration - 0 - 0 - 2 - - - true - 0 - C:\Users\John\OneDrive\Dev\Source\QT\build-QtTermTCP-Desktop_Qt_5_14_2_MSVC2015_64bit-Profile - C:/Users/John/OneDrive/Dev/Source/QT/build-QtTermTCP-Desktop_Qt_5_14_2_MSVC2015_64bit-Profile - - - true - QtProjectManager.QMakeBuildStep - - false - - - - true - Qt4ProjectManager.MakeStep - - false - - - false - - 2 - Build - Build - ProjectExplorer.BuildSteps.Build - - - - true - Qt4ProjectManager.MakeStep - - true - clean - - false - - 1 - Clean - Clean - ProjectExplorer.BuildSteps.Clean - - 2 - false - - - Profile - Qt4ProjectManager.Qt4BuildConfiguration - 0 - 0 - 0 - - 3 - - - 0 - Deploy - Deploy - ProjectExplorer.BuildSteps.Deploy - - 1 - - false - ProjectExplorer.DefaultDeployConfiguration - - 1 - - - dwarf - - cpu-cycles - - - 250 - - -e - cpu-cycles - --call-graph - dwarf,4096 - -F - 250 - - -F - true - 4096 - false - false - 1000 - - true - - false - false - false - false - true - 0.01 - 10 - true - kcachegrind - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - - 2 - - Qt4ProjectManager.Qt4RunConfiguration:C:/Users/John/OneDrive/Dev/Source/QT/QtTermTCP/QtTermTCP.pro - C:/Users/John/OneDrive/Dev/Source/QT/QtTermTCP/QtTermTCP.pro - - false - - false - true - true - false - false - true - - C:/Users/John/OneDrive/Dev/Source/QT/build-QtTermTCP-Desktop_Qt_5_14_2_MSVC2015_64bit-Debug - - 1 - - - - ProjectExplorer.Project.TargetCount - 1 - - - ProjectExplorer.Project.Updater.FileVersion - 22 - - - Version - 22 - - + + + + + + EnvironmentId + {49a2cf38-2251-47e3-97df-4ecaa9d41931} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + true + false + 0 + true + true + 0 + 8 + true + 1 + true + true + true + *.md, *.MD, Makefile + false + true + + + + ProjectExplorer.Project.PluginSettings + + + true + true + true + true + true + + + 0 + true + + -fno-delayed-template-parsing + + true + Builtin.Questionable + + true + Builtin.DefaultTidyAndClazy + 3 + + + + true + + + + + ProjectExplorer.Project.Target.0 + + Desktop + Desktop Qt 5.14.2 MSVC2015 64bit + Desktop Qt 5.14.2 MSVC2015 64bit + qt.qt5.5142.win64_msvc2015_64_kit + 0 + 0 + 0 + + true + 0 + C:\Users\John\OneDrive\Dev\Source\QT\build-QtTermTCP-Desktop_Qt_5_14_2_MSVC2015_64bit-Debug + C:/Users/John/OneDrive/Dev/Source/QT/build-QtTermTCP-Desktop_Qt_5_14_2_MSVC2015_64bit-Debug + + + true + QtProjectManager.QMakeBuildStep + + false + + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + 2 + 2 + + + true + 2 + C:\Users\John\OneDrive\Dev\Source\QT\build-QtTermTCP-Desktop_Qt_5_14_2_MSVC2015_64bit-Release + C:/Users/John/OneDrive/Dev/Source/QT/build-QtTermTCP-Desktop_Qt_5_14_2_MSVC2015_64bit-Release + + + true + QtProjectManager.QMakeBuildStep + + false + + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + 2 + + + true + 0 + C:\Users\John\OneDrive\Dev\Source\QT\build-QtTermTCP-Desktop_Qt_5_14_2_MSVC2015_64bit-Profile + C:/Users/John/OneDrive/Dev/Source/QT/build-QtTermTCP-Desktop_Qt_5_14_2_MSVC2015_64bit-Profile + + + true + QtProjectManager.QMakeBuildStep + + false + + + + true + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + 0 + + 3 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + + dwarf + + cpu-cycles + + + 250 + + -e + cpu-cycles + --call-graph + dwarf,4096 + -F + 250 + + -F + true + 4096 + false + false + 1000 + + true + + false + false + false + false + true + 0.01 + 10 + true + kcachegrind + 1 + 25 + + 1 + true + false + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + + 2 + + Qt4ProjectManager.Qt4RunConfiguration:C:/Users/John/OneDrive/Dev/Source/QT/QtTermTCP/QtTermTCP.pro + C:/Users/John/OneDrive/Dev/Source/QT/QtTermTCP/QtTermTCP.pro + + false + + false + true + true + false + false + true + + C:/Users/John/OneDrive/Dev/Source/QT/build-QtTermTCP-Desktop_Qt_5_14_2_MSVC2015_64bit-Debug + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + + diff --git a/QtTermTCP.qrc b/QtTermTCP.qrc index 1e71b3b..78dc32c 100644 --- a/QtTermTCP.qrc +++ b/QtTermTCP.qrc @@ -1,5 +1,10 @@ - - - QtTermTCP.ico - + + + 250x600Hz.wav + 250x1000Hz.wav + ding.wav + PCBeep.wav + Ring.wav + QtTermTCP.ico + diff --git a/QtTermTCP.sln b/QtTermTCP.sln index 01fdba7..8aa1110 100644 --- a/QtTermTCP.sln +++ b/QtTermTCP.sln @@ -1,25 +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 + +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 index a2bfd8c..163ac9a 100644 --- a/QtTermTCP.ui +++ b/QtTermTCP.ui @@ -1,66 +1,66 @@ - - - QtTermTCPClass - - - - 0 - 0 - 781 - 698 - - - - - Arial - 12 - - - - QtTermTCP - - - - :/QtTermTCP/QtTermTCP.ico:/QtTermTCP/QtTermTCP.ico - - - - - 5 - - - 5 - - - 5 - - - 5 - - - - - 6 - - - - - - - - - Listen - - - - - Disconnect - - - - - - - - - + + + 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 index e47863b..506de1f 100644 --- a/QtTermTCP.vcxproj +++ b/QtTermTCP.vcxproj @@ -1,461 +1,463 @@ - - - - - Debug - x64 - - - Release - Win32 - - - Debug - Win32 - - - Release - x64 - - - - {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 - release\ - false - NotSet - Application - release\ - QtTermTCP - - - v141 - debug\ - false - NotSet - Application - debug\ - QtTermTCP - - - v141 - debug\ - false - NotSet - Application - debug\ - QtTermTCP - - - - - - - - - - - - - - - - - - - - - - - - $(SolutionDir)$(Platform)\$(Configuration)\ - $(SolutionDir)Intermed\$(Platform)\$(Configuration)\ - QtTermTCP - true - true - - - $(SolutionDir)$(Platform)\$(Configuration)\ - $(SolutionDir)Intermed\$(Platform)\$(Configuration)\ - QtTermTCP - true - true - - - $(SolutionDir)$(Platform)\$(Configuration)\ - $(SolutionDir)Intermed\$(Platform)\$(Configuration)\ - QtTermTCP - true - false - - - $(SolutionDir)$(Platform)\$(Configuration)\ - $(SolutionDir)Intermed\$(Platform)\$(Configuration)\ - QtTermTCP - true - false - - - 5.14.2 - core;network;gui;widgets;serialport - - - 5.14.2_msvc2017 - core;network;gui;widgets;serialport - - - 5.14.2 - core;network;gui;widgets;serialport - - - 5.14.2_msvc2017_64 - 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;.;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 - EditAndContinue - 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 - false - - - 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 - - - - - 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 - false - - - 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 - - - - - - - EditAndContinue - ProgramDatabase - - - - - - - - - - - - - - - - - - Document - true - true - $(QTDIR)\mkspecs\features\data\dummy.cpp;%(AdditionalInputs) - $(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 - 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 - Generate moc_predefs.h - debug\moc_predefs.h;%(Outputs) - debug\moc_predefs.h;%(Outputs) - - - Document - $(QTDIR)\mkspecs\features\data\dummy.cpp;%(AdditionalInputs) - $(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 - 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 - Generate moc_predefs.h - release\moc_predefs.h;%(Outputs) - release\moc_predefs.h;%(Outputs) - true - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + Debug + x64 + + + Release + Win32 + + + Debug + Win32 + + + Release + x64 + + + + {14F3B24E-473C-324E-A99D-3B679FCF5F67} + QtTermTCP + QtVS_v304 + 10.0.17763.0 + 10.0.19041.0 + $(MSBuildProjectDirectory)\QtMsBuild + + + + v141 + release\ + false + NotSet + Application + release\ + QtTermTCP + + + v141 + release\ + false + NotSet + Application + release\ + QtTermTCP + + + v141 + debug\ + false + NotSet + Application + debug\ + QtTermTCP + + + v141 + debug\ + false + NotSet + Application + debug\ + QtTermTCP + + + + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)Intermed\$(Platform)\$(Configuration)\ + QtTermTCP + true + true + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)Intermed\$(Platform)\$(Configuration)\ + QtTermTCP + true + true + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)Intermed\$(Platform)\$(Configuration)\ + QtTermTCP + true + false + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)Intermed\$(Platform)\$(Configuration)\ + QtTermTCP + true + false + + + 5.14.2 + core;network;gui;widgets;serialport + + + 5.14.2_msvc2017 + core;network;gui;widgets;serialport + + + 5.14.2 + core;network;gui;widgets;serialport + + + 5.14.2_msvc2017_64 + 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;C:\Qt\Qt5.14.2\5.14.2\msvc2017\lib\Qt5Multimedia.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;.;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 + EditAndContinue + 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:\Qt\Qt5.14.2\5.14.2\msvc2017\lib\Qt5Multimediad.lib + 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 + false + + + 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 + + + + + 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 + false + + + 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 + + + + + + + EditAndContinue + ProgramDatabase + + + + + + + + + + + + + + + + + + Document + true + true + $(QTDIR)\mkspecs\features\data\dummy.cpp;%(AdditionalInputs) + $(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 + 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 + Generate moc_predefs.h + debug\moc_predefs.h;%(Outputs) + debug\moc_predefs.h;%(Outputs) + + + Document + $(QTDIR)\mkspecs\features\data\dummy.cpp;%(AdditionalInputs) + $(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 + 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 + Generate moc_predefs.h + release\moc_predefs.h;%(Outputs) + release\moc_predefs.h;%(Outputs) + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/QtTermTCP.vcxproj.filters b/QtTermTCP.vcxproj.filters index 5347f44..1737602 100644 --- a/QtTermTCP.vcxproj.filters +++ b/QtTermTCP.vcxproj.filters @@ -1,131 +1,137 @@ - - - - - {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 - - - - - + + + + + {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; + + + {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 + + + {71ED8ED8-ACB9-4CE9-BBE1-E00B30144E11} + cpp;c;cxx;moc;h;def;odl;idl;res; + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Form Files\Generated Files + + + + + Form Files\Generated Files + + + Form Files\Generated Files + + + + + Form Files + + + Form 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 index a562388..c34359b 100644 --- a/QtTermTCP.vcxproj.user +++ b/QtTermTCP.vcxproj.user @@ -1,22 +1,29 @@ - - - - - 2022-05-19T07:28:47.9186341Z - - - 2022-05-19T07:28:58.9302359Z - - - 2023-04-12T13:14:53.1644359Z - - - 2023-02-08T10:47:28.1396474Z - - - 2023-03-06T08:27:08.3742630Z - - - 2023-02-08T10:47:27.7430420Z - + + + + $(APPDATA) + WindowsLocalDebugger + + + ..\..\..\..\..\DevProgs\BPQ32 + WindowsLocalDebugger + + + 2022-05-19T07:28:47.9186341Z + + + 2022-05-19T07:28:58.9302359Z + + + 2023-10-14T13:14:56.5532991Z + + + 2023-10-14T13:14:56.7231497Z + + + 2023-10-14T13:14:55.9367569Z + + + 2023-10-14T13:14:56.2561805Z + \ No newline at end of file diff --git a/QtTermTCP52.zip b/QtTermTCP52.zip deleted file mode 100644 index e36d5dc4936f7438336c9efca994f49d63dc3e89..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 251141 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 z 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 G=>GuO-*8a? diff --git a/Ring.wav b/Ring.wav new file mode 100644 index 0000000000000000000000000000000000000000..ea59863c3e882b2f1e4761f63d2407f9e4b79bf9 GIT binary patch literal 63760 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

8lNx5hLVsgzD6vJ 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 - - 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 - - - - - + + + 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 index 2e2d665..98d3ed9 100644 --- a/TabDialog.cpp +++ b/TabDialog.cpp @@ -1,1166 +1,1182 @@ - -#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; -QLineEdit *SNameEdit; - -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 SessName[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; -extern "C" TAX25Port * get_user_port_by_calls(int snd_ch, char * CallFrom, char * CallTo); - -void KISSConnect::myaccept() -{ - QVariant Q; - - char CallTo[32]; - char Via[128]; - strcpy(CallTo, wCallTo->currentText().toUpper().toUtf8()); - strcpy(Via, Digis->text().toUpper().toUtf8()); - - TAX25Port * AX25Sess = 0; - - // Check for duplicate session - - AX25Sess = get_user_port_by_calls(0, MYCALL, CallTo); - - if (AX25Sess) - { - // Duplicate - - char Msg[256]; - - int Len = sprintf(Msg, "You already have a session between %s and %s so can't connect\r", MYCALL, CallTo); - - WritetoOutputWindow(ActiveSession, (unsigned char *)Msg, Len); -// KISSConnect::accept(); - return; - } - - 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]); - - QLabel *SNameLabel = new QLabel(tr("Session Name")); - SNameEdit = new QLineEdit(SessName[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->addWidget(SNameLabel); - layout->addWidget(SNameEdit); - - 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); - - val = SNameEdit->text(); - qb = val.toLatin1(); - ptr = qb.data(); - strcpy(SessName[ConfigHost], ptr); - - char Label[256]; - - if (ptr[0]) - sprintf(Label, "%s(%s)", Host[ConfigHost], SessName[ConfigHost]); - else - strcpy(Label, Host[ConfigHost]); - - actHost[ConfigHost]->setText(Label); - actSetup[ConfigHost]->setText(Label); - - 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(); -} + +#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; +QLineEdit *SNameEdit; + +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 SessName[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 (CallFrom[0] == 0) + { + QMessageBox msgBox; + msgBox.setText("Call From missing"); + msgBox.exec(); + return; + } + + if (CallTo[0] == 0) + { + QMessageBox msgBox; + msgBox.setText("Call To missing"); + msgBox.exec(); + return; + } + + // 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; +extern "C" TAX25Port * get_user_port_by_calls(int snd_ch, char * CallFrom, char * CallTo); + +void KISSConnect::myaccept() +{ + QVariant Q; + + char CallTo[32]; + char Via[128]; + strcpy(CallTo, wCallTo->currentText().toUpper().toUtf8()); + strcpy(Via, Digis->text().toUpper().toUtf8()); + + TAX25Port * AX25Sess = 0; + + // Check for duplicate session + + AX25Sess = get_user_port_by_calls(0, MYCALL, CallTo); + + if (AX25Sess) + { + // Duplicate + + char Msg[256]; + + int Len = sprintf(Msg, "You already have a session between %s and %s so can't connect\r", MYCALL, CallTo); + + WritetoOutputWindow(ActiveSession, (unsigned char *)Msg, Len); +// KISSConnect::accept(); + return; + } + + 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]); + + QLabel *SNameLabel = new QLabel(tr("Session Name")); + SNameEdit = new QLineEdit(SessName[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->addWidget(SNameLabel); + layout->addWidget(SNameEdit); + + 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); + + val = SNameEdit->text(); + qb = val.toLatin1(); + ptr = qb.data(); + strcpy(SessName[ConfigHost], ptr); + + char Label[256]; + + if (ptr[0]) + sprintf(Label, "%s(%s)", Host[ConfigHost], SessName[ConfigHost]); + else + strcpy(Label, Host[ConfigHost]); + + actHost[ConfigHost]->setText(Label); + actSetup[ConfigHost]->setText(Label); + + 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 index a7ac986..cd21067 100644 --- a/TabDialog.h +++ b/TabDialog.h @@ -1,208 +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; -}; - - - - + +#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 index 78464a0..0c20d35 100644 --- a/TermTCPCommon.cpp +++ b/TermTCPCommon.cpp @@ -1,1144 +1,1239 @@ -#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); -void DoTermResize(Ui_ListenSession * Sess); -void DecodeTeleText(Ui_ListenSession * Sess, char * page); - -int Bells = TRUE; -int StripLF = FALSE; -int LogMonitor = FALSE; -int LogOutput = FALSE; -int SendDisconnected = TRUE; -int ChatMode = TRUE; -int AutoTeletext = 1; - -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; - } - - // See if Teletext data - - // We need to identify a viewdata frame. Seems to start - - //1e 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a ........ ........ - //00000170 0a 0a 0a 0a 0a 0a 0a 0a 11 0c 1b - - // Page seems to start 1c - - // After page get - - //1e 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a ........ ........ - // 0000049D 0a 0a 0a 0a 0a 0a 0a 0a 11 14 1b 44 1b 5d 1b 43 ........ ...D.].C - // 000004AD 53 65 6c 65 Sele - // 000004B1 63 74 20 69 74 65 6d 20 6f 72 1b 47 2a 70 61 67 ct item or .G*pag - // 000004C1 65 5f 20 3a 20 20 20 20 20 20 20 20 20 20 20 20 e_ : - // 000004D1 20 0d 09 09 09 09 09 09 09 09 09 09 09 09 09 09 ....... ........ - // 000004E1 09 09 09 09 09 09 09 09 09 09 09 09 09 11 - - Buffer[len] = 0; - - if (AutoTeletext && (Buffer[0] == 0x1e || Buffer[0] == 0x0c)) - { - if (Sess->TTActive == 0) - { - Sess->TTActive = 1; - DoTermResize(Sess); - } - } - - if (Sess->TTActive) - { - // Feed to Teletext code - - // We need to decode a whole page. There is no obvious delimiter so process till data stops. - // Buffer is cleared when next input is sent - - if (strlen(&Sess->pageBuffer[0] + len) > 4090) - Sess->pageBuffer[0] = 0; // Protect buffer - - strcat(Sess->pageBuffer, (char *)Buffer); - - DecodeTeleText(Sess, (char *)Sess->pageBuffer); // Re-decode same data until we get the end - 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; - } - - MonLen = len; // in case no fe - - 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); - } - } -} - +#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); +void DoTermResize(Ui_ListenSession * Sess); +void DecodeTeleText(Ui_ListenSession * Sess, char * page); + +int Bells = TRUE; +int StripLF = FALSE; +int LogOutput = FALSE; +int SendDisconnected = TRUE; +int ChatMode = TRUE; +int AutoTeletext = 1; + +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; + +QString KeyWordsFile = "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; +} + +#ifdef WIN32 + +char * strcasestr(char *ch1, char *ch2) +{ + char *chN1, *chN2; + char *chNdx; + char *chRet = NULL; + + chN1 = _strdup(ch1); + chN2 = _strdup(ch2); + + if (chN1 && chN2) + { + chNdx = chN1; + while (*chNdx) + { + *chNdx = (char)tolower(*chNdx); + chNdx++; + } + chNdx = chN2; + + while (*chNdx) + { + *chNdx = (char)tolower(*chNdx); + chNdx++; + } + + chNdx = strstr(chN1, chN2); + + if (chNdx) + chRet = ch1 + (chNdx - chN1); + } + + free(chN1); + free(chN2); + return chRet; +} + +#endif + +void GetKeyWordFile() +{ + DWORD FileSize; + char * ptr1, *ptr2; + char * KeyWordFile; + + QFile file(KeyWordsFile); + + if (!file.open(QIODevice::ReadOnly)) + { + if (UseKeywords) // Don't need to alert if not being used + { + QMessageBox msgBox; + msgBox.setText("Keyword File " + KeyWordsFile + " not found"); + msgBox.exec(); + } + return; + } + + FileSize = file.size(); + + KeyWordFile = (char *)malloc(FileSize + 1); + + file.read(KeyWordFile, FileSize); + + file.close(); + + KeyWordFile[FileSize] = 0; + + ptr1 = KeyWordFile; + + while (ptr1) + { + if (*ptr1 == '\n') ptr1++; + + ptr2 = strtok_s(NULL, "\r\n", &ptr1); + if (ptr2) + { + if (*ptr2 != '#') + { + KeyWords = (char **)realloc(KeyWords, (++NumberofKeyWords + 1) * 4); + KeyWords[NumberofKeyWords] = ptr2; + } + } + else + break; + } +} + + +int CheckKeyWord(char * Word, char * Msg) +{ + char * ptr1 = Msg, *ptr2; + int len = (int)strlen(Word); + + while (*ptr1) // Stop at end + { + ptr2 = strcasestr(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) +{ + int i; + + if (UseKeywords == 0 || NumberofKeyWords == 0) + return FALSE; + + // we need to null terminate Msg, so create a copy + + unsigned char * copy = (unsigned char *)malloc(len + 1); + + memcpy(copy, Msg, len); + copy[len] = 0; + + for (i = 1; i <= NumberofKeyWords; i++) + { + if (CheckKeyWord(KeyWords[i], (char *)copy)) + { + myBeep(&AlertWAV); + free (copy); + return TRUE; // Alert + } + } + + free(copy); + 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; + } + + // See if Teletext data + + // We need to identify a viewdata frame. Seems to start + + //1e 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a ........ ........ + //00000170 0a 0a 0a 0a 0a 0a 0a 0a 11 0c 1b + + // Page seems to start 1c + + // After page get + + //1e 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a 0a ........ ........ + // 0000049D 0a 0a 0a 0a 0a 0a 0a 0a 11 14 1b 44 1b 5d 1b 43 ........ ...D.].C + // 000004AD 53 65 6c 65 Sele + // 000004B1 63 74 20 69 74 65 6d 20 6f 72 1b 47 2a 70 61 67 ct item or .G*pag + // 000004C1 65 5f 20 3a 20 20 20 20 20 20 20 20 20 20 20 20 e_ : + // 000004D1 20 0d 09 09 09 09 09 09 09 09 09 09 09 09 09 09 ....... ........ + // 000004E1 09 09 09 09 09 09 09 09 09 09 09 09 09 11 + + Buffer[len] = 0; + + if (AutoTeletext && (Buffer[0] == 0x1e || Buffer[0] == 0x0c)) + { + if (Sess->TTActive == 0) + { + Sess->TTActive = 1; + DoTermResize(Sess); + } + } + + if (Sess->TTActive) + { + // Feed to Teletext code + + // We need to decode a whole page. There is no obvious delimiter so process till data stops. + // Buffer is cleared when next input is sent + + if (strlen(&Sess->pageBuffer[0] + len) > 4090) + Sess->pageBuffer[0] = 0; // Protect buffer + + strcat(Sess->pageBuffer, (char *)Buffer); + + DecodeTeleText(Sess, (char *)Sess->pageBuffer); // Re-decode same data until we get the end + 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 < 2048) + memcpy(Sess->PortMonString, ptr, len); + + + // Remove old menu + + for (i = 0; i < 65; 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 = 64; + + if (Sess->portmask & (1ll << (m - 1))) + SetPortMonLine(portnum, msg, 1, 1); + else + SetPortMonLine(portnum, msg, 1, 0); + } + return; + } + + MonLen = len; // in case no fe + + 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 time 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 + + // Drop through + + 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 index 839bb64..1477a8f 100644 --- a/UZ7HOUtils.c +++ b/UZ7HOUtils.c @@ -1,320 +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; -} - +/* +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 index c5e4506..7e436e4 100644 --- a/VARA.ui +++ b/VARA.ui @@ -1,148 +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 - - - - - + + + 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 index c4baacd..6ce7d9f 100644 --- a/VARAConfig.ui +++ b/VARAConfig.ui @@ -1,608 +1,608 @@ - - - Dialog - - - - 0 - 0 - 571 - 589 - - - - VARA Configuration - - - - - 170 - 490 - 211 - 33 - - - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - OK - - - - - - - Cancel - - - - - - - - - 10 - 120 - 551 - 161 - - - - TNC Setup - - - - - 130 - 22 - 101 - 22 - - - - - - - 400 - 20 - 47 - 22 - - - - - - - 130 - 57 - 391 - 22 - - - - - - - 16 - 24 - 47 - 13 - - - - Host - - - - - - 286 - 19 - 47 - 22 - - - - Port - - - - - - 16 - 56 - 47 - 22 - - - - Path - - - - - - 130 - 91 - 391 - 22 - - - - - - - 16 - 90 - 101 - 22 - - - - Init Commands - - - - - - 80 - 120 - 371 - 31 - - - - You can specify more than one command, but the separator must be just a comma with no spaces - - - Qt::AlignCenter - - - true - - - - - - - 8 - 300 - 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 - - - - - - - - - - - - - + + + Dialog + + + + 0 + 0 + 571 + 589 + + + + VARA Configuration + + + + + 170 + 490 + 211 + 33 + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + OK + + + + + + + Cancel + + + + + + + + + 10 + 120 + 551 + 161 + + + + TNC Setup + + + + + 130 + 22 + 101 + 22 + + + + + + + 400 + 20 + 47 + 22 + + + + + + + 130 + 57 + 391 + 22 + + + + + + + 16 + 24 + 47 + 13 + + + + Host + + + + + + 286 + 19 + 47 + 22 + + + + Port + + + + + + 16 + 56 + 47 + 22 + + + + Path + + + + + + 130 + 91 + 391 + 22 + + + + + + + 16 + 90 + 101 + 22 + + + + Init Commands + + + + + + 80 + 120 + 371 + 31 + + + + You can specify more than one command, but the separator must be just a comma with no spaces + + + Qt::AlignCenter + + + true + + + + + + + 8 + 300 + 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/TeleText.ui b/YAPPRxSize.ui similarity index 74% rename from TeleText.ui rename to YAPPRxSize.ui index f3b5797..70c2ac3 100644 --- a/TeleText.ui +++ b/YAPPRxSize.ui @@ -1,106 +1,116 @@ - - - TeleTextDialog - - - - 0 - 0 - 665 - 551 - - - - Dialog - - - - - 224 - 516 - 153 - 31 - - - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - OK - - - - - - - Cancel - - - - - - - - - 28 - 20 - 600 - 475 - - - - TextLabel - - - - - - - okButton - clicked() - TeleTextDialog - accept() - - - 278 - 253 - - - 96 - 254 - - - - - cancelButton - clicked() - TeleTextDialog - reject() - - - 369 - 253 - - - 179 - 282 - - - - - + + + SizeDialog + + + + 0 + 0 + 243 + 157 + + + + Dialog + + + + + 20 + 80 + 181 + 33 + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + OK + + + + + + + Cancel + + + + + + + + + 20 + 30 + 81 + 20 + + + + Max RX Size + + + + + + 120 + 30 + 61 + 20 + + + + + + + + okButton + clicked() + SizeDialog + accept() + + + 278 + 253 + + + 96 + 254 + + + + + cancelButton + clicked() + SizeDialog + reject() + + + 369 + 253 + + + 179 + 282 + + + + + diff --git a/ax25.c b/ax25.c index c379b0a..f212148 100644 --- a/ax25.c +++ b/ax25.c @@ -1,2987 +1,3006 @@ -/* -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; -} - - - - - - - - +/* +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, "Incoming KISS 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((char *)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((char *)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) +{ + char * 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(TKISSMode)); + + 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 && Digis[0]) + { + 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"; + +int KISSLocalTime = 0; +int KISSMonEnable = 0; +int KISSMonNodes = 0; + +char * ShortDateTime() +{ + struct tm * tm; + time_t NOW = time(NULL); + + if (KISSLocalTime) + tm = localtime(&NOW); + else + 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) + { + if (datap[0] == 255) //Nodes broadcast + { + if (KISSMonNodes == 0) + { + freeString(data); + return 0; + } + } + } + + + + // 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 index 8895eee..d554b20 100644 --- a/ax25.h +++ b/ax25.h @@ -1,304 +1,293 @@ -// 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 -} +// 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 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 index b9003c2..e4dabb7 100644 --- a/ax25_l2.c +++ b/ax25_l2.c @@ -1,1735 +1,1737 @@ -/* -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]; -extern UCHAR axMYCALL[7] = ""; // Mycall in ax.25 - -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 repeated 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 - - // Check addresses to us - - if (memcmp(path, axMYCALL, 7) != 0) - return; // ignore - - 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 ?? - - // Make sure it is for us - - - - 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); - -} - - - - +/* +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; +int KISSListen = 1; + +void * KISSSockCopy[4]; +extern UCHAR axMYCALL[7] = ""; // Mycall in ax.25 + +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 * ax25IncomingConnect(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 (ax25IncomingConnect(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 repeated 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 + + // Check addresses to us + + if (memcmp(path, axMYCALL, 7) != 0) + return; // ignore + + if (KISSListen == 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 ?? + + // Make sure it is for us + + + + 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/debug/moc_predefs.h b/debug/moc_predefs.h new file mode 100644 index 0000000..6d708a1 --- /dev/null +++ b/debug/moc_predefs.h @@ -0,0 +1,12 @@ +#define _MSC_EXTENSIONS +#define _INTEGRAL_MAX_BITS 64 +#define _MSC_VER 1916 +#define _MSC_FULL_VER 191627043 +#define _MSC_BUILD 0 +#define _WIN32 +#define _M_IX86 600 +#define _M_IX86_FP 2 +#define _CPPRTTI +#define _DEBUG +#define _MT +#define _DLL diff --git a/ding.wav b/ding.wav new file mode 100644 index 0000000000000000000000000000000000000000..533112922c167a93bb0e99fb3f6fc0a4b4655b7a GIT binary patch literal 72972 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�V@iOP>=t5_{S(?+ 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 z z<)2)I(2+QtVJ&##0)6Nf5Ic@;ZZfM%lJ)p>ZChcDcsQ>7RMAk>e>}Q#kgVSVE!9BU n7f!hL@vjYhK7?1=f!93L@f_UMC$hx1O~_t6rm-uDRCD=1*LbpQ literal 0 HcmV?d00001 diff --git a/hid.c b/hid.c index 88ab1bc..6794c60 100644 --- a/hid.c +++ b/hid.c @@ -1,910 +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 +/******************************************************* + 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/main.cpp b/main.cpp index 270cffe..ed67d5f 100644 --- a/main.cpp +++ b/main.cpp @@ -1,26 +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(); -} +#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 index bdf2910..57d400f 100644 --- a/makeit +++ b/makeit @@ -1,10 +1,11 @@ -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 ./ -cp --preserve /mnt/Source/QT/QtTermTCP/*.pro ./ -cp --preserve /mnt/Source/QT/QtTermTCP/*.qrc ./ -cp --preserve /mnt/Source/QT/QtTermTCP/*.ico ./ +cp --preserve /mnt/Source/QT/QtTermTCP2/*.cpp ./ +cp --preserve /mnt/Source/QT/QtTermTCP2/*.c ./ +cp --preserve /mnt/Source/QT/QtTermTCP2/*.h ./ +cp --preserve /mnt/Source/QT/QtTermTCP2/*.ui ./ +cp --preserve /mnt/Source/QT/QtTermTCP2/*.pro ./ +cp --preserve /mnt/Source/QT/QtTermTCP2/*.qrc ./ +cp --preserve /mnt/Source/QT/QtTermTCP2/*.ico ./ +cp --preserve /mnt/Source/QT/QtTermTCP2/*.wav ./ qmake make -j4 diff --git a/release/moc_predefs.h b/release/moc_predefs.h new file mode 100644 index 0000000..54e9037 --- /dev/null +++ b/release/moc_predefs.h @@ -0,0 +1,11 @@ +#define _MSC_EXTENSIONS +#define _INTEGRAL_MAX_BITS 64 +#define _MSC_VER 1916 +#define _MSC_FULL_VER 191627043 +#define _MSC_BUILD 0 +#define _WIN32 +#define _M_IX86 600 +#define _M_IX86_FP 2 +#define _CPPRTTI +#define _MT +#define _DLL diff --git a/ui_TCPHostConfig.h b/ui_TCPHostConfig.h deleted file mode 100644 index 1f232a7..0000000 --- a/ui_TCPHostConfig.h +++ /dev/null @@ -1,65 +0,0 @@ -/******************************************************************************** -** 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 index 59b1e23..f5eecff 100644 --- a/utf8Routines.cpp +++ b/utf8Routines.cpp @@ -1,597 +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; - -} - +/* +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; + +} +