//********************************************************************************
//* File       : WaylandCB.cpp                                                   *
//* Author     : Mahlon R. Smith                                                 *
//*              Copyright (c) 2019-2026 Mahlon R. Smith, The Software Samurai   *
//*                  GNU GPL copyright notice located below                      *
//* Date       : 24-Mar-2026                                                     *
//* Version    : (see WaylandCB_Version below)                                   *
//*                                                                              *
//* Description:                                                                 *
//* The WaylandCB class provides a simple interface between console              *
//* applications and the GNOME/Wayland compositor's two clipboards:              *
//* "Primary" and "Regular".                                                     *
//*                                                                              *
//* There are two modes of access: "indirect" and "direct".                      *
//* [At this time, WaylandCB does not implement the "direct" method.]            *
//* -- The "indirect" method relies upon the wl-clipboard utilities              *
//*    "wl-copy" and "wl-paste".                                                 *
//* -- The "direct" method uses the same method for accessing the clipboards     *
//*    that the wl-copy/wl-paste utilities use, but bypasses the utilities       *
//*    and interfaces directly with GNOME/Wayland (presumably through the        *
//*    GTK+ toolkit).                                                            *
//*                                                                              *
//* Only plain text data (UTF-8 or UTF-32) may be placed on the clipboard or     *
//* retrieved from the clipboard. Null-termination of the source data for the    *
//* copy operation is HIGHLY RECOMMENDED, but not enforced.                      *
//* Copy-and-paste of Windoze text (UTF-16, etc.) and binary data are not        *
//* supported.                                                                   *
//*                                                                              *
//*                                                                              *
//* Development Tools:                                                           *
//*   Fedora Linux 41, GNOME terminal 3.54.5 (GNOME 47)                          *
//*   Wintel (Lenovo) hardware.                                                  *
//*   GNU G++ (Gcc v:14.3.1 20250808)                                            *
//********************************************************************************
//* 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/>.              *
//*                                                                              *
//*         Full text of the GPL License may be found in the Texinfo             *
//*         documentation for this program under 'Copyright Notice'.             *
//********************************************************************************
//* Version History (most recent first):                                         *
//*                                                                              *
//* v: 0.0.06 01-Oct-2025                                                        *
//*   -- Convert file stat reporting from stat64 to statx structure and function.*
//*   -- Replace manual identification of the system's temporary-file directory  *
//*      via environment variables with the C++17 temp_directory_path() function.*
//*   -- In the wcbGet() method, test for the clipboard-empty message being      *
//*      returned by 'wl-paste', and if found, substitute an actual empty string.*
//*   -- In the wcbGet() method, a trailing newline character is no longer       *
//*      deleted. The data are returned as captured.                             *
//*                                                                              *
//* v: 0.0.05 22-Feb-2025                                                        *
//*   -- Update to take advantage of gString v:0.00.37 which incorporates        *
//*      automatic-expansion of storage capacity to accomodate larger blocks     *
//*      of text data.                                                           *
//*      -- Many parameters and return values have been updated from short int   *
//*         to int32_t to accomodate the larger range of text data.              *
//*      -- Code is simplified because it is no longer necessary to process      *
//*         the text in 1Kb segments.                                            *
//*   -- The wcbSet and wcbGet prototypes have been redefined, placing the buffer*
//*      length parameter before the clipboard designator, AND 'len' parameter   *
//*      is no longer optional. This was done to bullet-proof the call to prevent*
//*      (or at least reduce) buffer overruns in applications written by         *
//*      programmers did not RTFM.                                               *
//*         Old :                                                                *
//*          short wcbGet(uint8_t* trg, bool primary=false, short len=DFLT);     *
//*         New :                                                                *
//*          int wcbGet(uint8_t* trg, int len, bool primary=false );             *
//*         Old :                                                                *
//*          short wcbSet(const uint8_t* csrc, bool primary=false, short cnt=-1);*
//*         New :                                                                *
//*          short wcbSet(const uint8_t* csrc, int cnt=-1, bool primary=false);  *
//*      Note that the deprecated prototypes have been retained temporarily      *
//*      to allow time for updating any code based on the deprecated prototypes. *
//*      These overloads are simply pass-throughs to the updated methods.        *
//*   -- The 'wl-copy' option "--clear" ( '-c' ) is now functional, as noted in  *
//*      the previous release; however, if the clipboard is actually empty the   *
//*      'wl-paste' command returns the string: "Nothing is copied\n".           *
//*      In our view this is an error. To avoid the problem of an empty clipboard*
//*      returning a non-empty string, the wcbClear() method does not use the    *
//*      'wl-paste' '--clear' parameter by default, and instead writes an actual *
//*      empty string ( "" ) to the clipboard. Note that not all applications    *
//*      which invoke 'wl-copy' will do this, so WaylandCB tests for the default *
//*      message and if found, will immediately set the clipboard contents       *
//*      to an actual empty string.                                              *
//*   -- Add a method wcbChars(). Report the number of _characters_ currently    *
//*      on the clipboard. Previously the only report was number of UTF-8 bytes. *
//*   -- Documentation update.                                                   *
//*                                                                              *
//* v: 0.0.04 08-Jun-2024                                                        *
//*   -- Define maximum size of transfer buffer for communication with the       *
//*      wl-copy/wl-paste utilities as 16Kbytes (or 4Kwords).                    *
//*      This was theoretically already the case, but now it's formalized and    *
//*      verified.                                                               *
//*   -- Because the 'wl-copy' option "--clear" ( '-c' ) is now functional, the  *
//*      optional 'useCmd' parameter of the 'wcbClear' method now defaults to    *
//*      'true'. This parameter will be removed entirely in a subsequent release.*
//*   -- Completed implementation of conversions to and from UTF-32(wchar_t)     *
//*      data. These conversions are done locally, so as not to stress the       *
//*      clipboard's abilities. Someday we may begin to trust the Wayland        *
//*      compositor, and may then delegate these conversions, but that day has   *
//*      not yet arrived.                                                        *
//*   -- Documentation update.                                                   *
//*                                                                              *
//* v: 0.0.03 18-Apr-2020                                                        *
//*   -- Redefine the return value for the wcbTest() method.                     *
//*      Formerly, it returned 'true' indicating a successful connection,        *
//*      or 'false' if unable to communicate with the system clipboard.          *
//*      The return value is now a member of enum wcbStatus which provides a     *
//*      more detailed status.                                                   *
//*   -- Redefine the return value for the wcbClear() method.                    *
//*      Formerly, it returned (0) on success or (-1) if error.                  *
//*      The return value is now 'true' for success or 'false' if error.         *
//*   -- Cosmetic changes: Update some source code comments.                     *
//*   -- Update documentation.                                                   *
//*                                                                              *
//* v: 0.0.02 12-Jan-2020                                                        *
//*   -- All functionality verified.                                             *
//*   -- Addition of the delay before every access to the external utilities     *
//*      has significantly reduced the communications failure rate.              *
//*   -- Integrated into the NcDialogAPI test application "Dialogx".             *
//*   -- Demonstration program, "Wayclip" is now a pure console application.     *
//*   -- Both "Dialogx" and "Wayclip" exercise the full functionality of         *
//*      the WaylandCB class.                                                    *
//*   -- Update documentation.                                                   *
//*                                                                              *
//* v: 0.0.01 29-Dec-2019                                                        *
//*   -- First effort: Built as part of the experimental NcDialog utility        *
//*      "Wayclip" which is modelled on the "Dialogx" utility previously         *
//*      designed as a way to access the X-clipboard.                            *
//*      When this experimental application is stable, it will either be         *
//*      integrated into OR replace the "Dialogx" application.                   *
//*   -- Test command-line access to the Wayland clipboard using the             *
//*      wl-clipboard pair of utilities: "wl-copy" and "wl-paste".               *
//*      These are simple (possibly too simple) utilities that communicate       *
//*      between the terminal and the Wayland clipboards.                        *
//*      Note that these utilities may not be installed by default, even         *
//*      on a GNOME/Wayland desktop. If invoked on a system where they are       *
//*      not installed, yum/dnf offers to install them. (/usr/bin)               *
//*   -- Temp-file manipulation is taken from our FileMangler application and    *
//*      simplified for this application.                                        *
//*   -- Escaping of "special" characters in clipboard data is borrowed from     *
//*      the FileMangler algorithm used to escape characters in filespecs.       *
//*      This escaping is necessary for passing commands to the shell program.   *
//*      (A much-simplified version of escaping is currently used.)              *
//*   -- "UTF8_STRING" is an outgrowth of X11 support for UTF-8 encoding an is   *
//*      an "official" X11 "atom" (token). It seems that the Wayland             *
//*      developers have embraced it as a proper representation for UTF-8        *
//*      encoding. The wl-clipboard sees subtle differences between              *
//*      "text/plain;charset=UTF-8" and its shortcut "UTF8_STRING".              *
//*      For our purposes, "text/plain;charset=UTF-8" is preferred and either    *
//*      it or its shortcut "UTF8_STRING" are used exclusively within this       *
//*      class when specifying the MIME type for an operation.                   *
//*                                                                              *
//*                                                                              *
//*                                                                              *
//********************************************************************************
//* NOTES:                                                                       *
//* ------                                                                       *
//* The wl-clipboard utilities have undergone significant improvements since we  *
//* first released WaylandCB. Well done, Sergey!!                                *
//*                                                                              *
//* The wl-clipboard documentation is simplistic, but serviceable. It is a       *
//* "man page", not even a Texinfo document (but we can't fault Sergey for not   *
//* being a documentation zombie like your humble servant Software Sam).         *
//* Recent versions of these utilities have added both '--version' and '--help'  *
//* options. Please see the Texinfo documentation for the WaylandCB class and    *
//* the 'wl-clipboard' man page for additional information.                      *
//*                                                                              *
//* Description                                                                  *
//* - - - - - -                                                                  *
//*    wl-copy copies the given text to the Wayland clipboard.                   *
//*    If no text is given, wl-copy copies data from its standard input.         *
//*                                                                              *
//*    wl-paste pastes data from the Wayland clipboard to its standard output.   *
//*                                                                              *
//*    Although  wl-copy  and  wl-paste are particularly optimized for plain     *
//*    text and other textual content formats, they fully support content of     *
//*    arbitrary MIME types. wl-copy automatically infers the type of the        *
//*    copied content by running xdg-mime(1) on it. wl-paste tries its best to   *
//*    pick a type to paste based on the list of offered MIME types and the      *
//*    extension of the file it's pasting into. If you're not satisfied with     *
//*    the type they pick or don't want to rely on this implicit type            *
//*    inference, you can explicitly specify the type to use with the            *
//*    --type option.                                                            *
//*                                                                              *
//* wl-clipboard command line                                                    *
//* - - - - - - - - - - - - -                                                    *
//* $ wl-copy --version                                                          *
//*   wl-clipboard 2.2.1                                                         *
//*   Copyright (C) 2018-2023 Sergey Bugaev                                      *
//*   License GPLv3+: GNU GPL version 3 or later                                 *
//*     <https://gnu.org/licenses/gpl.html>.                                     *
//*   This is free software: you are free to change and redistribute it.         *
//*   There is NO WARRANTY, to the extent permitted by law.                      *
//*                                                                              *
//* $ wl-copy --help                                                             *
//*   Usage:                                                                     *
//*    wl-copy [options] text to copy                                            *
//*    wl-copy [options] < file-to-copy                                          *
//*   Copy content to the Wayland clipboard.                                     *
//*   Options:                                                                   *
//*    -o, --paste-once      Only serve one paste request and then exit.         *
//*    -f, --foreground      Stay in the foreground instead of forking.          *
//*    -c, --clear           Instead of copying, clear the clipboard.            *
//*    -p, --primary         Use the "primary" clipboard.                        *
//*    -n, --trim-newline    Do not copy the trailing newline character.         *
//*    -t, --type mime/type  Override the inferred MIME type for the content.    *
//*    -s, --seat seat-name  Pick the seat to work with.                         *
//*    -v, --version         Display version info.                               *
//*    -h, --help            Display this message.                               *
//*  Mandatory arguments to long options are mandatory for short options too.    *
//*  See wl-clipboard(1) for more details.                                       *
//*                                                                              *
//* $ wl-paste --help                                                            *
//*   Usage:                                                                     *
//*   wl-paste [options]                                                         *
//*   Paste content from the Wayland clipboard.                                  *
//*   Options:                                                                   *
//*     -n, --no-newline      Do not append a newline character.                 *
//*     -l, --list-types      Instead of pasting, list the offered types.        *
//*     -p, --primary         Use the "primary" clipboard.                       *
//*     -w, --watch command   Run a command each time the selection changes.     *
//*     -t, --type mime/type  Override the inferred MIME type for the content.   *
//*     -s, --seat seat-name  Pick the seat to work with.                        *
//*     -v, --version         Display version info.                              *
//*     -h, --help            Display this message.                              *
//*  Mandatory arguments to long options are mandatory for short options too.    *
//*  See wl-clipboard(1) for more details.                                       *
//*                                                                              *
//* Technical Note                                                               *
//* - - - - - - - -                                                              *
//* Note also that these utilities are unbearably slow. If commands are sent     *
//* in quick succession, it can easily generate a system exception, either       *
//* by the utilities themselves OR by the shell program.                         *
//*          Example:  free(): double free detected in tcache 2                  *
//*                    malloc(): corrupted top size                              *
//*                                                                              *
//* For these reasons, we have implemented a small pause before executing        *
//* a call to either 'wl-copy' or 'wl-paste' (see Systemcall() method).          *
//* This may not fully compensate for the weaknesses in these utilities, but     *
//* it does seem to reduce the number of errors.                                 *
//*                                                                              *
//********************************************************************************

