First commit
This commit is contained in:
+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
|
||||
Reference in New Issue
Block a user