/* * 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 "keydef.h" #include "log.h" #include "state.h" #include #include #include #include #include #include #include #include guaclog_state* guaclog_state_alloc(const char* path) { /* Open output file */ int fd = open(path, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR); if (fd == -1) { guaclog_log(GUAC_LOG_ERROR, "Failed to open output file \"%s\": %s", path, strerror(errno)); goto fail_output_fd; } /* Create stream for output file */ FILE* output = fdopen(fd, "wb"); if (output == NULL) { guaclog_log(GUAC_LOG_ERROR, "Failed to allocate stream for output " "file \"%s\": %s", path, strerror(errno)); goto fail_output_file; } /* Allocate state */ guaclog_state* state = (guaclog_state*) calloc(1, sizeof(guaclog_state)); if (state == NULL) { goto fail_state; } /* Associate state with output file */ state->output = output; /* No keys are initially tracked */ state->active_keys = 0; return state; /* Free all allocated data in case of failure */ fail_state: fclose(output); fail_output_file: close(fd); fail_output_fd: return NULL; } int guaclog_state_free(guaclog_state* state) { int i; /* Ignore NULL state */ if (state == NULL) return 0; /* Free keydefs of all tracked keys */ for (i = 0; i < state->active_keys; i++) guaclog_keydef_free(state->key_states[i].keydef); /* Close output file */ fclose(state->output); free(state); return 0; } /** * Adds the given key state to the array of tracked keys. If the key is already * being tracked, its corresponding entry within the array of tracked keys is * updated, and the number of tracked keys remains the same. If the key is not * already being tracked, it is added to the end of the array of tracked keys * providing there is space available, and the number of tracked keys is * updated. Failures to add keys will be automatically logged. * * @param state * The Guacamole input log interpreter state being updated. * * @param keydef * The guaclog_keydef of the key being pressed or released. This * guaclog_keydef will automatically be freed along with the guaclog_state * if the key state was successfully added, and must be manually freed * otherwise. * * @param pressed * true if the key is being pressed, false if the key is being released. * * @return * Zero if the key state was successfully added, non-zero otherwise. */ static int guaclog_state_add_key(guaclog_state* state, guaclog_keydef* keydef, bool pressed) { int i; /* Update existing key, if already tracked */ for (i = 0; i < state->active_keys; i++) { guaclog_key_state* key = &state->key_states[i]; if (key->keydef->keysym == keydef->keysym) { guaclog_keydef_free(key->keydef); key->keydef = keydef; key->pressed = pressed; return 0; } } /* If not already tracked, we need space to add it */ if (state->active_keys == GUACLOG_MAX_KEYS) { guaclog_log(GUAC_LOG_WARNING, "Unable to log key 0x%X: Too many " "active keys.", keydef->keysym); return 1; } /* Add key to state */ guaclog_key_state* key = &state->key_states[state->active_keys++]; key->keydef = keydef; key->pressed = pressed; return 0; } /** * Removes released keys from the end of the array of tracked keys, such that * the last key in the array is a pressed key. This function should be invoked * after changes have been made to the interpreter state, to ensure that the * array of tracked keys does not grow longer than necessary. * * @param state * The Guacamole input log interpreter state to trim. */ static void guaclog_state_trim_keys(guaclog_state* state) { int i; /* Reset active_keys to contain only up to the last pressed key */ for (i = state->active_keys - 1; i >= 0; i--) { guaclog_key_state* key = &state->key_states[i]; if (key->pressed) { state->active_keys = i + 1; return; } /* Free all trimmed states */ guaclog_keydef_free(key->keydef); } /* No keys are active */ state->active_keys = 0; } /** * Returns whether the current tracked key state represents an in-progress * keyboard shortcut. * * @param state * The Guacamole input log interpreter state to test. * * @return * true if the given state represents an in-progress keyboard shortcut, * false otherwise. */ static bool guaclog_state_is_shortcut(guaclog_state* state) { int i; /* We are in a shortcut if at least one key is non-printable */ for (i = 0; i < state->active_keys; i++) { guaclog_key_state* key = &state->key_states[i]; if (key->keydef->value == NULL) return true; } /* All keys are printable - no shortcut */ return false; } int guaclog_state_update_key(guaclog_state* state, int keysym, bool pressed) { int i; /* Determine nature of key */ guaclog_keydef* keydef = guaclog_keydef_alloc(keysym); if (keydef == NULL) return 0; /* Update tracked key state for modifiers */ if (keydef->modifier) { /* Keydef will be automatically freed if successfully added to state */ if (guaclog_state_add_key(state, keydef, pressed)) guaclog_keydef_free(keydef); else guaclog_state_trim_keys(state); return 0; } /* Output key states only for printable keys */ if (pressed) { if (guaclog_state_is_shortcut(state)) { fprintf(state->output, "<"); /* Compose log entry by inspecting the state of each tracked key */ for (i = 0; i < state->active_keys; i++) { /* Translate keysym into human-readable name */ guaclog_key_state* key = &state->key_states[i]; /* Print name of key */ if (i == 0) fprintf(state->output, "%s", key->keydef->name); else fprintf(state->output, "+%s", key->keydef->name); } fprintf(state->output, "%s>", keydef->value); } /* Print the key itself */ else { if (keydef->value != NULL) fprintf(state->output, "%s", keydef->value); else fprintf(state->output, "<%s>", keydef->name); } } guaclog_keydef_free(keydef); return 0; }