summaryrefslogtreecommitdiff
path: root/src/common/input.c
blob: 471cd2b34381667cdcf1b73d9dace77800f666ac (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#include "input.h"

#include <stdlib.h>
#include <string.h>

static const char *event_text(const struct yasl_input_event *ev, size_t *len)
{
    switch (ev->kind) {
    case YASL_INPUT_TEXT: *len = ev->text.len; return ev->text.text;
    case YASL_INPUT_PASTE: *len = ev->paste.len; return ev->paste.text;
    case YASL_INPUT_DROP_FILES: *len = ev->drop_files.len; return ev->drop_files.files;
    default: *len = 0; return NULL;
    }
}

static void set_text(struct yasl_input_event *ev, const char *text)
{
    switch (ev->kind) {
    case YASL_INPUT_TEXT: ev->text.text = text; break;
    case YASL_INPUT_PASTE: ev->paste.text = text; break;
    case YASL_INPUT_DROP_FILES: ev->drop_files.files = text; break;
    default: break;
    }
}

static void discard_pending(struct input_queue *q)
{
    for (size_t i = 0; i < q->count; i++) {
        size_t len;
        free((void *)event_text(&q->events[(q->head + i) % q->capacity], &len));
    }
    q->head = q->count = 0;
}

bool input_queue_push(struct input_queue *q, const struct yasl_input_event *ev)
{
    struct yasl_input_event copy = *ev;
    char *owned = NULL;
    if (ev->kind == YASL_INPUT_TEXT || ev->kind == YASL_INPUT_PASTE ||
        ev->kind == YASL_INPUT_DROP_FILES) {
        size_t len;
        const char *text = event_text(ev, &len);
        if ((!text && len) || len == SIZE_MAX) goto failed;
        owned = malloc(len + 1);
        if (!owned) goto failed;
        if (len) memcpy(owned, text, len);
        owned[len] = '\0';
        set_text(&copy, owned);
    }
    if (q->count == q->capacity) {
        size_t capacity = q->capacity ? q->capacity * 2 : 32;
        if (capacity < q->capacity || capacity > SIZE_MAX / sizeof(*q->events))
            goto failed;
        struct yasl_input_event *events = malloc(capacity * sizeof(*events));
        if (!events) goto failed;
        if (q->events && q->count > 0) {
            size_t first = q->capacity - q->head;
            if (first > q->count) first = q->count;
            memcpy(events, q->events + q->head, first * sizeof(*events));
            if (q->count > first)
                memcpy(events + first, q->events, (q->count - first) * sizeof(*events));
        }
        free(q->events);
        q->events = events;
        q->capacity = capacity;
        q->head = 0;
    }
    q->events[(q->head + q->count) & (q->capacity - 1)] = copy;
    q->count++;
    return true;

failed:
    free(owned);
    /* A missing release must never silently leave app input state stuck.
     * Discard the old stream and deliver an allocation-free reset first. */
    discard_pending(q);
    q->reset_surface = ev->surface;
    q->reset_pending = true;
    return false;
}

bool input_queue_pop(struct input_queue *q, struct yasl_input_event *ev)
{
    if (!ev || (!q->count && !q->reset_pending)) return false;
    if (q->last_text) {
        free(q->last_text);
        q->last_text = NULL;
    }
    if (q->reset_pending) {
        *ev = (struct yasl_input_event){
            .surface = q->reset_surface, .kind = YASL_INPUT_RESET,
        };
        q->reset_pending = false;
        return true;
    }
    *ev = q->events[q->head];
    if (ev->kind == YASL_INPUT_TEXT || ev->kind == YASL_INPUT_PASTE ||
        ev->kind == YASL_INPUT_DROP_FILES) {
        size_t len;
        q->last_text = (char *)event_text(ev, &len);
    }
    q->head = (q->head + 1) & (q->capacity - 1);
    q->count--;
    return true;
}

void input_queue_clear(struct input_queue *q)
{
    discard_pending(q);
    free(q->last_text);
    free(q->events);
    *q = (struct input_queue){0};
}