From a3362dbe684eaf92e626d56ccdc52063452b2733 Mon Sep 17 00:00:00 2001 From: g8bpq Date: Thu, 12 Oct 2023 18:17:43 +0100 Subject: [PATCH] 0.0.0.69 --- .gitattributes | 63 + .gitignore | 261 + 250x1000Hz.wav | Bin 0 -> 6044 bytes 250x600Hz.wav | Bin 0 -> 6044 bytes AGWCode.cpp | 2 +- AlertSetup.ui | 353 + PCBeep.wav | Bin 0 -> 35152 bytes QtTermTCP-skigpi4-64.cpp | 7423 +++++++++++++++++ QtTermTCP.cpp | 218 +- QtTermTCP.h | 17 +- QtTermTCP.ini | 123 - QtTermTCP.pro | 4 +- QtTermTCP.vcxproj | 7 +- QtTermTCP.vcxproj.filters | 21 +- QtTermTCP.vcxproj.user | 17 +- Ring.wav | Bin 0 -> 63760 bytes TermTCPCommon.cpp | 106 +- debug/moc_predefs.h | 2 +- ding.wav | Bin 0 -> 72972 bytes .../QtTermTCP.h | 297 + .../TermTCPCommon.cpp | 1197 +++ .../TermTCPCommon.cpp | 1197 +++ makeit | 14 +- release/moc_predefs.h | 2 +- 24 files changed, 11137 insertions(+), 187 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-skigpi4-64.cpp delete mode 100644 QtTermTCP.ini create mode 100644 Ring.wav create mode 100644 ding.wav create mode 100644 enc_temp_folder/5aa81bc33ffd093ff53c1c62422a77/QtTermTCP.h create mode 100644 enc_temp_folder/7a76876058568f76dfb2539c1a2c2b/TermTCPCommon.cpp create mode 100644 enc_temp_folder/9d787c8a093194efc45ae72cda5d644/TermTCPCommon.cpp 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..2c4a1be 100644 --- a/AGWCode.cpp +++ b/AGWCode.cpp @@ -1062,7 +1062,7 @@ void on_AGW_C_frame(AGWUser * AGW, struct AGWHeader * Frame, byte * Msg) setMenus(true); if (ConnectBeep) - myBeep(); + myBeep(&ConnectWAV); // Send CText if defined diff --git a/AlertSetup.ui b/AlertSetup.ui new file mode 100644 index 0000000..441cc2d --- /dev/null +++ b/AlertSetup.ui @@ -0,0 +1,353 @@ + + + AlertDialog + + + + 0 + 0 + 558 + 461 + + + + Dialog + + + + + 86 + 373 + 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 + 267 + 107 + 20 + + + + Interval .wav File + + + + + + 200 + 267 + 207 + 20 + + + + + + + 24 + 292 + 169 + 20 + + + + Inbound Connect .wav File + + + + + + 24 + 317 + 165 + 20 + + + + Keyword Alert .wav File + + + + + + 258 + 95 + 89 + 20 + + + + Keyword File + + + + + + 200 + 292 + 207 + 20 + + + + + + + 200 + 317 + 207 + 20 + + + + + + + 20 + 130 + 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. Some sample wav files are provided. + + + true + + + + + + 416 + 267 + 61 + 20 + + + + Choose + + + + + + 416 + 292 + 61 + 20 + + + + Choose + + + + + + 416 + 317 + 61 + 20 + + + + Choose + + + + + + 24 + 15 + 289 + 20 + + + + Use Bells (ascii BELL char makes a sound) + + + + + + 200 + 242 + 207 + 20 + + + + + + + 416 + 242 + 61 + 20 + + + + Choose + + + + + + 24 + 242 + 107 + 20 + + + + Bells .wav File + + + + + + + + + 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-skigpi4-64.cpp b/QtTermTCP-skigpi4-64.cpp new file mode 100644 index 0000000..9a26140 --- /dev/null +++ b/QtTermTCP-skigpi4-64.cpp @@ -0,0 +1,7423 @@ +// Qt Version of BPQTermTCP + +#define VersionString "0.0.0.68" + + +// .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 Remember last used host on restart Sept 2023 +// Add AutoConnect in Tabbed Mode +// Fix auto copy when QtTerm not active window + + +#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 + +QString ConnectWAV(""); + +extern int MaxRXSize; + +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 *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; + +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; + Sess->Tab = 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) + { + Ui_ListenSession * Sess; + int index = 0; + + tabWidget = new QTabWidget(this); + + 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 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 (Send Keepalives)", 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); + + AlertAction = new QAction("Alert Setup", this); + setupMenu->addAction(AlertAction); + connect(AlertAction, SIGNAL(triggered()), this, SLOT(AlertSlot())); + AlertAction->setFont(*menufont); + + + 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); + 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; + + int n = myPoint.y() - (Wrect.height() - Brect.height()); + + myPoint.setY(n); + +// QPoint tabBarPoint = mapTo(tabBar, point); + + 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())); + + QAction * selected_action = 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(); +} + +// "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 + + 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); + + 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); + + 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 == 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; + } + } + } + + // 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(AlertBeep); + } + + // 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(Bells); + } + } 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(); + 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", "").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(); + 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]); + + 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("ConnectWAV", ConnectWAV); + 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("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); + + 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->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(int Type) +{ + if (useBeep) + { + QApplication::beep(); + return; + } +} + +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->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::AlertSlot() +{ + // This runs the VARA Configuration dialog + + Alert = new(Ui_AlertDialog); + + QDialog UI; + + Alert->setupUi(&UI); + + UI.setFont(*menufont); + + deviceUI = &UI; + + Alert->useBeep->setChecked(useBeep); + Alert->useFiles->setChecked(!useBeep); + + + Alert->InboundBeep->setChecked(ConnectBeep); + Alert->InactivityBeep->setChecked(AlertBeep); + Alert->Interval->setText(QString::number(AlertInterval)); + + Alert->connectFile->setText(ConnectWAV); + QObject::connect(Alert->chooseInbound, SIGNAL(clicked()), this, SLOT(chooseInboundWAV())); + + + 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->setText(ConnectWAV); + +} + + +void QtTermTCP::alertAccept() +{ + QVariant Q; + + useBeep = Alert->useBeep->isChecked(); + + ConnectBeep = Alert->InboundBeep->isChecked(); + AlertBeep = Alert->InactivityBeep->isChecked(); + AlertInterval = Alert->Interval->text().toInt(); + ConnectWAV = Alert->connectFile->text(); + + 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(ConnectBeep); +} + +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->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(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); + + 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(ConnectBeep); + + // 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]; + 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); +} + diff --git a/QtTermTCP.cpp b/QtTermTCP.cpp index 3edfb7b..9b03118 100644 --- a/QtTermTCP.cpp +++ b/QtTermTCP.cpp @@ -1,6 +1,6 @@ // Qt Version of BPQTermTCP -#define VersionString "0.0.0.68" +#define VersionString "0.0.0.69" // .12 Save font weight @@ -76,8 +76,17 @@ // .67 Add config Yapp RX Size dialog July 2023 // Fix 63 port montoring -// .68 Remember last used host on restart Sept 2023 -// Add AutoConnect in Tabbed Mode +// .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. #define _CRT_SECURE_NO_WARNINGS @@ -222,6 +231,17 @@ 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; @@ -879,6 +899,7 @@ Ui_ListenSession * newWindow(QObject * parent, int Type, const char * Label) Sess->TTActive = 0; Sess->TTFlashToggle = 0; Sess->pageBuffer[0] = 0; + Sess->Tab = 0; _sessions.push_back(Sess); @@ -1078,6 +1099,8 @@ QtTermTCP::QtTermTCP(QWidget *parent) : QMainWindow(parent) GetSettings(); + GetKeyWordFile(); + // Set background colours sprintf(monStyleSheet, "background-color: rgb(%d, %d, %d);", @@ -1340,9 +1363,15 @@ QtTermTCP::QtTermTCP(QWidget *parent) : QMainWindow(parent) 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())); @@ -1358,6 +1387,8 @@ QtTermTCP::QtTermTCP(QWidget *parent) : QMainWindow(parent) 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())); @@ -1373,13 +1404,13 @@ QtTermTCP::QtTermTCP(QWidget *parent) : QMainWindow(parent) 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); - 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->addSeparator(); setupMenu->addAction(new QAction("Interpret non-UTF8 input as:", this)); setupMenu->setFont(*menufont); @@ -2871,7 +2902,7 @@ extern "C" void WritetoOutputWindowEx(Ui_ListenSession * Sess, unsigned char * B if (AlertInterval && (NOW - LastWrite) > AlertInterval) { if (AlertBeep) - myBeep(); + myBeep(&IntervalWAV); } // if tabbed and not active tab set tab label red @@ -3011,7 +3042,7 @@ lineloop: if (ptr) { *(ptr) = 32; - myBeep(); + myBeep(&BellWAV); } } while (ptr); } @@ -3321,8 +3352,18 @@ void GetSettings() 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", "").toString().toUtf8(); + AlertWAV = settings->value("AlertWAV", "").toString().toUtf8(); + BellWAV = settings->value("BellWAV", "").toString().toUtf8(); + IntervalWAV = settings->value("IntervalWAV", "").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(); @@ -3495,7 +3536,17 @@ extern "C" void SaveSettings() 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); @@ -3748,9 +3799,17 @@ void QtTermTCP::MyTimerSlot() } -extern "C" void myBeep() +extern "C" void myBeep(QString * WAV) { - QApplication::beep(); + if (useBeep) + { + QApplication::beep(); + return; + } + + // Using .wav files + + QSound::play(*WAV); } void QtTermTCP::ListenSlot() @@ -3770,6 +3829,7 @@ void QtTermTCP::AGWSlot() } Ui_Dialog * Dev; +Ui_AlertDialog * Alert; static Ui_KISSDialog * KISS; static Ui_ColourDialog * COLOURS; @@ -3923,6 +3983,113 @@ void QtTermTCP::KISSreject() } +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->setText(ConnectWAV); + Alert->bellsFile->setText(BellWAV); + Alert->intervalFile->setText(IntervalWAV); + Alert->keywordWAV->setText(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->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->setText(ConnectWAV); + +} + +void QtTermTCP::chooseBellsWAV() +{ + BellWAV = QFileDialog::getOpenFileName(this, + tr("Select Wav"), "", tr("Sound Files (*.wav)")); + + Alert->bellsFile->setText(BellWAV); +} + +void QtTermTCP::chooseIntervalWAV() +{ + IntervalWAV = QFileDialog::getOpenFileName(this, + tr("Select Wav"), "", tr("Sound Files (*.wav)")); + + Alert->intervalFile->setText(IntervalWAV); +} + +void QtTermTCP::chooseAlertWAV() +{ + AlertWAV = QFileDialog::getOpenFileName(this, + tr("Select Wav"), "", tr("Sound Files (*.wav)")); + + Alert->keywordWAV->setText(AlertWAV); +} + +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->text(); + BellWAV = Alert->bellsFile->text(); + IntervalWAV = Alert->intervalFile->text(); + AlertWAV = Alert->keywordWAV->text(); + + delete(Alert); + SaveSettings(); + deviceUI->accept(); +} + +void QtTermTCP::alertReject() +{ + delete(Alert); + deviceUI->reject(); +} @@ -4289,7 +4456,7 @@ void QtTermTCP::onNewConnection() WritetoOutputWindow(Sess, (unsigned char *)Msg, (int)strlen(Msg)); if (ConnectBeep) - myBeep(); + myBeep(&ConnectWAV); } void QtTermTCP::onSocketStateChanged(QAbstractSocket::SocketState socketState) @@ -6419,7 +6586,7 @@ extern "C" Ui_ListenSession * ax25IncommingConnect(TAX25Port * AX25Sess) setMenus(true); if (ConnectBeep) - myBeep(); + myBeep(&ConnectWAV); // Send CText if defined @@ -7198,34 +7365,37 @@ void DecodeTeleText(Ui_ListenSession * Sess, char * page) // Just write char at current col and line isText: - char s[5] = " "; + char s[5]; + unsigned char su[5] = ""; // Some chars are in wrong char set - s[0] = ch; + su[0] = ch; if (ch == '_') - s[0] = '#'; + su[0] = '#'; else if (ch == 0x7e) // division { - s[0] = 0xC3; - s[1] = 0xB7; + su[0] = 0xC3; + su[1] = 0xB7; } else if (ch == 0x5e) // up arrow { - s[0] = 0xF0; - s[1] = 0x9F; - s[2] = 0xA0; - s[3] = 0x95; + su[0] = 0xF0; + su[1] = 0x9F; + su[2] = 0xA0; + su[3] = 0x95; } else if (ch == 0x7f) // up arrow { - s[0] = 0xE2; - s[1] = 0x96; - s[2] = 0x88; + su[0] = 0xE2; + su[1] = 0x96; + su[2] = 0x88; } + memcpy(s, su, 5); + // if (doubleHeight) // p.drawText(col * 15, line * 19 + 25, s); // else diff --git a/QtTermTCP.h b/QtTermTCP.h index b75d0e1..84bab07 100644 --- a/QtTermTCP.h +++ b/QtTermTCP.h @@ -2,7 +2,7 @@ #include #include "ui_QtTermTCP.h" -//#include "ui_AGWParams.h" +#include "ui_AlertSetup.h" #include "ui_YAPPRxSize.h" #include "ui_ColourConfig.h" #include "ui_VARAConfig.h" @@ -28,6 +28,7 @@ #include #include #include +#include #define MAXHOSTS 16 #define MAXPORTS 64 @@ -152,6 +153,13 @@ private slots: void KISSTimerSlot(); void ListenSlot(); void AGWSlot(); + void AlertSlot(); + void chooseInboundWAV(); + void chooseBellsWAV(); + void chooseIntervalWAV(); + void chooseAlertWAV(); + void alertAccept(); + void alertReject(); void VARASlot(); void KISSSlot(); void deviceaccept(); @@ -248,6 +256,7 @@ private: QAction *AGWAction; QAction *VARAAction; QAction *KISSAction; + QAction *AlertAction; QAction *quitAction; QList _sockets; @@ -269,15 +278,19 @@ extern "C" void setTraceOff(Ui_ListenSession * Sess); void SetPortMonLine(int i, char * Text, int visible, int enabled); void SaveSettings(); - void myBeep(); + 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); diff --git a/QtTermTCP.ini b/QtTermTCP.ini deleted file mode 100644 index e30c210..0000000 --- a/QtTermTCP.ini +++ /dev/null @@ -1,123 +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\xb6\0\0\0w\0\0\x4J\0\0\x3.\0\0\0\xb6\0\0\0w\0\0\x4J\0\0\x3.\0\0\0\0\0\0\0\0\x5\0\0\0\0\xb6\0\0\0w\0\0\x4J\0\0\x3.) -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\x95\0\0\x2\xa2\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|john|password|1 1 1 0 1 0 0 1\r| -HostParams3=192.168.1.44|8011|john|password|1 1 1 0 1 0 0 1\r|Pogo4 -HostParams4=127.0.0.1|8021|john|password|c000000100000006 1 1 0 1 0 0 1\r|LinBPQ -HostParams5=192.168.1.14|8011|john|password|c00000010000002e 1 1 0 1 0 0 1\r| -HostParams6=127.0.0.1|8121|john|password|3 1 1 0 1 0 0 1\r|CB -HostParams7=|0|||| -HostParams8=|0|||| -HostParams9=|0|||| -HostParams10=|0|||| -HostParams11=|0|||| -HostParams12=|0|||| -HostParams13=|0|||| -HostParams14=|0|||| -HostParams15=|0|||| -Split=58 -ChatMode=1 -AutoTeletext=1 -Bells=1 -StripLF=1 -AlertBeep=1 -ConnectBeep=1 -CurrentHost=3 2 3 2 0 0 0 0 0 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 -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="4|1, 3, 57, 19, 633, 795|2, 2, 18, 42, 605, 833|1, 3, 20, 410, 611, 1201|3, 2, 32, 598, 623, 1389|" -VARAInit="P2P SESSION,WINLINK SESSION" -MaxRXSize=100000 -AutoConnect=1 1 1 0 0 0 0 0 0 0 -TabType=1 3 3 1 1 1 1 2 2 0 - -[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 8719faf..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 @@ -41,7 +42,8 @@ FORMS += QtTermTCP.ui\ KISSConfig.ui \ ColourConfig.ui \ YAPPRxSize.ui \ - AGWConnect.ui + AGWConnect.ui \ + AlertSetup.ui RESOURCES += QtTermTCP.qrc diff --git a/QtTermTCP.vcxproj b/QtTermTCP.vcxproj index cc0795c..a911f95 100644 --- a/QtTermTCP.vcxproj +++ b/QtTermTCP.vcxproj @@ -22,7 +22,7 @@ {14F3B24E-473C-324E-A99D-3B679FCF5F67} QtTermTCP QtVS_v304 - 10.0.19041.0 + 10.0.17763.0 10.0.19041.0 $(MSBuildProjectDirectory)\QtMsBuild @@ -153,7 +153,7 @@ true - shell32.lib;setupapi.lib;%(AdditionalDependencies) + shell32.lib;setupapi.lib;C:\Qt\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 @@ -279,7 +279,7 @@ true - shell32.lib;setupapi.lib;%(AdditionalDependencies) + shell32.lib;setupapi.lib;%(AdditionalDependencies);C:\Qt\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 @@ -435,6 +435,7 @@ + diff --git a/QtTermTCP.vcxproj.filters b/QtTermTCP.vcxproj.filters index 9b27731..f0470d1 100644 --- a/QtTermTCP.vcxproj.filters +++ b/QtTermTCP.vcxproj.filters @@ -83,7 +83,7 @@ Header Files - Header Files + Generated Files @@ -95,28 +95,31 @@ - + Form Files - + Form Files - + Form Files - + Form Files - + Form Files - + Form Files - + Form Files - + + Form Files + + Form Files diff --git a/QtTermTCP.vcxproj.user b/QtTermTCP.vcxproj.user index 0ccc4d8..765762b 100644 --- a/QtTermTCP.vcxproj.user +++ b/QtTermTCP.vcxproj.user @@ -1,6 +1,13 @@  - + + ..\..\bpq32\CommonSource + WindowsLocalDebugger + + + ..\..\..\..\..\DevProgs\BPQ32 + WindowsLocalDebugger + 2022-05-19T07:28:47.9186341Z @@ -8,15 +15,15 @@ 2022-05-19T07:28:58.9302359Z - 2023-08-17T19:39:18.4928536Z + 2023-10-09T09:12:25.3524292Z - 2023-08-17T19:39:18.5988572Z + 2023-10-09T09:12:25.6116327Z - 2023-08-17T19:39:18.2968480Z + 2023-10-09T09:12:25.0706672Z - 2023-08-17T19:39:18.3758517Z + 2023-10-09T09:12:25.2024312Z \ No newline at end of file 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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�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/enc_temp_folder/5aa81bc33ffd093ff53c1c62422a77/QtTermTCP.h b/enc_temp_folder/5aa81bc33ffd093ff53c1c62422a77/QtTermTCP.h new file mode 100644 index 0000000..7b08e46 --- /dev/null +++ b/enc_temp_folder/5aa81bc33ffd093ff53c1c62422a77/QtTermTCP.h @@ -0,0 +1,297 @@ +#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 + +#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 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 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 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/enc_temp_folder/7a76876058568f76dfb2539c1a2c2b/TermTCPCommon.cpp b/enc_temp_folder/7a76876058568f76dfb2539c1a2c2b/TermTCPCommon.cpp new file mode 100644 index 0000000..113602a --- /dev/null +++ b/enc_temp_folder/7a76876058568f76dfb2539c1a2c2b/TermTCPCommon.cpp @@ -0,0 +1,1197 @@ +#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; + +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; +} + +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; + + _strlwr(KeyWordFile); // Compares are case-insensitive + + 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 = 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)) + { + myBeep(&AlertWAV); + 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 < 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 + + 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/enc_temp_folder/9d787c8a093194efc45ae72cda5d644/TermTCPCommon.cpp b/enc_temp_folder/9d787c8a093194efc45ae72cda5d644/TermTCPCommon.cpp new file mode 100644 index 0000000..440bfa5 --- /dev/null +++ b/enc_temp_folder/9d787c8a093194efc45ae72cda5d644/TermTCPCommon.cpp @@ -0,0 +1,1197 @@ +#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; + +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; +} + +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; + + _strlwr(KeyWordFile); // Compares are case-insensitive + + 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 = 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)) + { + myBeep(&AlertWAV); + 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 < 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 + + 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/makeit b/makeit index bdf2910..0d57f8d 100644 --- a/makeit +++ b/makeit @@ -1,10 +1,10 @@ -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 ./ qmake make -j4 diff --git a/release/moc_predefs.h b/release/moc_predefs.h index 4c9c0c7..b1c49a8 100644 --- a/release/moc_predefs.h +++ b/release/moc_predefs.h @@ -1,7 +1,7 @@ #define _MSC_EXTENSIONS #define _INTEGRAL_MAX_BITS 64 #define _MSC_VER 1916 -#define _MSC_FULL_VER 191627043 +#define _MSC_FULL_VER 191627045 #define _MSC_BUILD 0 #define _WIN32 #define _M_IX86 600