terminal.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820
  1. /*
  2. Terminal tracking
  3. Actually, I believe I only really need to track the color information.
  4. Everything else, I'm not sure I really care about. (NNY!)
  5. */
  6. #include "terminal.h"
  7. #include "utils.h"
  8. #include "zf_log.h"
  9. #include <ctype.h>
  10. #include <sstream>
  11. #include <stdio.h> // snprintf
  12. #include <stdlib.h>
  13. #include <string.h>
  14. #include <vector>
  15. Terminal::Terminal() { this->init(); }
  16. Terminal::Terminal(const Terminal &old) : saved_cursor_position( old.saved_cursor_position) {
  17. posx = old.posx;
  18. posy = old.posy;
  19. in_ansi = old.in_ansi;
  20. fgcolor = old.fgcolor;
  21. bgcolor = old.bgcolor;
  22. status = old.status;
  23. ansi = old.ansi;
  24. }
  25. Terminal & Terminal::operator=(Terminal &rhs) {
  26. posx = rhs.posx;
  27. posy = rhs.posy;
  28. in_ansi = rhs.in_ansi;
  29. fgcolor = rhs.fgcolor;
  30. bgcolor = rhs.bgcolor;
  31. status = rhs.status;
  32. ansi = rhs.ansi;
  33. saved_cursor_position = rhs.saved_cursor_position;
  34. return *this;
  35. }
  36. void Terminal::init(void) {
  37. this->posx = this->posy = 0;
  38. this->in_ansi = 0;
  39. this->fgcolor = 7;
  40. this->bgcolor = 0;
  41. this->status = 0;
  42. this->saved_cursor_position.clear();
  43. }
  44. int Terminal::getx(void) { return posx; }
  45. int Terminal::gety(void) { return posy; }
  46. int Terminal::getstatus(void) { return status; }
  47. int Terminal::inANSI(void) { return in_ansi; }
  48. int Terminal::fg(void) { return fgcolor; }
  49. int Terminal::bg(void) { return bgcolor; }
  50. bool Terminal::ansiempty(void) { return ansi.empty(); }
  51. void Terminal::ansi_color(int color) {
  52. // ZF_LOGV("ansi_color(%d)", color);
  53. if (color == 0) {
  54. this->status = 0;
  55. this->fgcolor = 7;
  56. this->bgcolor = 0;
  57. return;
  58. }
  59. if ((color == 1) || (color == 2) || (color == 3) || (color == 4) ||
  60. (color == 5)) {
  61. this->status = color;
  62. return;
  63. }
  64. if ((color >= 30) && (color <= 37)) {
  65. this->fgcolor = color - 30;
  66. return;
  67. }
  68. if ((color >= 40) && (color <= 47)) {
  69. this->bgcolor = color - 40;
  70. return;
  71. }
  72. if (color == 39) {
  73. // default fg color
  74. this->fgcolor = 7;
  75. return;
  76. }
  77. if (color == 49) {
  78. // default bg color
  79. this->bgcolor = 0;
  80. return;
  81. }
  82. ZF_LOGD("ansi_color( %d ) is unknown to me.", color);
  83. }
  84. std::string Terminal::color_restore(void) {
  85. std::ostringstream oss;
  86. // possible optimize: If fg is 7, don't set (0 reset does that), if bg is 0,
  87. // don't set (0 does that)
  88. if (this->status == 0) {
  89. oss << "\x1b[0;" << this->fgcolor + 30 << ";" << this->bgcolor + 40 << "m";
  90. } else {
  91. oss << "\x1b[0;" << this->status << ";" << this->fgcolor + 30 << ";"
  92. << this->bgcolor + 40 << "m";
  93. }
  94. std::string buffer = oss.str();
  95. return buffer;
  96. }
  97. Terminal::ANSI_TYPE Terminal::ansi_code(std::string ansi) {
  98. std::string::iterator cp = ansi.begin();
  99. std::string::iterator last = ansi.end() - 1;
  100. int number, number2;
  101. if (*cp == '\x1b') {
  102. ++cp;
  103. // Ok, that's expected.
  104. if (*cp == '[') {
  105. ++cp;
  106. // https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences
  107. switch (*last) {
  108. case 'A':
  109. // cursor up
  110. if (cp == last) {
  111. number = 1;
  112. } else {
  113. number = std::stoi(std::string(cp, last));
  114. if (number < 1) {
  115. ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi.c_str()),
  116. number);
  117. number = 1;
  118. }
  119. };
  120. this->posy -= number;
  121. if (this->posy < 0) {
  122. this->posy = 0;
  123. ZF_LOGD(
  124. "console_ansi( %s ): attempt to move above top of screen (%d)",
  125. repr(ansi.c_str()), number);
  126. }
  127. return CURSOR;
  128. case 'B':
  129. // cursor down
  130. if (cp == last) {
  131. number = 1;
  132. } else {
  133. number = std::stoi(std::string(cp, last));
  134. if (number < 1) {
  135. ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi.c_str()),
  136. number);
  137. number = 1;
  138. }
  139. };
  140. this->posy += number;
  141. // check range/"scroll"
  142. return CURSOR;
  143. case 'C':
  144. // cursor forward
  145. if (cp == last) {
  146. number = 1;
  147. } else {
  148. number = std::stoi(std::string(cp, last));
  149. if (number < 1) {
  150. ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi.c_str()),
  151. number);
  152. number = 1;
  153. }
  154. };
  155. this->posx += number;
  156. // Well. According to the "spec", the screen limits are hard
  157. // If the cursor is already at the edge of the screen, this has no
  158. // effect. ^ This is *NOT* how any ANSI BBS terminal program acts!
  159. while (this->posx > 79) {
  160. this->posy++;
  161. // check range/"scroll"
  162. this->posx -= 79;
  163. }
  164. return CURSOR;
  165. case 'D':
  166. // cursor backwards
  167. if (cp == last) {
  168. number = 1;
  169. } else {
  170. number = std::stoi(std::string(cp, last));
  171. if (number < 1) {
  172. ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi.c_str()),
  173. number);
  174. number = 0;
  175. }
  176. };
  177. this->posx -= number;
  178. // Well. According to the "spec", the screen limits are hard
  179. // If the cursor is already at the edge of the screen, this has no
  180. // effect. ^ This is *NOT* how any ANSI BBS terminal program acts!
  181. while (this->posx < 0) {
  182. this->posy--;
  183. if (this->posy < 0) {
  184. this->posy = 0;
  185. }
  186. this->posx += 79;
  187. }
  188. return CURSOR;
  189. case 'H':
  190. // cursor position
  191. if (*cp == ';') {
  192. // Missing first number
  193. number = 1;
  194. ++cp;
  195. if (cp == last) {
  196. // missing 2nd number as well?
  197. number2 = 1;
  198. } else {
  199. number2 = std::stoi(std::string(cp, last));
  200. }
  201. } else {
  202. // Ok, find the first number
  203. number = std::stoi(std::string(cp, last));
  204. // covert iterator to position
  205. std::size_t pos = ansi.find(';', std::distance(ansi.begin(), cp));
  206. if (pos == std::string::npos) {
  207. // Missing 2nd number
  208. number2 = 1;
  209. } else {
  210. // 2nd number found, maybe.
  211. cp = ansi.begin() + pos;
  212. ++cp;
  213. if (cp == last) {
  214. number2 = 1;
  215. } else {
  216. number2 = std::stoi(std::string(cp, last));
  217. }
  218. }
  219. }
  220. // Our positions start at zero, not one.
  221. this->posy = number - 1;
  222. this->posx = number2 - 1;
  223. return CURSOR;
  224. case 'J':
  225. // clear
  226. if (cp == last) {
  227. number = 0;
  228. } else {
  229. number = std::stoi(std::string(cp, last));
  230. };
  231. // clears ... part of the screen.
  232. if (number == 2) {
  233. this->posx = 0;
  234. this->posy = 0;
  235. };
  236. return CLEAR;
  237. case 'K':
  238. // clear line
  239. if (cp == last) {
  240. number = 0;
  241. } else {
  242. number = std::stoi(std::string(cp, last));
  243. };
  244. return CLEAR;
  245. case 'm':
  246. // color
  247. if (cp == last) {
  248. // nothing given, default to 0.
  249. number = 0;
  250. ansi_color(number);
  251. } else {
  252. while (cp != last) {
  253. number = std::stoi(std::string(cp, last));
  254. ansi_color(number);
  255. ++cp;
  256. while ((cp != last) && (isdigit(*cp))) {
  257. ++cp;
  258. };
  259. if (cp != last) {
  260. if (*cp == ';') {
  261. cp++;
  262. }
  263. }
  264. }
  265. }
  266. return COLOR;
  267. case 's':
  268. // save position
  269. saved_cursor_position.push_back(std::make_pair(this->posx, this->posy));
  270. return CURSOR;
  271. case 'u':
  272. // restore position
  273. if (saved_cursor_position.empty()) {
  274. ZF_LOGE("Restore from saved_cursor_position (empty).");
  275. } else {
  276. std::pair<int, int> pos = saved_cursor_position.back();
  277. this->posx = pos.first;
  278. this->posy = pos.second;
  279. saved_cursor_position.pop_back();
  280. }
  281. return CURSOR;
  282. case 't':
  283. case 'r':
  284. case 'h':
  285. case '!':
  286. // These are ones that I don't care about.
  287. case 'n': // This is terminal detect -- give me cursor position
  288. return OTHER;
  289. default:
  290. // unsure -- possibly not important
  291. ZF_LOGD("console_ansi( %s ): ???", repr(ansi.c_str()));
  292. return OTHER;
  293. }
  294. }
  295. }
  296. ZF_LOGD("console_ansi( %s ) : ???", repr(ansi.c_str()));
  297. return OTHER;
  298. }
  299. Terminal::termchar Terminal::putchar(char ch) {
  300. Terminal::termchar tc;
  301. if (this->in_ansi) {
  302. // Ok, append this char
  303. this->ansi.append(1, ch);
  304. if (isalpha(ch)) {
  305. // Ok! end of ANSI code, process it.
  306. tc.ansi = ansi_code(this->ansi);
  307. this->in_ansi = 0;
  308. this->ansi.clear();
  309. tc.in_ansi = 1;
  310. return tc;
  311. }
  312. tc.ansi = START;
  313. tc.in_ansi = 1;
  314. return tc;
  315. } else {
  316. tc.ansi = START;
  317. tc.in_ansi = 0;
  318. if (ch == '\x1b') {
  319. this->ansi.append(1, ch);
  320. this->in_ansi = 1;
  321. tc.in_ansi = 1;
  322. return tc;
  323. }
  324. // should I try reporting MOTION non-ANSI ?
  325. if (ch == '\r') {
  326. // Carriage return
  327. this->posx = 0;
  328. return tc;
  329. }
  330. if (ch == '\n') {
  331. this->posy++;
  332. // check range/"scroll"
  333. return tc;
  334. }
  335. if (ch == '\b') {
  336. // Backspace.
  337. if (this->posx > 0) {
  338. this->posx--;
  339. }
  340. return tc;
  341. }
  342. if (ch == '\f') {
  343. // form feed
  344. // treat as clear screen
  345. this->posx = 0;
  346. this->posy = 0;
  347. return tc;
  348. }
  349. /*
  350. I don't believe that anything else can possibly be here, other then an
  351. actual printable character. So!
  352. FUTURE: Store the screen text + colors
  353. */
  354. this->posx++;
  355. if (this->posx > 79) {
  356. this->posx = 0;
  357. this->posy++;
  358. // check range/"scroll"
  359. }
  360. return tc;
  361. }
  362. }
  363. void Terminal::putstring(std::string text) {
  364. // This gets noisy when called from render effect ^D
  365. // ZF_LOGI("console_char %lu chars", chars.size());
  366. for (auto strit = text.begin(); strit != text.end(); strit++)
  367. putchar(*strit);
  368. }
  369. // Old non-C++ ways
  370. void console_init(struct console_details *cdp) {
  371. cdp->posx = 0;
  372. cdp->posy = 0;
  373. cdp->savedx = 0;
  374. cdp->savedy = 0;
  375. cdp->in_ansi = 0;
  376. cdp->fgcolor = 7;
  377. cdp->bgcolor = 0;
  378. cdp->status = 0;
  379. }
  380. /*
  381. * Given the ansi number codes
  382. * Figure out fg, bg and status.
  383. */
  384. void ansi_color(struct console_details *cdp, int color) {
  385. // There are many calls to this.
  386. // ZF_LOGV("ansi_color(%d)", color);
  387. if (color == 0) {
  388. cdp->status = 0;
  389. cdp->fgcolor = 7;
  390. cdp->bgcolor = 0;
  391. return;
  392. }
  393. if ((color == 1) || (color == 2) || (color == 3) || (color == 4) ||
  394. (color == 5)) {
  395. cdp->status = color;
  396. return;
  397. }
  398. if ((color >= 30) && (color <= 37)) {
  399. cdp->fgcolor = color - 30;
  400. return;
  401. }
  402. if ((color >= 40) && (color <= 47)) {
  403. cdp->bgcolor = color - 40;
  404. return;
  405. }
  406. if (color == 39) {
  407. // default fg color
  408. cdp->fgcolor = 7;
  409. return;
  410. }
  411. if (color == 49) {
  412. // default bg color
  413. cdp->bgcolor = 0;
  414. return;
  415. }
  416. ZF_LOGD("ansi_color( %d ) is unknown to me.", color);
  417. }
  418. const char *color_restore(struct console_details *cdp) {
  419. static char buffer[30];
  420. int slen;
  421. // possible optimize: If fg is 7, don't set (0 reset does that), if bg is 0,
  422. // don't set (0 does that)
  423. if (cdp->status == 0) {
  424. slen = snprintf(buffer, sizeof(buffer), "\x1b[0;3%d;4%dm", cdp->fgcolor,
  425. cdp->bgcolor);
  426. if (slen >= (int)sizeof(buffer)) {
  427. ZF_LOGE("snprintf %d > size %d", slen, (int)sizeof(buffer));
  428. buffer[0] = 0;
  429. }
  430. } else {
  431. slen = snprintf(buffer, sizeof(buffer), "\x1b[0;%d;3%d;4%dm", cdp->status,
  432. cdp->fgcolor, cdp->bgcolor);
  433. if (slen >= (int)sizeof(buffer)) {
  434. ZF_LOGE("snprintf %d > size %d", slen, (int)sizeof(buffer));
  435. buffer[0] = 0;
  436. }
  437. };
  438. return buffer;
  439. }
  440. /* store cursor position X,Y */
  441. std::vector<std::pair<int, int>> cursor_position_history;
  442. ANSI_TYPE console_ansi(struct console_details *cdp, const char *ansi) {
  443. const char *cp = ansi;
  444. const char *last = ansi + strlen(ansi) - 1;
  445. int number, number2;
  446. if (*cp == '\x1b') {
  447. cp++;
  448. // Ok, that's expected.
  449. if (*cp == '[') {
  450. cp++;
  451. // https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_sequences
  452. switch (*last) {
  453. case 'A':
  454. // cursor up
  455. if (cp == last) {
  456. number = 1;
  457. } else {
  458. number = atoi(cp);
  459. if (number < 1) {
  460. ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi),
  461. number);
  462. number = 1;
  463. }
  464. };
  465. cdp->posy -= number;
  466. if (cdp->posy < 0) {
  467. cdp->posy = 0;
  468. ZF_LOGD(
  469. "console_ansi( %s ): attempt to move above top of screen (%d)",
  470. repr(ansi), number);
  471. }
  472. return CURSOR;
  473. case 'B':
  474. // cursor down
  475. if (cp == last) {
  476. number = 1;
  477. } else {
  478. number = atoi(cp);
  479. if (number < 1) {
  480. ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi),
  481. number);
  482. number = 1;
  483. }
  484. };
  485. cdp->posy += number;
  486. // check range/"scroll"
  487. return CURSOR;
  488. case 'C':
  489. // cursor forward
  490. if (cp == last) {
  491. number = 1;
  492. } else {
  493. number = atoi(cp);
  494. if (number < 1) {
  495. ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi),
  496. number);
  497. number = 1;
  498. }
  499. };
  500. cdp->posx += number;
  501. // Well. According to the "spec", the screen limits are hard
  502. // If the cursor is already at the edge of the screen, this has no
  503. // effect. ^ This is *NOT* how any ANSI BBS terminal program acts!
  504. while (cdp->posx > 79) {
  505. cdp->posy++;
  506. // check range/"scroll"
  507. cdp->posx -= 79;
  508. }
  509. return CURSOR;
  510. case 'D':
  511. // cursor backwards
  512. if (cp == last) {
  513. number = 1;
  514. } else {
  515. number = atoi(cp);
  516. if (number < 1) {
  517. ZF_LOGD("console_ansi( %s ): number error (%d)", repr(ansi),
  518. number);
  519. number = 0;
  520. }
  521. };
  522. cdp->posx -= number;
  523. // Well. According to the "spec", the screen limits are hard
  524. // If the cursor is already at the edge of the screen, this has no
  525. // effect. ^ This is *NOT* how any ANSI BBS terminal program acts!
  526. while (cdp->posx < 0) {
  527. cdp->posy--;
  528. if (cdp->posy < 0) {
  529. cdp->posy = 0;
  530. }
  531. cdp->posx += 79;
  532. }
  533. return CURSOR;
  534. case 'H':
  535. // cursor position
  536. if (*cp == ';') {
  537. // Missing first number
  538. number = 1;
  539. cp++;
  540. if (cp == last) {
  541. // missing 2nd number as well?
  542. number2 = 1;
  543. } else {
  544. number2 = atoi(cp);
  545. }
  546. } else {
  547. // Ok, find the first number
  548. number = atoi(cp);
  549. cp = strchr(cp, ';');
  550. if (cp == NULL) {
  551. // Missing 2nd number
  552. number2 = 1;
  553. } else {
  554. // 2nd number found, maybe.
  555. cp++;
  556. if (cp == last) {
  557. number2 = 1;
  558. } else {
  559. number2 = atoi(cp);
  560. }
  561. }
  562. }
  563. // Our positions start at zero, not one.
  564. cdp->posy = number - 1;
  565. cdp->posx = number2 - 1;
  566. return CURSOR;
  567. case 'J':
  568. // clear
  569. if (cp == last) {
  570. number = 0;
  571. } else {
  572. number = atoi(cp);
  573. };
  574. // clears ... part of the screen.
  575. if (number == 2) {
  576. cdp->posx = 0;
  577. cdp->posy = 0;
  578. };
  579. return CLEAR;
  580. case 'K':
  581. // clear line
  582. if (cp == last) {
  583. number = 0;
  584. } else {
  585. number = atoi(cp);
  586. };
  587. return CLEAR;
  588. case 'm':
  589. // color
  590. if (cp == last) {
  591. // nothing given, default to 0.
  592. number = 0;
  593. ansi_color(cdp, number);
  594. } else {
  595. while (cp != last) {
  596. number = atoi(cp);
  597. ansi_color(cdp, number);
  598. cp++;
  599. while ((cp != last) && (isdigit(*cp))) {
  600. cp++;
  601. };
  602. if (cp != last) {
  603. if (*cp == ';') {
  604. cp++;
  605. }
  606. }
  607. }
  608. }
  609. return COLOR;
  610. case 's':
  611. // save position
  612. cursor_position_history.push_back(std::make_pair(cdp->posx, cdp->posy));
  613. return CURSOR;
  614. case 'u':
  615. // restore position
  616. if (cursor_position_history.empty()) {
  617. ZF_LOGE("Restore from cursor_position_history (empty).");
  618. } else {
  619. std::pair<int, int> pos = cursor_position_history.back();
  620. cdp->posx = pos.first;
  621. cdp->posy = pos.second;
  622. cursor_position_history.pop_back();
  623. }
  624. return CURSOR;
  625. case 't':
  626. case 'r':
  627. case 'h':
  628. case '!':
  629. // These are ones that I don't care about.
  630. case 'n': // This is terminal detect -- give me cursor position
  631. return OTHER;
  632. default:
  633. // unsure -- possibly not important
  634. ZF_LOGD("console_ansi( %s ): ???", repr(ansi));
  635. return OTHER;
  636. }
  637. }
  638. }
  639. ZF_LOGD("console_ansi( %s ) : ???", repr(ansi));
  640. return OTHER;
  641. }
  642. /*
  643. * console_char()
  644. * return whether or not we are still in_ansi
  645. *
  646. * in_ansi TRUE: START (receiving start of/fragment of ANSI)
  647. * CURSOR, COLOR, CLEAR, OTHER
  648. * Results of the last ANSI parse.
  649. * in_ansi FALSE: START Normal character.
  650. *
  651. */
  652. termchar console_char(struct console_details *cdp, char ch) {
  653. char *cp;
  654. termchar tc;
  655. if (cdp->in_ansi) {
  656. // Ok, append this char
  657. cp = cdp->ansi + strlen(cdp->ansi);
  658. *cp = ch;
  659. cp++;
  660. *cp = 0;
  661. if (isalpha(ch)) {
  662. // Ok! end of ANSI code, process it.
  663. tc.ansi = console_ansi(cdp, cdp->ansi);
  664. cdp->in_ansi = 0;
  665. cdp->ansi[0] = 0;
  666. tc.in_ansi = 1;
  667. return tc;
  668. }
  669. tc.ansi = START;
  670. tc.in_ansi = 1;
  671. return tc;
  672. } else {
  673. tc.ansi = START;
  674. tc.in_ansi = 0;
  675. if (ch == '\x1b') {
  676. cp = cdp->ansi;
  677. *cp = ch;
  678. cp++;
  679. *cp = 0;
  680. cdp->in_ansi = 1;
  681. tc.in_ansi = 1;
  682. return tc;
  683. }
  684. // should I try reporting MOTION non-ANSI ?
  685. if (ch == '\r') {
  686. // Carriage return
  687. cdp->posx = 0;
  688. return tc;
  689. }
  690. if (ch == '\n') {
  691. cdp->posy++;
  692. // check range/"scroll"
  693. return tc;
  694. }
  695. if (ch == '\b') {
  696. // Backspace.
  697. if (cdp->posx > 0) {
  698. cdp->posx--;
  699. }
  700. return tc;
  701. }
  702. if (ch == '\f') {
  703. // form feed
  704. // treat as clear screen
  705. cdp->posx = 0;
  706. cdp->posy = 0;
  707. return tc;
  708. }
  709. /*
  710. I don't believe that anything else can possibly be here, other then an
  711. actual printable character. So!
  712. FUTURE: Store the screen text + colors
  713. */
  714. cdp->posx++;
  715. if (cdp->posx > 79) {
  716. cdp->posx = 0;
  717. cdp->posy++;
  718. // check range/"scroll"
  719. }
  720. return tc;
  721. }
  722. }
  723. void console_receive(struct console_details *cdp, std::string chars) {
  724. // This gets noisy when called from render effect ^D
  725. // ZF_LOGI("console_char %lu chars", chars.size());
  726. for (auto strit = chars.begin(); strit != chars.end(); strit++)
  727. console_char(cdp, *strit);
  728. }