Browse Source

Working with mc. No screen breaks.

The strategy of finding unicode before translating
seems to be working great.
Steve Thielemann 4 years ago
commit
6c23658337
10 changed files with 4852 additions and 0 deletions
  1. 125 0
      CMakeLists.txt
  2. 1013 0
      doorman.cpp
  3. 11 0
      logs_utils.cpp
  4. 8 0
      logs_utils.h
  5. 59 0
      notes/ANSI escape code - Wikipedia.html
  6. 2146 0
      notes/Console Virtual Terminal Sequences - Windows Console.html
  7. 853 0
      terminal.cpp
  8. 75 0
      terminal.h
  9. 505 0
      utils.cpp
  10. 57 0
      utils.h

+ 125 - 0
CMakeLists.txt

@@ -0,0 +1,125 @@
+cmake_minimum_required(VERSION 3.5)
+
+project(doorman
+  VERSION 0.1
+  LANGUAGES CXX C)
+
+
+###########
+# Debug or Release
+###########
+if (NOT CMAKE_BUILD_TYPE)
+  ## set default to Debug
+  set(CMAKE_BUILD_TYPE Debug)  # override with -DCMAKE_BUILD_TYPE=Release
+  message("==> CMAKE_BUILD_TYPE empty. Changing it to Debug.")
+else()
+  message("==> CMAKE_BUILD_TYPE == ${CMAKE_BUILD_TYPE}.")
+endif()
+
+FIND_PACKAGE(Git)
+IF(GIT_FOUND)
+  message("Ask git for version information")
+  EXECUTE_PROCESS(
+       COMMAND ${GIT_EXECUTABLE} describe --abbrev=12 --long --tags --dirty --always --match v[0-9]*
+       WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+       OUTPUT_VARIABLE GIT_DESCRIBE_VERSION
+       RESULT_VARIABLE GIT_DESCRIBE_RESULT
+       ERROR_VARIABLE GIT_DESCRIBE_ERROR
+       OUTPUT_STRIP_TRAILING_WHITESPACE
+   )
+   message("Version: " ${GIT_DESCRIBE_VERSION})
+   # message("Result: " ${GIT_DESCRIBE_RESULT})
+ENDIF(GIT_FOUND)
+
+## https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_macros.html
+## During Debug, use debug version of libstdc++ (asserts on access to invalid iterators, etc!)
+set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_GLIBCXX_DEBUG")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
+
+###########
+# Suppress certain warnings
+###########
+# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated-declarations")
+
+# rm CMakeCache.txt and cmake .   if you change any of the C++ Standards.
+
+##############
+# C++ Standard
+##############
+set(CMAKE_CXX_STANDARD   14)
+## set(CMAKE_CXX_STANDARD   17)
+set(CMAKE_CXX_EXTENSIONS ON)
+
+# Enable testing
+set(BUILD_TESTING ON)
+include(CTest)
+
+### ADD gtest
+
+add_subdirectory(googletest)
+
+### TESTS
+add_executable(test-lastseen test-lastseen.cpp lastseen.cpp)
+add_dependencies(test-lastseen gtest)
+target_link_libraries(test-lastseen gtest_main)
+
+enable_testing()
+add_test(NAME test-lastseen
+  COMMAND test-lastseen)
+
+add_executable(test-utils test-utils.cpp utils.cpp)
+add_dependencies(test-utils gtest)
+target_link_libraries(test-utils gtest_main)
+
+add_test(NAME test-utils
+  COMMAND test-utils)
+
+add_executable(test-render test-render.cpp render.cpp terminal.cpp utils.cpp)
+add_dependencies(test-render gtest)
+target_link_libraries(test-render gtest_main)
+target_link_libraries(test-render zf_log)
+
+
+# add_test(NAME test-mangle COMMAND test-mangle)
+
+# add_executable(test-terminal test-terminal.cpp terminal.cpp utils.cpp)
+# add_dependencies(test-terminal gtest)
+# target_link_libraries(test-terminal gtest_main)
+# target_link_libraries(test-terminal zf_log)
+
+add_test(NAME test-terminal COMMAND test-terminal)
+
+add_subdirectory(zf_log)
+
+
+# Example for how to define a test which uses gtest_gmock
+# add_executable(mytest tester.cpp)
+# target_link_libraries(mytest gmock_main)
+
+# Here's how to add all *.h and *.cpp files
+# to a project:
+#
+# file(GLOB SOURCES
+#     header-folder/*.h
+#     source-folder/*.cpp
+# )
+# add_executable(yourProj ${SOURCES})
+
+
+# add_executable(doorman doorman.cpp lastseen.cpp terminal.cpp render.cpp utils.cpp images.h wordplay.cpp charman.cpp logs_utils.cpp)
+add_executable(doorman doorman.cpp terminal.cpp utils.cpp logs_utils.cpp)
+target_link_libraries(doorman util)
+target_link_libraries(doorman zf_log)
+
+# target_compile_definitions(doorman PUBLIC DMVERSION="${GIT_DESCRIBE_VERSION}")
+
+# target_compile_definitions(doorman PUBLIC ZF_LOG_DEF_LEVEL=ZF_LOG_INFO)
+target_compile_definitions(doorman PUBLIC ZF_LOG_DEF_LEVEL=ZF_LOG_VERBOSE)
+
+# If you want this: ninja try-re or make try-re
+# add_executable(try-re EXCLUDE_FROM_ALL try-re.c)
+
+# add_executable(ansi-color EXCLUDE_FROM_ALL ansi-color.c)
+
+# add_executable(images images.cpp utils.cpp images.h)
+# target_link_libraries(images zf_log)

+ 1013 - 0
doorman.cpp

