123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534 |
- /*
- * 文件名: stdio_.c
- * 目标: 用于终端输出的控制 (stdout, stdin, stderr)
- * 因为不同平台上终端采用的编码不同
- */
- #include <cstdio>
- #include <cstring>
- #include <cstdarg>
- #include <csignal>
- #include "tool.hpp"
- using namespace aFuntool;
- /* 注意:
- * checkStdin在Windows和Linux之前行为具有差别, 本质目标时检查缓冲区是否有内容
- * Linux使用无阻塞读取直接检查, 检查结果确实为缓冲区是否有内容
- * Windows则是通过记录每次读取时是否已经读取`\n`来判断缓冲区是否有内容, 并且配合khbit来判断是否有内容输入
- * 实际上, khbit只能代表有内容输入而无法确定内容是否已经输入到缓冲区中
- */
- #ifdef aFunWIN32_NO_CYGWIN
- // 获取CodePage, 并将内存中utf-8字符串转换为对应编码输出
- // cygwin环境下, 终端默认为uft-8
- const int BUFF_SIZE = 40960;
- static char buffer[BUFF_SIZE + 1] = "";
- static size_t index = 0;
- static size_t next = 0;
- static size_t end = 0;
- volatile sig_atomic_t ctrl_c = 0;
- static pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER; // 只有 export 的函数统一处理该互斥锁
- static int setCursorPosition(HANDLE std_o, CONSOLE_SCREEN_BUFFER_INFO *info_, SHORT x_) {
- CONSOLE_SCREEN_BUFFER_INFO info;
- if (info_ == nullptr) {
- if (!GetConsoleScreenBufferInfo(std_o, &info))
- return -1;
- info_ = &info;
- }
- int x = info_->dwCursorPosition.X;
- int y = info_->dwCursorPosition.Y;
- x += x_;
- while (x >= info_->dwSize.X) {
- x -= info_->dwSize.X;
- y++;
- }
- while (x < 0) {
- x += info_->dwSize.X;
- y--;
- }
- if (y < 0)
- y = 0;
- else if (y > info_->dwSize.Y)
- y = info_->dwSize.Y;
- COORD coord = {.X=(SHORT)x, .Y=(SHORT)y};
- SetConsoleCursorPosition(std_o, coord);
- return 1;
- }
- static int nextToEnd(HANDLE std_o) {
- CONSOLE_SCREEN_BUFFER_INFO info;
- if (!GetConsoleScreenBufferInfo(std_o, &info))
- return 0;
- if (setCursorPosition(std_o, &info, (SHORT)(end - next)) == -1)
- return 0;
- next = end;
- return 1;
- }
- static int moveBuffer() {
- if (index == 0)
- return 0;
- memmove(buffer, buffer + index, BUFF_SIZE - index);
- end = end - index;
- next = next - index;
- index = 0;
- memset(buffer + end, 0, BUFF_SIZE - end);
- return 1;
- }
- static int backChar(HANDLE std_o) {
- if (index != next) { // 删除一个字符
- if (setCursorPosition(std_o, nullptr, -1) == -1) // 先一定位置在-1
- return 0;
- CONSOLE_SCREEN_BUFFER_INFO info;
- if (!GetConsoleScreenBufferInfo(std_o, &info))
- return 0;
- memmove(buffer + next - 1, buffer + next, end - next + 1);
- SetConsoleCursorPosition(std_o, info.dwCursorPosition);
- for (size_t n = next - 1; n < end; n++)
- fputc(' ', stdout);
- SetConsoleCursorPosition(std_o, info.dwCursorPosition);
- fputs(buffer + next - 1, stdout);
- SetConsoleCursorPosition(std_o, info.dwCursorPosition);
- next--;
- end--;
- }
- return 1;
- }
- static int enterChar(HANDLE std_o) {
- if(!nextToEnd(std_o))
- return 0;
- buffer[end] = '\n';
- end++;
- next++;
- fputc('\n', stdout);
- return 1;
- }
- /*
- * 函数名: newChar
- * 目标: 记录字符并显示
- * 返回1表示成功
- * 返回0表示失败
- */
- static int newChar(HANDLE std_i, char ch) {
- if (ch == 0)
- return 1;
- if (end == BUFF_SIZE && !moveBuffer()) // 对比 end 而不是 next
- return 0;
- if (next != end) { // insert 模式
- CONSOLE_SCREEN_BUFFER_INFO info;
- if (!GetConsoleScreenBufferInfo(std_i, &info))
- return 0;
- memmove(buffer + next + 1, buffer + next, end - next);
- buffer[next] = ch;
- fputs(buffer + next, stdout);
- if (setCursorPosition(std_i, &info, 1) == -1)
- return 0;
- } else {
- buffer[next] = ch;
- fputc(ch, stdout);
- }
- next++;
- end++;
- return 1;
- }
- /*
- * 函数名: checkNewInput
- * 目标: 获取输入并且显示
- * 返回-1表示遭遇错误
- * 返回0表示未完成行读取
- * 返回1表示完成行读取
- */
- static int checkNewInput(HANDLE std_i, HANDLE std_o) {
- DWORD len = 0;
- DWORD oldm;
- if (!GetConsoleMode(std_i, &oldm))
- return -1;
- if (!GetNumberOfConsoleInputEvents(std_i, &len))
- return -1;
- for (int i = 0; i < len; i++) {
- INPUT_RECORD record;
- DWORD read_len;
- if (!ReadConsoleInputA(std_i, &record, 1, &read_len) || read_len == 0)
- return -1;
- if (record.EventType == KEY_EVENT) {
- if (!record.Event.KeyEvent.bKeyDown)
- continue;
- else if (record.Event.KeyEvent.uChar.AsciiChar == 3) {
- ctrl_c = 1;
- continue;
- } else if (record.Event.KeyEvent.wVirtualKeyCode == VK_BACK) { // 退格
- if (backChar(std_o) == 0)
- return -1;
- continue;
- } if (record.Event.KeyEvent.wVirtualKeyCode == VK_RETURN) { // 回车
- if (enterChar(std_o) == 0)
- return -1;
- return 1;
- } else if (record.Event.KeyEvent.wVirtualKeyCode == VK_LEFT) { // 左
- CONSOLE_SCREEN_BUFFER_INFO info;
- if (!GetConsoleScreenBufferInfo(std_o, &info))
- return -1;
- if (next > index) {
- next--;
- if (setCursorPosition(std_o, nullptr, -1) == -1)
- return 0;
- }
- return 0;
- } else if (record.Event.KeyEvent.wVirtualKeyCode == VK_RIGHT) { // 右
- if (next < end) {
- next++;
- if(setCursorPosition(std_o, nullptr, 1) == -1)
- return 0;
- }
- return 0;
- }
- for (int r = record.Event.KeyEvent.wRepeatCount; r > 0; r--) {
- if (newChar(std_o, record.Event.KeyEvent.uChar.AsciiChar) == 0)
- return -1;
- }
- }
- }
- return 0;
- }
- static int fcheck_stdin(HANDLE std_i, HANDLE std_o) {
- if (end == index || end == 0 || buffer[end - 1] != '\n')
- return checkNewInput(std_i, std_o);
- return 1;
- }
- int aFuntool::fgetc_stdin() {
- if (!_isatty(_fileno(stdin)))
- return fgetc(stdin);
- HANDLE std_i = GetStdHandle(STD_INPUT_HANDLE);
- HANDLE std_o = GetStdHandle(STD_OUTPUT_HANDLE);
- if (std_i == INVALID_HANDLE_VALUE || std_o == INVALID_HANDLE_VALUE)
- return EOF;
- int re = EOF;
- pthread_mutex_lock(&buffer_mutex);
- for (int fs = 0; fs != 1 ; fs = fcheck_stdin(std_i, std_o)) { // 阻塞
- if (fs == -1)
- goto RETURN; // 返回EOF
- }
- re = (unsigned char)buffer[index];
- index++;
- RETURN:
- pthread_mutex_unlock(&buffer_mutex);
- return re;
- }
- char *aFuntool::fgets_stdin_(char *buf, size_t len) {
- if (!_isatty(_fileno(stdin)))
- return fgets(buf, (int)len, stdin);
- HANDLE std_i = GetStdHandle(STD_INPUT_HANDLE);
- HANDLE std_o = GetStdHandle(STD_OUTPUT_HANDLE);
- if (std_i == INVALID_HANDLE_VALUE || std_o == INVALID_HANDLE_VALUE)
- return nullptr;
- pthread_mutex_lock(&buffer_mutex);
- for (int fs = 0; fs != 1 ; fs = fcheck_stdin(std_i, std_o)) { // 阻塞
- if (fs == -1) {
- buf = nullptr;
- goto RETURN; // 返回nullptr
- }
- }
- {
- size_t len_ = len - 1;
- if (end - index < len_)
- len_ = end - index;
- memcpy(buf, buffer + index, len_);
- index += len_;
- nextToEnd(std_o);
- buf[len_] = '\0'; // 最后一位
- }
- RETURN:
- pthread_mutex_unlock(&buffer_mutex);
- return buf;
- }
- bool aFuntool::fclear_stdin() {
- if (!_isatty(_fileno(stdin))) {
- rewind(stdin); // 仅 winAPI 可用
- return true;
- }
- HANDLE std_o = GetStdHandle(STD_OUTPUT_HANDLE);
- if (std_o == INVALID_HANDLE_VALUE)
- return true;
- pthread_mutex_lock(&buffer_mutex);
- nextToEnd(std_o);
- index = 0;
- end = 0;
- next = 0;
- memset(buffer, 0, BUFF_SIZE);
- pthread_mutex_unlock(&buffer_mutex);
- return false;
- }
- /**
- * 接管ctrl+c信号初始化
- * @param signal 初始化/还原
- */
- void aFuntool::stdio_signal_init(bool signal) {
- HANDLE std_i = GetStdHandle(STD_INPUT_HANDLE);
- DWORD mode;
- GetConsoleMode(std_i, &mode);
- if (signal)
- mode &= ~ENABLE_PROCESSED_INPUT;
- else
- mode |= ENABLE_PROCESSED_INPUT; // 系统接管 ^c
- SetConsoleMode(std_i, mode);
- }
- /**
- * 检查是否有ctrl+c信号
- */
- bool aFuntool::stdio_check_signal() {
- HANDLE std_i = GetStdHandle(STD_INPUT_HANDLE);
- HANDLE std_o = GetStdHandle(STD_OUTPUT_HANDLE);
- if (std_i == INVALID_HANDLE_VALUE || std_o == INVALID_HANDLE_VALUE) {
- return false;
- }
- pthread_mutex_lock(&buffer_mutex);
- fcheck_stdin(std_i, std_o);
- bool res = ctrl_c == 1;
- ctrl_c = 0;
- pthread_mutex_unlock(&buffer_mutex);
- return res;
- }
- int aFuntool::convertMultiByte(char **dest, const char *str, UINT from, UINT to) {
- if (str == nullptr || dest == nullptr)
- return 0;
- int tmp_len = MultiByteToWideChar(from, 0, str, -1, nullptr, 0);
- if (tmp_len == 0)
- return 0;
- auto tmp = calloc(tmp_len + 1, wchar_t);
- if (MultiByteToWideChar(from, 0, str, -1, tmp, tmp_len) == 0)
- return 0;
- int dest_len = WideCharToMultiByte(to, 0, tmp, -1, nullptr, 0, nullptr, nullptr);
- if (dest_len == 0)
- return 0;
- *dest = calloc(dest_len + 1, char);
- int re = WideCharToMultiByte(to, 0, tmp, -1, *dest, dest_len, nullptr, nullptr);
- free(tmp);
- return re;
- }
- int aFuntool::convertWideByte(wchar_t **dest, const char *str, UINT from) {
- if (str == nullptr || dest == nullptr)
- return 0;
- int tmp_len = MultiByteToWideChar(from, 0, str, -1, nullptr, 0);
- if (tmp_len == 0)
- return 0;
- *dest = calloc(tmp_len + 1, wchar_t);
- return MultiByteToWideChar(from, 0, str, -1, *dest, tmp_len);
- }
- int aFuntool::convertFromWideByte(char **dest, const wchar_t *str, UINT to) {
- if (str == nullptr || dest == nullptr)
- return 0;
- int dest_len = WideCharToMultiByte(to, 0, str, -1, nullptr, 0, nullptr, nullptr);
- if (dest_len == 0)
- return 0;
- *dest = calloc(dest_len + 1, char);
- return WideCharToMultiByte(to, 0, str, -1, *dest, dest_len, nullptr, nullptr);
- }
- int aFuntool::fgets_stdin(char **dest, int len) {
- int re = 0;
- if (!_isatty(_fileno(stdin))) {
- *dest = calloc(len + 1, char);
- re = fgets(*dest, len, stdin) != nullptr;
- if (!re)
- free(*dest);
- return re;
- }
- char *wstr = calloc(len, char);
- UINT code_page = GetConsoleCP();
- if (fgets_stdin_(wstr, len) != nullptr) // 已经有互斥锁
- re = convertMultiByte(dest, wstr, code_page, CP_UTF8);
- return re;
- }
- int aFuntool::fungetc_stdin(int ch) {
- if (!_isatty(_fileno(stdin)))
- return ungetc(ch, stdin);
- pthread_mutex_lock(&buffer_mutex);
- if (ch == 0 || index == 0 && end == BUFF_SIZE) {
- pthread_mutex_unlock(&buffer_mutex);
- return 0;
- }
- if (index != 0) {
- index--;
- buffer[index] = (char)ch;
- } else if (end != BUFF_SIZE) { // index == 0;
- memmove(buffer, buffer + 1, end); // 往回移动
- end++;
- next++;
- buffer[0] = (char) ch;
- }
- pthread_mutex_unlock(&buffer_mutex);
- return 1;
- }
- /*
- * 函数名: checkStdin
- * 目标: 检查stdin缓冲区是否有内容
- * 有内容则返回true
- * 无内容则返回false
- */
- bool aFuntool::checkStdin() {
- HANDLE std_i = GetStdHandle(STD_INPUT_HANDLE);
- HANDLE std_o = GetStdHandle(STD_OUTPUT_HANDLE);
- pthread_mutex_lock(&buffer_mutex);
- int fs = fcheck_stdin(std_i, std_o);
- pthread_mutex_unlock(&buffer_mutex);
- if (fs == 0)
- return false;
- return true;
- }
- int aFuntool::fputs_std_(const char *str, FILE *std) {
- if (std == nullptr)
- return 0;
- if (!_isatty(_fileno(std)))
- return fputs(str, std);
- UINT code_page = GetConsoleCP();
- char *wstr = nullptr;
- int re = EOF;
- if (convertMultiByte(&wstr, str, CP_UTF8, code_page) == 0 || wstr != nullptr) {
- re = fputs(wstr, std);
- free(wstr);
- }
- return re;
- }
- size_t aFuntool::vprintf_std_(FILE *std, size_t buf_len, const char *format, va_list ap) {
- if (std == nullptr)
- return 0;
- if (!_isatty(_fileno(std)))
- return vfprintf(std, format, ap);
- if (buf_len == 0)
- buf_len = 1024;
- buf_len += 10; // 预留更多位置
- char *buf = calloc(buf_len, char);
- size_t re = vsnprintf(buf, buf_len, format, ap);
- if (fputs_std_(buf, std) == EOF)
- re = 0;
- free(buf);
- return re;
- }
- #else
- #include <unistd.h>
- #include <fcntl.h>
- namespace aFuntool {
- static pthread_mutex_t fcntl_mutex = PTHREAD_MUTEX_INITIALIZER; // 只有 export 的函数统一处理该互斥锁
- }
- // 用于Linux平台的IO函数
- // 默认Linux平台均使用utf-8
- int aFuntool::fgets_stdin(char **dest, int len) {
- *dest = calloc(len, char);
- if (fgets(*dest, len, stdin) == nullptr)
- return 0;
- return 1;
- }
- /*
- * 函数名: checkStdin
- * 目标: 检查stdin缓冲区是否有内容
- * 有内容则返回true
- * 无内容则返回false
- *
- * 参考自: https://gist.github.com/SuperH-0630/a4190b89d21c349a8d6882ca71453ae6
- */
- bool aFuntool::checkStdin(void) {
- if (!isatty(fileno(stdin)))
- return true;
- bool re = false;
- pthread_mutex_lock(&fcntl_mutex);
- int oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
- fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
- int ch = fgetc(stdin);
- CLEAR_FERROR(stdin);
- if (ch != EOF) {
- ungetc(ch, stdin);
- re = true;
- }
- fcntl(STDIN_FILENO, F_SETFL, oldf);
- pthread_mutex_unlock(&fcntl_mutex);
- return re;
- }
- bool aFuntool::fclear_stdin(void) {
- if (!isatty(fileno(stdin)))
- return true;
- pthread_mutex_lock(&fcntl_mutex);
- int oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
- fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
- int ch;
- do {
- ch = fgetc(stdin);
- CLEAR_FERROR(stdin);
- } while (ch != EOF);
- fcntl(STDIN_FILENO, F_SETFL, oldf);
- pthread_mutex_unlock(&fcntl_mutex);
- return !ferror(stdin) && !feof(stdin);
- }
- #endif
|