summaryrefslogtreecommitdiff
path: root/src/common/events.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/events.c')
-rw-r--r--src/common/events.c372
1 files changed, 372 insertions, 0 deletions
diff --git a/src/common/events.c b/src/common/events.c
new file mode 100644
index 0000000..a9bbcaf
--- /dev/null
+++ b/src/common/events.c
@@ -0,0 +1,372 @@
+#include "backend.h"
+#include "platform/sync.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct event_node {
+ struct yasl_event event;
+ uint64_t queue_id;
+ char *text;
+ void (*release)(void *);
+ struct event_node *next;
+};
+
+struct yasl_event_state {
+ yasl_mutex mutex;
+ struct event_node *head, *tail, *last;
+ uint64_t sequence, identity, request;
+ int error;
+};
+
+static void dispose(struct event_node *node)
+{
+ if (!node) return;
+ if (node->release) node->release(node->event.user.payload);
+ free(node->text);
+ free(node);
+}
+
+static void dispose_list(struct event_node *nodes)
+{
+ while (nodes) {
+ struct event_node *next = nodes->next;
+ dispose(nodes);
+ nodes = next;
+ }
+}
+
+bool yasl_events_open(struct yasl_display *d)
+{
+ struct yasl_event_state *state = calloc(1, sizeof(*state));
+ if (!state) { errno = ENOMEM; return false; }
+ int error = yasl_mutex_init(&state->mutex);
+ if (error) { free(state); errno = error; return false; }
+ d->events = state;
+ return true;
+}
+
+void yasl_events_close(struct yasl_display *d)
+{
+ struct yasl_event_state *state = d->events;
+ if (!state) return;
+ /* Producers must already have stopped. Detach before callbacks so cleanup
+ * can make harmless failed posts without touching a dying queue. */
+ d->events = NULL;
+ dispose_list(state->head);
+ dispose(state->last);
+ yasl_mutex_destroy(&state->mutex);
+ free(state);
+}
+
+int yasl_events_error(struct yasl_display *d)
+{
+ if (!d->events) return 0;
+ yasl_mutex_lock(&d->events->mutex);
+ int error = d->events->error;
+ yasl_mutex_unlock(&d->events->mutex);
+ return error;
+}
+
+bool yasl_events_pending(struct yasl_display *d)
+{
+ if (!d->events) return false;
+ yasl_mutex_lock(&d->events->mutex);
+ bool pending = d->events->head != NULL;
+ yasl_mutex_unlock(&d->events->mutex);
+ return pending;
+}
+
+struct yasl_surface *yasl_events_find(struct yasl_display *d, uint64_t id)
+{
+ for (struct yasl_surface *s = d->surfaces; s; s = s->event_next)
+ if (s->id == id) return s;
+ return NULL;
+}
+
+static bool publish_input(const struct yasl_event *event, void *ud)
+{
+ struct yasl_surface *owner = ud;
+ return yasl_events_push(owner->display, owner, event);
+}
+
+bool yasl_events_register(struct yasl_surface *s)
+{
+ struct yasl_display *d = s->display;
+ if (!d->events) return true; /* Internal test doubles need no queue. */
+ yasl_mutex_lock(&d->events->mutex);
+ if (d->events->identity == UINT64_MAX) {
+ yasl_mutex_unlock(&d->events->mutex);
+ errno = EOVERFLOW;
+ return false;
+ }
+ s->reported_scale = s->scale_120;
+ s->id = ++d->events->identity;
+ s->event_next = d->surfaces;
+ d->surfaces = s;
+ yasl_mutex_unlock(&d->events->mutex);
+ struct input_queue *q = d->ops->surface_input_queue
+ ? d->ops->surface_input_queue(s) : &s->input;
+ if (q) {
+ /* Preserve input generated during native creation. */
+ struct yasl_event event;
+ while (input_queue_pop(q, &event))
+ yasl_events_push(d, s, &event);
+ q->publish = publish_input;
+ q->publish_ud = s;
+ }
+ return true;
+}
+
+void yasl_events_unregister(struct yasl_surface *s)
+{
+ struct yasl_display *d = s->display;
+ if (!d->events) return;
+ struct event_node *discarded = NULL;
+ yasl_mutex_lock(&d->events->mutex);
+ struct yasl_surface **surface = &d->surfaces;
+ while (*surface && *surface != s) surface = &(*surface)->event_next;
+ if (*surface) *surface = s->event_next;
+ struct event_node **entry = &d->events->head;
+ d->events->tail = NULL;
+ while (*entry) {
+ struct event_node *node = *entry;
+ if (node->event.surface_id == s->id || node->queue_id == s->id) {
+ *entry = node->next;
+ node->next = discarded;
+ discarded = node;
+ } else {
+ d->events->tail = node;
+ entry = &node->next;
+ }
+ }
+ s->event_next = NULL;
+ yasl_mutex_unlock(&d->events->mutex);
+ dispose_list(discarded);
+}
+
+static bool append(struct yasl_display *d, struct event_node *node)
+{
+ struct yasl_event_state *state = d->events;
+ if (state->sequence == UINT64_MAX) { errno = EOVERFLOW; return false; }
+ node->event.sequence = ++state->sequence;
+ if (!node->event.time_ns) node->event.time_ns = yasl_loop_now_ns();
+ if (state->tail) state->tail->next = node;
+ else state->head = node;
+ state->tail = node;
+ return true;
+}
+
+static bool string_event(enum yasl_event_type type)
+{
+ return type == YASL_EVENT_TEXT || type == YASL_EVENT_PASTE ||
+ type == YASL_EVENT_DROP || type == YASL_EVENT_COMPOSITION;
+}
+
+bool yasl_events_push(struct yasl_display *d, struct yasl_surface *queue_owner,
+ const struct yasl_event *event)
+{
+ if (!d || !d->events || (event->surface && !event->surface->id)) return true;
+ struct event_node *node = calloc(1, sizeof(*node));
+ if (!node) goto failed;
+ node->event = *event;
+ node->event.surface_id = event->surface ? event->surface->id : 0;
+ node->queue_id = queue_owner ? queue_owner->id : node->event.surface_id;
+ if (string_event(event->type)) {
+ if (event->text.len == SIZE_MAX || (!event->text.data && event->text.len)) {
+ free(node);
+ goto failed;
+ }
+ node->text = malloc(event->text.len + 1);
+ if (!node->text) { free(node); goto failed; }
+ if (event->text.len) memcpy(node->text, event->text.data, event->text.len);
+ node->text[event->text.len] = '\0';
+ if (event->type == YASL_EVENT_PASTE) node->event.paste.data = node->text;
+ else if (event->type == YASL_EVENT_COMPOSITION) node->event.composition.data = node->text;
+ else node->event.text.data = node->text;
+ }
+ yasl_mutex_lock(&d->events->mutex);
+ if ((node->event.surface_id && !yasl_events_find(d, node->event.surface_id)) ||
+ (node->queue_id && !yasl_events_find(d, node->queue_id))) {
+ yasl_mutex_unlock(&d->events->mutex);
+ dispose(node);
+ return true; /* A native teardown notification for a canceled recipient. */
+ }
+ bool ok = append(d, node);
+ if (!ok) d->events->error = errno;
+ yasl_mutex_unlock(&d->events->mutex);
+ if (!ok) dispose(node);
+ return ok;
+failed:
+ yasl_mutex_lock(&d->events->mutex);
+ d->events->error = ENOMEM;
+ yasl_mutex_unlock(&d->events->mutex);
+ errno = ENOMEM;
+ return false;
+}
+
+bool yasl_display_post(struct yasl_display *d, const struct yasl_message *message)
+{
+ if (!d || !d->events || !message) { errno = EINVAL; return false; }
+ struct event_node *node = calloc(1, sizeof(*node));
+ if (!node) { errno = ENOMEM; return false; }
+ node->event = (struct yasl_event){
+ .type = YASL_EVENT_USER, .surface_id = message->target_id,
+ .user = {.code = message->code, .payload = message->payload, .size = message->size},
+ };
+ node->queue_id = message->target_id;
+ node->release = message->release;
+ yasl_mutex_lock(&d->events->mutex);
+ bool ok = !message->target_id || yasl_events_find(d, message->target_id);
+ if (!ok) errno = ENOENT;
+ else ok = append(d, node);
+ yasl_mutex_unlock(&d->events->mutex);
+ if (!ok) { free(node); return false; }
+ if (d->loop) yasl_loop_kick(d->loop);
+ return true;
+}
+
+static bool poll_event(struct yasl_display *d, uint64_t queue_id, struct yasl_event *event)
+{
+ if (!d || !d->events || !event) return false;
+ struct yasl_event_state *state = d->events;
+ yasl_mutex_lock(&state->mutex);
+ struct event_node *discarded = NULL;
+ struct event_node **entry = &state->head;
+ struct event_node *previous = NULL;
+ if (queue_id) {
+ /* The input-only migration adapter uses legacy callbacks for lifecycle.
+ * Retaining those notifications would keep dispatch permanently ready.
+ * Mixing this adapter with display polling is deliberately unsupported. */
+ while (*entry) {
+ struct event_node *candidate = *entry;
+ if (candidate->event.type < YASL_EVENT_KEY ||
+ candidate->event.type > YASL_EVENT_INPUT_RESET) {
+ *entry = candidate->next;
+ if (state->tail == candidate) state->tail = previous;
+ candidate->next = discarded;
+ discarded = candidate;
+ } else {
+ previous = candidate;
+ entry = &candidate->next;
+ }
+ }
+ entry = &state->head;
+ previous = NULL;
+ while (*entry && (*entry)->queue_id != queue_id) {
+ previous = *entry;
+ entry = &(*entry)->next;
+ }
+ }
+ struct event_node *node = *entry;
+ if (!node) {
+ yasl_mutex_unlock(&state->mutex);
+ dispose_list(discarded);
+ return false;
+ }
+ *entry = node->next;
+ if (state->tail == node) state->tail = previous;
+ node->next = NULL;
+ if (node->event.surface_id)
+ node->event.surface = yasl_events_find(d, node->event.surface_id);
+ struct event_node *last = state->last;
+ state->last = node;
+ *event = node->event;
+ yasl_mutex_unlock(&state->mutex);
+ dispose(last);
+ dispose_list(discarded);
+ return true;
+}
+
+bool yasl_display_poll_event(struct yasl_display *d, struct yasl_event *event)
+{
+ return poll_event(d, 0, event);
+}
+
+bool yasl_events_poll_surface(struct yasl_surface *s, struct yasl_event *event)
+{
+ return s && s->id ? poll_event(s->display, s->id, event) : false;
+}
+
+uint64_t yasl_events_request_id(struct yasl_display *d)
+{
+ if (!d->events || d->events->request == UINT64_MAX) { errno = EOVERFLOW; return 0; }
+ return ++d->events->request;
+}
+
+uint64_t yasl_surface_id(const struct yasl_surface *s) { return s ? s->id : 0; }
+void yasl_surface_set_userdata(struct yasl_surface *s, void *ud) { if (s) s->userdata = ud; }
+void *yasl_surface_userdata(const struct yasl_surface *s) { return s ? s->userdata : NULL; }
+
+void yasl_surface_notify_configure(struct yasl_surface *s, struct yasl_geometry geometry)
+{
+ uint32_t previous_scale = s->reported_scale;
+ s->reported_scale = geometry.scale_120;
+ s->size = geometry.size;
+ s->scale_120 = geometry.scale_120;
+ struct yasl_event event = {.type = YASL_EVENT_WINDOW_RESIZE, .surface = s,
+ .resize = {.geometry = geometry, .scale_changed = previous_scale != geometry.scale_120}};
+ yasl_events_push(s->display, s, &event);
+ if (s->display) yasl_surface_request_frame(s);
+ if (s->callbacks.configure) s->callbacks.configure(s, geometry, s->ud);
+}
+
+void yasl_surface_notify_close(struct yasl_surface *s)
+{
+ struct yasl_event event = {.type = YASL_EVENT_WINDOW_CLOSE, .surface = s};
+ yasl_events_push(s->display, s, &event);
+ if (s->callbacks.close) s->callbacks.close(s, s->ud);
+}
+
+void yasl_surface_notify_state(struct yasl_surface *s, uint32_t flags)
+{
+ s->state = flags;
+ struct yasl_event event = {.type = YASL_EVENT_WINDOW_STATE, .surface = s, .state.flags = flags};
+ yasl_events_push(s->display, s, &event);
+ if (s->callbacks.state) s->callbacks.state(s, flags, s->ud);
+}
+
+void yasl_surface_notify_drag(struct yasl_surface *s, enum yasl_drag_event action, struct yasl_vector2i pos)
+{
+ struct yasl_event event = {.type = YASL_EVENT_DRAG, .surface = s, .drag = {action, pos}};
+ yasl_events_push(s->display, s, &event);
+ if (s->callbacks.drag) s->callbacks.drag(s, action, pos, s->ud);
+}
+
+void yasl_surface_notify_drop(struct yasl_surface *s, enum yasl_drop_event action, struct yasl_vector2i pos)
+{
+ struct yasl_event event = {.type = YASL_EVENT_DROP_HOVER, .surface = s, .drop = {action, pos}};
+ yasl_events_push(s->display, s, &event);
+ if (s->callbacks.drop) s->callbacks.drop(s, action, pos, s->ud);
+}
+
+void yasl_surface_notify_composition(struct yasl_surface *s, const char *text, int begin, int end)
+{
+ struct yasl_event event = {.type = YASL_EVENT_COMPOSITION, .surface = s,
+ .composition = {.data = text ? text : "", .len = text ? strlen(text) : 0,
+ .begin = begin, .end = end, .active = text && text[0]}};
+ yasl_events_push(s->display, s, &event);
+}
+
+void yasl_surface_complete_paste(struct yasl_surface *s, uint64_t id,
+ enum yasl_transfer_status status, const char *text, size_t len)
+{
+ struct yasl_event event = {.type = YASL_EVENT_PASTE, .surface = s,
+ .paste = {.data = text ? text : "", .len = text ? len : 0, .request_id = id, .status = status}};
+ if (!s->display->events) {
+ struct input_queue *q = s->display->ops->surface_input_queue
+ ? s->display->ops->surface_input_queue(s) : &s->input;
+ input_queue_push(q, &event);
+ } else yasl_events_push(s->display, s, &event);
+}
+
+void yasl_display_notify_event(struct yasl_display *d, enum yasl_event_type type)
+{
+ struct yasl_event event = {.type = type};
+ yasl_events_push(d, NULL, &event);
+ void (*callback)(void *) = type == YASL_EVENT_DISPLAY_QUIT ? d->callbacks.quit
+ : type == YASL_EVENT_DISPLAY_REOPEN ? d->callbacks.reopen
+ : type == YASL_EVENT_DISPLAY_LOST ? d->callbacks.lost : NULL;
+ if (callback) callback(d->callback_ud);
+}