summaryrefslogtreecommitdiff
path: root/src/fruits
diff options
context:
space:
mode:
authorAndrew Opalach <andrew@akon.city> 2024-10-21 19:22:50 -0400
committerAndrew Opalach <andrew@akon.city> 2024-10-21 19:22:50 -0400
commit60b4ebfbf3be78dba9dc7c65ab2bdaa0b218c0c2 (patch)
tree08ff2ce7975f523112e7ad2fe4f797b4fc7db5de /src/fruits
parent2f9a0945bfeee3296cec3d38d094e4c49f9cb65f (diff)
downloadcamu-60b4ebfbf3be78dba9dc7c65ab2bdaa0b218c0c2.tar.gz
camu-60b4ebfbf3be78dba9dc7c65ab2bdaa0b218c0c2.tar.bz2
camu-60b4ebfbf3be78dba9dc7c65ab2bdaa0b218c0c2.zip
Everything before initial synced list
Signed-off-by: Andrew Opalach <andrew@akon.city>
Diffstat (limited to 'src/fruits')
-rw-r--r--src/fruits/cmc/cmc.c128
-rw-r--r--src/fruits/cmc/cmc.h27
-rw-r--r--src/fruits/cmc/cmc.py17
-rw-r--r--src/fruits/cmc/meson.build15
-rw-r--r--src/fruits/cmc/tui/pager.c88
-rw-r--r--src/fruits/cmc/tui/pager.h33
-rw-r--r--src/fruits/cmc/tui/pane_lists.c93
-rw-r--r--src/fruits/cmc/tui/pane_lists.h20
-rw-r--r--src/fruits/cmc/tui/pane_search.c335
-rw-r--r--src/fruits/cmc/tui/pane_search.h29
-rw-r--r--src/fruits/cmc/tui/resources.c85
-rw-r--r--src/fruits/cmc/tui/resources.h31
-rw-r--r--src/fruits/cmc/tui/ui.c144
-rw-r--r--src/fruits/cmc/tui/ui.h40
-rw-r--r--src/fruits/cmc/tui/view.h24
-rw-r--r--src/fruits/cmc/tui/view_twitter.c370
-rw-r--r--src/fruits/cmc/tui/view_twitter.h35
-rw-r--r--src/fruits/cmsrv/cmsrv.c196
-rw-r--r--src/fruits/cmsrv/meson.build19
-rw-r--r--src/fruits/cmsrv/ui.c210
-rw-r--r--src/fruits/cmsrv/ui.h44
-rw-r--r--src/fruits/cmv/cmv.c151
-rw-r--r--src/fruits/cmv/meson.build25
-rw-r--r--src/fruits/sink/meson.build15
-rw-r--r--src/fruits/sink/sink.c208
25 files changed, 662 insertions, 1720 deletions
diff --git a/src/fruits/cmc/cmc.c b/src/fruits/cmc/cmc.c
deleted file mode 100644
index 5bbdb0b..0000000
--- a/src/fruits/cmc/cmc.c
+++ /dev/null
@@ -1,128 +0,0 @@
-#include <al/lib.h>
-#include <al/log.h>
-#include <al/wstr.h>
-#include <al/random.h>
-#include <aki/common.h>
-#include <aki/event_loop.h>
-
-#include "../../server/common.h"
-
-#include "cmc.h"
-
-static void client_callback(void *userdata, u8 op, void *opaque)
-{
- struct cmc *c = (struct cmc *)userdata;
- switch (op) {
- case CAMU_CLIENT_STATE_UPDATED: {
- switch (c->action) {
- case CMC_ACTION_OPEN: {
- cmc_ui_open(&c->ui, c);
- break;
- }
- case CMC_ACTION_LIST: {
- switch (c->type) {
- case CMC_LIST: {
- struct camu_list *list;
- al_array_foreach(c->client.state.lists, i, list) {
- al_printf("%.*s:\n", AL_STR_PRINTF(&list->name));
- str *unique_id;
- al_array_foreach_ptr(list->entries, j, unique_id) {
- if ((s32)j == list->current) {
- al_printf(" > %.*s\n", AL_STR_PRINTF(unique_id));
- } else {
- al_printf(" %.*s\n", AL_STR_PRINTF(unique_id));
- }
- }
- }
- aki_event_loop_break(&c->loop);
- break;
- }
- case CMC_SEARCH: {
- struct camu_search *search;
- al_array_foreach(c->client.state.searches, i, search) {
- al_printf("%u %x %.*s\n", i, search->id, AL_STR_PRINTF(&search->query));
- }
- aki_event_loop_break(&c->loop);
- break;
- }
- }
- break;
- }
- }
- break;
- }
- case CAMU_CLIENT_SEARCH_CREATED: {
- aki_event_loop_break(&c->loop);
- break;
- }
- case CAMU_CLIENT_RESULTS: {
- cmc_ui_handle_results(&c->ui.search, (struct camu_result_list *)opaque);
- break;
- }
- case CAMU_CLIENT_END_OF_RESULTS: {
- cmc_ui_handle_end_of_results(&c->ui.search, *((s32 *)opaque));
- break;
- }
- case CAMU_CLIENT_DISCONNECTED:
- aki_event_loop_break(&c->loop);
- break;
- }
-}
-
-static struct cmc c = { 0 };
-
-s32 main(s32 argc, char *argv[])
-{
- if (argc < 2 || !aki_common_init()) return EXIT_FAILURE;
-
- str module;
- str query;
-
- str *cmd = al_str_cr(argv[1]);
- if (al_str_eq(cmd, al_str_c("search"))) {
- c.type = CMC_SEARCH;
- if (argc == 2) {
- c.action = CMC_ACTION_LIST;
- } else {
- c.action = CMC_ACTION_CREATE;
- al_str_from(&module, "twitter");
- al_str_from(&query, argv[2]);
- }
- } else if (al_str_eq(cmd, al_str_c("open"))) {
- c.action = CMC_ACTION_OPEN;
- } else if (al_str_eq(cmd, al_str_c("list"))) {
- c.type = CMC_LIST;
- if (argc == 2) {
- c.action = CMC_ACTION_LIST;
- } else {
- c.action = CMC_ACTION_CREATE;
- }
- } else {
- return EXIT_FAILURE;
- }
-
- aki_event_loop_init(&c.loop);
-
- camu_client_init(&c.client, &c.loop, client_callback, &c);
- if (!camu_client_login(&c.client, al_str_c("andrew"), CAMU_SERVER_IP, CAMU_PORT)) {
- goto err;
- }
-
- if (c.action == CMC_ACTION_CREATE) {
- if (c.type == CMC_SEARCH) {
- camu_client_create_search(&c.client, &module, &query);
- }
- }
-
- aki_event_loop_run(&c.loop);
-
- if (c.action == CMC_ACTION_OPEN && c.ui.nc) notcurses_stop(c.ui.nc);
-
- camu_client_close(&c.client);
-
- aki_common_close();
-
- return EXIT_SUCCESS;
-err:
- return EXIT_FAILURE;
-}
diff --git a/src/fruits/cmc/cmc.h b/src/fruits/cmc/cmc.h
deleted file mode 100644
index 625d256..0000000
--- a/src/fruits/cmc/cmc.h
+++ /dev/null
@@ -1,27 +0,0 @@
-#pragma once
-
-#include <al/types.h>
-#include <aki/event_loop.h>
-
-#include "../../libclient/client.h"
-
-#include "tui/ui.h"
-
-enum {
- CMC_LIST = 0,
- CMC_SEARCH
-};
-
-enum {
- CMC_ACTION_OPEN = 0,
- CMC_ACTION_LIST,
- CMC_ACTION_CREATE,
-};
-
-struct cmc {
- u8 type;
- u8 action;
- struct aki_event_loop loop;
- struct camu_client client;
- struct cmc_ui ui;
-};
diff --git a/src/fruits/cmc/cmc.py b/src/fruits/cmc/cmc.py
new file mode 100644
index 0000000..5c0a5b6
--- /dev/null
+++ b/src/fruits/cmc/cmc.py
@@ -0,0 +1,17 @@
+import cffi
+import locale
+from notcurses import notcurses
+
+ffi = cffi.FFI()
+
+print(ffi.string(notcurses.lib.notcurses_version()))
+
+class CMC:
+ def __init__(self):
+ pass
+
+c = CMC()
+
+locale.setlocale(locale.LC_ALL, "")
+nc = notcurses.Notcurses()
+nc.render()
diff --git a/src/fruits/cmc/meson.build b/src/fruits/cmc/meson.build
deleted file mode 100644
index 57ded9c..0000000
--- a/src/fruits/cmc/meson.build
+++ /dev/null
@@ -1,15 +0,0 @@
-cmc_src = [
- 'cmc.c',
- 'tui/ui.c',
- 'tui/resources.c',
- 'tui/pager.c',
- 'tui/view_twitter.c',
- 'tui/pane_lists.c',
- 'tui/pane_search.c'
-]
-cmc_deps = [common_deps, libclient, subproject('stb').get_variable('stb')]
-
-notcurses = dependency('notcurses')
-cmc_deps += [notcurses]
-
-executable('cmc', cmc_src, dependencies: cmc_deps)
diff --git a/src/fruits/cmc/tui/pager.c b/src/fruits/cmc/tui/pager.c
deleted file mode 100644
index a672315..0000000
--- a/src/fruits/cmc/tui/pager.c
+++ /dev/null
@@ -1,88 +0,0 @@
-#include "pager.h"
-
-void cmc_pager_init(struct cmc_pager *pager, u32 page)
-{
- pager->find_anchor = PAGE_NONE;
- pager->anchor = 0;
- pager->redraw = true;
- pager->selected = 0;
- al_array_init(pager->prev);
- pager->max_page_num = page;
- pager->min_page_num = page;
- pager->end_of_page = -1;
- pager->pending_requests = 0;
- al_array_init(pager->pages);
-}
-
-static bool any_pending_requests(struct cmc_view_page *page)
-{
- struct cmc_view *view;
- al_array_foreach(page->views, j, view) {
- if (view->pending_requests > 0) {
- return true;
- }
- }
- return false;
-}
-
-bool cmc_pager_maybe_process_pages(struct cmc_pager *pager, void (*func)(void *, struct cmc_view_page *), void *userdata)
-{
- bool any_page_processed = false;
- struct cmc_view_page *page;
- al_array_foreach(pager->pages, i, page) {
- if (page->processed) continue;
- if (!any_pending_requests(page)) {
- page->processed = true;
- func(userdata, page);
- any_page_processed = true;
- }
- }
- return any_page_processed;
-}
-
-struct cmc_view_page *cmc_get_page_from_num(struct cmc_pager *pager, u32 num)
-{
- struct cmc_view_page *page;
- al_array_foreach(pager->pages, i, page) {
- if (page->num == num) return page;
- }
- return NULL;
-}
-
-struct cmc_view_page *cmc_pager_page_containing_index(struct cmc_pager *pager, u32 index, u32 *offset)
-{
- u32 count = 0, size = 0;
- struct cmc_view_page *page = NULL;
- for (u32 i = pager->min_page_num; i <= pager->max_page_num; i++) {
- if (!(page = cmc_get_page_from_num(pager, i))) continue;
- size = page->views.size;
- count += size;
- if (count > index) {
- if (offset) *offset = size - (count - index);
- return page;
- }
- }
- return NULL;
-}
-
-bool cmc_pager_iterate_to_height(struct cmc_pager *pager, u32 height, u32 anchor, u32 *count,
- void (*func)(void *, struct cmc_view *, u32), void *userdata)
-{
- u32 y = 0, offset;
- struct cmc_view_page *page = cmc_pager_page_containing_index(pager, anchor, &offset);
- if (!page) return false;
- struct cmc_view *view;
- do {
- // If the page is not processed, return out of space as to not display a discontinuity.
- if (!page->processed) return true;
- for (; offset < page->views.size; offset++) {
- view = al_array_at(page->views, offset);
- // If this post would draw past the height, return out of space.
- if (y + view->rows > height) return true;
- if (func) func(userdata, view, y);
- (*count)++;
- y += view->rows + 1;
- }
- } while ((offset = 0, (page = cmc_get_page_from_num(pager, page->num + 1))));
- return false;
-}
diff --git a/src/fruits/cmc/tui/pager.h b/src/fruits/cmc/tui/pager.h
deleted file mode 100644
index 6aeeb9c..0000000
--- a/src/fruits/cmc/tui/pager.h
+++ /dev/null
@@ -1,33 +0,0 @@
-#pragma once
-
-#include <al/types.h>
-#include <al/array.h>
-
-#include "view.h"
-
-enum {
- PAGE_NONE = 0,
- PAGE_UP,
- PAGE_DOWN
-};
-
-struct cmc_pager {
- u8 find_anchor;
- s32 anchor;
- bool redraw;
- u32 count;
- s32 selected;
- array(struct cmc_view *) prev;
- u32 max_page_num;
- u32 min_page_num;
- s32 end_of_page;
- u32 pending_requests;
- array(struct cmc_view_page *) pages;
-};
-
-void cmc_pager_init(struct cmc_pager *pager, u32 page);
-bool cmc_pager_maybe_process_pages(struct cmc_pager *pager, void (*func)(void *, struct cmc_view_page *), void *userdata);
-struct cmc_view_page *cmc_get_page_from_num(struct cmc_pager *pager, u32 num);
-struct cmc_view_page *cmc_pager_page_containing_index(struct cmc_pager *pager, u32 index, u32 *offset);
-bool cmc_pager_iterate_to_height(struct cmc_pager *pager, u32 height, u32 anchor, u32 *count,
- void (*func)(void *, struct cmc_view *, u32), void *userdata);
diff --git a/src/fruits/cmc/tui/pane_lists.c b/src/fruits/cmc/tui/pane_lists.c
deleted file mode 100644
index 6367e57..0000000
--- a/src/fruits/cmc/tui/pane_lists.c
+++ /dev/null
@@ -1,93 +0,0 @@
-#include "../cmc.h"
-
-#include "pane_lists.h"
-#include "ui.h"
-
-void cmc_pane_lists_init(struct cmc_pane_lists *pane, struct cmc_ui *ui)
-{
- pane->offset = 0;
- pane->selected = 0;
- pane->list = al_array_at(ui->c->client.state.lists, 0);
- pane->ui = ui;
-}
-
-bool cmc_pane_lists_handle_input(struct cmc_pane_lists *pane, struct ncinput *input)
-{
- if (!(input->evtype == NCTYPE_PRESS || input->evtype == NCTYPE_UNKNOWN)) return false;
- s32 rows = pane->ui->rows - 1;
- s32 size = (s32)pane->list->entries.size;
- if (input->id == 'j') {
- if (pane->selected + 1 < size) {
- pane->selected++;
- if (pane->selected - pane->offset >= rows) {
- pane->offset += rows;
- }
- return true;
- }
- } else if (input->id == 'k') {
- if (pane->selected - 1 >= 0) {
- pane->selected--;
- if (pane->selected - pane->offset < 0) {
- pane->offset -= rows;
- }
- return true;
- }
- } else if (input->id == 'n') {
- if (pane->offset + rows < size) {
- pane->offset += rows;
- pane->selected += rows;
- if (pane->selected >= size) {
- pane->selected = size - 1;
- }
- return true;
- }
- } else if (input->id == 'p') {
- if (pane->offset - rows >= 0) {
- pane->offset -= rows;
- pane->selected -= rows;
- return true;
- }
- } else if (input->id == NCKEY_RETURN) {
- camu_client_skipto(&pane->ui->c->client, pane->selected);
- }
- return false;
-}
-
-bool cmc_pane_lists_handle_input_overlay(struct cmc_pane_lists *pane, struct ncinput *input)
-{
- (void)pane;
- (void)input;
- return false;
-}
-
-void cmc_pane_lists_draw_overlay(struct cmc_pane_lists *pane)
-{
- (void)pane;
-}
-
-void cmc_pane_lists_refresh(struct cmc_pane_lists *pane)
-{
- struct cmc_ui *ui = pane->ui;
-
- ncplane_cursor_move_yx(ui->p, 0, 0);
- ncplane_erase(ui->p);
- notcurses_refresh(ui->nc, NULL, NULL);
- notcurses_render(ui->nc);
-
- str *unique_id;
- u32 rows = AL_MIN(pane->list->entries.size - pane->offset, ui->rows - 1);
- for (u32 i = pane->offset; i < pane->offset + rows; i++) {
- unique_id = &al_array_at(pane->list->entries, i);
- if ((s32)i == pane->selected) {
- ncplane_set_fg_palindex(ui->p, 4);
- } else {
- ncplane_set_fg_default(ui->p);
- }
- u32 cols = AL_MIN(unique_id->len, ui->cols);
- for (u32 j = 0; j < cols; j++) {
- ncplane_putchar_yx(ui->p, i - pane->offset, j + 1, al_str_at(unique_id, j));
- }
- }
-
- notcurses_render(ui->nc);
-}
diff --git a/src/fruits/cmc/tui/pane_lists.h b/src/fruits/cmc/tui/pane_lists.h
deleted file mode 100644
index 323eb52..0000000
--- a/src/fruits/cmc/tui/pane_lists.h
+++ /dev/null
@@ -1,20 +0,0 @@
-#pragma once
-
-#include <al/types.h>
-#include <notcurses/notcurses.h>
-
-#include "../../libclient/client.h"
-
-struct cmc_ui;
-struct cmc_pane_lists {
- s32 offset;
- s32 selected;
- struct camu_list *list;
- struct cmc_ui *ui;
-};
-
-void cmc_pane_lists_init(struct cmc_pane_lists *pane, struct cmc_ui *ui);
-bool cmc_pane_lists_handle_input(struct cmc_pane_lists *pane, struct ncinput *input);
-bool cmc_pane_lists_handle_input_overlay(struct cmc_pane_lists *pane, struct ncinput *input);
-void cmc_pane_lists_draw_overlay(struct cmc_pane_lists *pane);
-void cmc_pane_lists_refresh(struct cmc_pane_lists *pane);
diff --git a/src/fruits/cmc/tui/pane_search.c b/src/fruits/cmc/tui/pane_search.c
deleted file mode 100644
index 002a4d1..0000000
--- a/src/fruits/cmc/tui/pane_search.c
+++ /dev/null
@@ -1,335 +0,0 @@
-#include <al/lib.h>
-#include <notcurses/nckeys.h>
-
-#include "../cmc.h"
-
-#include "pane_search.h"
-#include "view_twitter.h"
-#include "ui.h"
-
-static void request_page_internal(struct cmc *c, struct cmc_search *search, u32 num)
-{
- search->pager.pending_requests++;
- camu_client_get_page(&c->client, search->search, num);
-}
-
-void cmc_pane_search_init(struct cmc_pane_search *pane, struct cmc_ui *ui)
-{
- pane->ui = ui;
- al_array_init(pane->searches);
- pane->search = NULL;
- struct camu_search *s;
- al_array_foreach(pane->ui->c->client.state.searches, i, s) {
- struct cmc_search *search = al_alloc_object(struct cmc_search);
- search->search = s;
- cmc_pager_init(&search->pager, s->page);
- al_array_push(pane->searches, search);
- }
- if (pane->searches.size > 0) {
- pane->search = al_array_at(pane->searches, 0);
- }
-}
-
-static struct cmc_view *get_view_by_visual_index(struct cmc_pager *pager, s32 index)
-{
- u32 offset;
- struct cmc_view_page *page = cmc_pager_page_containing_index(pager, pager->anchor + index, &offset);
- if (!page || !page->processed) return NULL;
- return al_array_at(page->views, offset);
-}
-
-// Given an index of a post, request the page to the offset of the page containing that post.
-static bool request_page_at_page_offset(struct cmc_pane_search *pane, struct cmc_pager *pager, u32 index, s32 offset)
-{
- struct cmc_view_page *page = cmc_pager_page_containing_index(pager, index, NULL);
- if (page) {
- s32 req = (s32)page->num + offset;
- if (req < 0) return false;
- request_page_internal(pane->ui->c, pane->search, req);
- }
- return true;
-}
-
-static void add_selected_from_index(struct cmc *c, struct cmc_pager *pager, u32 index)
-{
- struct cmc_view *view = get_view_by_visual_index(pager, pager->selected);
- if (view && view->post->media.size > index) {
- camu_client_add(&c->client, &view->post->unique_id, index, true);
- }
-}
-
-#define ON_LAST_PAGE(pager) \
- (pager->end_of_page >= 0 && pager->anchor + pager->count > (u32)pager->end_of_page)
-
-bool cmc_pane_search_handle_input(struct cmc_pane_search *pane, struct ncinput *input)
-{
- struct cmc_pager *pager = &pane->search->pager;
- if (!(input->evtype == NCTYPE_PRESS || input->evtype == NCTYPE_UNKNOWN)) return false;
- if (input->id == 'j') {
- if (pager->selected + 1 >= (s32)pager->count) {
- if (pager->pending_requests > 0 || pager->find_anchor != PAGE_NONE) return false;
- if (ON_LAST_PAGE(pager)) return false;
- if (pager->count == 0) pager->count = 1;
- u32 index = pager->anchor + pager->count;
- pager->find_anchor = PAGE_DOWN;
- struct cmc_view_page *page = cmc_pager_page_containing_index(pager, index, NULL);
- if (!page) {
- request_page_at_page_offset(pane, pager, index - 1, 1);
- return false;
- }
- } else {
- pager->selected++;
- }
- return true;
- } else if (input->id == 'k') {
- if (pager->selected - 1 < 0) {
- if (pager->pending_requests > 0 || pager->find_anchor != PAGE_NONE) return false;
- if (pager->anchor - 1 < 0 && !request_page_at_page_offset(pane, pager, 0, -1)) {
- // The reason to do this here instead of letting it resolve in
- // refresh_pane_pager() is so we can return early if
- // we are at the top of the page.
- return false;
- }
- pager->anchor--;
- pager->find_anchor = PAGE_UP;
- // Wait for the requested previous page to come in.
- if (pager->anchor < 0) return false;
- } else {
- pager->selected--;
- }
- return true;
- } else if (input->id == NCKEY_RETURN || input->id == '1') {
- add_selected_from_index(pane->ui->c, pager, 0);
- } else if (input->id == '2') {
- add_selected_from_index(pane->ui->c, pager, 1);
- } else if (input->id == '3') {
- add_selected_from_index(pane->ui->c, pager, 2);
- } else if (input->id == '4') {
- add_selected_from_index(pane->ui->c, pager, 3);
- }
- return false;
-}
-
-bool cmc_pane_search_handle_input_overlay(struct cmc_pane_search *pane, struct ncinput *input)
-{
- if (!(input->evtype == NCTYPE_PRESS || input->evtype == NCTYPE_UNKNOWN)) return false;
- struct cmc_ui *ui = pane->ui;
- struct cmc_pager *pager = &pane->search->pager;
- if (input->id == NCKEY_TAB) {
- pager->redraw = true;
- } else if (input->id == 'j') {
- if (ui->overlay.selected + 1 < (s32)pane->searches.size) {
- ui->overlay.selected++;
- return true;
- }
- } else if (input->id == 'k') {
- if (ui->overlay.selected - 1 >= 0) {
- ui->overlay.selected--;
- return true;
- }
- } else if (input->id == NCKEY_RETURN) {
- cmc_ui_toggle_overlay(ui);
- struct cmc_view *view;
- al_array_foreach(pager->prev, j, view) {
- ncplane_erase(view->container);
- ncplane_reparent_family(view->container, view->container);
- }
- pager->prev.size = 0;
- pane->search = al_array_at(pane->searches, ui->overlay.selected);
- pager = &pane->search->pager;
- pager->redraw = true;
- return true;
- }
- return false;
-}
-
-void cmc_pane_search_maybe_draw_overlay(struct cmc_pane_search *pane)
-{
- struct cmc_ui *ui = pane->ui;
-
- if (!ui->overlay.active) return;
-
- ncplane_cursor_move_yx(ui->overlay.p, 0, 0);
- ncplane_erase(ui->overlay.p);
-
- u64 color = 0;
- u32 background = 0;
- ncchannel_set_rgb8(&background, 255, 255, 255);
- ncchannel_set_alpha(&background, 255);
- ncchannels_set_fchannel(&color, background);
-
- ncchannel_set_rgb8(&background, 124, 12, 53);
- ncchannels_set_bchannel(&color, background);
- ncchannels_set_fchannel(&color, background);
- ncplane_gradient(ui->overlay.p, 0, 0, ui->overlay.rows, ui->overlay.cols, "", 0, color, color, color, color);
-
- ncplane_set_bg_rgb8(ui->overlay.p, 124, 12, 53);
-
- struct cmc_search *search;
- al_array_foreach(ui->search.searches, i, search) {
- if ((s32)i == ui->overlay.selected) {
- ncplane_set_fg_palindex(ui->overlay.p, 4);
- } else {
- ncplane_set_fg_default(ui->overlay.p);
- }
- str *query = &search->search->query;
- for (u32 j = 0; j < query->len; j++) {
- ncplane_putchar_yx(ui->overlay.p, i, j + 1, al_str_at(query, j));
- }
- }
-}
-
-static void draw_selection_line(struct cmc_view *view, u32 y, u32 rows)
-{
- ncplane_move_yx(view->container, y, 0);
- wchar_t box = L'█';
- u32 k = ((struct cmc_view_twitter *)view)->repost ? 1 : 0;
- for (; k < rows - 1; k++) {
- ncplane_cursor_move_yx(view->container, k, 0);
- ncplane_putwc(view->container, box);
- }
-}
-
-void cmc_pane_search_refresh(struct cmc_pane_search *pane)
-{
- struct cmc_pager *pager = &pane->search->pager;
- if (pager->pending_requests > 0) return;
-
- u32 page = pane->search->search->page;
- if (!cmc_get_page_from_num(pager, page)) {
- request_page_internal(pane->ui->c, pane->search, page);
- return;
- }
-
- if (pager->find_anchor == PAGE_UP) {
- u32 count;
- // If find_anchor = PAGE_UP, search->anchor is index of the requested view.
- s32 anchor = pager->anchor;
- while (anchor >= 0) {
- count = 0;
- cmc_pager_iterate_to_height(pager, pane->ui->rows, anchor, &count, NULL, NULL);
- // TODO: Handle count == 0.
- // Is the requested view still visible?
- if ((s32)(anchor + count) - 1 < pager->anchor) break;
- anchor--;
- // Load more posts if needed.
- if (anchor < 0 && request_page_at_page_offset(pane, pager, 0, -1)) return;
- }
- anchor++; // We want the anchor before the last attempt above.
- pager->selected = abs(pager->anchor - anchor);
- pager->anchor = anchor;
- pager->redraw = true;
- pager->find_anchor = PAGE_NONE;
- } else if (pager->find_anchor == PAGE_DOWN) {
- // When moving down we already know how many views are being displayed
- // so just move the anchor 1 past that.
- pager->selected = 0;
- pager->anchor += pager->count;
- pager->redraw = true;
- pager->find_anchor = PAGE_NONE;
- }
-
- cmc_pane_search_maybe_draw_overlay(pane);
-
- struct cmc_view *view;
- bool out_of_space = true;
- if (pager->redraw) {
- al_array_foreach(pager->prev, j, view) {
- ncplane_erase(view->container);
- ncplane_reparent_family(view->container, view->container);
- }
- pager->prev.size = 0;
- pager->count = 0;
- out_of_space = cmc_pager_iterate_to_height(pager, pane->ui->rows, pager->anchor, &pager->count, cmc_draw_tweet_func, pane->ui);
- } else {
- al_array_foreach(pager->prev, j, view) {
- ncplane_erase(view->container);
- }
- }
- if ((view = get_view_by_visual_index(pager, pager->selected))) {
- draw_selection_line(view, view->y, view->rows);
- }
- if (pager->redraw) {
- pager->redraw = false;
- notcurses_refresh(pane->ui->nc, NULL, NULL);
- }
- notcurses_render(pane->ui->nc);
- if (pager->count > 0 && !out_of_space && pager->end_of_page == -1) {
- request_page_at_page_offset(pane, pager, (pager->anchor + pager->count) - 1, 1);
- }
-}
-
-static struct cmc_search *get_search_from_id(struct cmc_pane_search *pane, s32 id)
-{
- struct cmc_search *search;
- al_array_foreach(pane->searches, i, search) {
- if (search->search->id == id) return search;
- }
- return NULL;
-}
-
-static void process_page_func(void *userdata, struct cmc_view_page *page)
-{
- struct cmc_ui *ui = (struct cmc_ui *)userdata;
- struct cmc_view *view;
- al_array_foreach(page->views, i, view) {
- view->rows = cmc_layout_tweet(ui, (struct cmc_view_twitter *)view, ui->cols);
- cmc_draw_tweet((struct cmc_view_twitter *)view, view->container, view->rows, ui->cols);
- }
-}
-
-void cmc_ui_resources_callback(void *userdata)
-{
- struct cmc_pane_search *pane = (struct cmc_pane_search *)userdata;
- struct cmc_pager *pager = &pane->search->pager;
- if (cmc_pager_maybe_process_pages(pager, process_page_func, pane->ui)) {
- pager->redraw = true;
- cmc_pane_search_refresh(pane);
- }
-}
-
-void cmc_ui_handle_results(struct cmc_pane_search *pane, struct camu_result_list *results)
-{
- struct cmc_search *search = get_search_from_id(pane, results->search->id);
- if (!search) return;
- struct cmc_pager *pager = &search->pager;
- pager->pending_requests--;
-
- struct cmc_view_page *page = al_alloc_object(struct cmc_view_page);
- al_array_push(pager->pages, page);
- page->num = results->num;
- page->processed = false;
- if (page->num > pager->max_page_num) pager->max_page_num = page->num;
- if (page->num < pager->min_page_num) pager->min_page_num = page->num;
- if (pager->find_anchor == PAGE_UP) {
- pager->anchor += results->unique_ids.size;
- if (pager->end_of_page >= 0) {
- pager->end_of_page += results->unique_ids.size;
- }
- }
-
- str *unique_id;
- struct cmc_view_twitter *view;
- al_array_foreach_ptr(results->unique_ids, i, unique_id) {
- view = cmc_create_view_twitter(pane->ui, unique_id);
- al_array_push(page->views, &view->v);
- }
-
- if (cmc_pager_maybe_process_pages(pager, process_page_func, pane->ui)) {
- pager->redraw = true;
- cmc_pane_search_refresh(pane);
- }
-}
-
-void cmc_ui_handle_end_of_results(struct cmc_pane_search *pane, s32 id)
-{
- struct cmc_search *search = get_search_from_id(pane, id);
- if (!search) return;
- struct cmc_pager *pager = &search->pager;
- pager->pending_requests--;
- pager->end_of_page = (pager->anchor + pager->count) - 1;
- if (cmc_pager_maybe_process_pages(pager, process_page_func, pane->ui)) {
- pager->redraw = true;
- cmc_pane_search_refresh(pane);
- }
-}
diff --git a/src/fruits/cmc/tui/pane_search.h b/src/fruits/cmc/tui/pane_search.h
deleted file mode 100644
index b7c5e2c..0000000
--- a/src/fruits/cmc/tui/pane_search.h
+++ /dev/null
@@ -1,29 +0,0 @@
-#pragma once
-
-#include <notcurses/notcurses.h>
-
-#include "../../libclient/client.h"
-
-#include "pager.h"
-
-struct cmc_search {
- struct camu_search *search;
- struct cmc_pager pager;
-};
-
-struct cmc_ui;
-struct cmc_pane_search {
- array(struct cmc_search *) searches;
- struct cmc_search *search;
- struct cmc_ui *ui;
-};
-
-void cmc_pane_search_init(struct cmc_pane_search *pane, struct cmc_ui *ui);
-bool cmc_pane_search_handle_input(struct cmc_pane_search *pane, struct ncinput *input);
-bool cmc_pane_search_handle_input_overlay(struct cmc_pane_search *pane, struct ncinput *input);
-void cmc_pane_search_draw_overlay(struct cmc_pane_search *pane);
-void cmc_pane_search_refresh(struct cmc_pane_search *pane);
-
-void cmc_ui_resources_callback(void *userdata);
-void cmc_ui_handle_results(struct cmc_pane_search *pane, struct camu_result_list *results);
-void cmc_ui_handle_end_of_results(struct cmc_pane_search *pane, s32 id);
diff --git a/src/fruits/cmc/tui/resources.c b/src/fruits/cmc/tui/resources.c
deleted file mode 100644
index 19513bf..0000000
--- a/src/fruits/cmc/tui/resources.c
+++ /dev/null
@@ -1,85 +0,0 @@
-#include <al/log.h>
-#include <al/random.h>
-#define STB_IMAGE_IMPLEMENTATION
-#define STB_IMAGE_STATIC
-#define STBI_NO_STDIO
-#define STBI_NO_FAILURE_STRINGS
-#define STBI_MALLOC al_malloc
-#define STBI_FREE al_free
-#define STBI_REALLOC al_realloc
-#include <stb_image.h>
-
-#include "resources.h"
-#include "view.h"
-
-static void resource_client_callback(void *userdata, u16 id, struct aki_buffer *buffer)
-{
- struct cmc_resources *resources = (struct cmc_resources *)userdata;
- struct cmc_view_resource *resource;
- al_array_foreach(resources->cache, i, resource) {
- if (resource->id == id) {
- s32 w, h, channels;
- u8 *pixels = stbi_load_from_memory(aki_buffer_get_ptr(buffer, 0), buffer->size, &w, &h, &channels, 0);
- if (channels == 3) {
- resource->v = ncvisual_from_rgb_packed(pixels, h, w * channels, w, 255);
- } else if (channels == 4) {
- resource->v = ncvisual_from_rgba(pixels, h, w * channels, w);
- } else {
- al_log_warn("cmc", "Image has unsupported channel count.");
- }
- stbi_image_free(pixels);
- ncvisual_geom(resources->nc, resource->v, &resource->vopts, &resource->geom);
- struct cmc_view *view;
- al_array_foreach(resource->refs, j, view) {
- view->pending_requests--;
- }
- al_array_free(resource->refs);
- break;
- }
- }
- resources->callback(resources->userdata);
-}
-
-bool cmc_resources_connect(struct cmc_resources *resources, struct notcurses *nc,
- struct aki_event_loop *loop, str *addr, s32 port)
-{
- resources->nc = nc;
- resources->pending_requests = 0;
- camu_resource_client_init(&resources->client, loop, resource_client_callback, resources);
- return camu_resource_client_connect(&resources->client, addr, port);
-}
-
-void cmc_resources_set_callback(struct cmc_resources *resources, void (*callback)(void *), void *userdata)
-{
- resources->callback = callback;
- resources->userdata = userdata;
-}
-
-static struct cmc_view_resource *create_resource(struct cmc_resources *resources, str *unique_id, u32 index)
-{
- struct cmc_view_resource *resource = al_alloc_object(struct cmc_view_resource);
- al_array_push(resources->cache, resource);
- resource->vopts.blitter = NCBLIT_PIXEL;
- resource->vopts.scaling = NCSCALE_SCALE;
- resource->id = al_rand_u16();
- al_str_clone(&resource->unique_id, unique_id);
- resource->index = index;
- al_array_init(resource->refs);
- camu_resource_request(&resources->client, unique_id, index, resource->id);
- return resource;
-}
-
-struct cmc_view_resource *cmc_get_resource_from_cache(struct cmc_resources *resources, struct cmc_view *view,
- str *unique_id, u32 index)
-{
- struct cmc_view_resource *resource;
- al_array_foreach(resources->cache, i, resource) {
- if (al_str_eq(&resource->unique_id, unique_id) && resource->index == index) {
- return resource;
- }
- }
- resource = create_resource(resources, unique_id, index);
- view->pending_requests++;
- al_array_push(resource->refs, view);
- return resource;
-}
diff --git a/src/fruits/cmc/tui/resources.h b/src/fruits/cmc/tui/resources.h
deleted file mode 100644
index f12d861..0000000
--- a/src/fruits/cmc/tui/resources.h
+++ /dev/null
@@ -1,31 +0,0 @@
-#pragma once
-
-#include <notcurses/notcurses.h>
-
-#include "../../libclient/resource_client.h"
-
-struct cmc_view;
-struct cmc_view_resource {
- u16 id;
- str unique_id;
- u32 index;
- array(struct cmc_view *) refs;
- struct ncvisual *v;
- struct ncvgeom geom;
- struct ncvisual_options vopts;
-};
-
-struct cmc_resources {
- struct camu_resource_client client;
- u32 pending_requests;
- struct notcurses *nc;
- array(struct cmc_view_resource *) cache;
- void (*callback)(void *);
- void *userdata;
-};
-
-bool cmc_resources_connect(struct cmc_resources *resources, struct notcurses *nc,
- struct aki_event_loop *loop, str *addr, s32 port);
-void cmc_resources_set_callback(struct cmc_resources *resources, void (*callback)(void *), void *userdata);
-struct cmc_view_resource *cmc_get_resource_from_cache(struct cmc_resources *resources, struct cmc_view *view,
- str *unique_id, u32 index);
diff --git a/src/fruits/cmc/tui/ui.c b/src/fruits/cmc/tui/ui.c
deleted file mode 100644
index 2c406bd..0000000
--- a/src/fruits/cmc/tui/ui.c
+++ /dev/null
@@ -1,144 +0,0 @@
-#include "../../server/common.h"
-
-#include "../cmc.h"
-
-#include "ui.h"
-
-/*
-struct nccell ul, ur; nccell_init(&ul); nccell_init(&ur);
-struct nccell ll, lr; nccell_init(&ll); nccell_init(&lr);
-struct nccell hl, vl; nccell_init(&hl); nccell_init(&vl);
-//nccell_set_channels(&hl, color);
-//nccell_set_channels(&vl, color);
-
-ncplane_box(tui->overlay.p, &ul, &ur, &ll, &lr, &hl, &vl, tui->overlay.rows - 1, tui->overlay.cols - 1, 0);
-
-nccell_release(tui->overlay.p, &ul); nccell_release(tui->overlay.p, &ur);
-nccell_release(tui->overlay.p, &ll); nccell_release(tui->overlay.p, &lr);
-nccell_release(tui->overlay.p, &hl); nccell_release(tui->overlay.p, &vl);
-*/
-
-//ncplane_double_box(tui->overlay.p, NCSTYLE_NONE, color, tui->overlay.rows - 1, tui->overlay.cols - 1, 0);
-//ncplane_rounded_box(tui->overlay.p, NCSTYLE_NONE, color, tui->overlay.rows - 1, tui->overlay.cols - 1, 0);
-
-void cmc_ui_toggle_overlay(struct cmc_ui *ui)
-{
- if (ui->overlay.active) {
- ncplane_reparent_family(ui->overlay.p, ui->overlay.p);
- } else {
- ncplane_reparent_family(ui->overlay.p, ui->p);
- }
- ui->overlay.active = !ui->overlay.active;
-}
-
-static void refresh_internal(struct cmc_ui *ui)
-{
- switch (ui->pane) {
- case CMC_PANE_LIBRARY:
- break;
- case CMC_PANE_LISTS:
- cmc_pane_lists_refresh(&ui->lists);
- break;
- case CMC_PANE_BROWSE:
- break;
- case CMC_PANE_SEARCH:
- cmc_pane_search_refresh(&ui->search);
- break;
- }
-}
-
-static void input_poll_callback(void *userdata, s32 revents)
-{
- struct cmc_ui *ui = (struct cmc_ui *)userdata;
- (void)revents;
- struct ncinput input;
- u32 ret;
- bool do_refresh = false;
- do {
- ret = notcurses_get_nblock(ui->nc, &input);
- if (ret == (u32)-1 || ret == 0) break;
- if (input.evtype == NCTYPE_PRESS || input.evtype == NCTYPE_UNKNOWN) {
- if (input.id == 'q') {
- aki_event_loop_break(&ui->c->loop);
- break;
- } else if (input.id == NCKEY_TAB) {
- cmc_ui_toggle_overlay(ui);
- do_refresh = true;
- }
- switch (ui->pane) {
- case CMC_PANE_LIBRARY:
- break;
- case CMC_PANE_LISTS:
- if (ui->overlay.active) {
- do_refresh |= cmc_pane_lists_handle_input_overlay(&ui->lists, &input);
- } else {
- do_refresh |= cmc_pane_lists_handle_input(&ui->lists, &input);
- }
- break;
- case CMC_PANE_BROWSE:
- break;
- case CMC_PANE_SEARCH:
- if (ui->overlay.active) {
- do_refresh |= cmc_pane_search_handle_input_overlay(&ui->search, &input);
- } else {
- do_refresh |= cmc_pane_search_handle_input(&ui->search, &input);
- }
- break;
- }
- }
- } while (1);
- if (do_refresh) refresh_internal(ui);
-}
-
-static s32 resize_cb(struct ncplane *p)
-{
- struct cmc_ui *ui = (struct cmc_ui *)ncplane_userptr(p);
- notcurses_stddim_yx(ui->nc, &ui->rows, &ui->cols);
- ui->cols -= 4;
- ui->rows -= 1;
- struct ncplane_options nopts = { 0 };
- nopts.cols = ui->cols;
- nopts.rows = ui->rows;
- nopts.x = 3;
- nopts.y = 1;
- nopts.flags = 0;
- ui->p = ncplane_create(p, &nopts);
- ui->pane = CMC_PANE_SEARCH;
- ui->overlay.cols = nopts.cols = 35;
- ui->overlay.rows = nopts.rows = 20;
- nopts.x = (ui->cols / 2) - (ui->overlay.cols / 2);
- nopts.y = (ui->rows / 2) - (ui->overlay.rows / 2);
- ui->overlay.p = ncpile_create(ui->nc, &nopts);
- return 0;
-}
-
-bool cmc_ui_open(struct cmc_ui *ui, struct cmc *c)
-{
- if (!(ui->nc = notcurses_init(NULL, stdin))) return false;
-
- ui->c = c;
-
- cmc_pane_lists_init(&ui->lists, ui);
- cmc_pane_search_init(&ui->search, ui);
-
- //if (!cmc_resources_connect(&ui->resources, ui->nc, &ui->c->loop, CAMU_SERVER_IP, CAMU_RESOURCE_PORT)) {
- // return false;
- //}
- cmc_resources_set_callback(&ui->resources, cmc_ui_resources_callback, &ui->search);
-
- aki_poll_init(&ui->input, input_poll_callback, ui);
- aki_poll_set(&ui->input, notcurses_inputready_fd(ui->nc), AKI_POLL_READ);
- aki_poll_start(&ui->input, &c->loop);
-
- ui->overlay.active = false;
- ui->overlay.selected = 0;
-
- struct ncplane *stdplane = notcurses_stdplane(ui->nc);
- ncplane_set_resizecb(stdplane, resize_cb);
- ncplane_set_userptr(stdplane, ui);
- resize_cb(stdplane);
-
- refresh_internal(ui);
-
- return true;
-}
diff --git a/src/fruits/cmc/tui/ui.h b/src/fruits/cmc/tui/ui.h
deleted file mode 100644
index 76ea353..0000000
--- a/src/fruits/cmc/tui/ui.h
+++ /dev/null
@@ -1,40 +0,0 @@
-#pragma once
-
-#include <aki/event_loop.h>
-#include <notcurses/notcurses.h>
-
-#include "resources.h"
-#include "pane_lists.h"
-#include "pane_search.h"
-
-enum {
- CMC_PANE_LIBRARY = 0,
- CMC_PANE_LISTS,
- CMC_PANE_BROWSE,
- CMC_PANE_SEARCH
-};
-
-struct cmc;
-struct cmc_ui {
- struct notcurses *nc;
- u32 cols;
- u32 rows;
- struct ncplane *p;
- u8 pane;
- struct {
- struct ncplane *p;
- u32 cols;
- u32 rows;
- bool active;
- s32 selected;
- } overlay;
- struct aki_poll input;
- struct cmc_resources resources;
- struct cmc_pane_lists lists;
- struct cmc_pane_search search;
- struct cmc *c;
-};
-
-bool cmc_ui_open(struct cmc_ui *ui, struct cmc *c);
-void cmc_ui_toggle_overlay(struct cmc_ui *ui);
-void cmc_ui_maybe_draw_overlay(struct cmc_ui *ui);
diff --git a/src/fruits/cmc/tui/view.h b/src/fruits/cmc/tui/view.h
deleted file mode 100644
index 3c521c3..0000000
--- a/src/fruits/cmc/tui/view.h
+++ /dev/null
@@ -1,24 +0,0 @@
-#pragma once
-
-#include <al/types.h>
-#include <sho/post.h>
-
-enum {
- CMC_VIEW_NONE = 0,
- CMC_VIEW_SINGLE,
- CMC_VIEW_PARTITION
-};
-
-struct cmc_view {
- struct sho_post *post;
- struct ncplane *container;
- u32 pending_requests;
- u32 rows;
- u32 y;
-};
-
-struct cmc_view_page {
- u32 num;
- bool processed;
- array(struct cmc_view *) views;
-};
diff --git a/src/fruits/cmc/tui/view_twitter.c b/src/fruits/cmc/tui/view_twitter.c
deleted file mode 100644
index b1070a4..0000000
--- a/src/fruits/cmc/tui/view_twitter.c
+++ /dev/null
@@ -1,370 +0,0 @@
-#include "../cmc.h"
-
-#include "view_twitter.h"
-
-#define TEXT_OFFSET 10
-
-#define PROFILE_SIZE 5
-#define PREVIEW_OFFSET_WIDTH 26
-
-static u32 count_wrapped_lines(wstr *w, u32 width)
-{
- u32 counter = 0, wraps = 1;
- for (u32 i = 0; i < w->len; i++) {
- wchar_t wc = al_wstr_at(w, i);
- s32 wcwidth = al_wchar_width(wc);
- bool newline = wc == L'\n';
- if (newline || counter + wcwidth > width) {
- wraps++;
- counter = 0;
- }
- if (newline) continue;
- counter += wcwidth;
- }
- return wraps;
-}
-
-u32 cmc_layout_tweet(struct cmc_ui *ui, struct cmc_view_twitter *view, u32 width)
-{
- struct sho_post *post = view->v.post;
- struct sho_post *repost = view->repost;
-
- u32 rows = 1;
-
- view->text.width = width - TEXT_OFFSET;
- view->text.height = 1 + count_wrapped_lines(&post->text, view->text.width);
- view->text.y = repost ? 1 : 0;
- view->text.height += view->text.y;
- rows += view->text.height;
-
- if (view->preview.mode == CMC_VIEW_NONE) {
- return rows;
- }
-
- view->preview.width = view->text.width - 1;
- view->preview.x = TEXT_OFFSET;
- u32 partition_width = view->preview.width / 2;
-
- struct ncplane_options opts = { 0 };
- opts.cols = view->preview.width;
- opts.rows = ncplane_dim_y(notcurses_stdplane(ui->nc)) / 3;
- opts.x = 0;
- opts.y = 0;
- struct ncplane *inter = ncplane_create(notcurses_stdplane(ui->nc), &opts);
-
- u32 left = 0, right = 0;
- struct cmc_view_resource *resource;
- switch (view->preview.mode) {
- case CMC_VIEW_SINGLE: {
- resource = view->preview.resources[0];
- resource->vopts.n = inter;
- ncvisual_geom(ui->nc, resource->v, &resource->vopts, &resource->geom);
- // rcellx == width case very untested.
- left += resource->geom.rcelly + (s32)((resource->geom.rcellx != view->preview.width) ? 1 : -1);
- break;
- }
- case CMC_VIEW_PARTITION: {
- ncplane_resize_simple(inter, opts.rows, partition_width);
-
- resource = view->preview.resources[0];
- resource->vopts.n = inter;
- ncvisual_geom(ui->nc, resource->v, &resource->vopts, &resource->geom);
- left += resource->geom.rcelly + 1;
-
- resource = view->preview.resources[1];
- resource->vopts.n = inter;
- ncvisual_geom(ui->nc, resource->v, &resource->vopts, &resource->geom);
- right += resource->geom.rcelly + 1;
-
- if (view->preview.resources[2]) {
- resource = view->preview.resources[2];
- resource->vopts.n = inter;
- ncvisual_geom(ui->nc, resource->v, &resource->vopts, &resource->geom);
- left += resource->geom.rcelly + 1;
- }
-
- if (view->preview.resources[3]) {
- resource = view->preview.resources[3];
- resource->vopts.n = inter;
- ncvisual_geom(ui->nc, resource->v, &resource->vopts, &resource->geom);
- right += resource->geom.rcelly + 1;
- }
-
- break;
- }
- }
-
- ncplane_destroy(inter);
-
- view->preview.height = AL_MAX(right, left);
-
- return rows + view->preview.height;
-}
-
-static void date_to_relative_date(struct sho_post *post, char *out)
-{
- struct sho_post_date *created = NULL;
- struct sho_post_date *date;
- al_array_foreach_ptr(post->dates, i, date) {
- if (date->type == SHOKI_DATE_CREATED) {
- created = date;
- break;
- }
- }
- if (!created) return;
- f64 seconds = (aki_get_timestamp() / 1000000.0 - created->timestamp);
- s64 minutes = seconds / 60L;
- s64 hours = minutes / 60L;
- s64 days = hours / 24L;
- if (days > 0) {
- al_sprintf(out, "%lud", days);
- } else if (hours > 0) {
- al_sprintf(out, "%luh", hours);
- } else if (minutes > 0) {
- al_sprintf(out, "%lum", minutes);
- } else {
- al_sprintf(out, "%lus", (s64)seconds);
- }
-}
-
-static void number_to_shorthand(s64 number, char *out)
-{
- if (number >= 1000000000) {
- al_sprintf(out, "%.1fB", number / 1000000000.f);
- } else if (number >= 1000000) {
- al_sprintf(out, "%.1fM", number / 1000000.f);
- } else if (number >= 1000) {
- al_sprintf(out, "%.1fK", number / 1000.f);
- } else {
- al_sprintf(out, "%ld", number);
- }
-}
-
-static void draw_tweet_resources(struct cmc_ui *ui, struct cmc_view_twitter *view)
-{
- u32 partition_width = view->preview.width / 2;
- struct cmc_view_resource *resource;
- switch (view->preview.mode) {
- case CMC_VIEW_NONE:
- break;
- case CMC_VIEW_SINGLE: {
- resource = view->preview.resources[0];
- ncplane_resize_simple(view->preview.p[0], resource->geom.rcelly + 1, view->preview.width);
- ncplane_move_yx(view->preview.p[0], view->text.height, view->preview.x);
- resource->vopts.n = view->preview.p[0];
- ncvisual_blit(ui->nc, resource->v, &resource->vopts);
- break;
- }
- case CMC_VIEW_PARTITION: {
- resource = view->preview.resources[0];
- resource->vopts.n = view->preview.p[0];
- ncplane_resize_simple(view->preview.p[0], resource->geom.rcelly + 1, partition_width);
- ncplane_move_yx(view->preview.p[0], view->text.height, view->preview.x);
- ncvisual_blit(ui->nc, resource->v, &resource->vopts);
-
- resource = view->preview.resources[1];
- ncplane_resize_simple(view->preview.p[1], resource->geom.rcelly + 1, partition_width);
- ncplane_move_yx(view->preview.p[1], view->text.height, (view->preview.x + view->preview.resources[0]->geom.rcellx) + 3);
- resource->vopts.n = view->preview.p[1];
- ncvisual_blit(ui->nc, resource->v, &resource->vopts);
-
- if (view->preview.resources[2]) {
- resource = view->preview.resources[2];
- ncplane_resize_simple(view->preview.p[2], resource->geom.rcelly + 1, partition_width);
- ncplane_move_yx(view->preview.p[2], view->preview.resources[0]->geom.rcelly + view->text.height + 2, view->preview.x);
- resource->vopts.n = view->preview.p[2];
- ncvisual_blit(ui->nc, resource->v, &resource->vopts);
- }
-
- if (view->preview.resources[3]) {
- resource = view->preview.resources[3];
- ncplane_resize_simple(view->preview.p[3], resource->geom.rcelly + 1, partition_width);
- ncplane_move_yx(view->preview.p[3], view->preview.resources[1]->geom.rcelly + view->text.height + 2, (view->preview.x + view->preview.resources[2]->geom.rcellx) + 3);
- resource->vopts.n = view->preview.p[3];
- ncvisual_blit(ui->nc, resource->v, &resource->vopts);
- }
-
- break;
- }
- }
-
- if (view->profile.resource && view->profile.resource->v) {
- struct cmc_view_resource *resource = view->profile.resource;
- ncplane_resize_simple(view->profile.p, (PROFILE_SIZE + 1) / 2, PROFILE_SIZE);
- ncplane_move_yx(view->profile.p, view->text.y, PROFILE_SIZE - 2);
- resource->vopts.n = view->profile.p;
- ncvisual_blit(ui->nc, resource->v, &resource->vopts);
- }
-}
-
-void cmc_draw_tweet_func(void *userdata, struct cmc_view *view, u32 y)
-{
- struct cmc_ui *ui = (struct cmc_ui *)userdata;
- struct cmc_search *search = ui->search.search;
- ncplane_reparent_family(view->container, ui->p);
- ncplane_move_yx(view->container, y, 0);
- draw_tweet_resources(ui, (struct cmc_view_twitter *)view);
- view->y = y;
- al_array_push(search->pager.prev, view);
-}
-
-static char format_buf[32];
-
-void cmc_draw_tweet(struct cmc_view_twitter *view, struct ncplane *container, u32 rows, u32 width)
-{
- struct sho_post *post = view->v.post;
- struct sho_post *repost = view->repost;
-
- ncplane_resize_simple(container, rows, width);
-
- ncplane_resize_simple(view->text.p, view->text.height, width);
- ncplane_move_yx(view->text.p, 0, 0);
-
- s32 offset = TEXT_OFFSET;
-
- if (repost) {
- const wchar_t *retweet = L"⮤⮧";
- ncplane_set_styles(view->text.p, NCSTYLE_ITALIC);
- ncplane_putwstr_yx(view->text.p, 0, offset - 3, retweet);
- ncplane_set_styles(view->text.p, NCSTYLE_NONE);
- ncplane_putwstr_yx(view->text.p, 0, offset, &al_wstr_at(&repost->author.display_name, 0));
- }
-
- ncplane_set_styles(view->text.p, NCSTYLE_BOLD);
-
- ncplane_putwstr_yx(view->text.p, view->text.y, offset, &al_wstr_at(&post->author.display_name, 0));
- offset += al_wstr_width(&post->author.display_name) + 1;
-
- ncplane_set_styles(view->text.p, NCSTYLE_NONE);
- ncplane_putwstr_yx(view->text.p, view->text.y, offset, &al_wstr_at(&post->author.username, 0));
- offset += al_wstr_width(&post->author.username) + 1;
-
- ncplane_putwstr_yx(view->text.p, view->text.y, offset, L"∙");
- offset += 2;
-
- date_to_relative_date(post, format_buf);
- ncplane_putstr_yx(view->text.p, view->text.y, offset, format_buf);
-
- u32 text_y = view->text.y + 1, text_x = TEXT_OFFSET;
- for (u32 i = 0; i < post->text.len; i++) {
- wchar_t wc = al_wstr_at(&post->text, i);
- s32 wcwidth = al_wchar_width(wc);
- bool newline = wc == L'\n';
- if (newline || text_x + wcwidth > width) {
- text_y++;
- text_x = TEXT_OFFSET;
- }
- if (newline) continue;
- ncplane_putwc_yx(view->text.p, text_y, text_x, wc);
- text_x += wcwidth;
- }
-
- ncplane_resize_simple(view->stats.p, 1, view->text.width);
- ncplane_move_yx(view->stats.p, rows - 1, TEXT_OFFSET);
-
- u32 part = 10;
- offset = 0;
-
- if (post->comments.set) {
- const wchar_t *comment = L"🗩";
- ncplane_set_styles(view->stats.p, NCSTYLE_ITALIC);
- ncplane_putwstr_yx(view->stats.p, 0, offset, comment);
- ncplane_set_styles(view->stats.p, NCSTYLE_NONE);
- number_to_shorthand(post->comments.i, format_buf);
- ncplane_putstr_yx(view->stats.p, 0, offset + 3, format_buf);
- }
- offset += part;
-
- if (post->reposts.set) {
- const wchar_t *retweet = L"⮤⮧";
- ncplane_set_styles(view->stats.p, NCSTYLE_ITALIC);
- ncplane_putwstr_yx(view->stats.p, 0, offset, retweet);
- ncplane_set_styles(view->stats.p, NCSTYLE_NONE);
- number_to_shorthand(post->reposts.i, format_buf);
- ncplane_putstr_yx(view->stats.p, 0, offset + 3, format_buf);
- }
- offset += part;
-
- if (post->likes.set) {
- //const wchar_t *like = L"❤";
- const wchar_t *like = L"♥";
- ncplane_set_styles(view->stats.p, NCSTYLE_ITALIC);
- ncplane_putwstr_yx(view->stats.p, 0, offset, like);
- ncplane_set_styles(view->stats.p, NCSTYLE_NONE);
- number_to_shorthand(post->likes.i, format_buf);
- ncplane_putstr_yx(view->stats.p, 0, offset + 2, format_buf);
- }
-}
-
-static void get_post_and_maybe_repost(struct cmc_ui *ui, str *unique_id, struct sho_post **post, struct sho_post **repost)
-{
- struct sho_post_cache *cache = &ui->c->client.cache;
- *post = sho_post_cache_get(cache, unique_id);
- *repost = NULL;
- if ((*post)->type == SHOKI_POST_REPOST) {
- *repost = *post;
- *post = sho_post_cache_get(cache, &(*post)->post.unique_id);
- }
-}
-
-static void strip_link_from_tweet(struct sho_post *post)
-{
- wstr *check = al_wstr_c(L"https://t.co/");
- s32 index = al_wstr_rfind(&post->text, L':');
- if (index < 5) return;
- index -= 5; // [h]ttps:// (if matched).
- if (al_wstr_cmp(&post->text, check, index, check->len) == 0) {
- al_wstr_at(&post->text, index) = L'\0';
- post->text.len = index;
- }
-}
-
-struct cmc_view_twitter *cmc_create_view_twitter(struct cmc_ui *ui, str *unique_id)
-{
- struct cmc_view_twitter *view = al_alloc_object(struct cmc_view_twitter);
- get_post_and_maybe_repost(ui, unique_id, &view->v.post, &view->repost);
-
- if (view->v.post->media.size > 0) {
- strip_link_from_tweet(view->v.post);
- }
-
- view->v.rows = 0;
- view->v.y = 0;
- view->v.pending_requests = 0;
-
- struct ncplane_options default_options = { 0 };
- default_options.cols = 1;
- default_options.rows = 1;
- default_options.x = 0;
- default_options.y = 0;
-
- view->v.container = ncpile_create(ui->nc, &default_options);
- view->text.p = ncplane_create(view->v.container, &default_options);
- view->profile.p = ncplane_create(view->v.container, &default_options);
- for (u32 i = 0; i < 4; i++) {
- view->preview.p[i] = ncplane_create(view->v.container, &default_options);
- }
- view->stats.p = ncplane_create(view->v.container, &default_options);
-
- struct sho_post *post = view->v.post;
- view->profile.resource = cmc_get_resource_from_cache(&ui->resources, &view->v, &post->author.unique_id, 0);
- struct sho_post_media *media;
- al_array_foreach_ptr(post->media, i, media) {
- (void)media;
- view->preview.resources[i] = cmc_get_resource_from_cache(&ui->resources, &view->v, &post->unique_id, i);
- }
-
- switch (post->media.size) {
- case 0:
- view->preview.mode = CMC_VIEW_NONE;
- break;
- case 1:
- view->preview.mode = CMC_VIEW_SINGLE;
- break;
- default:
- view->preview.mode = CMC_VIEW_PARTITION;
- break;
- }
-
- return view;
-}
diff --git a/src/fruits/cmc/tui/view_twitter.h b/src/fruits/cmc/tui/view_twitter.h
deleted file mode 100644
index 9792826..0000000
--- a/src/fruits/cmc/tui/view_twitter.h
+++ /dev/null
@@ -1,35 +0,0 @@
-#pragma once
-
-#include "ui.h"
-#include "view.h"
-
-struct cmc_view_twitter {
- struct cmc_view v;
- struct sho_post *repost;
- struct {
- u32 width;
- u32 height;
- u32 y;
- struct ncplane *p;
- } text;
- struct {
- struct ncplane *p;
- struct cmc_view_resource *resource;
- } profile;
- struct {
- u8 mode;
- u32 width;
- u32 height;
- u32 x;
- struct ncplane *p[4];
- struct cmc_view_resource *resources[4];
- } preview;
- struct {
- struct ncplane *p;
- } stats;
-};
-
-struct cmc_view_twitter *cmc_create_view_twitter(struct cmc_ui *ui, str *unique_id);
-void cmc_draw_tweet(struct cmc_view_twitter *view, struct ncplane *container, u32 rows, u32 width);
-u32 cmc_layout_tweet(struct cmc_ui *ui, struct cmc_view_twitter *view, u32 width);
-void cmc_draw_tweet_func(void *userdata, struct cmc_view *view, u32 y);
diff --git a/src/fruits/cmsrv/cmsrv.c b/src/fruits/cmsrv/cmsrv.c
new file mode 100644
index 0000000..08f67dd
--- /dev/null
+++ b/src/fruits/cmsrv/cmsrv.c
@@ -0,0 +1,196 @@
+#define CAMU_LOCAL_SOCKET
+
+#include <al/log.h>
+#include <aki/common.h>
+#ifdef CAMU_LOCAL_SOCKET
+#include <aki/line_processor.h>
+#endif
+#include <aki/timer.h>
+
+#include "../../server/server.h"
+#include "../../server/list.h"
+#include "../../server/common.h"
+#include "../../cache/handlers/cdio.h"
+#include "../../codec/ffmpeg/common.h"
+#ifdef CAMU_LOCAL_SOCKET
+#include "../../server/local_compat.h"
+#endif
+
+#include "ui.h"
+
+struct cmsrv {
+ struct aki_event_loop loop;
+ struct aki_signal quit_signal;
+ struct camu_server server;
+#ifdef CAMU_LOCAL_SOCKET
+ struct {
+ struct aki_socket sock;
+ struct aki_line_processor cli;
+ struct camu_local_compat compat;
+ } local;
+#endif
+ struct cmsrv_ui ui;
+ struct aki_poll input_poll;
+ struct aki_timer render_timer;
+};
+
+#ifdef CAMU_LOCAL_SOCKET
+static u8 server_line_callback(void *userdata, str *line)
+{
+ struct cmsrv *s = (struct cmsrv *)userdata;
+ struct camu_list *list = al_array_at(s->server.lists, 0);
+ if (al_str_eq(line, al_str_c(";PAUSE"))) {
+ lia_list_toggle_pause(&list->impl, LIANA_SEQUENCE_ANY, -1.0);
+ } else if (al_str_eq(line, al_str_c(";NEXT"))) {
+ lia_list_skip(&list->impl, LIANA_SEQUENCE_ANY, 1);
+ } else if (al_str_eq(line, al_str_c(";PREV"))) {
+ lia_list_skip(&list->impl, LIANA_SEQUENCE_ANY, -1);
+ } else if (al_str_eq(line, al_str_c(";SHUFFLE"))) {
+ lia_list_shuffle(&list->impl);
+ } else if (al_str_eq(line, al_str_c(";SORT"))) {
+ lia_list_sort(&list->impl);
+ } else if (al_str_eq(line, al_str_c(";REVERSE"))) {
+ lia_list_reverse(&list->impl);
+ } else if (al_str_eq(line, al_str_c(";CLEAR"))) {
+ lia_list_clear(&list->impl);
+ } else {
+ struct aki_packet *packet = aki_packet_create();
+ aki_packet_write_str(packet, line);
+ camu_server_local_add(&s->server, packet);
+ }
+ return AKI_LINE_PROCESSOR_CONTINUE;
+}
+#endif
+
+static void meta_callback(void *userdata, u8 op, struct lia_list_entry *entry)
+{
+ struct cmsrv *s = (struct cmsrv *)userdata;
+ (void)s;
+ switch (op) {
+ case LIANA_META_PLAYING: {
+ struct camu_server_resource *resource = (struct camu_server_resource *)entry->opaque;
+#ifdef CAMU_HAVE_PORTAL
+ if (resource->post) {
+ al_log_info("server", "now playing: %.*ls.", AL_WSTR_PRINTF(&resource->post->title));
+ } else {
+#endif
+ al_log_info("server", "now playing: %.*s.", AL_STR_PRINTF(&resource->unique_id));
+#ifdef CAMU_HAVE_PORTAL
+ }
+#endif
+ break;
+ }
+ }
+}
+
+static void render_timer_callback(void *userdata, struct aki_timer *timer)
+{
+ struct cmsrv *s = (struct cmsrv *)userdata;
+ (void)timer;
+ cmsrv_ui_render(&s->ui);
+ aki_timer_again(&s->render_timer);
+}
+
+static void input_poll_callback(void *userdata, s32 revents)
+{
+ struct cmsrv *s = (struct cmsrv *)userdata;
+ (void)revents;
+ struct ncinput input;
+ do {
+ if (!cmsrv_ui_read_input(&s->ui, &input)) break;
+ if (input.evtype == NCTYPE_PRESS || input.evtype == NCTYPE_UNKNOWN) {
+ switch (input.id) {
+ case 'q':
+ aki_event_loop_break_one(&s->loop);
+ break;
+ case '1':
+ cmsrv_ui_set_pane(&s->ui, CMSRV_UI_LISTS);
+ break;
+ case '2':
+ cmsrv_ui_set_pane(&s->ui, CMSRV_UI_RESOURCES);
+ break;
+ case '3':
+ cmsrv_ui_set_pane(&s->ui, CMSRV_UI_NODES);
+ break;
+ }
+ }
+ } while (1);
+}
+
+static void quit_signal_callback(void *userdata)
+{
+ struct cmsrv *s = (struct cmsrv *)userdata;
+ aki_event_loop_break_one(&s->loop);
+}
+
+static s32 log_callback(void *userdata, char *message)
+{
+ struct cmsrv *s = (struct cmsrv *)userdata;
+ cmsrv_ui_push_message(&s->ui, message);
+ return al_strlen(message);
+}
+
+static struct cmsrv s = { 0 };
+
+static void sigint_handler(s32 signum)
+{
+ (void)signum;
+ aki_signal_send(&s.quit_signal);
+}
+
+#ifndef _WIN32
+s32 main(s32 argc, char *argv[])
+#else
+s32 wmain(s32 argc, wchar_t **argv)
+#endif
+{
+ (void)argc;
+ (void)argv;
+ if (!aki_common_init() || !cmsrv_ui_init(&s.ui, &s.server)) return EXIT_FAILURE;
+
+ signal(SIGINT, sigint_handler);
+
+ al_set_print(log_callback, &s);
+
+ camu_ff_set_default_log_callback();
+
+ aki_event_loop_init(&s.loop);
+
+ aki_signal_init(&s.quit_signal, quit_signal_callback, &s);
+ aki_signal_start(&s.quit_signal, &s.loop);
+
+ if (!camu_server_init(&s.server, CAMU_LOCAL_TYPE, &s.loop)) return EXIT_FAILURE;
+ camu_server_listen(&s.server, CAMU_LOCAL_ADDR, CAMU_PORT);
+ struct camu_list *list = al_array_at(s.server.lists, 0);
+ list->impl.callback = meta_callback;
+ list->impl.userdata = &s;
+
+#ifdef CAMU_LOCAL_SOCKET
+ s.local.sock.type = AKI_SOCKET_UNIX;
+ aki_socket_init(&s.local.sock);
+ aki_socket_set_blocking(&s.local.sock, false);
+ s.local.cli.callback = server_line_callback;
+ s.local.cli.userdata = &s;
+ aki_line_processor_init(&s.local.cli, al_str_c("\n"));
+ aki_line_processor_open_socket(&s.local.cli, &s.local.sock);
+ if (aki_socket_bind(&s.local.sock, CAMU_UNIX_LOCAL, 0) && aki_socket_listen(&s.local.sock)) {
+ aki_line_processor_run(&s.local.cli, &s.loop);
+ }
+#endif
+
+ aki_poll_init(&s.input_poll, input_poll_callback, &s);
+ aki_poll_set(&s.input_poll, cmsrv_ui_get_input_fd(&s.ui), AKI_POLL_READ);
+ aki_poll_start(&s.input_poll, &s.loop);
+
+ aki_timer_init(&s.render_timer, &s.loop, render_timer_callback, &s);
+ aki_timer_set_repeat(&s.render_timer, AKI_TS_FROM_USEC(100000));
+ aki_timer_again(&s.render_timer);
+
+ aki_event_loop_run(&s.loop);
+
+ cmsrv_ui_close(&s.ui);
+
+ aki_common_close();
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/fruits/cmsrv/meson.build b/src/fruits/cmsrv/meson.build
new file mode 100644
index 0000000..ee8eb91
--- /dev/null
+++ b/src/fruits/cmsrv/meson.build
@@ -0,0 +1,19 @@
+cmsrv_src = ['cmsrv.c']
+cmsrv_deps = [server]
+cmsrv_args = []
+
+if get_option('portal').enabled()
+ cmsrv_deps += [portal]
+endif
+
+use_tui = true
+if use_tui
+ cmsrv_src += ['ui.c']
+ cmsrv_deps += [dependency('notcurses-core')]
+endif
+
+if is_windows and meson.is_cross_build()
+ cmsrv_args += ['-static', '-static-libgcc', '-static-libstdc++', '-municode', '-mwindows']
+endif
+
+executable('cmsrv', cmsrv_src, dependencies: cmsrv_deps, link_args: cmsrv_args)
diff --git a/src/fruits/cmsrv/ui.c b/src/fruits/cmsrv/ui.c
new file mode 100644
index 0000000..48f50ca
--- /dev/null
+++ b/src/fruits/cmsrv/ui.c
@@ -0,0 +1,210 @@
+#include <al/lib.h>
+
+#include "../../server/list.h"
+
+#include "ui.h"
+
+#define LOG_RATIO 1.3
+
+static s32 resize_cb(struct ncplane *p)
+{
+ struct cmsrv_ui *ui = (struct cmsrv_ui *)ncplane_userptr(p);
+ ui->pending_layout = true;
+ return 0;
+}
+
+bool cmsrv_ui_init(struct cmsrv_ui *ui, struct camu_server *server)
+{
+ 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);
+ 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)
+{
+ al_array_push(ui->log.messages, message);
+}
+
+static void layout_log(struct cmsrv_ui *ui, struct ncplane *parent)
+{
+ 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.p = ncplane_create(parent, &nopts);
+}
+
+static void erase_log(struct cmsrv_ui *ui)
+{
+ struct ncplane *p = ui->log.p;
+ ncplane_erase(p);
+}
+
+static void render_log(struct cmsrv_ui *ui)
+{
+ struct ncplane *p = ui->log.p;
+ ncplane_erase(p);
+
+ u32 max_width = ncplane_dim_x(p) - 2;
+ u32 max_height = ncplane_dim_y(p) - 2;
+
+ u64 c = 0;
+ ncchannels_set_fg_default(&c);
+ ncplane_rounded_box(p, NCSTYLE_NONE, c, max_height + 1, max_width + 1, 0);
+
+ u32 size = ui->log.messages.size;
+ u32 index = size > max_height ? size - max_height : 0;
+ for (u32 i = index; i < size; i++) {
+ ncplane_putnstr_yx(p, (i - index) + 1, 1, max_width, al_array_at(ui->log.messages, i));
+ }
+}
+
+static void layout_lists(struct cmsrv_ui *ui, struct ncplane *parent)
+{
+ struct ncplane_options nopts = {
+ .rows = ui->term_rows - (ui->term_rows / LOG_RATIO),
+ .cols = ui->term_cols,
+ .x = 0,
+ .y = 0
+ };
+ ui->lists.p = ncplane_create(parent, &nopts);
+}
+
+static void erase_lists(struct cmsrv_ui *ui)
+{
+ struct ncplane *p = ui->lists.p;
+ ncplane_erase(p);
+}
+
+static void render_lists(struct cmsrv_ui *ui)
+{
+ struct ncplane *p = ui->lists.p;
+
+ u32 max_width = ncplane_dim_x(p);
+ u32 max_height = ncplane_dim_y(p);
+ u32 current_line = 0;
+ s32 entries_per_list = 10;
+
+ struct camu_list *list;
+ al_array_foreach(ui->server->lists, i, list) {
+ if (current_line++ >= max_height) break;
+ char *c_str = al_str_to_c_str(&list->name);
+ ncplane_putnstr_yx(p, i, 0, max_width, c_str);
+ al_free(c_str);
+ struct lia_list *impl = &list->impl;
+ s32 current = (s32)impl->current;
+ s32 index = AL_MAX(current - (entries_per_list / 2), 0);
+ s32 size = (s32)impl->entries.size;
+ s32 end = AL_MIN(index + entries_per_list, size);
+ for (s32 j = index; j < end; j++) {
+ struct lia_list_entry *entry = al_array_at(impl->entries, j);
+ c_str = al_str_to_c_str(&entry->name);
+ u32 y = i + (j - index) + 1;
+ if (j == current) {
+ ncplane_putchar_yx(p, y, 1, '>');
+ ncplane_putnstr_yx(p, y, 3, max_width - 3, c_str);
+ } else {
+ ncplane_putnstr_yx(p, y, 1, max_width - 1, c_str);
+ }
+ al_free(c_str);
+ if (++current_line >= max_height) break;
+ }
+ }
+}
+
+static void layout_nodes(struct cmsrv_ui *ui, struct ncplane *parent)
+{
+ struct ncplane_options nopts = {
+ .rows = ui->term_rows - (ui->term_rows / LOG_RATIO),
+ .cols = ui->term_cols,
+ .x = 0,
+ .y = 0
+ };
+ ui->nodes.p = ncplane_create(parent, &nopts);
+}
+
+static void erase_nodes(struct cmsrv_ui *ui)
+{
+ struct ncplane *p = ui->nodes.p;
+ ncplane_erase(p);
+}
+
+static void render_nodes(struct cmsrv_ui *ui)
+{
+ struct ncplane *p = ui->lists.p;
+
+ u32 max_width = ncplane_dim_x(p);
+ u32 max_height = ncplane_dim_y(p);
+ (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(p, 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.size);
+ ncplane_putnstr_yx(p, i + j + 1, 2, max_width, strbuf);
+ }
+ }
+}
+
+void cmsrv_ui_render(struct cmsrv_ui *ui)
+{
+ if (ui->pending_layout) {
+ ui->pending_layout = false;
+ struct ncplane *stdplane = notcurses_stdplane(ui->nc);
+ layout_log(ui, stdplane);
+ layout_lists(ui, stdplane);
+ layout_nodes(ui, stdplane);
+ }
+ erase_log(ui);
+ erase_lists(ui);
+ erase_nodes(ui);
+ 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);
+}
diff --git a/src/fruits/cmsrv/ui.h b/src/fruits/cmsrv/ui.h
new file mode 100644
index 0000000..08ad956
--- /dev/null
+++ b/src/fruits/cmsrv/ui.h
@@ -0,0 +1,44 @@
+#include <notcurses/notcurses.h>
+#include <al/types.h>
+#include <al/array.h>
+
+#include "../../server/server.h"
+
+enum {
+ CMSRV_UI_LISTS = 0,
+ CMSRV_UI_RESOURCES,
+ CMSRV_UI_NODES
+};
+
+struct cmsrv_ui {
+ struct notcurses *nc;
+ u32 term_cols;
+ u32 term_rows;
+ bool pending_layout;
+ u8 pane;
+ struct {
+ struct ncplane *p;
+ array(char *) messages;
+ } log;
+ struct {
+ struct ncplane *p;
+ } lists;
+ struct {
+ } resources;
+ struct {
+ struct ncplane *p;
+ } nodes;
+ struct camu_server *server;
+};
+
+bool cmsrv_ui_init(struct cmsrv_ui *ui, struct camu_server *server);
+
+s32 cmsrv_ui_get_input_fd(struct cmsrv_ui *ui);
+bool cmsrv_ui_read_input(struct cmsrv_ui *ui, struct ncinput *input);
+
+void cmsrv_ui_set_pane(struct cmsrv_ui *ui, u8 pane);
+
+void cmsrv_ui_push_message(struct cmsrv_ui *ui, char *message);
+void cmsrv_ui_render(struct cmsrv_ui *ui);
+
+void cmsrv_ui_close(struct cmsrv_ui *ui);
diff --git a/src/fruits/cmv/cmv.c b/src/fruits/cmv/cmv.c
new file mode 100644
index 0000000..8eaa8b7
--- /dev/null
+++ b/src/fruits/cmv/cmv.c
@@ -0,0 +1,151 @@
+#include <al/log.h>
+#include <aki/event_loop.h>
+#include <aki/thread.h>
+
+#include "../../sink/desktop.h"
+#include "../../server/common.h"
+#include "../../codec/ffmpeg/common.h"
+#ifndef CAMU_SINK_ONLY
+#include "../../server/server.h"
+#include "../../server/list.h"
+#else
+#include "../../server/common.c"
+#endif
+
+struct cmv {
+ struct aki_event_loop loop;
+ struct camu_desktop desktop;
+#ifndef CAMU_SINK_ONLY
+ struct camu_server server;
+#endif
+};
+
+#ifndef CAMU_SINK_ONLY
+static void meta_callback(void *userdata, u8 op, struct lia_list_entry *entry)
+{
+ struct cmv *c = (struct cmv *)userdata;
+ (void)c;
+ switch (op) {
+ case LIANA_META_PLAYING: {
+ struct camu_server_resource *resource = (struct camu_server_resource *)entry->opaque;
+ al_log_info("server", "now playing: %.*s.", AL_STR_PRINTF(&resource->unique_id));
+ break;
+ }
+ }
+}
+
+static void exit_callback(void *userdata, struct camu_desktop *desktop)
+{
+ struct cmv *c = (struct cmv *)userdata;
+ (void)desktop;
+ camu_server_close(&c->server);
+}
+#endif
+
+static aki_thread_result AKI_THREADCALL event_loop_thread(void *userdata)
+{
+ struct cmv *c = (struct cmv *)userdata;
+ aki_event_loop_run(&c->loop);
+ return 0;
+}
+
+static struct cmv c = { 0 };
+
+static void sigint_handler(int signum)
+{
+ (void)signum;
+ c.desktop.should_quit = 1;
+}
+
+#define failure() do { \
+ goto out; \
+} while (0)
+
+#ifndef _WIN32
+s32 main(s32 argc, char *argv[])
+#else
+s32 wmain(s32 argc, wchar_t **argv)
+#endif
+{
+ if (!aki_common_init() || !stl_global_init(false)) {
+ return EXIT_FAILURE;
+ }
+
+ s32 ret = EXIT_FAILURE;
+
+ signal(SIGINT, sigint_handler);
+
+ camu_ff_set_default_log_callback();
+
+ aki_event_loop_init(&c.loop);
+
+ u8 type;
+ str *addr;
+#ifndef CAMU_SINK_ONLY
+ bool local = argc > 1;
+ if (local) {
+ type = AKI_SOCKET_UNIX; addr = CAMU_UNIX_LOCAL;
+ if (!camu_server_init(&c.server, type, &c.loop)) failure();
+ if (!camu_server_listen(&c.server, addr, CAMU_PORT)) failure();
+ struct camu_list *list = al_array_at(c.server.lists, 0);
+ list->impl.callback = meta_callback;
+ list->impl.userdata = &c;
+ } else {
+ type = CAMU_LOCAL_TYPE;
+ addr = CAMU_LOCAL_ADDR;
+ }
+#else
+ type = AKI_SOCKET_TCP; addr = CAMU_SERVER_IP;
+#endif
+
+#ifndef CAMU_SINK_ONLY
+ if (local) {
+ for (s32 i = 1; i < argc; i++) {
+#ifndef _WIN32
+ str arg = *al_str_cr(argv[i]);
+#else
+ str arg;
+ al_wstr_to_str(al_wstr_cr(argv[i]), &arg);
+#endif
+ struct aki_packet *packet = aki_packet_create();
+ aki_packet_write_str(packet, &arg);
+ camu_server_local_add(&c.server, packet);
+ }
+ }
+#else
+ (void)argc;
+ (void)argv;
+#endif
+
+ if (!camu_desktop_init(&c.desktop, "cmv")) failure();
+#ifndef CAMU_SINK_ONLY
+ if (local) {
+ c.desktop.exit_callback = exit_callback;
+ c.desktop.userdata = &c;
+ }
+#endif
+ if (!camu_desktop_connect(&c.desktop, type, &c.loop, addr, CAMU_PORT)) failure();
+
+ struct aki_thread thread0;
+ aki_thread_create(&thread0, event_loop_thread, &c);
+
+ while (camu_desktop_tick(&c.desktop)) {}
+
+ camu_desktop_stop(&c.desktop);
+ aki_thread_join(&thread0);
+ camu_desktop_free(&c.desktop);
+#ifndef CAMU_SINK_ONLY
+ camu_server_free(&c.server);
+#endif
+
+ ret = EXIT_SUCCESS;
+out:
+ aki_event_loop_destroy(&c.loop);
+
+ camu_ff_free_default_log_callback();
+
+ stl_global_close();
+ aki_common_close();
+
+ return ret;
+}
diff --git a/src/fruits/cmv/meson.build b/src/fruits/cmv/meson.build
new file mode 100644
index 0000000..ba571c3
--- /dev/null
+++ b/src/fruits/cmv/meson.build
@@ -0,0 +1,25 @@
+cmv_src = ['cmv.c']
+cmv_deps = [desktop]
+cmv_args = []
+if get_option('sink-only')
+ cmv_args += ['-DCAMU_SINK_ONLY']
+else
+ cmv_deps += [server]
+endif
+
+if get_option('portal').enabled()
+ cmv_deps += [portal]
+endif
+
+use_tui = false
+if use_tui
+ cmv_src += []
+ cmv_deps += [dependency('notcurses')]
+endif
+
+cmv_link_args = []
+if is_windows and meson.is_cross_build()
+ cmv_link_args += ['-static', '-static-libgcc', '-static-libstdc++', '-municode', '-mwindows']
+endif
+
+executable('cmv', cmv_src, dependencies: cmv_deps, c_args: cmv_args, link_args: cmv_link_args)
diff --git a/src/fruits/sink/meson.build b/src/fruits/sink/meson.build
deleted file mode 100644
index 1dd9140..0000000
--- a/src/fruits/sink/meson.build
+++ /dev/null
@@ -1,15 +0,0 @@
-sink_src = ['sink.c']
-sink_deps = [common_deps, buffer, render, screen, mixer, libsink]
-sink_args = []
-
-use_tui = false
-if use_tui
- sink_src += []
- sink_deps += [dependency('notcurses')]
-endif
-
-if is_windows and meson.is_cross_build()
- sink_args += ['-static', '-static-libgcc', '-static-libstdc++', '-municode', '-mwindows']
-endif
-
-executable('sink', sink_src, dependencies: sink_deps, link_args: sink_args)
diff --git a/src/fruits/sink/sink.c b/src/fruits/sink/sink.c
deleted file mode 100644
index 2b3d451..0000000
--- a/src/fruits/sink/sink.c
+++ /dev/null
@@ -1,208 +0,0 @@
-#include <al/log.h>
-#include <aki/event_loop.h>
-
-#include "../../mixer/audio_miniaudio.h"
-
-#include "../../screen/screen.h"
-#include "../../render/renderer_libplacebo.h"
-//#include "../../render/renderer_tiger.h"
-
-#include "../../libsink/sink.h"
-#include "../../server/common.h"
-
-struct cmv {
- s32 quit;
- struct camu_screen scr;
- struct camu_renderer *renderer;
- struct camu_mixer mixer;
- struct aki_event_loop loop;
- struct camu_sink sink;
-};
-
-static u8 sink_callback(void *userdata, u8 op, u8 type, void *opaque)
-{
- struct cmv *c = (struct cmv *)userdata;
- switch (op) {
- case CAMU_SINK_ADD_BUFFER:
- switch (type) {
- case CAMU_SINK_AUDIO: {
- struct camu_audio_buffer *buf = (struct camu_audio_buffer *)opaque;
- camu_mixer_add_buffer(&c->mixer, buf);
- al_log_debug("cmv", "Audio buffer added.");
- break;
- }
- case CAMU_SINK_VIDEO: {
- struct camu_video_buffer *buf = (struct camu_video_buffer *)opaque;
- camu_screen_add_buffer(&c->scr, buf);
- al_log_debug("cmv", "Video buffer added.");
- break;
- }
- }
- break;
- case CAMU_SINK_REMOVE_BUFFER:
- switch (type) {
- case CAMU_SINK_AUDIO: {
- struct camu_audio_buffer *buf = (struct camu_audio_buffer *)opaque;
- camu_mixer_remove_buffer(&c->mixer, buf);
- al_log_debug("cmv", "Audio buffer removed.");
- break;
- }
- case CAMU_SINK_VIDEO: {
- struct camu_video_buffer *buf = (struct camu_video_buffer *)opaque;
- camu_screen_remove_buffer(&c->scr, buf);
- al_log_debug("cmv", "Video buffer removed.");
- break;
- }
- }
- break;
- case CAMU_SINK_SWAP_BUFFER:
- switch (type) {
- case CAMU_SINK_AUDIO: {
- break;
- }
- }
- break;
- case CAMU_SINK_SET_BUFFERED: {
- break;
- }
- case CAMU_SINK_START:
- switch (type) {
- case CAMU_SINK_AUDIO: {
- camu_mixer_resume(&c->mixer);
- al_log_debug("cmv", "Audio started.");
- break;
- }
- case CAMU_SINK_VIDEO: {
- camu_screen_set_state(&c->scr, CAMU_SCREEN_PLAYING);
- camu_screen_wake(&c->scr);
- al_log_debug("cmv", "Video started.");
- break;
- }
- }
- break;
- case CAMU_SINK_STOP:
- switch (type) {
- case CAMU_SINK_AUDIO: {
- camu_mixer_pause(&c->mixer);
- al_log_debug("cmv", "Audio stopped.");
- break;
- }
- case CAMU_SINK_VIDEO: {
- camu_screen_set_state(&c->scr, CAMU_SCREEN_PAUSED);
- al_log_debug("cmv", "Video stopped.");
- break;
- }
- }
- break;
- case CAMU_SINK_EXIT:
- camu_sink_close(&c->sink);
- break;
- }
- return CAMU_SINK_OK;
-}
-
-static void screen_callback(void *userdata, u8 op, f64 float0)
-{
- struct cmv *c = (struct cmv *)userdata;
- switch (op) {
- case CAMU_SCREEN_NEXT:
- camu_sink_skip(&c->sink, 1);
- break;
- case CAMU_SCREEN_PREVIOUS:
- camu_sink_skip(&c->sink, -1);
- break;
- case CAMU_SCREEN_TOGGLE_PAUSE:
- camu_sink_toggle_pause(&c->sink);
- break;
- case CAMU_SCREEN_SEEK:
- camu_sink_seek(&c->sink, float0);
- break;
- case CAMU_SCREEN_RESEEK:
- camu_sink_reseek(&c->sink);
- break;
- case CAMU_SCREEN_CLOSE:
- c->quit = 1;
- break;
- }
-}
-
-static aki_thread_result AKI_THREADCALL event_loop_thread(void *userdata)
-{
- struct cmv *c = (struct cmv *)userdata;
- aki_event_loop_init(&c->loop);
- camu_sink_init(&c->sink, &c->loop, &c->mixer, c->renderer);
- c->sink.callback = sink_callback;
- c->sink.userdata = c;
- if (camu_sink_connect(&c->sink, CAMU_SERVER_IP, CAMU_PORT)) {
- aki_event_loop_run(&c->loop);
- }
- aki_event_loop_destroy(&c->loop);
- return 0;
-}
-
-static struct cmv c = { 0 };
-
-static void sigint_handler(s32 signum)
-{
- (void)signum;
- c.quit = 1;
-}
-
-#ifndef _WIN32
-s32 main(s32 argc, char *argv[])
-#else
-s32 wmain(s32 argc, wchar_t **argv)
-#endif
-{
- (void)argc;
- (void)argv;
- if (!aki_common_init()) return EXIT_FAILURE;
-
- signal(SIGINT, sigint_handler);
-
- c.scr.callback = screen_callback;
- c.scr.userdata = &c;
- if (!camu_screen_init(&c.scr) || !camu_screen_create_window(&c.scr, "cmv")) {
- goto err;
- }
- c.renderer = camu_renderer_lp_create();
- //c.renderer = camu_renderer_tiger_create();
- if (!camu_screen_create_renderer(&c.scr, c.renderer)) {
- goto err;
- }
- c.renderer->render(c.renderer, &c.scr);
-
- if (!camu_mixer_init(&c.mixer, (struct camu_audio *)&audio_plugin_miniaudio)) {
- goto err;
- }
-
- struct aki_thread thread0;
- aki_thread_create(&thread0, event_loop_thread, &c);
-
- c.quit = 0;
-
- while (!c.quit) {
- if (camu_screen_tick(&c.scr)) {
- c.renderer->render(c.renderer, &c.scr);
- }
- }
-
- camu_screen_set_state(&c.scr, CAMU_SCREEN_CLOSED);
-
- camu_sink_stop(&c.sink);
-
- aki_thread_join(&thread0);
-
- camu_sink_free(&c.sink);
-
- camu_mixer_close(&c.mixer);
- c.renderer->free(&c.renderer);
- camu_screen_close(&c.scr);
-
- aki_common_close();
-
- return EXIT_SUCCESS;
-err:
- aki_common_close();
- return EXIT_FAILURE;
-}