diff options
Diffstat (limited to 'src/fruits/cmsrv')
| -rw-r--r-- | src/fruits/cmsrv/cmsrv.c | 196 | ||||
| -rw-r--r-- | src/fruits/cmsrv/meson.build | 19 | ||||
| -rw-r--r-- | src/fruits/cmsrv/ui.c | 210 | ||||
| -rw-r--r-- | src/fruits/cmsrv/ui.h | 44 |
4 files changed, 469 insertions, 0 deletions
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); |