From 4c8f7cffcc27be86dff2071a25e42cde6dc0b530 Mon Sep 17 00:00:00 2001 From: Michael Tremer Date: Thu, 19 Sep 2024 12:53:40 +0000 Subject: [PATCH] tui: Add scaffolding for a basic TUI Signed-off-by: Michael Tremer --- Makefile.am | 2 + src/ctx.h | 4 + src/main.c | 24 ++++++ src/tui.c | 217 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/tui.h | 34 ++++++++ 5 files changed, 281 insertions(+) create mode 100644 src/tui.c create mode 100644 src/tui.h diff --git a/Makefile.am b/Makefile.am index d920979..4c320b1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -75,6 +75,8 @@ fireperf_SOURCES = \ src/server.h \ src/stats.c \ src/stats.h \ + src/tui.c \ + src/tui.h \ src/util.c \ src/util.h \ src/worker.c \ diff --git a/src/ctx.h b/src/ctx.h index e8fceb1..2bc601f 100644 --- a/src/ctx.h +++ b/src/ctx.h @@ -25,11 +25,15 @@ #include "constants.h" #include "stats.h" +#include "tui.h" // Forward declarations struct fireperf_worker; struct fireperf_ctx { + // TUI + struct fireperf_tui* tui; + int terminated; int loglevel; enum { diff --git a/src/main.c b/src/main.c index d03017b..c726237 100644 --- a/src/main.c +++ b/src/main.c @@ -28,6 +28,8 @@ #include #include +#include + #include "client.h" #include "main.h" #include "logging.h" @@ -98,6 +100,7 @@ int main(int argc, char* argv[]) { void* data = NULL; int epollfd = -1; int timerfd = -1; + int stdinfd = -1; int ready; int r; @@ -117,6 +120,11 @@ int main(int argc, char* argv[]) { goto ERROR; } + // Setup the TUI + r = fireperf_tui_init(&ctx->tui, ctx); + if (r) + goto ERROR; + // Set limits r = set_limits(ctx); if (r) @@ -130,6 +138,13 @@ int main(int argc, char* argv[]) { goto ERROR; } + // Register the TUI with the event loop + stdinfd = fireperf_tui_register(ctx->tui, epollfd); + if (stdinfd < 0) { + ERROR(ctx, "Could not register the TUI: %s\n", strerror(-stdinfd)); + goto ERROR; + } + // Create a timer that fires once a second timerfd = setup_timer(ctx, epollfd); if (timerfd < 0) { @@ -187,6 +202,12 @@ int main(int argc, char* argv[]) { if (r) goto ERROR; + // Handle user input + } else if (fd == stdinfd) { + r = fireperf_tui_action(ctx->tui); + if (r) + goto ERROR; + // Handle everything else } else { switch (ctx->mode) { @@ -210,6 +231,9 @@ int main(int argc, char* argv[]) { } ERROR: + // Terminate the TUI + fireperf_tui_finish(ctx->tui); + switch (ctx->mode) { case FIREPERF_MODE_CLIENT: fireperf_client_free(ctx, data); diff --git a/src/tui.c b/src/tui.c new file mode 100644 index 0000000..20540d7 --- /dev/null +++ b/src/tui.c @@ -0,0 +1,217 @@ +/*############################################################################# +# # +# fireperf - A network benchmarking tool # +# Copyright (C) 2024 IPFire Development Team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +#############################################################################*/ + +#include +#include +#include +#include +#include + +#include + +#include "ctx.h" +#include "tui.h" + +struct fireperf_tui { + struct fireperf_ctx* ctx; + + // The main screen + WINDOW* screen; + + // The frame around the screen + WINDOW* frame; + + // The status bar + WINDOW* status; +}; + +static int fireperf_tui_setup_frame(struct fireperf_tui* tui) { + int r; + + // Create the window + tui->frame = newwin(LINES - 1, COLS, 0, 0); + if (!tui->frame) + return 1; + + // Make my brush blue + wattron(tui->frame, COLOR_PAIR(3)); + + // Draw a box around the frame + box(tui->frame, 0, 0); + + // Reset my brush + wattroff(tui->frame, COLOR_PAIR(3)); + + // Refresh + wrefresh(tui->frame); + + return 0; +} + +static int fireperf_tui_setup_status(struct fireperf_tui* tui) { + int r; + + // Create the window + tui->status = newwin(1, COLS, LINES - 1, 0); + if (!tui->status) + return 1; + + // Create status line + move(LINES - 1, 0); + + const char* help = "Press 'q' to quit"; + + // Write in black & white + wattron(tui->status, COLOR_PAIR(4)); + + // Push the help text to the right + for (int i = 0; i < COLS - strlen(help); i++) + waddch(tui->status, ' '); + + // Write the help text + waddstr(tui->status, help); + + // Reset the colour + wattroff(tui->status, COLOR_PAIR(4)); + + // Refresh + wrefresh(tui->status); + + return 0; +} + +static int fireperf_tui_setup(struct fireperf_tui* tui) { + int r; + + // Start ncurses + tui->screen = initscr(); + if (!tui->screen) + return -errno; + + // We would like to see colours + start_color(); + + // Configure a few colours + init_pair(1, COLOR_RED, COLOR_BLACK); + init_pair(2, COLOR_YELLOW, COLOR_BLACK); + init_pair(3, COLOR_BLUE, COLOR_BLACK); + init_pair(4, COLOR_BLACK, COLOR_WHITE); + + // Enable raw mode + raw(); + + // Disable character echoing + noecho(); + + // Set the cursor to the top + curs_set(0); + + // Configure the keyboard + keypad(stdscr, 1); + + // ??? + halfdelay(1); + + // Refresh! + refresh(); + + // Setup the frame + r = fireperf_tui_setup_frame(tui); + if (r) + return r; + + // Setup the status bar + r = fireperf_tui_setup_status(tui); + if (r) + return r; + + // Refresh! + refresh(); + + return 0; +} + +int fireperf_tui_init(struct fireperf_tui** tui, struct fireperf_ctx* ctx) { + struct fireperf_tui* t = NULL; + int r; + + // Allocate the TUI + t = calloc(1, sizeof(*t)); + if (!t) + return -errno; + + // Store a reference to the context + t->ctx = ctx; + + // Perform the basic setup + r = fireperf_tui_setup(t); + if (r) + return r; + + return 0; +} + +void fireperf_tui_finish(struct fireperf_tui* tui) { + endwin(); + + free(tui); +} + +int fireperf_tui_register(struct fireperf_tui* tui, int epollfd) { + const int fd = STDIN_FILENO; + int r; + + struct epoll_event ev = { + .events = EPOLLIN|EPOLLET, + .data.fd = fd, + }; + + // Register the timer with the event loop + r = epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev); + if (r) + return -errno; + + return fd; +} + +/* + Called when the user presses a button +*/ +int fireperf_tui_action(struct fireperf_tui* tui) { + int c; + + for (;;) { + // Fetch the next character + c = getch(); + if (c < 0) + break; + + switch (c) { + // Quit + case 'q': + return -ESHUTDOWN; + + default: + break; + } + } + + return 0; +} diff --git a/src/tui.h b/src/tui.h new file mode 100644 index 0000000..6c82266 --- /dev/null +++ b/src/tui.h @@ -0,0 +1,34 @@ +/*############################################################################# +# # +# fireperf - A network benchmarking tool # +# Copyright (C) 2024 IPFire Development Team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +#############################################################################*/ + +#ifndef FIREPERF_TUI_H +#define FIREPERF_TUI_H + +struct fireperf_tui; + +#include "ctx.h" + +int fireperf_tui_init(struct fireperf_tui** tui, struct fireperf_ctx* ctx); +void fireperf_tui_finish(struct fireperf_tui* tui); + +int fireperf_tui_register(struct fireperf_tui* tui, int epollfd); +int fireperf_tui_action(struct fireperf_tui* tui); + +#endif /* FIREPERF_TUI_H */ -- 2.47.2