|
|
|
|
@ -1,35 +1,28 @@
|
|
|
|
|
/*
|
|
|
|
|
* 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 <winsock2.h> |
|
|
|
|
#include <windows.h> |
|
|
|
|
#include <commctrl.h> |
|
|
|
|
|
|
|
|
|
#include "etcpmon_gui.h" |
|
|
|
|
#include "etcpmon_client.h" |
|
|
|
|
#include "etcpmon_protocol.h" |
|
|
|
|
#include <stdio.h> |
|
|
|
|
#include <stdlib.h> |
|
|
|
|
#include <string.h> |
|
|
|
|
|
|
|
|
|
//#pragma comment(lib, "comctl32.lib")
|
|
|
|
|
//#pragma comment(lib, "user32.lib")
|
|
|
|
|
//#pragma comment(lib, "gdi32.lib")
|
|
|
|
|
|
|
|
|
|
#define WINDOW_WIDTH 900 |
|
|
|
|
#define WINDOW_HEIGHT 900 |
|
|
|
|
#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); |
|
|
|
|
@ -38,7 +31,6 @@ 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); |
|
|
|
|
@ -47,7 +39,6 @@ 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); |
|
|
|
|
|
|
|
|
|
/* Helper functions */ |
|
|
|
|
static void SetDlgItemTextFmt(HWND hDlg, int nIDDlgItem, const char* fmt, ...) { |
|
|
|
|
char buf[256]; |
|
|
|
|
@ -57,7 +48,6 @@ static void SetDlgItemTextFmt(HWND hDlg, int nIDDlgItem, const char* fmt, ...) {
|
|
|
|
|
va_end(args); |
|
|
|
|
SetDlgItemTextA(hDlg, nIDDlgItem, buf); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
int etcpmon_gui_init(struct etcpmon_app* app, HINSTANCE hInstance) { |
|
|
|
|
if (!app) return -1; |
|
|
|
|
|
|
|
|
|
@ -111,7 +101,6 @@ int etcpmon_gui_init(struct etcpmon_app* app, HINSTANCE hInstance) {
|
|
|
|
|
|
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
HWND etcpmon_gui_create_window(struct etcpmon_app* app) { |
|
|
|
|
if (!app) return NULL; |
|
|
|
|
|
|
|
|
|
@ -141,7 +130,6 @@ HWND etcpmon_gui_create_window(struct etcpmon_app* app) {
|
|
|
|
|
/* Create all controls */ |
|
|
|
|
CreateControls(app); |
|
|
|
|
SetTimer(app->hWndMain, IDC_TIMER_UPDATE, UPDATE_INTERVAL, NULL); |
|
|
|
|
|
|
|
|
|
/* Set default values */ |
|
|
|
|
SetDlgItemTextA(app->hWndMain, IDC_EDIT_ADDR, "127.0.0.1"); |
|
|
|
|
SetDlgItemTextA(app->hWndMain, IDC_EDIT_PORT, "9090"); |
|
|
|
|
@ -159,12 +147,10 @@ HWND etcpmon_gui_create_window(struct etcpmon_app* app) {
|
|
|
|
|
for (int i = 0; i < GRAPH_METRICS_COUNT; i++) { |
|
|
|
|
app->hGraphPens[i] = CreatePen(PS_SOLID, 2, graph_colors[i]); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
UpdateUIState(app); |
|
|
|
|
|
|
|
|
|
return app->hWndMain; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void CreateControls(struct etcpmon_app* app) { |
|
|
|
|
HWND hWnd = app->hWndMain; |
|
|
|
|
HINSTANCE hInst = app->hInstance; |
|
|
|
|
@ -215,7 +201,6 @@ static void CreateControls(struct etcpmon_app* app) {
|
|
|
|
|
|
|
|
|
|
/* Graph area */ |
|
|
|
|
y += 140; |
|
|
|
|
|
|
|
|
|
app->hGraphWnd = CreateWindowExA( |
|
|
|
|
WS_EX_CLIENTEDGE, |
|
|
|
|
"ETCPMonGraph", "", |
|
|
|
|
@ -393,7 +378,6 @@ static void CreateControls(struct etcpmon_app* app) {
|
|
|
|
|
WS_CHILD | WS_VISIBLE | LBS_NOTIFY | WS_VSCROLL | LBS_NOINTEGRALHEIGHT, |
|
|
|
|
10, y + 20, 880, 100, hWnd, (HMENU)IDC_LIST_LINKS, hInst, NULL); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { |
|
|
|
|
struct etcpmon_app* app = NULL; |
|
|
|
|
|
|
|
|
|
@ -429,7 +413,6 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM l
|
|
|
|
|
SendMessage(app->hWndStatus, WM_SIZE, 0, 0); |
|
|
|
|
} |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
case WM_TIMER: |
|
|
|
|
if (wParam == IDC_TIMER_UPDATE && app) { |
|
|
|
|
OnTimer(app); |
|
|
|
|
@ -447,7 +430,6 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM l
|
|
|
|
|
|
|
|
|
|
return 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void OnConnect(struct etcpmon_app* app) { |
|
|
|
|
if (!app || !app->client) return; |
|
|
|
|
|
|
|
|
|
@ -470,7 +452,6 @@ static void OnConnect(struct etcpmon_app* app) {
|
|
|
|
|
etcpmon_gui_set_status(app, "Connection failed"); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void OnDisconnect(struct etcpmon_app* app) { |
|
|
|
|
if (!app || !app->client) return; |
|
|
|
|
|
|
|
|
|
@ -480,7 +461,6 @@ static void OnDisconnect(struct etcpmon_app* app) {
|
|
|
|
|
etcpmon_gui_clear_metrics(app); |
|
|
|
|
etcpmon_gui_set_status(app, "Disconnected"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void OnConnectionSelect(struct etcpmon_app* app) { |
|
|
|
|
if (!app || !app->client) return; |
|
|
|
|
|
|
|
|
|
@ -493,32 +473,26 @@ static void OnConnectionSelect(struct etcpmon_app* app) {
|
|
|
|
|
/* Request metrics for this connection */ |
|
|
|
|
etcpmon_client_select_connection(app->client, peer_id); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void OnTimer(struct etcpmon_app* app) { |
|
|
|
|
if (!app || !app->client) return; |
|
|
|
|
|
|
|
|
|
/* 1. Сеть */ |
|
|
|
|
etcpmon_client_process(app->client); |
|
|
|
|
|
|
|
|
|
/* 2. Первый запрос списка после подключения */ |
|
|
|
|
static int first_connect = 1; |
|
|
|
|
if (first_connect && app->isConnected) { |
|
|
|
|
etcpmon_client_request_list(app->client); |
|
|
|
|
first_connect = 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* 3. Запрос метрик */ |
|
|
|
|
if (app->client->selected_peer_id != 0) { |
|
|
|
|
etcpmon_client_request_metrics(app->client); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* 4. График — 20 раз в секунду */ |
|
|
|
|
if (app->hGraphWnd) { |
|
|
|
|
InvalidateRect(app->hGraphWnd, NULL, FALSE); |
|
|
|
|
UpdateWindow(app->hGraphWnd); /* форсируем немедленную перерисовку */ |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void UpdateUIState(struct etcpmon_app* app) { |
|
|
|
|
if (!app) return; |
|
|
|
|
|
|
|
|
|
@ -527,7 +501,6 @@ static void UpdateUIState(struct etcpmon_app* app) {
|
|
|
|
|
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; |
|
|
|
|
@ -540,7 +513,6 @@ static void on_connected(void* user_data) {
|
|
|
|
|
} |
|
|
|
|
etcpmon_client_request_list(app->client); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void etcpmon_gui_update_graph(struct etcpmon_app* app, struct etcpmon_rsp_metrics* metrics) { |
|
|
|
|
if (!app || !metrics) return; |
|
|
|
|
|
|
|
|
|
@ -556,26 +528,22 @@ 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; |
|
|
|
|
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); |
|
|
|
|
etcpmon_gui_set_status(app, "Connection list updated"); |
|
|
|
|
|
|
|
|
|
/* Automatically select the first connection if available */ |
|
|
|
|
if (count > 0 && app->hListConnections) { |
|
|
|
|
SendMessage(app->hListConnections, LB_SETCURSEL, 0, 0); |
|
|
|
|
OnConnectionSelect(app); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
static void on_metrics(struct etcpmon_rsp_metrics* metrics,
|
|
|
|
|
struct etcpmon_link_metrics* links,
|
|
|
|
|
uint8_t links_count, void* user_data) { |
|
|
|
|
@ -583,7 +551,6 @@ static void on_metrics(struct etcpmon_rsp_metrics* metrics,
|
|
|
|
|
if (app) { |
|
|
|
|
etcpmon_gui_update_metrics(app, metrics, links, links_count); |
|
|
|
|
etcpmon_client_add_to_history(app->client, metrics); |
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
@ -591,18 +558,14 @@ static void on_metrics(struct etcpmon_rsp_metrics* metrics,
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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); |
|
|
|
|
@ -610,29 +573,24 @@ int etcpmon_gui_run(struct etcpmon_app* app) {
|
|
|
|
|
} |
|
|
|
|
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) { |
|
|
|
|
@ -653,7 +611,6 @@ void etcpmon_gui_update_conn_list(struct etcpmon_app* app,
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Обновляет EDIT только если текст действительно изменился → убирает моргание */ |
|
|
|
|
static BOOL UpdateEditIfChanged(HWND hDlg, int nIDDlgItem, const char* fmt, ...) |
|
|
|
|
{ |
|
|
|
|
@ -662,27 +619,21 @@ static BOOL UpdateEditIfChanged(HWND hDlg, int nIDDlgItem, const char* fmt, ...)
|
|
|
|
|
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); |
|
|
|
|
@ -693,7 +644,6 @@ void etcpmon_gui_update_metrics(struct etcpmon_app* app,
|
|
|
|
|
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); |
|
|
|
|
@ -701,7 +651,6 @@ void etcpmon_gui_update_metrics(struct etcpmon_app* app,
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
|
/* Links list */ |
|
|
|
|
if (app->hListLinks) { |
|
|
|
|
SendMessage(app->hListLinks, LB_RESETCONTENT, 0, 0); |
|
|
|
|
@ -721,21 +670,17 @@ void etcpmon_gui_update_metrics(struct etcpmon_app* app,
|
|
|
|
|
(unsigned long long)links[i].total_decrypted, |
|
|
|
|
links[i].bandwidth, |
|
|
|
|
links[i].nat_changes_count); |
|
|
|
|
|
|
|
|
|
SendMessageA(app->hListLinks, LB_ADDSTRING, 0, (LPARAM)display); |
|
|
|
|
} |
|
|
|
|
InvalidateRect(app->hListLinks, NULL, FALSE); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* График */ |
|
|
|
|
if (app->hGraphWnd) { |
|
|
|
|
InvalidateRect(app->hGraphWnd, NULL, FALSE); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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, ""); |
|
|
|
|
|