M hlog/CMakeLists.txt +1 -0
@@ 61,6 61,7 @@ add_executable(hlog-contest
contest/save.c
contest/script.c
contest/ui-current.c
+ contest/ui-net.c
contest/ui-work.c
contest/ui.c
contest/wsjtx.c
M hlog/contest/contest.h +11 -0
@@ 24,6 24,7 @@
#define __CONTEST_H
#include <hlog/qso.h>
+#include <hlog-rpc/announce-sub.h>
#include <hlog-lua/xlua.h>
#include "../common/recent.h"
@@ 32,6 33,8 @@
#define ANNOUNCE_INFO_PERIOD 10 /* s */
+#define NET_LIST_LINES 5 /* number of network window lines */
+
struct contest_params {
struct str *name;
uint32_t year;
@@ 71,6 74,14 @@ extern void callout_destroy(void);
extern void callout_set(const char *str);
/*
+ * network window
+ */
+extern int net_init(int height, int width, int row, int col);
+extern void net_destroy(void);
+extern void net_refresh(void);
+extern void net_update_row(const struct instance_info *inst);
+
+/*
* message window
*/
enum msg_src {
A => hlog/contest/ui-net.c +181 -0
@@ 0,0 1,181 @@
+/*
+ * Copyright (c) 2024 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <jeffpc/mem.h>
+
+#include <hlog/util.h>
+
+#include "contest.h"
+
+static LOCK_CLASS(lines_lc);
+static struct lock lines_lock;
+static WINDOW *lines_win;
+static size_t nrows;
+static size_t lwidth;
+
+struct line {
+ uint64_t ts;
+ size_t row;
+ uint32_t id;
+ char buf[];
+};
+static struct line **lines;
+
+static void __attribute__((constructor)) net_init_mutex(void)
+{
+ MXINIT(&lines_lock, &lines_lc);
+}
+
+int net_init(int height, int width, int row, int col)
+{
+ size_t i;
+
+ lines = mem_recallocarray(NULL, 0, height, sizeof(struct line *));
+ if (!lines)
+ return -ENOMEM;
+
+ for (i = 0; i < height; i++) {
+ lines[i] = zalloc(sizeof(struct line) + width + 1);
+ ASSERT3P(lines[i], !=, NULL);
+
+ lines[i]->row = i;
+ }
+
+ lines_win = newwin(height, width, row, col);
+ nrows = height;
+ lwidth = width;
+
+ return 0;
+}
+
+void net_destroy(void)
+{
+ size_t i;
+
+ /*
+ * destroying even though it was initialized by the constructor to
+ * catch use-after-destory of the net window & data
+ */
+ MXDESTROY(&lines_lock);
+
+ delwin(lines_win);
+
+ for (i = 0; i < nrows; i++)
+ free(lines[i]);
+ free(lines);
+}
+
+void net_refresh(void)
+{
+ const uint64_t now = gettime();
+ size_t i;
+
+ MXLOCK(&lines_lock);
+ for (i = 0; i < nrows; i++) {
+ struct line *line = lines[i];
+ const char *age;
+ char age_buf[7];
+
+ /* calculate the age */
+ if (!line->ts || (line->ts > now)) {
+ age = "-";
+ } else {
+ const uint64_t delta = now - line->ts;
+ unsigned secs;
+
+ /* whole seconds */
+ secs = delta / 1000000000ull;
+
+ /* round up to the nearest second */
+ secs += ((delta % 1000000000ull) >= 500000000ull);
+
+ if (secs >= (100 * 60)) {
+ age = ">=100m";
+ } else if (secs > 59) {
+ snprintf(age_buf, sizeof(age_buf), "%um%us ",
+ secs / 60, secs % 60);
+ age = age_buf;
+ } else {
+ snprintf(age_buf, sizeof(age_buf), "%us ",
+ secs);
+ age = age_buf;
+ }
+ }
+
+ mvwprintw(lines_win, i, 0, "%s", lines[i]->buf);
+ mvwprintw(lines_win, i, lwidth - 5 - 1, "%s", age);
+ }
+ MXUNLOCK(&lines_lock);
+
+ wrefresh(lines_win);
+}
+
+void net_update_row(const struct instance_info *inst)
+{
+ const char *tx_mode;
+ const char *rx_mode;
+ char tx_freq[11];
+ char rx_freq[11];
+ char power[6];
+ struct line *line;
+ size_t i;
+
+ MXLOCK(&lines_lock);
+
+ for (i = 0, line = NULL; i < nrows; i++) {
+ if (lines[i]->id == inst->id) {
+ line = lines[i];
+ goto found;
+ }
+
+ if (!lines[i]->id && !line)
+ line = lines[i];
+ }
+
+ /* no instance id match found */
+
+ if (!line)
+ goto out; /* no free entries; just ignore this announcement */
+
+ line->id = inst->id;
+
+found:
+ pretty_print_power(power, sizeof(power), VAL_ALLOC_INT(inst->rig.power));
+ pretty_print_freq(tx_freq, sizeof(tx_freq), VAL_ALLOC_INT(inst->rig.tx_freq));
+ pretty_print_freq(rx_freq, sizeof(rx_freq), VAL_ALLOC_INT(inst->rig.rx_freq));
+
+ tx_mode = (inst->rig.tx_mode == HRIG_MODE_UNKNOWN) ? "?" :
+ hamlib_mode_name_raw(inst->rig.tx_mode);
+ rx_mode = (inst->rig.rx_mode == HRIG_MODE_UNKNOWN) ? "?" :
+ hamlib_mode_name_raw(inst->rig.rx_mode);
+
+ /* generate line */
+ snprintf(line->buf, lwidth, "%-11s %-11s %10s %-6s %-5s %10s %-6s",
+ inst->hostname, inst->op,
+ tx_freq, tx_mode, power,
+ rx_freq, rx_mode);
+
+ line->ts = gettime();
+
+out:
+ MXUNLOCK(&lines_lock);
+}
M hlog/contest/ui.c +17 -1
@@ 78,6 78,10 @@
#define RECENT_WIN_ROW (RECENT_WORK_WIN_ROW + RECENT_WORK_WIN_HEIGHT + 1)
#define RECENT_WIN_COL 0
#define RECENT_WIN_HEIGHT (CURRENT_WIN_ROW - RECENT_WIN_ROW)
+#define NET_WIN_HEIGHT NET_LIST_LINES
+#define NET_WIN_WIDTH RIGHT_PANEL_WIDTH
+#define NET_WIN_ROW (MESSAGES_WIN_ROW - NET_WIN_HEIGHT - 1)
+#define NET_WIN_COL (LEFT_PANEL_WIDTH + 1)
#define MESSAGES_WIN_HEIGHT (LINES / 2)
#define MESSAGES_WIN_WIDTH RIGHT_PANEL_WIDTH
#define MESSAGES_WIN_ROW (LINES - MESSAGES_WIN_HEIGHT)
@@ 701,6 705,12 @@ static void setup_windows(void)
xwhline(stdscr, RECENT_WIN_ROW - 1, 0, RECENT_WIN_WIDTH + 1,
ACS_HLINE, ACS_HLINE, ACS_RTEE);
+ /* net separator */
+ xwhline(stdscr, NET_WIN_ROW - 1, NET_WIN_COL - 1, NET_WIN_WIDTH + 1,
+ ACS_LTEE, ACS_HLINE, ACS_HLINE);
+ mvwprintw(stdscr, NET_WIN_ROW - 1, NET_WIN_COL + 1,
+ " Network ");
+
/* messages separator */
xwhline(stdscr, MESSAGES_WIN_ROW - 1, MESSAGES_WIN_COL - 1,
MESSAGES_WIN_WIDTH + 1, ACS_LTEE, ACS_HLINE, ACS_HLINE);
@@ 758,6 768,10 @@ static void setup_windows(void)
cfields, ARRAY_LEN(cfields));
ASSERT0(ret);
+ /* net */
+ ret = net_init(NET_WIN_HEIGHT, NET_WIN_WIDTH, NET_WIN_ROW, NET_WIN_COL);
+ ASSERT0(ret);
+
/* messages */
messages_win = newwin(MESSAGES_WIN_HEIGHT, MESSAGES_WIN_WIDTH,
MESSAGES_WIN_ROW, MESSAGES_WIN_COL);
@@ 773,6 787,7 @@ static void refresh_windows(void)
qso_get_station_call(qso_get_rx_side(contact)));
recent_refresh(&recent_list, NULL);
xform_refresh(¤t);
+ net_refresh();
wrefresh(messages_win);
/* move cursor to the active form */
@@ 872,7 887,7 @@ int contest_init(struct contest_params *
goto err_template;
}
- ret = announce_connect(NULL,
+ ret = announce_connect(net_update_row,
handle_qso_notification,
handle_location_notification,
handle_rig_notification);
@@ 1009,6 1024,7 @@ int contest_run(lua_State *L, struct xfi
recent_destroy(&recent_work_list);
recent_destroy(&recent_list);
status_deinit();
+ net_destroy();
delwin(messages_win);
endwin();