stdio.cpp 16 KB

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