summaryrefslogtreecommitdiff
path: root/src/common
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-11 21:53:42 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-11 21:53:42 +0300
commit927ae621c9a3d46dbc401b8e9ea7bbed1bf2e97b (patch)
treeaeb00c16770dcf5a36d28c1167cc39c6759059fb /src/common
initial: Yet Another Surface Library (YASL) 0.1.0
A minimal, modern desktop window surface library in C. Supports Wayland on Linux and Cocoa/Metal on macOS with presentation timing, fractional scaling, IME, and CSD.
Diffstat (limited to 'src/common')
-rw-r--r--src/common/loop.c124
-rw-r--r--src/common/timing.c69
-rw-r--r--src/common/utf8.c82
3 files changed, 275 insertions, 0 deletions
diff --git a/src/common/loop.c b/src/common/loop.c
new file mode 100644
index 0000000..4c2da0d
--- /dev/null
+++ b/src/common/loop.c
@@ -0,0 +1,124 @@
+#define _POSIX_C_SOURCE 200809L
+#include "yasl/yasl.h"
+
+#include <errno.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#define YASL_MAX_FDS 64
+
+typedef int (*yasl_loop_fd_fn)(int fd, short revents, void *ud);
+
+struct yasl_fd_source {
+ yasl_loop_fd_fn fn;
+ void *ud;
+ int id;
+ int fd;
+ short events;
+};
+
+struct yasl_loop {
+ struct yasl_fd_source fds[YASL_MAX_FDS];
+ struct pollfd pollfds[YASL_MAX_FDS];
+ int count;
+ int next_id;
+ bool running;
+};
+
+uint64_t yasl_loop_now_ns(void)
+{
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec;
+}
+
+struct yasl_loop *yasl_loop_create(void)
+{
+ struct yasl_loop *l = calloc(1, sizeof(*l));
+ if (!l) return NULL;
+ l->next_id = 1;
+ return l;
+}
+
+void yasl_loop_destroy(struct yasl_loop *l)
+{
+ free(l);
+}
+
+int yasl_loop_add_fd(struct yasl_loop *l, int fd, short events,
+ int (*fn)(int fd, short revents, void *ud), void *ud)
+{
+ if (!l || l->count >= YASL_MAX_FDS || fd < 0)
+ return -1;
+ int id = l->next_id++;
+ l->fds[l->count] = (struct yasl_fd_source){
+ .fn = fn, .ud = ud, .id = id, .fd = fd, .events = events,
+ };
+ l->pollfds[l->count] = (struct pollfd){
+ .fd = fd, .events = events, .revents = 0,
+ };
+ l->count++;
+ return id;
+}
+
+int yasl_loop_del_fd(struct yasl_loop *l, int id)
+{
+ if (!l || id <= 0) return -1;
+ for (int i = 0; i < l->count; i++) {
+ if (l->fds[i].id == id) {
+ l->count--;
+ l->fds[i] = l->fds[l->count];
+ l->pollfds[i] = l->pollfds[l->count];
+ return 0;
+ }
+ }
+ return -1;
+}
+
+int yasl_loop_set_fd_events(struct yasl_loop *l, int id, short events)
+{
+ if (!l || id <= 0) return -1;
+ for (int i = 0; i < l->count; i++) {
+ if (l->fds[i].id == id) {
+ l->fds[i].events = events;
+ l->pollfds[i].events = events;
+ return 0;
+ }
+ }
+ return -1;
+}
+
+void yasl_loop_kick(struct yasl_loop *l)
+{
+ (void)l;
+}
+
+int yasl_loop_run(struct yasl_loop *l)
+{
+ if (!l) return -1;
+ l->running = true;
+ while (l->running) {
+ int n = poll(l->pollfds, (nfds_t)l->count, -1);
+ if (n < 0) {
+ if (errno == EINTR) continue;
+ return -1;
+ }
+ for (int i = 0; i < l->count && l->running; i++) {
+ if (l->pollfds[i].revents) {
+ struct yasl_fd_source src = l->fds[i];
+ short revents = l->pollfds[i].revents;
+ l->pollfds[i].revents = 0;
+ if (src.fn)
+ src.fn(src.fd, revents, src.ud);
+ }
+ }
+ }
+ return 0;
+}
+
+void yasl_loop_quit(struct yasl_loop *l)
+{
+ if (l) l->running = false;
+}
diff --git a/src/common/timing.c b/src/common/timing.c
new file mode 100644
index 0000000..b5e342f
--- /dev/null
+++ b/src/common/timing.c
@@ -0,0 +1,69 @@
+#include "yasl/timing.h"
+
+uint64_t yasl_clock_extend_ms(uint32_t ms, uint64_t now_ns)
+{
+ uint64_t now_ms = now_ns / 1000000;
+ int64_t ahead = (int32_t)(ms - (uint32_t)now_ms);
+ if (ahead < 0 && (uint64_t)-ahead > now_ms)
+ return (uint64_t)ms * 1000000;
+ return (now_ms + (uint64_t)ahead) * 1000000;
+}
+
+void yasl_clock_presented(struct yasl_frame_clock *c, uint64_t now, bool sample)
+{
+ if (c->last_present_ns) {
+ double dt = (double)(now - c->last_present_ns);
+ if (sample && dt < 250e6)
+ c->refresh_ns += 0.125 * (dt - c->refresh_ns);
+ }
+ c->last_present_ns = now;
+}
+
+void yasl_clock_note_resize(struct yasl_frame_clock *c, uint64_t dt_ns)
+{
+ c->resize_count++;
+ c->resize_sum_ns += dt_ns;
+ if (dt_ns > c->resize_max_ns)
+ c->resize_max_ns = dt_ns;
+}
+
+void yasl_clock_resize_stats(const struct yasl_frame_clock *c,
+ struct yasl_resize_stats *out)
+{
+ out->count = c->resize_count;
+ out->avg_us = c->resize_count
+ ? (double)c->resize_sum_ns / 1e3 / (double)c->resize_count
+ : 0.0;
+ out->max_us = (double)c->resize_max_ns / 1e3;
+ out->minflt = c->resize_minflt;
+}
+
+double yasl_clock_refresh_ms(const struct yasl_frame_clock *c)
+{
+ return c->refresh_ns / 1e6;
+}
+
+uint64_t yasl_clock_next_present_ns(const struct yasl_frame_clock *c)
+{
+ return c->last_present_ns
+ ? c->last_present_ns + (uint64_t)c->refresh_ns
+ : 0;
+}
+
+uint64_t yasl_clock_frame_timestamp_ns(struct yasl_frame_clock *c, uint64_t now)
+{
+ if (!c->last_present_ns || c->refresh_ns <= 0.0) {
+ c->frame_time_ns = now;
+ return now;
+ }
+ double k = ((double)now - (double)c->last_present_ns) / c->refresh_ns + 0.5;
+ uint64_t grid = c->last_present_ns +
+ (uint64_t)((uint64_t)(k > 0.0 ? k : 0.0) * c->refresh_ns);
+ if (!c->frame_time_ns) {
+ c->frame_time_ns = grid;
+ return grid;
+ }
+ uint64_t t = grid > c->frame_time_ns ? grid : c->frame_time_ns;
+ c->frame_time_ns = t;
+ return t;
+}
diff --git a/src/common/utf8.c b/src/common/utf8.c
new file mode 100644
index 0000000..089fd89
--- /dev/null
+++ b/src/common/utf8.c
@@ -0,0 +1,82 @@
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+bool yasl_utf8_validate(const char *str, size_t len)
+{
+ const unsigned char *p = (const unsigned char *)str;
+ const unsigned char *end = p + len;
+
+ while (p < end) {
+ if (*p < 0x80) {
+ p++;
+ } else if ((*p & 0xe0) == 0xc0) {
+ if (p + 1 >= end || (p[1] & 0xc0) != 0x80 || (*p & 0x1e) == 0)
+ return false;
+ p += 2;
+ } else if ((*p & 0xf0) == 0xe0) {
+ if (p + 2 >= end || (p[1] & 0xc0) != 0x80 || (p[2] & 0xc0) != 0x80)
+ return false;
+ uint32_t cp = ((p[0] & 0x0f) << 12) | ((p[1] & 0x3f) << 6) | (p[2] & 0x3f);
+ if (cp < 0x800 || (cp >= 0xd800 && cp <= 0xdfff))
+ return false;
+ p += 3;
+ } else if ((*p & 0xf8) == 0xf0) {
+ if (p + 3 >= end || (p[1] & 0xc0) != 0x80 || (p[2] & 0xc0) != 0x80 || (p[3] & 0xc0) != 0x80)
+ return false;
+ uint32_t cp = ((p[0] & 0x07) << 18) | ((p[1] & 0x3f) << 12) | ((p[2] & 0x3f) << 6) | (p[3] & 0x3f);
+ if (cp < 0x10000 || cp > 0x10ffff)
+ return false;
+ p += 4;
+ } else {
+ return false;
+ }
+ }
+ return true;
+}
+
+char *yasl_utf8_sanitize_dup(const char *str)
+{
+ if (!str) return NULL;
+ size_t len = strlen(str);
+ char *out = malloc(len + 1);
+ if (!out) return NULL;
+
+ const unsigned char *p = (const unsigned char *)str;
+ size_t o = 0;
+ while (*p) {
+ if (*p < 0x80) {
+ out[o++] = (char)*p++;
+ } else if ((*p & 0xe0) == 0xc0 && (p[1] & 0xc0) == 0x80 && (*p & 0x1e)) {
+ out[o++] = (char)*p++;
+ out[o++] = (char)*p++;
+ } else if ((*p & 0xf0) == 0xe0 && (p[1] & 0xc0) == 0x80 && (p[2] & 0xc0) == 0x80) {
+ uint32_t cp = ((p[0] & 0x0f) << 12) | ((p[1] & 0x3f) << 6) | (p[2] & 0x3f);
+ if (cp >= 0x800 && !(cp >= 0xd800 && cp <= 0xdfff)) {
+ out[o++] = (char)*p++;
+ out[o++] = (char)*p++;
+ out[o++] = (char)*p++;
+ } else {
+ out[o++] = '?';
+ p++;
+ }
+ } else if ((*p & 0xf8) == 0xf0 && (p[1] & 0xc0) == 0x80 && (p[2] & 0xc0) == 0x80 && (p[3] & 0xc0) == 0x80) {
+ uint32_t cp = ((p[0] & 0x07) << 18) | ((p[1] & 0x3f) << 12) | ((p[2] & 0x3f) << 6) | (p[3] & 0x3f);
+ if (cp >= 0x10000 && cp <= 0x10ffff) {
+ out[o++] = (char)*p++;
+ out[o++] = (char)*p++;
+ out[o++] = (char)*p++;
+ out[o++] = (char)*p++;
+ } else {
+ out[o++] = '?';
+ p++;
+ }
+ } else {
+ out[o++] = '?';
+ p++;
+ }
+ }
+ out[o] = '\0';
+ return out;
+}