Files
ConsoleLib/ConsoleLib.cpp
T

475 lines
12 KiB
C++

#include "include/ConsoleLib.hpp"
#include <atomic>
#include <cerrno>
#include <chrono>
#include <cstdlib>
#include <mutex>
#include <string>
#include <thread>
#ifdef _WIN32
#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 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 auto 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 auto g_hStdin = INVALID_HANDLE_VALUE;
static auto g_hStdout = INVALID_HANDLE_VALUE;
static DWORD g_dwOriginalInMode = 0;
static DWORD g_dwOriginalOutMode = 0;
static bool g_bInitialized = false;
void init() {
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() {
const HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (hOut == INVALID_HANDLE_VALUE) {
return;
}
CONSOLE_SCREEN_BUFFER_INFO csbi;
DWORD count;
const 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;
}
const DWORD bytesToWrite = 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];
}
if (const Keycode key = parseEscapeSequence(buffer, charsRead); 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() {
if (bool expected = false; !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 init() {
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