/* * etcpmon_gui.c - ETCP Monitor GUI Implementation */ /* Must define this before any windows headers to exclude winsock.h */ #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif /* Must include winsock2.h before windows.h */ #include #include #include #include "etcpmon_gui.h" #include "etcpmon_client.h" #include "etcpmon_protocol.h" #include #include #include #include //#pragma comment(lib, "comctl32.lib") //#pragma comment(lib, "user32.lib") //#pragma comment(lib, "gdi32.lib") #define WINDOW_WIDTH 900 #define WINDOW_HEIGHT 1250 #define UPDATE_INTERVAL 10 /* 50ms → 20 samples per second */ /* Global app pointer for callbacks */ static struct etcpmon_app* g_app = NULL; /* Forward declarations */ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); static void CreateControls(struct etcpmon_app* app); static void OnConnect(struct etcpmon_app* app); static void OnDisconnect(struct etcpmon_app* app); static void OnConnectionSelect(struct etcpmon_app* app); static void OnTimer(struct etcpmon_app* app); static void UpdateUIState(struct etcpmon_app* app); /* Callback functions for client events */ static void on_connected(void* user_data); static void on_disconnected(void* user_data); static void on_conn_list(struct etcpmon_conn_info* list, uint8_t count, void* user_data); static void on_metrics(struct etcpmon_rsp_metrics* metrics, struct etcpmon_link_metrics* links, uint8_t links_count, void* user_data); static void on_error(const char* msg, void* user_data); int etcpmon_gui_init(struct etcpmon_app* app, HINSTANCE hInstance) { if (!app) return -1; memset(app, 0, sizeof(*app)); app->hInstance = hInstance; g_app = app; /* Initialize common controls */ INITCOMMONCONTROLSEX iccex; iccex.dwSize = sizeof(iccex); iccex.dwICC = ICC_LISTVIEW_CLASSES | ICC_STANDARD_CLASSES; InitCommonControlsEx(&iccex); /* === НОВОЕ === */ app->need_initial_request = 1; app->last_selected_peer_id = 0; app->isConnected = 0; /* Register window class */ WNDCLASSEXA wcex; memset(&wcex, 0, sizeof(wcex)); wcex.cbSize = sizeof(wcex); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); wcex.lpszClassName = ETCPMON_WINDOW_CLASS; wcex.hIconSm = LoadIcon(NULL, IDI_APPLICATION); if (!RegisterClassExA(&wcex)) { return -1; } /* Register Graph window class */ WNDCLASSEXA wcexGraph; memset(&wcexGraph, 0, sizeof(wcexGraph)); wcexGraph.cbSize = sizeof(wcexGraph); wcexGraph.style = CS_HREDRAW | CS_VREDRAW; wcexGraph.lpfnWndProc = GraphWndProc; wcexGraph.hInstance = hInstance; wcexGraph.hIcon = NULL; wcexGraph.hCursor = LoadCursor(NULL, IDC_CROSS); wcexGraph.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wcexGraph.lpszClassName = "ETCPMonGraph"; wcexGraph.hIconSm = NULL; RegisterClassExA(&wcexGraph); /* Initialize Winsock */ WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { return -1; } return 0; } HWND etcpmon_gui_create_window(struct etcpmon_app* app) { if (!app) return NULL; /* Create main window */ app->hWndMain = CreateWindowExA( 0, ETCPMON_WINDOW_CLASS, "ETCP Monitor", WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, app->hInstance, app ); if (!app->hWndMain) { return NULL; } /* Create status bar */ app->hWndStatus = CreateWindowExA( 0, STATUSCLASSNAME, NULL, WS_CHILD | WS_VISIBLE | SBARS_SIZEGRIP, 0, 0, 0, 0, app->hWndMain, (HMENU)IDC_STATIC, app->hInstance, NULL ); SetTimer(app->hWndMain, IDC_TIMER_UPDATE, UPDATE_INTERVAL, NULL); /* Initialize client */ app->client = (struct etcpmon_client*)malloc(sizeof(struct etcpmon_client)); if (app->client) { etcpmon_client_init(app->client); etcpmon_client_set_callbacks(app->client, on_connected, on_disconnected, on_conn_list, on_metrics, on_error, app); } CreateControls(app); for (int i = 0; i < GRAPH_METRICS_COUNT; i++) { app->hGraphPens[i] = CreatePen(PS_SOLID, 2, graph_colors[i]); } /* Set default values */ SetDlgItemTextA(app->hWndMain, IDC_EDIT_ADDR, "127.0.0.1"); SetDlgItemTextA(app->hWndMain, IDC_EDIT_PORT, "9090"); UpdateUIState(app); OnConnect(app); return app->hWndMain; } static void CreateControls(struct etcpmon_app* app) { HWND hWnd = app->hWndMain; HINSTANCE hInst = app->hInstance; int x = 10, y = 10; /* Connection group */ CreateWindowExA(0, "BUTTON", "Connection", WS_CHILD | WS_VISIBLE | BS_GROUPBOX, x, y, 880, 60, hWnd, (HMENU)IDC_STATIC, hInst, NULL); /* Address label and edit */ CreateWindowExA(0, "STATIC", "Server:", WS_CHILD | WS_VISIBLE, x + 10, y + 20, 40, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditAddr = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL, x + 55, y + 18, 120, 22, hWnd, (HMENU)IDC_EDIT_ADDR, hInst, NULL); /* Port label and edit */ CreateWindowExA(0, "STATIC", "Port:", WS_CHILD | WS_VISIBLE, x + 185, y + 20, 30, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditPort = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_NUMBER, x + 220, y + 18, 60, 22, hWnd, (HMENU)IDC_EDIT_PORT, hInst, NULL); /* Connect/Disconnect buttons */ app->hBtnConnect = CreateWindowExA(0, "BUTTON", "Connect", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, x + 290, y + 18, 80, 24, hWnd, (HMENU)IDC_BTN_CONNECT, hInst, NULL); app->hBtnDisconnect = CreateWindowExA(0, "BUTTON", "Disconnect", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_DISABLED, x + 380, y + 18, 80, 24, hWnd, (HMENU)IDC_BTN_DISCONNECT, hInst, NULL); y += 70; /* Connections list */ CreateWindowExA(0, "STATIC", "Connections:", WS_CHILD | WS_VISIBLE, x, y, 100, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hListConnections = CreateWindowExA(WS_EX_CLIENTEDGE, "LISTBOX", "", WS_CHILD | WS_VISIBLE | LBS_NOTIFY | WS_VSCROLL | LBS_NOINTEGRALHEIGHT, x, y + 20, 300, 120, hWnd, (HMENU)IDC_LIST_CONNECTIONS, hInst, NULL); /* Graph area */ y += 140; app->hGraphWnd = CreateWindowExA( WS_EX_CLIENTEDGE, "ETCPMonGraph", "", WS_CHILD | WS_VISIBLE | WS_BORDER, 10, y, GRAPH_WIDTH, GRAPH_HEIGHT, hWnd, (HMENU)IDC_GRAPH, hInst, app); /* Channel blocks below graph (checkbox + name в одной строке, значение ниже) */ y += GRAPH_HEIGHT + 8; const char* channel_short_names[] = { "RTT-L", "RTT10", "RTT100", "Jitter", "Retrns", "ACKs", "Inflght", "Bytes/s" }; int block_w = 200; int block_h = 48; /* компактная высота */ int cols = 4; int spacing = 6; for (int i = 0; i < GRAPH_METRICS_COUNT; i++) { int col = i % cols; int row = i / cols; 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_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 | 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 | 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; y += channel_grid_h; /* ETCP Metrics group */ y = 485; CreateWindowExA(0, "BUTTON", "ETCP Metrics", WS_CHILD | WS_VISIBLE | BS_GROUPBOX, x, y, 430, 210, hWnd, (HMENU)IDC_STATIC, hInst, NULL); int mx = x + 10, my = y + 20; CreateWindowExA(0, "STATIC", "Name:", WS_CHILD | WS_VISIBLE, mx, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditEtcpName = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_AUTOHSCROLL, mx + 85, my, 310, 20, hWnd, (HMENU)IDC_EDIT_ETCP_NAME, hInst, NULL); my += 25; CreateWindowExA(0, "STATIC", "RTT Last:", WS_CHILD | WS_VISIBLE, mx, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditEtcpRttLast = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY, mx + 85, my, 100, 20, hWnd, (HMENU)IDC_EDIT_ETCP_RTT_LAST, hInst, NULL); CreateWindowExA(0, "STATIC", "RTT Avg 10:", WS_CHILD | WS_VISIBLE, mx + 200, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditEtcpRttAvg10 = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY, mx + 285, my, 100, 20, hWnd, (HMENU)IDC_EDIT_ETCP_RTT_AVG10, hInst, NULL); my += 25; CreateWindowExA(0, "STATIC", "RTTAvg100:", WS_CHILD | WS_VISIBLE, mx, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditEtcpRttAvg100 = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY, mx + 85, my, 100, 20, hWnd, (HMENU)IDC_EDIT_ETCP_RTT_AVG100, hInst, NULL); CreateWindowExA(0, "STATIC", "Jitter:", WS_CHILD | WS_VISIBLE, mx + 200, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditEtcpJitter = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY, mx + 285, my, 100, 20, hWnd, (HMENU)IDC_EDIT_ETCP_JITTER, hInst, NULL); my += 25; CreateWindowExA(0, "STATIC", "Bytes Sent:", WS_CHILD | WS_VISIBLE, mx, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditEtcpBytesSent = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY, mx + 85, my, 100, 20, hWnd, (HMENU)IDC_EDIT_ETCP_BYTES_SENT, hInst, NULL); CreateWindowExA(0, "STATIC", "Retrans:", WS_CHILD | WS_VISIBLE, mx + 200, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditEtcpRetrans = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY, mx + 285, my, 100, 20, hWnd, (HMENU)IDC_EDIT_ETCP_RETRANS, hInst, NULL); my += 25; CreateWindowExA(0, "STATIC", "ACKs:", WS_CHILD | WS_VISIBLE, mx, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditEtcpAcks = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY, mx + 85, my, 100, 20, hWnd, (HMENU)IDC_EDIT_ETCP_ACKS, hInst, NULL); CreateWindowExA(0, "STATIC", "Inflight:", WS_CHILD | WS_VISIBLE, mx + 200, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditEtcpInflight = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY, mx + 285, my, 100, 20, hWnd, (HMENU)IDC_EDIT_ETCP_INFLIGHT, hInst, NULL); my += 25; CreateWindowExA(0, "STATIC", "Links:", WS_CHILD | WS_VISIBLE, mx, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditEtcpLinks = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY, mx + 85, my, 100, 20, hWnd, (HMENU)IDC_EDIT_ETCP_LINKS, hInst, NULL); /* TUN/Routing Metrics group - right of ETCP Metrics */ x = 460; y = 485; CreateWindowExA(0, "BUTTON", "TUN/Routing Metrics", WS_CHILD | WS_VISIBLE | BS_GROUPBOX, x, y, 430, 210, hWnd, (HMENU)IDC_STATIC, hInst, NULL); mx = x + 10; my = y + 20; CreateWindowExA(0, "STATIC", "Read Bytes:", WS_CHILD | WS_VISIBLE, mx, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditTunReadBytes = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY, mx + 85, my, 100, 20, hWnd, (HMENU)IDC_EDIT_TUN_READ_BYTES, hInst, NULL); CreateWindowExA(0, "STATIC", "Write Bytes:", WS_CHILD | WS_VISIBLE, mx + 200, my, 90, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditTunWriteBytes = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY, mx + 285, my, 100, 20, hWnd, (HMENU)IDC_EDIT_TUN_WRITE_BYTES, hInst, NULL); my += 25; CreateWindowExA(0, "STATIC", "Read Pkts:", WS_CHILD | WS_VISIBLE, mx, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditTunReadPkts = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY, mx + 85, my, 100, 20, hWnd, (HMENU)IDC_EDIT_TUN_READ_PKTS, hInst, NULL); CreateWindowExA(0, "STATIC", "Write Pkts:", WS_CHILD | WS_VISIBLE, mx + 200, my, 90, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditTunWritePkts = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY, mx + 285, my, 100, 20, hWnd, (HMENU)IDC_EDIT_TUN_WRITE_PKTS, hInst, NULL); my += 25; CreateWindowExA(0, "STATIC", "Read Errs:", WS_CHILD | WS_VISIBLE, mx, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditTunReadErrs = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY, mx + 85, my, 100, 20, hWnd, (HMENU)IDC_EDIT_TUN_READ_ERRS, hInst, NULL); CreateWindowExA(0, "STATIC", "Write Errs:", WS_CHILD | WS_VISIBLE, mx + 200, my, 90, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditTunWriteErrs = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY, mx + 285, my, 100, 20, hWnd, (HMENU)IDC_EDIT_TUN_WRITE_ERRS, hInst, NULL); /* Routing section */ my += 30; CreateWindowExA(0, "STATIC", "Routed:", WS_CHILD | WS_VISIBLE, mx, my, 60, 18, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditRtRouted = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, mx + 65, my - 2, 80, 18, hWnd, (HMENU)IDC_EDIT_RT_ROUTED, hInst, NULL); CreateWindowExA(0, "STATIC", "Dropped:", WS_CHILD | WS_VISIBLE, mx + 155, my, 60, 18, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditRtDropped = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, mx + 220, my - 2, 80, 18, hWnd, (HMENU)IDC_EDIT_RT_DROPPED, hInst, NULL); my += 25; CreateWindowExA(0, "STATIC", "TunInQ:", WS_CHILD | WS_VISIBLE, mx, my, 60, 18, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditRtTunInQPkts = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, mx + 65, my - 2, 50, 18, hWnd, (HMENU)IDC_EDIT_RT_TUN_IN_Q_PKTS, hInst, NULL); app->hEditRtTunInQBytes = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, mx + 120, my - 2, 60, 18, hWnd, (HMENU)IDC_EDIT_RT_TUN_IN_Q_BYTES, hInst, NULL); CreateWindowExA(0, "STATIC", "TunOutQ:", WS_CHILD | WS_VISIBLE, mx + 190, my, 60, 18, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditRtTunOutQPkts = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, mx + 255, my - 2, 50, 18, hWnd, (HMENU)IDC_EDIT_RT_TUN_OUT_Q_PKTS, hInst, NULL); app->hEditRtTunOutQBytes = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, mx + 310, my - 2, 60, 18, hWnd, (HMENU)IDC_EDIT_RT_TUN_OUT_Q_BYTES, hInst, NULL); my += 25; CreateWindowExA(0, "STATIC", "RT:", WS_CHILD | WS_VISIBLE, mx, my, 30, 18, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditRtCount = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, mx + 35, my - 2, 50, 18, hWnd, (HMENU)IDC_EDIT_RT_COUNT, hInst, NULL); CreateWindowExA(0, "STATIC", "Local:", WS_CHILD | WS_VISIBLE, mx + 95, my, 40, 18, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditRtLocal = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, mx + 140, my - 2, 50, 18, hWnd, (HMENU)IDC_EDIT_RT_LOCAL, hInst, NULL); CreateWindowExA(0, "STATIC", "Learned:", WS_CHILD | WS_VISIBLE, mx + 200, my, 55, 18, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditRtLearned = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, mx + 260, my - 2, 50, 18, hWnd, (HMENU)IDC_EDIT_RT_LEARNED, hInst, NULL); /* Links list */ y = 725; CreateWindowExA(0, "STATIC", "Links:", WS_CHILD | WS_VISIBLE, 10, y, 100, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hListLinks = CreateWindowExA(WS_EX_CLIENTEDGE, "LISTBOX", "", WS_CHILD | WS_VISIBLE | LBS_NOTIFY | WS_VSCROLL | LBS_NOINTEGRALHEIGHT, 10, y + 20, 880, 160, hWnd, (HMENU)IDC_LIST_LINKS, hInst, NULL); /* Queues & Errors group - below Links */ y = 915; CreateWindowExA(0, "BUTTON", "Queues & Errors", WS_CHILD | WS_VISIBLE | BS_GROUPBOX, 10, y, 880, 335, hWnd, (HMENU)IDC_STATIC_QUEUES, hInst, NULL); int qy = y + 22; int q_col1 = 20; int q_col2 = 300; int q_col3 = 580; /* Queue metrics - row 1 */ CreateWindowExA(0, "STATIC", "InQ:", WS_CHILD | WS_VISIBLE, q_col1, qy, 50, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditQInQBytes = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col1 + 55, qy - 2, 60, 18, hWnd, (HMENU)IDC_EDIT_Q_IN_Q_BYTES, hInst, NULL); app->hEditQInQPkts = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col1 + 120, qy - 2, 50, 18, hWnd, (HMENU)IDC_EDIT_Q_IN_Q_PKTS, hInst, NULL); CreateWindowExA(0, "STATIC", "InSend:", WS_CHILD | WS_VISIBLE, q_col2, qy, 60, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditQInSendBytes = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col2 + 65, qy - 2, 60, 18, hWnd, (HMENU)IDC_EDIT_Q_IN_SEND_BYTES, hInst, NULL); app->hEditQInSendPkts = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col2 + 130, qy - 2, 50, 18, hWnd, (HMENU)IDC_EDIT_Q_IN_SEND_PKTS, hInst, NULL); CreateWindowExA(0, "STATIC", "WaitAck:", WS_CHILD | WS_VISIBLE, q_col3, qy, 65, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditQWaitAckBytes = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col3 + 70, qy - 2, 60, 18, hWnd, (HMENU)IDC_EDIT_Q_WAIT_ACK_BYTES, hInst, NULL); app->hEditQWaitAckPkts = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col3 + 135, qy - 2, 50, 18, hWnd, (HMENU)IDC_EDIT_Q_WAIT_ACK_PKTS, hInst, NULL); /* Queue metrics - row 2 */ qy += 28; CreateWindowExA(0, "STATIC", "AckQ:", WS_CHILD | WS_VISIBLE, q_col1, qy, 50, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditQAckBytes = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col1 + 55, qy - 2, 60, 18, hWnd, (HMENU)IDC_EDIT_Q_ACK_BYTES, hInst, NULL); app->hEditQAckPkts = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col1 + 120, qy - 2, 50, 18, hWnd, (HMENU)IDC_EDIT_Q_ACK_PKTS, hInst, NULL); CreateWindowExA(0, "STATIC", "RecvQ:", WS_CHILD | WS_VISIBLE, q_col2, qy, 60, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditQRecvBytes = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col2 + 65, qy - 2, 60, 18, hWnd, (HMENU)IDC_EDIT_Q_RECV_BYTES, hInst, NULL); app->hEditQRecvPkts = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col2 + 130, qy - 2, 50, 18, hWnd, (HMENU)IDC_EDIT_Q_RECV_PKTS, hInst, NULL); CreateWindowExA(0, "STATIC", "OutQ:", WS_CHILD | WS_VISIBLE, q_col3, qy, 50, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditQOutBytes = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col3 + 55, qy - 2, 60, 18, hWnd, (HMENU)IDC_EDIT_Q_OUT_BYTES, hInst, NULL); app->hEditQOutPkts = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col3 + 120, qy - 2, 50, 18, hWnd, (HMENU)IDC_EDIT_Q_OUT_PKTS, hInst, NULL); /* Error counters - row 3 */ qy += 30; CreateWindowExA(0, "STATIC", "Errors:", WS_CHILD | WS_VISIBLE, q_col1, qy, 50, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditErrReinit = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col1 + 55, qy - 2, 55, 18, hWnd, (HMENU)IDC_EDIT_ERR_REINIT, hInst, NULL); app->hEditErrReset = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col1 + 115, qy - 2, 55, 18, hWnd, (HMENU)IDC_EDIT_ERR_RESET, hInst, NULL); app->hEditErrPktFmt = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col1 + 175, qy - 2, 55, 18, hWnd, (HMENU)IDC_EDIT_ERR_PKT_FMT, hInst, NULL); /* Timer flags - same row, to the right */ CreateWindowExA(0, "STATIC", "Timers:", WS_CHILD | WS_VISIBLE, q_col2, qy, 50, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditTimerRetrans = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col2 + 55, qy - 2, 50, 18, hWnd, (HMENU)IDC_EDIT_TIMER_RETRANS, hInst, NULL); app->hEditTimerAckResp = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col2 + 110, qy - 2, 50, 18, hWnd, (HMENU)IDC_EDIT_TIMER_ACK_RESP, hInst, NULL); /* WaitAck queue state */ qy += 30; CreateWindowExA(0, "STATIC", "WaitAck:", WS_CHILD | WS_VISIBLE, q_col1, qy, 60, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditWaitAckSuspend = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col1 + 65, qy - 2, 60, 18, hWnd, (HMENU)IDC_EDIT_WAITACK_SUSP, hInst, NULL); app->hEditWaitAckCb = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col1 + 130, qy - 2, 60, 18, hWnd, (HMENU)IDC_EDIT_WAITACK_CB, hInst, NULL); app->hEditWaitAckTimer = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col1 + 195, qy - 2, 60, 18, hWnd, (HMENU)IDC_EDIT_WAITACK_TIMER, hInst, NULL); /* ID metrics - row 4 */ // qy += 30; CreateWindowExA(0, "STATIC", "IDs:", WS_CHILD | WS_VISIBLE, q_col2, qy, 30, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditIdNextTx = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col2 + 35, qy - 2, 65, 18, hWnd, (HMENU)IDC_EDIT_ID_NEXT_TX, hInst, NULL); app->hEditIdLastRx = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col2 + 105, qy - 2, 65, 18, hWnd, (HMENU)IDC_EDIT_ID_LAST_RX, hInst, NULL); app->hEditIdLastDel = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col2 + 175, qy - 2, 65, 18, hWnd, (HMENU)IDC_EDIT_ID_LAST_DEL, hInst, NULL); app->hEditIdRxAckTill = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col2 + 245, qy - 2, 65, 18, hWnd, (HMENU)IDC_EDIT_ID_RX_ACK_TILL, hInst, NULL); /* Normalizer - row 5 */ qy += 28; CreateWindowExA(0, "STATIC", "Norm:", WS_CHILD | WS_VISIBLE, q_col1, qy, 40, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditNormInPkts = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col1 + 45, qy - 2, 40, 18, hWnd, (HMENU)IDC_EDIT_NORM_IN_PKTS, hInst, NULL); app->hEditNormInBytes = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col1 + 90, qy - 2, 55, 18, hWnd, (HMENU)IDC_EDIT_NORM_IN_BYTES, hInst, NULL); app->hEditNormOutPkts = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col1 + 150, qy - 2, 40, 18, hWnd, (HMENU)IDC_EDIT_NORM_OUT_PKTS, hInst, NULL); app->hEditNormOutBytes = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col1 + 195, qy - 2, 55, 18, hWnd, (HMENU)IDC_EDIT_NORM_OUT_BYTES, hInst, NULL); app->hEditNormAllocErr = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col1 + 255, qy - 2, 40, 18, hWnd, (HMENU)IDC_EDIT_NORM_ALLOC_ERR, hInst, NULL); app->hEditNormLogicErr = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col1 + 300, qy - 2, 40, 18, hWnd, (HMENU)IDC_EDIT_NORM_LOGIC_ERR, hInst, NULL); CreateWindowExA(0, "STATIC", "frag:", WS_CHILD | WS_VISIBLE, q_col1 + 345, qy, 30, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditNormFragSize = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col1 + 380, qy - 2, 40, 18, hWnd, (HMENU)IDC_EDIT_NORM_FRAG_SIZE, hInst, NULL); CreateWindowExA(0, "STATIC", "buf:", WS_CHILD | WS_VISIBLE, q_col1 + 425, qy, 30, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditNormDataPtr = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col1 + 460, qy - 2, 40, 18, hWnd, (HMENU)IDC_EDIT_NORM_DATA_PTR, hInst, NULL); app->hEditNormDataSize = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col1 + 505, qy - 2, 40, 18, hWnd, (HMENU)IDC_EDIT_NORM_DATA_SIZE, hInst, NULL); /* Normalizer cumulative totals - row 2 */ qy += 25; CreateWindowExA(0, "STATIC", "NormTot:", WS_CHILD | WS_VISIBLE, q_col1, qy, 50, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditNormInTotPkts = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col1 + 55, qy - 2, 60, 18, hWnd, (HMENU)IDC_EDIT_NORM_IN_TOT_PKTS, hInst, NULL); app->hEditNormInTotBytes = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col1 + 120, qy - 2, 70, 18, hWnd, (HMENU)IDC_EDIT_NORM_IN_TOT_BYTES, hInst, NULL); app->hEditNormOutTotPkts = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col1 + 200, qy - 2, 60, 18, hWnd, (HMENU)IDC_EDIT_NORM_OUT_TOT_PKTS, hInst, NULL); app->hEditNormOutTotBytes = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col1 + 265, qy - 2, 70, 18, hWnd, (HMENU)IDC_EDIT_NORM_OUT_TOT_BYTES, hInst, NULL); /* ACK Debug section */ qy += 25; CreateWindowExA(0, "STATIC", "ACK Debug:", WS_CHILD | WS_VISIBLE, q_col1, qy, 70, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL); CreateWindowExA(0, "STATIC", "HitInf:", WS_CHILD | WS_VISIBLE, q_col1 + 80, qy, 40, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditAckHitInf = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col1 + 125, qy - 2, 60, 18, hWnd, (HMENU)IDC_EDIT_ACK_HIT_INF, hInst, NULL); CreateWindowExA(0, "STATIC", "HitSndQ:", WS_CHILD | WS_VISIBLE, q_col1 + 195, qy, 50, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditAckHitSndq = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col1 + 250, qy - 2, 60, 18, hWnd, (HMENU)IDC_EDIT_ACK_HIT_SNDQ, hInst, NULL); CreateWindowExA(0, "STATIC", "Miss:", WS_CHILD | WS_VISIBLE, q_col2 + 80, qy, 40, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditAckMiss = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col2 + 125, qy - 2, 60, 18, hWnd, (HMENU)IDC_EDIT_ACK_MISS, hInst, NULL); CreateWindowExA(0, "STATIC", "LnkWait:", WS_CHILD | WS_VISIBLE, q_col2 + 195, qy, 50, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditCntLinkWait = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col2 + 250, qy - 2, 60, 18, hWnd, (HMENU)IDC_EDIT_CNT_LINK_WAIT, hInst, NULL); CreateWindowExA(0, "STATIC", "TxState:", WS_CHILD | WS_VISIBLE, q_col3 + 80, qy, 40, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL); app->hEditTxState = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col3 + 125, qy - 2, 60, 18, hWnd, (HMENU)IDC_EDIT_TX_STATE, hInst, NULL); qy += 22; CreateWindowExA(0, "STATIC", "Debug:", WS_CHILD | WS_VISIBLE, q_col1 + 30, qy, 50, 16, hWnd, (HMENU)IDC_STATIC, hInst, NULL); for (int i = 0; i < 8; i++) { app->hEditDebug[i] = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", WS_CHILD | WS_VISIBLE | ES_READONLY | ES_CENTER, q_col1 + 85 + i * 65, qy - 2, 60, 18, hWnd, (HMENU)((UINT_PTR)(IDC_EDIT_DEBUG_0 + i)), hInst, NULL); } } static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { struct etcpmon_app* app = NULL; if (message == WM_CREATE) { CREATESTRUCTA* cs = (CREATESTRUCTA*)lParam; app = (struct etcpmon_app*)cs->lpCreateParams; SetWindowLongPtrA(hWnd, GWLP_USERDATA, (LONG_PTR)app); } else { app = (struct etcpmon_app*)GetWindowLongPtrA(hWnd, GWLP_USERDATA); } switch (message) { case WM_COMMAND: if (app) { switch (LOWORD(wParam)) { case IDC_BTN_CONNECT: OnConnect(app); break; case IDC_BTN_DISCONNECT: OnDisconnect(app); break; case IDC_LIST_CONNECTIONS: if (HIWORD(wParam) == LBN_SELCHANGE) { OnConnectionSelect(app); } break; } } break; case WM_SIZE: if (app && app->hWndStatus) { SendMessage(app->hWndStatus, WM_SIZE, 0, 0); } break; case WM_TIMER: if (wParam == IDC_TIMER_UPDATE && app) { OnTimer(app); return 0; } break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProcA(hWnd, message, wParam, lParam); } return 0; } static void OnConnect(struct etcpmon_app* app) { if (!app || !app->client) return; char addr[64]; char port_str[16]; GetDlgItemTextA(app->hWndMain, IDC_EDIT_ADDR, addr, sizeof(addr)); GetDlgItemTextA(app->hWndMain, IDC_EDIT_PORT, port_str, sizeof(port_str)); uint16_t port = (uint16_t)atoi(port_str); if (port == 0) port = 9090; etcpmon_gui_set_status(app, "Connecting..."); if (etcpmon_client_connect(app->client, addr, port) == 0) { app->isConnected = 1; UpdateUIState(app); etcpmon_gui_set_status(app, "Connected"); } else { etcpmon_gui_set_status(app, "Connection failed"); } } static void OnDisconnect(struct etcpmon_app* app) { if (!app || !app->client) return; etcpmon_client_disconnect(app->client); app->isConnected = 0; app->need_initial_request = 1; app->last_selected_peer_id = 0; UpdateUIState(app); etcpmon_gui_set_status(app, "Disconnected"); } static void OnConnectionSelect(struct etcpmon_app* app) { if (!app || !app->hListConnections || !app->client) return; int idx = (int)SendMessage(app->hListConnections, LB_GETCURSEL, 0, 0); if (idx == LB_ERR) return; uint64_t peer_id = (uint64_t)SendMessage(app->hListConnections, LB_GETITEMDATA, idx, 0); if (peer_id == 0) return; /* Только если действительно изменилось — иначе спамим сервер */ if (peer_id != app->last_selected_peer_id) { app->last_selected_peer_id = peer_id; etcpmon_client_select_connection(app->client, peer_id); etcpmon_gui_clear_metrics(app); /* очищаем старые цифры */ etcpmon_gui_set_status(app, "Connection selected"); } } static void OnTimer(struct etcpmon_app* app) { if (!app || !app->client) return; // fprintf(app->client->log_file, "Timer... \n"); // fflush(app->client->log_file); int processed = etcpmon_client_process(app->client); if (processed < 0) { OnDisconnect(app); // если связь оборвалась return; } if (app->need_initial_request && app->isConnected) { etcpmon_client_request_list(app->client); app->need_initial_request = 0; } } static void UpdateUIState(struct etcpmon_app* app) { if (!app) return; EnableWindow(app->hEditAddr, !app->isConnected); EnableWindow(app->hEditPort, !app->isConnected); EnableWindow(app->hBtnConnect, !app->isConnected); EnableWindow(app->hBtnDisconnect, app->isConnected); } /* Client callbacks */ static void on_connected(void* user_data) { struct etcpmon_app* app = (struct etcpmon_app*)user_data; app->isConnected = 1; etcpmon_gui_set_status(app, "Connected to server"); } void etcpmon_gui_update_graph(struct etcpmon_app* app, struct etcpmon_rsp_metrics* metrics) { if (!app || !metrics) return; if (app->hGraphWnd) { InvalidateRect(app->hGraphWnd, NULL, FALSE); } if (!app->graph_cursor_active && app->client) { 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); } } } static void on_disconnected(void* user_data) { struct etcpmon_app* app = (struct etcpmon_app*)user_data; app->isConnected = 0; UpdateUIState(app); 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); /* Автоматически выбираем первое соединение (только один раз) */ if (count > 0 && app->last_selected_peer_id == 0) { uint64_t first_id = list[0].peer_node_id; app->last_selected_peer_id = first_id; etcpmon_client_select_connection(app->client, first_id); /* Выделяем первую строку в списке */ SendMessage(app->hListConnections, LB_SETCURSEL, 0, 0); etcpmon_gui_set_status(app, "Auto-selected first connection"); } } static void on_metrics(struct etcpmon_rsp_metrics* metrics, struct etcpmon_link_metrics* links, uint8_t links_count, void* user_data) { struct etcpmon_app* app = (struct etcpmon_app*)user_data; if (app) { 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); } } } } static void on_error(const char* msg, void* user_data) { struct etcpmon_app* app = (struct etcpmon_app*)user_data; etcpmon_gui_set_status(app, msg); } int etcpmon_gui_run(struct etcpmon_app* app) { if (!app || !app->hWndMain) return -1; ShowWindow(app->hWndMain, SW_SHOW); UpdateWindow(app->hWndMain); MSG msg; while (GetMessageA(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessageA(&msg); } return (int)msg.wParam; } void etcpmon_gui_cleanup(struct etcpmon_app* app) { if (!app) return; for (int i = 0; i < GRAPH_METRICS_COUNT; i++) { if (app->hGraphPens[i]) DeleteObject(app->hGraphPens[i]); } if (app->client) { etcpmon_client_cleanup(app->client); free(app->client); app->client = NULL; } WSACleanup(); g_app = NULL; } void etcpmon_gui_set_status(struct etcpmon_app* app, const char* text) { if (!app || !app->hWndStatus) return; SendMessageA(app->hWndStatus, SB_SETTEXTA, 0, (LPARAM)text); } void etcpmon_gui_update_conn_list(struct etcpmon_app* app, struct etcpmon_conn_info* list, uint8_t count) { if (!app || !app->hListConnections) return; /* Clear existing items */ SendMessage(app->hListConnections, LB_RESETCONTENT, 0, 0); /* Add new items */ for (uint8_t i = 0; i < count; i++) { char display[64]; snprintf(display, sizeof(display), "%s (%016llX)", list[i].name, (unsigned long long)list[i].peer_node_id); int idx = (int)SendMessageA(app->hListConnections, LB_ADDSTRING, 0, (LPARAM)display); if (idx != LB_ERR) { SendMessage(app->hListConnections, LB_SETITEMDATA, idx, (LPARAM)list[i].peer_node_id); } } } /* Обновляет EDIT только если текст действительно изменился → убирает моргание */ static BOOL UpdateEditIfChanged(HWND hDlg, int nIDDlgItem, const char* fmt, ...) { char new_text[128]; va_list args; va_start(args, fmt); vsnprintf(new_text, sizeof(new_text), fmt, args); va_end(args); char old_text[128] = {0}; GetDlgItemTextA(hDlg, nIDDlgItem, old_text, sizeof(old_text)); if (strcmp(old_text, new_text) == 0) return FALSE; SetDlgItemTextA(hDlg, nIDDlgItem, new_text); InvalidateRect(GetDlgItem(hDlg, nIDDlgItem), NULL, FALSE); return TRUE; } void etcpmon_gui_update_metrics(struct etcpmon_app* app, struct etcpmon_rsp_metrics* metrics, struct etcpmon_link_metrics* links, uint8_t links_count) { if (!app || !metrics) return; HWND hMain = app->hWndMain; /* ETCP Metrics — обновляем ТОЛЬКО при изменении */ UpdateEditIfChanged(hMain, IDC_EDIT_ETCP_RTT_LAST, "%u us", metrics->etcp.rtt_last * 100); UpdateEditIfChanged(hMain, IDC_EDIT_ETCP_RTT_AVG10, "%u us", metrics->etcp.rtt_avg_10 * 100); UpdateEditIfChanged(hMain, IDC_EDIT_ETCP_RTT_AVG100, "%u us", metrics->etcp.rtt_avg_100 * 100); UpdateEditIfChanged(hMain, IDC_EDIT_ETCP_JITTER, "%u us", metrics->etcp.jitter * 100); UpdateEditIfChanged(hMain, IDC_EDIT_ETCP_BYTES_SENT, "%llu", (unsigned long long)metrics->etcp.bytes_sent_total); UpdateEditIfChanged(hMain, IDC_EDIT_ETCP_RETRANS, "%u", metrics->etcp.retrans_count); UpdateEditIfChanged(hMain, IDC_EDIT_ETCP_ACKS, "%u", metrics->etcp.ack_count); UpdateEditIfChanged(hMain, IDC_EDIT_ETCP_INFLIGHT, "%u bytes", metrics->etcp.unacked_bytes); UpdateEditIfChanged(hMain, IDC_EDIT_ETCP_LINKS, "%u", metrics->etcp.links_count); /* TUN Metrics */ UpdateEditIfChanged(hMain, IDC_EDIT_TUN_READ_BYTES, "%llu", (unsigned long long)metrics->tun.bytes_read); UpdateEditIfChanged(hMain, IDC_EDIT_TUN_WRITE_BYTES, "%llu", (unsigned long long)metrics->tun.bytes_written); UpdateEditIfChanged(hMain, IDC_EDIT_TUN_READ_PKTS, "%u", metrics->tun.packets_read); UpdateEditIfChanged(hMain, IDC_EDIT_TUN_WRITE_PKTS, "%u", metrics->tun.packets_written); UpdateEditIfChanged(hMain, IDC_EDIT_TUN_READ_ERRS, "%u", metrics->tun.read_errors); UpdateEditIfChanged(hMain, IDC_EDIT_TUN_WRITE_ERRS, "%u", metrics->tun.write_errors); /* Routing Metrics */ UpdateEditIfChanged(hMain, IDC_EDIT_RT_ROUTED, "%llu", (unsigned long long)metrics->tun.routed_packets); UpdateEditIfChanged(hMain, IDC_EDIT_RT_DROPPED, "%llu", (unsigned long long)metrics->tun.dropped_packets); UpdateEditIfChanged(hMain, IDC_EDIT_RT_TUN_IN_Q_PKTS, "%u", metrics->tun.tun_in_q_packets); UpdateEditIfChanged(hMain, IDC_EDIT_RT_TUN_IN_Q_BYTES, "%u", metrics->tun.tun_in_q_bytes); UpdateEditIfChanged(hMain, IDC_EDIT_RT_TUN_OUT_Q_PKTS, "%u", metrics->tun.tun_out_q_packets); UpdateEditIfChanged(hMain, IDC_EDIT_RT_TUN_OUT_Q_BYTES, "%u", metrics->tun.tun_out_q_bytes); UpdateEditIfChanged(hMain, IDC_EDIT_RT_COUNT, "%u", metrics->tun.rt_count); UpdateEditIfChanged(hMain, IDC_EDIT_RT_LOCAL, "%u", metrics->tun.rt_local); UpdateEditIfChanged(hMain, IDC_EDIT_RT_LEARNED, "%u", metrics->tun.rt_learned); /* Queue Metrics */ UpdateEditIfChanged(hMain, IDC_EDIT_Q_IN_Q_BYTES, "%u", metrics->etcp.input_queue_bytes); UpdateEditIfChanged(hMain, IDC_EDIT_Q_IN_Q_PKTS, "%u", metrics->etcp.input_queue_packets); UpdateEditIfChanged(hMain, IDC_EDIT_Q_IN_SEND_BYTES, "%u", metrics->etcp.input_send_q_bytes); UpdateEditIfChanged(hMain, IDC_EDIT_Q_IN_SEND_PKTS, "%u", metrics->etcp.input_send_q_packets); UpdateEditIfChanged(hMain, IDC_EDIT_Q_WAIT_ACK_BYTES, "%u", metrics->etcp.input_wait_ack_bytes); UpdateEditIfChanged(hMain, IDC_EDIT_Q_WAIT_ACK_PKTS, "%u", metrics->etcp.input_wait_ack_packets); UpdateEditIfChanged(hMain, IDC_EDIT_Q_ACK_BYTES, "%u", metrics->etcp.ack_q_bytes); UpdateEditIfChanged(hMain, IDC_EDIT_Q_ACK_PKTS, "%u", metrics->etcp.ack_q_packets); UpdateEditIfChanged(hMain, IDC_EDIT_Q_RECV_BYTES, "%u", metrics->etcp.recv_q_bytes); UpdateEditIfChanged(hMain, IDC_EDIT_Q_RECV_PKTS, "%u", metrics->etcp.recv_q_packets); UpdateEditIfChanged(hMain, IDC_EDIT_Q_OUT_BYTES, "%u", metrics->etcp.output_queue_bytes); UpdateEditIfChanged(hMain, IDC_EDIT_Q_OUT_PKTS, "%u", metrics->etcp.output_queue_packets); /* Error Counters */ UpdateEditIfChanged(hMain, IDC_EDIT_ERR_REINIT, "%u", metrics->etcp.reinit_count); UpdateEditIfChanged(hMain, IDC_EDIT_ERR_RESET, "%u", metrics->etcp.reset_count); UpdateEditIfChanged(hMain, IDC_EDIT_ERR_PKT_FMT, "%u", metrics->etcp.pkt_format_errors); /* Timer Flags */ UpdateEditIfChanged(hMain, IDC_EDIT_TIMER_RETRANS, "%s", metrics->etcp.retrans_timer_active ? "ON" : "OFF"); UpdateEditIfChanged(hMain, IDC_EDIT_TIMER_ACK_RESP, "%s", metrics->etcp.ack_resp_timer_active ? "ON" : "OFF"); /* WaitAck queue state */ UpdateEditIfChanged(hMain, IDC_EDIT_WAITACK_SUSP, "%s", metrics->etcp.wait_ack_cb_suspended ? "ON" : "OFF"); UpdateEditIfChanged(hMain, IDC_EDIT_WAITACK_CB, "%s", metrics->etcp.wait_ack_cb_set ? "ON" : "OFF"); UpdateEditIfChanged(hMain, IDC_EDIT_WAITACK_TIMER, "%s", metrics->etcp.wait_ack_resume_timeout ? "ON" : "OFF"); /* ID Metrics */ UpdateEditIfChanged(hMain, IDC_EDIT_ID_NEXT_TX, "%u", metrics->etcp.next_tx_id); UpdateEditIfChanged(hMain, IDC_EDIT_ID_LAST_RX, "%u", metrics->etcp.last_rx_id); UpdateEditIfChanged(hMain, IDC_EDIT_ID_LAST_DEL, "%u", metrics->etcp.last_delivered_id); UpdateEditIfChanged(hMain, IDC_EDIT_ID_RX_ACK_TILL, "%u", metrics->etcp.rx_ack_till); /* Normalizer */ UpdateEditIfChanged(hMain, IDC_EDIT_NORM_IN_PKTS, "%u", metrics->etcp.norm_input_pkts); UpdateEditIfChanged(hMain, IDC_EDIT_NORM_IN_BYTES, "%u", metrics->etcp.norm_input_bytes); UpdateEditIfChanged(hMain, IDC_EDIT_NORM_OUT_PKTS, "%u", metrics->etcp.norm_output_pkts); UpdateEditIfChanged(hMain, IDC_EDIT_NORM_OUT_BYTES, "%u", metrics->etcp.norm_output_bytes); UpdateEditIfChanged(hMain, IDC_EDIT_NORM_ALLOC_ERR, "%u", metrics->etcp.norm_alloc_errors); UpdateEditIfChanged(hMain, IDC_EDIT_NORM_LOGIC_ERR, "%u", metrics->etcp.norm_logic_errors); UpdateEditIfChanged(hMain, IDC_EDIT_NORM_FRAG_SIZE, "%u", metrics->etcp.norm_frag_size); UpdateEditIfChanged(hMain, IDC_EDIT_NORM_DATA_PTR, "%u", metrics->etcp.norm_data_ptr); UpdateEditIfChanged(hMain, IDC_EDIT_NORM_DATA_SIZE, "%u", metrics->etcp.norm_data_size); UpdateEditIfChanged(hMain, IDC_EDIT_NORM_IN_TOT_PKTS, "%llu", (unsigned long long)metrics->etcp.norm_in_total_pkts); UpdateEditIfChanged(hMain, IDC_EDIT_NORM_IN_TOT_BYTES, "%llu", (unsigned long long)metrics->etcp.norm_in_total_bytes); UpdateEditIfChanged(hMain, IDC_EDIT_NORM_OUT_TOT_PKTS, "%llu", (unsigned long long)metrics->etcp.norm_out_total_pkts); UpdateEditIfChanged(hMain, IDC_EDIT_NORM_OUT_TOT_BYTES, "%llu", (unsigned long long)metrics->etcp.norm_out_total_bytes); /* ACK Debug counters */ UpdateEditIfChanged(hMain, IDC_EDIT_ACK_HIT_INF, "%u", metrics->etcp.cnt_ack_hit_inf); UpdateEditIfChanged(hMain, IDC_EDIT_ACK_HIT_SNDQ, "%u", metrics->etcp.cnt_ack_hit_sndq); UpdateEditIfChanged(hMain, IDC_EDIT_ACK_MISS, "%u", metrics->etcp.cnt_ack_miss); UpdateEditIfChanged(hMain, IDC_EDIT_CNT_LINK_WAIT, "%u", metrics->etcp.cnt_link_wait); UpdateEditIfChanged(hMain, IDC_EDIT_TX_STATE, "%u", metrics->etcp.tx_state); for (int i = 0; i < 8; i++) { UpdateEditIfChanged(hMain, IDC_EDIT_DEBUG_0 + i, "%u", metrics->etcp.debug[i]); } /* Links list */ if (app->hListLinks) { SendMessage(app->hListLinks, LB_RESETCONTENT, 0, 0); for (uint8_t i = 0; i < links_count; i++) { char line1[320]; char line2[256]; snprintf(line1, sizeof(line1), "Link %u: Status=%s, EncErr=%u, DecErr=%u, " "SndErr=%u, RcvErr=%u, Enc=%llu, Dec=%llu, " "BW=%u Kbps, NAT=%u, RTT=%u us, TT=%u us", links[i].local_link_id, links[i].status ? "UP" : "DOWN", links[i].encrypt_errors, links[i].decrypt_errors, links[i].send_errors, links[i].recv_errors, (unsigned long long)links[i].total_encrypted, (unsigned long long)links[i].total_decrypted, links[i].bandwidth, links[i].nat_changes_count, links[i].rtt_last * 100, links[i].tt_last * 100); snprintf(line2, sizeof(line2), " Timers: Init=%s, KA=%s, Shaper=%s | " "KAsent=%u, KArecv=%u", links[i].init_timer_active ? "ON" : "OFF", links[i].keepalive_timer_active ? "ON" : "OFF", links[i].shaper_timer_active ? "ON" : "OFF", links[i].keepalive_sent, links[i].keepalive_recv); SendMessageA(app->hListLinks, LB_ADDSTRING, 0, (LPARAM)line1); SendMessageA(app->hListLinks, LB_ADDSTRING, 0, (LPARAM)line2); } InvalidateRect(app->hListLinks, NULL, FALSE); } /* График — принудительно перерисовываем */ if (app->hGraphWnd) { InvalidateRect(app->hGraphWnd, NULL, TRUE); /* TRUE = стираем фон */ UpdateWindow(app->hGraphWnd); /* сразу отрисовать */ } } void etcpmon_gui_clear_metrics(struct etcpmon_app* app) { if (!app) return; SetDlgItemTextA(app->hWndMain, IDC_EDIT_ETCP_NAME, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_ETCP_RTT_LAST, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_ETCP_RTT_AVG10, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_ETCP_RTT_AVG100, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_ETCP_JITTER, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_ETCP_BYTES_SENT, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_ETCP_RETRANS, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_ETCP_ACKS, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_ETCP_INFLIGHT, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_ETCP_LINKS, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_TUN_READ_BYTES, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_TUN_WRITE_BYTES, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_TUN_READ_PKTS, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_TUN_WRITE_PKTS, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_TUN_READ_ERRS, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_TUN_WRITE_ERRS, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_RT_ROUTED, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_RT_DROPPED, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_RT_TUN_IN_Q_PKTS, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_RT_TUN_IN_Q_BYTES, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_RT_TUN_OUT_Q_PKTS, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_RT_TUN_OUT_Q_BYTES, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_RT_COUNT, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_RT_LOCAL, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_RT_LEARNED, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_Q_IN_Q_BYTES, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_Q_IN_Q_PKTS, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_Q_IN_SEND_BYTES, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_Q_IN_SEND_PKTS, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_Q_WAIT_ACK_BYTES, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_Q_WAIT_ACK_PKTS, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_Q_ACK_BYTES, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_Q_ACK_PKTS, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_Q_RECV_BYTES, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_Q_RECV_PKTS, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_Q_OUT_BYTES, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_Q_OUT_PKTS, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_ERR_REINIT, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_ERR_RESET, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_ERR_PKT_FMT, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_TIMER_RETRANS, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_TIMER_ACK_RESP, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_WAITACK_SUSP, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_WAITACK_CB, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_WAITACK_TIMER, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_ID_NEXT_TX, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_ID_LAST_RX, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_ID_LAST_DEL, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_ID_RX_ACK_TILL, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_NORM_IN_PKTS, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_NORM_IN_BYTES, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_NORM_OUT_PKTS, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_NORM_OUT_BYTES, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_NORM_ALLOC_ERR, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_NORM_LOGIC_ERR, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_NORM_FRAG_SIZE, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_NORM_DATA_PTR, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_NORM_DATA_SIZE, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_NORM_IN_TOT_PKTS, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_NORM_IN_TOT_BYTES, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_NORM_OUT_TOT_PKTS, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_NORM_OUT_TOT_BYTES, ""); /* ACK Debug counters */ SetDlgItemTextA(app->hWndMain, IDC_EDIT_ACK_HIT_INF, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_ACK_HIT_SNDQ, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_ACK_MISS, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_CNT_LINK_WAIT, ""); SetDlgItemTextA(app->hWndMain, IDC_EDIT_TX_STATE, ""); for (int i = 0; i < 8; i++) { SetDlgItemTextA(app->hWndMain, IDC_EDIT_DEBUG_0 + i, ""); } if (app->hListLinks) { SendMessage(app->hListLinks, LB_RESETCONTENT, 0, 0); } }