Files
wterm/src/main.c
T
wt ee29bfe525
Prepare Build and Testing executable binary / Prepare-Build-Testing-With-Make-Without-sqlite3 (push) Successful in 5s
Prepare Build and Testing executable binary / Prepare-Build-Testing-With-CMake-Without-sqlite3 (push) Successful in 9s
Prepare Build and Testing executable binary / Prepare-Build-Testing-With-Meson-Without-sqlite3 (push) Successful in 7s
updated added tabs and closing tabs
2025-11-18 13:01:34 +07:00

386 lines
13 KiB
C

#include "terminal.h"
#include <cJSON.h>
#include <errno.h>
#include <mongoose.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#define HTTP_PORT "8888"
#define STATIC_DIR "./static/"
#define INPUT_BUFFER_SIZE 256
static const char *s_http_addr = "http://0.0.0.0:8888";
static struct mg_mgr mgr;
struct client_data {
struct terminal_session term;
bool terminal_ready;
bool closing; // Флаг для закрытия соединения
char input_buffer[INPUT_BUFFER_SIZE];
size_t input_buffer_len;
};
// Функция для логирования запросов
static void log_request(struct mg_connection *c, struct mg_http_message *hm) {
printf("HTTP Request: %.*s %.*s\n", (int)hm->method.len, hm->method.buf,
(int)hm->uri.len, hm->uri.buf);
}
// Проверяем, жив ли дочерний процесс
static bool is_process_alive(pid_t pid) {
if (pid <= 0)
return false;
int status;
pid_t result = waitpid(pid, &status, WNOHANG);
if (result == 0) {
// Процесс все еще работает
return true;
} else if (result == -1) {
// Ошибка
if (errno == ECHILD) {
// Процесс не существует
return false;
}
perror("waitpid");
return false;
} else {
// Процесс завершился
return false;
}
}
// Перезапускаем терминальную сессию
static void restart_terminal_session(struct mg_connection *c,
struct client_data *cd) {
if (!cd)
return;
printf("Restarting terminal session...\n");
// Уничтожаем старую сессию
if (cd->terminal_ready) {
destroy_terminal_session(&cd->term);
}
// Создаем новую сессию
const char *shell = getenv("SHELL");
if (!shell)
shell = "/bin/bash";
if (create_terminal_session(&cd->term, shell) == 0) {
cd->terminal_ready = true;
mg_ws_send(c, "\r\n*** Terminal restarted ***\r\n", 29, WEBSOCKET_OP_TEXT);
} else {
mg_ws_send(c, "\r\n*** Failed to restart terminal ***\r\n", 37,
WEBSOCKET_OP_TEXT);
cd->terminal_ready = false;
}
}
// Функция для проверки команды exit в буфере
static bool check_for_exit_command(struct mg_connection *c,
struct client_data *cd) {
for (size_t i = 0; i < cd->input_buffer_len; i++) {
if (cd->input_buffer[i] == 0x04) {
printf("Ctrl+D pressed\n");
write_to_terminal(&cd->term, &cd->input_buffer[i], 1);
usleep(100);
mg_ws_send(c, "CLOSE_TAB", 9, WEBSOCKET_OP_TEXT);
cd->closing = true;
cd->input_buffer_len = 0;
return true;
}
}
// Ищем символ новой строки в буфере
for (size_t i = 0; i < cd->input_buffer_len; i++) {
if (cd->input_buffer[i] == '\n' || cd->input_buffer[i] == '\r') {
// Нашли конец строки, проверяем команду
cd->input_buffer[i] = '\0'; // Завершаем строку
printf("Command: %s\n", cd->input_buffer);
if (cd->input_buffer[i - 1] == 'x') {
printf("Ctrl+D pressed\n");
write_to_terminal(&cd->term, &cd->input_buffer[i], 1);
usleep(100);
mg_ws_send(c, "CLOSE_TAB", 9, WEBSOCKET_OP_TEXT);
cd->closing = true;
cd->input_buffer_len = 0;
return true;
}
// Проверяем, является ли команда "exit"
if (strcmp(cd->input_buffer, "exit") == 0) {
printf("Exit command detected, closing terminal session\n");
// Ждем немного чтобы команда выполнилась
usleep(100);
// Отправляем клиенту сообщение о закрытии вкладки
mg_ws_send(c, "CLOSE_TAB", 9, WEBSOCKET_OP_TEXT);
// Помечаем соединение для закрытия
cd->closing = true;
// Сбрасываем буфер
cd->input_buffer_len = 0;
return true;
}
// Сдвигаем буфер, удаляя обработанную строку
size_t remaining = cd->input_buffer_len - i - 1;
if (remaining > 0) {
memmove(cd->input_buffer, cd->input_buffer + i + 1, remaining);
}
cd->input_buffer_len = remaining;
return false;
}
}
return false;
}
// Обработчик WebSocket сообщений
static void handle_websocket_message(struct mg_connection *c,
struct mg_ws_message *wm,
struct client_data *cd) {
if (!cd)
return;
if (wm->data.len == 0) {
return;
}
// Парсим JSON для проверки resize сообщения
cJSON *json = cJSON_ParseWithLength(wm->data.buf, wm->data.len);
if (json != NULL) {
cJSON *cols = cJSON_GetObjectItemCaseSensitive(json, "cols");
cJSON *rows = cJSON_GetObjectItemCaseSensitive(json, "rows");
if (cJSON_IsNumber(cols) && cJSON_IsNumber(rows)) {
printf("Resizing terminal to %dx%d\n", rows->valueint, cols->valueint);
resize_terminal(&cd->term, rows->valueint, cols->valueint);
cJSON_Delete(json);
return;
}
cJSON_Delete(json);
}
// Добавляем данные в буфер ввода
size_t bytes_to_copy = wm->data.len;
if (cd->input_buffer_len + bytes_to_copy >= INPUT_BUFFER_SIZE) {
// Буфер переполнен, сбрасываем его
cd->input_buffer_len = 0;
}
memcpy(cd->input_buffer + cd->input_buffer_len, wm->data.buf, bytes_to_copy);
cd->input_buffer_len += bytes_to_copy;
printf("Input received: %s\n", wm->data.buf);
if (check_for_exit_command(c, cd)) {
return; // Вкладка будет закрыта, выходим
}
// Обычный ввод от пользователя
write_to_terminal(&cd->term, wm->data.buf, wm->data.len);
}
// Обработчик HTTP запросов
static void fn(struct mg_connection *c, int ev, void *ev_data) {
if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *hm = (struct mg_http_message *)ev_data;
log_request(c, hm);
if (mg_match(hm->uri, mg_str("/ws"), nullptr)) {
// Проверяем, нет ли уже активного соединения
if (c->fn_data != NULL) {
printf("WebSocket connection already exists for this connection, "
"cleaning up...\n");
struct client_data *old_cd = (struct client_data *)c->fn_data;
destroy_terminal_session(&old_cd->term);
free(old_cd);
c->fn_data = NULL;
}
// Upgrade до WebSocket
struct client_data *cd = calloc(1, sizeof(struct client_data));
if (!cd) {
mg_http_reply(c, 500, "Content-Type: text/plain\r\n",
"Memory allocation failed");
return;
}
// Создаем терминальную сессию
const char *shell = getenv("SHELL");
if (!shell)
shell = "/bin/bash";
printf("Creating new terminal session with shell: %s\n", shell);
if (create_terminal_session(&cd->term, shell) != 0) {
mg_http_reply(c, 500, "Content-Type: text/plain\r\n",
"Failed to create terminal session");
free(cd);
return;
}
cd->terminal_ready = true;
cd->closing = false;
c->fn_data = cd;
// Upgrade to WebSocket
struct mg_str *ws_proto =
mg_http_get_header(hm, "Sec-WebSocket-Protocol");
if (ws_proto != NULL) {
mg_ws_upgrade(c, hm, ws_proto->buf);
} else {
mg_ws_upgrade(c, hm, NULL);
}
printf("WebSocket upgrade completed\n");
} else if (mg_match(hm->uri, mg_str("/"), nullptr)) {
// Главная страница
struct mg_http_serve_opts opts = {.root_dir = STATIC_DIR};
mg_http_serve_file(c, hm, "./static/index.html", &opts);
} else if (mg_match(hm->uri, mg_str("/xterm.js"), nullptr)) {
// xterm.js
struct mg_http_serve_opts opts = {.root_dir = STATIC_DIR};
mg_http_serve_file(c, hm, "./static/xterm.js", &opts);
} else if (mg_match(hm->uri, mg_str("/xterm.css"), nullptr)) {
// xterm.css
struct mg_http_serve_opts opts = {.root_dir = STATIC_DIR};
mg_http_serve_file(c, hm, "./static/xterm.css", &opts);
} else if (mg_match(hm->uri, mg_str("/xterm-addon-fit.js"), nullptr)) {
// xterm-addon-fit.js
struct mg_http_serve_opts opts = {.root_dir = STATIC_DIR};
mg_http_serve_file(c, hm, "./static/xterm-addon-fit.js", &opts);
} else {
mg_http_reply(c, 404, "Content-Type: text/plain\r\n", "Not Found");
}
} else if (ev == MG_EV_WS_OPEN) {
// WebSocket соединение установлено
printf("WebSocket connection opened\n");
struct client_data *cd = (struct client_data *)c->fn_data;
if (cd) {
mg_ws_send(c, "Terminal ready - type 'exit' to close tab\r\n", 44,
WEBSOCKET_OP_TEXT);
}
} else if (ev == MG_EV_WS_MSG) {
// Сообщение от WebSocket клиента
struct mg_ws_message *wm = (struct mg_ws_message *)ev_data;
struct client_data *cd = (struct client_data *)c->fn_data;
handle_websocket_message(c, wm, cd);
} else if (ev == MG_EV_CLOSE) {
// Закрытие соединения
struct client_data *cd = (struct client_data *)c->fn_data;
if (cd) {
printf("WebSocket connection closed, cleaning up...\n");
destroy_terminal_session(&cd->term);
free(cd);
c->fn_data = NULL;
}
} else if (ev == MG_EV_POLL) {
// Проверяем данные от терминала и отправляем через WebSocket
struct mg_connection *conn;
for (conn = mgr.conns; conn != NULL; conn = conn->next) {
if (!conn->is_websocket)
continue;
struct client_data *cd = (struct client_data *)conn->fn_data;
if (cd && cd->terminal_ready && !cd->closing) {
// Проверяем, не завершился ли процесс
if (!is_process_alive(cd->term.child_pid)) {
printf("Terminal process died, restarting...\n");
restart_terminal_session(conn, cd);
continue;
}
// Читаем данные из терминала
char buffer[1024];
ssize_t bytes_read =
read_from_terminal(&cd->term, buffer, sizeof(buffer));
if (bytes_read > 0) {
mg_ws_send(conn, buffer, bytes_read, WEBSOCKET_OP_BINARY);
} else if (bytes_read == 0) {
// EOF - процесс завершился
printf("EOF from terminal, restarting...\n");
restart_terminal_session(conn, cd);
} else if (bytes_read < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
// Ошибка чтения
printf("Error reading from terminal: %s\n", strerror(errno));
restart_terminal_session(conn, cd);
}
}
}
}
}
// Обработчик сигналов для graceful shutdown
static void signal_handler(int sig) {
printf("\nShutting down server...\n");
// Очищаем все соединения
struct mg_connection *conn;
for (conn = mgr.conns; conn != NULL; conn = conn->next) {
if (conn->fn_data) {
struct client_data *cd = (struct client_data *)conn->fn_data;
destroy_terminal_session(&cd->term);
free(cd);
conn->fn_data = NULL;
}
}
mg_mgr_free(&mgr);
exit(0);
}
// Проверяем существование директории
static bool directory_exists(const char *path) {
struct stat st;
return stat(path, &st) == 0 && S_ISDIR(st.st_mode);
}
int main(void) {
// Устанавливаем обработчики сигналов
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
// Инициализируем менеджер соединений
mg_mgr_init(&mgr);
printf("Mongoose version: %s\n", MG_VERSION);
// Проверяем существование статической директории
if (!directory_exists(STATIC_DIR)) {
fprintf(stderr, "Static directory '%s' not found! Creating it...\n",
STATIC_DIR);
if (mkdir(STATIC_DIR, 0755) != 0) {
fprintf(stderr, "Failed to create static directory\n");
return 1;
}
}
// Создаем HTTP сервер
struct mg_connection *c = mg_http_listen(&mgr, s_http_addr, fn, NULL);
if (c == NULL) {
fprintf(stderr, "Failed to start server on %s\n", s_http_addr);
return 1;
}
printf("Starting web terminal server on %s\n", s_http_addr);
printf("Static files directory: %s\n", STATIC_DIR);
printf("Server is ready. Open http://localhost:8000 in your browser\n");
// Основной цикл
while (true) {
mg_mgr_poll(&mgr, 50); // 50ms timeout
}
mg_mgr_free(&mgr);
return 0;
}