// Program to start or stop a Software TNC on a remote host // Should work with ARDOP, WINMOR or VARA // Version 1. 0. 0. 1 June 2013 // Version 1. 0. 2. 1 April 2018 // Updated to support running programs with command line parameters // Add option to KILL by program name // Version 1. 0. 3. 1 April 2019 // Add remote rigcontrol feature // Version 1. 0. 3. 2 Feb 2020 // Fix Rigcontol // Version 1. 0. 3. 3 Dec 2020 // Set working directory when starting TNC // Version 1. 0. 3. 4 Jan 2021 // Add trace window #define _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_DEPRECATE #include #include #include #include #include #include #define BPQICON 400 #define WSA_READ WM_USER + 1 HINSTANCE hInst; char AppName[] = "WinmorControl"; char Title[80] = "WinmorControl"; // Foward declarations of functions included in this code module: ATOM MyRegisterClass(CONST WNDCLASS*); BOOL InitApplication(HINSTANCE); BOOL InitInstance(HINSTANCE, int); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK About(HWND, UINT, WPARAM, LPARAM); VOID WINAPI CompletedReadRoutine(DWORD dwErr, DWORD cbBytesRead, LPOVERLAPPED lpOverLap); VOID WINAPI txCompletedReadRoutine(DWORD dwErr, DWORD cbBytesRead, LPOVERLAPPED lpOverLap); VOID CATThread(); int TimerHandle = 0; LOGFONT LFTTYFONT ; HFONT hFont; struct sockaddr_in sinx; struct sockaddr rx; SOCKET sock; int udpport = 8500; int addrlen = sizeof(struct sockaddr_in); BOOL MinimizetoTray=FALSE; VOID __cdecl Debugprintf(const char * format, ...) { char Mess[10000]; va_list(arglist); va_start(arglist, format); vsprintf(Mess, format, arglist); strcat(Mess, "\r\n"); OutputDebugString(Mess); return; } char * strlop(char * buf, char delim) { // Terminate buf at delim, and return rest of string char * ptr; if (buf == NULL) return NULL; // Protect ptr = strchr(buf, delim); if (ptr == NULL) return NULL; *(ptr)++ = 0; return ptr; } BOOL KillOldTNC(char * Path) { HANDLE hProc; char ExeName[256] = ""; DWORD Pid = 0; DWORD Processes[1024], Needed, Count; unsigned int i; if (!EnumProcesses(Processes, sizeof(Processes), &Needed)) return FALSE; // Calculate how many process identifiers were returned. Count = Needed / sizeof(DWORD); for (i = 0; i < Count; i++) { if (Processes[i] != 0) { hProc = OpenProcess(PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, Processes[i]); if (hProc) { GetModuleFileNameEx(hProc, 0, ExeName, 255); if (_memicmp(ExeName, Path, strlen(ExeName)) == 0) { Debugprintf("Killing Pid %d %s", Processes[i], ExeName); TerminateProcess(hProc, 0); CloseHandle(hProc); return TRUE; } CloseHandle(hProc); } } } return FALSE; } KillTNC(int PID) { HANDLE hProc = OpenProcess(PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, PID); Debugprintf("KillTNC Called Pid %d", PID); if (hProc) { TerminateProcess(hProc, 0); CloseHandle(hProc); } return 0; } RestartTNC(char * Path) { STARTUPINFO SInfo; // pointer to STARTUPINFO PROCESS_INFORMATION PInfo; // pointer to PROCESS_INFORMATION int i, n = 0; char workingDirectory[256]; SInfo.cb=sizeof(SInfo); SInfo.lpReserved=NULL; SInfo.lpDesktop=NULL; SInfo.lpTitle=NULL; SInfo.dwFlags=0; SInfo.cbReserved2=0; SInfo.lpReserved2=NULL; Debugprintf("RestartTNC Called for %s", Path); strcpy(workingDirectory, Path); i = strlen(Path); while (i--) { if (workingDirectory[i] == '\\' || workingDirectory[i] == '/') { workingDirectory[i] = 0; break; } } while (KillOldTNC(Path) && n++ < 100) { Sleep(100); } if (CreateProcess(NULL, Path, NULL, NULL, FALSE,0 ,NULL ,workingDirectory, &SInfo, &PInfo)) Debugprintf("Restart TNC OK"); else Debugprintf("Restart TNC Failed %d ", GetLastError()); } char * RigPort = NULL; char * RigSpeed = NULL; HANDLE RigHandle = 0; int RigType = 0; // Flag for possible RTS/DTR #define RTS 1 #define DTR 2 int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { MSG msg; if (lpCmdLine[0]) { // Port Name and Speed for Remote CAT RigPort = _strdup(lpCmdLine); RigSpeed = strlop(RigPort, ':'); } if (!InitApplication(hInstance)) return (FALSE); if (!InitInstance(hInstance, nCmdShow)) return (FALSE); // Main message loop: while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } KillTimer(NULL, TimerHandle); return (msg.wParam); } // // // FUNCTION: InitApplication(HANDLE) // // PURPOSE: Initializes window data and registers window class // // COMMENTS: // // In this function, we initialize a window class by filling out a data // structure of type WNDCLASS and calling either RegisterClass or // the internal MyRegisterClass. // BOOL InitApplication(HINSTANCE hInstance) { WNDCLASS wc; // Fill in window class structure with parameters that describe // the main window. wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC)WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon (hInstance, MAKEINTRESOURCE(BPQICON)); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); // wc.lpszMenuName = MAKEINTRESOURCE(BPQMENU) ; wc.lpszClassName = AppName; // Register the window class and return success/failure code. return RegisterClass(&wc); } // // FUNCTION: InitInstance(HANDLE, int) // // PURPOSE: Saves instance handle and creates main window // // COMMENTS: // // In this function, we save the instance handle in a global variable and // create and display the main program window. // HFONT FAR PASCAL MyCreateFont( void ) { CHOOSEFONT cf; LOGFONT lf; HFONT hfont; // Initialize members of the CHOOSEFONT structure. cf.lStructSize = sizeof(CHOOSEFONT); cf.hwndOwner = (HWND)NULL; cf.hDC = (HDC)NULL; cf.lpLogFont = &lf; cf.iPointSize = 0; cf.Flags = CF_SCREENFONTS | CF_FIXEDPITCHONLY; cf.rgbColors = RGB(0,0,0); cf.lCustData = 0L; cf.lpfnHook = (LPCFHOOKPROC)NULL; cf.lpTemplateName = (LPSTR)NULL; cf.hInstance = (HINSTANCE) NULL; cf.lpszStyle = (LPSTR)NULL; cf.nFontType = SCREEN_FONTTYPE; cf.nSizeMin = 0; cf.nSizeMax = 0; // Display the CHOOSEFONT common-dialog box. ChooseFont(&cf); // Create a logical font based on the user's // selection and return a handle identifying // that font. hfont = CreateFontIndirect(cf.lpLogFont); return (hfont); } VOID COMSetDTR(HANDLE fd) { EscapeCommFunction(fd, SETDTR); } VOID COMClearDTR(HANDLE fd) { EscapeCommFunction(fd, CLRDTR); } VOID COMSetRTS(HANDLE fd) { EscapeCommFunction(fd, SETRTS); } VOID COMClearRTS(HANDLE fd) { EscapeCommFunction(fd, CLRRTS); } OVERLAPPED txOverlapped; HANDLE txEvent; char RXBuffer[1024]; int RXLen; int ReadCOMBlock(HANDLE fd, char * Block, int MaxLength) { BOOL fReadStat ; COMSTAT ComStat ; DWORD dwErrorFlags; DWORD dwLength; BOOL ret; // only try to read number of bytes in queue ret = ClearCommError(fd, &dwErrorFlags, &ComStat); if (ret == 0) { int Err = GetLastError(); return 0; } dwLength = min((DWORD) MaxLength, ComStat.cbInQue); if (dwLength > 0) { fReadStat = ReadFile(fd, Block, dwLength, &dwLength, NULL) ; if (!fReadStat) { dwLength = 0 ; ClearCommError(fd, &dwErrorFlags, &ComStat ) ; } } return dwLength; } BOOL WriteCOMBlock(HANDLE fd, char * Block, int BytesToWrite) { BOOL fWriteStat; DWORD BytesWritten; DWORD ErrorFlags; COMSTAT ComStat; int Err, txLength; Err = WaitForSingleObject(txEvent, 1000); if (Err == WAIT_TIMEOUT) { Debugprintf("TX Event Wait Timout"); } else { ResetEvent(txEvent); Err = GetOverlappedResult(RigHandle, &txOverlapped, &txLength, FALSE); } memset(&txOverlapped, 0, sizeof(OVERLAPPED)); txOverlapped.hEvent = txEvent; fWriteStat = WriteFile(fd, Block, BytesToWrite, &BytesWritten, &txOverlapped); if (!fWriteStat) { Err = GetLastError(); if (Err != ERROR_IO_PENDING) { ClearCommError(fd, &ErrorFlags, &ComStat); return FALSE; } } return TRUE; } HANDLE OpenCOMPort(char * pPort, int speed, BOOL SetDTR, BOOL SetRTS, BOOL Quiet, int Stopbits) { char szPort[80]; BOOL fRetVal ; COMMTIMEOUTS CommTimeOuts ; char buf[100]; HANDLE fd; DCB dcb; sprintf( szPort, "\\\\.\\%s", pPort); // open COMM device fd = CreateFile( szPort, GENERIC_READ | GENERIC_WRITE, 0, // exclusive access NULL, // no security attrs OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL ); if (fd == (HANDLE) -1) { char Msg[80]; sprintf(Msg, "%s could not be opened - Error %d", pPort, GetLastError()); MessageBox(NULL, Msg, "WinmorControl", MB_OK); return FALSE; } // setup device buffers SetupComm(fd, 4096, 4096); // purge any information in the buffer PurgeComm(fd, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR ) ; // set up for overlapped I/O CommTimeOuts.ReadIntervalTimeout = 20; CommTimeOuts.ReadTotalTimeoutMultiplier = 0 ; CommTimeOuts.ReadTotalTimeoutConstant = 0 ; CommTimeOuts.WriteTotalTimeoutMultiplier = 0 ; // CommTimeOuts.WriteTotalTimeoutConstant = 0 ; CommTimeOuts.WriteTotalTimeoutConstant = 500 ; SetCommTimeouts(fd, &CommTimeOuts); dcb.DCBlength = sizeof(DCB); GetCommState(fd, &dcb); dcb.BaudRate = speed; dcb.ByteSize = 8; dcb.Parity = 0; dcb.StopBits = TWOSTOPBITS; dcb.StopBits = Stopbits; // setup hardware flow control dcb.fOutxDsrFlow = 0; dcb.fDtrControl = DTR_CONTROL_DISABLE ; dcb.fOutxCtsFlow = 0; dcb.fRtsControl = RTS_CONTROL_DISABLE ; // setup software flow control dcb.fInX = dcb.fOutX = 0; dcb.XonChar = 0; dcb.XoffChar = 0; dcb.XonLim = 100 ; dcb.XoffLim = 100 ; // other various settings dcb.fBinary = TRUE ; dcb.fParity = FALSE; fRetVal = SetCommState(fd, &dcb); if (fRetVal) { if (SetDTR) EscapeCommFunction(fd, SETDTR); else EscapeCommFunction(fd, CLRDTR); if (SetRTS) EscapeCommFunction(fd, SETRTS); else EscapeCommFunction(fd, CLRRTS); } else { sprintf(buf,"%s Setup Failed %d ", pPort, GetLastError()); OutputDebugString(buf); CloseHandle(fd); return 0; } txEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // Start Read Thread _beginthread(CATThread, 0, 0); return fd; } VOID CloseCOMPort(HANDLE fd) { SetCommMask(fd, 0); // drop DTR COMClearDTR(fd); // purge any outstanding reads/writes and close device handle PurgeComm(fd, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR ) ; CloseHandle(fd); } HWND hMonitor; BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { HWND hWnd; u_long param=1; BOOL bcopt=TRUE; char Msg[255]; int ret, err; WSADATA WsaData; // receives data from WSAStartup hInst = hInstance; // Store instance handle in our global variable WSAStartup(MAKEWORD(2, 0), &WsaData); hWnd = CreateWindow(AppName, Title, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, 420,275, NULL, NULL, hInstance, NULL); if (!hWnd) { return (FALSE); } // setup default font information LFTTYFONT.lfHeight = 12; LFTTYFONT.lfWidth = 8 ; LFTTYFONT.lfEscapement = 0 ; LFTTYFONT.lfOrientation = 0 ; LFTTYFONT.lfWeight = 0 ; LFTTYFONT.lfItalic = 0 ; LFTTYFONT.lfUnderline = 0 ; LFTTYFONT.lfStrikeOut = 0 ; LFTTYFONT.lfCharSet = OEM_CHARSET ; LFTTYFONT.lfOutPrecision = OUT_DEFAULT_PRECIS ; LFTTYFONT.lfClipPrecision = CLIP_DEFAULT_PRECIS ; LFTTYFONT.lfQuality = DEFAULT_QUALITY ; LFTTYFONT.lfPitchAndFamily = FIXED_PITCH | FF_MODERN ; lstrcpy( LFTTYFONT.lfFaceName, "Fixedsys" ) ; hFont = CreateFontIndirect(&LFTTYFONT) ; // hFont = MyCreateFont(); SetWindowText(hWnd,Title); hMonitor = CreateWindowEx(0, "LISTBOX", "", WS_CHILD | WS_VISIBLE | LBS_NOINTEGRALHEIGHT | LBS_DISABLENOSCROLL | WS_HSCROLL | WS_VSCROLL, 2,2,400,230, hWnd, NULL, hInstance, NULL); ShowWindow(hWnd, nCmdShow); sock = socket(AF_INET,SOCK_DGRAM,0); if (sock == INVALID_SOCKET) { err = WSAGetLastError(); sprintf(Msg, "Failed to create UDP socket - error code = %d", err); MessageBox(NULL, Msg, "WinmorControl", MB_OK); return FALSE; } if (WSAAsyncSelect(sock, hWnd, WSA_READ, FD_READ) > 0) { wsprintf(Msg, TEXT("WSAAsyncSelect failed Error %d"), WSAGetLastError()); MessageBox(hWnd, Msg, TEXT("WinmorControl"), MB_OK); closesocket(sock); return FALSE; } // ioctlsocket (sock, FIONBIO, ¶m); setsockopt (sock,SOL_SOCKET,SO_BROADCAST,(const char FAR *)&bcopt,4); sinx.sin_family = AF_INET; sinx.sin_addr.s_addr = INADDR_ANY; sinx.sin_port = htons(udpport); ret = bind(sock, (struct sockaddr *) &sinx, sizeof(sinx)); if (ret != 0) { // Bind Failed err = WSAGetLastError(); sprintf(Msg, "Bind Failed for UDP socket - error code = %d", err); MessageBox(NULL, Msg, "WinmorControl", MB_OK); return FALSE; } if (RigPort) { int Speed = 9600; if (RigSpeed) Speed = atoi(RigSpeed); RigHandle = OpenCOMPort(RigPort, Speed, FALSE, FALSE, FALSE, TWOSTOPBITS); if (RigHandle) { if (RigType == RTS) COMClearRTS(RigHandle); else COMSetRTS(RigHandle); if (RigType == DTR) COMClearDTR(RigHandle); else COMSetDTR(RigHandle); } else return FALSE; // Open Failed } TimerHandle = SetTimer(hWnd, 1, 100, NULL); return TRUE; } void Trace(char * Msg) { int index = SendMessage(hMonitor, LB_ADDSTRING, 0, (LPARAM)(LPCTSTR) Msg); if (index > 1200) do index=index=SendMessage(hMonitor, LB_DELETESTRING, 0, 0); while (index > 1000); if (index > -1) index=SendMessage(hMonitor, LB_SETCARETINDEX,(WPARAM) index, MAKELPARAM(FALSE, 0)); } LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int wmId, wmEvent; char Msg[256]; int len; switch (message) { case WSA_READ: // Notification on data socket len = recvfrom(sock, Msg, 256, 0, &rx, &addrlen); if (len <= 0) return 0; Msg[len] = 0; if (_memicmp(Msg, "REMOTE:", 7) == 0) { Trace(Msg); RestartTNC(&Msg[7]); } else if (_memicmp(Msg, "KILL ", 5) == 0) { Trace(Msg); KillTNC(atoi(&Msg[5])); } else if (_memicmp(Msg, "KILLBYNAME ", 11) == 0) { Trace(Msg); KillOldTNC(&Msg[11]); } else { // Anything else is Rig Control len = WriteCOMBlock(RigHandle, Msg, len); } break; case WM_SYSCOMMAND: wmId = LOWORD(wParam); // Remember, these are... wmEvent = HIWORD(wParam); // ...different for Win32! switch (wmId) { case SC_MINIMIZE: if (MinimizetoTray) return ShowWindow(hWnd, SW_HIDE); else return (DefWindowProc(hWnd, message, wParam, lParam)); break; default: return (DefWindowProc(hWnd, message, wParam, lParam)); } case WM_CLOSE: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, message, wParam, lParam)); } return (0); } BOOL EndCATThread = FALSE; VOID CATThread() { DWORD dwLength = 0; int Length, ret; HANDLE Event; OVERLAPPED Overlapped; EndCATThread = FALSE; Event = CreateEvent(NULL, TRUE, FALSE, NULL); memset(&Overlapped, 0, sizeof(OVERLAPPED)); Overlapped.hEvent = Event; ReadFile(RigHandle, RXBuffer, 1024, &RXLen, &Overlapped); while (EndCATThread == FALSE) { ret = WaitForSingleObject(Event, 10000); if (ret == WAIT_TIMEOUT) { if (EndCATThread) { CancelIo(RigHandle); CloseHandle(RigHandle); RigHandle = INVALID_HANDLE_VALUE; CloseHandle(Event); EndCATThread = FALSE; return; } continue; } ResetEvent(Event); ret = GetOverlappedResult(RigHandle, &Overlapped, &Length, FALSE); if (ret && Length) { // got something so send to BPQ sendto(sock, RXBuffer, Length, 0, &rx, sizeof(struct sockaddr)); } memset(&Overlapped, 0, sizeof(OVERLAPPED)); Overlapped.hEvent = Event; ReadFile(RigHandle, RXBuffer, 1024, &RXLen, &Overlapped); } }