//************
//* Includes *
//************
#include "WaylandCB.hpp"

//*********************
//* Local Definitions *
//*********************
#define ZERO (0)
#define NULLCHAR ('\0')       // NULL terminator character
#define TILDE ('~')           // Highest ASCII character code
#define SPACE (' ')           // ASCII space character
#define DSPACE (0x3000)       // CJK 2-column space character
#define TAB ('\t')            // Horizontal TAB character
#define VTAB ('\v')           // Vertical TAB character
#define NEWLINE ('\n')        // Newline character
#define CR ('\r')             // Carriage-return character
#define FF ('\f')             // Form-feed character

typedef struct statx FileStatx ;    // C++ syntax for statx

//*****************
//* Constant Data *
//*****************
static const char* const WaylandCB_Version = "0.0.06" ;   // class version

//* Except for development/debugging, this is the only      *
//* MIME-type/encoding used within this class, and except   *
//* for legacy data (and Windoze) this is the only          *
//* acceptable format for plain-text data.                  *
static const char* const dfltMIME = "text/plain;charset=UTF-8" ;
static const short dfltMIME_LEN = 24 ;
//* This is an X11 shortcut for "text/plain;charset=UTF-8". *
static const char* const dfltX11 = "UTF8_STRING" ;
static const short dfltX11_LEN = 11 ; // length of the string (not including nullchar)
static const short PADCNT = 32 ;    // padding added to conversion buffer for escaped chars
static const short BYTES_PER_CODEPOINT = 4 ;  // four bytes in a 32-bit word
static const char* emptyMessage = "Nothing is copied\n" ; // (see note above)
//* Define the bitfields for statx() call *
static int wsx_flags = (AT_NO_AUTOMOUNT | AT_STATX_SYNC_AS_STAT) ;
static unsigned int wsx_mask = (STATX_BASIC_STATS | STATX_BTIME) ;

//*************************
//* Non-member Prototypes *
//*************************


//*************************
//*      ~WaylandCB       *
//*************************
//********************************************************************************
//* Destructor:                                                                  *
//*  1) Delete all temp files.                                                   *
//*  2) Return all resources to the system                                       *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

WaylandCB::~WaylandCB ( void )
{
   this->DeleteTempname ( this->tfspec ) ;

}  //* End ~WaylandCB() *

//*************************
//*       WaylandCB       *
//*************************
//********************************************************************************
//* Default Constructor:                                                         *
//*  1) Initialize all class data members.                                       *
//*  2) Locate the system's temp-file directory.                                 *
//*  3) Create a temporary file for communicating with the clipboard interface.  *
//*  4) Determine whether "wl-copy" and "wl-paste" utilities are installed.      *
//*  5) Test access to the Wayland clipboard.                                    *
//*                                                                              *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: constructors implicitly return a pointer to the object              *
//********************************************************************************

WaylandCB::WaylandCB ( void )
{

   this->reset () ;                 // initialize all data members

   #if DEBUG_WCB != 0
   //* Create a debugging log.*
   this->dbofs.open( "./wcblog.txt", ofstream::out | ofstream::trunc ) ;
   if ( this->dbofs.is_open() )
      this->dbofs << "WaylandCB Debug Log\n===================" << endl ;
   this->gsdb.reAlloc( gsALLOCMAX ) ; // expand debug buffer to maximum
   #endif   // DEBUG_WCB

   //* Execute the startup sequence.*
   this->StartupSequence () ;

}  //* End WaylandCB() *

//*************************
//*        wcbGet         *
//*************************
//********************************************************************************
//* Public Method                                                                *
//* -------------                                                                *
//* Get a copy of the Wayland clipboard data.                                    *
//* Data returned will be NULL terminated, no trailing newline.                  *
//*                                                                              *
//* Input  : trg     : pointer to buffer to receive data                         *
//*                    wchar_t*  or  uint8_t* (aka char*)  or  gString&          *
//*          len     : specify length of target buffer                           *
//*                    -- The minimum buffer length is five(5) bytes. This is    *
//*                       space for one Unicode codepoint plus null terminator.  *
//*                       However, a target buffer size of at least 1Kbyte is    *
//*                       recommended. This value is defined in gString.hpp as   *
//*                       gsALLOCDFLT.                                           *
//*                    -- The maximum supported data size is MAX_CB_UTF8 UTF-8   *
//*                       bytes.This is currently defined as 16Kbytes.           *
//*                       The maximum buffer length for UTF-32 (wchar_t, 32-bit) *
//*                       data is: MAX_CB_UTF32  which is currently defined as   *
//*                       4K characters.                                         *
//*                    -- For gString targets, the 'len' parameter is optional.  *
//*                       The gString object will automatically expand to        *
//*                       accomodate the received data (either UTF-8 or UTF-32)  *
//*                       up to the data-block limit.                            *
//*          primary : (optional, 'false' by default)                            *
//*                    'false', read from "regular" (ordinary, main) clipboard   *
//*                    'true', read from "primary" (highlighted-data) clipboard  *
//*                                                                              *
//* Returns: for wchar_t target, number of UTF-32 characters read (incl.NULLCHAR)*
//*          for other target types, the number of UTF-8 bytes read              *
//*           (incl. NULLCHAR) is returned.                                      *
//*          Error Codes:                                                        *
//*            Returns wcbsNOCONNECT if clipboard connection not established.    *
//*            Returns wcbsNOINSTALL if 'wl-copy' utility not installed.         *
//********************************************************************************
//* Notes:                                                                       *
//* 1) If 'wl-paste' is not installed, there will be a message simiar to:        *
//*          "sh: wl-paste: command not found"                                   *
//*    which will be captured in the temp file.                                  *
//* 2) If 'wl-paste' returns a string indicating that the system clipboard       *
//*    is empty, then return an actual empty string, NOT the clipboard-empty     *
//*    message.                                                                  *
//*                                                                              *
//********************************************************************************

