#define _POSIX_C_SOURCE 199309L #include #include #include #include #include #include #include #include #include #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(); }