summaryrefslogtreecommitdiff
path: root/src/common/input.c
blob: 0b55f1fe92defe8276f5cdf188070ca044ee84c5 (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
114
115
116
117
118
119
#include "input.h"
#include "yasl/loop.h"

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

static bool has_text(const struct yasl_event *ev)
{
    return ev->type == YASL_EVENT_TEXT || ev->type == YASL_EVENT_PASTE ||
           ev->type == YASL_EVENT_DROP;
}

static void discard_pending(struct input_queue *q)
{
    for (size_t i = 0; i < q->count; i++) {
        struct yasl_event *ev = &q->events[(q->head + i) & (q->capacity - 1)];
        if (has_text(ev)) free((void *)ev->text.data);
    }
    q->head = q->count = 0;
}

bool input_queue_push(struct input_queue *q, const struct yasl_event *ev)
{
    struct yasl_event copy = *ev;
    char *owned = NULL;
    if (!copy.time_ns) copy.time_ns = yasl_loop_now_ns();
    if (has_text(ev)) {
        size_t len = ev->text.len;
        if ((!ev->text.data && len) || len == SIZE_MAX) goto failed;
        owned = malloc(len + 1);
        if (!owned) goto failed;
        if (len) memcpy(owned, ev->text.data, len);
        owned[len] = '\0';
        copy.text.data = owned;
    }
    switch (ev->type) {
    case YASL_EVENT_POINTER_MOTION:
        if (!q->relative) {
            copy.pointer.delta = q->has_last_pos
                ? (struct yasl_vector2d){ev->pointer.pos.x - q->last_pos.x,
                                         ev->pointer.pos.y - q->last_pos.y}
                : (struct yasl_vector2d){0, 0};
        }
        [[fallthrough]];
    case YASL_EVENT_POINTER_DOWN:
    case YASL_EVENT_POINTER_UP:
        q->last_pos = ev->pointer.pos;
        q->has_last_pos = true;
        break;
    case YASL_EVENT_POINTER_LEAVE:
    case YASL_EVENT_INPUT_RESET:
        q->has_last_pos = false;
        break;
    default:
        break;
    }
    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_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;
    q->has_last_pos = false;
    return false;
}

bool input_queue_pop(struct input_queue *q, struct yasl_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_event){
            .type = YASL_EVENT_INPUT_RESET, .surface = q->reset_surface,
            .time_ns = yasl_loop_now_ns(),
        };
        q->reset_pending = false;
        return true;
    }
    *ev = q->events[q->head];
    if (has_text(ev))
        q->last_text = (char *)ev->text.data;
    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};
}