int WaylandCB::wcbGet ( uint8_t* trg, int len, bool primary )
{
   //* Template for calling the external 'wl-copy' utility.*
   const char* const getTemplate = "wl-paste %s -nt \"%s\" 1>\"%s\" 2>&1" ;
   int byteCount = ZERO ;     // return value

   *trg = NULLCHAR ;          // initialize caller's buffer

   //* If not connected to the Wayland clipboard *
   //* interface, return an error condition.     *
   if ( ! this->connected )
      return ( wcbsNOCONNECT ) ;

   //* Discard contents (if any) of the target temp file.*
   ofstream ofs( this->tfspec, ofstream::out | ofstream::trunc ) ;
   if ( ofs.is_open() )
      ofs.close() ;

   //* Range-check buffer size. Note that if caller lies to us *
   //* about the size of target buffer, a crash is possible.   *
   if ( len < 5 )    { len = 5 ; }        // (one CHARACTER plus nullchar)
   else if ( len > MAX_CB_UTF8 )          // max target buffer size
      len = MAX_CB_UTF8 ;

   //* Create the invocation command *
   gString cmdBuff( gsALLOCMED ) ;
   cmdBuff.compose( getTemplate, (char*)(primary ? "-p" : " "), dfltX11, this->tfspec ) ;

   #if DEBUG_WCB != 0
   if ( this->dbofs.is_open() )
   {
      int ubytes = cmdBuff.utfbytes() ;
      this->gsdb.compose( "-- wcbGet( cmdbytes:%hd )\n   %s", &ubytes, cmdBuff.ustr() ) ;
      this->dbofs << this->gsdb << endl ;
   }
   #endif   // DEBUG_WCB

   //* Capture the clipboard to our temp file. *
   this->Systemcall ( cmdBuff.ustr() ) ;

   //* Retrieve the results from the temp file.*
   ifstream ifs( this->tfspec, ifstream::in ) ; // open the file
   if ( ifs.is_open() )                         // if file opened
   {
      char inByte ;                             // input buffer
      while ( byteCount < (len - 1) )           // loop
      {
         ifs.read( &inByte, 1 ) ;               // read a byte
         if ( ifs.good() || (ifs.gcount() == 1) ) // if read successful
            trg[byteCount++] = inByte ;         // save the byte
         else                                   // end-of-file
            break ;
      }
      //* Ensure that data are null terminated *
      if ( (byteCount == ZERO) || (trg[byteCount - 1] != NULLCHAR) )
         trg[byteCount++] = NULLCHAR ;
      ifs.close() ;                             // close the file

      //* Test for the 'clipboard-empty' message, and *
      //* if found, substitute an actual empty string.*
      gString gs( trg ) ;
      if ( (gs.compare( emptyMessage, true )) == ZERO )
      { trg[ZERO] = NULLCHAR ; byteCount = 1 ; }

      //* Test for a "wl-paste not found" message.*
      else if ( ((gs.find( L"wl-paste" )) >= ZERO ) &&
                ((gs.find( L"command not found" )) >= ZERO) )
      {
         // Programmer's Note: This should never happen because on startup,
         // 'wl-copy' is called first, and if it fails, this method should 
         // never be called. This assumes that if 'wl-copy' is installed 
         // then 'wl-paste' will also be installed, but note that our general 
         // philosophy is that "assumptions are for bumpkins".
         trg[ZERO] = NULLCHAR ;
         byteCount = wcbsNOINSTALL ;
      }
   }

   #if DEBUG_WCB != 0
   if ( this->dbofs.is_open() )
   {
      this->gsdb.compose( "   \"%s\"\n   ----------byteCount:%d----------\n", trg, &byteCount ) ;
      // Programmer's Note: When testing very large data blocks, write only the head 
      // and tail of the data to the debugging file; otherwise it's too difficult to read.
      if ( byteCount >= gsALLOCDFLT )
      {
         const wchar_t* ELIPS = L". . ." ;
         int indx  = this->gsdb.after( L"\"Lorem ipsum " ),
             oindx ;
         if ( indx > ZERO )
         {
            this->gsdb.insert( ELIPS, indx ) ;
            indx += 5 ;
            oindx = this->gsdb.findlast( L'"' ) ;
            oindx -= 28 ;
            this->gsdb.erase( indx, (oindx - indx) ) ;
         }
      }
      this->dbofs << this->gsdb.ustr() << endl ;
   }
   #endif   // DEBUG_WCB

   return byteCount ;

}  //* End wcbGet() *

int WaylandCB::wcbGet ( char* trg, int len, bool primary )
{

   return ( (this->wcbGet ( (uint8_t*)trg, len, primary )) ) ;

}  //* End wcbGet() *

int WaylandCB::wcbGet ( wchar_t* trg, int len, bool primary )
{
   uint8_t* ubuff = new uint8_t[MAX_CB_UTF8 + PADCNT] ;
   int charCount = wcbsNOCONNECT ;        // return value
   *trg = NULLCHAR ;                      // initialize caller's buffer

   #if DEBUG_WCB != 0
   if ( this->dbofs.is_open() )
   {
      this->gsdb.compose( "-- wcbGet(wchar_t len:%d primary:%hhd)", &len, &primary ) ;
      this->dbofs << this->gsdb << endl ;
   }
   #endif   // DEBUG_WCB

   //* Convert 'len' from character count to byte count. *
   if ( len < ZERO )                { len = MAX_CB_UTF8 ; }
   else if ( len > MAX_CB_UTF32 )   { len = MAX_CB_UTF8 ; }
   else                             { len *= 4 ;          }

   //* Retrieve the UTF-8 data from the clipboard.*
   int byteCount = this->wcbGet ( ubuff, len, primary ) ;

   if ( byteCount > ZERO )
   {
      //* Convert from UTF-8 to UTF-32 and copy to caller's buffer. *
      gString gs( ubuff ) ;
      charCount = gs.copy( trg, len ) ;

      #if DEBUG_WCB != 0
      if ( this->dbofs.is_open() )
      {
         int recChars = gs.gschars(), recBytes = gs.utfbytes() ;
         this->gsdb.compose( "   -->(recBytes:%d recChars:%d charCount:%d)", 
                             &recBytes, &recChars, &charCount ) ;
         this->dbofs << this->gsdb << endl ;
      }
      #endif   // DEBUG_WCB
   }
   else                                   // else return the error code
      charCount = byteCount ;

   delete [] ubuff ;                      // release the dynamic allocation

   return charCount ;

}  //* End wcbGet() *

int WaylandCB::wcbGet ( gString& trg, int len, bool primary )
{
   uint8_t* ctrg = new uint8_t[MAX_CB_UTF8 + PADCNT] ; // allocate work buffer
   trg.clear() ;                          // initialize caller's buffer

   if ( len < 5 ) { len = 5 ; }           // (one CHARACTER plus nullchar)
   else           { len *= 4 ; }          // convert UTF-32 characters to UTF-8 bytes

   //* Get UTF-8 clipboard data *
   int byteCount = this->wcbGet ( ctrg, len, primary ) ;

   //* If valid data captured, copy it to caller's buffer.*
   if ( byteCount >= ZERO )
      trg = ctrg ;
   delete [] ctrg ;                       // release the dynamic allocation
   return byteCount ;

}  //* End wcbGet() *

#if DEPRECATED_OVERLOADS != 0
//**************************************************
//* See note in header file about these overloads. *
//**************************************************
short WaylandCB::wcbGet ( uint8_t* trg, bool primary, short len )
{
   return ( (this->wcbGet ( trg, len, primary )) ) ;
}  //* End wcbGet() *

short WaylandCB::wcbGet ( char* trg, bool primary, short len )
{
   return ( (this->wcbGet ( (uint8_t*)trg, len, primary )) ) ;
}  //* End wcbGet() *

short WaylandCB::wcbGet ( wchar_t* trg, bool primary, short len )
{
   return ( (this->wcbGet ( trg, len, primary )) ) ;
}  //* End wcbGet() *

short WaylandCB::wcbGet ( gString& trg, bool primary, short len )
{
   return ( (this->wcbGet ( trg, len, primary )) ) ;
}  //* End wcbGet() *
#endif   // DEPRECATED_OVERLOADS

//*************************
//*        wcbSet         *
//*************************
//********************************************************************************
//* Public Method                                                                *
//* -------------                                                                *
//* Set contents of the Wayland clipboard.                                       *
//* Source data terminated at null terminator or if 'cnt' specified, then        *
//* specified number of items (PLUS null terminator if not included in count).   *
//*                                                                              *
//* Please Note: UTF-8 data are unsigned-char data, NOT (signed) char data;      *
//* however, data referenced by a 'char*' are recognized.                        *
//*                                                                              *
//* Note also that if the source data ('csrc') are wchar_t (UTF-32) characters,  *
//* then the data are converted to UTF-8 for transmission to the system          *
//* clipboard. Communications between the WaylandCB class and the wl-clipboard   *
//* utilities uses UTF-8 character encoding exclusively.                         *
//*                                                                              *
//* Input  : csrc    : pointer to buffer containing source data                  *
//*                    wchar_t*  or  uint_t* (aka char*)  or  gString&           *
//*          cnt     : (optional, -1 by default)                                 *
//*                    number of characters or bytes to write                    *
//*                    -- If default (-1) specified, write all source data to    *
//*                       and including the null terminator (up to defined max). *
//*                    -- If 'cnt' is reached before null terminator is          *
//*                       reached, a null terminator is appended to the output   *
//*                       and any remaining source data are ignored.             *
//*                    -- If 'cnt' greater than defined maximum buffer size      *
//*                       (MAX_CB_UTF8 or MAX_CB_UTF32), then 'cnt' is set to    *
//*                       maximum and remaining source data will be ignored.     *
//*                    -- If 'cnt' == ZERO, then instead of processing 'csrc'    *
//*                       data, the wcbClear() method is called instead.         *
//*          primary : (optional, 'false' by default)                            *
//*                    'false', write to "regular" (ordinary, main) clipboard    *
//*                    'true', write to "primary" (highlighted-data) clipboard   *
//*                                                                              *
//* Returns: for char source or gString source, number of UTF-8 bytes written    *
//*            (incl. NULLCHAR)                                                  *
//*          for wchar_t source, number of UTF-32 characters written             *
//*            (incl.NULLCHAR)                                                   *
//*          returns wcbsNOCONNECT if error i.e. not connected to clipboard      *
//*          returns wcbsNOINSTALL if 'wl-copy' utility not installed            *
//********************************************************************************
//* Notes:                                                                       *
//* 1) If 'wl-copy' is not installed, there will be a message simiar to:         *
//*         "sh: wl-copy: command not found"                                     *
//*                                                                              *
//* 2) Note that double-quotes (and potentially other 'special' characters)      *
//*    in the source data must be escaped to avoid shell parsing errors.         *
//*                                                                              *
//* 3) For wchar_t source the conversion between characters and bytes may be     *
//*    slightly inaccurate if truncation of source occurs.                       *
//*    See note in overload for wchar_t source data.                             *
//*                                                                              *
//********************************************************************************

