#include #include #include "ui.h" #define LOG_RATIO 1.25 static s32 resize_cb(struct ncplane *p) { struct cmsrv_ui *ui = (struct cmsrv_ui *)ncplane_userptr(p); ncplane_erase(p); notcurses_refresh(ui->nc, &ui->term_rows, &ui->term_cols); notcurses_render(ui->nc); notcurses_stddim_yx(ui->nc, &ui->term_rows, &ui->term_cols); ui->pending_layout = true; return 0; } bool cmsrv_ui_init(struct cmsrv_ui *ui, struct camu_server *server) { al_memset(ui, 0, sizeof(struct cmsrv_ui)); if (!(ui->nc = notcurses_core_init(NULL, stdin))) { return false; } ui->server = server; notcurses_stddim_yx(ui->nc, &ui->term_rows, &ui->term_cols); struct ncplane *stdplane = notcurses_stdplane(ui->nc); ncplane_set_resizecb(stdplane, resize_cb); ncplane_set_userptr(stdplane, ui); ui->pending_layout = true; al_array_init(ui->log.messages); nn_mutex_init(&ui->log.lock); return true; } s32 cmsrv_ui_get_input_fd(struct cmsrv_ui *ui) { return notcurses_inputready_fd(ui->nc); } bool cmsrv_ui_read_input(struct cmsrv_ui *ui, struct ncinput *input) { al_memset(input, 0, sizeof(struct ncinput)); u32 ret = notcurses_get_nblock(ui->nc, input); return !(ret == (u32)-1 || ret == 0); } void cmsrv_ui_set_pane(struct cmsrv_ui *ui, u8 pane) { ui->pane = pane; } void cmsrv_ui_push_message(struct cmsrv_ui *ui, char *message) { nn_mutex_lock(&ui->log.lock); al_array_push(ui->log.messages, message); nn_mutex_unlock(&ui->log.lock); } static void layout_log(struct cmsrv_ui *ui, struct ncplane *parent) { if (ui->log.n) ncplane_destroy(ui->log.n); struct ncplane_options nopts = { .rows = ui->term_rows / LOG_RATIO, .cols = ui->term_cols, .x = 0 }; nopts.y = ui->term_rows - nopts.rows; ui->log.n = ncplane_create(parent, &nopts); } static void erase_log(struct cmsrv_ui *ui) { struct ncplane *n = ui->log.n; if (n) ncplane_erase(n); } static void render_log(struct cmsrv_ui *ui) { struct ncplane *n = ui->log.n; ncplane_erase(n); u32 max_width = ncplane_dim_x(n) - 2; u32 max_height = ncplane_dim_y(n) - 2; nn_mutex_lock(&ui->log.lock); u32 size = ui->log.messages.count; if (size > max_height) { u32 index = size - max_height; for (u32 i = 0; i < index; i++) { al_free(al_array_at(ui->log.messages, i)); } al_array_remove_range(ui->log.messages, 0, index); } size = ui->log.messages.count; al_assert(size <= max_height); for (u32 i = 0; i < size; i++) { u32 x = 1; u32 y = i + 1; // We can't use putnstr here to control the width because lots of these // messages will have wide characters. ncplane_putstr_yx(n, y, x, al_array_at(ui->log.messages, i)); } nn_mutex_unlock(&ui->log.lock); u64 c = 0; ncchannels_set_fg_default(&c); ncplane_cursor_move_yx(n, 0, 0); ncplane_rounded_box(n, NCSTYLE_NONE, c, max_height + 1, max_width + 1, 0); } static void layout_lists(struct cmsrv_ui *ui, struct ncplane *parent) { if (ui->lists.n) ncplane_destroy(ui->lists.n); struct ncplane_options nopts = { .rows = ui->term_rows - (ui->term_rows / LOG_RATIO), .cols = ui->term_cols, .x = 0, .y = 0 }; ui->lists.n = ncplane_create(parent, &nopts); } static void erase_lists(struct cmsrv_ui *ui) { struct ncplane *n = ui->lists.n; if (n) ncplane_erase(n); } #ifdef CAMU_HAVE_PORTAL static void putnwstr_maxwidth_yx(struct ncplane *n, u32 y, u32 x, u32 maxwidth, wstr *w) { ncplane_cursor_move_yx(n, y, x); u32 end = MIN(w->length, maxwidth); for (u32 i = 0; i < end; i++) { ncplane_putwc(n, al_wstr_at(w, i)); } } #endif static void render_lists(struct cmsrv_ui *ui) { struct ncplane *n = ui->lists.n; u32 max_width = ncplane_dim_x(n); u32 max_height = ncplane_dim_y(n); u32 current_line = 0; s32 entries_per_list = max_height; struct lia_list *list; al_array_foreach(ui->server->lists, i, list) { if (current_line++ >= max_height) break; for (u32 j = 0; j < MIN(max_width, list->name.length); j++) { ncplane_putchar_yx(n, i, j, al_str_at(&list->name, j)); } s32 index = MAX(list->current - (entries_per_list / 2), 0); s32 size = (s32)list->entries.count; s32 end = MIN(index + entries_per_list, size); for (s32 j = index; j < end; j++) { u32 x = 1; u32 y = i + (j - index) + 1; if (j == list->current) { ncplane_putchar_yx(n, y, 1, '>'); x += 2; } struct lia_list_entry *entry = al_array_at(list->entries, j); #ifdef CAMU_HAVE_PORTAL struct camu_resource *resource = (struct camu_resource *)entry->opaque; if (resource->type == CAMU_RESOURCE_PORTAL) { putnwstr_maxwidth_yx(n, y, x, max_width - x, &resource->post->title); } else { #endif char *c_str = al_str_to_c_str(&entry->brief); ncplane_putstr_yx(n, y, x, c_str); al_free(c_str); #ifdef CAMU_HAVE_PORTAL } #endif if (++current_line >= max_height) break; } } } static void layout_nodes(struct cmsrv_ui *ui, struct ncplane *parent) { if (ui->nodes.n) ncplane_destroy(ui->nodes.n); struct ncplane_options nopts = { .rows = ui->term_rows - (ui->term_rows / LOG_RATIO), .cols = ui->term_cols, .x = 0, .y = 0 }; ui->nodes.n = ncplane_create(parent, &nopts); } static void erase_nodes(struct cmsrv_ui *ui) { struct ncplane *n = ui->nodes.n; if (n) ncplane_erase(n); } static void render_nodes(struct cmsrv_ui *ui) { struct ncplane *n = ui->lists.n; u32 max_width = ncplane_dim_x(n); u32 max_height = ncplane_dim_y(n); (void)max_height; char strbuf[16]; struct lia_node *node; al_array_foreach(ui->server->data.server.nodes, i, node) { al_snprintf(strbuf, sizeof(strbuf), "%hu", node->id); ncplane_putnstr_yx(n, i, 0, max_width, strbuf); struct lia_node_connection *conn; al_array_foreach(node->connections, j, conn) { al_snprintf(strbuf, sizeof(strbuf), "%u", conn->pool.ready.count); ncplane_putnstr_yx(n, i + j + 1, 2, max_width, strbuf); } } } void cmsrv_ui_render(struct cmsrv_ui *ui) { struct ncplane *stdplane = notcurses_stdplane(ui->nc); erase_log(ui); erase_lists(ui); erase_nodes(ui); // If erasing is broken first assume it could be a screen bug. if (ui->pending_layout) { layout_log(ui, stdplane); layout_lists(ui, stdplane); layout_nodes(ui, stdplane); ui->pending_layout = false; } render_log(ui); switch (ui->pane) { case CMSRV_UI_LISTS: render_lists(ui); break; case CMSRV_UI_RESOURCES: break; case CMSRV_UI_NODES: render_nodes(ui); break; } notcurses_render(ui->nc); } void cmsrv_ui_close(struct cmsrv_ui *ui) { notcurses_stop(ui->nc); char *message; al_array_foreach(ui->log.messages, i, message) { al_free(message); } al_array_free(ui->log.messages); nn_mutex_destroy(&ui->log.lock); }