stdio_.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. /*
  2. * 文件名: stdio_.c
  3. * 目标: 用于终端输出的控制 (stdout, stdin, stderr)
  4. * 因为不同平台上终端采用的编码不同
  5. */
  6. #include <cstdio>
  7. #include <cstring>
  8. #include <cstdarg>
  9. #include <csignal>
  10. #include "tool.hpp"
  11. using namespace aFuntool;
  12. /* 注意:
  13. * checkStdin在Windows和Linux之前行为具有差别, 本质目标时检查缓冲区是否有内容
  14. * Linux使用无阻塞读取直接检查, 检查结果确实为缓冲区是否有内容
  15. * Windows则是通过记录每次读取时是否已经读取`\n`来判断缓冲区是否有内容, 并且配合khbit来判断是否有内容输入
  16. * 实际上, khbit只能代表有内容输入而无法确定内容是否已经输入到缓冲区中
  17. */
  18. #ifdef aFunWIN32_NO_CYGWIN
  19. // 获取CodePage, 并将内存中utf-8字符串转换为对应编码输出
  20. // cygwin环境下, 终端默认为uft-8
  21. const int BUFF_SIZE = 40960;
  22. static char buffer[BUFF_SIZE + 1] = "";
  23. static size_t index = 0;
  24. static size_t next = 0;
  25. static size_t end = 0;
  26. volatile sig_atomic_t ctrl_c = 0;
  27. static pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER; // 只有 export 的函数统一处理该互斥锁
  28. static int setCursorPosition(HANDLE std_o, CONSOLE_SCREEN_BUFFER_INFO *info_, SHORT x_) {
  29. CONSOLE_SCREEN_BUFFER_INFO info;
  30. if (info_ == nullptr) {
  31. if (!GetConsoleScreenBufferInfo(std_o, &info))
  32. return -1;
  33. info_ = &info;
  34. }
  35. int x = info_->dwCursorPosition.X;
  36. int y = info_->dwCursorPosition.Y;
  37. x += x_;
  38. while (x >= info_->dwSize.X) {
  39. x -= info_->dwSize.X;
  40. y++;
  41. }
  42. while (x < 0) {
  43. x += info_->dwSize.X;
  44. y--;
  45. }
  46. if (y < 0)
  47. y = 0;
  48. else if (y > info_->dwSize.Y)
  49. y = info_->dwSize.Y;
  50. COORD coord = {.X=(SHORT)x, .Y=(SHORT)y};
  51. SetConsoleCursorPosition(std_o, coord);
  52. return 1;
  53. }
  54. static int nextToEnd(HANDLE std_o) {
  55. CONSOLE_SCREEN_BUFFER_INFO info;
  56. if (!GetConsoleScreenBufferInfo(std_o, &info))
  57. return 0;
  58. if (setCursorPosition(std_o, &info, (SHORT)(end - next)) == -1)
  59. return 0;
  60. next = end;
  61. return 1;
  62. }
  63. static int moveBuffer() {
  64. if (index == 0)
  65. return 0;
  66. memmove(buffer, buffer + index, BUFF_SIZE - index);
  67. end = end - index;
  68. next = next - index;
  69. index = 0;
  70. memset(buffer + end, 0, BUFF_SIZE - end);
  71. return 1;
  72. }
  73. static int backChar(HANDLE std_o) {
  74. if (index != next) { // 删除一个字符
  75. if (setCursorPosition(std_o, nullptr, -1) == -1) // 先一定位置在-1
  76. return 0;
  77. CONSOLE_SCREEN_BUFFER_INFO info;
  78. if (!GetConsoleScreenBufferInfo(std_o, &info))
  79. return 0;
  80. memmove(buffer + next - 1, buffer + next, end - next + 1);
  81. SetConsoleCursorPosition(std_o, info.dwCursorPosition);
  82. for (size_t n = next - 1; n < end; n++)
  83. fputc(' ', stdout);
  84. SetConsoleCursorPosition(std_o, info.dwCursorPosition);
  85. fputs(buffer + next - 1, stdout);
  86. SetConsoleCursorPosition(std_o, info.dwCursorPosition);
  87. next--;
  88. end--;
  89. }
  90. return 1;
  91. }
  92. static int enterChar(HANDLE std_o) {
  93. if(!nextToEnd(std_o))
  94. return 0;
  95. buffer[end] = '\n';
  96. end++;
  97. next++;
  98. fputc('\n', stdout);
  99. return 1;
  100. }
  101. /*
  102. * 函数名: newChar
  103. * 目标: 记录字符并显示
  104. * 返回1表示成功
  105. * 返回0表示失败
  106. */
  107. static int newChar(HANDLE std_i, char ch) {
  108. if (ch == 0)
  109. return 1;
  110. if (end == BUFF_SIZE && !moveBuffer()) // 对比 end 而不是 next
  111. return 0;
  112. if (next != end) { // insert 模式
  113. CONSOLE_SCREEN_BUFFER_INFO info;
  114. if (!GetConsoleScreenBufferInfo(std_i, &info))
  115. return 0;
  116. memmove(buffer + next + 1, buffer + next, end - next);
  117. buffer[next] = ch;
  118. fputs(buffer + next, stdout);
  119. if (setCursorPosition(std_i, &info, 1) == -1)
  120. return 0;
  121. } else {
  122. buffer[next] = ch;
  123. fputc(ch, stdout);
  124. }
  125. next++;
  126. end++;
  127. return 1;
  128. }
  129. /*
  130. * 函数名: checkNewInput
  131. * 目标: 获取输入并且显示
  132. * 返回-1表示遭遇错误
  133. * 返回0表示未完成行读取
  134. * 返回1表示完成行读取
  135. */
  136. static int checkNewInput(HANDLE std_i, HANDLE std_o) {
  137. DWORD len = 0;
  138. DWORD oldm;
  139. if (!GetConsoleMode(std_i, &oldm))
  140. return -1;
  141. if (!GetNumberOfConsoleInputEvents(std_i, &len))
  142. return -1;
  143. for (int i = 0; i < len; i++) {
  144. INPUT_RECORD record;
  145. DWORD read_len;
  146. if (!ReadConsoleInputA(std_i, &record, 1, &read_len) || read_len == 0)
  147. return -1;
  148. if (record.EventType == KEY_EVENT) {
  149. if (!record.Event.KeyEvent.bKeyDown)
  150. continue;
  151. else if (record.Event.KeyEvent.uChar.AsciiChar == 3) {
  152. ctrl_c = 1;
  153. continue;
  154. } else if (record.Event.KeyEvent.wVirtualKeyCode == VK_BACK) { // 退格
  155. if (backChar(std_o) == 0)
  156. return -1;
  157. continue;
  158. } if (record.Event.KeyEvent.wVirtualKeyCode == VK_RETURN) { // 回车
  159. if (enterChar(std_o) == 0)
  160. return -1;
  161. return 1;
  162. } else if (record.Event.KeyEvent.wVirtualKeyCode == VK_LEFT) { // 左
  163. CONSOLE_SCREEN_BUFFER_INFO info;
  164. if (!GetConsoleScreenBufferInfo(std_o, &info))
  165. return -1;
  166. if (next > index) {
  167. next--;
  168. if (setCursorPosition(std_o, nullptr, -1) == -1)
  169. return 0;
  170. }
  171. return 0;
  172. } else if (record.Event.KeyEvent.wVirtualKeyCode == VK_RIGHT) { // 右
  173. if (next < end) {
  174. next++;
  175. if(setCursorPosition(std_o, nullptr, 1) == -1)
  176. return 0;
  177. }
  178. return 0;
  179. }
  180. for (int r = record.Event.KeyEvent.wRepeatCount; r > 0; r--) {
  181. if (newChar(std_o, record.Event.KeyEvent.uChar.AsciiChar) == 0)
  182. return -1;
  183. }
  184. }
  185. }
  186. return 0;
  187. }
  188. static int fcheck_stdin(HANDLE std_i, HANDLE std_o) {
  189. if (end == index || end == 0 || buffer[end - 1] != '\n')
  190. return checkNewInput(std_i, std_o);
  191. return 1;
  192. }
  193. int aFuntool::fgetc_stdin() {
  194. if (!_isatty(_fileno(stdin)))
  195. return fgetc(stdin);
  196. HANDLE std_i = GetStdHandle(STD_INPUT_HANDLE);
  197. HANDLE std_o = GetStdHandle(STD_OUTPUT_HANDLE);
  198. if (std_i == INVALID_HANDLE_VALUE || std_o == INVALID_HANDLE_VALUE)
  199. return EOF;
  200. int re = EOF;
  201. pthread_mutex_lock(&buffer_mutex);
  202. for (int fs = 0; fs != 1 ; fs = fcheck_stdin(std_i, std_o)) { // 阻塞
  203. if (fs == -1)
  204. goto RETURN; // 返回EOF
  205. }
  206. re = (unsigned char)buffer[index];
  207. index++;
  208. RETURN:
  209. pthread_mutex_unlock(&buffer_mutex);
  210. return re;
  211. }
  212. char *aFuntool::fgets_stdin_(char *buf, size_t len) {
  213. if (!_isatty(_fileno(stdin)))
  214. return fgets(buf, (int)len, stdin);
  215. HANDLE std_i = GetStdHandle(STD_INPUT_HANDLE);
  216. HANDLE std_o = GetStdHandle(STD_OUTPUT_HANDLE);
  217. if (std_i == INVALID_HANDLE_VALUE || std_o == INVALID_HANDLE_VALUE)
  218. return nullptr;
  219. pthread_mutex_lock(&buffer_mutex);
  220. for (int fs = 0; fs != 1 ; fs = fcheck_stdin(std_i, std_o)) { // 阻塞
  221. if (fs == -1) {
  222. buf = nullptr;
  223. goto RETURN; // 返回nullptr
  224. }
  225. }
  226. {
  227. size_t len_ = len - 1;
  228. if (end - index < len_)
  229. len_ = end - index;
  230. memcpy(buf, buffer + index, len_);
  231. index += len_;
  232. nextToEnd(std_o);
  233. buf[len_] = '\0'; // 最后一位
  234. }
  235. RETURN:
  236. pthread_mutex_unlock(&buffer_mutex);
  237. return buf;
  238. }
  239. bool aFuntool::fclear_stdin() {
  240. if (!_isatty(_fileno(stdin))) {
  241. rewind(stdin); // 仅 winAPI 可用
  242. return true;
  243. }
  244. HANDLE std_o = GetStdHandle(STD_OUTPUT_HANDLE);
  245. if (std_o == INVALID_HANDLE_VALUE)
  246. return true;
  247. pthread_mutex_lock(&buffer_mutex);
  248. nextToEnd(std_o);
  249. index = 0;
  250. end = 0;
  251. next = 0;
  252. memset(buffer, 0, BUFF_SIZE);
  253. pthread_mutex_unlock(&buffer_mutex);
  254. return false;
  255. }
  256. /**
  257. * 接管ctrl+c信号初始化
  258. * @param signal 初始化/还原
  259. */
  260. void aFuntool::stdio_signal_init(bool signal) {
  261. HANDLE std_i = GetStdHandle(STD_INPUT_HANDLE);
  262. DWORD mode;
  263. GetConsoleMode(std_i, &mode);
  264. if (signal)
  265. mode &= ~ENABLE_PROCESSED_INPUT;
  266. else
  267. mode |= ENABLE_PROCESSED_INPUT; // 系统接管 ^c
  268. SetConsoleMode(std_i, mode);
  269. }
  270. /**
  271. * 检查是否有ctrl+c信号
  272. */
  273. bool aFuntool::stdio_check_signal() {
  274. HANDLE std_i = GetStdHandle(STD_INPUT_HANDLE);
  275. HANDLE std_o = GetStdHandle(STD_OUTPUT_HANDLE);
  276. if (std_i == INVALID_HANDLE_VALUE || std_o == INVALID_HANDLE_VALUE) {
  277. return false;
  278. }
  279. pthread_mutex_lock(&buffer_mutex);
  280. fcheck_stdin(std_i, std_o);
  281. bool res = ctrl_c == 1;
  282. ctrl_c = 0;
  283. pthread_mutex_unlock(&buffer_mutex);
  284. return res;
  285. }
  286. int aFuntool::convertMultiByte(char **dest, const char *str, UINT from, UINT to) {
  287. if (str == nullptr || dest == nullptr)
  288. return 0;
  289. int tmp_len = MultiByteToWideChar(from, 0, str, -1, nullptr, 0);
  290. if (tmp_len == 0)
  291. return 0;
  292. auto tmp = calloc(tmp_len + 1, wchar_t);
  293. if (MultiByteToWideChar(from, 0, str, -1, tmp, tmp_len) == 0)
  294. return 0;
  295. int dest_len = WideCharToMultiByte(to, 0, tmp, -1, nullptr, 0, nullptr, nullptr);
  296. if (dest_len == 0)
  297. return 0;
  298. *dest = calloc(dest_len + 1, char);
  299. int re = WideCharToMultiByte(to, 0, tmp, -1, *dest, dest_len, nullptr, nullptr);
  300. free(tmp);
  301. return re;
  302. }
  303. int aFuntool::convertWideByte(wchar_t **dest, const char *str, UINT from) {
  304. if (str == nullptr || dest == nullptr)
  305. return 0;
  306. int tmp_len = MultiByteToWideChar(from, 0, str, -1, nullptr, 0);
  307. if (tmp_len == 0)
  308. return 0;
  309. *dest = calloc(tmp_len + 1, wchar_t);
  310. return MultiByteToWideChar(from, 0, str, -1, *dest, tmp_len);
  311. }
  312. int aFuntool::convertFromWideByte(char **dest, const wchar_t *str, UINT to) {
  313. if (str == nullptr || dest == nullptr)
  314. return 0;
  315. int dest_len = WideCharToMultiByte(to, 0, str, -1, nullptr, 0, nullptr, nullptr);
  316. if (dest_len == 0)
  317. return 0;
  318. *dest = calloc(dest_len + 1, char);
  319. return WideCharToMultiByte(to, 0, str, -1, *dest, dest_len, nullptr, nullptr);
  320. }
  321. int aFuntool::fgets_stdin(char **dest, int len) {
  322. int re = 0;
  323. if (!_isatty(_fileno(stdin))) {
  324. *dest = calloc(len + 1, char);
  325. re = fgets(*dest, len, stdin) != nullptr;
  326. if (!re)
  327. free(*dest);
  328. return re;
  329. }
  330. char *wstr = calloc(len, char);
  331. UINT code_page = GetConsoleCP();
  332. if (fgets_stdin_(wstr, len) != nullptr) // 已经有互斥锁
  333. re = convertMultiByte(dest, wstr, code_page, CP_UTF8);
  334. return re;
  335. }
  336. int aFuntool::fungetc_stdin(int ch) {
  337. if (!_isatty(_fileno(stdin)))
  338. return ungetc(ch, stdin);
  339. pthread_mutex_lock(&buffer_mutex);
  340. if (ch == 0 || index == 0 && end == BUFF_SIZE) {
  341. pthread_mutex_unlock(&buffer_mutex);
  342. return 0;
  343. }
  344. if (index != 0) {
  345. index--;
  346. buffer[index] = (char)ch;
  347. } else if (end != BUFF_SIZE) { // index == 0;
  348. memmove(buffer, buffer + 1, end); // 往回移动
  349. end++;
  350. next++;
  351. buffer[0] = (char) ch;
  352. }
  353. pthread_mutex_unlock(&buffer_mutex);
  354. return 1;
  355. }
  356. /*
  357. * 函数名: checkStdin
  358. * 目标: 检查stdin缓冲区是否有内容
  359. * 有内容则返回true
  360. * 无内容则返回false
  361. */
  362. bool aFuntool::checkStdin() {
  363. HANDLE std_i = GetStdHandle(STD_INPUT_HANDLE);
  364. HANDLE std_o = GetStdHandle(STD_OUTPUT_HANDLE);
  365. pthread_mutex_lock(&buffer_mutex);
  366. int fs = fcheck_stdin(std_i, std_o);
  367. pthread_mutex_unlock(&buffer_mutex);
  368. if (fs == 0)
  369. return false;
  370. return true;
  371. }
  372. int aFuntool::fputs_std_(const char *str, FILE *std) {
  373. if (std == nullptr)
  374. return 0;
  375. if (!_isatty(_fileno(std)))
  376. return fputs(str, std);
  377. UINT code_page = GetConsoleCP();
  378. char *wstr = nullptr;
  379. int re = EOF;
  380. if (convertMultiByte(&wstr, str, CP_UTF8, code_page) == 0 || wstr != nullptr) {
  381. re = fputs(wstr, std);
  382. free(wstr);
  383. }
  384. return re;
  385. }
  386. size_t aFuntool::vprintf_std_(FILE *std, size_t buf_len, const char *format, va_list ap) {
  387. if (std == nullptr)
  388. return 0;
  389. if (!_isatty(_fileno(std)))
  390. return vfprintf(std, format, ap);
  391. if (buf_len == 0)
  392. buf_len = 1024;
  393. buf_len += 10; // 预留更多位置
  394. char *buf = calloc(buf_len, char);
  395. size_t re = vsnprintf(buf, buf_len, format, ap);
  396. if (fputs_std_(buf, std) == EOF)
  397. re = 0;
  398. free(buf);
  399. return re;
  400. }
  401. #else
  402. #include <unistd.h>
  403. #include <fcntl.h>
  404. namespace aFuntool {
  405. static pthread_mutex_t fcntl_mutex = PTHREAD_MUTEX_INITIALIZER; // 只有 export 的函数统一处理该互斥锁
  406. }
  407. // 用于Linux平台的IO函数
  408. // 默认Linux平台均使用utf-8
  409. int aFuntool::fgets_stdin(char **dest, int len) {
  410. *dest = calloc(len, char);
  411. if (fgets(*dest, len, stdin) == nullptr)
  412. return 0;
  413. return 1;
  414. }
  415. /*
  416. * 函数名: checkStdin
  417. * 目标: 检查stdin缓冲区是否有内容
  418. * 有内容则返回true
  419. * 无内容则返回false
  420. *
  421. * 参考自: https://gist.github.com/SuperH-0630/a4190b89d21c349a8d6882ca71453ae6
  422. */
  423. bool aFuntool::checkStdin(void) {
  424. if (!isatty(fileno(stdin)))
  425. return true;
  426. bool re = false;
  427. pthread_mutex_lock(&fcntl_mutex);
  428. int oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
  429. fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
  430. int ch = fgetc(stdin);
  431. CLEAR_FERROR(stdin);
  432. if (ch != EOF) {
  433. ungetc(ch, stdin);
  434. re = true;
  435. }
  436. fcntl(STDIN_FILENO, F_SETFL, oldf);
  437. pthread_mutex_unlock(&fcntl_mutex);
  438. return re;
  439. }
  440. bool aFuntool::fclear_stdin(void) {
  441. if (!isatty(fileno(stdin)))
  442. return true;
  443. pthread_mutex_lock(&fcntl_mutex);
  444. int oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
  445. fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
  446. int ch;
  447. do {
  448. ch = fgetc(stdin);
  449. CLEAR_FERROR(stdin);
  450. } while (ch != EOF);
  451. fcntl(STDIN_FILENO, F_SETFL, oldf);
  452. pthread_mutex_unlock(&fcntl_mutex);
  453. return !ferror(stdin) && !feof(stdin);
  454. }
  455. #endif