/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ #include "config.h" #include "terminal/color-scheme.h" #include "terminal/palette.h" #include "terminal/xparsecolor.h" #include #include #include #include #include /** * Compare a non-null-terminated string to a null-terminated literal, in the * same manner as strcmp(). * * @param str_start * Start of the non-null-terminated string. * * @param str_end * End of the non-null-terminated string, after the last character. * * @param literal * The null-terminated literal to compare against. * * @return * Zero if the two strings are equal and non-zero otherwise. */ static int guac_terminal_color_scheme_compare_token(const char* str_start, const char* str_end, const char* literal) { const int result = strncmp(literal, str_start, str_end - str_start); if (result != 0) return result; /* At this point, literal is same length or longer than * | str_end - str_start |, so if the two are equal, literal should * have its null-terminator at | str_end - str_start |. */ return (int) (unsigned char) literal[str_end - str_start]; } /** * Strip the leading and trailing spaces of a bounded string. * * @param[in,out] str_start * Address of a pointer to the start of the string. On return, the pointer * is advanced to after any leading spaces. * * @param[in,out] str_end * Address of a pointer to the end of the string, after the last character. * On return, the pointer is moved back to before any trailing spaces. */ static void guac_terminal_color_scheme_strip_spaces(const char** str_start, const char** str_end) { /* Strip leading spaces. */ while (*str_start < *str_end && isspace(**str_start)) (*str_start)++; /* Strip trailing spaces. */ while (*str_end > *str_start && isspace(*(*str_end - 1))) (*str_end)--; } /** * Parse the name part of the name-value pair within the color-scheme * configuration. * * @param client * The client that the terminal is connected to. * * @param name_start * Start of the name string. * * @param name_end * End of the name string, after the last character. * * @param foreground * Pointer to the foreground color. * * @param background * Pointer to the background color. * * @param palette * Pointer to the palette array. * * @param[out] target * On return, pointer to the color struct that corresponds to the name. * * @return * Zero if successful or non-zero otherwise. */ static int guac_terminal_parse_color_scheme_name(guac_client* client, const char* name_start, const char* name_end, guac_terminal_color* foreground, guac_terminal_color* background, guac_terminal_color (*palette)[256], guac_terminal_color** target) { guac_terminal_color_scheme_strip_spaces(&name_start, &name_end); if (!guac_terminal_color_scheme_compare_token( name_start, name_end, GUAC_TERMINAL_SCHEME_FOREGROUND)) { *target = foreground; return 0; } if (!guac_terminal_color_scheme_compare_token( name_start, name_end, GUAC_TERMINAL_SCHEME_BACKGROUND)) { *target = background; return 0; } /* Parse color value. */ int index = -1; if (sscanf(name_start, GUAC_TERMINAL_SCHEME_NUMBERED "%d", &index) && index >= 0 && index <= 255) { *target = &(*palette)[index]; return 0; } guac_client_log(client, GUAC_LOG_WARNING, "Unknown color name: \"%.*s\".", name_end - name_start, name_start); return 1; } /** * Parse the value part of the name-value pair within the color-scheme * configuration. * * @param client * The client that the terminal is connected to. * * @param value_start * Start of the value string. * * @param value_end * End of the value string, after the last character. * * @param palette * The current color palette. * * @param[out] target * On return, the parsed color. * * @return * Zero if successful or non-zero otherwise. */ static int guac_terminal_parse_color_scheme_value(guac_client* client, const char* value_start, const char* value_end, const guac_terminal_color (*palette)[256], guac_terminal_color* target) { guac_terminal_color_scheme_strip_spaces(&value_start, &value_end); /* Parse color value. */ int index = -1; if (sscanf(value_start, GUAC_TERMINAL_SCHEME_NUMBERED "%d", &index) && index >= 0 && index <= 255) { *target = (*palette)[index]; return 0; } /* Parse X11 value. */ if (!guac_terminal_xparsecolor(value_start, target)) return 0; guac_client_log(client, GUAC_LOG_WARNING, "Invalid color value: \"%.*s\".", value_end - value_start, value_start); return 1; } void guac_terminal_parse_color_scheme(guac_client* client, const char* color_scheme, guac_terminal_color* foreground, guac_terminal_color* background, guac_terminal_color (*palette)[256]) { /* Special cases. */ if (color_scheme[0] == '\0') { /* guac_terminal_parse_color_scheme defaults to gray-black */ } else if (strcmp(color_scheme, GUAC_TERMINAL_SCHEME_GRAY_BLACK) == 0) { color_scheme = "foreground:color7;background:color0"; } else if (strcmp(color_scheme, GUAC_TERMINAL_SCHEME_BLACK_WHITE) == 0) { color_scheme = "foreground:color0;background:color15"; } else if (strcmp(color_scheme, GUAC_TERMINAL_SCHEME_GREEN_BLACK) == 0) { color_scheme = "foreground:color2;background:color0"; } else if (strcmp(color_scheme, GUAC_TERMINAL_SCHEME_WHITE_BLACK) == 0) { color_scheme = "foreground:color15;background:color0"; } /* Set default gray-black color scheme and initial palette. */ *foreground = GUAC_TERMINAL_INITIAL_PALETTE[GUAC_TERMINAL_COLOR_GRAY]; *background = GUAC_TERMINAL_INITIAL_PALETTE[GUAC_TERMINAL_COLOR_BLACK]; memcpy(palette, GUAC_TERMINAL_INITIAL_PALETTE, sizeof(GUAC_TERMINAL_INITIAL_PALETTE)); /* Current char being parsed, or NULL if at end of parsing. */ const char* cursor = color_scheme; while (cursor) { /* Start of the current "name: value" pair. */ const char* pair_start = cursor; /* End of the current name-value pair. */ const char* pair_end = strchr(pair_start, ';'); if (pair_end) { cursor = pair_end + 1; } else { pair_end = pair_start + strlen(pair_start); cursor = NULL; } guac_terminal_color_scheme_strip_spaces(&pair_start, &pair_end); if (pair_start >= pair_end) /* Allow empty pairs, which happens, e.g., when the configuration * string ends in a semi-colon. */ continue; /* End of the name part of the pair. */ const char* name_end = memchr(pair_start, ':', pair_end - pair_start); if (name_end == NULL) { guac_client_log(client, GUAC_LOG_WARNING, "Expecting colon: \"%.*s\".", pair_end - pair_start, pair_start); return; } /* The color that the name corresponds to. */ guac_terminal_color* color_target = NULL; if (guac_terminal_parse_color_scheme_name( client, pair_start, name_end, foreground, background, palette, &color_target)) return; /* Parsing failed. */ if (guac_terminal_parse_color_scheme_value( client, name_end + 1, pair_end, (const guac_terminal_color(*)[256]) palette, color_target)) return; /* Parsing failed. */ } /* Persist pseudo-index for foreground/background colors */ foreground->palette_index = GUAC_TERMINAL_COLOR_FOREGROUND; background->palette_index = GUAC_TERMINAL_COLOR_BACKGROUND; }