First commit

This commit is contained in:
wcjbr
2026-08-15 19:11:54 +08:00
commit 5f51a52ca6
18 changed files with 982 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
Language: Cpp
BasedOnStyle: Google
IndentWidth: 4
ColumnLimit: 100
AllowShortIfStatementsOnASingleLine: false
AlignTrailingComments: true
+3
View File
@@ -0,0 +1,3 @@
build
.vscode
example/build
+22
View File
@@ -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.aWindows 生成 ConsoleLib.lib
add_library(ConsoleLib STATIC ConsoleLib.cpp include/ConsoleLib.h)
# 共享库:Linux/macOS 生成 libConsoleLib.soWindows 生成 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()
+24
View File
@@ -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"
}
]
}
+477
View File
@@ -0,0 +1,477 @@
#include "include/ConsoleLib.h"
#include <atomic>
#include <cerrno>
#include <chrono>
#include <cstdlib>
#include <mutex>
#include <string>
#include <thread>
#ifdef _WIN32
#include <locale.h>
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#endif
#ifdef __unix
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <termios.h>
#include <unistd.h>
#endif
namespace ConsoleLib {
using ::Color;
namespace {
std::array<bool, KEYCODE_COUNT> g_keyTable{};
std::mutex g_keyMutex;
std::atomic<bool> g_inputRunning{false};
std::atomic<std::uint8_t> 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<std::mutex> lock(g_keyMutex);
g_keyTable[static_cast<size_t>(keycode)] = true;
}
Keycode parseEscapeSequence(const char* buffer, size_t length) {
if (length < 2) {
return KEY_UNKNOWN;
}
const unsigned char first = static_cast<unsigned char>(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<DWORD>(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<Keycode>(static_cast<unsigned char>(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<size_t>(n));
size_t pos = 0;
while (pos < pending.size()) {
unsigned char c = static_cast<unsigned char>(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<Keycode>(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<bool, KEYCODE_COUNT>& getConsoleKeyInput() {
static thread_local std::array<bool, KEYCODE_COUNT> snapshot{};
std::lock_guard<std::mutex> 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<std::mutex> lock(g_keyMutex);
return g_keyTable[static_cast<size_t>(key)];
}
void sleep_ms(unsigned int ms) {
#ifdef _WIN32
Sleep(static_cast<DWORD>(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
+229
View File
@@ -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 <ConsoleLib.h>
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 <ConsoleLib.h>
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 <ConsoleLib.h>
```
## 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<bool, KEYCODE_COUNT> &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<bool, KEYCODE_COUNT>`,下标即 `Keycode`;调用后内部按键记录会被清空。`isKeyPressed(Keycode)` 仅查询、不会清空记录。
## 说明
- `initConsole()` 会将终端切换到非回显的原始模式;程序结束前请调用 `quit()``restoreConsoleMode()`,否则终端状态可能无法恢复。
- `quit()` 已包含 `stopInputThread()``restoreConsoleMode()`
- `getinput()` 仅返回单个 ASCII 字符;方向键、功能键等特殊键请使用 `getConsoleKeyInput()``isKeyPressed()`
+6
View File
@@ -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
Executable
+5
View File
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
cmake --preset Clang
cmake --build --preset Clang -j
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

+11
View File
@@ -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)
+6
View File
@@ -0,0 +1,6 @@
{
"version": 8,
"include": [
"../CMakePresets.json"
]
}
+6
View File
@@ -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
+5
View File
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
cmake --preset Clang
cmake --build --preset Clang -j
+2
View File
@@ -0,0 +1,2 @@
add_executable(helloworld main.cpp)
target_link_libraries(helloworld PRIVATE ConsoleLib)
+14
View File
@@ -0,0 +1,14 @@
#include <ConsoleLib.h>
#include <atomic>
#include <string>
using namespace ConsoleLib;
signed main() {
initConsole();
clearConsole();
printToConsole("HelloWorld!\r\n");
printToConsole("Red", 0xff0000);
printToConsole("Green", 0x00ff00);
printToConsole("Blue", 0x0000ff);
quit(0);
}
+2
View File
@@ -0,0 +1,2 @@
add_executable(keyinput main.cpp)
target_link_libraries(keyinput PRIVATE ConsoleLib)
+63
View File
@@ -0,0 +1,63 @@
#include <ConsoleLib.h>
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<int>(ASCII_TABLE_SIZE); ++c) {
if (keys[static_cast<Keycode>(c)]) {
char text[2] = {static_cast<char>(c), '\0'};
printToConsole(text);
}
}
if (keys[KEY_q] || keys[KEY_ESCAPE]) {
break;
}
sleep_ms(16);
}
quit(0);
}
+101
View File
@@ -0,0 +1,101 @@
#ifndef CONSOLELIB_H
#define CONSOLELIB_H
#include <array>
#include <cstddef>
#include <cstdint>
#include <string_view>
// 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<size_t>(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<bool, KEYCODE_COUNT>& 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