diff options
| author | Martin Stensgård <mastensg@mastensg.net> | 2025-07-22 11:41:10 +0200 |
|---|---|---|
| committer | Martin Stensgård <mastensg@mastensg.net> | 2025-07-22 11:41:48 +0200 |
| commit | 5fead1096ee4ffdfed9957b575e2b09e6d9637cf (patch) | |
| tree | 2d07fe2ff64235bd505157d8ad947ad7532d47e1 /opplysning-vis.c | |
| parent | e6d85d4516b7d79304c8bb331a05f9ea15070967 (diff) | |
install, bin/opplysning-*
Diffstat (limited to 'opplysning-vis.c')
| -rw-r--r-- | opplysning-vis.c | 306 |
1 files changed, 306 insertions, 0 deletions
diff --git a/opplysning-vis.c b/opplysning-vis.c new file mode 100644 index 0000000..ba4d9c9 --- /dev/null +++ b/opplysning-vis.c @@ -0,0 +1,306 @@ +#define _POSIX_C_SOURCE 199309L + +#include <err.h> +#include <signal.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include <libical/ical.h> +#include <raylib.h> + +#include "sqlite/sqlite3.h" + +enum { + SCREEN_W = 1920, + SCREEN_H = 1080, +}; + +const char *maaned[] = { + "", "JANUAR", "FEBRUAR", "MARS", "APRIL", "MAI", "JUNI", + "JULI", "AUGUST", "SEPTEMBER", "OKTOBER", "NOVEMBER", "DESEMBER", +}; +const char *ukedag[] = { + "SØNDAG", "MANDAG", "TIRSDAG", "ONSDAG", "TORSDAG", "FREDAG", "LØRDAG", +}; + +enum { + MAX_EVENTS = 64, + SIZEOF_TITLE = 60, +}; + +struct event { + time_t start; + time_t end; + int year; + int month; + int day; + int wday; + int hour; + int minute; + int end_hour; + int end_minute; + char title[SIZEOF_TITLE]; +}; + +struct calendar { + char *database; + char *title; + char *subtitle; + size_t num_events; + struct event events[MAX_EVENTS]; +}; + +void +load_events(struct calendar *cal) +{ + sqlite3 *db; + if (sqlite3_open(cal->database, &db)) + errx(1, "sqlite3_open: %s", sqlite3_errmsg(db)); + + const char *sql = "SELECT " + "unixepoch(start), " + "unixepoch(end), " + "CAST(strftime('%Y', start, 'localtime') as INT), " + "CAST(strftime('%m', start, 'localtime') as INT), " + "CAST(strftime('%d', start, 'localtime') as INT), " + "CAST(strftime('%w', start, 'localtime') as INT), " + "CAST(strftime('%H', start, 'localtime') as INT), " + "CAST(strftime('%M', start, 'localtime') as INT), " + "CAST(strftime('%H', end , 'localtime') as INT), " + "CAST(strftime('%M', end , 'localtime') as INT), " + "summary " + "FROM event " + "WHERE datetime('now', 'start of day') < start " + "AND summary IS NOT NULL " + "ORDER BY start"; + sqlite3_stmt *stmt = NULL; + if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL)) + errx(1, "sqlite3_prepare_v2: %s", sqlite3_errmsg(db)); + + size_t n = 0; + for (;;) { + int step = sqlite3_step(stmt); + if (SQLITE_DONE == step) + break; + if (SQLITE_ROW != step) + errx(1, "sqlite3_step: %s", sqlite3_errmsg(db)); + + if (MAX_EVENTS <= n) { + warnx("MAX_EVENTS exceeded at %zu", n); + break; + } + + int start = sqlite3_column_int(stmt, 0); + int end = sqlite3_column_int(stmt, 1); + int year = sqlite3_column_int(stmt, 2); + int month = sqlite3_column_int(stmt, 3); + int day = sqlite3_column_int(stmt, 4); + int wday = sqlite3_column_int(stmt, 5); + int hour = sqlite3_column_int(stmt, 6); + int minute = sqlite3_column_int(stmt, 7); + int end_hour = sqlite3_column_int(stmt, 8); + int end_minute = sqlite3_column_int(stmt, 9); + const char *summary = + (const void *)sqlite3_column_text(stmt, 10); + assert(summary); + + cal->events[n].start = start; + cal->events[n].end = end; + cal->events[n].year = year; + cal->events[n].month = month; + cal->events[n].day = day; + cal->events[n].hour = hour; + cal->events[n].minute = minute; + cal->events[n].end_hour = end_hour; + cal->events[n].end_minute = end_minute; + strncpy(cal->events[n].title, summary, SIZEOF_TITLE); + cal->events[n].wday = wday; + ++n; + } + + if (sqlite3_finalize(stmt)) + errx(1, "sqlite3_finalize: %s", sqlite3_errmsg(db)); + + if (sqlite3_close(db)) + errx(1, "sqlite3_close: %s", sqlite3_errmsg(db)); + + cal->num_events = n; +} + +struct ray { + Shader shader; + RenderTexture2D target; + int shader_u_time; + + Color bg; + Color fg; + Color hd; + Color rd; + + Font font_h; + Font font_p; +}; +struct ray R; + +const char *the_non_ascii = "ÄÅÉËÞÜÚÍÓÖÁÐFGHÏŒØÆŒ©®BÑΜ" + "äåéëþüúíóöáðfghïœøæœ©®bñµß" + "«»"; + +void +ray_init(void) +{ + SetTraceLogLevel(LOG_WARNING); + InitWindow(SCREEN_W, SCREEN_H, "opplysning"); + SetTargetFPS(60); + + char *codes = calloc(128 + strlen(the_non_ascii), 1); + if (!codes) + err(1, "calloc"); + for (int i = 1; i < 128; ++i) + codes[i] = i; + memcpy(codes + 128, the_non_ascii, strlen(the_non_ascii)); + int ncp = 0; + int *cp = LoadCodepoints(codes + 1, &ncp); + R.font_h = LoadFontEx("font/adventpro-bold.ttf", 48, cp, ncp); + R.font_p = + LoadFontEx("font/BellCentennialStd-Address.ttf", 28, cp, ncp); + UnloadCodepoints(cp); + free(codes); + + R.bg = RAYWHITE; + R.fg = BLACK; + R.hd = (Color){0xf0, 0x4a, 0x00, 0xff}; + R.rd = (Color){0xd7, 0x21, 0x17, 0xff}; + + R.shader = LoadShader(0, "s.glsl"); + R.shader_u_time = GetShaderLocation(R.shader, "u_time"); + R.target = LoadRenderTexture(SCREEN_W, SCREEN_H); +} + +void +blur(int x, int y, Font f, Color c, char *s) +{ + Vector2 m = MeasureTextEx(f, s, f.baseSize, 0); + + BeginTextureMode(R.target); + ClearBackground((Color){0}); + DrawTextEx(f, s, (Vector2){0, 0}, (float)f.baseSize, 0, c); + EndTextureMode(); + + BeginShaderMode(R.shader); + float t = GetTime(); + SetShaderValue(R.shader, R.shader_u_time, &t, SHADER_UNIFORM_FLOAT); + DrawTextureRec(R.target.texture, + (Rectangle){0, SCREEN_H - m.y, m.x, -m.y}, + (Vector2){x, y}, WHITE); + EndShaderMode(); +} +void +line(int x, int y, Font f, Color c, const char *s) +{ + DrawTextEx(f, s, (Vector2){x, y}, (float)f.baseSize, 0, c); +} + +void +draw_time(int x, int y, struct timespec now) +{ + struct tm *ti = localtime(&now.tv_sec); + char ts[64] = {0}; + snprintf(ts, sizeof(ts), "%02d:%02d", ti->tm_hour, ti->tm_min); + Vector2 v2_ts = MeasureTextEx(R.font_h, ts, R.font_h.baseSize, 0); + line(x - v2_ts.x / 2, y, R.font_h, R.fg, ts); +} + +void +draw_event(int x, int y, struct timespec now, const struct event *e) +{ + char s[128] = {0}; + snprintf(s, sizeof(s), "%02u:%02u - %02u:%02u %s", e->hour, e->minute, + e->end_hour, e->end_minute, e->title); + Color c = R.fg; + if (e->start <= now.tv_sec && now.tv_sec < e->end) + c = R.rd; + line(x, y, R.font_p, c, s); +} + +void +draw_date(int x, int y, struct timespec now, int wday, int day, int month, + int year) +{ + struct tm *ti = localtime(&now.tv_sec); + Color c = R.fg; + if ((year == 1900 + ti->tm_year) && (month == 1 + ti->tm_mon) && + (day == ti->tm_mday)) + c = R.rd; + char s[64] = {0}; + snprintf(s, sizeof(s), "%s %u. %s", ukedag[wday], day, maaned[month]); + line(x, y, R.font_h, c, s); +} + +void +draw_calendar(int x, int y, struct timespec now, struct calendar *cal) + +{ + x += 20; + line(x, y, R.font_h, R.fg, cal->title); + y += 50; + line(x, y, R.font_p, R.fg, cal->subtitle); + + int year = 0, month = 0, day = 0; + for (size_t i = 0; i < cal->num_events; ++i) { + const struct event *e = &cal->events[i]; + if (year != e->year || month != e->month || day != e->day) { + if (SCREEN_H - 2 * R.font_h.baseSize < y) + break; + year = e->year; + month = e->month; + day = e->day; + y += 40; + draw_date(x, y, now, e->wday, e->day, e->month, + e->year); + y += 20; + } + y += 40; + draw_event(x, y, now, e); + } +} + +int +main(void) +{ + struct calendar google = { + .database = "var/google.db", + .title = "Reservasjoner", + .subtitle = "Hentet fra Google-kalenderen «Bitraf booking»", + }; + struct calendar meetup = { + .database = "var/meetup.db", + .title = "Begivenheter", + .subtitle = "Hentet fra https://www.meetup.com/bitraf/", + }; + + ray_init(); + while (!WindowShouldClose()) { + if (IsKeyPressed(KEY_R)) { + double t0 = GetTime(); + load_events(&google); + load_events(&meetup); + double t1 = GetTime(); + fprintf(stderr, "%6.3f ms\n", (t1 - t0) * 1000.0); + } + + struct timespec now = {0}; + clock_gettime(CLOCK_REALTIME, &now); + + BeginDrawing(); + ClearBackground(R.bg); + draw_time(SCREEN_W / 2 - 100, 0, now); + draw_calendar(0, 0, now, &meetup); + draw_calendar(SCREEN_W / 2, 0, now, &google); + EndDrawing(); + } + CloseWindow(); +} |