@@ -0,0 +1,1013 @@
+#include "terminal.h"
+#include "utils.h"
+#include <ctype.h>
+#include <fcntl.h>
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <map>
+#include <pty.h>
+#include <sstream>
+#include <stdio.h>
+#include <stdlib.h> // random()
+#include <string.h>
+#include <string>
+#include <strings.h> // strcasecmp
+#include <sys/select.h>
+#include <sys/wait.h>
+#include <termios.h>
+#include <time.h>
+#include <unistd.h>
+
+/*
+  `Tip the DoorMan`
+
+  DoorMan is a BBS door manager that handles translating UTF-8 to CP437 for
+  using ncurses based programs as BBS doors.
+
+  We might start with using the quite excellent iconv library for our
+  conversions, but I believe I'll eventually switch to using a map of some sort
+  to do the process. (Calling a library over and over again to convert the same
+  chars to the same thing over and over again just sounds "wrong".)
+
+  -NOXLATE   (No Translation, leave UTF-8 as-is)
+  -ICONV     (Use ICONV)
+
+  (Default will be using our own table.)
+
+  We will also need to convert the user input (which will be a BBS Terminal
+  program) into whatever Linux expects from users.  Function keys, arrow keys,
+  etc.
+
+  -NOTERM   (Don't translate the terminal input)
+
+  Logging will be a huge requirement to figure much of this out!
+
+  -NOLOG    (Disable logging)
+
+  We'll want to optionally capture/eat Ctrl-C, because most programs quit on
+  that.
+
+  -NOC      (No Ctrl-C, not the default)
+
+  Anything not matching here is considered to be the program + arguments.
+
+ */
+
+/*
+
+"FEATURES"
+
+We don't understand color code 96/97.  (Bright white/bright cyan).  Maybe try
+mapping those back to the original non-bright colors?
+
+There's a possibility of missing keys (if they aren't all received in one
+"packet").  ?
+
+Unicode double lines are definitely missing!    Yikes!
+Anything else that exists in unicode that maps over to high ASCII.
+
+https://en.wikipedia.org/wiki/Code_page_437
+
+I'm not sure, but it almost looks like it has the CP437 char and then the
+unicode char below it.  That might be exactly what I need!
+
+https://jrgraphix.net/r/Unicode/2500-257F
+
+http://xahlee.info/comp/unicode_drawing_shapes.html
+
+Still need to finish up function keys.
+
+still having issues with chars (mc, pageup/pagedown -- garbled chars, that looks
+like missing unicode multi-byte issues)
+ */
+
+enum TRANSLATE { NONE, ICONV, INTERNAL } translate = NONE;
+
+/*
+https://softwareengineering.stackexchange.com/questions/141973/how-do-you-achieve-a-numeric-versioning-scheme-with-git
+
+Use tags to mark commits with version numbers:
+
+git tag -a v2.5 -m 'Version 2.5'
+
+Push tags upstream—this is not done by default:
+
+git push --tags
+
+Then use the describe command:
+
+git describe --tags --long
+*/
+
+std::string version = "0.0.0"; // DMVERSION;
+
+// #include <signal.h> // handle Ctrl-C/SIGINT
+
+/* Log level guideline:
+ * - ZF_LOG_FATAL - happened something impossible and absolutely unexpected.
+ *   Process can't continue and must be terminated.
+ *   Example: division by zero, unexpected modifications from other thread.
+ * - ZF_LOG_ERROR - happened something possible, but highly unexpected. The
+ *   process is able to recover and continue execution.
+ *   Example: out of memory (could also be FATAL if not handled properly).
+ * - ZF_LOG_WARN - happened something that *usually* should not happen and
+ *   significantly changes application behavior for some period of time.
+ *   Example: configuration file not found, auth error.
+ * - ZF_LOG_INFO - happened significant life cycle event or major state
+ *   transition.
+ *   Example: app started, user logged in.
+ * - ZF_LOG_DEBUG - minimal set of events that could help to reconstruct the
+ *   execution path. Usually disabled in release builds.
+ * - ZF_LOG_VERBOSE - all other events. Usually disabled in release builds.
+ *
+ * *Ideally*, log file of debugged, well tested, production ready application
+ * should be empty or very small. Choosing a right log level is as important as
+ * providing short and self descriptive log message.
+ */
+/*
+#define ZF_LOG_VERBOSE 1
+#define ZF_LOG_DEBUG   2
+#define ZF_LOG_INFO    3
+#define ZF_LOG_WARN    4
+#define ZF_LOG_ERROR   5
+#define ZF_LOG_FATAL   6
+*/
+
+// When debugging low-level, use this:
+// #define ZF_LOG_LEVEL ZF_LOG_VERBOSE
+// Except this doesn't work.  It needs to be anywere the
+// zf_log.h is included.
+
+// LOGGING with file output
+
+#include "zf_log.h"
+
+FILE *g_log_file;
+
+static void file_output_callback(const zf_log_message *msg, void *arg) {
+  (void)arg;
+  *msg->p = '\n';
+  fwrite(msg->buf, msg->p - msg->buf + 1, 1, g_log_file);
+  fflush(g_log_file);
+}
+
+static void file_output_close(void) { fclose(g_log_file); }
+
+static int file_output_open(const char *const log_path) {
+  g_log_file = fopen(log_path, "a");
+  if (!g_log_file) {
+    ZF_LOGW("Failed to open log file %s", log_path);
+    return 0;
+  }
+  atexit(file_output_close);
+  zf_log_set_output_v(ZF_LOG_PUT_STD, 0, file_output_callback);
+  return 1;
+}
+
+void log_flush(void) { fflush(g_log_file); }
+
+// END LOGGING
+
+/*
+
+This is done.  :D  My buffering system works with stack'em.
+
+TO FIX:  Stop using c strings, must use char * buffer + int length.
+MAY CONTAIN NULL VALUES.
+
+Rework some things here.
+
+Here's the "plan":
+
+  if buffer is EMPTY:
+      time_idle = 1;
+      // setup for "random timeout value mess"
+        // we're in luck!  The last parameter is time interval/timeout.  :D
+      timeout.tv_sec = 10; // randrange(10-25)
+      timeout.tv_usec = 0;
+
+  NOT EMPTY:
+      // we're in luck!  The last parameter is time interval/timeout.  :D
+      timeout.tv_sec = 0;
+      timeout.tv_usec = 10;   // Wild Guess Here?  Maybe higher, maybe
+lower? time_idle = 0;
+
+  ON READ:
+    read/append to current buffer.
+    We can't use nulls -- what if they are using ZModem, there's nulls in
+the file! Look for trailing  / the very last "\r\n".
+
+    (I could mangle/chunk it line by line.  But I'm not sure I'd need to do
+that.)
+
+    Optional "mangle" buffer up to that very point -- and send up to that
+point.
+
+    Option #2:  Maybe we send everything if program has been running for
+under 20 seconds.  This would allow the ANSI detect to not get screwed up by
+this new idea.
+
+  ON TIMEOUT:
+    if time_idle:
+      Activate funny harry timeout events.
+    else:
+      Ok, we *STILL* haven't received any more characters into the buffer --
+      even after waiting.  (Maybe we haven't waited long enough?)
+      send the pending information in the buffer and clear it out.
+      Maybe this is a prompt, and there won't be a \r\n.
+
+This allows for cleaner process of "lines" of buffer.  We shouldn't break
+in the midDLE OF A WORD.  Downside is that we sit on buffer contents a
+little while / some amount of time -- which will add some lag to prompts
+showing up.
+
+(LAG?  Are you kidding?)
+
+
+ZModem:
+
+    start:  "rz^M**"...
+
+    05-12 18:12:15.916 >> rz^M**^XB00000000000000^M<8A>^Q
+    05-12 18:12:15.928 << **\x18B0100000023be50\r\n\x11
+    05-12 18:12:15.928 >> *^XC^D
+    05-12 18:12:15.939 << **\x18B0900000000a87c\r\n\x11
+    05-12 18:12:15.940 >> *^XC
+    # Start of PK zipfile.
+    05-12 18:12:15.941 >> PK^C^D^T
+
+    end:
+    05-12 18:26:38.700 << **\x18B0100000023be50\r\n\x11
+    05-12 18:26:38.700 >> **^XB0823a77600344c^M<8A>
+    05-12 18:26:38.711 << **\x18B0800000000022d\r\n
+    05-12 18:26:38.712 >> OO^MESC[0m
+
+ */
+
+// TODO:  Get everything above this -- into another file.
+
+/*
+
+void display_line(const char *line) {
+  char input[1024];
+  char output[1024];
+
+  strcpy(input, line);
+  converter.convert(input, output, sizeof(output));
+  printf("%s\n", output);
+}
+*/
+
+void terminal_output(TRANSLATE xlate, std::string &buffer) {
+  /*
+  The current problem is this.  The unichoad characters are getting spilt by
+  buffer breaks.
+   */
+  static Terminal term;
+  // static IConv converter("UTF-8", "CP437");
+
+  // static IConv converter("CP437", "UTF-8"); // TO, FROM
+
+  ZF_LOGV_MEM(buffer.data(), buffer.size(), "Buffer now:");
+  ZF_LOGD("Buffer: %s", repr(buffer.c_str()));
+
+  if (xlate == NONE) {
+    // no translation
+    write(STDOUT_FILENO, buffer.data(), buffer.size());
+    buffer.clear();
+  } else {
+
+    std::string ansi;
+    int dcs_mode = term.dcs();
+
+    // The Hunt for Red Unichoad
+    // https://en.wikipedia.org/wiki/UTF-8
+    /*
+    Number
+    of bytes 	First
+              code point 	Last
+                          code point 	Byte 1 	Byte 2 	Byte 3 	Byte 4
+    1 	      U+0000 	    U+007F 	0xxxxxxx
+    2 	      U+0080 	    U+07FF 	110xxxxx 	10xxxxxx
+    3 	      U+0800 	    U+FFFF 	1110xxxx 	10xxxxxx 	10xxxxxx
+    4 	      U+10000 	  U+10FFFF[18] 	11110xxx 	10xxxxxx 10xxxxxx
+    10xxxxxx
+     */
+
+    int length = buffer.size();
+    int save = 0; // default to saving zero characters.
+
+    char uchoad;
+    // 1 char searches:
+    if (length >= 1) {
+      uchoad = buffer[length - 1];
+      if ((uchoad & 0xe0) == 0xc0) {
+        // CHOAD!
+        save = 1;
+      } else {
+        if ((uchoad & 0xf0) == 0xe0) {
+          save = 1;
+        }
+      }
+    }
+
+    if (length >= 2) {
+      uchoad = buffer[length - 2];
+      if ((uchoad & 0xf0) == 0xe0) {
+        // CHOAD
+        save = 2;
+      }
+    }
+
+    /*  // if we're going back 3 chars, and the len=3 chars, leave it along. :P
+    if (length >= 3) {
+      uchoad = buffer[length - 3];
+      if ((uchoad & 0xf0) == 0xe0) {
+        // CHOAD
+        save = 3;
+      }
+    }
+    */
+
+    std::string saved;
+
+    if (save) {
+      ZF_LOGE("Saving %d chars", save);
+      ZF_LOGV_MEM(buffer.data(), buffer.size(), "PRE-SAVE:");
+      saved = buffer.substr(length - save, save);
+      buffer = buffer.substr(0, length - save);
+      ZF_LOGV_MEM(buffer.data(), buffer.size(), "After Save:");
+      ZF_LOGV_MEM(saved.data(), saved.size(), "SAVED:");
+    }
+
+    static std::map<const char *, int> utf8cp437 = {
+
+        {"\xe2\x98\xba", 1},
+        {"\xe2\x98\xbb", 2},
+        {"\xe2\x99\xa5", 3},
+        {"\xe2\x99\xa6", 4},
+        {"\xe2\x99\xa3", 5},
+        {"\xe2\x99\xa0", 6},
+        // {"\xe2\x80\xa2", 7},    {"\xe2\x97\x98", 8},
+        {"\xe2\x97\x8b", 9},
+        //{"\xe2\x97\x99", 0x0a},
+        {"\xe2\x99\x82", 0x0b},
+        {"\xe2\x99\x80", 0x0c},
+        //{"\xe2\x99\xaa", 0x0d},
+        {"\xe2\x99\xab", 0x0e},
+        {"\xe2\x98\xbc", 0x0f},
+        {"\xe2\x96\xba", 0x10},
+        {"\xe2\x97\x84", 0x11},
+        {"\xe2\x86\x95", 0x12},
+        {"\xe2\x80\xbc", 0x13},
+        {"\xc2\xb6", 0x14},
+        {"\xc2\xa7", 0x15},
+        {"\xe2\x96\xac", 0x16},
+        {"\xe2\x86\xa8", 0x17},
+        {"\xe2\x86\x91", 0x18},
+        {"\xe2\x86\x93", 0x19},
+        {"\xe2\x86\x92", 0x1a},
+        {"\xe2\x86\x90", 0x1b},
+        {"\xe2\x88\x9f", 0x1c},
+        {"\xe2\x86\x94", 0x1d},
+        {"\xe2\x96\xb2", 0x1e},
+        {"\xe2\x96\xbc", 0x1f},
+        {"\xe2\x8c\x82", 0x7f},
+        {"\xc3\x87", 0x80},
+        {"\xc3\xbc", 0x81},
+        {"\xc3\xa9", 0x82},
+        {"\xc3\xa2", 0x83},
+        {"\xc3\xa4", 0x84},
+        {"\xc3\xa0", 0x85},
+        {"\xc3\xa5", 0x86},
+        {"\xc3\xa7", 0x87},
+        {"\xc3\xaa", 0x88},
+        {"\xc3\xab", 0x89},
+        {"\xc3\xa8", 0x8a},
+        {"\xc3\xaf", 0x8b},
+        {"\xc3\xae", 0x8c},
+        {"\xc3\xac", 0x8d},
+        {"\xc3\x84", 0x8e},
+        {"\xc3\x85", 0x8f},
+        {"\xc3\x89", 0x90},
+        {"\xc3\xa6", 0x91},
+        {"\xc3\x86", 0x92},
+        {"\xc3\xb4", 0x93},
+        {"\xc3\xb6", 0x94},
+        {"\xc3\xb2", 0x95},
+        {"\xc3\xbb", 0x96},
+        {"\xc3\xb9", 0x97},
+        {"\xc3\xbf", 0x98},
+        {"\xc3\x96", 0x99},
+        {"\xc3\x9c", 0x9a},
+        {"\xc2\xa2", 0x9b},
+        {"\xc2\xa3", 0x9c},
+        {"\xc2\xa5", 0x9d},
+        {"\xe2\x82\xa7", 0x9e},
+        {"\xc6\x92", 0x9f},
+        {"\xc3\xa1", 0xa0},
+        {"\xc3\xad", 0xa1},
+        {"\xc3\xb3", 0xa2},
+        {"\xc3\xba", 0xa3},
+        {"\xc3\xb1", 0xa4},
+        {"\xc3\x91", 0xa5},
+        {"\xc2\xaa", 0xa6},
+        {"\xc2\xba", 0xa7},
+        {"\xc2\xbf", 0xa8},
+        {"\xe2\x8c\x90", 0xa9},
+        {"\xc2\xac", 0xaa},
+        {"\xc2\xbd", 0xab},
+        {"\xc2\xbc", 0xac},
+        {"\xc2\xa1", 0xad},
+        {"\xc2\xab", 0xae},
+        {"\xc2\xbb", 0xaf},
+        {"\xe2\x96\x91", 0xb0},
+        {"\xe2\x96\x92", 0xb1},
+        {"\xe2\x96\x93", 0xb2},
+        {"\xe2\x94\x82", 0xb3},
+        {"\xe2\x94\xa4", 0xb4},
+        {"\xe2\x95\xa1", 0xb5},
+        {"\xe2\x95\xa2", 0xb6},
+        {"\xe2\x95\x96", 0xb7},
+        {"\xe2\x95\x95", 0xb8},
+        {"\xe2\x95\xa3", 0xb9},
+        {"\xe2\x95\x91", 0xba},
+        {"\xe2\x95\x97", 0xbb},
+        {"\xe2\x95\x9d", 0xbc},
+        {"\xe2\x95\x9c", 0xbd},
+        {"\xe2\x95\x9b", 0xbe},
+        {"\xe2\x94\x90", 0xbf},
+        {"\xe2\x94\x94", 0xc0},
+        {"\xe2\x94\xb4", 0xc1},
+        {"\xe2\x94\xac", 0xc2},
+        {"\xe2\x94\x9c", 0xc3},
+        {"\xe2\x94\x80", 0xc4},
+        {"\xe2\x94\xbc", 0xc5},
+        {"\xe2\x95\x9e", 0xc6},
+        {"\xe2\x95\x9f", 0xc7},
+        {"\xe2\x95\x9a", 0xc8},
+        {"\xe2\x95\x94", 0xc9},
+        {"\xe2\x95\xa9", 0xca},
+        {"\xe2\x95\xa6", 0xcb},
+        {"\xe2\x95\xa0", 0xcc},
+        {"\xe2\x95\x90", 0xcd},
+        {"\xe2\x95\xac", 0xce},
+        {"\xe2\x95\xa7", 0xcf},
+        {"\xe2\x95\xa8", 0xd0},
+        {"\xe2\x95\xa4", 0xd1},
+        {"\xe2\x95\xa5", 0xd2},
+        {"\xe2\x95\x99", 0xd3},
+        {"\xe2\x95\x98", 0xd4},
+        {"\xe2\x95\x92", 0xd5},
+        {"\xe2\x95\x93", 0xd6},
+        {"\xe2\x95\xab", 0xd7},
+        {"\xe2\x95\xaa", 0xd8},
+        {"\xe2\x94\x98", 0xd9},
+        {"\xe2\x94\x8c", 0xda},
+        {"\xe2\x96\x88", 0xdb},
+        {"\xe2\x96\x84", 0xdc},
+        {"\xe2\x96\x8c", 0xdd},
+        {"\xe2\x96\x90", 0xde},
+        {"\xe2\x96\x80", 0xdf},
+        {"\xce\xb1", 0xe0},
+        {"\xc3\x9f", 0xe1},
+        {"\xce\x93", 0xe2},
+        {"\xcf\x80", 0xe3},
+        {"\xce\xa3", 0xe4},
+        {"\xcf\x83", 0xe5},
+        {"\xc2\xb5", 0xe6},
+        {"\xcf\x84", 0xe7},
+        {"\xce\xa6", 0xe8},
+        {"\xce\x98", 0xe9},
+        {"\xce\xa9", 0xea},
+        {"\xce\xb4", 0xeb},
+        {"\xe2\x88\x9e", 0xec},
+        {"\xcf\x86", 0xed},
+        {"\xce\xb5", 0xee},
+        {"\xe2\x88\xa9", 0xef},
+        {"\xe2\x89\xa1", 0xf0},
+        {"\xc2\xb1", 0xf1},
+        {"\xe2\x89\xa5", 0xf2},
+        {"\xe2\x89\xa4", 0xf3},
+        {"\xe2\x8c\xa0", 0xf4},
+        {"\xe2\x8c\xa1", 0xf5},
+        {"\xc3\xb7", 0xf6},
+        {"\xe2\x89\x88", 0xf7},
+        {"\xc2\xb0", 0xf8},
+        {"\xe2\x88\x99", 0xf9},
+        {"\xc2\xb7", 0xfa},
+        {"\xe2\x88\x9a", 0xfb},
+        {"\xe2\x81\xbf", 0xfc},
+        {"\xc2\xb2", 0xfd},
+        {"\xe2\x96\xa0", 0xfe},
+        {"\xc2\xa0", 0xff}};
+
+    if ((buffer.find('\xe2') != std::string::npos) ||
+        (buffer.find('\xc2') != std::string::npos) ||
+        (buffer.find('\xc3') != std::string::npos) ||
+        (buffer.find('\xce') != std::string::npos) ||
+        (buffer.find('\xcf') != std::string::npos)) {
+      int c = 0;
+
+      for (auto it = utf8cp437.begin(); it != utf8cp437.end(); ++it) {
+        while (replace(buffer, it->first, std::string(1, char(it->second)))) {
+          c++;
+        }
+      }
+      if (c) {
+        ZF_LOGE("Replaced %d", c);
+        ZF_LOGV_MEM(buffer.data(), buffer.size(), "After Replace:");
+      }
+    }
+
+    /*
+          // \xe2 found:
+          int c = 0;
+
+          // BUG:  If we replace the very last character, it looks like a
+       unichoad
+          // character.  It would be nice if we knew it was a translated
+       character
+          // and ignore it.
+          while (replace(buffer, "\xe2\x94\x80", "\xc4")) {
+            c++;
+          }
+          while (replace(buffer, "\xe2\x94\x98", "\xd9")) {
+            c++;
+          }
+          while (replace(buffer, "\xe2\x94\x90", "\xbf")) {
+            c++;
+          }
+          while (replace(buffer, "\xe2\x94\x8c", "\xda")) {
+            c++;
+          }
+          while (replace(buffer, "\xe2\x94\x82", "\xb3")) {
+            c++;
+          }
+          while (replace(buffer, "\xe2\x94\xa4", "\xb4")) {
+            c++;
+          }
+          while (replace(buffer, "\xe2\x94\x9c", "\xc3")) {
+            c++;
+          }
+          while (replace(buffer, "\xe2\x94\x94", "\xc0")) {
+            c++;
+          }
+
+    if (buffer.find('\xe2') != std::string::npos) {
+      ZF_LOGE_MEM(buffer.data(), buffer.size(), "Buffer still contains \xe2:");
+    };
+  }
+    */
+
+    while (replace(buffer, "\x1b[90m", "\x1b[1;30m")) {
+    };
+    while (replace(buffer, "\x1b[91m", "\x1b[1;31m")) {
+    };
+    while (replace(buffer, "\x1b[92m", "\x1b[1;32m")) {
+    };
+    while (replace(buffer, "\x1b[93m", "\x1b[1;33m")) {
+    };
+    while (replace(buffer, "\x1b[94m", "\x1b[1;34m")) {
+    };
+    while (replace(buffer, "\x1b[95m", "\x1b[1;35m")) {
+    };
+    while (replace(buffer, "\x1b[96m", "\x1b[1;36m")) {
+    };
+    while (replace(buffer, "\x1b[97m", "\x1b[1;37m")) {
+    };
+
+    for (auto iter = buffer.begin(); iter != buffer.end(); ++iter) {
+      char c = *iter;
+      Terminal::termchar tc = term.putchar(c);
+      if (tc.in_ansi) {
+        ansi.append(1, c);
+        if (tc.ansi == Terminal::ANSI_TYPE::START)
+          continue;
+        // Ok, the ansi command is completed.
+        if (tc.ansi ==
+            Terminal::ANSI_TYPE::DCS) { // Terminal::ANSI_TYPE::DCS) {
+          // Ok, EAT this command!  it's worthless to the terminal we're talking
+          // to.
+          dcs_mode = term.dcs();
+        }
+      } else {
+        // Ok, we're not in any ANSI mode.
+        /*
+        ┌ ─ ┬ ┐
+        │ │ │ │
+        ├ ─ ┼ ┤
+        └ ─ ┴ ┘
+         */
+        if (dcs_mode == 0) {
+          switch (c) {
+          case 'j':
+            c = '\xd9'; // '┘';
+            break;
+          case 'k':
+            c = '\xbf'; // '┐';
+            break;
+          case 'l':
+            c = '\xda'; // '┌';
+            break;
+          case 'm':
+            c = '\xc0'; // '└';
+            break;
+          case 'n':
+            c = '\xc5'; // '┼';
+            break;
+          case 'q':
+            c = '\xc4'; // '─';
+            break;
+          case 't':
+            c = '\xc3'; // '├';
+            break;
+          case 'u':
+            c = '\xb4'; // '┤';
+            break;
+          case 'v':
+            c = '\xc1'; // '┴';
+            break;
+          case 'w':
+            c = '\xc2'; // '┬';
+            break;
+          case 'x':
+            c = '\xb3'; // '│';
+            break;
+          default:
+            ZF_LOGE("DCS(0): I don't have translation for [%c]", c);
+          }
+          ZF_LOGE("DCS(0) Converted [%c] to [%c]", *iter, c);
+          *iter = c;
+        }
+      }
+    }
+
+    write(STDOUT_FILENO, buffer.data(), buffer.size());
+
+    buffer.clear();
+    buffer.insert(0, saved);
+    saved.clear();
+
+    /*
+    char input[2048];
+    char cp437[2048];
+
+    // is ansi codes iconv safe?  let's find out! TIAS!
+
+    strcpy(input, buffer.c_str());
+    converter.convert(input, cp437, sizeof(cp437));
+
+    ZF_LOGV_MEM(cp437, strlen(cp437), "IConv Buffer:");
+
+    write(STDOUT_FILENO, cp437, strlen(cp437));
+    */
+
+    // buffer.clear();
+  };
+}
+
+void help(void) {
+  printf("Usage:\n");
+  printf("Translate (default to internal)\n");
+  printf("\t-NOXLATE\tNo translation\n");
+  printf("\t-ICONV\tUse ICONV library\n");
+  printf("\t-NOLOG\tNo logging\n");
+  printf("\t-NOC\tDon't allow Ctrl-C\n");
+}
+
+int main(int argc, char *argv[]) {
+  int master;
+  pid_t pid;
+
+  // init_harry();
+  srandom(time(NULL));
+
+  /*
+    -NOXLATE   (No Translation, leave UTF-8 as-is)
+    -ICONV     (Use ICONV)
+    -NOTERM   (Don't translate the terminal input)
+    -NOLOG    (Disable logging)
+    -NOC      (No Ctrl-C, not the default)
+  */
+
+  translate =
+      ICONV; // Default to internal translation map (well, not initially.)
+
+  int CATCH_CTRLC = 0;
+  int NO_LOGGING = 0;
+  int TERM_XLATE = 1;
+
+  int x;
+  for (x = 1; x < argc; x++) {
+    if (strcasecmp("-NOXLATE", argv[x]) == 0) {
+      translate = NONE;
+      continue;
+    }
+    if (strcasecmp("-ICONV", argv[x]) == 0) {
+      translate = ICONV;
+      continue;
+    }
+    if (strcasecmp("-NOC", argv[x]) == 0) {
+      CATCH_CTRLC = 1;
+      continue;
+    }
+    if (strcasecmp("-NOLOG", argv[x]) == 0) {
+      NO_LOGGING = 1;
+      continue;
+    }
+    if (strcasecmp("-NOTERM", argv[x]) == 0) {
+      TERM_XLATE = 0;
+      continue;
+    }
+    if ((strcasecmp("-H", argv[x]) == 0) || (strcmp("-?", argv[x]) == 0)) {
+      // display help information.
+      help();
+      return 0;
+    }
+    if (argv[x][0] == '-') {
+      printf("I don't understand arg %s\n", argv[x]);
+      help();
+      return 0;
+    }
+    break;
+  }
+
+  if (x == argc) {
+    help();
+    return 0;
+  }
+
+  std::string logfile;
+  {
+    std::ostringstream buffer;
+
+    time_t now = time(NULL);
+    struct tm *tmp;
+    tmp = gmtime(&now);
+    // tmp->tm_mon
+
+    buffer << "doorman-" << tmp->tm_year + 1900 << "-" << std::setfill('0')
+           << std::setw(2) << tmp->tm_mon + 1 << "-" << std::setfill('0')
+           << std::setw(2) << tmp->tm_mday << ".log";
+    logfile = buffer.str();
+  };
+
+  if (NO_LOGGING) {
+    zf_log_set_output_level(ZF_LOG_NONE);
+  } else {
+    if (!file_output_open((const char *)logfile.c_str()))
+      return 2;
+  };
+
+  ZF_LOGE("DoorMan %s", version.c_str());
+
+  // Build the new command to run here
+  char *args[20]; // max 20 args
+  char *target_exec = argv[x];
+  ZF_LOGE("Target: (%d) [%s]", x, target_exec);
+
+  // build new args list
+  args[0] = target_exec;
+
+  // We have the target, skip to the next arg
+  if (x < argc)
+    ++x;
+
+  int ax = 1;
+  for (; x < argc; x++) {
+    args[ax] = argv[x];
+    ZF_LOGD("ARG %d : %s", ax, args[ax]);
+    ax++;
+  };
+
+  // null term the list
+  args[ax] = NULL;
+
+  pid = forkpty(&master, NULL, NULL, NULL);
+
+  // impossible to fork
+  if (pid < 0) {
+    return 1;
+  }
+
+  // child
+  else if (pid == 0) {
+    // This has been done up above.
+
+    /*
+    char *args[20]; // max 20 args
+    int x;
+    char new_exec[] = TARGET;
+
+    // build new args list
+    args[0] = new_exec;
+
+    for (x = 1; x < argc; x++) {
+      args[x] = argv[x];
+    };
+
+    // null term the list
+    args[x] = NULL;
+    */
+    // run TARGET, run!
+    /*
+    for (x = 0; args[x] != nullptr; x++) {
+      ZF_LOGD("%d : %s", x, args[x]);
+    }
+    */
+
+    execvp(target_exec, args);
+  }
+
+  // parent
+  else {
+    struct termios tios, orig1;
+    struct timeval timeout;
+
+    ZF_LOGD("starting");
+
+    tcgetattr(master, &tios);
+    tios.c_lflag &= ~(ECHO | ECHONL | ICANON);
+    /*
+    tios.c_iflag &= ~(ICRNL | IXON | BRKINT);
+    tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
+    tios.c_oflag &= ~(OPOST);
+    */
+    tcsetattr(master, TCSAFLUSH, &tios);
+
+    tcgetattr(1, &orig1);
+    tios = orig1;
+    /*
+    tios.c_iflag &= ~(ICRNL | IXON | BRKINT);
+    tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
+    tios.c_oflag &= ~(OPOST);
+    */
+
+    tios.c_iflag &= ~(ICRNL | IXON | ISTRIP | BRKINT);
+    tios.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
+    tios.c_oflag &= ~(OPOST);
+    tios.c_cflag |= (CS8);
+
+    // https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html
+    tcsetattr(1, TCSAFLUSH, &tios);
+
+    /*
+    This doesn't need to be static -- because it is part of
+    main.  Once main ends, we're done.
+    */
+#define BSIZE 128
+
+    std::string buffer;
+    buffer.reserve(BSIZE);
+
+    for (;;) {
+      // define estruturas para o select, que serve para verificar qual
+      // se tornou "pronto pra uso"
+      fd_set read_fd;
+      fd_set write_fd;
+      fd_set except_fd;
+
+      // inicializa as estruturas
+      FD_ZERO(&read_fd);
+      FD_ZERO(&write_fd);
+      FD_ZERO(&except_fd);
+
+      // atribui o descritor master, obtido pelo forkpty, ao read_fd
+      FD_SET(master, &read_fd);
+      // atribui o stdin ao read_fd
+      FD_SET(STDIN_FILENO, &read_fd);
+
+      // o descritor tem que ser unico para o programa, a documentacao
+      // recomenda um calculo entre os descritores sendo usados + 1
+
+      timeout.tv_sec = 0;
+      timeout.tv_usec = 0;
+      // if (select(master + 1, &read_fd, &write_fd, &except_fd, &timeout) == 0)
+      // {
+      if (select(master + 1, &read_fd, &write_fd, &except_fd, NULL) == 0) {
+        ZF_LOGI("TIMEOUT");
+        // This means timeout!
+      }
+
+      // read_fd esta atribuido com read_fd?
+      if (FD_ISSET(master, &read_fd)) {
+        // leia o que bc esta mandando
+        // ZF_LOGD("read (%d) %d bytes", size, BSIZE - size);
+        char read_buffer[BSIZE + 1];
+        int total;
+
+        // We may adjust this later on (adjusting read length).
+        if ((total = read(master, read_buffer, BSIZE)) != -1) {
+
+          // Ok, we've read more into the buffer.
+          ZF_LOGV("Read %d bytes", total);
+          buffer.append(read_buffer, total);
+
+          terminal_output(translate, buffer);
+
+          /*
+          if (0) { // zmodem) {
+            // ZF_LOGI("Buffer %lu bytes, zmodem...", buffer.size());
+
+            write(STDOUT_FILENO, buffer.data(), buffer.size());
+            // console_receive(&console, buffer);
+            buffer.clear();
+          } else {
+            // ZF_LOGV_MEM(buffer + size, total, "Read %d bytes:", total);
+            // size += total;
+            ZF_LOGV_MEM(buffer.data(), buffer.size(), "Buffer now:");
+
+            write(STDOUT_FILENO, buffer.data(), buffer.size());
+            // console_receive(&console, buffer);
+            buffer.clear();
+          }
+          */
+
+        } else
+          break;
+      }
+
+      // read_fd esta atribuido com a entrada padrao?
+      if (FD_ISSET(STDIN_FILENO, &read_fd)) {
+        // leia a entrada padrao
+        char input[BSIZE];
+        int r = read(STDIN_FILENO, &input, BSIZE);
+        input[r] = 0;
+        // e escreva no bc
+        ZF_LOGI("<< %s", repr(input));
+
+        if (CATCH_CTRLC) {
+          char *cp;
+          int found = 0;
+          while ((cp = strchr(input, '\x03')) != NULL) {
+            memmove(cp, cp + 1, strlen(cp));
+            r--;
+            found++;
+          }
+          ZF_LOGI("Removed %d ^Cs", found);
+        }
+
+        std::string input_str = input;
+
+        if (TERM_XLATE) {
+          // We're going to try it without any buffer or understanding of ANSI
+          // codes.
+          int c;
+
+          while (replace(input_str, "\x1b[A", "\x1bOA")) {
+            c++;
+          };
+          while (replace(input_str, "\x1b[B", "\x1bOB")) {
+            c++;
+          };
+          while (replace(input_str, "\x1b[C", "\x1bOC")) {
+            c++;
+          };
+          while (replace(input_str, "\x1b[D", "\x1bOD")) {
+            c++;
+          }
+
+          while (replace(input_str, "\x1b[U", "\x1b[6\x7e")) {
+            c++;
+          }
+          while (replace(input_str, "\x1b[V", "\x1b[5\x7e")) {
+            c++;
+          }
+          while (replace(input_str, "\x1b[H", "\x1bOH")) {
+            c++;
+          }
+          while (replace(input_str, "\x1b[K", "\x1bOF")) {
+            c++;
+          }
+          while (replace(input_str, "\x1b[@", "\x1b[2\x7e")) {
+            c++;
+          }
+          while (replace(input_str, "\x1b[1", "\x1bOR")) { // F3
+            c++;
+          }
+
+          while (replace(input_str, "\r\n", "\r")) {
+            c++;
+          }
+          if (c) {
+            ZF_LOGE("Input %d now [%s]", c, repr(input_str.c_str()));
+          }
+        }
+
+        // write(master, &input, r);
+        write(master, input_str.data(), input_str.size());
+
+        // This is INPUT from the USER
+        // ZF_LOGI_MEM( input, strlen(input), "<< ");
+      }
+    }
+
+    // Restore terminal
+    tcsetattr(1, TCSAFLUSH, &orig1);
+    ZF_LOGD("exit");
+  }
+
+  return 0;
+}

+ 11 - 0
logs_utils.cpp

@@ -0,0 +1,11 @@
+#include "logs_utils.h"
+#include "utils.h"
+#include "zf_log.h"
+#include <string>
+
+void ZF_LOGV_LR(const char *desc, std::string &buffer, int len) {
+  ZF_LOGV("%s", desc);
+  for (size_t i = 0; i < buffer.length(); i += len) {
+    ZF_LOGV("%s", logrepr(buffer.substr(i, len).c_str()));
+  }
+}

+ 8 - 0
logs_utils.h

@@ -0,0 +1,8 @@
+#ifndef LOGS_UTILS_H
+#define LOGS_UTILS_H
+
+#include <string>
+
+void ZF_LOGV_LR(const char *desc, std::string &buffer, int len = 64);
+
+#endif

File diff suppressed because it is too large
+ 59 - 0
notes/ANSI escape code - Wikipedia.html


+ 2146 - 0
notes/Console Virtual Terminal Sequences - Windows Console.html

