diff --git a/src/socket_protocol.c b/src/socket_protocol.c index 7a2e224..bad18e5 100644 --- a/src/socket_protocol.c +++ b/src/socket_protocol.c @@ -1,3 +1,14 @@ +/** + * @file socket_protocol.c + * @brief Socket 消息协议实现 + * + * 功能: + * - 消息的序列化和反序列化 + * - 可靠的消息读写(处理部分读写) + * - 终端模式控制(原始模式、鼠标跟踪) + * - 可选的 LZ4 压缩支持 + */ + #include "socket_protocol.h" #include @@ -8,23 +19,66 @@ #include "debug.h" -// 保存原始终端设置 +/* ============================================================================ + * 常量定义 + * ============================================================================ */ + +/** 最大载荷大小限制(64MB),防止恶意或损坏数据导致的内存耗尽 */ +#define MAX_PAYLOAD_SIZE (64 * 1024 * 1024) + +/* ============================================================================ + * 全局变量 - 终端状态 + * ============================================================================ */ + +/** 原始终端设置(用于恢复) */ static struct termios g_original_termios; -static int g_termios_saved = 0; + +/** + * 终端设置是否已保存 + * 使用 volatile 确保多次调用时状态正确可见 + * 注:实际访问是串行的(通过 pthread_join 保证),不需要互斥锁 + */ +static volatile int g_termios_saved = 0; + +/* ============================================================================ + * LZ4 压缩支持(条件编译) + * ============================================================================ */ #ifdef HAVE_LZ4 -// 初始化协议上下文 -void protocol_init(ProtocolContext* ctx, CompressionType compress_type, int compress_level) { - if (ctx == NULL) return; - +/** + * @brief 初始化协议上下文 + * + * @param ctx 协议上下文指针 + * @param compress_type 压缩类型 + * @param compress_level 压缩级别 + */ +void protocol_init(ProtocolContext* ctx, CompressionType compress_type, + int compress_level) { + if (ctx == NULL) { + return; + } + compression_init(&ctx->compress_ctx, compress_type, compress_level); ctx->compression_enabled = (compress_type != COMPRESS_NONE); - + DEBUG_LOG("协议上下文初始化: compression=%d", ctx->compression_enabled); } #endif -// 向 socket 完整写入指定字节数(处理部分写入) +/* ============================================================================ + * 底层 I/O 函数 + * ============================================================================ */ + +/** + * @brief 向 socket 完整写入指定字节数 + * + * 处理部分写入和信号中断,确保所有数据都被写入 + * + * @param sock Socket 文件描述符 + * @param buf 源数据缓冲区 + * @param count 要写入的字节数 + * @return 成功返回写入的字节数,失败返回 -1 + */ static ssize_t write_full(int sock, const void* buf, size_t count) { size_t total_written = 0; const char* ptr = (const char*)buf; @@ -33,25 +87,87 @@ static ssize_t write_full(int sock, const void* buf, size_t count) { ssize_t n = write(sock, ptr + total_written, count - total_written); if (n < 0) { if (errno == EINTR) { - continue; // 被信号中断,重试 + continue; /* 被信号中断,重试 */ } - DEBUG_LOG("write_full: write error: %s", strerror(errno)); + DEBUG_LOG("write_full: 写入错误: %s", strerror(errno)); return -1; } - total_written += n; - if (total_written < count) { - DEBUG_LOG("write_full: 进度 %zu/%zu 字节", total_written, count); + if (n == 0) { + /* write 返回 0 通常不应该发生,视为错误 */ + DEBUG_LOG("write_full: write 返回 0"); + errno = EIO; + return -1; } + total_written += (size_t)n; } return (ssize_t)total_written; } -// 写入完整消息 +/** + * @brief 从 socket 完整读取指定字节数 + * + * 处理部分读取和信号中断,确保读取完整数据或检测到连接关闭 + * + * @param sock Socket 文件描述符 + * @param buf 目标缓冲区 + * @param count 要读取的字节数 + * @return 成功返回读取的字节数,连接正常关闭返回 0,错误返回 -1 + * + * @note 如果在读取过程中连接关闭(部分读取),返回 -1 并设置 errno = ECONNRESET + */ +static ssize_t read_full(int sock, void* buf, size_t count) { + size_t total_read = 0; + char* ptr = (char*)buf; + + while (total_read < count) { + ssize_t n = read(sock, ptr + total_read, count - total_read); + if (n < 0) { + if (errno == EINTR) { + continue; /* 被信号中断,重试 */ + } + DEBUG_LOG("read_full: 读取错误: %s", strerror(errno)); + return -1; + } + if (n == 0) { + /* 连接关闭 */ + if (total_read > 0) { + /* + * 已读取部分数据但连接关闭,这是不完整的消息 + * 视为错误,避免上层误用不完整数据 + */ + DEBUG_LOG("read_full: 连接中途关闭,已读取 %zu/%zu 字节", + total_read, count); + errno = ECONNRESET; + return -1; + } + /* 干净的连接关闭(未读取任何数据) */ + return 0; + } + total_read += (size_t)n; + } + + return (ssize_t)total_read; +} + +/* ============================================================================ + * 消息读写函数 + * ============================================================================ */ + +/** + * @brief 写入完整消息到 socket + * + * @param sock Socket 文件描述符 + * @param type 消息类型 + * @param payload 消息载荷(可以为 NULL) + * @param payload_len 载荷长度 + * @return 0 成功,-1 失败 + */ int write_message(int sock, MessageType type, const void* payload, uint32_t payload_len) { DEBUG_LOG("写入消息: type=%d, payload_len=%u", type, payload_len); + /* 构建消息头 */ MessageHeader header; header.magic = MESSAGE_MAGIC; header.type = type; @@ -60,25 +176,22 @@ int write_message(int sock, MessageType type, const void* payload, DEBUG_HEX("消息头", &header, sizeof(header)); - // 发送消息头(确保完整发送) + /* 发送消息头 */ ssize_t written = write_full(sock, &header, sizeof(header)); - if (written != sizeof(header)) { - DEBUG_LOG( - "Failed to write message header: 期望 %zu 字节, 实际写入 %zd " - "字节\n", - sizeof(header), written); + if (written < 0 || (size_t)written != sizeof(header)) { + DEBUG_LOG("写入消息头失败: 期望 %zu 字节, 实际 %zd 字节", + sizeof(header), written); return -1; } - // 写入载荷(确保完整发送) + /* 发送载荷 */ if (payload_len > 0 && payload != NULL) { DEBUG_HEX("消息载荷", payload, payload_len > 64 ? 64 : payload_len); + written = write_full(sock, payload, payload_len); - if (written != (ssize_t)payload_len) { - DEBUG_LOG( - "Failed to write message payload: 期望 %u 字节, 实际写入 %zd " - "字节\n", - payload_len, written); + if (written < 0 || (size_t)written != payload_len) { + DEBUG_LOG("写入消息载荷失败: 期望 %u 字节, 实际 %zd 字节", + payload_len, written); return -1; } DEBUG_LOG("载荷写入成功: %zd 字节", written); @@ -89,124 +202,123 @@ int write_message(int sock, MessageType type, const void* payload, } #ifdef HAVE_LZ4 -// 带压缩支持的消息写入 +/** + * @brief 写入压缩消息到 socket + * + * 如果压缩失败或未启用压缩,自动回退到普通写入 + * + * @param sock Socket 文件描述符 + * @param ctx 协议上下文 + * @param type 消息类型 + * @param payload 消息载荷 + * @param payload_len 载荷长度 + * @return 0 成功,-1 失败 + */ int write_message_compressed(int sock, ProtocolContext* ctx, MessageType type, const void* payload, uint32_t payload_len) { DEBUG_LOG("写入压缩消息: type=%d, payload_len=%u", type, payload_len); - - // 如果没有上下文或压缩未启用,使用普通写入 + + /* 如果没有上下文或压缩未启用,使用普通写入 */ if (ctx == NULL || !ctx->compression_enabled || payload_len == 0) { return write_message(sock, type, payload, payload_len); } - - // 分配压缩缓冲区 + + /* 分配压缩缓冲区 */ size_t compress_bound = get_compress_bound(payload_len); void* compressed_buf = malloc(compress_bound); if (compressed_buf == NULL) { - DEBUG_LOG("分配压缩缓冲区失败"); + DEBUG_LOG("分配压缩缓冲区失败,回退到普通写入"); return write_message(sock, type, payload, payload_len); } - - // 压缩数据 + + /* 压缩数据 */ uint32_t flags = 0; - int compressed_size = compress_data(&ctx->compress_ctx, + int compressed_size = compress_data(&ctx->compress_ctx, payload, payload_len, compressed_buf, compress_bound, &flags); - + if (compressed_size < 0) { - DEBUG_LOG("压缩失败,使用原始数据"); + DEBUG_LOG("压缩失败,回退到普通写入"); free(compressed_buf); return write_message(sock, type, payload, payload_len); } - - // 构建消息头 + + /* 构建消息头 */ MessageHeader header; header.magic = MESSAGE_MAGIC; header.type = type; header.payload_len = (uint32_t)compressed_size; header.reserved = MAKE_RESERVED(flags, payload_len); - - DEBUG_LOG("压缩消息头: type=%d, compressed_len=%d, original_len=%u, flags=0x%x", + + DEBUG_LOG("压缩消息头: type=%d, compressed=%d, original=%u, flags=0x%x", type, compressed_size, payload_len, flags); DEBUG_HEX("压缩消息头", &header, sizeof(header)); - - // 发送消息头 + + /* 发送消息头 */ ssize_t written = write_full(sock, &header, sizeof(header)); - if (written != sizeof(header)) { - DEBUG_LOG("写入消息头失败"); + if (written < 0 || (size_t)written != sizeof(header)) { + DEBUG_LOG("写入压缩消息头失败"); free(compressed_buf); return -1; } - - // 发送压缩后的载荷 + + /* 发送压缩后的载荷 */ if (compressed_size > 0) { - written = write_full(sock, compressed_buf, compressed_size); - if (written != compressed_size) { + written = write_full(sock, compressed_buf, (size_t)compressed_size); + if (written < 0 || (size_t)written != (size_t)compressed_size) { DEBUG_LOG("写入压缩载荷失败"); free(compressed_buf); return -1; } } - + free(compressed_buf); DEBUG_LOG("压缩消息写入完成: %u -> %d 字节", payload_len, compressed_size); return 0; } #endif -// 从 socket 完整读取指定字节数(处理部分读取) -static ssize_t read_full(int sock, void* buf, size_t count) { - size_t total_read = 0; - char* ptr = (char*)buf; - - while (total_read < count) { - ssize_t n = read(sock, ptr + total_read, count - total_read); - if (n < 0) { - if (errno == EINTR) { - continue; // 被信号中断,重试 - } - DEBUG_LOG("read_full: read error: %s", strerror(errno)); - return -1; - } - if (n == 0) { - // 连接关闭 - DEBUG_LOG("read_full: 连接关闭,已读取 %zu/%zu 字节", total_read, - count); - return total_read > 0 ? (ssize_t)total_read : 0; - } - total_read += n; - DEBUG_LOG("read_full: 进度 %zu/%zu 字节", total_read, count); - } - - return (ssize_t)total_read; -} - -// 读取完整消息 +/** + * @brief 从 socket 读取完整消息 + * + * @param sock Socket 文件描述符 + * @param type [out] 消息类型 + * @param payload [out] 消息载荷(调用者负责释放) + * @param payload_len [out] 载荷长度 + * @return 1 成功读取,0 连接正常关闭,-1 错误 + */ int read_message(int sock, MessageType* type, void** payload, uint32_t* payload_len) { MessageHeader header; DEBUG_LOG("开始读取消息..."); - // 读取消息头(确保读取完整) + /* 读取消息头 */ ssize_t bytes_read = read_full(sock, &header, sizeof(header)); - if (bytes_read != sizeof(header)) { - if (bytes_read == 0) { - DEBUG_LOG("连接正常关闭"); - return 0; // 连接正常关闭 - } - DEBUG_LOG( - "Failed to read message header, got %zd bytes, expected %zu\n", - bytes_read, sizeof(header)); + if (bytes_read == 0) { + DEBUG_LOG("连接正常关闭"); + return 0; + } + if (bytes_read < 0 || (size_t)bytes_read != sizeof(header)) { + DEBUG_LOG("读取消息头失败: 期望 %zu 字节, 实际 %zd 字节", + sizeof(header), bytes_read); return -1; } DEBUG_HEX("收到消息头", &header, sizeof(header)); - // 验证魔数 + /* 验证魔数 */ if (header.magic != MESSAGE_MAGIC) { - DEBUG_LOG("Invalid message magic: 0x%x\n", header.magic); + DEBUG_LOG("无效的消息魔数: 0x%x (期望 0x%x)", + header.magic, MESSAGE_MAGIC); + return -1; + } + + /* 验证载荷大小 */ + if (header.payload_len > MAX_PAYLOAD_SIZE) { + DEBUG_LOG("载荷大小超出限制: %u > %u", + header.payload_len, MAX_PAYLOAD_SIZE); return -1; } @@ -215,28 +327,29 @@ int read_message(int sock, MessageType* type, void** payload, DEBUG_LOG("消息类型=%d, 载荷长度=%u", *type, *payload_len); - // 读取载荷(确保读取完整) + /* 读取载荷 */ if (header.payload_len > 0) { - *payload = malloc(header.payload_len + 1); // +1 用于可能的字符串终止符 + /* +1 用于字符串终止符 */ + *payload = malloc(header.payload_len + 1); if (*payload == NULL) { - DEBUG_LOG("Failed to allocate memory for payload\n"); + DEBUG_LOG("分配载荷内存失败: %u 字节", header.payload_len); return -1; } bytes_read = read_full(sock, *payload, header.payload_len); - if (bytes_read != (ssize_t)header.payload_len) { - DEBUG_LOG( - "Failed to read message payload: 期望%u字节, 实际读取%zd字节\n", - header.payload_len, bytes_read); + if (bytes_read < 0 || (size_t)bytes_read != header.payload_len) { + DEBUG_LOG("读取载荷失败: 期望 %u 字节, 实际 %zd 字节", + header.payload_len, bytes_read); free(*payload); *payload = NULL; return -1; } DEBUG_LOG("载荷读取成功: %zd 字节", bytes_read); - DEBUG_HEX("收到载荷", *payload, bytes_read > 64 ? 64 : bytes_read); + DEBUG_HEX("收到载荷", *payload, + (size_t)bytes_read > 64 ? 64 : (size_t)bytes_read); - // 如果是字符串,添加终止符 + /* 添加字符串终止符(便于作为字符串使用) */ ((char*)(*payload))[header.payload_len] = '\0'; } else { *payload = NULL; @@ -246,83 +359,107 @@ int read_message(int sock, MessageType* type, void** payload, } #ifdef HAVE_LZ4 -// 带解压支持的消息读取 +/** + * @brief 从 socket 读取消息并解压 + * + * @param sock Socket 文件描述符 + * @param type [out] 消息类型 + * @param payload [out] 解压后的载荷(调用者负责释放) + * @param payload_len [out] 解压后的载荷长度 + * @param original_len [out] 原始(压缩后)载荷长度(可选,可为 NULL) + * @return 1 成功读取,0 连接正常关闭,-1 错误 + */ int read_message_decompressed(int sock, MessageType* type, void** payload, uint32_t* payload_len, uint32_t* original_len) { MessageHeader header; DEBUG_LOG("开始读取消息(带解压)..."); - // 读取消息头 + /* 读取消息头 */ ssize_t bytes_read = read_full(sock, &header, sizeof(header)); - if (bytes_read != sizeof(header)) { - if (bytes_read == 0) { - DEBUG_LOG("连接正常关闭"); - return 0; - } - DEBUG_LOG("读取消息头失败: got %zd bytes, expected %zu", bytes_read, sizeof(header)); + if (bytes_read == 0) { + DEBUG_LOG("连接正常关闭"); + return 0; + } + if (bytes_read < 0 || (size_t)bytes_read != sizeof(header)) { + DEBUG_LOG("读取消息头失败: 期望 %zu 字节, 实际 %zd 字节", + sizeof(header), bytes_read); return -1; } DEBUG_HEX("收到消息头", &header, sizeof(header)); - // 验证魔数 + /* 验证魔数 */ if (header.magic != MESSAGE_MAGIC) { - DEBUG_LOG("无效魔数: 0x%x", header.magic); + DEBUG_LOG("无效的消息魔数: 0x%x", header.magic); + return -1; + } + + /* 验证载荷大小 */ + if (header.payload_len > MAX_PAYLOAD_SIZE) { + DEBUG_LOG("载荷大小超出限制: %u > %u", + header.payload_len, MAX_PAYLOAD_SIZE); return -1; } *type = (MessageType)header.type; uint32_t flags = GET_COMPRESS_FLAGS(header.reserved); uint32_t size_hint = GET_ORIGINAL_SIZE_HINT(header.reserved); - + DEBUG_LOG("消息类型=%d, 压缩载荷=%u, flags=0x%x, size_hint=%u", *type, header.payload_len, flags, size_hint); - // 读取压缩载荷 + /* 空载荷处理 */ if (header.payload_len == 0) { *payload = NULL; *payload_len = 0; - if (original_len) *original_len = 0; + if (original_len != NULL) { + *original_len = 0; + } return 1; } - void* compressed_buf = malloc(header.payload_len); + /* 分配缓冲区(+1 用于字符串终止符) */ + void* compressed_buf = malloc(header.payload_len + 1); if (compressed_buf == NULL) { DEBUG_LOG("分配压缩缓冲区失败"); return -1; } + /* 读取载荷 */ bytes_read = read_full(sock, compressed_buf, header.payload_len); - if (bytes_read != (ssize_t)header.payload_len) { - DEBUG_LOG("读取压缩载荷失败: 期望%u字节, 实际%zd字节", + if (bytes_read < 0 || (size_t)bytes_read != header.payload_len) { + DEBUG_LOG("读取压缩载荷失败: 期望 %u 字节, 实际 %zd 字节", header.payload_len, bytes_read); free(compressed_buf); return -1; } - // 检查是否需要解压 + /* 检查是否需要解压 */ if (!(flags & MSG_FLAG_COMPRESSED)) { - // 数据未压缩,直接返回 + /* 数据未压缩,添加终止符后直接返回 */ + ((char*)compressed_buf)[header.payload_len] = '\0'; *payload = compressed_buf; *payload_len = header.payload_len; - if (original_len) *original_len = header.payload_len; - - // 添加字符串终止符(重新分配以确保空间) - void* new_buf = realloc(compressed_buf, header.payload_len + 1); - if (new_buf) { - *payload = new_buf; - ((char*)(*payload))[header.payload_len] = '\0'; + if (original_len != NULL) { + *original_len = header.payload_len; } - return 1; } - // 需要解压 - // 估算解压后大小(使用 size_hint 或默认 4 倍) - size_t decompress_capacity = size_hint > 0 ? size_hint + 256 : header.payload_len * 4; - if (decompress_capacity < 256) decompress_capacity = 256; - + /* 需要解压 */ + /* 估算解压后大小(使用 size_hint 或默认 4 倍) */ + size_t decompress_capacity = (size_hint > 0) + ? (size_hint + 256) + : (header.payload_len * 4); + if (decompress_capacity < 256) { + decompress_capacity = 256; + } + if (decompress_capacity > MAX_PAYLOAD_SIZE) { + decompress_capacity = MAX_PAYLOAD_SIZE; + } + + /* 分配解压缓冲区(+1 用于字符串终止符) */ void* decompressed_buf = malloc(decompress_capacity + 1); if (decompressed_buf == NULL) { DEBUG_LOG("分配解压缓冲区失败"); @@ -330,120 +467,203 @@ int read_message_decompressed(int sock, MessageType* type, void** payload, return -1; } - int decompressed_size = decompress_data(flags, + /* 解压数据 */ + int decompressed_size = decompress_data(flags, compressed_buf, header.payload_len, decompressed_buf, decompress_capacity); - + free(compressed_buf); - + if (decompressed_size < 0) { DEBUG_LOG("解压失败"); free(decompressed_buf); return -1; } - // 添加字符串终止符 + /* 添加字符串终止符 */ ((char*)decompressed_buf)[decompressed_size] = '\0'; - + *payload = decompressed_buf; *payload_len = (uint32_t)decompressed_size; - if (original_len) *original_len = header.payload_len; + if (original_len != NULL) { + *original_len = header.payload_len; + } DEBUG_LOG("解压成功: %u -> %d 字节", header.payload_len, decompressed_size); return 1; } #endif -// 释放消息载荷 +/** + * @brief 释放消息载荷 + * + * @param payload 要释放的载荷指针 + */ void free_message_payload(void* payload) { - if (payload != NULL) { - free(payload); - } + free(payload); /* free(NULL) 是安全的 */ } -// 设置终端为原始模式(捕获所有输入) +/* ============================================================================ + * 终端控制函数 + * ============================================================================ */ + +/** + * @brief 设置终端为原始模式 + * + * 原始模式下: + * - 禁用回显 + * - 禁用规范模式(逐字符读取) + * - 禁用信号生成(Ctrl+C 等) + * - 禁用输入/输出处理 + * + * @param fd 终端文件描述符 + * @return 0 成功,-1 失败 + */ int setup_terminal_raw_mode(int fd) { DEBUG_LOG("设置终端为原始模式: fd=%d", fd); - struct termios raw; + /* 保存原始设置 */ if (tcgetattr(fd, &g_original_termios) < 0) { - DEBUG_LOG("保存原始终端设置失败"); - return -1; - } - g_termios_saved = 1; - DEBUG_LOG("原始终端设置已保存"); - - if (tcgetattr(fd, &raw) < 0) { + DEBUG_LOG("保存原始终端设置失败: %s", strerror(errno)); return -1; } - // 设置原始模式 - raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); - raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); - raw.c_cflag &= ~(CSIZE | PARENB); - raw.c_cflag |= CS8; - raw.c_oflag &= ~(OPOST); + /* 基于原始设置创建修改版本 */ + struct termios raw = g_original_termios; - // 设置读取超时 - raw.c_cc[VMIN] = 0; - raw.c_cc[VTIME] = 1; + /* 输入模式:禁用各种处理 */ + raw.c_iflag &= ~(tcflag_t)(BRKINT | /* 忽略 BREAK */ + ICRNL | /* 不将 CR 转换为 NL */ + INPCK | /* 禁用奇偶校验 */ + ISTRIP | /* 不剥离第 8 位 */ + IXON); /* 禁用软件流控制 */ + /* 输出模式:禁用输出处理 */ + raw.c_oflag &= ~(tcflag_t)(OPOST); /* 禁用输出处理 */ + + /* 控制模式:设置字符大小为 8 位 */ + raw.c_cflag &= ~(tcflag_t)(CSIZE | /* 清除字符大小位 */ + PARENB); /* 禁用奇偶校验 */ + raw.c_cflag |= CS8; /* 8 位字符 */ + + /* 本地模式:禁用回显和规范模式 */ + raw.c_lflag &= ~(tcflag_t)(ECHO | /* 禁用回显 */ + ICANON | /* 禁用规范模式 */ + IEXTEN | /* 禁用扩展输入处理 */ + ISIG); /* 禁用信号生成 */ + + /* 设置读取参数 */ + raw.c_cc[VMIN] = 0; /* 非阻塞读取 */ + raw.c_cc[VTIME] = 1; /* 100ms 超时 */ + + /* 应用新设置 */ if (tcsetattr(fd, TCSAFLUSH, &raw) < 0) { - DEBUG_LOG("设置终端原始模式失败"); + DEBUG_LOG("设置终端原始模式失败: %s", strerror(errno)); return -1; } + /* 只有成功后才标记为已保存 */ + g_termios_saved = 1; DEBUG_LOG("终端原始模式设置成功"); + return 0; } -// 恢复终端模式 +/** + * @brief 恢复终端模式 + * + * 将终端恢复到调用 setup_terminal_raw_mode 之前的状态 + * 此函数可以安全地多次调用 + * + * @param fd 终端文件描述符 + * @return 0 成功,-1 失败 + */ int restore_terminal_mode(int fd) { DEBUG_LOG("恢复终端模式: fd=%d", fd); + if (!g_termios_saved) { - DEBUG_LOG("没有保存的终端设置,跳过恢复"); - return 0; // 没有保存过或已经恢复过,这不是错误 + DEBUG_LOG("没有保存的终端设置,跳过恢复"); + return 0; /* 没有保存过或已经恢复过,不是错误 */ } if (tcsetattr(fd, TCSAFLUSH, &g_original_termios) < 0) { - DEBUG_LOG("恢复终端模式失败"); + DEBUG_LOG("恢复终端模式失败: %s", strerror(errno)); + /* 不清除 g_termios_saved,允许后续重试 */ return -1; } DEBUG_LOG("终端模式恢复成功"); - g_termios_saved = 0; // 标记已恢复,防止重复恢复 + g_termios_saved = 0; /* 标记已恢复,防止重复恢复 */ + return 0; } -// 启用鼠标跟踪(发送ANSI转义序列) +/** + * @brief 启用鼠标跟踪 + * + * 发送 ANSI 转义序列启用鼠标事件报告 + * 启用的模式: + * - X11 鼠标报告 (1000) + * - 单元格运动跟踪 (1002) + * - SGR 扩展模式 (1006) + * + * @param fd 终端文件描述符 + * @return 0 成功,-1 失败 + */ int enable_mouse_tracking(int fd) { - // 启用鼠标跟踪模式 - // \033[?1000h - 启用X11鼠标报告 - // \033[?1002h - 启用单元格运动鼠标跟踪 - // \033[?1003h - 启用所有运动鼠标跟踪 - // \033[?1006h - 启用SGR扩展鼠标模式 - const char* enable_seq = "\033[?1000h\033[?1002h\033[?1006h"; + /* + * 转义序列说明: + * \033[?1000h - 启用 X11 鼠标报告(基本鼠标事件) + * \033[?1002h - 启用按钮事件跟踪(拖拽) + * \033[?1006h - 启用 SGR 扩展模式(支持大于 223 的坐标) + */ + static const char enable_seq[] = "\033[?1000h\033[?1002h\033[?1006h"; + size_t len = sizeof(enable_seq) - 1; /* 不包括 '\0' */ - ssize_t written = write(fd, enable_seq, strlen(enable_seq)); + ssize_t written = write(fd, enable_seq, len); if (written < 0) { + DEBUG_LOG("启用鼠标跟踪失败: %s", strerror(errno)); + return -1; + } + if ((size_t)written != len) { + DEBUG_LOG("启用鼠标跟踪: 部分写入 %zd/%zu 字节", written, len); return -1; } + DEBUG_LOG("鼠标跟踪已启用"); return 0; } -// 禁用鼠标跟踪 +/** + * @brief 禁用鼠标跟踪 + * + * 发送 ANSI 转义序列禁用鼠标事件报告 + * 按启用的相反顺序禁用各模式 + * + * @param fd 终端文件描述符 + * @return 0 成功,-1 失败 + */ int disable_mouse_tracking(int fd) { - // 禁用鼠标跟踪模式 - // \033[?1000l - 禁用X11鼠标报告 - // \033[?1002l - 禁用单元格运动鼠标跟踪 - // \033[?1006l - 禁用SGR扩展鼠标模式 - const char* disable_seq = "\033[?1006l\033[?1002l\033[?1000l"; + /* + * 转义序列说明: + * \033[?1006l - 禁用 SGR 扩展模式 + * \033[?1002l - 禁用按钮事件跟踪 + * \033[?1000l - 禁用 X11 鼠标报告 + * 注:按启用的相反顺序禁用 + */ + static const char disable_seq[] = "\033[?1006l\033[?1002l\033[?1000l"; + size_t len = sizeof(disable_seq) - 1; /* 不包括 '\0' */ - ssize_t written = write(fd, disable_seq, strlen(disable_seq)); + ssize_t written = write(fd, disable_seq, len); if (written < 0) { + DEBUG_LOG("禁用鼠标跟踪失败: %s", strerror(errno)); + return -1; + } + if ((size_t)written != len) { + DEBUG_LOG("禁用鼠标跟踪: 部分写入 %zd/%zu 字节", written, len); return -1; } + DEBUG_LOG("鼠标跟踪已禁用"); return 0; -} +} \ No newline at end of file