int WaylandCB::wcbSet ( const uint8_t* csrc, int cnt, bool primary )
{
   //* Template for calling the external 'wl-copy' utility.*
   const char* const setTemplate = "wl-copy %s -t '%s' \"%s\" 1>\"%s\" 2>&1" ;
   //* "Special" characters" which must be escaped to prevent *
   //* the shell from misinterpreting them as commands.       *
   const char DQ = '"',
              DS = '$',
              BS = '\\',
              BT = '`' ;

   //* If not connected to the Wayland clipboard *
   //* interface, return an error condition.     *
   if ( ! this->connected )
      return ( wcbsNOCONNECT ) ;

   //* If 'cnt' == ZERO it is assumed that  *
   //* caller wants to clear the clipboard. *
   //* (Note: this is a recursive call.)    *
   if ( cnt == ZERO )
      return ( this->wcbClear ( primary ) ) ;

   //* Discard contents (if any) of the target temp file.*
   ofstream ofs( this->tfspec, ofstream::out | ofstream::trunc ) ;
   if ( ofs.is_open() )
      ofs.close() ;

   #if DEBUG_WCB != 0
   int rawSrcBytes ;
   for ( rawSrcBytes = ZERO ; csrc[rawSrcBytes] != NULLCHAR ; ++rawSrcBytes ) ;
   ++rawSrcBytes ;   // include the null terminator
   #endif   // DEBUG_WCB

   //* If "store all" specified or if sepecified *
   //* value is out of range, set to max.        *
   if ( (cnt < ZERO) || (cnt > MAX_CB_UTF8) )
      cnt = MAX_CB_UTF8 ;

   //* Construct our local source buffer *
   uint8_t* src = new uint8_t[MAX_CB_UTF8 + PADCNT] ;

   //* Determine the actual byte count of the source data *
   //* _including_ any escaped characters and copy the    *
   //* normalized source to the local buffer.             *
   int byteCount = ZERO,      // return value
       specChars = ZERO ;     // number of escaped "special characters" found in source
   for ( byteCount = ZERO ; byteCount < cnt ; ++byteCount )
   {
      //* The backslash character ('\') IS the escape  *
      //* character, so special processing is required.*
      if ( csrc[byteCount] == BS )
      {
         //* Test whether an already-escaped character follows.*
         if ( (csrc[byteCount + 1] == DQ) || (csrc[byteCount + 1] == DS) ||
              (csrc[byteCount + 1] == BS) || (csrc[byteCount + 1] == BT) )
            ;  // do nothing
         else
            src[byteCount + specChars++] = BS ;
      }
      //* Test for other "special" characters.*
      else if ( (csrc[byteCount] == DQ) || (csrc[byteCount] == DS) ||
                (csrc[byteCount] == BT) )
      {
         src[byteCount + specChars++] = BS ;
      }

      //* Copy source byte to temp buffer.*
      src[byteCount + specChars] = csrc[byteCount] ;

      //* If end-of-data reached, count the  *
      //* null terminator and end the loop.  *
      if ( src[byteCount + specChars] == NULLCHAR )
      {
         ++byteCount ;
         break ;
      }

      //* Else if buffer is full before null terminator  *
      //* found. terminate the string and end the loop.  *
      else if ( byteCount == (cnt - 1) ) // null terminator not found
      {
         src[byteCount + specChars] = NULLCHAR ;
         ++byteCount ;
         break ;
      }
   }

   //* Build the shell command *
   gString cmdBuff( gsALLOCMAX ) ;
   cmdBuff.compose( setTemplate, (char*)(primary ? "-p" : " "), 
                    dfltMIME, src, this->tfspec ) ;

   #if DEBUG_WCB != 0
   if ( this->dbofs.is_open() )
   {
      int cmdbytes = cmdBuff.utfbytes() ;
      this->gsdb.compose( 
            "-- wcbSet( rawSrcBytes:%d cmdbytes:%d )\n   %s\n   ----------byteCount:%d----------\n", 
            &rawSrcBytes, &cmdbytes, cmdBuff.ustr(), &byteCount ) ;
      // Programmer's Note: When testing for data clipping at the text-block 
      // maximum write only the head and tail of the data to the debugging file; 
      // otherwise it's too difficult to read.
      if ( cmdbytes >= gsALLOCDFLT )
      {
         const wchar_t* ELIPS = L". . ." ;
         int indx  = this->gsdb.after( L"\"Lorem ipsum " ),
             oindx ;
         this->gsdb.insert( ELIPS, indx ) ;
         indx += 5 ;
         oindx = this->gsdb.findlast( L'"' ) ;
         oindx -= 42 ;
         this->gsdb.erase( indx, (oindx - indx) ) ;
      }
      this->dbofs << this->gsdb << endl ;
   }
   #endif   // DEBUG_WCB

   this->Systemcall ( cmdBuff.ustr() ) ;

   //* A successful call will produce no output to stdin/stderr. *
   //* If output generated, we can be relatively certain that an *
   //* error has occurred, but just in case, we read the first   *
   //* line of the file and test for tell-tale output.           *
   ifstream ifs( this->tfspec, ifstream::in ) ; // open the file
   if ( ifs.is_open() )                         // if file opened
   {
      src[ZERO] = NULLCHAR ;
      ifs.getline( (char*)(src), MAX_CB_UTF8, NEWLINE ) ;
      if ( ifs.good() || ifs.gcount() > ZERO )
      {
         gString gs = src ;
         if ( ((gs.find( L"wl-copy" )) >= ZERO ) &&
              ((gs.find( L"command not found" )) >= ZERO) )
         {
            byteCount = wcbsNOINSTALL ;

            #if DEBUG_WCB != 0
            if ( this->dbofs.is_open() )
            {
               gs.erase( L'\n' ) ;
               this->gsdb.compose( "   wcbSet err: '%S'", gs.gstr() ) ;
               this->dbofs << this->gsdb.ustr() << endl ;
            }
            #endif   // DEBUG_WCB
         }
      }
      ifs.close() ;                       // close the file
   }

   if ( src != NULL )                     // release dynamic allocation
      delete [] src ;

   return byteCount ;

}  //* End wcbSet() *

int WaylandCB::wcbSet ( const char* csrc, int cnt, bool primary )
{
   return ( this->wcbSet ( (uint8_t*)csrc, cnt, primary ) ) ;
}  //* End wcbSet() *

//*******************************
//* UTF-32 overload of wcbSet() *
//*******************************
int WaylandCB::wcbSet ( const wchar_t* src, int cnt, bool primary )
{
   //* If not connected to the Wayland clipboard *
   //* interface, return an error condition.     *
   if ( ! this->connected )
      return ( wcbsNOCONNECT ) ;

   //* If 'cnt' == ZERO it is assumed that  *
   //* caller wants to clear the clipboard. *
   //* (Note: this is a recursive call.)    *
   if ( cnt == ZERO )
      return ( this->wcbClear ( primary ) ) ;

   gString gs( src ) ;                    // analyze the source data
   int srcChars  = gs.gschars(),          // raw source characters
       srcBytes  = gs.utfbytes(),         // raw source bytes
       //setBytes  = ZERO,                  // bytes actually written
       setChars  = srcChars ;             // return value
   if ( cnt < ZERO ) cnt = gsALLOCMED ;   // if default buffer size specified
   else if ( cnt > MAX_CB_UTF32 )         // if greater than max buffer size
      cnt = MAX_CB_UTF32 ;

   #if DEBUG_WCB != 0
   int rawSrcChars = ZERO ;
   bool overflow = false ;
   if ( this->dbofs.is_open() )
   {
      for ( rawSrcChars = ZERO ; src[rawSrcChars] != NULLCHAR ; ++rawSrcChars ) ;
      ++rawSrcChars ;   // include the null terminator
   }
   #endif   // DEBUG_WCB

   if ( srcChars > cnt )                  // if transmitting less than total data
   {
      gString gstmp( gs ) ;               // copy the source data
      gstmp.limitChars( cnt ) ;           // truncate the source data
      srcBytes = gstmp.utfbytes() ;       // adjust count of source bytes
      setChars = gstmp.gschars() ;        // EXPECTED character count
      #if DEBUG_WCB != 0
      overflow = true ;
      #endif   // DEBUG_WCB
      // Programmer's Note: Source data are longer than specified character count; 
      // therefore, truncation of the data will occur. If the actual number of 
      // characters written to clipboard is not the number calculated here, it 
      // will only be off by 1 or two, and is not worth the time to re-calculate.
   }

   #if DEBUG_WCB != 0
   if ( this->dbofs.is_open() )
   {
      this->gsdb.compose( "-- wcbSet(wchar_t rawSrcChars:%d setChars:%d srcBytes:%d overflow:%hhd)", 
                          &rawSrcChars, &setChars, &srcBytes, &overflow ) ;
      this->dbofs << this->gsdb << endl ;
   }
   #endif   // DEBUG_WCB

   //* Write the data to clipboard *
   this->wcbSet ( gs.ustr(), srcBytes, primary ) ;

   //* If returned byte count != expected byte count. *
   //* This is unlikely, and not worth re-calculating.*
   //if ( setBytes != srcBytes ) {}

   #if DEBUG_WCB != 0
   if ( this->dbofs.is_open() )
   {
      this->gsdb.compose( "   ----------charCount:%d----------\n", &setChars ) ;
      this->dbofs << this->gsdb << endl ;
   }
   #endif   // DEBUG_WCB

   return ( setChars ) ;

}  //* End wcbSet() *

