diff --git a/AGENTS.md b/AGENTS.md index c464ef86..a77c31b6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,7 +12,6 @@ This file contains essential information for AI coding agents working in the uTu ## 🔧 Build Commands ### Full Build -win (msys2 ucrt64): build.bat ```bash ./autogen.sh # Generate configure script (if needed) ./configure # Configure build diff --git a/src/control_server.c b/src/control_server.c index 9793855e..b786dcfc 100644 --- a/src/control_server.c +++ b/src/control_server.c @@ -238,7 +238,7 @@ void control_server_shutdown(struct control_server* server) { } /* Close listening socket */ - if (server->listen_fd >= 0) { + if (server->listen_fd != INVALID_SOCKET) { uasync_remove_socket_t(server->ua, server->listen_fd); server->listen_socket_id = NULL; } @@ -419,10 +419,6 @@ static void client_read_callback(socket_t fd, void* arg) { if (received == SOCKET_ERROR) { int err = WSAGetLastError(); if (err != WSAEWOULDBLOCK) { - if (err == 10054) { - client->connected = 0; - return; - } if (server && server->log_file) { fprintf(server->log_file, "%llu: [ERROR] Client recv error: %d\n", (unsigned long long)get_timestamp_ms(), err); @@ -508,7 +504,7 @@ static void close_client(struct control_server* server, struct control_client* c } /* Remove from uasync */ - if (client->fd >= 0) { + if (client->fd != INVALID_SOCKET) { uasync_remove_socket_t(server->ua, client->fd); } diff --git a/src/route_bgp.c b/src/route_bgp.c index b89c246c..00f0fe38 100644 --- a/src/route_bgp.c +++ b/src/route_bgp.c @@ -22,12 +22,6 @@ // Размер пакета (без заголовка ETCP) #define BGP_PACKET_SIZE (sizeof(struct ROUTE_BGP_PACKET)) -static inline void format_network(uint32_t network, char* out, size_t out_size) -{ - uint32_t addr = htonl(network); - inet_ntop(AF_INET, &addr, out, out_size); -} - // Forward declarations static void route_bgp_send_route(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn, const struct ROUTE_ENTRY* route); @@ -70,20 +64,11 @@ static void route_bgp_receive_cbk(struct ETCP_CONN* from_conn, struct ll_entry* return; } - char network_str[INET_ADDRSTRLEN]; - format_network(ntohl(pkt->network), network_str, sizeof(network_str)); - - DEBUG_INFO(DEBUG_CATEGORY_BGP, "Received BGP packet: subcmd=%d, network=%s/%d, node_id=%016llx", - pkt->subcmd, network_str, pkt->prefix_length & 0x3F, (unsigned long long)pkt->node_id); + DEBUG_INFO(DEBUG_CATEGORY_BGP, "Received BGP packet: subcmd=%d, network=%08x/%d, node_id=%016llx", + pkt->subcmd, ntohl(pkt->network), pkt->prefix_length & 0x3F, (unsigned long long)pkt->node_id); switch (pkt->subcmd) { case ROUTE_SUBCMD_ENTRY: { - // Игнорируем маршруты от себя - if (pkt->node_id == instance->node_id) { - DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Ignoring own route: %s/%d", network_str, pkt->prefix_length & 0x3F); - break; - } - // Создаем запись маршрута struct ROUTE_ENTRY route; memset(&route, 0, sizeof(route)); @@ -112,44 +97,32 @@ static void route_bgp_receive_cbk(struct ETCP_CONN* from_conn, struct ll_entry* } // Вставляем в таблицу - char network_str[INET_ADDRSTRLEN]; - format_network(route.network, network_str, sizeof(network_str)); - if (route_table_insert(instance->rt, &route)) { - DEBUG_INFO(DEBUG_CATEGORY_BGP, "Added learned route: %s/%d via node %016llx (hops=%d)", - network_str, route.prefix_length, + DEBUG_INFO(DEBUG_CATEGORY_BGP, "Added learned route: %08x/%d via node %016llx (hops=%d)", + route.network, route.prefix_length, (unsigned long long)route.destination_node_id, route.metrics.hop_count); } else { - DEBUG_WARN(DEBUG_CATEGORY_BGP, "Failed to insert route: %s/%d", - network_str, route.prefix_length); + DEBUG_WARN(DEBUG_CATEGORY_BGP, "Failed to insert route: %08x/%d", + route.network, route.prefix_length); } break; } case ROUTE_SUBCMD_WITHDRAW: { - // Игнорируем withdraw от себя - if (pkt->node_id == instance->node_id) { - DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Ignoring own withdraw: %s/%d", network_str, pkt->prefix_length & 0x3F); - break; - } - // Удаление маршрута (withdrawal) uint32_t network = ntohl(pkt->network); uint8_t prefix_length = pkt->prefix_length & 0x3F; - char network_str[INET_ADDRSTRLEN]; - format_network(network, network_str, sizeof(network_str)); - - DEBUG_INFO(DEBUG_CATEGORY_BGP, "Processing route withdrawal: %s/%d from conn %p", - network_str, prefix_length, (void*)from_conn); + DEBUG_INFO(DEBUG_CATEGORY_BGP, "Processing route withdrawal: %08x/%d from conn %p", + network, prefix_length, (void*)from_conn); if (route_table_delete_entry(instance->rt, network, prefix_length, from_conn)) { - DEBUG_INFO(DEBUG_CATEGORY_BGP, "Route withdrawn successfully: %s/%d", - network_str, prefix_length); + DEBUG_INFO(DEBUG_CATEGORY_BGP, "Route withdrawn successfully: %08x/%d", + network, prefix_length); } else { - DEBUG_WARN(DEBUG_CATEGORY_BGP, "Route to withdraw not found: %s/%d", - network_str, prefix_length); + DEBUG_WARN(DEBUG_CATEGORY_BGP, "Route to withdraw not found: %08x/%d", + network, prefix_length); } break; } @@ -181,11 +154,6 @@ static void route_bgp_send_withdraw(struct ROUTE_BGP* bgp, struct ETCP_CONN* con return; } - // Не отправляем withdraw автору маршрута - if (route->destination_node_id == conn->peer_node_id) { - return; - } - // Создаем ETCP пакет struct ll_entry* entry = ll_alloc_lldgram(sizeof(struct ROUTE_BGP_PACKET)); if (!entry) { @@ -207,11 +175,8 @@ static void route_bgp_send_withdraw(struct ROUTE_BGP* bgp, struct ETCP_CONN* con entry->len = sizeof(struct ROUTE_BGP_PACKET); - char network_str[INET_ADDRSTRLEN]; - format_network(route->network, network_str, sizeof(network_str)); - - DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Sending withdraw: %s/%d to conn %p", - network_str, route->prefix_length, (void*)conn); + DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Sending withdraw: %08x/%d to conn %p", + route->network, route->prefix_length, (void*)conn); // Отправляем через ETCP if (etcp_send(conn, entry) != 0) { @@ -273,11 +238,8 @@ static void route_bgp_on_route_change(struct ROUTE_TABLE* table, // Не рассылаем маршруты через которые мы их получили (чтобы избежать петель) // Это делается в цикле ниже - char network_str[INET_ADDRSTRLEN]; - format_network(entry->network, network_str, sizeof(network_str)); - - DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Route change: action=%d, network=%s/%d, type=%d", - action, network_str, entry->prefix_length, entry->type); + DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Route change: action=%d, network=%08x/%d, type=%d", + action, entry->network, entry->prefix_length, entry->type); // Перебираем все соединения в senders_list struct ll_entry* item_entry = bgp->senders_list->head; @@ -310,11 +272,6 @@ static void route_bgp_send_route(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn, return; } - // Не отправляем маршрут автору этого маршрута - if (route->destination_node_id == conn->peer_node_id) { - return; - } - // Пропускаем неактивные маршруты if (!(route->flags & ROUTE_FLAG_ACTIVE)) { return; @@ -356,11 +313,8 @@ static void route_bgp_send_route(struct ROUTE_BGP* bgp, struct ETCP_CONN* conn, entry->len = sizeof(struct ROUTE_BGP_PACKET); - char network_str[INET_ADDRSTRLEN]; - format_network(route->network, network_str, sizeof(network_str)); - - DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Sending route: %s/%d to conn %p", - network_str, route->prefix_length & 0x3F, (void*)conn); + DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "Sending route: %08x/%d to conn %p", + route->network, route->prefix_length & 0x3F, (void*)conn); // Отправляем через ETCP if (etcp_send(conn, entry) != 0) { diff --git a/tests/Makefile.am b/tests/Makefile.am index 48f94e23..8df0472c 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -23,6 +23,7 @@ check_PROGRAMS = \ test_route_lib \ test_bgp_route_exchange \ test_routing_mesh \ + test_control_server \ bench_timeout_heap \ bench_uasync_timeouts @@ -204,6 +205,11 @@ bench_uasync_timeouts_SOURCES = bench_uasync_timeouts.c bench_uasync_timeouts_CFLAGS = -I$(top_srcdir)/lib bench_uasync_timeouts_LDADD = $(COMMON_LIBS) +# Control Server Test - Tests the ETCP monitoring protocol +test_control_server_SOURCES = test_control_server.c +test_control_server_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib -I$(top_srcdir)/tools/etcpmon +test_control_server_LDADD = $(COMMON_LIBS) + # Build tinycrypt objects before tests that need them BUILT_SOURCES = $(TINYCRYPT_BUILT) diff --git a/tools/etcpmon/Makefile b/tools/etcpmon/Makefile new file mode 100644 index 00000000..02a235f5 --- /dev/null +++ b/tools/etcpmon/Makefile @@ -0,0 +1,40 @@ +# ETCP Monitor Makefile for MinGW +# Usage: make [CC=gcc] [CFLAGS=...] [LDFLAGS=...] + +# Compiler +CC ?= gcc + +# Target +TARGET = etcpmon.exe + +# Source files +SRCS = etcpmon_main.c \ + etcpmon_gui.c \ + etcpmon_client.c \ + etcpmon_graph.c + +# Object files +OBJS = $(SRCS:.c=.o) + +# Windows-specific libraries +LIBS = -lws2_32 -lgdi32 -lcomdlg32 -lkernel32 -luser32 -lcomctl32 +# Compiler flags +CFLAGS += -Wall -Wextra +CFLAGS += -O2 +CFLAGS += -D_WIN32_WINNT=0x0601 +CFLAGS += -DUNICODE -D_UNICODE +CFLAGS += -I. + +# Build rule +.PHONY: all clean + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) -o $@ $^ $(LDFLAGS) $(LIBS) + +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm -f $(OBJS) $(TARGET) *.o diff --git a/tools/etcpmon/etcpmon_graph.c b/tools/etcpmon/etcpmon_graph.c new file mode 100644 index 00000000..6639f801 --- /dev/null +++ b/tools/etcpmon/etcpmon_graph.c @@ -0,0 +1,285 @@ +/* + * etcpmon_graph.c - ETCP Monitor Graph (20 SPS, 1px=1sample, left scroll, low CPU) + * + * Изменения по твоему запросу: + * - Вертикальная линия курсора остаётся + * - Строка с значениями под курсором ("RTT-L: 45.2 ...") полностью убрана + * - Значения в блоках под графиком всегда живые (обновляются в on_metrics) + */ + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include "etcpmon_graph.h" +#include "etcpmon_client.h" +#include +#include +#include "etcpmon_gui.h" + +const COLORREF graph_colors[GRAPH_METRICS_COUNT] = { + RGB(255,0,0), RGB(0,255,0), RGB(0,0,255), RGB(255,255,0), + RGB(255,0,255), RGB(0,255,255), RGB(255,128,0), RGB(128,0,255) +}; + +const char* graph_metric_names[GRAPH_METRICS_COUNT] = { + "RTT Last","RTT Avg10","RTT Avg100","Jitter", + "Retrns","ACKs","Inflght","Bytes/s" +}; + +static void RecalculateMinMax(struct metrics_history* hist, int vis, int oldest) +{ + for (int m = 0; m < GRAPH_METRICS_COUNT; m++) { + float mn = 1e9f; + float mx = -1e9f; + + for (int i = 0; i < vis; i++) { + float v = hist->values[m][(oldest + i) % GRAPH_HISTORY_SIZE]; + if (v < mn) mn = v; + if (v > mx) mx = v; + } + + if (mx - mn < 0.001f) { + if (mn > 0.0f) { mn *= 0.9f; mx *= 1.1f; } + else { mx = 1.0f; } + } + + hist->min_val[m] = mn; + hist->max_val[m] = mx; + } +} + +void GetSelectedChannels(struct etcpmon_app* app, int* selected, int* count) { + *count = 0; + if (!app) return; + for (int i = 0; i < GRAPH_METRICS_COUNT; i++) { + if (app->hChannelCheck[i] && + SendMessageA(app->hChannelCheck[i], BM_GETCHECK, 0, 0) == BST_CHECKED) { + selected[(*count)++] = i; + } + } + if (*count == 0) + for (int i = 0; i < 4 && i < GRAPH_METRICS_COUNT; i++) + selected[(*count)++] = i; +} + +void DrawGraph(HDC hdc, RECT* rc, struct etcpmon_app* app) +{ + if (!app || !rc) return; + + int w = rc->right - rc->left; + int h = rc->bottom - rc->top; + + /* Off-screen буфер */ + HDC memDC = CreateCompatibleDC(hdc); + HBITMAP bmp = CreateCompatibleBitmap(hdc, w, h); + HBITMAP oldBmp = (HBITMAP)SelectObject(memDC, bmp); + + SetBkMode(memDC, TRANSPARENT); + + /* Фон */ + HBRUSH bg = CreateSolidBrush(GetSysColor(COLOR_WINDOW)); + FillRect(memDC, rc, bg); + DeleteObject(bg); + + /* Нет данных */ + if (!app->client || !etcpmon_client_get_history(app->client) || + etcpmon_client_get_history(app->client)->count < 2) + { + SetTextColor(memDC, RGB(128, 128, 128)); + DrawTextA(memDC, "Waiting for data...", -1, rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + goto end; + } + + struct metrics_history* hist = etcpmon_client_get_history(app->client); + + /* Выбранные каналы */ + int sel[GRAPH_METRICS_COUNT]; + int sc = 0; + GetSelectedChannels(app, sel, &sc); + + int pad = 5; + int gl = pad + 45; /* левая граница графика */ + int gr = w - pad; /* правая граница */ + int gt = pad; /* верх */ + int gb = h - 25; /* низ */ + int gh = gb - gt; + + /* === Сетка === */ + HPEN grid = CreatePen(PS_DOT, 1, GetSysColor(COLOR_BTNSHADOW)); + HPEN old = (HPEN)SelectObject(memDC, grid); + for (int i = 1; i < 5; i++) { + int y = pad + (h - 2 * pad) * i / 5; + MoveToEx(memDC, pad, y, NULL); + LineTo(memDC, w - pad, y); + } + SelectObject(memDC, old); + DeleteObject(grid); + + /* === Оси === */ + HPEN axis = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_WINDOWTEXT)); + old = (HPEN)SelectObject(memDC, axis); + MoveToEx(memDC, gl, gt, NULL); LineTo(memDC, gl, gb); + MoveToEx(memDC, gl, gb, NULL); LineTo(memDC, gr, gb); + SelectObject(memDC, old); + DeleteObject(axis); + + /* Сколько точек видно */ + int vis = (hist->count < gr - gl) ? hist->count : (gr - gl); + int oldest = (hist->head - vis + GRAPH_HISTORY_SIZE) % GRAPH_HISTORY_SIZE; + int startx = gr - vis; + + /* Пересчёт min/max 5 раз в секунду */ + DWORD now = GetTickCount(); + if (now - hist->last_minmax_update >= 200) { + RecalculateMinMax(hist, vis, oldest); + hist->last_minmax_update = now; + } + + /* Буфер точек */ + POINT* points = (POINT*)malloc((vis + 1) * sizeof(POINT)); + + /* Рисуем графики */ + for (int ch = 0; ch < sc; ch++) { + int m = sel[ch]; + float mn = hist->min_val[m]; + float mx = hist->max_val[m]; + + SelectObject(memDC, app->hGraphPens[m]); + + for (int i = 0; i < vis; i++) { + float v = hist->values[m][(oldest + i) % GRAPH_HISTORY_SIZE]; + int x = startx + i; + int y = gb - (int)((v - mn) / (mx - mn + 1e-9f) * (float)gh); + if (y < gt) y = gt; + if (y > gb) y = gb; + points[i].x = x; + points[i].y = y; + } + Polyline(memDC, points, vis); + } + + free(points); + + /* === ВЕРТИКАЛЬНАЯ ЛИНИЯ КУРСОРА (только линия, без текста) === */ + if (app->graph_cursor_active) { + HPEN cp = CreatePen(PS_SOLID, 1, RGB(80, 80, 80)); + old = (HPEN)SelectObject(memDC, cp); + MoveToEx(memDC, app->graph_cursor_x, gt, NULL); + LineTo(memDC, app->graph_cursor_x, gb); + SelectObject(memDC, old); + DeleteObject(cp); + } + +end: + /* Копируем на экран */ + BitBlt(hdc, 0, 0, w, h, memDC, 0, 0, SRCCOPY); + + SelectObject(memDC, oldBmp); + DeleteObject(bmp); + DeleteDC(memDC); +} + +void UpdateChannelValues(struct etcpmon_app* app, int history_idx) +{ + if (!app || !app->client) return; + + struct metrics_history* h = etcpmon_client_get_history(app->client); + if (!h || history_idx < 0 || history_idx >= GRAPH_HISTORY_SIZE) { + for (int i = 0; i < GRAPH_METRICS_COUNT; i++) { + if (app->hChannelValue[i]) + SetWindowTextA(app->hChannelValue[i], "-"); + } + return; + } + + static char last_text[GRAPH_METRICS_COUNT][32] = {0}; + + for (int i = 0; i < GRAPH_METRICS_COUNT; i++) { + if (!app->hChannelValue[i]) continue; + + float val = h->values[i][history_idx]; + char buf[32] = {0}; + + if (i == GRAPH_METRIC_BYTES_SENT) { + if (val >= 1024*1024) snprintf(buf, sizeof(buf), "%.1f MB/s", val/(1024.*1024.)); + else if (val >= 1024) snprintf(buf, sizeof(buf), "%.1f KB/s", val/1024.); + else snprintf(buf, sizeof(buf), "%.0f B/s", val); + } else if (i == GRAPH_METRIC_INFLIGHT) { + if (val >= 1024) snprintf(buf, sizeof(buf), "%.1f KB", val/1024.); + else snprintf(buf, sizeof(buf), "%.0f B", val); + } else if (i == GRAPH_METRIC_RETRANS || i == GRAPH_METRIC_ACKS) { + snprintf(buf, sizeof(buf), "%.0f /s", val); + } else { + snprintf(buf, sizeof(buf), "%.1f ms", val); + } + + if (strcmp(buf, last_text[i]) == 0) + continue; + + strcpy(last_text[i], buf); + + SendMessageA(app->hChannelValue[i], WM_SETREDRAW, FALSE, 0); + SetWindowTextA(app->hChannelValue[i], buf); + SendMessageA(app->hChannelValue[i], WM_SETREDRAW, TRUE, 0); + InvalidateRect(app->hChannelValue[i], NULL, FALSE); + } +} + +LRESULT CALLBACK GraphWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { + struct etcpmon_app* app = (struct etcpmon_app*)GetWindowLongPtrA(hWnd, GWLP_USERDATA); + + switch (message) { + case WM_CREATE: { + CREATESTRUCTA* cs = (CREATESTRUCTA*)lParam; + app = cs->lpCreateParams; + SetWindowLongPtrA(hWnd, GWLP_USERDATA, (LONG_PTR)app); + if (app) app->hGraphWnd = hWnd; + break; + } + case WM_PAINT: { + PAINTSTRUCT ps; + HDC hdc = BeginPaint(hWnd, &ps); + RECT rc; + GetClientRect(hWnd, &rc); + DrawGraph(hdc, &rc, app); + EndPaint(hWnd, &ps); + return 0; + } + case WM_MOUSEMOVE: { + if (!app) break; + + int x = LOWORD(lParam); + int y = HIWORD(lParam); + RECT rc; + GetClientRect(hWnd, &rc); + + BOOL inside = (x >= 0 && x < rc.right && y >= 0 && y < rc.bottom); + + if (inside) { + app->graph_cursor_x = x; + app->graph_cursor_active = 1; + + static DWORD last_invalidate = 0; + DWORD now = GetTickCount(); + if (now - last_invalidate >= 10) { + InvalidateRect(hWnd, NULL, FALSE); + last_invalidate = now; + } + } else { + app->graph_cursor_active = 0; + InvalidateRect(hWnd, NULL, FALSE); + } + break; + } + case WM_MOUSELEAVE: + if (app) { + app->graph_cursor_active = 0; + InvalidateRect(hWnd, NULL, FALSE); + } + break; + default: + return DefWindowProcA(hWnd, message, wParam, lParam); + } + return 0; +} diff --git a/tools/etcpmon/etcpmon_graph.h b/tools/etcpmon/etcpmon_graph.h new file mode 100644 index 00000000..4b280409 --- /dev/null +++ b/tools/etcpmon/etcpmon_graph.h @@ -0,0 +1,44 @@ +/* + * etcpmon_graph.h - ETCP Monitor Graph Definitions + */ + +#ifndef ETCPMON_GRAPH_H +#define ETCPMON_GRAPH_H + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Graph constants */ +#define GRAPH_WIDTH 860 +#define GRAPH_HEIGHT 150 +#define CHANNEL_LIST_HEIGHT 80 +#define GRAPH_METRICS_COUNT 8 +#define GRAPH_HISTORY_SIZE 860 + +/* Forward declarations */ +struct etcpmon_app; +struct metrics_history; + +/* Graph colors for each metric */ +extern const COLORREF graph_colors[GRAPH_METRICS_COUNT]; +extern const char* graph_metric_names[GRAPH_METRICS_COUNT]; + +/* Graph functions */ +LRESULT CALLBACK GraphWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); +void DrawGraph(HDC hdc, RECT* rc, struct etcpmon_app* app); +void GetSelectedChannels(struct etcpmon_app* app, int* selected, int* count); +void UpdateChannelValues(struct etcpmon_app* app, int cursor_sample); + +#ifdef __cplusplus +} +#endif + +#endif /* ETCPMON_GRAPH_H */ diff --git a/tools/etcpmon/etcpmon_gui.c b/tools/etcpmon/etcpmon_gui.c index e11c4f80..37d45d39 100644 --- a/tools/etcpmon/etcpmon_gui.c +++ b/tools/etcpmon/etcpmon_gui.c @@ -49,6 +49,14 @@ static void on_metrics(struct etcpmon_rsp_metrics* metrics, static void on_error(const char* msg, void* user_data); /* Helper functions */ +static void SetDlgItemTextFmt(HWND hDlg, int nIDDlgItem, const char* fmt, ...) { + char buf[256]; + va_list args; + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + SetDlgItemTextA(hDlg, nIDDlgItem, buf); +} int etcpmon_gui_init(struct etcpmon_app* app, HINSTANCE hInstance) { if (!app) return -1; @@ -76,7 +84,7 @@ int etcpmon_gui_init(struct etcpmon_app* app, HINSTANCE hInstance) { wcex.lpszClassName = ETCPMON_WINDOW_CLASS; wcex.hIconSm = LoadIcon(NULL, IDI_APPLICATION); -if (!RegisterClassExA(&wcex)) { + if (!RegisterClassExA(&wcex)) { return -1; } @@ -205,10 +213,8 @@ static void CreateControls(struct etcpmon_app* app) { WS_CHILD | WS_VISIBLE | LBS_NOTIFY | WS_VSCROLL | LBS_NOINTEGRALHEIGHT, x, y + 20, 300, 120, hWnd, (HMENU)IDC_LIST_CONNECTIONS, hInst, NULL); - /* Graph area - between Connections and ETCP Metrics */ + /* Graph area */ y += 140; -// app->hGraphWnd = CreateWindowA("STATIC", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER, -// 10, y, GRAPH_WIDTH, GRAPH_HEIGHT, hWnd, (HMENU)IDC_GRAPH, hInst, app); app->hGraphWnd = CreateWindowExA( WS_EX_CLIENTEDGE, @@ -217,8 +223,8 @@ static void CreateControls(struct etcpmon_app* app) { 10, y, GRAPH_WIDTH, GRAPH_HEIGHT, hWnd, (HMENU)IDC_GRAPH, hInst, app); - /* Channel list - grid blocks below graph */ - y += GRAPH_HEIGHT + 5; + /* Channel blocks below graph (checkbox + name в одной строке, значение ниже) */ + y += GRAPH_HEIGHT + 8; const char* channel_short_names[] = { "RTT-L", "RTT10", "RTT100", "Jitter", @@ -226,9 +232,9 @@ static void CreateControls(struct etcpmon_app* app) { }; int block_w = 200; - int block_h = 28; + int block_h = 48; /* компактная высота */ int cols = 4; - int spacing = 5; + int spacing = 6; for (int i = 0; i < GRAPH_METRICS_COUNT; i++) { int col = i % cols; @@ -236,17 +242,23 @@ static void CreateControls(struct etcpmon_app* app) { int bx = 10 + col * (block_w + spacing); int by = y + row * (block_h + spacing); + /* Галочка слева */ app->hChannelCheck[i] = CreateWindowExA(0, "BUTTON", "", - WS_CHILD | WS_VISIBLE | BS_CHECKBOX | BST_CHECKED, - bx, by + 4, 16, 14, hWnd, (HMENU)(UINT_PTR)(IDC_CH_CHECK_0 + i), hInst, NULL); + WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX | BST_CHECKED, + bx, by, 20, 18, + hWnd, (HMENU)(UINT_PTR)(IDC_CH_CHECK_0 + i), hInst, NULL); + /* Название сразу справа от галочки */ app->hChannelName[i] = CreateWindowExA(0, "STATIC", channel_short_names[i], - WS_CHILD | WS_VISIBLE, - bx + 20, by + 4, 60, 16, hWnd, (HMENU)(UINT_PTR)(IDC_CH_NAME_0 + i), hInst, NULL); + WS_CHILD | WS_VISIBLE | SS_LEFT, + bx + 24, by + 2, block_w - 30, 16, + hWnd, (HMENU)(UINT_PTR)(IDC_CH_NAME_0 + i), hInst, NULL); + /* Значение под названием */ app->hChannelValue[i] = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", - WS_CHILD | WS_VISIBLE | ES_READONLY, - bx + 85, by + 2, 110, 20, hWnd, (HMENU)(UINT_PTR)(IDC_CH_VALUE_0 + i), hInst, NULL); + WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, + bx, by + 21, block_w, 22, + hWnd, (HMENU)(UINT_PTR)(IDC_CH_VALUE_0 + i), hInst, NULL); } int channel_grid_h = ((GRAPH_METRICS_COUNT + cols - 1) / cols) * (block_h + spacing) - spacing; @@ -505,34 +517,6 @@ static void OnTimer(struct etcpmon_app* app) { InvalidateRect(app->hGraphWnd, NULL, FALSE); UpdateWindow(app->hGraphWnd); /* форсируем немедленную перерисовку */ } - - /* 5. Цифры под графиком — обновляем если курсор не активен */ - if (!app->graph_cursor_active) { - struct metrics_history* h = etcpmon_client_get_history(app->client); - if (h && h->count > 0) { - int last_idx = (h->head - 1 + GRAPH_HISTORY_SIZE) % GRAPH_HISTORY_SIZE; - UpdateChannelValues(app, last_idx); - } - } else { - RECT rc; - GetClientRect(app->hGraphWnd, &rc); - struct metrics_history* h = etcpmon_client_get_history(app->client); - if (h && h->count > 2) { - int w = rc.right - rc.left; - int pad = 5; - int gl = pad + 45; - int gr = w - pad; - int vis = (h->count < gr - gl) ? h->count : (gr - gl); - int oldest = (h->head - vis + GRAPH_HISTORY_SIZE) % GRAPH_HISTORY_SIZE; - int startx = gr - vis; - int x = app->graph_cursor_x; - if (x >= startx && x < startx + vis) { - int rel = x - startx; - int idx = (oldest + rel) % GRAPH_HISTORY_SIZE; - UpdateChannelValues(app, idx); - } - } - } } static void UpdateUIState(struct etcpmon_app* app) { @@ -545,17 +529,11 @@ static void UpdateUIState(struct etcpmon_app* app) { } /* Client callbacks */ -//static void on_connected(void* user_data) { -// struct etcpmon_app* app = (struct etcpmon_app*)user_data; -// etcpmon_gui_set_status(app, "Connected"); -//} - static void on_connected(void* user_data) { struct etcpmon_app* app = (struct etcpmon_app*)user_data; app->isConnected = 1; UpdateUIState(app); etcpmon_gui_set_status(app, "Connected"); - // Добавьте для теста if (app->client->log_file) { fprintf(app->client->log_file, "[DEBUG] on_connected called\n"); fflush(app->client->log_file); @@ -579,7 +557,6 @@ void etcpmon_gui_update_graph(struct etcpmon_app* app, struct etcpmon_rsp_metric } } - static void on_disconnected(void* user_data) { struct etcpmon_app* app = (struct etcpmon_app*)user_data; app->isConnected = 0; @@ -587,21 +564,14 @@ static void on_disconnected(void* user_data) { etcpmon_gui_set_status(app, "Disconnected"); } -//static void on_conn_list(struct etcpmon_conn_info* list, uint8_t count, void* user_data) { -// struct etcpmon_app* app = (struct etcpmon_app*)user_data; -// etcpmon_gui_update_conn_list(app, list, count); -//} static void on_conn_list(struct etcpmon_conn_info* list, uint8_t count, void* user_data) { struct etcpmon_app* app = (struct etcpmon_app*)user_data; etcpmon_gui_update_conn_list(app, list, count); etcpmon_gui_set_status(app, "Connection list updated"); - // Automatically select the first connection if available + /* Automatically select the first connection if available */ if (count > 0 && app->hListConnections) { - // Select the first item (index 0) SendMessage(app->hListConnections, LB_SETCURSEL, 0, 0); - - // Trigger the selection handler to request metrics OnConnectionSelect(app); } } @@ -614,12 +584,10 @@ static void on_metrics(struct etcpmon_rsp_metrics* metrics, etcpmon_gui_update_metrics(app, metrics, links, links_count); etcpmon_client_add_to_history(app->client, metrics); - if (!app->graph_cursor_active) { - struct metrics_history* h = etcpmon_client_get_history(app->client); - if (h && h->count > 0) { - int last_idx = (h->head - 1 + GRAPH_HISTORY_SIZE) % GRAPH_HISTORY_SIZE; - UpdateChannelValues(app, last_idx); - } + struct metrics_history* h = etcpmon_client_get_history(app->client); + if (h && h->count > 0) { + int last_idx = (h->head - 1 + GRAPH_HISTORY_SIZE) % GRAPH_HISTORY_SIZE; + UpdateChannelValues(app, last_idx); } } } @@ -699,7 +667,7 @@ static BOOL UpdateEditIfChanged(HWND hDlg, int nIDDlgItem, const char* fmt, ...) GetDlgItemTextA(hDlg, nIDDlgItem, old_text, sizeof(old_text)); if (strcmp(old_text, new_text) == 0) - return FALSE; /* ничего не изменилось — не трогаем */ + return FALSE; SetDlgItemTextA(hDlg, nIDDlgItem, new_text); InvalidateRect(GetDlgItem(hDlg, nIDDlgItem), NULL, FALSE); @@ -734,7 +702,7 @@ void etcpmon_gui_update_metrics(struct etcpmon_app* app, UpdateEditIfChanged(hMain, IDC_EDIT_TUN_READ_ERRS, "%u", metrics->tun.read_errors); UpdateEditIfChanged(hMain, IDC_EDIT_TUN_WRITE_ERRS, "%u", metrics->tun.write_errors); - /* Links list (перестраивается всегда — он маленький, моргание почти незаметно) */ + /* Links list */ if (app->hListLinks) { SendMessage(app->hListLinks, LB_RESETCONTENT, 0, 0); for (uint8_t i = 0; i < links_count; i++) { diff --git a/tools/etcpmon/etcpmon_gui.h b/tools/etcpmon/etcpmon_gui.h index abaa871d..fcc76ffa 100644 --- a/tools/etcpmon/etcpmon_gui.h +++ b/tools/etcpmon/etcpmon_gui.h @@ -62,12 +62,10 @@ extern "C" { /* Link list control ID */ #define IDC_LIST_LINKS 400 -/* Graph control IDs */ +/* Graph control ID */ #define IDC_GRAPH 500 -#define IDC_STATIC_GRAPH 501 -#define IDC_LIST_CHANNELS 502 -/* Channel block IDs - 8 channels, 4 columns x 2 rows */ +/* Channel block IDs */ #define IDC_CH_NAME_0 510 #define IDC_CH_VALUE_0 520 #define IDC_CH_CHECK_0 530 @@ -100,6 +98,7 @@ struct etcpmon_app { HWND hWndStatus; HPEN hGraphPens[GRAPH_METRICS_COUNT]; + /* Connection controls */ HWND hEditAddr; HWND hEditPort; @@ -130,19 +129,21 @@ struct etcpmon_app { /* Links list */ HWND hListLinks; - /* Graph controls (managed by graph module) */ + /* Graph controls */ HWND hGraphWnd; HWND hChannelName[GRAPH_METRICS_COUNT]; HWND hChannelValue[GRAPH_METRICS_COUNT]; HWND hChannelCheck[GRAPH_METRICS_COUNT]; + + /* Cursor (вертикальная линия остаётся) */ int graph_cursor_x; int graph_cursor_y; int graph_cursor_active; - /* Client state - pointer to actual client structure */ + /* Client state */ struct etcpmon_client* client; - /* Timer ID */ + /* Timer */ UINT_PTR updateTimer; DWORD last_poll_time; int isConnected;