@@ -0,0 +1,2146 @@
+<!DOCTYPE html>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+	
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+<html class="hasSidebar hasPageActions hasBreadcrumb conceptual has-default-focus theme-light" lang="en-us" dir="ltr" data-css-variable-support="true" data-authenticated="false" data-auth-status-determined="false" data-target="docs" x-ms-format-detection="none">
+
+<head>
+	<meta charset="utf-8" />
+	<meta name="viewport" content="width=device-width, initial-scale=1.0" />
+	<meta property="og:title" content="Console Virtual Terminal Sequences - Windows Console" />
+	<meta property="og:type" content="website" />
+	<meta property="og:url" content="https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences" />
+			<meta property="og:description" content="Virtual terminal sequences are control character sequences that can control cursor movement, color/font mode, and other operations when written to the output stream." />
+		<meta property="og:image" content="https://docs.microsoft.com/en-us/media/logos/logo-ms-social.png" />
+		<meta property="og:image:alt" content="Microsoft Logo" />
+
+	<meta name="twitter:card" content="summary" />
+	<meta name="twitter:site" content="@docsmsft" />
+
+
+	<meta name="author" content="bitcrazed" />
+<meta name="breadcrumb_path" content="/windows/console/breadcrumb/toc.json" />
+<meta name="depot_name" content="MSDN.console" />
+<meta name="description" content="Virtual terminal sequences are control character sequences that can control cursor movement, color/font mode, and other operations when written to the output stream." />
+<meta name="document_id" content="fc536527-f75d-5a03-7b5a-338a08e40d2f" />
+<meta name="document_version_independent_id" content="097f43d9-243f-466e-1f99-6cfa1f4bfb1f" />
+<meta name="gitcommit" content="https://github.com/MicrosoftDocs/Console-Docs/blob/5cca96a7f2833c54e226c41344a620cc1c253be6/docs/console-virtual-terminal-sequences.md" />
+<meta name="keywords" content="console, character mode applications, command line applications, terminal applications, console api" />
+<meta name="locale" content="en-us" />
+<meta name="ms.assetid" content="A5C553A5-FD84-4D16-A814-EDB3B8699B91" />
+<meta name="ms.author" content="richturn" />
+<meta name="ms.date" content="07/12/2018" />
+<meta name="ms.prod" content="console" />
+<meta name="ms.topic" content="article" />
+<meta name="MSHAttr" content="PreferredSiteName:MSDN" />
+<meta name="MSHAttr" content="PreferredLib:/library/windows/desktop" />
+<meta name="original_content_git_url" content="https://github.com/MicrosoftDocs/Console-Docs/blob/live/docs/console-virtual-terminal-sequences.md" />
+<meta name="search.ms_docsetname" content="console" />
+<meta name="search.ms_product" content="MSDN" />
+<meta name="search.ms_sitename" content="Docs" />
+<meta name="site_name" content="Docs" />
+<meta name="updated_at" content="2020-06-24 04:09 PM" />
+<meta name="page_type" content="conceptual" />
+<meta name="toc_rel" content="toc.json" />
+<meta name="word_count" content="5220" />
+
+
+	<meta name="scope" content="Console" />
+<link href="https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences" rel="canonical">
+	<title>Console Virtual Terminal Sequences - Windows Console | Microsoft Docs</title>
+
+		<link rel="stylesheet" href="/_themes/docs.theme/master/en-us/_themes/styles/62c7ff0c.site-ltr.css ">
+
+	<link rel="stylesheet" href="/_themes/docs.theme/master/en-us/_themes/styles/5a8a71c1.conceptual.css ">
+
+
+	<script>
+	var msDocs = {
+		data: {
+			timeOrigin: Date.now(),
+			contentLocale: 'en-us',
+			contentDir: 'ltr',
+			userLocale: 'en-us',
+			userDir: 'ltr',
+			pageTemplate: 'Conceptual',
+			brand: '',
+			context: {
+
+			},
+			hasBinaryRating: true,
+			hasGithubIssues: true,
+			showFeedbackReport: false,
+			enableTutorialFeedback: false,
+			feedbackSystem: 'GitHub',
+			feedbackGitHubRepo: 'MicrosoftDocs/Console-Docs',
+			feedbackProductUrl: 'https://github.com/microsoft/console/issues',
+			contentGitUrl: 'https://github.com/Microsoft/Console-Docs/blob/master/docs/console-virtual-terminal-sequences.md',
+			extendBreadcrumb: true,
+			isEditDisplayable: true,
+			hideViewSource: false,
+			hasPageActions: true,
+			hasBookmark: true,
+			hasShare: true
+		},
+		functions:{}
+	};
+	</script>
+	<script nomodule src="/static/third-party/bluebird/3.5.0/bluebird.min.js" integrity="sha384-aD4BDeDGeLXLpPK4yKeqtZQa9dv4a/7mQ+4L5vwshIYH1Mc2BrXvHd32iHzYCQy5" crossorigin="anonymous"></script>
+	<script nomodule src="/static/third-party/fetch/3.0.0/fetch.umd.min.js" integrity="sha384-EQIXrC5K2+7X8nGgLkB995I0/6jfAvvyG1ieZ+WYGxgJHFMD/alsG9fSDWvzb5Y1" crossorigin="anonymous"></script>
+	<script nomodule src="/static/third-party/template/1.4.0/template.min.js" integrity="sha384-1zKzI6ldTVHMU7n0W2HpE/lhHI+UG4D9IIaxbj3kT2UhCWicdTuJkTtnKuu0CQzN" crossorigin="anonymous"></script>
+	<script nomodule src="/static/third-party/url/0.5.7/url.min.js" integrity="sha384-vn7xBMtpSTfzaTRWxj0kVq0UcsbBrTOgZ/M1ISHqe1V358elYva+lfiEC+T8jLPc" crossorigin="anonymous"></script>
+	<script src="/_themes/docs.theme/master/en-us/_themes/scripts/7501e2bd.index-polyfills.js"></script>
+		<script src="/_themes/docs.theme/master/en-us/_themes/scripts/55f6956b.index-docs.js"></script>
+</head>
+
+<body lang="en-us" dir="ltr">
+<div class="header-holder has-default-focus">
+	<a href="#main" class="skip-to-main-link has-outline-color-text visually-hidden-until-focused is-fixed has-inner-focus focus-visible has-top-zero has-left-zero has-right-zero has-padding-medium has-text-centered has-body-background-medium" tabindex="1">Skip to main content</a>
+		<div id="headerAreaHolder" data-bi-name="header">
+<header role="banner" itemscope="itemscope" itemtype="http://schema.org/Organization">
+	<div class="nav-bar">
+		<div class="nav-bar-brand">
+			<a itemprop="url" href="https://www.microsoft.com" aria-label="Microsoft" class="nav-bar-button">
+			<div class="nav-bar-logo has-background-image theme-display is-light" role="presentation" aria-hidden="true" itemprop="logo" itemscope="itemscope"></div>
+			<div class="nav-bar-logo has-background-image theme-display is-dark is-high-contrast" role="presentation" aria-hidden="true" itemprop="logo" itemscope="itemscope"></div>
+		</a>
+		</div>
+		<div class="nav-bar-item is-hidden-tablet">
+			<button class="nav-bar-button nav-bar-menu" title="Global navigation">
+				<span class="nav-bar-menu-title"></span>
+				<div class="nav-bar-burger">
+					<span></span>
+					<span></span>
+					<span></span>
+					<span class="nav-bar-burger-label">Global navigation</span>
+				</div>
+			</button>
+		</div>
+		<nav class="nav-bar-nav" role="navigation" aria-label="Global">
+			<ul class="nav-bar-nav-list">
+				<li class="nav-bar-item is-category">
+					<a class="nav-bar-button is-title has-hover-underline" itemprop="url" href="/en-us/">
+						<span>Docs</span>
+					</a>
+				</li>
+				<li class="nav-bar-item">
+					<a class="nav-bar-button has-hover-underline" href="/en-us/">
+						<span>Documentation</span>
+					</a>
+				</li>
+				<li class="nav-bar-item">
+					<a class="nav-bar-button has-hover-underline" href="/en-us/learn/">
+						<span>Learn</span>
+					</a>
+				</li>
+				<li class="nav-bar-item">
+					<a class="nav-bar-button has-hover-underline" href="/en-us/samples/browse/">
+						<span>Code Samples</span>
+					</a>
+				</li>
+				<li class="dropdown nav-bar-item" hidden="">
+					<button aria-expanded="false" class="dropdown-trigger nav-bar-button has-hover-underline" aria-controls="ax-53">
+						<span>More</span>
+						<span class="nav-bar-button-chevron" aria-hidden="true">
+							<span class="docon docon-chevron-down-light expanded-indicator"></span>
+						</span>
+					</button>
+					<ul class="dropdown-menu" id="ax-53" aria-label="More">
+						<li class="nav-bar-item" hidden="">
+							<a class="nav-bar-button" href="/en-us/">Documentation</a>
+						</li>
+						<li class="nav-bar-item" hidden="">
+							<a class="nav-bar-button" href="/en-us/learn/">Learn</a>
+						</li>
+						<li class="nav-bar-item" hidden="">
+							<a class="nav-bar-button" href="/en-us/samples/browse/">Code Samples</a>
+						</li>
+					</ul>
+				</li>
+			</ul>
+		</nav>
+		<span class="nav-bar-spacer"></span>
+		<div class="nav-bar-item">
+		</div>
+		<div class="nav-bar-item is-size-small is-hidden-mobile">
+		</div>
+	</div>
+	<div class="nav-bar is-content">
+	</div>
+</header>		</div>
+		<div class="content-header uhf-container has-padding has-default-focus has-border-bottom-none" data-bi-name="content-header">
+			<nav class="has-padding-none has-padding-left-medium-tablet has-padding-right-medium-tablet has-padding-left-none-uhf-tablet has-padding-left-none-uhf-tablet has-padding-none-desktop has-flex-grow" data-bi-name="breadcrumb" itemscope itemtype="http://schema.org/BreadcrumbList" role="navigation" aria-label="Breadcrumb">
+				<ul id="page-breadcrumbs" class="breadcrumbs">
+				</ul>
+			</nav>
+		<div class="content-header-controls">
+			<button type="button" class="contents-button button" data-bi-name="contents-expand" aria-haspopup="true">
+				<span class="icon"><span class="docon docon-menu" aria-hidden="true"></span></span>
+				<span class="contents-expand-title">Contents</span>
+			</button>
+			<button type="button" class="ap-collapse-behavior ap-expanded button" data-bi-name="ap-collapse" aria-controls="action-panel">
+				<span class="icon"><span class="docon docon-exit-mode" aria-hidden="true"></span></span>
+				<span>Exit focus mode</span>
+			</button>
+		</div>
+		<div class="has-padding-none-tablet has-padding-medium is-size-small is-flex-touch has-flex-justify-content-space-between-touch has-flex-grow">
+			<ul class="is-hidden-mobile action-list has-flex-justify-content-start has-flex-justify-content-end-tablet is-flex is-flex-row has-flex-wrap has-flex-grow is-unstyled">
+				<li>
+					<button type="button" class="bookmark button is-text has-inner-focus is-small is-icon-only-touch" data-list-type="bookmarks" data-bi-name="bookmark" title="Bookmark this page">
+						<span class="icon" aria-hidden="true">
+							<span class="docon docon-single-bookmark"></span>
+						</span>
+						<span class="bookmark-status is-visually-hidden-touch is-hidden-portrait">Bookmark</span>
+					</button>
+				</li>
+					<li id="feedback-section-link">
+						<a href="#feedback" class="button is-text has-inner-focus is-small is-icon-only-touch" data-bi-name="comments" title="Send feedback about this page">
+							<span class="icon">
+								<span class="docon docon-comment-lines" aria-hidden="true"></span>
+							</span>
+							<span class="is-visually-hidden-touch is-hidden-portrait">Feedback</span>
+						</a>
+					</li>
+						<li id="contenteditbtn">
+							<a href="https://github.com/Microsoft/Console-Docs/blob/master/docs/console-virtual-terminal-sequences.md" class="button is-text has-inner-focus is-icon-only-touch is-small" title="Edit This Document" data-bi-name="edit" data-original_content_git_url="https://github.com/MicrosoftDocs/Console-Docs/blob/live/docs/console-virtual-terminal-sequences.md" data-original_content_git_url_template="{repo}/blob/{branch}/docs/console-virtual-terminal-sequences.md" data-pr_repo="" data-pr_branch="">
+							<span class="icon">
+								<span class="docon docon-edit-outline" aria-hidden="true"></span>
+							</span>
+							<span class="is-visually-hidden-touch is-hidden-portrait">Edit</span>
+						</a>
+					</li>
+				<li>
+<div class="sharing dropdown has-caret">
+	<button class="dropdown-trigger button is-text is-fullwidth has-flex-justify-content-start has-inner-focus is-small is-icon-only-touch" aria-controls="sharing-menu" aria-expanded="false" title="Share This Document" data-bi-name="share">
+		<span class="icon" aria-hidden="true">
+			<span class="docon docon-sharing"></span>
+		</span>
+		<span class="is-visually-hidden-touch is-hidden-portrait">Share</span>
+	</button>
+	<div class="dropdown-menu has-padding-small" id="sharing-menu">
+		<ul data-bi-name="share-links">
+			<li>
+				<a class="button is-text is-fullwidth has-flex-justify-content-start has-inner-focus is-small share-twitter" data-bi-name="twitter">
+					<span class="icon">
+						<span class="docon docon-brand-twitter has-text-primary" aria-hidden="true"></span>
+					</span>
+					<span>Twitter</span>
+				</a>
+			</li>
+			<li>
+				<a class="button is-text is-fullwidth has-flex-justify-content-start has-inner-focus is-small share-linkedin" data-bi-name="linkedin">
+					<span class="icon">
+						<span class="docon docon-brand-linkedin has-text-primary" aria-hidden="true"></span>
+					</span>
+					<span>LinkedIn</span>
+				</a>
+			</li>
+			<li>
+				<a class="button is-text is-fullwidth has-flex-justify-content-start has-inner-focus is-small share-facebook" data-bi-name="facebook">
+					<span class="icon">
+						<span class="docon docon-brand-facebook has-text-primary" aria-hidden="true"></span>
+					</span>
+					<span>Facebook</span>
+				</a>
+			</li>
+			<li>
+				<a class="button is-text is-fullwidth has-flex-justify-content-start has-inner-focus is-small share-email" data-bi-name="email">
+					<span class="icon">
+						<span class="docon docon-mail-message-fill has-text-primary" aria-hidden="true"></span>
+					</span>
+					<span>Email</span>
+				</a>
+			</li>
+		</ul>
+	</div>
+</div>				</li>
+			</ul>
+			<button type="button" class="has-border contents-button button is-small is-text is-hidden-tablet has-inner-focus" aria-label="Contents" data-bi-name="contents-expand">
+				<span class="icon">
+					<span class="docon docon-editor-list-bullet" aria-hidden="true"></span>
+				</span>
+				<span class="contents-expand-title">Table of contents</span>
+			</button>
+			<div class="is-invisible"></div>
+			<div class="is-hidden-tablet level-item is-flexible level-right">
+				<button type="button" class="page-actions-button button is-small is-text is-hidden-tablet has-inner-focus has-border is-full-height  has-margin-left-small" aria-label="Page Actions" data-bi-name="pageactions">
+					<span class="icon">
+						<span class="docon docon-more-vertical" aria-hidden="true"></span>
+					</span>
+				</button>
+			</div>
+		</div>
+	</div>
+
+	<div id="disclaimer-holder" class="has-overflow-hidden has-default-focus"></div>
+	</div>
+
+	<div class="mainContainer  uhf-container has-top-padding  has-default-focus" data-bi-name="body">
+
+		<div class="columns has-large-gaps is-gapless-mobile ">
+
+			<div id="left-container" class="left-container is-hidden-mobile column is-one-third-tablet is-one-quarter-desktop">
+				<nav id="affixed-left-container" class="is-fixed is-flex is-flex-column" role="navigation" aria-label="Primary"></nav>
+			</div>
+
+			<section class="primary-holder column is-two-thirds-tablet is-three-quarters-desktop">
+				<div class="columns is-gapless-mobile has-large-gaps ">
+
+
+				<div id="main-column" class="column  is-full is-four-fifths-desktop ">
+
+					<main id="main" role="main" class="content " data-bi-name="content" lang="en-us" dir="ltr">
+
+
+
+						<h1 id="console-virtual-terminal-sequences">Console Virtual Terminal Sequences</h1>
+
+						<ul class="metadata page-metadata" data-bi-name="page info" lang="en-us" dir="ltr">
+							<li>
+								<time class="is-invisible" data-article-date aria-label="Article review date" datetime="2018-07-12T00:00:00.000Z" data-article-date-source="ms.date">07/12/2018</time>
+							</li>
+								<li class="readingTime">26 minutes to read</li>
+								<li class="contributors-holder">
+									<a href="https://github.com/Microsoft/Console-Docs/blob/master/docs/console-virtual-terminal-sequences.md" title="10 Contributors" aria-label="10 Contributors">
+										<ul class="contributors" data-bi-name="contributors" aria-hidden="true">
+													<li><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNMsAcAAQUAoVnxsV8AAAAASUVORK5CYII=" data-src="https://github.com/bitcrazed.png?size=32" role="presentation"/></li>
+													<li><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNMsAcAAQUAoVnxsV8AAAAASUVORK5CYII=" data-src="https://github.com/GoBigorGoHome.png?size=32" role="presentation"/></li>
+													<li><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNMsAcAAQUAoVnxsV8AAAAASUVORK5CYII=" data-src="https://github.com/miniksa.png?size=32" role="presentation"/></li>
+													<li><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNMsAcAAQUAoVnxsV8AAAAASUVORK5CYII=" data-src="https://github.com/zadjii-msft.png?size=32" role="presentation"/></li>
+													<li><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNMsAcAAQUAoVnxsV8AAAAASUVORK5CYII=" data-src="https://github.com/JPRuskin.png?size=32" role="presentation"/></li>
+											<li><span class="is-size-extra-small has-text-subtle">+5</span></li>
+										</ul>
+									</a>
+								</li>
+						</ul>
+
+						<nav id="center-doc-outline" class="doc-outline is-hidden-desktop" data-bi-name="intopic toc" role="navigation" aria-label="Article Outline">
+							<h3>In this article</h3>
+						</nav>
+
+						<!-- <content> -->
+							<p>Virtual terminal sequences are control character sequences that can control cursor movement, color/font mode, and other operations when written to the output stream. Sequences may also be received on the input stream in response to an output stream query information sequence or as an encoding of user input when the appropriate mode is set.</p>
+<p>You can use <a href="getconsolemode" data-linktype="relative-path"><strong>GetConsoleMode</strong></a> and <a href="setconsolemode" data-linktype="relative-path"><strong>SetConsoleMode</strong></a> functions to configure this behavior. A sample of the suggested way to enable virtual terminal behaviors is included at the end of this document.</p>
+<p>The behavior of the following sequences is based on the VT100 and derived terminal emulator technologies, most specifically the xterm terminal emulator. More information about terminal sequences can be found at <a href="http://vt100.net" data-linktype="external">http://vt100.net</a> and at <a href="http://invisible-island.net/xterm/ctlseqs/ctlseqs.html" data-linktype="external">http://invisible-island.net/xterm/ctlseqs/ctlseqs.html</a>.</p>
+<h2 id="output-sequences"><span id="Output_Sequences"></span><span id="output_sequences"></span><span id="OUTPUT_SEQUENCES"></span>Output Sequences</h2>
+<p>The following terminal sequences are intercepted by the console host when written into the output stream, if the ENABLE_VIRTUAL_TERMINAL_PROCESSING flag is set on the screen buffer handle using the <a href="setconsolemode" data-linktype="relative-path"><strong>SetConsoleMode</strong></a> function. Note that the DISABLE_NEWLINE_AUTO_RETURN flag may also be useful in emulating the cursor positioning and scrolling behavior of other terminal emulators in relation to characters written to the final column in any row.</p>
+<h2 id="simple-cursor-positioning"><span id="Simple_Cursor_Positioning"></span><span id="simple_cursor_positioning"></span><span id="SIMPLE_CURSOR_POSITIONING"></span>Simple Cursor Positioning</h2>
+<p>In all of the following descriptions, ESC is always the hexadecimal value 0x1B. No spaces are to be included in terminal sequences. For an example of how these sequences are used in practice, please see the <a href="#example" data-linktype="self-bookmark">example</a> at the end of this topic.</p>
+<p>The following table describes simple escape sequences with a single action command directly after the ESC character. These sequences have no parameters and take effect immediately.</p>
+<p>All commands in this table are generally equivalent to calling the <a href="setconsolecursorposition" data-linktype="relative-path"><strong>SetConsoleCursorPosition</strong></a> console API to place the cursor.</p>
+<p>Cursor movement will be bounded by the current viewport into the buffer. Scrolling (if available) will not occur.</p>
+<table>
+<thead>
+<tr>
+<th>Sequence</th>
+<th>Shorthand</th>
+<th>Behavior</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>ESC A</td>
+<td>CUU</td>
+<td>Cursor Up by 1</td>
+</tr>
+<tr>
+<td>ESC B</td>
+<td>CUD</td>
+<td>Cursor Down by 1</td>
+</tr>
+<tr>
+<td>ESC C</td>
+<td>CUF</td>
+<td>Cursor Forward (Right) by 1</td>
+</tr>
+<tr>
+<td>ESC D</td>
+<td>CUB</td>
+<td>Cursor Backward (Left) by 1</td>
+</tr>
+<tr>
+<td>ESC M</td>
+<td>RI</td>
+<td>Reverse Index – Performs the reverse operation of \n, moves cursor up one line, maintains horizontal position, scrolls buffer if necessary*</td>
+</tr>
+<tr>
+<td>ESC 7</td>
+<td>DECSC</td>
+<td>Save Cursor Position in Memory**</td>
+</tr>
+<tr>
+<td>ESC 8</td>
+<td>DECSR</td>
+<td>Restore Cursor Position from Memory**</td>
+</tr>
+</tbody>
+</table>
+<p><strong>Note</strong><br/>
+* If there are scroll margins set, RI inside the margins will scroll only the contents of the margins, and leave the viewport unchanged. (See Scrolling Margins)</p>
+<p>**There will be no value saved in memory until the first use of the save command. The only way to access the saved value is with the restore command.</p>
+<h2 id="cursor-positioning"><span id="Cursor_Positioning"></span><span id="cursor_positioning"></span><span id="CURSOR_POSITIONING"></span>Cursor Positioning</h2>
+<p>The following tables encompass Control Sequence Introducer (CSI) type sequences. All CSI sequences start with ESC (0x1B) followed by [ (left bracket, 0x5B) and may contain parameters of variable length to specify more information for each operation. This will be represented by the shorthand &lt;n&gt;. Each table below is grouped by functionality with notes below each table explaining how the group works.</p>
+<p>For all parameters, the following rules apply unless otherwise noted:</p>
+<ul>
+<li>&lt;n&gt; represents the distance to move and is an optional parameter</li>
+<li>If &lt;n&gt; is omitted or equals 0, it will be treated as a 1</li>
+<li>&lt;n&gt; cannot be larger than 32,767 (maximum short value)</li>
+<li>&lt;n&gt; cannot be negative</li>
+</ul>
+<p>All commands in this section are generally equivalent to calling the <a href="setconsolecursorposition" data-linktype="relative-path"><strong>SetConsoleCursorPosition</strong></a> console API.</p>
+<p>Cursor movement will be bounded by the current viewport into the buffer. Scrolling (if available) will not occur.</p>
+<table>
+<thead>
+<tr>
+<th>Sequence</th>
+<th>Code</th>
+<th>Description</th>
+<th>Behavior</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>ESC [ &lt;n&gt; A</td>
+<td>CUU</td>
+<td>Cursor Up</td>
+<td>Cursor up by &lt;n&gt;</td>
+</tr>
+<tr>
+<td>ESC [ &lt;n&gt; B</td>
+<td>CUD</td>
+<td>Cursor Down</td>
+<td>Cursor down by &lt;n&gt;</td>
+</tr>
+<tr>
+<td>ESC [ &lt;n&gt; C</td>
+<td>CUF</td>
+<td>Cursor Forward</td>
+<td>Cursor forward (Right) by &lt;n&gt;</td>
+</tr>
+<tr>
+<td>ESC [ &lt;n&gt; D</td>
+<td>CUB</td>
+<td>Cursor Backward</td>
+<td>Cursor backward (Left) by &lt;n&gt;</td>
+</tr>
+<tr>
+<td>ESC [ &lt;n&gt; E</td>
+<td>CNL</td>
+<td>Cursor Next Line</td>
+<td>Cursor down to beginning of &lt;n&gt;th line in the viewport</td>
+</tr>
+<tr>
+<td>ESC [ &lt;n&gt; F</td>
+<td>CPL</td>
+<td>Cursor Previous Line</td>
+<td>Cursor up to beginning of &lt;n&gt;th line in the viewport</td>
+</tr>
+<tr>
+<td>ESC [ &lt;n&gt; G</td>
+<td>CHA</td>
+<td>Cursor Horizontal Absolute</td>
+<td>Cursor moves to &lt;n&gt;th position horizontally in the current line</td>
+</tr>
+<tr>
+<td>ESC [ &lt;n&gt; d</td>
+<td>VPA</td>
+<td>Vertical Line Position Absolute</td>
+<td>Cursor moves to the &lt;n&gt;th position vertically in the current column</td>
+</tr>
+<tr>
+<td>ESC [ &lt;y&gt; ; &lt;x&gt; H</td>
+<td>CUP</td>
+<td>Cursor Position</td>
+<td>*Cursor moves to &lt;x&gt;; &lt;y&gt; coordinate within the viewport, where &lt;x&gt; is the column of the &lt;y&gt; line</td>
+</tr>
+<tr>
+<td>ESC [ &lt;y&gt; ; &lt;x&gt; f</td>
+<td>HVP</td>
+<td>Horizontal Vertical Position</td>
+<td>*Cursor moves to &lt;x&gt;; &lt;y&gt; coordinate within the viewport, where &lt;x&gt; is the column of the &lt;y&gt; line</td>
+</tr>
+<tr>
+<td>ESC [ s</td>
+<td>ANSISYSSC</td>
+<td>Save Cursor – Ansi.sys emulation</td>
+<td>**With no parameters, performs a save cursor operation like DECSC</td>
+</tr>
+<tr>
+<td>ESC [ u</td>
+<td>ANSISYSSC</td>
+<td>Restore Cursor – Ansi.sys emulation</td>
+<td>**With no parameters, performs a restore cursor operation like DECRC</td>
+</tr>
+</tbody>
+</table>
+<p><strong>Note</strong><br/>
+*&lt;x&gt; and &lt;y&gt; parameters have the same limitations as &lt;n&gt; above. If &lt;x&gt; and &lt;y&gt; are omitted, they will be set to 1;1.</p>
+<p>**ANSI.sys historical documentation can be found at <a href="https://msdn.microsoft.com/library/cc722862.aspx" data-linktype="external">https://msdn.microsoft.com/library/cc722862.aspx</a> and is implemented for convenience/compatibility.</p>
+<h2 id="cursor-visibility"><span id="Cursor_Visibility"></span><span id="cursor_visibility"></span><span id="CURSOR_VISIBILITY"></span>Cursor Visibility</h2>
+<p>The following commands control the visibility of the cursor and its blinking state. The DECTCEM sequences are generally equivalent to calling <a href="setconsolecursorinfo" data-linktype="relative-path"><strong>SetConsoleCursorInfo</strong></a> console API to toggle cursor visibility.</p>
+<table>
+<thead>
+<tr>
+<th>Sequence</th>
+<th>Code</th>
+<th>Description</th>
+<th>Behavior</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>ESC [ ? 12 h</td>
+<td>ATT160</td>
+<td>Text Cursor Enable Blinking</td>
+<td>Start the cursor blinking</td>
+</tr>
+<tr>
+<td>ESC [ ? 12 l</td>
+<td>ATT160</td>
+<td>Text Cursor Disable Blinking</td>
+<td>Stop blinking the cursor</td>
+</tr>
+<tr>
+<td>ESC [ ? 25 h</td>
+<td>DECTCEM</td>
+<td>Text Cursor Enable Mode Show</td>
+<td>Show the cursor</td>
+</tr>
+<tr>
+<td>ESC [ ? 25 l</td>
+<td>DECTCEM</td>
+<td>Text Cursor Enable Mode Hide</td>
+<td>Hide the cursor</td>
+</tr>
+</tbody>
+</table>
+<h2 id="viewport-positioning"><span id="Viewport_Positioning"></span><span id="viewport_positioning"></span><span id="VIEWPORT_POSITIONING"></span>Viewport Positioning</h2>
+<p>All commands in this section are generally equivalent to calling <a href="scrollconsolescreenbuffer" data-linktype="relative-path"><strong>ScrollConsoleScreenBuffer</strong></a> console API to move the contents of the console buffer.</p>
+<p><strong>Caution</strong>  The command names are misleading. Scroll refers to which direction the text moves during the operation, not which way the viewport would seem to move.</p>
+<table>
+<thead>
+<tr>
+<th>Sequence</th>
+<th>Code</th>
+<th>Description</th>
+<th>Behavior</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>ESC [ &lt;n&gt; S</td>
+<td>SU</td>
+<td>Scroll Up</td>
+<td>Scroll text up by &lt;n&gt;. Also known as pan down, new lines fill in from the bottom of the screen</td>
+</tr>
+<tr>
+<td>ESC [ &lt;n&gt; T</td>
+<td>SD</td>
+<td>Scroll Down</td>
+<td>Scroll down by &lt;n&gt;. Also known as pan up, new lines fill in from the top of the screen</td>
+</tr>
+</tbody>
+</table>
+<p>The text is moved starting with the line the cursor is on. If the cursor is on the middle row of the viewport, then scroll up would move the bottom half of the viewport, and insert blank lines at the bottom. Scroll down would move the top half of the viewport’s rows, and insert new lines at the top.</p>
+<p>Also important to note is scroll up and down are also affected by the scrolling margins. Scroll up and down won’t affect any lines outside the scrolling margins.</p>
+<p>The default value for &lt;n&gt; is 1, and the value can be optionally omitted.</p>
+<h2 id="text-modification"><span id="Text_Modification"></span><span id="text_modification"></span><span id="TEXT_MODIFICATION"></span>Text Modification</h2>
+<p>All commands in this section are generally equivalent to calling <a href="fillconsoleoutputcharacter" data-linktype="relative-path"><strong>FillConsoleOutputCharacter</strong></a>, <a href="fillconsoleoutputattribute" data-linktype="relative-path"><strong>FillConsoleOutputAttribute</strong></a>, and <a href="scrollconsolescreenbuffer" data-linktype="relative-path"><strong>ScrollConsoleScreenBuffer</strong></a> console APIs to modify the text buffer contents.</p>
+<table>
+<thead>
+<tr>
+<th>Sequence</th>
+<th>Code</th>
+<th>Description</th>
+<th>Behavior</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>ESC [ &lt;n&gt; @</td>
+<td>ICH</td>
+<td>Insert Character</td>
+<td>Insert &lt;n&gt; spaces at the current cursor position, shifting all existing text to the right. Text exiting the screen to the right is removed.</td>
+</tr>
+<tr>
+<td>ESC [ &lt;n&gt; P</td>
+<td>DCH</td>
+<td>Delete Character</td>
+<td>Delete &lt;n&gt; characters at the current cursor position, shifting in space characters from the right edge of the screen.</td>
+</tr>
+<tr>
+<td>ESC [ &lt;n&gt; X</td>
+<td>ECH</td>
+<td>Erase Character</td>
+<td>Erase &lt;n&gt; characters from the current cursor position by overwriting them with a space character.</td>
+</tr>
+<tr>
+<td>ESC [ &lt;n&gt; L</td>
+<td>IL</td>
+<td>Insert Line</td>
+<td>Inserts &lt;n&gt; lines into the buffer at the cursor position. The line the cursor is on, and lines below it, will be shifted downwards.</td>
+</tr>
+<tr>
+<td>ESC [ &lt;n&gt; M</td>
+<td>DL</td>
+<td>Delete Line</td>
+<td>Deletes &lt;n&gt; lines from the buffer, starting with the row the cursor is on.</td>
+</tr>
+</tbody>
+</table>
+<p><strong>Note</strong><br/>
+For IL and DL, only the lines in the scrolling margins (see Scrolling Margins) are affected. If no margins are set, the default margin borders are the current viewport. If lines would be shifted below the margins, they are discarded. When lines are deleted, blank lines are inserted at the bottom of the margins, lines from outside the viewport are never affected.</p>
+<p>For each of the sequences, the default value for &lt;n&gt; if it is omitted is 0.</p>
+<p>For the following commands, the parameter &lt;n&gt; has 3 valid values:</p>
+<ul>
+<li>0 erases from the current cursor position (inclusive) to the end of the line/display</li>
+<li>1 erases from the beginning of the line/display up to and including the current cursor position</li>
+<li>2 erases the entire line/display</li>
+</ul>
+<table>
+<thead>
+<tr>
+<th>Sequence</th>
+<th>Code</th>
+<th>Description</th>
+<th>Behavior</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>ESC [ &lt;n&gt; J</td>
+<td>ED</td>
+<td>Erase in Display</td>
+<td>Replace all text in the current viewport/screen specified by &lt;n&gt; with space characters</td>
+</tr>
+<tr>
+<td>ESC [ &lt;n&gt; K</td>
+<td>EL</td>
+<td>Erase in Line</td>
+<td>Replace all text on the line with the cursor specified by &lt;n&gt; with space characters</td>
+</tr>
+</tbody>
+</table>
+<h2 id="text-formatting"><span id="Text_Formatting"></span><span id="text_formatting"></span><span id="TEXT_FORMATTING"></span>Text Formatting</h2>
+<p>All commands in this section are generally equivalent to calling <a href="setconsoletextattribute" data-linktype="relative-path"><strong>SetConsoleTextAttribute</strong></a> console APIs to adjust the formatting of all future writes to the console output text buffer.</p>
+<p>This command is special in that the &lt;n&gt; position below can accept between 0 and 16 parameters separated by semicolons.</p>
+<p>When no parameters are specified, it is treated the same as a single 0 parameter.</p>
+<table>
+<thead>
+<tr>
+<th>Sequence</th>
+<th>Code</th>
+<th>Description</th>
+<th>Behavior</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>ESC [ &lt;n&gt; m</td>
+<td>SGR</td>
+<td>Set Graphics Rendition</td>
+<td>Set the format of the screen and text as specified by &lt;n&gt;</td>
+</tr>
+</tbody>
+</table>
+<p>The following table of values can be used in &lt;n&gt; to represent different formatting modes.</p>
+<p>Formatting modes are applied from left to right. Applying competing formatting options will result in the right-most option taking precedence.</p>
+<p>For options that specify colors, the colors will be used as defined in the console color table which can be modified using the <a href="setconsolescreenbufferinfoex" data-linktype="relative-path"><strong>SetConsoleScreenBufferInfoEx</strong></a> API. If the table is modified to make the “blue” position in the table display an RGB shade of red, then all calls to <strong>Foreground Blue</strong> will display that red color until otherwise changed.</p>
+<table>
+<thead>
+<tr>
+<th>Value</th>
+<th>Description</th>
+<th>Behavior</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>0</td>
+<td>Default</td>
+<td>Returns all attributes to the default state prior to modification</td>
+</tr>
+<tr>
+<td>1</td>
+<td>Bold/Bright</td>
+<td>Applies brightness/intensity flag to foreground color</td>
+</tr>
+<tr>
+<td>4</td>
+<td>Underline</td>
+<td>Adds underline</td>
+</tr>
+<tr>
+<td>24</td>
+<td>No underline</td>
+<td>Removes underline</td>
+</tr>
+<tr>
+<td>7</td>
+<td>Negative</td>
+<td>Swaps foreground and background colors</td>
+</tr>
+<tr>
+<td>27</td>
+<td>Positive (No negative)</td>
+<td>Returns foreground/background to normal</td>
+</tr>
+<tr>
+<td>30</td>
+<td>Foreground Black</td>
+<td>Applies non-bold/bright black to foreground</td>
+</tr>
+<tr>
+<td>31</td>
+<td>Foreground Red</td>
+<td>Applies non-bold/bright red to foreground</td>
+</tr>
+<tr>
+<td>32</td>
+<td>Foreground Green</td>
+<td>Applies non-bold/bright green to foreground</td>
+</tr>
+<tr>
+<td>33</td>
+<td>Foreground Yellow</td>
+<td>Applies non-bold/bright yellow to foreground</td>
+</tr>
+<tr>
+<td>34</td>
+<td>Foreground Blue</td>
+<td>Applies non-bold/bright blue to foreground</td>
+</tr>
+<tr>
+<td>35</td>
+<td>Foreground Magenta</td>
+<td>Applies non-bold/bright magenta to foreground</td>
+</tr>
+<tr>
+<td>36</td>
+<td>Foreground Cyan</td>
+<td>Applies non-bold/bright cyan to foreground</td>
+</tr>
+<tr>
+<td>37</td>
+<td>Foreground White</td>
+<td>Applies non-bold/bright white to foreground</td>
+</tr>
+<tr>
+<td>38</td>
+<td>Foreground Extended</td>
+<td>Applies extended color value to the foreground (see details below)</td>
+</tr>
+<tr>
+<td>39</td>
+<td>Foreground Default</td>
+<td>Applies only the foreground portion of the defaults (see 0)</td>
+</tr>
+<tr>
+<td>40</td>
+<td>Background Black</td>
+<td>Applies non-bold/bright black to background</td>
+</tr>
+<tr>
+<td>41</td>
+<td>Background Red</td>
+<td>Applies non-bold/bright red to background</td>
+</tr>
+<tr>
+<td>42</td>
+<td>Background Green</td>
+<td>Applies non-bold/bright green to background</td>
+</tr>
+<tr>
+<td>43</td>
+<td>Background Yellow</td>
+<td>Applies non-bold/bright yellow to background</td>
+</tr>
+<tr>
+<td>44</td>
+<td>Background Blue</td>
+<td>Applies non-bold/bright blue to background</td>
+</tr>
+<tr>
+<td>45</td>
+<td>Background Magenta</td>
+<td>Applies non-bold/bright magenta to background</td>
+</tr>
+<tr>
+<td>46</td>
+<td>Background Cyan</td>
+<td>Applies non-bold/bright cyan to background</td>
+</tr>
+<tr>
+<td>47</td>
+<td>Background White</td>
+<td>Applies non-bold/bright white to background</td>
+</tr>
+<tr>
+<td>48</td>
+<td>Background Extended</td>
+<td>Applies extended color value to the background (see details below)</td>
+</tr>
+<tr>
+<td>49</td>
+<td>Background Default</td>
+<td>Applies only the background portion of the defaults (see 0)</td>
+</tr>
+<tr>
+<td>90</td>
+<td>Bright Foreground Black</td>
+<td>Applies bold/bright black to foreground</td>
+</tr>
+<tr>
+<td>91</td>
+<td>Bright Foreground Red</td>
+<td>Applies bold/bright red to foreground</td>
+</tr>
+<tr>
+<td>92</td>
+<td>Bright Foreground Green</td>
+<td>Applies bold/bright green to foreground</td>
+</tr>
+<tr>
+<td>93</td>
+<td>Bright Foreground Yellow</td>
+<td>Applies bold/bright yellow to foreground</td>
+</tr>
+<tr>
+<td>94</td>
+<td>Bright Foreground Blue</td>
+<td>Applies bold/bright blue to foreground</td>
+</tr>
+<tr>
+<td>95</td>
+<td>Bright Foreground Magenta</td>
+<td>Applies bold/bright magenta to foreground</td>
+</tr>
+<tr>
+<td>96</td>
+<td>Bright Foreground Cyan</td>
+<td>Applies bold/bright cyan to foreground</td>
+</tr>
+<tr>
+<td>97</td>
+<td>Bright Foreground White</td>
+<td>Applies bold/bright white to foreground</td>
+</tr>
+<tr>
+<td>100</td>
+<td>Bright Background Black</td>
+<td>Applies bold/bright black to background</td>
+</tr>
+<tr>
+<td>101</td>
+<td>Bright Background Red</td>
+<td>Applies bold/bright red to background</td>
+</tr>
+<tr>
+<td>102</td>
+<td>Bright Background Green</td>
+<td>Applies bold/bright green to background</td>
+</tr>
+<tr>
+<td>103</td>
+<td>Bright Background Yellow</td>
+<td>Applies bold/bright yellow to background</td>
+</tr>
+<tr>
+<td>104</td>
+<td>Bright Background Blue</td>
+<td>Applies bold/bright blue to background</td>
+</tr>
+<tr>
+<td>105</td>
+<td>Bright Background Magenta</td>
+<td>Applies bold/bright magenta to background</td>
+</tr>
+<tr>
+<td>106</td>
+<td>Bright Background Cyan</td>
+<td>Applies bold/bright cyan to background</td>
+</tr>
+<tr>
+<td>107</td>
+<td>Bright Background White</td>
+<td>Applies bold/bright white to background</td>
+</tr>
+</tbody>
+</table>
+<h3 id="extended-colors"><span id="Extended_Colors"></span><span id="extended_colors"></span><span id="EXTENDED_COLORS"></span>Extended Colors</h3>
+<p>Some virtual terminal emulators support a palette of colors greater than the 16 colors provided by the Windows Console. For these extended colors, the Windows Console will choose the nearest appropriate color from the existing 16 color table for display. Unlike typical SGR values above, the extended values will consume additional parameters after the initial indicator according to the table below.</p>
+<table>
+<thead>
+<tr>
+<th>SGR Subsequence</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>38 ; 2 ; &lt;r&gt; ; &lt;g&gt; ; &lt;b&gt;</td>
+<td>Set foreground color to RGB value specified in &lt;r&gt;, &lt;g&gt;, &lt;b&gt; parameters*</td>
+</tr>
+<tr>
+<td>48 ; 2 ; &lt;r&gt; ; &lt;g&gt; ; &lt;b&gt;</td>
+<td>Set background color to RGB value specified in &lt;r&gt;, &lt;g&gt;, &lt;b&gt; parameters*</td>
+</tr>
+<tr>
+<td>38 ; 5 ; &lt;s&gt;</td>
+<td>Set foreground color to &lt;s&gt; index in 88 or 256 color table*</td>
+</tr>
+<tr>
+<td>48 ; 5 ; &lt;s&gt;</td>
+<td>Set background color to &lt;s&gt; index in 88 or 256 color table*</td>
+</tr>
+</tbody>
+</table>
+<p>*The 88 and 256 color palettes maintained internally for comparison are based from the xterm terminal emulator. The comparison/rounding tables cannot be modified at this time.</p>
+<h2 id="screen-colors"><span id="Screen_Colors"></span><span id="screen_colors"></span><span id="SCREEN_COLORS"></span>Screen Colors</h2>
+<p>The following command allows the application to set the screen colors palette values to any RGB value.</p>
+<p>The RGB values should be hexadecimal values between <code>0</code> and <code>ff</code>, and separated by the forward-slash character (e.g. <code>rgb:1/24/86</code>).</p>
+<p>Note that this sequence is an OSC “Operating system command” sequence, and not a CSI like many of the other sequences listed, and as such start with “\x1b]”, not “\x1b[”.</p>
+<table>
+<thead>
+<tr>
+<th>Sequence</th>
+<th>Description</th>
+<th>Behavior</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>ESC ] 4 ; &lt;i&gt; ; rgb : &lt;r&gt; / &lt;g&gt; / &lt;b&gt; ESC</td>
+<td>Modify Screen Colors</td>
+<td>Sets the screen color palette index &lt;i&gt; to the RGB values specified in &lt;r&gt;, &lt;g&gt;, &lt;b&gt;</td>
+</tr>
+</tbody>
+</table>
+<h2 id="mode-changes"><span id="Mode_Changes__"></span><span id="mode_changes__"></span><span id="MODE_CHANGES__"></span>Mode Changes</h2>
+<p>These are sequences that control the input modes. There are two different sets of input modes, the Cursor Keys Mode and the Keypad Keys Mode. The Cursor Keys Mode controls the sequences that are emitted by the arrow keys as well as Home and End, while the Keypad Keys Mode controls the sequences emitted by the keys on the numpad primarily, as well as the function keys.</p>
+<p>Each of these modes are simple boolean settings – the Cursor Keys Mode is either Normal (default) or Application, and the Keypad Keys Mode is either Numeric (default) or Application.</p>
+<p>See the Cursor Keys and Numpad &amp; Function Keys sections for the sequences emitted in these modes.</p>
+<table>
+<thead>
+<tr>
+<th>Sequence</th>
+<th>Code</th>
+<th>Description</th>
+<th>Behavior</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>ESC =</td>
+<td>DECKPAM</td>
+<td>Enable Keypad Application Mode</td>
+<td>Keypad keys will emit their Application Mode sequences.</td>
+</tr>
+<tr>
+<td>ESC &gt;</td>
+<td>DECKPNM</td>
+<td>Enable Keypad Numeric Mode</td>
+<td>Keypad keys will emit their Numeric Mode sequences.</td>
+</tr>
+<tr>
+<td>ESC [ ? 1 h</td>
+<td>DECCKM</td>
+<td>Enable Cursor Keys Application Mode</td>
+<td>Keypad keys will emit their Application Mode sequences.</td>
+</tr>
+<tr>
+<td>ESC [ ? 1 l</td>
+<td>DECCKM</td>
+<td>Disable Cursor Keys Application Mode (use Normal Mode)</td>
+<td>Keypad keys will emit their Numeric Mode sequences.</td>
+</tr>
+</tbody>
+</table>
+<h2 id="query-state"><span id="Query_State"></span><span id="query_state"></span><span id="QUERY_STATE"></span>Query State</h2>
+<p>All commands in this section are generally equivalent to calling Get* console APIs to retrieve status information about the current console buffer state.</p>
+<p><strong>Note</strong>  These queries will emit their responses into the console input stream immediately after being recognized on the output stream while ENABLE_VIRTUAL_TERMINAL_PROCESSING is set. The ENABLE_VIRTUAL_TERMINAL_INPUT flag does not apply to query commands as it is assumed that an application making the query will always want to receive the reply.</p>
+<table>
+<thead>
+<tr>
+<th>Sequence</th>
+<th>Code</th>
+<th>Description</th>
+<th>Behavior</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>ESC [ 6 n</td>
+<td>DECXCPR</td>
+<td>Report Cursor Position</td>
+<td>Emit the cursor position as: ESC [ &lt;r&gt; ; &lt;c&gt; R Where &lt;r&gt; = cursor row and &lt;c&gt; = cursor column</td>
+</tr>
+<tr>
+<td>ESC [ 0 c</td>
+<td>DA</td>
+<td>Device Attributes</td>
+<td>Report the terminal identity. Will emit “\x1b[?1;0c”, indicating &quot;VT101 with No Options&quot;.</td>
+</tr>
+</tbody>
+</table>
+<h2 id="tabs"><span id="Tabs"></span><span id="tabs"></span><span id="TABS"></span>Tabs</h2>
+<p>While the windows console traditionally expects tabs to be exclusively eight characters wide, *nix applications utilizing certain sequences can manipulate where the tab stops are within the console windows to optimize cursor movement by the application.</p>
+<p>The following sequences allow an application to set the tab stop locations within the console window, remove them, and navigate between them.</p>
+<table>
+<thead>
+<tr>
+<th>Sequence</th>
+<th>Code</th>
+<th>Description</th>
+<th>Behavior</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>ESC H</td>
+<td>HTS</td>
+<td>Horizontal Tab Set</td>
+<td>Sets a tab stop in the current column the cursor is in.</td>
+</tr>
+<tr>
+<td>ESC [ &lt;n&gt; I</td>
+<td>CHT</td>
+<td>Cursor Horizontal (Forward) Tab</td>
+<td>Advance the cursor to the next column (in the same row) with a tab stop. If there are no more tab stops, move to the last column in the row. If the cursor is in the last column, move to the first column of the next row.</td>
+</tr>
+<tr>
+<td>ESC [ &lt;n&gt; Z</td>
+<td>CBT</td>
+<td>Cursor Backwards Tab</td>
+<td>Move the cursor to the previous column (in the same row) with a tab stop. If there are no more tab stops, moves the cursor to the first column. If the cursor is in the first column, doesn’t move the cursor.</td>
+</tr>
+<tr>
+<td>ESC [ 0 g</td>
+<td>TBC</td>
+<td>Tab Clear (current column)</td>
+<td>Clears the tab stop in the current column, if there is one. Otherwise does nothing.</td>
+</tr>
+<tr>
+<td>ESC [ 3 g</td>
+<td>TBC</td>
+<td>Tab Clear (all columns)</td>
+<td>Clears all currently set tab stops.</td>
+</tr>
+</tbody>
+</table>
+<ul>
+<li>For both CHT and CBT, &lt;n&gt; is an optional parameter that (default=1) indicating how many times to advance the cursor in the specified direction.</li>
+<li>If there are no tab stops set via HTS, CHT and CBT will treat the first and last columns of the window as the only two tab stops.</li>
+<li>Using HTS to set a tab stop will also cause the console to navigate to the next tab stop on the output of a TAB (0x09, ‘\t’) character, in the same manner as CHT.</li>
+</ul>
+<h2 id="designate-character-set"><span id="Designate_Character_Set"></span><span id="designate_character_set"></span><span id="DESIGNATE_CHARACTER_SET"></span>Designate Character Set</h2>
+<p>The following sequences allow a program to change the active character set mapping. This allows a program to emit 7-bit ASCII characters, but have them displayed as other glyphs on the terminal screen itself. Currently, the only two supported character sets are ASCII (default) and the DEC Special Graphics Character Set. See <a href="http://vt100.net/docs/vt220-rm/table2-4.html" data-linktype="external">http://vt100.net/docs/vt220-rm/table2-4.html</a> for a listing of all of the characters represented by the DEC Special Graphics Character Set.</p>
+<table>
+<thead>
+<tr>
+<th>Sequence</th>
+<th>Description</th>
+<th>Behavior</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>ESC ( 0</td>
+<td>Designate Character Set – DEC Line Drawing</td>
+<td>Enables DEC Line Drawing Mode</td>
+</tr>
+<tr>
+<td>ESC ( B</td>
+<td>Designate Character Set – US ASCII</td>
+<td>Enables ASCII Mode (Default)</td>
+</tr>
+</tbody>
+</table>
+<p>Notably, the DEC Line Drawing mode is used for drawing borders in console applications. The following table shows what ASCII character maps to which line drawing character.</p>
+<table>
+<thead>
+<tr>
+<th>Hex</th>
+<th>ASCII</th>
+<th>DEC Line Drawing</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>0x6a</td>
+<td>j</td>
+<td>┘</td>
+</tr>
+<tr>
+<td>0x6b</td>
+<td>k</td>
+<td>┐</td>
+</tr>
+<tr>
+<td>0x6c</td>
+<td>l</td>
+<td>┌</td>
+</tr>
+<tr>
+<td>0x6d</td>
+<td>m</td>
+<td>└</td>
+</tr>
+<tr>
+<td>0x6e</td>
+<td>n</td>
+<td>┼</td>
+</tr>
+<tr>
+<td>0x71</td>
+<td>q</td>
+<td>─</td>
+</tr>
+<tr>
+<td>0x74</td>
+<td>t</td>
+<td>├</td>
+</tr>
+<tr>
+<td>0x75</td>
+<td>u</td>
+<td>┤</td>
+</tr>
+<tr>
+<td>0x76</td>
+<td>v</td>
+<td>┴</td>
+</tr>
+<tr>
+<td>0x77</td>
+<td>w</td>
+<td>┬</td>
+</tr>
+<tr>
+<td>0x78</td>
+<td>x</td>
+<td>│</td>
+</tr>
+</tbody>
+</table>
+<h2 id="scrolling-margins"><span id="Scrolling_Margins"></span><span id="scrolling_margins"></span><span id="SCROLLING_MARGINS"></span>Scrolling Margins</h2>
+<p>The following sequences allow a program to configure the “scrolling region” of the screen that is affected by scrolling operations. This is a subset of the rows that are adjusted when the screen would otherwise scroll, for example, on a ‘\n’ or RI. These margins also affect the rows modified by Insert Line (IL) and Delete Line (DL), Scroll Up (SU) and Scroll Down (SD).</p>
+<p>The scrolling margins can be especially useful for having a portion of the screen that doesn’t scroll when the rest of the screen is filled, such as having a title bar at the top or a status bar at the bottom of your application.</p>
+<p>For DECSTBM, there are two optional parameters, &lt;t&gt; and &lt;b&gt;, which are used to specify the rows that represent the top and bottom lines of the scroll region, inclusive. If the parameters are omitted, &lt;t&gt; defaults to 1 and &lt;b&gt; defaults to the current viewport height.</p>
+<p>Scrolling margins are per-buffer, so importantly, the Alternate Buffer and Main Buffer maintain separate scrolling margins settings (so a full screen application in the alternate buffer will not poison the main buffer’s margins).</p>
+<table>
+<thead>
+<tr>
+<th>Sequence</th>
+<th>Code</th>
+<th>Description</th>
+<th>Behavior</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>ESC [ &lt;t&gt; ; &lt;b&gt; r</td>
+<td>DECSTBM</td>
+<td>Set Scrolling Region</td>
+<td>Sets the VT scrolling margins of the viewport.</td>
+</tr>
+</tbody>
+</table>
+<h2 id="window-title"><span id="Window_Title"></span><span id="window_title"></span><span id="WINDOW_TITLE"></span>Window Title</h2>
+<p>The following commands allows the application to set the title of the console window to the given &lt;string&gt; parameter. The string must be less than 255 characters to be accepted. This is equivalent to calling SetConsoleTitle with the given string.</p>
+<p>Note that these sequences are OSC “Operating system command” sequences, and not a CSI like many of the other sequences listed, and as such starts with “\x1b]”, not “\x1b[”.</p>
+<table>
+<thead>
+<tr>
+<th>Sequence</th>
+<th>Description</th>
+<th>Behavior</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>ESC ] 0 ; &lt;string&gt; BEL</td>
+<td>Set Icon and Window Title</td>
+<td>Sets the console window’s title to &lt;string&gt;.</td>
+</tr>
+<tr>
+<td>ESC ] 2 ; &lt;string&gt; BEL</td>
+<td>Set Window Title</td>
+<td>Sets the console window’s title to &lt;string&gt;.</td>
+</tr>
+</tbody>
+</table>
+<p>The terminating character here is the “Bell” character, ‘\x07’</p>
+<h2 id="alternate-screen-buffer"><span id="Alternate_Screen_Buffer__"></span><span id="alternate_screen_buffer__"></span><span id="ALTERNATE_SCREEN_BUFFER__"></span>Alternate Screen Buffer</h2>
+<p>*Nix style applications often utilize an alternate screen buffer, so that they can modify the entire contents of the buffer, without affecting the application that started them. The alternate buffer is exactly the dimensions of the window, without any scrollback region.</p>
+<p>For an example of this behavior, consider when vim is launched from bash. Vim uses the entirety of the screen to edit the file, then returning to bash leaves the original buffer unchanged.</p>
+<table>
+<thead>
+<tr>
+<th>Sequence</th>
+<th>Description</th>
+<th>Behavior</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>ESC [ ? 1 0 4 9 h</td>
+<td>Use Alternate Screen Buffer</td>
+<td>Switches to a new alternate screen buffer.</td>
+</tr>
+<tr>
+<td>ESC [ ? 1 0 4 9 l</td>
+<td>Use Main Screen Buffer</td>
+<td>Switches to the main buffer.</td>
+</tr>
+</tbody>
+</table>
+<h2 id="window-width"><span id="Window_Width"></span><span id="window_width"></span><span id="WINDOW_WIDTH"></span>Window Width</h2>
+<p>The following sequences can be used to control the width of the console window. They are roughly equivalent to the calling the SetConsoleScreenBufferInfoEx console API to set the window width.</p>
+<table>
+<thead>
+<tr>
+<th>Sequence</th>
+<th>Code</th>
+<th>Description</th>
+<th>Behavior</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>ESC [ ? 3 h</td>
+<td>DECCOLM</td>
+<td>Set Number of Columns to 132</td>
+<td>Sets the console width to 132 columns wide.</td>
+</tr>
+<tr>
+<td>ESC [ ? 3 l</td>
+<td>DECCOLM</td>
+<td>Set Number of Columns to 80</td>
+<td>Sets the console width to 80 columns wide.</td>
+</tr>
+</tbody>
+</table>
+<h2 id="soft-reset"><span id="Soft_Reset"></span><span id="soft_reset"></span><span id="SOFT_RESET"></span>Soft Reset</h2>
+<p>The following sequence can be used to reset certain properties to their default values.The following properties are reset to the following default values (also listed are the sequences that control those properties):</p>
+<ul>
+<li>Cursor visibility: visible (DECTEM)</li>
+<li>Numeric Keypad: Numeric Mode (DECNKM)</li>
+<li>Cursor Keys Mode: Normal Mode (DECCKM)</li>
+<li>Top and Bottom Margins: Top=1, Bottom=Console height (DECSTBM)</li>
+<li>Character Set: US ASCII</li>
+<li>Graphics Rendition: Default/Off (SGR)</li>
+<li>Save cursor state: Home position (0,0) (DECSC)</li>
+</ul>
+<table>
+<thead>
+<tr>
+<th>Sequence</th>
+<th>Code</th>
+<th>Description</th>
+<th>Behavior</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>ESC [ ! p</td>
+<td>DECSTR</td>
+<td>Soft Reset</td>
+<td>Reset certain terminal settings to their defaults.</td>
+</tr>
+</tbody>
+</table>
+<h2 id="input-sequences"><span id="Input_Sequences"></span><span id="input_sequences"></span><span id="INPUT_SEQUENCES"></span>Input Sequences</h2>
+<p>The following terminal sequences are emitted by the console host on the input stream if the ENABLE_VIRTUAL_TERMINAL_INPUT flag is set on the input buffer handle using the SetConsoleMode flag.</p>
+<p>There are two internal modes that control which sequences are emitted for the given input keys, the Cursor Keys Mode and the Keypad Keys Mode. These are described in the Mode Changes section.</p>
+<h3 id="cursor-keys"><span id="Cursor_Keys__"></span><span id="cursor_keys__"></span><span id="CURSOR_KEYS__"></span>Cursor Keys</h3>
+<table>
+<thead>
+<tr>
+<th>Key</th>
+<th>Normal Mode</th>
+<th>Application Mode</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>Up Arrow</td>
+<td>ESC [ A</td>
+<td>ESC O A</td>
+</tr>
+<tr>
+<td>Down Arrow</td>
+<td>ESC [ B</td>
+<td>ESC O B</td>
+</tr>
+<tr>
+<td>Right Arrow</td>
+<td>ESC [ C</td>
+<td>ESC O C</td>
+</tr>
+<tr>
+<td>Left Arrow</td>
+<td>ESC [ D</td>
+<td>ESC O D</td>
+</tr>
+<tr>
+<td>Home</td>
+<td>ESC [ H</td>
+<td>ESC O H</td>
+</tr>
+<tr>
+<td>End</td>
+<td>ESC [ F</td>
+<td>ESC O F</td>
+</tr>
+</tbody>
+</table>
+<p>Additionally, if Ctrl is pressed with any of these keys, the following sequences are emitted instead, regardless of the Cursor Keys Mode:</p>
+<table>
+<thead>
+<tr>
+<th>Key</th>
+<th>Any Mode</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>Ctrl + Up Arrow</td>
+<td>ESC [ 1 ; 5 A</td>
+</tr>
+<tr>
+<td>Ctrl + Down Arrow</td>
+<td>ESC [ 1 ; 5 B</td>
+</tr>
+<tr>
+<td>Ctrl + Right Arrow</td>
+<td>ESC [ 1 ; 5 C</td>
+</tr>
+<tr>
+<td>Ctrl + Left Arrow</td>
+<td>ESC [ 1 ; 5 D</td>
+</tr>
+</tbody>
+</table>
+<h3 id="numpad--function-keys"><span id="Numpad___Function_Keys__"></span><span id="numpad___function_keys__"></span><span id="NUMPAD___FUNCTION_KEYS__"></span>Numpad &amp; Function Keys</h3>
+<table>
+<thead>
+<tr>
+<th>Key</th>
+<th>Sequence</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>Backspace</td>
+<td>0x7f (DEL)</td>
+</tr>
+<tr>
+<td>Pause</td>
+<td>0x1a (SUB)</td>
+</tr>
+<tr>
+<td>Escape</td>
+<td>0x1b (ESC)</td>
+</tr>
+<tr>
+<td>Insert</td>
+<td>ESC [ 2 ~</td>
+</tr>
+<tr>
+<td>Delete</td>
+<td>ESC [ 3 ~</td>
+</tr>
+<tr>
+<td>Page Up</td>
+<td>ESC [ 5 ~</td>
+</tr>
+<tr>
+<td>Page Down</td>
+<td>ESC [ 6 ~</td>
+</tr>
+<tr>
+<td>F1</td>
+<td>ESC O P</td>
+</tr>
+<tr>
+<td>F2</td>
+<td>ESC O Q</td>
+</tr>
+<tr>
+<td>F3</td>
+<td>ESC O R</td>
+</tr>
+<tr>
+<td>F4</td>
+<td>ESC O S</td>
+</tr>
+<tr>
+<td>F5</td>
+<td>ESC [ 1 5 ~</td>
+</tr>
+<tr>
+<td>F6</td>
+<td>ESC [ 1 7 ~</td>
+</tr>
+<tr>
+<td>F7</td>
+<td>ESC [ 1 8 ~</td>
+</tr>
+<tr>
+<td>F8</td>
+<td>ESC [ 1 9 ~</td>
+</tr>
+<tr>
+<td>F9</td>
+<td>ESC [ 2 0 ~</td>
+</tr>
+<tr>
+<td>F10</td>
+<td>ESC [ 2 1 ~</td>
+</tr>
+<tr>
+<td>F11</td>
+<td>ESC [ 2 3 ~</td>
+</tr>
+<tr>
+<td>F12</td>
+<td>ESC [ 2 4 ~</td>
+</tr>
+</tbody>
+</table>
+<h3 id="modifiers"><span id="Modifiers__"></span><span id="modifiers__"></span><span id="MODIFIERS__"></span>Modifiers</h3>
+<p>Alt is treated by prefixing the sequence with an escape: ESC &lt;c&gt; where &lt;c&gt; is the character passed by the operating system. Alt+Ctrl is handled the same way except that the operating system will have pre-shifted the &lt;c&gt; key to the appropriate control character which will be relayed to the application.</p>
+<p>Ctrl is generally passed through exactly as received from the system. This is typically a single character shifted down into the control character reserved space (0x0-0x1f). For example, Ctrl+@ (0x40) becomes NUL (0x00), Ctrl+[ (0x5b) becomes ESC (0x1b), etc. A few Ctrl key combinations are treated specially according to the following table:</p>
+<table>
+<thead>
+<tr>
+<th>Key</th>
+<th>Sequence</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td>Ctrl + Space</td>
+<td>0x00 (NUL)</td>
+</tr>
+<tr>
+<td>Ctrl + Up Arrow</td>
+<td>ESC [ 1 ; 5 A</td>
+</tr>
+<tr>
+<td>Ctrl + Down Arrow</td>
+<td>ESC [ 1 ; 5 B</td>
+</tr>
+<tr>
+<td>Ctrl + Right Arrow</td>
+<td>ESC [ 1 ; 5 C</td>
+</tr>
+<tr>
+<td>Ctrl + Left Arrow</td>
+<td>ESC [ 1 ; 5 D</td>
+</tr>
+</tbody>
+</table>
+<p><strong>Note</strong>  Left Ctrl + Right Alt is treated as AltGr. When both are seen together, they will be stripped and the Unicode value of the character presented by the system will be passed into the target. The system will pre-translate AltGr values according to the current system input settings.</p>
+<h2 id="samples"><span id="Samples"></span><span id="samples"></span><span id="SAMPLES"></span>Samples</h2>
+<h3 id="example-of-sgr-terminal-sequences"><span id="example"></span><span id="EXAMPLE"></span>Example of SGR terminal sequences</h3>
+<p>The following code provides several examples of text formatting.</p>
+<pre><code>#include &lt;stdio.h&gt;
+#include &lt;wchar.h&gt;
+#include &lt;windows.h&gt;
+
+int main()
+{
+    // Set output mode to handle virtual terminal sequences
+    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
+    if (hOut == INVALID_HANDLE_VALUE)
+    {
+        return GetLastError();
+    }
+
+    DWORD dwMode = 0;
+    if (!GetConsoleMode(hOut, &amp;dwMode))
+    {
+        return GetLastError();
+    }
+
+    dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
+    if (!SetConsoleMode(hOut, dwMode))
+    {
+        return GetLastError();
+    }
+
+    // Try some Set Graphics Rendition (SGR) terminal escape sequences
+    wprintf(L&quot;\x1b[31mThis text has a red foreground using SGR.31.\r\n&quot;);
+    wprintf(L&quot;\x1b[1mThis text has a bright (bold) red foreground using SGR.1 to affect the previous color setting.\r\n&quot;);
+    wprintf(L&quot;\x1b[mThis text has returned to default colors using SGR.0 implicitly.\r\n&quot;);
+    wprintf(L&quot;\x1b[34;46mThis text shows the foreground and background change at the same time.\r\n&quot;);
+    wprintf(L&quot;\x1b[0mThis text has returned to default colors using SGR.0 explicitly.\r\n&quot;);
+    wprintf(L&quot;\x1b[31;32;33;34;35;36;101;102;103;104;105;106;107mThis text attempts to apply many colors in the same command. Note the colors are applied from left to right so only the right-most option of foreground cyan (SGR.36) and background bright white (SGR.107) is effective.\r\n&quot;);
+    wprintf(L&quot;\x1b[39mThis text has restored the foreground color only.\r\n&quot;);
+    wprintf(L&quot;\x1b[49mThis text has restored the background color only.\r\n&quot;);
+
+    return 0;
+}
+</code></pre>
+<p><strong>Note</strong>  In the previous example, the string '<code>\x1b[31m</code>' is the implementation of <strong>ESC [ &lt;n&gt; m</strong> with &lt;n&gt; being 31.</p>
+<p>The following graphic shows the output of the previous code example.</p>
+<p><img src="images/sgr.png" alt="output of the console using the sgr command" data-linktype="relative-path"/></p>
+<h3 id="example-of-enabling-virtual-terminal-processing"><span id="Example_of_Enabling_Virtual_Terminal_Processing"></span><span id="example_of_enabling_virtual_terminal_processing"></span><span id="EXAMPLE_OF_ENABLING_VIRTUAL_TERMINAL_PROCESSING"></span>Example of Enabling Virtual Terminal Processing</h3>
+<p>The following code provides an example of the recommended way to enable virtual terminal processing for an application. The intent of the sample is to demonstrate:</p>
+<ol>
+<li><p>The existing mode should always be retrieved via GetConsoleMode and analyzed before being set with SetConsoleMode.</p>
+</li>
+<li><p>Checking whether SetConsoleMode returns <code>0</code> and GetLastError returns STATUS_INVALID_PARAMETER is the current mechanism to determine when running on a down-level system. An application receiving STATUS_INVALID_PARAMETER with one of the newer console mode flags in the bit field should gracefully degrade behavior and try again.</p>
+</li>
+</ol>
+<pre><code class="lang-C">#include &lt;stdio.h&gt;
+#include &lt;wchar.h&gt;
+#include &lt;windows.h&gt;
+
+int main()
+{
+    // Set output mode to handle virtual terminal sequences
+    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
+    if (hOut == INVALID_HANDLE_VALUE)
+    {
+        return false;
+    }
+    HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
+    if (hIn == INVALID_HANDLE_VALUE)
+    {
+        return false;
+    }
+
+    DWORD dwOriginalOutMode = 0;
+    DWORD dwOriginalInMode = 0;
+    if (!GetConsoleMode(hOut, &amp;dwOriginalOutMode))
+    {
+        return false;
+    }
+    if (!GetConsoleMode(hIn, &amp;dwOriginalInMode))
+    {
+        return false;
+    }
+
+    DWORD dwRequestedOutModes = ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;
+    DWORD dwRequestedInModes = ENABLE_VIRTUAL_TERMINAL_INPUT;
+
+    DWORD dwOutMode = dwOriginalOutMode | dwRequestedOutModes;
+    if (!SetConsoleMode(hOut, dwOutMode))
+    {
+        // we failed to set both modes, try to step down mode gracefully.
+        dwRequestedOutModes = ENABLE_VIRTUAL_TERMINAL_PROCESSING;
+        dwOutMode = dwOriginalOutMode | dwRequestedOutModes;
+        if (!SetConsoleMode(hOut, dwOutMode))
+        {
+            // Failed to set any VT mode, can't do anything here.
+            return -1;
+        }
+    }
+
+    DWORD dwInMode = dwOriginalInMode | ENABLE_VIRTUAL_TERMINAL_INPUT;
+    if (!SetConsoleMode(hIn, dwInMode))
+    {
+        // Failed to set VT input mode, can't do anything here.
+        return -1;
+    }
+
+    return 0;
+}
+</code></pre>
+<h3 id="example-of-select-anniversary-update-features"><span id="Example_of_Select_Anniversary_Update_Features"></span><span id="example_of_select_anniversary_update_features"></span><span id="EXAMPLE_OF_SELECT_ANNIVERSARY_UPDATE_FEATURES"></span>Example of Select Anniversary Update Features</h3>
+<p>The following example is intended to be a more robust example of code using a variety of escape sequences to manipulate the buffer, with an emphasis on the features added in the Anniversary Update for Windows 10.</p>
+<p>This example makes use of the alternate screen buffer, manipulating tab stops, setting scrolling margins, and changing the character set.</p>
+<pre><code class="lang-C">//
+//    Copyright (C) Microsoft.  All rights reserved.
+//
+#define DEFINE_CONSOLEV2_PROPERTIES
+
+// System headers
+#include &lt;windows.h&gt;
+
+// Standard library C-style
+#include &lt;wchar.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdio.h&gt;
+
+#define ESC &quot;\x1b&quot;
+#define CSI &quot;\x1b[&quot;
+
+bool EnableVTMode()
+{
+    // Set output mode to handle virtual terminal sequences
+    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
+    if (hOut == INVALID_HANDLE_VALUE)
+    {
+        return false;
+    }
+
+    DWORD dwMode = 0;
+    if (!GetConsoleMode(hOut, &amp;dwMode))
+    {
+        return false;
+    }
+
+    dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
+    if (!SetConsoleMode(hOut, dwMode))
+    {
+        return false;
+    }
+    return true;
+}
+
+void PrintVerticalBorder()
+{
+    printf(ESC &quot;(0&quot;);       // Enter Line drawing mode
+    printf(CSI &quot;104;93m&quot;);   // bright yellow on bright blue
+    printf(&quot;x&quot;);            // in line drawing mode, \x78 -&gt; \u2502 &quot;Vertical Bar&quot;
+    printf(CSI &quot;0m&quot;);       // restore color
+    printf(ESC &quot;(B&quot;);       // exit line drawing mode
+}
+
+void PrintHorizontalBorder(COORD const Size, bool fIsTop)
+{
+    printf(ESC &quot;(0&quot;);       // Enter Line drawing mode
+    printf(CSI &quot;104;93m&quot;);  // Make the border bright yellow on bright blue
+    printf(fIsTop? &quot;l&quot; : &quot;m&quot;); // print left corner 
+
+    for (int i = 1; i &lt; Size.X - 1; i++) 
+        printf(&quot;q&quot;); // in line drawing mode, \x71 -&gt; \u2500 &quot;HORIZONTAL SCAN LINE-5&quot;
+
+    printf(fIsTop? &quot;k&quot; : &quot;j&quot;); // print right corner
+    printf(CSI &quot;0m&quot;);
+    printf(ESC &quot;(B&quot;);       // exit line drawing mode
+}
+
+void PrintStatusLine(char* const pszMessage, COORD const Size)
+{
+    printf(CSI &quot;%d;1H&quot;, Size.Y);
+    printf(CSI &quot;K&quot;); // clear the line
+    printf(pszMessage);  
+}
+
+int __cdecl wmain(int argc, WCHAR* argv[])
+{   
+    argc; // unused
+    argv; // unused
+    //First, enable VT mode
+    bool fSuccess = EnableVTMode();
+    if (!fSuccess)
+    {
+        printf(&quot;Unable to enter VT processing mode. Quitting.\n&quot;);
+        return -1;
+    }
+    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
+    if (hOut == INVALID_HANDLE_VALUE)
+    {
+        printf(&quot;Couldn't get the console handle. Quitting.\n&quot;);
+        return -1;
+    }
+
+    CONSOLE_SCREEN_BUFFER_INFO ScreenBufferInfo;
+    GetConsoleScreenBufferInfo(hOut, &amp;ScreenBufferInfo);
+    COORD Size;
+    Size.X = ScreenBufferInfo.srWindow.Right - ScreenBufferInfo.srWindow.Left + 1;
+    Size.Y = ScreenBufferInfo.srWindow.Bottom -  ScreenBufferInfo.srWindow.Top + 1;
+
+    // Enter the alternate buffer
+    printf(CSI &quot;?1049h&quot;);
+
+    // Clear screen, tab stops, set, stop at columns 16, 32
+    printf(CSI &quot;1;1H&quot;);
+    printf(CSI &quot;2J&quot;); // Clear screen
+
+    int iNumTabStops = 4; // (0, 20, 40, width)
+    printf(CSI &quot;3g&quot;); // clear all tab stops
+    printf(CSI &quot;1;20H&quot;); // Move to column 20
+    printf(ESC &quot;H&quot;); // set a tab stop
+
+    printf(CSI &quot;1;40H&quot;); // Move to column 40
+    printf(ESC &quot;H&quot;); // set a tab stop
+
+    // Set scrolling margins to 3, h-2
+    printf(CSI &quot;3;%dr&quot;, Size.Y-2);
+    int iNumLines = Size.Y - 4;
+
+    printf(CSI &quot;1;1H&quot;);
+    printf(CSI &quot;102;30m&quot;);
+    printf(&quot;Windows 10 Anniversary Update - VT Example&quot;); 
+    printf(CSI &quot;0m&quot;);
+
+    // Print a top border - Yellow
+    printf(CSI &quot;2;1H&quot;);
+    PrintHorizontalBorder(Size, true);
+
+    // // Print a bottom border
+    printf(CSI &quot;%d;1H&quot;, Size.Y-1);
+    PrintHorizontalBorder(Size, false);
+
+    wchar_t wch;
+
+    // draw columns
+    printf(CSI &quot;3;1H&quot;); 
+    int line = 0;
+    for (line = 0; line &lt; iNumLines * iNumTabStops; line++)
+    {
+        PrintVerticalBorder();
+        if (line + 1 != iNumLines * iNumTabStops) // don't advance to next line if this is the last line
+            printf(&quot;\t&quot;); // advance to next tab stop
+
+    }
+
+    PrintStatusLine(&quot;Press any key to see text printed between tab stops.&quot;, Size);
+    wch = _getwch();
+
+    // Fill columns with output
+    printf(CSI &quot;3;1H&quot;); 
+    for (line = 0; line &lt; iNumLines; line++)
+    {
+        int tab = 0;
+        for (tab = 0; tab &lt; iNumTabStops-1; tab++)
+        {
+            PrintVerticalBorder();
+            printf(&quot;line=%d&quot;, line);
+            printf(&quot;\t&quot;); // advance to next tab stop
+        }
+        PrintVerticalBorder();// print border at right side
+        if (line+1 != iNumLines)
+            printf(&quot;\t&quot;); // advance to next tab stop, (on the next line)
+    }
+
+    PrintStatusLine(&quot;Press any key to demonstrate scroll margins&quot;, Size);
+    wch = _getwch();
+
+    printf(CSI &quot;3;1H&quot;); 
+    for (line = 0; line &lt; iNumLines * 2; line++)
+    {
+        printf(CSI &quot;K&quot;); // clear the line
+        int tab = 0;
+        for (tab = 0; tab &lt; iNumTabStops-1; tab++)
+        {
+            PrintVerticalBorder();
+            printf(&quot;line=%d&quot;, line);
+            printf(&quot;\t&quot;); // advance to next tab stop
+        }
+        PrintVerticalBorder(); // print border at right side
+        if (line+1 != iNumLines * 2)
+        {
+            printf(&quot;\n&quot;); //Advance to next line. If we're at the bottom of the margins, the text will scroll.
+            printf(&quot;\r&quot;); //return to first col in buffer
+        }
+    }
+
+    PrintStatusLine(&quot;Press any key to exit&quot;, Size);
+    wch = _getwch();
+
+    // Exit the alternate buffer
+    printf(CSI &quot;?1049l&quot;);
+
+}
+</code></pre>
+
+						<!-- </content> -->
+
+						</main>
+
+						<!-- recommended content page section -->
+
+							<nav data-bi-name="recommendation-bottom" hidden id="recommended-content-center" class="is-hidden-desktop" aria-labelledby="recommended-content-center-title">
+								<h3 id="recommended-content-center-title" class="is-size-h2 has-margin-top-large has-margin-bottom-small">Related Articles</h3>
+							</nav>
+
+						<!-- end recommended content page section -->
+
+						<!-- page rating section -->
+								<div class="is-hidden-desktop has-border-top has-margin-top-large has-padding-top-small">
+									
+									
+<div class="feedback-verbatim has-border-bottom has-padding-bottom-small has-margin-bottom-small" data-bi-name="rating">
+    <div class="binary-rating">
+        <div class="binary-rating-buttons">
+            <h3 class="has-text-weight-semibold has-margin-top-none has-margin-bottom-small">Is this page helpful?</h3>
+            <div>
+                <button class="thumb-rating like has-inner-focus has-padding-left-extra-small has-padding-right-extra-small" title="Yes" data-bi-name="rating-yes" aria-expanded="false" data-bi-sat="1" aria-controls="rating-container-mobile">
+                    <span aria-hidden="true" class="icon docon docon-like"></span>
+                    <span>Yes</span>
+                </button>
+                <button class="thumb-rating dislike has-inner-focus has-padding-none has-padding-right-extra-small" title="No" data-bi-name="rating-no" data-bi-sat="0" aria-expanded="false" aria-controls="rating-container-mobile">
+                    <span aria-hidden="true" class="icon docon docon-dislike"></span>
+                    <span>No</span>
+                </button>
+            </div>
+        </div>
+        <form class="feedback-verbatim-form is-hidden" id="rating-container-mobile">
+            <div class="verbatim-textarea box is-relative has-box-shadow-none has-border has-margin-top-small has-padding-extra-small is-size-extra-small">
+                <label for="rating-textarea-mobile" class="visually-hidden">Any additional feedback?</label>
+                <textarea id="rating-textarea-mobile" rows="4" maxlength="999" placeholder="Any additional feedback?" required class="textarea has-border-none has-box-shadow-none has-inner-focus"></textarea>
+            </div>
+            <div class="buttons is-right has-margin-top-medium has-margin-right-extra-small">
+                <button class="skip-rating link-button is-small" type="button">Skip</button>
+                <button class="submit-rating button is-primary is-small" data-bi-name="rating-verbatim" disabled type="submit">Submit</button>
+            </div>
+        </form>
+    </div>
+    <div class="thankyou-rating is-hidden" tabindex="-1">
+        <p>Thank you.</p>
+    </div>
+</div>								</div>
+						<!-- end page rating section -->
+
+
+						<!-- feedback section -->
+<section class="feedback-section is-relative" data-bi-name="feedback-section">
+
+    <h2 id="feedback" class="is-size-h2 has-margin-top-large">Feedback</h2>
+
+    <div class="alert choose-feedback-type has-margin-top-small has-margin-bottom-none">
+        <p aria-hidden="true" id="send-feedback-about" class="has-margin-top-none has-margin-bottom-none">Submit and view feedback for</p>
+
+        <div class="choose-feedback-buttons has-margin-top-large has-margin-bottom-none">
+                <a class="button feedback-type-product has-margin-bottom-small" aria-label="Send feedback about this product" href="https://github.com/microsoft/console/issues" data-bi-name="product-feedback">
+                    <span>This product</span>
+                    <span aria-hidden="true" class="icon docon docon-navigate-external is-size-h2 has-margin-left-none"></span>
+                </a>
+
+			<a class="button feedback-type-product has-margin-bottom-small github-link" aria-label="Send feedback about this page" data-bi-name="create-issue-on-github">
+				<span aria-hidden="true" class="docon docon-brand-github has-padding-right-extra-small"></span>
+				<span>This page</span>
+			</a>
+        </div>
+    </div>
+
+    <div class="action-container is-flex has-flex-justify-content-end has-margin-top-small has-margin-bottom-small">
+        <a class="view-on-github" data-bi-name="view-on-github" href="https://github.com/MicrosoftDocs/Console-Docs/issues">
+            <span aria-hidden="true" class="docon docon-brand-github"></span>
+            <span>View all page feedback</span>
+            <span aria-hidden="true" class="icon docon docon-navigate-external is-size-h5"></span>
+        </a>
+    </div>
+</section>
+
+						<!-- end feedback section -->
+
+						<!-- feedback report section -->
+						<!-- end feedback report section -->
+
+						<div class="footerContainer is-visible-interactive has-default-focus ">
+<footer id="footer-interactive" data-bi-name="footer" class="footer-layout">
+
+    <div class="is-flex is-full-height has-padding-right-extra-large-desktop">
+			<a data-mscc-ic="false" class="locale-selector-link has-flex-shrink-none" href="#" data-bi-name="select-locale"><span class="icon docon docon-world is-size-large has-margin-right-small" aria-hidden="true"></span><span class="local-selector-link-text"></span></a>
+		<div class="has-margin-left-medium has-margin-right-medium has-flex-shrink-none">
+<div class="dropdown has-caret-up">
+	<button class="dropdown-trigger button is-transparent is-small is-icon-only-touch has-inner-focus theme-dropdown-trigger"
+		aria-controls="theme-menu-interactive" aria-expanded="false" title="Theme" data-bi-name="theme">
+		<span class="icon">
+			<span class="docon docon-sun" aria-hidden="true"></span>
+		</span>
+		<span>Theme</span>
+	</button>
+	<div class="dropdown-menu" id="theme-menu-interactive" role="menu">
+		<ul class="theme-selector has-padding-small">
+			<li class="theme is-block">
+				<button class="button is-text is-small theme-control is-fullwidth has-flex-justify-content-start"
+					data-theme-to="light">
+					<span class="theme-light has-margin-right-small">
+						<span
+							class="theme-selector-icon css-variable-support has-border is-inline-block has-body-background"
+							aria-hidden="true">
+							<svg class="svg" xmlns="http://www.w3.org/2000/svg"
+								viewBox="0 0 22 14">
+								<rect width="22" height="14" class="has-fill-body-background" />
+								<rect x="5" y="5" width="12" height="4" class="has-fill-secondary" />
+								<rect x="5" y="2" width="2" height="1" class="has-fill-secondary" />
+								<rect x="8" y="2" width="2" height="1" class="has-fill-secondary" />
+								<rect x="11" y="2" width="3" height="1" class="has-fill-secondary" />
+								<rect x="1" y="1" width="2" height="2" class="has-fill-secondary" />
+								<rect x="5" y="10" width="7" height="2" rx="0.3" class="has-fill-primary" />
+								<rect x="19" y="1" width="2" height="2" rx="1" class="has-fill-secondary" />
+							</svg>
+						</span>
+					</span>
+					<span role="menuitem">
+Light					</span>
+				</button>
+			</li>
+			<li class="theme is-block">
+				<button class="button is-text is-small theme-control is-fullwidth has-flex-justify-content-start"
+					data-theme-to="dark">
+					<span class="theme-dark has-margin-right-small">
+						<span
+							class="has-border theme-selector-icon css-variable-support is-inline-block has-body-background"
+							aria-hidden="true">
+							<svg class="svg" xmlns="http://www.w3.org/2000/svg"
+								viewBox="0 0 22 14">
+								<rect width="22" height="14" class="has-fill-body-background" />
+								<rect x="5" y="5" width="12" height="4" class="has-fill-secondary" />
+								<rect x="5" y="2" width="2" height="1" class="has-fill-secondary" />
+								<rect x="8" y="2" width="2" height="1" class="has-fill-secondary" />
+								<rect x="11" y="2" width="3" height="1" class="has-fill-secondary" />
+								<rect x="1" y="1" width="2" height="2" class="has-fill-secondary" />
+								<rect x="5" y="10" width="7" height="2" rx="0.3" class="has-fill-primary" />
+								<rect x="19" y="1" width="2" height="2" rx="1" class="has-fill-secondary" />
+							</svg>
+						</span>
+					</span>
+					<span role="menuitem">
+Dark					</span>
+				</button>
+			</li>
+			<li class="theme is-block">
+				<button class="button is-text is-small theme-control is-fullwidth has-flex-justify-content-start"
+					data-theme-to="high-contrast">
+					<span class="theme-high-contrast has-margin-right-small">
+						<span
+							class="has-border theme-selector-icon css-variable-support is-inline-block has-body-background"
+							aria-hidden="true">
+							<svg class="svg" xmlns="http://www.w3.org/2000/svg"
+								viewBox="0 0 22 14">
+								<rect width="22" height="14" class="has-fill-body-background" />
+								<rect x="5" y="5" width="12" height="4" class="has-fill-secondary" />
+								<rect x="5" y="2" width="2" height="1" class="has-fill-secondary" />
+								<rect x="8" y="2" width="2" height="1" class="has-fill-secondary" />
+								<rect x="11" y="2" width="3" height="1" class="has-fill-secondary" />
+								<rect x="1" y="1" width="2" height="2" class="has-fill-secondary" />
+								<rect x="5" y="10" width="7" height="2" rx="0.3" class="has-fill-primary" />
+								<rect x="19" y="1" width="2" height="2" rx="1" class="has-fill-secondary" />
+							</svg>
+						</span>
+					</span>
+					<span role="menuitem">
+High contrast					</span>
+				</button>
+			</li>
+		</ul>
+	</div>
+</div>		</div>
+	</div>
+
+    <ul class="links" data-bi-name="footerlinks">
+		<li><a data-mscc-ic="false" href="https://docs.microsoft.com/en-us/previous-versions/" data-bi-name="archivelink">Previous Version Docs</a></li>
+		<li><a data-mscc-ic="false" href="https://docs.microsoft.com/en-us/teamblog" data-bi-name="bloglink">Blog</a></li>
+		<li><a data-mscc-ic="false" href="https://docs.microsoft.com/en-us/contribute" data-bi-name="contributorGuide">Contribute</a></li>
+				<li><a data-mscc-ic="false" href="https://go.microsoft.com/fwlink/?LinkId=521839" data-bi-name="privacy">Privacy &amp; Cookies</a></li>
+		<li><a data-mscc-ic="false" href="https://docs.microsoft.com/en-us/legal/termsofuse" data-bi-name="termsofuse">Terms of Use</a></li>
+		<li><a data-mscc-ic="false" href="https://aka.ms/sitefeedback" data-bi-name="feedback">Site Feedback</a></li>
+			<li><a data-mscc-ic="false" href="https://www.microsoft.com/en-us/legal/intellectualproperty/Trademarks/EN-US.aspx" data-bi-name="trademarks">Trademarks</a></li>
+		<li>&copy; Microsoft 2020</li>
+    </ul>
+</footer>
+						</div>
+					</div>
+
+					<div class="is-size-small right-container column is-one-quarter is-one-fifth-desktop is-hidden-mobile is-hidden-tablet-only" data-bi-name="pageactions" role="complementary" aria-label="Page Actions">
+						<div id="affixed-right-container" class="doc-outline is-fixed is-vertically-scrollable">
+								
+								
+<div class="feedback-verbatim has-border-bottom has-padding-bottom-small has-margin-bottom-small" data-bi-name="rating">
+    <div class="binary-rating">
+        <div class="binary-rating-buttons">
+            <h3 class="has-text-weight-semibold has-margin-top-none has-margin-bottom-small">Is this page helpful?</h3>
+            <div>
+                <button class="thumb-rating like has-inner-focus has-padding-left-extra-small has-padding-right-extra-small" title="Yes" data-bi-name="rating-yes" aria-expanded="false" data-bi-sat="1" aria-controls="rating-container-desktop">
+                    <span aria-hidden="true" class="icon docon docon-like"></span>
+                    <span>Yes</span>
+                </button>
+                <button class="thumb-rating dislike has-inner-focus has-padding-none has-padding-right-extra-small" title="No" data-bi-name="rating-no" data-bi-sat="0" aria-expanded="false" aria-controls="rating-container-desktop">
+                    <span aria-hidden="true" class="icon docon docon-dislike"></span>
+                    <span>No</span>
+                </button>
+            </div>
+        </div>
+        <form class="feedback-verbatim-form is-hidden" id="rating-container-desktop">
+            <div class="verbatim-textarea box is-relative has-box-shadow-none has-border has-margin-top-small has-padding-extra-small is-size-extra-small">
+                <label for="rating-textarea-desktop" class="visually-hidden">Any additional feedback?</label>
+                <textarea id="rating-textarea-desktop" rows="4" maxlength="999" placeholder="Any additional feedback?" required class="textarea has-border-none has-box-shadow-none has-inner-focus"></textarea>
+            </div>
+            <div class="buttons is-right has-margin-top-medium has-margin-right-extra-small">
+                <button class="skip-rating link-button is-small" type="button">Skip</button>
+                <button class="submit-rating button is-primary is-small" data-bi-name="rating-verbatim" disabled type="submit">Submit</button>
+            </div>
+        </form>
+    </div>
+    <div class="thankyou-rating is-hidden" tabindex="-1">
+        <p>Thank you.</p>
+    </div>
+</div>								<nav data-bi-name="recommendation-sidebar" hidden id="recommended-content-nav" role="navigation" aria-labelledby="recommended-content-nav-title">
+									<h3 id="recommended-content-nav-title">Related Articles</h3>
+								</nav>
+							<nav id="side-doc-outline" data-bi-name="intopic toc" role="navigation" aria-label="Article Outline">
+								<h3>In this article</h3>
+							</nav>
+						</div>
+					</div>
+
+					<!--end of div.columns -->
+				</div>
+
+			<!--end of .primary-holder -->
+			</section>
+
+			<aside id="interactive-container" class="interactive-container is-visible-interactive column has-body-background-dark ">
+			</aside>
+		</div>
+
+		<!--end of .mainContainer -->
+	</div>
+
+	<div id="openFeedbackContainer" class="openfeedback-container"></div>
+
+	<div class="footerContainer has-default-focus is-hidden-interactive ">
+<footer id="footer" data-bi-name="footer" class="footer-layout uhf-container has-padding" role="contentinfo">
+
+    <div class="is-flex is-full-height has-padding-right-extra-large-desktop">
+			<a data-mscc-ic="false" class="locale-selector-link has-flex-shrink-none" href="#" data-bi-name="select-locale"><span class="icon docon docon-world is-size-large has-margin-right-small" aria-hidden="true"></span><span class="local-selector-link-text"></span></a>
+		<div class="has-margin-left-medium has-margin-right-medium has-flex-shrink-none">
+<div class="dropdown has-caret-up">
+	<button class="dropdown-trigger button is-transparent is-small is-icon-only-touch has-inner-focus theme-dropdown-trigger"
+		aria-controls="theme-menu" aria-expanded="false" title="Theme" data-bi-name="theme">
+		<span class="icon">
+			<span class="docon docon-sun" aria-hidden="true"></span>
+		</span>
+		<span>Theme</span>
+	</button>
+	<div class="dropdown-menu" id="theme-menu" role="menu">
+		<ul class="theme-selector has-padding-small">
+			<li class="theme is-block">
+				<button class="button is-text is-small theme-control is-fullwidth has-flex-justify-content-start"
+					data-theme-to="light">
+					<span class="theme-light has-margin-right-small">
+						<span
+							class="theme-selector-icon css-variable-support has-border is-inline-block has-body-background"
+							aria-hidden="true">
+							<svg class="svg" xmlns="http://www.w3.org/2000/svg"
+								viewBox="0 0 22 14">
+								<rect width="22" height="14" class="has-fill-body-background" />
+								<rect x="5" y="5" width="12" height="4" class="has-fill-secondary" />
+								<rect x="5" y="2" width="2" height="1" class="has-fill-secondary" />
+								<rect x="8" y="2" width="2" height="1" class="has-fill-secondary" />
+								<rect x="11" y="2" width="3" height="1" class="has-fill-secondary" />
+								<rect x="1" y="1" width="2" height="2" class="has-fill-secondary" />
+								<rect x="5" y="10" width="7" height="2" rx="0.3" class="has-fill-primary" />
+								<rect x="19" y="1" width="2" height="2" rx="1" class="has-fill-secondary" />
+							</svg>
+						</span>
+					</span>
+					<span role="menuitem">
+Light					</span>
+				</button>
+			</li>
+			<li class="theme is-block">
+				<button class="button is-text is-small theme-control is-fullwidth has-flex-justify-content-start"
+					data-theme-to="dark">
+					<span class="theme-dark has-margin-right-small">
+						<span
+							class="has-border theme-selector-icon css-variable-support is-inline-block has-body-background"
+							aria-hidden="true">
+							<svg class="svg" xmlns="http://www.w3.org/2000/svg"
+								viewBox="0 0 22 14">
+								<rect width="22" height="14" class="has-fill-body-background" />
+								<rect x="5" y="5" width="12" height="4" class="has-fill-secondary" />
+								<rect x="5" y="2" width="2" height="1" class="has-fill-secondary" />
+								<rect x="8" y="2" width="2" height="1" class="has-fill-secondary" />
+								<rect x="11" y="2" width="3" height="1" class="has-fill-secondary" />
+								<rect x="1" y="1" width="2" height="2" class="has-fill-secondary" />
+								<rect x="5" y="10" width="7" height="2" rx="0.3" class="has-fill-primary" />
+								<rect x="19" y="1" width="2" height="2" rx="1" class="has-fill-secondary" />
+							</svg>
+						</span>
+					</span>
+					<span role="menuitem">
+Dark					</span>
+				</button>
+			</li>
+			<li class="theme is-block">
+				<button class="button is-text is-small theme-control is-fullwidth has-flex-justify-content-start"
+					data-theme-to="high-contrast">
+					<span class="theme-high-contrast has-margin-right-small">
+						<span
+							class="has-border theme-selector-icon css-variable-support is-inline-block has-body-background"
+							aria-hidden="true">
+							<svg class="svg" xmlns="http://www.w3.org/2000/svg"
+								viewBox="0 0 22 14">
+								<rect width="22" height="14" class="has-fill-body-background" />
+								<rect x="5" y="5" width="12" height="4" class="has-fill-secondary" />
+								<rect x="5" y="2" width="2" height="1" class="has-fill-secondary" />
+								<rect x="8" y="2" width="2" height="1" class="has-fill-secondary" />
+								<rect x="11" y="2" width="3" height="1" class="has-fill-secondary" />
+								<rect x="1" y="1" width="2" height="2" class="has-fill-secondary" />
+								<rect x="5" y="10" width="7" height="2" rx="0.3" class="has-fill-primary" />
+								<rect x="19" y="1" width="2" height="2" rx="1" class="has-fill-secondary" />
+							</svg>
+						</span>
+					</span>
+					<span role="menuitem">
+High contrast					</span>
+				</button>
+			</li>
+		</ul>
+	</div>
+</div>		</div>
+	</div>
+    <ul class="links" data-bi-name="footerlinks">
+		<li><a data-mscc-ic="false" href="https://docs.microsoft.com/en-us/previous-versions/" data-bi-name="archivelink">Previous Version Docs</a></li>
+		<li><a data-mscc-ic="false" href="https://docs.microsoft.com/en-us/teamblog" data-bi-name="bloglink">Blog</a></li>
+		<li><a data-mscc-ic="false" href="https://docs.microsoft.com/en-us/contribute" data-bi-name="contributorGuide">Contribute</a></li>
+				<li><a data-mscc-ic="false" href="https://go.microsoft.com/fwlink/?LinkId=521839" data-bi-name="privacy">Privacy &amp; Cookies</a></li>
+		<li><a data-mscc-ic="false" href="https://docs.microsoft.com/en-us/legal/termsofuse" data-bi-name="termsofuse">Terms of Use</a></li>
+		<li><a data-mscc-ic="false" href="https://aka.ms/sitefeedback" data-bi-name="feedback">Site Feedback</a></li>
+			<li><a data-mscc-ic="false" href="https://www.microsoft.com/en-us/legal/intellectualproperty/Trademarks/EN-US.aspx" data-bi-name="trademarks">Trademarks</a></li>
+		<li>&copy; Microsoft 2020</li>
+    </ul>
+</footer>
+	</div>
+
+	<div id="action-panel" role="region" aria-label="Action Panel" class="action-panel has-default-focus" tabindex="-1"></div>
+	<span id="adobe-target-experiment-container" hidden></span>
+</body>
+</html>

+ 853 - 0
terminal.cpp

@@ -0,0 +1,853 @@
+/*
+Terminal tracking
+
+Actually, I believe I only really need to track the color information.
+Everything else, I'm not sure I really care about.   (NNY!)
+
+ */
+
+#include "terminal.h"
+#include "utils.h"
+#include "zf_log.h"
+#include <ctype.h>
+#include <sstream>
+#include <stdio.h> // snprintf
+#include <stdlib.h>
+#include <string.h>
+#include <vector>
+
+Terminal::Terminal() { this->init(); }
+
+Terminal::Terminal(const Terminal &old)
+    : saved_cursor_position(old.saved_cursor_position) {
+  posx = old.posx;
+  posy = old.posy;
+  in_ansi = old.in_ansi;
+  fgcolor = old.fgcolor;
+  bgcolor = old.bgcolor;
+  status = old.status;
+  ansi = old.ansi;
+}
+
+Terminal &Terminal::operator=(Terminal &rhs) {
+  posx = rhs.posx;
+  posy = rhs.posy;
+  in_ansi = rhs.in_ansi;
+  fgcolor = rhs.fgcolor;
+  bgcolor = rhs.bgcolor;
+  status = rhs.status;
+  ansi = rhs.ansi;
+  saved_cursor_position = rhs.saved_cursor_position;
+  return *this;
+}
+
+void Terminal::init(void) {
+  this->posx = this->posy = 0;
+  this->in_ansi = 0;
+  this->fgcolor = 7;
+  this->bgcolor = 0;
+  this->status = 0;
+  this->saved_cursor_position.clear();
+  this->dcs_map = 0x0b;
+}
+
+int Terminal::getx(void) { return posx; }
+int Terminal::gety(void) { return posy; }
+int Terminal::getstatus(void) { return status; }
+int Terminal::inANSI(void) { return in_ansi; }
+int Terminal::fg(void) { return fgcolor; }
+int Terminal::bg(void) { return bgcolor; }
+bool Terminal::ansiempty(void) { return ansi.empty(); }
+int Terminal::dcs(void) { return dcs_map; }
+
+void Terminal::ansi_color(int color) {
+  // ZF_LOGV("ansi_color(%d)", color);
+
+  if (color == 0) {
+    this->status = 0;
+    this->fgcolor = 7;
+    this->bgcolor = 0;
+    return;
+  }
+  if ((color == 1) || (color == 2) || (color == 3) || (color == 4) ||
+      (color == 5)) {
+    this->status = color;
+    return;
+  }
+  if ((color >= 30) && (color <= 37)) {
+    this->fgcolor = color - 30;
+    return;
+  }
+  if ((color >= 40) && (color <= 47)) {
+    this->bgcolor = color - 40;
+    return;
+  }
+  if (color == 39) {
+    // default fg color
+    this->fgcolor = 7;
+    return;
+  }
+  if (color == 49) {
+    // default bg color
+    this->bgcolor = 0;
+    return;
+  }
+  ZF_LOGD("ansi_color( %d ) is unknown to me.", color);
+}
+
+std::string Terminal::color_restore(void) {
+  std::ostringstream oss;
+
+  // possible optimize:  If fg is 7, don't set (0 reset does that), if bg is 0,
+  // don't set (0 does that)
+
+  if (this->status == 0) {
+    oss << "\x1b[0;" << this->fgcolor + 30 << ";" << this->bgcolor + 40 << "m";
+  } else {
+    oss << "\x1b[0;" << this->status << ";" << this->fgcolor + 30 << ";"
+        << this->bgcolor + 40 << "m";
+  }
+
+  std::string buffer = oss.str();
+  return buffer;
+}
+
+Terminal::ANSI_TYPE Terminal::ansi_code(std::string ansi) {
+  std::string::iterator cp = ansi.begin();
+  std::string::iterator last = ansi.end() - 1;
+
+  int number, number2;
+
+  if (*cp == '\x1b') {
+    ++cp;
+    // Ok, that's expected.
+    if (*cp == '[') {
+      ++cp;
+
+      // https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences
+
+      switch (*last) {
+      case 'A':
+        // cursor up
+        if (cp == last) {
+          number = 1;
+        } else {
+          number = std::stoi(std::string(cp, last));
+          if (number < 1) {
+            ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi.c_str()),
+                    number);
+            number = 1;
+          }
+        };
+        this->posy -= number;
+        if (this->posy < 0) {
+          this->posy = 0;
+          ZF_LOGD(
+              "console_ansi( %s ): attempt to move above top of screen (%d)",
+              repr(ansi.c_str()), number);
+        }
+        return CURSOR;
+      case 'B':
+        // cursor down
+        if (cp == last) {
+          number = 1;
+        } else {
+          number = std::stoi(std::string(cp, last));
+          if (number < 1) {
+            ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi.c_str()),
+                    number);
+            number = 1;
+          }
+        };
+        this->posy += number;
+        // check range/"scroll"
+        return CURSOR;
+
+      case 'C':
+        // cursor forward
+        if (cp == last) {
+          number = 1;
+        } else {
+          number = std::stoi(std::string(cp, last));
+          if (number < 1) {
+            ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi.c_str()),
+                    number);
+            number = 1;
+          }
+        };
+        this->posx += number;
+
+        // Well.  According to the "spec", the screen limits are hard
+        // If the cursor is already at the edge of the screen, this has no
+        // effect. ^ This is *NOT* how any ANSI BBS terminal program acts!
+
+        while (this->posx > 79) {
+          this->posy++;
+          // check range/"scroll"
+          this->posx -= 79;
+        }
+        return CURSOR;
+
+      case 'D':
+        // cursor backwards
+        if (cp == last) {
+          number = 1;
+        } else {
+          number = std::stoi(std::string(cp, last));
+          if (number < 1) {
+            ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi.c_str()),
+                    number);
+            number = 0;
+          }
+        };
+        this->posx -= number;
+
+        // Well.  According to the "spec", the screen limits are hard
+        // If the cursor is already at the edge of the screen, this has no
+        // effect. ^ This is *NOT* how any ANSI BBS terminal program acts!
+
+        while (this->posx < 0) {
+          this->posy--;
+          if (this->posy < 0) {
+            this->posy = 0;
+          }
+          this->posx += 79;
+        }
+        return CURSOR;
+
+      case 'H':
+        // cursor position
+
+        if (cp == last) {
+          // Missing first and last
+          number = 1;
+          number2 = 1;
+        } else {
+          if (*cp == ';') {
+            // Missing first number
+            number = 1;
+            ++cp;
+            if (cp == last) {
+              // missing 2nd number as well?
+              number2 = 1;
+            } else {
+              number2 = std::stoi(std::string(cp, last));
+            }
+          } else {
+            // Ok, find the first number
+            number = std::stoi(std::string(cp, last));
+
+            // covert iterator to position
+            std::size_t pos = ansi.find(';', std::distance(ansi.begin(), cp));
+
+            if (pos == std::string::npos) {
+              // Missing 2nd number
+              number2 = 1;
+            } else {
+              // 2nd number found, maybe.
+              cp = ansi.begin() + pos;
+              ++cp;
+              if (cp == last) {
+                number2 = 1;
+              } else {
+                number2 = std::stoi(std::string(cp, last));
+              }
+            }
+          }
+        }
+
+        // Our positions start at zero, not one.
+        this->posy = number - 1;
+        this->posx = number2 - 1;
+        return CURSOR;
+
+      case 'J':
+        // clear
+        if (cp == last) {
+          number = 0;
+        } else {
+          number = std::stoi(std::string(cp, last));
+        };
+
+        // clears ... part of the screen.
+        if (number == 2) {
+          this->posx = 0;
+          this->posy = 0;
+        };
+        return CLEAR;
+
+      case 'K':
+        // clear line
+        if (cp == last) {
+          number = 0;
+        } else {
+          number = std::stoi(std::string(cp, last));
+        };
+        return CLEAR;
+
+      case 'm':
+        // color
+        if (cp == last) {
+          // nothing given, default to 0.
+          number = 0;
+          ansi_color(number);
+        } else {
+          while (cp != last) {
+            number = std::stoi(std::string(cp, last));
+            ansi_color(number);
+            ++cp;
+
+            while ((cp != last) && (isdigit(*cp))) {
+              ++cp;
+            };
+
+            if (cp != last) {
+              if (*cp == ';') {
+                cp++;
+              }
+            }
+          }
+        }
+        return COLOR;
+
+      case 's':
+        // save position
+        saved_cursor_position.push_back(std::make_pair(this->posx, this->posy));
+        return CURSOR;
+      case 'u':
+        // restore position
+        if (saved_cursor_position.empty()) {
+          ZF_LOGE("Restore cursor position from empty history.");
+        } else {
+          std::pair<int, int> pos = saved_cursor_position.back();
+          this->posx = pos.first;
+          this->posy = pos.second;
+          saved_cursor_position.pop_back();
+        }
+        return CURSOR;
+
+      case 't':
+      case 'r':
+      case 'h':
+      case '!':
+        // These are ones that I don't care about.
+      case 'n': // This is terminal detect -- give me cursor position
+        return OTHER;
+
+      default:
+        // unsure -- possibly not important
+        ZF_LOGD("console_ansi( %s ): ???", repr(ansi.c_str()));
+        return OTHER;
+      }
+    }
+  }
+  ZF_LOGD("console_ansi( %s ) : ???", repr(ansi.c_str()));
+  return OTHER;
+}
+
+Terminal::termchar Terminal::putchar(char ch) {
+  Terminal::termchar tc;
+
+  if (this->in_ansi) {
+    // Ok, append this char
+    this->ansi.append(1, ch);
+
+    // DCS (Designate Character Set)
+    if (ansi[1] == '(') {
+      if (ansi.size() == 3) {
+        // End DCS
+        switch (ansi[2]) {
+        case '0':
+          dcs_map = 0;
+          break;
+        case 'B':
+          dcs_map = 0x0b;
+          break;
+        default:
+          ZF_LOGE("Unknown DCS Mode ^ESC(%c Selected!", ansi[2]);
+          // Use our default, but we know it's wrong!
+          dcs_map = 0x0b;
+          break;
+        }
+        this->in_ansi = 0;
+        this->ansi.clear();
+        tc.in_ansi = 1;
+        tc.ansi = DCS;
+        return tc;
+      }
+    }
+    if (isalpha(ch)) {
+      // Ok!  end of ANSI code, process it.
+      tc.ansi = ansi_code(this->ansi);
+      this->in_ansi = 0;
+      this->ansi.clear();
+      tc.in_ansi = 1;
+      return tc;
+    }
+    tc.ansi = START;
+    tc.in_ansi = 1;
+    return tc;
+  } else {
+    tc.ansi = START;
+    tc.in_ansi = 0;
+
+    if (ch == '\x1b') {
+      this->ansi.append(1, ch);
+      this->in_ansi = 1;
+      tc.in_ansi = 1;
+      return tc;
+    }
+
+    // should I try reporting MOTION non-ANSI ?
+    if (ch == '\r') {
+      // Carriage return
+      this->posx = 0;
+      return tc;
+    }
+    if (ch == '\n') {
+      this->posy++;
+      // check range/"scroll"
+      return tc;
+    }
+    if (ch == '\b') {
+      // Backspace.
+      if (this->posx > 0) {
+        this->posx--;
+      }
+      return tc;
+    }
+    if (ch == '\f') {
+      // form feed
+      // treat as clear screen
+      this->posx = 0;
+      this->posy = 0;
+      return tc;
+    }
+
+    /*
+      I don't believe that anything else can possibly be here, other then an
+      actual printable character.  So!
+
+      FUTURE: Store the screen text + colors
+    */
+
+    this->posx++;
+
+    if (this->posx > 79) {
+      this->posx = 0;
+      this->posy++;
+      // check range/"scroll"
+    }
+    return tc;
+  }
+}
+
+void Terminal::putstring(std::string text) {
+  // This gets noisy when called from render effect ^D
+  // ZF_LOGI("console_char %lu chars", chars.size());
+
+  for (auto strit = text.begin(); strit != text.end(); strit++)
+    putchar(*strit);
+}
+
+// Old non-C++ ways
+
+void console_init(struct console_details *cdp) {
+  cdp->posx = 0;
+  cdp->posy = 0;
+  cdp->savedx = 0;
+  cdp->savedy = 0;
+  cdp->in_ansi = 0;
+  cdp->fgcolor = 7;
+  cdp->bgcolor = 0;
+  cdp->status = 0;
+}
+
+/*
+ * Given the ansi number codes
+ * Figure out fg, bg and status.
+ */
+void ansi_color(struct console_details *cdp, int color) {
+  // There are many calls to this.
+  // ZF_LOGV("ansi_color(%d)", color);
+
+  if (color == 0) {
+    cdp->status = 0;
+    cdp->fgcolor = 7;
+    cdp->bgcolor = 0;
+    return;
+  }
+  if ((color == 1) || (color == 2) || (color == 3) || (color == 4) ||
+      (color == 5)) {
+    cdp->status = color;
+    return;
+  }
+  if ((color >= 30) && (color <= 37)) {
+    cdp->fgcolor = color - 30;
+    return;
+  }
+  if ((color >= 40) && (color <= 47)) {
+    cdp->bgcolor = color - 40;
+    return;
+  }
+  if (color == 39) {
+    // default fg color
+    cdp->fgcolor = 7;
+    return;
+  }
+  if (color == 49) {
+    // default bg color
+    cdp->bgcolor = 0;
+    return;
+  }
+  ZF_LOGD("ansi_color( %d ) is unknown to me.", color);
+}
+
+const char *color_restore(struct console_details *cdp) {
+  static char buffer[30];
+  int slen;
+
+  // possible optimize:  If fg is 7, don't set (0 reset does that), if bg is 0,
+  // don't set (0 does that)
+
+  if (cdp->status == 0) {
+    slen = snprintf(buffer, sizeof(buffer), "\x1b[0;3%d;4%dm", cdp->fgcolor,
+                    cdp->bgcolor);
+    if (slen >= (int)sizeof(buffer)) {
+      ZF_LOGE("snprintf %d > size %d", slen, (int)sizeof(buffer));
+      buffer[0] = 0;
+    }
+  } else {
+    slen = snprintf(buffer, sizeof(buffer), "\x1b[0;%d;3%d;4%dm", cdp->status,
+                    cdp->fgcolor, cdp->bgcolor);
+    if (slen >= (int)sizeof(buffer)) {
+      ZF_LOGE("snprintf %d > size %d", slen, (int)sizeof(buffer));
+      buffer[0] = 0;
+    }
+  };
+  return buffer;
+}
+
+/* store cursor position X,Y */
+std::vector<std::pair<int, int>> cursor_position_history;
+
+ANSI_TYPE console_ansi(struct console_details *cdp, const char *ansi) {
+  const char *cp = ansi;
+  const char *last = ansi + strlen(ansi) - 1;
+  int number, number2;
+
+  if (*cp == '\x1b') {
+    cp++;
+    // Ok, that's expected.
+    if (*cp == '[') {
+      cp++;
+
+      // https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences
+
+      switch (*last) {
+      case 'A':
+        // cursor up
+        if (cp == last) {
+          number = 1;
+        } else {
+          number = atoi(cp);
+          if (number < 1) {
+            ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi),
+                    number);
+            number = 1;
+          }
+        };
+        cdp->posy -= number;
+        if (cdp->posy < 0) {
+          cdp->posy = 0;
+          ZF_LOGD(
+              "console_ansi( %s ): attempt to move above top of screen (%d)",
+              repr(ansi), number);
+        }
+        return CURSOR;
+      case 'B':
+        // cursor down
+        if (cp == last) {
+          number = 1;
+        } else {
+          number = atoi(cp);
+          if (number < 1) {
+            ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi),
+                    number);
+            number = 1;
+          }
+        };
+        cdp->posy += number;
+        // check range/"scroll"
+        return CURSOR;
+
+      case 'C':
+        // cursor forward
+        if (cp == last) {
+          number = 1;
+        } else {
+          number = atoi(cp);
+          if (number < 1) {
+            ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi),
+                    number);
+            number = 1;
+          }
+        };
+        cdp->posx += number;
+
+        // Well.  According to the "spec", the screen limits are hard
+        // If the cursor is already at the edge of the screen, this has no
+        // effect. ^ This is *NOT* how any ANSI BBS terminal program acts!
+
+        while (cdp->posx > 79) {
+          cdp->posy++;
+          // check range/"scroll"
+          cdp->posx -= 79;
+        }
+        return CURSOR;
+
+      case 'D':
+        // cursor backwards
+        if (cp == last) {
+          number = 1;
+        } else {
+          number = atoi(cp);
+          if (number < 1) {
+            ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi),
+                    number);
+            number = 0;
+          }
+        };
+        cdp->posx -= number;
+
+        // Well.  According to the "spec", the screen limits are hard
+        // If the cursor is already at the edge of the screen, this has no
+        // effect. ^ This is *NOT* how any ANSI BBS terminal program acts!
+
+        while (cdp->posx < 0) {
+          cdp->posy--;
+          if (cdp->posy < 0) {
+            cdp->posy = 0;
+          }
+          cdp->posx += 79;
+        }
+        return CURSOR;
+
+      case 'H':
+        // cursor position
+
+        if (*cp == ';') {
+          // Missing first number
+          number = 1;
+          cp++;
+          if (cp == last) {
+            // missing 2nd number as well?
+            number2 = 1;
+          } else {
+            number2 = atoi(cp);
+          }
+        } else {
+          // Ok, find the first number
+          number = atoi(cp);
+
+          cp = strchr(cp, ';');
+          if (cp == NULL) {
+            // Missing 2nd number
+            number2 = 1;
+          } else {
+            // 2nd number found, maybe.
+            cp++;
+            if (cp == last) {
+              number2 = 1;
+            } else {
+              number2 = atoi(cp);
+            }
+          }
+        }
+
+        // Our positions start at zero, not one.
+        cdp->posy = number - 1;
+        cdp->posx = number2 - 1;
+        return CURSOR;
+
+      case 'J':
+        // clear
+        if (cp == last) {
+          number = 0;
+        } else {
+          number = atoi(cp);
+        };
+
+        // clears ... part of the screen.
+        if (number == 2) {
+          cdp->posx = 0;
+          cdp->posy = 0;
+        };
+        return CLEAR;
+
+      case 'K':
+        // clear line
+        if (cp == last) {
+          number = 0;
+        } else {
+          number = atoi(cp);
+        };
+        return CLEAR;
+
+      case 'm':
+        // color
+        if (cp == last) {
+          // nothing given, default to 0.
+          number = 0;
+          ansi_color(cdp, number);
+        } else {
+          while (cp != last) {
+            number = atoi(cp);
+            ansi_color(cdp, number);
+            cp++;
+
+            while ((cp != last) && (isdigit(*cp))) {
+              cp++;
+            };
+
+            if (cp != last) {
+              if (*cp == ';') {
+                cp++;
+              }
+            }
+          }
+        }
+        return COLOR;
+
+      case 's':
+        // save position
+        cursor_position_history.push_back(std::make_pair(cdp->posx, cdp->posy));
+        return CURSOR;
+      case 'u':
+        // restore position
+        if (cursor_position_history.empty()) {
+          ZF_LOGE("Restore cursor position from empty history.");
+        } else {
+          std::pair<int, int> pos = cursor_position_history.back();
+          cdp->posx = pos.first;
+          cdp->posy = pos.second;
+          cursor_position_history.pop_back();
+        }
+        return CURSOR;
+
+      case 't':
+      case 'r':
+      case 'h':
+      case '!':
+        // These are ones that I don't care about.
+      case 'n': // This is terminal detect -- give me cursor position
+        return OTHER;
+
+      default:
+        // unsure -- possibly not important
+        ZF_LOGD("console_ansi( %s ): ???", repr(ansi));
+        return OTHER;
+      }
+    }
+  }
+  ZF_LOGD("console_ansi( %s ) : ???", repr(ansi));
+  return OTHER;
+}
+
+/*
+ * console_char()
+ *  return whether or not we are still in_ansi
+ *
+ * in_ansi TRUE: START (receiving start of/fragment of ANSI)
+ *               CURSOR, COLOR, CLEAR, OTHER
+ *               Results of the last ANSI parse.
+ * in_ansi FALSE: START Normal character.
+ *
+ */
+termchar console_char(struct console_details *cdp, char ch) {
+  char *cp;
+  termchar tc;
+
+  if (cdp->in_ansi) {
+    // Ok, append this char
+    cp = cdp->ansi + strlen(cdp->ansi);
+    *cp = ch;
+    cp++;
+    *cp = 0;
+    if (isalpha(ch)) {
+      // Ok!  end of ANSI code, process it.
+      tc.ansi = console_ansi(cdp, cdp->ansi);
+      cdp->in_ansi = 0;
+      cdp->ansi[0] = 0;
+      tc.in_ansi = 1;
+      return tc;
+    }
+    tc.ansi = START;
+    tc.in_ansi = 1;
+    return tc;
+  } else {
+
+    tc.ansi = START;
+    tc.in_ansi = 0;
+
+    if (ch == '\x1b') {
+      cp = cdp->ansi;
+      *cp = ch;
+      cp++;
+      *cp = 0;
+      cdp->in_ansi = 1;
+      tc.in_ansi = 1;
+      return tc;
+    }
+
+    // should I try reporting MOTION non-ANSI ?
+    if (ch == '\r') {
+      // Carriage return
+      cdp->posx = 0;
+      return tc;
+    }
+    if (ch == '\n') {
+      cdp->posy++;
+      // check range/"scroll"
+      return tc;
+    }
+    if (ch == '\b') {
+      // Backspace.
+      if (cdp->posx > 0) {
+        cdp->posx--;
+      }
+      return tc;
+    }
+    if (ch == '\f') {
+      // form feed
+      // treat as clear screen
+      cdp->posx = 0;
+      cdp->posy = 0;
+      return tc;
+    }
+
+    /*
+      I don't believe that anything else can possibly be here, other then an
+      actual printable character.  So!
+
+      FUTURE: Store the screen text + colors
+    */
+
+    cdp->posx++;
+
+    if (cdp->posx > 79) {
+      cdp->posx = 0;
+      cdp->posy++;
+      // check range/"scroll"
+    }
+    return tc;
+  }
+}
+
+void console_receive(struct console_details *cdp, std::string chars) {
+  // This gets noisy when called from render effect ^D
+  // ZF_LOGI("console_char %lu chars", chars.size());
+
+  for (auto strit = chars.begin(); strit != chars.end(); strit++)
+    console_char(cdp, *strit);
+}

+ 75 - 0
terminal.h

@@ -0,0 +1,75 @@
+#ifndef TERMINAL_H
+#define TERMINAL_H
+
+#include <string>
+#include <vector>
+
+class Terminal {
+private:
+  int posx, posy;
+  std::vector<std::pair<int, int>> saved_cursor_position;
+  std::string ansi;
+  int in_ansi;
+  int fgcolor, bgcolor, status;
+  int dcs_map; // Designate Charater Set
+
+public:
+  enum ANSI_TYPE { START, CURSOR, COLOR, CLEAR, DCS, OTHER };
+  struct termchar {
+    int in_ansi;
+    Terminal::ANSI_TYPE ansi;
+  };
+
+public:
+  Terminal(void);
+  Terminal(const Terminal &old);
+  Terminal &operator=(Terminal &rhs);
+
+  void init(void);
+  std::string color_restore(void);
+  termchar putchar(char ch);
+  void putstring(std::string text);
+
+  int getx(void);
+  int gety(void);
+  int getstatus(void);
+  int inANSI(void);
+  int fg(void);
+  int bg(void);
+  bool ansiempty(void);
+  int dcs(void);
+
+private:
+  void ansi_color(int color);
+  ANSI_TYPE ansi_code(std::string ansi);
+};
+
+// notice the curstor position history vector is missing from the struct below?
+// Yes, it is a global.  Completely WRONG.  Broken!  Bad!
+
+struct console_details {
+  int posx, posy;
+  int savedx, savedy;
+  char ansi[20]; // current ANSI command being processed.
+  int in_ansi;
+  int fgcolor; // 0-7 // not 0-15
+  int bgcolor; // 0-7
+  int status;  // 0, 1 or 5 (Blink)
+};
+
+enum ANSI_TYPE { START, CURSOR, COLOR, CLEAR, OTHER };
+
+struct termchar {
+  int in_ansi;
+  ANSI_TYPE ansi; // undefined if in_ansi is false
+};
+
+void console_init(struct console_details *cdp);
+void ansi_color(struct console_details *cdp, int color);
+const char *color_restore(struct console_details *cdp);
+ANSI_TYPE console_ansi(struct console_details *cdp, const char *ansi);
+
+termchar console_char(struct console_details *cdp, char ch);
+void console_receive(struct console_details *cdp, std::string chars);
+
+#endif

+ 505 - 0
utils.cpp

@@ -0,0 +1,505 @@
+#include "utils.h"
+#include <algorithm> // transform
+#include <fstream>
+#include <sstream>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string>
+#include <time.h>
+
+// http://c-faq.com/lib/randrange.html
+int randint(int N) { return rand() / (RAND_MAX / N + 1); }
+
+// http://c-faq.com/lib/randrange.html
+// numbers in the range [M, N] could be generated with something like
+
+int randrange(int M, int N) {
+  return M + rand() / (RAND_MAX / (N - M + 1) + 1);
+}
+
+/**
+ * random_activate()
+ *
+ * Is a weight (1-10),
+ * tests if random number is < weight * 10.
+ *
+ * So random_activate(9) happens more frequently
+ * then random_activate(8) or lower.
+ *
+ * This probably needs to be fixed.
+ * We need a better randint(RANGE) code.
+ */
+int random_activate(int w) {
+  int r = randint(100);
+  if (r <= w) {
+    return 1;
+  };
+  return 0;
+}
+
+/**
+ * Display a repr of the given string.
+ *
+ * This converts most \n\r\v\f\t codes,
+ * defaults to \xHH (hex value).
+ */
+char *repr(const char *data) {
+  static char buffer[40960];
+  char *cp;
+
+  strcpy(buffer, data);
+  cp = buffer;
+  while (*cp != 0) {
+    char c = *cp;
+
+    if (c == ' ') {
+      cp++;
+      continue;
+    };
+    /* Ok, it's form-feed ('\f'), newline ('\n'), carriage return ('\r'),
+     * horizontal tab ('\t'), and vertical tab ('\v') */
+    if (strchr("\f\n\r\t\v\?", c) != NULL) {
+      memmove(cp + 1, cp, strlen(cp) + 1);
+      *cp = '\\';
+      cp++;
+      switch (c) {
+      case '\f':
+        *cp = 'f';
+        cp++;
+        break;
+      case '\n':
+        *cp = 'n';
+        cp++;
+        break;
+      case '\r':
+        *cp = 'r';
+        cp++;
+        break;
+      case '\t':
+        *cp = 't';
+        cp++;
+        break;
+      case '\v':
+        *cp = 'v';
+        cp++;
+        break;
+      default:
+        *cp = '?';
+        cp++;
+        break;
+      }
+      continue;
+    }
+
+    if (c == '\\') {
+      memmove(cp + 1, cp, strlen(cp) + 1);
+      *cp = '\\';
+      cp += 2;
+      continue;
+    }
+    if (c == '"') {
+      memmove(cp + 1, cp, strlen(cp) + 1);
+      *cp = '\\';
+      cp += 2;
+      continue;
+    }
+    if (strchr("[()]{}:;,.<>?!@#$%^&*", c) != NULL) {
+      cp++;
+      continue;
+    }
+    if (strchr("0123456789", c) != NULL) {
+      cp++;
+      continue;
+    }
+    if (strchr("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", c) !=
+        NULL) {
+      cp++;
+      continue;
+    }
+
+    // Ok, default to \xHH output.
+    memmove(cp + 3, cp, strlen(cp) + 1);
+    char buffer[10];
+    // int slen =
+    snprintf(buffer, sizeof(buffer), "\\x%02x", (int)c & 0xff);
+    /*
+    if (slen >= sizeof(buffer)) {
+      ZF_LOGE("snprintf %d > size %d", slen, (int)sizeof(buffer));
+    }
+    */
+    strncpy(cp, buffer, 4);
+    cp += 4;
+    continue;
+  }
+
+  return buffer;
+}
+
+/*
+ * strnstr()
+ *
+ * buffer safe version that looks for a string.
+ */
+const char *strnstr(const char *source, int len, const char *needle) {
+  int pos;
+
+  for (pos = 0; pos < len; pos++) {
+    if (source[pos] == needle[0]) {
+      if (strncmp(source + pos, needle, strlen(needle)) == 0) {
+        return source + pos;
+      }
+    }
+  }
+  return NULL;
+}
+
+/*
+ * rstrnstr() Reverse string find in a string
+ *
+ * This obeys the len, and handles nulls in buffer.
+ * find is a c-string (null terminated)
+ */
+int rstrnstr(const char *buffer, int len, const char *find) {
+  int flen = strlen(find);
+
+  if (len < flen) {
+    // I can't find a string in a buffer smaller then it is!
+    return -1;
+  }
+  int pos = len - flen;
+  while (pos > 0) {
+    if (buffer[pos] == find[0]) {
+      // First chars match, check them all.
+      if (strncmp(buffer + pos, find, flen) == 0) {
+        return pos;
+      }
+    }
+    pos--;
+  }
+  return -1;
+}
+
+/*
+ * string_insert()
+ * Inserts a string into a given position.
+ * This safely checks to make sure the buffer isn't overrun.
+ *
+ * buffer is a c null terminated string.
+ */
+int string_insert(char *buffer, size_t max_length, size_t pos,
+                  const char *insert) {
+  /*
+  assert(max_length > pos);
+  assert(buffer != NULL);
+  assert(insert != NULL);
+  */
+  if (pos >= max_length)
+    return 0;
+  if (buffer == NULL)
+    return 0;
+  if (insert == NULL)
+    return 0;
+  if (strlen(insert) == 0)
+    return 0;
+  if (pos > strlen(buffer))
+    return 0;
+
+  if (strlen(buffer) + strlen(insert) >= max_length) {
+    /*
+    ZF_LOGD("string_insert() failed inserting [%s]", repr(insert));
+    */
+    return 0;
+  }
+  memmove(buffer + pos + strlen(insert), buffer + pos,
+          strlen(buffer + pos) + 1);
+  // cp + strlen(display), cp, strlen(cp) + 1);
+  strncpy(buffer + pos, insert, strlen(insert));
+  // (cp, display, strlen(display));
+  return 1; // success
+}
+
+/*
+Pascal String Copy.  Copy from pascal string, to C String.
+
+First char is pascal string length.  (Max 255).
+ */
+void pcopy(char *pstring, char *str) {
+  int len = (int)*pstring;
+  strncpy(str, pstring + 1, len);
+  str[len] = 0;
+}
+
+/*
+ * tail file, return new lines of text.
+ *
+ * Only handles lines < 256 chars.
+ * Does not handle if the file is closed/unlinked/...
+ *
+ */
+std::string &find_new_text(std::ifstream &infile,
+                           std::streampos &last_position) {
+  static std::string line;
+  line.clear();
+
+  infile.seekg(0, std::ios::end);
+  std::streampos filesize = infile.tellg();
+
+  if (filesize == -1) {
+    // Ok, we've failed somehow.  Now what?
+    // cout << "SNAP!";
+    return line;
+  }
+
+  // check if the new file started
+  // we don't detect if the file has been unlinked/reset
+  if (filesize < last_position) {
+    // cout << "reset! " << filesize << endl;
+    last_position = 0;
+  }
+
+  while (last_position < filesize) {
+    // this loop -- seems broken.
+    // for (long n = last_position; n < filesize; n++) {
+
+    infile.seekg(last_position, std::ios::beg);
+
+    char test[256];
+    infile.getline(test, sizeof(test));
+
+    if (infile.eof()) {
+      // We got EOF instead of something.
+      // Seek back to our last, good, known position
+      // and exit (wait for the rest of the line)
+      infile.seekg(last_position, std::ios::beg);
+      return line;
+    }
+
+    std::streampos new_pos = infile.tellg();
+    if (new_pos == -1)
+      return line;
+
+    last_position = new_pos;
+    line.assign(test);
+    return line;
+  }
+  return line;
+}
+
+void string_toupper(std::string &str) {
+  std::transform(str.begin(), str.end(), str.begin(), ::toupper);
+}
+
+void string_trim(std::string &value) {
+  while (*value.begin() == ' ')
+    value.erase(value.begin());
+  while (*(value.end() - 1) == ' ')
+    value.erase(value.end() - 1);
+}
+
+std::map<std::string, std::string> read_configfile(std::string filename) {
+  std::ifstream file(filename);
+  std::string line;
+  std::map<std::string, std::string> config;
+  if (!file.is_open())
+    return config;
+
+  while (std::getline(file, line)) {
+    if ((line[0] == '#') || (line[0] == ';'))
+      continue;
+    if (line.empty())
+      continue;
+
+    // I'm not so sure this part is very good.
+
+    std::istringstream is_line(line);
+    std::string key;
+    if (std::getline(is_line, key, '=')) {
+      string_trim(key);
+      string_toupper(key);
+
+      std::string value;
+      if (std::getline(is_line, value)) {
+        string_trim(value);
+
+        config[key] = value;
+        /*
+                std::cout << "Key: [" << key << "] Value: [" << value << "]"
+                          << std::endl;
+        */
+      }
+    }
+  }
+
+  return config;
+}
+
+bool replace(std::string &str, const std::string &from, const std::string &to) {
+  size_t start_pos = str.find(from);
+  if (start_pos == std::string::npos)
+    return false;
+  str.replace(start_pos, from.length(), to);
+  return true;
+}
+
+IConv::IConv(const char *to, const char *from) : ic(iconv_open(to, from)) {}
+IConv::~IConv() { iconv_close(ic); }
+
+int IConv::convert(char *input, char *output, size_t outbufsize) {
+  size_t inbufsize = strlen(input);
+  // size_t orig_size = outbufsize;
+  // memset(output, 0, outbufsize);
+  // https://www.gnu.org/savannah-checkouts/gnu/libiconv/documentation/libiconv-1.15/iconv.3.html
+  int r = iconv(ic, &input, &inbufsize, &output, &outbufsize);
+  *output = 0;
+  return r;
+}
+
+static IConv converter("UTF-8", "CP437");
+
+static time_t last_time = 0;
+
+std::map<std::string, std::string> CONFIG = read_configfile("hharry.cfg");
+
+/*
+ * harry_level:
+ *
+ *  0 - inactive
+ *  1 - Week 1 (1-7)
+ *  2 - Week 2 (8-14)
+ *  3 - Week 3 (15-21)
+ *  4 - Week 4 (22-31)
+ */
+int harry_level(void) {
+  struct tm *tmp;
+  time_t now = time(NULL);
+  static int last = 0;
+
+  auto search = CONFIG.find("LEVEL");
+  if (search != CONFIG.end()) {
+    last = stoi(search->second);
+    if (last > 4) {
+      last = 4;
+    }
+    if (last < 0) {
+      last = 0;
+    }
+    return last;
+  }
+
+  if (last_time < now + 10) {
+    last_time = now;
+    // Do our every 10 second checks here
+
+    tmp = gmtime(&now);
+    if (tmp->tm_mon != 9)
+      return (last = 0);
+    if (tmp->tm_mday < 8)
+      return (last = 1);
+    if (tmp->tm_mday < 15)
+      return (last = 2);
+    if (tmp->tm_mday < 22)
+      return (last = 3);
+    return (last = 4);
+  }
+  return last;
+}
+
+/*
+ * This is similar to repr, but --
+ *
+ * It converts high ASCII to UTF8, so it will display correctly
+ * in the logfiles!
+ */
+const char *logrepr(const char *input) {
+  static char buffer[10240];
+  char *cp;
+
+  strcpy(buffer, input);
+  cp = buffer;
+  while (*cp != 0) {
+    unsigned char c = *cp;
+
+    if (c == ' ') {
+      cp++;
+      continue;
+    };
+    /* Ok, it's form-feed ('\f'), newline ('\n'), carriage return ('\r'),
+     * horizontal tab ('\t'), and vertical tab ('\v') */
+    if (strchr("\f\n\r\t\v", c) != NULL) {
+      memmove(cp + 1, cp, strlen(cp) + 1);
+      *cp = '\\';
+      cp++;
+      switch (c) {
+      case '\f':
+        *cp = 'f';
+        cp++;
+        break;
+      case '\n':
+        *cp = 'n';
+        cp++;
+        break;
+      case '\r':
+        *cp = 'r';
+        cp++;
+        break;
+      case '\t':
+        *cp = 't';
+        cp++;
+        break;
+      case '\v':
+        *cp = 'v';
+        cp++;
+        break;
+        /*      default:
+                *cp = '?';
+                cp++;
+                break; */
+      }
+      continue;
+    }
+
+    if (c == '\\') {
+      memmove(cp + 1, cp, strlen(cp) + 1);
+      *cp = '\\';
+      cp += 2;
+      continue;
+    }
+    if (c == '"') {
+      memmove(cp + 1, cp, strlen(cp) + 1);
+      *cp = '\\';
+      cp += 2;
+      continue;
+    }
+    if (strchr("[()]{}:;,.<>?!@#$%^&*", c) != NULL) {
+      cp++;
+      continue;
+    }
+    if (strchr("0123456789", c) != NULL) {
+      cp++;
+      continue;
+    }
+    if (strchr("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", c) !=
+        NULL) {
+      cp++;
+      continue;
+    }
+
+    if ((int)c < 0x20) {
+      // Ok, default to \xHH output.
+      memmove(cp + 3, cp, strlen(cp) + 1);
+      char buffer[10];
+      // int slen =
+      snprintf(buffer, sizeof(buffer), "\\x%02x", (int)c & 0xff);
+      strncpy(cp, buffer, 4);
+      cp += 4;
+      continue;
+    };
+    cp++;
+    continue;
+  }
+
+  static char fancybuffer[16384];
+  converter.convert(buffer, fancybuffer, sizeof(fancybuffer));
+  return fancybuffer;
+}

+ 57 - 0
utils.h

@@ -0,0 +1,57 @@
+#ifndef UTILS_H
+#define UTILS_H
+
+#include <fstream>
+#include <map>
+#include <string>
+
+// http://c-faq.com/lib/randrange.html
+int randint(int N);
+
+// http://c-faq.com/lib/randrange.html
+// numbers in the range [M, N] could be generated with something like
+
+int randrange(int M, int N);
+int random_activate(int w);
+
+/**
+ * Display a repr of the given string.
+ *
+ * This converts most \n\r\v\f\t codes,
+ * defaults to \xHH (hex value).
+ */
+
+char *repr(const char *data);
+const char *logrepr(const char *input);
+const char *strnstr(const char *source, int len, const char *needle);
+int rstrnstr(const char *buffer, int len, const char *find);
+int string_insert(char *buffer, size_t max_length, size_t pos,
+                  const char *insert);
+void pcopy(char *pstring, char *str);
+
+std::string &find_new_text(std::ifstream &infile,
+                           std::streampos &last_position);
+
+void string_toupper(std::string &str);
+void string_trim(std::string &value);
+std::map<std::string, std::string> read_configfile(std::string filename);
+extern std::map<std::string, std::string> CONFIG;
+bool replace(std::string &str, const std::string &from, const std::string &to);
+
+int harry_level(void);
+
+#include <iconv.h>
+
+class IConv {
+  iconv_t ic;
+
+public:
+  IConv(const char *to, const char *from);
+  ~IConv();
+
+  int convert(char *input, char *output, size_t outbufsize);
+};
+
+// IConv converter("UTF-8", "CP437");
+
+#endif

Some files were not shown because too many files changed in this diff