From f4834a4b0de1d52aa233bff60536a3884c6bc93a Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Thu, 30 Nov 2023 14:03:18 -0500 Subject: [PATCH 01/22] check-format: Trim weird line that's getting added to output --- scripts/check-format.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check-format.sh b/scripts/check-format.sh index aa04e2e3..8f280dda 100755 --- a/scripts/check-format.sh +++ b/scripts/check-format.sh @@ -29,7 +29,7 @@ find -name '*.[ch]' -exec git diff -- {} \; >> before interdiff -B --no-revert-omitted before after > diff -if [ -n "$(cat diff)" ]; then +if [ -n "$(cat diff | grep -vE '^only in patch[12]:')" ]; then echo "Uncrustify found style abnormalities" 2>&1 cat diff exit 1 From 69e941e2d0054788b5099a2e9b4c58ba677fdaeb Mon Sep 17 00:00:00 2001 From: n3rdopolis Date: Fri, 9 Dec 2022 13:07:36 -0500 Subject: [PATCH 02/22] populate-initrd: Ensure a monospace font is in the initrd, along with fonts defined in the theme. --- scripts/plymouth-populate-initrd.in | 39 ++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/scripts/plymouth-populate-initrd.in b/scripts/plymouth-populate-initrd.in index 6ee6b353..1c29e4c7 100755 --- a/scripts/plymouth-populate-initrd.in +++ b/scripts/plymouth-populate-initrd.in @@ -545,6 +545,35 @@ fi PLYMOUTH_MODULE_NAME=$(grep "ModuleName *= *" ${PLYMOUTH_SYSROOT}${PLYMOUTH_THEME_DIR}/${PLYMOUTH_THEME_NAME}.plymouth | sed 's/ModuleName *= *//') PLYMOUTH_IMAGE_DIR=$(grep "ImageDir *= *" ${PLYMOUTH_SYSROOT}${PLYMOUTH_THEME_DIR}/${PLYMOUTH_THEME_NAME}.plymouth | sed 's/ImageDir *= *//') + +PLYMOUTH_FONT_PATH="" +PLYMOUTH_FONT=$(grep "\bFont *= *" ${PLYMOUTH_SYSROOT}${PLYMOUTH_THEME_DIR}/${PLYMOUTH_THEME_NAME}.plymouth | sed 's/Font *= *//' | head -1) +if [ ! -z "$PLYMOUTH_FONT" ]; then + PLYMOUTH_FONT_PATH=$(fc-match -f %{file} "$PLYMOUTH_FONT") + if [ ! -z "$PLYMOUTH_FONT_PATH" ]; then + inst "$PLYMOUTH_FONT_PATH" $INITRDDIR + fi +fi + +PLYMOUTH_TITLE_FONT_PATH="" +PLYMOUTH_TITLE_FONT=$(grep "\bTitleFont *= *" ${PLYMOUTH_SYSROOT}${PLYMOUTH_THEME_DIR}/${PLYMOUTH_THEME_NAME}.plymouth | sed 's/TitleFont *= *//' | head -1) +if [ ! -z "$PLYMOUTH_TITLE_FONT" ]; then + PLYMOUTH_TITLE_FONT_PATH=$(fc-match -f %{file} "$PLYMOUTH_TITLE_FONT") + if [ ! -z "$PLYMOUTH_TITLE_FONT_PATH" ]; then + inst "$PLYMOUTH_TITLE_FONT_PATH" $INITRDDIR + fi +fi + +PLYMOUTH_MONOSPACE_FONT_PATH="" +PLYMOUTH_MONOSPACE_FONT=$(grep "\bMonospaceFont *= *" ${PLYMOUTH_SYSROOT}${PLYMOUTH_THEME_DIR}/${PLYMOUTH_THEME_NAME}.plymouth | sed 's/MonospaceFont *= *//' | head -1) +if [ ! -z "$PLYMOUTH_MONOSPACE_FONT" ]; then + PLYMOUTH_MONOSPACE_FONT_PATH=$(fc-match -f %{file} "$PLYMOUTH_MONOSPACE_FONT") + if [ ! -z "$PLYMOUTH_MONOSPACE_FONT_PATH" ]; then + inst "$PLYMOUTH_MONOSPACE_FONT_PATH" $INITRDDIR + fi +fi + + if [ ! -f ${PLYMOUTH_SYSROOT}${PLYMOUTH_PLUGIN_PATH}/${PLYMOUTH_MODULE_NAME}.so ]; then echo "The default plymouth plugin (${PLYMOUTH_MODULE_NAME}) doesn't exist" >&2 exit 1 @@ -563,13 +592,17 @@ if [ "${PLYMOUTH_IMAGE_DIR}" != "${PLYMOUTH_THEME_DIR}" -a -d "${PLYMOUTH_SYSROO inst_recur "${PLYMOUTH_IMAGE_DIR}" fi +DefaultFont=$(fc-match -f %{file}) +inst "$DefaultFont" $INITRDDIR +DefaultMonospaceFont=$(fc-match -f %{file} monospace) +inst "$DefaultMonospaceFont" $INITRDDIR + if [ -f "${PLYMOUTH_PLUGIN_PATH}/label-freetype.so" ]; then inst ${PLYMOUTH_PLUGIN_PATH}/label-freetype.so $INITRDDIR - font=$(fc-match -f %{file}) - inst "$font" $INITRDDIR # The label-freetype plugin expects it at this location mkdir -p $INITRDDIR/usr/share/fonts - ln -s "$font" $INITRDDIR/usr/share/fonts/Plymouth.ttf + ln -s "$DefaultFont" $INITRDDIR/usr/share/fonts/Plymouth.ttf + ln -s "$DefaultMonospaceFont" $INITRDDIR/usr/share/fonts/Plymouth-monospace.ttf fi if [ -L ${PLYMOUTH_SYSROOT}${PLYMOUTH_DATADIR}/plymouth/themes/default.plymouth ]; then From 53d6e5a8447764756d0f065945d884c63da85b6c Mon Sep 17 00:00:00 2001 From: n3rdopolis Date: Sun, 26 Nov 2023 11:37:30 -0500 Subject: [PATCH 03/22] libply-splash-core: Add class to handle rich text In order to implement a terminal that supports colors and styles, we need some way assign colors and styles to text. This commit adds a new class ply_rich_text_t to handle that. Code split out of ply-terminal-emulator.c by Ray Strode. --- src/libply-splash-core/meson.build | 2 + src/libply-splash-core/ply-rich-text.c | 221 +++++++++++++++++++++++++ src/libply-splash-core/ply-rich-text.h | 81 +++++++++ 3 files changed, 304 insertions(+) create mode 100644 src/libply-splash-core/ply-rich-text.c create mode 100644 src/libply-splash-core/ply-rich-text.h diff --git a/src/libply-splash-core/meson.build b/src/libply-splash-core/meson.build index 69636b13..4d846975 100644 --- a/src/libply-splash-core/meson.build +++ b/src/libply-splash-core/meson.build @@ -6,6 +6,7 @@ libply_splash_core_sources = files( 'ply-pixel-buffer.c', 'ply-pixel-display.c', 'ply-renderer.c', + 'ply-rich-text.c', 'ply-terminal.c', 'ply-text-display.c', 'ply-text-progress-bar.c', @@ -59,6 +60,7 @@ libply_splash_core_headers = files( 'ply-pixel-display.h', 'ply-renderer-plugin.h', 'ply-renderer.h', + 'ply-rich-text.h', 'ply-terminal.h', 'ply-text-display.h', 'ply-text-progress-bar.h', diff --git a/src/libply-splash-core/ply-rich-text.c b/src/libply-splash-core/ply-rich-text.c new file mode 100644 index 00000000..0cc87893 --- /dev/null +++ b/src/libply-splash-core/ply-rich-text.c @@ -0,0 +1,221 @@ +/* ply-rich-text.c - Text with colors and styles + * + * 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-rich-text.h" +#include "ply-logger.h" +#include +#include +#include +#include + +#include + +struct _ply_rich_text_t +{ + ply_array_t *characters; + size_t reference_count; +}; + +ply_rich_text_t * +ply_rich_text_new (void) +{ + ply_rich_text_t *rich_text; + + rich_text = calloc (1, sizeof(ply_rich_text_t)); + rich_text->characters = ply_array_new (PLY_ARRAY_ELEMENT_TYPE_POINTER); + rich_text->reference_count = 1; + + return rich_text; +} + +void +ply_rich_text_take_reference (ply_rich_text_t *rich_text) +{ + rich_text->reference_count++; +} + +void +ply_rich_text_drop_reference (ply_rich_text_t *rich_text) +{ + rich_text->reference_count--; + + if (rich_text->reference_count == 0) + ply_rich_text_free (rich_text); +} + +void +ply_rich_text_free (ply_rich_text_t *rich_text) +{ + ply_rich_text_character_t **characters; + + if (rich_text == NULL) + return; + + characters = ply_rich_text_get_characters (rich_text); + characters = (ply_rich_text_character_t **) ply_array_get_pointer_elements (rich_text->characters); + for (size_t i = 0; characters[i] != NULL; i++) { + ply_rich_text_character_free (characters[i]); + } + + ply_array_free (rich_text->characters); + free (rich_text); +} + +char * +ply_rich_text_get_string (ply_rich_text_t *rich_text, + ply_rich_text_span_t *span) +{ + char *string = NULL; + ply_buffer_t *buffer; + ply_rich_text_character_t **characters; + + characters = ply_rich_text_get_characters (rich_text); + + buffer = ply_buffer_new (); + for (size_t i = span->offset; characters[i] != NULL; i++) { + if (span->range >= 0) { + if (i >= span->offset + span->range) + break; + } + + ply_buffer_append_bytes (buffer, + characters[i]->bytes, + characters[i]->length); + } + + string = ply_buffer_steal_bytes (buffer); + + ply_buffer_free (buffer); + + return string; +} + +void +ply_rich_text_remove_characters (ply_rich_text_t *rich_text) +{ + ply_rich_text_character_t **characters; + + if (rich_text == NULL) + return; + + characters = ply_rich_text_get_characters (rich_text); + characters = (ply_rich_text_character_t **) ply_array_get_pointer_elements (rich_text->characters); + for (size_t i = 0; characters[i] != NULL; i++) { + ply_rich_text_character_free (characters[i]); + } + + ply_array_free (rich_text->characters); + rich_text->characters = ply_array_new (PLY_ARRAY_ELEMENT_TYPE_POINTER); +} + +size_t +ply_rich_text_get_length (ply_rich_text_t *rich_text) +{ + ply_rich_text_character_t **characters; + size_t length = 0; + + characters = ply_rich_text_get_characters (rich_text); + + for (length = 0; characters[length] != NULL; length++) { + } + + return length; +} + +ply_rich_text_character_t * +ply_rich_text_character_new (void) +{ + ply_rich_text_character_t *character = calloc (1, sizeof(ply_rich_text_character_t)); + character->bytes = NULL; + character->length = 0; + + return character; +} + +void +ply_rich_text_character_free (ply_rich_text_character_t *character) +{ + free (character->bytes); + free (character); +} + +ply_rich_text_character_t ** +ply_rich_text_get_characters (ply_rich_text_t *rich_text) +{ + ply_rich_text_character_t **characters; + + characters = (ply_rich_text_character_t **) ply_array_get_pointer_elements (rich_text->characters); + return characters; +} + +void +ply_rich_text_remove_character (ply_rich_text_t *rich_text, + size_t character_index) +{ + ply_rich_text_character_t **characters; + + characters = ply_rich_text_get_characters (rich_text); + + if (characters[character_index] == NULL) + return; + + ply_rich_text_character_free (characters[character_index]); + characters[character_index] = NULL; +} + +void +ply_rich_text_move_character (ply_rich_text_t *rich_text, + size_t old_index, + size_t new_index) +{ + ply_rich_text_character_t **characters = ply_rich_text_get_characters (rich_text); + characters[new_index] = characters[old_index]; + characters[old_index] = NULL; +} + +void +ply_rich_text_set_character (ply_rich_text_t *rich_text, + ply_rich_text_character_style_t style, + size_t character_index, + const char *character_string, + size_t length) +{ + ply_rich_text_character_t **characters; + ply_rich_text_character_t *character; + + characters = ply_rich_text_get_characters (rich_text); + + if (characters[character_index] == NULL) { + character = ply_rich_text_character_new (); + ply_array_add_pointer_element (rich_text->characters, character); + characters = (ply_rich_text_character_t **) ply_array_get_pointer_elements (rich_text->characters); + } else { + character = characters[character_index]; + if (character->bytes) { + free (character->bytes); + character->bytes = NULL; + } + } + + characters[character_index] = character; + character->bytes = strdup (character_string); + character->length = length; + character->style = style; +} diff --git a/src/libply-splash-core/ply-rich-text.h b/src/libply-splash-core/ply-rich-text.h new file mode 100644 index 00000000..27692deb --- /dev/null +++ b/src/libply-splash-core/ply-rich-text.h @@ -0,0 +1,81 @@ +/* ply-rich-text.h - Text with colors and styles + * + * 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_RICH_TEXT_H +#define PLY_RICH_TEXT_H + +#include "ply-array.h" +#include "ply-terminal.h" +#include + +#define PLY_UTF8_CHARACTER_MAX_SIZE 5 + +typedef struct _ply_rich_text_t ply_rich_text_t; + +typedef struct +{ + ply_terminal_color_t foreground_color; + ply_terminal_color_t background_color; + uint32_t bold_enabled : 1; + uint32_t dim_enabled : 1; + uint32_t italic_enabled : 1; + uint32_t underline_enabled : 1; + uint32_t reverse_enabled : 1; +} ply_rich_text_character_style_t; + +typedef struct +{ + char *bytes; + size_t length; + + ply_rich_text_character_style_t style; +} ply_rich_text_character_t; + +typedef struct +{ + ssize_t offset; + ssize_t range; +} ply_rich_text_span_t; + +#ifndef PLY_HIDE_FUNCTION_DECLARATIONS +ply_rich_text_t *ply_rich_text_new (void); +void ply_rich_text_take_reference (ply_rich_text_t *rich_text); +void ply_rich_text_drop_reference (ply_rich_text_t *rich_text); +char *ply_rich_text_get_string (ply_rich_text_t *rich_text, + ply_rich_text_span_t *span); +size_t ply_rich_text_get_length (ply_rich_text_t *rich_text); +void ply_rich_text_set_character (ply_rich_text_t *rich_text, + ply_rich_text_character_style_t style, + size_t index, + const char *bytes, + size_t length); +void ply_rich_text_move_character (ply_rich_text_t *rich_text, + size_t old_index, + size_t new_index); +void ply_rich_text_remove_character (ply_rich_text_t *rich_text, + size_t character_index); + +void ply_rich_text_remove_characters (ply_rich_text_t *rich_text); +ply_rich_text_character_t **ply_rich_text_get_characters (ply_rich_text_t *rich_text); +void ply_rich_text_free (ply_rich_text_t *rich_text); + +ply_rich_text_character_t *ply_rich_text_character_new (void); +void ply_rich_text_character_free (ply_rich_text_character_t *character); + +#endif //PLY_HIDE_FUNCTION_DECLARATIONS +#endif //PLY_RICH_TEXT_H From 8729f840a444c3d5791d53f4faece310633239af Mon Sep 17 00:00:00 2001 From: nerdopolis Date: Sat, 27 May 2023 21:22:32 -0400 Subject: [PATCH 04/22] Introduce ply-terminal-emulator to handle various console sequences from kernel logging facilities --- src/libply-splash-core/meson.build | 2 + .../ply-terminal-emulator.c | 1288 +++++++++++++++++ .../ply-terminal-emulator.h | 70 + 3 files changed, 1360 insertions(+) create mode 100644 src/libply-splash-core/ply-terminal-emulator.c create mode 100644 src/libply-splash-core/ply-terminal-emulator.h diff --git a/src/libply-splash-core/meson.build b/src/libply-splash-core/meson.build index 4d846975..0a7a6c5b 100644 --- a/src/libply-splash-core/meson.build +++ b/src/libply-splash-core/meson.build @@ -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 index 00000000..47526224 --- /dev/null +++ b/src/libply-splash-core/ply-terminal-emulator.c @@ -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 +#include +#include +#include + +#include + +#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 '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 '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 '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 index 00000000..4fc93610 --- /dev/null +++ b/src/libply-splash-core/ply-terminal-emulator.h @@ -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 + +/* 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 From 9637be7d88925bdb5bac37e56f598d2f9a6d69e0 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Sat, 2 Dec 2023 12:36:54 -0500 Subject: [PATCH 05/22] ply-utils: Add a utf-8 string iterator API This will make it easier to walk through a string and extract each character. --- src/libply/ply-utils.c | 58 ++++++++++++++++++++++++++++++++++++++++++ src/libply/ply-utils.h | 18 +++++++++++++ 2 files changed, 76 insertions(+) diff --git a/src/libply/ply-utils.c b/src/libply/ply-utils.c index fb77e74b..2de5f401 100644 --- a/src/libply/ply-utils.c +++ b/src/libply/ply-utils.c @@ -776,6 +776,64 @@ ply_utf8_string_get_length (const char *string, return count; } +size_t +ply_utf8_string_get_byte_offset_from_character_offset (const char *string, + size_t character_offset) +{ + size_t byte_offset = 0; + size_t i; + + for (i = 0; i < character_offset && string[byte_offset] != '\0'; i++) { + byte_offset += ply_utf8_character_get_size (string + byte_offset, PLY_UTF8_CHARACTER_SIZE_MAX); + } + + return byte_offset; +} + +void +ply_utf8_string_iterator_init (ply_utf8_string_iterator_t *iterator, + const char *string, + ssize_t starting_offset, + ssize_t range) +{ + size_t byte_offset; + + iterator->character_range = range; + iterator->string = string; + + byte_offset = ply_utf8_string_get_byte_offset_from_character_offset (string, starting_offset); + iterator->current_byte_offset = byte_offset; + iterator->number_characters_iterated = 0; +} + +bool +ply_utf8_string_iterator_next (ply_utf8_string_iterator_t *iterator, + const char **character, + size_t *size) +{ + size_t size_of_current_character; + + if (iterator->number_characters_iterated >= iterator->character_range) + return false; + + if (iterator->string[iterator->current_byte_offset] == '\0') + return false; + + size_of_current_character = ply_utf8_character_get_size (iterator->string + iterator->current_byte_offset, + PLY_UTF8_CHARACTER_SIZE_MAX); + + if (size_of_current_character == 0) + return false; + + *character = &iterator->string[iterator->current_byte_offset]; + *size = size_of_current_character; + + iterator->current_byte_offset += size_of_current_character; + iterator->number_characters_iterated++; + + return true; +} + char * ply_get_process_command_line (pid_t pid) { diff --git a/src/libply/ply-utils.h b/src/libply/ply-utils.h index 3893a6f6..89bb37fd 100644 --- a/src/libply/ply-utils.h +++ b/src/libply/ply-utils.h @@ -55,6 +55,14 @@ typedef enum PLY_UNIX_SOCKET_TYPE_TRIMMED_ABSTRACT } ply_unix_socket_type_t; +typedef struct +{ + const char *string; + ssize_t character_range; + ssize_t current_byte_offset; + ssize_t number_characters_iterated; +} ply_utf8_string_iterator_t; + #ifndef PLY_HIDE_FUNCTION_DECLARATIONS #define ply_round_to_multiple(n, m) (((n) + (((m) - 1))) & ~((m) - 1)) @@ -117,6 +125,16 @@ int ply_utf8_character_get_size (const char *string, int ply_utf8_string_get_length (const char *string, size_t n); +size_t ply_utf8_string_get_byte_offset_from_character_offset (const char *string, + size_t character_offset); +void ply_utf8_string_iterator_init (ply_utf8_string_iterator_t *iterator, + const char *string, + ssize_t starting_offset, + ssize_t range); +bool ply_utf8_string_iterator_next (ply_utf8_string_iterator_t *iterator, + const char **character, + size_t *size); + char *ply_get_process_command_line (pid_t pid); pid_t ply_get_process_parent_pid (pid_t pid); From 8d9e3c0350163fca8b0c7745ce5084f41c71e84e Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Sat, 2 Dec 2023 12:22:35 -0500 Subject: [PATCH 06/22] rich-text: Add iterator API This commit adds a small wrapper around ply_rich_text_get_characters to make it easier to iterate over every character in a loop. --- src/libply-splash-core/ply-rich-text.c | 33 ++++++++++++++++++++++++++ src/libply-splash-core/ply-rich-text.h | 13 ++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/libply-splash-core/ply-rich-text.c b/src/libply-splash-core/ply-rich-text.c index 0cc87893..13a9791f 100644 --- a/src/libply-splash-core/ply-rich-text.c +++ b/src/libply-splash-core/ply-rich-text.c @@ -219,3 +219,36 @@ ply_rich_text_set_character (ply_rich_text_t *rich_text, character->length = length; character->style = style; } + +void +ply_rich_text_iterator_init (ply_rich_text_iterator_t *iterator, + ply_rich_text_t *rich_text, + ply_rich_text_span_t *span) +{ + iterator->rich_text = rich_text; + iterator->span = *span; + iterator->current_offset = span->offset; +} + +bool +ply_rich_text_iterator_next (ply_rich_text_iterator_t *iterator, + ply_rich_text_character_t **character) +{ + ply_rich_text_t *rich_text = iterator->rich_text; + ply_rich_text_span_t *span = &iterator->span; + ply_rich_text_character_t **characters = ply_rich_text_get_characters (rich_text); + + if (iterator->current_offset >= span->offset + span->range) { + return false; + } + + if (characters[iterator->current_offset] == NULL) { + return false; + } + + *character = characters[iterator->current_offset]; + + iterator->current_offset++; + + return true; +} diff --git a/src/libply-splash-core/ply-rich-text.h b/src/libply-splash-core/ply-rich-text.h index 27692deb..6455158c 100644 --- a/src/libply-splash-core/ply-rich-text.h +++ b/src/libply-splash-core/ply-rich-text.h @@ -52,6 +52,13 @@ typedef struct ssize_t range; } ply_rich_text_span_t; +typedef struct +{ + ply_rich_text_t *rich_text; + ply_rich_text_span_t span; + ssize_t current_offset; +} ply_rich_text_iterator_t; + #ifndef PLY_HIDE_FUNCTION_DECLARATIONS ply_rich_text_t *ply_rich_text_new (void); void ply_rich_text_take_reference (ply_rich_text_t *rich_text); @@ -77,5 +84,11 @@ void ply_rich_text_free (ply_rich_text_t *rich_text); ply_rich_text_character_t *ply_rich_text_character_new (void); void ply_rich_text_character_free (ply_rich_text_character_t *character); +void ply_rich_text_iterator_init (ply_rich_text_iterator_t *iterator, + ply_rich_text_t *rich_text, + ply_rich_text_span_t *span); +bool ply_rich_text_iterator_next (ply_rich_text_iterator_t *iterator, + ply_rich_text_character_t **character); + #endif //PLY_HIDE_FUNCTION_DECLARATIONS #endif //PLY_RICH_TEXT_H From e0a8a9973b824b168c82d4865f75943eb4b10e7b Mon Sep 17 00:00:00 2001 From: nerdopolis Date: Tue, 13 Jun 2023 22:49:51 -0400 Subject: [PATCH 07/22] ply-label: Introduce set_rich_text_for_control () for allowing text properties like color to be set within labels --- src/libply-splash-graphics/ply-label-plugin.h | 5 +- src/libply-splash-graphics/ply-label.c | 76 +++++++++++++++---- src/libply-splash-graphics/ply-label.h | 6 ++ 3 files changed, 72 insertions(+), 15 deletions(-) diff --git a/src/libply-splash-graphics/ply-label-plugin.h b/src/libply-splash-graphics/ply-label-plugin.h index a44efb39..d5c266e8 100644 --- a/src/libply-splash-graphics/ply-label-plugin.h +++ b/src/libply-splash-graphics/ply-label-plugin.h @@ -36,7 +36,7 @@ typedef struct _ply_label_plugin_control ply_label_plugin_control_t; typedef struct { - ply_label_plugin_control_t * (*create_control)(void); + ply_label_plugin_control_t *(*create_control)(void); void (*destroy_control)(ply_label_plugin_control_t *label); bool (*show_control)(ply_label_plugin_control_t *label, ply_pixel_display_t *display, @@ -53,6 +53,9 @@ typedef struct void (*set_text_for_control)(ply_label_plugin_control_t *label, const char *text); + void (*set_rich_text_for_control)(ply_label_plugin_control_t *label, + ply_rich_text_t *rich_text, + ply_rich_text_span_t *span); void (*set_font_for_control)(ply_label_plugin_control_t *label, const char *fontdesc); void (*set_color_for_control)(ply_label_plugin_control_t *label, diff --git a/src/libply-splash-graphics/ply-label.c b/src/libply-splash-graphics/ply-label.c index bac5947f..f3a99fd8 100644 --- a/src/libply-splash-graphics/ply-label.c +++ b/src/libply-splash-graphics/ply-label.c @@ -37,6 +37,7 @@ #include "ply-list.h" #include "ply-logger.h" #include "ply-utils.h" +#include "ply-array.h" struct _ply_label { @@ -46,9 +47,13 @@ struct _ply_label ply_label_plugin_control_t *control; char *text; + + ply_rich_text_t *rich_text; + ply_rich_text_span_t span; + ply_label_alignment_t alignment; long width; - char *fontdesc; + char *font; float red; float green; float blue; @@ -86,6 +91,14 @@ ply_label_free (ply_label_t *label) ply_label_unload_plugin (label); } + free (label->text); + free (label->font); + + if (label->rich_text) { + ply_rich_text_drop_reference (label->rich_text); + label->rich_text = NULL; + } + free (label); } @@ -98,7 +111,7 @@ ply_label_load_plugin (ply_label_t *label) label->module_handle = ply_open_module (PLYMOUTH_PLUGIN_PATH "label-pango.so"); - /* ...and the FreeType based one after that, it is not a complete substitute (yet). */ + /* and the FreeType based one after that, it is not a complete substitute (yet). */ if (label->module_handle == NULL) label->module_handle = ply_open_module (PLYMOUTH_PLUGIN_PATH "label-freetype.so"); @@ -138,16 +151,23 @@ ply_label_load_plugin (ply_label_t *label) return false; } - if (label->text != NULL) - label->plugin_interface->set_text_for_control (label->control, - label->text); + if (label->text != NULL) { + if (label->rich_text == NULL) { + label->plugin_interface->set_text_for_control (label->control, + label->text); + } else { + label->plugin_interface->set_rich_text_for_control (label->control, + label->rich_text, + &label->span); + } + } label->plugin_interface->set_alignment_for_control (label->control, label->alignment); label->plugin_interface->set_width_for_control (label->control, label->width); - if (label->fontdesc != NULL) + if (label->font != NULL) label->plugin_interface->set_font_for_control (label->control, - label->fontdesc); + label->font); label->plugin_interface->set_color_for_control (label->control, label->red, @@ -231,6 +251,11 @@ ply_label_set_text (ply_label_t *label, free (label->text); label->text = strdup (text); + if (label->rich_text) { + ply_rich_text_drop_reference (label->rich_text); + label->rich_text = NULL; + } + if (label->plugin_interface == NULL) return; @@ -238,6 +263,29 @@ ply_label_set_text (ply_label_t *label, text); } +void +ply_label_set_rich_text (ply_label_t *label, + ply_rich_text_t *rich_text, + ply_rich_text_span_t *span) +{ + free (label->text); + label->text = ply_rich_text_get_string (rich_text, span); + + if (label->rich_text) + ply_rich_text_drop_reference (label->rich_text); + label->rich_text = rich_text; + ply_rich_text_take_reference (rich_text); + + label->span = *span; + + if (label->plugin_interface == NULL) + return; + + label->plugin_interface->set_rich_text_for_control (label->control, + label->rich_text, + &label->span); +} + void ply_label_set_alignment (ply_label_t *label, ply_label_alignment_t alignment) @@ -265,25 +313,25 @@ ply_label_set_width (ply_label_t *label, } /* - * Please see pango documentation, for fontdesc format: + * Please see pango documentation, for font format: * http://library.gnome.org/devel/pango/stable/pango-Fonts.html#pango-font-description-from-string * If you pass NULL, it will use default font. */ void ply_label_set_font (ply_label_t *label, - const char *fontdesc) + const char *font) { - free (label->fontdesc); - if (fontdesc) - label->fontdesc = strdup (fontdesc); + free (label->font); + if (font) + label->font = strdup (font); else - label->fontdesc = NULL; + label->font = NULL; if (label->plugin_interface == NULL) return; label->plugin_interface->set_font_for_control (label->control, - fontdesc); + font); } void diff --git a/src/libply-splash-graphics/ply-label.h b/src/libply-splash-graphics/ply-label.h index 55debd84..085aa692 100644 --- a/src/libply-splash-graphics/ply-label.h +++ b/src/libply-splash-graphics/ply-label.h @@ -29,6 +29,7 @@ #include "ply-event-loop.h" #include "ply-pixel-buffer.h" #include "ply-pixel-display.h" +#include "ply-rich-text.h" typedef struct _ply_label ply_label_t; @@ -60,6 +61,11 @@ bool ply_label_is_hidden (ply_label_t *label); void ply_label_set_text (ply_label_t *label, const char *text); + +void ply_label_set_rich_text (ply_label_t *label, + ply_rich_text_t *rich_text, + ply_rich_text_span_t *span); + void ply_label_set_alignment (ply_label_t *label, ply_label_alignment_t alignment); void ply_label_set_width (ply_label_t *label, From 328a61cf51bc6626b403ac98b52bdbbd64ef5419 Mon Sep 17 00:00:00 2001 From: n3rdopolis Date: Sun, 19 Mar 2023 22:40:52 -0400 Subject: [PATCH 08/22] ply-label: Add ply_label_set_hex_color () based off of ply_pixel_buffer_fill_with_hex_color for configuration of label text colors --- src/libply-splash-graphics/ply-label.c | 24 ++++++++++++++++++++++++ src/libply-splash-graphics/ply-label.h | 2 ++ 2 files changed, 26 insertions(+) diff --git a/src/libply-splash-graphics/ply-label.c b/src/libply-splash-graphics/ply-label.c index f3a99fd8..ecb79404 100644 --- a/src/libply-splash-graphics/ply-label.c +++ b/src/libply-splash-graphics/ply-label.c @@ -334,6 +334,30 @@ ply_label_set_font (ply_label_t *label, font); } +void +ply_label_set_hex_color (ply_label_t *label, + uint32_t hex_color) +{ + double red; + double green; + double blue; + double alpha; + + red = ((double) (hex_color & 0xff000000) / 0xff000000); + green = ((double) (hex_color & 0x00ff0000) / 0x00ff0000); + blue = ((double) (hex_color & 0x0000ff00) / 0x0000ff00); + alpha = ((double) (hex_color & 0x000000ff) / 0x000000ff); + + if (label->plugin_interface == NULL) + return; + + label->plugin_interface->set_color_for_control (label->control, + red, + green, + blue, + alpha); +} + void ply_label_set_color (ply_label_t *label, float red, diff --git a/src/libply-splash-graphics/ply-label.h b/src/libply-splash-graphics/ply-label.h index 085aa692..5f4e6e81 100644 --- a/src/libply-splash-graphics/ply-label.h +++ b/src/libply-splash-graphics/ply-label.h @@ -72,6 +72,8 @@ void ply_label_set_width (ply_label_t *label, long width); void ply_label_set_font (ply_label_t *label, const char *fontdesc); +void ply_label_set_hex_color (ply_label_t *label, + uint32_t hex_color); void ply_label_set_color (ply_label_t *label, float red, float green, From d4560c71e791cf6c80584ff7c67cdb1ec7034339 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Sat, 2 Dec 2023 17:18:10 -0500 Subject: [PATCH 09/22] ply-label: Set font before sizing label This commit makes sure the right font is loaded before sizing the label, so the size comes out correctly. --- src/libply-splash-graphics/ply-label.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libply-splash-graphics/ply-label.c b/src/libply-splash-graphics/ply-label.c index ecb79404..5f783087 100644 --- a/src/libply-splash-graphics/ply-label.c +++ b/src/libply-splash-graphics/ply-label.c @@ -151,6 +151,10 @@ ply_label_load_plugin (ply_label_t *label) return false; } + if (label->font != NULL) + label->plugin_interface->set_font_for_control (label->control, + label->font); + if (label->text != NULL) { if (label->rich_text == NULL) { label->plugin_interface->set_text_for_control (label->control, @@ -165,10 +169,6 @@ ply_label_load_plugin (ply_label_t *label) label->alignment); label->plugin_interface->set_width_for_control (label->control, label->width); - if (label->font != NULL) - label->plugin_interface->set_font_for_control (label->control, - label->font); - label->plugin_interface->set_color_for_control (label->control, label->red, label->green, From 06cd316a34af36653ae14be91e7553cd45ba19ec Mon Sep 17 00:00:00 2001 From: nerdopolis Date: Tue, 10 Oct 2023 07:45:35 -0400 Subject: [PATCH 10/22] label-pango: Replace characters not supported by fonts with a replacement character Using a replacement character prevents hexboxes from appearing, which can mess up alignment of monospace fonts --- src/plugins/controls/label-pango/plugin.c | 45 +++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/plugins/controls/label-pango/plugin.c b/src/plugins/controls/label-pango/plugin.c index d53da97c..9c9fbcd2 100644 --- a/src/plugins/controls/label-pango/plugin.c +++ b/src/plugins/controls/label-pango/plugin.c @@ -164,6 +164,50 @@ get_cairo_context_for_sizing (ply_label_plugin_control_t *label) return cairo_context; } +void +remove_hexboxes_from_pango_layout (PangoLayout *pango_layout) +{ + PangoLayoutIter *iter; + bool hexbox_removed = false; + ply_buffer_t *buffer = ply_buffer_new (); + const char *old_string = pango_layout_get_text (pango_layout); + + iter = pango_layout_get_iter (pango_layout); + + do { + PangoLayoutRun *run; + PangoGlyphItem *glyph_items; + PangoGlyphString *glyph_string; + + run = pango_layout_iter_get_run_readonly (iter); + if (!run) + continue; + + glyph_items = (PangoGlyphItem *) run; + glyph_string = glyph_items->glyphs; + + if (glyph_string->num_glyphs == 0) + continue; + + for (size_t i = 0; i < glyph_string->num_glyphs; i++) { + if (glyph_string->glyphs[i].glyph & PANGO_GLYPH_UNKNOWN_FLAG) { + hexbox_removed = true; + ply_buffer_append (buffer, "%c", '?'); + } else { + ply_buffer_append_bytes (buffer, old_string + glyph_items->item->offset, glyph_items->item->length); + } + } + } while (pango_layout_iter_next_run (iter)); + pango_layout_iter_free (iter); + + if (hexbox_removed) { + const char *new_string = ply_buffer_get_bytes (buffer); + pango_layout_set_text (pango_layout, new_string, -1); + } + + ply_buffer_free (buffer); +} + static PangoLayout * init_pango_text_layout (cairo_t *cairo_context, char *text, @@ -245,6 +289,7 @@ draw_control (ply_label_plugin_control_t *label, cairo_context = get_cairo_context_for_pixel_buffer (label, pixel_buffer, ¢er_x, ¢er_y); pango_layout = init_pango_text_layout (cairo_context, label->text, label->fontdesc, label->alignment, label->width); + remove_hexboxes_from_pango_layout (pango_layout); pango_layout_get_size (pango_layout, &text_width, &text_height); label->area.width = (long) ((double) text_width / PANGO_SCALE); From 42f6b8513e0d413341023c3ffb2aac89b34b6a45 Mon Sep 17 00:00:00 2001 From: nerdopolis Date: Wed, 14 Jun 2023 19:35:50 -0400 Subject: [PATCH 11/22] label-pango: Add support for set_rich_text Now that the ply-label interface supports rich text, we need to implement it in the plugins. This commit implements it for the pango plugin. --- src/plugins/controls/label-pango/plugin.c | 335 +++++++++++++++++++++- 1 file changed, 323 insertions(+), 12 deletions(-) diff --git a/src/plugins/controls/label-pango/plugin.c b/src/plugins/controls/label-pango/plugin.c index 9c9fbcd2..57194689 100644 --- a/src/plugins/controls/label-pango/plugin.c +++ b/src/plugins/controls/label-pango/plugin.c @@ -43,6 +43,7 @@ #include #include +#include "ply-terminal.h" #include "ply-pixel-buffer.h" #include "ply-pixel-display.h" #include "ply-utils.h" @@ -56,9 +57,10 @@ struct _ply_label_plugin_control ply_rectangle_t area; char *text; - char *fontdesc; + char *font; PangoAlignment alignment; + PangoAttrList *attribute_list; long width; float red; float green; @@ -81,6 +83,7 @@ create_control (void) label->is_hidden = true; label->alignment = PANGO_ALIGN_LEFT; label->width = -1; + label->attribute_list = pango_attr_list_new (); return label; } @@ -88,9 +91,19 @@ create_control (void) static void destroy_control (ply_label_plugin_control_t *label) { + GSList *attributes, *attribute; + if (label == NULL) return; + if (label->attribute_list) { + attributes = pango_attr_list_get_attributes (label->attribute_list); + for (attribute = attributes; attribute; attribute = attribute->next) { + pango_attribute_destroy (attribute->data); + } + pango_attr_list_unref (label->attribute_list); + g_slist_free (attributes); + } free (label); } @@ -208,11 +221,70 @@ remove_hexboxes_from_pango_layout (PangoLayout *pango_layout) ply_buffer_free (buffer); } +void +look_up_rgb_color_from_terminal_color (ply_terminal_color_t color, + uint16_t *red, + uint16_t *green, + uint16_t *blue) +{ + switch (color) { + case PLY_TERMINAL_COLOR_BLACK: + *red = 0x0000; + *green = 0x0000; + *blue = 0x0000; + break; + /* Linux VT Color: 0xaa0000 */ + case PLY_TERMINAL_COLOR_RED: + *red = 0xaa00; + *green = 0x0000; + *blue = 0x0000; + break; + /* Linux VT Color: 0x00aa00 */ + case PLY_TERMINAL_COLOR_GREEN: + *red = 0x0000; + *green = 0xaa00; + *blue = 0x0000; + break; + /* Linux VT Color: 0xaa5500 */ + case PLY_TERMINAL_COLOR_BROWN: + *red = 0xaa00; + *green = 0x5500; + *blue = 0x0000; + break; + /* Linux VT Color: 0x0000aa */ + case PLY_TERMINAL_COLOR_BLUE: + *red = 0x0000; + *green = 0x0000; + *blue = 0xaa00; + break; + /* Linux VT Color: 0xaa00aa */ + case PLY_TERMINAL_COLOR_MAGENTA: + *red = 0xaa00; + *green = 0x0000; + *blue = 0xaa00; + break; + /* Linux VT Color: 0x00aaaa */ + case PLY_TERMINAL_COLOR_CYAN: + *red = 0x0000; + *green = 0xaa00; + *blue = 0xaa00; + break; + /* Linux VT Color: 0xaaaaaa */ + case PLY_TERMINAL_COLOR_WHITE: + default: + *red = 0xaa00; + *green = 0xaa00; + *blue = 0xaa00; + break; + } +} + static PangoLayout * init_pango_text_layout (cairo_t *cairo_context, char *text, char *font_description, PangoAlignment alignment, + PangoAttrList *attribute_list, long width) { PangoLayout *pango_layout; @@ -233,6 +305,7 @@ init_pango_text_layout (cairo_t *cairo_context, pango_layout_set_width (pango_layout, width * PANGO_SCALE); pango_layout_set_text (pango_layout, text ?: "", -1); + pango_layout_set_attributes (pango_layout, attribute_list); pango_cairo_update_layout (cairo_context, pango_layout); return pango_layout; @@ -257,7 +330,7 @@ size_control (ply_label_plugin_control_t *label, cairo_context = get_cairo_context_for_sizing (label); - pango_layout = init_pango_text_layout (cairo_context, label->text, label->fontdesc, label->alignment, label->width); + pango_layout = init_pango_text_layout (cairo_context, label->text, label->font, label->alignment, label->attribute_list, label->width); pango_layout_get_size (pango_layout, &text_width, &text_height); label->area.width = (long) ((double) text_width / PANGO_SCALE); @@ -288,7 +361,7 @@ draw_control (ply_label_plugin_control_t *label, cairo_context = get_cairo_context_for_pixel_buffer (label, pixel_buffer, ¢er_x, ¢er_y); - pango_layout = init_pango_text_layout (cairo_context, label->text, label->fontdesc, label->alignment, label->width); + pango_layout = init_pango_text_layout (cairo_context, label->text, label->font, label->alignment, label->attribute_list, label->width); remove_hexboxes_from_pango_layout (pango_layout); pango_layout_get_size (pango_layout, &text_width, &text_height); @@ -361,8 +434,24 @@ set_width_for_control (ply_label_plugin_control_t *label, } static void -set_text_for_control (ply_label_plugin_control_t *label, - const char *text) +clear_text (ply_label_plugin_control_t *label) +{ + GSList *attributes, *attribute; + + if (label->attribute_list) { + attributes = pango_attr_list_get_attributes (label->attribute_list); + for (attribute = attributes; attribute; attribute = attribute->next) { + pango_attribute_destroy (attribute->data); + } + pango_attr_list_unref (label->attribute_list); + g_slist_free (attributes); + label->attribute_list = pango_attr_list_new (); + } +} + +static void +set_text (ply_label_plugin_control_t *label, + const char *text) { ply_rectangle_t dirty_area; @@ -378,19 +467,241 @@ set_text_for_control (ply_label_plugin_control_t *label, } } +static void +set_text_for_control (ply_label_plugin_control_t *label, + const char *text) +{ + clear_text (label); + set_text (label, text); +} + +static void +stage_pango_attribute_for_list (PangoAttrList *attribute_list, + PangoAttribute **staged_attributes, + PangoAttribute *new_attribute) +{ + PangoAttrType attribute_type = new_attribute->klass->type; + + if (staged_attributes[attribute_type] != NULL) { + if (!pango_attribute_equal (staged_attributes[attribute_type], new_attribute)) { + pango_attr_list_insert (attribute_list, staged_attributes[attribute_type]); + staged_attributes[attribute_type] = new_attribute; + } else { + staged_attributes[attribute_type]->end_index = new_attribute->end_index; + pango_attribute_destroy (new_attribute); + } + } else { + staged_attributes[attribute_type] = new_attribute; + } +} + +static void +flush_pango_attributes_to_list (PangoAttrList *attribute_list, + PangoAttribute **staged_attributes) +{ + for (size_t i = 0; i <= PANGO_ATTR_FONT_SCALE; i++) { + if (staged_attributes[i] == NULL) + continue; + + pango_attr_list_insert (attribute_list, staged_attributes[i]); + staged_attributes[i] = NULL; + } +} + +static void +set_rich_text_for_control (ply_label_plugin_control_t *label, + ply_rich_text_t *rich_text, + ply_rich_text_span_t *span) +{ + int i; + size_t start_index = 0; + size_t length; + char *string; + PangoAttribute *staged_attributes[PANGO_ATTR_FONT_SCALE + 1] = { NULL }; + ply_rich_text_character_t **characters; + + clear_text (label); + if (label->attribute_list) { + pango_attr_list_unref (label->attribute_list); + label->attribute_list = pango_attr_list_new (); + } + + characters = ply_rich_text_get_characters (rich_text); + for (i = span->offset; characters[i] != NULL; i++) { + PangoAttribute *pango_attribute = NULL; + uint16_t foreground_red, background_red; + uint16_t foreground_green, background_green; + uint16_t foreground_blue, background_blue; + + length = characters[i]->length; + + PangoWeight bold_style = PANGO_WEIGHT_NORMAL; + PangoStyle italic_style = PANGO_STYLE_NORMAL; + PangoUnderline underline_style = PANGO_UNDERLINE_NONE; + + ply_terminal_color_t foreground_color = PLY_TERMINAL_COLOR_DEFAULT; + ply_terminal_color_t background_color = PLY_TERMINAL_COLOR_DEFAULT; + + if (!characters[i]->style.reverse_enabled) { + foreground_color = characters[i]->style.foreground_color; + background_color = characters[i]->style.background_color; + } else { + foreground_color = characters[i]->style.background_color; + background_color = characters[i]->style.foreground_color; + + /* if no background color is specified, the label is transparent. + * When reversed, and the background color is default + */ + if (background_color == PLY_TERMINAL_COLOR_DEFAULT) { + background_color = PLY_TERMINAL_COLOR_WHITE; + + if (foreground_color == PLY_TERMINAL_COLOR_DEFAULT) + foreground_color = PLY_TERMINAL_COLOR_BLACK; + } + } + + /* Default to a black background when none is set so bright text is readable on bright backgrounds */ + if (background_color == PLY_TERMINAL_COLOR_DEFAULT) + background_color = PLY_TERMINAL_COLOR_BLACK; + + look_up_rgb_color_from_terminal_color (foreground_color, + &foreground_red, + &foreground_green, + &foreground_blue); + + look_up_rgb_color_from_terminal_color (background_color, + &background_red, + &background_green, + &background_blue); + + if (characters[i]->style.bold_enabled && characters[i]->style.dim_enabled) { + /* xterm subtracts 0x44 when bold and dim*/ + if (foreground_red > 0x4400) { + foreground_red -= 0x4400; + } else { + foreground_red = 0; + } + + if (foreground_green > 0x4400) { + foreground_green -= 0x4400; + } else { + foreground_green = 0; + } + + if (foreground_blue > 0x4400) { + foreground_blue -= 0x4400; + } else { + foreground_blue = 0; + } + bold_style = PANGO_WEIGHT_SEMIBOLD; + } else { + if (characters[i]->style.bold_enabled) { + /* Linux VT adds 0x55 when bold */ + if (foreground_red + 0x55ff < 0xffff) { + foreground_red += 0x55ff; + } else { + foreground_red = 0xffff; + } + + if (foreground_green + 0x55ff < 0xffff) { + foreground_green += 0x55ff; + } else { + foreground_green = 0xffff; + } + + if (foreground_blue + 0x55ff < 0xffff) { + foreground_blue += 0x55ff; + } else { + foreground_blue = 0xffff; + } + bold_style = PANGO_WEIGHT_BOLD; + } + + if (characters[i]->style.dim_enabled) { + /* xterm subtracts 0x23 when dim */ + if (foreground_red > 0x2300) { + foreground_red -= 0x2300; + } else { + foreground_red = 0; + } + + if (foreground_green > 0x2300) { + foreground_green -= 0x2300; + } else { + foreground_green = 0; + } + + if (foreground_blue > 0x2300) { + foreground_blue -= 0x2300; + } else { + foreground_blue = 0; + } + bold_style = PANGO_WEIGHT_LIGHT; + } + } + + if (foreground_color != PLY_TERMINAL_COLOR_DEFAULT) { + pango_attribute = pango_attr_foreground_new (foreground_red, foreground_green, foreground_blue); + pango_attribute->start_index = start_index; + pango_attribute->end_index = start_index + length; + + stage_pango_attribute_for_list (label->attribute_list, staged_attributes, pango_attribute); + } + + if (background_color != PLY_TERMINAL_COLOR_DEFAULT) { + pango_attribute = pango_attr_background_new (background_red, background_green, background_blue); + pango_attribute->start_index = start_index; + pango_attribute->end_index = start_index + length; + stage_pango_attribute_for_list (label->attribute_list, staged_attributes, pango_attribute); + } + + pango_attribute = pango_attr_weight_new (bold_style); + pango_attribute->start_index = start_index; + pango_attribute->end_index = start_index + length; + stage_pango_attribute_for_list (label->attribute_list, staged_attributes, pango_attribute); + + + if (characters[i]->style.italic_enabled == true) + italic_style = PANGO_STYLE_ITALIC; + + pango_attribute = pango_attr_style_new (italic_style); + pango_attribute->start_index = start_index; + pango_attribute->end_index = start_index + length; + stage_pango_attribute_for_list (label->attribute_list, staged_attributes, pango_attribute); + + if (characters[i]->style.underline_enabled == true) + underline_style = PANGO_UNDERLINE_SINGLE; + + pango_attribute = pango_attr_underline_new (underline_style); + pango_attribute->start_index = start_index; + pango_attribute->end_index = start_index + length; + stage_pango_attribute_for_list (label->attribute_list, staged_attributes, pango_attribute); + + start_index += length; + + if (i >= span->offset + span->range) + break; + } + flush_pango_attributes_to_list (label->attribute_list, staged_attributes); + + string = ply_rich_text_get_string (rich_text, span); + set_text (label, string); + free (string); +} + static void set_font_for_control (ply_label_plugin_control_t *label, - const char *fontdesc) + const char *font) { ply_rectangle_t dirty_area; - if (label->fontdesc != fontdesc) { + if (label->font != font) { dirty_area = label->area; - free (label->fontdesc); - if (fontdesc) - label->fontdesc = strdup (fontdesc); + free (label->font); + if (font) + label->font = strdup (font); else - label->fontdesc = NULL; + label->font = NULL; size_control (label, false); if (!label->is_hidden && label->display != NULL) ply_pixel_display_draw_area (label->display, @@ -489,6 +800,7 @@ ply_label_plugin_get_interface (void) .draw_control = draw_control, .is_control_hidden = is_control_hidden, .set_text_for_control = set_text_for_control, + .set_rich_text_for_control = set_rich_text_for_control, .set_alignment_for_control = set_alignment_for_control, .set_width_for_control = set_width_for_control, .set_font_for_control = set_font_for_control, @@ -499,4 +811,3 @@ ply_label_plugin_get_interface (void) return &plugin_interface; } - From 8af62d12848353292b03d3982c8cffc6c084de82 Mon Sep 17 00:00:00 2001 From: nerdopolis Date: Mon, 8 May 2023 19:24:19 -0400 Subject: [PATCH 12/22] label-freetype: Allow the label size to be set and calculated even when hidden --- src/plugins/controls/label-freetype/plugin.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/plugins/controls/label-freetype/plugin.c b/src/plugins/controls/label-freetype/plugin.c index 0fbf0945..9c9120cb 100644 --- a/src/plugins/controls/label-freetype/plugin.c +++ b/src/plugins/controls/label-freetype/plugin.c @@ -184,9 +184,6 @@ size_control (ply_label_plugin_control_t *label) FT_Int width; const char *text = label->text; - if (label->is_hidden) - return; - label->area.width = 0; label->area.height = 0; @@ -216,12 +213,12 @@ trigger_redraw (ply_label_plugin_control_t *label, { ply_rectangle_t dirty_area = label->area; - if (label->is_hidden || label->display == NULL) - return; - if (adjust_size) size_control (label); + if (label->is_hidden || label->display == NULL) + return; + ply_pixel_display_draw_area (label->display, dirty_area.x, dirty_area.y, dirty_area.width, dirty_area.height); From cc5e07c6d0278c14fff2fde5b9cc63913419810e Mon Sep 17 00:00:00 2001 From: n3rdopolis Date: Tue, 2 May 2023 17:03:03 -0400 Subject: [PATCH 13/22] label-freetype: Support monospaced fonts --- src/plugins/controls/label-freetype/plugin.c | 78 ++++++++++++++++---- 1 file changed, 62 insertions(+), 16 deletions(-) diff --git a/src/plugins/controls/label-freetype/plugin.c b/src/plugins/controls/label-freetype/plugin.c index 9c9120cb..4981b6ff 100644 --- a/src/plugins/controls/label-freetype/plugin.c +++ b/src/plugins/controls/label-freetype/plugin.c @@ -39,6 +39,7 @@ /* This is used if fontconfig (fc-match) is not available, like in the initrd. */ #define FONT_FALLBACK "/usr/share/fonts/Plymouth.ttf" +#define MONOSPACE_FONT_FALLBACK "/usr/share/fonts/Plymouth-monospace.ttf" struct _ply_label_plugin_control { @@ -58,6 +59,7 @@ struct _ply_label_plugin_control float alpha; uint32_t is_hidden : 1; + uint32_t is_monospaced : 1; }; ply_label_plugin_interface_t *ply_label_plugin_get_interface (void); @@ -80,10 +82,45 @@ query_fc_match () return fc_match_out; } +/* Query fontconfig, if available, for the default monospace font. */ +static const char * +query_fc_match_monospace () +{ + FILE *fp; + static char fc_match_out[PATH_MAX]; + + fp = popen ("/usr/bin/fc-match -f %{file} monospace", "r"); + if (!fp) + return NULL; + + fgets (fc_match_out, sizeof(fc_match_out), fp); + + pclose (fp); + + return fc_match_out; +} + +static FT_Error +set_font_with_fallback (ply_label_plugin_control_t *label, + const char *primary_font_path, + const char *fallback_font_path) +{ + FT_Error error; + if (primary_font_path) + error = FT_New_Face (label->library, primary_font_path, 0, &label->face); + + if (!fallback_font_path || error) { + printf ("label-ft: trying font fallback\n"); + error = FT_New_Face (label->library, fallback_font_path, 0, &label->face); + } + + return error; +} + static ply_label_plugin_control_t * create_control (void) { - FT_Error error; + int error; ply_label_plugin_control_t *label; const char *font_path; @@ -100,19 +137,11 @@ create_control (void) } font_path = query_fc_match (); - if (font_path) - error = FT_New_Face (label->library, font_path, 0, &label->face); - - if (!font_path || error) { - printf ("label-ft: trying font fallback\n"); - font_path = FONT_FALLBACK; - error = FT_New_Face (label->library, font_path, 0, &label->face); - - if (error) { - FT_Done_FreeType (label->library); - free (label); - return NULL; - } + error = set_font_with_fallback (label, font_path, FONT_FALLBACK); + if (error) { + FT_Done_FreeType (label->library); + free (label); + return NULL; } /* 12pt/96dpi as default */ @@ -393,13 +422,30 @@ static void set_font_for_control (ply_label_plugin_control_t *label, const char *fontdesc) { - /* Only able to set size */ + /* Only able to set size and monospaced/nonmonospaced */ + int error = 0; char *size_str_after; - const char *size_str; + const char *size_str, *font_path; unsigned long size; bool size_in_pixels; + if (strstr (fontdesc, "Mono") || strstr (fontdesc, "mono")) { + if (label->is_monospaced == false) { + FT_Done_Face (label->face); + font_path = query_fc_match_monospace (); + error = set_font_with_fallback (label, font_path, MONOSPACE_FONT_FALLBACK); + } + } else { + if (label->is_monospaced == true) { + FT_Done_Face (label->face); + font_path = query_fc_match (); + error = set_font_with_fallback (label, font_path, FONT_FALLBACK); + } + } + if (error) + FT_Done_Face (label->face); + size = 25; /* Default, if not set. */ size_in_pixels = false; From e190f3b9527f247f716ff367f8813f5e2eb11f7c Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Sat, 2 Dec 2023 13:11:37 -0500 Subject: [PATCH 14/22] label-freetype: Handle utf-8 characters better The plugin currently assumes all characters are 7 byte ascii. This commit just adds a mbrtowc call around the text to handle UTF-8 text somewhat better. --- src/plugins/controls/label-freetype/plugin.c | 38 +++++++++++++------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/src/plugins/controls/label-freetype/plugin.c b/src/plugins/controls/label-freetype/plugin.c index 4981b6ff..4744f335 100644 --- a/src/plugins/controls/label-freetype/plugin.c +++ b/src/plugins/controls/label-freetype/plugin.c @@ -181,26 +181,44 @@ get_height_of_control (ply_label_plugin_control_t *label) return label->area.height; } +static bool +load_character (ply_label_plugin_control_t *label, + const char **text) +{ + FT_Error error; + size_t character_size; + wchar_t character; + const char *input_text = *text; + + character_size = mbrtowc (&character, input_text, PLY_UTF8_CHARACTER_SIZE_MAX, NULL); + + if (character_size <= 0) { + character = (wchar_t) *input_text; + character_size = 1; + } + + error = FT_Load_Char (label->face, (FT_ULong) character, FT_LOAD_RENDER | FT_LOAD_TARGET_LIGHT); + + *text = input_text + character_size; + + return !error; +} + static FT_Int width_of_line (ply_label_plugin_control_t *label, const char *text) { - FT_Error error; FT_Int width = 0; FT_Int left_bearing = 0; while (*text != '\0' && *text != '\n') { - error = FT_Load_Char (label->face, *text, FT_LOAD_RENDER | FT_LOAD_TARGET_LIGHT); - - if (!error) { + if (load_character (label, &text)) { width += label->face->glyph->advance.x >> 6; left_bearing = label->face->glyph->bitmap_left; /* We don't "go back" when drawing, so when left bearing is * negative (like for 'j'), we simply add to the width. */ if (left_bearing < 0) width += -left_bearing; - - ++text; } } @@ -352,10 +370,8 @@ draw_control (ply_label_plugin_control_t *label, while (*cur_c && *cur_c != '\n') { FT_Int extraAdvance = 0, positiveBearingX = 0; - /* TODO: Unicode support. */ - error = FT_Load_Char (label->face, *cur_c, - FT_LOAD_RENDER | FT_LOAD_TARGET_LIGHT); - if (error) + + if (!load_character (label, &cur_c)) continue; /* We consider negative left bearing an increment in size, @@ -375,8 +391,6 @@ draw_control (ply_label_plugin_control_t *label, pen.x += slot->advance.x + extraAdvance; pen.y += slot->advance.y; - - ++cur_c; } /* skip newline character */ if (*cur_c) From 8fd5f9f2494532401a0cf9646270f2667e7cb8d9 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Fri, 14 Jul 2023 20:47:55 -0400 Subject: [PATCH 15/22] label-freetype: Add basic support for rich text Now that the ply-label interface supports rich text, we need to implement it in the plugins. This commit changes the freetype plugin to process rich text in a rudimentary way that picks up colors, but not other style attributes. Based on initial work from nerdopolis --- src/plugins/controls/label-freetype/plugin.c | 281 +++++++++++++++---- 1 file changed, 228 insertions(+), 53 deletions(-) diff --git a/src/plugins/controls/label-freetype/plugin.c b/src/plugins/controls/label-freetype/plugin.c index 4744f335..aef34f8c 100644 --- a/src/plugins/controls/label-freetype/plugin.c +++ b/src/plugins/controls/label-freetype/plugin.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include FT_FREETYPE_H @@ -53,6 +54,8 @@ struct _ply_label_plugin_control FT_Face face; char *text; + ply_rich_text_t *rich_text; + ply_rich_text_span_t span; float red; float green; float blue; @@ -205,8 +208,8 @@ load_character (ply_label_plugin_control_t *label, } static FT_Int -width_of_line (ply_label_plugin_control_t *label, - const char *text) +width_of_string (ply_label_plugin_control_t *label, + const char *text) { FT_Int width = 0; FT_Int left_bearing = 0; @@ -228,26 +231,65 @@ width_of_line (ply_label_plugin_control_t *label, static void size_control (ply_label_plugin_control_t *label) { - FT_Int width; - const char *text = label->text; + FT_Int character_width, line_width; + ply_rich_text_iterator_t rich_text_iterator; + ply_utf8_string_iterator_t utf8_string_iterator; + bool should_stop = false; + if (label->rich_text == NULL && label->text == NULL) { + label->area.width = 0; + label->area.height = 0; + return; + } + + if (label->rich_text != NULL) { + ply_rich_text_iterator_init (&rich_text_iterator, + label->rich_text, + &label->span); + } else { + ply_utf8_string_iterator_init (&utf8_string_iterator, + label->text, + 0, + ply_utf8_string_get_length (label->text, strlen (label->text))); + } label->area.width = 0; label->area.height = 0; + line_width = 0; + /* Go through each line */ - while (text && *text) { - width = width_of_line (label, text); - if ((uint32_t) width > label->area.width) - label->area.width = width; + do { + const char *current_character; - label->area.height += (label->face->size->metrics.ascender - - label->face->size->metrics.descender) >> 6; + if (label->rich_text != NULL) { + ply_rich_text_character_t *rich_text_character; + should_stop = !ply_rich_text_iterator_next (&rich_text_iterator, + &rich_text_character); + if (!should_stop) + current_character = rich_text_character->bytes; + } else { + size_t character_size; - text = strchr (text, '\n'); - /* skip newline character */ - if (text) - ++text; - } + should_stop = !ply_utf8_string_iterator_next (&utf8_string_iterator, + ¤t_character, + &character_size); + } + + if (!should_stop) { + character_width = width_of_string (label, current_character); + line_width += character_width; + } + + if (should_stop || character_width == 0) { + if ((uint32_t) line_width > label->area.width) + label->area.width = line_width; + + line_width = 0; + + label->area.height += (label->face->size->metrics.ascender + - label->face->size->metrics.descender) >> 6; + } + } while (!should_stop); /* If centered, area.x is not the origin anymore */ if ((long) label->area.width < label->width) @@ -277,7 +319,10 @@ draw_bitmap (ply_label_plugin_control_t *label, ply_rectangle_t target_size, FT_Bitmap *source, FT_Int x_start, - FT_Int y_start) + FT_Int y_start, + uint8_t rs, + uint8_t gs, + uint8_t bs) { FT_Int x, y, xs, ys; FT_Int x_end = MIN (x_start + source->width, target_size.width); @@ -287,10 +332,7 @@ draw_bitmap (ply_label_plugin_control_t *label, (uint32_t) y_start >= target_size.height) return; - uint8_t rs, gs, bs, rd, gd, bd, ad; - rs = 255 * label->red; - gs = 255 * label->green; - bs = 255 * label->blue; + uint8_t rd, gd, bd, ad; for (y = y_start, ys = 0; y < y_end; ++y, ++ys) { for (x = x_start, xs = 0; x < x_end; ++x, ++xs) { @@ -317,6 +359,67 @@ draw_bitmap (ply_label_plugin_control_t *label, } } +static void +look_up_rgb_color_from_terminal_color (ply_label_plugin_control_t *label, + ply_terminal_color_t color, + uint8_t *red, + uint8_t *green, + uint8_t *blue) +{ + switch (color) { + case PLY_TERMINAL_COLOR_BLACK: + *red = 0x00; + *green = 0x00; + *blue = 0x00; + break; + /* Linux VT Color: 0xaa0000 */ + case PLY_TERMINAL_COLOR_RED: + *red = 0xaa; + *green = 0x00; + *blue = 0x00; + break; + /* Linux VT Color: 0x00aa00 */ + case PLY_TERMINAL_COLOR_GREEN: + *red = 0x00; + *green = 0xaa; + *blue = 0x00; + break; + /* Linux VT Color: 0xaa5500 */ + case PLY_TERMINAL_COLOR_BROWN: + *red = 0xaa; + *green = 0x55; + *blue = 0x00; + break; + /* Linux VT Color: 0x0000aa */ + case PLY_TERMINAL_COLOR_BLUE: + *red = 0x00; + *green = 0x00; + *blue = 0xaa; + break; + /* Linux VT Color: 0xaa00aa */ + case PLY_TERMINAL_COLOR_MAGENTA: + *red = 0xaa; + *green = 0x00; + *blue = 0xaa; + break; + /* Linux VT Color: 0x00aaaa */ + case PLY_TERMINAL_COLOR_CYAN: + *red = 0x00; + *green = 0xaa; + *blue = 0xaa; + break; + /* Linux VT Color: 0xaaaaaa */ + case PLY_TERMINAL_COLOR_WHITE: + break; + + default: + *red = 255 * label->red; + *green = 255 * label->green; + *blue = 255 * label->blue; + break; + } +} + static void draw_control (ply_label_plugin_control_t *label, ply_pixel_buffer_t *pixel_buffer, @@ -325,16 +428,19 @@ draw_control (ply_label_plugin_control_t *label, unsigned long width, unsigned long height) { - FT_Error error; FT_Vector pen; FT_GlyphSlot slot; - const char *cur_c; + ply_rich_text_iterator_t rich_text_iterator; + ply_utf8_string_iterator_t utf8_string_iterator; uint32_t *target; ply_rectangle_t target_size; - if (label->is_hidden) return; + if (label->rich_text == NULL && + label->text == NULL) + return; + /* Check for overlap. * TODO: Don't redraw everything if only a part should be drawn! */ if (label->area.x > x + (long) width || label->area.y > y + (long) height @@ -344,7 +450,16 @@ draw_control (ply_label_plugin_control_t *label, slot = label->face->glyph; - cur_c = label->text; + if (label->rich_text != NULL) { + ply_rich_text_iterator_init (&rich_text_iterator, + label->rich_text, + &label->span); + } else { + ply_utf8_string_iterator_init (&utf8_string_iterator, + label->text, + 0, + ply_utf8_string_get_length (label->text, strlen (label->text))); + } target = ply_pixel_buffer_get_argb32_data (pixel_buffer); ply_pixel_buffer_get_size (pixel_buffer, &target_size); @@ -359,46 +474,77 @@ draw_control (ply_label_plugin_control_t *label, pen.y += label->face->size->metrics.ascender; /* Go through each line */ - while (*cur_c) { + do { + bool should_stop; + const char *current_character; + uint8_t red, green, blue; + + FT_Int extraAdvance = 0, positiveBearingX = 0; + ply_rich_text_character_t *rich_text_character; + + red = 255 * label->red; + green = 255 * label->green; + blue = 255 * label->blue; + + if (label->rich_text != NULL) { + should_stop = !ply_rich_text_iterator_next (&rich_text_iterator, + &rich_text_character); + if (should_stop) + break; + + current_character = rich_text_character->bytes; + + look_up_rgb_color_from_terminal_color (label, + rich_text_character->style.foreground_color, + &red, + &green, + &blue); + } else { + size_t character_size; + + should_stop = !ply_utf8_string_iterator_next (&utf8_string_iterator, + ¤t_character, + &character_size); + } + + if (*current_character == '\n') + continue; + pen.x = label->area.x << 6; /* Start at start position (alignment) */ if (label->alignment == PLY_LABEL_ALIGN_CENTER) - pen.x += (label->area.width - width_of_line (label, cur_c)) << 5; + pen.x += (label->area.width - width_of_string (label, current_character)) << 5; else if (label->alignment == PLY_LABEL_ALIGN_RIGHT) - pen.x += (label->area.width - width_of_line (label, cur_c)) << 6; + pen.x += (label->area.width - width_of_string (label, current_character)) << 6; - while (*cur_c && *cur_c != '\n') { - FT_Int extraAdvance = 0, positiveBearingX = 0; + if (!load_character (label, ¤t_character)) + continue; - if (!load_character (label, &cur_c)) - continue; + /* We consider negative left bearing an increment in size, + * as we draw full character boxes and don't "go back" in + * this plugin. Positive left bearing is treated as usual. + * For definitions see + * https://freetype.org/freetype2/docs/glyphs/glyphs-3.html + */ + if (slot->bitmap_left < 0) + extraAdvance = -slot->bitmap_left; + else + positiveBearingX = slot->bitmap_left; - /* We consider negative left bearing an increment in size, - * as we draw full character boxes and don't "go back" in - * this plugin. Positive left bearing is treated as usual. - * For definitions see - * https://freetype.org/freetype2/docs/glyphs/glyphs-3.html - */ - if (slot->bitmap_left < 0) { - extraAdvance = -slot->bitmap_left; - } else { - positiveBearingX = slot->bitmap_left; - } - draw_bitmap (label, target, target_size, &slot->bitmap, - (pen.x >> 6) + positiveBearingX, - (pen.y >> 6) - slot->bitmap_top); + draw_bitmap (label, target, target_size, &slot->bitmap, + (pen.x >> 6) + positiveBearingX, + (pen.y >> 6) - slot->bitmap_top, + red, + green, + blue); - pen.x += slot->advance.x + extraAdvance; - pen.y += slot->advance.y; - } - /* skip newline character */ - if (*cur_c) - ++cur_c; + pen.x += slot->advance.x + extraAdvance; + pen.y += slot->advance.y; /* Next line */ pen.y += label->face->size->metrics.height; - } + } while (true); } static void @@ -421,17 +567,45 @@ set_width_for_control (ply_label_plugin_control_t *label, } } +static void +clear_text (ply_label_plugin_control_t *label) +{ + free (label->text); + label->text = NULL; + + if (label->rich_text != NULL) { + ply_rich_text_drop_reference (label->rich_text); + label->rich_text = NULL; + label->span.offset = 0; + label->span.range = 0; + } +} + static void set_text_for_control (ply_label_plugin_control_t *label, const char *text) { if (label->text != text) { - free (label->text); + clear_text (label); label->text = strdup (text); trigger_redraw (label, true); } } +static void +set_rich_text_for_control (ply_label_plugin_control_t *label, + ply_rich_text_t *rich_text, + ply_rich_text_span_t *span) +{ + clear_text (label); + + label->rich_text = rich_text; + ply_rich_text_take_reference (rich_text); + label->span = *span; + + trigger_redraw (label, true); +} + static void set_font_for_control (ply_label_plugin_control_t *label, const char *fontdesc) @@ -557,6 +731,7 @@ ply_label_plugin_get_interface (void) .draw_control = draw_control, .is_control_hidden = is_control_hidden, .set_text_for_control = set_text_for_control, + .set_rich_text_for_control = set_rich_text_for_control, .set_alignment_for_control = set_alignment_for_control, .set_width_for_control = set_width_for_control, .set_font_for_control = set_font_for_control, From 20b019a4893444dc15ceb0516f29a90ec5d5cc72 Mon Sep 17 00:00:00 2001 From: Ray Strode Date: Fri, 24 Nov 2023 11:02:33 -0500 Subject: [PATCH 16/22] ply-utils: Add function for getting current kmsg log levels We need to know two things: 1. What log level threshold to follow when showing kernel messages to the console 2. What log level to use if a message comes in without a log level This commit adds a new function `ply_get_kmsg_log_levels` that returns those two values. --- src/libply/ply-utils.c | 60 ++++++++++++++++++++++++++++++++++++++++++ src/libply/ply-utils.h | 3 +++ 2 files changed, 63 insertions(+) diff --git a/src/libply/ply-utils.c b/src/libply/ply-utils.c index 2de5f401..dd6294f6 100644 --- a/src/libply/ply-utils.c +++ b/src/libply/ply-utils.c @@ -997,6 +997,66 @@ int ply_guess_device_scale (uint32_t width, return get_device_scale (width, height, 0, 0, true); } +void +ply_get_kmsg_log_levels (int *current_log_level, + int *default_log_level) +{ + static double last_update_time = 0; + static int cached_current_log_level = 0; + static int cached_default_log_level = 0; + char log_levels[4096] = ""; + double current_time; + char *field, *fields; + int fd; + + current_time = ply_get_timestamp (); + + if ((current_time - last_update_time) < 1.0) { + *current_log_level = cached_current_log_level; + *default_log_level = cached_default_log_level; + return; + } + + ply_trace ("opening /proc/sys/kernel/printk"); + fd = open ("/proc/sys/kernel/printk", O_RDONLY); + + if (fd < 0) { + ply_trace ("couldn't open it: %m"); + return; + } + + ply_trace ("reading kmsg log levels"); + if (read (fd, log_levels, sizeof(log_levels) - 1) < 0) { + ply_trace ("couldn't read it: %m"); + close (fd); + return; + } + close (fd); + + field = strtok_r (log_levels, " \t", &fields); + + if (field == NULL) { + ply_trace ("Couldn't parse current log level: %m"); + return; + } + + *current_log_level = atoi (field); + + field = strtok_r (NULL, " \t", &fields); + + if (field == NULL) { + ply_trace ("Couldn't parse default log level: %m"); + return; + } + + *default_log_level = atoi (field); + + cached_current_log_level = *current_log_level; + cached_default_log_level = *default_log_level; + + last_update_time = current_time; +} + static const char * ply_get_kernel_command_line (void) { diff --git a/src/libply/ply-utils.h b/src/libply/ply-utils.h index 89bb37fd..8000632b 100644 --- a/src/libply/ply-utils.h +++ b/src/libply/ply-utils.h @@ -148,6 +148,9 @@ int ply_get_device_scale (uint32_t width, int ply_guess_device_scale (uint32_t width, uint32_t height); +void ply_get_kmsg_log_levels (int *current_log_level, + int *default_log_level); + const char *ply_kernel_command_line_get_string_after_prefix (const char *prefix); bool ply_kernel_command_line_has_argument (const char *argument); void ply_kernel_command_line_override (const char *command_line); From a3cd4f9c08627b6267b9c9c833ff9f304a77936e Mon Sep 17 00:00:00 2001 From: n3rdopolis Date: Sun, 19 Mar 2023 22:38:26 -0400 Subject: [PATCH 17/22] Introduce ply-kmsg-reader to read kernel log messages from /dev/kmsg --- src/libply-splash-core/meson.build | 2 + src/libply-splash-core/ply-kmsg-reader.c | 236 +++++++++++++++++++++++ src/libply-splash-core/ply-kmsg-reader.h | 64 ++++++ 3 files changed, 302 insertions(+) create mode 100644 src/libply-splash-core/ply-kmsg-reader.c create mode 100644 src/libply-splash-core/ply-kmsg-reader.h diff --git a/src/libply-splash-core/meson.build b/src/libply-splash-core/meson.build index 0a7a6c5b..cd22345c 100644 --- a/src/libply-splash-core/meson.build +++ b/src/libply-splash-core/meson.build @@ -2,6 +2,7 @@ libply_splash_core_sources = files( 'ply-boot-splash.c', 'ply-device-manager.c', 'ply-input-device.c', + 'ply-kmsg-reader.c', 'ply-keyboard.c', 'ply-pixel-buffer.c', 'ply-pixel-display.c', @@ -56,6 +57,7 @@ libply_splash_core_headers = files( 'ply-boot-splash.h', 'ply-device-manager.h', 'ply-input-device.h', + 'ply-kmsg-reader.h', 'ply-keyboard.h', 'ply-pixel-buffer.h', 'ply-pixel-display.h', diff --git a/src/libply-splash-core/ply-kmsg-reader.c b/src/libply-splash-core/ply-kmsg-reader.c new file mode 100644 index 00000000..439da415 --- /dev/null +++ b/src/libply-splash-core/ply-kmsg-reader.c @@ -0,0 +1,236 @@ +/* ply-kmsg-reader.c - kernel log message reader + * + * 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-list.h" +#include "ply-kmsg-reader.h" +#include "ply-terminal-emulator.h" +#include "ply-event-loop.h" +#include "ply-logger.h" +#include "ply-utils.h" +#include +#include +#include + + +#include +#include + +#ifndef LOG_LINE_MAX +#define LOG_LINE_MAX 8192 +#endif + +#define from_hex(c) (isdigit (c) ? c - '0' : tolower (c) - 'a' + 10) + +size_t +unhexmangle_to_buffer (const char *s, + char *buf, + size_t len) +{ + size_t sz = 0; + const char *buf0 = buf; + + if (!s) + return 0; + + while (*s && sz < len - 1) { + if (*s == '\\' && sz + 3 < len - 1 && s[1] == 'x' && + isxdigit (s[2]) && isxdigit (s[3])) { + *buf++ = from_hex (s[2]) << 4 | from_hex (s[3]); + s += 4; + sz += 4; + } else { + *buf++ = *s++; + sz++; + } + } + *buf = '\0'; + return buf - buf0 + 1; +} + +int +handle_kmsg_message (ply_kmsg_reader_t *kmsg_reader, + int fd) +{ + ssize_t bytes_read; + char read_buffer[LOG_LINE_MAX] = ""; + int current_log_level = LOG_ERR, default_log_level = LOG_WARNING; + + ply_get_kmsg_log_levels (¤t_log_level, + &default_log_level); + + bytes_read = read (fd, read_buffer, sizeof(read_buffer) - 1); + if (bytes_read > 0) { + ply_terminal_style_attributes_t bold_enabled = PLY_TERMINAL_ATTRIBUTE_NO_BOLD; + ply_terminal_color_t color = PLY_TERMINAL_ATTRIBUTE_FOREGROUND_COLOR_OFFSET + PLY_TERMINAL_COLOR_DEFAULT; + char *fields, *field_prefix, *field_sequence, *field_timestamp, *message, *message_substr, *msgptr, *saveptr, *format_begin, *new_message; + int prefix, priority, facility; + uint64_t sequence; + unsigned long long timestamp; + kmsg_message_t *kmsg_message; + + fields = strtok_r (read_buffer, ";", &message); + + /* Messages end in \n, any following lines are machine readable. Actual multiline messages are expanded with unhexmangle_to_buffer */ + msgptr = strchr (message, '\n'); + if (*msgptr && *msgptr != '\n') + msgptr--; + + unhexmangle_to_buffer (message, (char *) message, msgptr - message + 1); + + field_prefix = strtok_r (fields, ",", &fields); + field_sequence = strtok_r (fields, ",", &fields); + field_timestamp = strtok_r (fields, ",", &fields); + + prefix = atoi (field_prefix); + sequence = strtoull (field_sequence, NULL, 0); + timestamp = strtoull (field_timestamp, NULL, 0); + + if (prefix > 0) { + priority = LOG_PRI (prefix); + facility = LOG_FAC (prefix); + } else { + priority = default_log_level; + facility = LOG_USER; + } + + if (priority > current_log_level) + return 0; + + if (priority < LOG_ALERT) + bold_enabled = PLY_TERMINAL_ATTRIBUTE_BOLD; + + switch (priority) { + case LOG_EMERG: + case LOG_ALERT: + case LOG_CRIT: + case LOG_ERR: + color = PLY_TERMINAL_ATTRIBUTE_FOREGROUND_COLOR_OFFSET + PLY_TERMINAL_COLOR_RED; + break; + case LOG_WARNING: + color = PLY_TERMINAL_ATTRIBUTE_FOREGROUND_COLOR_OFFSET + PLY_TERMINAL_COLOR_BROWN; + break; + case LOG_NOTICE: + color = PLY_TERMINAL_ATTRIBUTE_FOREGROUND_COLOR_OFFSET + PLY_TERMINAL_COLOR_GREEN; + break; + } + asprintf (&format_begin, "\033[0;%i;%im", bold_enabled, color); + + message_substr = strtok_r (message, "\n", &saveptr); + while (message_substr != NULL) { + kmsg_message = calloc (1, sizeof(kmsg_message_t)); + + kmsg_message->priority = priority; + kmsg_message->facility = facility; + kmsg_message->sequence = sequence; + kmsg_message->timestamp = timestamp; + + asprintf (&new_message, "%s%s%s", format_begin, message_substr, "\033[0m"); + kmsg_message->message = strndup (new_message, strlen (new_message)); + + ply_trigger_pull (kmsg_reader->kmsg_trigger, kmsg_message); + ply_list_append_data (kmsg_reader->kmsg_messages, kmsg_message); + free (new_message); + + message_substr = strtok_r (NULL, "\n", &saveptr); + } + free (format_begin); + + return 0; + } else { + ply_event_loop_stop_watching_fd (ply_event_loop_get_default (), kmsg_reader->fd_watch); + close (kmsg_reader->kmsg_fd); + return -1; + } +} + +ply_kmsg_reader_t * +ply_kmsg_reader_new (void) +{ + ply_kmsg_reader_t *kmsg_reader = calloc (1, sizeof(ply_kmsg_reader_t)); + kmsg_reader->kmsg_trigger = ply_trigger_new (NULL); + kmsg_reader->kmsg_messages = ply_list_new (); + + return kmsg_reader; +} + +void +ply_kmsg_message_free (kmsg_message_t *kmsg_message) +{ + if (kmsg_message == NULL) + return; + + free (kmsg_message->message); + free (kmsg_message); +} + +void +ply_kmsg_reader_free (ply_kmsg_reader_t *kmsg_reader) +{ + ply_list_node_t *node; + kmsg_message_t *kmsg_message; + + if (kmsg_reader == NULL) + return; + + ply_list_foreach (kmsg_reader->kmsg_messages, node) { + kmsg_message = ply_list_node_get_data (node); + ply_kmsg_message_free (kmsg_message); + } + + ply_trigger_free (kmsg_reader->kmsg_trigger); + free (kmsg_reader); +} + +void +ply_kmsg_reader_start (ply_kmsg_reader_t *kmsg_reader) +{ + kmsg_reader->kmsg_fd = open ("/dev/kmsg", O_RDWR | O_NONBLOCK); + if (kmsg_reader->kmsg_fd < 0) + return; + + kmsg_reader->fd_watch = ply_event_loop_watch_fd (ply_event_loop_get_default (), kmsg_reader->kmsg_fd, PLY_EVENT_LOOP_FD_STATUS_HAS_DATA, + (ply_event_handler_t) handle_kmsg_message, + NULL, + kmsg_reader); +} + +void +ply_kmsg_reader_stop (ply_kmsg_reader_t *kmsg_reader) +{ + if (kmsg_reader->kmsg_fd < 0) + return; + + ply_event_loop_stop_watching_fd (ply_event_loop_get_default (), + kmsg_reader->fd_watch); + kmsg_reader->fd_watch = NULL; + + close (kmsg_reader->kmsg_fd); + kmsg_reader->kmsg_fd = -1; +} + +void +ply_kmsg_reader_watch_for_messages (ply_kmsg_reader_t *kmsg_reader, + ply_kmsg_reader_message_handler_t message_handler, + void *user_data) +{ + ply_trigger_add_handler (kmsg_reader->kmsg_trigger, + (ply_trigger_handler_t) + message_handler, + user_data); +} diff --git a/src/libply-splash-core/ply-kmsg-reader.h b/src/libply-splash-core/ply-kmsg-reader.h new file mode 100644 index 00000000..cec3deb7 --- /dev/null +++ b/src/libply-splash-core/ply-kmsg-reader.h @@ -0,0 +1,64 @@ +/* ply-kmsg-reader.h - kernel log message reader + * + * 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_KMSG_READER_H +#define PLY_KMSG_READER_H + +#include "ply-list.h" +#include "ply-boot-splash.h" +#include + +typedef struct _ply_kmsg_reader ply_kmsg_reader_t; + +struct dmesg_name +{ + const char *name; +}; + +typedef struct +{ + int priority; + int facility; + unsigned long sequence; + unsigned long long timestamp; + char *message; +} kmsg_message_t; + +struct _ply_kmsg_reader +{ + int kmsg_fd; + ply_fd_watch_t *fd_watch; + ply_trigger_t *kmsg_trigger; + ply_list_t *kmsg_messages; +}; + +typedef void (* ply_kmsg_reader_message_handler_t) (void *, + kmsg_message_t *); + +#ifndef PLY_HIDE_FUNCTION_DECLARATIONS +ply_kmsg_reader_t *ply_kmsg_reader_new (void); +void ply_kmsg_reader_free (ply_kmsg_reader_t *kmsg_reader); +void ply_kmsg_reader_start (ply_kmsg_reader_t *kmsg_reader); +void ply_kmsg_reader_stop (ply_kmsg_reader_t *kmsg_reader); +void ply_kmsg_reader_watch_for_messages (ply_kmsg_reader_t *kmsg_reader, + ply_kmsg_reader_message_handler_t message_handler, + void *user_data); + +#endif //PLY_HIDE_FUNCTION_DECLARATIONS + +#endif //PLY_KMSG_READER_H From 21cdb5541a89528b051059adc61fd0fe0ef5d14b Mon Sep 17 00:00:00 2001 From: n3rdopolis Date: Sun, 19 Mar 2023 22:40:18 -0400 Subject: [PATCH 18/22] main: start ply-kmsg-reader, and fix handling ESC key when there are no VTs --- src/main.c | 43 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/src/main.c b/src/main.c index ae029bf0..86a24aa9 100644 --- a/src/main.c +++ b/src/main.c @@ -55,6 +55,7 @@ #include "ply-trigger.h" #include "ply-utils.h" #include "ply-progress.h" +#include "ply-kmsg-reader.h" #define BOOT_DURATION_FILE PLYMOUTH_TIME_DIRECTORY "/boot-duration" #define SHUTDOWN_DURATION_FILE PLYMOUTH_TIME_DIRECTORY "/shutdown-duration" @@ -79,6 +80,7 @@ typedef struct ply_event_loop_t *loop; ply_boot_server_t *boot_server; ply_boot_splash_t *boot_splash; + ply_kmsg_reader_t *kmsg_reader; ply_terminal_session_t *session; ply_buffer_t *boot_buffer; ply_progress_t *progress; @@ -93,6 +95,7 @@ typedef struct ply_trigger_t *deactivate_trigger; ply_trigger_t *quit_trigger; + ply_trigger_t *kmsg_trigger; double start_time; double splash_delay; @@ -157,6 +160,8 @@ static void on_backspace (state_t *state); static void on_quit (state_t *state, bool retain_splash, ply_trigger_t *quit_trigger); +static void on_new_kmsg_message (state_t *state, + kmsg_message_t *kmsg_message); static bool sh_is_init (state_t *state); static void cancel_pending_delayed_show (state_t *state); static void prepare_logging (state_t *state); @@ -1455,6 +1460,22 @@ on_quit (state_t *state, } } +void +on_new_kmsg_message (state_t *state, + kmsg_message_t *kmsg_message) +{ + long size = strlen (kmsg_message->message) + 1; + char output[size]; + + strcpy (output, kmsg_message->message); + strcat (output, "\n"); + + ply_buffer_append_bytes (state->boot_buffer, output, size); + + if (state->boot_splash != NULL) + ply_boot_splash_update_output (state->boot_splash, output, size); +} + static bool on_has_active_vt (state_t *state) { @@ -1578,15 +1599,16 @@ static void on_escape_pressed (state_t *state) { ply_trace ("escape key pressed"); + bool has_vt_consoles = true; if (state->local_console_terminal != NULL) { if (!ply_terminal_is_vt (state->local_console_terminal)) - return; + has_vt_consoles = false; } else { - return; + has_vt_consoles = false; } - if (validate_input (state, "", "\e")) + if (validate_input (state, "", "\e") && has_vt_consoles == true) toggle_between_splash_and_details (state); } @@ -1891,6 +1913,18 @@ attach_to_running_session (state_t *state) return false; } + if (state->kmsg_reader == NULL) { + ply_trace ("Creating new kmsg reader"); + state->kmsg_reader = ply_kmsg_reader_new (); + + ply_kmsg_reader_watch_for_messages (state->kmsg_reader, + (ply_kmsg_reader_message_handler_t) + on_new_kmsg_message, + state); + } + + ply_kmsg_reader_start (state->kmsg_reader); + #ifdef PLY_ENABLE_SYSTEMD_INTEGRATION tell_systemd_to_print_details (state); #endif @@ -1915,6 +1949,9 @@ detach_from_running_session (state_t *state) tell_systemd_to_stop_printing_details (state); #endif + ply_trace ("stopping kmsg reader"); + ply_kmsg_reader_stop (state->kmsg_reader); + ply_trace ("detaching from terminal session"); ply_terminal_session_detach (state->session); state->is_redirected = false; From f995183bd820b779fce2fcb0b89c90f86dfbdff7 Mon Sep 17 00:00:00 2001 From: n3rdopolis Date: Sun, 19 Mar 2023 22:41:32 -0400 Subject: [PATCH 19/22] libply-splash-graphics: Introduce new ply-console-viewer control --- src/libply-splash-graphics/meson.build | 2 + .../ply-console-viewer.c | 324 ++++++++++++++++++ .../ply-console-viewer.h | 55 +++ 3 files changed, 381 insertions(+) create mode 100644 src/libply-splash-graphics/ply-console-viewer.c create mode 100644 src/libply-splash-graphics/ply-console-viewer.h diff --git a/src/libply-splash-graphics/meson.build b/src/libply-splash-graphics/meson.build index 32fad963..22d1092e 100644 --- a/src/libply-splash-graphics/meson.build +++ b/src/libply-splash-graphics/meson.build @@ -1,6 +1,7 @@ libply_splash_graphics_sources = files( 'ply-animation.c', 'ply-capslock-icon.c', + 'ply-console-viewer.c', 'ply-entry.c', 'ply-image.c', 'ply-keymap-icon.c', @@ -40,6 +41,7 @@ libply_splash_graphics_dep = declare_dependency( libply_splash_graphics_headers = files( 'ply-animation.h', 'ply-capslock-icon.h', + 'ply-console-viewer.h', 'ply-entry.h', 'ply-image.h', 'ply-keymap-icon.h', diff --git a/src/libply-splash-graphics/ply-console-viewer.c b/src/libply-splash-graphics/ply-console-viewer.c new file mode 100644 index 00000000..274e389b --- /dev/null +++ b/src/libply-splash-graphics/ply-console-viewer.c @@ -0,0 +1,324 @@ +/* ply-console-view.c - console message viewer + * + * 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 +#include + +#include "ply-label.h" +#include "ply-array.h" +#include "ply-pixel-display.h" +#include "ply-image.h" +#include "ply-kmsg-reader.h" +#include "ply-console-viewer.h" +#include "ply-rich-text.h" + +#define TERMINAL_OUTPUT_UPDATE_INTERVAL (1.0 / 60) + +struct _ply_console_viewer +{ + ply_event_loop_t *loop; + + ply_terminal_emulator_t *terminal_emulator; + + ply_pixel_display_t *display; + ply_rectangle_t area; + + ply_list_t *message_labels; + + uint32_t is_hidden : 1; + uint32_t output_queued : 1; + uint32_t needs_redraw : 1; + + char *font; + long font_height; + long font_width; + int line_max_chars; + + uint32_t text_color; +}; + +static void update_console_messages (ply_console_viewer_t *console_viewer); +static void on_terminal_emulator_output (ply_console_viewer_t *console_viewer); + +bool ply_console_viewer_preferred (void) +{ + return !ply_kernel_command_line_has_argument ("plymouth.prefer-fbcon"); +} + +ply_console_viewer_t * +ply_console_viewer_new (ply_pixel_display_t *display, + const char *font) +{ + ply_console_viewer_t *console_viewer; + ply_label_t *console_message_label, *measure_label; + size_t line_count; + + console_viewer = calloc (1, sizeof(struct _ply_console_viewer)); + + console_viewer->message_labels = ply_list_new (); + console_viewer->is_hidden = true; + + console_viewer->font = strdup (font); + + measure_label = ply_label_new (); + ply_label_set_text (measure_label, " "); + ply_label_set_font (measure_label, console_viewer->font); + + console_viewer->text_color = PLY_CONSOLE_VIEWER_LOG_TEXT_COLOR; + + console_viewer->font_height = ply_label_get_height (measure_label); + console_viewer->font_width = ply_label_get_width (measure_label); + /* Allow the label to be the size of how many characters can fit in the width of the screeen, minus one for larger fonts that have some size overhead */ + console_viewer->line_max_chars = ply_pixel_display_get_width (display) / console_viewer->font_width - 1; + line_count = ply_pixel_display_get_height (display) / console_viewer->font_height; + + /* Display at least one line */ + if (line_count < 0) + line_count = 1; + + ply_label_free (measure_label); + + for (size_t label_index = 0; label_index < line_count; label_index++) { + console_message_label = ply_label_new (); + ply_label_set_font (console_message_label, console_viewer->font); + ply_list_append_data (console_viewer->message_labels, console_message_label); + } + + console_viewer->terminal_emulator = ply_terminal_emulator_new (line_count); + + ply_terminal_emulator_watch_for_output (console_viewer->terminal_emulator, + (ply_terminal_emulator_output_handler_t) + on_terminal_emulator_output, + console_viewer); + + return console_viewer; +} + +void +ply_console_viewer_free (ply_console_viewer_t *console_viewer) +{ + ply_list_node_t *node; + ply_label_t *console_message_label; + + ply_list_foreach (console_viewer->message_labels, node) { + console_message_label = ply_list_node_get_data (node); + ply_label_free (console_message_label); + } + ply_list_free (console_viewer->message_labels); + ply_terminal_emulator_free (console_viewer->terminal_emulator); + + free (console_viewer->font); + free (console_viewer); +} + +static void +update_console_messages (ply_console_viewer_t *console_viewer) +{ + ply_list_node_t *node; + ply_label_t *console_message_label; + size_t message_number; + ssize_t characters_left; + ply_rich_text_span_t span; + + console_viewer->output_queued = false; + + if (console_viewer->terminal_emulator == NULL) + return; + + if (console_viewer->display == NULL) + return; + + message_number = ply_terminal_emulator_get_line_count (console_viewer->terminal_emulator) - 1; + + if (message_number < 0) + return; + + ply_pixel_display_pause_updates (console_viewer->display); + node = ply_list_get_first_node (console_viewer->message_labels); + while (node != NULL) { + ply_rich_text_t *line = NULL; + + characters_left = 0; + if (message_number >= 0) { + line = ply_terminal_emulator_get_nth_line (console_viewer->terminal_emulator, message_number); + + if (line != NULL) { + ply_rich_text_take_reference (line); + characters_left = ply_rich_text_get_length (line); + } + } + + span.offset = characters_left; + while (characters_left >= 0) { + console_message_label = ply_list_node_get_data (node); + + span.range = span.offset % console_viewer->line_max_chars; + if (span.range == 0) + span.range = console_viewer->line_max_chars; + + characters_left = span.offset - span.range - 1; + + if (span.offset - span.range >= 0) + span.offset -= span.range; + else + span.offset = 0; + + if (line != NULL) { + ply_label_set_rich_text (console_message_label, line, &span); + } else { + ply_label_set_text (console_message_label, ""); + } + + node = ply_list_get_next_node (console_viewer->message_labels, node); + if (node == NULL) + break; + } + + if (line != NULL) + ply_rich_text_drop_reference (line); + + if (message_number <= 0) + break; + + message_number--; + } + console_viewer->needs_redraw = true; + ply_pixel_display_draw_area (console_viewer->display, 0, 0, + ply_pixel_display_get_width (console_viewer->display), + ply_pixel_display_get_height (console_viewer->display)); + ply_pixel_display_unpause_updates (console_viewer->display); +} + +void +ply_console_viewer_show (ply_console_viewer_t *console_viewer, + ply_pixel_display_t *display) +{ + uint32_t label_color; + size_t label_index; + ply_list_node_t *node; + + assert (console_viewer != NULL); + + console_viewer->display = display; + console_viewer->is_hidden = false; + + label_color = console_viewer->text_color; + + label_index = 0; + ply_list_foreach (console_viewer->message_labels, node) { + ply_label_t *console_message_label; + console_message_label = ply_list_node_get_data (node); + ply_label_show (console_message_label, console_viewer->display, + console_viewer->font_width / 2, + (ply_pixel_display_get_height (console_viewer->display) - (console_viewer->font_height * label_index) - console_viewer->font_height)); + ply_label_set_hex_color (console_message_label, label_color); + label_index++; + } + + update_console_messages (console_viewer); +} + +void +ply_console_viewer_draw_area (ply_console_viewer_t *console_viewer, + ply_pixel_buffer_t *buffer, + long x, + long y, + unsigned long width, + unsigned long height) +{ + ply_list_node_t *node; + size_t label_index; + ply_label_t *console_message_label; + + if (!console_viewer->needs_redraw) + return; + + if (console_viewer->is_hidden) + return; + + label_index = 0; + ply_list_foreach (console_viewer->message_labels, node) { + console_message_label = ply_list_node_get_data (node); + ply_label_draw_area (console_message_label, buffer, + MAX (x, console_viewer->font_width / 2), + MAX (y, (ply_pixel_display_get_height (console_viewer->display) - (console_viewer->font_height * label_index) - console_viewer->font_height)), + MIN (ply_label_get_width (console_message_label), width), + MIN (height, console_viewer->font_height)); + label_index++; + } + + console_viewer->needs_redraw = false; +} + +void +ply_console_viewer_hide (ply_console_viewer_t *console_viewer) +{ + ply_list_node_t *node; + ply_label_t *console_message_label; + + if (console_viewer->is_hidden) + return; + + console_viewer->is_hidden = true; + + ply_list_foreach (console_viewer->message_labels, node) { + console_message_label = ply_list_node_get_data (node); + ply_label_hide (console_message_label); + } + + console_viewer->display = NULL; +} + +static void +on_terminal_emulator_output (ply_console_viewer_t *console_viewer) +{ + if (console_viewer->output_queued) + return; + + if (console_viewer->is_hidden) + return; + + ply_event_loop_watch_for_timeout (ply_event_loop_get_default (), + TERMINAL_OUTPUT_UPDATE_INTERVAL, + (ply_event_loop_timeout_handler_t) + update_console_messages, console_viewer); + console_viewer->output_queued = true; +} + +void +ply_console_viewer_set_text_color (ply_console_viewer_t *console_viewer, + uint32_t hex_color) +{ + console_viewer->text_color = hex_color; +} + +void +ply_console_viewer_convert_boot_buffer (ply_console_viewer_t *console_viewer, + ply_buffer_t *boot_buffer) +{ + ply_terminal_emulator_convert_boot_buffer (console_viewer->terminal_emulator, boot_buffer); +} + +void +ply_console_viewer_parse_lines (ply_console_viewer_t *console_viewer, + const char *text, + size_t size) +{ + ply_terminal_emulator_parse_lines (console_viewer->terminal_emulator, text, size); +} diff --git a/src/libply-splash-graphics/ply-console-viewer.h b/src/libply-splash-graphics/ply-console-viewer.h new file mode 100644 index 00000000..88b93b05 --- /dev/null +++ b/src/libply-splash-graphics/ply-console-viewer.h @@ -0,0 +1,55 @@ +/* ply-console-viewer.h - console message viewer + * + * 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_CONSOLE_VIEWER_H +#define PLY_CONSOLE_VIEWER_H + +#include "ply-key-file.h" +#include "ply-list.h" +#include "ply-event-loop.h" +#include "ply-pixel-display.h" +#include "ply-terminal-emulator.h" + +typedef struct _ply_console_viewer ply_console_viewer_t; + +#define PLY_CONSOLE_VIEWER_LOG_TEXT_COLOR 0xffffffff /* white */ + +#ifndef PLY_HIDE_FUNCTION_DECLARATIONS +bool ply_console_viewer_preferred (void); +ply_console_viewer_t *ply_console_viewer_new (ply_pixel_display_t *display, + const char *fontdesc); +void ply_console_viewer_free (ply_console_viewer_t *console_viewer); +void ply_console_viewer_show (ply_console_viewer_t *console_viewer, + ply_pixel_display_t *display); +void ply_console_viewer_draw_area (ply_console_viewer_t *console_viewer, + ply_pixel_buffer_t *buffer, + long x, + long y, + unsigned long width, + unsigned long height); +void ply_console_viewer_hide (ply_console_viewer_t *console_viewer); +void ply_console_viewer_set_text_color (ply_console_viewer_t *console_viewer, + uint32_t hex_color); +void ply_console_viewer_convert_boot_buffer (ply_console_viewer_t *console_viewer, + ply_buffer_t *boot_buffer); +void ply_console_viewer_parse_lines (ply_console_viewer_t *console_viewer, + const char *text, + size_t size); +#endif //PLY_HIDE_FUNCTION_DECLARATIONS + +#endif //PLY_CONSOLE_VIEWER_H From 32b2bffd9d6b3d34744355db2b2ab5d0ecd96201 Mon Sep 17 00:00:00 2001 From: n3rdopolis Date: Sun, 19 Mar 2023 22:41:51 -0400 Subject: [PATCH 20/22] two-step: Get the messages from the console and ply-kmsg-reader, and use ply-terminal-emulator and ply-console-viewer to the display messages --- src/plugins/splash/two-step/plugin.c | 183 ++++++++++++++++++++++++++- 1 file changed, 182 insertions(+), 1 deletion(-) diff --git a/src/plugins/splash/two-step/plugin.c b/src/plugins/splash/two-step/plugin.c index 516a66c4..78ca7adf 100644 --- a/src/plugins/splash/two-step/plugin.c +++ b/src/plugins/splash/two-step/plugin.c @@ -61,6 +61,7 @@ #include "ply-progress-animation.h" #include "ply-throbber.h" #include "ply-progress-bar.h" +#include "ply-console-viewer.h" #include @@ -113,6 +114,8 @@ typedef struct ply_trigger_t *end_trigger; ply_pixel_buffer_t *background_buffer; int animation_bottom; + + ply_console_viewer_t *console_viewer; } view_t; typedef struct @@ -192,6 +195,12 @@ struct _ply_boot_splash_plugin uint32_t background_image_is_scaled : 1; uint32_t dialog_clears_firmware_background : 1; uint32_t message_below_animation : 1; + + char *monospace_font; + uint32_t plugin_console_messages_updating : 1; + uint32_t should_show_console_messages : 1; + ply_buffer_t *boot_buffer; + uint32_t console_text_color; }; ply_boot_splash_plugin_interface_t *ply_boot_splash_plugin_get_interface (void); @@ -204,6 +213,16 @@ static void become_idle (ply_boot_splash_plugin_t *plugin, ply_trigger_t *idle_trigger); static void view_show_message (view_t *view, const char *message); +static bool validate_input (ply_boot_splash_plugin_t *plugin, + const char *entry_text, + const char *add_text); +static void on_boot_output (ply_boot_splash_plugin_t *plugin, + const char *output, + size_t size); +static void toggle_console_messages (ply_boot_splash_plugin_t *plugin); +static void display_console_messages (ply_boot_splash_plugin_t *plugin); +static void hide_console_messages (ply_boot_splash_plugin_t *plugin); +static void unhide_console_messages (ply_boot_splash_plugin_t *plugin); static view_t * view_new (ply_boot_splash_plugin_t *plugin, @@ -244,6 +263,16 @@ view_new (ply_boot_splash_plugin_t *plugin, view->subtitle_label = ply_label_new (); ply_label_set_font (view->subtitle_label, plugin->font); + if (ply_console_viewer_preferred ()) { + view->console_viewer = ply_console_viewer_new (view->display, plugin->monospace_font); + ply_console_viewer_set_text_color (view->console_viewer, plugin->console_text_color); + + if (plugin->boot_buffer) + ply_console_viewer_convert_boot_buffer (view->console_viewer, plugin->boot_buffer); + } else { + view->console_viewer = NULL; + } + return view; } @@ -262,6 +291,9 @@ view_free (view_t *view) ply_label_free (view->title_label); ply_label_free (view->subtitle_label); + if (view->console_viewer) + ply_console_viewer_free (view->console_viewer); + if (view->background_buffer != NULL) ply_pixel_buffer_free (view->background_buffer); @@ -1191,6 +1223,21 @@ create_plugin (ply_key_file_t *key_file) } free (transition); + plugin->plugin_console_messages_updating = false; + plugin->should_show_console_messages = false; + + /* Likely only able to set the font if the font is in the initrd */ + plugin->monospace_font = ply_key_file_get_value (key_file, "two-step", "MonospaceFont"); + + if (plugin->monospace_font == NULL) + plugin->monospace_font = strdup ("monospace 10"); + + + plugin->console_text_color = + ply_key_file_get_long (key_file, "two-step", + "ConsoleLogTextColor", + PLY_CONSOLE_VIEWER_LOG_TEXT_COLOR); + plugin->transition_duration = ply_key_file_get_double (key_file, "two-step", "TransitionDuration", 0.0); @@ -1314,6 +1361,7 @@ destroy_plugin (ply_boot_splash_plugin_t *plugin) ply_event_loop_stop_watching_for_exit (plugin->loop, (ply_event_loop_exit_handler_t) detach_from_event_loop, plugin); + detach_from_event_loop (plugin); } @@ -1347,6 +1395,7 @@ destroy_plugin (ply_boot_splash_plugin_t *plugin) free (plugin->font); free (plugin->title_font); + free (plugin->monospace_font); free (plugin->animation_dir); free_views (plugin); free (plugin); @@ -1514,6 +1563,9 @@ draw_background (view_t *view, using_fw_background && plugin->dialog_clears_firmware_background) use_black_background = true; + if (plugin->should_show_console_messages) + use_black_background = true; + if (use_black_background) ply_pixel_buffer_fill_with_hex_color (pixel_buffer, &area, 0); else if (view->background_buffer != NULL) @@ -1526,6 +1578,9 @@ draw_background (view_t *view, ply_pixel_buffer_fill_with_hex_color (pixel_buffer, &area, plugin->background_start_color); + if (plugin->should_show_console_messages) + return; + if (plugin->watermark_image != NULL) { uint32_t *data; @@ -1648,6 +1703,10 @@ on_draw (view_t *view, ply_label_draw_area (view->message_label, pixel_buffer, x, y, width, height); + + if (!plugin->plugin_console_messages_updating && view->console_viewer) { + ply_console_viewer_draw_area (view->console_viewer, pixel_buffer, x, y, width, height); + } } static void @@ -1707,11 +1766,30 @@ show_splash_screen (ply_boot_splash_plugin_t *plugin, ply_buffer_t *boot_buffer, ply_boot_splash_mode_t mode) { + ply_list_node_t *node; + assert (plugin != NULL); plugin->loop = loop; plugin->mode = mode; + if (boot_buffer && ply_console_viewer_preferred ()) { + plugin->boot_buffer = boot_buffer; + + node = ply_list_get_first_node (plugin->views); + while (node != NULL) { + view_t *view; + ply_list_node_t *next_node; + + view = ply_list_node_get_data (node); + next_node = ply_list_get_next_node (plugin->views, node); + + ply_console_viewer_convert_boot_buffer (view->console_viewer, plugin->boot_buffer); + + node = next_node; + } + } + ply_trace ("loading lock image"); if (!ply_image_load (plugin->lock_image)) return false; @@ -2053,7 +2131,13 @@ display_normal (ply_boot_splash_plugin_t *plugin) hide_prompt (plugin); plugin->state = PLY_BOOT_SPLASH_DISPLAY_NORMAL; - start_progress_animation (plugin); + + if (!plugin->should_show_console_messages) { + start_progress_animation (plugin); + } else { + unhide_console_messages (plugin); + } + redraw_views (plugin); unpause_views (plugin); } @@ -2095,6 +2179,101 @@ display_message (ply_boot_splash_plugin_t *plugin, show_message (plugin, message); } +static bool +validate_input (ply_boot_splash_plugin_t *plugin, + const char *entry_text, + const char *add_text) +{ + if (!ply_console_viewer_preferred ()) + return true; + + if (strcmp (add_text, "\e") == 0) { + toggle_console_messages (plugin); + return false; + } + + return true; +} + +static void +toggle_console_messages (ply_boot_splash_plugin_t *plugin) +{ + if (plugin->should_show_console_messages) { + plugin->should_show_console_messages = false; + hide_console_messages (plugin); + } else { + unhide_console_messages (plugin); + } +} + +static void +display_console_messages (ply_boot_splash_plugin_t *plugin) +{ + ply_list_node_t *node; + view_t *view; + + pause_views (plugin); + + plugin->plugin_console_messages_updating = true; + node = ply_list_get_first_node (plugin->views); + while (node != NULL) { + view = ply_list_node_get_data (node); + ply_console_viewer_show (view->console_viewer, view->display); + node = ply_list_get_next_node (plugin->views, node); + } + plugin->plugin_console_messages_updating = false; + + redraw_views (plugin); + unpause_views (plugin); +} + +static void +unhide_console_messages (ply_boot_splash_plugin_t *plugin) +{ + plugin->should_show_console_messages = true; + stop_animation (plugin); + display_console_messages (plugin); +} + +static void +hide_console_messages (ply_boot_splash_plugin_t *plugin) +{ + ply_list_node_t *node; + view_t *view; + + plugin->should_show_console_messages = false; + + plugin->plugin_console_messages_updating = true; + node = ply_list_get_first_node (plugin->views); + while (node != NULL) { + view = ply_list_node_get_data (node); + ply_console_viewer_hide (view->console_viewer); + node = ply_list_get_next_node (plugin->views, node); + } + plugin->plugin_console_messages_updating = false; + if (plugin->state == PLY_BOOT_SPLASH_DISPLAY_NORMAL) + start_progress_animation (plugin); +} + +static void +on_boot_output (ply_boot_splash_plugin_t *plugin, + const char *output, + size_t size) +{ + ply_list_node_t *node; + view_t *view; + + if (!ply_console_viewer_preferred ()) + return; + + node = ply_list_get_first_node (plugin->views); + while (node != NULL) { + view = ply_list_node_get_data (node); + ply_console_viewer_parse_lines (view->console_viewer, output, size); + node = ply_list_get_next_node (plugin->views, node); + } +} + ply_boot_splash_plugin_interface_t * ply_boot_splash_plugin_get_interface (void) { @@ -2115,6 +2294,8 @@ ply_boot_splash_plugin_get_interface (void) .display_question = display_question, .display_message = display_message, .system_update = system_update, + .on_boot_output = on_boot_output, + .validate_input = validate_input, }; return &plugin_interface; From d80237fad58454ee9f4a50b18f29876affe72da7 Mon Sep 17 00:00:00 2001 From: n3rdopolis Date: Sun, 19 Mar 2023 22:42:14 -0400 Subject: [PATCH 21/22] fade-throbber: Get the messages from the console and ply-kmsg-reader, and use ply-terminal-emulator and ply-console-viewer to the display messages --- src/plugins/splash/fade-throbber/plugin.c | 176 +++++++++++++++++++++- 1 file changed, 174 insertions(+), 2 deletions(-) diff --git a/src/plugins/splash/fade-throbber/plugin.c b/src/plugins/splash/fade-throbber/plugin.c index 64aedbdd..18ad72e9 100644 --- a/src/plugins/splash/fade-throbber/plugin.c +++ b/src/plugins/splash/fade-throbber/plugin.c @@ -52,6 +52,7 @@ #include "ply-pixel-display.h" #include "ply-trigger.h" #include "ply-utils.h" +#include "ply-console-viewer.h" #include @@ -59,7 +60,6 @@ #define FRAMES_PER_SECOND 30 #endif - typedef enum { PLY_BOOT_SPLASH_DISPLAY_NORMAL, @@ -86,6 +86,8 @@ typedef struct ply_label_t *message_label; ply_rectangle_t lock_area; double logo_opacity; + + ply_console_viewer_t *console_viewer; } view_t; struct _ply_boot_splash_plugin @@ -105,9 +107,25 @@ struct _ply_boot_splash_plugin uint32_t is_animating : 1; uint32_t is_visible : 1; + + char *monospace_font; + uint32_t plugin_console_messages_updating : 1; + uint32_t should_show_console_messages : 1; + ply_buffer_t *boot_buffer; + uint32_t console_text_color; }; ply_boot_splash_plugin_interface_t *ply_boot_splash_plugin_get_interface (void); +static bool validate_input (ply_boot_splash_plugin_t *plugin, + const char *entry_text, + const char *add_text); +static void on_boot_output (ply_boot_splash_plugin_t *plugin, + const char *output, + size_t size); +static void toggle_console_messages (ply_boot_splash_plugin_t *plugin); +static void display_console_messages (ply_boot_splash_plugin_t *plugin); +static void hide_console_messages (ply_boot_splash_plugin_t *plugin); +static void unhide_console_messages (ply_boot_splash_plugin_t *plugin); static void view_show_prompt (view_t *view, @@ -183,6 +201,20 @@ create_plugin (ply_key_file_t *key_file) plugin->lock_image = ply_image_new (image_path); free (image_path); + plugin->plugin_console_messages_updating = false; + plugin->should_show_console_messages = false; + + /* Likely only able to set the font if the font is in the initrd */ + plugin->monospace_font = ply_key_file_get_value (key_file, "two-step", "MonospaceFont"); + + if (plugin->monospace_font == NULL) + plugin->monospace_font = strdup ("monospace 10"); + + plugin->console_text_color = + ply_key_file_get_long (key_file, "two-step", + "ConsoleLogTextColor", + PLY_CONSOLE_VIEWER_LOG_TEXT_COLOR); + plugin->image_dir = image_dir; plugin->state = PLY_BOOT_SPLASH_DISPLAY_NORMAL; @@ -255,6 +287,16 @@ view_new (ply_boot_splash_plugin_t *plugin, view->message_label = ply_label_new (); + if (ply_console_viewer_preferred ()) { + view->console_viewer = ply_console_viewer_new (view->display, plugin->monospace_font); + ply_console_viewer_set_text_color (view->console_viewer, plugin->console_text_color); + + if (plugin->boot_buffer) + ply_console_viewer_convert_boot_buffer (view->console_viewer, plugin->boot_buffer); + } else { + view->console_viewer = NULL; + } + return view; } @@ -265,6 +307,9 @@ view_free (view_t *view) ply_label_free (view->message_label); free_stars (view); + if (view->console_viewer) + ply_console_viewer_free (view->console_viewer); + ply_pixel_display_set_draw_handler (view->display, NULL, NULL); free (view); @@ -419,6 +464,7 @@ destroy_plugin (ply_boot_splash_plugin_t *plugin) ply_image_free (plugin->logo_image); ply_image_free (plugin->star_image); ply_image_free (plugin->lock_image); + free (plugin->monospace_font); free (plugin); } @@ -740,6 +786,10 @@ on_draw (view_t *view, ply_label_draw_area (view->message_label, pixel_buffer, x, y, width, height); + + if (plugin->plugin_console_messages_updating == false && view->console_viewer) { + ply_console_viewer_draw_area (view->console_viewer, pixel_buffer, x, y, width, height); + } } static void @@ -794,12 +844,31 @@ show_splash_screen (ply_boot_splash_plugin_t *plugin, ply_buffer_t *boot_buffer, ply_boot_splash_mode_t mode) { + ply_list_node_t *node; + assert (plugin != NULL); assert (plugin->logo_image != NULL); plugin->loop = loop; plugin->mode = mode; + if (boot_buffer && ply_console_viewer_preferred ()) { + plugin->boot_buffer = boot_buffer; + + node = ply_list_get_first_node (plugin->views); + while (node != NULL) { + view_t *view; + ply_list_node_t *next_node; + + view = ply_list_node_get_data (node); + next_node = ply_list_get_next_node (plugin->views, node); + + ply_console_viewer_convert_boot_buffer (view->console_viewer, plugin->boot_buffer); + + node = next_node; + } + } + ply_trace ("loading logo image"); if (!ply_image_load (plugin->logo_image)) return false; @@ -965,6 +1034,7 @@ hide_splash_screen (ply_boot_splash_plugin_t *plugin, ply_event_loop_stop_watching_for_exit (plugin->loop, (ply_event_loop_exit_handler_t) detach_from_event_loop, plugin); + detach_from_event_loop (plugin); } } @@ -1040,7 +1110,12 @@ display_normal (ply_boot_splash_plugin_t *plugin) hide_prompt (plugin); plugin->state = PLY_BOOT_SPLASH_DISPLAY_NORMAL; - start_animation (plugin); + if (!plugin->should_show_console_messages) { + start_animation (plugin); + } else { + unhide_console_messages (plugin); + } + redraw_views (plugin); unpause_views (plugin); } @@ -1083,6 +1158,101 @@ display_message (ply_boot_splash_plugin_t *plugin, show_message (plugin, message); } +static bool +validate_input (ply_boot_splash_plugin_t *plugin, + const char *entry_text, + const char *add_text) +{ + if (!ply_console_viewer_preferred ()) + return true; + + if (strcmp (add_text, "\e") == 0) { + toggle_console_messages (plugin); + return false; + } + + return true; +} + +static void +toggle_console_messages (ply_boot_splash_plugin_t *plugin) +{ + if (plugin->should_show_console_messages) { + plugin->should_show_console_messages = false; + hide_console_messages (plugin); + } else { + unhide_console_messages (plugin); + } +} + +static void +display_console_messages (ply_boot_splash_plugin_t *plugin) +{ + ply_list_node_t *node; + view_t *view; + + pause_views (plugin); + + plugin->plugin_console_messages_updating = true; + node = ply_list_get_first_node (plugin->views); + while (node != NULL) { + view = ply_list_node_get_data (node); + ply_console_viewer_show (view->console_viewer, view->display); + node = ply_list_get_next_node (plugin->views, node); + } + plugin->plugin_console_messages_updating = false; + + redraw_views (plugin); + unpause_views (plugin); +} + +static void +unhide_console_messages (ply_boot_splash_plugin_t *plugin) +{ + plugin->should_show_console_messages = true; + stop_animation (plugin); + display_console_messages (plugin); +} + +static void +hide_console_messages (ply_boot_splash_plugin_t *plugin) +{ + ply_list_node_t *node; + view_t *view; + + plugin->should_show_console_messages = false; + + plugin->plugin_console_messages_updating = true; + node = ply_list_get_first_node (plugin->views); + while (node != NULL) { + view = ply_list_node_get_data (node); + ply_console_viewer_hide (view->console_viewer); + node = ply_list_get_next_node (plugin->views, node); + } + plugin->plugin_console_messages_updating = false; + if (plugin->state == PLY_BOOT_SPLASH_DISPLAY_NORMAL) + start_animation (plugin); +} + +static void +on_boot_output (ply_boot_splash_plugin_t *plugin, + const char *output, + size_t size) +{ + ply_list_node_t *node; + view_t *view; + + if (!ply_console_viewer_preferred ()) + return; + + node = ply_list_get_first_node (plugin->views); + while (node != NULL) { + view = ply_list_node_get_data (node); + ply_console_viewer_parse_lines (view->console_viewer, output, size); + node = ply_list_get_next_node (plugin->views, node); + } +} + ply_boot_splash_plugin_interface_t * ply_boot_splash_plugin_get_interface (void) { @@ -1099,6 +1269,8 @@ ply_boot_splash_plugin_get_interface (void) .display_password = display_password, .display_question = display_question, .display_message = display_message, + .on_boot_output = on_boot_output, + .validate_input = validate_input, }; return &plugin_interface; From d3d0c9d6505b1b9c5b61a8f14ad04438c23e89c5 Mon Sep 17 00:00:00 2001 From: n3rdopolis Date: Sun, 19 Mar 2023 22:42:41 -0400 Subject: [PATCH 22/22] space-flares: Get the messages from the console and ply-kmsg-reader, and use ply-terminal-emulator and ply-console-viewer to the display messages --- src/plugins/splash/space-flares/plugin.c | 177 ++++++++++++++++++++++- 1 file changed, 176 insertions(+), 1 deletion(-) diff --git a/src/plugins/splash/space-flares/plugin.c b/src/plugins/splash/space-flares/plugin.c index 2a942229..bea8acc6 100644 --- a/src/plugins/splash/space-flares/plugin.c +++ b/src/plugins/splash/space-flares/plugin.c @@ -54,6 +54,7 @@ #include "ply-pixel-display.h" #include "ply-trigger.h" #include "ply-utils.h" +#include "ply-console-viewer.h" #ifndef FRAMES_PER_SECOND #define FRAMES_PER_SECOND 40 @@ -168,6 +169,8 @@ typedef struct ply_list_t *sprites; ply_rectangle_t box_area, lock_area, logo_area; ply_image_t *scaled_background_image; + + ply_console_viewer_t *console_viewer; } view_t; struct _ply_boot_splash_plugin @@ -202,10 +205,26 @@ struct _ply_boot_splash_plugin uint32_t root_is_mounted : 1; uint32_t is_visible : 1; uint32_t is_animating : 1; + + char *monospace_font; + uint32_t plugin_console_messages_updating : 1; + uint32_t should_show_console_messages : 1; + ply_buffer_t *boot_buffer; + uint32_t console_text_color; }; ply_boot_splash_plugin_interface_t *ply_boot_splash_plugin_get_interface (void); static void detach_from_event_loop (ply_boot_splash_plugin_t *plugin); +static bool validate_input (ply_boot_splash_plugin_t *plugin, + const char *entry_text, + const char *add_text); +static void on_boot_output (ply_boot_splash_plugin_t *plugin, + const char *output, + size_t size); +static void toggle_console_messages (ply_boot_splash_plugin_t *plugin); +static void display_console_messages (ply_boot_splash_plugin_t *plugin); +static void hide_console_messages (ply_boot_splash_plugin_t *plugin); +static void unhide_console_messages (ply_boot_splash_plugin_t *plugin); static view_t * view_new (ply_boot_splash_plugin_t *plugin, @@ -223,6 +242,16 @@ view_new (ply_boot_splash_plugin_t *plugin, view->sprites = ply_list_new (); + if (ply_console_viewer_preferred ()) { + view->console_viewer = ply_console_viewer_new (view->display, plugin->monospace_font); + ply_console_viewer_set_text_color (view->console_viewer, plugin->console_text_color); + + if (plugin->boot_buffer) + ply_console_viewer_convert_boot_buffer (view->console_viewer, plugin->boot_buffer); + } else { + view->console_viewer = NULL; + } + return view; } @@ -237,6 +266,9 @@ view_free (view_t *view) view_free_sprites (view); ply_list_free (view->sprites); + if (view->console_viewer) + ply_console_viewer_free (view->console_viewer); + ply_image_free (view->scaled_background_image); free (view); @@ -541,6 +573,21 @@ create_plugin (ply_key_file_t *key_file) plugin->progress_barimage = ply_image_new (image_path); free (image_path); #endif + + plugin->plugin_console_messages_updating = false; + plugin->should_show_console_messages = false; + + /* Likely only able to set the font if the font is in the initrd */ + plugin->monospace_font = ply_key_file_get_value (key_file, "two-step", "MonospaceFont"); + + if (plugin->monospace_font == NULL) + plugin->monospace_font = strdup ("monospace 10"); + + plugin->console_text_color = + ply_key_file_get_long (key_file, "two-step", + "ConsoleLogTextColor", + PLY_CONSOLE_VIEWER_LOG_TEXT_COLOR); + plugin->state = PLY_BOOT_SPLASH_DISPLAY_NORMAL; plugin->progress = 0; plugin->progress_target = -1; @@ -564,6 +611,7 @@ destroy_plugin (ply_boot_splash_plugin_t *plugin) ply_event_loop_stop_watching_for_exit (plugin->loop, (ply_event_loop_exit_handler_t) detach_from_event_loop, plugin); + detach_from_event_loop (plugin); } @@ -583,6 +631,8 @@ destroy_plugin (ply_boot_splash_plugin_t *plugin) ply_image_free (plugin->progress_barimage); #endif + free (plugin->monospace_font); + free_views (plugin); free (plugin); @@ -1346,6 +1396,10 @@ on_draw (view_t *view, ply_label_draw_area (view->message_label, pixel_buffer, x, y, width, height); + + if (plugin->plugin_console_messages_updating == false && view->console_viewer) { + ply_console_viewer_draw_area (view->console_viewer, pixel_buffer, x, y, width, height); + } } static void @@ -1623,12 +1677,31 @@ show_splash_screen (ply_boot_splash_plugin_t *plugin, ply_buffer_t *boot_buffer, ply_boot_splash_mode_t mode) { + ply_list_node_t *node; + assert (plugin != NULL); assert (plugin->logo_image != NULL); plugin->loop = loop; plugin->mode = mode; + if (boot_buffer && ply_console_viewer_preferred ()) { + plugin->boot_buffer = boot_buffer; + + node = ply_list_get_first_node (plugin->views); + while (node != NULL) { + view_t *view; + ply_list_node_t *next_node; + + view = ply_list_node_get_data (node); + next_node = ply_list_get_next_node (plugin->views, node); + + ply_console_viewer_convert_boot_buffer (view->console_viewer, plugin->boot_buffer); + + node = next_node; + } + } + ply_trace ("loading logo image"); if (!ply_image_load (plugin->logo_image)) return false; @@ -1795,7 +1868,12 @@ display_normal (ply_boot_splash_plugin_t *plugin) hide_prompt (plugin); plugin->state = PLY_BOOT_SPLASH_DISPLAY_NORMAL; - start_animation (plugin); + if (!plugin->should_show_console_messages) { + start_animation (plugin); + } else { + unhide_console_messages (plugin); + } + redraw_views (plugin); unpause_views (plugin); } @@ -1837,6 +1915,101 @@ display_message (ply_boot_splash_plugin_t *plugin, show_message (plugin, message); } +static bool +validate_input (ply_boot_splash_plugin_t *plugin, + const char *entry_text, + const char *add_text) +{ + if (!ply_console_viewer_preferred ()) + return true; + + if (strcmp (add_text, "\e") == 0) { + toggle_console_messages (plugin); + return false; + } + + return true; +} + +static void +toggle_console_messages (ply_boot_splash_plugin_t *plugin) +{ + if (plugin->should_show_console_messages) { + plugin->should_show_console_messages = false; + hide_console_messages (plugin); + } else { + unhide_console_messages (plugin); + } +} + +static void +display_console_messages (ply_boot_splash_plugin_t *plugin) +{ + ply_list_node_t *node; + view_t *view; + + pause_views (plugin); + + plugin->plugin_console_messages_updating = true; + node = ply_list_get_first_node (plugin->views); + while (node != NULL) { + view = ply_list_node_get_data (node); + ply_console_viewer_show (view->console_viewer, view->display); + node = ply_list_get_next_node (plugin->views, node); + } + plugin->plugin_console_messages_updating = false; + + redraw_views (plugin); + unpause_views (plugin); +} + +static void +unhide_console_messages (ply_boot_splash_plugin_t *plugin) +{ + plugin->should_show_console_messages = true; + stop_animation (plugin); + display_console_messages (plugin); +} + +static void +hide_console_messages (ply_boot_splash_plugin_t *plugin) +{ + ply_list_node_t *node; + view_t *view; + + plugin->should_show_console_messages = false; + + plugin->plugin_console_messages_updating = true; + node = ply_list_get_first_node (plugin->views); + while (node != NULL) { + view = ply_list_node_get_data (node); + ply_console_viewer_hide (view->console_viewer); + node = ply_list_get_next_node (plugin->views, node); + } + plugin->plugin_console_messages_updating = false; + if (plugin->state == PLY_BOOT_SPLASH_DISPLAY_NORMAL) + start_animation (plugin); +} + +static void +on_boot_output (ply_boot_splash_plugin_t *plugin, + const char *output, + size_t size) +{ + ply_list_node_t *node; + view_t *view; + + if (!ply_console_viewer_preferred ()) + return; + + node = ply_list_get_first_node (plugin->views); + while (node != NULL) { + view = ply_list_node_get_data (node); + ply_console_viewer_parse_lines (view->console_viewer, output, size); + node = ply_list_get_next_node (plugin->views, node); + } +} + ply_boot_splash_plugin_interface_t * ply_boot_splash_plugin_get_interface (void) { @@ -1856,6 +2029,8 @@ ply_boot_splash_plugin_get_interface (void) .display_password = display_password, .display_question = display_question, .display_message = display_message, + .on_boot_output = on_boot_output, + .validate_input = validate_input, }; return &plugin_interface;