int WaylandCB::wcbSet ( const gString& src, int cnt, bool primary )
{
   return ( (this->wcbSet ( (uint8_t*)src.ustr(), cnt, primary )) ) ;
}  //* End wcbSet() *

#if DEPRECATED_OVERLOADS != 0
//**************************************************
//* See note in header file about these overloads. *
//**************************************************
short WaylandCB::wcbSet ( const uint8_t* csrc, bool primary, int cnt )
{
   return ( (this->wcbSet ( csrc, cnt, primary )) ) ;
}  //* End wcbSet() *

short WaylandCB::wcbSet ( const char* csrc, bool primary, int cnt )
{
   return ( this->wcbSet ( (uint8_t*)csrc, cnt, primary ) ) ;
}  //* End wcbSet() *

short WaylandCB::wcbSet ( const wchar_t* src, bool primary, int cnt )
{
   return ( (this->wcbSet ( src, cnt, primary )) ) ;
}  //* End wcbSet() *

short WaylandCB::wcbSet ( const gString& src, bool primary, int cnt )
{
   return ( (this->wcbSet ( (uint8_t*)src.ustr(), cnt, primary )) ) ;
}  //* End wcbSet() *
#endif   // DEPRECATED_OVERLOADS

#if 0    // Set and retrieve using wchar_t data. Not implemented.
//*************************
//*      wcbGet32bit      *
//*************************
//********************************************************************************
//********************************************************************************
//* Protected Method                                                             *
//* ----------------                                                             *
//* Get a copy of the Wayland clipboard data.                                    *
//* Data returned will be NULL terminated, no trailing newline.                  *
//*                                                                              *
//* Input  : trg     : pointer to buffer to receive data                         *
//*          primary : (optional, 'false' by default)                            *
//*                    'false', read from "regular" (ordinary, main) clipboard   *
//*                    'true', read from "primary" (highlighted-data) clipboard  *
//*          len     : (optional) specify length of target buffer in 32-bit words*
//*                      default == gsALLOCDFLT                                  *
//*                      maximum == MAX_CB_UTF32 words                           *
//*                                                                              *
//* Returns: number of UTF-32 characters read from clipboard (incl.NULLCHAR)     *
//*          returns wcbsNOCONNECT if error i.e. not connected to clipboard      *
//*          returns wcbsNOINSTALL if 'wl-copy' utility not installed            *
//********************************************************************************

short wcbGet32bit ( wchar_t* trg, bool primary, short len )
{
   //* Template for calling the external 'wl-copy' utility.*
   const char* const getTemplate = "wl-paste %s -nt \"%s\" 1>\"%s\" 2>&1" ;

   short charCount = wcbsNOCONNECT ;      // return value
   *trg = NULLCHAR ;                      // initalize caller's buffer

   //* If not connected to the Wayland clipboard *
   //* interface, return an error condition.     *
   if ( ! this->connected )
      return ( wcbsNOCONNECT ) ;

   //* Discard contents (if any) of the target temp file.*
   ofstream ofs( this->tfspec, ofstream::out | ofstream::trunc ) ;
   if ( ofs.is_open() )
      ofs.close() ;

   if ( len < ZERO ) len = gsALLOCDFLT ;  // if default buffer size specified
   else if ( len > MAX_CB_UTF32 )         // if greater than max buffer size
      len = MAX_CB_UTF32 ;

   //* Construct the command *
   gString cmdBuff( gsALLOCMED ) ;
   cmdBuff.compose( getTemplate, (char*)(primary ? "-p" : " "), dfltX11, this->tfspec ) ;

   return charCount ;

}  //* End wcbGet32bit() *

//*************************
//*      wcbSet32bit      *
//*************************
//********************************************************************************
//* Protected Method                                                             *
//* ----------------                                                             *
//* Set contents of the Wayland clipboard.                                       *
//* Source data terminated at null terminator or if 'cnt' specified, then        *
//* specified number of items (PLUS null terminator if not included in count).   *
//*                                                                              *
//* Input  : csrc    : pointer to buffer containing UTF-32 source data           *
//*          primary : (optional, 'false' by default)                            *
//*                    'false', write to "regular" (ordinary, main) clipboard    *
//*                    'true', write to "primary" (highlighted-data) clipboard   *
//*          cnt     : (optional, -1 by default) number of UTF-32 words to write *
//*                      default == gsALLOCDFLT                                  *
//*                      maximum == MAX_CB_UTF32 words                           *
//*                                                                              *
//* Returns: number of UTF-32 characters written to clipboard (incl.NULLCHAR)    *
//*          returns wcbsNOCONNECT if error i.e. not connected to clipboard      *
//*          returns wcbsNOINSTALL if 'wl-copy' utility not installed            *
//********************************************************************************

short WaylandCB::wcbSet32bit ( const wchar_t* src, bool primary, short cnt )
{
   short charCount = wcbsNOCONNECT ;      // return value

   if ( cnt < ZERO ) cnt = gsALLOCDFLT ;  // if default buffer size specified
   else if ( cnt > MAX_CB_UTF32 )         // if greater than max buffer size
      cnt = MAX_CB_UTF32 ;


   return charCount ;

}  //* End wcbSet32bit() *
#endif   // Set and retrieve using wchar_t data.

//*************************
//*       wcbClear        *
//*************************
//********************************************************************************
//* Public Method:                                                               *
//* --------------                                                               *
//* Clear the clipboard.                                                         *
//*                                                                              *
//* Important Note: As originally written, this method compensated for the fact  *
//* that the '--clear' and '-c' options for early versions of 'wl-copy' were     *
//* non-functional.                                                              *
//*                                                                              *
//* While the '--clear' option is now functional, unfortunately the 'wl-paste'   *
//* command will report the literal string "Nothing is copied\n" as an indicator  *
//* that the clipboard is empty. This is very poor programming logic because it  *
//* means that every time 'wl-paste' is called, the data must be compared with   *
//* the "Nothing is copied\n" string to determine whether the clipboard is       *
//* actually empty.                                                              *
//*                                                                              *
//* For this reason, the wcbClear() method does not use the wl-clipboard         *
//* '--clear' command by default. Instead the empty string ( "" ) is written to  *
//* system clipboard. The 'wl-paste' command will subsequently return the empty  *
//* string, which seems to be the logical way to indicate that the clipboard     *
//* is empty. To clear the clipboard using the '--clear' command, set the        *
//* 'useCmd' parameter to 'true'.                                                *
//*                                                                              *
//*                                                                              *
//* Input  : primary: (optional, 'false' by default)                             *
//*                   if 'false', clear the "Regular" clipboard                  *
//*                   if 'true',  clear the "Primary" clipboard                  *
//*          useCmd : (optional, 'false' by default)                             *
//*                   if 'false', clear clipboard by sending an empty string     *
//*                   if 'true',  clear the clipboard using the wl-copy          *
//*                               "--clear" argument (see note)                  *
//*                                                                              *
//* Returns: 'true' if successful                                                *
//*          'false' if not connected, or other error                            *
//********************************************************************************

bool WaylandCB::wcbClear ( bool primary, bool useCmd )
{
   bool status = false ;      // return value

   if ( this->connected )
   {
      if ( useCmd )
      {
         //* It is assumed that the command will fit into a gString object.*
         const char* const clrTemplate = 
                     "wl-copy %s --clear 1>\"%s\" 2>&1" ;
         gString gsCmd( clrTemplate, (primary ? "-p" : " "), this->tfspec ) ;

         //* Execute the command *
         this->Systemcall ( gsCmd.ustr() ) ;
      }
      else
      {
         this->wcbSet ( (uint8_t*)(""), -1, primary ) ;
      }

      //* Verify that target clipboard is clear.*
      //* Only the nullchar should be returned. *
      gString gsIn ;
      if ( ((this->wcbGet ( gsIn, gsALLOCDFLT, primary )) == 1) ||
           ((gsIn.compare( emptyMessage, true )) == ZERO) )
      { status = true ; }

      #if DEBUG_WCB != 0
      if ( this->dbofs.is_open() )
      {
         this->gsdb.compose( "   -- wcbClear(p:%hhd u:%hhd - %s)", 
                             &primary, &useCmd, (status != false ? "OK" : "ERROR") ) ;
         this->dbofs << this->gsdb.ustr() << endl ;
      }
      #endif   // DEBUG_WCB
   }
   return status ;

}  //* End wcbClear() *

//*************************
//*       wcbBytes        *
//*************************
//********************************************************************************
//* Public Method:                                                               *
//* --------------                                                               *
//* Report number of bytes of data stored on the specified clipboard.            *
//*                                                                              *
//* Input  : primary: (optional, 'false' by default)                             *
//*                   if 'false', report the "Regular" clipboard                 *
//*                   if 'true',  report the "Primary" clipboard                 *
//*                                                                              *
//* Returns: bytes of data on the clipboard incl. NULLCHAR                       *
//*          returns wcbsNOCONNECT if error i.e. not connected to clipboard      *
//*          returns wcbsNOINSTALL if 'wl-copy' utility not installed            *
//********************************************************************************
//* Note: If clipboard contains more data than our buffer will hold, the value   *
//*       returned will be an undercount.                                        *
//********************************************************************************

