From f0de4abfb81dc57ff0d665443c2aaa3d3849c6d8 Mon Sep 17 00:00:00 2001 From: "K. Lange" Date: Sat, 31 Jul 2021 16:10:05 +0900 Subject: [PATCH] toastd: Finish things up, I guess. --- apps/toastd.c | 178 +++++++++++++++++++++++++++++++------- base/home/local/.yutanirc | 1 + 2 files changed, 147 insertions(+), 32 deletions(-) diff --git a/apps/toastd.c b/apps/toastd.c index 6eb0173f..bf8147a9 100644 --- a/apps/toastd.c +++ b/apps/toastd.c @@ -13,19 +13,96 @@ #include #include #include +#include #include #include #include #include #include +#include +#include + +typedef struct JSON_Value JSON_Value; static yutani_t * yctx; static FILE * pex_endpoint = NULL; static sprite_t background_sprite; +static list_t * windows = NULL; +static list_t * garbage = NULL; + +struct ToastNotification { + yutani_window_t * window; + struct timespec created; + int duration; +}; #define PAD_RIGHT 10 #define PAD_TOP 48 +static void handle_msg(JSON_Value * msg) { + if (msg->type != JSON_TYPE_OBJECT) { + fprintf(stderr, "expected an object, but json value was of type %d\n", msg->type); + return; + } + + JSON_Value * msg_body = JSON_KEY(msg, "body"); + + if (!msg_body) { + fprintf(stderr, "missing 'body'\n"); + return; + } + if (msg_body->type != JSON_TYPE_STRING) { + fprintf(stderr, "'body' should have been a string, but got type %d instead\n", msg_body->type); + return; + } + + /* At this point, we're going to show something, at least... */ + yutani_window_t * win = yutani_window_create_flags(yctx, + background_sprite.width, + background_sprite.height, + YUTANI_WINDOW_FLAG_NO_STEAL_FOCUS | YUTANI_WINDOW_FLAG_ALT_ANIMATION); + + /* TODO: We need to figure out how to place this... */ + yutani_window_move(yctx, win, yctx->display_width - background_sprite.width - PAD_RIGHT, PAD_TOP + background_sprite.height * windows->length); + + struct ToastNotification * notification = malloc(sizeof(struct ToastNotification)); + notification->window = win; + clock_gettime(CLOCK_MONOTONIC, ¬ification->created); + list_insert(windows, notification); + + JSON_Value * msg_duration = JSON_KEY(msg,"duration"); + if (msg_duration && msg_duration->type == JSON_TYPE_NUMBER) { + notification->duration = msg_duration->number; + } else { + notification->duration = 5; + } + + /* Establish the rendering context for this window, we'll only need it for a bit. + * We won't even both double buffering... */ + gfx_context_t * ctx = init_graphics_yutani(win); + draw_fill(ctx, rgba(0,0,0,0)); + draw_sprite(ctx, &background_sprite, 0, 0); + int textOffset = 0; + + /* Does it have an icon? */ + JSON_Value * msg_icon = JSON_KEY(msg, "icon"); + if (msg_icon && msg_icon->type == JSON_TYPE_STRING) { + /* Just ignore the icon if it's not a string... */ + sprite_t myIcon; + if (!load_sprite(&myIcon, msg_icon->string)) { + /* Is this a reasonable icon to display? */ + if (myIcon.width < 100) { + textOffset = myIcon.width + 4; /* Sounds like a fine padding... */ + draw_sprite(ctx, &myIcon, 10, (background_sprite.height - myIcon.height) / 2); + } + free(myIcon.bitmap); + } + } + + markup_draw_string(ctx, 10 + textOffset, 26, msg_body->string, rgb(255,255,255)); + yutani_flip(yctx, win); +} + int main(int argc, char * argv[]) { /* Make sure we were actually expecting to be run... */ if (argc < 2 || strcmp(argv[1],"--really")) { @@ -53,43 +130,17 @@ int main(int argc, char * argv[]) { /* Set up our text rendering and sprite contexts... */ markup_text_init(); load_sprite(&background_sprite, "/usr/share/ttk/toast/default.png"); - /* Make a test window? */ - yutani_window_t * wina = yutani_window_create_flags(yctx, background_sprite.width, background_sprite.height, YUTANI_WINDOW_FLAG_NO_STEAL_FOCUS | YUTANI_WINDOW_FLAG_ALT_ANIMATION); - yutani_window_move(yctx, wina, yctx->display_width - background_sprite.width - PAD_RIGHT, PAD_TOP); /* We need to be able to query the panel location... */ - gfx_context_t * ctx = init_graphics_yutani_double_buffer(wina); - draw_fill(ctx, rgba(0,0,0,0)); - draw_sprite(ctx, &background_sprite, 0, 0); - /* Wait for messages from pex, or from compositor... */ - markup_draw_string(ctx,10,26,"

