//********************************************************************************
//* File       : AnsiCmd.cpp                                                     *
//* Author     : Mahlon R. Smith                                                 *
//*              Copyright (c) 2022-2026 Mahlon R. Smith, The Software Samurai   *
//*                 GNU GPL copyright notice below                               *
//* Date       : 02-Apr-2026                                                     *
//* Version    : (see AnsiCmdVersion string below.)                              *
//*                                                                              *
//* Description: AnsiCmd class implementation.                                   *
//*                                                                              *
//* This class defines a simple and comprehensive way to set the terminal        *
//* program's text attributes. These include foreground and background color     *
//* and intensity, as well as bold, underline, italic, blinking, and other,      *
//* less used and less widely supported attributes.                              *
//* These include cursor positioning, overline, strike-through and all other     *
//* attributes defined in the ANSI X3.64 standard.                               *
//* See also ECMA-48, ISO/IEC 6429, FIPS 86, ISO/IEC 2022, JIS X 0211.           *
//*                                                                              *
//* This implementation is intended primarily for Linux in its various flavors,  *
//* but it is also well-suited for other platforms that support some subset      *
//* of the ANSI escape sequence set.                                             *
//*                                                                              *
//* Note that if the terminal receives an escape sequence or control code        *
//* which is not supported by the system, it will _probably_ be ignored;         *
//* however, it is prudent to verify, wherever possible, that all invoked        *
//* sequences are supported by the target system.                                *
//*                                                                              *
//*            https://en.wikipedia.org/wiki/ANSI_escape_code                    *
//********************************************************************************
//* Copyright Notice:                                                            *
//* This program is free software: you can redistribute it and/or modify it      *
//* under the terms of the GNU General Public License as published by the Free   *
//* Software Foundation, either version 3 of the License, or (at your option)    *
//* any later version.                                                           *
//*                                                                              *
//* This program is distributed in the hope that it will be useful, but          *
//* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY   *
//* or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License     *
//* for more details.                                                            *
//*                                                                              *
//* You should have received a copy of the GNU General Public License along      *
//* with this program.  If not, see <http://www.gnu.org/licenses/>.              *
//*                                                                              *
//********************************************************************************
//* Version History:                                                             *
//* ================                                                             *
//* v: 0.00.07 20-Dec-2025                                                       *
//*    -- The tempfile manager "TFMan.hpp":                                      *
//*       This class has proven to be surprisingly useful, and may be            *
//*       incorporated into other projects; therefore a stand-alone package is   *
//*       under development. This includes a simple demo app. TFMan and the      *
//*       TFMTest demo app are still included as an integral part of the AnsiCmd *
//*       package.                                                               *
//*    -- Add a conditional compile directive, 'DEBUG_ANSICMD_TEST' to isolate   *
//*       compilation of the AnsiCmd Test suite located in AnsiCmdTest.cpp.      *
//*       This update allows the CsvView, AcEdit and other secondary demo apps   *
//*       to be compiled without the test suite which is unnecessary for these   *
//*       secondary apps.                                                        *
//*    -- Reconfigure the softWrite() method from 'private' to 'protected'.      *
//*       This makes it available to the ACWin class.                            *
//*    -- Implement a new method: acWait() which waits a specified amount of     *
//*       time or until user presses a key, whichever comes first.               *
//*    -- Update documentation.                                                  *
//*    -- Posted to website: 02-Apr-2026                                         *
//*                                                                              *
//* v: 0.00.06 24-Mar-2025                                                       *
//*    -- The beta-test release of the gString class is now stable, so all beta  *
//*       gString references have been updated to reflect the mainline code.     *
//*    -- Add the acPause method to capture a single keystroke from the input    *
//*       stream. (keycode is discarded)                                         *
//*    -- Integrate the TFMan class which handles creation and deletion of       *
//*       temporary files.                                                       *
//*    -- Integrate the WaylandCB class which provides a communications channel  *
//*       with the system clipboard.                                             *
//*    -- Encapsulate all methods of AnsiCmdTest.cpp under both the              *
//*       DEBUG_ANSICMD && INCLUDE_API directives. This allows the library to    *
//*       build when the INCLUDE_API directive is reset.                         *
//*    -- Improve capture and handling of stand-alone Escape key.                *
//*    -- Implement an overload for acSetFgBg() which takes an acAttr bitfield   *
//*       as an argument. While the acAttr attribute group is designed for use   *
//*       within the API/ACWin/ACDlg method group, the bitfield encoding makes   *
//*       for considerably cleaner application code (if you like that sort of    *
//*       thing). This overload required that the protected setAttributes()      *
//*       method be moved outside the API conditional compilation directive.     *
//*    -- Implement acShellOut() method which temporarily returns control to     *
//*       the terminal shell program.                                            *
//*    -- Update documentation.                                                  *
//*    -- limited release                                                        *
//*                                                                              *
//* v: 0.00.05 21-Aug-2023                                                       *
//*    -- For the 'noEcho' input option, all "special" keycodes are now reported *
//*       as valid keycodes. See 'specialkeyDecode' and 'isSpecialKey' methods   *
//*       for more information.                                                  *
//*    -- Add method acGetEchoOption which returns the current input stream      *
//*       echo option.                                                           *
//*    -- Add AnsiCmd-class acFormatParagraph method and the ACWin-class         *
//*       FormatParagraph method which performs automatic line breaks in a text  *
//*       string so the text will fit into a specified width and height.         *
//*       -- Note: Both of these methods are actually just pass-throughs to      *
//*          the underlying gString-class method which does the actual work.     *
//*    -- Add additional tests to exercise the new and/or modified functionality *
//*       of the library.                                                        *
//*    -- Update and expand "Special" keys captured and converted.               *
//*       See specialkeyDecode().                                                *
//*    -- Capture and decode as many of the ASCII control codes as possible.     *
//*       These codes may then be used as application-specific commands.         *
//*       -- Special codes not included in this list: Escape (1Bh), Null (00h),  *
//*          Ctrl+H (Backspace), Ctrl+I (Tab), Newline (0Ah, incl. Ctrl+J        *
//*          and CtrlM).                                                         *
//*       -- Note that four control codes are used as signals by the terminal:   *
//*          Ctrl+C (03h) Break signal,       Ctrl+Z (1Ah) Stop signal           *
//*          Ctrl+S (13h) Pause the output    Ctrl+Q (11h) Resume the output     *
//*          Ctrl+4 (1Ch) Quit (also Ctrl+\)                                     *
//*          Capture and rerouting of these signals is done where practical.     *
//*       -- Redefine the ASCII control code test to exercise the new method     *
//*          of capture and decoding of these codes.                             *
//*    -- Capture the escape sequences associated with Left Alt key in           *
//*       combination with alpha and punctuation keys.                           *
//*       -- To fascilitate this development, a new test method: Test_RawInput() *
//*          was created.                                                        *
//*    -- Add isCommandKey() method to identify the converted escape sequences   *
//*       and control codes which are not included among the special keycodes.   *
//*    -- Implement 'cchtCopy' of enum CCH_Type. This captures the break signal  *
//*       (Ctrl+C) and converts the control signal to an ordinary keycode        *
//*       which can be interpreted as the copy-to-clipboard command.             *
//*    -- Implement capture of the "quit" signal (Ctrl+4). Instead of simply     *
//*       killing the application and leaving a pile of smoking rubble           *
//*       i.e. a core dump, by default, this signal now performs an orderly      *
//*       shutdown: deletes temp files, releases dynamic memory allocation and   *
//*       displays a message at the bottom of the terminal window.               *
//*    -- Renamed acEchoOption() to acSetEchoOption() to conform to established  *
//*       naming style.                                                          *
//*    -- Revise and expand specialkeyProc() method for handling the "special"   *
//*       keycodes.                                                              *
//*    -- Add the conditional compilation directive INCLUDE_API to isolate the   *
//*       API methods and data (this includes the ACWin class).                  *
//*    -- Redesign the ttyWrite(const char*, bool) overload.                     *
//*       If multi-byte UTF-8 characters are written to the wide stream, they    *
//*       will not be rendered correctly (if at all). For this reason, for       *
//*       wide-stream output of UTF-8 data only: we convert the data to wchar_t  *
//*       (UTF-32) before sending it to stdout. This includes both "char*" and   *
//*       "uint8_t" source data. Note that UTF-8 data are not actually 'char'    *
//*       data; but are in fact, unsigned char data.                             *
//*       -- Add an overload to the ttyWrite method for uint8_t data because     *
//*          without an explicit cast, the compiler chooses the wrong overload.  *
//*       -- Update the test suite to exercise the new functionality.            *
//*          Output test: epts --test=To                                         *
//*       -- Note that the "acWrite" method group routes all input through       *
//*          the gString object, which avoids the wide/narrow problem.           *
//*    -- Add include file: "cstdint". This was made necessary by changes to     *
//*       the GNU compiler for C++13, which no longer automatically includes     *
//*       cstdint within cstdlib.                                                *
//*    -- Integrate the AcEdit demo application into the AnsiCmd package.        *
//*       This application creates an ACWin object directly, without the         *
//*       intermediate step of creating an AnsiCmd object.                       *
//*       The primary purpose of this application is to test the various ways    *
//*       in which the skForm/skField classes can be used to create a user       *
//*       interface for an application. (unfinished)                             *
//*    -- Bug Fix: For acSetFg() and acSetBg(), include support for setting      *
//*       AIX attributes directly. Note that this makes the acSetAixFg() and     *
//*       acSetAixBg() redundant, so they have been removed.                     *
//*       Modify existing code:                                                  *
//*       acSetAixFg( aesAIXFGb_BLUE );  becomes  acSetFg( aesAIXFGb_BLUE );     *
//*    -- Bug Fix: In acClearArea(), if 'height' parameter was one(1), we got    *
//*       an endless loop. (Bad programmer! Go to your room! :-)                 *
//*    -- Redefine acWrite() method to keep output within the terminal window.   *
//*    -- Integrate the experimental BString class which will eventually be      *
//*       re-integrated into the gString class when the code is stable.          *
//*    -- Decode the stand-alone escape key (1Bh) as wcsESCAPE.                  *
//*    -- Add support for capture of escape sequences for the Function keys,     *
//*       F01-F12 and their conditioned variation.                               *
//*    -- More robust support for keycodes generated by the keyboard controller's*
//*       auto-repeat function.                                                  *
//*    -- Note: For future releases, changes to the ACWin class and its support  *
//*             classes will be described at the top of the AnsiCmdWin.cpp       *
//*             module. The ACWin class now also has an independent version      *
//*             number.                                                          *
//*    -- Update documentation.                                                  *
//*    -- No public release.                                                     *
//*                                                                              *
//* v: 0.00.04 21-Jul-2023                                                       *
//*    -- Completed testing of conversion between setting attributes using ANSI  *
//*       escape sequences and using bit-mapped settings.                        *
//*    -- Completed implementation of RGB foreground/background attributes.      *
//*    -- Remove the 'normText' flag from the acaExpand class. It was unused.    *
//*    -- Implemented mix-and-match foreground/background color attributes.      *
//*        Previously, if foreground was indexed, the background also had to be  *
//*        indexed. If foreground was RGB, then background also had to be RGB.   *
//*        Foreground and background settings are now independent.               *
//*    -- Implement ACWin method DrawLine(). This was formerly a stub.           *
//*    -- Add a "soft return" key to the group of special keys.                  *
//*       Alt+Enter is recognized within multi-row fields of an skForm object,   *
//*       and inserts a newline into the text. Note that under Wayland, currently*
//*       Alt+Enter, Alt+Shift+Enter, Alt+Ctrl+Enter and Alt+Ctrl+Shift+Enter    *
//*       all return the same hex sequence (1B 0A).                              *
//*    -- Bug Fix: Corrected off-by-one error in acDrawLine().                   *
//*    -- Bug Fix: Corrected error when drawing invisible window border.         *
//*    -- Cleaned up and/or removed some temporary debugging code.               *
//*    -- Major sections of the documentation are now complete, but are subject  *
//*       to change as the project moves forward.                                *
//*    -- Posted to website: 22-Aug-2023                                         *
//*                                                                              *
//* v: 0.00.03 28-Mar-2023                                                       *
//*    -- Implement the first effort at the ACWin class which defines a simple   *
//*       "window" or dialog. This is not intended as a full-featured dialog     *
//*       as implemented in the author's NcDialog API which is based on the      *
//*       ncurses library. Instead, it is a lean, no-nonsense window for         *
//*       display of information and basic interaction with the user.            *
//*    -- Clean up and formalize definitions for command-line arguments used     *
//*       to configure the terminal. This includes a stand-alone function,       *
//*       "ansicmdSetup()" which converts the command-line text to               *
//*       configuration parameters in a TermConfig structure which is then       *
//*       used to configure the terminal environment for the application.        *
//*    -- Greatly expand the test code to cover all aspects of the AnsiCmd       *
//*       and ACWin functionality.                                               *
//*    -- Implement a non-blocking read in order to perform look-ahead into      *
//*       the input stream. Useful for capture of ANSI escape sequences.         *
//*    -- Speed enhancement for acWrite() group.                                 *
//*    -- Implement the AC_Box class. Previously it was just a stub.             *
//*       This is a greatly-simplified version of the ACWin class which          *
//*       provides the LOOK of a window, but with only a very basic              *
//*       functionality. See the Test_Box() method for details.                  *
//*    -- ACWin class is now stable enough to integrate it as an output option   *
//*       for the EarthPoint test app.                                           *
//*    -- Limited distribution.                                                  *
//*                                                                              *
//* v: 0.00.02 25-Nov-2022                                                       *
//*    -- Expand terminal setup options (TermConfig class).                      *
//*    -- Add support for setting foreground/background by specifying the        *
//*       RGB component colors.                                                  *
//*    -- Outline some higher-level methods for simplified access from the       *
//*       application. (not fully implemented)                                   *
//*    -- Support capture of break key, Ctrl+C.                                  *
//*    -- Support setting cursor style.                                          *
//*    -- Implement a local sleep (pause) method using nanosleep().              *
//*       See the nsleep() method for details.                                   *
//*    -- Remove some experimental code.                                         *
//*    -- Isolate help text for command-line options: '--term' and '--test'.     *
//*    -- Squash bugs, (how did they get into the code? My code!?! 这不可能！).       *
//*    -- Clean up comments.                                                     *
//*    -- General layout of formal documentation.                                *
//*    -- Limited distribution.                                                  *
//*                                                                              *
//* v: 0.00.01 12-Oct-2022                                                       *
//*    -- First Effort.                                                          *
//*       - Define the basic class functionality for implementation of the       *
//*         ANSI commands, most of which are defined as "escape sequences".      *
//*    -- Limited distribution.                                                  *
//*                                                                              *
//*                                                                              *
//********************************************************************************
//* Notes:                                                                       *
//* ------                                                                       *
//* -- Practically speaking, when modifying terminal settings, it doesn't        *
//*    matter whether the stdIn, stdOut or stdErr stream is referenced because   *
//*    stdIn/stdOut/stdErr are not actual file descriptors. Instead, they        *
//*    reference the underlying terminal-attributes structure.                   *
//*    See note in setTermInfo() method of the TermSet class.                    *
//*                                                                              *
//* -- Use of zero-based vs. one-based cursor positioning:                       *
//*    Gnometerm (v:3.40.3), Konsole (v:21.08.3) and Xterm (v:366) all           *
//*    accept zero as the target row or column, however:                         *
//*    - When a 0 row is specified, and subsequently a 1 row is specified,       *
//*      that the 1th row overwrites the 0th row.                                *
//*    - It appears that when a 0 column is specified, it is silently            *
//*      converted to 1.                                                         *
//*    For this reason, all methods of the AnsiCmd class which accept            *
//*    row/column parameters, silently convert zero(0) values to one(1) values.  *
//*                                                                              *
//* -- ANSI escape sequences should not be used in multi-threaded applications   *
//*    due to timing issues and resource conflicts. For multi-threaded           *
//*    applications, the author recommends our NcDialog API, which is a fully    *
//*    thread-safe ncurses-based library for creating formatted-text console     *
//*    applications.                                                             *
//*                                                                              *
//* -- In C++, the accepted way to delay the action is to use the 'chrono'       *
//*    class to define the delay, and the 'sleep_for' method to execute it.      *
//*    However, ANSI escape sequences are inherently incompatible with a         *
//*    multi-threaded environment. For this reason, the AnsiCmd class uses       *
//*    the C-language nanosleep() function. This function is technically         *
//*    sophisticated, but clunky in implementation.                              *
//*    See the AnsiCmd implementation of nanosleep(): nsleep() for details.      *
//*                                                                              *
//*                                                                              *
//*                                                                              *
//*  ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----  *
//* ANSI Colors (3-bit and 4-bit colors) are specified using escape sequences.   *
//* Examples:                                                                    *
//* ---------                                                                    *
//* Default foreground color specified by: "\x1B[0;39m"                          *
//* Default background color specified by: "\x1B[0;49m"                          *
//* Red foreground specified by          : "\x1B[22;31m"                         *
//* Red background specified by          : "\x1B[22;41m"                         *
//*                                                                              *
//*  ** Notes on the "Escape" designator within an ANSI sequence:                *
//*       "\x1B["   hexadecimal escape sequence (preferred for C/C++)            *
//*       "\u001B[" unicode escape sequence                                      *
//*       "\033["   octal escape sequence   (so 1970s)                           *
//*       "\27["    decimal escape sequence (idiocy)                             *
//*                                                                              *
//* For 8-bit color, the color may be selected by indexing a color table:        *
//*  \x1B[38;5;⟨n⟩m Select foreground color                                      *
//*  \x1B[48;5;⟨n⟩m Select background color                                      *
//*  where 'n' is:                                                               *
//*    0-  7:  standard colors (as in ESC [ 30–37 m)                             *
//*    8- 15:  high intensity colors (as in ESC [ 90–97 m)                       *
//*   16-231:  6 × 6 × 6 cube (216 colors): 16 + 36 × r + 6 × g + b              *
//*            (0 ≤ r, g, b ≤ 5)                                                 *
//*  232-255:  grayscale from dark to light in 24 steps                          *
//*                                                                              *
//* For 24-bit color, the color is specified by the red, green, and blue         *
//* register combination to be used. Each value in the range: 16-231 (or 0).     *
//* See also selection of "web-safe" RGB color.                                  *
//********************************************************************************
//* To Do:                                                                       *
//* ======                                                                       *
//* -- Implement configuration file and interface.                               *
//* -- Research program control over system speaker. (see acBeep())              *
//* -- See 'atexit' function which performs clean-up for a "normal"              *
//*    application exit. "Normal" in this case means exiting the application     *
//*    in response to a BREAK signal (CTRL+C, etc.) rather than the usual        *
//*    call to the class destructor(s).                                          *
//* -- Additional cursor shapes and colors may be available. Needs research.     *
//* -- Control key auto-repeat. Repeat should stop immediately when key is       *
//*    released; however, there may be several keycodes already in the stream    *
//*    so they spew out. How to capture the key release?                         *
//*    -- See "xset -r off" and "xset -r on" to disable/enable key repeat.       *
//*       Unfortunately, xset only works with X11 clients under Wayland.         *
//*                                                                              *
//* -- Update documentation.                                                     *
//* -- WaylandCB - (AcEdit app only, not integrated into library)                *
//*    -- Integrate WaylandCB documentation as part ofthe AcEdit application.    *
//* --                                                                           *
//*                                                                              *
//* --                                                                           *
//*                                                                              *
//* --                                                                           *
//*                                                                              *
//* --                                                                           *
//*                                                                              *
//* --                                                                           *
//*                                                                              *
//* --                                                                           *
//*                                                                              *
//*                                                                              *
//********************************************************************************

//*****************
//* Include Files *
//*****************
#include "AnsiCmd.hpp"     //* AnsiCmd-class definition

//***************
//* Definitions *
//***************
#if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0
//* An output file-stream object and text formatting tool for debug file.*
//* Defined here and declared extern in all other modules.               *
const char* dbgLog = "dbg.log" ;
ofstream ofsdbg ;
gString gsdbg ;
#endif   // DEBUG_ANSICMD && DEBUG_LOG

//* Timing parameters for internal calls to non-blocking read: acReadNB().*
#define NB_REPEAT (10)     // non-blocking loop count
#define NB_DELAY  (100)    // non-blocking delay (100 milliseconds, 0.1 sec.)

//**************
//* Local data *
//**************
static const char *AnsiCmdVersion = "0.0.07" ;   // AnsiCmd version

//* Number of active instances of the AnsiCmd class and classes derived *
//* from it (ACWin objects). Referenced by the constructor and the      *
//* destructor to avoid redundant initializations or premature release  *
//* of resources by derived class objects.                              *
static short instanceCount = 0 ;

//* Maximum number of bytes to read when capturing an ANSI escape       *
//* sequence. Note: no known escape sequence is longer than 16 bytes.   *
static const short MAX_SEQ_BYTES = 32 ;

//* USE CARE!  Pointer to the active AnsiCmd object. THIS IS DANGEROUS! *
//* 1) The non-member signal-handler methods, BreakSignal_Handler(),    *
//*    SuspendSignal_Handler() and QuitSignal_Handler() use this        *
//*    pointer to call the actual (member) signal handler.              *
//* 2) The TermSet object uses this pointer only to write ANSI escape   *
//*    sequences.                                                       *
//* 3) The ACWin constructor uses this pointer to get a copy of the     *
//*    protected data members. It then uses this pointer to point to    *
//*    itself until the ACWin destructor is called, at which time it    *
//*    restores the original value.                                     *
//*            !!! None of these modifies AnsiCmd data. !!!             *
AnsiCmd *acObjPtr = NULL ;

//********************
//* Local prototypes *
//********************
//* Non-member method. When the Ctrl+C key signal is under application  *
//* control, this is the default target method registered as the signal *
//* handler. See acCaptureBreakSignal() method for more information.    *
static void BreakSignal_Handler ( int sigNum ) ;

//* Non-member method. When the Ctrl+Z key signal is under application  *
//* control, this is the default target method registered as the signal *
//* handler.                                                            *
static void SuspendSignal_Handler ( int sigNum ) ;

//* Non-member method. When the Ctrl+4 (also Ctrl+\) signal is under    *
//* application control, this is the default target method registered   *
//* as the signal handler.                                              *
static void QuitSignal_Handler ( int sigNum ) ;


//**************************************************
//** ANSI commands (escape sequences)             **
//** These are indexed via members of enum aeSeq. **
//**************************************************
#if DEBUG_ANSICMD == 0     // Production (used only within this module)
static const wchar_t *ansiSeq[aesCOUNT] = 
#else                      // Make these visible to the AnsiCmdTest.cpp module
const wchar_t *ansiSeq[aesCOUNT] = 
#endif   // DEBUG_ANSICMD
{
   //** Font-modification Options.       **
   //** (supported by most Linux systems **
   //**  and terminal applications)      **
   L"\x1B[0m",       // aesRESET        -- reset all attributes 
   L"\x1B[1m",       // aesBOLD         -- text attribute modification commands
   L"\x1B[2m",       // aesFAINT
   L"\x1B[3m",       // aesITALIC
   L"\x1B[4m",       // aesUNDERLINE
   L"\x1B[5m",       // aesBLINK_SLOW
   L"\x1B[6m",       // aesBLINK_FAST
   L"\x1B[7m",       // aesREVERSE
   L"\x1B[8m",       // aesCONCEAL
   L"\x1B[9m",       // aesXOUT
   L"\x1B[20m",      // aesFRACTUR
   L"\x1B[21m",      // aesDBL_UNDERLINE (usually implemented same as aesUNDERLINE)
   L"\x1B[51m",      // aesFRAMED
   L"\x1B[52m",      // aesENCIRCLE
   L"\x1B[53m",      // aesOVERLINE
   L"\x1B[22m",      // aesBOLD_OFF (neither bold nor dim)
   L"\x1B[23m",      // aesITALIC_OFF (and Fractur off)
   L"\x1B[24m",      // aesUNDERLINE_OFF
   L"\x1B[25m",      // aesBLINK_OFF
   L"\x1B[27m",      // aesREVERSE_OFF
   L"\x1B[28m",      // aesCONCEAL_OFF
   L"\x1B[29m",      // aesXOUT_OFF
   L"\x1B[54m",      // aesFRAM_ENC_OFF
   L"\x1B[55m",      // aesOVERLINE_OFF

   //** Foreground Color Attributes (3-bit and 4-bit color selection) **
   L"\x1B[22;30m",   // aesFG_BLACK
   L"\x1B[22;31m",   // aesFG_RED
   L"\x1B[22;32m",   // aesFG_GREEN
   L"\x1B[22;33m",   // aesFG_BROWN
   L"\x1B[22;34m",   // aesFG_BLUE
   L"\x1B[22;35m",   // aesFG_MAGENTA
   L"\x1B[22;36m",   // aesFG_CYAN
   L"\x1B[22;37m",   // aesFG_GREY
   L"\x1B[1;30m",    // aesFGb_BLACK
   L"\x1B[1;31m",    // aesFGb_RED
   L"\x1B[1;32m",    // aesFGb_GREEN
   L"\x1B[1;33m",    // aesFGb_BROWN
   L"\x1B[1;34m",    // aesFGb_BLUE
   L"\x1B[1;35m",    // aesFGb_MAGENTA
   L"\x1B[1;36m",    // aesFGb_CYAN
   L"\x1B[1;37m",    // aesFGb_GREY
   L"\x1B[22;24;25;27;39m", // aesFG_DFLT

   //** Background Color Attributes (3-bit and 4-bit color selection) **
   L"\x1B[22;40m",   // aesBG_BLACK
   L"\x1B[22;41m",   // aesBG_RED
   L"\x1B[22;42m",   // aesBG_GREEN
   L"\x1B[22;43m",   // aesBG_BROWN
   L"\x1B[22;44m",   // aesBG_BLUE
   L"\x1B[22;45m",   // aesBG_MAGENTA
   L"\x1B[22;46m",   // aesBG_CYAN
   L"\x1B[22;47m",   // aesBG_GREY
   L"\x1B[1;40m",    // aesBGb_BLACK
   L"\x1B[1;41m",    // aesBGb_RED
   L"\x1B[1;42m",    // aesBGb_GREEN
   L"\x1B[1;43m",    // aesBGb_BROWN
   L"\x1B[1;44m",    // aesBGb_BLUE
   L"\x1B[1;45m",    // aesBGb_MAGENTA
   L"\x1B[1;46m",    // aesBGb_CYAN
   L"\x1B[1;47m",    // aesBGb_GREY
   L"\x1B[22;24;25;27;49m", // aesBG_DFLT

   //** Indexed Color Attribute Options (8-bit color and greyscale) **
   //*  0 - 7     basic standard color indices
   //*  8 - 15    basic intense colors indices
   //*  16 - 231  216 8-bit color indices
   //*  232 - 255 greyscale color indices (24 steps)
   L"\x1B[38;5;%hhum",// aesFG_INDEX -- Template (receives 8-bit index, range:16-231)
   L"\x1B[48;5;%hhum",// aesBG_INDEX -- Template (receives 8-bit index, range:16-231)

   //** Red/Green/Blue Color Attribute Options (24-bit color) **
   //*  Encoding. Fg: \x1B[38;2;(r);(g);(b)m  Bg: \x1B[48;2;(r);(g);(b)m
   L"\x1B[38;2;%hhu;%hhu;%hhum", // aesFG_RGB -- Template (receives 8-bit r, g, and b values)
   L"\x1B[48;2;%hhu;%hhu;%hhum", // aesBG_RGB -- Template (receives 8-bit r, g, and b values)

   //* Cursor-positioning Options       *
   //* Note: Requires 16-bit parameters.*
   L"\x1B[H",        // aesCUR_HOME,         set cursor at 0,0
   L"\x1B[%hd;%hdH", // aesCUR_ABSPOS, L"\x1B[%hd;%hdf"  set cursor at specified row;column
   L"\x1B[%hdA",     // aesCUR_ROWSUP,       move cursor upward by specified number of rows
   L"\x1B[%hdB",     // aesCUR_ROWSDN,       move cursor downward by specified number of rows
   L"\x1B[%hdC",     // aesCUR_COLSRIGHT,    move cursor right by specified number of columns
   L"\x1B[%hdD",     // aesCUR_COLSLEFT,     move cursor left by specified number of columns
   L"\x1B[%hdE",     // aesCUR_NEXTROW,      move cursor to beginning of line below (or downward line count?)
   L"\x1B[%hdF",     // aesCUR_PREVROW,      move cursor to beginning of line above (or upward line count?)
   L"\x1B[%hdG",     // aesCUR_ABSCOL,       move cursor to specified column on current line
   L"\x1B[6n",       // aesCUR_REPORT,       request current cursor position reported as "\x1B[#;#R"
   L"\x1B[M",        // aesCUR_ROWDEL,       delete the current row (scrolling the lower rows upward)
   L"\x1B[7",        // aesCUR_SAVE_DEC,     save current cursor position (DEC)
   L"\x1B[8",        // aesCUR_RESTORE_DEC,  restore cursor to last saved position (DEC)
   L"\x1B[s",        // aesCUR_SAVE_SCO,     save current cursor position (SCO, unreliable)
   L"\x1B[u",        // aesCUR_RESTORE_SCO,        restore cursor to last saved position (SCO, unreliable)

   //* Text Erasure Options *
   L"\x1B[0J",       // aesERASE_BOW,        erase from cursor to bottom of window
   L"\x1B[1J",       // aesERASE_TOW,        erase from cursor to top of window
   L"\x1B[2J",       // aesERASE_WIN,        erase entire window ('clear')
   L"\x1B[3J",       // aesERASE_SAVED,      erase "saved" lines
   L"\x1B[0K",       // aesERASE_EOL,        erase from cursor to end of line
   L"\x1B[1K",       // aesERASE_BOL,        erase from cursor to beginning of line
   L"\x1B[2K",       // aesERASE_LINE,       erase entire line

   //* Alternate Font group *
   L"\x1B[10m",      // aesPRIMARY_FONT   -- (font 00)
   L"\x1B[%hhum",    // aesALTERNATE_FONT -- Template (font 01 through font 9)

   //* ASCII Control Codes *
   L"\x07",          // aesBELL,             '\a' terminal "bell" (beep, ping, etc.)
   L"\x08",          // aesBKSP,             '\b' backspace
   L"\x09",          // aesHTAB,             '\t' horizontal tab
   L"\x0A",          // aesLINEFEED,         '\n' linefeed (end-of-line)
   L"\x0B",          // aesVTAB,             '\v' vertical tab
   L"\x0C",          // aesFORMFEED,         '\f' formfeed (new page)
   L"\x0D",          // aesRETURN,           '\r' carriage return (used by Windoze)
   L"\x1B",          // aesESC,              '\e' escape character ( ^[ or \x1B[ )
   L"\x07F",         // aesDEL,              delete character under cursor

   //* Ideogram (logogram) group of options *
   L"\x1B[60m",      // aesIDEO_UNDER,       ideogram underlined
   L"\x1B[61m",      // aesIDEO_UDOUBLE,     ideogram double-underlined
   L"\x1B[62m",      // aesIDEO_OVER,        ideogram overlined
   L"\x1B[63m",      // aesIDEO_ODOUBLE,     ideogram double-overlined
   L"\x1B[64m",      // aesIDEO_STRESS,      ideogram stress marking
   L"\x1B[65m",      // aesIDEO_OFF,         all ideogram attributes off

   //* NOTE: The Aixterm-specific bright fg/bg codes are not part of the    *
   //*       ANSI standard, but are widely supported by terminal emulators. *
   L"\x1B[90m",      // aesAIXFGb_BLACK,     set foreground color to bright black (grey)
   L"\x1B[91m",      // aesAIXFGb_RED,       set foreground color to bright red
   L"\x1B[92m",      // aesAIXFGb_GREEN,     set foreground color to bright green
   L"\x1B[93m",      // aesAIXFGb_BROWN,     set foreground color to yellow (bright brown)
   L"\x1B[94m",      // aesAIXFGb_BLUE,      set foreground color to bright blue
   L"\x1B[95m",      // aesAIXFGb_MAGENTA,   set foreground color to bright magenta
   L"\x1B[96m",      // aesAIXFGb_CYAN,      set foreground color to bright cyan
   L"\x1B[97m",      // aesAIXFGb_GREY,      set foreground color to white (bright grey)

   L"\x1B[100m",     // aesAIXBGb_BLACK,     set background color to bright black (grey)
   L"\x1B[101m",     // aesAIXBGb_RED,       set background color to bright red
   L"\x1B[102m",     // aesAIXBGb_GREEN,     set background color to bright green
   L"\x1B[103m",     // aesAIXBGb_BROWN,     set background color to yellow (bright brown)
   L"\x1B[104m",     // aesAIXBGb_BLUE,      set background color to bright blue
   L"\x1B[105m",     // aesAIXBGb_MAGENTA,   set background color to bright magenta
   L"\x1B[106m",     // aesAIXBGb_CYAN,      set background color to bright cyan
   L"\x1B[107m",     // aesAIXBGb_GREY,      set background color to white (bright grey)

   // aesCOUNT
} ;


//*************************
//*        AnsiCmd        *
//*************************
//********************************************************************************
//* Default Constructor: Terminal configuration parameters are set to default    *
//* values. See the Initialization() method for details on default values.       *
//*                                                                              *
//* Programmer's Note: This class should be instantiated before any data is      *
//* sent to the terminal's output stream. This is because the first output       *
//* may override any subsequent output-stream designation for the current        *
//* application.                                                                 *
//*                                                                              *
//* Programmer's Note: The wide stream is strongly recommended for all modern    *
//* applications. The narrow stream is outdated due to its close ties to         *
//* ASCII text.                                                                  *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: implicitly returns pointer to object                                *
//********************************************************************************
   
AnsiCmd::AnsiCmd ( void )
{
   //* Initialize all data members and perform terminal setup.*
   if ( instanceCount++ == ZERO )
   {
      TermConfig tc ;                     // default configuration parameters
      this->Initialize ( tc ) ;
   }
   //* Initialize data members only *
   else
      this->reset () ;

   #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0
   if ( (ofsdbg.is_open()) )
   { ofsdbg << "AnsiCmd::AnsiCmd(dflt)" << endl ; }
   #endif   // DEBUG_ANSICMD && DEBUG_LOG

}

//*************************
//*        AnsiCmd        *
//*************************
//********************************************************************************
//* Initialization Constructor: Terminal configuration parameters are passed     *
//* in the TermConfig object.                                                    *
//*                                                                              *
//* Programmer's Note: This class should be instantiated before any data is      *
//* sent to the terminal's output stream. This is because the first output       *
//* may override any subsequent output-stream designation for the current        *
//* application.                                                                 *
//*                                                                              *
//* Programmer's Note: The wide stream is strongly recommended for all modern    *
//* applications. The narrow stream is outdated due to its close ties to         *
//* ASCII text.                                                                  *
//*                                                                              *
//* Input  : tCfg : (by reference) terminal configuration parameters             *
//*                 See Initialization() method for details on the parameters    *
//*                 passed in the 'tCfg' object.                                 *
//*                                                                              *
//* Returns: implicitly returns pointer to object                                *
//********************************************************************************

AnsiCmd::AnsiCmd ( const TermConfig& tCfg )
{
   //* Initialize all data members and perform terminal setup.*
   if ( instanceCount++ == ZERO )
      this->Initialize ( tCfg ) ;
   else                 // initialize data members only
      this->reset () ;

   #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0
   if ( (ofsdbg.is_open()) )
   { ofsdbg << "AnsiCmd::AnsiCmd(init)" << endl ; }
   #endif   // DEBUG_ANSICMD && DEBUG_LOG

}

//*************************
//*       ~AnsiCmd        *
//*************************
//********************************************************************************
//* Destructor. Return all resources to the system before exit.                  *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

AnsiCmd::~AnsiCmd ( void )
{
   if ( --instanceCount <= ZERO )
   {
      //* Reset foreground/background color attributes to defaults.    *
      this->acSetFgBg ( aesFG_DFLT, aesBG_DFLT ) ;

      //* Flush the Input/Output streams *
      this->acFlushStreams () ;

      //* If a locale object has been allocated, delete it now.        *
      if ( this->ioLocale != NULL )
      {
         delete ( this->ioLocale ) ;
         this->ioLocale = NULL ;
      }

      #if INCLUDE_API != 0
      //* If temp-files have been created, delete them now.*
      this->acDeleteTemppath () ;

      #if INCLUDE_WAYLANDCB != 0
      //* Terminate the system clipboard interface. *
      this->acClipboardDisable () ;
      #endif   // INCLUDE_WAYLANDCB
      #endif   // INCLUDE_API

      //* Release the dynamic allocation. As a side-effect, if this  *
      //* is the only remaining instance, the original terminal      *
      //* settings will be restored.                                 *
      if ( this->tset != NULL )
      {
         delete ( this->tset ) ;
         this->tset = NULL ;
      }

      #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0
      if ( (ofsdbg.is_open()) )
      {
         ofsdbg << "AnsiCmd Log: ~AnsiCmd()" << endl ;
         ofsdbg.close () ;
      }
      #endif   // DEBUG_ANSICMD && DEBUG_LOG
   }  // instanceCount

   #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0
   else if ( (ofsdbg.is_open()) )
   { ofsdbg << "~AnsiCmd(ACWin)" << endl ; }
   #endif   // DEBUG_ANSICMD && DEBUG_LOG

   //* Disable the local object pointer (be safe).*
   if ( instanceCount <= ZERO )
      acObjPtr = NULL ;

}  //* ~AnsiCmd() *

//*************************
//*      Initialize       *
//*************************
//********************************************************************************
//* PRIVATE METHOD:                                                              *
//* Called during instantiation to initialize the data members and the           *
//* terminal environment.                                                        *
//*                                                                              *
//* Members of the TermConfig object:                                            *
//* ---------------------------------                                            *
//* -- termStream  :                                                             *
//*      specify the target stream (member of enum TermStream)                   *
//*      Default: stdIn                                                          *
//* -- configFile  :                                                             *
//*      not yet implemented                                                     *
//* -- localeName  : name of alternate locale filename                           *
//*      Default: "\0" indicates that locale should be set from the              *
//*      terminal environment.                                                   *
//* -- echoOption  :                                                             *
//*      member of enum EchoOpt. termEcho by default                             *
//*      Note that ACWin constructor may override this setting.                  *
//* -- bufferInput :                                                             *
//*      'true'  == terminal control over input buffer (default)                 *
//*      'false' == input buffering disabled.                                    *
//*                 (blocking read remains enabled)                              *
//*      Note that ACWin constructor may override this setting.                  *
//* -- wideStream  :                                                             *
//*      'true'  == wide (32-bit) data streams (wcout, wcin) (default)           *
//*      'false' == narrow (8-bit) data streams (cout, cin)                      *
//* -- nonStandard :                                                             *
//*      'false' == standard terminal support for ANSI commands (default)        *
//*         When 'nonStandard' is reset, it is assumed that the host             *
//*         terminal supports most or all of the ANSI command set.               *
//*      'true'  == non-standard terminal support for ANSI commands              *
//*         Whe 'nonStandard' is set, it indicates that the host terminal        *
//*         fails to support some critical commands of the ANSI command set.     *
//* -- bcType      :                                                             *
//*      member of enum CCH_Type, break signal handler                           *
//*      By default terminal retains control of break signal: cchtTerm           *
//*      Note that ACWin constructor may override this setting.                  *
//* -- bcAlert     :                                                             *
//*      If bcType==cchtIgnore, then indicate whether to sound a beep when       *
//*      break signal captured and discarded.                                    *
//*      'true'  == discard alert enabled (default)                              *
//*      'false' == silently discarded                                           *
//* -- suspendSignal:                                                            *
//*      'true'  == capture suspend signal and convert it to a keycode.(default) *
//*      'false' == terminal processes the suspend signal                        *
//* -- quitSignal:                                                               *
//*      'true'  == capture quit signal and perform orderly shudtown (default)   *
//*      'false' == terminal processes the quit signal (messy)                   *
//* -- cursorStyle :                                                             *
//*      member of enum CurStyle, specifies cursor style                         *
//*      csDflt == use terminal default (default)                                *
//* -- foreground  :                                                             *
//*      member of enum aeSeq aesFG_xxxx    (default: aesFG_DFLT)                *
//* -- background  :                                                             *
//*      member of enum aeSeq aesBG_xxxx    (default: aesBG_DFLT)                *
//*                                                                              *
//*                                                                              *
//*                                                                              *
//* Input  : tCfg : (by reference) terminal configuration parameters             *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::Initialize ( const TermConfig& tCfg )
{
   //* If the wide-stream flag is reset i.e. *
   //* "use narrow I/O streams," exercise    *
   //* 'cout' to lock in the narrow stream.  *
   //* See the ansiTest() method for details.*
   if ( ! tCfg.wideStream )
   { cout.flush() ; }

   //* Pointer to a TFMan-class object. Not yet instantiated.*
   //* Note that temp-file management is compiled under the  *
   //* INCLUDE_API conditional compilation directive.        *
   this->tfm = NULL ;

   //* Set the "locale" (character encoding and text-formatting defaults) *
   //* If 'locale' == "", then take locale from terminal environment.     *
   //* Otherwise set to specified locale.                                 *
   this->ioLocale = NULL ;             // locale not yet specified
   this->acSetLocale ( tCfg.localeName ) ;

   this->wideStream = tCfg.wideStream ; // initialize the input/output stream width
   this->fullReset = tCfg.nonStandard ; // initialize the full-reset flag

   //* Initialize the external object pointer,     *
   //* for access by the non-member signal handler.*
   acObjPtr = this ;

   //* Dynamically allocate and initialize the TermSet object. *
   //* The TermSet class controls the low-level terminal       *
   //* configuration and the data members track the current    *
   //* configuration.                                          *
   //* Optional Configurations:                                *
   //*  1) Break-signal capture (default signal handler)       *
   //*  2) Suspend-signal capture                              *
   //*  3) Quit-signal capture                                 *
   //*  4) Cursor style (default color attribute)              *
   this->tset = new TermSet( tCfg ) ;

   //* Initialize terminal settings to their default values.  *
   //* Note: This call also resets attribute-tracking members.*
   this->acReset () ;

   //* Initialize 'termRows' and 'termCols' members.*
   this->acGetTermSize () ;

   //* If non-default color attributes specified *
   if ( tCfg.background != aesBG_DFLT )
      this->acSetBg ( tCfg.background ) ;
   if ( tCfg.foreground != aesFG_DFLT )
      this->acSetFg ( tCfg.foreground ) ;

   this->acFlushStreams () ;           // flush the input and output streams

   #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0
   if ( !(ofsdbg.is_open()) )
      ofsdbg.open ( dbgLog, ofstream::out | ofstream::trunc ) ;
   if ( (ofsdbg.is_open()) )
   {
      gsdbg.compose( "AnsiCmd Log: Initialize( inpBuf:%hhd "
                     "inpEch:%hd inpBlk:%hhd cchType:%hd, suspCap:%hhd )",
                     &this->tset->inpBuf, &this->tset->inpEch, 
                     &this->tset->inpBlk, &this->tset->cchType, &this->tset->suspCap ) ;
      ofsdbg << gsdbg.ustr() << endl ;
   }
   #endif   // DEBUG_ANSICMD && DEBUG_LOG

}  //* End Initialize() *

//*************************
//*         reset         *
//*************************
//********************************************************************************
//* Reset all member variables to default values.                                *
//* Called only by constructors.                                                 *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::reset ( void )
{
/* Appears to be functional, keep an eye on it. */
   if ( acObjPtr != NULL )
   {
      this->attrBits   = acObjPtr->attrBits ;
      this->termRows   = acObjPtr->termRows ;
      this->termCols   = acObjPtr->termCols ;
      this->fontNum    = acObjPtr->fontNum ;
      this->tset       = acObjPtr->tset ;
      this->ioLocale   = acObjPtr->ioLocale ;
      this->tfm        = acObjPtr->tfm ;
      this->fullReset  = acObjPtr->fullReset ;
      this->wideStream = acObjPtr->wideStream ;
   }
   else
   { /* this should never happen */ }

}  //* End reset() *

//*************************
//*        acReset        *
//*************************
//********************************************************************************
//* Return all ANSI attributes to terminal defaults.                             *
//* Set all attribute-tracking members to their default values.                  *
//* Flush the input and output buffers.                                          *
//*                                                                              *
//* This method does not modify the terminal configuration setup.                *
//* It resets only the ANSI control environment.                                 *
//*                                                                              *
//* Programmer's Note: It is not possible to programatically check whether       *
//* each attribute was actually reset; however, the ANSI reset code should be    *
//* trustworthy.                                                                 *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: 'true'                                                              *
//********************************************************************************

bool AnsiCmd::acReset ( void )
{
   bool status = true ;                // return value

   //* Perform full reset of terminal color/text attributes *
   this->ttyWrite ( ansiSeq[aesRESET] ) ;

   //* Return all tracking members to default values *
   this->attrBits.reset () ;

   this->fontNum = primeFONT ;         // using primary font

   this->acFlushStreams () ;           // flush the input and output streams

   return status ;

}  //* End acReset() *

//*************************
//*       acSetFgBg       *
//*************************
//********************************************************************************
//* Set text foreground AND background colors (3-bit and 4-bit color).           *
//*                                                                              *
//* Input  : fg   : foreground color attribute (member of aeSeq)                 *
//*          bg   : background color attribute (member of aeSeq)                 *
//*                                                                              *
//* Returns: 'true'  if valid color attributes specified and set                 *
//*          'false' if invalid parameter(s) specified                           *
//*                  (attributes will not be modified)                           *
//********************************************************************************
//* Programmer's Note: The foreground is set AFTER the background so that if     *
//* the 'bold' flag is used in the foreground escape sequence, it will not be    *
//* masked by a subsequent non-bold background.                                  *
//* Note also that bold background color escape sequences include the 'bold'     *
//* flag, but this affects only the foreground attribute.                        *
//*                                                                              *
//* Exception: "synthesized" bold backgrounds are used by default, which         *
//* bypasses the 'bold' flag problem by using a different member from the        *
//* color lookup table which is outside the basic 16 (4-bit) color group.        *
//* Note that tracking data do not know anything about synthesized backgrounds.  *
//********************************************************************************

bool AnsiCmd::acSetFgBg ( aeSeq fg, aeSeq bg )
{
   bool status = false ;         // return value

   if ( (this->acSetBg ( bg )) != false )
      status = this->acSetFg ( fg ) ;

   return status ;

}  //* End acSetFgBg() *

//*************************
//*       acSetFgBg       *
//*************************
//********************************************************************************
//* Set text foreground background colors encoded as an acAttr bitmap value.     *
//* Although the acAttr group of color attribute bitfields is defined for use    *
//* within the AnsiCmd API; this method allows for color attributes to be        *
//* specified using these encoded bitfield attributes regardless of whether      *
//* the API code is compiled into the library.                                   *
//*                                                                              *
//* The specified value may be created directly as a logical OR of multiple      *
//* members of enum acAttr.                                                      *
//* Example: acAttr( acaFG_GREEN | acaBG_BLACK | acaUNDERLINE)                   *
//*                                                                              *
//* The bitfield value may also be built by a call to one of the acaExpand-class *
//* methods, such as:                                                            *
//*    acaExpand acax ;                                                          *
//*    acax.build8bit( bool fg, uint8_t fgVal, bool bg, uint8_t bgVal );         *
//*    acax.buildRgb( bool fg, WebSafeRGB fgVal, bool bg, WebSafeRGB bgVal );    *
//*    acax.update( aeSeq aesValue, uint8_t colorIndx );                         *
//*    acax.modify( acAttr acaValue, bool add );                                 *
//* Please see documentation for a full discussion of the acsExpand class.       *
//*                                                                              *
//* Note that this is a simplified version of the API method: acSetAttributes(), *
//* which is preferred when the API methods are available.                       *
//* This method should be used only if the library has been compiled without     *
//* the API method group.                                                        *
//*                                                                              *
//* Input  : fgbg : foreground/background/modifiers encoded as an acAttr bitfield*
//*                                                                              *
//* Returns: 'true'                                                              *
//********************************************************************************
//* Programmer's Note: This method required that the protected 'setAttributes'   *
//* method be moved outside the API conditional compile directive.               *
//*   Confession: While this is only 30 lines of additional code in the base     *
//*   library, (see below), it did cause the author a small twinge, since he     *
//*   is an efficiency-oriented assembly-language programmer at heart.           *
//********************************************************************************

bool AnsiCmd::acSetFgBg ( acAttr fgbg )
{
   //* Decode the bitfield attribute, then invoke *
   //* the appropriate low-level method(s).       *
   acaExpand acax( fgbg ) ;
   this->setAttributes ( acax, true, true, true ) ;
   return true ;

}  //* End acSetFgBg() *

//*************************
//*     setAttributes     *
//*************************
//********************************************************************************
//* Protected Method:                                                            *
//* -----------------                                                            *
//* Set the current attributes using the provided, fully-decoded bitfield data.  *
//*                                                                              *
//* At this level, the target area is irrelevant, but caller's target could      *
//* be any of the following:                                                     *
//*   1) ACWin border                                                            *
//*   2) ACWin interior                                                          *
//*   3) skField                                                                 *
//*   4) terminal window                                                         *
//* Note that the tracking data for the base AnsiCmd-class object                *
//* (attrBits member) will be updated by the low-level calls; however, if the    *
//* target is an ACWin or a skField object, it is the caller's responsibility    *
//* to update the appropriate tracking members.                                  *
//*                                                                              *
//* It is important to note that the background is set _before_ the foreground   *
//* to avoid problems with the terminal's "intense" bit.                         *
//*                                                                              *
//* Note also that there may be some inefficiency or redundancy in the way       *
//* low-level calls are made; however, this avoids unintended side effects.      *
//* For instance, all text modifiers are reset before the foreground attribute   *
//* is set, and the specified modifiers are set afterward.                       *
//*                                                                              *
//* Input  : acaEx   : (by reference) a fully decoded set of attributes          *
//*          setFgnd : (optional, 'true' by default)                             *
//*                    if 'true',  set foreground attributes                     *
//*                    if 'false', foreground attributes unchanged               *
//*          setBgnd : (optional, 'true' by default)                             *
//*                    if 'true',  set background attributes                     *
//*                    if 'false', background attributes unchanged               *
//*          setMods : (optional, 'true' by default)                             *
//*                    if 'true',  set the specified text modifiers              *
//*                    if 'false', text modifiers are unchanged                  *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::setAttributes ( const acaExpand& acaEx, bool setFgnd, 
                              bool setBgnd, bool setMods )
{
   if ( setBgnd )                                  // set target background
   {
      if ( acaEx.bgRgb )                           // RGB bkgnd
            this->acSetWebBg ( acaEx.bgHue, acaEx.bgShade ) ;
      else if ( acaEx.bgIndex >= min8BIT )         // 8-bit bkgnd
         this->acSet8bitBg ( acaEx.bgIndex ) ;
      else                                         // 4-bit bkgnd
         this->acSetBg ( acaEx.aesBgnd ) ;
   }

   if ( setFgnd || setMods )
   {
      this->acSetMod ( aesRESET ) ;                // clear all modifiers

      if ( setFgnd )
      {
         if ( acaEx.fgRgb )                        // RGB fgnd
            this->acSetWebFg ( acaEx.fgHue, acaEx.fgShade ) ;
         else if ( acaEx.fgIndex >= min8BIT )      // 8-bit fgnd
            this->acSet8bitFg ( acaEx.fgIndex ) ;
         else                                      // 4-bit fgnd
            this->acSetFg ( acaEx.aesFgnd ) ;
      }

      //* Set specified text modifiers (if any) *
      if ( acaEx.modFlags != acaPLAINTXT )
      {
         if ( acaEx.boldMod )                // Bold modifier
            this->acSetMod ( aesBOLD ) ;
         if ( acaEx.italicMod )              // Italic modifier
            this->acSetMod ( aesITALIC ) ;
         if ( acaEx.ulineMod )               // Underline modifier
            this->acSetMod ( aesUNDERLINE ) ;
         if ( acaEx.olineMod )               // Overline modifier
            this->acSetMod ( aesOVERLINE ) ;
         if ( acaEx.xoutMod )                // Xout (strikethrough) modifier
            this->acSetMod ( aesXOUT ) ;
         if ( acaEx.blinkMod )               // Blink modifier
            this->acSetMod ( aesBLINK_SLOW ) ;
         if ( acaEx.invisMod )               // Invisible modifier
            this->acSetMod ( aesCONCEAL ) ;
         if ( acaEx.revMod )                 // Reverse modifier
            this->acSetMod ( aesREVERSE ) ;
      }
   }

}  //* End setAttributes() *

//*************************
//*     setAttributes     *
//*************************
//********************************************************************************
//* Protected Method:                                                            *
//* -----------------                                                            *
//* Set the current attributes using the provided, fully-decoded bitfield data.  *
//*                                                                              *
//* Technical Note: For some reason, the GNU compiler (v:14.3.1 20251022)        *
//* is unable to distinguish between the parameters:                             *
//*    ( const acaExpand& acaEd, ... )  and                                      *
//*    ( acAttr acaBits, ... )                                                   *
//* For this reason, this secondary overload is provided to compensate.          *
//*                                                                              *
//* Input  : acAttr  : a bitfield attribute value                                *
//*          setFgnd : (optional, 'true' by default)                             *
//*                    if 'true',  set foreground attributes                     *
//*                    if 'false', foreground attributes unchanged               *
//*          setBgnd : (optional, 'true' by default)                             *
//*                    if 'true',  set background attributes                     *
//*                    if 'false', background attributes unchanged               *
//*          setMods : (optional, 'true' by default)                             *
//*                    if 'true',  set the specified text modifiers              *
//*                    if 'false', text modifiers are unchanged                  *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::setAttributes ( acAttr acaval, bool setFgnd, bool setBgnd, bool setMods )
{

   acaExpand acax( acaval ) ;
   this->setAttributes ( acax, setFgnd, setBgnd, setMods ) ;

}  //* End setAttributes() *

//*************************
//*        acSetFg        *
//*************************
//********************************************************************************
//* Set text foreground color attribute (3-bit and 4-bit color).                 *
//*                                                                              *
//* Input  : fg   : foreground color attribute (member of aeSeq)                 *
//*                                                                              *
//* Returns: 'true'  if valid color attributes specified and set                 *
//*          'false' if invalid parameter(s) specified                           *
//*                  (attributes will not be modified)                           *
//********************************************************************************

bool AnsiCmd::acSetFg ( aeSeq fg )
{
   gString gsOut ;               // text formatting
   bool status = false ;         // return value

   //* Perform range check *
   if ( ((fg >= aesFG_BLACK) && (fg <= aesFG_DFLT)) || 
        ((fg >= aesAIXFGb_BLACK) && (fg <= aesAIXFGb_GREY)) )
   {
      if ( this->fullReset!= false )         // call reset if specified
         this->acReset () ;

      gsOut.compose( "%S", ansiSeq[fg] ) ;   // retrieve the specified sequence
      this->ttyWrite ( gsOut ) ;             // write to output stream

      //* Update tracking data to reflect the new settings *
      this->attrBits.update ( fg ) ;

      status = true ;                        // success
   }

   return status ;

}  //* End acSetFg() *

//*************************
//*        acSetBg        *
//*************************
//********************************************************************************
//* Set text background color attribute (3-bit and 4-bit color).                 *
//*                                                                              *
//* Input  : bg   : background color attribute (member of aeSeq)                 *
//*          synth: (optional, 'true' by default)                                *
//*                 if 'true',  use synthesized background color attributes      *
//*                 if 'false', use standard ANSI background color attributes    *
//*                                                                              *
//* Returns: 'true'  if valid color attributes specified and set                 *
//*          'false' if invalid parameter(s) specified                           *
//*                  (attributes will not be modified)                           *
//********************************************************************************
//* Notes on synthesis of intense background color attributes:                   *
//* ----------------------------------------------------------                   *
//* The default color attributes for intense (bold) backgrounds are inadequate   *
//* because the 'intense' bit affects only the foreground color.                 *
//*                                                                              *
//* "Synthesized" bold backgrounds are used by default, which bypasses the       *
//* 'bold' flag problem by using a different member from the color lookup        *
//* table which is outside the basic 16 (4-bit) color group.                     *
//*                                                                              *
//* Note that tracking data do not know anything about synthesized backgrounds.  *
//********************************************************************************

bool AnsiCmd::acSetBg ( aeSeq bg, bool synth )
{
   gString gsOut ;               // text formatting
   bool status = false ;         // return value

   if ( ((bg >= aesBG_BLACK) && (bg <= aesBG_DFLT)) || 
        ((bg >= aesAIXBGb_BLACK) && (bg <= aesAIXBGb_GREY)) )
   {
      if ( this->fullReset != false )        // call reset if specified
         this->acReset () ;

      //* Standard (non-intense) background attributes, or default background *
      if ( ((bg < aesBGb_BLACK) || (bg == aesBG_DFLT)) ||
           ((bg >= aesAIXBGb_BLACK) && (bg <= aesAIXBGb_GREY)) )
      {
         gsOut.compose( "%S", ansiSeq[bg] ) ; // retrieve the specified sequence
      }

      //* Intense background attributes *
      else if ( ((bg >= aesBGb_BLACK) && (bg <= aesBGb_GREY)) )
      {
         //* Synthesize the background color attribute *
         if ( synth != false )
         {
            uint8_t r = min8BIT, g = min8BIT, b = min8BIT ;
            switch ( bg )
            {
               case aesBGb_RED:     r = max8BIT ;           break ;
               case aesBGb_GREEN:   g = max8BIT ;           break ;
               case aesBGb_BLUE:    b = max8BIT ;           break ;
               case aesBGb_BROWN:   r = g = max8BIT ;       break ;
               case aesBGb_MAGENTA: r = b = max8BIT ;       break ;
               case aesBGb_CYAN:    g = b = max8BIT ;       break ;
               case aesBGb_GREY:    r = g = b = max8BIT ;   break ;
               case aesBGb_BLACK:   default:                break ;
            } ;
            gsOut.compose( ansiSeq[aesBG_RGB], &r, &g, &b ) ;
         }

         //* Use standard ANSI commands to set background color attribute *
         else
            gsOut.compose( "%S", ansiSeq[bg] ) ; // retrieve the specified sequence
      }

      //* Write the command and flush output buffer *
      this->ttyWrite ( gsOut ) ;

      //* Update tracking data to reflect the new settings *
      this->attrBits.update ( bg ) ;

      status = true ;
   }

   return status ;

}  //* End acSetBg() *

//*************************
//*       acSetMod        *
//*************************
//********************************************************************************
//* Set/reset text modifier using the specified escape sequence.                 *
//*                                                                              *
//* Input  : mod  : text modification attribute (member of aeSeq)                *
//*                                                                              *
//* Returns: 'true'  if valid modifier attributes specified and set              *
//*          'false' if invalid parameter(s) specified                           *
//*                  (attributes will not be modified)                           *
//********************************************************************************
//* Programmer's Note: The aesRESET value _technically_ refers to a reset        *
//* of ALL ANSI attributes and modifiers; however, within this method,           *
//* aesRESET causes only the text modifiers to be reset.                         *
//********************************************************************************

bool AnsiCmd::acSetMod ( aeSeq mod )
{
   gString gsOut ;               // text formatting
   bool reset_all_mods = false,  // true if full reset performed
        do_update = false,       // 'true' if update of bitfield needed
        status = false ;         // return value

   //* Perform range check *
   if ( (mod >= aesRESET) && (mod <= aesOVERLINE_OFF) )
   {
      if ( this->fullReset || (mod == aesRESET) ) // reset all modifiers
      {
         this->acResetMods ( this->fullReset ) ;
         reset_all_mods = true ;
      }

      //* If command is to _set_ a modifier flag *
      if ( (mod > aesRESET) && (mod < aesBOLD_OFF) )
         do_update = true ;

      //* If command is to _reset_ a modifier flag, *
      //* and full reset not performed above.       *
      else if ( ! reset_all_mods )
         do_update = true ;

      //* Set/reset local flag to indicate the operation performed.*
      if ( do_update )
      {
         gsOut = ansiSeq[mod] ;           // write the ANSI escape sequence
         this->ttyWrite ( gsOut ) ;
         this->attrBits.update ( mod ) ;  // update tracking data
      }
      status = true ;         // success
   }
   return status ;

}  //* End acSetMod() *

//*************************
//*      acResetMods      *
//*************************
//********************************************************************************
//* Reset all text modifiers, leaving the base foreground and background color   *
//* attributes unchanged.                                                        *
//* The text modifier attributes are:                                            *
//*   aesBOLD,                   // bold (increase intensity)                    *
//*   aesFAINT,                  // faint/dim (decrease intensity)               *
//*   aesITALIC,                 // italic (or inverse)                          *
//*   aesUNDERLINE,              // underline                                    *
//*   aesBLINK_SLOW,             // slow blink                                   *
//*   aesBLINK_FAST,             // fast blink                                   *
//*   aesREVERSE,                // reverse foreground and background colors     *
//*   aesCONCEAL,                // hide text                                    *
//*   aesXOUT,                   // crossed out text (marked for deletion)       *
//*   aesFRACTUR,                // a variety of German calligraphy              *
//*   aesDBL_UNDERLINE,          // double-underline (or bold off)               *
//*   aesFRAMED,                 // "frame" the text                             *
//*   aesENCIRCLE,               // "encircle" the text                          *
//*   aesOVERLINE,               // overlined text                               *
//* Calling this method is equivalent to calling acSetMod() nine times,          *
//* once for each of the modifer resets.                                         *
//*   aesBOLD_OFF,               // normal intensity (neither bold nor faint)    *
//*   aesITALIC_OFF,             // italic off (and Fractur off)                 *
//*   aesUNDERLINE_OFF,          // not underlined                               *
//*   aesBLINK_OFF,              // not blinking                                 *
//*   aesREVERSE_OFF,            // not reversed                                 *
//*   aesCONCEAL_OFF,            // conceal off                                  *
//*   aesXOUT_OFF,               // not crossed out                              *
//*   aesFRM_ENC_OFF,            // not framed, not encircled                    *
//*   aesOVERLINE_OFF,           // overline off                                 *
//*                                                                              *
//*                                                                              *
//* Input  : full: (optional, 'false' by default)                                *
//*                if 'true',  write a reset command instead of writing the      *
//*                            ANSI modifier-off sequences.                      *
//*                            This option is available in case the host         *
//*                            terminal does not support some or all of the      *
//*                            modifier-off sequences.                           *
//*                            NOTE: this ALSO resets the color attributes.      *
//*                if 'false', do not write the reset command                    *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::acResetMods ( bool full )
{
   if ( full != false )
      this->acReset () ;
   else
   {
      gString gsOut( "%S%S%S%S%S%S%S%S%S", 
                     ansiSeq[aesBOLD_OFF],      ansiSeq[aesITALIC_OFF], 
                     ansiSeq[aesUNDERLINE_OFF], ansiSeq[aesBLINK_OFF], 
                     ansiSeq[aesREVERSE_OFF],   ansiSeq[aesCONCEAL_OFF], 
                     ansiSeq[aesXOUT_OFF],      ansiSeq[aesFRM_ENC_OFF], 
                     ansiSeq[aesOVERLINE_OFF] ) ;
      this->ttyWrite ( gsOut ) ;

      //* Reset the text-modifier tracking flags *
      this->attrBits.reset ( true ) ;
   }

}  //* End acResetMods() *

//*************************
//*      acSet8bitFg      *
//*************************
//********************************************************************************
//* Set foreground color from 8-bit-color lookup table.                          *
//* Select an index number between min8BIT through max8BIT (16-231).             *
//*                                                                              *
//* Undocumented Feature: We also allow indices 0-15 (3/4-bit color indices)     *
//* and indices 232-255 (greyscale indices) Curious programmer's want to know.   *
//*                                                                              *
//* Input  : fg   : index into the lookup table for 8-bit color attributes       *
//*                                                                              *
//* Returns: 'true'  if valid color attribute specified and set                  *
//*          'false' if invalid parameter(s) specified                           *
//*                  (attributes will not be modified)                           *
//********************************************************************************

bool AnsiCmd::acSet8bitFg ( uint8_t fg )
{

   return ( (this->set8Bit ( false, fg )) ) ;

}  //* End acSet8bitFg

//*************************
//*      acSet8bitBg      *
//*************************
//********************************************************************************
//* Set background color from 8-bit-color lookup table.                          *
//* Select an index number between min8BIT through max8BIT (16-231).             *
//*                                                                              *
//* Undocumented Feature: We also allow indices 0-15 (3/4-bit color indices)     *
//* and indices 232-255 (greyscale indices) Curious programmer's want to know.   *
//*                                                                              *
//* Input  : bg   : index into the lookup table for 8-bit color attributes       *
//*                                                                              *
//* Returns: 'true'  if valid color attribute specified and set                  *
//*          'false' if invalid parameter(s) specified                           *
//*                  (attributes will not be modified)                           *
//********************************************************************************

bool AnsiCmd::acSet8bitBg ( uint8_t bg )
{

   return ( (this->set8Bit ( true, bg )) ) ;

}  //* End acSet8bitBg

//*************************
//*        set8Bit        *
//*************************
//********************************************************************************
//* Private Method:                                                              *
//* ---------------                                                              *
//* Called only by acSet8bitFg() and acSet8bitBg().                              *
//*                                                                              *
//* Set background color from 8-bit-color lookup table.                          *
//* Select an index number between min8BIT through max8BIT (16-231).             *
//*                                                                              *
//* Undocumented Feature: We also allow indices 0-15 (4-bit color indices) and   *
//* indices 232-255 (greyscale indices) Curious programmer's want to know.       *
//* See the "STRICT_RANGECHECK" conditional-compile definition.                  *
//*                                                                              *
//* Input  : bgnd : 'false' == set 8-bit foreground                              *
//*                 'true'  == set 8-bit background                              *
//*        : fgbg : index into the lookup table for 8-bit color attributes       *
//*                                                                              *
//* Returns: 'true'  if valid color attribute specified and set                  *
//*          'false' if invalid parameter(s) specified                           *
//*                  (attributes will not be modified)                           *
//********************************************************************************

bool AnsiCmd::set8Bit ( bool bgnd, uint8_t fgbg )
{
   #define STRICT_RANGECHECK (0)

   gString gsOut ;               // text formatting
   bool status = false ;

   //* Perform range check. (see notes above) *
   #if STRICT_RANGE_CHECK != 0
   if ( (fgbg >= min8BIT) && (fgbg <= max8BIT) )
   #else    // Access full table range
   if ( (fgbg >= minLOOKUP) && (fgbg <= maxLOOKUP) )
   #endif   // STRICT_RANGE_CHECK
   {
      if ( this->fullReset != false ) // if full reset specified
         this->acReset () ;

      if ( bgnd != false )       // set the 8-bit background
         gsOut.compose( ansiSeq[aesBG_INDEX], &fgbg ) ;
      else                       // set the 8-bit foreground
         gsOut.compose( ansiSeq[aesFG_INDEX], &fgbg ) ;
      this->ttyWrite ( gsOut ) ; // write the sequence

      //* Update tracking data to reflect the new settings *
      this->attrBits.update ( bgnd ? aesBG_INDEX : aesFG_INDEX, fgbg ) ;

      status = true ;
   }
   return status ;

   #undef STRICT_RANGECHECK
}  //* End set8Bit()

//*************************
//*      acSetRgbFg       *
//*************************
//********************************************************************************
//* Set foreground color using R/G/B register numbers.                           *
//*                                                                              *
//* Each of the basic Red, Green and Blue color attributes may be displayed      *
//* in any of 216 shades, and the color values may be in any combination.        *
//* Each of the values may be specified as any register number between           *
//* minRGB (16) and maxRGB (231).                                                *
//*                                                                              *
//* Smaller values indicate less saturation (darker colors), and larger          *
//* values indicate greater saturation (lighter, more intense colors).           *
//* If desired, the special value zero (0) may be used to indicate minimum       *
//* saturation.                                                                  *
//*                                                                              *
//* Input  : rReg   : red value                                                  *
//*          gReg   : green value                                                *
//*          bReg   : blue value                                                 *
//*                                                                              *
//* Returns: 'true'  if valid R/G/B registers specified and set                  *
//*          'false' if specified parameters are out-of-range                    *
//*                  (attribute will not be modified)                            *
//********************************************************************************

bool AnsiCmd::acSetRgbFg ( uint8_t rReg, uint8_t gReg, uint8_t bReg )
{

   return ( (this->setRgbReg ( false, rReg, gReg, bReg )) ) ;

}  //* End acSetRgbFg() *

//*************************
//*      acSetRgbBg       *
//*************************
//********************************************************************************
//* Set background color using R/G/B register numbers.                           *
//*                                                                              *
//* Each of the basic Red, Green and Blue color attributes may be displayed      *
//* in any of 216 shades, and the color values may be in any combination.        *
//* Each of the values may be specified as any register number between           *
//* minRGB (16) and maxRGB (231).                                                *
//*                                                                              *
//* Smaller values indicate less saturation (darker colors), and larger          *
//* values indicate greater saturation (lighter, more intense colors).           *
//* If desired, the special value zero (0) may be used to indicate minimum       *
//* saturation.                                                                  *
//*                                                                              *
//* Input  : rReg   : red value                                                  *
//*          gReg   : green value                                                *
//*          bReg   : blue value                                                 *
//*                                                                              *
//* Returns: 'true'  if valid R/G/B registers specified and set                  *
//*          'false' if specified parameters are out-of-range                    *
//*                  (attribute will not be modified)                            *
//********************************************************************************

bool AnsiCmd::acSetRgbBg ( uint8_t rReg, uint8_t gReg, uint8_t bReg )
{

   return ( (this->setRgbReg ( true, rReg, gReg, bReg )) ) ;

}  //* End acSetRgbBg() *

//*************************
//*       setRgbReg       *
//*************************
//********************************************************************************
//* Private Method:                                                              *
//* ---------------                                                              *
//* Called only by acSetRgbFg(uint8_t,uint8_t,uint8_t),                          *
//*                acSetRgbBg(uint8_t,uint8_t,uint8_t), and                      *
//*                setRgbWeb(bool,WebSafeRGB,uint8_t).                           *
//*                                                                              *
//* Set foreground or background color using R/G/B register values.              *
//*                                                                              *
//* Each of the basic Red, Green and Blue color attributes may be displayed      *
//* in any of 216 shades, and the color values may be in any combination.        *
//* Each of the values may be specified as any register number between           *
//* minRGB (16) and maxRGB (231).                                                *
//*                                                                              *
//* Smaller values indicate less saturation (darker colors), and larger          *
//* values indicate greater saturation (lighter, more intense colors).           *
//* If desired, the special value zero (0) may be used to indicate minimum       *
//* saturation.                                                                  *
//*                                                                              *
//* Input  : bgnd   : determines whether foreground or background is to be set   *
//*                   'false' == set foreground                                  *
//*                   'true'  == set background                                  *
//*          rReg   : red value                                                  *
//*          gReg   : green value                                                *
//*          bReg   : blue value                                                 *
//*                                                                              *
//* Returns: 'true'  if valid R/G/B registers specified and set                  *
//*          'false' if specified parameters are out-of-range                    *
//*                  (attribute will not be modified)                            *
//********************************************************************************

bool AnsiCmd::setRgbReg ( bool bgnd, uint8_t rReg, uint8_t gReg, uint8_t bReg )
{
   gString gsOut ;               // text formatting
   bool status = false ;         // return value

   //* Range check the R/G/B parameters *
   if ( (((rReg >= minRGB) && (rReg <= maxRGB)) || (rReg == ZERO)) &&
        (((gReg >= minRGB) && (gReg <= maxRGB)) || (gReg == ZERO)) &&
        (((bReg >= minRGB) && (bReg <= maxRGB)) || (bReg == ZERO)) )
   {
      if ( bgnd != false )       // set the R/G/B background
         gsOut.compose( ansiSeq[aesBG_RGB], &rReg, &gReg, &bReg ) ;
      else                       // set the R/G/B foreground
         gsOut.compose( ansiSeq[aesFG_RGB], &rReg, &gReg, &bReg ) ;
      this->ttyWrite ( gsOut ) ;
      status = true ;

      // Note that this method is not called directly by the API code.
      // Because the API methods update the tracking data, we do not 
      // update the tracking data here.
   }

   return status ;

}  //* End setRgbReg() *

//*************************
//*      acSetWebFg       *
//*************************
//********************************************************************************
//* Set foreground color using "web-safe" R/G/B register value combinations.     *
//*                                                                              *
//* Input  : hue    : member of enum WebSafeRGB                                  *
//*                   wsrgbBLACK   : black color index                           *
//*                   wsrgbRED     : base color index (red color block)          *
//*                   wsrgbGREEN   : base color index (green color block)        *
//*                   wsrgbBLUE    : base color index (blue color block)         *
//*                   wsrgbBROWN   : base color index (brown color block)        *
//*                   wsrgbMAGENTA : base color index (magenta color block)      *
//*                   wsrgbCYAN    : base color index (cyan color block)         *
//*                   wsrgbGREY    : base color index (grey color block)         *
//*                   Remaining members of WebSafeRGB are reserved for other     *
//*                   purposes.                                                  *
//*          shade  : intensity of the color specified by 'hue'                  *
//*                   wsrgbSHADE_MIN <= shade <= wsrgbSHADE_MAX                  *
//*                   where:                                                     *
//*                   wsrgbSHADE_MIN == minimum saturation (subdued)             *
//*                   wsrgbSHADE_MAX == maximum saturation (brightest)           *
//*                                                                              *
//* Returns: 'true'  if valid hue and shade values specified                     *
//*          'false' if specified parameters are out-of-range                    *
//*                  (attribute not be modified)                                 *
//********************************************************************************

bool AnsiCmd::acSetWebFg ( WebSafeRGB hue, uint8_t shade )
{
   bool status = false ;

   if ( (status = this->setRgbWeb ( false, hue, shade )) != false )
   {
      this->attrBits.update ( aesFG_RGB, (hue + shade) ) ;
   }
   return status ;

}  //* End acSetWebFg() *

//*************************
//*      acSetWebBg       *
//*************************
//********************************************************************************
//* Set background color using "web-safe" R/G/B register value combinations.     *
//*                                                                              *
//* Input  : hue    : member of enum WebSafeRGB                                  *
//*                   wsrgbBLACK   : black color index                           *
//*                   wsrgbRED     : base color index (red color block)          *
//*                   wsrgbGREEN   : base color index (green color block)        *
//*                   wsrgbBLUE    : base color index (blue color block)         *
//*                   wsrgbBROWN   : base color index (brown color block)        *
//*                   wsrgbMAGENTA : base color index (magenta color block)      *
//*                   wsrgbCYAN    : base color index (cyan color block)         *
//*                   wsrgbGREY    : base color index (grey color block)         *
//*                   Remaining members of WebSafeRGB are reserved for other     *
//*                   purposes.                                                  *
//*          shade  : intensity of the color specified by 'hue'                  *
//*                   wsrgbSHADE_MIN <= shade <= wsrgbSHADE_MAX                  *
//*                   where:                                                     *
//*                   wsrgbSHADE_MIN == minimum saturation (subdued)             *
//*                   wsrgbSHADE_MAX == maximum saturation (brightest)           *
//*                                                                              *
//* Returns: 'true'  if valid hue and shade values specified                     *
//*          'false' if specified parameters are out-of-range                    *
//*                  (attribute not be modified)                                 *
//********************************************************************************

bool AnsiCmd::acSetWebBg ( WebSafeRGB hue, uint8_t shade )
{
   bool status = false ;

   if ( (status = this->setRgbWeb ( true, hue, shade )) != false )
      this->attrBits.update ( aesBG_RGB, (hue + shade) ) ;
   return status ;

}  //* End acSetWebBg() *

//*************************
//*       setRgbWeb       *
//*************************
//********************************************************************************
//* Private Method:                                                              *
//* --------------                                                               *
//* Called only by acSetWebFg(WebSafeRGB,short) and                              *
//*                acSetWebBg(WebSafeRGB,short)                                  *
//* Set foreground or background color using the "web-safe" R/G/B "hue" and      *
//* "shade" combination.                                                         *
//*                                                                              *
//* Input  : bgnd   : determines whether foreground or background is to be set   *
//*                   'false' == set foreground                                  *
//*                   'true'  == set background                                  *
//*          hue    : member of enum WebSafeRGB                                  *
//*                   wsrgbBLACK   : black color index                           *
//*                   wsrgbRED     : base color index (red color block)          *
//*                   wsrgbGREEN   : base color index (green color block)        *
//*                   wsrgbBLUE    : base color index (blue color block)         *
//*                   wsrgbBROWN   : base color index (brown color block)        *
//*                   wsrgbMAGENTA : base color index (magenta color block)      *
//*                   wsrgbCYAN    : base color index (cyan color block)         *
//*                   wsrgbGREY    : base color index (grey color block)         *
//*                   Remaining members of WebSafeRGB are reserved for other     *
//*                   purposes.                                                  *
//*          shade  : intensity of the color specified by 'hue'                  *
//*                   wsrgbSHADE_MIN <= shade <= wsrgbSHADE_MAX                  *
//*                   where:                                                     *
//*                   wsrgbSHADE_MIN == minimum saturation (subdued)             *
//*                   wsrgbSHADE_MAX == maximum saturation (brightest)           *
//*                                                                              *
//* Returns: 'true'  if valid hue and shade values specified                     *
//*          'false' if specified parameters are out-of-range                    *
//*                  (attribute not modified)                                    *
//********************************************************************************

bool AnsiCmd::setRgbWeb ( bool bgnd, WebSafeRGB hue, uint8_t shade )
{
   uint8_t r = minRGB,           // Red register index
           g = minRGB,           // Green register index
           b = minRGB ;          // Blue register index
   bool    status = false ;      // return value

   //* Range check the 'shade' parameter *
   if ( (shade >= wsrgbSHADE_MIN) && (shade <= wsrgbSHADE_MAX) )
   {
      status = true ;            // provisional success

      switch ( hue )
      {
         // NOTE: wsrgbBLACK (shade ignored) is the same as 
         //       wsrgbRED with shade==wsrgbSHADE_MIN.
         case wsrgbBLACK:        // for black, all registers set to minRGB
            break ;
         case wsrgbRED:
            if ( (r = minRGB + (shade * wsrgbSTEP)) > maxRGB )
               r = maxRGB ;
            break ;
         case wsrgbGREEN:
            if ( (g = minRGB + (shade * wsrgbSTEP)) > maxRGB )
               g = maxRGB ;
            break ;
         case wsrgbBLUE:
            if ( (b = minRGB + (shade * wsrgbSTEP)) > maxRGB )
               b = maxRGB ;
            break ;
         case wsrgbBROWN:
            if ( (r = g = minRGB + (shade * wsrgbSTEP)) > maxRGB )
               r = g = maxRGB ;
            break ;
         case wsrgbMAGENTA:
            if ( (r = b = minRGB + (shade * wsrgbSTEP)) > maxRGB )
               r = b = maxRGB ;
            break ;
         case wsrgbCYAN:
            if ( (g = b = minRGB + (shade * wsrgbSTEP)) > maxRGB )
               g = b = maxRGB ;
            break ;
         case wsrgbGREY:
            if ( (r = g = b = minRGB + (shade * wsrgbSTEP)) > maxRGB )
               r = g = b = maxRGB ;
            break ;
         default:                // invalid hue specified
            status = false ;
            break ;
      } ;

      //* Set the color attribute *
      if ( status )
         status = this->setRgbReg ( bgnd, r, g, b ) ;

      // Note: Tracking data are not updated within this method.
      //       Caller is responsible for updating the data.
   }
   return status ;

}  //* End setRgbWeb() *

//*************************
//*   acSetGreyscaleFg    *
//*************************
//********************************************************************************
//* Set Greyscale foreground color attribute. (background unchanged)             *
//*                                                                              *
//* For systems that have at least 256 colors, 24 shades of greyscale intensity  *
//* are available. These are accessed through indices 232-255 of the terminal's  *
//* 256-color lookup table.                                                      *
//*                                                                              *
//* Input  : shade : brightness of grey color: minGSCALE <= shade <= maxGSCALE   *
//*                  example: acSetGreyscaleFg ( minGSCALE + 2 ) ;               *
//*                                                                              *
//* Returns: 'true'  if valid greyscale attribute specified and set              *
//*          'false' if invalid parameter(s) specified                           *
//*                  (attributes will not be modified)                           *
//********************************************************************************

bool AnsiCmd::acSetGreyscaleFg ( uint8_t shade )
{

   return ( (this->setGreyscale ( false, shade )) ) ;

}  //* End acSetGreyscaleFg() *

//*************************
//*   acSetGreyscaleBg    *
//*************************
//********************************************************************************
//* Set Greyscale background color attribute. (foreground unchanged)             *
//*                                                                              *
//* For systems that have at least 256 colors, 24 shades of greyscale intensity  *
//* are available. These are accessed through indices 232-255 of the terminal's  *
//* 256-color lookup table.                                                      *
//*                                                                              *
//*                                                                              *
//* Input  : shade : brightness of grey color: minGSCALE <= shade <= maxGSCALE   *
//*                  example: acSetGreyscaleBg ( minGSCALE + 2 ) ;               *
//*                                                                              *
//* Returns: 'true'  if valid greyscale attribute specified and set              *
//*          'false' if invalid parameter(s) specified                           *
//*                  (attributes will not be modified)                           *
//********************************************************************************

bool AnsiCmd::acSetGreyscaleBg ( uint8_t shade )
{

   return ( (this->setGreyscale ( true, shade )) ) ;

}  //* End acSetGreyscaleBg() *

//*************************
//*     setGreyscale      *
//*************************
//********************************************************************************
//* Private Method:                                                              *
//* ---------------                                                              *
//* Called only by public methods acSetGreyscaleFg(uint8_t) and                  *
//* acSetGreyscaleBg(uint8_t).                                                   *
//*                                                                              *
//* Set Greyscale foreground or background color attribute.                      *
//*                                                                              *
//* For systems that have at least 256 colors, 24 shades of greyscale intensity  *
//* are available. These are accessed through indices minGSCALE-maxGSCALE        *
//* (232-255) of the terminal's 256-color lookup table.                          *
//*                                                                              *
//* Input  : bgnd   : determines whether foreground or background is to be set   *
//*                   'false' == set foreground                                  *
//*                   'true'  == set background                                  *
//*        : shade : brightness of grey color: minGSCALE <= scale <= maxGSCALE   *
//*                                                                              *
//* Returns: 'true'  if valid greyscale attribute specified and set              *
//*          'false' if invalid parameter(s) specified                           *
//*                  (attributes will not be modified)                           *
//********************************************************************************

bool AnsiCmd::setGreyscale ( bool bgnd, uint8_t shade )
{
   gString gsOut ;               // text formatting
   bool status = false ;         // return value

   //* Perform range check *
   if ( (shade >= minGSCALE) && (shade <= maxGSCALE) )
   {
      if ( bgnd != false )    // set the greyscale background
         gsOut.compose( ansiSeq[aesBG_INDEX], &shade ) ;
      else                    // set the greyscale foreground
         gsOut.compose( ansiSeq[aesFG_INDEX], &shade ) ;

      if ( this->fullReset != false )
         this->acReset () ;
      this->ttyWrite ( gsOut ) ;

      //* Update tracking data to reflect the new settings *
      this->attrBits.update ( (bgnd ? aesBG_INDEX : aesFG_INDEX), shade ) ;
      status = true ;
   }
   return status ;

}  //* End setGreyscale() *

//*************************
//*      acSetCursor      *
//*************************
//********************************************************************************
//* Set cursor position within terminal window.                                  *
//*                                                                              *
//* Some ANSI cursor commands perform cursor movements relative to the current   *
//* position, while other commands set the cursor at an absolute position.       *
//*                                                                              *
//* NOTE: One member of the cursor-positioning group: aesCUR_REPORT, is not      *
//*       handled by this method. Instead please see acGetCursor().              *
//*       If this method receives aesCUR_REPORT, it will ignore it and will      *
//*       return 'false' to indicate that the request was not handled.           *
//*                                                                              *
//* ALSO: See note in module header regarding 1-based vs. 0-based cursor         *
//*       positioning.                                                           *
//*                                                                              *
//* Input  : curcmd: one of the members of the cursor-positioninng group which   *
//*                  are included in enum aeSeq.                                 *
//*                  Example: acSetCursor ( aesCUR_HOME ) ;                      *
//*          arga  : (optional, 1 by default)                                    *
//*                  for options which require arguments, 'arga' specifies the   *
//*                  first argument.                                             *
//*                  Example: acSetCursor ( aesCUR_ABSCOL, 30 ) ;                *
//*          argb  : (optional, 1 by default)                                    *
//*                  for options which require arguments, 'argb' specifies the   *
//*                  second argument.                                            *
//*                  Example: acSetCursor ( aesCUR_ABSPOS, 12, 60 ) ;            *
//*                                                                              *
//* Returns: 'true'  if valid cursor command (and valid arguments, if required)  *
//*          'false' if invalid parameter(s) specified                           *
//*                  (position will not be modified)                             *
//********************************************************************************
//* Note: Digital Equipment Corporation (DEC) and the SCO Group (UnixWare)       *
//*       defined some of the cursor-management commands differently. It is      *
//*       recommended that the DEC varients be used when possible.               *
//*       Also, as a veteran of the open-source wars. the author points out      *
//*       that: SCO Group is "The most hated company in Tech." -- Wikipedia      *
//*                                                                              *
//********************************************************************************

bool AnsiCmd::acSetCursor ( aeSeq curcmd, short arga, short argb ) const
{
   gString gsOut ;               // text formatting
   bool status = false ;         // return value

   if ( (curcmd >= aesCUR_HOME) && (curcmd <= aesCUR_RESTORE_SCO) )
   {
      status = true ;                     // declare success

      //* For static commands, output the command directly.*
      if ( (curcmd == aesCUR_HOME)     || (curcmd == aesCUR_ROWDEL) || 
           (curcmd == aesCUR_SAVE_DEC) || (curcmd == aesCUR_RESTORE_DEC) ||
           (curcmd == aesCUR_SAVE_SCO) || (curcmd == aesCUR_RESTORE_SCO)
         )
      {
         gsOut = ansiSeq[curcmd] ;
      }

      //* Request for current cursor position. (See note in method header.) *
      else if ( curcmd == aesCUR_REPORT )
      {
         status = false ;
      }

      //* Otherwise, construct the command sequence for: *
      //*      aesCUR_ABSPOS      aesCUR_COLSLT          *
      //*      aesCUR_ROWSUP      aesCUR_NEXTROW         *
      //*      aesCUR_ROWSDN      aesCUR_PREVROW         *
      //*      aesCUR_COLSRT      aesCUR_ABSCOL          *
      else
      {
         //* If argument is out-of-range low, silently repair it. *
         //* Currently, we do not perform a high-range check      *
         //* because it is too inefficient to decide whether      *
         //* 'arga' represents a row or column value.             *
         //* Accept zero value, but convert to 1.                 *
         if ( arga == ZERO )  arga = 1 ;
         if ( argb == ZERO )  argb = 1 ;
         gsOut.compose( ansiSeq[curcmd], &arga, &argb ) ;
      }

      if ( status != false )
         this->ttyWrite ( gsOut ) ;    // write the command
   }

   return status ;

}  //* End acSetCursor() *

//*************************
//*      acSetCursor      *
//*************************
//********************************************************************************
//* Set absolute cursor position within terminal window.                         *
//* These methods reformat the parameters and call the low-level                 *
//* acSetCursor() method.                                                        *
//*                                                                              *
//* Input  : One of the following:                                               *
//*             row : target row                                                 *
//*             col : target column                                              *
//*          or:                                                                 *
//*             wp  : (by reference) target row and column                       *
//*                                                                              *
//* Returns: 'true'  if specified row/column are within the terminal window      *
//*          'false' if invalid position (cursor position not modified)          *
//********************************************************************************

bool AnsiCmd::acSetCursor ( short row, short col ) const
{

   return ( this->acSetCursor ( aesCUR_ABSPOS, row, col ) ) ;

}  //* End acSetCursor() *

bool AnsiCmd::acSetCursor ( const WinPos& wp ) const
{

   return ( this->acSetCursor ( aesCUR_ABSPOS, wp.row, wp.col ) ) ;

}  //* End acSetCursor() *

//*************************
//*      acGetCursor      *
//*************************
//********************************************************************************
//* Get the current cursor position within terminal window.                      *
//*                                                                              *
//* Important Note:                                                              *
//* ---------------                                                              *
//* This method requires a specific terminal configuration:                      *
//*   1) input buffering disabled,                                               *
//*   2) non-blocking read of input stream, and                                  *
//*   3) input echo disabled                                                     *
//* The reason for this is that when buffering is active, the response from the  *
//* terminal will not become available to the application until the Enter key    *
//* is detected in the input stream.                                             *
//* In addition, echo of input characters to the terminal window must also       *
//* be disabled because when the system writes the response to the position      *
//* query, it is written to stdin, which if echo were enabled would likely       *
//* trash the currently-displayed text, or at minimum would move the cursor,     *
//* causing a big surprise at the application level.                             *
//* While setting and restoring the buffering/echo options is an expensive       *
//* operation in terms of CPU cycles, it is necessary for smooth operation       *
//* at the application level.                                                    *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: an initialized WinPos object                                        *
//*          (If system error or parsing error, window origin is returned.)      *
//********************************************************************************
//* Notes:                                                                       *
//* ------                                                                       *
//* -- Write the ANSI sequence requesting the current cursor position.           *
//* -- Read the response (binary data) from stdin.                               *
//* -- Decode the response which is of the form: 1B 5B (row) 3B (col) 52         *
//*      1B       escape character                                               *
//*      5B       left square bracket                                            *
//*      row      one or more ASCII numeric chars indicating row number          *
//*      3B       ';' (ASCII semicolon)                                          *
//*      col      one or more ASCII nummeric chars indicating column number      *
//*      52       ASCII 'R'                                                      *
//*                                                                              *
//*                                                                              *
//********************************************************************************

WinPos AnsiCmd::acGetCursor ( void ) const
{
   gString gsIn ;             // captures data from stdin
   WinPos wp( 1, 1 ) ;        // return value (if error, window origin is returned)
   #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0
   #define DEBUG_CURPOS (0)
   #if DEBUG_CURPOS != 0
   #define DEBUG_SEQUENCE (0)
   gString gstmp ;
   if ( (ofsdbg.is_open()) )
   {
      gstmp.compose( "acGetCursor    ( %hhd %hd %hhd ", 
                     &this->tset->inpBuf, &this->tset->inpEch, 
                     &this->tset->inpBlk ) ;
   }
   #endif   // DEBUG_CURPOS
   #endif   // DEBUG_ANSICMD && DEBUG_LOG

   //* Because this command requests information from the system, and *
   //* because this information arrives via 'stdin', input buffering  *
   //* and data echo to the terminal window must be disabled.         *
   EchoOpt orig_ech = this->tset->inpEch ;
   bool    orig_buf = this->tset->inpBuf ;
   bool    orig_blk = this->tset->inpBlk ;

   if ( this->tset->inpBuf || (this->tset->inpBlk) || (this->tset->inpEch != noEcho) )
   {
      if ( this->tset->inpBuf )
         this->tset->setTermInfo ( stdIn, false, noEcho ) ;
      else
         this->tset->echoOption ( noEcho ) ;
      if ( this->tset->inpBlk )
         this->tset->blockingInput ( false ) ;
   }

   #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_CURPOS != 0
   if ( (ofsdbg.is_open()) )
   {
      gstmp.append( "-> %hhd %hd %hhd ", 
                    &this->tset->inpBuf, &this->tset->inpEch, 
                    &this->tset->inpBlk ) ;
   }
   #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_CURPOS

   //* Request the current cursor position *
   this->ttyWrite ( ansiSeq[aesCUR_REPORT] ) ;     // make the request
   short inChars = this->readSeq ( gsIn, L'R' ) ;  // read the response

   if ( (inChars < 6) || (gsIn.find( L'R')) <= ZERO ) // if error, flush the input buffer
      this->acFlushIn () ;

   #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_CURPOS != 0 && DEBUG_SEQUENCE != 0
   if ( (ofsdbg.is_open()) )
   {
      const wchar_t *wptr = gsIn.gstr() ;
      short i = ZERO ;
      gstmp.append( L"[" ) ;
      do
      {
         gstmp.append( "%02X", &wptr[i++] ) ;
         if ( wptr[i] != NULLCHAR )
            gstmp.append( L' ' ) ;
         else
         { gstmp.append( L"] " ) ; break ; }
      }
      while ( i < MAX_SEQ_BYTES ) ;
   }
   #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_CURPOS && DEBUG_SEQUENCE

   //* Decode the coordinates (see notes above) *
   if ( (inChars >= 6) && (gsIn.find( L'R' )) > ZERO )
   {
      if ( (gsIn.gscanf( 2, L"%hd;%hd", &wp.row, &wp.col )) == 2 )
      {
         if ( (wp.row <= ZERO) )       wp.row = 1 ;
         if ( (wp.col <= ZERO) )       wp.col = 1 ;
      }
      else        // decoding error
         ;
   }

   //* Restore previous buffering/echo/blocking options *
   //* If originally buffered input, return to buffered *
   //* input, blocking-read, and termEcho.              *
   if ( orig_buf )
      this->tset->setTermInfo ( stdIn, orig_buf, orig_ech ) ;
   else  // originally unbuffered input
   {
      //* Return to original echo option *
      if ( (orig_ech != this->tset->inpEch) )
         this->tset->echoOption ( orig_ech ) ;
      //* Return to original blocking-read option *
      if ( (orig_blk != this->tset->inpBlk) )
         this->tset->blockingInput ( orig_blk ) ;
   }

   #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_CURPOS != 0
   if ( (ofsdbg.is_open()) )
   {
      gsdbg = gstmp ;
      gsdbg.append( "-> %hhd %hd %hhd )", 
                    &this->tset->inpBuf, &this->tset->inpEch, 
                    &this->tset->inpBlk ) ;
      ofsdbg << gsdbg.ustr() << endl ;
   }
   #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_CURPOS

   return wp ;

   #undef DEBUG_CURPOS
   #undef DEBUG_SEQUENCE
}  //* End acGetCursor() *

//*************************
//*   acSetCursorStyle    *
//*************************
//********************************************************************************
//* Set cursor type (shape of the cursor).                                       *
//*                                                                              *
//* Note: Cursor color attribute is automatically set by the terminal to the     *
//*       same color as the text. If a way around this can be found, we will     *
//*       implement it in a future release.                                      *
//*                                                                              *
//* Input  : style : member of enum CurStyle                                     *
//*          color : (optional, default from terminal profile)                   *
//*                  member of aeSeq, foreground attribute group: aesFG_xxx      *
//*                  color attribute for cursor [CURRENTLY IGNORED]              *
//*                                                                              *
//* Returns: 'true' if valid argument(s), else 'false'                           *
//********************************************************************************

bool AnsiCmd::acSetCursorStyle ( CurStyle style, aeSeq color )
{

   return ( (this->tset->cursorStyle ( style, color )) ) ;

}  //* End acSetCursorStyle() *

//*************************
//*       acSetFont       *
//*************************
//********************************************************************************
//* Specify the terminal font used to write subsequent text data.                *
//*                                                                              *
//* 1) User specifies 'font' parameter as a member of enum aeSeq:                *
//*    a) aesPRIMARY_FONT   set the primary font as the active font              *
//*                         ('fontnum' parameter is ignored)                     *
//*    b) aesALTERNATE_FONT set one of the alternate fonts as the active font    *
//*             USER#   1  2  3  4  5  6  7  8  9  translates to:                *
//*             ANSI#  11 12 13 14 15 16 17 18 19                                *
//*                                                                              *
//* Input  : font   : one of aesPRIMARY_FONT or aesALTERNATE_FONT which are      *
//*                   members of enum eSeq.                                      *
//*          fontnum: (optional, zero(0) by default)                             *
//*                   specify the alternate font, range: 1 through 9             *
//*                                                                              *
//* Returns: 'true'  if valid greyscale attribute specified and set              *
//*          'false' if invalid parameter(s) specified                           *
//*                  (attributes will not be modified)                           *
//********************************************************************************

bool AnsiCmd::acSetFont ( aeSeq font, uint8_t fontnum )
{
   const uint8_t minUSERNUM = 0 ;   // minimum user designation for font selection
   const uint8_t maxUSERNUM = 9 ;   // maximum user designation for font selection

   gString gsOut ;                  // text formatting
   bool status = true ;             // return value

   if ( font == aesPRIMARY_FONT )
   {
      fontnum = primeFONT ;
      gsOut = ansiSeq[aesPRIMARY_FONT] ;
   }
   else if ( (font == aesALTERNATE_FONT) &&
             ((fontnum > minUSERNUM) && (fontnum <= maxUSERNUM)) )
   {
      fontnum += primeFONT ;
      gsOut.compose( ansiSeq[aesALTERNATE_FONT], &fontnum ) ;
   }
   else     // invalid font command, or parameter out-of-range
      status = false ;

   if ( status != false )
   {
      this->ttyWrite ( gsOut ) ;
      this->fontNum = fontnum ;
      status = true ;
   }

   return status ;

}  //* End acSetFont() *

//*************************
//*      acEraseArea      *
//*************************
//********************************************************************************
//* Erase the specified area of the terminal window.                             *
//* The area to be erased is specified by one of the ANSI erasure commands.      *
//*      aesERASE_BOW,           // erase from cursor to bottom of window        *
//*      aesERASE_TOW,           // erase from cursor to top of window           *
//*      aesERASE_WIN,           // erase entire window ('clear')                *
//*      aesERASE_SAVED,         // erase "saved" lines                          *
//*      aesERASE_EOL,           // erase from cursor to end of line             *
//*      aesERASE_BOL,           // erase from cursor to beginning of line       *
//*      aesERASE_LINE,          // erase entire line                            *
//* The specified erasure is performed relative to the current cursor position   *
//* UNLESS an offset is specified:                                               *
//*      rowOff : row offset from current row                                    *
//*      colOff : column offset from current column                              *
//* Important Note: If the specified offset moves the cursor beyond the          *
//* edge of the terminal window, unexpected side-effects may occur.              *
//*                                                                              *
//* See also the direct erasure method, acClearArea().                           *
//*                                                                              *
//*                                                                              *
//* Input  : cur   : one of the members of the text-erasure group within         *
//*                  enum aeSeq.                                                 *
//*                  example: acEraseArea ( aesERASE_WIN ) ;                     *
//*          rowOff: (optional, ZERO by default) If a non-zero value is          *
//*                  specified, the cursor will be shifted upward (negative      *
//*                  values) or downward (positive values) from the current      *
//*                  cursor position before the erasure occurs.                  *
//*          colOff: (optional, ZERO by default) If a non-zero value is          *
//*                  specified, the cursor will be shifted leftward (negative    *
//*                  values) or rightward (positive values) from the current     *
//*                  cursor position before the erasure occurs.                  *
//*                                                                              *
//* Returns: 'true'  if successful                                               *
//*          'false' if invalid parameter(s) specified                           *
//********************************************************************************

bool AnsiCmd::acEraseArea ( aeSeq area, short rowOff, short colOff ) const
{
   bool  status = false ;              // return value

   if ( (area >= aesERASE_BOW) && (area <= aesERASE_LINE) )
   {
      //* Offsets do not apply to erasure of the entire window *
      //* nor to erasure of the off-screen data.               *
      if ( (area == aesERASE_WIN) || (area == aesERASE_SAVED) )
      { rowOff = ZERO ; colOff = ZERO ; }
      short absrowOff = abs( rowOff ),    // absolute row offset
            abscolOff = abs( colOff ) ;   // absolute column offfset

      if ( (rowOff != ZERO) && (absrowOff <= this->termRows) )
      {
         if ( rowOff < ZERO )          // shift upward
            this->acSetCursor ( aesCUR_ROWSUP, absrowOff ) ;
         else                          // shift downward
            this->acSetCursor ( aesCUR_ROWSDN, absrowOff ) ;
      }
      if ( (colOff != ZERO) && (abscolOff <= this->termCols) )
      {
         if ( colOff < ZERO )          // shift leftward
            this->acSetCursor ( aesCUR_COLSLT, abscolOff ) ;
         else                          // shift rightward
            this->acSetCursor ( aesCUR_COLSRT, abscolOff ) ;
      }

      this->ttyWrite ( ansiSeq[area] ) ;
      status = true ;
   }
   return status ;

}  //* End acEraseArea() *

//*************************
//*     acSetIdeogram     *
//*************************
//********************************************************************************
//* Interface to the seldom-supported and little-used "ideogram" (logogram)      *
//* group of ANSI commands.                                                      *
//*                                                                              *
//* It is unclear what this group of commands was originally intended to do.     *
//* Presumably they were intended to provide some kind of border or              *
//* highlighting around a block of text. They are probably non-functional, but   *
//* they can be tested using this method.                                        *
//*                                                                              *
//* aesIDEO_UNDER       // ideogram underlined                                   *
//* aesIDEO_UDOUBLE     // ideogram double-underlined                            *
//* aesIDEO_OVER        // ideogram overlined                                    *
//* aesIDEO_ODOUBLE     // ideogram double-overlined                             *
//* aesIDEO_STRESS      // ideogram stress marking                               *
//* aesIDEO_OFF         // all ideogram attributes off                           *
//*                                                                              *
//* Input  : idea  : member of enum aeSeq within the ideogram-command group.     *
//*                                                                              *
//* Returns: 'true'  if valid argument                                           *
//*          'false' if invalid parameter(s) specified                           *
//*                  (attribute will not be modified)                            *
//********************************************************************************

bool AnsiCmd::acSetIdeogram ( aeSeq idea )
{
   gString gsOut ;                        // text formatting
   bool status = false ;                  // return value

   if ( (idea >= aesIDEO_UNDER) && (idea <= aesIDEO_OFF) )
   {
      gsOut = ansiSeq[idea] ;
      this->ttyWrite ( gsOut ) ;
      status = true ;
   }
   return status ;

}  //* End acSetIdeogram() *

//*************************
//*        acBeep         *
//*************************
//********************************************************************************
//* Invoke the console's default auditory alert (beep).                          *
//*                                                                              *
//* Programmer's Note: Currently, this method simply outputs an ASCII BELL       *
//* control code; however, future releases will implement more robust and        *
//* flexible sound control.                                                      *
//*                                                                              *
//* Input  : repeat : (optional, 1 by default) repeat count                      *
//*                   Range: 1 - 32,767                                          *
//*          delay  : (optional, 5 by default i.e. 1/2 second)                   *
//*                   delay between successive beeps in tenths of a second       *
//*                   in the range: 2 - 600 (0.2sec through 1.0min)              *
//*                   Note: The system is unable to handle short delays, so      *
//*                         a delay of two-tenths second is the safe minimum.    *
//*          freq   : (optional, -1 by default)                                  *
//*                   if specified, this is the auditory frequency of the beep   *
//*                   [CURRENTLY IGNORED]                                        *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* NOTES:                                                                       *
//* ------                                                                       *
//* Programming the internal speaker:	https://tldp.org/LDP/lpg/node83.html      *
//* is part of the Linux Programmer’s Guide:                                     *
//*               https://tldp.org/LDP/lpg/lpg.html                              *
//* Sven van der Meer, Scott Burkett, Matt Welsh, v:0.4 March 1995               *
//*                                                                              *
//* Believe it or not, your PC speaker is part of the Linux console and thus     *
//* a character device. Therefore, ioctl() requests exist to manipulate it.      *
//* For the internal speaker the following 2 requests exist:                     *
//*                                                                              *
//* KDMKTONE                                                                     *
//* Generates a beep for a specified time using the kernel timer.                *
//* Example: ioctl (fd, KDMKTONE,(long) argument).                               *
//*          const int  KDMKTONE  = 0x00004B30 ;                                 *
//*          ioctl ( stdIn, KDMKTONE, dfltBEEP ) ;                               *
//*                                                                              *
//* KIOCSOUND                                                                    *
//* Generates an endless beep or stops a currently sounding beep.                *
//* Example: ioctl(fd,KIOCSOUND,(int) tone).                                     *
//*          const int  KIOCSOUND = 0x00004B2F ;                                 *
//*          const long dfltBEEP = (125 << 16) + 0x0637 ;                        *
//*                                                                              *
//* The argument consists of the tone value in the low word and the duration     *
//* in the high word. The tone value is not the frequency. The PC mainboard      *
//* timer 8254 is clocked at 1.19 MHz and so it's 1190000/frequency.             *
//* The duration is measured in timer ticks. Both ioctl calls return             *
//* immediately so you can this way produce beeps without blocking the program.  *
//* KDMKTONE should be used for warning signals because you don't have to        *
//* worry about stopping the tone.                                               *
//* KIOCSOUND can be used to play melodies as demonstrated in the example        *
//* program splay (please send more .sng files to me). To stop the beep you      *
//* have to use the tone value 0.                                                *
//*                                                                              *
//* <include/linux/kd.h>                                                         *
//*                                                                              *
//********************************************************************************

void AnsiCmd::acBeep ( short repeat, short delay, int freq ) const
{
   const char  CTRL_G = '\x07' ;    // ASCII BELL control code
   const short MIN_DELAY = 2 ;      // minimum delay == 0.2 sec
   const short MAX_DELAY = 600 ;    // maximum delay == 60.0 sec (1.0 min)

   if ( repeat < ZERO )          repeat = 1 ;
   if ( delay < MIN_DELAY )      delay = MIN_DELAY ;
   else if ( delay > MAX_DELAY ) delay = MAX_DELAY ;

   do
   {
      this->ttyWrite ( CTRL_G ) ;
      if ( repeat > 1 )
         this->nsleep ( delay ) ;
   }
   while ( --repeat > ZERO ) ;

}  //* End acBeep() *

//*************************
//*  acGetTermDimensions  *
//*************************
//********************************************************************************
//* Reports the dimensions of the terminal window in rows and columns.           *
//*                                                                              *
//* Input  : rows  : (by reference) receives number of terminal rows             *
//*          cols  : (by reference) receives number of terminal columns          *
//*                                                                              *
//* Returns: 'true'  if terminal dimensions are known                            *
//*          'false' if system call to get terminal dimensions failed            *
//********************************************************************************

bool AnsiCmd::acGetTermDimensions ( short& rows, short& cols ) const
{
   bool status = false ;         // return value

   if ( ((rows = this->termRows) > ZERO) && ((cols = this->termCols) > ZERO) )
      status = true ;

   return status ;

}  //* End acGetTermDimensions() *

//*************************
//*     acGetTermSize     *
//*************************
//********************************************************************************
//* Returns the terminal dimensions in both character cells and pixels.          *
//*                                                                              *
//* From /usr/include/bits/ioctl-types.h                                         *
//* ------------------------------------                                         *
//*  struct winsize                                                              *
//*  {                                                                           *
//*     unsigned short int ws_row;                                               *
//*     unsigned short int ws_col;                                               *
//*     unsigned short int ws_xpixel;                                            *
//*     unsigned short int ws_ypixel;                                            *
//*   };                                                                         *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: current terminal dimensions in a WinSize object                     *
//********************************************************************************
//* Note on terminal dimensions: Although the environment may contain the        *
//* terminal dimensions when the application was invoked, this is a _copy_ of    *
//* the environment, not the active environment, and therefore will not reflect  *
//* user changes to the terminal window size while the application is active.    *
//*                                                                              *
//* The $LINES and $COLUMNS are available from the environment at the command    *
//* line ("echo $LINES" and "echo $COLUMNS"), but are not exported to programs   *
//* running in the terminal window.                                              *
//*                                                                              *
//* Note also that the pixel dimensions will probably be returned as zeros       *
//* because the terminal program likely doesn't know the screen resolution.      *
//********************************************************************************

WinSize AnsiCmd::acGetTermSize ( void )
{
   WinSize ws ;

   if ( (ioctl ( stdOut, TIOCGWINSZ, &ws )) == ZERO )
   {
      //* Update our members *
      this->termRows = ws.ws_row ;
      this->termCols = ws.ws_col ;
   }
   else
   {
      ws.ws_row = ws.ws_col = ws.ws_xpixel = ws.ws_ypixel = ZERO ;
   }
   return ws ;

}  //* End acGetTermSize() *

//*************************
//*     acGetTermInfo     *
//*************************
//********************************************************************************
//* Get a copy of the current terminal I/O stream configuration (or the          *
//* original settings captured on application start-up).                         *
//* This is a pass-through to the private TermSet method.                        *
//*                                                                              *
//* This method is primarily for development and debugging.                      *
//*                                                                              *
//* Input  : tStream : one of stdIn, stdOut, stdErr                              *
//*          tios    : (by reference) receives settings                          *
//*          orig    : (optional, 'false' by default)                            *
//*                    'false': the current terminal settings are returned       *
//*                    'true' : the original terminal settings are returned      *
//*                                                                              *
//* Returns: 'true'  if settings captured                                        *
//*          'false' if system error                                             *
//********************************************************************************

bool AnsiCmd::acGetTermInfo ( TermStream tStream, TermIos& tIos, bool orig )
{

   return ( (this->tset->getTermInfo ( tStream, tIos, orig )) ) ;

}  //* End acGetTermInfo() *

#if 0    // THIS METHOD IS CURRENTLY NOT USED.
//*************************
//*     acSetTermInfo     *
//*************************
//********************************************************************************
//* Set the terminal I/O stream configuration.                                   *
//* This is a pass-through to the private TermSet.termConfig() method.           *
//*                                                                              *
//* Input  : tios   : (by reference) a fully initialized TermIos object          *
//*                   This structure is defined as "struct termios" in termios.h.*
//*                                                                              *
//* Returns: 'true'  if settings modified                                        *
//*          'false' if system reports an error                                  *
//********************************************************************************

   //* Set the terminal I/O stream configuration.*
   bool acSetTermInfo ( const TermIos& tios ) ;
bool AnsiCmd::acSetTermInfo ( const TermIos& tios )
{

   return ( (this->tset->termConfig ( stdIn, tios )) ) ;

}  //* End acSetTermInfo() *
#endif   // THIS METHOD IS CURRENTLY NOT USED.

//*************************
//*    acFlushStreams     *
//*************************
//********************************************************************************
//* Flush stdout and discard data from stdin.                                    *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::acFlushStreams ( void ) const
{
   //* Flush (write) all data in the output buffer *
   this->acFlushOut () ;

   //* Flush (discard) all data in the input stream *
   this->acFlushIn () ;

}  //* End acFlushStreams() *

//*************************
//*      acFlushOut       *
//*************************
//********************************************************************************
//* Flush stdout, (the active output stream, wcout or cout).                     *
//* Any data still in the output stream will be written to the terminal window.  *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::acFlushOut ( void ) const
{
   if ( this->wideStream )
      wcout.flush() ;
   else
      cout.flush() ;

}  //* End acFlushOut() *

//*************************
//*       acFlushIn       *
//*************************
//********************************************************************************
//* Flush stdin, (the active input stream, wcin or cin).                         *
//* Any data still in the input stream will be discarded.                        *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Notes:                                                                       *
//* ------                                                                       *
//* -- If buffered input is enabled, there could be any number of stale keycodes *
//*    waiting to be flushed because the data will not be retrieved by a read    *
//*    operation until the Enter key is pressed.                                 *
//*    -- To test this:                                                          *
//*       1) Set buffered input.                                                 *
//*       2) Type something, but DO NOT press Enter.                             *
//*       3) Call acFlushIn().                                                   *
//*       4) Type something else and THEN press Enter.                           *
//*       5) Determine whether the data read is from the first write, the        *
//*          second write, or both.                                              *
//*                                                                              *
//* -- If buffering is already disabled, there may still be data in the buffer   *
//*    if a read operation has not been invoked since user typed something on    *
//*    the keyboard. This is less likely, but still possible. For instance, if   *
//*    the thread is asleep or otherwise occupied and has not had time to read   *
//*    from stdin.                                                               *
//*    -- To test this:                                                          *
//*       1) Set unbuffered input with-or-without echo and with-or-without       *
//*          blocking read.                                                      *
//*       2) Call nsleep() so the application will sit quietly and do nothing    *
//*          for a while, and while the thread is asleep...                      *
//*       3) Type something.                                                     *
//*       4) Call acFlushIn().                                                   *
//*       5) Call nsleep() again, and while the thread is asleep...              *
//*       6) Type something different.                                           *
//*       7) Call acRead().                                                      *
//*       8) Determine whether the data read is from the first write, the        *
//*          second write, or both.                                              *
//*    See also: Test_UnbufferedInput().                                         *
//*                                                                              *
//* -- Note that the standard C++ method does not work. (Traits::eof() == WEOF)  *
//*    wcin.clear() ;    // reset the error flags                                *
//*    wcin.ignore( std::numeric_limits<std::streamsize>::max(), WEOF ) ;        *
//*    wcin.clear() ;    // reset the error flags to clear the EOF condition     *
//*                                                                              *
//*                                                                              *
//********************************************************************************

void AnsiCmd::acFlushIn ( void ) const
{
   #define DEBUG_FI (0)

   wchar_t wchar ;
   EchoOpt orig_ech = this->tset->inpEch ;
   bool    orig_buf = this->tset->inpBuf,
           orig_blk = this->tset->inpBlk ;

   #if DEBUG_FI != 0
   gString gstmp ;
   if ( (ofsdbg.is_open()) )
   {
      gstmp.compose( "acFlushIn      ( %hhd %hd %hhd ", 
         &this->tset->inpBuf, &this->tset->inpEch, &this->tset->inpBlk ) ;
   }
   #endif   // DEBUG_FI

   if ( this->tset->inpBuf || (this->tset->inpBlk) || (this->tset->inpEch != noEcho) )
   {
      if ( this->tset->inpBuf )
         this->tset->setTermInfo ( stdIn, false, noEcho ) ;
      else
         this->tset->echoOption ( noEcho ) ;
      if ( this->tset->inpBlk )
         this->tset->blockingInput ( false ) ;
   }

   #if DEBUG_FI != 0
   if ( (ofsdbg.is_open()) )
   {
      gstmp.append( "-> %hhd %hd %hhd ", 
         &this->tset->inpBuf, &this->tset->inpEch, &this->tset->inpBlk ) ;
   }
   #endif   // DEBUG_FI

   //* Read characters from the stream until *
   //* stream is reported as empty.          *
   //* Note that the 'silent' parameter      *
   //* prevents echo of data to the display. *
   while ( (this->acReadNB ( wchar, 2, (NB_DELAY / 10), true )) ) ;

   //* Restore previous buffering/echo/blocking options *
   //* If originally buffered input, return to buffered *
   //* input, blocking-read, and termEcho.              *
   if ( orig_buf )
      this->tset->setTermInfo ( stdIn, orig_buf, orig_ech ) ;
   else  // originally unbuffered input
   {
      //* Return to original echo option *
      if ( (orig_ech != this->tset->inpEch) )
         this->tset->echoOption ( orig_ech ) ;
      //* Return to original blocking-read option *
      if ( (orig_blk != this->tset->inpBlk) )
         this->tset->blockingInput ( orig_blk ) ;
   }

   #if DEBUG_FI != 0
   if ( (ofsdbg.is_open()) )
   {
      gstmp.append( "-> %hhd %hd %hhd )", 
         &this->tset->inpBuf, &this->tset->inpEch, &this->tset->inpBlk ) ;
      ofsdbg << gstmp.ustr() << endl  ;
   }
   #endif   // DEBUG_FI
   #undef DEBUG_FI
}  //* End acFlushIn() *

//*************************
//*        acRead         *
//*************************
//********************************************************************************
//* Capture user input as a sequence of characters terminated by the Enter       *
//* key, OR when the number of characters specified by the 'limit' parameter     *
//* is reached.                                                                  *
//*                                                                              *
//* If 'tset->inpEch' is one of the "softEcho" options:                          *
//*   1) If the captured character is a standard printing character, echo        *
//*      the character to the terminal window.                                   *
//*   2) If the captured keycode is one of the "special" keys, the caller        *
//*      is responsible for processing it or discarding it.                      *
//*      Please refer to the notes in the specialkeyDecode() below, for more     *
//*      information.                                                            *
//*                                                                              *
//* Note on capture of the Enter key:                                            *
//* -- If the Enter key is the first and only keycode captured, the Enter        *
//*    keycode is returned and the count will be one(1).                         *
//* -- If the input is terminated by the Enter key, the characters captured      *
//*    _before_ the Enter key are returned. The Enter keycode will not be        *
//*    returned and is not included in the character total.                      *
//*                                                                              *
//* Input  : gsIn  : (by reference) receives captured text (null terminated)     *
//*          limit : (optional, storage capacity if 'gsIn' by default, see note) *
//*                  (This argument is referenced for unbuffered input only.)    *
//*                  If specified, 'limit' indicates the maximum number of       *
//*                  characters (wide-stream input) OR the maximum number of     *
//*                  bytes (narrow-stream input) that will be extracted from     *
//*                  the stdin buffer.                                           *
//*                                                                              *
//* Returns: number of characters captured (NOT the number of bytes)             *
//********************************************************************************
//* Notes:                                                                       *
//* ======                                                                       *
//* 1) Unbuffered input: (preferred)                                             *
//*    a) Read one character at a time from the input stream.                    *
//*    b) If the captured character is not a newline (0x0A, '\n'), OR if the     *
//*       character is a newline, but is the only character, then store it       *
//*       in our work buffer.                                                    *
//*    c) Echo of input to the terminal window:                                  *
//*         i) If 'termEcho', the terminal will automatically echo the input     *
//*            to the window.                                                    *
//*        ii) If 'noEcho', then input is not echoed to the window, but all      *
//*            all special keys are decoded and returned to caller as valid      *
//*            glyphs. See discussion of the soft-echo optios, below.            *
//*       iii) If 'softEcho[A|C|D|E]', then special processing occurs.           *
//*            Cursor keys, Delete, Backspace and Insert keys are converted      *
//*            from escape sequences to known glyphs (printing characters) where *
//*            possible. Note however, that it is the caller's responsibility to *
//*            process or discard these special characters as desired.           *
//*            Under most circumstances they _should not_ be displayed.          *
//*            See note below and the acRead(void) and isSpecialKey() methods    *
//*            for additional information.                                       *
//*    d) Note that our local work buffer is a wide-character array regardless   *
//*       of whether the wide or narrow input stream is used.                    *
//*    e) Terminate the string and copy our work buffer to caller's object.      *
//*                                                                              *
//* 2) Buffered input (input terminated by Enter key):                           *
//*    a) Use the C++ getline method to capture the input stream.                *
//*    b) Input is terminated by the newline key, but the newline is not         *
//*       returned. Note that the 'limit' parameter is ignored.                  *
//*    c) Copy the contents of the work buffer to caller's object.               *
//*                                                                              *
//* Processing the special softEcho keycodes:                                    *
//* -----------------------------------------                                    *
//* Certain keycodes are returned from the call to acRead(void) as "special"     *
//* keycodes. These are cursor movement keycodes as well as the Insert,          *
//* Backspace and Delete keycodes.                                               *
//*                                                                              *
//* These keys are presented by the terminal to the application as three-byte    *
//* or four-byte escape sequences. They are translated to specific 32-bit        *
//* Unicode keycodes modelled on the ncurses library translation method.         *
//* These Unicode keycodes are printing characters (arrow key glyphs)            *
//* indicating the desired direction of cursor movement, etc.                    *
//* -- See the private specialkeyDecode() method for how these keys are captured *
//*    and translated to Unicode keycodes.                                       *
//* -- See the isSpecialKey() method for additional information on handling      *
//*    the "special" keys.                                                       *
//*                                                                              *
//********************************************************************************

int AnsiCmd::acRead ( gString& gsIn, short limit ) const
{
   gsIn.clear() ;                // clear caller's buffer
   int wbSize = gsIn.wAlloc() ;           // size of caller's buffer (wchar_t characters
   wchar_t* wbuff = new wchar_t[wbSize] ; // raw input buffer

   if ( ! this->tset->inpBuf )   //** unbuffered input **
   {
      wchar_t winchar ;          // raw input character
      int     indx ;             // loop control

      for ( indx = ZERO ; indx < limit ; )
      {
         //* Read a character from stdin.       *
         //* (soft-echo is handled by the call) *
         winchar = this->acRead () ;

         //* If the character is not an Enter keycode *
         //* (newline) save it to caller's buffer.    *
         //* For non-printing (and non-special)       *
         //* characters, the nullchar is returned     *
         //* indicating that the received keycode was *
         //* discarded.                               *
         if ( (winchar != NEWLINE) && (winchar != NULLCHAR) )
         {
            wbuff[indx++] = winchar ;
         }
         else if ( winchar == NEWLINE ) // Enter key terminates input
         {
            //* If newline is the first character captured, *
            //* return it to caller. Else discard it.       *
            if ( indx == ZERO )
               wbuff[indx++] = winchar ;
            break ;
         }
      }
      wbuff[indx] = NULLCHAR ;   // null terminator
      gsIn = wbuff ;             // copy data to caller's buffer
   }
   else                          //** buffered input **
   {
      if ( this->wideStream )
      {
         wcin.getline( wbuff, wbSize ) ;
         gsIn = wbuff ;
      }
      else
      {
         // Programmer's Note: This is 1/4th the actual maximum UTF-8 bytes the buffer
         // can hold, but it is assumed that the actual bytes received is _approximately_ 
         // the same as the number of characters typed on the keyboard.
         char* cbuff = new char[wbSize] ;
         cin.getline( cbuff, (wbSize) ) ;
         gsIn = cbuff ;
         delete [] cbuff ;                // release the dynamic allocation
      }
   }

   delete [] wbuff ;                      // release the dynamic allocation

   //* Return the number of characters captured (not incl. null terminator) *
   return ( (gsIn.gschars()) - 1 ) ;

}  //* End acRead() *

//*************************
//*        acRead         *
//*************************
//********************************************************************************
//* Capture a single keystroke from the user.                                    *
//*                                                                              *
//* If 'tset->inpEch' is one of the "softEcho" options:                          *
//*   1) If the captured character is a standard printing character, echo        *
//*      the character to the terminal window.                                   *
//*   2) If the captured keycode is one of the "special" keys, the caller        *
//*      is responsible for processing it or discarding it.                      *
//*      Please refer to the notes in the private specialkeyDecode() below, and  *
//*      the string-oriented acRead(gString& gsIn) above for more information.   *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: keycode from user                                                   *
//********************************************************************************
//* Notes:                                                                       *
//* (Remember: stdin is a FIFO buffer.)                                          *
//*                                                                              *
//* 1) Buffered Input: When buffering is enabled, we must wait for the Enter     *
//*    key, then return the first (oldest) keycode in the buffer. This may be    *
//*    the Enter key itself, or may be old data left in the buffer.              *
//*                                                                              *
//* 2) Unbuffered Input: With unbuffered input we have additional flexibility.   *
//*    If there is data waiting in the buffer, we can return the first (oldest)  *
//*    keycode in the buffer. Otherwise, we wait until the user presses a key.   *
//*    'softEcho' : When one of the 'softEcho' options is specified, the         *
//*    specialkeyDecode() method is called to determine whether the              *
//*    character/byte should be echoed and reported to caller.                   *
//*    See that method for more information.                                     *
//*                                                                              *
//* 3) If the narrow input stream (cin) is active, we promote the captured       *
//*    keycode to wchar_t (int width).                                           *
//*    -- Note that use of 'cin' is strongly discouraged because it can never    *
//*       be assumed that one byte equals one character. It's that kind of       *
//*       1970s thinking which can ruin your reputation as a genius software     *
//*       designer. Don't do it!                                                 *
//*                                                                              *
//* 4) Stale data in the buffer can be a problem. We could just work with        *
//*    whatever data happens to be in the buffer, but this can lead to           *
//*    some random keycode that has been lying around in the buffer being        *
//*    returned to the user (application level) which would be confusing.        *
//*    We could clear the buffer either before or after capturing the character  *
//*    to be returned, but of course this risks losing meaningful input data.    *
//********************************************************************************

wchar_t AnsiCmd::acRead ( void ) const
{
   wchar_t wchar ;

   if ( ! this->tset->inpBuf )      // unbuffered input
   {
      //* For non-blocking read, wait for keypress.       *
      //* If echo enabled, called method echoes the input.*
      if ( ! this->tset->inpBlk )
      {
         while ( !(this->acReadNB ( wchar, NB_REPEAT, NB_DELAY, true )) ) ;
      }
      //* For blocking read, the thread automagically blocks until keypress.*
      else
      {
         if ( this->wideStream )    // wide stream input
            wchar = wcin.get() ;
         else                       // narrow stream input
            wchar = wchar_t(cin.get()) ;
      }

      //* Decode "special keycode" sequences *
      this->specialkeyDecode ( wchar ) ;

      //* For the soft-echo options, if the character is a  *
      //* printing character, and has not yet been echoed   *
      //* to the display, but should be, echo it now.       *
      this->softWrite ( wchar ) ;
   }
   else                             // buffered input
   {
      if ( this->wideStream )       // wide stream input
         wchar = wcin.get() ;
      else                          // narrow stream input
         wchar = wchar_t(cin.get()) ;
   }

   return wchar ;

}  //* End acRead() *

//*************************
//*        acPause        *
//*************************
//********************************************************************************
//* Wait for user to press a key. This is an unbuffered read, with no echo of    *
//* the keycode to the display. Typically, this method would be used after the   *
//* application displays a 'press any key' or similar message.                   *
//*                                                                              *
//* Input  : nl  : (optional, default: false)                                    *
//*                if 'true',  after user keypress, output a newline character   *
//*                if 'false', no output will be written to display              *
//*                                                                              *
//* Returns: keycode from user                                                   *
//********************************************************************************
//* Note: It is likely that input buffering has already been disabled and that   *
//* one of the soft-echo options has been set, (e.g. when called from the        *
//* ACWin class). If not, these settings are adjusted for the read from input    *
//* stream, then restored on exit.                                               *
//*                                                                              *
//* Note: After the keycode is received, the input stream is flushed, that is,   *
//* any data remaining in the input buffer is read and discarded.                *
//* This is done to protect against stale data being left in the buffer in case  *
//* the key pressed by the user generates an escape sequence or another          *
//* multi-byte sequence. If the keycode received is the Escape keycode (0x001B), *
//* the keycode for the space key (0x0020) will be returned instead.             *
//*                                                                              *
//********************************************************************************

wchar_t AnsiCmd::acPause ( bool nl ) const
{
   EchoOpt orig_inpEch = noEcho ;         // initial setting of echo option
   bool orig_inpBuf = this->tset->inpBuf ;// initial setting of buffered-input flag
   wchar_t wkey ;                         // return value

   //* If not already in unbuffered, no-echo mode *
   if ( orig_inpBuf )
      this->tset->setTermInfo ( stdIn, false, noEcho ) ;
   else if ( (orig_inpEch = this->tset->inpEch) == termEcho )
      this->tset->echoOption ( noEcho ) ;

   wkey = this->silentRead () ;
   this->acFlushIn () ;
   if ( wkey == 0x001B )
      wkey = wcsSPACE ;

   //* If specified, write a newline to the terminal window.*
   if ( nl ) { this->ttyWrite ( L"\n" ) ; }

   //* If input buffering was enabled by caller, restore it now. *
   if ( orig_inpBuf )
      this->tset->setTermInfo ( stdIn, true, termEcho ) ;
   else if ( orig_inpEch != noEcho )
      this->tset->echoOption ( orig_inpEch ) ;

   return wkey ;

}  //* End acPause() *

//*************************
//*        acWait         *
//*************************
//********************************************************************************
//* Pause for a specified amount of time OR until user presses a key.            *
//* This can be thought of as a combination of acPause() and nsleep().           *
//*                                                                              *
//* The specified delay is broken into segments and after each delay segment,    *
//* the input stream is pinged to determine whether user has pressed a key.      *
//* The pause continues until either full pause interval is complete, or         *
//* until user presses any key, whichever comes first.                           *
//*                                                                              *
//* Any keycode received is discarded, and before return, the input stream is    *
//* flushed to avoid abandoning any keycode or partial escape sequence.          *
//*                                                                              *
//* Input  : seconds : specifies the sleep interval in seconds (1 minute max)    *
//*                       0 <= seconds <= 60                                     *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::acWait ( uint16_t seconds ) const
{
   const uint16_t step = 3 ;              // tenths of a second for each iteration
   wchar_t wchar ;                        // keycode received
   EchoOpt orig_inpEcho = this->tset->inpEch ; // original stream configuration
   bool    orig_inpBuf = this->tset->inpBuf,
           orig_inpBlk = this->tset->inpBlk ;
   if ( seconds > 60 )                    // range check seconds parameter
      seconds = 60 ;
   uint16_t tenths = seconds * 10 ;       // total tenths of a second

   //* Both unbuffered input AND non-blocking read must be enabled *
   if ( orig_inpBuf )
      this->tset->setTermInfo ( stdIn, false, noEcho ) ;
   else if ( orig_inpEcho == termEcho )
      this->tset->echoOption ( noEcho ) ;
   if ( orig_inpBlk )
      this->tset->blockingInput ( false ) ;

   for ( uint16_t i = ZERO ; i < tenths ; i += step )
   {
      this->nsleep ( step ) ;
      if ( (this->acReadNB ( wchar, ZERO, ZERO )) )
         break ;
   }
   this->acFlushIn () ;                   // flush the input stream

   //* Restore original input stream configuration *
   if ( orig_inpBuf )
      this->tset->setTermInfo ( stdIn, orig_inpBuf, orig_inpEcho ) ;
   else if ( orig_inpEcho != noEcho )
      this->tset->echoOption ( orig_inpEcho ) ;
   if ( orig_inpBlk )
      this->tset->blockingInput ( orig_inpBlk ) ;

}  //* End acWait() *

//*************************
//*      silentRead       *
//*************************
//********************************************************************************
//* Private Method:                                                              *
//* ---------------                                                              *
//* Capture a single keystroke from the user without echo to the window.         *
//*                                                                              *
//* This method is used to capture bytes of an ANSI escape sequence or other     *
//* data that is not echoed to the display.                                      *
//*                                                                              *
//* SHOULD NOT BE CALLED when 'termEcho' is the input echo option.               *
//* If called when 'termEcho' is active, the input will be echoed to the         *
//* terminal window.                                                             *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: keycode from user                                                   *
//********************************************************************************
//* Note: silentRead() should be a non-blocking read in order to process the     *
//*       stand-alone Escape key correctly.                                      *
//*       See in_avail(), egptr(), gptr(), showmanyc(), readsome().              *
//*       That is not enforced at this time, but should be considered.           *
//********************************************************************************

wchar_t AnsiCmd::silentRead ( void ) const
{
   wchar_t wchar ;

   if ( ! this->tset->inpBuf )      // unbuffered input
   {
      //* For non-blocking read, wait for keypress *
      if ( ! this->tset->inpBlk )
      {
         while ( !(this->acReadNB ( wchar, NB_REPEAT, NB_DELAY, true )) ) ;
      }
      //* For blocking read, the thread automagically blocks until keypress.*
      else
      {
         if ( this->wideStream )    // wide stream input
            wchar = wcin.get() ;
         else                       // narrow stream input
            wchar = wchar_t(cin.get()) ;
      }
   }
   else                             // buffered input
   {
      if ( this->wideStream )        // wide stream input
         wchar = wcin.get() ;
      else                          // narrow stream input
         wchar = wchar_t(cin.get()) ;
   }

   return wchar ;

}  //* End silentRead() *

//*************************
//*       acReadNB        *
//*************************
//********************************************************************************
//* Protected Method:                                                            *
//* -----------------                                                            *
//* Capture a single keystroke from the user.                                    *
//* This is a "non-blocking" read from the 'stdin' input stream.                 *
//*                                                                              *
//* IMPORTANT NOTE: Non-blocking input must have been previously enabled for     *
//* the non-blocking read to occur. See acBlockingInput() for more               *
//* information.                                                                 *
//* If non-blocking input HAS NOT been enabled (programmer stupidity), then      *
//* the standard blocking read: acRead(void) will be called instead.             *
//*                                                                              *
//* NOTE: For the sake of the poor user's sanity, the combination of             *
//*       'repeat' count and 'delay' between repeats may not exceed ten (10)     *
//*       seconds, AND 'repeat' may not exceed one hundred (100).                *
//*                'delay'  'repeat'                                             *
//*       Example:    1ms * 100 == 100ms or 0.1 second                           *
//*                  10ms * 100 == 1000ms or 1.0 second                          *
//*                 100ms * 100 == 10,000ms or 10.0 seconds                      *
//*                1000ms *  10 == 10,000ms or 10.0 seconds                      *
//*                                                                              *
//* Input  : wchar   : (by reference) receives the captured keycode (if any).    *
//*                    If a keycode extracted from the input stream, it is       *
//*                    stored here.                                              *
//*                    If keycode not captured, 'wchar' receives the value       *
//*                    returned from the peek() method (probably WEOF i.e. -1)   *
//*          repeat  : maximum number of times to query the input stream while   *
//*                    waiting for input. Range: 0 <= repeat <= 100              *
//*          delay   : delay in milliseconds between pings of the input stream   *
//*                    Range: 1 <= delay <= 1000                                 *
//*                    (This argument ignored if 'repeat' == zero)               *
//*          silent  : (optional, 'true' by default) referenced only when        *
//*                    terminal is set up for one of the soft-echo options       *
//*                    if 'true',  The input will not be echoed by this method,  *
//*                                (but may already have been echoed if the      *
//*                                terminal is configured for termEcho).         *
//*                    if 'false', the input is echoed (or not) according to     *
//*                                the configuraton of the echo option.          *
//*                                (enum EchoOpt). (see 'softWrite()' method)    *
//*          flagPtr : (optional, null pointer by default)                       *
//*                    [the object is actually defined as ios_base::iostate]     *
//*                    If specified, this is a pointer to an integer variable    *
//*                    to receive the status flags (bitfield) after the call to  *
//*                    the pinging method. (This is primarily for debugging.)    *
//*                                                                              *
//* Returns: 'true'  if a character was extracted from the input stream          *
//*                  and stored in the 'wchar' argument                          *
//*          'false' if no data in the input stream                              *
//*                  (wchar receives the value returned from peek())             *
//********************************************************************************

bool AnsiCmd::acReadNB ( wchar_t& wchar, uint16_t repeat, uint16_t delay, 
                        bool silent, uint32_t *flagPtr ) const
{
   const uint32_t maxMS  = 10000 ;     // 10,000 milliseconds (10 seconds)
   const short maxDELAY  = 1000 ;      // default delay if value out-of-range (1.0 second)
   const short maxREPEAT = 100 ;       // default repeat if value out-of-range
   bool status = false ;

   //* If unbuffered input AND non-blocking read enabled *
   if ( ! this->tset->inpBlk && ! this->tset->inpBuf )
   {
      uint32_t peekval ;               // value returned by call to peek()

      //* Perform range check on 'repeat' and 'delay' parameters *
      //* and limit total elapsed time. (see note above)         *
      if ( repeat == ZERO )      { repeat = 1 ; } // prevent counter rollover
      else if ( repeat > maxREPEAT)   { repeat = maxREPEAT ; }
      if ( delay == ZERO )       { delay = 1 ; }
      uint32_t total_ms = delay * repeat ;
      if ( total_ms > maxMS )    { delay = maxDELAY ; }
      short rpt = repeat ;    // for safety, loop control is a signed value

      do
      {
         if ( this->wideStream )          // wide stream query
            peekval = wcin.peek() ;
         else                             // narrow stream query
            peekval = uint32_t(cin.peek()) ;

         if ( flagPtr != NULL )           // if specified, get status flags
         {
            if ( this->wideStream )
               *flagPtr = wcin.rdstate() ;
            else
               *flagPtr = cin.rdstate() ;
         }

         //* If valid data waiting in the queue, extract *
         //* the next character from the input stream.   *
         if ( peekval != WEOF )
         {
            if ( this->wideStream )    // wide stream input
               wchar = wcin.get() ;
            else                       // narrow stream input
               wchar = wchar_t(cin.get()) ;

            //* If specified, AND if one of the the soft-echo *
            //* options, echo character to the display.       *
            if ( ! silent )
            {
               this->specialkeyDecode ( wchar ) ;  // decode special-key sequences
               this->softWrite ( wchar ) ;
            }

            status = true ;
            break ;
         }
         else                          // input error (probably buffer empty)
         {
            if ( this->wideStream )    // clear the error flags
               std::wcin.clear() ;
            else
               std::cin.clear() ;
            wchar = peekval ;          // report the error code
            if ( rpt > 1 )             // don't sleep on last iteration
               this->nsleep ( ZERO, delay ) ;
         }
      }
      while ( --rpt > ZERO ) ;
   }

   //* Caller is an idiot, non-blocking read has not been enabled.*
   else  // (this->tset->inpBlk!=false || this->tset->inpBuf != false)
   {
      wchar = this->acRead() ;
      status = true ;
   }
   return status ;

}  //* End acReadNB() *

//*************************
//*       acPeekNB        *
//*************************
//********************************************************************************
//* Protected Method:                                                            *
//* -----------------                                                            *
//* Capture a single keystroke from the user.                                    *
//* This is a "non-blocking" read from the 'stdin' input stream.                 *
//* The purpose of this method is to prevent corruption of escape sequences      *
//* caused by reading beyond the end of the current escape sequence.             *
//* See specialkeyDecode().                                                      *
//*                                                                              *
//* This method functions the same as acReadNP() above EXCEPT that:              *
//* a) If the character specified by 'pushChar' is seen, it will be left in the  *
//*    input stream, NOT returned to caller. In this case, A COPY of the         *
//*    identified character will be returned; however, the return value will     *
//*    be 'false', indicating that the character was not extracted from the      *
//*    input stream.                                                             *
//* b) The character is never echoed to the display.                             *
//* c) There is no option to return the stream flags.                            *
//*                                                                              *
//*                                                                              *
//* Input  : wchar   : (by reference) receives the captured keycode (if any).    *
//*                    If a keycode extracted from the input stream, it is       *
//*                    stored here.                                              *
//*                    If keycode not captured, 'wchar' receives the value       *
//*                    returned from the peek() method (probably WEOF i.e. -1)   *
//*          repeat  : maximum number of times to query the input stream while   *
//*                    waiting for input. Range: 0 <= repeat <= 100              *
//*          delay   : delay in milliseconds between pings of the input stream   *
//*                    Range: 1 <= delay <= 1000                                 *
//*                    (This argument ignored if 'repeat' == zero)               *
//*          pushChar: character which if encountered, will be pushed back into  *
//*                    the stream.                                               *
//*                                                                              *
//* Returns: 'true'  if a character was extracted from the input stream          *
//*                  and stored in the 'wchar' argument                          *
//*          'false' if no data in the input stream OR 'pushChar' was not        *
//*                  extracted from the stream                                   *
//*                  (wchar receives the value returned from peek())             *
//********************************************************************************

bool AnsiCmd::acPeekNB ( wchar_t& wchar, uint16_t repeat, uint16_t delay, 
                         wchar_t pushChar ) const
{
   const uint32_t maxMS  = 10000 ;     // 10,000 milliseconds (10 seconds)
   const short maxDELAY  = 1000 ;      // default delay if value out-of-range (1.0 second)
   const short maxREPEAT = 100 ;       // default repeat if value out-of-range
   bool status = false ;

   //* If unbuffered input AND non-blocking read enabled *
   if ( ! this->tset->inpBlk && ! this->tset->inpBuf )
   {
      uint32_t peekval ;               // value returned by call to peek()

      //* Perform range check on 'repeat' and 'delay' parameters *
      //* and limit total elapsed time. (see note above)         *
      if ( repeat == ZERO )      { repeat = 1 ; } // prevent counter rollover
      else if ( repeat > maxREPEAT)   { repeat = maxREPEAT ; }
      if ( delay == ZERO )       { delay = 1 ; }
      uint32_t total_ms = delay * repeat ;
      if ( total_ms > maxMS )    { delay = maxDELAY ; }
      short rpt = repeat ;    // for safety, loop control is a signed value

      do
      {
         if ( this->wideStream )          // wide stream query
            peekval = wcin.peek() ;
         else                             // narrow stream query
            peekval = uint32_t(cin.peek()) ;

         //* If valid data waiting in the queue, extract *
         //* the next character from the input stream.   *
         if ( (peekval != WEOF) && (peekval != uint32_t(pushChar)) )
         {
            if ( this->wideStream )    // wide stream input
               wchar = wcin.get() ;
            else                       // narrow stream input
               wchar = wchar_t(cin.get()) ;

            status = true ;
            break ;
         }
         else                          // input error (probably buffer empty)
         {
            if ( this->wideStream )    // clear the error flags
               std::wcin.clear() ;
            else
               std::cin.clear() ;
            wchar = peekval ;          // report the error code
            if ( rpt > 1 )             // don't sleep on last iteration
               this->nsleep ( ZERO, delay ) ;
         }
      }
      while ( (--rpt > ZERO) && (peekval != uint32_t(pushChar)) ) ;
   }

   //* Caller is an idiot, non-blocking read has not been enabled.*
   else  // (this->tset->inpBlk!=false || this->tset->inpBuf != false)
   {
      wchar = this->acRead() ;
      status = true ;
   }
   return status ;

}  //* End acPeekNB() *

//*************************
//*   specialkeyDecode    *
//*************************
//********************************************************************************
//* PRIVATE METHOD:                                                              *
//* ---------------                                                              *
//* Called only by the acRead(void) and acReadNB() methods.                      *
//*                                                                              *
//* Determine whether the specified keycode represents the beginning of one of   *
//* the "special" non-printing keycodes or escape sequences.                     *
//* If so, the remaining data of the sequence are captured and the decoded       *
//* value) is returned so the application can either perform the indicated       *
//* action or simply discard the character.                                      *
//*                                                                              *
//* Note that these special keycodes are rendered "harmless" by assigning to     *
//* 'wchar' a printing character not readily available from the keyboard,        *
//* so if it is not handled by the caller, it will simply be printed  to the     *
//* terminal window causing minor ugliness but no harm.                          *
//* See below for notes on translation of ANSI escape sequences and ASCII        *
//* control codes into "special" keycodes.                                       *
//*                                                                              *
//* Programmer's Note: If the decoded value is to be displayed (for debugging    *
//* purposes or because the code was not handled), then the terminal must be     *
//* using a fully UTF-8 compliant font.                                          *
//*   Examples: Liberation Mono, Dejavu Sans Mono, Monospace.                    *
//* If the font is not UTF-8 compliant, the ugliness may become more severe.     *
//*                                                                              *
//*                                                                              *
//* Input  : wchar  : (by reference) on entry, contains either a graphing        *
//*                   character (printing character), OR the possible first      *
//*                   byte of a supported escape sequence or ASCII control       *
//*                   code, or garbage.                                          *
//*                   On Return:                                                 *
//*                   -- If a graphing character, it is returned unchanged.      *
//*                   -- If a supported ANSI escape sequence or ASCII control    *
//*                      code was captured, the decoded value is returned in     *
//*                      'wchar' See notes below on decoding.                    *
//*                   -- Else, returns the null character '\0' (0x0000).         *
//*                                                                              *
//* Returns: 'true'  if the character returned is a decoded "special" keycode    *
//*                  OR if the character is an ordinary graphing character.      *
//*          'false' if the input value was discarded and replaced by            *
//*                  a null character ('\0').                                    *
//********************************************************************************
//* Valid "soft-echo" keys:                                                      *
//*  1) Printing characters. This includes graphical (visible) characters        *
//*     plus the space ' ' character, but not control characters                 *
//*              '\t' (tab), '\n' (newline), '\f', '\r', '\v'                    *
//*     which are often thought of as whitespace characters.                     *
//*     See the documentation for the isprint(std::locale) library function      *
//*     for more information about how the printing-character determination is   *
//*     made.                                                                    *
//*                                                                              *
//*  2) Enter key. Newline/Linefeed Ctrl-J (0Ah) is returned unmodified, but     *
//*     IS NOT considered to be a valid printing character because its function  *
//*     is to terminate the key-input sequence. Therefore it can be echoed or    *
//*     discarded according to the needs of the application.                     *
//*                                                                              *
//*  3) Tab and Shift+Tab keys. These keycodes are primarily cursor-positioning  *
//*     keys because they move the cursor to some predefined column, field or    *
//*     cell within a formatted application window. However they are rather      *
//*     useless in a terminal window, and are discarded by default by the        *
//*     terminal shell program. They are however of value in a formatted         *
//*     window such as a table or a form created within the terminal window.     *
//*         Tab Key      : ASCII TAB (09h)                                       *
//*         Shift+Tab key: Escape sequence: "ESC[5A"                             *
//*                                                                              *
//*  4) The cursor-positioning keys. These are: Up Arrow, Down Arrow,            *
//*     Left Arrow, Right Arrow, Home key, End key, Page Up key, Page Down key.  *
//*     They are provided by the keyboard controller as 3-byte or 4-byte         *
//*     escape sequences. (see below)                                            *
//*                                                                              *
//*  5) Backspace and Delete keys. This is the nightmare of Linux terminals.     *
//*     The terminal default encoding for these keys will usually be set in      *
//*     the "compatibility" section of the preferences.                          *
//*     These defaults are (usually):                                            *
//*         Backspace Key: ASCII DEL (7Fh)                                       *
//*         Delete Key   : Escape sequence: "ESC[3~" 1B 5B 33 7E                 *
//*     These encodings may be swapped, or the same encoding could have been     *
//*     assigned to both keys (although that would be foolish).                  *
//*                                                                              *
//*     Alternate encodings are also available for each of these keys:           *
//*         CTRL+H (08h) which is the ASCII backspace code.                      *
//*         The so-called "TTY Erase" which on gnometerm at least, is also       *
//*         the ASCII Delete keycode (7Fh).                                      *
//*                                                                              *
//*     -- To prevent insanity, we assume that the Backspace and Delete keys     *
//*        are provided in the default encodings.                                *
//*        -- If we see an ASCII Delete character (7Fh), OR an ASCII backspace   *
//*           character (08h) it is assumed to have been generated by the        *
//*           Backspace key.                                                     *
//*        -- If we see the "ESC[3~" escape sequences, it is assumed to have     *
//*           been generated by the Delete key.                                  *
//*                                                                              *
//*  6) Insert key. The function of this key is to toggle between Insert Mode    *
//*     and Overstrike Mode. When writing a method to collect key input from     *
//*     the user, it is convenient to know if the user tries to toggle the       *
//*     input mode. This key is provided by the keyboard controller as a 4-byte  *
//*     escape sequence. (see below)                                             *
//*                                                                              *
//*  7) Soft Enter key. This is a two-byte escape sequence generated by          *
//*     Alt+Enter (actually leftAlt+Enter). This provides a way for the user to  *
//*     signal a newline during edit without triggering an exit from the edit    *
//*     routine. It is practical only during edit of a multi-row skField object, *
//*     but may be applicable to other situations in the future.                 *
//*     Note that if the ALT key is held down when the Enter key is pressed,     *
//*     then this sequence is transmitted to the application _regardless_ of     *
//*     whether the Shift and/or Ctrl keys are also held down. At a guess, this  *
//*     is probably due to the incomplete nature of the Wayland implementation.  *
//*                                                                              *
//*  8) Copy, Cut, Paste:                                                        *
//*     Traditionally, three ASCII control codes Ctrl+C, Ctrl+X, Ctrl+V          *
//*     implement Copy, Cut and Paste, respectively. Unfortunately Ctrl+C is     *
//*     also used as the Break signal i.e. the Panic Button. To implement        *
//*     the Copy/Cut/Paste functionality, it is necessary to configure a         *
//*     signal handler to capture the Break signal and convert it to a keycode   *
//*     that can be read as a normal key.                                        *
//*     In addition, sharing the data with other applications requires access    *
//*     to the system clipboard. In our NcDialog API this is handled through     *
//*     a third-party mechanism. See the NcDialog API documentation for more     *
//*     information. (A "local clipboard" is available to transfer data within   *
//*     the application. See the documentation for more information.)            *
//*                                                                              *
//*  9) ASCII Control Codes and Ctrl+Alt+n group:                                *
//*     a) ASCII single-byte control codes. The value received is the same       *
//*        regardless of whether the Shift key is pressed; that is:              *
//*        Ctrl+a == 1  and Ctrl+Shift+a == 1.                                   *
//*     b) As mentioned elsewhere, the Ctrl+C _should be_ 0x03, but that code is *
//*        tied to the Break Signal. To recapture Ctrl+C it is necessary for     *
//*        the break handler to capture the signal and push the code back into   *
//*        the input stream. Break signal handler type "cchtCopy" performs       *
//*        this conversion.                                                      *
//*     c) As mentioned elsewhere, the Ctrl+Z _should be_ 0x1A, but that code is *
//*        tied to the Suspend Signal. To recapture Ctrl+Z it is necessary for   *
//*        the suspend handler to capture the signal and push the code back into *
//*        the input stream. The suspend signal handler performs this conversion.*
//*     f) By default, the Ctrl+S and Ctrl+Q keys are used by the terminal to    *
//*        control the data flow through the 'stdout' stream. Since this         *
//*        functionality is completely useless (unless you have a Teletype       *
//*        machine on your desk), these signals are captured and converted to    *
//*        ordinary keycodes when unbuffered input is enabled. (see below)       *
//*                                                                              *
//*        HEX BYTE    HEX BYTE    HEX BYTE    HEX BYTE                          *
//*        ---------   ---------   ---------   ----------                        *
//*        01 Ctrl+A   0B Ctrl+K   15 Ctrl+U   1D Ctrl+5 (Ctrl+])                *
//*        02 Ctrl+B   0C Ctrl+L   16 Ctrl+V   1E Ctrl+6 (Ctrl+^)                *
//*        03 Ctrl+C   0E Ctrl+N   17 Ctrl+W   1F Ctrl+7 (Ctrl+_)                *
//*        04 Ctrl+D   0F Ctrl+O   18 Ctrl+X                                     *
//*        05 Ctrl+E   10 Ctrl+P   19 Ctrl+Y                                     *
//*        06 Ctrl+F   12 Ctrl+R   1A Ctrl+Z                                     *
//*        07 Ctrl+G   14 Ctrl+T                                                 *
//*                                                                              *
//*        Exceptions:                                                           *
//*        08 Ctrl+H  Backspace, decoded as wcsBKSP (21B0h)                      *
//*        09 Ctrl+I  Tab,       decoded as wcsTAB  (21B1h)                      *
//*        0A Ctrl+J  Enter key i.e. newline                                     *
//*        0D Ctrl+M  (reported as Ctrl+J, newline)                              *
//*        11 Ctrl+Q  Captured by the terminal (release terminal output)         *
//*                   This signal is disabled when setting unbuffered input.     *
//*        13 Ctrl+S  Captured by the terminal (pause terminal output)           *
//*                   This signal is disabled when setting unbuffered input.     *
//*        1A Ctrl+Z  This generates the SIGTSTP "Stop" signal which puts the    *
//*                   current process to sleep. (wake using the 'fg' utility)    *
//*                   This signal is captured and converted to wcsCTL_Z.         *
//*        1B  ---    Escape key, decoded as wcsESCAPE (21BBh)                   *
//*        1C Ctrl+4  This generates the SIGQUIT "Quit" signal which kills the   *
//*                   current application. This prints ^\ in cooked mode.        *
//*                   This signal is captured and handled as if it were the      *
//*                   break signal: an orderly shutdown.                         *
//*                  (see acCaptureQuitSignal())                                 *
//*     - - - - -                                                                *
//*     See info Chapter 24.2 Standard Signals/Termination Signals:              *
//*     SIGQUIT, SIGKILL, SIGTERM, SIGINT, SIGSTOP, SIGTSTP                      *
//*     Chapter 17.4.9 Special Characters                                        *
//*     Flow control in termios.c_cc[VSTART] and termios.c_cc[VSTOP]             *
//*     - - - - -                                                                *
//*                                                                              *
//*     c) Ctrl+Alt+ a-z and Ctrl+Alt+ A-Z sequences:                            *
//*        The following are reported as two-byte esccape sequences.             *
//*        These codes are the same regardless of the state of the Shift key.    *
//*        Ctrl+Alt+a (1B 01)  Ctrl+Alt+i (1B 09)  Ctrl+Alt+t (1B 14)            *
//*        Ctrl+Alt+b (1B 02)  Ctrl+Alt+k (1B 0B)  Ctrl+Alt+u (1B 15)            *
//*        Ctrl+Alt+d (1B 04)  Ctrl+Alt+l (1B 0C)  Ctrl+Alt+v (1B 16)            *
//*        Ctrl+Alt+e (1B 05)  Ctrl+Alt+n (1B 0E)  Ctrl+Alt+w (1B 17)            *
//*        Ctrl+Alt+f (1B 06)  Ctrl+Alt+o (1B 0F)  Ctrl+Alt+x (1B 18)            *
//*        Ctrl+Alt+g (1B 07)  Ctrl+Alt+p (1B 10)  Ctrl+Alt+y (1B 19)            *
//*        Ctrl+Alt+h (1B 08)  Ctrl+Alt+r (1B 12)                                *
//*                                                                              *
//*        Unavailable sequences:                                                *
//*        Ctrl+Alt+c  (same as Ctrl+C)                                          *
//*        Ctrl+Alt+j  (same as wcsAENTER)                                       *
//*        Ctrl+Alt+m  (same as wcsAENTER)                                       *
//*        Ctrl+Alt+z  (same as Ctrl+z signal code)                              *
//*        Ctrl+Alt+q  (same as Ctrl+q signal)                                   *
//*        Ctrl+Alt+s  (same as Ctrl+s signal)                                   *
//*                                                                              *
//* 10) ALT+ and Shift+ and CTRL+ key combinations:                              *
//*     Note that ALT keys refer to the _left_ Alt key only. The right Alt key   *
//*     is used to produce keycodes not directly available on the keyboard.      *
//*                                                                              *
//*     a) The following are reported as two-byte escape sequences.              *
//*        Alt+a (1B 61) - Alt+z (1B 7A)     lower-case alpha                    *
//*        Alt+A (1B 41) - Alt+Z (1B 5A)     upper-case alpha                    *
//*        Alt+  !  "  #  $  %  &  '  (  )   various punctuation (1B xx)         *
//*              *  +  ,  -  .  /  :  ;                                          *
//*              <  =  >  ?  @  \  ]  ^  _  {  |  }                              *
//*                                                                              *
//*        Note that Alt+1 through Alt+0 are silently converted to their shifted *
//*        equivalents: Alt+! through Alt+).                                     *
//*                                                                              *
//*        These escape sequences are converted to constant values of the        *
//*        wcsALT_nnn group.                                                     *
//*                                                                              *
//*        Unavailable sequences:                                                *
//*        ALT+[ is the sequence indicating one of the mainline escape sequences.*
//*        ALT+` and ALT+~ are used by the window manager to select window.      *
//*                                                                              *
//*     b) The following are reported as six-byte escape sequences.              *
//*        Alt+Up       1B 5B 31 3B 33 41    wcsAUP                              *
//*        Alt+Down     1B 5B 31 3B 33 42    wcsADOWN                            *
//*        Alt+Right    1B 5B 31 3B 33 43    wcsARIGHT                           *
//*        Alt+Left     1B 5B 31 3B 33 44    wcsALEFT                            *
//*        Alt+End      1B 5B 31 3B 33 46    wcsAEND                             *
//*        Alt+Home     1B 5B 31 3B 33 48    wcsAHOME                            *
//*        Alt+PgUp     1B 5B 35 3B 33 7E    wcsAPGUP                            *
//*        Alt+PgDn     1B 5B 36 3B 33 7E    wcsAPGDN                            *
//*                                                                              *
//*     c) Miscellaneous keycodes                                                *
//*        These keycodes do not fit neatly within any of the above keycode      *
//*        groups, but they are available, so we capture them.                   *
//*        -- For SHIFT plus navigation keys and CTRL plus navigation keys, see  *
//*           special keys below.                                                *
//*        -- Ctrl + navigation keys.                                            *
//*           Ctrl+UpArrow       1B 5B 31 3B 35 41   wcsCUP                      *
//*           Ctrl+DownArrow     1B 5B 31 3B 35 42   wcsCDOWN                    *
//*           Ctrl+RightArrow    1B 5B 31 3B 35 43   wcsCRIGHT                   *
//*           Ctrl+LeftArrow     1B 5B 31 3B 35 44   wcsCLEFT                    *
//*           Ctrl+End           1B 5B 31 3B 35 46   wcsCEND                     *
//*           Ctrl+Home          1B 5B 31 3B 35 48   wcsCHOME                    *
//*           Ctrl+Alt+PgUp      1B 5B 35 3B 37 7E   wcsCAPGUP                   *
//*           Ctrl+Alt+PgDn      1B 5B 36 3B 37 7E   wcsCAPGDN                   *
//*        -- Delete key plus conditioning keys.                                 *
//*           Shift+ Delete      1B 5B 33 3B 32 7E   wcsSDEL                     *
//*           Alt  + Delete      1B 5B 33 3B 33 7E   wcsADEL                     *
//*           Alt  + Shift+Del   1B 5B 33 3B 34 7E   wcsASDEL                    *
//*           Ctrl + Delete      1B 5B 33 3B 35 7E   wcsCDEL                     *
//*           Ctrl + Shift+Del   1B 5B 33 3B 36 7E   wcsCSDEL                    *
//*           Ctrl+Alt+Sh+Del    1B 5B 33 3B 38 7E   wcsCASDEL                   *
//*        -- Ctrl+PgUp and Ctrl+PgDown are used to select terminal tab and are  *
//*           therefore unavailable to terminal applications.                    *
//*        -- Note that the following sequences may not be seen by the console   *
//*           application because they may be used by the terminal (bash) to     *
//*           page up and down through the displayed data or to re-order tabs    *
//*           within the terminal window. (These may be configurable.)           *
//*           Shift+PgUp         1B 5B 35 3B 32 7E   wcsSPGUP                    *
//*           Shift+PgDn         1B 5B 36 3B 32 7E   wcsSPGDN                    *
//*           Alt+Shift+PgUp     1B 5B 35 3B 34 7E   wcsASPGUP                   *
//*           Alt+Shift+PgDn     1B 5B 36 3B 34 7E   wcsASPGDN                   *
//*           Ctrl+Shift+PgUp    1B 5B 35 3B 36 7E   wcsCSPGUP                   *
//*           Ctrl+Shift+PgDn    1B 5B 36 3B 36 7E   wcsCSPGDN                   *
//*                                                                              *
//* 11) Function key keycodes                                                    *
//*     These keys may be stand-along keycodes or presented in combination with  *
//*     one or more conditioning keys: Shift, Ctrl or Ctrl+Shift.                *
//*     Some of these will be captured by the terminal emulator by default;      *
//*     however, the terminal can be configured to ignore them and pass them     *
//*     through to the application.                                              *
//*     a) F01 through F04 are a special case because they are encoded as        *
//*        3-byte sequences:                                                     *
//*        F01   1B 4F 50         wcsF01                                         *
//*        F02   1B 4F 51         wcsF02                                         *
//*        F03   1B 4F 52         wcsF03                                         *
//*        F04   1B 4F 53         wcsF04                                         *
//*     b) The remaining unconditioned function keys are 5-byte sequences:       *
//*        F05   1B 5B 31 35 7E   wcsF05                                         *
//*        F06   1B 5B 31 37 7E   wcsF06                                         *
//*        F07   1B 5B 31 38 7E   wcsF07                                         *
//*        F08   1B 5B 31 39 7E   wcsF08                                         *
//*        F09   1B 5B 32 30 7E   wcsF09                                         *
//*        F10   1B 5B 32 31 7E   wcsF10                                         *
//*        F11   1B 5B 32 33 7E   wcsF11                                         *
//*        F12   1B 5B 32 34 7E   wcsF12                                         *
//*     c) The conditioned function keys are encoded as 6- or 7-byte sequences.  *
//*        SF01 1B 5B 31 3B 32 50    wcsSF01  CF01 1B 5B 31 3B 35 50     wcsCF01 *
//*        SF02 1B 5B 31 3B 32 51    wcsSF02  CF02 1B 5B 31 3B 35 51     wcsCF02 *
//*        SF03 1B 5B 31 3B 32 52    wcsSF03  CF03 1B 5B 31 3B 35 52     wcsCF03 *
//*        SF04 1B 5B 31 3B 32 53    wcsSF04  CF04 1B 5B 31 3B 35 53     wcsCF04 *
//*        SF05 1B 5B 31 35 3B 32 7E wcsSF05  CF05 1B 5B 31 35 3B 35 7E  wcsCF05 *
//*        SF06 1B 5B 31 37 3B 32 7E wcsSF06  CF06 1B 5B 31 37 3B 35 7E  wcsCF06 *
//*        SF07 1B 5B 31 38 3B 32 7E wcsSF07  CF07 1B 5B 31 38 3B 35 7E  wcsCF07 *
//*        SF08 1B 5B 31 39 3B 32 7E wcsSF08  CF08 1B 5B 31 39 3B 35 7E  wcsCF08 *
//*        SF09 1B 5B 32 30 3B 32 7E wcsSF09  CF09 1B 5B 32 30 3B 35 7E  wcsCF09 *
//*        SF10 1B 5B 32 31 3B 32 7E wcsSF10  CF10 1B 5B 32 31 3B 35 7E  wcsCF10 *
//*        SF11 1B 5B 32 33 3B 32 7E wcsSF11  CF11 1B 5B 32 33 3B 35 7E  wcsCF11 *
//*        SF12 1B 5B 32 34 3B 32 7E wcsSF12  CF12 1B 5B 32 34 3B 35 7E  wcsCF12 *
//*     D) The Ctrl+Shift+Fnn group are silently converted to their              *
//*        Ctrl+Fnn equivalents.                                                 *
//*                                                                              *
//* 12) All other escape sequences and non-printing control codes are silently   *
//*     captured and discarded.                                                  *
//*     a) Escape. The dilemma with the escape is is determining whether it is   *
//*        an actual escape keycode, or whether it introduces an escape sequence.*
//*        To determine this, query of the input stream is done with a time-out  *
//*        so that if the escape code (1Bh) is received, but another code is not *
//*        received within the timeout period, it is assumed that the received   *
//*        code is from a stand-alone Escape keypress.                           *
//*        -- Note that the wait for the code (if any) following receipt of the  *
//*           Escape code can be VERY long--approximately half a second;         *
//*           however, the interface between the keyboard controller and the     *
//*           terminal window requires a long time to determine whether the      *
//*           escape code signals the beginning of an escape sequence.           *
//*     b) If the code received following the Escape is the left-square-bracket  *
//*        '[' (5Bh), it is assumed that this key combination signals an escape  *
//*        sequence, although the number of codes in the sequence is initially   *
//*        unknown.                                                              *
//*     c) Note that escape sequences can be of any length between 3 and 7 bytes *
//*        (or more) and there is no way to know in advance the format or length *
//*        of any given sequence. For this reason, some partial escape sequences *
//*        may be returned to the application as garbage characters.             *
//*        Conditioned Function keys F01-F12 and key combinations such as        *
//*        CTRL+ALT+7 are the most likely causes of such garbage. Unfortunately, *
//*        there is no practical way to accurately anticipate and capture all    *
//*        possible escape sequence formats without a dedicated escape-sequence  *
//*        processor.                                                            *
//*        -- Special Case: An escape sequence of only two(2) characters i.e.    *
//*           the Escape code followed ONLY by '[' (5Bh) is the ALT+[ key        *
//*           combination.                                                       *
//*     d) The terminal handles escape sequences automagically when input is     *
//*        buffered, so we will not see them when 'termEcho' is active.          *
//*        Note however that some artifacts may be displayed by the terminal     *
//*        handler due to incomplete processing of some escape sequences.        *
//*                                                                              *
//*                                                                              *
//* Capture and Translation of the "special" keycodes.                           *
//* --------------------------------------------------                           *
//* The cursor-positioning keys, as well as the Delete and Insert keys are       *
//* provided by the keyboard controller as escape sequences of the form:         *
//*    ESC[nn(~)                                                                 *
//*    1B 5B nn 7E                                                               *
//* where 'nn' is a byte value(s) representing ASCII characters 33=='3',         *
//* 41=='A', etc.                                                                *
//* Some of the longer sequences contain two numeric values seperated by the     *
//* semicolon character(3Bh) and many are terminated by the tilde character(7Eh).*
//* (As described above, the Backspace is provided as ASCII 7Fh by default.)     *
//*                                                                              *
//* KEY NAME           ESCAPE SEQ.         CONST NAME  GLYPH  HEX CODE           *
//* ----------------   -----------------   ----------  -----  --------           *
//* Up Arrow           1B 5B 41            wcsUP         ↑    2091               *
//* Down Arrow         1B 5B 42            wcsDOWN       ↓    2093               *
//* Right Arrow        1B 5B 43            wcsRIGHT      →    2192               *
//* Left Arrow         1B 5B 44            wcsLEFT       ←    2190               *
//* Home Key           1B 5B 48            wcsHOME       ↖    2196               *
//* End Key            1B 5B 46            wcsEND        ↘    2198               *
//* Page Up            1B 5B 35 7E         wcsPGUP       ↗    2197               *
//* Page Down          1B 5B 36 7E         wcsPGDN       ↙    2199               *
//* Backspace          7F                  wcsBKSP       ↰    21B0               *
//* Delete Key         1B 5B 33 7E         wcsDELETE     ↳    21B3               *
//* Tab Key            09                  wcsTAB        ↱    21B1               *
//* Shift+Tab          1B 5B 5A            wcsSTAB       ↲    21B2               *
//* Insert Key         1B 5B 32 7E         wcsINSERT     ↥    21A5               *
//* Alt+Enter          1B 0A               wcsAENTER     ↧    21A7               *
//* Shift+UpArrow      1B 5B 31 3B 32 41   wcsSUP        ↾    21BE               *
//* Shift+DownArrow    1B 5B 31 3B 32 42   wcsSDOWN      ⇂    21C2               *
//* Shift+RightArrow   1B 5B 31 3B 32 43   wcsSRIGHT     ⇀    21C0               *
//* Shift+LeftArrow    1B 5B 31 3B 32 44   wcsSLEFT      ↼    21BC               *
//* Shift+Home         1B 5B 31 3B 32 48   wcsSHOME      ⇇    21C7               *
//* Shift+End          1B 5B 31 3B 32 46   wcsSEND       ⇉    21C9               *
//*                                                                              *
//*                                                                              *
//* Processing the captured and translated "special" keycodes.                   *
//* ----------------------------------------------------------                   *
//* These are the keycodes are intended primarily for editing of text within     *
//* a defined input field (see the skForm and skField classes).                  *
//* Note: Currently PageDown and PageUp are automatically converted to           *
//*       Tab and Shift+Tab, respectively.                                       *
//*                                                                              *
//* Option: softEchoA: All (default):                                            *
//*         Translate all special keycodes and return them to caller.            *
//*         NOTE: This is the option used internally within the ACWin class      *
//*         which redefines certain keycode according to the type of field       *
//*         being edited.                                                        *
//*                                                                              *
//* Option: softEchoB: Basic set of special keycodes.                            *
//*         This group includes the cursor-movement keys and the edit key group. *
//*         This group excludes the text-selection and copy/cut/paste keycodes.  *
//*                                                                              *
//* Option: softEchoC: Cursor keys, etc.:                                        *
//*         Translate only thecursor-positioning keys UpArrow, DownArrow,        *
//*         LeftArrow, RightArrow, Home, End, PageUp, PageDown, Tab and          *
//*         Shift+Tab keys. Discard all other special keycodes.                  *
//*                                                                              *
//* Option: softEchoE: Editing keys:                                             *
//*         Translate and return the edit keys: Backspace, Delete, Insert,       *
//*         Tab and ShiftTab keys only. Discard the cursor-positioning keys.     *
//*         This option is useful primarily during development and debugging.    *
//*                                                                              *
//* Option: softEchoD: Disable:                                                  *
//*         Translation of special keycode is disabled and all special           *
//*         keycodes are captured and discarded.                                 *
//*                                                                              *
//********************************************************************************

bool AnsiCmd::specialkeyDecode ( wchar_t& wchar ) const
{
   #define DEBUG_SOFTKEY (0)              // For debugging only

   const wchar_t wBKSP  = L'\b' ;         // ASCII Backspace (08h)
   const wchar_t wDEL   = 0x007F ;        // ASCII Delete
   const wchar_t wTAB   = 0x0009 ;        // ASCII Tab
   const wchar_t wTILDE = L'~' ;          // ASCII Tilde
   const wchar_t ESCAPE = 0x001B ;        // ASCII Escape
   const wchar_t LBRACKET = L'[' ;        // ASCII Left-square-bracket
   const wchar_t wONE   = L'1' ;          // byte 3 of 6-byte sequence
   const wchar_t wSEMI  = L';' ;          // byte 4 of 6-byte sequence
   const wchar_t wTWO   = L'2' ;          // byte 5 of 6-byte sequence
   const wchar_t wTHREE = L'3' ;          // alternate byte 5 of 6-byte sequence
   const wchar_t wFOUR  = L'4' ;          // alternate byte 5 of 6-byte sequence
   const wchar_t wFIVE  = L'5' ;          // alternate byte 5 of 6-byte sequence
   const wchar_t wSIX   = L'6' ;          // alternate byte 3 of 6-byte sequence
   const uint16_t readREPEAT = (NB_REPEAT / 2), // timing for reading input stream
                  readDELAY = (NB_DELAY / 2) ;


   wchar_t capCode ;                      // code captured from input stream
   bool validKey = false ;                // return value


   //* Graphing character (see notes above).   *
   //* Printing characters (as defined by the  *
   //* current locale) are assumed to be valid.*
   if ( (isprint ( wchar, *this->ioLocale )) )
      validKey = true ;

   //* Enter key returned unmodified, but marked as an *
   //* invalid character, i.e. end-of-input signal.    *
   else if ( wchar == NEWLINE )
   {
      /* do nothing */

      #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_SOFTKEY != 0
      if ( (ofsdbg.is_open()) )
      {
         gsdbg.compose( "specialkeyDecode  ( Nln:%02X )", &wchar ) ;
         ofsdbg << gsdbg.ustr() << endl ;
      }
      #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_SOFTKEY
   }

   //* ASCII Backspace, Delete and Tab keycodes are translated   *
   //* to constant values for caller to process. See notes above.*
   else if ( (wchar == wDEL) || (wchar == wBKSP) || (wchar == wTAB) )
   {
      #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_SOFTKEY != 0
      capCode = wchar ;
      #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_SOFTKEY

      if ( wchar == wTAB )
         wchar = wcsTAB ;
      else
         wchar = wcsBKSP ;

      validKey = true ;       // raw ASCII control code was decoded

      #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_SOFTKEY != 0
      if ( (ofsdbg.is_open()) )
      {
         gsdbg.compose( "specialkeyDecode  ( Asc:%02X '%C' )", &capCode, &wchar ) ;
         ofsdbg << gsdbg.ustr() << endl ;
      }
      #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_SOFTKEY
   }  // ASCII Del/Bksp or Tab

   //* If an ASCII Escape, is it a stand-alone keycode *
   //* or the beginning of an escape sequence?         *
   else if ( wchar == ESCAPE )
   {
      //* If no additional byte captured - assume stand-alone Escape key. *
      if ( !(this->acPeekNB ( capCode, readREPEAT, NB_DELAY, 0x0001 )) )
      { wchar = wcsESCAPE ; validKey = true ; }

      //* If sequential Escape keys, assume an over-caffinated user's *
      //* finger on the Escape key and discard the duplicate.         *
      else if ( capCode == ESCAPE )
      { wchar = wcsESCAPE ; validKey = true ; }

      //* Escape sequence beginning with the standard *
      //* byte pair: "\x1B["                          *
      else if ( capCode == LBRACKET )
      {
         wchar_t w3 = WEOF, w4 = WEOF,    // additional sequence bytes
                 w5 = WEOF, w6 = WEOF, w7 = WEOF ;

         //* Read the remainder of the sequence (up to 7 bytes).  *
         //* a) If sequence contains a tilde, it is always the    *
         //*    terminating character of the sequence.            *
         //* b) If WEOF (0xFFFFFFFF) is returned, it indicates    *
         //*    that all bytes of the sequence have been captured.*
         //* c) If ESCAPE (1Bh) is returned, but is flagged as an *
         //*    invalid character, it means that a subsequent     *
         //*    escape sequence was identified in the input       *
         //*    stream, but WAS NOT extracted from the stream.    *
         //*    (This can happen if keyboard auto-repeat active.) *
         //* Programmer's Note: This read sequence is a bit slow, *
         //* but the code is cleaner and more reliable than       *
         //* testing each group before scanning additional bytes. *
         if ( (this->acPeekNB ( w3, readREPEAT, readDELAY, 0x0001 )) )
         {
            if ( (this->acPeekNB ( w4, readREPEAT, readDELAY, ESCAPE ))
                 && (w4 != wTILDE) )
            {
               if ( (this->acPeekNB ( w5, readREPEAT, readDELAY, ESCAPE ))
                    && (w5 != wTILDE) )
               {
                  if ( (this->acPeekNB ( w6, readREPEAT, readDELAY, ESCAPE ))
                       && (w6 != wTILDE) )
                  {
                     if ( !(this->acPeekNB ( w7, readREPEAT, readDELAY, ESCAPE )) )
                        w7 = wchar_t(WEOF) ;
                  }
                  else if ( w6 == ESCAPE ) { w6 = wchar_t(WEOF) ; }
               }
               else if ( w5 == ESCAPE ) { w5 = wchar_t(WEOF) ; }
            }
            else if ( w4 == ESCAPE ) { w4 = wchar_t(WEOF) ; }
         }

         #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_SOFTKEY != 0
         if ( (ofsdbg.is_open()) )
         {
            gsdbg.compose( "specialkeyDecode  ( Esc%C %02X %02X %02X %02X %02X )", 
                           &capCode, &w3, &w4, &w5, &w6, &w7 ) ;
            ofsdbg << gsdbg << endl ;
         }
         #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_SOFTKEY

         //* If byte 7 was captured, test the sequence *
         //* for one of the conditioned function keys. *
         if ( w7 != wchar_t(WEOF) )
         {
// All 7-byte sequences converted successfully.
            if ( (w5 == wSEMI) && (w7 == wTILDE) && ((w3 == wONE) || (w3 == wTWO)) )
            {
               if ( w6 == wTWO )          // Shift+Fnn
               {
                  if ( w3 == wONE )
                  {
                     validKey = true ;    // assume a valid, supported escape sequence

                     switch ( w4 )
                     {
                        case L'5':  wchar = wcsSF05;   break ; // 35h
                        case L'7':  wchar = wcsSF06;   break ; // 37h
                        case L'8':  wchar = wcsSF07;   break ; // 38h
                        case L'9':  wchar = wcsSF08;   break ; // 39h
                        default:    wchar = NULLCHAR ; validKey = false ; break ;
                     }
                  }
                  else  // (w3 == wTWO)
                  {
                     validKey = true ;    // assume a valid, supported escape sequence

                     switch ( w4 )
                     {
                        case L'0':  wchar = wcsSF09;   break ; // 30h
                        case L'1':  wchar = wcsSF10;   break ; // 31h
                        case L'3':  wchar = wcsSF11;   break ; // 33h
                        case L'4':  wchar = wcsSF12;   break ; // 34h
                        default:    wchar = NULLCHAR ; validKey = false ; break ;
                     }
                  }
               }
               else if ( (w6 == wFIVE) || (w6 == wSIX) ) // Ctrl+Fnn || Ctrl+Shift+Fnn
               {
                  // Note: when w6==wSIX, it signifies one of the
                  // Ctrl+Shift+Fnn combinations. These are silently 
                  // converted to Ctrl+Fnn.
                  if ( w3 == wONE )
                  {
                     validKey = true ;    // assume a valid, supported escape sequence

                     switch ( w4 )
                     {
                        case L'5':  wchar = wcsCF05;   break ; // 35h
                        case L'7':  wchar = wcsCF06;   break ; // 37h
                        case L'8':  wchar = wcsCF07;   break ; // 38h
                        case L'9':  wchar = wcsCF08;   break ; // 39h
                        default:    wchar = NULLCHAR ; validKey = false ; break ;
                     }
                  }
                  else  // (w3 == wTWO)
                  {
                     validKey = true ;    // assume a valid, supported escape sequence

                     switch ( w4 )
                     {
                        case L'0':  wchar = wcsCF09;   break ; // 30h
                        case L'1':  wchar = wcsCF10;   break ; // 31h
                        case L'3':  wchar = wcsCF11;   break ; // 33h
                        case L'4':  wchar = wcsCF12;   break ; // 34h
                        default:    wchar = NULLCHAR ; validKey = false ; break ;
                     }
                  }
               }
               else     // unsupported 7-byte sequence
               { wchar = NULLCHAR ; }
            }
            else     // unsupported 7-byte sequence
            { wchar = NULLCHAR ; }
         }  // (7-byte sequences)

         //* If byte 6 was captured *
         else if ( w6 != wchar_t(WEOF) )
         {
            //* If w3 and w5 are separated by a semicolon *
            if ( w4 == wSEMI )
            {
               //* For 6-byte sequences which terminate with a tilde *
               if ( w6 == wTILDE )
               {
                  //* Delete key + Ctrl || Alt || Shift *
                  if ( w3 == wTHREE )
                  {
                     validKey = true ;    // assume a valid, supported escape sequence

                     switch ( w5 )
                     {
                        case L'2':  wchar = wcsSDEL ;    break ;
                        case L'3':  wchar = wcsADEL ;    break ;
                        case L'4':  wchar = wcsASDEL ;   break ;
                        case L'5':  wchar = wcsCDEL ;    break ;
                        case L'6':  wchar = wcsCSDEL ;   break ;
                        case L'8':  wchar = wcsCASDEL ;  break ;
                        default:    wchar = NULLCHAR ; validKey = false ; break ;
                     }
                  }
                  //* [Shift || Ctrl || Alt || Alt+Shift] + PageUp   *
                  //* May not be available (see note above)  *
                  else if ( w3 == wFIVE )
                  {
                     validKey = true ;    // assume a valid, supported escape sequence

                     switch ( w5 )
                     {
                        case L'2':  wchar = wcsSPGUP ;   break ; // not available to terminal
                        case L'3':  wchar = wcsAPGUP ;   break ;
                        case L'4':  wchar = wcsASPGUP ;  break ; // not available to terminal
                        case L'5':  wchar = wcsCPGUP ;   break ; // (terminal: Previous Tab key)
                        case L'6':  wchar = wcsCSPGUP ;  break ;
                        case L'7':  wchar = wcsCAPGUP ;  break ;
                        default:    wchar = NULLCHAR ; validKey = false ; break ;
                     }
                  }
                  //* [Shift || Ctrl || Alt || Alt+Shift] + PageDown *
                  //* May not be available (see note above)  *
                  else if ( w3 == wSIX )
                  {
                     validKey = true ;    // assume a valid, supported escape sequence

                     switch ( w5 )
                     {
                        case L'2':  wchar = wcsSPGDN ;   break ; // not available to terminal
                        case L'3':  wchar = wcsAPGDN ;   break ;
                        case L'4':  wchar = wcsASPGDN ;  break ; // not available to terminal
                        case L'5':  wchar = wcsCPGDN ;   break ; // (terminal: Next Tab key)
                        case L'6':  wchar = wcsCSPGDN ;  break ;
                        case L'7':  wchar = wcsCAPGDN ;  break ;
                        default:    wchar = NULLCHAR ; validKey = false ; break ;
                     }
                  }
                  // Else, unsupported 6-byte sequence ending with tilde
                  else { wchar = NULLCHAR ; }
               }
               else  // (w4==wSEMI && w6!=wTILDE)
               {
                  if ( w3 == wONE )
                  {
                     if ( (w5 == wTHREE) || (w5 == wFOUR) )
                     {
                        // Programmer's Note: For this group, the w6 byte _is not_ 
                        // sensitive to the Shift key; however, the w5 byte is '3' 
                        // for unshifted and '4' for shifted.
                        // We silently convert the shifted versions to unshifted.

                        validKey = true ;    // assume a valid, supported escape sequence

                        switch ( w6 )
                        {
                           case L'A':  wchar = wcsAUP ;     break ;
                           case L'B':  wchar = wcsADOWN ;   break ;
                           case L'C':  wchar = wcsARIGHT ;  break ;
                           case L'D':  wchar = wcsALEFT ;   break ;
                           case L'F':  wchar = wcsAEND ;    break ;
                           case L'H':  wchar = wcsAHOME ;   break ;
                           default:    wchar = NULLCHAR ;   validKey = false ;   break ;
                        }
                     }
                     else if ( w5 == wTWO )
                     {
                        validKey = true ;    // assume a valid, supported escape sequence

                        switch ( w6 )
                        {
                           case L'A':  wchar = wcsSUP ;     break ; // 41h
                           case L'B':  wchar = wcsSDOWN ;   break ; // 42h
                           case L'C':  wchar = wcsSRIGHT ;  break ; // 43h
                           case L'D':  wchar = wcsSLEFT ;   break ; // 44h
                           case L'F':  wchar = wcsSEND ;    break ; // 46h (not seen)
                           case L'H':  wchar = wcsSHOME ;   break ; // 48h (not seen)
                           case L'P':  wchar = wcsSF01;     break ; // 50h
                           case L'Q':  wchar = wcsSF02;     break ; // 51h
                           case L'R':  wchar = wcsSF03;     break ; // 52h
                           case L'S':  wchar = wcsSF04;     break ; // 53h
                        }
                     }
                     else if ( (w5 == wFIVE) || (w5 == wSIX) )
                     {
                        if ( w5 == wFIVE )
                        {
                           validKey = true ; // assume a valid, supported escape sequence
                           switch ( w6 )
                           {
                              case L'A':  wchar = wcsCUP ;     break ;
                              case L'B':  wchar = wcsCDOWN ;   break ;
                              case L'C':  wchar = wcsCRIGHT ;  break ;
                              case L'D':  wchar = wcsCLEFT ;   break ;
                              case L'F':  wchar = wcsCEND ;    break ;
                              case L'H':  wchar = wcsCHOME ;   break ;
                              // (next swich determines validity)
                              default: validKey = false ;      break ;
                           }
                        }

                        if ( ! validKey )
                        {
                           // Note: when w5==wSIX, it signifies one of the
                           // Ctrl+Shift+Fnn combinations. These are silently 
                           // converted to Ctrl+Fnn.
                           validKey = true ; // assume a valid, supported escape sequence
                           switch ( w6 )
                           {
                              case L'P':  wchar = wcsCF01;   break ; // 50h
                              case L'Q':  wchar = wcsCF02;   break ; // 51h
                              case L'R':  wchar = wcsCF03;   break ; // 52h
                              case L'S':  wchar = wcsCF04;   break ; // 53h
                              default:  wchar = NULLCHAR ; validKey = false ; break ;
                           }
                        }
                     }
                     // Else, w5 is not a supported value, so unsupported sequence
                     else
                     { wchar = NULLCHAR ; }
                  }
                  // Else, w3 != wONE, unsupported sequence
                  else
                  { wchar = NULLCHAR ; }
               }
            }  // (w4 == wSEMI_
            //* Else, w4 is not a semicolon, unsupported sequence. *
            else
            { wchar = NULLCHAR ; }
         }  // (6-byte sequences)

         //* If byte 5 was captured, test the sequence *
         //* for:                                      *
         else if ( w5 != wchar_t(WEOF) )
         {
            if ( w5 == wTILDE )
            {
               if ( w3 == wONE )
               {
                  //* Convert special keys to standardized values *
                  validKey = true ;    // assume a valid, supported escape sequence

                  switch ( w4 )
                  {
                     case '5': wchar = wcsF05 ;   break ;
                     case '7': wchar = wcsF06 ;   break ;
                     case '8': wchar = wcsF07 ;   break ;
                     case '9': wchar = wcsF08 ;   break ;
                     // unsupported sequence
                     default:  wchar = NULLCHAR ; validKey = false ; break ;
                  }
               }
               else if ( w3 == wTWO )
               {
                  //* Convert special keys to standardized values *
                  validKey = true ;    // assume a valid, supported escape sequence

                  switch ( w4 )
                  {
                     case '0': wchar = wcsF09 ; break ;
                     case '1': wchar = wcsF10 ; break ;
                     case '3': wchar = wcsF11 ; break ;
                     case '4': wchar = wcsF12 ; break ;
                     // unsupported sequence
                     default:  wchar = NULLCHAR ; validKey = false ; break ;
                  }
               }
               else     // unsupported 5-byte sequence (w3)
               { wchar = NULLCHAR ; }
            }
            else     // unsupported 5-byte sequence (w5!=tilde)
            { wchar = NULLCHAR ; }
         }  // (5-byte sequences)

         //* If 4 bytes were captured, test the sequence *
         //* for: tilde termination or a semicolon that  *
         //* seperates values.                           *
         else if ( w4 != wchar_t(WEOF) )
         {
            //* If Tilde ends the 4-byte sequence *
            if ( w4 == wTILDE )
            {
               //* Convert special keys to standardized values *
               validKey = true ;    // assume a valid, supported escape sequence

               switch ( w3 )
               {
                  case '5': wchar = wcsPGUP ;   break ;
                  case '6': wchar = wcsPGDN ;   break ;
                  case '3': wchar = wcsDELETE ; break ;
                  case '2': wchar = wcsINSERT ; break ;
                  // unsupported sequence ending in tilde
                  default:  wchar = NULLCHAR ; validKey = false ; break ;
               } ;
            }  // tilde-terminated 4-byte sequence
            else     // unsupported 4-byte sequence not terminated by tilde
            { wchar = NULLCHAR ; }
         }  // (4-byte sequences)

         //* If 3 bytes were captured, test the sequence *
         //* for one of the known 3-bytes sequences.     *
         else if ( w3 != wchar_t(WEOF) )
         {
            //* Convert special keys to standardized values *
            validKey = true ;    // assume a valid, supported escape sequence

            switch ( w3 )
            {
               case 'A': wchar = wcsUARROW ; break ;
               case 'B': wchar = wcsDARROW ; break ;
               case 'C': wchar = wcsRARROW ; break ;
               case 'D': wchar = wcsLARROW ; break ;
               case 'H': wchar = wcsHOME ;   break ;
               case 'F': wchar = wcsEND ;    break ;
               case 'Z': wchar = wcsSTAB ;   break ;
               // unsupported 3-byte sequence
               default:  wchar = NULLCHAR ; validKey = false ; break ;
            }
         }  // (3-byte sequences)

         //* If a timeout without capturing a third byte, assume the     *
         //* ALT+[ (Left Square bracket) key combination "\xEsc[" 1B 5B  *
         else if ( w3 == wchar_t(WEOF) )
         { wchar = wcsALT_LS ; validKey = true ; }

      }  // left bracket '['

      //* Alt+Enter or Alt+Ctrl+Enter or Alt+Ctrl+Shift+Enter *
      //* or Alt+Ctrl+j or Alt+Ctrl+Shift+j.These are all the *
      //* same sequence: \x1B 0A                              *
      else if ( capCode == NEWLINE )
      { wchar = wcsAENTER ; validKey = true ; }

      //* If this is one of the two-byte Ctrl+Alt+[a-z] key combinations.  *
      //* Note: Some of these are shadowed by ASCII control codes.         *
      //* Keycodes of this group (except Ctrl+Alt+j, above) are discarded. *
      else if ( (capCode >= 1) && (capCode <= ESCAPE) )
      {
         #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_SOFTKEY != 0
         if ( (ofsdbg.is_open()) )
         {
            gsdbg.compose( "specialkeyDecode  ( %02X,%02X --- CTRL+ALT-key sequence )", 
                           &wchar, &capCode ) ;
         }
         #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_SOFTKEY
         //wchar = L'^' ; validKey = true ;    // echo placeholder to display

         wchar = NULLCHAR ;      // discard the sequence
      }

      //* Two-byte Alt+n key combinations:                              *
      //* This is potentially one of the two-byte ALT key combinations. *
      //* Programmer's Note If capCode==4Fh ('O'), it _could be_ the    *
      //* second byte of a 3-byte sequence for F01-F04.                 *
      else if ( ((capCode >= L'a') && (capCode <= L'z')) ||
                ((capCode >= L'A') && (capCode <= L'Z')) ||
                ((capCode >= L'!') && (capCode <  L'0')) ||
                ((capCode >= L':') && (capCode <= L'@')) ||
                ((capCode >= L'\\') && (capCode < L'`')) ||
                ((capCode >= L'{') && (capCode <  L'~')) ||
                ((capCode >= L'0') && (capCode <= L'9')) )
      {
         //* The value 'O' (4Fh) can either be ALT+Shift+o, or it can be     *
         //* the second byte of the 3-byte sequence for the F01-F04 keycodes.*
         //* If a third byte of the sequence is captured, then it is not     *
         //* 'ALT+Shift+o'.                                                  *
         // Programmer's Note: If a third byte is captured, but is not a valid 
         // function key sequence, the sequence may be corrupted; however, that 
         // is unlikely.
         if ( (capCode == L'O') &&
//              (this->xcPeekNB ( capCode, readREPEAT, NB_DELAY, true ))
              (this->acPeekNB ( capCode, readREPEAT, NB_DELAY, 0x0001 ))
               && (capCode >= L'P' && (capCode <= L'S')) )
         {
            #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_SOFTKEY != 0
            if ( (ofsdbg.is_open()) )
            {
               wchar_t wtmp = L'O' ;
               gsdbg.compose( "specialkeyDecode  ( %02X %02X %02X --- F01-F04 sequence )", 
                              &wchar, &wtmp, &capCode ) ;
               { ofsdbg << gsdbg << endl ; }
            }
            #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_SOFTKEY
            //* If no additional byte captured - pass capCode on to the Alt-key test.*
            switch ( capCode )
            {
               case L'P':  wchar = wcsF01 ;  break ;
               case L'Q':  wchar = wcsF02 ;  break ;
               case L'R':  wchar = wcsF03 ;  break ;
               case L'S':  wchar = wcsF04 ;  break ;
               default:    break ;  // (will not happen)
            }
            validKey = true ;
         }

         //* Not a function key *
         else
         {
            #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_SOFTKEY != 0
            if ( (ofsdbg.is_open()) )
            {
               gsdbg.compose( "specialkeyDecode  ( %02X,%02X --- ALT-key sequence )", 
                              &wchar, &capCode ) ;
               { ofsdbg << gsdbg << endl ; }
            }
            #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_SOFTKEY

            //* Convert lower-case to upper-case *
            if ( (capCode >= L'a') && (capCode <= L'z') )
               capCode -= 0x0020 ;

            //* Convert the two-byte sequence to a single keycode *
            validKey = true ;    // assume a valid, supported escape sequence

            if ( (capCode >= L'A') && (capCode <= L'Z') )
            {
               switch ( capCode )
               {
                  case L'A': wchar = wcsALT_A ; break ;
                  case L'B': wchar = wcsALT_B ; break ;
                  case L'C': wchar = wcsALT_C ; break ;
                  case L'D': wchar = wcsALT_D ; break ;
                  case L'E': wchar = wcsALT_E ; break ;
                  case L'F': wchar = wcsALT_F ; break ;
                  case L'G': wchar = wcsALT_G ; break ;
                  case L'H': wchar = wcsALT_H ; break ;
                  case L'I': wchar = wcsALT_I ; break ;
                  case L'J': wchar = wcsALT_J ; break ;
                  case L'K': wchar = wcsALT_K ; break ;
                  case L'L': wchar = wcsALT_L ; break ;
                  case L'M': wchar = wcsALT_M ; break ;
                  case L'N': wchar = wcsALT_N ; break ;
                  case L'O': wchar = wcsALT_O ; break ;
                  case L'P': wchar = wcsALT_P ; break ;
                  case L'Q': wchar = wcsALT_Q ; break ;
                  case L'R': wchar = wcsALT_R ; break ;
                  case L'S': wchar = wcsALT_S ; break ;
                  case L'T': wchar = wcsALT_T ; break ;
                  case L'U': wchar = wcsALT_U ; break ;
                  case L'V': wchar = wcsALT_V ; break ;
                  case L'W': wchar = wcsALT_W ; break ;
                  case L'X': wchar = wcsALT_X ; break ;
                  case L'Y': wchar = wcsALT_Y ; break ;
                  case L'Z': wchar = wcsALT_Z ; break ;
                  default:                      break ;
               }
            }
            else
            {
               switch ( capCode )
               {
                  case L'0':  case L')':  wchar = wcsALT_0 ;  break ;
                  case L'1':  case L'!':  wchar = wcsALT_1 ;  break ;
                  case L'2':  case L'@':  wchar = wcsALT_2 ;  break ;
                  case L'3':  case L'#':  wchar = wcsALT_3 ;  break ;
                  case L'4':  case L'$':  wchar = wcsALT_4 ;  break ;
                  case L'5':  case L'%':  wchar = wcsALT_5 ;  break ;
                  case L'6':  case L'^':  wchar = wcsALT_6 ;  break ;
                  case L'7':  case L'&':  wchar = wcsALT_7 ;  break ;
                  case L'8':  case L'*':  wchar = wcsALT_8 ;  break ;
                  case L'9':  case L'(':  wchar = wcsALT_9 ;  break ;
                  case L'"':  wchar = wcsALT_DQ ; break ;
                  case L'\'': wchar = wcsALT_SQ ; break ;
                  case L'+':  wchar = wcsALT_PL ; break ;
                  case L',':  wchar = wcsALT_CO ; break ;
                  case L'-':  wchar = wcsALT_MI ; break ;
                  case L'.':  wchar = wcsALT_ST ; break ;
                  case L'/':  wchar = wcsALT_FS ; break ;
                  case L':':  wchar = wcsALT_CN ; break ;
                  case L';':  wchar = wcsALT_SC ; break ;
                  case L'<':  wchar = wcsALT_LT ; break ;
                  case L'=':  wchar = wcsALT_EQ ; break ;
                  case L'>':  wchar = wcsALT_GT ; break ;
                  case L'?':  wchar = wcsALT_QU ; break ;
                  case L'[':  wchar = wcsALT_LS ; break ; // (this is handled above)
                  case L'\\': wchar = wcsALT_BS ; break ;
                  case L']':  wchar = wcsALT_RS ; break ;
                  case L'_':  wchar = wcsALT_US ; break ;
                  case L'{':  wchar = wcsALT_LB ; break ;
                  case L'|':  wchar = wcsALT_VB ; break ;
                  case L'}':  wchar = wcsALT_RB ; break ;
                  default:                        break ;
               }
            }
         }  // Two-byte Alt key combination
      }     // Alt key combination (or F01-F04)

      else                    // unsupported sequence
      {
         #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_SOFTKEY != 0
         if ( (ofsdbg.is_open()) )
         {
            gsdbg.compose( "specialkeyDecode  ( %02X,%02X,--- unsupported sequence )", 
                           &wchar, &capCode ) ;
            { ofsdbg << gsdbg << endl ; }
         }
         #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_SOFTKEY

         wchar = NULLCHAR ;
      }     // unsupported sequence
   }  // ASCII Escape captured

   //* If one of the ASCII control codes, capture it here.       *
   //* Newline, Escape, Tab, Delete, Backspace are handled above.*
   else if ( wchar < wcsSPACE )
   {
      #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_SOFTKEY != 0
      if ( (ofsdbg.is_open()) )
      {
         gsdbg.compose( "specialkeyDecode  ( Ctrl:%04X )", &wchar ) ;
         ofsdbg << gsdbg.ustr() << endl ;
      }
      #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_SOFTKEY

      //* Convert the two-byte sequence to a single keycode *
      validKey = true ;    // assume a valid, supported escape sequence

      switch ( wchar )
      {
         case 0x01:  wchar = wcsCTL_A ;   break ;
         case 0x02:  wchar = wcsCTL_B ;   break ;
         case 0x03:  wchar = wcsCTL_C ;   break ;     // captured SIGINT
         case 0x04:  wchar = wcsCTL_D ;   break ;
         case 0x05:  wchar = wcsCTL_E ;   break ;
         case 0x06:  wchar = wcsCTL_F ;   break ;
         case 0x07:  wchar = wcsCTL_G ;   break ;
         case 0x08:  wchar = wcsCTL_H ;   break ;
         case 0x09:  wchar = wcsCTL_I ;   break ;
         case 0x0A:  wchar = wcsCTL_J ;   break ;
         case 0x0B:  wchar = wcsCTL_K ;   break ;
         case 0x0C:  wchar = wcsCTL_L ;   break ;
         case 0x0D:  wchar = wcsCTL_M ;   break ;
         case 0x0E:  wchar = wcsCTL_N ;   break ;
         case 0x0F:  wchar = wcsCTL_O ;   break ;
         case 0x10:  wchar = wcsCTL_P ;   break ;
         case 0x11:  wchar = wcsCTL_Q ;   break ;     // VSTART disabled
         case 0x12:  wchar = wcsCTL_R ;   break ;
         case 0x13:  wchar = wcsCTL_S ;   break ;     // VSTOP disabled
         case 0x14:  wchar = wcsCTL_T ;   break ;
         case 0x15:  wchar = wcsCTL_U ;   break ;
         case 0x16:  wchar = wcsCTL_V ;   break ;
         case 0x17:  wchar = wcsCTL_W ;   break ;
         case 0x18:  wchar = wcsCTL_X ;   break ;
         case 0x19:  wchar = wcsCTL_Y ;   break ;
         case 0x1A:  wchar = wcsCTL_Z ;   break ;     // captured SIGTSTP
         case 0x1C:  wchar = wcsCTL_4 ;   break ;     // SIGQUIT (with core dump)
         case 0x1D:  wchar = wcsCTL_5 ;   break ;
         case 0x1E:  wchar = wcsCTL_6 ;   break ;
         case 0x1F:  wchar = wcsCTL_7 ;   break ;
         default:    wchar = NULLCHAR ;   validKey = false ;   break ;
         // case 0x1B: Escape is handled above.
      } ;
   }

   //* If not a valid printing keycode, and not *
   //* one of the "special" keys. Discard it    *
   //* and return a null character.             *
   else
   {
      #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_SOFTKEY != 0
      if ( (ofsdbg.is_open()) )
      {
         gsdbg.compose( "specialkeyDecode  ( Unk:%04X )", &wchar ) ;
         ofsdbg << gsdbg.ustr() << endl ;
      }
      #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_SOFTKEY

      wchar = NULLCHAR ;
   }

   return validKey ;

   #undef DEBUG_SOFTKEY
}  //* End specialKeyDecode() *

//*************************
//*       softWrite       *
//*************************
//********************************************************************************
//* Protected Method:                                                            *
//* -----------------                                                            *
//* Write a single character to stdout using the current echo option.            *
//* 1) If 'termEcho', the character has already been written,                    *
//*    so don't write it again.                                                  *
//* 2) If 'noEcho', the character will not be written. (duh!)                    *
//* 3) Otherwise, if the character is a graphing character according to the      *
//*    rules of the current locale, but IS NOT one of the "special" keycodes,    *
//*    the character will be written to the display.                             *
//*                                                                              *
//* Input  : wchar  : character to be written                                    *
//*                                                                              *
//* Returns: 'true' if character written to stdout                               *
//*          'false' if character not written                                    *
//********************************************************************************

bool AnsiCmd::softWrite ( wchar_t wchar ) const
{
   EchoOpt anySpecKey = softEchoA ; // test for ANY "special" key
   bool charOut = false ;           // return value

   if ( (this->tset->inpEch != termEcho) && (this->tset->inpEch != noEcho) &&
        (isprint ( wchar, *this->ioLocale )) &&
        !(this->isSpecialKey ( wchar, false, &anySpecKey )) )
   {
      if ( this->wideStream ) // wide stream input
         wcout << wchar ;
      else                    // narrow stream input
         cout << wchar ;

      charOut = true ;
   }

   return charOut ;

}  //* End softWrite() *

//*************************
//*        nsleep         *
//*************************
//********************************************************************************
//* Sleep (pause) execution for the specified time period.                       *
//* This method encapsulates access to the C-language nanosleep() function.      *
//* It is intended to simplify the the interface and replaces direct calls to    *
//* the obsolete C-language sleep() and usleep().                                *
//*                                                                              *
//* The primary delay value is specified in tenths of a second (0.10sec), with   *
//* a secondary delay specification in milliseconds (0.001sec).                  *
//* The sum of the two values specifies the duration of the delay.               *
//* Thus the effective minimum delay is 1 millisecond, while the theoretical     *
//* maximum delay is: 65535/10 or 6553 minutes + 65535 milliseconds or           *
//* approximately 6618 minutes.This is an impractically large number, so we      *
//* arbitrarily limit the maximum delay to 61 minutes, which is approximately    *
//* 60 minutes more than anyone will ever need.                                  *
//*               1 millisecond <= delay <= 61.0 minutes                         *
//*                                                                              *
//* The philosophy here is that delays of less than one tenth of a second are    *
//* not very useful for a human interface, while smaller delays are oriented     *
//* toward computer-oriented tasks and which are limited only by the             *
//* resolution of the clock referenced by the called function. (see below)       *
//*                                                                              *
//*                                                                              *
//* Input  : tenths  : specifies the sleep interval in tenths of a second        *
//*                    (1/10 sec == 0.10 sec)                                    *
//*          millisec: (optional, zero by default) for finer control over the    *
//*                    length of the delay, specify a delay in milliseconds.     *
//*          nanosec : (optional, zero by default) for even finer control over   *
//*                    the length of the delay, specify a delay in nanoseconds.  *
//*                    [CURRENTLY IGNORED]                                       *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Notes:                                                                       *
//* ------                                                                       *
//* -- Althouth nanosleep() seems to properly handle a delay of zero i.e.        *
//*   (tenths==0 && msec==0), we do not make the call if the total delay==zero.  *
//*                                                                              *
//* -- The nanosecond value is limited to 9,000,000,000. A higher value in       *
//*    the 'tv_nsec' field will generate an error.                               *
//*                                                                              *
//* -- In C++, the cosmopolitan way to delay the action is to use the 'chrono'   *
//*    class to define the delay, and the 'sleep_for' method to execute it.      *
//*    Example:  chrono::duration<short>aWhile( 10 ) ;                           *
//*              this_thread::sleep_for( aWhile ) ;                              *
//*    Example:  chrono::duration<short, std::milli>aMoment( 250 ) ;             *
//*              this_thread::sleep_for( aMoment ) ;                             *
//* -- The chrono/sleep_for() C++ implementation is much cleaner and more        *
//*    intuitive than nanosleep(); however, ANSI escape sequences are by         *
//*    definition incompatible with multi-threaded environments, so we do        *
//*    not introduce multi-threading in the AnsiCmd class.                       *
//*    Instead, we use the nanosleep() function which is technically             *
//*    sophisticated, but clunky in implementation. That is why we isolate       *
//*    it here to avoid cluttering up our lovely code.                           *
//*                                                                              *
//*    #include <time.h>                                                         *
//*    struct timespec                                                           *
//*    {                                                                         *
//*       time_t tv_sec ;  /* seconds */                                         *
//*       long   tv_nsec ; /* nanoseconds */                                     *
//*    };                                                                        *
//*    nanosleep ( const struct timespec *req, struct timespec *rem ) ;          *
//*                                                                              *
//* -- nanosleep() rounds up the specified 'tv_nsec' value to the resolution     *
//*    of the clock.                                                             *
//*                                                                              *
//* -- It is important to keep in mind that although nanosleep() claims to       *
//*    be accurate in the nanosecond range, a number of factors may affect       *
//*    the results. For instance, the default Linux scheduler is based on        *
//*    ten-millisecond intervals, so sequential calls may be limited to          *
//*    this resolution. Some newer systems with newer kernel support may         *
//*    be able to provide a resolution of one(1) millisecond.                    *
//*    See the clock_getres() function to get more information on the            *
//*    clocks available on the host system.                                      *
//*                                                                              *
//* -- See man 7 time, which indicates that the _effective_ resolution is        *
//*    between 1 and 10 milliseconds.                                            *
//*    Also see the resolutions specified in /proc/timer_list. Most of those     *
//*    specify a resolution of 1 nanosecond, but we see this as more             *
//*    aspirational, than mathematical.                                          *
//*                                                                              *
//********************************************************************************

void AnsiCmd::nsleep ( uint16_t tenths, uint16_t millisec, time_t nanosec ) const
{
   const time_t NS_SEC  = 1000000000 ;    // nanoseconds per second (1 billion ns)
   const time_t NS_10TH = NS_SEC / 10 ;   // nanoseconds per 1/10 second (100 million ns)
   const time_t NS_MSEC = NS_SEC / 1000 ; // nanoseconds per millisecond (1 million ns)
   const uint16_t MAX_10THS = 3600 ;      // arbitrary upper limit==60 minutes
   const uint16_t MAX_MSEC  = 60000 ;     // arbitrary upper limit==60 seconds

   if ( tenths > MAX_10THS )  tenths   = MAX_10THS ; // range checking
   if ( millisec > MAX_MSEC ) millisec = MAX_MSEC ;

   time_t   fullSec  = tenths / 10,          // full seconds
            tenthSec = tenths % 10,          // tenths of a second
            nsec     = (NS_10TH * tenthSec) ;// nanoseconds
            nsec += (millisec * NS_MSEC) ;
            if ( nsec >= NS_SEC )             // test for overflow
            { ++fullSec ; nsec -= NS_SEC ; }

   TimeSpec ts,                              // requested sleep interval
            //* If interval is interrupted, this receives remaining time.*
            rem = { ZERO, ZERO } ;
   int      status ;                         // return value from nanosleep()
   short    max_loop = 10 ;                  // limit loop iterations


   ts.tv_sec  = fullSec ;
   ts.tv_nsec = nsec ;

   #if 0    // FOR DEBUGGING ONLY
   gString gsfmt( ts.tv_nsec, FI_MAX_FIELDWIDTH, true ) ;
   gString gstmp( "tv_sec:%lu tv_nsec:%S\n", &ts.tv_sec, gsfmt.gstr() ) ;
   this->ttyWrite ( gstmp ) ;
   #endif   // FOR DEBUGGING ONLY

   if ( (ts.tv_nsec > ZERO) || (ts.tv_sec > ZERO) )
   {
      do
      {
         //* Execute the specified delay.*
         status = nanosleep ( &ts, &rem ) ;

         //* If the sleep was interrupted, call *
         //* again with the remaining time.     *
         if ( (status != ZERO) && (errno == EINTR) )
            ts = rem ;
         else           // success (or non-interrupt error)
            break ;
      }
      while ( (status != ZERO) && (--max_loop > ZERO) ) ;
   }

}  //* End nsleep() *

//*************************
//*        readSeq        *
//*************************
//********************************************************************************
//* PRIVATE METHOD:                                                              *
//* Capture the response to one of the ANSI information-request commands:        *
//*                                                                              *
//* Currently-supported sequences:                                               *
//* ------------------------------                                               *
//* -- aesCUR_REPORT : Report the current cursor position.                       *
//*                    (The report terminates with an ASCII 'R'.)                *
//*                                                                              *
//* Important Note: Unbuffered input should be enabled before calling this       *
//*                 method. Also, echo of input to the screen should be          *
//*                 disabled (or one of the soft-echo options) to avoid          *
//*                 artifacts being displayed during the capture of the system   *
//*                 response.                                                    *
//*                                                                              *
//* Input  : gsIn     : (by reference) receives captured data                    *
//*          termChar : last character in the sequence                           *
//*                     (returns when this character is received from stdin)     *
//*                                                                              *
//* Returns: number of characters captured (NOT the number of bytes)             *
//********************************************************************************
//* Programmer's Note: Consider REQUIRING non-blocking read for this method,     *
//* so we can reliably flush the input buffer after the sequence is captured.    *
//* The only method that currently uses this method is acGetCursor() which       *
//* which explicitly sets non-blocking read before the call.                     *
//********************************************************************************

short AnsiCmd::readSeq ( gString& gsIn, wchar_t termChar ) const
{
   gsIn.clear() ;                // clear caller's buffer

   //* Unbuffered input with local echo (or no echo).*
   if ( ! this->tset->inpBuf && (this->tset->inpEch != termEcho) )
   {
      wchar_t wbuff[gsALLOCMIN] ;// raw input buffer
      wchar_t winchar ;          // raw input character
      short   indx = ZERO ;      // index into wbuff[]

      do
      {
         winchar = this->silentRead () ; // read a character from stdin

         //* An escape sequence is the Escape byte *
         //* followed by a series of ASCII bytes.  *
         //* (ignore newline characters)           *
         if ( winchar != NEWLINE )
            wbuff[indx++] = winchar ;
      }
      while ( (winchar != termChar) && (indx < MAX_SEQ_BYTES) ) ;
      wbuff[indx] = NULLCHAR ;   // terminate the string
      gsIn = wbuff ;             // copy data to caller's buffer
   }

   //* Return the number of characters captured (not incl. null terminator) *
   return ( (gsIn.gschars()) - 1 ) ;

}  //* End readSeq() *

//*************************
//*        acWrite        *
//*************************
//********************************************************************************
//* Write formatted (paragraph-oriented) text data to stdout beginning at the    *
//* specified cursor position. Final cursor position is returned.                *
//*                                                                              *
//* Note: This method truncates output lines automatically at the right edge     *
//*       of the terminal window. Line wrap does not occur.                      *
//*       If the output is INTENDED to wrap at the right edge of the terminal    *
//*       window, then use the ttyWrite() method instead.                        *
//* Note: If the bottom edge of the terminal window is reached, any additional   *
//*       lines of text will be ignored. Scrolling does not occur.               *
//*                                                                              *
//* Input  : starting cursor position in one of the following formats:           *
//*             wp    : a WinPos object (by reference)                           *
//*                   OR                                                         *
//*             row   : target row                                               *
//*             col   : target column                                            *
//*                                                                              *
//*          text data to be written in one of the following formats:            *
//*             gsTxt : (by reference) a gString object                          *
//*                   OR                                                         *
//*             wTxt  : a wchar_t string (32-bit characters)                     *
//*                   OR                                                         *
//*             cTxt  : a char (byte) string                                     *
//*                   OR                                                         *
//*             wchar : a single wchar_t (wide) character                        *
//*                   OR                                                         *
//*             cbyte : a single byte (generally: an ASCII control code)         *
//*                                                                              *
//*          flush : (optional, 'true' by default)                               *
//*                  if 'true,   flush the output buffer before return           *
//*                  if 'false', do not flush the output buffer                  *
//*                              (This is faster if multiple sequential    )     *
//*                              (writes planned. In that case, flush only )     *
//*                              (on last write of the sequence.           )     *
//*                                                                              *
//* Returns: current cursor position (WinPos object)                             *
//********************************************************************************
//* Programmer's Note: This method tracks the cursor position by counting the    *
//* data-columns written to the terminal window. While this may seem slow, it    *
//* is at least as fast as waiting for the system to report the cursor position, *
//* AND it it much less likely to trash the display than trying to capture the   *
//* response from the ANSI escape sequence several times in succession.          *
//*             (See acGetCursor() for additional information.)                  *
//*                                                                              *
//********************************************************************************

WinPos AnsiCmd::acWrite ( WinPos wp, const gString& gsTxt, bool flush ) const
{  // This is the primary acWrite() method, and the only one that calls writeNL().

   //* Range check the cursor position *
   if ( wp.row <= ZERO )
      wp.row = 1 ;
   if ( wp.row > this->termRows )
      wp.row = this->termRows ;
   if ( wp.col <= ZERO )
      wp.col = 1 ;
   if ( wp.col > this->termCols )
      wp.col = this->termCols ;

   wp = this->writeNL ( wp, gsTxt ) ;     // write the formatted data

   if ( flush )                           // if flush stream specified
   {
      if ( this->wideStream )             // wide output stream
         wcout.flush() ;
      else                                // narrow output stream
         cout.flush() ;
   }

   return ( wp ) ;

}  //* End acWrite() *

//********************************************************************************
//* Overload of acWrite() - gString object, seperate row/col                     *
//********************************************************************************
WinPos AnsiCmd::acWrite ( short row, short col, const gString& gsTxt, bool flush ) const
{
   WinPos wp( row, col ) ;
   return ( this->acWrite ( wp, gsTxt, flush ) ) ;
}  //* End acWrite() *

//********************************************************************************
//* Overload of acWrite() - pointer to wchar_t string                            *
//********************************************************************************
WinPos AnsiCmd::acWrite ( WinPos wp, const wchar_t* wTxt, bool flush ) const
{
   gString gs( wTxt ) ;
   return ( (this->acWrite ( wp, gs, flush )) ) ;
}  //* End acWrite() *
WinPos AnsiCmd::acWrite ( short row, short col, const wchar_t* wTxt, bool flush ) const
{
   WinPos wp( row, col ) ;
   gString gs( wTxt ) ;
   return ( (this->acWrite ( wp, gs, flush )) ) ;
}  //* End acWrite() *

//********************************************************************************
//* Overload of acWrite() for single wchar_t (uint32_t) value.                   *
//********************************************************************************
WinPos AnsiCmd::acWrite ( WinPos wp, wchar_t wchar, bool flush ) const
{
   wchar_t wstr[2] = { wchar, L'\0' } ;
   return ( this->acWrite ( wp, wstr, flush ) ) ;
}  //* End acWrite() *
WinPos AnsiCmd::acWrite ( short row, short col, wchar_t wchar, bool flush ) const
{
   WinPos wp( row, col ) ;
   wchar_t wstr[2] = { wchar, L'\0' } ;
   return ( this->acWrite ( wp, wstr, flush ) ) ;
}  //* End acWrite() *

//********************************************************************************
//* Overload of acWrite() - pointer to char string                               *
//********************************************************************************
WinPos AnsiCmd::acWrite ( WinPos wp, const char* cTxt, bool flush ) const
{
   gString gs( cTxt ) ;
   return ( this->acWrite ( wp, gs, flush ) ) ;
}  //* End acWrite() *
WinPos AnsiCmd::acWrite ( short row, short col, const char* cTxt, bool flush ) const
{
   WinPos wp( row, col ) ;
   return ( (this->acWrite ( wp, cTxt, flush )) ) ;
}  //* End acWrite() *

//********************************************************************************
//* Overload of acWrite() for single char (byte) value.                          *
//********************************************************************************
WinPos AnsiCmd::acWrite ( WinPos wp, char cbyte, bool flush ) const
{
   char cstr[2] = { cbyte, '\0' } ;
   return ( (this->acWrite ( wp, cstr, flush )) ) ;
}  //* End acWrite() *
WinPos AnsiCmd::acWrite ( short row, short col, char cbyte, bool flush ) const
{
   WinPos wp( row, col ) ;
   char cstr[2] = { cbyte, '\0' } ;
   return ( this->acWrite ( wp, cstr, flush ) ) ;
}  //* End acWrite() *

//*************************
//*       ttyWrite        *
//*************************
//********************************************************************************
//* Write text data (or ANSI sequence) to stdout beginning at the current        *
//* cursor position. This emulates the action of a teletype machine (TTY).       *
//*                                                                              *
//* (If you don't know what a teletype machine is, your grandparents will        *
//*  explain it to you.)                                                         *
//*                                                                              *
//* -- Any newline characters encountered will cause the cursor to be            *
//*    positioned on the first column of the line below, and if on the last      *
//*    line in the window, all data will be scrolled upward.                     *
//* -- The system will automatically wrap text at the right edge of the window.  *
//*                                                                              *
//* Input  : text data to be written in one of the following formats:            *
//*             gsTxt : (by reference) a gString object                          *
//*                   OR                                                         *
//*             wTxt  : a wchar_t string (32-bit characters)                     *
//*                   OR                                                         *
//*             cTxt  : a char (byte) string                                     *
//*                   OR                                                         *
//*             uTxt  : a UTF-8 (byte) string                                    *
//*                   OR                                                         *
//*             wchar : a single UTF-32 character                                *
//*                   OR                                                         *
//*             cbyte : a single byte (generally: an ASCII control code)         *
//*          flush : (optional, default: 'true')                                 *
//*                  if 'true,   flush the output buffer before return           *
//*                  if 'false', do not flush the output buffer                  *
//*                              (This is faster if multiple sequential    )     *
//*                              (writes planned. In that case, flush only )     *
//*                              (on last write of the sequence.           )     *
//*          nl    : (optional, default: 'false')                                *
//*                  append a newline character to the output                    *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Technical Note:                                                              *
//* ---------------                                                              *
//* If multi-byte UTF-8 characters are written directly to the wide stream,      *
//* they will not be rendered correctly (if at all). For this reason, for        *
//* wide-stream output, char data are converted before sending it to stdout.     *
//********************************************************************************

void AnsiCmd::ttyWrite ( const gString& gsTxt, bool flush, bool nl ) const
{  // This is the primary ttyWrite() method.

   //* Write the text *
   if ( this->wideStream )    // wide output stream
   {
      wcout << gsTxt.gstr() ;
      if ( nl )
         wcout << L'\n' ;
      if ( flush != false )
         wcout.flush() ;
   }
   else                       // narrow output stream
   {
      cout << gsTxt.ustr() ;
      if ( nl )
         cout << '\n' ;
      if ( flush != false )
         cout.flush() ;
   }
} //* End ttyWrite() *

//********************************************************************************
//* Overload of ttyWrite() - pointetr to a wchar_t string                        *
//********************************************************************************
void AnsiCmd::ttyWrite ( const wchar_t* wTxt, bool flush, bool nl ) const
{
   gString bs( wTxt ) ;
   this->ttyWrite ( bs, flush, nl ) ;
}  //* End ttyWrite() *

//********************************************************************************
//* Overload of ttyWrite() - single UTF-32 character                             *
//********************************************************************************
void AnsiCmd::ttyWrite ( wchar_t wchar, bool flush, bool nl ) const
{

   wchar_t wtxt[2] = { wchar, L'\0' } ;
   this->ttyWrite ( wtxt, flush, nl ) ;

}  //* End ttyWrite() *

//********************************************************************************
//* Overload of ttyWrite() - pointer to a char string                            *
//********************************************************************************
void AnsiCmd::ttyWrite ( const char* cTxt, bool flush, bool nl ) const
{
      gString bs( cTxt ) ;                      // text re-encoding
      this->ttyWrite ( bs, flush, nl ) ;
}  //* End ttyWrite() *

//********************************************************************************
//* Overload of ttyWrite() - pointer to a uint8_t string                         *
//********************************************************************************
void AnsiCmd::ttyWrite ( const uint8_t* uTxt, bool flush, bool nl ) const
{
   this->ttyWrite ( (const char*)(uTxt), flush, nl ) ;
}  //* End ttyWrite() *

//********************************************************************************
//* Overload of ttyWrite() - single UTF-8 character                              *
//********************************************************************************
void AnsiCmd::ttyWrite ( char cbyte, bool flush, bool nl ) const
{

   char txt[2] = { cbyte, '\0' } ;
   this->ttyWrite ( txt, flush, nl ) ;

}  //* End ttyWrite() *

//*************************
//*        writeNL        *
//*************************
//********************************************************************************
//* Private Method: Called only by acWrite().                                    *
//* ---------------                                                              *
//* Write the specified data to stdout. The primary purpose of this method is    *
//* to support multi-line output i.e. text which includes newline characters,    *
//* BUT the base horizontal offset is not the left edge of the terminal window.  *
//*                                                                              *
//* This is the same as the public ttyWrite() methods EXCEPT that newline        *
//* characters ('\n') are handled as a special case.                             *
//* When a newline character is encountered,the following row of text begins     *
//* on the same column as the previous row instead of returning to the left      *
//* edge of the terminal window.                                                 *
//*                                                                              *
//* IMPORTANT NOTE: This method does not flush the output stream. It is the      *
//*                 caller's responsibility to flush the stream if required.     *
//*                                                                              *
//* Note:                                                                        *
//*  a) If a row of text would extend to or beyond the right edge of the terminal*
//*     window, that row will be truncated at _one less_ than the width of the   *
//*     terminal, and if a newline was removed by the truncation, it will be     *
//*     added back at the end of the truncated row. This prevents the terminal   *
//*     from automagically wrapping the text. Note that we _could_ omit the      *
//*     newline and allow the terminal to perform the wrap; however, that would  *
//*     mean loss of direct control over the data.                               *
//*  b) Output is truncated an the bottom edge of the terminal window.           *
//*     Scrolling will not occur.                                                *
//*                                                                              *
//* Input  : wp    : (by reference) initial cursor position for output           *
//**                 (caller performs range check on position)                   *
//*          gsTxt : text data to be written                                     *
//*                                                                              *
//* Returns: current cursor position (WinPos object)                             *
//********************************************************************************

WinPos AnsiCmd::writeNL ( WinPos& wp, const gString& gsTxt ) const
{
   gString gsOut ;                        // text formatting
   int  windx = ZERO,                     // index into wbuff[]
        wbase  = ZERO,                    // index first character of current line
        wcnt = gsTxt.gschars() - 1,       // number of characters in wide-character array (not incl. null char)
        buffsize = wcnt + 64,             // size of wbuff[]
        wrapCols = this->termCols - wp.col + 1 ; // available columns on row
   wchar_t* wbuff = new wchar_t[buffsize] ;
   bool nextRow ;                         // signal whether to advance to next row

   gsTxt.copy( wbuff, buffsize ) ;        // copy data to work buffer
   this->acSetCursor ( wp ) ;             // set cursor to initial position

   while ( windx < wcnt )                 // while null terminator not reached
   {
      //* Find the end of the current line *
      while ( (wbuff[windx] != NEWLINE) && (wbuff[windx] != NULLCHAR) )
         ++windx ;

      if ( wbuff[windx] == NEWLINE )      // end-of-line
      {
         if ( windx > wbase )             // if at least one non-newline character
         {
            wbuff[windx++] = NULLCHAR ;   // terminate the current line
            gsOut.compose( L"%S\n", &wbuff[wbase] ) ; // format for output
            wbase = windx ;               // index first character of next line
         }
         //* Else, newline is first (i.e. only) character on line.*
         else
         {
            gsOut = L"\n" ;               // only newline written on current line
            wbase = ++windx ;             // index first character of next line
         }
         nextRow = true ;                 // output continues on next row
      }
      else                                // end-of-data (wbuff[windx]==NULLCHAR)
      {
         gsOut = &wbuff[wbase] ;          // copy final line to output buffer
         nextRow = false ;                // output ends on current row
      }

      //* Output the current line.*
      if ( gsOut.gschars() > 1 )          // if not an empty string
      {
         //* If output would extend beyond the right *
         //* edge of terminal window, truncate line. *
         if ( gsOut.gscols() >= wrapCols )
         {
            gsOut.limitCols( wrapCols - 1 ) ;
            if ( nextRow )
               gsOut.append( L'\n' ) ;
         }
         //* If data will be written to bottom row of terminal *
         //* window, strip trailing newline (if any), and      *
         //* adjust 'windx' to terminate the loop.             *
         if ( wp.row == this->termRows )
         {
            if ( (gsOut.gstr()[gsOut.gschars() - 2]) == NEWLINE )
            {
               gsOut.limitChars( gsOut.gschars() - 2 ) ;
               nextRow = false ;
            }
            windx = wcnt ;                // signal loop termination
         }

         // NOTE: There is still a timing issue here, probably related to 
         //       setting the cursor position. Think about this.
         this->ttyWrite ( gsOut, false ) ;   // write the text

         //* Advance the insertion point to the next row.                    *
         //* If at bottom of window, the newline has scrolled data upward.   *
         //* Note: ANSI cursor-positioning sequences DO NOT induce scrolling.*
         if ( nextRow )                   // if additional data for next row
         {
            if ( ++wp.row > this->termRows )
               --wp.row ;                 // reference last terminal row
            this->acSetCursor ( aesCUR_ABSCOL, wp.col ) ;
         }
         else                             // else end-of-data
            wp.col += gsOut.gscols() ;    // add columns in last line to base column
         // Programmer's Note: If the text wraps at the edge of the window,
         // the column of the returned value will be incorrect.
         // acGetCursor() would be more reliable, but it's too slow for the flow.
      }
   }

   delete [] wbuff ;                   // release the dynamic allocation
   return ( wp ) ;                     // return the current cursor position

}  //* End writeNL() *

//*************************
//*       acVersion       *
//*************************
//********************************************************************************
//* Return the AnsiCmd class version.                                            *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: pointer to version string                                           *
//********************************************************************************

const char * AnsiCmd::acVersion ( void ) const
{

   return AnsiCmdVersion ;

}  //* End acVersion() *

//*************************
//*      acSetLocale      *
//*************************
//********************************************************************************
//* Set the "locale" for the application.                                        *
//* By default, the application locale is taken from terminal environment.       *
//* (Type: echo $LANG   at the command line to get the terminal's locale.        *
//* An alternate locale may be specified.                                        *
//*                                                                              *
//* Input  : (optional, null pointer by default)                                 *
//*          If specified, pointer to name of UTF-8 compliant locale filename.   *
//*          If null pointer (or empty string: "\0"), set locale from terminal   *
//*          environment.                                                        *
//*                                                                              *
//* Returns: 'true'                                                              *
//*          Currently, we do not verify that 'localeName' is a valid UTF-8      *
//*          locale filename.                                                    *
//********************************************************************************

bool AnsiCmd::acSetLocale ( const char * localeName )
{
   //* Create a locale object if it does not exist *
   if ( this->ioLocale == NULL )
      this->ioLocale = new locale("") ;// get default locale from environment

   //* If a locale name (filename) is specified, overwrite *
   //* the default locale with the specified locale.       *
   if ( (localeName != NULL) && (localeName[0] != NULLCHAR) )
   {
      locale tmp(localeName) ;   // create a locale object using specified file
      *this->ioLocale = tmp ;    // copy it to our data member
   }

   //* Give the locale "global" (application/process) scope *
   this->ioLocale->global( *this->ioLocale ) ;

   return true ;

}  //* End acSetLocale() *

//*************************
//*      acGetLocale      *
//*************************
//********************************************************************************
//* Returns a pointer to the name of the current locale.                         *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: pointer to locale name                                              *
//********************************************************************************
//* Programmer's Note: We use a static buffer to capture the string whose        *
//* pointer is returned by the called library function. This is because the      *
//* pointer may be deleted before the calling method can copy the string.        *
//* We sincerely dislike std::string.                                            *
//*                                                                              *
//* Note also that depending on system configuration, multiple locales may be    *
//* reported, separated by semicolons (';'). Only the first reported locale is   *
//* returned to caller.                                                          *
//********************************************************************************

const char* AnsiCmd::acGetLocale ( void )
{
   static char locName[64] ;     // query the system for current locale setting

   gString gs( this->ioLocale->name().c_str() ) ;
   int i;
   if ( (i = gs.after( L'=' )) > ZERO )
      gs.shiftChars( -(i) ) ;
   if ( (i = gs.find( L';' )) > ZERO )
      gs.limitChars( i ) ;
   gs.copy( locName, 64) ;

   return locName ;

}  //* End acGetLocale() *

//*************************
//*   acSetStreamWidth    *
//*************************
//********************************************************************************
//* Set input/output streams' character width.                                   *
//* This determines which input/output streams will be used:                     *
//*    wide stream: wcin/wcout                                                   *
//*  narrow stream: cin/cout                                                     *
//*                                                                              *
//* IMPORTANT NOTE: This method will have no effect if called after the          *
//*                 application has performed I/O.                               *
//*                                                                              *
//* Input  : widechar : select 'true' (wide stream) or                           *
//*                            'false' (narrow stream)                           *
//*                                                                              *
//* Returns: current state of character-width flag                               *
//********************************************************************************

bool AnsiCmd::acSetStreamWidth ( bool widechar )
{

   this->wideStream = widechar ;
   return this->wideStream ;

}  //* End acSetStreamWidth() *

//*************************
//*    acBufferedInput    *
//*************************
//********************************************************************************
//* Disable buffering so each keystroke can be received as soon as the key is    *
//* pressed, or restore terminal's control over input buffering.                 *
//*                                                                              *
//* Normally, user input to the terminal (stdin) is buffered. That is, typed     *
//* characters are stored in the terminal's private input buffer and are         *
//* returned to the application only when the ENTER key is pressed.              *
//*                                                                              *
//* In some circumstances, however, when a "Press any key to continue..."        *
//* functionality is desired. Local control of the input buffer is also          *
//* necessary when capturing ANSI sequences transmitted from the terminal in     *
//* order to prevent cluttering the window with data which is meaningless to     *
//* the user.                                                                    *
//* See acRead(gString& gsIn) for additional info.                               *
//*                                                                              *
//* IMPORTANT NOTE: When buffering is enabled (buffered!=false), then the        *
//* 'echoType' and 'block' parameters are ignored.                               *
//*   -- When buffering is enabled, termEcho is always enabled.                  *
//*   -- When buffering is enabled, blocking read of input is always enabled.    *
//* Consequently, if modifying the echo option and/or the input-blocking         *
//* option, then input buffering must be disabled.                               *
//*                                                                              *
//*                                                                              *
//* Input  : buffered :                                                          *
//*            if 'true',  enable input buffering by the terminal                *
//*            if 'false', disable buffering and allow local control over the    *
//*                        input stream                                          *
//*          echoType : member of enum EchoOpt:                                  *
//*                       referenced ONLY when 'buffered' == false               *
//*                   noEcho  : data from the stdin stream will not be echoed    *
//*                             to the terminal window                           *
//*                   termEcho: terminal echoes all stdin data to the window     *
//*                   softEchoA, softEchoC, softEchoD, softEchoE:                *
//*                             terminal echo is disabled, but the acRead()      *
//*                             methods will echo PRINTING characters to the     *
//*                             terminal window and handle "special" keys        *
//*                             according to the specific soft-echo sub-option.  *
//*          block    : (optional, 'true' by default)                            *
//*                     enable or disable blocking-read of input stream          *
//*                     referenced ONLY when 'buffered' == false                 *
//*                     if 'true',  enable blocking read of stdin                *
//*                     if 'false', disable blocking read of stdin               *
//*                                                                              *
//* Returns: current state of the 'buffered-input' flag (inpBuf).                *
//********************************************************************************
//* Programmer's Notes:                                                          *
//* 1) The 'ICANON' flag actually controls whether the terminal references       *
//*    'VTIME' and 'VMIN' and how those values are interpreted.                  *
//* 2) Buffering has no direct dependence on whether or not the characters       *
//*    are echoed when they arrive, and again, the 'ICANON' flag determines      *
//*    whether the terminal references the 'ECHO' flag'.                         *
//* 3) Traditionally (and logically) echo is disabled when buffering is          *
//*    disabled. However, we provide an option to keep echo active while         *
//*    buffering is disabled.                                                    *
//* 4) Echo should be disabled if the system is queried for a response in the    *
//*    form of an escape sequence (for instance, asking for the current cursor   *
//*    position). If echo is active, then it is likely that the printing         *
//*    characters in the the escape sequence will inappropriately be echoed      *
//*    to the screen.                                                            *
//*    To bake a cake and eat it too, see the soft-echo option.                  *
//*                                                                              *
//*                                                                              *
//********************************************************************************

bool AnsiCmd::acBufferedInput ( bool buffered, EchoOpt echoType, bool block )
{
   //**********************************************
   //* Validate caller's logic (see notes above). *
   //**********************************************
   if ( buffered )
   { echoType = termEcho ; block = true ; }


   #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0
   if ( (ofsdbg.is_open()) )
   {
      gsdbg.compose( "acBufferedInput( %hhd %hd %hhd <- %hhd %hd %hhd )", 
                     &buffered, &echoType, &block,
                     &this->tset->inpBuf, &this->tset->inpEch, &this->tset->inpBlk ) ;
      ofsdbg << gsdbg.ustr() << endl ;
   }
   #endif   // DEBUG_ANSICMD && DEBUG_LOG

   //* If not already set to specified configuration, set new configuration.*
   if ( (buffered != this->tset->inpBuf) ||
        (echoType != this->tset->inpEch) ||
        (block    != this->tset->inpBlk) )
   {
      //* If input buffering is to be updated, then update *
      //* buffering AND echo options simultaneously.       *
      if ( (buffered != this->tset->inpBuf) )
      {
         #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0
         if ( (ofsdbg.is_open()) )
         { gsdbg.compose( "  ->setTermInfo( %hhd %hd %hhd ", &buffered, &echoType, &block ) ; }
         #endif   // DEBUG_ANSICMD && DEBUG_LOG

         //* Note: If this call enables buffering, then blocking *
         //* read will be enabled automagically by this call.    *
         this->tset->setTermInfo ( stdIn, buffered, echoType ) ;

         //* If buffering has been disabled, AND if   *
         //* non-blocking read specified, update now. *
         if ( ! this->tset->inpBuf && ! block )
            this->tset->blockingInput ( block ) ;

         #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0
         if ( (ofsdbg.is_open()) )
         { gsdbg.append( "-> %hhd %hd %hhd )", &this->tset->inpBuf, &this->tset->inpEch, &this->tset->inpBlk ) ;
           ofsdbg << gsdbg.ustr() << endl ; }
         #endif   // DEBUG_ANSICMD && DEBUG_LOG
      }

      //* Else, if buffering was previously disabled, AND if echo *
      //* or blocking option change specified, update them now.   *
      else if ( ! this->tset->inpBuf && 
                ((echoType != this->tset->inpEch) || (block != this->tset->inpBlk)) )
      {
         if ( (echoType != this->tset->inpEch) )
         {
            #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0
            if ( (ofsdbg.is_open()) )
            { gsdbg.compose( "  ->echoOption ( %hhd %hd %hhd ", &buffered, &echoType, &block ) ; }
            #endif   // DEBUG_ANSICMD && DEBUG_LOG

            this->tset->echoOption ( echoType ) ;

            #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0
            if ( (ofsdbg.is_open()) )
            { gsdbg.append( "-> %hhd %hd %hhd )", &this->tset->inpBuf, &this->tset->inpEch, &this->tset->inpBlk ) ;
              ofsdbg << gsdbg.ustr() << endl ; }
            #endif   // DEBUG_ANSICMD && DEBUG_LOG
         }
         if ( (block != this->tset->inpBlk) )
         {
            #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0
            if ( (ofsdbg.is_open()) )
            { gsdbg.compose( "  ->blockingIn ( %hhd %hd %hhd ", &buffered, &echoType, &block ) ; }
            #endif   // DEBUG_ANSICMD && DEBUG_LOG
   
            this->tset->blockingInput ( block ) ;
   
            #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0
            if ( (ofsdbg.is_open()) )
            { gsdbg.append( "-> %hhd %hd %hhd )", &this->tset->inpBuf, &this->tset->inpEch, &this->tset->inpBlk ) ;
              ofsdbg << gsdbg.ustr() << endl ; }
            #endif   // DEBUG_ANSICMD && DEBUG_LOG
         }
      }
   }

   return this->tset->inpBuf ;

}  //* End acBufferedInput() *

//*************************
//*    acSetEchoOption    *
//*************************
//********************************************************************************
//* Select the input character echo type. (enum EchoOpt).                        *
//* This option is available ONLY when input buffering has been disabled.        *
//* See acBufferedInput() method for additional information.                     *
//*                                                                              *
//* This is a pass-through to the TermSet method.                                *
//*                                                                              *
//* Input  : echoType: member of enum EchoOpt:                                   *
//*                    noEcho   : data from the stdin stream will not be         *
//*                               echoed to the terminal window                  *
//*                    termEcho : terminal echoes all stdin data to the window   *
//*                       For the soft-echo options, terminal echo is disabled,  *
//*                       and echo of input characters to the display is         *
//*                       handled locally by the acRead() methods which will     *
//*                       echo all PRINTING characters to the display, and       *
//*                       will decode the "special keys" (cursor keys, etc.)     *
//*                       according to the specific soft-echo sub-option.        *
//*                    softEchoA: all special keys (default)                     *
//*                    softEchoC: cursor-movement keys only                      *
//*                    softEchoD: disable all special keys                       *
//*                    softEchoE: edit keys only (Bksp/Del/Ins/Tab/Shift+Tab)    *
//*                                                                              *
//* Returns: 'true'  if echo option modified                                     *
//*          'false' if input buffering currently enabled (or invalid type)      *
//********************************************************************************

bool AnsiCmd::acSetEchoOption ( EchoOpt echoType )
{

   return ( (this->tset->echoOption ( echoType )) ) ;

}  //* End acSetEchoOption() *

//*************************
//*    acGetEchoOption    *
//*************************
//********************************************************************************
//* Get the current input-echo setting.                                          *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: current echo option: member of enum EchoOpt                         *
//********************************************************************************

EchoOpt AnsiCmd::acGetEchoOption ( void )
{

   return ( this->tset->inpEch ) ;

}  //* End acGetEchoOption() *

//*************************
//*    acBlockingInput    *
//*************************
//********************************************************************************
//* Enable or disable blocking read from the input stream (stdin).               *
//* This is a pass-through method for the TermSet function which sets/resets     *
//* The blocking/non-blocking functionality.                                     *
//*                                                                              *
//* IMPORTANT NOTE: Non-blocking input requires that unbuffered input be         *
//* enabled. If not already enabled, unbuffered input (with 'noEcho') will be    *
//* automatically enabled.                                                       *
//*                                                                              *
//* Input  : block : if 'true',  enable blocking read of input stream            *
//*                  if 'false', disable blocking read of input stream           *
//*                                                                              *
//* Returns: current state of the inpBlk flag                                    *
//*          'true'  == blocking-read of stdin                                   *
//*          'false' == non-blocking read of stdin                               *
//********************************************************************************

bool AnsiCmd::acBlockingInput ( bool block )
{

   return ( (this->tset->blockingInput ( block )) ) ;

}  //* End acBlockingInput() *

//**************************
//*    acCaptureSignals    *
//**************************
//********************************************************************************
//* Disable input buffering and capture all signals generated by keyboard input. *
//*                                                                              *
//* This method is provided as a housekeeping convenience, performing a common   *
//* sequence of terminal setup operations:                                       *
//* 1) If input buffering has not yet been disabled, it will be disabled using   *
//*    the specified soft-echo option, and with non-blocking read of the input   *
//*    stream.                                                                   *
//*    a) As a side-effect, the output flow control signals will be disabled.    *
//*       This means that Ctrl+S and Ctrl+Q become ordinary keycodes.            *
//* 2) The Break signal will be captured and converted to an ordinary keycode:   *
//*    Break Signal  : SIGINT  - Ctrl+C  becomes wcsCTL_C                        *
//* 3) The Suspend signal will be captured and converted to an ordinary keycode: *
//*    Suspend Signal: SIGTSTP - Ctrl+Z  becomes wcsCTL_Z                        *
//* 4) The Quit signal will be captured so that the application will be          *
//*    terminated in an orderly manner.                                          *
//*    Quit Signal   : SIGQUIT - Ctrl+4  performs an orderly application shutdown*
//*                                                                              *
//* Refer to the individual capture methods for details:                         *
//*    acBufferedInput()                                                         *
//*    acCaptureBreakSignal()                                                    *
//*    acCaptureSuspendSignal()                                                  *
//*    acCaptureQuitSignal()                                                     *
//*                                                                              *
//* Input  : eopt  : (optional, softEchoA by default)                            *
//*                  specify one of the soft-echo options (enum EchoOpt)         *
//*                                                                              *
//* Returns: 'true'  if all settings successful                                  *
//*          'false' if one or more settings failed                              *
//********************************************************************************

bool AnsiCmd::acCaptureSignals ( EchoOpt eopt )
{
   //* If input buffering is enabled OR if blocking read, *
   if ( this->tset->inpBuf || this->tset->inpBlk )
      this->acBufferedInput ( false, eopt, false ) ;

   //* If not already specified echo option *
   else if ( eopt != this->tset->inpEch )
      this->acSetEchoOption ( eopt ) ;

   //* If not already captured, capture break signal. *
   if ( this->tset->cchType != cchtCopy )
      this->acCaptureBreakSignal ( true, cchtCopy ) ;

   //* If not already captured, capture the suspend signal. *
   if ( ! this->tset->suspCap )
      this->acCaptureSuspendSignal ( true ) ;

   //* If not already captured, capture the quit signal. *
   if ( ! this->tset->quitCap )
      this->acCaptureQuitSignal ( true ) ;

   return ( bool( ! this->tset->inpBuf              &&
                  ! this->tset->inpBlk              &&
                  (this->tset->inpEch == eopt)      &&
                  (this->tset->cchType == cchtCopy) &&
                  this->tset->flowCap               &&
                  this->tset->suspCap               &&
                  this->tset->quitCap)
                ) ;

}  //* End acCaptureSignals() *

//**************************
//*    acReleaseSignals    *
//**************************
//********************************************************************************
//* Release all captured signals. Return control to the terminal software.       *
//* Releases the following signals:                                              *
//* 1) Break Signal  : SIGINT  - Ctrl+C                                          *
//* 2) Suspend Signal: SIGTSTP - Ctrl+Z                                          *
//* 3) Quit Signal   : SIGQUIT - Ctrl+4                                          *
//*                                                                              *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::acReleaseSignals ( void )
{
   if ( this->tset->cchType != cchtTerm )
      this->acCaptureBreakSignal ( false ) ;
   if ( this->tset->suspCap )
      this->acCaptureSuspendSignal ( false ) ;
   if ( this->tset->quitCap )
      this->acCaptureQuitSignal ( false ) ;

}  //* End acReleaseSignals() *

//**************************
//*  acCaptureBreakSignal  *
//**************************
//********************************************************************************
//* Enable or disable application-level capture and processing of the            *
//* BREAK key (CTRL+C).                                                          *
//*                                                                              *
//* By default, the terminal software processes the break signal by immediately  *
//* killing the application which is running in the terminal window.             *
//* While this is certainly an efficient and reliable way to terminate an        *
//* application, it is grossly messy and potentially dangerous. For instance,    *
//* what if the application is editing a critical system file when it is         *
//* terminated in mid-update? Chaos.                                             *
//*                                                                              *
//* In contrast, if the application itself captures the break signal, the        *
//* application can shut down in an orderly manner, ask the user what to do,     *
//* or simply ignore the break signal.                                           *
//*                                                                              *
//*                   -----  -----   -----   -----   -----                       *
//* When local capture of the break signal is active, by default, the signal     *
//* will be handled by the default (non-member) method, BreakSignal_Handler().   *
//*                                                                              *
//* The handler type is specified by the 'ccType' member variable which is       *
//* initialized here.                                                            *
//*                                                                              *
//* Input  : enable : if 'true',  enable application capture of the break key    *
//*                   if 'false', disable application capture of the break key   *
//*                               terminal handles the break signal              *
//*          handlerType: (optional, cctDefault by default)                      *
//*                   member of enum CCH_Type, specify the break-handler type.   *
//*          alert      : (optional, 'false' by default)                         *
//*                       Referenced only when 'handlerType' == cchIgnore.       *
//*                       Optionally sound a beep when the break signal is       *
//*                       received and discarded.                                *
//*                       if 'false', silently discard the break signal.         *
//*                       if 'true',  beep when discarding the break signal.     *
//*          handlerAddr: (optional, null pointer by default)                    *
//*                   - by default, the internal AnsiCmd method,                 *
//*                     BreakSignal_Handler() will provide an orderly response   *
//*                     to receipt of the break signal. See below for details.   *
//*                   - if the address of a handler method is specified, that    *
//*                     method will be activated by the break signal. That       *
//*                     method controls the response (if any) to the signal.     *
//*                     Note: The name of the target method is the address of    *
//*                           that method.                                       *
//*                   This argument is referenced only if 'enable' is set.       *
//*                                                                              *
//* Returns: current state of capture flag                                       *
//*          'true'  break key will be captured by the application               *
//*          'false' break key will be handled by the terminal program           *
//********************************************************************************

bool AnsiCmd::acCaptureBreakSignal ( bool enable, CCH_Type handlerType, bool alert, 
                                     void* handlerAddr )
{

   return ( (this->tset->captureBreakSignal ( enable, handlerType, alert, handlerAddr )) ) ;

}  //* End acCaptureBreakSignal() *

//*************************
//*  breaksignalReceived  *
//**************************
//********************************************************************************
//* This is technically a public method; however, it is called ONLY by the       *
//* non-member method which handles receipt of the break-signal (CTRL+C).        *
//*                                                                              *
//* When the break signal is received, this method is called by a non-member     *
//* method specified as the dedicated CTRL+C signal handler.                     *
//* The handler type is specified by the 'ccType' member variable which is       *
//* set to one of the following:                                                 *
//*                                                                              *
//* Valid Handler Types:                                                         *
//* --------------------                                                         *
//* cchtIgnore: Ignore the Ctrl+C key (default, cctDefault)                      *
//*   Application will continue as if the signal had not been received.          *
//*                                                                              *
//* cchtExit  : Exit the application in an orderly way:                          *
//*    a) the cursor will be placed on the bottom row of the terminal window,    *
//*    b) the terminal bell will sound,                                          *
//*    c) a short message will be displayed, and                                 *
//*    d) the application will be terminated with a minus-one (-1) exit code.    *
//*                                                                              *
//* cchtUser  : Ask the user if they really want to exit the application:        *
//*    a) the terminal bell will sound,                                          *
//*    b) Clear a small area in the upper-right-hand corner of the               *
//*       terminal window. (This is assumed to be the least cluttered,           *
//*       least used area of the window.)                                        *
//*    c) Display a message asking the user to answer:                           *
//*         Exit? 'y'es or 'n'o: _                                               *
//*    d) Wait for the user's response.                                          *
//*                                                                              *
//* cchtCopy  : Push the literal value for Ctrl+C (0x03) back into the input     *
//*             stream. This allows the specialkeyDecode() method to capture     *
//*             the keycode and convert it to a "special" keycode.               *
//*                                                                              *
//* cchtTerm  : Local handler is disabled, so this method is not called.         *
//*                                                                              *
//*                                                                              *
//* Input  : sigNum: signal number to be handled                                 *
//*                  Currently, only the SIGINT (interrupt) is handled.          *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Notes:                                                                       *
//* It is almost certain that when a break signal is received, the application   *
//* will be waiting for key input. For this reason, it is possible that the      *
//* interactive handler option will interfere with the ordinary read operation   *
//* that is already in progress.                                                 *
//* To minimize the damage, unbuffered, non-echo, non-blocking read from         *
//* stdin is used. So the input buffer can be reliably flushed before return     *
//* from the interactive handler.                                                *
//********************************************************************************

void AnsiCmd::breaksignalReceived ( int sigNum )
{
   #define DEBUG_BSR (0)         // for debugging only

   WinSize ws = this->acGetTermSize () ;           // get window dimensions
   WinPos  wp ;                                    // current cursor position
   EchoOpt orig_inpEch = this->tset->inpEch ;      // remember echo option
   bool exitApp = false ;                          // if 'true', exit application
   bool orig_inpBuf   = this->tset->inpBuf ;       // remember buffering flag
   bool orig_inpBlk   = this->tset->inpBlk ;       // remember blocking-read flag
   bool orig_blinkMod = this->attrBits.blinkMod ;  // remember blinking flag
   bool orig_revMod = this->attrBits.revMod ;      // remember fg/bg reverse flag

   //* Ask the user if they really want to exit the application.*
   if ( this->tset->cchType == cchtUser )
   {
      wchar_t wchar ;                        // user response

      #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_BSR != 0
      gString gstmp ;
      if ( (ofsdbg.is_open()) )
      {
         gstmp.compose( "breaksignalReceived ( %hhd %hd %hhd %hd ", 
                        &this->tset->inpBuf, &this->tset->inpEch, 
                        &this->tset->inpBlk, &this->tset->cchType ) ;
         ofsdbg << "---breaksignalReceived[cchtUser]:---" << endl ;
      }
      #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_BSR

      //* If buffered input, disable buffering, input-echo and blocking-read.*
      if ( this->tset->inpBuf )
         this->acBufferedInput ( false, noEcho, false ) ;
      else if ( this->tset->inpBlk )
         this->acBlockingInput ( false ) ;

      //* Get the current cursor position *
      wp = this->acGetCursor () ;

      //* If blinking and reversed fg/bg not enabled, enable them now *
      if ( ! this->attrBits.blinkMod )
         this->acSetMod ( aesBLINK_SLOW ) ;
      if ( ! this->attrBits.revMod )
         this->acSetMod ( aesREVERSE ) ;

      //* Display the user prompt in top right corner of terminal window.*
      gString gsOut( " Break detected. Exit? (N|y):" ) ;
      short promptWidth = gsOut.gscols() + 1 ;
      short  promptRow = 1,
             promptCol = ws.ws_col - promptWidth - 4 ;
      this->acSetCursor ( aesCUR_ABSPOS, promptRow, promptCol ) ;
      this->ttyWrite ( gsOut ) ;
      wchar = this->silentRead () ;  // get user response

      //* Set blinking to original previous setting *
      if ( ! orig_blinkMod )
         this->acSetMod ( aesBLINK_OFF ) ;
      if ( ! orig_revMod )
         this->acSetMod ( aesREVERSE_OFF ) ;

      //* Erase the prompt *
      gsOut.clear() ;
      gsOut.padCols( promptWidth ) ;
      this->acSetCursor ( aesCUR_ABSPOS, promptRow, promptCol ) ;
      this->ttyWrite ( gsOut ) ;

      //* Clear the input buffer *
      this->acFlushIn () ;

      //* Set buffered input, input-echo and blocking vs. non-blocking *
      //* read to previous settings.                                   *
      //* Logic: Minimize the time spent twiddling bits during the     *
      //*        system call(s).                                       *
      //* A) If input buffering previously enabled, OR if current echo *
      //*    option != previous echo option, simultaneously return all *
      //*    all three parameters to their previous values.            *
      //* B) Else, current buffering and echo options are the same as  *
      //*    previous settings. If blocking option has changed, return *
      //*    it to its previous value.                                 *
      if ( orig_inpBuf || (orig_inpEch != this->tset->inpEch) )
         this->acBufferedInput ( orig_inpBuf, orig_inpEch, orig_inpBlk ) ;
      else if ( orig_inpBlk != this->tset->inpBlk )
         this->acBlockingInput ( orig_inpBlk ) ;

      //* If user specified that application be terminated *
      if ( (wchar == L'y') || (wchar == L'Y') )
      {
         exitApp = true ;        // signal exit
      }

      else           // discard the break request
      {
         //* Restore previous cursor position *
         this->acSetCursor ( aesCUR_ABSPOS, wp.row, wp.col ) ;
      }

      #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_BSR != 0
      if ( (ofsdbg.is_open()) )
      {
         gsdbg = gstmp ;
         gsdbg.append( "-> %hhd %hd %hhd %hd - %hd/%hd )", 
                       &this->tset->inpBuf, &this->tset->inpEch, 
                       &this->tset->inpBlk, &this->tset->cchType,
                       &wp.row, &wp.col ) ;
         ofsdbg << gsdbg.ustr() << endl ;
      }
      #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_BSR

   }  // (cchType == cchtUser)

   //* Exit the application in an orderly way.*
   else if ( this->tset->cchType == cchtExit )
   {
      exitApp = true ;           // signal exit
   }  // (cchType == cchtExit)

   //* Interpret Ctrl+C as the copy command *
   else if ( this->tset->cchType == cchtCopy )
   {
      const wchar_t CC = 0x0003 ;
      //* Push the literal Ctrl+C keycode (0x03) back into the stream.*
      if ( this->wideStream )
         wcin.putback( CC ) ;
      else
         cin.putback( CC ) ;
      #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_BSR != 0
      if ( (ofsdbg.is_open()) )
      {
         gsdbg.compose( "break handler (cchtCopy %02Xh)", &CC ) ;
         ofsdbg << gsdbg.ustr() << endl ;
      }
      #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_BSR
   }  // (cchType == cchtCopy)

   //* Ignore the Ctrl+C key.*
   else     // cchType == cchtIgnore
   {
      //* If specified make some noise. *
      //* Otherwise, do nothing.        *
      if ( this->tset->cchAlert )
         this->acBeep () ;
   }  // (cchType == cchtIgnore)

   //* If exit flag set *
   if ( exitApp != false )
   {
      //* If application-layer shut-down method specified, *
      //* call it now. (currently not implemented)         *

      //* Set cursor at bottom of terminal window *
      //* and display termination message.        *
      this->acSetCursor ( aesCUR_ABSPOS, (ws.ws_row - 1), 1 ) ;
      this->acEraseArea ( aesERASE_BOW ) ;
      this->ttyWrite ( "Application Terminated.\n" ) ;

      //* Call the AnsiCmd destructor. This will delete    *
      //* both the TermSet object and the AnsiCmd object   *
      //* in an orderly manner. Any application-layer      *
      //* housekeeping needed should be specified by the   *
      //* 'atexit' function. (see man pages for more info) *
      this->~AnsiCmd () ;

      //* Exit the application (with error status) *
      // Programmer's Note: Depending on how the compiler structures the destructor 
      // sequence, it is possible that the exit() function will not be called.
      // In any case, the application WILL exit.
      exit ( -1 ) ;
   }

   #undef DEBUG_BSR
}  //* End breaksignalReceived() *

//*************************
//*  BreakSignal_Handler  *
//*************************
//********************************************************************************
//* Non-member Method:                                                           *
//* ------------------                                                           *
//* This is the default method specified as the call-back method for handling    *
//* the break signal generated by pressing the Ctrl+C key. It is initialized     *
//* via the acCaptureBreakSignal() method, above.                                *
//*                                                                              *
//* When the break signal is received, this method calls the dedicated member    *
//* method above which will process the signal according to the handler type     *
//* specified during initialization.                                             *
//* Handler type is specified by the 'ccType' member variable.                   *
//*                                                                              *
//* Input  : sigNum: signal number to be handled                                 *
//*                  Currently, only the SIGINT (interrupt) is handled.          *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Programmer's Note:                                                           *
//* In order to access AnsiCmd-class functionality, this non-member method       *
//* reqires a pointer to the AnsiCmd object. This is technically and             *
//* philosophically suspect; however, this method promises not to trash          *
//* anything within the object. See 'acObjPtr' at the top of this module.        *
//*                                                                              *
//********************************************************************************

static void BreakSignal_Handler ( int sigNum )
{

   //* Call the member method to process the signal.*
   acObjPtr->breaksignalReceived ( sigNum )  ;

}  //* End BreakSignal_Handler() *

//**************************
//* acCaptureSuspendSignal *
//**************************
//********************************************************************************
//* Enable or disable application-level capture and processing of the            *
//* SUSPEND key CTRL+Z. Signal name: SIGTSTP.                                    *
//*                                                                              *
//* If this signal is seen by the terminal program, it will suspend the active   *
//* process. That is, it will put the process into the background and disable    *
//* direct access to it. A different process may then occupy the foreground,     *
//* interacting with the input and output streams.                               *
//* Use the 'fg' utility to restore the hibernating process.                     *
//*                                                                              *
//* Because Ctrl+Z is often used as a command (undo) within an application, this *
//* signal is captured by default and converted to an ordinary keycode: wcsCTL_Z.*
//*                                                                              *
//* Input  : enable : if 'true',  enable application capture of the suspend key  *
//*                   if 'false', disable application capture of the suspend key *
//*                               terminal handles the suspend signal            *
//* Returns: current state of capture flag                                       *
//*          'true'  suspend key will be captured by the application             *
//*          'false' suspend key will be handled by the terminal program         *
//********************************************************************************

bool AnsiCmd::acCaptureSuspendSignal ( bool enable )
{

   return ( (this->tset->captureSuspendSignal ( enable )) ) ;

}  //* End acCaptureSuspendSignal() *

//*************************
//* suspendsignalReceived *
//*************************
//********************************************************************************
//* This is technically a public method; however, it is called ONLY by the       *
//* non-member method which handles receipt of the suspend signal (CTRL+Z).      *
//*                                                                              *
//* When the suspend signal is received, this method is called by a non-member   *
//* method specified as the dedicated CTRL+Z signal handler.                     *
//*                                                                              *
//* Input  : sigNum: signal number to be handled                                 *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::suspendsignalReceived ( int sigNum )
{
   #define DEBUG_SSR (0)         // for debugging only

   //* Interpret Ctrl+Z as the 'undo' command *
   const wchar_t CZ = 0x001A ;
   //* Push the literal Ctrl+Z keycode (0x1A) back into the stream.*
   if ( this->wideStream )
      wcin.putback( CZ ) ;
   else
      cin.putback( CZ ) ;

   #if DEBUG_ANSICMD != 0 && DEBUG_LOG != 0 && DEBUG_SSR != 0
   if ( (ofsdbg.is_open()) )
   {
      gsdbg.compose( "suspend handler (%02Xh)", &CZ ) ;
      ofsdbg << gsdbg.ustr() << endl ;
   }
   #endif   // DEBUG_ANSICMD && DEBUG_LOG && DEBUG_SSR

   #undef DEBUG_SSR
}  //* End suspendsignalReceived() *

//*************************
//* SuspendSignal_Handler *
//*************************
//********************************************************************************
//* Non-member Method:                                                           *
//* ------------------                                                           *
//* This is the default method specified as the call-back method for handling    *
//* the suspend signal generated by pressing the Ctrl+Z key. It is initialized   *
//* via the acCaptureSuspendSignal() method, above.                              *
//*                                                                              *
//* Input  : sigNum: signal number to be handled                                 *
//*                  Currently, only the SIGTSTP (suspend) is handled.           *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Programmer's Note:                                                           *
//* In order to access AnsiCmd-class functionality, this non-member method       *
//* reqires a pointer to the AnsiCmd object. This is technically and             *
//* philosophically suspect; however, this method promises not to trash          *
//* anything within the object. See 'acObjPtr' at the top of this module.        *
//********************************************************************************

static void SuspendSignal_Handler ( int sigNum )
{
   //* Call the member method to process the signal.*
   acObjPtr->suspendsignalReceived ( sigNum )  ;

}  //* End SuspendSignal_Handler() *

//**************************
//*  acCaptureQuitSignal   *
//**************************
//********************************************************************************
//* Enable or disable application-level capture and processing of the            *
//* QUIT key (CTRL+4  or  CTRL+\). Signal name: SIGQUIT.                         *
//*                                                                              *
//* If this signal is seen by the terminal program, it will immediately kill     *
//* the application and execute a core dump. We consider this to be entirely     *
//* too Wild-West and generally uncivilized. While there are valid reasons to    *
//* perform a core dump, they should not be generated by accident. For this      *
//* reason, the QUIT signal is captured and the application is shut down in an   *
//* orderly manner, releasing all dynamic memory allocations, deleting all       *
//* temporary files and performing general housekeeping duties before exiting    *
//* the application. These are the same steps taken when the Break signal        *
//* (Ctrl+C) is invoked to shut down the application.                            *
//*                                                                              *
//*                                                                              *
//* Input  : enable : if 'true',  enable application capture of the quit key     *
//*                   if 'false', disable application capture of the quit key    *
//*                               terminal handles the signal                    *
//*                                                                              *
//* Returns: current state of capture flag                                       *
//*          'true'  quit key will be captured by the application                *
//*          'false' quit key will be handled by the terminal program            *
//********************************************************************************

bool AnsiCmd::acCaptureQuitSignal ( bool enable )
{

   return ( (this->tset->captureQuitSignal ( enable )) ) ;

}  //* End acCaptureQuitSignal() *

//*************************
//*  quitsignalReceived   *
//*************************
//********************************************************************************
//* This is technically a public method; however, it is called ONLY by the       *
//* non-member method which handles receipt of the quit signal (CTRL+4 or CTRL+\)*
//*                                                                              *
//* When the quit signal is received, this method is called by a non-member      *
//* method specified as the dedicated CTRL+4 signal handler.                     *
//*                                                                              *
//* Input  : sigNum: signal number to be handled                                 *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::quitsignalReceived ( int sigNum )
{
   //* If application-layer shut-down method specified, *
   //* call it now. (currently not implemented)         *

   //* Reset color attributes and modifiers *
   this->acReset () ;

   //* Set cursor at bottom of terminal window *
   //* and display termination message.        *
   WinSize ws = this->acGetTermSize () ;           // get window dimensions
   this->acSetCursor ( aesCUR_ABSPOS, (ws.ws_row - 1), 1 ) ;
   this->acEraseArea ( aesERASE_BOW ) ;
   this->ttyWrite ( "Application Terminated.\n" ) ;

   //* Call the AnsiCmd destructor. This will delete    *
   //* both the TermSet object and the AnsiCmd object   *
   //* in an orderly manner. Any application-layer      *
   //* housekeeping needed should be specified by the   *
   //* 'atexit' function. (see man pages for more info) *
   this->~AnsiCmd () ;

   //* Exit the application (with error status) *
   exit ( -1 ) ;

}  //* End quitsignalReceived() *

//*************************
//*  QuitSignal_Handler   *
//*************************
//********************************************************************************
//* Non-member Method:                                                           *
//* ------------------                                                           *
//* This is the default method specified as the call-back method for handling    *
//* the quit signal generated by pressing the Ctrl+4 (or Ctrl+\) keys.           *
//* It is initialized via the acCaptureQuitSignal() method, above.               *
//*                                                                              *
//* Input  : sigNum: signal number to be handled                                 *
//*                  Currently, only the SIGQUIT (quit) is handled.              *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* Programmer's Note:                                                           *
//* In order to access AnsiCmd-class functionality, this non-member method       *
//* reqires a pointer to the AnsiCmd object. This is technically and             *
//* philosophically suspect; however, this method promises not to trash          *
//* anything within the object. See 'acObjPtr' at the top of this module.        *
//********************************************************************************

static void QuitSignal_Handler ( int sigNum )
{
   //* Call the member method to process the signal.*
   acObjPtr->quitsignalReceived ( sigNum )  ;

}  //* End QuitSignal_Handler() *

//*************************
//*      acFullReset      *
//*************************
//********************************************************************************
//* Reset _all_ ANSI terminal attributes before modifying an attribute.          *
//*                                                                              *
//* The AnsiCmd class is constructed so that when modifying an ANSI parameter    *
//* other parameters will be affected as little as possible. Specifically,       *
//* when enabling or disabling an attribute such as Bold, Underline, etc.,       *
//* the intent is to avoid disturbing the existing forground and background      *
//* color settings.                                                              *
//*                                                                              *
//* However, some console software or their underlying system software may       *
//* not support all of the single-attribute commands such as aesBOLD_OFF,        *
//* aesITALIC_OFF, aesUNDERLINE_OFF, aesBLINK_OFF aesREVERSE_OFF,                *
//* aesCONCEAL_OFF, aesXOUT_OFF, aesFRM_ENC_OFF, aesOVERLINE_OFF, aesIDEO_OFF.   *
//* If these reset commands are not supported on a given platform, then set      *
//* the 'fullReset' flag, and the AnsiCmd class will use the full reset          *
//* command, aesRESET when changing terminal settings. The aesRESET command      *
//* is supported by most, if not all terminal emulators.                         *
//*                                                                              *
//*                                                                              *
//* Input  : enable : 'true' to enable full reset for attribute modifications    *
//*                   'false' to disable full reset and rely upon the            *
//*                           setting/resetting the flag which controls the      *
//*                           specific attribute being modified                  *
//*                                                                              *
//* Returns: current state of the full-reset flag                                *
//********************************************************************************

bool AnsiCmd::acFullReset ( bool enable )
{

   this->fullReset = enable ;
   return this->fullReset ;

}  //* End acFullReset() *

//*************************
//*   acRestoreTermInfo   *
//*************************
//********************************************************************************
//* Restore terminal stream settings to original values captured during first    *
//* terminal-info read (probably during the TermSet class instantiation).        *
//* This is a pass-through to the TermSet method.                                *
//*                                                                              *
//* Terminal configuration returned to state captured during instantiation of    *
//* TermSet object.                                                              *
//*  a) Buffering of input stream.                                               *
//*  b) Echo option (see enum EchoOpt).                                          *
//*  c) Blocking vs. non-blocking read of input stream.                          *
//*  d) Disable local break-key handler, if active.                              *
//*  e) Set cursor style to terminal default                                     *
//*                                                                              *
//* Input  : restoreAll : (optional, 'false' by default)                         *
//*                       if 'false' this method restores input buffering,       *
//*                          input echo, and blocking-read settings only.        *
//*                          Break-handler, cursor style, and other terminal     *
//*                          configuration options are not affected.             *
//*                       if 'true',  all terminal setting under control of      *
//*                          the TermSet object will be returned to the states   *
//*                          when the object was instantiated. (see list above)  *
//*                                                                              *
//*                                                                              *
//* Returns: 'true' if settings restored                                         *
//*          'false' if original settings not previously saved                   *
//*                  or if system error                                          *
//********************************************************************************

bool AnsiCmd::acRestoreTermInfo ( bool restoreAll )
{

   return ( (this->tset->restoreTermInfo ( restoreAll )) ) ;

}  //* End acRestoreTermInfo() *

//*************************
//*     isSpecialKey      *
//*************************
//********************************************************************************
//* Determine whether the specified keycode is one of the "special" keys, AND    *
//* whether that keycode is a member of the active soft-echo sub-group.          *
//*                                                                              *
//* The test is performed against the soft-echo option specified by the prior    *
//* terminal setup, (but see the 'eopt' argument).                               *
//*                                                                              *
//* The available character-echo options are members of enum EchoOpt. Please     *
//* refer to the documentation for the TermConfig class, and specifically the    *
//* 'echoOption' member variable for terminal setup information.                 *
//*                                                                              *
//* NOTE: This method should not be called when the echo option is not one       *
//*       of the soft-echo options, but if it is, it will only result in a       *
//*       harmess waste of CPU cycles.                                           *
//*                                                                              *
//* Each of the soft-echo options encompasses a subset of the supported          *
//* "special" keycodes. Please see the documentation for a list of keycodes      *
//* included in each of the soft-echo options.                                   *
//*                                                                              *
//* Input  : wkey : (by reference) keycode to be tested                          *
//*                                                                              *
//*          alert: (optional, 'false' by default) audible alert                 *
//*                 'true'  execute a terminal beep if keycode under test        *
//*                         IS one of the supported soft-echo keycodes, but      *
//*                         IS NOT not included in the active soft-echo group.   *
//*                 'false' do not execute an audible alert                      *
//*          eopt : (optional, null pointer by default)                          *
//*                 If specified, this is a pointer to an alternate soft-echo    *
//*                 option to be used for the test.                              *
//*                    (used internally for special-key verification)            *
//*                                                                              *
//* Returns: 'true'  if key is one of the ACTIVE special keys                    *
//*          'false' if key is not one of the active special keys                *
//*                  If the caller's keycode is a Special key that IS NOT a      *
//*                  member of the active group. the keycode will be set to      *
//*                  the null character (NULLCHAR) on return.                    *
//********************************************************************************
//* Certain "special" keys are defined for editing within a field:               *
//*    wcsUARROW  Up-Arrow key                                                   *
//*    wcsDARROW  Down-Arrow key                                                 *
//*    wcsLARROW  Left-Arrow key                                                 *
//*    wcsRARROW  Right-Arrow key                                                *
//*    wcsHOME    Home key                                                       *
//*    wcsEND     End key                                                        *
//*    wcsCHOME   Ctrl+Home key                                                  *
//*    wcsCEND    Ctrl+End key                                                   *
//*    wcsPGUP    Page-Up key                                                    *
//*    wcsPGDN    Page-Down key                                                  *
//*    wcsBKSP    Backspace key                                                  *
//*    wcsDELETE  Delete key                                                     *
//*    wcsINSERT  Insert/Overstrike key                                          *
//*    wcsAENTER  Alt+Enter key used as a soft line break.                       *
//*    wcsSRIGHT  Select moving toward the right.                                *
//*    wcsSLEFT   Select moving toward the left.                                 *
//*    wcsSUP     Select moving upward (multi-row fields)                        *
//*    wcsSDOWN   Select moving downward (multi-row fields)                      *
//*    wcsSELECT  Select all text in the field.                                  *
//*    wcsCOPY    Copy field text to the clipboard.                              *
//*    wcsCUT     Copy field text to clipboard and erase the text in the field.  *
//*    wcsPASTE   Paste text from the clipboard into the field.                  *
//*                                                                              *
//* Moving among the fields of the skForm is implemented by additional           *
//* "special" keycodes:                                                          *
//*    wcsTAB     Tab key                                                        *
//*    wcsSTAB    Shift+Tab key                                                  *
//*    wcsPGUP    Page-Up key                                                    *
//*    wcsPGDN    Page-Up key                                                    *
//*                                                                              *
//********************************************************************************

bool AnsiCmd::isSpecialKey ( wchar_t& wkey, bool alert, EchoOpt *eopt ) const
{
   //* Local copy of echo option *
   EchoOpt localOpt = eopt != NULL ? *eopt : this->tset->inpEch ;
   bool isSpecKey = false ;               // return value

   switch ( localOpt )
   {
      case softEchoA:      // all special keys recognized
      case noEcho:         // all special keys are also recognized under no-echo
      case softEchoD:      // all special keys identified and discarded
         switch ( wkey )
         {  // Basic special-character sub-group
            case wcsUARROW:  case wcsDARROW:  case wcsRARROW:  case wcsLARROW:
            case wcsHOME:    case wcsEND:     case wcsTAB:     case wcsSTAB:
            case wcsCHOME:   case wcsCEND:
            case wcsPGUP:    case wcsPGDN:
            case wcsBKSP:    case wcsDELETE:  case wcsINSERT:  case wcsAENTER:
            //* Text selection and clipboard sub-group *
            case wcsSUP:     case wcsSDOWN:   case wcsSRIGHT:  case wcsSLEFT:
            case wcsSELECT:  case wcsCOPY:    case wcsCUT:     case wcsPASTE:
               if ( (localOpt == softEchoA) || (localOpt == noEcho) )
                  isSpecKey = true ;      // verified
               else
                  wkey = NULLCHAR ;       // discarded
               break ;
            default: break ;
         } ;
         break ;

      case softEchoB:      // basic set of special keys
         switch ( wkey )
         {  // Basic special-character sub-group
            case wcsUARROW:  case wcsDARROW:  case wcsRARROW:  case wcsLARROW:
            case wcsHOME:    case wcsEND:     case wcsTAB:     case wcsSTAB:
            case wcsCHOME:   case wcsCEND:
            case wcsPGUP:    case wcsPGDN:
            case wcsBKSP:    case wcsDELETE:  case wcsINSERT:  case wcsAENTER:
               isSpecKey = true ;
               break ;
            //* Discard text selection and clipboard sub-group *
            case wcsSUP:   case wcsSDOWN: case wcsSRIGHT: case wcsSLEFT:
            case wcsCOPY:  case wcsCUT:   case wcsPASTE:  case wcsUNDO:
            case wcsSELECT:
               wkey = NULLCHAR ;
               break ;
            default: break ;
         } ;
         break ;

      case softEchoC:      // soft-echo cursor-movement keys only
         switch ( wkey )
         {
            case wcsUARROW:  case wcsDARROW:  case wcsRARROW:  case wcsLARROW:
            case wcsHOME:    case wcsEND:     case wcsTAB:     case wcsSTAB:
            case wcsCHOME:   case wcsCEND:
            case wcsPGUP:    case wcsPGDN:
               isSpecKey = true ;
               break ;
            //* Discard edit keys and  text selection/clipboard sub-group *
            case wcsBKSP:  case wcsDELETE:  case wcsINSERT:  case wcsAENTER:
            case wcsSUP:   case wcsSDOWN:   case wcsSRIGHT:  case wcsSLEFT:
            case wcsCOPY:  case wcsCUT:     case wcsPASTE:   case wcsUNDO:
            case wcsSELECT:
               wkey = NULLCHAR ;   break ;
            default:               break ;
         } ;
         break ;

      case softEchoE:      // soft-echo edit keys
         // (this group is useful mostly for testing)
         switch ( wkey )
         {
            case wcsBKSP:     case wcsDELETE:   case wcsTAB:      case wcsSTAB:
            case wcsINSERT:   case wcsPGUP:     case wcsPGDN:     case wcsAENTER:
               isSpecKey = true ;  break ;
            //* Discard cursor-movement keys and text selection/clipboard sub-group *
            case wcsUARROW: case wcsDARROW: case wcsRARROW: case wcsLARROW:
            case wcsHOME:   case wcsEND:
            case wcsCHOME:  case wcsCEND:
            case wcsSUP:    case wcsSDOWN:  case wcsSRIGHT: case wcsSLEFT:
            case wcsCOPY:   case wcsCUT:    case wcsPASTE:  case wcsUNDO:
            case wcsSELECT:
               wkey = NULLCHAR ;    break ;
            default:                break ;
         } ;
         break ;

      //* Echo option is not a member of the soft-echo group.*
      //* See note above.                                    *
      case termEcho:
      default:
         break ;
   } ;

   //* Make noise if keycode IS a special key, but *
   //* IS NOT a member of the active group.        *
   if ( alert && ! isSpecKey && (wkey == NULLCHAR) )
      this->acBeep () ;

   return isSpecKey ;

}  //* End isSpecialKey() *

//*************************
//*     isCommandKey      *
//*************************
//********************************************************************************
//* Determine whether the specified keycode is one of the converted keycodes:    *
//* 1) ASCII control codes: Ctrl+A - Ctrl+Z, Ctrl+4, Ctrl+5, Ctrl+6, Ctrl+7.     *
//* 2) "GUI" command keys: Alt+A - Alt+Z, Alt+punctuation characters.            *
//* 3) Ctrl + navigation keys.                                                   *
//* 4) Left Alt + navigation keys                                                *
//* 5) Function keys: wcsF01-fcsF12, wcsSF01-wcsSF12, wcsCF01-wcsCF12            *
//*                                                                              *
//* By default, this test _excludes_ the "special" keycodes. (see isSpecialKey)  *
//* However, this method may optionally _include_ the "special" keycodes         *
//* within the command-key group by setting the 'all' flag.                      *
//*                                                                              *
//* Technical Note:                                                              *
//* ---------------                                                              *
//* These keycodes have been converted from escape sequences or ASCII control    *
//* codes to graphing characters; however, they should not be echoed to the      *
//* display. They would just look like garbage from the user's point of view.    *
//* If the keycode is not associated with an action within the application,      *
//* it should be discarded. See normalkeyProc().                                 *
//*                                                                              *
//*                                                                              *
//* Input  : wkey : (by reference) keycode to be tested                          *
//*                                                                              *
//*          all  : (optional, 'false' by default)                               *
//*                 if 'true', include the "special" keycodes as command keycodes*
//*                 if 'false, "special" keycodes are excluded                   *
//*                                                                              *
//* Returns: 'true'  if keycode is one of the command keycodes, else 'false'     *
//********************************************************************************

bool AnsiCmd::isCommandKey ( wchar_t& wkey, bool all ) const
{
   bool isCmdKey = false ;                // return value

   //* Converted ASCII control keys (Ctrl+n) *
   if ( ((wkey >= wcsCTL_A) && (wkey <= wcsCTL_7)) &&
       !((wkey == wcsCOPY) || (wkey == wcsPASTE) || (wkey == wcsCUT) ||
         (wkey == wcsSELECT) || (wkey == wcsUNDO)) )
   { isCmdKey = true ; }

   //* Converted Alt+n keycodes *
   else if ( (wkey >= wcsALT_A) && (wkey <= wcsALT_RB) )
      isCmdKey = true ;

   //* Function keys are included the command-key group. *
   else if ( (wkey >= wcsF01) && (wkey <= wcsCF12) )
      isCmdKey = true ;

   //* Other converted keycodes: *
   else
   {
      isCmdKey = true ;                   // assume success

      switch ( wkey )
      {  //* Alt + Cursor Keys *
         case wcsAUP:   case wcsADOWN: case wcsARIGHT: case wcsALEFT:
         case wcsAHOME: case wcsAEND:  case wcsAPGUP:  case wcsAPGDN:
            break ;
         //* Ctrl + Cursor Key *
         case wcsCUP:   case wcsCDOWN: case wcsCRIGHT: case wcsCLEFT:
         case wcsCHOME: case wcsCEND:
            break ;
         //* Delete key combinations *
         //* Note that Ctrl+Alt+Delete is reserved by the system as "Logout". *
         case wcsSDEL:  case wcsADEL:  case wcsASDEL: case wcsCDEL:  
         case wcsCSDEL: case wcsCASDEL:
            break ;
         //* PageUp and PageDown combinations *
         case wcsSPGUP:  case wcsSPGDN:  case wcsASPGUP:  case wcsASPGDN:
         case wcsCSPGUP: case wcsCSPGDN: case wcsCAPGUP:  case wcsCAPGDN:
            break ;
         default:
            isCmdKey = false ;
            break ;
      } ;
   }

   if ( all && !isCmdKey )
   {
      EchoOpt allOpt = softEchoA ;  // test for all "special" keycodes
      isCmdKey = this->isSpecialKey ( wkey, false, &allOpt ) ;
   }

   return isCmdKey ;

}  //* End isCommandKey() *

//*************************
//*       getConfig       *
//*************************
//********************************************************************************
//* For use during development: Get a copy of current configuration.             *
//*                                                                              *
//*                                                                              *
//* Input  : tCfg : (by reference) receives configuration data                   *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::getConfig ( termConfig& tCfg )
{
   tCfg.termRows   = this->termRows ;
   tCfg.termCols   = this->termCols ;
   tCfg.ioLocale   = this->ioLocale ;
   tCfg.tios       = this->tset->strMods ;
   tCfg.curStyle   = this->tset->curStyle ;
   tCfg.cchType    = this->tset->cchType ;
   tCfg.inpEch     = this->tset->inpEch ;
   tCfg.inpBuf     = this->tset->inpBuf ;
   tCfg.inpBlk     = this->tset->inpBlk ;
   tCfg.cchAlert   = this->tset->cchAlert ;
   tCfg.curVis     = this->tset->curVis ;
   tCfg.quitCap    = this->tset->quitCap ;
   tCfg.suspCap    = this->tset->suspCap ;
   tCfg.flowCap    = this->tset->flowCap ;
   tCfg.isWin      = this->tset->isWin ;
   tCfg.strCap     = this->tset->strCap ;
   tCfg.fullReset  = this->fullReset ;
   tCfg.wideStream = this->fullReset ;

}  //* End getConfig() *

//*************************
//*    getDataMembers     *
//*************************
//********************************************************************************
//* Protected Method:                                                            *
//* -----------------                                                            *
//* Returns a copy of the protected data members of the class.                   *
//* Used primarily by the ACWin class to synchronize terminal configuration.     *
//* Note that tall data and methods of the TermSet class are private.            *
//*                                                                              *
//* Input  : ts : receives selected data members of TermSet object               *
//*          ab : receives decoded terminal color attributes                     *
//*          lo : receives a pointer to current locale structure                 *
//*          tr : (by reference) receives terminal rows                          *
//*          tc : (by reference) receives terminal columns                       *
//*          fn : (by reference) receives current font number                    *
//*          fr : (by reference) receives the 'full-reset' flag value            *
//*          ws : (by reference) receives the 'wide-stream' flag value           *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::getDataMembers ( TermSet* ts, acaExpand* ab, locale* lo, 
                               short& tr, short& tc, 
                               short& fn, bool& fr, bool& ws ) const
{

   ts->curStyle = this->tset->curStyle ;
   ts->cchType  = this->tset->cchType ;
   ts->inpEch   = this->tset->inpEch ;
   ts->inpBuf   = this->tset->inpBuf ;
   ts->inpBlk   = this->tset->inpBlk ;
   ts->cchAlert = this->tset->cchAlert ;
   ts->quitCap  = this->tset->quitCap ;
   ts->suspCap  = this->tset->suspCap ;
   *ab = this->attrBits ;
   if ( this->ioLocale != NULL )
      *lo = *this->ioLocale ;
   tr = this->termRows ;
   tc = this->termCols ;
   fr = this->fullReset ;
   ws = this->wideStream ;

}  //* End getDataMembers() ;

//*************************
//*       ansiTest        *
//*************************
//********************************************************************************
//* This method is a gateway to the debugging code. If debugging disabled        *
//* (DEBUG_ANSICMD == 0), then a warning message is displayed.                   *
//*                                                                              *
//* Perform the specified test of terminal functionality. This functionality     *
//* is divided into different groups. See ansiTest_Menu method for details.      *
//*                                                                              *
//* Input  : args    : (by reference) test to perform and test options           *
//*                    major   : specifies the test to perform                   *
//*                    minor   : specifies test sub-section                      *
//*                    supp    : specifies sub-test for RGB, etc.                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void AnsiCmd::ansiTest ( dbgArgs& args )
{
   #if DEBUG_ANSICMD != 0 && DEBUG_ANSICMD_TEST != 0 && INCLUDE_API != 0
   this->ansiTest_Menu ( args ) ;
   #else    // Debug methods disabled
   this->ttyWrite ( L"These tests are disabled under the "
                     "\"DEBUG_ANSICMD\" && \"DEBUG_ANSICMD_TEST\" && \"INCLUDE_API\" "
                     "compile-time directives.\n" )   ;
   #endif   // DEBUG_ANSICMD && DEBUG_ANSICMD_TEST && INCLUDE_API

   this->ttyWrite ( L"\n", true ) ; // flush the output buffer and prepare to exit

}  //* End ansiTest() *


//******************************************************************************
//**  ----  ----  ----  ----  ----  ----  ----  ----  ----  ----  ----  ----  **
//**  ----  ----  ----  Methods Of The TermSet Class  ----  ----  ----  ----  **
//**  ----  ----  ----  ----  ----  ----  ----  ----  ----  ----  ----  ----  **
//******************************************************************************

//*************************
//*        TermSet        *
//*************************
//********************************************************************************
//* Default constructor.                                                         *
//* See initialization constructor for information about configuration           *
//* defaults                                                                     *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: implicitly returns a pointer to object                              *
//********************************************************************************

TermSet::TermSet ( void )
{

   this->reset () ;                 // initialize all data members
   TermIos tios ;                   // temporary local copy of term info

   //* Initialize the default stream i.e. stdIn *
   //* As a side-effect, the captured data are  *
   //* saved to the 'strOrig' member.           *
   this->getTermInfo ( stdIn, tios ) ;

}  //* End TermSet() *

//*************************
//*        TermSet        *
//*************************
//********************************************************************************
//* Initialization constructor: initialization taken from the specified          *
//* TermConfig object.                                                           *
//*                                                                              *
//* -- termStream  : specify the target stream (member of enum TermStream)       *
//*                  Default: stdIn                                              *
//* -- bufferInput : specify whether input from stdin stream is buffered by      *
//*                  the terminal or is immediately available to the             *
//*                  application                                                 *
//*                  Default: true                                               *
//* -- echoOption :  specify the echo option for data input through stdin        *
//*                  stream                                                      *
//*                  Default: termEcho                                           *
//*                                                                              *
//* The remaining members of the TermConfig object are ignored at this time.     *
//*                                                                              *
//* Input  : tCfg : configuration parameters                                     *
//*                                                                              *
//* Returns: implicitly returns a pointer to object                              *
//********************************************************************************

TermSet::TermSet ( const TermConfig& tCfg )
{

   this->reset () ;                 // initialize all data members
   TermIos tios ;                   // temporary local copy of term info

   //* Initialize the target stream dataset.    *
   //* As a side-effect, the captured data are  *
   //* saved to the 'strOrig' member.           *
   this->getTermInfo ( tCfg.termStream, tios ) ;

   //* Set terminal configuration.              *
   //* Programmer's Note: We may be configuring *
   //* the same setup redundantly, but it is    *
   //* safer to work with a known dataset.      *
   this->setTermInfo ( tCfg.termStream, tCfg.bufferInput, tCfg.echoOption ) ;

   //* If specified, capture the "break" signal (CTRL+C),   *
   //* according to the specified processing type. Default  *
   //* handler function will be used.                       *
   if ( tCfg.bcType != cchtTerm )
      this->captureBreakSignal ( true, tCfg.bcType, tCfg.bcAlert ) ;

   //* If specified, capture the "suspend" signal (CTRL+Z), *
   //* and convert it to an ordinary keycode.               *
   if ( tCfg.suspendSignal )
      this->captureSuspendSignal ( true ) ;

   //* If specified, capture the "quit" signal (CTRL+4 and CTRL+\).*
   //* to initiate an orderly shutdown of the application.         *
   if ( tCfg.quitSignal )
      this->captureQuitSignal ( true ) ;

   //* If specified, set the cursor style.   *
   //* (color attribute is currently ignored)*
   if ( tCfg.cursorStyle != csDflt )
      this->cursorStyle ( tCfg.cursorStyle, aesFG_DFLT ) ;

}  //* End TermSet() *

//*************************
//*       ~TermSet        *
//*************************
//********************************************************************************
//* Destructor: Delete the current class object.                                 *
//*                                                                              *
//* Note that the terminal software itself may or may not restore                *
//* its original settings when the application returns control to                *
//* the system. However, if original settings were captured, we also restore     *
//* those settings here to provide the cleanest exit.                            *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

TermSet::~TermSet ( void )
{
   //* If break signal captured, release it back into the wild.*
   if ( this->cchType != cchtTerm )
      this->captureBreakSignal ( false ) ;

   //* If suspend signal captured, release it *
   if ( this->suspCap != false )
      this->captureSuspendSignal ( false ) ;

   //* If quit signal captured, release it *
   if ( this->quitCap )
      this->captureQuitSignal ( false ) ;

   //* If Cursor Style != default, return it to default.*
   if ( this->curStyle != csDflt )
      this->cursorStyle ( csDflt ) ;

   //* Buffered Input and Input Echo Option:       *
   //* If original settings have been captured,    *
   //* return terminal to original settings before *
   //* object returns its resources to the system. *
   if ( this->strCap && instanceCount <= ZERO )
      this->restoreTermInfo () ;

}  //* End ~TermSet() *

//*************************
//*      getTermInfo      *
//*************************
//********************************************************************************
//* Get the current terminal settings (or the original settings).                *
//*                                                                              *
//* Input  : tStream : one of stdIn, stdOut, stdErr                              *
//*          tios    : (by reference) receives settings                          *
//*          orig    : (optional, 'false' by default)                            *
//*                    'false': the current terminal settings are returned       *
//*                    'true' : the original terminal settings are returned      *
//*                                                                              *
//* Returns: 'true' if  settings captured                                        *
//*          'false' if system error                                             *
//********************************************************************************
//* Notes:                                                                       *
//* ======                                                                       *
//*                                                                              *
//* struct termios                                                               *
//* {                                                                            *
//*    tcflag_t c_iflag ;    /* input mode flags   */                            *
//*    tcflag_t c_oflag ;    /* output mode flags  */                            *
//*    tcflag_t c_cflag ;    /* control mode flags */                            *
//*    tcflag_t c_lflag ;    /* local mode flags   */                            *
//*    cc_t     c_line ;     /* line discipline    */                            *
//*    cc_t     c_cc[NCCS] ; /* control characters */                            *
//*                                                                              *
//*    /* See note below. */                                                     *
//* } ;                                                                          *
//*                                                                              *
//* NOTE: The structure also contains members which encode input and output      *
//*       transmission speeds, but the representation is not specified.          *
//*                                                                              *
//* tcflag_t is an unsigned integer (short) bit-field type.                      *
//* cc_t     is an unsigned integer type (uint8_t) representing characters       *
//*          associated with various terminal control functions.                 *
//* 'NCCS'   is a macro indicating the number of elements in the c_cc[] array.   *
//*          (this is 19 in /usr/include/asm-generic/termbits.h                  *
//*                                                                              *
//* See man 3 'termios' for a full description of all bit flags.                 *
//*                                                                              *
//********************************************************************************

bool TermSet::getTermInfo ( TermStream tStream, TermIos& tIos, bool orig )
{
   bool status = false ;

   //* If the request was for the original settings AND  *
   //* if the original settings were previously captured.*
   if ( orig && this->strCap )
   {
      tIos = this->strOrig ;              // get the original settings
      status = true ;                     // success
   }

   //* Query the system for the current settings *
   else if ( (tcgetattr ( tStream, &tIos )) == 0 )
   {
      status = true ;

      //* If settings not yet saved (and not yet modified), *
      //* save a copy of the settings to our local member.  *
      if ( ! this->strCap && ! this->strMod )
      {
         this->aStr = tStream ;
         this->strOrig = tIos ;
         this->strCap = true ;
      }
   }
   else
   {
      //* System error: reset all bitfield members of caller's structure.     *
      //* Unfortunately, the data in the 'c_cc' array may not be available,   *
      //* but caller should not rely on it anyway if error status is returned.*
      tIos.c_iflag = tIos.c_oflag = 
      tIos.c_cflag = tIos.c_lflag = 0 ;
   }
   return status ;

}  //* End getTermInfo() *

//*************************
//*      setTermInfo      *
//*************************
//********************************************************************************
//* Modify current terminal-stream settings for input buffering and input echo.  *
//* This also requires adjusting the input delay and the minimum bytes to        *
//* accumulate in the buffer before a 'read' operation returns.                  *
//*                                                                              *
//* Note: If the original settings were captured, the caller's selection         *
//* overrides the ICANON and ECHO flags. To restore the original settings for    *
//* these flags, call the RestoreTermInfo() method instead.                      *
//*                                                                              *
//* NOTE: When setting unbuffered input, the IXON flag which handles flow        *
//*       control is also reset. This disables stdout flow control which by      *
//*       default is attached to the Ctrl+S (stop-flow) and Ctrl+Q (resume-flow) *
//*       keys. These keys can then be assigned to application-specific purposes.*
//*                                                                              *
//*                                                                              *
//* Input  : tStream    : member of enum TermStream                              *
//*          buffered   : 'true'  enable buffering                               *
//*                       'false; disable buffering                              *
//*          eopt       : (if 'buffered' != false, this is ignored)              *
//*                       'true'  terminal echoes input                          *
//*                       'false' input is not echoed                            *
//*                                                                              *
//* Returns: 'true' if settings modified                                         *
//*          'false' if invalid stream identifier                                *
//********************************************************************************
//* Programmer's Note: When disabling echo, should we also disable display of    *
//* ASCII control codes of the form "^d"?                                        *
//* Some of them are captured by the terminal, but a few get through. Currently, *
//* the ASCII control codes that reach the application are captured and          *
//* converted by specialkeyDecode(). However, when termEcho is set, they are     *
//* displayed. That includes escape sequences which appear in the form: "^[[A"   *
//* which is the opposite of user friendly. Think about this....                 *
//*                                                                              *
//* -- Currently, the following bitfields are not modified:                      *
//*    c_oflag - Output control flags. Mostly output delays for slow devices.    *
//*    c_cflag - Control mode flags. These flags are used to control a serial    *
//*              I/O connection which is mostly irrelevant in terminal emulation.*
//*                                                                              *
//* -- The ISIG flag enables/disables the following signals:                     *
//*              SIGINTR, SIGQUIT, SIGSUSP, SIGDSUSP.                            *
//*    At this time, we see no reason to disable these signals because we        *
//*    capture them and convert them to ordinary keycodes.                       *
//*                                                                              *
//********************************************************************************

bool TermSet::setTermInfo ( TermStream tStream, bool buffered, EchoOpt eopt )
{
   TermIos tIos ;                         // configuration data
   bool status = false ;                  // return value

   this->getTermInfo ( tStream, tIos ) ;  // current terminal settings

   //* Enable buffering and enable echo *
   if ( buffered )
   {
      //* If non-blocking read is currently active, re-enable blocking read.*
      if ( ! this->inpBlk )
         this->blockingInput ( true ) ;

      if ( this->strCap )                 // if original settings captured
      {
         tIos.c_iflag     = this->strOrig.c_iflag ;      // input-mode flags
         tIos.c_lflag     = this->strOrig.c_lflag ;      // local-mode flags
         tIos.c_cc[VTIME] = this->strOrig.c_cc[VTIME] ;
         tIos.c_cc[VMIN]  = this->strOrig.c_cc[VMIN] ;
         tIos.c_lflag |= (ICANON | ECHO) ; // be sure ICANON and ECHO flags are set
      }
      else                                // set the ICANON, ECHO and IXON flags
      {
         tIos.c_iflag |= (IXON) ;
         tIos.c_lflag |= (ICANON | ECHO) ;
         //tIos.c_lflag |= (ISIG) ;
      }
      eopt = termEcho ;    // buffered input always uses terminal echo
   }

   //* Disable buffering, optionally disabling echo.*
   else
   {
      if ( eopt == termEcho )             // non-canonical mode (with echo)
      {
         tIos.c_lflag &= (~ICANON) ;
         tIos.c_lflag |= ECHO ;
      }
      else                                // non-canonical mode (disable echo)
      {
         tIos.c_lflag &= (~ICANON & ~ECHO) ;
      }
      tIos.c_cc[VTIME] = ZERO ;           // disable VTIME (no wait)
      tIos.c_cc[VMIN] = 1 ;               // min bytes in queue before 'read()' returns

      tIos.c_iflag &= ~(IXON) ;           // disable flow-control (see note above)
      //tIos.c_lflag &= ~(ISIG) ;           // disable signals (see note aove)
   }

   //* Configure the terminal *
   if ( (status = this->termConfig ( tStream, tIos )) )
   {
      //* Update the tracking data *
      this->inpBuf   = buffered ;
      this->inpEch   = eopt ;
      this->flowCap  = this->inpBuf ? false : true ;
   }
   return status ;

}  //* End setTermInfo() *

//*************************
//*    restoreTermInfo    *
//*************************
//********************************************************************************
//* Restore terminal configuration to original settings at the time the          *
//* TermSet object was instantiated.                                             *
//*                                                                              *
//* Terminal configuration returned to state captured during instantiation of    *
//* TermSet object.                                                              *
//*  a) Buffering of input stream.                                               *
//*  b) Echo option (see enum EchoOpt).                                          *
//*  c) Blocking vs. non-blocking read of input stream.                          *
//*  d) Disable local break-key handler, if active.                              *
//*  e) Set cursor style to terminal default                                     *
//*                                                                              *
//* Input  : restoreAll : (optional, 'false' by default)                         *
//*                       if 'false' this method restores input buffering,       *
//*                          input echo, and blocking-read settings only.        *
//*                          Break-handler, cursor style, and other terminal     *
//*                          configuration options are not affected.             *
//*                       if 'true',  all terminal setting under control of      *
//*                          the TermSet object will be returned to the states   *
//*                          when the object was instantiated. (see list above)  *
//*                                                                              *
//* Returns: 'true'  if settings restored                                        *
//*          'false' if system error,                                            *
//*                  or if original settings were never saved (unlikely)         *
//********************************************************************************

bool TermSet::restoreTermInfo ( bool restoreAll )
{
   bool status = false ;            // return value

   //* Assume that blocking-read of input stream *
   //* was originally enabled, and re-enable it. *
   if ( ! this->inpBlk )
      this->blockingInput ( true ) ;

   if ( this->strCap )
   {
      //* If specified, return all remaining terminal    *
      //* configuration options to their original values.*
      if ( restoreAll )
      {
         //* If cursor style is not terminal *
         //* default, return to default.     *
         if ( this->curStyle != csDflt )
            this->cursorStyle ( csDflt ) ;

         //* If break signal previously captured, *
         //* release it back into the wild.       *
         if ( this->cchType != cchtTerm )
            this->captureBreakSignal ( false ) ;
      }

      if ( (status = this->termConfig ( this->aStr, this->strOrig )) )
      {
         //* See the termConfig() method for an explanation of *
         //* the logic used for setting the tracking variables.*
         bool canonflag = bool(this->strOrig.c_lflag & ICANON),
              echoflag  = bool(this->strOrig.c_lflag & ECHO) ;
         this->inpBuf   = bool(canonflag ||
                              (this->strOrig.c_cc[VTIME] > ZERO) ||
                              (this->strOrig.c_cc[VMIN] > 1)) ;
         this->inpEch   = (canonflag || 
                          (!canonflag && echoflag) ? termEcho : noEcho) ;
      }
   }

   return status ;

}  //* End restoreTermInfo() *

//*************************
//*      termConfig       *
//*************************
//********************************************************************************
//* Configure terminal-stream settings using data in 'tIos'.                     *
//* Called only by SetTermInfo() and RestoreTermInfo().                          *
//*                                                                              *
//* Input  : tStream : member of enum TermStream                                 *
//*          tios    : (by reference) new terminal settings                      *
//*                                                                              *
//* Returns: 'true' if settings modified                                         *
//*          'false' if system error                                             *
//********************************************************************************
//* Unbuffered Input Stream                                                      *
//* ------------------------                                                     *
//* Setting the input stream for unbuffered input requires three modifications.  *
//* 1) Canonical input must be disabled:                                         *
//*         (tIos.c_lflag & ~ICANON)                                             *
//* 2) The delay after a keypress must be disabled:                              *
//*         (tIos.c_cc[VTIME] = ZERO)                                            *
//* 3) The number of bytes in the input queue that allows an input read to be    *
//*    performed must be set to one:                                             *
//*         (tIos.c_cc[VMIN] = 1)                                                *
//*                                                                              *
//* Disabling Echo Of Input                                                      *
//* -----------------------                                                      *
//* To disable echo of input characters to the terminal window, the input        *
//* stream must be set for unbuffered input as described above.                  *
//* In addition, the echo flag must be reset:                                    *
//*         (tIos.c_lflag & ~ECHO)                                               *
//* Note that the ECHO flag is not referenced when ICANON flag is set.           *
//*                                                                              *
//* Valid values for "when" (second) parameter of call to tcsetrattr().          *
//* -------------------------------------------------------------------          *
//* ‘TCSANOW’   Make the change immediately.                                     *
//* ‘TCSADRAIN’ Make the change after waiting until all queued output has been   *
//*             written.  You should usually use this option when changing       *
//*             parameters that affect output.                                   *
//* ‘TCSAFLUSH’ This is like ‘TCSADRAIN’, but also discards any queued input.    *
//*                                                                              *
//* These modifications are handled explicity in the overloaded                  *
//* SetTermInfo() method, above, which is currently not used.                    *
//*  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *
//*                                                                              *
//* From the man page for 'tcsetattr':                                           *
//* ----------------------------------                                           *
//* Although ‘tcgetattr’ and ‘tcsetattr’ specify the terminal device with        *
//* a file descriptor, the attributes are those of the terminal device           *
//* itself and not of the file descriptor.  This means that the effects of       *
//* changing terminal attributes are persistent; if another process opens        *
//* the terminal file later on, it will see the changed attributes even          *
//* though it doesn’t have anything to do with the open file descriptor you      *
//* originally specified in changing the attributes.                             *
//*                                                                              *
//* Similarly, if a single process has multiple or duplicated file               *
//* descriptors for the same terminal device, changing the terminal              *
//* attributes affects input and output to all of these file descriptors.        *
//* This means, for example, that you can’t open one file descriptor or          *
//* stream to read from a terminal in the normal line-buffered, echoed mode;     *
//* and simultaneously have another file descriptor for the same terminal        *
//* that you use to read from it in single-character, non-echoed mode.           *
//* Instead, you have to explicitly switch the terminal back and forth           *
//* between the two modes.                                                       *
//*                                                                              *
//********************************************************************************

bool TermSet::termConfig ( TermStream tStream, const TermIos& tIos )
{
   bool status = false ;            // return value

   if ( (tcsetattr ( tStream, TCSANOW, &tIos )) == 0 )
   {
      //* Save the new settings to our local member *
      this->aStr = tStream ;        // target stream
      this->strMods = tIos ;        // save the data
      this->strMod = true ;         // set flag indicating data saved

      //* Update the local tracking members *
      this->inpBuf = bool(tIos.c_lflag & ICANON) ;
      status = true ;
   }
   return status ;

}  //* End termConfig() *

//*************************
//*      echoOption       *
//*************************
//********************************************************************************
//* Select the input character echo type. (enum EchoOpt).                        *
//* This option is available ONLY when input buffering has been disabled.        *
//* Input  : eopt : member of enum EchoOpt:                                      *
//*                 noEcho   : data from the stdin stream will not be            *
//*                            echoedto the terminal window                      *
//*                 termEcho : terminal echoes all stdin data to the window      *
//*                    For the soft-echo options, terminal echo is disabled,     *
//*                    and echo of input characters to the display is            *
//*                    handled locally by the acRead() methods which will        *
//*                    echo all PRINTING characters to the display, and          *
//*                    will decode the "special keys" (cursor keys, etc.)        *
//*                    according to the specific soft-echo sub-option.           *
//*                 softEchoA: all special keys (default)                        *
//*                 softEchoB: basic: all special keys EXCEPT text-selection keys*
//*                 softEchoC: cursor-movement keys only                         *
//*                 softEchoD: disable (discard) all special keys                *
//*                 softEchoE: edit keys only (Bksp/Del/Ins/Tab/Shift+Tab)       *
//*                                                                              *
//* Returns: 'true'  if echo option modified                                     *
//*          'false' if input buffering currently enabled (or invalid type)      *
//********************************************************************************
//* Programmer's Note:                                                           *
//* The 'isWin' member indicates whether the TermSet object is part of           *
//* an ACWin-class object.                                                       *
//********************************************************************************

bool TermSet::echoOption ( EchoOpt eopt )
{
   bool status = false ;            // return value

   //* If called from within an ACWin object, *
   //* limit echo options to: -A, -B or none. *
   if ( this->isWin &&
        !((eopt == softEchoA) || (eopt == softEchoB) || (eopt == noEcho)) )
   { eopt = softEchoA ; }

   //* Buffered input must have been previously disabled.*
   if ( ! this->inpBuf )
   {
      //* If terminal currently controls echo of input data, or *
      //* if option specifies returning control to the terminal,*
      //* call the system to update its control structure.      *
      if ( (this->inpEch == termEcho) || (eopt == termEcho) )
      {
         this->setTermInfo ( stdIn, this->inpBuf, eopt ) ;
      }

      //* If current option is 'noEcho' or one of the soft-echo *
      //* options, simply update the 'inpEch' data member.      *
      else
      {
         this->inpEch = eopt ;
      }
      status = true ;
   }

   return status ;

}  //* End echoOption() *

//*************************
//*      cursorStyle      *
//*************************
//********************************************************************************
//* Set cursor type (shape of the cursor).                                       *
//*                                                                              *
//* Input  : style : member of enum CurStyle                                     *
//*          color : (optional, default from terminal profile)                   *
//*                  member of aeSeq, foreground attribute group: aesFG_xxx      *
//*                  color attribute for cursor [CURRENTLY IGNORED]              *
//*                                                                              *
//* Returns: 'true' if valid argument(s), else 'false'                           *
//********************************************************************************
//* Notes:                                                                       *
//* ------                                                                       *
//* xterm supports the VT520 sequence DECSCUSR Esc[... q since patch 282.        *
//*                                                                              *
//* DECSUSR escape sequence which was used (and extended)                        *
//* in xterm since December 2009.                                                *
//*  VTE_CURSOR_STYLE_TERMINAL_DEFAULT = 0,                                      *
//*  VTE_CURSOR_STYLE_BLINK_BLOCK      = 1,                                      *
//*  VTE_CURSOR_STYLE_STEADY_BLOCK     = 2,                                      *
//*  VTE_CURSOR_STYLE_BLINK_UNDERLINE  = 3,                                      *
//*  VTE_CURSOR_STYLE_STEADY_UNDERLINE = 4,                                      *
//*  /* *_IBEAM are xterm extensions */                                          *
//*  VTE_CURSOR_STYLE_BLINK_IBEAM      = 5,                                      *
//*  VTE_CURSOR_STYLE_STEADY_IBEAM     = 6                                       *
//*                                                                              *
//* These codes are supported under Gnometerm, Konsole and XTerm.                *
//* ECMA-48 (aka "ISO 6429") documents C1 (8-bit) and C0 (7-bit) codes.          *
//* Those are respectively codes 128 to 159 and 0 to 31.                         *
//* https://invisible-island.net/xterm/ctlseqs/ctlseqs.html                      *
//* Set cursor style (DECSCUSR, VT520).                                          *
//*                                                                              *
//* CSI Ps SP q    Escape Sequence: "\x1B[%hd q" where 'Ps' ==                   *
//*     Ps = 0  -> blinking block. (this is steady block in Konsole)             *
//*     Ps = 1  -> blinking block (default).                                     *
//*     Ps = 2  -> steady block.                                                 *
//*     Ps = 3  -> blinking underline.                                           *
//*     Ps = 4  -> steady underline.                                             *
//*     Ps = 5  -> blinking vertical bar        (in the system docs this is    ) *
//*     Ps = 6  -> steady vertical bar          (called "IBeam" which it is not) *
//*                                                                              *
//* Hide / Show Cursor: These are taken from the VT100 terminal specification.   *
//*     "\x1B?25l"      hide cursor                                              *
//*     "\x1B?25h"      show cursor                                              *
//*                                                                              *
//* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*
//* From Linux Gazette: https://linuxgazette.net/137/anonymous.html              *
//* Note: This is from 2007 and may apply only to hardward terminals (and not    *
//*       all of those). Also may not apply under Wayland. Seems non-functional. *
//*                                                                              *
//* Hardware vs. Software Cursor:                                                *
//* The Linux cursor is, by default, the normal PC text-mode cursor.             *
//* It looks like a short blinking underscore, whitish on black, with weak       *
//* contrast in daylight. This is the hardware cursor, i.e., it is generated     *
//* by the hardware.                                                             *
//* However, Linux has the ability to manipulate the cursor appearance using,    *
//* e.g., a software-generated non-blinking full block cursor. This is done with *
//* the escape sequence:                                                         *
//*      \e[?p1;p2;p3;c                                                          *
//* where \e is the escape character, and p1, p2 and p3 are parameters.          *
//* The last semi-colon is optional. It is used here to tell the sequence        *
//* closing letter 'c' from the parameters. If you omit any of the parameters,   *
//* they will default to zero. Only decimals are accepted, and you will never    *
//* get an error: your input is going to be restrained to the valid range.       *
//* Each console can have its own custom softcursor.                             *
//*                                                                              *
//* Parameters:                                                                  *
//* Parameter 1 specifies cursor size as follows:                                *
//*   0  default                                                                 *
//*   1  invisible                                                               *
//*   2  underscore                                                              *
//*   3  lower_third                                                             *
//*   4  lower_half                                                              *
//*   5  two_thirds                                                              *
//*   6  full block                                                              *
//* Parameter 2 is the foreground attribute.                                     *
//* Parameter 3 is the background attribute.                                     *
//*                                                                              *
//* A softcursor is obtained setting parameter 1 to 16.                          *
//*   echo -e "\e[?4;0;16c"                                                     *
//*                                                                              *
//* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -*
//* Cursor Color Attribute:                                                      *
//* Research so far indicates that the color attribute of the cursor is taken    *
//* automatically from the color attribute of the text. If this is true, then    *
//* it is not possible to independently set the color attribute of the cursor.   *
//*                                                                              *
//********************************************************************************

bool TermSet::cursorStyle ( CurStyle style, aeSeq color )
{
   // Programmer's Note: Technically, an enum value should not be used as an
   // ordinary integer; however, we have assigned an actual value to each 
   // element of the CurStyle enumerated type, so we feel safe in this instance.
   const char *CurSetTemplate = "\x1B[%hd q" ; // xterm VT520 format
   const char *CurHide = "\x1B[?25l" ;
   const char *CurShow = "\x1B[?25h" ;
   gString gsOut ;                     // text formatting
   bool status = false ;               // return value

   if ( (style >= csBblock) && (style <= csSvbar) )
   {
      gsOut.compose( CurSetTemplate, &style ) ;
      acObjPtr->ttyWrite ( gsOut ) ;
      this->curStyle = style ;
      status = true ;
   }
   else if ( (style == csHide) || (style == csShow) )
   {
      if ( style == csHide )
      { gsOut = CurHide ; this->curVis = false ; }
      else
      { gsOut = CurShow ; this->curVis = true ; }
      acObjPtr->ttyWrite ( gsOut ) ;
      status = true ;
   }
   return status ;

}  //* End cursorStyle() *

//*************************
//*     blockingInput     *
//*************************
//********************************************************************************
//* Enable or disable blocking read of data from input stream (stdin).           *
//*                                                                              *
//* A "blocking read" is one in which the active execution thread requests       *
//* data from the input stream, then goes to sleep until the data arrives.       *
//*                                                                              *
//* A "non-blocking read" is one in which the active execution thread queries    *
//* the input stream, and if data are not yet available, returns immediately.    *
//*                                                                              *
//* IMPORTANT NOTE: Non-blocking input requires that unbuffered input be         *
//* enabled. If not already enabled when this method is called, unbuffered       *
//* input (with 'noEcho') will automatically be enabled.                         *
//*                                                                              *
//* Input  : block : if 'true',  enable input block i.e. thread sleeps until     *
//*                              input data become available.                    *
//*                  if 'false', disable input blocking i.e. thread returns      *
//*                              immediately if inpuut data not available.       *
//*                                                                              *
//* Returns: current state of the inpBlk flag                                    *
//*          'true'  == blocking-read of stdin                                   *
//*          'false' == non-blocking read of stdin                               *
//********************************************************************************
//* Notes:                                                                       *
//* ------                                                                       *
//* Enabling and disabling this functionality is done through the low-level      *
//* fcntl() C function which sets or resets the 'O_NONBLOCK' flag in the         *
//* terminal's open input stream (stdin) which functions as a file descriptor,   *
//* although it is technically a VIRTUAL file descriptor.                        *
//* Macros defined in fcntl.h:                                                   *
//*   F_GETFL  : Get the flags associated with the open file descriptor (stdin)  *
//*   F_SETFL  : Modify the flags associated with the open file descriptor.      *
//* See the man pages for detailed information.                                  *
//*                                                                              *
//* Note: fcntl() returns EINVAL if kernel does not support the operation;       *
//* however, all Linux kernels support the O_NONBLOCK flag, so we keep it        *
//* simple by assuming that the call will be successful.                         *
//********************************************************************************

bool TermSet::blockingInput ( bool block )
{
   //* Get current state of the input stream flags.*
   int fcntlFlags = fcntl (stdIn, F_GETFL, 0 ) ;

   //* Blocking Read: reset the 'O_NONBLOCK' flag *
   if ( block )
   {
      fcntl( stdIn, F_SETFL, fcntlFlags & ~O_NONBLOCK ) ;
      this->inpBlk = true ;
   }
   //* Non-blocking Read: set the 'O_NONBLOCK' flag *
   else
   {
      //* Unbuffered input must be enabled *
      //* for non-blocking read.           *
      if ( this->inpBuf )
         this->setTermInfo ( stdIn, false, noEcho ) ;

      fcntl( stdIn, F_SETFL, fcntlFlags | O_NONBLOCK ) ;
      this->inpBlk = false ;
   }
   return this->inpBlk ;

}  //* End blockingInput() *

//**************************
//*   captureBreakSignal   *
//**************************
//********************************************************************************
//* Enable or disable application-level capture and processing of the            *
//* BREAK key (CTRL+C).                                                          *
//*                                                                              *
//* By default, the terminal software processes the break signal by immediately  *
//* killing the application which is running in the terminal window.             *
//* While this is certainly an efficient and reliable way to terminate an        *
//* application, it is grossly messy and potentially dangerous. For instance,    *
//* what if the application is editing a critical system file when it is         *
//* terminated in mid-update? Chaos.                                             *
//*                                                                              *
//* In contrast, if the application itself captures the break signal, the        *
//* application can shut down in an orderly manner, ask the user what to do,     *
//* or simply ignore the break signal.                                           *
//*                                                                              *
//*                  -----  -----   -----   -----   -----                        *
//* When local capture of the break signal is active, by default, the signal     *
//* will be handled by the default (non-member) method, BreakSignal_Handler().   *
//*                                                                              *
//* The handler type is specified by the 'cchType' member variable which is      *
//* initialized here.                                                            *
//*                                                                              *
//* Input  : enable : if 'true',  enable application capture of the break key    *
//*                   if 'false', disable application capture of the break key   *
//*                               terminal handles the break signal              *
//*          handlerType: (optional, cctDefault by default)                      *
//*                   member of enum CCH_Type, specify the break-handler type.   *
//*                   Referenced only when break signals handled locally.        *
//*                   Ignored when terminal regains control of break signals.    *
//*          alert      : (optional, 'false' by default)                         *
//*                       Referenced only when 'handlerType' == cchIgnore.       *
//*                       Optionally sound a beep when the break signal is       *
//*                       received and discarded.                                *
//*                       if 'false', silent discard the break signal.           *
//*                       if 'true',  beep when discarding the break signal.     *
//*          handlerAddr: (optional, null pointer by default)                    *
//*                   - by default, the internal AnsiCmd method,                 *
//*                     BreakSignal_Handler() will provide an orderly response   *
//*                     to receipt of thebreak signal. See below for details.    *
//*                   - if the address of a handler method is specified, that    *
//*                     method will be activated by the break signal. That       *
//*                     method controls the response (if any) to the signal.     *
//*                     Note: The name of the target method is the address of    *
//*                           that method.                                       *
//*                   This argument is referenced only if 'enable' is set.       *
//*                                                                              *
//* Returns: current state of capture flag                                       *
//*          'true'  break key will be captured by the application               *
//*          'false' break key will be handled by the terminal program           *
//********************************************************************************

bool TermSet::captureBreakSignal ( bool enable, CCH_Type handlerType, bool alert, 
                                   void* handlerAddr )
{
   //* If enabling local signal processing *
   if ( enable )
   {
      //* Initialize the variable indicating handler type *
      switch ( handlerType )
      {
         case cchtIgnore:
            this->cchAlert = alert ;
            // Note: Fall through...
         case cchtExit:
         case cchtUser:
         case cchtCopy:
            this->cchType = handlerType ;
            break ;
         default:
            this->cchType = cchtIgnore ;
            break ;
      } ;

      //* Register signal and default signal handler function.*
      if ( handlerAddr == NULL )
         signal( SIGINT, BreakSignal_Handler ) ;

      //* Register signal and use address of caller's handler function.*
      else
         signal( SIGINT, (__sighandler_t)handlerAddr ) ;
   }

   //* If returning signal processing to the terminal *
   else
   {
      signal( SIGINT, SIG_DFL ) ;   // invoke the terminal default signal handler
      this->cchType = cchtTerm ;    // reset to default handler type
   }

   return ( this->cchType == cchtTerm ? false : true ) ;

}  //* End captureBreakSignal() *

//**************************
//*  captureSuspendSignal  *
//**************************
//********************************************************************************
//* Enable or disable application-level capture of the "suspend" signal.         *
//* SUSPEND key (CTRL+Z). Signal name: SIGTSTP.                                  *
//*                                                                              *
//* If this signal is seen by the terminal program, it will suspend the active   *
//* process. That is, it will put the process into the background and disable    *
//* direct access to it. A different process may then occupy the foreground,     *
//* interacting with the input and output streams.                               *
//* Use the 'fg' utility to restore the hibernating process.                     *
//*                                                                              *
//* Because Ctrl+Z is often used as a command (undo) within an application, this *
//* signal may be captured and discarded.                                        *
//* The AnsiCmd library captures the Ctrl+Z signal and converts it to an ordinary*
//* keycode: wcsCTL_Z.                                                           *
//*                                                                              *
//*                                                                              *
//* Input  : enable : if 'true',  enable application capture of the suspend key  *
//*                   if 'false', disable application capture of the suspend key *
//*                               terminal handles the suspend signal            *
//*                                                                              *
//* Returns: current state of capture flag                                       *
//*          'true'  suspend key will be captured by the application             *
//*          'false' suspend key will be handled by the terminal program         *
//********************************************************************************

bool TermSet::captureSuspendSignal ( bool enable )
{
   //* Register signal and default signal handler function.*
   if ( enable != false )
   {
      signal( SIGTSTP, SuspendSignal_Handler ) ;
      this->suspCap = true ;        // signal captured
   }

   //* If returning signal processing to the terminal *
   else
   {
      signal( SIGTSTP, SIG_DFL ) ;  // invoke the terminal default signal handler
      this->suspCap = false ;
   }
   return this->suspCap ;

}  //* End captureSuspendSignal() *

//**************************
//*   captureQuitSignal    *
//**************************
//********************************************************************************
//* Enable or disable application-level capture and processing of the            *
//* QUIT key (CTRL+4  or  CTRL+\). Signal name: SIGQUIT.                         *
//*                                                                              *
//* If this signal is seen by the terminal program, it will immediately kill     *
//* the application and execute a core dump. We consider this to be entirely     *
//* too Wild-West and generally uncivilized. While there are valid reasons to    *
//* perform a core dump, they should not be generated by accident. For this      *
//* reason, the QUIT signal is captured and the application is shut down in an   *
//* orderly manner, releasing all dynamic memory allocations, deleting all       *
//* temporary files and performing general housekeeping duties before exiting    *
//* the application. These are the same steps taken when the Break signal        *
//* (Ctrl+C) is invoked to shut down the application.                            *
//*                                                                              *
//* Input  : enable : if 'true',  enable application capture of the quit key     *
//*                   if 'false', disable application capture of the quit key    *
//*                               terminal handles the signal                    *
//*                                                                              *
//* Returns: current state of capture flag                                       *
//*          'true'  quit key will be captured by the application                *
//*          'false' quit key will be handled by the terminal program            *
//********************************************************************************

bool TermSet::captureQuitSignal ( bool enable )
{
   //* Register signal and default signal handler function.*
   if ( enable != false )
   {
      signal( SIGQUIT, QuitSignal_Handler ) ;
      this->quitCap = true ;        // signal captured
   }

   //* If returning signal processing to the terminal *
   else
   {
      signal( SIGQUIT, SIG_DFL ) ;  // invoke the terminal default signal handler
      this->quitCap = false ;
   }
   return this->quitCap ;

}  //* End captureQuitSignal() *

//*************************
//*      FlushStdin       *
//*************************
//********************************************************************************
//* PRIVATE METHOD:                                                              *
//* Flush the terminal's input buffer (stdin).                                   *
//* This discards all data currently in the buffer.                              *
//* See SetTermInfo() for details of using the 'TCSAFLUSH' macro.                *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: 'true' if successful, else 'false'                                  *
//********************************************************************************

bool TermSet::FlushStdin ( void )
{
   bool status = false ;            // return value

   //* If original terminal settings have not *
   //* yet been captured, capture them now.   *
   if ( ! this->strCap )
   {
      TermIos tios ;
      this->getTermInfo ( stdIn, tios ) ;
   }

   //* Set terminal configuration using current settings. *
   //* As a side effect, the input buffer will be flushed,*
   //* (or so the documentation says...).                 *
   if ( (tcsetattr ( stdIn, TCSAFLUSH, 
        (this->strMod ? &this->strMods : &this->strOrig) )) )
   {
      status = true ;
   }
   return status ;

}  //* End FlushStdin() *

//*************************
//*         reset         *
//*************************
//********************************************************************************
//* PRIVATE METHOD:                                                              *
//* Initialize all data members to default values.                               *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void TermSet::reset ( void )
{
   this->strOrig.c_iflag = this->strOrig.c_oflag = 
   this->strOrig.c_cflag = this->strOrig.c_lflag = 0 ;
   this->strMods.c_iflag = this->strMods.c_oflag = 
   this->strMods.c_cflag = this->strMods.c_lflag = 0 ;
   this->aStr     = stdIn ;   // assume std input stream
   this->curStyle = csDflt ;  // cursor style == terminal default
   this->cchType  = cchtTerm ;// CTRL+C handler type (terminal handler)
   this->inpEch   = termEcho ;// type of echo (terminal echo)
   this->inpBuf   = true ;    // buffered input stream
   this->inpBlk   = true ;    // input blocking-read enabled
   this->curVis   = true ;    // cursor is initially visible
   this->suspCap  = false ;   // suspend signal not captured
   this->quitCap  = false ;   // quit signal not captured
   this->flowCap  = false ;   // flow control signals not captured
   this->isWin    = false ;   // assume that owner _is not_ ACWin class
   this->strCap   = false ;   // reset the stream-info-captured flag
   this->strMod   = false ;   // reset the stream-info-modified flag
   this->cchAlert = false ;   // no audible alert for CTRL+C capture

}  //* End reset() *

//*************************
//*                       *
//*************************
//********************************************************************************
//*                                                                              *
//*                                                                              *
//*                                                                              *
//* Input  :                                                                     *
//*                                                                              *
//* Returns:                                                                     *
//********************************************************************************