int WaylandCB::wcbBytes ( bool primary )
{
   uint8_t* buff = new uint8_t[MAX_CB_UTF8 + PADCNT] ;

   int cbBytes = this->wcbGet ( buff, MAX_CB_UTF8, primary ) ;
   delete [] buff ;

   return cbBytes ;

}  //* End wcbBytes() *

//*************************
//*       wcbBytes        *
//*************************
//********************************************************************************
//* Public Method:                                                               *
//* --------------                                                               *
//* Report number of characters of data stored on the specified clipboard.       *
//* Note that this cannot be assumed to be the same as the number of UTF-8 bytes *
//* reported by wcbBytes(), above.                                               *
//*                                                                              *
//* Input  : primary: (optional, 'false' by default)                             *
//*                   if 'false', report the "Regular" clipboard                 *
//*                   if 'true',  report the "Primary" clipboard                 *
//*                                                                              *
//* Returns: bytes of data on the clipboard incl. NULLCHAR                       *
//*          returns wcbsNOCONNECT if error i.e. not connected to clipboard      *
//*          returns wcbsNOINSTALL if 'wl-copy' utility not installed            *
//********************************************************************************
//* Note: If clipboard contains more data than our buffer will hold, the value   *
//*       returned will be an undercount.                                        *
//********************************************************************************

int WaylandCB::wcbChars ( bool primary )
{
   int cbChars ;                          // return value
   uint8_t* ubuff = new uint8_t[MAX_CB_UTF8 + PADCNT] ;
   this->wcbGet ( ubuff, MAX_CB_UTF8, primary ) ;

   //* Convert byte count to character count *
   gString gs( gsALLOCMAX ) ;
   gs = ubuff ;
   cbChars = gs.gschars() ;
   delete [] ubuff ;

   return ( cbChars ) ;

}  //* End wcbChars() *

//*************************
//*       wcbTypes        *
//*************************
//********************************************************************************
//* Public Method:                                                               *
//* --------------                                                               *
//* Report the available formats for retrieving data from the specified          *
//* clipboard.                                                                   *
//*                                                                              *
//* Input  : trg    : buffer to receive descriptive text for available formats   *
//*          cnt    : (optional, -1 by default) size of target buffer            *
//*                   for char* and gString targets, default: gsALLOCDFLT        *
//*                   for wchar_t* target default: gsALLOCMED                    *
//*                   NOTE: On average, approximately 200-300 bytes of (ASCII)   *
//*                         data will be returned, but be safe.                  *
//*          primary: (optional, 'false' by default)                             *
//*                   if 'false', report the "Regular" clipboard                 *
//*                   if 'true',  report the "Primary" clipboard                 *
//*                                                                              *
//* Returns: for wchar_t target, number of characters read (incl.NULLCHAR)       *
//*          for char target or gString target, number of bytes read             *
//*           (incl. NULLCHAR)                                                   *
//*          returns wcbsNOCONNECT if error i.e. not connected to clipboard      *
//*          returns wcbsNOINSTALL if 'wl-paste' utility not installed           *
//********************************************************************************

int WaylandCB::wcbTypes ( gString& trg, int cnt, bool primary )
{
   const char* const fmtTemplate = "wl-paste %s -nl 1>>\"%s\" 2>>\"%s\"" ;
   char buff[gsALLOCMED] ;    // input buffer
   int byteCount = ZERO ;     // return value

   trg.clear() ;             // initialize caller's buffer

   //* If not connected to the Wayland clipboard *
   //* interface, return an error condition.     *
   if ( ! this->connected )
      return ( wcbsNOCONNECT ) ;

   //* Discard contents (if any) of the target temp file.*
   ofstream ofs( this->tfspec, ofstream::out | ofstream::trunc ) ;
   if ( ofs.is_open() )
      ofs.close() ;

   gString cmdBuff( gsALLOCMED ) ;
   cmdBuff.compose( fmtTemplate, (char*)(primary ? "-p" : " "), 
                    this->tfspec, this->tfspec ) ;

   #if DEBUG_WCB != 0
   if ( this->dbofs.is_open() )
   {
      int cmdbytes = cmdBuff.utfbytes() ;
      this->gsdb.compose( "-- wcbTypes( cmdbytes:%hd )\n   %s", &cmdbytes, cmdBuff.ustr() ) ;
      this->dbofs << this->gsdb << endl ;
   }
   #endif   // DEBUG_WCB

   //* Capture the clipboard *
   this->Systemcall ( cmdBuff.ustr() ) ;

   //* Retrieve the results from the temp file.*
   ifstream ifs( this->tfspec, ifstream::in ) ; // open the file
   if ( ifs.is_open() )                         // if file opened
   {
      char inByte ;                             // input buffer
      while ( byteCount < (gsALLOCMED - 1) )    // loop
      {
         ifs.read( &inByte, 1 ) ;               // read a byte
         if ( ifs.good() || (ifs.gcount() == 1) ) // if read successful
            buff[byteCount++] = inByte ;        // save the byte
         else                                   // end-of-file
            break ;
      }
      //* Discard any trailing newline and terminate the string.*
      if ( (byteCount > ZERO) && (buff[byteCount - 1] == NEWLINE) )
         --byteCount ;
      if ( (byteCount == ZERO) || (buff[byteCount] != NULLCHAR) )
         buff[byteCount++] = NULLCHAR ;
      ifs.close() ;                             // close the file
   }

   #if DEBUG_WCB != 0
   if ( this->dbofs.is_open() )
   {
      this->gsdb.compose( "   '%s'\n   byteCount:%hd\n", buff, &byteCount ) ;
      this->dbofs << this->gsdb.ustr() << endl ;
   }
   #endif   // DEBUG_WCB

   trg = buff ;

   //* Test for a "wl-paste not found" message.*
   if ( ((trg.find( L"wl-paste" )) >= ZERO ) &&
        ((trg.find( L"command not found" )) >= ZERO) )
   {
      trg.clear() ;
      byteCount = wcbsNOINSTALL ;
   }

   return ( byteCount ) ;

}  //* End wcbTypes() *

int WaylandCB::wcbTypes ( char* trg, int cnt, bool primary )
{
   gString gs ;
   this->wcbTypes ( gs, cnt, primary ) ;
   if ( cnt < ZERO )
      cnt = gsALLOCMED ;
   gs.copy( trg, cnt ) ;
   gs = trg ;        // count the bytes
   int utfbytes = gs.utfbytes() ;

   return utfbytes ;

}  //* End wcbTypes() *

int WaylandCB::wcbTypes ( wchar_t* trg, int cnt, bool primary )
{
   gString gs ;
   this->wcbTypes ( gs, cnt, primary ) ;
   if ( cnt < ZERO )
      cnt = gsALLOCMED ;
   gs.copy( trg, cnt ) ;
   gs = trg ;        // count the characters
   int wchars = gs.gschars() ;

   return wchars ;

}  //* End wcbTypes() *

//*************************
//*    wcbIsConnected     *
//*************************
//********************************************************************************
//* Public Method:                                                               *
//* --------------                                                               *
//* Report whether connection to the Wayland clipboard has been established.     *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: 'true' if connected to clipboard, else 'false'                      *
//********************************************************************************

bool WaylandCB::wcbIsConnected ( void )
{

   return ( this->connected ) ;

}  //* End wcbIsConnected() *

//*************************
//*        wcbTest        *
//*************************
//********************************************************************************
//* Public Method                                                                *
//* -------------                                                                *
//* Test the connection with the Wayland clipboard.                              *
//* Send a short (< 1000 bytes) message to the clipboard, then retrieve the      *
//* data and compare the results.                                                *
//*                                                                              *
//* Input  : testdata : a short null-terminated string to use as test data       *
//*                                                                              *
//* Returns: Member of enum wcbStatus:                                           *
//********************************************************************************

wcbStatus WaylandCB::wcbTest ( const char* testdata )
{
   gString gs ;
   wcbStatus status = wcbsNOCONNECT ;

   //* If currently connected, test the connection.*
   if ( this->connected )
   {
      if ( (this->wcbSet ( testdata, -1 )) >= ZERO )
      {
         if ( (this->wcbGet ( gs, gsALLOCDFLT )) >= ZERO )
         {
            if ( (gs.compare( testdata )) == ZERO )
               status = wcbsACTIVE ;
         }
      }
   }

   //* Else, not connected, diagnose the connection.*
   else
   {
      int s = this->wcbTypes ( gs ) ; //(should never return zero)
      if ( s >= ZERO )
         status = wcbsACTIVE ;
      else if ( s == wcbsNOCONNECT )
         status = wcbsNOCONNECT ;
      else if ( s <= wcbsNOINSTALL )
         status = wcbsNOINSTALL ;
   }
   return status ;

}  //* End wcbTest() *

//*************************
//*       wcbReinit       *
//*************************
//********************************************************************************
//* Public Method                                                                *
//* -------------                                                                *
//* Terminate the current connection (if any) with the Wayland clipboard,        *
//* release all local resources, reinitialize local data members, and            *
//* re-establish the connection.                                                 *
//*                                                                              *
//* The 'wl-copy' and/or 'wl-paste' utility will occasionally go off into the    *
//* weeds. The reasons for this are unclear, but communication can usually be    *
//* re-established by running the initialization sequence again. However, to     *
//* do this, it is necessary to first reset all class resources to the initial   *
//* state and then run the startup sequence.                                     *
//*                                                                              *
//* Note that this method does not reference the 'connected' member, but         *
//* unconditionally attempts the reconnection.                                   *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: 'true' if connection re-established, else 'false'                   *
//********************************************************************************

