door.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164
  1. #include "door.h"
  2. #include <algorithm>
  3. #include <chrono>
  4. #include <ctype.h>
  5. #include <string.h>
  6. #include <string>
  7. #include <thread>
  8. #include <libgen.h> // basename
  9. // time/date output std::put_time()
  10. // https://en.cppreference.com/w/cpp/io/manip/put_time
  11. #include <ctime>
  12. #include <iomanip>
  13. // alarm signal
  14. #include <signal.h>
  15. #include <unistd.h>
  16. #include <iconv.h>
  17. #include <algorithm>
  18. #include <iostream>
  19. /*
  20. My strategy here has failed.
  21. If I set enigma to cp437, then it handles everything but the cp437
  22. symbols (diamonds/hearts/spades/clubs) correctly on the unicode side.
  23. [And my door thinks it's all cp437 always]
  24. If I set enigma to utf8, then it works right on the ssh terminal side.
  25. But cp437 turns to puke because it's trying to convert cp437 from
  26. utf8 to cp437. The symbols get '?'.
  27. I can't detect unicode (when set to utf8), but I can detect cp437
  28. (by sending the diamonds/hearts characters).
  29. But I can't get through the enigma translation system. If only iconv worked
  30. correctly with hearts/clubs symbols! Then I wouldn't need this broken
  31. work-around code.
  32. */
  33. namespace door {
  34. void to_lower(std::string &text) {
  35. transform(text.begin(), text.end(), text.begin(), ::tolower);
  36. }
  37. bool replace(std::string &str, const std::string &from, const std::string &to) {
  38. size_t start_pos = str.find(from);
  39. if (start_pos == std::string::npos)
  40. return false;
  41. str.replace(start_pos, from.length(), to);
  42. return true;
  43. }
  44. static bool hangup = false;
  45. void sig_handler(int signal) {
  46. hangup = true;
  47. /*
  48. ofstream sigf;
  49. sigf.open("signal.log", std::ofstream::out | std::ofstream::app);
  50. sigf << "SNAP! GOT: " << signal << std::endl;
  51. sigf.close();
  52. */
  53. // 13 SIGPIPE -- ok, what do I do with this, eh?
  54. }
  55. class IConv {
  56. iconv_t ic;
  57. public:
  58. IConv(const char *to, const char *from);
  59. ~IConv();
  60. int convert(char *input, char *output, size_t outbufsize);
  61. };
  62. IConv::IConv(const char *to, const char *from) : ic(iconv_open(to, from)) {}
  63. IConv::~IConv() { iconv_close(ic); }
  64. int IConv::convert(char *input, char *output, size_t outbufsize) {
  65. size_t inbufsize = strlen(input);
  66. // size_t orig_size = outbufsize;
  67. // memset(output, 0, outbufsize);
  68. // https://www.gnu.org/savannah-checkouts/gnu/libiconv/documentation/libiconv-1.15/iconv.3.html
  69. int r = iconv(ic, &input, &inbufsize, &output, &outbufsize);
  70. *output = 0;
  71. return r;
  72. }
  73. static IConv converter("UTF-8", "CP437");
  74. void cp437toUnicode(std::string input, std::string &out) {
  75. char buffer[10240];
  76. char output[16384];
  77. strcpy(buffer, input.c_str());
  78. converter.convert(buffer, output, sizeof(output));
  79. out.assign(output);
  80. }
  81. void cp437toUnicode(const char *input, std::string &out) {
  82. char buffer[10240];
  83. char output[16384];
  84. strcpy(buffer, input);
  85. converter.convert(buffer, output, sizeof(output));
  86. out.assign(output);
  87. }
  88. bool unicode = false;
  89. bool debug_capture = false;
  90. /**
  91. * Construct a new Door object using the commandline parameters
  92. * given to the main function.
  93. *
  94. * @example door_example.cpp
  95. */
  96. Door::Door(std::string dname, int argc, char *argv[])
  97. : std::ostream(this), doorname{dname},
  98. has_dropfile{false}, debugging{false}, seconds_elapsed{0},
  99. previous(COLOR::WHITE), track{true}, cx{1}, cy{1},
  100. inactivity{120}, node{1} {
  101. // Setup commandline options
  102. opt.addUsage("Door++ library by BUGZ (C) 2021");
  103. opt.addUsage("");
  104. opt.addUsage(" -h --help Displays this help");
  105. opt.addUsage(" -l --local Local Mode");
  106. opt.addUsage(" -d --dropfile [FILENAME] Load Dropfile");
  107. opt.addUsage(" -n --node N Set node number");
  108. // opt.addUsage(" -b --bbsname NAME Set BBS Name");
  109. opt.addUsage(" -u --username NAME Set Username");
  110. opt.addUsage(" -t --timeleft N Set time left");
  111. opt.addUsage(" --maxtime N Set max time");
  112. opt.addUsage("");
  113. opt.setFlag("help", 'h');
  114. opt.setFlag("local", 'l');
  115. opt.setFlag("debuggering");
  116. opt.setOption("dropfile", 'd');
  117. // opt.setOption("bbsname", 'b');
  118. opt.setOption("username", 'u');
  119. opt.setOption("timeleft", 't');
  120. opt.setOption("maxtime");
  121. opt.processCommandArgs(argc, argv);
  122. if (!opt.hasOptions()) {
  123. opt.printUsage();
  124. exit(1);
  125. }
  126. if (opt.getFlag("help") || opt.getFlag('h')) {
  127. opt.printUsage();
  128. exit(1);
  129. }
  130. if (opt.getValue("username") != nullptr) {
  131. username = opt.getValue("username");
  132. }
  133. if (opt.getFlag("debuggering")) {
  134. debugging = true;
  135. }
  136. if (opt.getValue("node") != nullptr) {
  137. node = atoi(opt.getValue("node"));
  138. }
  139. if (opt.getValue("timeleft") != nullptr) {
  140. time_left = atoi(opt.getValue("timeleft"));
  141. } else {
  142. // sensible default
  143. time_left = 25;
  144. }
  145. /*
  146. if (opt.getValue("bbsname") != nullptr) {
  147. bbsname = opt.getValue("bbsname");
  148. }
  149. */
  150. parse_dropfile(opt.getValue("dropfile"));
  151. /*
  152. If the dropfile has time_left, we'll use it.
  153. Adjust time_left by maxtime value (if given).
  154. */
  155. if (opt.getValue("maxtime") != nullptr) {
  156. int maxtime = atoi(opt.getValue("maxtime"));
  157. if (time_left > maxtime) {
  158. logf << "Adjusting time from " << time_left << " to " << maxtime
  159. << std::endl;
  160. time_left = maxtime;
  161. }
  162. }
  163. if (opt.getFlag("local")) {
  164. if (username.empty()) {
  165. std::cout << "Local mode requires username to be set." << std::endl;
  166. opt.printUsage();
  167. exit(1);
  168. }
  169. } else {
  170. // we must have a dropfile, or else!
  171. if (!has_dropfile) {
  172. std::cout << "I require a dropfile. And a shrubbery." << std::endl;
  173. opt.printUsage();
  174. exit(1);
  175. }
  176. }
  177. // Set program name
  178. std::string logFileName = dname + ".log";
  179. logf.open(logFileName.c_str(), std::ofstream::out | std::ofstream::app);
  180. log() << "Door init" << std::endl;
  181. init();
  182. // door.sys doesn't give BBS name. system_name
  183. if (!debugging) {
  184. detect_unicode_and_screen();
  185. logf << "Screen " << width << " X " << height << std::endl;
  186. }
  187. }
  188. Door::~Door() {
  189. // restore default mode
  190. // tcsetattr(STDIN_FILENO, TCSANOW, &tio_default);
  191. log() << "dtor" << std::endl;
  192. tcsetattr(STDIN_FILENO, TCOFLUSH, &tio_default);
  193. signal(SIGHUP, SIG_DFL);
  194. signal(SIGPIPE, SIG_DFL);
  195. // time thread
  196. stop_thread.set_value();
  197. time_thread.join();
  198. log() << "done" << std::endl;
  199. logf.close();
  200. }
  201. // https://www.tutorialspoint.com/how-do-i-terminate-a-thread-in-cplusplus11
  202. void Door::time_thread_run(std::future<void> future) {
  203. while (future.wait_for(std::chrono::milliseconds(1)) ==
  204. std::future_status::timeout) {
  205. // std::cout << "Executing the thread....." << std::endl;
  206. std::this_thread::sleep_for(std::chrono::seconds(1));
  207. seconds_elapsed++;
  208. // log("TICK");
  209. // logf << "TICK " << seconds_elapsed << std::endl;
  210. if (seconds_elapsed % 60 == 0) {
  211. if (time_left > 0)
  212. time_left--;
  213. }
  214. }
  215. }
  216. void Door::init(void) {
  217. // https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html
  218. // https://viewsourcecode.org/snaptoken/kilo/03.rawInputAndOutput.html
  219. // ok! I didn't know about VTIME ! That's something different!
  220. // enable terminal RAW mode
  221. struct termios tio_raw;
  222. tcgetattr(STDIN_FILENO, &tio_default);
  223. tio_raw = tio_default;
  224. cfmakeraw(&tio_raw);
  225. // local terminal magic
  226. tio_raw.c_cc[VMIN] = 0;
  227. tio_raw.c_cc[VTIME] = 1;
  228. bpos = 0;
  229. tcsetattr(STDIN_FILENO, TCSANOW, &tio_raw);
  230. startup = std::time(nullptr);
  231. signal(SIGHUP, sig_handler);
  232. signal(SIGPIPE, sig_handler);
  233. // time thread
  234. std::future<void> stop_future = stop_thread.get_future();
  235. time_thread =
  236. std::thread(&Door::time_thread_run, this, std::move(stop_future));
  237. }
  238. void Door::detect_unicode_and_screen(void) {
  239. unicode = false;
  240. width = 0;
  241. height = 0;
  242. if (!isatty(STDIN_FILENO)) {
  243. // https://stackoverflow.com/questions/273261/force-telnet-client-into-character-mode
  244. *this << "\377\375\042\377\373\001"; // fix telnet client
  245. }
  246. // maybe I need to be trying to detect cp437 instead of trying to detect
  247. // unicde!
  248. *this << "\x1b[0;30;40m\x1b[2J\x1b[H"; // black on black, clrscr, go home
  249. // *this << "\u2615"
  250. *this << "\x03\x04" // hearts and diamonds
  251. << "\x1b[6n"; // hot beverage + cursor pos
  252. *this << "\x1b[999C\x1b[999B\x1b[6n"; // goto end of screen + cursor pos
  253. *this << "\x1b[H"; // go home
  254. this->flush();
  255. usleep(1000 * 1000);
  256. if (haskey()) {
  257. char buffer[101];
  258. int len;
  259. len = read(STDIN_FILENO, &buffer, sizeof(buffer) - 1);
  260. // logf << "read " << len << std::endl;
  261. if (len > 0) {
  262. buffer[len] = 0;
  263. int x;
  264. /*
  265. for (x = 0; x < len; x++) {
  266. if (buffer[x] < 0x20)
  267. logf << std::hex << (int)buffer[x] << " ";
  268. else
  269. logf << buffer[x] << " ";
  270. }
  271. */
  272. for (x = 0; x < len; x++) {
  273. if (buffer[x] == 0)
  274. buffer[x] = ' ';
  275. }
  276. /*
  277. logf << std::endl;
  278. logf << "BUFFER [" << (char *)buffer << "]" << std::endl;
  279. */
  280. if (1) {
  281. std::string cleanbuffer = buffer;
  282. std::string esc = "\x1b";
  283. std::string esc_text = "^[";
  284. while (replace(cleanbuffer, esc, esc_text)) {
  285. };
  286. logf << "BUFFER [" << cleanbuffer << "]" << std::endl;
  287. }
  288. // this did not work -- because of the null characters in the buffer.
  289. // 1;3R required on David's machine. I'm not sure why.
  290. // 1;3R also happens under VSCodium.
  291. // 1;4R is what I get from syncterm.
  292. if ((strstr(buffer, "1;1R") != nullptr)) {
  293. // if ((strstr(buffer, "1;2R") != nullptr) or
  294. // (strstr(buffer, "1;3R") != nullptr)) {
  295. unicode = true;
  296. log() << "unicode enabled \u2615" << std::endl; // "U0001f926");
  297. }
  298. // Get the terminal screen size
  299. // \x1b[1;2R\x1b[41;173R
  300. // log(buffer);
  301. char *cp;
  302. cp = strchr(buffer, '\x1b');
  303. if (cp != nullptr) {
  304. cp = strchr(cp + 1, '\x1b');
  305. } else {
  306. log() << "Failed terminal size detection. See buffer:" << std::endl;
  307. log() << buffer << std::endl;
  308. return;
  309. }
  310. if (cp != nullptr) {
  311. cp++;
  312. if (*cp == '[') {
  313. cp++;
  314. height = atoi(cp);
  315. cp = strchr(cp, ';');
  316. if (cp != nullptr) {
  317. cp++;
  318. width = atoi(cp);
  319. } else {
  320. height = 0;
  321. }
  322. }
  323. }
  324. }
  325. } else {
  326. logf << "FAIL-WHALE, no response to terminal getposition." << std::endl;
  327. }
  328. }
  329. void Door::parse_dropfile(const char *filepath) {
  330. if (filepath == nullptr)
  331. return;
  332. // Ok, parse file here...
  333. std::ifstream file(filepath);
  334. std::string line;
  335. while (std::getline(file, line)) {
  336. // These are "DOS" files. Remove trailing \r.
  337. if (!line.empty() && line[line.size() - 1] == '\r')
  338. line.erase(line.size() - 1);
  339. dropfilelines.push_back(line);
  340. }
  341. file.close();
  342. std::string filename;
  343. {
  344. // converting const char * to char * for basename.
  345. char *temp = strdup(filepath);
  346. filename = basename(temp);
  347. free(temp);
  348. }
  349. to_lower(filename);
  350. // for now, just door.sys.
  351. if (filename == "door.sys") {
  352. // Ok, parse away!
  353. node = atoi(dropfilelines[3].c_str());
  354. username = dropfilelines[9];
  355. location = dropfilelines[10];
  356. time_left = atoi(dropfilelines[18].c_str());
  357. sysop = dropfilelines[34];
  358. handle = dropfilelines[35];
  359. } else {
  360. std::string msg = "Unknown dropfile: ";
  361. msg += filename;
  362. log() << msg << std::endl;
  363. *this << msg << std::endl;
  364. exit(2);
  365. }
  366. has_dropfile = true;
  367. }
  368. ofstream &Door::log(void) {
  369. // todo: have logging
  370. std::time_t t = std::time(nullptr);
  371. std::tm tm = *std::localtime(&t);
  372. logf << std::put_time(&tm, "%c ");
  373. return logf; // << output << std::endl;
  374. }
  375. bool Door::haskey(void) {
  376. fd_set socket_set;
  377. struct timeval tv;
  378. int select_ret = -1;
  379. if (hangup)
  380. return -2;
  381. if (time_left < 2)
  382. return -3;
  383. while (select_ret == -1) {
  384. FD_ZERO(&socket_set);
  385. FD_SET(STDIN_FILENO, &socket_set);
  386. tv.tv_sec = 0;
  387. tv.tv_usec = 1;
  388. select_ret = select(STDIN_FILENO + 1, &socket_set, NULL, NULL, &tv);
  389. if (select_ret == -1) {
  390. if (errno == EINTR)
  391. continue;
  392. log() << "hangup detected" << std::endl;
  393. hangup = true;
  394. return (-2);
  395. }
  396. if (select_ret == 0)
  397. return false;
  398. }
  399. return true;
  400. }
  401. /*
  402. low-lever read a key from terminal or stdin.
  403. Returns key, or
  404. -1 (no key available)
  405. -2 (read error)
  406. */
  407. signed int Door::getch(void) {
  408. fd_set socket_set;
  409. struct timeval tv;
  410. int select_ret = -1;
  411. int recv_ret;
  412. char key;
  413. if (door::hangup)
  414. return -2;
  415. if (time_left < 2)
  416. return -3;
  417. while (select_ret == -1) {
  418. FD_ZERO(&socket_set);
  419. FD_SET(STDIN_FILENO, &socket_set);
  420. tv.tv_sec = 0;
  421. tv.tv_usec = 100;
  422. select_ret = select(STDIN_FILENO + 1, &socket_set, NULL, NULL, &tv);
  423. // select(STDIN_FILENO + 1, &socket_set, NULL, NULL, bWait ? NULL : &tv);
  424. if (select_ret == -1) {
  425. if (errno == EINTR)
  426. continue;
  427. log() << "hangup detected" << std::endl;
  428. door::hangup = true;
  429. return (-2);
  430. }
  431. if (select_ret == 0)
  432. return (-1);
  433. }
  434. recv_ret = read(STDIN_FILENO, &key, 1);
  435. if (recv_ret != 1) {
  436. // possibly log this.
  437. log() << "hangup" << std::endl;
  438. hangup = true;
  439. return -2;
  440. }
  441. return key;
  442. }
  443. void Door::unget(char c) {
  444. if (bpos < sizeof(buffer) - 1) {
  445. buffer[bpos] = c;
  446. bpos++;
  447. }
  448. }
  449. char Door::get(void) {
  450. if (bpos == 0)
  451. return 0;
  452. bpos--;
  453. return buffer[bpos];
  454. }
  455. signed int Door::getkey(void) {
  456. signed int c, c2;
  457. if (bpos != 0) {
  458. c = get();
  459. } else {
  460. c = getch();
  461. }
  462. if (c < 0)
  463. return c;
  464. /*
  465. We get 0x0d 0x00 for [Enter] (Syncterm)
  466. This strips out the null.
  467. */
  468. if (c == 0x0d) {
  469. c2 = getch();
  470. if ((c2 != 0) and (c2 >= 0))
  471. unget(c2);
  472. return c;
  473. }
  474. if (c == 0x1b) {
  475. // possible extended key
  476. c2 = getch();
  477. if (c2 < 0) {
  478. // nope, just plain ESC key
  479. return c;
  480. }
  481. // consume extended key values int extended buffer
  482. char extended[16];
  483. unsigned int pos = 0;
  484. extended[pos] = (char)c2;
  485. extended[pos + 1] = 0;
  486. pos++;
  487. while ((pos < sizeof(extended) - 1) and ((c2 = getch()) >= 0)) {
  488. // special case here where I'm sending out cursor location requests
  489. // and the \x1b[X;YR strings are getting buffered.
  490. if (c2 == 0x1b) {
  491. unget(c2);
  492. break;
  493. }
  494. extended[pos] = (char)c2;
  495. extended[pos + 1] = 0;
  496. pos++;
  497. }
  498. // convert extended buffer to special key
  499. if (extended[0] == '[') {
  500. switch (extended[1]) {
  501. case 'A':
  502. return XKEY_UP_ARROW;
  503. case 'B':
  504. return XKEY_DOWN_ARROW;
  505. case 'C':
  506. return XKEY_RIGHT_ARROW;
  507. case 'D':
  508. return XKEY_LEFT_ARROW;
  509. case 'H':
  510. return XKEY_HOME;
  511. case 'F':
  512. return XKEY_END; // terminal
  513. case 'K':
  514. return XKEY_END;
  515. case 'U':
  516. return XKEY_PGUP;
  517. case 'V':
  518. return XKEY_PGDN;
  519. case '@':
  520. return XKEY_INSERT;
  521. }
  522. if (extended[pos - 1] == '~') {
  523. // \x1b[digits~
  524. int number = atoi(extended + 1);
  525. switch (number) {
  526. case 2:
  527. return XKEY_INSERT; // terminal
  528. case 3:
  529. return XKEY_DELETE; // terminal
  530. case 5:
  531. return XKEY_PGUP; // terminal
  532. case 6:
  533. return XKEY_PGDN; // terminal
  534. case 15:
  535. return XKEY_F5; // terminal
  536. case 17:
  537. return XKEY_F6; // terminal
  538. case 18:
  539. return XKEY_F7; // terminal
  540. case 19:
  541. return XKEY_F8; // terminal
  542. case 20:
  543. return XKEY_F9; // terminal
  544. case 21:
  545. return XKEY_F10; // terminal
  546. case 23:
  547. return XKEY_F11;
  548. case 24:
  549. return XKEY_F12; // terminal
  550. }
  551. }
  552. }
  553. if (extended[0] == 'O') {
  554. switch (extended[1]) {
  555. case 'P':
  556. return XKEY_F1;
  557. case 'Q':
  558. return XKEY_F2;
  559. case 'R':
  560. return XKEY_F3;
  561. case 'S':
  562. return XKEY_F4;
  563. case 't':
  564. return XKEY_F5; // syncterm
  565. }
  566. }
  567. // unknown -- This needs to be logged
  568. logf << "\r\nDEBUG:\r\nESC + ";
  569. for (unsigned int x = 0; x < pos; x++) {
  570. char z = extended[x];
  571. if (iscntrl(z)) {
  572. logf << (int)z << " ";
  573. } else {
  574. logf << "'" << (char)z << "'"
  575. << " ";
  576. };
  577. }
  578. logf << "\r\n";
  579. logf.flush();
  580. return XKEY_UNKNOWN;
  581. }
  582. return c;
  583. }
  584. int Door::get_input(void) {
  585. signed int c;
  586. c = getkey();
  587. if (c < 0)
  588. return 0;
  589. return c;
  590. /*
  591. tODInputEvent event;
  592. od_get_input(&event, OD_NO_TIMEOUT, GETIN_NORMAL);
  593. */
  594. }
  595. /*
  596. The following code will wait for 1.5 second:
  597. #include <sys/select.h>
  598. #include <sys/time.h>
  599. #include <unistd.h>`
  600. int main() {
  601. struct timeval t;
  602. t.tv_sec = 1;
  603. t.tv_usec = 500000;
  604. select(0, NULL, NULL, NULL, &t);
  605. }
  606. */
  607. signed int Door::sleep_key(int secs) {
  608. fd_set socket_set;
  609. struct timeval tv;
  610. int select_ret = -1;
  611. /*
  612. int recv_ret;
  613. char key;
  614. */
  615. if (hangup)
  616. return -2;
  617. if (time_left < 2)
  618. return -3;
  619. while (select_ret == -1) {
  620. FD_ZERO(&socket_set);
  621. FD_SET(STDIN_FILENO, &socket_set);
  622. tv.tv_sec = secs;
  623. tv.tv_usec = 0;
  624. select_ret = select(STDIN_FILENO + 1, &socket_set, NULL, NULL, &tv);
  625. // select(STDIN_FILENO + 1, &socket_set, NULL, NULL, bWait ? NULL : &tv);
  626. if (select_ret == -1) {
  627. if (errno == EINTR)
  628. continue;
  629. hangup = true;
  630. log() << "hangup detected" << std::endl;
  631. return (-2);
  632. }
  633. if (select_ret == 0)
  634. return (-1);
  635. }
  636. return getkey();
  637. }
  638. std::string Door::input_string(int max) {
  639. std::string input;
  640. // draw the input area.
  641. *this << std::string(max, ' ');
  642. *this << std::string(max, '\x08');
  643. int c;
  644. while (true) {
  645. c = sleep_key(inactivity);
  646. if (c < 0) {
  647. input.clear();
  648. return input;
  649. }
  650. if (c > 0x1000)
  651. continue;
  652. if (isprint(c)) {
  653. if (int(input.length()) < max) {
  654. *this << char(c);
  655. input.append(1, c);
  656. } else {
  657. // bell
  658. *this << '\x07';
  659. }
  660. } else {
  661. switch (c) {
  662. case 0x08:
  663. case 0x7f:
  664. if (input.length() > 0) {
  665. *this << "\x08 \x08";
  666. // this->flush();
  667. input.erase(input.length() - 1);
  668. };
  669. break;
  670. case 0x0d:
  671. return input;
  672. }
  673. }
  674. }
  675. }
  676. /**
  677. * Take given buffer and output it.
  678. *
  679. * This originally stored output in the buffer. We now directly output
  680. * via the OpenDoor od_disp_emu.
  681. *
  682. * @param s const char *
  683. * @param n std::streamsize
  684. * @return std::streamsize
  685. */
  686. std::streamsize Door::xsputn(const char *s, std::streamsize n) {
  687. if (debug_capture) {
  688. debug_buffer.append(s, n);
  689. } else {
  690. static std::string buffer;
  691. buffer.append(s, n);
  692. // setp(&(*buffer.begin()), &(*buffer.end()));
  693. if (!hangup) {
  694. std::cout << buffer;
  695. std::cout.flush();
  696. }
  697. // od_disp_emu(buffer.c_str(), TRUE);
  698. // Tracking character position could be a problem / local terminal unicode.
  699. if (track)
  700. cx += n;
  701. buffer.clear();
  702. }
  703. return n;
  704. }
  705. /**
  706. * Stores a character into the buffer.
  707. * This does still use the buffer.
  708. * @todo Replace this also with a direct call to od_disp_emu.
  709. *
  710. * @param c char
  711. * @return int
  712. */
  713. int Door::overflow(char c) {
  714. /*
  715. char temp[2];
  716. temp[0] = c;
  717. temp[1] = 0;
  718. */
  719. // buffer.push_back(c);
  720. // od_disp_emu(temp, TRUE);
  721. if (debug_capture) {
  722. debug_buffer.append(1, c);
  723. } else {
  724. if (!hangup) {
  725. std::cout << c;
  726. std::cout.flush();
  727. }
  728. }
  729. if (track)
  730. cx++;
  731. // setp(&(*buffer.begin()), &(*buffer.end()));
  732. return c;
  733. }
  734. /**
  735. * Construct a new Color Output:: Color Output object
  736. * We default to BLACK/BLACK (not really a valid color),
  737. * pos=0 and len=0.
  738. */
  739. ColorOutput::ColorOutput() : c(COLOR::BLACK, COLOR::BLACK) {
  740. pos = 0;
  741. len = 0;
  742. }
  743. /**
  744. * Reset pos and len to 0.
  745. */
  746. void ColorOutput::reset(void) {
  747. pos = 0;
  748. len = 0;
  749. }
  750. /**
  751. * Construct a new Render:: Render object
  752. *
  753. * Render consists of constant text,
  754. * and vector of ColorOutput
  755. *
  756. * @param txt Text
  757. */
  758. Render::Render(const std::string txt) : text{txt} {}
  759. /**
  760. * Output the Render.
  761. *
  762. * This consists of going through the vector, getting
  763. * the fragment (from pos and len), and outputting the
  764. * color and fragment.
  765. *
  766. * @param os
  767. */
  768. void Render::output(std::ostream &os) {
  769. for (auto out : outputs) {
  770. std::string fragment = text.substr(out.pos, out.len);
  771. os << out.c << fragment;
  772. }
  773. }
  774. /**
  775. * Construct a new Clrscr:: Clrscr object
  776. *
  777. * This is used to clear the screen.
  778. */
  779. Clrscr::Clrscr() {}
  780. /**
  781. * Clear the screen using ANSI codes.
  782. *
  783. * Not all systems home the cursor after clearing the screen.
  784. * We automatically home the cursor as well.
  785. *
  786. * @param os std::ostream&
  787. * @param clr const Clrscr&
  788. * @return std::ostream&
  789. */
  790. std::ostream &operator<<(std::ostream &os, const Clrscr &clr) {
  791. Door *d = dynamic_cast<Door *>(&os);
  792. if (d != nullptr) {
  793. d->track = false;
  794. *d << "\x1b[2J"
  795. "\x1b[H";
  796. d->cx = 1;
  797. d->cy = 1;
  798. d->track = true;
  799. } else {
  800. os << "\x1b[2J"
  801. "\x1b[H";
  802. }
  803. return os;
  804. }
  805. Clrscr cls;
  806. /**
  807. * This is used to issue NL+CR
  808. *
  809. */
  810. NewLine::NewLine() {}
  811. /**
  812. * Output Newline + CarriageReturn
  813. * @param os std::ostream
  814. * @param nl const NewLine
  815. * @return std::ostream&
  816. */
  817. std::ostream &operator<<(std::ostream &os, const NewLine &nl) {
  818. Door *d = dynamic_cast<Door *>(&os);
  819. if (d != nullptr) {
  820. d->track = false;
  821. *d << "\r\n";
  822. d->cx = 1;
  823. d->cy++;
  824. d->track = true;
  825. } else {
  826. os << "\r\n";
  827. };
  828. return os;
  829. }
  830. NewLine nl;
  831. /**
  832. * Construct a new Goto:: Goto object
  833. *
  834. * @param xpos
  835. * @param ypos
  836. */
  837. Goto::Goto(int xpos, int ypos) {
  838. x = xpos;
  839. y = ypos;
  840. }
  841. void Goto::set(int xpos, int ypos) {
  842. x = xpos;
  843. y = ypos;
  844. }
  845. /**
  846. * Output the ANSI codes to position the cursor to the given y,x position.
  847. *
  848. * @todo Optimize the ANSI goto string output.
  849. * @todo Update the Door object so it know where the cursor
  850. * is positioned.
  851. *
  852. * @param os std::ostream
  853. * @param g const Goto
  854. * @return std::ostream&
  855. */
  856. std::ostream &operator<<(std::ostream &os, const Goto &g) {
  857. Door *d = dynamic_cast<Door *>(&os);
  858. if (d != nullptr) {
  859. d->track = false;
  860. *d << "\x1b[";
  861. if (g.y > 1)
  862. *d << std::to_string(g.y);
  863. if (g.x > 1) {
  864. os << ";";
  865. *d << std::to_string(g.x);
  866. }
  867. *d << "H";
  868. d->cx = g.x;
  869. d->cy = g.y;
  870. d->track = true;
  871. } else {
  872. os << "\x1b[" << std::to_string(g.y) << ";" << std::to_string(g.x) << "H";
  873. };
  874. return os;
  875. }
  876. const char SaveCursor[] = "\x1b[s";
  877. const char RestoreCursor[] = "\x1b[u";
  878. // EXAMPLES
  879. /// BlueYellow Render example function
  880. renderFunction rBlueYellow = [](const std::string &txt) -> Render {
  881. Render r(txt);
  882. ColorOutput co;
  883. bool uc = true;
  884. ANSIColor blue(COLOR::BLUE, ATTR::BOLD);
  885. ANSIColor cyan(COLOR::YELLOW, ATTR::BOLD);
  886. co.pos = 0;
  887. co.len = 0;
  888. co.c = blue;
  889. // d << blue;
  890. int tpos = 0;
  891. for (char const &c : txt) {
  892. if (uc) {
  893. if (!isupper(c)) {
  894. // possible color change
  895. if (co.len != 0) {
  896. r.outputs.push_back(co);
  897. co.reset();
  898. co.pos = tpos;
  899. }
  900. co.c = cyan;
  901. // d << cyan;
  902. uc = false;
  903. }
  904. } else {
  905. if (isupper(c)) {
  906. if (co.len != 0) {
  907. r.outputs.push_back(co);
  908. co.reset();
  909. co.pos = tpos;
  910. }
  911. co.c = blue;
  912. // d << blue;
  913. uc = true;
  914. }
  915. }
  916. co.len++;
  917. tpos++;
  918. // d << c;
  919. }
  920. if (co.len != 0) {
  921. r.outputs.push_back(co);
  922. }
  923. return r;
  924. };
  925. door::renderFunction renderStatusValue(door::ANSIColor status,
  926. door::ANSIColor value) {
  927. door::renderFunction rf = [status,
  928. value](const std::string &txt) -> door::Render {
  929. door::Render r(txt);
  930. door::ColorOutput co;
  931. co.pos = 0;
  932. co.len = 0;
  933. co.c = status;
  934. size_t pos = txt.find(':');
  935. if (pos == std::string::npos) {
  936. // failed to find - use entire string as status color.
  937. co.len = txt.length();
  938. r.outputs.push_back(co);
  939. } else {
  940. pos++; // Have : in status color
  941. co.len = pos;
  942. r.outputs.push_back(co);
  943. co.reset();
  944. co.pos = pos;
  945. co.c = value;
  946. co.len = txt.length() - pos;
  947. r.outputs.push_back(co);
  948. }
  949. return r;
  950. };
  951. return rf;
  952. }
  953. door::renderFunction rStatusValue = [](const std::string &txt) -> door::Render {
  954. door::Render r(txt);
  955. door::ColorOutput co;
  956. // default colors STATUS: value
  957. door::ANSIColor status(door::COLOR::BLUE, door::ATTR::BOLD);
  958. door::ANSIColor value(door::COLOR::YELLOW, door::ATTR::BOLD);
  959. co.pos = 0;
  960. co.len = 0;
  961. co.c = status;
  962. size_t pos = txt.find(':');
  963. if (pos == std::string::npos) {
  964. // failed to find - use entire string as status color.
  965. co.len = txt.length();
  966. r.outputs.push_back(co);
  967. } else {
  968. pos++; // Have : in status color
  969. co.len = pos;
  970. r.outputs.push_back(co);
  971. co.reset();
  972. co.pos = pos;
  973. co.c = value;
  974. co.len = txt.length() - pos;
  975. r.outputs.push_back(co);
  976. }
  977. return r;
  978. };
  979. /*
  980. std::function<void(Door &d, std::string &txt)> BlueYellow2 =
  981. [](Door &d, std::string &txt) -> void {
  982. bool uc = true;
  983. ANSIColor blue(COLOR::BLACK, COLOR::CYAN);
  984. ANSIColor cyan(COLOR::YELLOW, COLOR::BLUE, ATTR::BOLD);
  985. d << blue;
  986. for (char const &c : txt) {
  987. if (uc) {
  988. if (c == ':') {
  989. d << cyan;
  990. uc = false;
  991. }
  992. }
  993. d << c;
  994. }
  995. };
  996. std::function<void(Door &d, std::string &txt)> Aweful =
  997. [](Door &d, std::string &txt) -> void {
  998. for (char const &c : txt) {
  999. // Color clr((Colors)((c % 14) + 1), Colors::BLACK, 0);
  1000. // Use only BRIGHT/LIGHT colors.
  1001. ANSIColor clr((COLOR)(c % 8), ATTR::BOLD);
  1002. d << clr << c;
  1003. }
  1004. };
  1005. */
  1006. } // namespace door