cscg24-guacamole

CSCG 2024 Challenge 'Guacamole Mashup'
git clone https://git.sinitax.com/sinitax/cscg24-guacamole
Log | Files | Refs | sfeed.txt

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