一些基础组件
This commit is contained in:
@@ -0,0 +1,571 @@
|
||||
#include <c_Console.h>
|
||||
#include <locale.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <termios.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/select.h>
|
||||
#endif
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
// 仅在 POSIX (Linux/macOS) 平台下使用的全局变量,用于恢复终端
|
||||
#if defined(_WIN32) && defined(_WIN64)
|
||||
// 备份用户进入程序前的原始代码页
|
||||
static UINT orig_in_cp = 0;
|
||||
static UINT orig_out_cp = 0;
|
||||
#else
|
||||
static struct termios orig_termios;
|
||||
static int is_terminal_initialized = 0;
|
||||
#endif
|
||||
|
||||
// 内部私有回调:用于程序退出时自动恢复终端属性
|
||||
static void c_Console_ResetOnExit(void) {
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
// 恢复 Windows 用户原本的代码页环境,避免污染用户的 CMD/PowerShell 终端
|
||||
if (orig_in_cp != 0) SetConsoleCP(orig_in_cp);
|
||||
if (orig_out_cp != 0) SetConsoleOutputCP(orig_out_cp);
|
||||
#else
|
||||
// Linux 恢复原始 Raw Mode 设置(参考上一轮代码)
|
||||
extern void disable_raw_mode(void);
|
||||
disable_raw_mode();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 跨平台初始化控制台环境
|
||||
* Windows: 激活全局 ANSI 转义序列支持
|
||||
* Linux/macOS: 关闭行缓冲、关闭按键回显,并注册退出恢复钩子
|
||||
*/
|
||||
void c_Console_Init(void) {
|
||||
// 1. 全平台通用的 C 标准库本地化声明(促使 printf/scanf 内部行为适配 UTF-8)
|
||||
setlocale(LC_ALL, ".UTF-8");
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
// ----------------------------------------------------
|
||||
// Windows 平台:激活 VT 模式并全面强制 UTF-8 (65001)
|
||||
// ----------------------------------------------------
|
||||
// 备份旧代码页
|
||||
orig_in_cp = GetConsoleCP();
|
||||
orig_out_cp = GetConsoleOutputCP();
|
||||
|
||||
// 强行设为 UTF-8 编码(65001 == CP_UTF8)
|
||||
SetConsoleCP(CP_UTF8); // 影响控制台输入(如 scanf/fgets)
|
||||
SetConsoleOutputCP(CP_UTF8); // 影响控制台输出(如 printf)
|
||||
|
||||
// 注册退出钩子,以便程序正常退出或 exit 时自动还原环境
|
||||
atexit(c_Console_ResetOnExit);
|
||||
|
||||
// 激活虚拟终端 (VT) 从而支持 ANSI 转义序列
|
||||
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
if (hOut != INVALID_HANDLE_VALUE) {
|
||||
DWORD dwMode = 0;
|
||||
if (GetConsoleMode(hOut, &dwMode)) {
|
||||
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||
SetConsoleMode(hOut, dwMode);
|
||||
}
|
||||
}
|
||||
|
||||
HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
|
||||
if (hIn!=INVALID_HANDLE_VALUE) {
|
||||
DWORD mode;
|
||||
// 1. 获取当前控制台的输入模式
|
||||
if (GetConsoleMode(hIn, &mode)) {
|
||||
// 2. 启用鼠标输入
|
||||
mode |= ENABLE_MOUSE_INPUT;
|
||||
|
||||
// 3. 禁用快速编辑模式(非常关键!如果不禁用,鼠标事件会被控制台自身拦截用于复制文本)
|
||||
mode &= ~ENABLE_QUICK_EDIT_MODE;
|
||||
|
||||
// 4. 启用扩展属性(如果要修改 QUICK_EDIT,必须同时带上这个标志)
|
||||
mode |= ENABLE_EXTENDED_FLAGS;
|
||||
|
||||
// 5. 应用新模式
|
||||
SetConsoleMode(hIn, mode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#else
|
||||
// ----------------------------------------------------
|
||||
// Linux/macOS 平台:开启 Raw Mode(沿用上一轮逻辑)
|
||||
// ----------------------------------------------------
|
||||
extern struct termios orig_termios;
|
||||
extern int is_terminal_initialized;
|
||||
|
||||
if (!isatty(STDIN_FILENO)) return;
|
||||
if (tcgetattr(STDIN_FILENO, &orig_termios) < 0) return;
|
||||
|
||||
atexit(c_Console_ResetOnExit);
|
||||
is_terminal_initialized = 1;
|
||||
|
||||
struct termios raw = orig_termios;
|
||||
raw.c_lflag &= ~(ICANON | ECHO); // 禁用回显和标准缓冲
|
||||
raw.c_cc[VMIN] = 1;
|
||||
raw.c_cc[VTIME] = 0;
|
||||
tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
|
||||
#endif
|
||||
|
||||
// 确保标准输出缓冲区立即刷新
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
int c_Console_kbhit(void) {
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
return _kbhit();
|
||||
#else
|
||||
struct timeval tv = {0, 0};
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(STDIN_FILENO, &fds);
|
||||
return select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv) > 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
int c_Console_getch(void) {
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
return _getch();
|
||||
#else
|
||||
char ch = 0;
|
||||
if (read(STDIN_FILENO, &ch, 1) < 0) return 0;
|
||||
return ch;
|
||||
#endif
|
||||
}
|
||||
|
||||
void c_Console_Sleep(int milliseconds) {
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
Sleep(milliseconds);
|
||||
#else
|
||||
usleep(milliseconds * 1000);
|
||||
#endif
|
||||
}
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
|
||||
void c_Console_BlankLine(int y) {
|
||||
// 1. 将光标移动到目标行的第 1 列
|
||||
// \033[%d;1H : 移动到第 y 行,第 1 列
|
||||
printf("\033[%d;1H", y);
|
||||
|
||||
// 2. 清除从光标位置到行尾的所有内容(现代终端推荐直接用 2K 清除整行)
|
||||
// \033[2K : 清除光标所在的整行内容,但光标位置保持不变
|
||||
printf("\033[2K");
|
||||
|
||||
// 3. 强制刷新输出缓冲区,确保屏幕立刻更新
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从指定坐标 (x, y) 开始清空到该行的末尾
|
||||
*/
|
||||
void c_Console_BlankLineFrom(int x, int y) {
|
||||
printf("\033[%d;%dH", y, x); // 移动到 (x, y)
|
||||
printf("\033[0K"); // 清除从当前光标到行尾
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 跨平台移动控制台光标到指定坐标
|
||||
* @param x 目标列坐标 (Column/Horizontal),从 1 开始计,自左向右递增
|
||||
* @param y 目标行坐标 (Row/Vertical),从 1 开始计,自上向下递增
|
||||
*/
|
||||
void c_Console_GotoXY(int x, int y) {
|
||||
// 防御性保护:ANSI 坐标必须从 1 开始
|
||||
if (x < 1) x = 1;
|
||||
if (y < 1) y = 1;
|
||||
|
||||
// \033[%d;%dH : 第一个参数是行(y),第二个参数是列(x)
|
||||
// 这是 ANSI X3.64 标准的固定顺序,切勿将 x 和 y 的位置颠倒
|
||||
printf("\033[%d;%dH", y, x);
|
||||
|
||||
// 强制刷新缓冲区,确保光标立即移动到位
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
void c_Console_GetSize(int *width, int *height) {
|
||||
if (!width || !height) return;
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
||||
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
if (hOut != INVALID_HANDLE_VALUE && GetConsoleScreenBufferInfo(hOut, &csbi)) {
|
||||
// srWindow 存储了当前可视窗口的矩形边界(0-based 坐标)
|
||||
*width = csbi.srWindow.Right - csbi.srWindow.Left + 1;
|
||||
*height = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
|
||||
} else {
|
||||
*width = 80; // 失败时的兜底默认值
|
||||
*height = 25;
|
||||
}
|
||||
#else
|
||||
struct winsize w;
|
||||
// 使用 ioctl 的 TIOCGWINSZ 标志直接获取终端窗口大小
|
||||
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) == 0) {
|
||||
*width = w.ws_col;
|
||||
*height = w.ws_row;
|
||||
} else {
|
||||
*width = 80; // 失败时的兜底默认值
|
||||
*height = 25;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
int c_Console_GetVisualWidth(const char *str) {
|
||||
if (!str) return 0;
|
||||
|
||||
int visual_width = 0;
|
||||
int i = 0;
|
||||
|
||||
while (str[i] != '\0') {
|
||||
unsigned char c = (unsigned char)str[i];
|
||||
|
||||
if (c < 0x80) {
|
||||
// ----------------------------------------------------
|
||||
// 1. ASCII 字符 (0x00 - 0x7F) -> 占 1 字节,视觉宽度 1
|
||||
// ----------------------------------------------------
|
||||
visual_width += 1;
|
||||
i += 1;
|
||||
}
|
||||
else if ((c & 0xE0) == 0xC0) {
|
||||
// ----------------------------------------------------
|
||||
// 2. 2字节 UTF-8 字符 (如部分拉丁文、希腊字母) -> 视觉宽度 1
|
||||
// ----------------------------------------------------
|
||||
visual_width += 1;
|
||||
i += 2;
|
||||
}
|
||||
else if ((c & 0xF0) == 0xE0) {
|
||||
// ----------------------------------------------------
|
||||
// 3. 3字节 UTF-8 字符 (绝大多数中日韩汉字、常用标点) -> 视觉宽度 2
|
||||
// ----------------------------------------------------
|
||||
visual_width += 2;
|
||||
i += 3;
|
||||
}
|
||||
else if ((c & 0xF8) == 0xF0) {
|
||||
// ----------------------------------------------------
|
||||
// 4. 4字节 UTF-8 字符 (如 Emoji 表情、生僻字) -> 视觉宽度 2
|
||||
// ----------------------------------------------------
|
||||
visual_width += 2;
|
||||
i += 4;
|
||||
}
|
||||
else {
|
||||
// 异常安全降级处理
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
return visual_width;
|
||||
}
|
||||
|
||||
void c_Console_PrintCenter(int y, int box_width, const char *str) {
|
||||
if (!str) return;
|
||||
|
||||
// 1. 计算文本的实际视觉宽度
|
||||
int text_width = c_Console_GetVisualWidth(str);
|
||||
|
||||
// 2. 计算居中所需的左侧起始 X 坐标 (1-based 坐标系)
|
||||
int start_x = (box_width - text_width) / 2 + 1;
|
||||
if (start_x < 1) start_x = 1; // 边界防御
|
||||
|
||||
// 3. 跨平台移动光标并打印
|
||||
// 注意:需要使用外部已实现的 c_Console_GotoXY,此处以标准 ANSI 语法演示
|
||||
printf("\033[%d;%dH%s", y, start_x, str);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void c_Console_PrintLeftAligned(const char *str, int align_len) {
|
||||
if (!str) return;
|
||||
|
||||
// 打印文本
|
||||
printf("%s", str);
|
||||
|
||||
// 计算实际占用的宽度,并动态补齐缺失的物理空格
|
||||
int text_width = c_Console_GetVisualWidth(str);
|
||||
int padding = align_len - text_width;
|
||||
|
||||
for (int i = 0; i < padding; i++) {
|
||||
putchar(' ');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int c_Console_WriteXY(int x, int y, const char *format, ...) {
|
||||
// 1. 防御性保护:确保坐标合法
|
||||
if (x < 1) x = 1;
|
||||
if (y < 1) y = 1;
|
||||
|
||||
// 2. 移动光标到目标坐标 (使用 ANSI CUP 序列)
|
||||
printf("\033[%d;%dH", y, x);
|
||||
|
||||
// 3. 处理 C 语言可变参数并安全输出
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
int result = vprintf(format, args); // 核心:使用 vprintf 将参数包直接输出到控制台
|
||||
va_end(args);
|
||||
|
||||
// 4. 强制刷新输出缓冲区,确保文本和光标立即更新,防止画面滞后
|
||||
fflush(stdout);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
int c_Console_WriteColorXY(int x, int y, c_ConsoleColor_t fg, c_ConsoleColor_t bg, const char *format, ...) {
|
||||
// 1. 防御性保护:确保坐标合法
|
||||
if (x < 1) x = 1;
|
||||
if (y < 1) y = 1;
|
||||
|
||||
// 2. 移动光标到指定位置
|
||||
printf("\033[%d;%dH", y, x);
|
||||
|
||||
// 3. 构建并发送 ANSI 颜色控制序列
|
||||
// 标准前景色: 30-37, 高亮前景色: 90-97
|
||||
// 标准背景色: 40-47, 高亮背景色: 100-107
|
||||
if (fg != C_COLOR_NONE) {
|
||||
if (fg < 8) {
|
||||
printf("\033[%dm", 30 + fg); // 标准前景色
|
||||
} else {
|
||||
printf("\033[%dm", 90 + (fg - 8)); // 高亮前景色
|
||||
}
|
||||
}
|
||||
if (bg != C_COLOR_NONE) {
|
||||
if (bg < 8) {
|
||||
printf("\033[%dm", 40 + bg); // 标准背景色
|
||||
} else {
|
||||
printf("\033[%dm", 100 + (bg - 8));// 高亮背景色
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 处理可变参数并安全输出文本
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
int result = vprintf(format, args);
|
||||
va_end(args);
|
||||
|
||||
// 5. 关键:格式化文本打印结束后,**必须重置颜色**,否则会污染后续的所有打印
|
||||
printf("\033[0m");
|
||||
|
||||
// 6. 强制刷新输出缓冲区
|
||||
fflush(stdout);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
void c_Console_EnableMouse(void) {
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
|
||||
DWORD dwMode = 0;
|
||||
GetConsoleMode(hIn, &dwMode);
|
||||
// 启用鼠标输入标志
|
||||
dwMode |= ENABLE_MOUSE_INPUT;
|
||||
// 如果启用了快速编辑模式(QuickEdit),会导致鼠标点击被系统拦截变成选中文字,必须关闭
|
||||
dwMode &= ~ENABLE_QUICK_EDIT_MODE;
|
||||
SetConsoleMode(hIn, dwMode);
|
||||
#else
|
||||
// Linux/macOS: 发送 ANSI 序列开启鼠标追踪
|
||||
// \033[?1003h: 追踪所有鼠标动作(包括移动、点击、释放)
|
||||
// \033[?1006h: 启用 SGR 鼠标编码模式(现代终端标配,坐标支持超过 255)
|
||||
printf("\033[?1003h\033[?1006h");
|
||||
fflush(stdout);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void c_Console_DisableMouse(void) {
|
||||
#if !defined(_WIN32) && !defined(_WIN64)
|
||||
printf("\033[?1003l\033[?1006l"); // 关闭鼠标追踪
|
||||
fflush(stdout);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 跨平台非阻塞读取鼠标事件
|
||||
* @param mouse_evt 用于接收转换后的通用鼠标事件结构体
|
||||
* @return int 如果成功捕获到鼠标事件返回 1,否则返回 0
|
||||
*/
|
||||
int c_Console_ReadMouse(c_ConsoleMouseEvent_t *mouse_evt) {
|
||||
if (!mouse_evt) return 0;
|
||||
mouse_evt->type = MOUSE_EVENT_NONE;
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
// ----------------------------------------------------
|
||||
// Windows 平台实现
|
||||
// ----------------------------------------------------
|
||||
HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
|
||||
DWORD numEvents = 0;
|
||||
|
||||
// 检查输入缓冲区中是否有事件
|
||||
GetNumberOfConsoleInputEvents(hIn, &numEvents);
|
||||
if (numEvents == 0) return 0;
|
||||
|
||||
INPUT_RECORD inRec;
|
||||
DWORD numRead = 0;
|
||||
// 窥视而不直接移出,先确认是不是鼠标事件
|
||||
PeekConsoleInput(hIn, &inRec, 1, &numRead);
|
||||
|
||||
if (numRead > 0 && inRec.EventType == MOUSE_EVENT) {
|
||||
// 确实是鼠标事件,正式读出
|
||||
ReadConsoleInput(hIn, &inRec, 1, &numRead);
|
||||
MOUSE_EVENT_RECORD mer = inRec.Event.MouseEvent;
|
||||
|
||||
// 转换坐标 (Windows 是 0-based,我们要统一转换为 1-based)
|
||||
mouse_evt->x = mer.dwMousePosition.X + 1;
|
||||
mouse_evt->y = mer.dwMousePosition.Y + 1;
|
||||
|
||||
// 判断事件类型
|
||||
if (mer.dwEventFlags == 0) {
|
||||
// 点击或释放
|
||||
if (mer.dwButtonState & FROM_LEFT_1ST_BUTTON_PRESSED) {
|
||||
mouse_evt->type = MOUSE_EVENT_PRESS_LEFT;
|
||||
} else if (mer.dwButtonState & RIGHTMOST_BUTTON_PRESSED) {
|
||||
mouse_evt->type = MOUSE_EVENT_PRESS_RIGHT;
|
||||
} else if (mer.dwButtonState & FROM_LEFT_2ND_BUTTON_PRESSED) {
|
||||
mouse_evt->type = MOUSE_EVENT_PRESS_MIDDLE;
|
||||
} else {
|
||||
mouse_evt->type = MOUSE_EVENT_RELEASE;
|
||||
}
|
||||
} else if (mer.dwEventFlags == MOUSE_WHEELED) {
|
||||
// 滚轮滚动 (HIWORD 为正向上,为负向下)
|
||||
if ((short)HIWORD(mer.dwButtonState) > 0) {
|
||||
mouse_evt->type = MOUSE_EVENT_WHEEL_UP;
|
||||
} else {
|
||||
mouse_evt->type = MOUSE_EVENT_WHEEL_DOWN;
|
||||
}
|
||||
} else if (mer.dwEventFlags == MOUSE_MOVED) {
|
||||
mouse_evt->type = MOUSE_EVENT_MOVE;
|
||||
}
|
||||
return 1;
|
||||
} else if (numRead > 0) {
|
||||
// 如果不是鼠标事件(比如是键盘事件),则丢弃或由其他函数处理
|
||||
ReadConsoleInput(hIn, &inRec, 1, &numRead);
|
||||
}
|
||||
|
||||
#else
|
||||
// ----------------------------------------------------
|
||||
// Linux/macOS 平台实现 (解析 SGR 鼠标协议序列)
|
||||
// SGR 格式一般为: \033[<按钮代号>;X坐标;Y坐标M (或 m 代表释放)
|
||||
// ----------------------------------------------------
|
||||
|
||||
if (!c_Console_kbhit()) return 0;
|
||||
|
||||
int ch = c_Console_getch();
|
||||
if (ch == 27) { // 捕获到 ESC (\033)
|
||||
if (c_Console_getch() == '[') {
|
||||
if (c_Console_getch() == '<') {
|
||||
int btn = 0, x = 0, y = 0;
|
||||
char action = 0;
|
||||
|
||||
// 解析 SGR 密文参数,例如 "0;45;12M"
|
||||
// 现代终端标准,直接使用 scanf 变体或手动循环读取
|
||||
if (scanf("%d;%d;%d%c", &btn, &x, &y, &action) == 4) {
|
||||
mouse_evt->x = x;
|
||||
mouse_evt->y = y;
|
||||
|
||||
if (action == 'm') {
|
||||
mouse_evt->type = MOUSE_EVENT_RELEASE;
|
||||
} else if (action == 'M') {
|
||||
if (btn == 0) mouse_evt->type = MOUSE_EVENT_PRESS_LEFT;
|
||||
else if (btn == 1) mouse_evt->type = MOUSE_EVENT_PRESS_MIDDLE;
|
||||
else if (btn == 2) mouse_evt->type = MOUSE_EVENT_PRESS_RIGHT;
|
||||
else if (btn == 32) mouse_evt->type = MOUSE_EVENT_MOVE; // 伴随左键的移动
|
||||
else if (btn == 35) mouse_evt->type = MOUSE_EVENT_MOVE; // 纯移动
|
||||
else if (btn == 64) mouse_evt->type = MOUSE_EVENT_WHEEL_UP;
|
||||
else if (btn == 65) mouse_evt->type = MOUSE_EVENT_WHEEL_DOWN;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------------------------------------------ */
|
||||
/* */
|
||||
|
||||
/**
|
||||
* @brief 跨平台精准读取并转换键盘按键(包含方向键、Esc、Enter)
|
||||
* @note 必须确保终端已通过 c_Console_Init() 切换到非阻塞/Raw 模式
|
||||
* @return int 返回转换后的统一 c_ConsoleKeyCode_t 键值,若为普通字符则原样返回其 ASCII 码
|
||||
*/
|
||||
int c_Console_ReadKey(void) {
|
||||
if (!c_Console_kbhit()) return C_KEY_UNKNOWN;
|
||||
|
||||
int ch = c_Console_getch();
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
// ----------------------------------------------------
|
||||
// Windows 平台:处理扩展键双字节
|
||||
// ----------------------------------------------------
|
||||
if (ch == 0 || ch == 224) {
|
||||
// 遇到特殊前缀,必须紧接着读取第二个字节
|
||||
int sub_ch = _getch();
|
||||
switch (sub_ch) {
|
||||
case 72: return C_KEY_UP;
|
||||
case 80: return C_KEY_DOWN;
|
||||
case 75: return C_KEY_LEFT;
|
||||
case 77: return C_KEY_RIGHT;
|
||||
default: return C_KEY_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
// Windows 的回车可能返回 '\r' (13),统一规范化
|
||||
if (ch == '\r' || ch == '\n') return C_KEY_ENTER;
|
||||
|
||||
return ch; // 普通 ASCII 字符 (如 'w', 'a', 's', 'd') 直接原样返回
|
||||
|
||||
#else
|
||||
// ----------------------------------------------------
|
||||
// Linux/macOS 平台:处理 ANSI 键盘转义序列流
|
||||
// ----------------------------------------------------
|
||||
if (ch == 27) { // 捕获到第一个字节是 ESC
|
||||
// 立即检查缓冲区,看后面有没有跟着字符。如果没有,说明用户真的只按了独立的 Esc 键
|
||||
if (!c_Console_kbhit()) {
|
||||
return C_KEY_ESC;
|
||||
}
|
||||
|
||||
int next1 = c_Console_getch();
|
||||
if (next1 == '[') {
|
||||
if (!c_Console_kbhit()) return C_KEY_UNKNOWN;
|
||||
int next2 = c_Console_getch();
|
||||
|
||||
// 标准方向键序列判定: ESC [ A/B/C/D
|
||||
switch (next2) {
|
||||
case 'A': return C_KEY_UP;
|
||||
case 'B': return C_KEY_DOWN;
|
||||
case 'C': return C_KEY_RIGHT;
|
||||
case 'D': return C_KEY_LEFT;
|
||||
default: return C_KEY_UNKNOWN;
|
||||
}
|
||||
}
|
||||
return C_KEY_UNKNOWN;
|
||||
}
|
||||
|
||||
// Linux 退格键适配
|
||||
if (ch == 127) return C_KEY_BACKSPACE;
|
||||
// 回车键统一
|
||||
if (ch == '\n' || ch == '\r') return C_KEY_ENTER;
|
||||
|
||||
return ch; // 普通 ASCII 字符原样返回
|
||||
#endif
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user