state.c (7638B)
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#include "config.h" 21#include "keydef.h" 22#include "log.h" 23#include "state.h" 24 25#include <guacamole/mem.h> 26#include <guacamole/string.h> 27 28#include <errno.h> 29#include <sys/types.h> 30#include <sys/stat.h> 31#include <fcntl.h> 32#include <stdio.h> 33#include <stdlib.h> 34#include <string.h> 35#include <unistd.h> 36 37guaclog_state* guaclog_state_alloc(const char* path) { 38 39 /* Open output file */ 40 int fd = open(path, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR); 41 if (fd == -1) { 42 guaclog_log(GUAC_LOG_ERROR, "Failed to open output file \"%s\": %s", 43 path, strerror(errno)); 44 goto fail_output_fd; 45 } 46 47 /* Create stream for output file */ 48 FILE* output = fdopen(fd, "wb"); 49 if (output == NULL) { 50 guaclog_log(GUAC_LOG_ERROR, "Failed to allocate stream for output " 51 "file \"%s\": %s", path, strerror(errno)); 52 goto fail_output_file; 53 } 54 55 /* Allocate state */ 56 guaclog_state* state = (guaclog_state*) guac_mem_zalloc(sizeof(guaclog_state)); 57 if (state == NULL) { 58 goto fail_state; 59 } 60 61 /* Associate state with output file */ 62 state->output = output; 63 64 /* No keys are initially tracked */ 65 state->active_keys = 0; 66 67 return state; 68 69 /* Free all allocated data in case of failure */ 70fail_state: 71 fclose(output); 72 73fail_output_file: 74 close(fd); 75 76fail_output_fd: 77 return NULL; 78 79} 80 81int guaclog_state_free(guaclog_state* state) { 82 83 int i; 84 85 /* Ignore NULL state */ 86 if (state == NULL) 87 return 0; 88 89 /* Free keydefs of all tracked keys */ 90 for (i = 0; i < state->active_keys; i++) 91 guaclog_keydef_free(state->key_states[i].keydef); 92 93 /* Close output file */ 94 fclose(state->output); 95 96 guac_mem_free(state); 97 return 0; 98 99} 100 101/** 102 * Adds the given key state to the array of tracked keys. If the key is already 103 * being tracked, its corresponding entry within the array of tracked keys is 104 * updated, and the number of tracked keys remains the same. If the key is not 105 * already being tracked, it is added to the end of the array of tracked keys 106 * providing there is space available, and the number of tracked keys is 107 * updated. Failures to add keys will be automatically logged. 108 * 109 * @param state 110 * The Guacamole input log interpreter state being updated. 111 * 112 * @param keydef 113 * The guaclog_keydef of the key being pressed or released. This 114 * guaclog_keydef will automatically be freed along with the guaclog_state 115 * if the key state was successfully added, and must be manually freed 116 * otherwise. 117 * 118 * @param pressed 119 * true if the key is being pressed, false if the key is being released. 120 * 121 * @return 122 * Zero if the key state was successfully added, non-zero otherwise. 123 */ 124static int guaclog_state_add_key(guaclog_state* state, guaclog_keydef* keydef, 125 bool pressed) { 126 127 int i; 128 129 /* Update existing key, if already tracked */ 130 for (i = 0; i < state->active_keys; i++) { 131 guaclog_key_state* key = &state->key_states[i]; 132 if (key->keydef->keysym == keydef->keysym) { 133 guaclog_keydef_free(key->keydef); 134 key->keydef = keydef; 135 key->pressed = pressed; 136 return 0; 137 } 138 } 139 140 /* If not already tracked, we need space to add it */ 141 if (state->active_keys == GUACLOG_MAX_KEYS) { 142 guaclog_log(GUAC_LOG_WARNING, "Unable to log key 0x%X: Too many " 143 "active keys.", keydef->keysym); 144 return 1; 145 } 146 147 /* Add key to state */ 148 guaclog_key_state* key = &state->key_states[state->active_keys++]; 149 key->keydef = keydef; 150 key->pressed = pressed; 151 return 0; 152 153} 154 155/** 156 * Removes released keys from the end of the array of tracked keys, such that 157 * the last key in the array is a pressed key. This function should be invoked 158 * after changes have been made to the interpreter state, to ensure that the 159 * array of tracked keys does not grow longer than necessary. 160 * 161 * @param state 162 * The Guacamole input log interpreter state to trim. 163 */ 164static void guaclog_state_trim_keys(guaclog_state* state) { 165 166 int i; 167 168 /* Reset active_keys to contain only up to the last pressed key */ 169 for (i = state->active_keys - 1; i >= 0; i--) { 170 171 guaclog_key_state* key = &state->key_states[i]; 172 if (key->pressed) { 173 state->active_keys = i + 1; 174 return; 175 } 176 177 /* Free all trimmed states */ 178 guaclog_keydef_free(key->keydef); 179 180 } 181 182 /* No keys are active */ 183 state->active_keys = 0; 184 185} 186 187/** 188 * Returns whether the current tracked key state represents an in-progress 189 * keyboard shortcut. 190 * 191 * @param state 192 * The Guacamole input log interpreter state to test. 193 * 194 * @return 195 * true if the given state represents an in-progress keyboard shortcut, 196 * false otherwise. 197 */ 198static bool guaclog_state_is_shortcut(guaclog_state* state) { 199 200 int i; 201 202 /* We are in a shortcut if at least one key is non-printable */ 203 for (i = 0; i < state->active_keys; i++) { 204 guaclog_key_state* key = &state->key_states[i]; 205 if (key->keydef->value == NULL) 206 return true; 207 } 208 209 /* All keys are printable - no shortcut */ 210 return false; 211 212} 213 214int guaclog_state_update_key(guaclog_state* state, int keysym, bool pressed) { 215 216 int i; 217 218 /* Determine nature of key */ 219 guaclog_keydef* keydef = guaclog_keydef_alloc(keysym); 220 if (keydef == NULL) 221 return 0; 222 223 /* Update tracked key state for modifiers */ 224 if (keydef->modifier) { 225 226 /* Keydef will be automatically freed if successfully added to state */ 227 if (guaclog_state_add_key(state, keydef, pressed)) 228 guaclog_keydef_free(keydef); 229 else 230 guaclog_state_trim_keys(state); 231 232 return 0; 233 234 } 235 236 /* Output key states only for printable keys */ 237 if (pressed) { 238 239 if (guaclog_state_is_shortcut(state)) { 240 241 fprintf(state->output, "<"); 242 243 /* Compose log entry by inspecting the state of each tracked key */ 244 for (i = 0; i < state->active_keys; i++) { 245 246 /* Translate keysym into human-readable name */ 247 guaclog_key_state* key = &state->key_states[i]; 248 249 /* Print name of key */ 250 if (i == 0) 251 fprintf(state->output, "%s", key->keydef->name); 252 else 253 fprintf(state->output, "+%s", key->keydef->name); 254 255 } 256 257 fprintf(state->output, "%s>", keydef->value); 258 259 } 260 261 /* Print the key itself */ 262 else { 263 if (keydef->value != NULL) 264 fprintf(state->output, "%s", keydef->value); 265 else 266 fprintf(state->output, "<%s>", keydef->name); 267 } 268 269 } 270 271 guaclog_keydef_free(keydef); 272 return 0; 273 274} 275