Welcome!


This is a sample toast notification.",rgb(255,255,255)); - flip(ctx); - yutani_flip(yctx, wina); + windows = list_create(); + garbage = list_create(); int should_exit = 0; while (!should_exit) { - int fds[1] = {fileno(yctx->sock)}; - int index = fswait2(1,fds,20); + int fds[2] = {fileno(yctx->sock),fileno(pex_endpoint)}; + int index = fswait2(2,fds,windows->length ? 20 : -1); if (index == 0) { yutani_msg_t * m = yutani_poll(yctx); while (m) { switch (m->type) { - case YUTANI_MSG_KEY_EVENT: - { - struct yutani_msg_key_event * ke = (void*)m->data; - if (ke->event.action == KEY_ACTION_DOWN && ke->event.keycode == 'q') { - should_exit = 1; - sched_yield(); - } - } - break; - case YUTANI_MSG_WINDOW_MOUSE_EVENT: - { - struct yutani_msg_window_mouse_event * me = (void*)m->data; - if (me->command == YUTANI_MOUSE_EVENT_DOWN && me->buttons & YUTANI_MOUSE_BUTTON_LEFT) { - yutani_window_drag_start(yctx, wina); - } - } - break; - case YUTANI_MSG_WINDOW_CLOSE: case YUTANI_MSG_SESSION_END: should_exit = 1; break; @@ -99,10 +150,73 @@ int main(int argc, char * argv[]) { free(m); m = yutani_poll_async(yctx); } + } else if (index == 1) { + pex_packet_t * p = calloc(PACKET_SIZE, 1); + pex_listen(pex_endpoint, p); + + JSON_Value * msg = json_parse((char*)p->data); + + if (msg) { + handle_msg(msg); + json_free(msg); + } + + free(p); + } + + if (windows->length) { + /* Check all the existing toasts for expired ones... */ + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + foreach(node, windows) { + struct ToastNotification * notification = node->value; + + /* How long has it been since this notification was created? */ + struct timespec diff; + diff.tv_sec = now.tv_sec - notification->created.tv_sec; + diff.tv_nsec = now.tv_nsec - notification->created.tv_nsec; + if (diff.tv_nsec < 0) { diff.tv_sec--; diff.tv_nsec += 1000000000L; } + + if (diff.tv_sec >= notification->duration) { + if (notification->window) { + yutani_close(yctx, notification->window); + notification->window = NULL; + } + if (diff.tv_sec >= notification->duration + 1) { + list_insert(garbage, node); + } + } + } + + /* Expunge garbage */ + if (garbage->length) { + while (garbage->length) { + node_t * n = list_pop(garbage); + node_t * node = n->value; + free(n); + list_delete(windows, node); + free(node->value); + free(node); + } + } + + /* Figure out if we need to move anything */ + if (index == 2) { + int index = 0; + foreach(node, windows) { + struct ToastNotification * notification = node->value; + if (notification->window && notification->window->y > PAD_TOP + background_sprite.height * index) { + yutani_window_move(yctx, notification->window, notification->window->x, notification->window->y - 4); + } + + index++; + } + + sched_yield(); + } } } - - yutani_close(yctx, wina); } return 0; } diff --git a/base/home/local/.yutanirc b/base/home/local/.yutanirc index cb1e71e7..3d85191e 100755 --- a/base/home/local/.yutanirc +++ b/base/home/local/.yutanirc @@ -3,6 +3,7 @@ if stat -Lq /usr/share/fonts/DejaVuSansMono.ttf then export FREETYPE_PROPERTIES="truetype:interpreter-version=35" cd ~/Desktop file-browser --wallpaper & +toastd --really # Daemonizes cd ~ tutorial & exec panel --really