273 lines
7.4 KiB
C
273 lines
7.4 KiB
C
/*
|
|
* 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;
|
|
|
|
}
|
|
|