commit 5f51a52ca6b41ba3865b42b272eeacc1287c762b Author: wcjbr Date: Sat Aug 15 19:11:54 2026 +0800 First commit diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..9026068 --- /dev/null +++ b/.clang-format @@ -0,0 +1,6 @@ +Language: Cpp +BasedOnStyle: Google +IndentWidth: 4 +ColumnLimit: 100 +AllowShortIfStatementsOnASingleLine: false +AlignTrailingComments: true \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c00a89a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build +.vscode +example/build \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..36c5402 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.10.0) +project(ConsoleLib VERSION 0.1.0 LANGUAGES C CXX) + +option(ConsoleLib_BUILD_EXAMPLES "Build ConsoleLib examples" ON) + +# 静态库:Linux/macOS 生成 libConsoleLib.a,Windows 生成 ConsoleLib.lib +add_library(ConsoleLib STATIC ConsoleLib.cpp include/ConsoleLib.h) + +# 共享库:Linux/macOS 生成 libConsoleLib.so,Windows 生成 ConsoleLib.dll +add_library(ConsoleLib_shared SHARED ConsoleLib.cpp) + +target_include_directories(ConsoleLib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_include_directories(ConsoleLib_shared PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) + +set_target_properties(ConsoleLib_shared PROPERTIES + OUTPUT_NAME ConsoleLib + WINDOWS_EXPORT_ALL_SYMBOLS ON +) + +if(ConsoleLib_BUILD_EXAMPLES) + add_subdirectory(example) +endif() diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..792edb7 --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,24 @@ +{ + "version": 8, + "configurePresets": [ + { + "name": "Clang", + "displayName": "Clang (Ninja, Debug)", + "description": "使用 Clang 和 Ninja 配置 ConsoleLib / 示例", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++", + "CMAKE_INSTALL_PREFIX": "${sourceDir}/build/install" + } + } + ], + "buildPresets": [ + { + "name": "Clang", + "configurePreset": "Clang" + } + ] +} diff --git a/ConsoleLib.cpp b/ConsoleLib.cpp new file mode 100644 index 0000000..3530fac --- /dev/null +++ b/ConsoleLib.cpp @@ -0,0 +1,477 @@ +#include "include/ConsoleLib.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#include +#include +#endif + +#ifdef __unix +#include +#include +#include +#include +#include +#include +#endif + +namespace ConsoleLib { +using ::Color; + +namespace { + +std::array g_keyTable{}; +std::mutex g_keyMutex; +std::atomic g_inputRunning{false}; +std::atomic g_inputFrequency{DEFAULT_CONSOLE_INPUT_FREQUENCY}; +std::thread g_inputThread; + +std::chrono::milliseconds currentPollInterval() { + std::uint8_t frequency = g_inputFrequency.load(std::memory_order_relaxed); + if (frequency == 0) { + frequency = 1; + } + return std::chrono::milliseconds(1000 / frequency); +} + +void recordKey(Keycode keycode) { + if (keycode <= KEY_UNKNOWN || keycode >= KEY_COUNT) { + return; + } + + std::lock_guard lock(g_keyMutex); + g_keyTable[static_cast(keycode)] = true; +} + +Keycode parseEscapeSequence(const char* buffer, size_t length) { + if (length < 2) { + return KEY_UNKNOWN; + } + + const unsigned char first = static_cast(buffer[0]); + + // Unix/Linux VT 序列:ESC [ X / ESC O X + if (first == 0x1B) { + if (length < 3) { + return KEY_UNKNOWN; + } + + if (buffer[1] == '[') { + switch (buffer[2]) { + case 'A': + return KEY_UP; + case 'B': + return KEY_DOWN; + case 'C': + return KEY_RIGHT; + case 'D': + return KEY_LEFT; + case 'H': + return KEY_HOME; + case 'F': + return KEY_END; + default: + return KEY_UNKNOWN; + } + } + + if (buffer[1] == 'O') { + switch (buffer[2]) { + case 'P': + return KEY_F1; + case 'Q': + return KEY_F2; + case 'R': + return KEY_F3; + case 'S': + return KEY_F4; + default: + return KEY_UNKNOWN; + } + } + + return KEY_UNKNOWN; + } + + // Windows 扩展键序列:0x00 / 0xE0 + 扫描码。 + if (first == 0x00 || first == 0xE0) { + switch (buffer[1]) { + case 'H': + return KEY_UP; + case 'P': + return KEY_DOWN; + case 'K': + return KEY_LEFT; + case 'M': + return KEY_RIGHT; + case 'G': + return KEY_HOME; + case 'O': + return KEY_END; + case ';': + return KEY_F1; + case '<': + return KEY_F2; + case '=': + return KEY_F3; + case '>': + return KEY_F4; + default: + return KEY_UNKNOWN; + } + } + + return KEY_UNKNOWN; +} + +std::string makeAnsiColorPrefix(Color color) { + return "\033[38;2;" + std::to_string((color >> 16) & 0xFFu) + ";" + + std::to_string((color >> 8) & 0xFFu) + ";" + std::to_string(color & 0xFFu) + "m"; +} + +} // namespace + +void startInputThread(); +void restoreConsoleMode(); + +#ifdef _WIN32 + +static HANDLE g_hStdin = INVALID_HANDLE_VALUE; +static HANDLE g_hStdout = INVALID_HANDLE_VALUE; +static DWORD g_dwOriginalInMode = 0; +static DWORD g_dwOriginalOutMode = 0; +static bool g_bInitialized = false; + +void initConsole() { + if (g_bInitialized) { + startInputThread(); + return; + } + + g_hStdin = GetStdHandle(STD_INPUT_HANDLE); + g_hStdout = GetStdHandle(STD_OUTPUT_HANDLE); + + if (g_hStdin == INVALID_HANDLE_VALUE || g_hStdout == INVALID_HANDLE_VALUE) { + return; + } + + if (!GetConsoleMode(g_hStdin, &g_dwOriginalInMode)) { + return; + } + + DWORD dwNewInMode = g_dwOriginalInMode; + dwNewInMode &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT); + dwNewInMode |= ENABLE_VIRTUAL_TERMINAL_INPUT; + + if (!SetConsoleMode(g_hStdin, dwNewInMode)) { + return; + } + + if (!GetConsoleMode(g_hStdout, &g_dwOriginalOutMode)) { + return; + } + + DWORD dwNewOutMode = g_dwOriginalOutMode; + dwNewOutMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + + if (!SetConsoleMode(g_hStdout, dwNewOutMode)) { + return; + } + + g_bInitialized = true; + startInputThread(); +} + +void clearConsole() { + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (hOut == INVALID_HANDLE_VALUE) { + return; + } + + CONSOLE_SCREEN_BUFFER_INFO csbi; + DWORD count; + COORD homeCoords = {0, 0}; + + if (!GetConsoleScreenBufferInfo(hOut, &csbi)) { + return; + } + + count = csbi.dwSize.X * csbi.dwSize.Y; + FillConsoleOutputCharacterA(hOut, ' ', count, homeCoords, &count); + FillConsoleOutputAttribute(hOut, csbi.wAttributes, count, homeCoords, &count); + SetConsoleCursorPosition(hOut, homeCoords); +} + +void RawPrint(std::string_view text) { + if (g_hStdout == INVALID_HANDLE_VALUE) { + return; + } + + DWORD bytesToWrite = static_cast(text.size()); + DWORD bytesWritten = 0; + WriteFile(g_hStdout, text.data(), bytesToWrite, &bytesWritten, nullptr); +} + +void printToConsole(std::string_view data) { RawPrint(data); } + +void printToConsole(std::string_view data, Color color) { + std::string prefix = makeAnsiColorPrefix(color); + RawPrint(prefix); + RawPrint(data); + RawPrint("\033[0m"); +} + +char getinput() { + if (!g_bInitialized || g_hStdin == INVALID_HANDLE_VALUE) { + return 0; + } + + char buffer[16] = {0}; + DWORD charsRead = 0; + + if (ReadFile(g_hStdin, buffer, sizeof(buffer) - 1, &charsRead, NULL) && charsRead > 0) { + if (charsRead == 1) { + return buffer[0]; + } + + Keycode key = parseEscapeSequence(buffer, charsRead); + if (key != KEY_UNKNOWN) { + return 0; // 特殊键请使用 getConsoleKeyInput() / isKeyPressed() + } + return buffer[0]; + } + + return 0; +} + +void restoreConsoleMode() { + if (!g_bInitialized) { + return; + } + + if (g_hStdin != INVALID_HANDLE_VALUE) { + SetConsoleMode(g_hStdin, g_dwOriginalInMode); + } + if (g_hStdout != INVALID_HANDLE_VALUE) { + SetConsoleMode(g_hStdout, g_dwOriginalOutMode); + } + + g_bInitialized = false; +} + +void startInputThread() { + bool expected = false; + if (!g_inputRunning.compare_exchange_strong(expected, true)) { + return; + } + + if (g_inputThread.joinable()) { + g_inputThread.join(); + } + + g_inputThread = std::thread([] { + while (g_inputRunning.load(std::memory_order_relaxed)) { + if (g_bInitialized && g_hStdin != INVALID_HANDLE_VALUE) { + DWORD available = 0; + if (GetNumberOfConsoleInputEvents(g_hStdin, &available) && available > 0) { + char buffer[16] = {0}; + DWORD charsRead = 0; + + if (ReadFile(g_hStdin, buffer, sizeof(buffer) - 1, &charsRead, nullptr) && + charsRead > 0) { + if (charsRead == 1) { + recordKey(static_cast(static_cast(buffer[0]))); + } else { + Keycode key = parseEscapeSequence(buffer, charsRead); + if (key != KEY_UNKNOWN) { + recordKey(key); + } else { + recordKey(KEY_ESCAPE); + } + } + } + } + } + + std::this_thread::sleep_for(currentPollInterval()); + } + }); +} + +#endif // _WIN32 + +#ifdef __unix + +static struct termios original_termios; +static bool raw_mode_enabled = false; + +void initConsole() { + if (!raw_mode_enabled) { + struct termios raw; + if (tcgetattr(STDIN_FILENO, &original_termios) == -1) { + return; + } + + raw = original_termios; + + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN); + raw.c_iflag &= ~(IXON | ICRNL | BRKINT | INPCK | ISTRIP); + raw.c_oflag &= ~(OPOST); + raw.c_cflag |= (CS8); + + raw.c_cc[VMIN] = 0; + raw.c_cc[VTIME] = 0; + + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) == -1) { + return; + } + + raw_mode_enabled = true; + } + + startInputThread(); +} + +void RawPrint(std::string_view text) { write(STDOUT_FILENO, text.data(), text.size()); } + +void printToConsole(std::string_view data) { RawPrint(data); } + +void printToConsole(std::string_view data, Color color) { + std::string prefix = makeAnsiColorPrefix(color); + RawPrint(prefix); + RawPrint(data); + RawPrint("\033[0m"); +} + +void clearConsole() { RawPrint("\033[2J\033[H"); } + +char getinput() { + char ch = 0; + return read(STDIN_FILENO, &ch, 1) == 1 ? ch : 0; +} + +void restoreConsoleMode() { + if (!raw_mode_enabled) { + return; + } + + tcsetattr(STDIN_FILENO, TCSAFLUSH, &original_termios); + raw_mode_enabled = false; +} + +void startInputThread() { + bool expected = false; + if (!g_inputRunning.compare_exchange_strong(expected, true)) { + return; + } + + if (g_inputThread.joinable()) { + g_inputThread.join(); + } + + g_inputThread = std::thread([] { + std::string pending; + char buffer[32] = {0}; + + while (g_inputRunning.load(std::memory_order_relaxed)) { + ssize_t n = read(STDIN_FILENO, buffer, sizeof(buffer)); + if (n > 0) { + pending.append(buffer, static_cast(n)); + + size_t pos = 0; + while (pos < pending.size()) { + unsigned char c = static_cast(pending[pos]); + + if (c == 0x1B) { + if (pending.size() - pos >= 2 && + (pending[pos + 1] == '[' || pending[pos + 1] == 'O')) { + if (pending.size() - pos >= 3) { + Keycode key = parseEscapeSequence(pending.data() + pos, 3); + recordKey(key); + pos += 3; + continue; + } + break; // 序列还不完整,等待下一次读取 + } + + recordKey(KEY_ESCAPE); + ++pos; + continue; + } + + if (c < ASCII_TABLE_SIZE) { + recordKey(static_cast(c)); + } + ++pos; + } + + pending.erase(0, pos); + } else if (n < 0 && errno != EINTR) { + // If stdin is no longer readable, stop polling instead of spinning + // forever. + g_inputRunning.store(false); + break; + } + + std::this_thread::sleep_for(currentPollInterval()); + } + }); +} + +#endif // __unix + +void stopInputThread() { + g_inputRunning.store(false); + + if (g_inputThread.joinable()) { + g_inputThread.join(); + } +} + +std::array& getConsoleKeyInput() { + static thread_local std::array snapshot{}; + + std::lock_guard lock(g_keyMutex); + snapshot = g_keyTable; + g_keyTable.fill(false); + + return snapshot; +} + +bool isKeyPressed(Keycode key) { + if (key <= KEY_UNKNOWN || key >= KEY_COUNT) { + return false; + } + + std::lock_guard lock(g_keyMutex); + return g_keyTable[static_cast(key)]; +} +void sleep_ms(unsigned int ms) { +#ifdef _WIN32 + Sleep(static_cast(ms)); +#else + std::this_thread::sleep_for(std::chrono::milliseconds(ms)); +#endif +} + +void setInputFrequency(std::uint8_t frequency) { + g_inputFrequency.store(frequency == 0 ? 1 : frequency, std::memory_order_relaxed); +} + +std::uint8_t getInputFrequency() { return g_inputFrequency.load(std::memory_order_relaxed); } + +void quit(int exitCode) { + stopInputThread(); + restoreConsoleMode(); + exit(exitCode); +} +} // namespace ConsoleLib diff --git a/README.md b/README.md new file mode 100644 index 0000000..d980882 --- /dev/null +++ b/README.md @@ -0,0 +1,229 @@ +# ConsoleLib + +> 一个用 C++ 编写的跨平台控制台操作库。 + +ConsoleLib 提供 Windows / Linux 下的控制台初始化、原始键盘输入、后台按键轮询、ANSI 真彩色输出、清屏和毫秒级休眠等功能,并同时产出静态库和动态库。 + +## 特性 + +- 控制台原始模式 / 原生模式初始化与恢复 +- 后台键盘输入线程,轮询并记录按键 +- 24 位 ANSI 真彩色输出 +- 清屏 +- 毫秒级跨平台休眠 `sleep_ms` +- 安全的 `quit` 退出 +- 同时生成静态库和动态库 + +## 平台产物 + +| 平台 | 静态库 | 动态库 | +| --- | --- | --- | +| Linux | `libConsoleLib.a` | `libConsoleLib.so` | +| Windows | `ConsoleLib.lib` | `ConsoleLib.dll` | + +## 环境要求 + +- CMake 3.10+(使用 `CMakePresets.json` 时建议 3.28+) +- 支持 C++17 的编译器(Clang / GCC / MSVC) +- 使用默认 preset 时需要 [Ninja](https://ninja-build.org/) + +## 目录结构 + +```text +ConsoleLib/ +├── CMakeLists.txt +├── CMakePresets.json +├── ConsoleLib.cpp +├── include/ +│ └── ConsoleLib.h +├── example/ +│ ├── CMakeLists.txt +│ ├── CMakePresets.json +│ ├── build.sh +│ ├── build.bat +│ ├── helloworld/ +│ │ ├── CMakeLists.txt +│ │ └── main.cpp +│ └── keyinput/ +│ ├── CMakeLists.txt +│ └── main.cpp +└── docs/ + └── image.png +``` + +## 快速开始 + +### 方式一:使用 CMake Presets(推荐) + +```bash +# 项目根目录 +./build.sh +``` + +等价命令: + +```bash +cmake --preset Clang +cmake --build --preset Clang -j +``` + +构建产物默认在 `build/`。 + +Windows 下可运行: + +```bat +build.bat +``` + +### 方式二:手动构建 + +```bash +cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug +cmake --build build -j +``` + +### 单独构建示例 + +```bash +cd example +./build.sh +``` + +或: + +```bash +cd example +cmake --preset Clang +cmake --build --preset Clang -j +./build/helloworld/helloworld +./build/keyinput/keyinput +``` + +## 示例 + +### HelloWorld + +`example/helloworld/main.cpp`: + +```cpp +#include + +using namespace ConsoleLib; + +signed main() { + initConsole(); + clearConsole(); + printToConsole("HelloWorld!\r\n"); + printToConsole("Red", 0xff0000); + printToConsole("Green", 0x00ff00); + printToConsole("Blue", 0x0000ff); + quit(0); +} +``` + +输出效果见 `docs/image.png`。 + +### 按键输入(含方向键) + +`example/keyinput/main.cpp` 展示了 `getConsoleKeyInput()` 与方向键、`Home`、`End`、`F1` ~ `F4` 以及普通可打印字符的检测: + +```cpp +#include + +using namespace ConsoleLib; + +signed main() { + initConsole(); + clearConsole(); + printToConsole("Press arrow keys, q or ESC to quit.\r\n"); + + while (true) { + auto &keys = getConsoleKeyInput(); + + if (keys[KEY_UP]) { + printToConsole("UP\r\n"); + } + if (keys[KEY_DOWN]) { + printToConsole("DOWN\r\n"); + } + if (keys[KEY_LEFT]) { + printToConsole("LEFT\r\n"); + } + if (keys[KEY_RIGHT]) { + printToConsole("RIGHT\r\n"); + } + + if (keys[KEY_q] || keys[KEY_ESCAPE]) { + break; + } + + sleep_ms(16); + } + + quit(0); +} +``` + +## 集成到你的项目 + +### 静态库 + +```cmake +add_subdirectory(ConsoleLib) +target_link_libraries(your_target PRIVATE ConsoleLib) +``` + +### 动态库 + +```cmake +add_subdirectory(ConsoleLib) +target_link_libraries(your_target PRIVATE ConsoleLib_shared) +``` + +`ConsoleLib` 目标已通过 `target_include_directories(... PUBLIC ...)` 暴露 `include/` 目录,因此直接包含即可: + +```cpp +#include +``` + +## API 参考 + +| 函数 | 说明 | +| --- | --- | +| `void initConsole()` | 初始化控制台并启动后台输入线程 | +| `void stopInputThread()` | 停止后台输入线程 | +| `void restoreConsoleMode()` | 恢复终端原始模式 / Windows 控制台模式 | +| `void RawPrint(std::string_view text)` | 直接输出文本,不做颜色处理 | +| `void printToConsole(std::string_view data)` | 打印文本 | +| `void printToConsole(std::string_view data, Color color)` | 打印指定颜色的文本 | +| `void clearConsole()` | 清屏并将光标移动到左上角 | +| `char getinput()` | 读取一个输入字符 | +| `std::array &getConsoleKeyInput()` | 返回并清空按键状态表,下标为 `Keycode` | +| `bool isKeyPressed(Keycode key)` | 查询某个按键当前是否被按下 | +| `void sleep_ms(unsigned int ms)` | 休眠指定毫秒数 | +| `void setInputFrequency(std::uint8_t frequency)` | 设置输入轮询频率(Hz) | +| `std::uint8_t getInputFrequency()` | 获取当前输入轮询频率 | +| `void quit(int exitCode)` | 停止线程、恢复终端并退出进程 | + +`Color` 为 `uint32_t`,格式为 `0xRRGGBB`。 + +## 按键码 + +`Keycode` 为 SDL 风格的按键码: + +- ASCII 字符直接使用其 ASCII 值,例如 `KEY_a`、`KEY_0`、`KEY_SPACE` +- 特殊键从 `128` 开始,例如 `KEY_UP`、`KEY_DOWN`、`KEY_LEFT`、`KEY_RIGHT`、`KEY_HOME`、`KEY_END`、`KEY_F1` ~ `KEY_F4` + +| 常量 | 含义 | +| --- | --- | +| `ASCII_TABLE_SIZE` | ASCII 范围大小,固定为 `128` | +| `KEYCODE_COUNT` | 按键状态表大小 | +| `DEFAULT_CONSOLE_INPUT_FREQUENCY` | 默认输入轮询频率(50 Hz) | + +`getConsoleKeyInput()` 返回长度为 `KEYCODE_COUNT` 的 `std::array`,下标即 `Keycode`;调用后内部按键记录会被清空。`isKeyPressed(Keycode)` 仅查询、不会清空记录。 + +## 说明 + +- `initConsole()` 会将终端切换到非回显的原始模式;程序结束前请调用 `quit()` 或 `restoreConsoleMode()`,否则终端状态可能无法恢复。 +- `quit()` 已包含 `stopInputThread()` 和 `restoreConsoleMode()`。 +- `getinput()` 仅返回单个 ASCII 字符;方向键、功能键等特殊键请使用 `getConsoleKeyInput()` 或 `isKeyPressed()`。 diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..0ca97ae --- /dev/null +++ b/build.bat @@ -0,0 +1,6 @@ +@echo off +cd /d "%~dp0" +cmake --preset Clang +if errorlevel 1 exit /b 1 +cmake --build --preset Clang -j +if errorlevel 1 exit /b 1 diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..5b03384 --- /dev/null +++ b/build.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "$0")" +cmake --preset Clang +cmake --build --preset Clang -j diff --git a/docs/image.png b/docs/image.png new file mode 100644 index 0000000..0b1d006 Binary files /dev/null and b/docs/image.png differ diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 0000000..5163d87 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.10) +project(example LANGUAGES CXX) + +# 作为顶层项目单独构建时,带上 ConsoleLib;作为子目录时目标已存在。 +if(NOT TARGET ConsoleLib) + set(ConsoleLib_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/.. ConsoleLib) +endif() + +add_subdirectory(helloworld) +add_subdirectory(keyinput) diff --git a/example/CMakePresets.json b/example/CMakePresets.json new file mode 100644 index 0000000..c562ff9 --- /dev/null +++ b/example/CMakePresets.json @@ -0,0 +1,6 @@ +{ + "version": 8, + "include": [ + "../CMakePresets.json" + ] +} diff --git a/example/build.bat b/example/build.bat new file mode 100644 index 0000000..0ca97ae --- /dev/null +++ b/example/build.bat @@ -0,0 +1,6 @@ +@echo off +cd /d "%~dp0" +cmake --preset Clang +if errorlevel 1 exit /b 1 +cmake --build --preset Clang -j +if errorlevel 1 exit /b 1 diff --git a/example/build.sh b/example/build.sh new file mode 100755 index 0000000..5b03384 --- /dev/null +++ b/example/build.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail +cd "$(dirname "$0")" +cmake --preset Clang +cmake --build --preset Clang -j diff --git a/example/helloworld/CMakeLists.txt b/example/helloworld/CMakeLists.txt new file mode 100644 index 0000000..ff28d57 --- /dev/null +++ b/example/helloworld/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(helloworld main.cpp) +target_link_libraries(helloworld PRIVATE ConsoleLib) diff --git a/example/helloworld/main.cpp b/example/helloworld/main.cpp new file mode 100644 index 0000000..6809e72 --- /dev/null +++ b/example/helloworld/main.cpp @@ -0,0 +1,14 @@ +#include + +#include +#include +using namespace ConsoleLib; +signed main() { + initConsole(); + clearConsole(); + printToConsole("HelloWorld!\r\n"); + printToConsole("Red", 0xff0000); + printToConsole("Green", 0x00ff00); + printToConsole("Blue", 0x0000ff); + quit(0); +} diff --git a/example/keyinput/CMakeLists.txt b/example/keyinput/CMakeLists.txt new file mode 100644 index 0000000..47178ad --- /dev/null +++ b/example/keyinput/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(keyinput main.cpp) +target_link_libraries(keyinput PRIVATE ConsoleLib) diff --git a/example/keyinput/main.cpp b/example/keyinput/main.cpp new file mode 100644 index 0000000..4075efe --- /dev/null +++ b/example/keyinput/main.cpp @@ -0,0 +1,63 @@ +#include + +using namespace ConsoleLib; + +signed main() { + initConsole(); + clearConsole(); + + printToConsole("ConsoleLib key input example\r\n"); + printToConsole("Press arrow keys, Home/End, F1-F4, or any printable key.\r\n"); + printToConsole("Press q or ESC to quit.\r\n"); + + while (true) { + // getConsoleKeyInput() 返回按键表并清空本次记录。 + auto& keys = getConsoleKeyInput(); + + if (keys[KEY_UP]) { + printToConsole("UP\r\n"); + } + if (keys[KEY_DOWN]) { + printToConsole("DOWN\r\n"); + } + if (keys[KEY_LEFT]) { + printToConsole("LEFT\r\n"); + } + if (keys[KEY_RIGHT]) { + printToConsole("RIGHT\r\n"); + } + if (keys[KEY_HOME]) { + printToConsole("HOME\r\n"); + } + if (keys[KEY_END]) { + printToConsole("END\r\n"); + } + if (keys[KEY_F1]) { + printToConsole("F1\r\n"); + } + if (keys[KEY_F2]) { + printToConsole("F2\r\n"); + } + if (keys[KEY_F3]) { + printToConsole("F3\r\n"); + } + if (keys[KEY_F4]) { + printToConsole("F4\r\n"); + } + + for (int c = 32; c < static_cast(ASCII_TABLE_SIZE); ++c) { + if (keys[static_cast(c)]) { + char text[2] = {static_cast(c), '\0'}; + printToConsole(text); + } + } + + if (keys[KEY_q] || keys[KEY_ESCAPE]) { + break; + } + + sleep_ms(16); + } + + quit(0); +} diff --git a/include/ConsoleLib.h b/include/ConsoleLib.h new file mode 100644 index 0000000..3ad49b5 --- /dev/null +++ b/include/ConsoleLib.h @@ -0,0 +1,101 @@ +#ifndef CONSOLELIB_H +#define CONSOLELIB_H + +#include +#include +#include +#include + +// SDL 风格的按键码:ASCII 字符直接使用其 ASCII 值,特殊键从 128 开始。 +enum Keycode : uint16_t { + KEY_UNKNOWN = 0, + + KEY_BACKSPACE = 8, + KEY_TAB = 9, + KEY_RETURN = 13, + KEY_ESCAPE = 27, + KEY_SPACE = 32, + + KEY_0 = '0', + KEY_1 = '1', + KEY_2 = '2', + KEY_3 = '3', + KEY_4 = '4', + KEY_5 = '5', + KEY_6 = '6', + KEY_7 = '7', + KEY_8 = '8', + KEY_9 = '9', + + KEY_a = 'a', + KEY_b = 'b', + KEY_c = 'c', + KEY_d = 'd', + KEY_e = 'e', + KEY_f = 'f', + KEY_g = 'g', + KEY_h = 'h', + KEY_i = 'i', + KEY_j = 'j', + KEY_k = 'k', + KEY_l = 'l', + KEY_m = 'm', + KEY_n = 'n', + KEY_o = 'o', + KEY_p = 'p', + KEY_q = 'q', + KEY_r = 'r', + KEY_s = 's', + KEY_t = 't', + KEY_u = 'u', + KEY_v = 'v', + KEY_w = 'w', + KEY_x = 'x', + KEY_y = 'y', + KEY_z = 'z', + + KEY_DELETE = 127, + + KEY_UP = 128, + KEY_DOWN, + KEY_LEFT, + KEY_RIGHT, + KEY_HOME, + KEY_END, + KEY_F1, + KEY_F2, + KEY_F3, + KEY_F4, + + KEY_COUNT +}; + +constexpr size_t KEYCODE_COUNT = static_cast(KEY_COUNT); +constexpr size_t ASCII_TABLE_SIZE = 128; +constexpr uint8_t DEFAULT_CONSOLE_INPUT_FREQUENCY = 50; +typedef std::uint32_t Color; + +namespace ConsoleLib { +void initConsole(); +void stopInputThread(); +void restoreConsoleMode(); + +void RawPrint(std::string_view text); +void printToConsole(std::string_view data); +void printToConsole(std::string_view data, Color color); +void clearConsole(); + +char getinput(); + +// 按 Keycode 返回按键状态,并清空内部记录。 +std::array& getConsoleKeyInput(); +bool isKeyPressed(Keycode key); + +void sleep_ms(unsigned int ms); +void setInputFrequency(std::uint8_t frequency); +std::uint8_t getInputFrequency(); + +void quit(int exitCode); +} // namespace ConsoleLib + +#endif // CONSOLELIB_H