initial commit

This commit is contained in:
wt committed 2025-11-17 16:41:34 +07:00
commit 6d39a0c3b0
16 files changed
+33718

No files matched your search

+13
View File
@@ -0,0 +1,13 @@
*.o
.vscode
.cache
.idea
build
builddir
auth_service
users.db
users_db.json
*.swp
*.log
.proxy.sh
.no-proxy.sh
+27
View File
@@ -0,0 +1,27 @@
cmake_minimum_required (VERSION 3.6)
project(wterm LANGUAGES C)
set(CMAKE_C_STANDARD 23)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_C_FLAGS "-ffunction-sections -fdata-sections")
if (APPLE)
elseif (UNIX)
set(CMAKE_EXE_LINKER_FLAGS "-Wl,--gc-sections")
endif()
include_directories(./src/lib/ ./src ${SQLite3_INCLUDE_DIRS})
FILE(GLOB SOURCES src/*.c src/lib/*.c src/file_db/*.c src/config/*.c src/api/*.c src/database/*.c src/security/*.c src/utils/*.c)
option(USE_WERROR "Enable SQLite3 support" OFF)
if(USE_WERROR)
add_definitions(-Werror)
endif()
add_executable(
wterm
${SOURCES}
)
target_link_libraries(wterm pthread)
+36
View File
@@ -0,0 +1,36 @@
CC = gcc
CFLAGS = -Wall -g -std=gnu2x -ffunction-sections -fdata-sections
# -DMG_TLS=MG_TLS_OPENSSL
LDFLAGS =
INCLUDES = -I./src/lib -I./src
SRCS = $(wildcard src/lib/*.c src/file_db/*.c src/*.c src/config/*.c src/database/*.c src/api/*.c src/security/*.c src/utils/*.c)
OBJS = $(SRCS:.c=.o)
TARGET = wterm
BUILD_DIR=./build
CROSS_COMPILE =
LIBS = -lpthread
# -lcrypto -lssl
USE_SQLITE3 ?= 0
WERROR ?= 0
ifeq ($(shell uname), Linux)
LDFLAGS += -Wl,--gc-sections
endif
ifeq ($(WERROR), 1)
CFLAGS += -Werror
endif
all: $(TARGET)
$(TARGET): $(OBJS)
$(CROSS_COMPILE)$(CC) $(LDFLAGS) $^ -o $@ $(LIBS)
%.o: %.c
$(CROSS_COMPILE)$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
clean:
rm -f $(OBJS) $(TARGET) $(shell find . -type f -name '*.log')
test: $(TARGET)
bash ./tests.sh
+30
View File
@@ -0,0 +1,30 @@
project('wterm', 'c', default_options: ['c_std=gnu2x'])
include_dirs = include_directories('src')
deps = [dependency('openssl')]
extra_c_args = ['-ffunction-sections', '-fdata-sections']
extra_c_link_args = []
system = host_machine.system()
if system == 'linux'
extra_c_link_args += ['-Wl,--gc-sections']
endif
use_werror = get_option('use_werror')
sources = [
'src/main.c',
'src/mongoose.c',
'src/cJSON.c',
'src/terminal.c',
]
if use_werror
add_project_arguments('-Werror', language: 'c')
endif
executable(
'wterm',
sources,
include_directories: include_dirs,
dependencies: deps,
c_args: extra_c_args,
link_args: extra_c_link_args,
)
+1
View File
@@ -0,0 +1 @@
option('use_werror', type: 'boolean', value: false, description: 'Build with warning as errors')
+3191
View File
File diff suppressed because it is too large. Load diff
+306
View File
@@ -0,0 +1,306 @@
/*
Copyright (c) 2009-2017 Dave Gamble and cJSON contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#ifndef cJSON__h
#define cJSON__h
#ifdef __cplusplus
extern "C"
{
#endif
#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32))
#define __WINDOWS__
#endif
#ifdef __WINDOWS__
/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options:
CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols
CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default)
CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol
For *nix builds that support visibility attribute, you can define similar behavior by
setting default visibility to hidden by adding
-fvisibility=hidden (for gcc)
or
-xldscope=hidden (for sun cc)
to CFLAGS
then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does
*/
#define CJSON_CDECL __cdecl
#define CJSON_STDCALL __stdcall
/* export symbols by default, this is necessary for copy pasting the C and header file */
#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS)
#define CJSON_EXPORT_SYMBOLS
#endif
#if defined(CJSON_HIDE_SYMBOLS)
#define CJSON_PUBLIC(type) type CJSON_STDCALL
#elif defined(CJSON_EXPORT_SYMBOLS)
#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL
#elif defined(CJSON_IMPORT_SYMBOLS)
#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL
#endif
#else /* !__WINDOWS__ */
#define CJSON_CDECL
#define CJSON_STDCALL
#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY)
#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type
#else
#define CJSON_PUBLIC(type) type
#endif
#endif
/* project version */
#define CJSON_VERSION_MAJOR 1
#define CJSON_VERSION_MINOR 7
#define CJSON_VERSION_PATCH 18
#include <stddef.h>
/* cJSON Types: */
#define cJSON_Invalid (0)
#define cJSON_False (1 << 0)
#define cJSON_True (1 << 1)
#define cJSON_NULL (1 << 2)
#define cJSON_Number (1 << 3)
#define cJSON_String (1 << 4)
#define cJSON_Array (1 << 5)
#define cJSON_Object (1 << 6)
#define cJSON_Raw (1 << 7) /* raw json */
#define cJSON_IsReference 256
#define cJSON_StringIsConst 512
/* The cJSON structure: */
typedef struct cJSON
{
/* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
struct cJSON *next;
struct cJSON *prev;
/* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
struct cJSON *child;
/* The type of the item, as above. */
int type;
/* The item's string, if type==cJSON_String and type == cJSON_Raw */
char *valuestring;
/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
int valueint;
/* The item's number, if type==cJSON_Number */
double valuedouble;
/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
char *string;
} cJSON;
typedef struct cJSON_Hooks
{
/* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */
void *(CJSON_CDECL *malloc_fn)(size_t sz);
void (CJSON_CDECL *free_fn)(void *ptr);
} cJSON_Hooks;
typedef int cJSON_bool;
/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them.
* This is to prevent stack overflows. */
#ifndef CJSON_NESTING_LIMIT
#define CJSON_NESTING_LIMIT 1000
#endif
/* Limits the length of circular references can be before cJSON rejects to parse them.
* This is to prevent stack overflows. */
#ifndef CJSON_CIRCULAR_LIMIT
#define CJSON_CIRCULAR_LIMIT 10000
#endif
/* returns the version of cJSON as a string */
CJSON_PUBLIC(const char*) cJSON_Version(void);
/* Supply malloc, realloc and free functions to cJSON */
CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks);
/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */
/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */
CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value);
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length);
/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */
/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */
CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated);
CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated);
/* Render a cJSON entity to text for transfer/storage. */
CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item);
/* Render a cJSON entity to text for transfer/storage without any formatting. */
CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item);
/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */
CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt);
/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */
/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */
CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format);
/* Delete a cJSON entity and all subentities. */
CJSON_PUBLIC(void) cJSON_Delete(cJSON *item);
/* Returns the number of items in an array (or object). */
CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array);
/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */
CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index);
/* Get item "string" from object. Case insensitive. */
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string);
CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string);
CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string);
/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */
CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void);
/* Check item type and return its value */
CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item);
CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item);
/* These functions check the type of an item */
CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item);
CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item);
/* These calls create a cJSON item of the appropriate type. */
CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean);
CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num);
CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string);
/* raw json */
CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw);
CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void);
CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void);
/* Create a string where valuestring references a string so
* it will not be freed by cJSON_Delete */
CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string);
/* Create an object/array that only references it's elements so
* they will not be freed by cJSON_Delete */
CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child);
CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child);
/* These utilities create an Array of count items.
* The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/
CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count);
CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count);
/* Append item to the specified array/object. */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item);
/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object.
* WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before
* writing to `item->string` */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item);
/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item);
/* Remove/Detach items from Arrays/Objects. */
CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string);
CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string);
/* Update array items. */
CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);
CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem);
/* Duplicate a cJSON item */
CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse);
/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will
* need to be released. With recurse!=0, it will duplicate any children connected to the item.
* The item->next and ->prev pointers are always zero on return from Duplicate. */
/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal.
* case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */
CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive);
/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings.
* The input pointer json cannot point to a read-only address area, such as a string constant,
* but should point to a readable and writable address area. */
CJSON_PUBLIC(void) cJSON_Minify(char *json);
/* Helper functions for creating and adding items to an object at the same time.
* They return the added item or NULL on failure. */
CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean);
CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number);
CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string);
CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw);
CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name);
CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);
/* When assigning an integer value, it needs to be propagated to valuedouble too. */
#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number))
/* helper for the cJSON_SetNumberValue macro */
CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number);
#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number))
/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */
CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring);
/* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/
#define cJSON_SetBoolValue(object, boolValue) ( \
(object != NULL && ((object)->type & (cJSON_False|cJSON_True))) ? \
(object)->type=((object)->type &(~(cJSON_False|cJSON_True)))|((boolValue)?cJSON_True:cJSON_False) : \
cJSON_Invalid\
)
/* Macro for iterating over an array or object */
#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next)
/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */
CJSON_PUBLIC(void *) cJSON_malloc(size_t size);
CJSON_PUBLIC(void) cJSON_free(void *object);
#ifdef __cplusplus
}
#endif
#endif
+299
View File
@@ -0,0 +1,299 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <errno.h>
#include <mongoose.h>
#include <cjson/cJSON.h>
#include "terminal.h"
#define HTTP_PORT "8888"
#define STATIC_DIR "./static/"
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;
};
// Функция для логирования запросов
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;
}
}
// Обработчик 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);
}
// Проверяем, не команда ли это exit
if (wm->data.len >= 4 && strncmp(wm->data.buf, "exit", 4) == 0) {
printf("Exit command detected, restarting terminal session\n");
write_to_terminal(&cd->term, wm->data.buf, wm->data.len);
// Даем команде выполниться, затем перезапускаем
usleep(100);
restart_terminal_session(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;
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 restart session\r\n", 49, 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) {
// Проверяем, не завершился ли процесс
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;
}
+25463
View File
File diff suppressed because it is too large. Load diff
+3780
View File
File diff suppressed because it is too large. Load diff
+158
View File
@@ -0,0 +1,158 @@
#include "terminal.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <pty.h>
#include <utmp.h>
#include <signal.h>
#include <errno.h>
int create_terminal_session(struct terminal_session *session, const char *shell) {
// Создаем pty
int master, slave;
char slave_name[64];
if (openpty(&master, &slave, slave_name, NULL, NULL) == -1) {
perror("openpty failed");
return -1;
}
pid_t pid = fork();
if (pid == -1) {
perror("fork failed");
close(master);
close(slave);
return -1;
}
if (pid == 0) {
// Дочерний процесс
close(master);
// Устанавливаем pty как контролирующий терминал
setsid();
ioctl(slave, TIOCSCTTY, 1);
// Перенаправляем стандартные потоки
dup2(slave, STDIN_FILENO);
dup2(slave, STDOUT_FILENO);
dup2(slave, STDERR_FILENO);
if (slave > STDERR_FILENO) {
close(slave);
}
// Устанавливаем переменные окружения
setenv("TERM", "xterm-256color", 1);
setenv("COLORTERM", "truecolor", 1);
// Запускаем shell
execlp(shell, shell, NULL);
// Если execlp вернулся, значит произошла ошибка
perror("execlp failed");
exit(EXIT_FAILURE);
}
// Родительский процесс
close(slave);
session->master_pty = master;
session->slave_pty = slave;
session->child_pid = pid;
session->active = true;
// Устанавливаем неблокирующий режим
int flags = fcntl(master, F_GETFL, 0);
fcntl(master, F_SETFL, flags | O_NONBLOCK);
printf("Created terminal session with PID %d\n", pid);
return 0;
}
void destroy_terminal_session(struct terminal_session *session) {
if (!session || !session->active) {
return;
}
printf("Destroying terminal session with PID %d\n", session->child_pid);
// Завершаем дочерний процесс
if (session->child_pid > 0) {
// Отправляем SIGKILL
int status;
kill(session->child_pid, SIGKILL);
waitpid(session->child_pid, &status, 0);
}
// Закрываем pty
if (session->master_pty != -1) {
close(session->master_pty);
session->master_pty = -1;
}
session->active = false;
session->child_pid = 0;
printf("Terminal session destroyed\n");
}
ssize_t read_from_terminal(struct terminal_session *session, char *buffer, size_t size) {
if (!session || !session->active) {
return -1;
}
ssize_t bytes_read = read(session->master_pty, buffer, size - 1);
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
} else if (bytes_read == 0) {
// EOF - соединение закрыто
return 0;
} else if (bytes_read < 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
perror("read from terminal failed");
return -1;
}
// Нет данных для чтения - это нормально в неблокирующем режиме
return -2;
}
return bytes_read;
}
ssize_t write_to_terminal(struct terminal_session *session, const char *data, size_t size) {
if (!session || !session->active) {
return -1;
}
ssize_t bytes_written = write(session->master_pty, data, size);
if (bytes_written < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
perror("write to terminal failed");
return -1;
}
return bytes_written;
}
int resize_terminal(struct terminal_session *session, uint16_t rows, uint16_t cols) {
if (!session || !session->active) {
return -1;
}
struct winsize ws = {
.ws_row = rows,
.ws_col = cols,
.ws_xpixel = 0,
.ws_ypixel = 0
};
if (ioctl(session->master_pty, TIOCSWINSZ, &ws) == -1) {
perror("ioctl TIOCSWINSZ failed");
return -1;
}
return 0;
}
+25
View File
@@ -0,0 +1,25 @@
#ifndef TERMINAL_H
#define TERMINAL_H
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/types.h>
struct terminal_session {
int master_pty;
int slave_pty;
pid_t child_pid;
bool active;
};
// Функции для работы с терминалом
int create_terminal_session(struct terminal_session *session, const char *shell);
void destroy_terminal_session(struct terminal_session *session);
ssize_t read_from_terminal(struct terminal_session *session, char *buffer, size_t size);
ssize_t write_to_terminal(struct terminal_session *session, const char *data, size_t size);
int resize_terminal(struct terminal_session *session, uint16_t rows, uint16_t cols);
#endif
+176
View File
@@ -0,0 +1,176 @@
<!DOCTYPE html>
<html>
<head>
<title>Web Terminal</title>
<link rel="stylesheet" href="xterm.css">
<style>
body {
margin: 0;
padding: 20px;
background-color: #1e1e1e;
color: #fff;
font-family: Arial, sans-serif;
height: 100vh;
display: flex;
flex-direction: column;
}
#terminal-container {
flex: 1;
border: 1px solid #333;
border-radius: 5px;
overflow: hidden;
margin-top: 10px;
}
.status {
padding: 10px;
border-radius: 3px;
font-weight: bold;
margin-bottom: 10px;
}
.connected { background-color: #2d5a2d; }
.disconnected { background-color: #5a2d2d; }
.connecting { background-color: #5a5a2d; }
</style>
</head>
<body>
<h1>Web Terminal</h1>
<div class="status connecting" id="status">Connecting...</div>
<div id="terminal-container"></div>
<script src="xterm.js"></script>
<script src="xterm-addon-fit.js"></script>
<script>
class WebTerminal {
constructor() {
this.terminal = new Terminal({
cursorBlink: true,
theme: {
background: '#000000',
foreground: '#ffffff',
cursor: '#ffffff'
}
});
this.fitAddon = new FitAddon.FitAddon();
this.terminal.loadAddon(this.fitAddon);
this.ws = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectDelay = 3000;
this.init();
}
init() {
const terminalContainer = document.getElementById('terminal-container');
this.terminal.open(terminalContainer);
this.fitAddon.fit();
this.terminal.writeln('Welcome to Web Terminal');
this.terminal.writeln('Connecting to server...');
this.connect();
// Handle terminal input
this.terminal.onData((data) => {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(data);
}
});
// Handle resize
window.addEventListener('resize', () => {
this.fitAddon.fit();
this.sendResize();
});
// Handle page unload
window.addEventListener('beforeunload', () => {
if (this.ws) {
this.ws.close();
}
});
}
connect() {
this.updateStatus('Connecting...', 'connecting');
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/ws`;
this.ws = new WebSocket(wsUrl);
this.ws.onopen = () => {
console.log('WebSocket connected');
this.reconnectAttempts = 0;
this.updateStatus('Connected', 'connected');
this.terminal.clear();
this.sendResize();
};
this.ws.onmessage = (event) => {
if (typeof event.data === 'string') {
this.terminal.write(event.data);
} else {
// Handle binary data
const reader = new FileReader();
reader.onload = () => {
const data = new Uint8Array(reader.result);
this.terminal.write(data);
};
reader.readAsArrayBuffer(event.data);
}
};
this.ws.onclose = (event) => {
console.log('WebSocket disconnected:', event.code, event.reason);
this.updateStatus('Disconnected', 'disconnected');
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
const delay = this.reconnectDelay * this.reconnectAttempts;
this.terminal.write(`\r\nConnection lost. Reconnecting in ${delay/1000}s...\r\n`);
setTimeout(() => {
this.connect();
}, delay);
} else {
this.terminal.write('\r\nFailed to reconnect. Please refresh the page.\r\n');
}
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
this.updateStatus('Connection Error', 'disconnected');
};
}
sendResize() {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
const dimensions = this.terminal;
const resizeMsg = {
cols: dimensions.cols,
rows: dimensions.rows
};
this.ws.send(JSON.stringify(resizeMsg));
}
}
updateStatus(text, className) {
const status = document.getElementById('status');
status.textContent = text;
status.className = `status ${className}`;
}
}
// Initialize terminal when page loads
document.addEventListener('DOMContentLoaded', () => {
new WebTerminal();
});
</script>
</body>
</html>
+2
View File
@@ -0,0 +1,2 @@
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FitAddon=t():e.FitAddon=t()}(self,(()=>(()=>{"use strict";var e={};return(()=>{var t=e;Object.defineProperty(t,"__esModule",{value:!0}),t.FitAddon=void 0,t.FitAddon=class{activate(e){this._terminal=e}dispose(){}fit(){const e=this.proposeDimensions();if(!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows))return;const t=this._terminal._core;this._terminal.rows===e.rows&&this._terminal.cols===e.cols||(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}proposeDimensions(){if(!this._terminal)return;if(!this._terminal.element||!this._terminal.element.parentElement)return;const e=this._terminal._core,t=e._renderService.dimensions;if(0===t.css.cell.width||0===t.css.cell.height)return;const r=0===this._terminal.options.scrollback?0:e.viewport.scrollBarWidth,i=window.getComputedStyle(this._terminal.element.parentElement),o=parseInt(i.getPropertyValue("height")),s=Math.max(0,parseInt(i.getPropertyValue("width"))),n=window.getComputedStyle(this._terminal.element),l=o-(parseInt(n.getPropertyValue("padding-top"))+parseInt(n.getPropertyValue("padding-bottom"))),a=s-(parseInt(n.getPropertyValue("padding-right"))+parseInt(n.getPropertyValue("padding-left")))-r;return{cols:Math.max(2,Math.floor(a/t.css.cell.width)),rows:Math.max(1,Math.floor(l/t.css.cell.height))}}}})(),e})()));
//# sourceMappingURL=xterm-addon-fit.js.map
+209
View File
@@ -0,0 +1,209 @@
/**
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
* https://github.com/chjj/term.js
* @license MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* Originally forked from (with the author's permission):
* Fabrice Bellard's javascript vt100 for jslinux:
* http://bellard.org/jslinux/
* Copyright (c) 2011 Fabrice Bellard
* The original design remains. The terminal itself
* has been extended to include xterm CSI codes, among
* other features.
*/
/**
* Default styles for xterm.js
*/
.xterm {
cursor: text;
position: relative;
user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
}
.xterm.focus,
.xterm:focus {
outline: none;
}
.xterm .xterm-helpers {
position: absolute;
top: 0;
/**
* The z-index of the helpers must be higher than the canvases in order for
* IMEs to appear on top.
*/
z-index: 5;
}
.xterm .xterm-helper-textarea {
padding: 0;
border: 0;
margin: 0;
/* Move textarea out of the screen to the far left, so that the cursor is not visible */
position: absolute;
opacity: 0;
left: -9999em;
top: 0;
width: 0;
height: 0;
z-index: -5;
/** Prevent wrapping so the IME appears against the textarea at the correct position */
white-space: nowrap;
overflow: hidden;
resize: none;
}
.xterm .composition-view {
/* TODO: Composition position got messed up somewhere */
background: #000;
color: #FFF;
display: none;
position: absolute;
white-space: nowrap;
z-index: 1;
}
.xterm .composition-view.active {
display: block;
}
.xterm .xterm-viewport {
/* On OS X this is required in order for the scroll bar to appear fully opaque */
background-color: #000;
overflow-y: scroll;
cursor: default;
position: absolute;
right: 0;
left: 0;
top: 0;
bottom: 0;
}
.xterm .xterm-screen {
position: relative;
}
.xterm .xterm-screen canvas {
position: absolute;
left: 0;
top: 0;
}
.xterm .xterm-scroll-area {
visibility: hidden;
}
.xterm-char-measure-element {
display: inline-block;
visibility: hidden;
position: absolute;
top: 0;
left: -9999em;
line-height: normal;
}
.xterm.enable-mouse-events {
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
cursor: default;
}
.xterm.xterm-cursor-pointer,
.xterm .xterm-cursor-pointer {
cursor: pointer;
}
.xterm.column-select.focus {
/* Column selection mode */
cursor: crosshair;
}
.xterm .xterm-accessibility,
.xterm .xterm-message {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
z-index: 10;
color: transparent;
pointer-events: none;
}
.xterm .live-region {
position: absolute;
left: -9999px;
width: 1px;
height: 1px;
overflow: hidden;
}
.xterm-dim {
/* Dim should not apply to background, so the opacity of the foreground color is applied
* explicitly in the generated class and reset to 1 here */
opacity: 1 !important;
}
.xterm-underline-1 { text-decoration: underline; }
.xterm-underline-2 { text-decoration: double underline; }
.xterm-underline-3 { text-decoration: wavy underline; }
.xterm-underline-4 { text-decoration: dotted underline; }
.xterm-underline-5 { text-decoration: dashed underline; }
.xterm-overline {
text-decoration: overline;
}
.xterm-overline.xterm-underline-1 { text-decoration: overline underline; }
.xterm-overline.xterm-underline-2 { text-decoration: overline double underline; }
.xterm-overline.xterm-underline-3 { text-decoration: overline wavy underline; }
.xterm-overline.xterm-underline-4 { text-decoration: overline dotted underline; }
.xterm-overline.xterm-underline-5 { text-decoration: overline dashed underline; }
.xterm-strikethrough {
text-decoration: line-through;
}
.xterm-screen .xterm-decoration-container .xterm-decoration {
z-index: 6;
position: absolute;
}
.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer {
z-index: 7;
}
.xterm-decoration-overview-ruler {
z-index: 8;
position: absolute;
top: 0;
right: 0;
pointer-events: none;
}
.xterm-decoration-top {
z-index: 2;
position: relative;
}
+2
View File
@@ -0,0 +1,2 @@
!function(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var i=t();for(var s in i)("object"==typeof exports?exports:e)[s]=i[s]}}(self,(()=>(()=>{"use strict";var e={4567:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.AccessibilityManager=void 0;const n=i(9042),o=i(6114),a=i(9924),h=i(844),c=i(5596),l=i(4725),d=i(3656);let _=t.AccessibilityManager=class extends h.Disposable{constructor(e,t){super(),this._terminal=e,this._renderService=t,this._liveRegionLineCount=0,this._charsToConsume=[],this._charsToAnnounce="",this._accessibilityContainer=document.createElement("div"),this._accessibilityContainer.classList.add("xterm-accessibility"),this._rowContainer=document.createElement("div"),this._rowContainer.setAttribute("role","list"),this._rowContainer.classList.add("xterm-accessibility-tree"),this._rowElements=[];for(let e=0;e<this._terminal.rows;e++)this._rowElements[e]=this._createAccessibilityTreeNode(),this._rowContainer.appendChild(this._rowElements[e]);if(this._topBoundaryFocusListener=e=>this._handleBoundaryFocus(e,0),this._bottomBoundaryFocusListener=e=>this._handleBoundaryFocus(e,1),this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions(),this._accessibilityContainer.appendChild(this._rowContainer),this._liveRegion=document.createElement("div"),this._liveRegion.classList.add("live-region"),this._liveRegion.setAttribute("aria-live","assertive"),this._accessibilityContainer.appendChild(this._liveRegion),this._liveRegionDebouncer=this.register(new a.TimeBasedDebouncer(this._renderRows.bind(this))),!this._terminal.element)throw new Error("Cannot enable accessibility before Terminal.open");this._terminal.element.insertAdjacentElement("afterbegin",this._accessibilityContainer),this.register(this._terminal.onResize((e=>this._handleResize(e.rows)))),this.register(this._terminal.onRender((e=>this._refreshRows(e.start,e.end)))),this.register(this._terminal.onScroll((()=>this._refreshRows()))),this.register(this._terminal.onA11yChar((e=>this._handleChar(e)))),this.register(this._terminal.onLineFeed((()=>this._handleChar("\n")))),this.register(this._terminal.onA11yTab((e=>this._handleTab(e)))),this.register(this._terminal.onKey((e=>this._handleKey(e.key)))),this.register(this._terminal.onBlur((()=>this._clearLiveRegion()))),this.register(this._renderService.onDimensionsChange((()=>this._refreshRowsDimensions()))),this._screenDprMonitor=new c.ScreenDprMonitor(window),this.register(this._screenDprMonitor),this._screenDprMonitor.setListener((()=>this._refreshRowsDimensions())),this.register((0,d.addDisposableDomListener)(window,"resize",(()=>this._refreshRowsDimensions()))),this._refreshRows(),this.register((0,h.toDisposable)((()=>{this._accessibilityContainer.remove(),this._rowElements.length=0})))}_handleTab(e){for(let t=0;t<e;t++)this._handleChar(" ")}_handleChar(e){this._liveRegionLineCount<21&&(this._charsToConsume.length>0?this._charsToConsume.shift()!==e&&(this._charsToAnnounce+=e):this._charsToAnnounce+=e,"\n"===e&&(this._liveRegionLineCount++,21===this._liveRegionLineCount&&(this._liveRegion.textContent+=n.tooMuchOutput)),o.isMac&&this._liveRegion.textContent&&this._liveRegion.textContent.length>0&&!this._liveRegion.parentNode&&setTimeout((()=>{this._accessibilityContainer.appendChild(this._liveRegion)}),0))}_clearLiveRegion(){this._liveRegion.textContent="",this._liveRegionLineCount=0,o.isMac&&this._liveRegion.remove()}_handleKey(e){this._clearLiveRegion(),/\p{Control}/u.test(e)||this._charsToConsume.push(e)}_refreshRows(e,t){this._liveRegionDebouncer.refresh(e,t,this._terminal.rows)}_renderRows(e,t){const i=this._terminal.buffer,s=i.lines.length.toString();for(let r=e;r<=t;r++){const e=i.translateBufferLineToString(i.ydisp+r,!0),t=(i.ydisp+r+1).toString(),n=this._rowElements[r];n&&(0===e.length?n.innerText=" ":n.textContent=e,n.setAttribute("aria-posinset",t),n.setAttribute("aria-setsize",s))}this._announceCharacters()}_announceCharacters(){0!==this._charsToAnnounce.length&&(this._liveRegion.textContent+=this._charsToAnnounce,this._charsToAnnounce="")}_handleBoundaryFocus(e,t){const i=e.target,s=this._rowElements[0===t?1:this._rowElements.length-2];if(i.getAttribute("aria-posinset")===(0===t?"1":`${this._terminal.buffer.lines.length}`))return;if(e.relatedTarget!==s)return;let r,n;if(0===t?(r=i,n=this._rowElements.pop(),this._rLine truncated
//# sourceMappingURL=xterm.js.map