| /* |
| * 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 <errno.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| 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; |
| |
| } |
| |