bool WaylandCB::wcbReinit ( void )
{
   //* Delete the temporary file.*
   this->DeleteTempname ( this->tfspec ) ;

   //* Reset all data members EXCEPT for debugging members.*
   this->reset () ;

   //* Execute the startup sequence.*
   this->StartupSequence () ;

   return ( this->connected ) ;

}  //* End wcbReinit() *

//*************************
//*      wcbVersion       *
//*************************
//********************************************************************************
//* Public Method:                                                               *
//* --------------                                                               *
//* Report the WaylandCB-class version number.                                   *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: pointer to const string of the format: "x.y.zz"                     *
//********************************************************************************

const char* WaylandCB::wcbVersion ( void )
{

   return ( WaylandCB_Version ) ;

}  //* End wcbVersion() *

//*************************
//*      wcbVersion       *
//*************************
//********************************************************************************
//* Public Method:                                                               *
//* --------------                                                               *
//* Report the WaylandCB-class version AND the wl-clipboard version.             *
//* The two version-number strings are returned in a gString object, and are     *
//* separated by a newline character ('\n') as shown:                            *
//*    "x.y.zz\na.b.c"                                                           *
//* where "x.y.zz" is the WaylandCB version, and "a.b.c" is the reported         *
//* wl-copy/wl-paste version. Example:  "0.0.04\n2.2.1"                          *
//*                                                                              *
//*                                                                              *
//* Input  : gsVersion : (by reference) receives concatenated version strings    *
//*                                                                              *
//* Returns: ZERO if successful, or                                              *
//*             wcbsNOCONNECT if error i.e. not connected to clipboard           *
//*             wcbsNOINSTALL if 'wl-copy' utility not installed                 *
//********************************************************************************

short WaylandCB::wcbVersion ( gString& gsVersion )
{
   const char* const verTemplate = "wl-copy --version 1>\"%s\" 2>&1" ;
   const char* const verUnknown  = "\nunknown" ;

   short status = wcbsNOCONNECT ;         // return value

   gsVersion = WaylandCB_Version ;        // report class version

   //* If not connected to the Wayland clipboard *
   //* interface, return an error condition.     *
   if ( this->connected )
   {
      //* Discard contents (if any) of the target temp file.*
      ofstream ofs( this->tfspec, ofstream::out | ofstream::trunc ) ;
      if ( ofs.is_open() )
         ofs.close() ;

      //* Construct the command and capture the response in our temp file. *
      gString gs( verTemplate, this->tfspec ) ;
      this->Systemcall ( gs.ustr() ) ;

      //* Retrieve the results from the temp file.*
      ifstream ifs( this->tfspec, ifstream::in ) ; // open the file
      if ( ifs.is_open() )                         // if file opened
      {
         char inbuff[gsALLOCMED] ;
         //* Read the first line. It S/B in the format:  *
         //* "wl-clipboard x.x.x"                        *
         ifs.getline( inbuff, gsALLOCMED, NEWLINE ) ;
         gString gs( inbuff ) ;
         short indx ;
         if ( (indx = gs.after( L' ' )) > ZERO )
            gs.shiftChars( -(indx) ) ;
         if ( (gs.gstr()[0] >= L'0') && (gs.gstr()[0] <= L'9') )
         {
            gsVersion.append( "\n%S", gs.gstr() ) ;
            status = ZERO ;                        // return success
         }
         ifs.close() ;                             // close the file
      }
   }
   if ( status != ZERO )
   { gsVersion.append( verUnknown ) ; }

   return status ;

}  //* End wcbVersion() *

//*************************
//*         reset         *
//*************************
//********************************************************************************
//* Protected Method:                                                            *
//* -----------------                                                            *
//* Set all data members to their initial state.                                 *
//* Called only by constructors and by wcbReinitialize() method.                 *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************

void WaylandCB::reset ( void )
{
   *this->tfdir    = NULLCHAR ;
   *this->tfspec   = NULLCHAR ;
   this->tfspecLen = ZERO ;
   this->connected = false ;

}  //* End reset() *

//*************************
//*    StartupSequence    *
//*************************
//********************************************************************************
//* Protected Method:                                                            *
//* -----------------                                                            *
//* Establish temp file for communications with the external "wl-clipboard"      *
//* utilities, "wl-copy" and "wl-paste".                                         *
//*                                                                              *
//* Establish the connection with the system clipboard.                          *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: nothing                                                             *
//*          'connected' data member set if successful                           *
//********************************************************************************

void WaylandCB::StartupSequence ( void )
{
   gString gs ;

   //* Locate the system's temp-file directory.*
   if ( (this->GetTempdirPath ( gs )) )
   {
      //* Save the temp-dir filespec.*
      gs.copy( this->tfdir, gsALLOCMED ) ;

      //* Create the primary temp file.*
      if ( (this->CreateTempname ( gs )) )
      {
         gs.copy( this->tfspec, gsALLOCMED ) ;
         this->tfspecLen = gs.utfbytes() ;

         //* Establish connection with wl-clipboard.*
         this->connected = this->EstablishIndirectConnection () ;
      }
   }

}  //* End StartupSequence() *

//*******************************
//* EstablishIndirectConnection *
//*******************************
//********************************************************************************
//* Protected Method:                                                            *
//* -----------------                                                            *
//* Determine whether the "wl-copy" and "wl-paste" utilities are installed       *
//* on the system, and if they are, perform a simple copy-and-paste operation    *
//* to establish the connection.                                                 *
//*                                                                              *
//* Input  : none                                                                *
//*                                                                              *
//* Returns: 'true' if utilities are installed and clipboards are accessible.    *
//*          'false' otherwise                                                   *
//********************************************************************************
//* The connection is looped for a maximum success rate.                         *
//* As noted elsewhere, the 'wl-copy' and 'wl-paste' utilities are slown and     *
//* fragile. Problems that mysteriously arise, just as mysteriously disappear.   *
//* The looped sequence seems to be the best way to overcome the gremlins        *
//* in the wl-clipboard interface.                                               *
//*                                                                              *
//* Additional Note: Because the 'wl-copy' utility tends to be fragile,          *
//* if the application encounters and error during copy/paste then the next      *
//* time the application starts we may unable to establish the connection.       *
//* We need an elegant way to clear the error, so we can report a successful     *
//* connection.                                                                  *
//*                                                                              *
//********************************************************************************

bool WaylandCB::EstablishIndirectConnection ( void )
{
   const char* const testMsg = "WaylandCB Test Message" ;
   chrono::duration<short, std::milli>aMoment( 6 ) ;  // 6-millisecond delay
   gString gs( "%s (primary)", testMsg ) ;            // test message
   uint8_t trg[gsALLOCDFLT] ;                         // target buffer
   int setStatus ;                                    // bytes received by clipboard
   bool status = false ;                              // return value

   this->connected = true ;   // temporarily allow access to the public methods

   #if DEBUG_WCB !=  0  // DEBUG ONLY
   this->dbofs << "-- EstablishIndirectConnection()" << endl ;
   #endif   // DEBUG_WCB

   //* Perform the copy-and-paste test. *
   //* Loop until success or exhaustion.*
   for ( short testCnt = 3 ; testCnt > ZERO ; --testCnt )
   {
      //* Place the test message on the "Primary" clipboard.*
      if ( (setStatus = this->wcbSet ( gs, -1, true )) >= ZERO )
      {
         this_thread::sleep_for( aMoment ) ;    // wait a moment

         //* Retrieve the contents of the "Primary" clipboard.*
         if ( (this->wcbGet ( trg, gsALLOCDFLT, true )) >= ZERO )
         {
            //* Compare the data returned with the data transmitted. *
            //* If a match, then connection is verified.             *
            if ( (gs.compare( trg, true )) == ZERO )
               status = true ;

            //* Delete the test message from the primary clipboard.*
            this->wcbClear ( true ) ;
            break ;  // success !!
         }
      }
      else if ( setStatus == wcbsNOINSTALL ) // 'wl-copy' not installed
         break ;

      //* If operation failed, wait a moment before retry.*
      this_thread::sleep_for( aMoment ) ;
   }

   this->connected = status ;    // remember the results of the test

   #if DEBUG_WCB !=  0  // DEBUG ONLY
   this->dbofs << "-- EstablishIndirectConnection(" 
               << (this->connected ? "OK" : "ERROR") ;
    if ( setStatus == wcbsNOINSTALL )
       dbofs << " - 'wl-copy' not installed" ;
    this->dbofs << ")\n" << endl ;
   #endif   // DEBUG_WCB

   return status ;

}  //* End EstablishIndirectConnection() *

//*************************
//*      Systemcall       *
//*************************
//********************************************************************************
//* Protected Method:                                                            *
//* -----------------                                                            *
//* Send a command to the shell program.                                         *
//*                                                                              *
//* Input  : cmd :                                                               *
//*                                                                              *
//* Returns: nothing                                                             *
//********************************************************************************
//* We execute a short delay before calling the system.                          *
//* This is done because the wl-clipboard utilities are outrageously slow        *
//* to respond. See notes in the module header.                                  *
//*                                                                              *
//********************************************************************************

void WaylandCB::Systemcall ( const char* cmd )
{
   static chrono::duration<short, std::milli>aMoment( 50 ) ;

   this_thread::sleep_for( aMoment ) ;
   system ( cmd ) ;

}  //* End Systemcall() *

