color-scheme.c (8882B)
1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20 21#include "terminal/color-scheme.h" 22#include "terminal/palette.h" 23#include "terminal/xparsecolor.h" 24 25#include <ctype.h> 26#include <stdio.h> 27#include <stdlib.h> 28#include <string.h> 29 30#include <guacamole/client.h> 31 32/** 33 * Compare a non-null-terminated string to a null-terminated literal, in the 34 * same manner as strcmp(). 35 * 36 * @param str_start 37 * Start of the non-null-terminated string. 38 * 39 * @param str_end 40 * End of the non-null-terminated string, after the last character. 41 * 42 * @param literal 43 * The null-terminated literal to compare against. 44 * 45 * @return 46 * Zero if the two strings are equal and non-zero otherwise. 47 */ 48static int guac_terminal_color_scheme_compare_token(const char* str_start, 49 const char* str_end, const char* literal) { 50 51 const int result = strncmp(literal, str_start, str_end - str_start); 52 if (result != 0) 53 return result; 54 55 /* At this point, literal is same length or longer than 56 * | str_end - str_start |, so if the two are equal, literal should 57 * have its null-terminator at | str_end - str_start |. */ 58 return (int) (unsigned char) literal[str_end - str_start]; 59} 60 61/** 62 * Strip the leading and trailing spaces of a bounded string. 63 * 64 * @param[in,out] str_start 65 * Address of a pointer to the start of the string. On return, the pointer 66 * is advanced to after any leading spaces. 67 * 68 * @param[in,out] str_end 69 * Address of a pointer to the end of the string, after the last character. 70 * On return, the pointer is moved back to before any trailing spaces. 71 */ 72static void guac_terminal_color_scheme_strip_spaces(const char** str_start, 73 const char** str_end) { 74 75 /* Strip leading spaces. */ 76 while (*str_start < *str_end && isspace(**str_start)) 77 (*str_start)++; 78 79 /* Strip trailing spaces. */ 80 while (*str_end > *str_start && isspace(*(*str_end - 1))) 81 (*str_end)--; 82} 83 84/** 85 * Parse the name part of the name-value pair within the color-scheme 86 * configuration. 87 * 88 * @param client 89 * The client that the terminal is connected to. 90 * 91 * @param name_start 92 * Start of the name string. 93 * 94 * @param name_end 95 * End of the name string, after the last character. 96 * 97 * @param foreground 98 * Pointer to the foreground color. 99 * 100 * @param background 101 * Pointer to the background color. 102 * 103 * @param palette 104 * Pointer to the palette array. 105 * 106 * @param[out] target 107 * On return, pointer to the color struct that corresponds to the name. 108 * 109 * @return 110 * Zero if successful or non-zero otherwise. 111 */ 112static int guac_terminal_parse_color_scheme_name(guac_client* client, 113 const char* name_start, const char* name_end, 114 guac_terminal_color* foreground, guac_terminal_color* background, 115 guac_terminal_color (*palette)[256], 116 guac_terminal_color** target) { 117 118 guac_terminal_color_scheme_strip_spaces(&name_start, &name_end); 119 120 if (!guac_terminal_color_scheme_compare_token( 121 name_start, name_end, GUAC_TERMINAL_SCHEME_FOREGROUND)) { 122 *target = foreground; 123 return 0; 124 } 125 126 if (!guac_terminal_color_scheme_compare_token( 127 name_start, name_end, GUAC_TERMINAL_SCHEME_BACKGROUND)) { 128 *target = background; 129 return 0; 130 } 131 132 /* Parse color<n> value. */ 133 int index = -1; 134 if (sscanf(name_start, GUAC_TERMINAL_SCHEME_NUMBERED "%d", &index) && 135 index >= 0 && index <= 255) { 136 *target = &(*palette)[index]; 137 return 0; 138 } 139 140 guac_client_log(client, GUAC_LOG_WARNING, 141 "Unknown color name: \"%.*s\".", 142 name_end - name_start, name_start); 143 return 1; 144} 145 146/** 147 * Parse the value part of the name-value pair within the color-scheme 148 * configuration. 149 * 150 * @param client 151 * The client that the terminal is connected to. 152 * 153 * @param value_start 154 * Start of the value string. 155 * 156 * @param value_end 157 * End of the value string, after the last character. 158 * 159 * @param palette 160 * The current color palette. 161 * 162 * @param[out] target 163 * On return, the parsed color. 164 * 165 * @return 166 * Zero if successful or non-zero otherwise. 167 */ 168static int guac_terminal_parse_color_scheme_value(guac_client* client, 169 const char* value_start, const char* value_end, 170 const guac_terminal_color (*palette)[256], 171 guac_terminal_color* target) { 172 173 guac_terminal_color_scheme_strip_spaces(&value_start, &value_end); 174 175 /* Parse color<n> value. */ 176 int index = -1; 177 if (sscanf(value_start, GUAC_TERMINAL_SCHEME_NUMBERED "%d", &index) && 178 index >= 0 && index <= 255) { 179 *target = (*palette)[index]; 180 return 0; 181 } 182 183 /* Parse X11 value. */ 184 if (!guac_terminal_xparsecolor(value_start, target)) 185 return 0; 186 187 guac_client_log(client, GUAC_LOG_WARNING, 188 "Invalid color value: \"%.*s\".", 189 value_end - value_start, value_start); 190 return 1; 191} 192 193void guac_terminal_parse_color_scheme(guac_client* client, 194 const char* color_scheme, guac_terminal_color* foreground, 195 guac_terminal_color* background, 196 guac_terminal_color (*palette)[256]) { 197 198 /* Special cases. */ 199 if (color_scheme[0] == '\0') { 200 /* guac_terminal_parse_color_scheme defaults to gray-black */ 201 } 202 else if (strcmp(color_scheme, GUAC_TERMINAL_SCHEME_GRAY_BLACK) == 0) { 203 color_scheme = "foreground:color7;background:color0"; 204 } 205 else if (strcmp(color_scheme, GUAC_TERMINAL_SCHEME_BLACK_WHITE) == 0) { 206 color_scheme = "foreground:color0;background:color15"; 207 } 208 else if (strcmp(color_scheme, GUAC_TERMINAL_SCHEME_GREEN_BLACK) == 0) { 209 color_scheme = "foreground:color2;background:color0"; 210 } 211 else if (strcmp(color_scheme, GUAC_TERMINAL_SCHEME_WHITE_BLACK) == 0) { 212 color_scheme = "foreground:color15;background:color0"; 213 } 214 215 /* Set default gray-black color scheme and initial palette. */ 216 *foreground = GUAC_TERMINAL_INITIAL_PALETTE[GUAC_TERMINAL_COLOR_GRAY]; 217 *background = GUAC_TERMINAL_INITIAL_PALETTE[GUAC_TERMINAL_COLOR_BLACK]; 218 memcpy(palette, GUAC_TERMINAL_INITIAL_PALETTE, 219 sizeof(GUAC_TERMINAL_INITIAL_PALETTE)); 220 221 /* Current char being parsed, or NULL if at end of parsing. */ 222 const char* cursor = color_scheme; 223 224 while (cursor) { 225 /* Start of the current "name: value" pair. */ 226 const char* pair_start = cursor; 227 228 /* End of the current name-value pair. */ 229 const char* pair_end = strchr(pair_start, ';'); 230 if (pair_end) { 231 cursor = pair_end + 1; 232 } 233 else { 234 pair_end = pair_start + strlen(pair_start); 235 cursor = NULL; 236 } 237 238 guac_terminal_color_scheme_strip_spaces(&pair_start, &pair_end); 239 if (pair_start >= pair_end) 240 /* Allow empty pairs, which happens, e.g., when the configuration 241 * string ends in a semi-colon. */ 242 continue; 243 244 /* End of the name part of the pair. */ 245 const char* name_end = memchr(pair_start, ':', pair_end - pair_start); 246 if (name_end == NULL) { 247 guac_client_log(client, GUAC_LOG_WARNING, 248 "Expecting colon: \"%.*s\".", 249 pair_end - pair_start, pair_start); 250 return; 251 } 252 253 /* The color that the name corresponds to. */ 254 guac_terminal_color* color_target = NULL; 255 256 if (guac_terminal_parse_color_scheme_name( 257 client, pair_start, name_end, foreground, background, 258 palette, &color_target)) 259 return; /* Parsing failed. */ 260 261 if (guac_terminal_parse_color_scheme_value( 262 client, name_end + 1, pair_end, 263 (const guac_terminal_color(*)[256]) palette, color_target)) 264 return; /* Parsing failed. */ 265 } 266 267 /* Persist pseudo-index for foreground/background colors */ 268 foreground->palette_index = GUAC_TERMINAL_COLOR_FOREGROUND; 269 background->palette_index = GUAC_TERMINAL_COLOR_BACKGROUND; 270 271} 272