]> git.ipfire.org Git - thirdparty/plymouth.git/commitdiff
Introduce ply-terminal-emulator to handle various console sequences from kernel loggi...
authornerdopolis <bluescreen_avenger@verizon.net>
Sun, 28 May 2023 01:22:32 +0000 (21:22 -0400)
committerRay Strode <rstrode@redhat.com>
Sat, 2 Dec 2023 22:44:11 +0000 (17:44 -0500)
src/libply-splash-core/meson.build
src/libply-splash-core/ply-terminal-emulator.c [new file with mode: 0644]
src/libply-splash-core/ply-terminal-emulator.h [new file with mode: 0644]

index 4d8469752fd58611fa9932789e4fe6caca227357..0a7a6c5b651ef148a7557fd4828b56ed51073b06 100644 (file)
@@ -8,6 +8,7 @@ libply_splash_core_sources = files(
   'ply-renderer.c',
   'ply-rich-text.c',
   'ply-terminal.c',
+  'ply-terminal-emulator.c',
   'ply-text-display.c',
   'ply-text-progress-bar.c',
   'ply-text-step-bar.c',
@@ -62,6 +63,7 @@ libply_splash_core_headers = files(
   'ply-renderer.h',
   'ply-rich-text.h',
   'ply-terminal.h',
+  'ply-terminal-emulator.h',
   'ply-text-display.h',
   'ply-text-progress-bar.h',
   'ply-text-step-bar.h',
diff --git a/src/libply-splash-core/ply-terminal-emulator.c b/src/libply-splash-core/ply-terminal-emulator.c
new file mode 100644 (file)
index 0000000..4752622
--- /dev/null
@@ -0,0 +1,1288 @@
+/* ply-terminal-emulator.c - Minimal Terminal Emulator
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#include "ply-array.h"
+#include "ply-terminal-emulator.h"
+#include "ply-logger.h"
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include <stdio.h>
+
+#ifndef PLY_TERMINAL_LINE_MAX
+#define PLY_TERMINAL_LINE_MAX 4096
+#endif
+
+#define PLY_TERMINAL_SPACES_PER_TAB 8
+
+/* Characters between 64 to 157 end the escape sequence strings (in testing)
+ *  for i in $(seq 1 255)
+ *  do
+ *          if [[ $i == 72 || $i == 99 || $i == 100 || $i == 101 || $i == 102 || $i == 114 ]]
+ *          then
+ *                 continue
+ *          fi
+ *          printf -v CHARHEX "%x" $i
+ *          printf -v CHAR '%b' "\U$CHARHEX"
+ *          echo -e "$i $CHAR \033[${CHAR}aabc"
+ *  done
+ * (meaning that $CHAR ends the sequence instead of the first a)
+ */
+#define PLY_TERMINAL_ESCAPE_CODE_COMMAND_MINIMUM 64
+#define PLY_TERMINAL_ESCAPE_CODE_COMMAND_MAXIMUM 157
+
+typedef enum
+{
+        PLY_TERMINAL_EMULATOR_PARSE_STATE_UNESCAPED,
+        PLY_TERMINAL_EMULATOR_PARSE_STATE_ESCAPED,
+        PLY_TERMINAL_EMULATOR_PARSE_STATE_CONTROL_SEQUENCE_PARAMETER
+} ply_terminal_emulator_parse_state_t;
+
+typedef enum
+{
+        PLY_TERMINAL_EMULATOR_COMMAND_TYPE_CONTROL_CHARACTER,
+        PLY_TERMINAL_EMULATOR_COMMAND_TYPE_ESCAPE,
+        PLY_TERMINAL_EMULATOR_COMMAND_TYPE_CONTROL_SEQUENCE
+} ply_terminal_emulator_command_type_t;
+
+typedef enum
+{
+        PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE,
+        PLY_TERMINAL_EMULATOR_BREAK_STRING,
+} ply_terminal_emulator_break_string_t;
+
+typedef enum
+{
+        PLY_TERMINAL_EMULATOR_ERASE_LINE_TYPE_CURSOR_TO_RIGHT,
+        PLY_TERMINAL_EMULATOR_ERASE_LINE_TYPE_CURSOR_TO_LEFT,
+        PLY_TERMINAL_EMULATOR_ERASE_LINE_TYPE_WHOLE_LINE,
+} ply_terminal_emulator_erase_line_type_t;
+
+typedef enum
+{
+        PLY_TERMINAL_EMULATOR_BREAK_STRING_ACTION_RESET_CURSOR_COLUMN,
+        PLY_TERMINAL_EMULATOR_BREAK_STRING_ACTION_PRESERVE_CURSOR_COLUMN,
+} ply_terminal_emulator_break_string_action_t;
+
+typedef struct
+{
+        char                                 code;
+        ply_terminal_emulator_command_type_t type;
+        ply_array_t                         *parameters;
+        uint32_t                             parameters_valid : 1;
+} ply_terminal_emulator_command_t;
+
+struct _ply_terminal_emulator
+{
+        ply_terminal_emulator_parse_state_t         state;
+
+        size_t                                      maximum_line_count;
+        size_t                                      line_count;
+        ply_array_t                                *lines;
+
+        ply_trigger_t                              *output_trigger;
+
+        ssize_t                                     cursor_row_offset; /* Relative to the bottom-most allocated line */
+        size_t                                      cursor_column;
+        ply_terminal_emulator_break_string_action_t break_action;
+
+        uint32_t                                    last_parameter_was_integer : 1;
+        ply_terminal_emulator_command_t            *staged_command;
+        ply_list_t                                 *pending_commands;
+
+        ply_rich_text_t                            *current_line;
+        ply_rich_text_character_style_t             current_style;
+
+        uint32_t                                    default_colors_forced : 1;
+};
+
+typedef ply_terminal_emulator_break_string_t (*ply_terminal_emulator_dispatch_handler_t)();
+typedef ply_terminal_emulator_break_string_t (*ply_terminal_emulator_control_sequence_handler_t) (ply_terminal_emulator_t *terminal_emulator,
+                                                                                                  const char               code,
+                                                                                                  uint32_t                 parameters[],
+                                                                                                  size_t                   number_of_parameters,
+                                                                                                  bool                     parameters_valid);
+typedef ply_terminal_emulator_break_string_t (*ply_terminal_emulator_control_character_handler_t) (ply_terminal_emulator_t *terminal_emulator,
+                                                                                                   const char               code);
+typedef ply_terminal_emulator_break_string_t (*ply_terminal_emulator_escape_sequence_handler_t) (ply_terminal_emulator_t *terminal_emulator,
+                                                                                                 const char               code);
+
+static ply_terminal_emulator_command_t *ply_terminal_emulator_command_new (void);
+static void ply_terminal_emulator_command_free (ply_terminal_emulator_command_t *command);
+
+ply_terminal_emulator_t *
+ply_terminal_emulator_new (size_t maximum_line_count)
+{
+        ply_terminal_emulator_t *terminal_emulator;
+
+        terminal_emulator = calloc (1, sizeof(struct _ply_terminal_emulator));
+
+        terminal_emulator->line_count = 1;
+        terminal_emulator->maximum_line_count = maximum_line_count;
+        terminal_emulator->lines = ply_array_new (PLY_ARRAY_ELEMENT_TYPE_POINTER);
+        for (int i = 0; i < terminal_emulator->maximum_line_count; i++) {
+                ply_array_add_pointer_element (terminal_emulator->lines, ply_rich_text_new ());
+        }
+
+        terminal_emulator->cursor_row_offset = 0;
+
+        terminal_emulator->state = PLY_TERMINAL_EMULATOR_PARSE_STATE_UNESCAPED;
+
+        terminal_emulator->break_action = PLY_TERMINAL_EMULATOR_BREAK_STRING_ACTION_PRESERVE_CURSOR_COLUMN;
+        terminal_emulator->output_trigger = ply_trigger_new (NULL);
+
+        terminal_emulator->pending_commands = ply_list_new ();
+
+        terminal_emulator->current_style.foreground_color = PLY_TERMINAL_COLOR_DEFAULT;
+        terminal_emulator->current_style.background_color = PLY_TERMINAL_COLOR_DEFAULT;
+        terminal_emulator->current_style.bold_enabled = false;
+        terminal_emulator->current_style.dim_enabled = false;
+        terminal_emulator->current_style.italic_enabled = false;
+        terminal_emulator->current_style.underline_enabled = false;
+        terminal_emulator->current_style.reverse_enabled = false;
+
+        terminal_emulator->default_colors_forced = false;
+
+        return terminal_emulator;
+}
+
+void
+ply_terminal_emulator_free (ply_terminal_emulator_t *terminal_emulator)
+{
+        ply_rich_text_t **lines;
+        ply_list_node_t *node;
+
+        ply_list_foreach (terminal_emulator->pending_commands, node) {
+                ply_terminal_emulator_command_t *command;
+                command = ply_list_node_get_data (node);
+                ply_terminal_emulator_command_free (command);
+        }
+        ply_list_free (terminal_emulator->pending_commands);
+
+        lines = (ply_rich_text_t **) ply_array_get_pointer_elements (terminal_emulator->lines);
+        for (size_t i = 0; lines[i] != NULL; i++) {
+                ply_rich_text_drop_reference (lines[i]);
+        }
+
+        ply_array_free (terminal_emulator->lines);
+
+        ply_trigger_free (terminal_emulator->output_trigger);
+
+        free (terminal_emulator);
+}
+
+static ply_terminal_emulator_command_t *
+ply_terminal_emulator_command_new (void)
+{
+        ply_terminal_emulator_command_t *command_object = calloc (1, sizeof(ply_terminal_emulator_command_t));
+        return command_object;
+}
+
+static void
+ply_terminal_emulator_command_free (ply_terminal_emulator_command_t *command)
+{
+        free (command);
+}
+
+void
+fill_offsets_with_padding (ply_terminal_emulator_t *terminal_emulator,
+                           size_t                   pad_start,
+                           size_t                   pad_stop)
+{
+        ssize_t bytes_to_pad = pad_stop - pad_start;
+
+        if (pad_start < 0 || bytes_to_pad <= 0)
+                return;
+
+        if (pad_stop > pad_start) {
+                for (size_t i = pad_start; i <= pad_stop; i++) {
+                        ply_rich_text_set_character (terminal_emulator->current_line, terminal_emulator->current_style, i, " ", 1);
+                }
+        }
+}
+
+/* escape sequence '<ESC>D' */
+ply_terminal_emulator_break_string_t
+on_escape_sequence_linefeed (ply_terminal_emulator_t *terminal_emulator,
+                             const char               code)
+{
+        ply_trace ("terminal escape equence: line feed");
+
+        assert (code == 'D');
+
+        terminal_emulator->cursor_row_offset++;
+        terminal_emulator->break_action = PLY_TERMINAL_EMULATOR_BREAK_STRING_ACTION_PRESERVE_CURSOR_COLUMN;
+
+        return PLY_TERMINAL_EMULATOR_BREAK_STRING;
+}
+
+/* escape sequence '<ESC>E' */
+ply_terminal_emulator_break_string_t
+on_escape_sequence_newline (ply_terminal_emulator_t *terminal_emulator,
+                            const char               code)
+{
+        ply_trace ("terminal escape equence: new line");
+
+        assert (code == 'E');
+
+        terminal_emulator->cursor_row_offset++;
+        terminal_emulator->break_action = PLY_TERMINAL_EMULATOR_BREAK_STRING_ACTION_RESET_CURSOR_COLUMN;
+
+        return PLY_TERMINAL_EMULATOR_BREAK_STRING;
+}
+
+/* escape sequence '<ESC>M' */
+ply_terminal_emulator_break_string_t
+on_escape_sequence_reverse_linefeed (ply_terminal_emulator_t *terminal_emulator,
+                                     const char               code)
+{
+        ply_trace ("terminal escape equence: reverse line feed");
+
+        assert (code == 'M');
+
+        terminal_emulator->cursor_row_offset--;
+        terminal_emulator->break_action = PLY_TERMINAL_EMULATOR_BREAK_STRING_ACTION_PRESERVE_CURSOR_COLUMN;
+
+        return PLY_TERMINAL_EMULATOR_BREAK_STRING;
+}
+
+/* Control sequence '@' for '[@' */
+ply_terminal_emulator_break_string_t
+on_control_sequence_insert_blank_characters (ply_terminal_emulator_t *terminal_emulator,
+                                             const char               code,
+                                             uint32_t                 parameters[],
+                                             size_t                   number_of_parameters,
+                                             bool                     paramaters_valid)
+{
+        int parameter;
+        size_t string_length = ply_rich_text_get_length (terminal_emulator->current_line);
+        size_t new_string_length;
+        size_t append_count;
+        size_t string_move_end_offset;
+
+        ply_trace ("terminal control sequence: insert blank characters");
+
+        assert (code == '@');
+
+        if (paramaters_valid != true)
+                return PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+
+        terminal_emulator->break_action = PLY_TERMINAL_EMULATOR_BREAK_STRING_ACTION_PRESERVE_CURSOR_COLUMN;
+
+        if (number_of_parameters > 0) {
+                parameter = parameters[0];
+
+                /* 0 acts as 1, and this should not be negative */
+                if (parameter <= 0)
+                        parameter = 1;
+        } else {
+                parameter = 1;
+        }
+
+        new_string_length = string_length + parameter;
+        if (new_string_length >= PLY_TERMINAL_LINE_MAX) {
+                append_count = PLY_TERMINAL_LINE_MAX - string_length - 1;
+                new_string_length = PLY_TERMINAL_LINE_MAX - 1;
+        } else {
+                append_count = parameter;
+        }
+
+        string_move_end_offset = string_length - 1;
+        if (string_move_end_offset >= PLY_TERMINAL_LINE_MAX)
+                string_move_end_offset = PLY_TERMINAL_LINE_MAX - 1;
+
+        if (new_string_length <= 0)
+                return PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+
+        fill_offsets_with_padding (terminal_emulator, string_length, new_string_length);
+
+        for (int i = string_move_end_offset; i >= terminal_emulator->cursor_column; i--) {
+                ply_rich_text_move_character (terminal_emulator->current_line,
+                                              i,
+                                              i + append_count);
+                ply_rich_text_set_character (terminal_emulator->current_line, terminal_emulator->current_style, i, " ", 1);
+
+                if (i <= 0)
+                        break;
+        }
+
+        return PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+}
+
+/* Control sequence 'A' for '[A' */
+ply_terminal_emulator_break_string_t
+on_control_sequence_move_cursor_up_rows (ply_terminal_emulator_t *terminal_emulator,
+                                         const char               code,
+                                         uint32_t                 parameters[],
+                                         size_t                   number_of_parameters,
+                                         bool                     paramaters_valid)
+{
+        int parameter;
+
+        ply_trace ("terminal control sequence: move cursor up rows");
+
+        assert (code == 'A');
+
+        if (paramaters_valid != true)
+                return PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+
+        terminal_emulator->break_action = PLY_TERMINAL_EMULATOR_BREAK_STRING_ACTION_PRESERVE_CURSOR_COLUMN;
+
+        if (number_of_parameters > 0) {
+                parameter = parameters[0];
+
+                /* 0 acts as 1, and this should not be negative */
+                if (parameter <= 0)
+                        parameter = 1;
+        } else {
+                parameter = 1;
+        }
+
+        terminal_emulator->cursor_row_offset -= parameter;
+        return PLY_TERMINAL_EMULATOR_BREAK_STRING;
+}
+
+/* Control sequence 'B' for '[B' */
+ply_terminal_emulator_break_string_t
+on_control_sequence_move_cursor_down_rows (ply_terminal_emulator_t *terminal_emulator,
+                                           const char               code,
+                                           uint32_t                 parameters[],
+                                           size_t                   number_of_parameters,
+                                           bool                     paramaters_valid)
+{
+        int parameter;
+
+        ply_trace ("terminal control sequence: move cursor down rows");
+
+        assert (code == 'B');
+
+        if (paramaters_valid != true)
+                return PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+
+        terminal_emulator->break_action = PLY_TERMINAL_EMULATOR_BREAK_STRING_ACTION_PRESERVE_CURSOR_COLUMN;
+
+        if (number_of_parameters > 0) {
+                parameter = parameters[0];
+
+                /* 0 acts as 1, and this should not be negative */
+                if (parameter <= 0)
+                        parameter = 1;
+        } else {
+                parameter = 1;
+        }
+
+        terminal_emulator->cursor_row_offset += parameter;
+
+        return PLY_TERMINAL_EMULATOR_BREAK_STRING;
+}
+
+/* Control sequence 'C' for '[C' */
+ply_terminal_emulator_break_string_t
+on_control_sequence_move_cursor_right (ply_terminal_emulator_t *terminal_emulator,
+                                       const char               code,
+                                       uint32_t                 parameters[],
+                                       size_t                   number_of_parameters,
+                                       bool                     paramaters_valid)
+{
+        int parameter;
+        size_t string_length = ply_rich_text_get_length (terminal_emulator->current_line);
+
+        ply_trace ("terminal control sequence: move cursor right");
+
+        assert (code == 'C');
+
+        if (paramaters_valid != true)
+                return PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+
+        terminal_emulator->break_action = PLY_TERMINAL_EMULATOR_BREAK_STRING_ACTION_PRESERVE_CURSOR_COLUMN;
+
+        if (number_of_parameters > 0) {
+                parameter = parameters[0];
+
+                /* 0 acts as 1, and this should not be negative */
+                if (parameter <= 0)
+                        parameter = 1;
+        } else {
+                parameter = 1;
+        }
+
+        terminal_emulator->cursor_column += parameter;
+
+        if (terminal_emulator->cursor_column >= PLY_TERMINAL_LINE_MAX)
+                return PLY_TERMINAL_EMULATOR_BREAK_STRING;
+
+        fill_offsets_with_padding (terminal_emulator, string_length, terminal_emulator->cursor_column);
+
+        return PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+}
+
+/* Control sequence 'D' for '[D' */
+ply_terminal_emulator_break_string_t
+on_control_sequence_move_cursor_left (ply_terminal_emulator_t *terminal_emulator,
+                                      const char               code,
+                                      uint32_t                 parameters[],
+                                      size_t                   number_of_parameters,
+                                      bool                     paramaters_valid)
+{
+        int parameter;
+        size_t string_length = ply_rich_text_get_length (terminal_emulator->current_line);
+
+        ply_trace ("terminal control sequence: move cursor left");
+
+        assert (code == 'D');
+
+        if (paramaters_valid != true)
+                return PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+
+        terminal_emulator->break_action = PLY_TERMINAL_EMULATOR_BREAK_STRING_ACTION_PRESERVE_CURSOR_COLUMN;
+
+        if (number_of_parameters > 0) {
+                parameter = parameters[0];
+
+                /* 0 acts as 1, and this should not be negative */
+                if (parameter <= 0)
+                        parameter = 1;
+        } else {
+                parameter = 1;
+        }
+
+        if (parameter > string_length) {
+                terminal_emulator->cursor_column = 0;
+        } else {
+                terminal_emulator->cursor_column -= parameter;
+        }
+
+        fill_offsets_with_padding (terminal_emulator, string_length, terminal_emulator->cursor_column);
+
+        return PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+}
+
+/* Control sequence 'E' for '[E' */
+ply_terminal_emulator_break_string_t
+on_control_sequence_move_cursor_down_rows_to_first_column (ply_terminal_emulator_t *terminal_emulator,
+                                                           const char               code,
+                                                           uint32_t                 parameters[],
+                                                           size_t                   number_of_parameters,
+                                                           bool                     paramaters_valid)
+{
+        int parameter;
+
+        ply_trace ("terminal control sequence: move cursor down rows to first column");
+
+        assert (code == 'E');
+
+        if (paramaters_valid != true)
+                return PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+
+        terminal_emulator->break_action = PLY_TERMINAL_EMULATOR_BREAK_STRING_ACTION_RESET_CURSOR_COLUMN;
+
+        if (number_of_parameters > 0) {
+                parameter = parameters[0];
+
+                /* 0 acts as 1, and this should not be negative */
+                if (parameter <= 0)
+                        parameter = 1;
+        } else {
+                parameter = 1;
+        }
+
+        terminal_emulator->cursor_row_offset += parameter;
+
+        return PLY_TERMINAL_EMULATOR_BREAK_STRING;
+}
+
+/* Control sequence 'F' for '[F' */
+ply_terminal_emulator_break_string_t
+on_control_sequence_move_cursor_up_rows_to_first_column (ply_terminal_emulator_t *terminal_emulator,
+                                                         const char               code,
+                                                         uint32_t                 parameters[],
+                                                         size_t                   number_of_parameters,
+                                                         bool                     paramaters_valid)
+{
+        size_t parameter;
+
+        ply_trace ("terminal control sequence: move cursor up rows to column");
+
+        assert (code == 'F');
+
+        if (paramaters_valid != true)
+                return PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+
+        terminal_emulator->break_action = PLY_TERMINAL_EMULATOR_BREAK_STRING_ACTION_RESET_CURSOR_COLUMN;
+
+        if (number_of_parameters > 0) {
+                parameter = parameters[0];
+
+                /* 0 acts as 1, and this should not be negative */
+                if (parameter <= 0)
+                        parameter = 1;
+        } else {
+                parameter = 1;
+        }
+
+        terminal_emulator->cursor_row_offset -= parameter;
+        return PLY_TERMINAL_EMULATOR_BREAK_STRING;
+}
+
+/* Control sequence 'G' for '[G' */
+ply_terminal_emulator_break_string_t
+on_control_sequence_move_cursor_to_column (ply_terminal_emulator_t *terminal_emulator,
+                                           const char               code,
+                                           uint32_t                 parameters[],
+                                           size_t                   number_of_parameters,
+                                           bool                     paramaters_valid)
+{
+        int parameter;
+
+        size_t string_length = ply_rich_text_get_length (terminal_emulator->current_line);
+
+        ply_trace ("terminal control sequence: move cursor to column");
+
+        assert (code == 'G');
+
+        if (paramaters_valid != true)
+                return PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+
+        terminal_emulator->break_action = PLY_TERMINAL_EMULATOR_BREAK_STRING_ACTION_PRESERVE_CURSOR_COLUMN;
+
+        if (number_of_parameters > 0) {
+                parameter = parameters[0];
+
+                /* 0 acts as 1, and this should not be negative */
+                if (parameter <= 0)
+                        parameter = 1;
+        } else {
+                parameter = 1;
+        }
+
+        if (parameter > PLY_TERMINAL_LINE_MAX) {
+                terminal_emulator->cursor_column = 1;
+        } else {
+                /* parameter is never 0. the column '1' represents the 0 index on the string */
+                terminal_emulator->cursor_column = parameter - 1;
+        }
+
+        if (terminal_emulator->cursor_column < 0)
+                terminal_emulator->cursor_column = 0;
+
+        fill_offsets_with_padding (terminal_emulator, string_length, terminal_emulator->cursor_column);
+
+        return PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+}
+
+/* Control sequence 'K' for '[K' */
+ply_terminal_emulator_break_string_t
+on_control_sequence_erase_line (ply_terminal_emulator_t *terminal_emulator,
+                                const char               code,
+                                uint32_t                 parameters[],
+                                size_t                   number_of_parameters,
+                                bool                     paramaters_valid)
+{
+        ply_terminal_emulator_erase_line_type_t erase_line_type;
+        size_t starting_offset = terminal_emulator->cursor_column;
+        size_t string_length = ply_rich_text_get_length (terminal_emulator->current_line);
+        size_t i;
+
+        ply_trace ("terminal control sequence: erase line");
+
+        assert (code == 'K');
+
+        if (paramaters_valid != true)
+                return PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+
+
+        terminal_emulator->break_action = PLY_TERMINAL_EMULATOR_BREAK_STRING_ACTION_PRESERVE_CURSOR_COLUMN;
+
+        if (number_of_parameters > 0) {
+                erase_line_type = (ply_terminal_emulator_erase_line_type_t) parameters[0];
+
+                if (erase_line_type < PLY_TERMINAL_EMULATOR_ERASE_LINE_TYPE_CURSOR_TO_RIGHT ||
+                    erase_line_type > PLY_TERMINAL_EMULATOR_ERASE_LINE_TYPE_WHOLE_LINE)
+                        return PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+        } else {
+                erase_line_type = PLY_TERMINAL_EMULATOR_ERASE_LINE_TYPE_CURSOR_TO_RIGHT;
+        }
+
+        if (starting_offset >= PLY_TERMINAL_LINE_MAX)
+                starting_offset = PLY_TERMINAL_LINE_MAX - 1;
+
+        if (erase_line_type == PLY_TERMINAL_EMULATOR_ERASE_LINE_TYPE_CURSOR_TO_LEFT || erase_line_type == PLY_TERMINAL_EMULATOR_ERASE_LINE_TYPE_WHOLE_LINE) {
+                /* Ensure that all characters from the start of the string to the cursor are spaces */
+                for (i = starting_offset; i >= 0; i--) {
+                        /* Clear all characters at and before the current column */
+                        ply_rich_text_set_character (terminal_emulator->current_line, terminal_emulator->current_style, i, " ", 1);
+
+                        if (i <= 0)
+                                break;
+                }
+        }
+        if (erase_line_type == PLY_TERMINAL_EMULATOR_ERASE_LINE_TYPE_CURSOR_TO_RIGHT || erase_line_type == PLY_TERMINAL_EMULATOR_ERASE_LINE_TYPE_WHOLE_LINE) {
+                /* Clear all characters at and after the current column (until the end of the string */
+                for (i = starting_offset; i < string_length; i++) {
+                        ply_rich_text_remove_character (terminal_emulator->current_line, i);
+                }
+        }
+
+        return PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+}
+
+/* Control sequence 'P' for '[P' */
+ply_terminal_emulator_break_string_t
+on_control_sequence_delete_characters (ply_terminal_emulator_t *terminal_emulator,
+                                       const char               code,
+                                       uint32_t                 parameters[],
+                                       size_t                   number_of_parameters,
+                                       bool                     paramaters_valid)
+{
+        int parameter;
+        size_t i;
+        size_t string_length = ply_rich_text_get_length (terminal_emulator->current_line);
+
+        ply_trace ("terminal control sequence: delete characters");
+
+        assert (code == 'P');
+
+        if (paramaters_valid != true)
+                return PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+
+        terminal_emulator->break_action = PLY_TERMINAL_EMULATOR_BREAK_STRING_ACTION_PRESERVE_CURSOR_COLUMN;
+
+        if (number_of_parameters > 0) {
+                parameter = parameters[0];
+
+                /* 0 acts as 1, and this should not be negative */
+                if (parameter <= 0)
+                        parameter = 1;
+        } else {
+                parameter = 1;
+        }
+
+        if (terminal_emulator->cursor_column + parameter >= string_length)
+                parameter = string_length - 1;
+
+        for (i = terminal_emulator->cursor_column; i < string_length; i++) {
+                ply_rich_text_move_character (terminal_emulator->current_line,
+                                              i + parameter,
+                                              i);
+        }
+
+        return PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+}
+
+/* Control sequence 'X' for '[X' */
+ply_terminal_emulator_break_string_t
+on_control_sequence_erase_characters (ply_terminal_emulator_t *terminal_emulator,
+                                      const char               code,
+                                      uint32_t                 parameters[],
+                                      size_t                   number_of_parameters,
+                                      bool                     paramaters_valid)
+{
+        int parameter;
+        size_t i;
+        size_t string_length = ply_rich_text_get_length (terminal_emulator->current_line);
+        size_t delete_offset;
+
+        ply_trace ("terminal control sequence: erase characters");
+
+        assert (code == 'X');
+
+        if (paramaters_valid != true)
+                return PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+
+        terminal_emulator->break_action = PLY_TERMINAL_EMULATOR_BREAK_STRING_ACTION_PRESERVE_CURSOR_COLUMN;
+
+        if (number_of_parameters > 0) {
+                parameter = parameters[0];
+
+                /* 0 acts as 1, and this should not be negative */
+                if (parameter <= 0)
+                        parameter = 1;
+        } else {
+                parameter = 1;
+        }
+        for (i = 0; i < parameter; i++) {
+                delete_offset = terminal_emulator->cursor_column + i;
+
+                if (delete_offset >= string_length)
+                        break;
+
+                ply_rich_text_set_character (terminal_emulator->current_line, terminal_emulator->current_style, delete_offset, " ", 1);
+        }
+
+        return PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+}
+
+/* Control sequence 'm' for '[m' */
+ply_terminal_emulator_break_string_t
+on_control_sequence_set_attributes (ply_terminal_emulator_t *terminal_emulator,
+                                    const char               code,
+                                    uint32_t                 parameters[],
+                                    size_t                   number_of_parameters,
+                                    bool                     paramaters_valid)
+{
+        bool ignore_value = false;
+        ply_terminal_color_t default_foreground_color = PLY_TERMINAL_COLOR_DEFAULT;
+        ply_terminal_color_t default_background_color = PLY_TERMINAL_COLOR_DEFAULT;
+
+        ply_trace ("terminal control sequence: set attributes");
+
+        assert (code == 'm');
+
+        if (paramaters_valid != true)
+                return PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+
+        terminal_emulator->break_action = PLY_TERMINAL_EMULATOR_BREAK_STRING_ACTION_PRESERVE_CURSOR_COLUMN;
+
+        for (int i = 0; i < number_of_parameters; i++) {
+                int parameter = parameters[i];
+
+                if (ignore_value == true) {
+                        ignore_value = false;
+                        continue;
+                }
+
+                /* Paramaters cannot be negative. When no paramter is specified it acts as 0 */
+                if (parameter < 0)
+                        parameter = 0;
+
+                switch (parameter) {
+                case PLY_TERMINAL_ATTRIBUTE_RESET:
+                        terminal_emulator->current_style.foreground_color = default_foreground_color;
+                        terminal_emulator->current_style.background_color = default_background_color;
+                        terminal_emulator->current_style.bold_enabled = false;
+                        terminal_emulator->current_style.dim_enabled = false;
+                        terminal_emulator->current_style.italic_enabled = false;
+                        terminal_emulator->current_style.underline_enabled = false;
+                        terminal_emulator->current_style.reverse_enabled = false;
+                        break;
+                case PLY_TERMINAL_ATTRIBUTE_BOLD:
+                        terminal_emulator->current_style.bold_enabled = true;
+                        break;
+                case PLY_TERMINAL_ATTRIBUTE_NO_BOLD:
+                        terminal_emulator->current_style.bold_enabled = false;
+                        break;
+                case PLY_TERMINAL_ATTRIBUTE_DIM:
+                        terminal_emulator->current_style.dim_enabled = true;
+                        break;
+                case PLY_TERMINAL_ATTRIBUTE_NO_DIM:
+                        terminal_emulator->current_style.dim_enabled = false;
+                        break;
+                case PLY_TERMINAL_ATTRIBUTE_ITALIC:
+                        terminal_emulator->current_style.italic_enabled = true;
+                        break;
+                case PLY_TERMINAL_ATTRIBUTE_NO_ITALIC:
+                        terminal_emulator->current_style.italic_enabled = false;
+                        break;
+                case PLY_TERMINAL_ATTRIBUTE_UNDERLINE:
+                        terminal_emulator->current_style.underline_enabled = true;
+                        break;
+                case PLY_TERMINAL_ATTRIBUTE_NO_UNDERLINE:
+                        terminal_emulator->current_style.underline_enabled = false;
+                        break;
+                case PLY_TERMINAL_ATTRIBUTE_REVERSE:
+                        terminal_emulator->current_style.reverse_enabled = true;
+                        break;
+                case PLY_TERMINAL_ATTRIBUTE_NO_REVERSE:
+                        terminal_emulator->current_style.reverse_enabled = false;
+                        break;
+
+                /* foreground color handling */
+                case PLY_TERMINAL_ATTRIBUTE_FOREGROUND_COLOR_OFFSET + PLY_TERMINAL_COLOR_BLACK
+                        ...
+                        PLY_TERMINAL_ATTRIBUTE_FOREGROUND_COLOR_OFFSET + PLY_TERMINAL_COLOR_WHITE:
+                        terminal_emulator->current_style.foreground_color = (ply_terminal_color_t) (parameters[i] - PLY_TERMINAL_ATTRIBUTE_FOREGROUND_COLOR_OFFSET);
+                        break;
+                case PLY_TERMINAL_ATTRIBUTE_FOREGROUND_COLOR_OFFSET + PLY_TERMINAL_COLOR_DEFAULT:
+                        terminal_emulator->current_style.foreground_color = default_foreground_color;
+                        break;
+
+                /* background color handling */
+                case PLY_TERMINAL_ATTRIBUTE_BACKGROUND_COLOR_OFFSET + PLY_TERMINAL_COLOR_BLACK
+                        ...
+                        PLY_TERMINAL_ATTRIBUTE_BACKGROUND_COLOR_OFFSET + PLY_TERMINAL_COLOR_WHITE:
+                        terminal_emulator->current_style.background_color = (ply_terminal_color_t) (parameters[i] - PLY_TERMINAL_ATTRIBUTE_BACKGROUND_COLOR_OFFSET);
+                        break;
+                case PLY_TERMINAL_ATTRIBUTE_BACKGROUND_COLOR_OFFSET + PLY_TERMINAL_COLOR_DEFAULT:
+                        terminal_emulator->current_style.background_color = default_background_color;
+                        break;
+
+                /* bright color handling, fallback to standard colors */
+                case PLY_TERMINAL_ATTRIBUTE_FOREGROUND_BRIGHT_OFFSET + PLY_TERMINAL_COLOR_BLACK
+                        ...
+                        PLY_TERMINAL_ATTRIBUTE_FOREGROUND_BRIGHT_OFFSET + PLY_TERMINAL_COLOR_WHITE:
+                        terminal_emulator->current_style.foreground_color = (ply_terminal_color_t) (parameters[i] - PLY_TERMINAL_ATTRIBUTE_FOREGROUND_BRIGHT_OFFSET);
+                        terminal_emulator->current_style.dim_enabled = false;
+                        break;
+                case PLY_TERMINAL_ATTRIBUTE_BACKGROUND_BRIGHT_OFFSET + PLY_TERMINAL_COLOR_BLACK
+                        ...
+                        PLY_TERMINAL_ATTRIBUTE_BACKGROUND_BRIGHT_OFFSET + PLY_TERMINAL_COLOR_WHITE:
+                        terminal_emulator->current_style.background_color = (ply_terminal_color_t) (parameters[i] - PLY_TERMINAL_ATTRIBUTE_BACKGROUND_BRIGHT_OFFSET);
+                        break;
+
+                /* If this is 38 or 48, it means that the next parameter is the attribute for 256 color. Skip it */
+                case PLY_TERMINAL_ATTRIBUTE_FOREGROUND_COLOR_OFFSET + PLY_TERMINAL_COLOR_DEFAULT - 1:
+                case PLY_TERMINAL_ATTRIBUTE_BACKGROUND_COLOR_OFFSET + PLY_TERMINAL_COLOR_DEFAULT - 1:
+                        ignore_value = true;
+                        break;
+                }
+        }
+
+        return PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+}
+
+/* backspace character ('\b') */
+ply_terminal_emulator_break_string_t
+on_escape_character_backspace (ply_terminal_emulator_t *terminal_emulator,
+                               const char               code)
+{
+        ply_trace ("terminal escape character: backspace");
+
+        assert (code == '\b');
+
+        terminal_emulator->break_action = PLY_TERMINAL_EMULATOR_BREAK_STRING_ACTION_PRESERVE_CURSOR_COLUMN;
+
+        if (terminal_emulator->cursor_column != 0)
+                terminal_emulator->cursor_column--;
+
+        return PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+}
+
+/* tab character ('\t') */
+ply_terminal_emulator_break_string_t
+on_escape_character_tab (ply_terminal_emulator_t *terminal_emulator,
+                         const char               code)
+{
+        int pad_character_count = 0;
+        size_t string_length = ply_rich_text_get_length (terminal_emulator->current_line);
+        size_t new_cursor_position;
+        size_t new_string_length;
+
+        ply_trace ("terminal escape character: tab");
+
+        assert (code == '\t');
+
+        terminal_emulator->break_action = PLY_TERMINAL_EMULATOR_BREAK_STRING_ACTION_PRESERVE_CURSOR_COLUMN;
+
+        if (terminal_emulator->cursor_column <= 0) {
+                pad_character_count = PLY_TERMINAL_SPACES_PER_TAB;
+        } else {
+                pad_character_count = PLY_TERMINAL_SPACES_PER_TAB - (terminal_emulator->cursor_column % PLY_TERMINAL_SPACES_PER_TAB);
+        }
+
+        new_cursor_position = terminal_emulator->cursor_column + pad_character_count;
+        if (new_cursor_position >= PLY_TERMINAL_LINE_MAX - 1)
+                new_cursor_position = PLY_TERMINAL_LINE_MAX - 1;
+
+        terminal_emulator->cursor_column = new_cursor_position;
+
+        /* If the cursor row offset is not on the same line, don't pad the string
+         * This is for when a tab character is inside an escape code, after a new line
+         */
+        if (terminal_emulator->cursor_row_offset != 0)
+                return PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+
+        if (new_cursor_position < string_length)
+                return PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+
+
+        new_string_length = string_length + pad_character_count;
+        if (new_string_length >= PLY_TERMINAL_LINE_MAX - 1)
+                new_string_length = PLY_TERMINAL_LINE_MAX - 1;
+
+        for (size_t i = string_length; i < new_string_length; i++) {
+                ply_rich_text_set_character (terminal_emulator->current_line, terminal_emulator->current_style, i, " ", 1);
+        }
+
+        return PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+}
+
+/* linefeed characters ('\n', '\v', '\f') */
+ply_terminal_emulator_break_string_t
+on_escape_character_linefeed (ply_terminal_emulator_t *terminal_emulator,
+                              const char               code)
+{
+        ply_trace ("terminal escape character: line feed");
+
+        assert (code == '\n' || code == '\v' || code == '\f');
+
+        terminal_emulator->cursor_row_offset++;
+        terminal_emulator->break_action = PLY_TERMINAL_EMULATOR_BREAK_STRING_ACTION_RESET_CURSOR_COLUMN;
+
+        return PLY_TERMINAL_EMULATOR_BREAK_STRING;
+}
+
+/* carriage return ('\r') */
+ply_terminal_emulator_break_string_t
+on_escape_character_carriage_return (ply_terminal_emulator_t *terminal_emulator,
+                                     const char               code)
+{
+        ply_trace ("terminal escape character: carriage return");
+
+        assert (code == '\r');
+
+        terminal_emulator->cursor_column = 0;
+        terminal_emulator->break_action = PLY_TERMINAL_EMULATOR_BREAK_STRING_ACTION_RESET_CURSOR_COLUMN;
+
+        return PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+}
+
+struct
+{
+        union
+        {
+                void                                             *handler;
+                ply_terminal_emulator_control_character_handler_t control_character_handler;
+                ply_terminal_emulator_escape_sequence_handler_t   escape_sequence_handler;
+                ply_terminal_emulator_control_sequence_handler_t  control_sequence_handler;
+        };
+        const char                           code;
+        ply_terminal_emulator_command_type_t type;
+}  control_code_dispatch_table[] = {
+        { { on_escape_sequence_linefeed                               }, 'D',  PLY_TERMINAL_EMULATOR_COMMAND_TYPE_ESCAPE            },
+        { { on_escape_sequence_newline                                }, 'E',  PLY_TERMINAL_EMULATOR_COMMAND_TYPE_ESCAPE            },
+        { { on_escape_sequence_reverse_linefeed                       }, 'M',  PLY_TERMINAL_EMULATOR_COMMAND_TYPE_ESCAPE            },
+        { { on_control_sequence_insert_blank_characters               }, '@',  PLY_TERMINAL_EMULATOR_COMMAND_TYPE_CONTROL_SEQUENCE  },
+        { { on_control_sequence_move_cursor_up_rows                   }, 'A',  PLY_TERMINAL_EMULATOR_COMMAND_TYPE_CONTROL_SEQUENCE  },
+        { { on_control_sequence_move_cursor_down_rows                 }, 'B',  PLY_TERMINAL_EMULATOR_COMMAND_TYPE_CONTROL_SEQUENCE  },
+        { { on_control_sequence_move_cursor_right                     }, 'C',  PLY_TERMINAL_EMULATOR_COMMAND_TYPE_CONTROL_SEQUENCE  },
+        { { on_control_sequence_move_cursor_left                      }, 'D',  PLY_TERMINAL_EMULATOR_COMMAND_TYPE_CONTROL_SEQUENCE  },
+        { { on_control_sequence_move_cursor_down_rows_to_first_column }, 'E',  PLY_TERMINAL_EMULATOR_COMMAND_TYPE_CONTROL_SEQUENCE  },
+        { { on_control_sequence_move_cursor_up_rows_to_first_column   }, 'F',  PLY_TERMINAL_EMULATOR_COMMAND_TYPE_CONTROL_SEQUENCE  },
+        { { on_control_sequence_move_cursor_to_column                 }, 'G',  PLY_TERMINAL_EMULATOR_COMMAND_TYPE_CONTROL_SEQUENCE  },
+        { { on_control_sequence_erase_line                            }, 'K',  PLY_TERMINAL_EMULATOR_COMMAND_TYPE_CONTROL_SEQUENCE  },
+        { { on_control_sequence_delete_characters                     }, 'P',  PLY_TERMINAL_EMULATOR_COMMAND_TYPE_CONTROL_SEQUENCE  },
+        { { on_control_sequence_erase_characters                      }, 'X',  PLY_TERMINAL_EMULATOR_COMMAND_TYPE_CONTROL_SEQUENCE  },
+        { { on_control_sequence_set_attributes                        }, 'm',  PLY_TERMINAL_EMULATOR_COMMAND_TYPE_CONTROL_SEQUENCE  },
+        { { on_escape_character_tab                                   }, '\t', PLY_TERMINAL_EMULATOR_COMMAND_TYPE_CONTROL_CHARACTER },
+        { { on_escape_character_backspace                             }, '\b', PLY_TERMINAL_EMULATOR_COMMAND_TYPE_CONTROL_CHARACTER },
+        { { on_escape_character_linefeed                              }, '\n', PLY_TERMINAL_EMULATOR_COMMAND_TYPE_CONTROL_CHARACTER },
+        { { on_escape_character_linefeed                              }, '\v', PLY_TERMINAL_EMULATOR_COMMAND_TYPE_CONTROL_CHARACTER },
+        { { on_escape_character_linefeed                              }, '\f', PLY_TERMINAL_EMULATOR_COMMAND_TYPE_CONTROL_CHARACTER },
+        { { on_escape_character_carriage_return                       }, '\r', PLY_TERMINAL_EMULATOR_COMMAND_TYPE_CONTROL_CHARACTER },
+        { { NULL                                                      } }
+};
+
+bool
+ply_terminal_emulator_dispatch_control_sequence_command (ply_terminal_emulator_t         *terminal_emulator,
+                                                         ply_terminal_emulator_command_t *command)
+{
+        bool break_string = false;
+
+        for (int i = 0; control_code_dispatch_table[i].handler != NULL; i++) {
+                if (control_code_dispatch_table[i].code == command->code && control_code_dispatch_table[i].type == command->type) {
+                        switch (command->type) {
+                        case PLY_TERMINAL_EMULATOR_COMMAND_TYPE_CONTROL_SEQUENCE:
+                                break_string = control_code_dispatch_table[i].control_sequence_handler (terminal_emulator, command->code,
+                                                                                                        (uint32_t *) ply_array_get_uint32_elements (command->parameters),
+                                                                                                        ply_array_get_size (command->parameters),
+                                                                                                        command->parameters_valid);
+                                ply_array_free (command->parameters);
+                                break;
+                        case PLY_TERMINAL_EMULATOR_COMMAND_TYPE_CONTROL_CHARACTER:
+                                break_string = control_code_dispatch_table[i].control_character_handler (terminal_emulator, command->code);
+                                break;
+                        case PLY_TERMINAL_EMULATOR_COMMAND_TYPE_ESCAPE:
+                                break_string = control_code_dispatch_table[i].escape_sequence_handler (terminal_emulator, command->code);
+                                break;
+                        }
+                        break;
+                }
+        }
+
+        return break_string;
+}
+
+ply_rich_text_t *
+ply_terminal_emulator_get_nth_line (ply_terminal_emulator_t *terminal_emulator,
+                                    int                      line_number)
+{
+        ply_rich_text_t *const *console_lines = (ply_rich_text_t *const *) ply_array_get_pointer_elements (terminal_emulator->lines);
+        return console_lines[line_number % terminal_emulator->maximum_line_count];
+}
+
+int
+ply_terminal_emulator_get_line_count (ply_terminal_emulator_t *terminal_emulator)
+{
+        return terminal_emulator->line_count;
+}
+
+void
+ply_terminal_emulator_parse_substring (ply_terminal_emulator_t *terminal_emulator,
+                                       ply_rich_text_t         *terminal_emulator_line,
+                                       const char              *input,
+                                       size_t                   number_of_bytes_to_parse,
+                                       const char             **unparsed_input,
+                                       size_t                  *number_of_unparsed_bytes)
+{
+        char character_string[PLY_UTF8_CHARACTER_MAX_SIZE];
+        size_t input_length = number_of_bytes_to_parse;
+        size_t new_length;
+        size_t i = 0;
+        ply_terminal_emulator_break_string_t break_string = PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+        int parameter_value;
+        ply_terminal_emulator_command_t *command;
+
+        int character_length;
+        ply_list_node_t *node;
+
+        terminal_emulator->current_line = terminal_emulator_line;
+
+        /* avoid duplicate empty lines, the end of the line implies a newline */
+        if (input_length == 1 && input[0] == '\n') {
+                *unparsed_input = &input[1];
+                *number_of_unparsed_bytes = number_of_bytes_to_parse - 1;
+                return;
+        }
+
+        if (terminal_emulator->cursor_column >= PLY_TERMINAL_LINE_MAX)
+                terminal_emulator->cursor_column = 0;
+
+        new_length = ply_rich_text_get_length (terminal_emulator->current_line);
+
+        if (terminal_emulator->cursor_column >= new_length)
+                fill_offsets_with_padding (terminal_emulator, new_length, terminal_emulator->cursor_column);
+
+        while (i < input_length) {
+                if (break_string == PLY_TERMINAL_EMULATOR_BREAK_STRING && terminal_emulator->state == PLY_TERMINAL_EMULATOR_PARSE_STATE_UNESCAPED) {
+                        break_string = PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+                        break;
+                }
+
+                parameter_value = 0;
+
+                terminal_emulator->break_action = PLY_TERMINAL_EMULATOR_BREAK_STRING_ACTION_PRESERVE_CURSOR_COLUMN;
+
+                /* Non-ASCII Unicode characters have no impact on escape code handling */
+                character_length = ply_utf8_character_get_size (&input[i], 4);
+
+                /* skip, if the character_length is -2, it's a auxiliary unicode byte */
+                if (character_length < 0) {
+                        i++;
+                        continue;
+                } else if (character_length > 1) {
+                        /* Last element is a nullchar */
+                        character_string[character_length] = '\0';
+
+                        for (int j = 0; j < character_length; j++) {
+                                character_string[j] = input[i];
+
+                                i++;
+
+                                if (i >= PLY_TERMINAL_LINE_MAX)
+                                        break;
+                        }
+                        ply_rich_text_set_character (terminal_emulator->current_line, terminal_emulator->current_style, terminal_emulator->cursor_column, character_string, character_length);
+                        terminal_emulator->cursor_column++;
+                        continue;
+                }
+
+                switch (terminal_emulator->state) {
+                case PLY_TERMINAL_EMULATOR_PARSE_STATE_UNESCAPED:
+                        if (input[i] == '\e') {
+                                terminal_emulator->staged_command = ply_terminal_emulator_command_new ();
+
+                                terminal_emulator->state = PLY_TERMINAL_EMULATOR_PARSE_STATE_ESCAPED;
+                        } else if (iscntrl (input[i]) && input[i] != '\e') {
+                                terminal_emulator->staged_command = ply_terminal_emulator_command_new ();
+                                terminal_emulator->staged_command->code = input[i];
+                                terminal_emulator->staged_command->type = PLY_TERMINAL_EMULATOR_COMMAND_TYPE_CONTROL_CHARACTER;
+                                ply_list_append_data (terminal_emulator->pending_commands, terminal_emulator->staged_command);
+                        } else {
+                                character_string[0] = input[i];
+                                character_string[1] = '\0';
+                                ply_rich_text_set_character (terminal_emulator->current_line, terminal_emulator->current_style, terminal_emulator->cursor_column, character_string, 1);
+                                terminal_emulator->cursor_column++;
+
+                                if (terminal_emulator->cursor_column >= PLY_TERMINAL_LINE_MAX) {
+                                        terminal_emulator->cursor_column = 0;
+                                        break_string = PLY_TERMINAL_EMULATOR_BREAK_STRING;
+                                }
+                        }
+                        break;
+                case PLY_TERMINAL_EMULATOR_PARSE_STATE_ESCAPED:
+                        if (input[i] == '[') {
+                                terminal_emulator->staged_command->parameters = ply_array_new (PLY_ARRAY_ELEMENT_TYPE_UINT32);
+                                terminal_emulator->staged_command->type = PLY_TERMINAL_EMULATOR_COMMAND_TYPE_CONTROL_SEQUENCE;
+                                terminal_emulator->staged_command->parameters_valid = true;
+
+                                terminal_emulator->last_parameter_was_integer = false;
+
+                                terminal_emulator->state = PLY_TERMINAL_EMULATOR_PARSE_STATE_CONTROL_SEQUENCE_PARAMETER;
+                        } else {
+                                terminal_emulator->staged_command->code = input[i];
+                                terminal_emulator->staged_command->type = PLY_TERMINAL_EMULATOR_COMMAND_TYPE_ESCAPE;
+                                ply_list_append_data (terminal_emulator->pending_commands, terminal_emulator->staged_command);
+                                terminal_emulator->state = PLY_TERMINAL_EMULATOR_PARSE_STATE_UNESCAPED;
+                        }
+                        break;
+                case PLY_TERMINAL_EMULATOR_PARSE_STATE_CONTROL_SEQUENCE_PARAMETER:
+                        /* Characters that end the control sequence, and define the command */
+                        if ((unsigned char) input[i] >= PLY_TERMINAL_ESCAPE_CODE_COMMAND_MINIMUM &&
+                            (unsigned char) input[i] <= PLY_TERMINAL_ESCAPE_CODE_COMMAND_MAXIMUM) {
+                                terminal_emulator->state = PLY_TERMINAL_EMULATOR_PARSE_STATE_UNESCAPED;
+                                terminal_emulator->staged_command->code = input[i];
+
+                                if (terminal_emulator->last_parameter_was_integer == false)
+                                        ply_array_add_uint32_element (terminal_emulator->staged_command->parameters, parameter_value);
+
+                                ply_list_append_data (terminal_emulator->pending_commands, terminal_emulator->staged_command);
+
+                                break;
+                        } else if (iscntrl (input[i]) && input[i] != '\e') {
+                                ply_terminal_emulator_command_t *nested_command = ply_terminal_emulator_command_new ();
+                                nested_command->code = input[i];
+                                nested_command->type = PLY_TERMINAL_EMULATOR_COMMAND_TYPE_CONTROL_CHARACTER;
+                                ply_list_append_data (terminal_emulator->pending_commands, nested_command);
+                        } else if (input[i] == ';' || (isdigit (input[i]))) {
+                                if (isdigit (input[i])) {
+                                        /* If the previous character was an integer, and this one is an integer, it is probably the next digit*/
+                                        if (terminal_emulator->last_parameter_was_integer == true) {
+                                                parameter_value = -1;
+                                        } else {
+                                                parameter_value = atoi (&input[i]);
+                                        }
+
+                                        terminal_emulator->last_parameter_was_integer = true;
+                                } else if (input[i] == ';') {
+                                        /* Skip, and do not add the default value of 0 if the last character encountered was a valid parameter
+                                         * Double ;;'s imply a 0
+                                         */
+                                        if (terminal_emulator->last_parameter_was_integer == true)
+                                                parameter_value = -1;
+
+                                        terminal_emulator->last_parameter_was_integer = false;
+                                }
+
+                                /* Skip parameter if less than 0 */
+                                if (parameter_value >= 0)
+                                        ply_array_add_uint32_element (terminal_emulator->staged_command->parameters, parameter_value);
+
+                                break;
+                        } else {
+                                /* invalid characters in the middle of the escape sequence invalidate it */
+                                terminal_emulator->staged_command->parameters_valid = false;
+                        }
+                        break;
+                }
+
+                if (terminal_emulator->state == PLY_TERMINAL_EMULATOR_PARSE_STATE_UNESCAPED) {
+                        ply_list_foreach (terminal_emulator->pending_commands, node) {
+                                ply_terminal_emulator_break_string_t break_string_value = PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE;
+
+                                command = ply_list_node_get_data (node);
+
+                                break_string_value = ply_terminal_emulator_dispatch_control_sequence_command (terminal_emulator, command);
+                                if (break_string_value != PLY_TERMINAL_EMULATOR_BREAK_STRING_NONE)
+                                        break_string = break_string_value;
+
+                                free (command);
+                        }
+                        ply_list_remove_all_nodes (terminal_emulator->pending_commands);
+                }
+
+                i++;
+        }
+
+        *unparsed_input = &input[i];
+        *number_of_unparsed_bytes = number_of_bytes_to_parse - i;
+
+        /* Moving down, so create new lines */
+        while (terminal_emulator->cursor_row_offset > 0) {
+                ply_rich_text_t *terminal_emulator_line;
+
+                terminal_emulator->cursor_row_offset--;
+                terminal_emulator_line = ply_terminal_emulator_get_nth_line (terminal_emulator, terminal_emulator->line_count);
+
+                if (terminal_emulator) {
+                        ply_rich_text_remove_characters (terminal_emulator_line);
+                }
+
+                terminal_emulator->line_count++;
+        }
+
+        if (terminal_emulator->break_action == PLY_TERMINAL_EMULATOR_BREAK_STRING_ACTION_RESET_CURSOR_COLUMN)
+                terminal_emulator->cursor_column = 0;
+
+        if (terminal_emulator->default_colors_forced == true) {
+                terminal_emulator->default_colors_forced = false;
+                terminal_emulator->current_style.foreground_color = PLY_TERMINAL_COLOR_DEFAULT;
+                terminal_emulator->current_style.background_color = PLY_TERMINAL_COLOR_DEFAULT;
+        }
+
+        terminal_emulator->current_line = NULL;
+}
+
+void
+ply_terminal_emulator_parse_lines (ply_terminal_emulator_t *terminal_emulator,
+                                   const char              *text,
+                                   size_t                   size)
+{
+        ply_rich_text_t *terminal_emulator_line = NULL;
+        size_t cursor_row;
+        size_t first_row;
+        size_t last_row;
+        size_t unparsed_text_length;
+        const char *unparsed_text;
+
+        unparsed_text = text;
+        unparsed_text_length = size;
+        while (unparsed_text_length > 0) {
+
+                assert (terminal_emulator->line_count != 0);
+
+                first_row = 0;
+                last_row = terminal_emulator->line_count - 1;
+
+                /* Moving up, make sure to stop it at the top */
+                if (terminal_emulator->cursor_row_offset < 0) {
+                        size_t lines_to_move_up = -1 * terminal_emulator->cursor_row_offset;
+
+                        if (lines_to_move_up > terminal_emulator->line_count)
+                                terminal_emulator->cursor_row_offset = first_row;
+                }
+
+                cursor_row = last_row + terminal_emulator->cursor_row_offset;
+
+                terminal_emulator_line = ply_terminal_emulator_get_nth_line (terminal_emulator, cursor_row);
+                ply_terminal_emulator_parse_substring (terminal_emulator, terminal_emulator_line, unparsed_text, unparsed_text_length, &unparsed_text, &unparsed_text_length);
+        }
+
+        if (unparsed_text != text)
+                ply_trigger_pull (terminal_emulator->output_trigger, text);
+}
+
+void
+ply_terminal_emulator_convert_boot_buffer (ply_terminal_emulator_t *terminal_emulator,
+                                           ply_buffer_t            *boot_buffer)
+{
+        ply_terminal_emulator_parse_lines (terminal_emulator, ply_buffer_get_bytes (boot_buffer), ply_buffer_get_size (boot_buffer));
+}
+
+void
+ply_terminal_emulator_watch_for_output (ply_terminal_emulator_t               *terminal_emulator,
+                                        ply_terminal_emulator_output_handler_t handler,
+                                        void                                  *user_data)
+{
+        ply_trigger_add_handler (terminal_emulator->output_trigger,
+                                 (ply_trigger_handler_t)
+                                 handler,
+                                 user_data);
+}
diff --git a/src/libply-splash-core/ply-terminal-emulator.h b/src/libply-splash-core/ply-terminal-emulator.h
new file mode 100644 (file)
index 0000000..4fc9361
--- /dev/null
@@ -0,0 +1,70 @@
+/* ply-terminal-emulator.h - Minimal Terminal Emulator
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+#ifndef PLY_TERMINAL_EMULATOR_H
+#define PLY_TERMINAL_EMULATOR_H
+
+#include "ply-boot-splash.h"
+#include "ply-rich-text.h"
+#include <sys/syslog.h>
+
+/* Terminal attribute values are determined from the "ECMA-48 Select Graphic Rendition" section from
+ * https://man7.org/linux/man-pages/man4/console_codes.4.html
+ */
+#define PLY_TERMINAL_ATTRIBUTE_FOREGROUND_COLOR_OFFSET 30
+#define PLY_TERMINAL_ATTRIBUTE_BACKGROUND_COLOR_OFFSET 40
+#define PLY_TERMINAL_ATTRIBUTE_FOREGROUND_BRIGHT_OFFSET 90
+#define PLY_TERMINAL_ATTRIBUTE_BACKGROUND_BRIGHT_OFFSET 100
+
+typedef struct _ply_terminal_emulator ply_terminal_emulator_t;
+
+typedef enum
+{
+        PLY_TERMINAL_ATTRIBUTE_RESET,
+        PLY_TERMINAL_ATTRIBUTE_BOLD,
+        PLY_TERMINAL_ATTRIBUTE_DIM,
+        PLY_TERMINAL_ATTRIBUTE_ITALIC,
+        PLY_TERMINAL_ATTRIBUTE_UNDERLINE,
+        PLY_TERMINAL_ATTRIBUTE_REVERSE    = 7,
+        PLY_TERMINAL_ATTRIBUTE_NO_BOLD    = 21,
+        PLY_TERMINAL_ATTRIBUTE_NO_DIM,
+        PLY_TERMINAL_ATTRIBUTE_NO_ITALIC,
+        PLY_TERMINAL_ATTRIBUTE_NO_UNDERLINE,
+        PLY_TERMINAL_ATTRIBUTE_NO_REVERSE = 27
+} ply_terminal_style_attributes_t;
+
+typedef void (*ply_terminal_emulator_output_handler_t) (void       *user_data,
+                                                        const char *output);
+
+#ifndef PLY_HIDE_FUNCTION_DECLARATIONS
+ply_terminal_emulator_t *ply_terminal_emulator_new (size_t maximum_line_count);
+void ply_terminal_emulator_free (ply_terminal_emulator_t *terminal_emulator);
+void ply_terminal_emulator_parse_lines (ply_terminal_emulator_t *terminal_emulator,
+                                        const char              *text,
+                                        size_t                   size);
+ply_rich_text_t *ply_terminal_emulator_get_nth_line (ply_terminal_emulator_t *terminal_emulator,
+                                                     int                      line_number);
+int ply_terminal_emulator_get_line_count (ply_terminal_emulator_t *terminal_emulator);
+void ply_terminal_emulator_convert_boot_buffer (ply_terminal_emulator_t *terminal_emulator,
+                                                ply_buffer_t            *boot_buffer);
+void ply_terminal_emulator_watch_for_output (ply_terminal_emulator_t               *terminal_emulator,
+                                             ply_terminal_emulator_output_handler_t handler,
+                                             void                                  *user_data);
+
+#endif //PLY_HIDE_FUNCTION_DECLARATIONS
+#endif //PLY_TERMINAL_EMULATOR_H