//*************************
//*    GetTempdirPath     *
//*************************
//********************************************************************************
//* Protected Method:                                                            *
//* -----------------                                                            *
//* Query the system for the path reserved for creating temporary files.         *
//*                                                                              *
//* These functions are available in C++17 (released Jan. 2018):                 *
//* #include <filesystem>                                                        *
//* std::filesystem::path fs::temp_directory_path ( void ) ;                     *
//* std::filesystem::path fs::temp_directory_path ( std::error_code& ec ) ;      *
//*                                                                              *
//* Programmer's Rant:                                                           *
//* ------------------                                                           *
//* C++17 provides a function specifically for querying the system for the       *
//* tempfile path; however, it is bloody inefficient and returns the path in     *
//* a complex object "std::filesystem::path" which is also grossly inefficient.  *
//* Despite these inefficiencies, we have (reluctantly) decided to move toward   *
//* the future of computing, otherwise known as wasted CPU cycles.               *
//*                                                                              *
//* Input  : tdPath : (by reference) receives the path string                    *
//*                                                                              *
//* Returns: 'true'  if successful, 'false' if system error                      *
//********************************************************************************
//* Programmer's Note:                                                           *
//* ------------------                                                           *
//* On POSIX systems, the path may be specified by any of several system-specific*
//* environment variables, and if none of them are specified, the path "/tmp"    *
//* is returned by the system call.                                              *
//*         (This WILL NOT work under Windoze--and we don't care.)               *
//*         (Screw Windoze, and the horse it rode in on.         )               *
//********************************************************************************

bool WaylandCB::GetTempdirPath ( gString& tdPath )
{
   FileStatx   fStats ;             // target filestats
   bool        status = false ;     // return value

   //* Get the filespec of the system temporary-file directory *
   filesystem::path p = filesystem::temp_directory_path () ;
      tdPath = p.c_str() ;          // temp-directory filespec
   if ( (statx ( 0, tdPath.ustr(), wsx_flags, wsx_mask, &fStats )) == 0 )
   {
      if ( ((S_ISDIR(fStats.stx_mode)) != false) &&
           ((access ( tdPath.ustr(), R_OK )) == 0) && 
           ((access ( tdPath.ustr(), W_OK )) == 0) )
      { status = true ; }
   }
   return status ;

}  //* End GetTempdirPath()

//*************************
//*    CreateTempname     *
//*************************
//********************************************************************************
//* Protected Method:                                                            *
//* -----------------                                                            *
//* Create a unique path/filename for a temporary file.                          *
//*                                                                              *
//* Input  : tmpPath: (by reference) receives the path/filename                  *
//*                                                                              *
//* Returns: 'true'  if path/filename created successfully                       *
//*          'false' if library call failed                                      *
//********************************************************************************

bool WaylandCB::CreateTempname ( gString& tmpPath )
{
   bool  status = false ;

   char tn[gsALLOCMED] ;
   gString gstmp( "%s/WLCB_XXXXXX", this->tfdir ) ;
   gstmp.copy( tn, gsALLOCMED ) ;
   int descriptor ;
   if ( (descriptor = mkstemp ( tn )) != (-1) )
   {
      close ( descriptor ) ;     // close the file
      tmpPath = tn ;             // copy target path to caller's buffer
      status = true ;            // declare success
   }
   else
      tmpPath.clear() ;
   return status ;

}  //* End CreateTempname() *

//*************************
//*    DeleteTempname     *
//*************************
//********************************************************************************
//* Protected Method:                                                            *
//* -----------------                                                            *
//* Delete the specified temporary file.                                         *
//*                                                                              *
//* Input  : tmpPath: (by reference) filespec of temp file to be deleted         *
//*                                                                              *
//* Returns: 'true'  if file deleted                                             *
//*          'false' if library call failed (file not found, or system error)    *
//********************************************************************************

bool WaylandCB::DeleteTempname ( const gString& tmpPath )
{
   bool  status = false ;

   if ( (unlink ( tmpPath.ustr())) == ZERO )
      status = true ;

   return status ;

}  //* End DeleteTempname() *

#if 0    // NOT CURRENTLY USED
//* Specifies the character or group of characters to be "escaped" *
//* by a call to the EscapeSpecialChars() method.                  *
enum escGroup : short
{
   escDQ,      // escape only double-quote character ( " )
   escMIN,     // escape the four most critical characters: ( $ \ ` " )
   escLINUX,   // escMIN plus the Linux shell special characters ( \ ' " ? : ; & > < | * )
   escMAX,     // escLINUX plus all remaining special characters
               // # + = % ! ~ { } @ ( ) [ ] and space(' ' 20h)
   escWCHAR    // escape only the specified character
} ;

static short EscapeSpecialChars ( gString& src, escGroup group, 
                                  wchar_t wch = NULLCHAR ) ;
static short EscapeSpecialChars ( wchar_* src, escGroup group, 
                                  wchar_t wch = NULLCHAR ) ;
static short EscapeSpecialChars ( char* src, escGroup group, 
                                  char ch = NULLCHAR ) ;

//*************************
//*  EscapeSpecialChars   *
//*************************
//********************************************************************************
//* Scan the source string (typically a filespec). If the string contains any    *
//* characters of the specified character group (enum escGroup) 'escape' them    *
//* with a backslash character.                                                  *
//*                                                                              *
//* Do not use this method for URL (Universal Resource Locator) specs.           *
//* URL specifications require a specific protocol which is not handled here.    *
//*                                                                              *
//* Input  : pathSpec : (by reference) source text data                          *
//*                     on return the data has bee 'escaped' accoring to the     *
//*                     specified criteria                                       *
//*          group    : (member of enum escGroup) specifies the group of         *
//*                     characters to be 'escaped'                               *
//*          wch      : (optional, NULLCHAR by default)                          *
//*                     if 'group' parameter == escWCHAR, then 'wch' specifies   *
//*                     a single character (NOT nullchar) to be 'escaped'        *
//*                                                                              *
//* Returns: number of characters 'escaped'                                      *
//********************************************************************************
//* Special Case Testing for '\' as the special character:                       *
//* 1) If the incomming data contains a '\', we must test whether:               *
//*    a) The '\' itself has already been escaped,                               *
//*    b) The '\' is escaping some other special character,                      *
//*    c) The '\' is a special character which needs to be escaped.              *
//* 2) If data contains an escaped special character, but caller intended        *
//*    for it to be two SEPERATE special characters, caller will be              *
//*    disappointed that we could not read his/her/its mind. (sorry about that)  *
//*                                                                              *
//* -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -  - *
//* For more information, see "Quoting Special Characters in BASH.odt".          *
//********************************************************************************

static short EscapeSpecialChars ( gString& pathSpec, escGroup group, wchar_t wch )
{
   //* List of critical "special" characters.*
   const short   minSpecialCount = 4 ;
   const wchar_t minSpecialChars[minSpecialCount] = 
   { L'\\', L'"', L'`', L'$' } ;

   //* List of Linux/UNIX shell "special" characters.*
   const short   linuxSpecialCount = 13 ;
   const wchar_t linuxSpecialChars[linuxSpecialCount] = 
   { L'\\', L'"', L'\'', L'?', L':', L';', L'&', L'>', L'<', L'|', L'*', L'`', L'$' } ;

   //* List of all known "special" characters which might be interpreted *
   //* by the shell or shell programs as commands rather than plain text.*
   const short   maxSpecialCount = 27 ;
   const wchar_t maxSpecialChars[maxSpecialCount] = 
   { L'\\', L'"', L'\'', L'?', L':', L';', L'&', L'>', L'<', L'|', L'*', L'`', L'$',
     L'#', L'+', L'=', L'%', L'!', L'~', L'{', L'}', L'@', L'(', L')', L'[', L']', L' ' } ;

   const wchar_t wDQ = L'"',
                 wBS = L'\\' ;

   const wchar_t *wPtr ;            // pointer to wchar_t array
   short wCount,                    // number of elements in wchar_t array
         indx,                      // text index
         specChars = ZERO ;         // return value

   //* Point to the reference array and establish number of array elements.*
   if ( group == escMIN )
   { wPtr = minSpecialChars ;   wCount = minSpecialCount ; }
   else if ( group == escLINUX )
   { wPtr = linuxSpecialChars ; wCount = linuxSpecialCount ; }
   else if ( group == escMAX )
   { wPtr = maxSpecialChars ;   wCount = maxSpecialCount ; }
   else if ( group == escDQ )
   { wPtr = &wDQ ;              wCount = 1 ; }
   else if ( group == escWCHAR )
   {  //* Do not allow escaping of null terminator.*
      wPtr = &wch ;
      wCount = (wch != NULLCHAR) ? 1 : 0 ;
   }

   for ( short specIndx = ZERO ; specIndx < wCount ; ++specIndx )
   {
      indx = ZERO ;
      do
      {
         if ( (indx = pathSpec.find( wPtr[specIndx], indx )) >= ZERO )
         {
            //* Special character == '\'. (see note above) *
            if ( (pathSpec.gstr()[indx]) == wBS )
            {
               bool insert = true ;
               //* If escape character is itself escaped *
               if ( (indx > ZERO) && ((pathSpec.gstr()[indx - 1]) == wBS) )
               { ++indx ; insert = false ; }
               //* If '\' is being used as an escape character *
               if ( insert )
               {
                  for ( short i = ZERO ; i < wCount && insert ; ++i )
                  {
                     if ( (pathSpec.gstr()[indx + 1]) == wPtr[i] )
                     { indx += 2 ; insert = false ; }
                  }
               }

               if ( insert )  // escape the escape character
               {
                  pathSpec.insert( wBS, indx ) ;
                  ++specChars ;
                  indx += 2 ;
               }
               else
                  ++indx ;
            }
            else
            {
               if ( indx == ZERO )       // first character in array
               {
                  pathSpec.insert( wBS, indx ) ;
                  ++specChars ;
                  indx += 2 ;
               }
               else // (indx > ZERO), second or later character in array
               {
                  if ( (pathSpec.gstr()[indx - 1]) != wBS )
                  {
                     pathSpec.insert( wBS, indx ) ;
                     ++specChars ;
                     indx += 2 ;
                  }
                  else
                     ++indx ;
               }
            }
         }
      }
      while ( (indx >= ZERO) && (pathSpec.gstr()[indx] != NULLCHAR) ) ;
   }

   return specChars ;

}  //* End EscapeSpecialChars() *
#endif   // NOT CURRENTLY USED

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

