windows-nt/Source/XPSP1/NT/net/rras/ras/ui/rasscrpt/terminal.c
2020-09-26 16:20:57 +08:00

2706 lines
68 KiB
C

//THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright 1993-1995 Microsoft Corporation. All Rights Reserved.
//
// MODULE: terminal.c
//
// PURPOSE: Terminal screen simulation
//
// PLATFORMS: Windows 95
//
// FUNCTIONS:
// TransferData()
// TerminalDlgWndProc()
// TerminalScreenWndProc()
// OnCommand()
// InitTerminalDlg()
// TerminateTerminal()
// GetInput()
// SendByte()
// AdjustTerminal()
// UpdateTerminalCaption()
// TerminalThread()
//
// SPECIAL INSTRUCTIONS: N/A
//
#include "proj.h" // includes common header files and global declarations
#include "rcids.h" // includes the resource definitions
#ifndef WINNT_RAS
//
// See scrpthlp.h for information on why this has been commented out.
//
#include "scrpthlp.h"// include context-sensitive help
#endif // WINNT_RAS
//****************************************************************************
// Constants Declaration
//****************************************************************************
#define MAXTITLE 32
#define MAXMESSAGE 256
#define WM_MODEMNOTIFY (WM_USER + 998)
#define WM_EOLFROMDEVICE (WM_USER + 999)
#define WM_PROCESSSCRIPT (WM_USER + 1000)
#ifndef WINNT_RAS
//
// The definitions below are overriden in nthdr2.h, and have been removed here
// to avoid multiple definitions
//
#define SIZE_ReceiveBuf 1024
#define SIZE_SendBuf 1
#endif // !WINNT_RAS
#define Y_MARGIN 4
#define Y_SMALL_MARGIN 2
#define X_SPACING 2
#define MIN_X 170
#define MIN_Y 80
#define TERMINAL_BK_COLOR (RGB( 0, 0, 0 ))
#define TERMINAL_FR_COLOR (RGB( 255, 255, 255 ))
#define MAXTERMLINE 24
#define READ_EVENT 0
#define STOP_EVENT 1
#define MAX_EVENT 2
#define SENDTIMEOUT 50
#define CE_DELIM 256
#define TRACE_MARK "->"
#define TRACE_UNMARK " "
#define INVALID_SCRIPT_LINE 0xFFFFFFFF
#define PROMPT_AT_COMPLETION 1
//****************************************************************************
// Type Definitions
//****************************************************************************
typedef struct tagFINDFMT
{
LPSTR pszFindFmt; // Allocated: formatted string to find
LPSTR pszBuf; // Optional pointer to buffer; may be NULL
UINT cbBuf;
DWORD dwFlags; // FFF_*
} FINDFMT;
DECLARE_STANDARD_TYPES(FINDFMT);
typedef struct tagTERMDLG {
HANDLE hport;
HANDLE hThread;
HANDLE hEvent[MAX_EVENT];
HWND hwnd;
PBYTE pbReceiveBuf; // circular buffer
PBYTE pbSendBuf;
UINT ibCurFind;
UINT ibCurRead;
UINT cbReceiveMax; // count of read bytes
HBRUSH hbrushScreenBackgroundE;
HBRUSH hbrushScreenBackgroundD;
HFONT hfontTerminal;
PSCANNER pscanner;
PMODULEDECL pmoduledecl;
ASTEXEC astexec;
SCRIPT script;
WNDPROC WndprocOldTerminalScreen;
BOOL fInputEnabled;
BOOL fStartRestored;
BOOL rgbDelim[CE_DELIM];
// The following fields are strictly for test screen
//
BOOL fContinue;
HWND hwndDbg;
DWORD iMarkLine;
} TERMDLG, *PTERMDLG, FAR* LPTERMDLG;
#define IS_TEST_SCRIPT(ptd) (ptd->script.uMode == TEST_MODE)
//****************************************************************************
// Function prototypes
//****************************************************************************
LRESULT FAR PASCAL TerminalDlgWndProc(HWND hwnd,
UINT wMsg,
WPARAM wParam,
LPARAM lParam );
LRESULT FAR PASCAL TerminalScreenWndProc(HWND hwnd,
UINT wMsg,
WPARAM wParam,
LPARAM lParam );
BOOL NEAR PASCAL OnCommand (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
LRESULT NEAR PASCAL InitTerminalDlg(HWND hwnd);
void NEAR PASCAL TerminateTerminal(HWND hwnd, UINT id);
BOOL NEAR PASCAL GetInput (HWND hwnd);
VOID NEAR PASCAL AdjustTerminal (HWND hwnd, int wWidth, int wHeight);
void NEAR PASCAL UpdateTerminalCaption(PTERMDLG ptd, UINT ids);
void WINAPI TerminalThread (PTERMDLG pTerminaldialog);
void PRIVATE Terminal_NextCommand(PTERMDLG ptd, HWND hwnd);
// The following functions are used by test screen only
//
BOOL NEAR PASCAL DisplayScript (PTERMDLG ptd);
LRESULT FAR PASCAL DbgScriptDlgProc(HWND hwnd,
UINT wMsg,
WPARAM wParam,
LPARAM lParam );
BOOL NEAR PASCAL InitDebugWindow (HWND hwnd);
void NEAR PASCAL TrackScriptLine(PTERMDLG ptd, DWORD iLine);
/*----------------------------------------------------------------------------
** Terminal dialog routines
**----------------------------------------------------------------------------
*/
BOOL PUBLIC TransferData(
HWND hwnd,
HANDLE hComm,
PSESS_CONFIGURATION_INFO psci)
/* Executes the Terminal dialog including error handling. 'hwndOwner' is
** the handle of the parent window. 'hport' is the open RAS Manager port
** handle to talk on. 'msgidTitle' is the string ID for the Terminal window
** caption.
**
** Returns true if successful, false otherwise.
*/
{
PTERMDLG ptd;
COMMTIMEOUTS commtimeout;
DWORD id;
int i;
int iRet;
// Allocate the terminal buffer
//
if ((ptd = (PTERMDLG)LocalAlloc(LPTR, sizeof(*ptd)))
== NULL)
return FALSE;
if ((ptd->pbReceiveBuf = (PBYTE)LocalAlloc(LPTR,
SIZE_ReceiveBuf+ SIZE_SendBuf))
== NULL)
{
LocalFree((HLOCAL)ptd);
return FALSE;
};
ptd->pbSendBuf = ptd->pbReceiveBuf + SIZE_ReceiveBuf;
ptd->ibCurFind = 0;
ptd->ibCurRead = 0;
ptd->cbReceiveMax = 0;
ptd->fInputEnabled= FALSE;
ptd->fStartRestored = FALSE;
ptd->iMarkLine = 0;
// Initialize the terminal buffer
//
ptd->hport = hComm;
ptd->hbrushScreenBackgroundE = (HBRUSH)GetStockObject( BLACK_BRUSH );
ptd->hbrushScreenBackgroundD = (HBRUSH)GetStockObject( WHITE_BRUSH );
ptd->hfontTerminal = (HFONT)GetStockObject( SYSTEM_FIXED_FONT );
// Create the scanner
if (RFAILED(Scanner_Create(&ptd->pscanner, psci)))
{
LocalFree((HLOCAL)ptd->pbReceiveBuf);
LocalFree((HLOCAL)ptd);
return FALSE;
};
// Is there a script for this connection?
if (GetScriptInfo(psci->szEntryName, &ptd->script))
{
// Yes; open the script file for scanning
RES res = Scanner_OpenScript(ptd->pscanner, ptd->script.szPath);
if (RES_E_FAIL == res)
{
MsgBox(g_hinst,
hwnd,
MAKEINTRESOURCE(IDS_ERR_ScriptNotFound),
MAKEINTRESOURCE(IDS_CAP_Script),
NULL,
MB_WARNING,
ptd->script.szPath);
ptd->fInputEnabled= TRUE;
*ptd->script.szPath = '\0';
ptd->script.uMode = NORMAL_MODE;
}
else if (RFAILED(res))
{
Scanner_Destroy(ptd->pscanner);
LocalFree((HLOCAL)ptd->pbReceiveBuf);
LocalFree((HLOCAL)ptd);
return FALSE;
}
else
{
res = Astexec_Init(&ptd->astexec, hComm, psci,
Scanner_GetStxerrHandle(ptd->pscanner));
if (RSUCCEEDED(res))
{
// Parse the script
res = ModuleDecl_Parse(&ptd->pmoduledecl, ptd->pscanner, ptd->astexec.pstSystem);
if (RSUCCEEDED(res))
{
res = ModuleDecl_Codegen(ptd->pmoduledecl, &ptd->astexec);
}
if (RFAILED(res))
{
Stxerr_ShowErrors(Scanner_GetStxerrHandle(ptd->pscanner), hwnd);
}
}
}
}
else
{
ptd->fInputEnabled= TRUE;
ptd->script.uMode = NORMAL_MODE;
ptd->fStartRestored = TRUE;
};
// Set comm timeout
//
commtimeout.ReadIntervalTimeout = MAXDWORD;
commtimeout.ReadTotalTimeoutMultiplier = 0;
commtimeout.ReadTotalTimeoutConstant = 0;
commtimeout.WriteTotalTimeoutMultiplier= SENDTIMEOUT;
commtimeout.WriteTotalTimeoutConstant = 0;
SetCommTimeouts(hComm, &commtimeout);
// Start receiving from the port
//
SetCommMask(hComm, EV_RXCHAR);
// Create read thread and the synchronization objects
for (i = 0; i < MAX_EVENT; i++)
{
ptd->hEvent[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
};
ptd->hThread = CreateThread(NULL, 0,
(LPTHREAD_START_ROUTINE) TerminalThread,
ptd, 0, &id);
// Create the terminal window
#ifdef MODAL_DIALOG
iRet = DialogBoxParam(g_hinst,
MAKEINTRESOURCE(IDD_TERMINALDLG),
hwnd,
(DLGPROC)TerminalDlgWndProc,
(LPARAM)(LPTERMDLG)ptd);
#else
if (CreateDialogParam(g_hinst,
MAKEINTRESOURCE(IDD_TERMINALDLG),
hwnd,
(DLGPROC)TerminalDlgWndProc,
(LPARAM)(LPTERMDLG)ptd))
{
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
if ((!IsDialogMessage(ptd->hwnd, &msg)) &&
((ptd->hwndDbg == NULL) || !IsDialogMessage(ptd->hwndDbg, &msg)))
{
TranslateMessage(&msg); /* Translates virtual key codes */
DispatchMessage(&msg); /* Dispatches message to window */
};
};
iRet = (int)msg.wParam;
DestroyWindow(ptd->hwnd);
}
else
{
iRet = IDCANCEL;
};
#endif // MODAL_DIALOG
// The terminal dialog was terminated, free resources
//
SetEvent(ptd->hEvent[STOP_EVENT]);
SetCommMask(hComm, 0);
DEBUG_MSG (TF_ALWAYS, "Set stop event and cleared comm mask.");
WaitForSingleObject(ptd->hThread, INFINITE);
DEBUG_MSG (TF_ALWAYS, "Read thread was terminated.");
for (i = 0; i < MAX_EVENT; i++)
{
CloseHandle(ptd->hEvent[i]);
};
CloseHandle(ptd->hThread);
Decl_Delete((PDECL)ptd->pmoduledecl);
Astexec_Destroy(&ptd->astexec);
Scanner_Destroy(ptd->pscanner);
LocalFree((HLOCAL)ptd->pbReceiveBuf);
LocalFree((HLOCAL)ptd);
return (iRet == IDOK);
}
/*----------------------------------------------------------------------------
** Terminal Window Procedure
**----------------------------------------------------------------------------
*/
void PRIVATE PostProcessScript(
HWND hwnd)
{
MSG msg;
if (!PeekMessage(&msg, hwnd, WM_PROCESSSCRIPT, WM_PROCESSSCRIPT,
PM_NOREMOVE))
{
PostMessage(hwnd, WM_PROCESSSCRIPT, 0, 0);
}
}
/*----------------------------------------------------------
Purpose: Execute next command in the script
Returns: --
Cond: --
*/
void PRIVATE Terminal_NextCommand(
PTERMDLG ptd,
HWND hwnd)
{
if (RES_OK == Astexec_Next(&ptd->astexec))
{
if (!Astexec_IsReadPending(&ptd->astexec) &&
!Astexec_IsPaused(&ptd->astexec))
{
HWND hwndNotify;
if (IS_TEST_SCRIPT(ptd))
{
// Do not start processing the next one yet
//
ptd->fContinue = FALSE;
hwndNotify = ptd->hwndDbg;
}
else
{
hwndNotify = hwnd;
};
// Process the next command
//
PostProcessScript(hwndNotify);
};
};
};
LRESULT FAR PASCAL TerminalDlgWndProc(HWND hwnd,
UINT wMsg,
WPARAM wParam,
LPARAM lParam )
{
PTERMDLG ptd;
switch (wMsg)
{
case WM_INITDIALOG:
ptd = (PTERMDLG)lParam;
DEBUG_MSG (TF_ALWAYS, "ptd = %x", ptd);
SetWindowLongPtr(hwnd, DWLP_USER, (LONG_PTR)lParam);
ptd->hwnd = hwnd;
Astexec_SetHwnd(&ptd->astexec, hwnd);
return (InitTerminalDlg(hwnd));
case WM_CTLCOLOREDIT:
// Adjust the screen window only
//
if ((HWND)lParam == GetDlgItem(hwnd, CID_T_EB_SCREEN))
{
HBRUSH hBrush;
COLORREF crColorBk, crColorTxt;
ptd = (PTERMDLG)GetWindowLongPtr(hwnd, DWLP_USER);
if (ptd->fInputEnabled)
{
hBrush = ptd->hbrushScreenBackgroundE;
crColorBk = TERMINAL_BK_COLOR;
crColorTxt = TERMINAL_FR_COLOR;
}
else
{
hBrush = ptd->hbrushScreenBackgroundD;
crColorBk = TERMINAL_FR_COLOR;
crColorTxt = TERMINAL_BK_COLOR;
};
/* Set terminal screen colors to TTY-ish green on black.
*/
if (hBrush)
{
SetBkColor( (HDC)wParam, crColorBk );
SetTextColor((HDC)wParam, crColorTxt );
return (LRESULT)hBrush;
}
};
break;
case WM_MODEMNOTIFY:
ptd = (PTERMDLG)GetWindowLongPtr(hwnd, DWLP_USER);
TRACE_MSG(TF_BUFFER, "Received WM_MODEMNOTIFY");
GetInput(hwnd);
// Kick the script processing
//
PostProcessScript(hwnd);
return TRUE;
case WM_PROCESSSCRIPT:
{
ptd = (PTERMDLG)GetWindowLongPtr(hwnd, DWLP_USER);
TRACE_MSG(TF_BUFFER, "Received WM_PROCESSSCRIPT");
if (!ptd->fContinue)
{
// We are not allowed to process a new command yet
//
return TRUE;
};
Terminal_NextCommand(ptd, hwnd);
// If we are done or halt, show the status
//
if (Astexec_IsDone(&ptd->astexec) ||
Astexec_IsHalted(&ptd->astexec))
{
BOOL bHalted = Astexec_IsHalted(&ptd->astexec);
// Update the title
//
UpdateTerminalCaption(ptd, bHalted ? IDS_HALT : IDS_COMPLETE);
// If the script completes successfully, continue the connection
//
if (!bHalted)
{
// Terminate the script successfully
//
TerminateTerminal(hwnd, IDOK);
}
else
{
// We are halted, need the user's attention.
//
if (IsIconic(hwnd))
{
ShowWindow(hwnd, SW_RESTORE);
};
SetForegroundWindow(hwnd);
};
};
return TRUE;
}
case WM_TIMER: {
HWND hwndNotify;
ptd = (PTERMDLG)GetWindowLongPtr(hwnd, DWLP_USER);
TRACE_MSG(TF_GENERAL, "Killing timer");
Astexec_ClearPause(&ptd->astexec);
KillTimer(hwnd, TIMER_DELAY);
// Did we time out on a 'wait..until' statement?
if (Astexec_IsWaitUntil(&ptd->astexec))
{
// Yes; we need to finish processing the 'wait' statement
// before we step to the next command
Astexec_SetStopWaiting(&ptd->astexec);
Astexec_ClearWaitUntil(&ptd->astexec);
hwndNotify = hwnd;
ASSERT(TRUE == ptd->fContinue);
}
else
{
if (IS_TEST_SCRIPT(ptd))
{
// Do not start processing the next one yet
//
ptd->fContinue = FALSE;
hwndNotify = ptd->hwndDbg;
}
else
{
hwndNotify = hwnd;
ASSERT(TRUE == ptd->fContinue);
}
}
PostProcessScript(hwndNotify);
}
return TRUE;
case WM_COMMAND:
// Handle the control activities
//
return OnCommand(hwnd, wMsg, wParam, lParam);
case WM_DESTROY:
ptd = (PTERMDLG)GetWindowLongPtr(hwnd, DWLP_USER);
SetWindowLongPtr( GetDlgItem(hwnd, CID_T_EB_SCREEN), GWLP_WNDPROC,
(ULONG_PTR)ptd->WndprocOldTerminalScreen );
break;
case WM_SIZE:
AdjustTerminal(hwnd, (int)LOWORD(lParam), (int)HIWORD(lParam));
break;
case WM_GETMINMAXINFO:
{
MINMAXINFO FAR* lpMinMaxInfo = (MINMAXINFO FAR*)lParam;
DWORD dwUnit = GetDialogBaseUnits();
lpMinMaxInfo->ptMinTrackSize.x = (MIN_X*LOWORD(dwUnit))/4;
lpMinMaxInfo->ptMinTrackSize.y = (MIN_Y*LOWORD(dwUnit))/4;
break;
};
case WM_HELP:
case WM_CONTEXTMENU:
ContextHelp(gaTerminal, wMsg, wParam, lParam);
break;
};
return 0;
}
/*----------------------------------------------------------------------------
** Terminal Screen Subclasses Window Procedure
**----------------------------------------------------------------------------
*/
LRESULT FAR PASCAL TerminalScreenWndProc(HWND hwnd,
UINT wMsg,
WPARAM wParam,
LPARAM lParam )
{
HWND hwndParent;
PTERMDLG pTerminaldialog;
hwndParent = GetParent(hwnd);
pTerminaldialog = (PTERMDLG)GetWindowLongPtr(hwndParent, DWLP_USER);
if (wMsg == WM_EOLFROMDEVICE)
{
/* Remove the first line if the next line exceeds the maximum line
*/
if (SendMessage(hwnd, EM_GETLINECOUNT, 0, 0L) == MAXTERMLINE)
{
SendMessage(hwnd, EM_SETSEL, 0,
SendMessage(hwnd, EM_LINEINDEX, 1, 0L));
SendMessage(hwnd, EM_REPLACESEL, 0, (LPARAM)(LPSTR)"");
SendMessage(hwnd, EM_SETSEL, 32767, 32767);
SendMessage(hwnd, EM_SCROLLCARET, 0, 0);
};
/* An end-of-line in the device input was received. Send a linefeed
** character to the window.
*/
wParam = '\n';
wMsg = WM_CHAR;
}
else
{
BOOL fCtrlKeyDown = (GetKeyState( VK_CONTROL ) < 0);
BOOL fShiftKeyDown = (GetKeyState( VK_SHIFT ) < 0);
if (wMsg == WM_KEYDOWN)
{
/* The key was pressed by the user.
*/
if (wParam == VK_RETURN && !fCtrlKeyDown && !fShiftKeyDown)
{
/* Enter key pressed without Shift or Ctrl is discarded. This
** prevents Enter from being interpreted as "press default
** button" when pressed in the edit box.
*/
return 0;
}
if (fCtrlKeyDown && wParam == VK_TAB)
{
/* Ctrl+Tab pressed. Send a tab character to the device.
** Pass tab thru to let the edit box handle the visuals.
** Ctrl+Tab doesn't generate a WM_CHAR.
*/
if (pTerminaldialog->fInputEnabled)
{
SendByte(hwndParent, (BYTE)VK_TAB);
};
}
if (GetKeyState( VK_MENU ) < 0)
{
return (CallWindowProc(pTerminaldialog->WndprocOldTerminalScreen, hwnd, wMsg, wParam, lParam ));
};
}
else if (wMsg == WM_CHAR)
{
/* The character was typed by the user.
*/
if (wParam == VK_TAB)
{
/* Ignore tabs...Windows sends this message when Tab (leave
** field) is pressed but not when Ctrl+Tab (insert a TAB
** character) is pressed...weird.
*/
return 0;
}
if (pTerminaldialog->fInputEnabled)
{
SendByte(hwndParent, (BYTE)wParam);
};
return 0;
}
}
/* Call the previous window procedure for everything else.
*/
return (CallWindowProc(pTerminaldialog->WndprocOldTerminalScreen, hwnd, wMsg, wParam, lParam ));
}
/*----------------------------------------------------------------------------
** Terminal Window's Control Handler
**----------------------------------------------------------------------------
*/
BOOL NEAR PASCAL OnCommand (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (LOWORD(wParam))
{
case CID_T_EB_SCREEN:
{
switch (HIWORD(wParam))
{
case EN_SETFOCUS:
{
/* Turn off the default button whenever the terminal
** window has the focus. Pressing [Return] in the
** terminal acts like a normal terminal.
*/
SendDlgItemMessage(hwnd, CID_T_PB_ENTER, BM_SETSTYLE,
(WPARAM)BS_DEFPUSHBUTTON, TRUE);
/* Don't select the entire string on entry.
*/
SendDlgItemMessage(hwnd, CID_T_EB_SCREEN, EM_SETSEL,
32767, 32767);
SendMessage(hwnd, EM_SCROLLCARET, 0, 0);
break;
};
};
break;
};
case CID_T_CB_INPUT:
{
PTERMDLG ptd;
ptd = (PTERMDLG)GetWindowLongPtr(hwnd, DWLP_USER);
ptd->fInputEnabled = IsDlgButtonChecked(hwnd, CID_T_CB_INPUT);
InvalidateRect(hwnd, NULL, FALSE);
SetFocus(GetDlgItem(hwnd, CID_T_EB_SCREEN));
break;
}
case IDOK:
case IDCANCEL:
TerminateTerminal(hwnd, LOWORD(wParam));
break;
};
return 0;
}
/*----------------------------------------------------------------------------
** Initialize Terminal window
**----------------------------------------------------------------------------
*/
LRESULT NEAR PASCAL InitTerminalDlg(HWND hwnd)
{
HWND hwndScrn;
RECT rect;
PTERMDLG ptd;
WINDOWPLACEMENT wp;
BOOL fRet;
ptd = (PTERMDLG)GetWindowLongPtr(hwnd, DWLP_USER);
// Install subclassed WndProcs.
//
hwndScrn = GetDlgItem(hwnd, CID_T_EB_SCREEN);
ptd->WndprocOldTerminalScreen =
(WNDPROC)SetWindowLongPtr( hwndScrn, GWLP_WNDPROC,
(ULONG_PTR)TerminalScreenWndProc );
// Set the terminal screen font
//
SendMessage(hwndScrn, WM_SETFONT, (WPARAM)ptd->hfontTerminal,
0L);
// Get the recorded window placement
//
if ((fRet = GetSetTerminalPlacement(ptd->pscanner->psci->szEntryName,
&wp, TRUE)) &&
(wp.length >= sizeof(wp)))
{
// We have one, set it
//
SetWindowPlacement(hwnd, &wp);
}
else
{
// If nothing was specified at all, default to minimized
// otherwise use the state set by scripter
//
if (!fRet)
{
wp.showCmd = SW_SHOWMINNOACTIVE;
};
// Start with minimized window
//
ShowWindow(hwnd, wp.showCmd);
};
// Adjust the dimension
//
GetClientRect(hwnd, &rect);
AdjustTerminal(hwnd, rect.right-rect.left, rect.bottom-rect.top);
// Adjust window activation
//
if (!IsIconic(hwnd))
{
SetForegroundWindow(hwnd);
}
else
{
CheckDlgButton(hwnd, CID_T_CB_MIN, BST_CHECKED);
// if we are in debug mode, just bring it up
//
if (IS_TEST_SCRIPT(ptd) || ptd->fStartRestored)
{
ShowWindow(hwnd, SW_NORMAL);
SetForegroundWindow(hwnd);
};
};
// Initialize the input enable
//
CheckDlgButton(hwnd, CID_T_CB_INPUT,
ptd->fInputEnabled ? BST_CHECKED : BST_UNCHECKED);
// Set the window icon
//
SendMessage(hwnd, WM_SETICON, TRUE,
(LPARAM)LoadIcon(g_hinst, MAKEINTRESOURCE(IDI_SCRIPT)));
// Set the input focus to the screen
//
UpdateTerminalCaption(ptd, IDS_RUN);
// Display the script window
//
if (IS_TEST_SCRIPT(ptd))
{
// Do not start until the debug window says so
//
ptd->fContinue = FALSE;
// Start the debug window
//
if (!DisplayScript(ptd))
{
// Cannot start the debug window, switch to normal mode
//
ptd->fContinue = TRUE;
ptd->script.uMode = NORMAL_MODE;
};
}
else
{
// Start immediately
//
ptd->fContinue = TRUE;
ptd->hwndDbg = NULL;
};
// Start receiving from the port
//
PostMessage(hwnd, WM_MODEMNOTIFY, 0, 0);
return 0;
}
/*----------------------------------------------------------------------------
** Terminal window termination
**----------------------------------------------------------------------------
*/
void NEAR PASCAL TerminateTerminal(HWND hwnd, UINT id)
{
PTERMDLG ptd;
WINDOWPLACEMENT wp;
ptd = (PTERMDLG)GetWindowLongPtr(hwnd, DWLP_USER);
// Get the current window placement and record it
//
wp.length = sizeof(wp);
if (GetWindowPlacement(hwnd, &wp))
{
// If user specifies start-minimized, remember it
//
if (IsDlgButtonChecked(hwnd, CID_T_CB_MIN) == BST_CHECKED)
{
wp.showCmd = SW_SHOWMINNOACTIVE;
};
// Recorded the window placement
//
GetSetTerminalPlacement(ptd->pscanner->psci->szEntryName, &wp, FALSE);
};
if (IS_TEST_SCRIPT(ptd))
{
// Destroy the script window here
//
DestroyWindow(ptd->hwndDbg);
};
// Terminate the window
//
#ifdef MODAL_DIALOG
EndDialog(hwnd, id);
#else
PostQuitMessage(id);
#endif // MODAL_DIALOG
return;
}
/*----------------------------------------------------------------------------
** Terminal Input Handler
**----------------------------------------------------------------------------
*/
#ifdef DEBUG
/*----------------------------------------------------------
Purpose: Dumps the read buffer
Returns:
Cond: --
*/
void PRIVATE DumpBuffer(
PTERMDLG ptd)
{
#define IS_PRINTABLE(ch) InRange(ch, 32, 126)
if (IsFlagSet(g_dwDumpFlags, DF_READBUFFER))
{
UINT ib;
UINT cb;
UINT cbMax = ptd->cbReceiveMax;
char szBuf[SIZE_ReceiveBuf+1];
LPSTR psz = szBuf;
ASSERT(ptd->ibCurRead >= ptd->ibCurFind);
ASSERT(SIZE_ReceiveBuf > ptd->ibCurFind);
ASSERT(SIZE_ReceiveBuf > ptd->ibCurRead);
ASSERT(SIZE_ReceiveBuf >= cbMax);
*szBuf = 0;
for (ib = ptd->ibCurFind, cb = 0;
cb < cbMax;
ib = (ib + 1) % SIZE_ReceiveBuf, cb++)
{
char ch = ptd->pbReceiveBuf[ib];
if (IS_PRINTABLE(ch))
*psz++ = ch;
else
*psz++ = '.';
}
*psz = 0; // add null terminator
TRACE_MSG(TF_ALWAYS, "Read buffer: {%s}", (LPSTR)szBuf);
}
}
#endif // DEBUG
/*----------------------------------------------------------
Purpose: Creates a find format handle. This function should
be called to get a handle to use with FindFormat.
Returns: RES_OK
RES_E_OUTOFMEMORY
Cond: --
*/
RES PUBLIC CreateFindFormat(
PHANDLE phFindFmt)
{
RES res = RES_OK;
HSA hsa;
ASSERT(phFindFmt);
if ( !SACreate(&hsa, sizeof(FINDFMT), 8) )
res = RES_E_OUTOFMEMORY;
*phFindFmt = (HANDLE)hsa;
return res;
}
/*----------------------------------------------------------
Purpose: Adds a formatted search string to the list.
Returns: RES_OK
Cond: --
*/
RES PUBLIC AddFindFormat(
HANDLE hFindFmt,
LPCSTR pszFindFmt,
DWORD dwFlags, // FFF_*
LPSTR pszBuf, // May be NULL
DWORD cbBuf)
{
RES res = RES_OK;
FINDFMT ff;
HSA hsa = (HSA)hFindFmt;
ZeroInit(&ff, FINDFMT);
if (GSetString(&ff.pszFindFmt, pszFindFmt))
{
ff.pszBuf = pszBuf;
ff.cbBuf = cbBuf;
ff.dwFlags = dwFlags;
if ( !SAInsertItem(hsa, SA_APPEND, &ff) )
res = RES_E_OUTOFMEMORY;
}
else
res = RES_E_OUTOFMEMORY;
return res;
}
/*----------------------------------------------------------
Purpose: Free a find format item
Returns: --
Cond: --
*/
void CALLBACK FreeSAFindFormat(
PVOID pv,
LPARAM lParam)
{
PFINDFMT pff = (PFINDFMT)pv;
if (pff->pszFindFmt)
GSetString(&pff->pszFindFmt, NULL); // free
}
/*----------------------------------------------------------
Purpose: Destroys a find format handle.
Returns: RES_OK
RES_E_INVALIDPARAM
Cond: --
*/
RES PUBLIC DestroyFindFormat(
HANDLE hFindFmt)
{
RES res;
HSA hsa = (HSA)hFindFmt;
if (hsa)
{
SADestroyEx(hsa, FreeSAFindFormat, 0);
res = RES_OK;
}
else
res = RES_E_INVALIDPARAM;
return res;
}
BOOL PRIVATE ChrCmp(WORD w1, WORD wMatch);
BOOL PRIVATE ChrCmpI(WORD w1, WORD wMatch);
/*----------------------------------------------------------
Purpose: Compares the given character with the current format
string. If the character matches the expected format,
the function returns RES_OK.
If the current character does not match the expected
format, but the minimum sequence of characters for
the format has been fulfilled, this function will
increment *ppszFind to the next appropriate character
or escape sequence, and check for the next expected
format match.
If the end of the format string is reached, RES_HALT
is returned.
Returns: RES_OK (if the character compares)
RES_HALT (to stop immediately)
RES_FALSE (if the character does not compare)
Cond: --
*/
RES PRIVATE CompareFormat(
PFINDFMT pff,
LPCSTR * ppszFind,
char chRec)
{
RES res = RES_FALSE;
LPCSTR pszFind = *ppszFind;
LPCSTR pszNext;
DWORD dwFlags = 0;
char ch;
char chNext;
#define IS_ESCAPE(ch) ('%' == (ch))
pszNext = MyNextChar(pszFind, &ch, &dwFlags);
// Is this a DBCS trailing byte?
if (IsFlagSet(dwFlags, MNC_ISTAILBYTE))
{
// Yes; handle this normally
goto CompareNormal;
}
else
{
// No; check for special formatting characters first
switch (ch)
{
case '%':
chNext = *pszNext;
if ('u' == chNext)
{
// Look for unsigned digits
if (IS_DIGIT(chRec))
{
res = RES_OK;
SetFlag(pff->dwFlags, FFF_MATCHEDONCE);
}
else
{
// Have we already found some digits?
if (IsFlagSet(pff->dwFlags, FFF_MATCHEDONCE))
{
// Yes; then move on to the next thing to find
ClearFlag(pff->dwFlags, FFF_MATCHEDONCE);
pszNext = CharNext(pszNext);
res = CompareFormat(pff, &pszNext, chRec);
if (RES_FALSE != res)
*ppszFind = pszNext;
}
else
{
// No
res = RES_FALSE;
}
}
}
else if (IS_ESCAPE(chNext))
{
// Looking for a single '%'
res = (chNext == chRec) ? RES_OK : RES_FALSE;
if (RES_OK == res)
*ppszFind = CharNext(pszNext);
}
else
{
goto CompareNormal;
}
break;
case 0: // null terminator
res = RES_HALT;
break;
default: {
BOOL bMatch;
CompareNormal:
// (The ChrCmp* functions return FALSE if they match)
if (IsFlagSet(pff->dwFlags, FFF_MATCHCASE))
bMatch = !ChrCmp(ch, chRec);
else
bMatch = !ChrCmpI(ch, chRec);
if (bMatch)
{
res = RES_OK;
*ppszFind = pszNext;
}
else
res = RES_FALSE;
}
break;
}
}
return res;
}
/*----------------------------------------------------------
Purpose: Scans for the specific find format string.
The function returns two indexes and the count of
matched bytes. Both indexes refer to the read
buffer. *pibMark indexes the first character
of a substring candidate. *pib indexes the
character that was compared last.
If the sequence of characters completely match the
requested string, the function returns RES_OK. The
matching sequence of characters is copied into
the collection buffer (if one is given).
If there is no complete match, the function returns
RES_FALSE. The caller should read in more data
before calling this function with the same requested
string.
If some trailing string in the read buffer matches
the requested string,
If the collection buffer becomes full before a
complete match is found, this function returns
RES_E_MOREDATA.
Returns: see above
Cond: --
*/
RES PRIVATE ScanFormat(
PFINDFMT pff,
UINT ibCurFind,
LPCSTR pszRecBuf,
UINT cbRecMax,
LPUINT pibMark,
LPUINT pib,
LPINT pcbMatched)
{
// The trick is keeping track of when we've found a partial
// string across read boundaries. Consider the search string
// "ababc". We need to find it in the following cases (the
// character '|' indicates read boundary, '.' is an arbitrary
// byte):
//
// |...ababc..|
// |.abababc..|
// |......abab|c.........|
// |......abab|abc.......|
//
// This assumes the read buffer is larger than the search
// string. In order to do this, the read buffer must be
// a circular buffer, always retaining that portion of the
// possible string match from the last read.
//
// 1st read: |...ababc..| -> match found
//
// 1st read: |.abababc..| -> match found
//
// 1st read: |......abab| -> possible match (retain "abab")
// 2nd read: |ababc.....| -> match found
//
// 1st read: |......abab| -> possible match (retain "abab")
// 2nd read: |abababc...| -> match found
//
#define NOT_MARKED ((UINT)-1)
RES res = RES_FALSE; // assume the string is not here
LPSTR psz;
LPSTR pszBuf;
UINT ib;
UINT ibMark = NOT_MARKED;
UINT cb;
int cbMatched = 0;
int cbThrowAway = 0;
UINT cbBuf;
pszBuf = pff->pszBuf;
if (pszBuf)
{
TRACE_MSG(TF_GENERAL, "FindFormat: current buffer is {%s}", pszBuf);
cbBuf = pff->cbBuf;
if (cbBuf == pff->cbBuf)
{
ASSERT(0 < cbBuf);
cbBuf--; // save space for null terminator (first time only)
}
}
// Search for the (formatted) string in the receive
// buffer. Optionally store the matching received
// characters in the findfmt buffer.
for (psz = pff->pszFindFmt, ib = ibCurFind, cb = 0;
*psz && cb < cbRecMax;
ib = (ib + 1) % SIZE_ReceiveBuf, cb++)
{
// Match?
RES resT = CompareFormat(pff, &psz, pszRecBuf[ib]);
if (RES_OK == resT)
{
// Yes
if (NOT_MARKED == ibMark)
{
ibMark = ib; // Mark starting position
cbMatched = 0;
}
cbMatched++;
if (pszBuf)
{
if (0 == cbBuf)
{
res = RES_E_MOREDATA;
break;
}
*pszBuf++ = pszRecBuf[ib]; // Copy character to buffer
cbBuf--;
}
}
else if (RES_HALT == resT)
{
ASSERT(0 == *psz);
res = RES_HALT;
break;
}
else
{
// No; add this to our throw away count
cbThrowAway++;
// Are we in a partial find?
if (NOT_MARKED != ibMark)
{
// Yes; go back to where we thought the string might
// have started. The loop will increment one
// position and then resume search.
cb -= cbMatched;
ib = ibMark;
ibMark = NOT_MARKED;
psz = pff->pszFindFmt;
if (pszBuf)
{
pszBuf = pff->pszBuf;
cbBuf += cbMatched;
}
}
}
}
ASSERT(RES_FALSE == res || RES_HALT == res || RES_E_MOREDATA == res);
if (0 == *psz)
res = RES_OK;
if (pszBuf)
*pszBuf = 0; // add null terminator
ASSERT(RES_FALSE == res || RES_OK == res || RES_E_MOREDATA == res);
*pib = ib;
*pibMark = ibMark;
*pcbMatched = cbMatched;
if (RES_OK == res)
{
// Include any junk characters that preceded the matched string.
*pcbMatched += cbThrowAway;
}
else if (RES_FALSE == res)
{
// Should be at the end of the read buffer.
ASSERT(cb == cbRecMax);
}
return res;
}
/*----------------------------------------------------------
Purpose: This function attempts to find a formatted string in
the read buffer. See description of ScanFormat.
Returns: RES_OK (if complete string is found)
RES_FALSE (otherwise)
RES_E_MOREDATA (if no string found and pszBuf is full)
Cond: --
*/
RES PUBLIC FindFormat(
HWND hwnd,
HANDLE hFindFmt,
LPDWORD piFound)
{
RES res;
#ifndef WINNT_RAS
//
// On NT, the 'hwnd' parameter is actually a pointer to the SCRIPTDATA
// for the current script, hence the #if-#else.
//
PTERMDLG ptd = (PTERMDLG)GetWindowLongPtr(hwnd, DWLP_USER);
#else // !WINNT_RAS
SCRIPTDATA* ptd = (SCRIPTDATA*)hwnd;
#endif // !WINNT_RAS
HSA hsa = (HSA)hFindFmt;
UINT ib;
UINT ibMark;
int cbMatched = -1;
DWORD iff;
DWORD cff;
ASSERT(hsa);
DBG_ENTER(FindFormat);
DEBUG_CODE( DumpBuffer(ptd); )
// Consider each of the requested strings separately. If
// there are multiple candidate matches, choose the one
// that has the most matched characters.
cff = SAGetCount(hsa);
for (iff = 0; iff < cff; iff++)
{
PFINDFMT pff;
RES resT;
UINT ibMarkT;
UINT ibT;
int cbMatchedT;
SAGetItemPtr(hsa, iff, &pff);
resT = ScanFormat(pff, ptd->ibCurFind, ptd->pbReceiveBuf,
ptd->cbReceiveMax, &ibMarkT, &ibT, &cbMatchedT);
// Did this string match?
switch (resT)
{
case RES_OK:
// Yes; stop right now
ibMark = ibMarkT;
ib = ibT;
cbMatched = cbMatchedT;
*piFound = iff;
// Fall thru
case RES_E_MOREDATA:
res = resT;
goto GetOut;
case RES_FALSE:
if (cbMatchedT > cbMatched)
{
res = resT;
ibMark = ibMarkT;
ib = ibT;
cbMatched = cbMatchedT;
}
break;
default:
ASSERT(0);
break;
}
}
GetOut:
// Update the read buffer pointers to preserve any trailing
// substring that may have matched.
if (RES_OK == res)
{
// Found string!
TRACE_MSG(TF_BUFFER, "Found string in buffer");
// It is okay to have characters following the matched string
// that have not been scanned yet. However, it is not okay
// to still think there are characters preceding the matched
// string that need scanning.
ASSERT((UINT)cbMatched == ptd->cbReceiveMax && ib == ptd->ibCurRead ||
(UINT)cbMatched <= ptd->cbReceiveMax);
ptd->ibCurFind = ib;
ptd->cbReceiveMax -= cbMatched;
}
else if (RES_E_MOREDATA == res)
{
// Throw away whatever is in the receive buffer
TRACE_MSG(TF_BUFFER, "String too long in buffer");
ptd->ibCurFind = ptd->ibCurRead;
ptd->cbReceiveMax = 0;
}
else
{
ASSERT(RES_FALSE == res);
// End of receive buffer; did we even find a potential substring?
if (NOT_MARKED == ibMark)
{
// No; throw away whatever is in the receive buffer
TRACE_MSG(TF_BUFFER, "String not found in buffer");
ptd->ibCurFind = ptd->ibCurRead;
ptd->cbReceiveMax = 0;
}
else
{
// Yes; keep the substring part
TRACE_MSG(TF_BUFFER, "Partial string found in buffer");
ASSERT(ibMark >= ptd->ibCurFind);
ptd->ibCurFind = ibMark;
ptd->cbReceiveMax = cbMatched;
}
}
DBG_EXIT_RES(FindFormat, res);
return res;
}
#ifdef OLD_FINDFORMAT
/*----------------------------------------------------------
Purpose: This function attempts to find a formatted string in
the read buffer. If a sequence of characters match
the complete string, the function returns TRUE. The
matching sequence of characters is copied into pszBuf.
If a portion of the string (ie, from the beginning
of pszFindFmt to some middle of pszFindFmt) is found
in the buffer, the buffer is marked so the next read will
not overwrite the possible substring match. This
function then returns FALSE. The caller should
read in more data before calling this function again.
The formatted string may have the following characters:
%u - expect a number (to first non-digit)
^M - expect a carriage-return
<cr> - expect a carriage-return
<lf> - expect a line-feed
All other characters are taken literally.
If pszBuf becomes full before a delimiter is
encountered, this function returns RES_E_MOREDATA.
Returns: RES_OK (if complete string is found)
RES_FALSE (otherwise)
RES_E_MOREDATA (if no delimiter encountered and pszBuf is full)
Cond: --
*/
RES PUBLIC FindFormat(
HWND hwnd,
HANDLE hFindFmt)
{
// The trick is keeping track of when we've found a partial
// string across read boundaries. Consider the search string
// "ababc". We need to find it in the following cases (the
// character '|' indicates read boundary, '.' is an arbitrary
// byte):
//
// |...ababc..|
// |.abababc..|
// |......abab|c.........|
// |......abab|abc.......|
//
// This assumes the read buffer is larger than the search
// string. In order to do this, the read buffer must be
// a circular buffer, always retaining that portion of the
// possible string match from the last read.
//
// 1st read: |...ababc..| -> match found
//
// 1st read: |.abababc..| -> match found
//
// 1st read: |......abab| -> possible match (retain "abab")
// 2nd read: |ababc.....| -> match found
//
// 1st read: |......abab| -> possible match (retain "abab")
// 2nd read: |abababc...| -> match found
//
#define NOT_MARKED ((UINT)-1)
RES res = RES_FALSE;
PTERMDLG ptd = (PTERMDLG)GetWindowLongPtr(hwnd, DWLP_USER);
PFINDFMT pff = (PFINDFMT)hFindFmt;
LPCSTR psz;
LPSTR pszBuf;
LPSTR pszRecBuf = ptd->pbReceiveBuf;
UINT ib;
UINT ibMark = NOT_MARKED;
UINT cb;
UINT cbMatched = 0;
UINT cbRecMax = ptd->cbReceiveMax;
UINT cbBuf;
ASSERT(pff);
DBG_ENTER_SZ(FindFormat, pff->pszFindFmt);
DEBUG_CODE( DumpBuffer(ptd); )
pszBuf = pff->pszBuf;
if (pszBuf)
{
TRACE_MSG(TF_GENERAL, "FindFormat: current buffer is {%s}", pff->pszBuf);
cbBuf = pff->cbBuf;
if (cbBuf == pff->cbBuf)
{
ASSERT(0 < cbBuf);
cbBuf--; // save space for null terminator (first time only)
}
}
// Search for the (formatted) string in the receive
// buffer. Optionally store the matching received
// characters in the findfmt buffer.
for (psz = pff->pszFindFmt, ib = ptd->ibCurFind, cb = 0;
*psz && cb < cbRecMax;
ib = (ib + 1) % SIZE_ReceiveBuf, cb++)
{
// Match?
res = CompareFormat(pff, &psz, pszRecBuf[ib]);
if (RES_OK == res)
{
// Yes
if (NOT_MARKED == ibMark)
{
ibMark = ib; // Mark starting position
cbMatched = 0;
}
cbMatched++;
if (pszBuf)
{
if (0 == cbBuf)
{
res = RES_E_MOREDATA;
break;
}
*pszBuf++ = pszRecBuf[ib]; // Copy character to buffer
cbBuf--;
}
}
else if (RES_HALT == res)
{
ASSERT(0 == *psz);
break;
}
else if (NOT_MARKED != ibMark)
{
// No; go back to where we thought the string might
// have started. The loop will increment one
// position and then resume search.
cb -= cbMatched;
ib = ibMark;
ibMark = NOT_MARKED;
psz = pff->pszFindFmt;
if (pszBuf)
{
pszBuf = pff->pszBuf;
cbBuf += cbMatched;
}
}
}
if (pszBuf)
*pszBuf = 0; // add null terminator
if ( !*psz )
{
// Found string!
TRACE_MSG(TF_BUFFER, "Found string in buffer");
ASSERT(cbMatched <= ptd->cbReceiveMax);
ptd->ibCurFind = ib;
ptd->cbReceiveMax -= cbMatched;
res = RES_OK;
}
else if (RES_E_MOREDATA == res)
{
// Throw away whatever is in the receive buffer
TRACE_MSG(TF_BUFFER, "String too long in buffer");
ptd->ibCurFind = ptd->ibCurRead;
ptd->cbReceiveMax = 0;
}
else
{
// End of receive buffer; did we even find a potential substring?
ASSERT(cb == cbRecMax);
if (NOT_MARKED == ibMark)
{
// No; throw away whatever is in the receive buffer
TRACE_MSG(TF_BUFFER, "String not found in buffer");
ptd->ibCurFind = ptd->ibCurRead;
ptd->cbReceiveMax = 0;
}
else
{
// Yes; keep the substring part
TRACE_MSG(TF_BUFFER, "Partial string found in buffer");
ASSERT(ibMark >= ptd->ibCurFind);
ptd->ibCurFind = ibMark;
ptd->cbReceiveMax = cbMatched;
}
res = RES_FALSE;
}
DBG_EXIT_RES(FindFormat, res);
return res;
}
#endif
#ifdef COPYTODELIM
/*----------------------------------------------------------
Purpose: Sets or clears the list of delimiters
Returns: --
Cond: --
*/
void PRIVATE SetDelimiters(
PTERMDLG ptd,
LPCSTR pszTok,
BOOL bSet)
{
PBOOL rgbDelim = ptd->rgbDelim;
LPCSTR psz;
char ch;
for (psz = pszTok; *psz; )
{
psz = MyNextChar(psz, &ch);
ASSERT(InRange(ch, 0, CE_DELIM-1));
rgbDelim[ch] = bSet;
}
}
/*----------------------------------------------------------
Purpose: This function reads to one of the given token
delimiters. All characters in the read buffer (to
the delimiter) are copied into pszBuf, not including
the delimiter.
Any token delimiters that are encountered before the
first non-token delimiter are skipped, and the function
starts at the first non-token delimiter.
If a token delimiter is found, the function
returns RES_OK.
If a token delimiter is not found in the current
read buffer, the function returns RES_FALSE and the
characters that were read are still copied into
pszBuf. The caller should read in more data before
calling this function again.
If pszBuf becomes full before a delimiter is
encountered, this function returns RES_E_MOREDATA.
The string returned in pszBuf is null terminated.
Returns: RES_OK
RES_FALSE (if no delimiter encountered this time)
RES_E_MOREDATA (if no delimiter encountered and pszBuf is full)
Cond: --
*/
RES PUBLIC CopyToDelimiter(
HWND hwnd,
LPSTR pszBuf,
UINT cbBuf,
LPCSTR pszTok)
{
RES res;
PTERMDLG ptd = (PTERMDLG)GetWindowLongPtr(hwnd, DWLP_USER);
PBOOL rgbDelim = ptd->rgbDelim;
LPSTR pszReadBuf = ptd->pbReceiveBuf;
LPSTR psz;
UINT ib;
UINT cb;
UINT cbMax = ptd->cbReceiveMax;
DBG_ENTER_SZ(CopyToDelimiter, pszTok);
DEBUG_CODE( DumpBuffer(ptd); )
#ifdef DEBUG
for (ib = 0; ib < CE_DELIM; ib++)
ASSERT(FALSE == rgbDelim[ib]);
#endif
// Initialize the delimiters
SetDelimiters(ptd, pszTok, TRUE);
cbBuf--; // save space for terminator
// Skip to the first non-delimiter
for (ib = ptd->ibCurFind, cb = 0;
cb < cbMax;
ib = (ib + 1) % SIZE_ReceiveBuf, cb++)
{
char ch = pszReadBuf[ib];
ASSERT(InRange(ch, 0, CE_DELIM-1));
// Is this one of the delimiters?
if ( !rgbDelim[ch] )
{
// No; stop
break;
}
}
if (cb < cbMax || 0 == cbMax)
res = RES_FALSE; // assume no delimiter in this pass
else
res = RES_OK;
// Copy to the first delimiter encountered
for (psz = pszBuf;
0 < cbBuf && cb < cbMax;
psz++, ib = (ib + 1) % SIZE_ReceiveBuf, cb++, cbBuf--)
{
char ch = pszReadBuf[ib];
ASSERT(InRange(ch, 0, CE_DELIM-1));
// Is this one of the delimiters?
if (rgbDelim[ch])
{
// Yes; we're done
res = RES_OK;
break;
}
else
{
// No;
*psz = ch;
}
}
*psz = 0; // add null terminator
ptd->ibCurFind = ib;
ptd->cbReceiveMax -= cb;
if (RES_FALSE == res)
{
res = (0 == cbBuf) ? RES_E_MOREDATA : RES_FALSE;
}
else
{
TRACE_MSG(TF_BUFFER, "Copied to delimiter %#02x", pszReadBuf[ib]);
}
// Deinitialize the delimiters
SetDelimiters(ptd, pszTok, FALSE);
DBG_EXIT_RES(CopyToDelimiter, res);
return res;
}
#endif // COPYTODELIM
/*----------------------------------------------------------
Purpose: Reads from the comm port into the circular buffer.
This functions sets *ppbBuf to the location of
the first new character read.
there is a potential but rare bug
that can occur with Internet providers that send
DBCS over the wire. If the last character in the
buffer is a DBCS lead-byte, and it is being thrown
away, then the next byte might be matched with an
existing character. The entire string following
this must match for us to find a false match.
Returns: TRUE (if a character was read successfully)
FALSE (otherwise)
Cond: --
*/
BOOL PRIVATE ReadIntoBuffer(
#ifndef WINNT_RAS
//
// On NT, we use a SCRIPTDATA pointer to access the circular-buffer.
//
PTERMDLG ptd,
#else // !WINNT_RAS
SCRIPTDATA *ptd,
#endif // !WINNT_RAS
PDWORD pibStart, // start of newly read characters
PDWORD pcbRead) // count of newly read characters
{
BOOL bRet;
OVERLAPPED ov;
DWORD cb;
DWORD cbRead;
DWORD ib;
DBG_ENTER(ReadIntoBuffer);
ASSERT(pibStart);
ASSERT(pcbRead);
ov.Internal = 0;
ov.InternalHigh = 0;
ov.Offset = 0;
ov.OffsetHigh = 0;
ov.hEvent = NULL;
*pcbRead = 0;
// This is a circular buffer, so at most do two reads
ASSERT(ptd->ibCurRead >= ptd->ibCurFind);
ASSERT(SIZE_ReceiveBuf > ptd->ibCurFind);
ASSERT(SIZE_ReceiveBuf > ptd->ibCurRead);
*pcbRead = 0;
*pibStart = ptd->ibCurRead;
// (*pibStart can be different from ptd->ibCurFind)
ib = ptd->ibCurRead;
cb = SIZE_ReceiveBuf - ib;
do
{
DWORD ibNew;
ASSERT(SIZE_ReceiveBuf > *pcbRead);
#ifndef WINNT_RAS
//
// In order to read data on NT, we go through RxReadFile
// which reads from the buffer which was filled by RASMAN.
//
bRet = ReadFile(ptd->hport, &ptd->pbReceiveBuf[ib], cb, &cbRead, &ov);
SetEvent(ptd->hEvent[READ_EVENT]);
#else // !WINNT_RAS
bRet = RxReadFile(
ptd->hscript, &ptd->pbReceiveBuf[ib], cb, &cbRead
);
#endif // !WINNT_RAS
ptd->cbReceiveMax += cbRead;
*pcbRead += cbRead;
// Is this going to wrap around?
ibNew = (ib + cbRead) % SIZE_ReceiveBuf;
if (ibNew > ib)
cb -= cbRead; // No
else
cb = ptd->ibCurFind; // Yes
ib = ibNew;
} while (bRet && 0 != cbRead && SIZE_ReceiveBuf > *pcbRead);
ptd->ibCurRead = (ptd->ibCurRead + *pcbRead) % SIZE_ReceiveBuf;
DEBUG_CODE( DumpBuffer(ptd); )
DBG_EXIT_BOOL(ReadIntoBuffer, bRet);
return bRet;
}
/*----------------------------------------------------------
Purpose: Get input from the com port
Returns: TRUE
Cond: --
*/
BOOL NEAR PASCAL GetInput(
HWND hwnd)
{
BOOL bRet = TRUE;
PTERMDLG ptd;
DWORD cbRead;
DWORD ibStart;
DBG_ENTER(GetInput);
ptd = (PTERMDLG)GetWindowLongPtr(hwnd, DWLP_USER);
#ifndef WINNT_RAS
//
// On NT, the information for the script is stored in a SCRIPTDATA.
// The code below exists only to allow this file to compile;
// "GetInput" is not called at all on NT.
//
if (ReadIntoBuffer(ptd, &ibStart, &cbRead) && 0 < cbRead)
#else // !WINNT_RAS
if (ReadIntoBuffer((SCRIPTDATA *)ptd, &ibStart, &cbRead) && 0 < cbRead)
#endif // !WINNT_RAS
{
char szBuf[SIZE_ReceiveBuf + 1];
LPSTR pch = szBuf;
UINT ib;
UINT cb;
HWND hwndScrn = GetDlgItem(hwnd, CID_T_EB_SCREEN);
for (ib = ibStart, cb = 0; cb < cbRead; cb++, ib = (ib + 1) % SIZE_ReceiveBuf)
{
char ch = ptd->pbReceiveBuf[ib];
/* Formatting: Converts CRs to LFs (there seems to be no VK_
** for LF) and throws away LFs. This prevents the user from
** exiting the dialog when they press Enter (CR) in the
** terminal screen. LF looks like CRLF in the edit box. Also,
** throw away TABs because otherwise they change focus to the
** next control.
*/
if (ch == VK_RETURN)
{
/* Must send whenever end-of-line is encountered because
** EM_REPLACESEL doesn't handle VK_RETURN characters well
** (prints garbage).
*/
*pch = '\0';
/* Turn off current selection, if any, and replace the null
** selection with the current buffer. This has the effect
** of adding the buffer at the caret. Finally, send the
** EOL to the window which (unlike EM_REPLACESEL) handles
** it correctly.
*/
SendMessage(hwndScrn, WM_SETREDRAW, (WPARAM )FALSE, 0);
SendMessage(hwndScrn, EM_SETSEL, 32767, 32767 );
SendMessage(hwndScrn, EM_REPLACESEL, 0, (LPARAM )szBuf );
SendMessage(hwndScrn, WM_EOLFROMDEVICE, 0, 0 );
SendMessage(hwndScrn, WM_SETREDRAW, (WPARAM )TRUE, 0);
SendMessage(hwndScrn, EM_SCROLLCARET, 0, 0);
InvalidateRect(hwndScrn, NULL, FALSE);
/* Start afresh on the output buffer.
*/
pch = szBuf;
continue;
}
else if (ch == '\n' || ch == VK_TAB)
continue;
*pch++ = ch;
}
*pch = '\0';
if (pch != szBuf)
{
/* Send the last remnant of the line.
*/
SendMessage(hwndScrn, EM_SETSEL, 32767, 32767);
SendMessage(hwndScrn, EM_REPLACESEL, 0, (LPARAM)szBuf );
SendMessage(hwndScrn, EM_SCROLLCARET, 0, 0);
}
}
DBG_EXIT_BOOL(GetInput, bRet);
return bRet;
}
/*----------------------------------------------------------------------------
** Terminal Output Handler
**----------------------------------------------------------------------------
*/
/*----------------------------------------------------------
Purpose: Send a byte to the device.
Returns: --
Cond: --
*/
void PUBLIC SendByte(
HWND hwnd,
BYTE byte)
{
#ifndef WINNT_RAS
//
// On NT. we use a SCRIPTDATA structure to hold information about the script.
//
PTERMDLG ptd;
#else // !WINNT_RAS
SCRIPTDATA* ptd;
#endif // !WINNT_RAS
DWORD cbWrite;
OVERLAPPED ov;
DBG_ENTER(SendByte);
#ifndef WINNT_RAS
//
// On NT, the "hwnd" argument is actually a pointer to a SCRIPTDATA structure
// for the script being parsed.
//
ptd = (PTERMDLG)GetWindowLongPtr(hwnd, DWLP_USER);
#else // !WINNT_RAS
ptd = (SCRIPTDATA *)hwnd;
#endif // !WINNT_RAS
/* Send the character to the device. It is not passed thru
** because the device will echo it.
*/
ptd->pbSendBuf[0] = (BYTE)byte;
/* Make sure we still have the comm port
*/
ov.Internal = 0;
ov.InternalHigh = 0;
ov.Offset = 0;
ov.OffsetHigh = 0;
ov.hEvent = NULL;
cbWrite = 0;
#ifndef WINNT_RAS
//
// On NT, we output data on the COM port by calling RxWriteFile
// which in turn passes the data to RasPortSend
//
WriteFile(ptd->hport, ptd->pbSendBuf, SIZE_SendBuf, &cbWrite, &ov);
#else // !WINNT_RAS
RxWriteFile(ptd->hscript, ptd->pbSendBuf, SIZE_SendBuf, &cbWrite);
#endif // !WINNT_RAS
TRACE_MSG(TF_BUFFER, "Sent byte %#02x", byte);
DBG_EXIT(SendByte);
}
/*----------------------------------------------------------------------------
** Terminal Appearance Adjuster
**----------------------------------------------------------------------------
*/
VOID NEAR PASCAL AdjustTerminal (HWND hwnd, int wWidth, int wHeight)
{
HWND hwndCtrl;
RECT rect;
SIZE sizeButton;
POINT ptPos;
DWORD dwUnit;
// Get the sizes of the push buttons
//
dwUnit = GetDialogBaseUnits();
hwndCtrl = GetDlgItem(hwnd, IDOK);
GetWindowRect(hwndCtrl, &rect);
sizeButton.cx = rect.right - rect.left;
sizeButton.cy = rect.bottom - rect.top;
ptPos.x = wWidth/2 - ((X_SPACING*LOWORD(dwUnit))/4)/2 - sizeButton.cx;
ptPos.y = wHeight - (sizeButton.cy+((Y_MARGIN*HIWORD(dwUnit))/4));
// Move the push buttons
MoveWindow(hwndCtrl, ptPos.x, ptPos.y, sizeButton.cx, sizeButton.cy, TRUE);
ptPos.x += ((X_SPACING*LOWORD(dwUnit))/4) + sizeButton.cx;
MoveWindow(GetDlgItem(hwnd, IDCANCEL), ptPos.x, ptPos.y,
sizeButton.cx, sizeButton.cy, TRUE);
// Move the input enable box
hwndCtrl = GetDlgItem(hwnd, CID_T_CB_MIN);
GetWindowRect(hwndCtrl, &rect);
sizeButton.cx = rect.right - rect.left;
sizeButton.cy = rect.bottom - rect.top;
ptPos.y -= (sizeButton.cy + ((Y_MARGIN*HIWORD(dwUnit))/4));
ScreenToClient(hwnd, (LPPOINT)&rect);
MoveWindow(hwndCtrl, rect.left, ptPos.y,
sizeButton.cx,
sizeButton.cy,
TRUE);
// Move the start-minimized box
hwndCtrl = GetDlgItem(hwnd, CID_T_CB_INPUT);
GetWindowRect(hwndCtrl, &rect);
sizeButton.cx = rect.right - rect.left;
sizeButton.cy = rect.bottom - rect.top;
ptPos.y -= (sizeButton.cy + ((Y_SMALL_MARGIN*HIWORD(dwUnit))/4));
ScreenToClient(hwnd, (LPPOINT)&rect);
MoveWindow(hwndCtrl, rect.left, ptPos.y,
sizeButton.cx,
sizeButton.cy,
TRUE);
// Get the current position of the terminal screen
hwndCtrl = GetDlgItem(hwnd, CID_T_EB_SCREEN);
GetWindowRect(hwndCtrl, &rect);
ScreenToClient(hwnd, (LPPOINT)&rect);
MoveWindow(hwndCtrl, rect.left, rect.top,
wWidth - 2*rect.left,
ptPos.y - rect.top - ((Y_SMALL_MARGIN*HIWORD(dwUnit))/4),
TRUE);
InvalidateRect(hwnd, NULL, TRUE);
return;
}
/*----------------------------------------------------------------------------
** Terminal caption update
**----------------------------------------------------------------------------
*/
void NEAR PASCAL UpdateTerminalCaption(PTERMDLG ptd, UINT ids)
{
LPSTR szTitle, szFmt;
UINT iRet;
// If we are not running a script, do not update the caption
//
if (*ptd->script.szPath == '\0')
return;
// Allocate buffer
//
if ((szFmt = (LPSTR)LocalAlloc(LMEM_FIXED, 2*MAX_PATH)) != NULL)
{
// Load the display format
//
if((iRet = LoadString(g_hinst, ids, szFmt, MAX_PATH)))
{
// Get the title buffer
//
szTitle = szFmt+iRet+1;
{
// Build up the title
//
wsprintf(szTitle, szFmt, ptd->script.szPath);
SetWindowText(ptd->hwnd, szTitle);
};
};
LocalFree((HLOCAL)szFmt);
};
return;
}
/*----------------------------------------------------------------------------
** Terminal read-notification thread
**----------------------------------------------------------------------------
*/
void WINAPI TerminalThread (PTERMDLG ptd)
{
DWORD dwEvent;
DWORD dwMask;
while((dwEvent = WaitForMultipleObjects(MAX_EVENT, ptd->hEvent,
FALSE, INFINITE))
< WAIT_OBJECT_0+MAX_EVENT)
{
switch (dwEvent)
{
case READ_EVENT:
// Are we stopped?
if (WAIT_TIMEOUT == WaitForSingleObject(ptd->hEvent[STOP_EVENT], 0))
{
// No; wait for next character
dwMask = 0;
TRACE_MSG(TF_BUFFER, "Waiting for comm traffic...");
WaitCommEvent(ptd->hport, &dwMask, NULL);
if ((dwMask & EV_RXCHAR) && (ptd->hwnd != NULL))
{
TRACE_MSG(TF_BUFFER, "...EV_RXCHAR incoming");
PostMessage(ptd->hwnd, WM_MODEMNOTIFY, 0, 0);
}
else
{
TRACE_MSG(TF_BUFFER, "...EV_other (%#08lx) incoming", dwMask);
}
}
else
{
// Yes; just get out of here
ExitThread(ERROR_SUCCESS);
}
break;
case STOP_EVENT:
ExitThread(ERROR_SUCCESS);
break;
default:
ASSERT(0);
break;
}
}
}
/*----------------------------------------------------------------------------
** Set IP address
**----------------------------------------------------------------------------
*/
DWORD NEAR PASCAL TerminalSetIP(HWND hwnd, LPCSTR pszIPAddr)
{
PTERMDLG ptd;
DWORD dwRet;
ptd = (PTERMDLG)GetWindowLongPtr(hwnd, DWLP_USER);
if (IS_TEST_SCRIPT(ptd))
{
// We are testing, just display the ip address
//
MsgBox(g_hinst,
hwnd,
MAKEINTRESOURCE(IDS_IP_Address),
MAKEINTRESOURCE(IDS_CAP_Script),
NULL,
MB_OK | MB_ICONINFORMATION,
pszIPAddr);
};
// Set the IP address permanently
//
dwRet = AssignIPAddress(ptd->pscanner->psci->szEntryName,
pszIPAddr);
return dwRet;
}
/*----------------------------------------------------------------------------
** Terminal Input settings
**----------------------------------------------------------------------------
*/
void NEAR PASCAL TerminalSetInput(HWND hwnd, BOOL fEnable)
{
PTERMDLG ptd;
ptd = (PTERMDLG)GetWindowLongPtr(hwnd, DWLP_USER);
// If the status is not changed, do nothing
//
if ((ptd->fInputEnabled && !fEnable) ||
(!ptd->fInputEnabled && fEnable))
{
// Mark the input enabling flag
//
ptd->fInputEnabled = fEnable;
// Check the control properly
//
CheckDlgButton(hwnd, CID_T_CB_INPUT,
fEnable ? BST_CHECKED : BST_UNCHECKED);
// Repaint the terminal screen
//
InvalidateRect(hwnd, NULL, FALSE);
// If enable and the window is iconic, restore it
//
if (fEnable)
{
if (IsIconic(hwnd))
{
ShowWindow(hwnd, SW_RESTORE);
};
SetFocus(GetDlgItem(hwnd, CID_T_EB_SCREEN));
};
};
}
/*----------------------------------------------------------------------------
** Dump the script window
**----------------------------------------------------------------------------
*/
BOOL NEAR PASCAL DisplayScript (PTERMDLG ptd)
{
// Create the debug script window
//
ptd->hwndDbg = CreateDialogParam(g_hinst,
MAKEINTRESOURCE(IDD_TERMINALTESTDLG),
NULL,
DbgScriptDlgProc,
(LPARAM)ptd);
// Did we have the debug window?
//
if (!IsWindow(ptd->hwndDbg))
{
ptd->hwndDbg = NULL;
return FALSE;
};
return TRUE;
}
/*----------------------------------------------------------------------------
** Script debug window procedure
**----------------------------------------------------------------------------
*/
LRESULT FAR PASCAL DbgScriptDlgProc(HWND hwnd,
UINT wMsg,
WPARAM wParam,
LPARAM lParam )
{
PTERMDLG ptd;
switch (wMsg)
{
case WM_INITDIALOG:
{
HMENU hMenuSys;
ptd = (PTERMDLG)lParam;
SetWindowLongPtr(hwnd, DWLP_USER, (ULONG_PTR)lParam);
ptd->hwndDbg = hwnd;
// Show its own icon
//
SendMessage(hwnd, WM_SETICON, TRUE,
(LPARAM)LoadIcon(g_hinst, MAKEINTRESOURCE(IDI_SCRIPT)));
// Always gray out size and maximize command
//
hMenuSys = GetSystemMenu(hwnd, FALSE);
EnableMenuItem(hMenuSys, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED);
return (InitDebugWindow(hwnd));
}
case WM_PROCESSSCRIPT:
{
PTERMDLG ptd;
HWND hCtrl;
//
// The main window notifies that it is done with the current line
//
ptd = (PTERMDLG)GetWindowLongPtr(hwnd, DWLP_USER);
// Make sure we do not continue processing the script
//
ptd->fContinue = FALSE;
hCtrl = GetDlgItem(hwnd, CID_T_PB_STEP);
EnableWindow(hCtrl, TRUE);
SetFocus(hCtrl);
TrackScriptLine(ptd, Astexec_GetCurLine(&ptd->astexec)-1);
break;
}
case WM_COMMAND:
switch (LOWORD(wParam))
{
case CID_T_EB_SCRIPT:
{
HWND hCtrl = GET_WM_COMMAND_HWND(wParam, lParam);
if (GET_WM_COMMAND_CMD(wParam, lParam)==EN_SETFOCUS)
{
PTERMDLG ptd;
ptd = (PTERMDLG)GetWindowLongPtr(hwnd, DWLP_USER);
TrackScriptLine(ptd, ptd->iMarkLine);
};
break;
}
case CID_T_PB_STEP:
{
PTERMDLG ptd;
ptd = (PTERMDLG)GetWindowLongPtr(hwnd, DWLP_USER);
// Allow the next step
//
EnableWindow(GET_WM_COMMAND_HWND(wParam, lParam), FALSE);
// Tell the main window to process the next script line
//
ptd->fContinue = TRUE;
PostProcessScript(ptd->hwnd);
break;
}
};
break;
case WM_HELP:
case WM_CONTEXTMENU:
ContextHelp(gaDebug, wMsg, wParam, lParam);
break;
};
return 0;
}
/*----------------------------------------------------------------------------
** Init script debug window
**----------------------------------------------------------------------------
*/
BOOL NEAR PASCAL InitDebugWindow (HWND hwnd)
{
PTERMDLG ptd;
HANDLE hFile;
LPBYTE lpBuffer;
DWORD cbRead;
HWND hCtrl;
UINT iLine, cLine, iMark;
ptd = (PTERMDLG)GetWindowLongPtr(hwnd, DWLP_USER);
ASSERT(IS_TEST_SCRIPT(ptd));
// Do not start the script yet
//
ptd->fContinue = FALSE;
// Allocate the read buffer
//
if ((lpBuffer = (LPBYTE)LocalAlloc(LMEM_FIXED, SIZE_ReceiveBuf)) == NULL)
return FALSE;
hCtrl = GetDlgItem(hwnd, CID_T_EB_SCRIPT);
hFile = CreateFile(ptd->script.szPath, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE != hFile)
{
while(ReadFile(hFile, lpBuffer, sizeof(SIZE_ReceiveBuf), &cbRead, NULL) &&
(cbRead != 0))
{
// Clean up the end garbage
//
if (cbRead < SIZE_ReceiveBuf)
lpBuffer[cbRead] = '\0';
SendMessage(hCtrl, EM_SETSEL, 32767, 32767 );
SendMessage(hCtrl, EM_REPLACESEL, 0, (LPARAM)lpBuffer );
};
CloseHandle(hFile);
};
// Display the file name
//
cbRead = GetDlgItemText(hwnd, CID_T_ST_FILE, lpBuffer, SIZE_ReceiveBuf) + 1;
if(SIZE_ReceiveBuf >= (cbRead + (lstrlen(ptd->script.szPath) * sizeof(TCHAR))))
{
wsprintf(lpBuffer+cbRead, lpBuffer, ptd->script.szPath);
SetDlgItemText(hwnd, CID_T_ST_FILE, lpBuffer+cbRead);
}
else
{
ASSERT(FALSE);
SetDlgItemText(hwnd, CID_T_ST_FILE, ptd->script.szPath);
}
LocalFree(lpBuffer);
// Init script track
//
for (iLine = 0, cLine = Edit_GetLineCount(hCtrl);
iLine < cLine; iLine++)
{
iMark = Edit_LineIndex(hCtrl, iLine);
Edit_SetSel(hCtrl, iMark, iMark);
Edit_ReplaceSel(hCtrl, TRACE_UNMARK);
};
// Initialize script tracking
//
PostProcessScript(hwnd);
return TRUE;
}
/*----------------------------------------------------------------------------
** Trace the script
**----------------------------------------------------------------------------
*/
void NEAR PASCAL TrackScriptLine(PTERMDLG ptd, DWORD iLine)
{
HWND hCtrl;
UINT iMark, iRange;
#pragma data_seg(DATASEG_READONLY)
const static char c_szLastline[] = {0x0d, 0x0a, ' '};
#pragma data_seg()
ASSERT(0 <= iLine || INVALID_SCRIPT_LINE == iLine);
hCtrl = GetDlgItem(ptd->hwndDbg, CID_T_EB_SCRIPT);
// Do not update the screen until we are done
//
SendMessage(hCtrl, WM_SETREDRAW, (WPARAM )FALSE, 0);
if ((ptd->iMarkLine != iLine) || (iLine == INVALID_SCRIPT_LINE))
{
// Remove the old mark
//
iMark = Edit_LineIndex(hCtrl, ptd->iMarkLine);
Edit_SetSel(hCtrl, iMark, iMark+sizeof(TRACE_MARK)-1);
Edit_ReplaceSel(hCtrl, TRACE_UNMARK);
// If this is the last line, make a dummy line
//
if (iLine == INVALID_SCRIPT_LINE)
{
Edit_SetSel(hCtrl, 32767, 32767);
Edit_ReplaceSel(hCtrl, c_szLastline);
iLine = Edit_GetLineCount(hCtrl);
EnableWindow(GetDlgItem(ptd->hwndDbg, CID_T_PB_STEP), FALSE);
#ifdef PROMPT_AT_COMPLETION
// Prompt the user to continue
//
SetFocus(GetDlgItem(ptd->hwnd, IDOK));
#else
// We are done processing the script, continue automatically
//
ptd->fContinue = TRUE;
PostProcessScript(ptd->hwnd);
#endif // PROMPT_AT_COMPLETION
};
// Mark the current line
//
iMark = Edit_LineIndex(hCtrl, iLine);
Edit_SetSel(hCtrl, iMark, iMark+sizeof(TRACE_UNMARK)-1);
Edit_ReplaceSel(hCtrl, TRACE_MARK);
ptd->iMarkLine = iLine;
}
else
{
iMark = Edit_LineIndex(hCtrl, iLine);
};
// Select the current line
//
iRange = Edit_LineLength(hCtrl, iMark)+1;
Edit_SetSel(hCtrl, iMark, iMark+iRange);
// Update the screen now
//
SendMessage(hCtrl, WM_SETREDRAW, (WPARAM )TRUE, 0);
InvalidateRect(hCtrl, NULL, FALSE);
Edit_ScrollCaret(hCtrl);
return;
};