First commit
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
Language: Cpp
|
||||
BasedOnStyle: Google
|
||||
IndentWidth: 4
|
||||
ColumnLimit: 100
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AlignTrailingComments: true
|
||||
@@ -0,0 +1,3 @@
|
||||
build
|
||||
.vscode
|
||||
example/build
|
||||
@@ -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()
|
||||
@@ -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
@@ -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
|
||||
@@ -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()`。
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
cmake --preset Clang
|
||||
cmake --build --preset Clang -j
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.6 KiB |
@@ -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)
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"version": 8,
|
||||
"include": [
|
||||
"../CMakePresets.json"
|
||||
]
|
||||
}
|
||||
@@ -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
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
cmake --preset Clang
|
||||
cmake --build --preset Clang -j
|
||||
@@ -0,0 +1,2 @@
|
||||
add_executable(helloworld main.cpp)
|
||||
target_link_libraries(helloworld PRIVATE ConsoleLib)
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
add_executable(keyinput main.cpp)
|
||||
target_link_libraries(keyinput PRIVATE ConsoleLib)
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user