/platform/osx/scaffold/console.d

http://github.com/wilkie/djehuty · D · 759 lines · 584 code · 97 blank · 78 comment · 219 complexity · 273b60f39cc4e4d1a5de6f7983274c4d MD5 · raw file

  1. /*
  2. * console.d
  3. *
  4. * This file implements the Console interfaces for the OS X system.
  5. *
  6. * Author: Dave Wilkinson
  7. *
  8. */
  9. module scaffold.console;
  10. import djehuty;
  11. import platform.unix.common;
  12. import platform.application;
  13. import synch.thread;
  14. import cui.application;
  15. import cui.window;
  16. private int _toNearestConsoleColor(Color clr) {
  17. // 16 colors on console
  18. // For each channel, it can be 00, 88, or ff
  19. // That is, something mid range
  20. int nearRed, nearGreen, nearBlue;
  21. int ret;
  22. nearRed = cast(int)((clr.red * 3.0) + 0.5);
  23. nearGreen = cast(int)((clr.green * 3.0) + 0.5);
  24. nearBlue = cast(int)((clr.blue * 3.0) + 0.5);
  25. if ((nearRed == nearGreen) && (nearGreen == nearBlue)) {
  26. // gray
  27. if (clr.red < (Color.DarkGray.red / 2.0)) {
  28. // Closer to black
  29. ret = 0;
  30. }
  31. else if (clr.red < ((Color.Gray.red - Color.DarkGray.red) / 2.0) + Color.DarkGray.red) {
  32. // Closer to dark gray
  33. ret = 8;
  34. }
  35. else if (clr.red < ((Color.White.red - Color.Gray.red) / 2.0) + Color.Gray.red) {
  36. // Closer to light gray
  37. ret = 7;
  38. }
  39. else {
  40. // Closer to white
  41. ret = 15;
  42. }
  43. }
  44. else {
  45. // Nearest color match
  46. static int[3][] translations = [
  47. [1,0,0], // 1, Dark Red
  48. [0,1,0], // 2, Dark Green
  49. [1,1,0], // 3, Dark Yellow
  50. [0,0,1], // 4, Dark Blue
  51. [1,0,1], // 5, Dark Magenta
  52. [0,1,1], // 6, Dark Cyan
  53. [2,0,0], // 9, Dark Red
  54. [0,2,0], // 10, Dark Green
  55. [2,2,0], // 11, Dark Yellow
  56. [0,0,2], // 12, Dark Blue
  57. [2,0,2], // 13, Dark Magenta
  58. [0,2,2], // 14, Dark Cyan
  59. ];
  60. float mindistance = 4*3;
  61. foreach(size_t i, coord; translations) {
  62. // Compare euclidian distance
  63. float distance = 0.0;
  64. float intermediate;
  65. intermediate = coord[0] - nearRed;
  66. intermediate *= intermediate;
  67. distance += intermediate;
  68. intermediate = coord[1] - nearGreen;
  69. intermediate *= intermediate;
  70. distance += intermediate;
  71. intermediate = coord[2] - nearBlue;
  72. intermediate *= intermediate;
  73. distance += intermediate;
  74. // Omitting square root, it is unnecessary for comparison
  75. if (mindistance > distance) {
  76. mindistance = distance;
  77. ret = i;
  78. ret++;
  79. if (ret > 6) {
  80. ret += 2;
  81. }
  82. }
  83. }
  84. }
  85. return ret;
  86. }
  87. void ConsoleSetColors(Color fg, Color bg) {
  88. int fgidx = _toNearestConsoleColor(fg);
  89. int bgidx = _toNearestConsoleColor(bg);
  90. int bright = 0;
  91. if (fgidx > 7) {
  92. fgidx %= 8;
  93. bright = 1;
  94. }
  95. if (ApplicationController.instance.usingCurses) {
  96. int idx = fgidx << 3;
  97. idx |= bgidx;
  98. if (bright) {
  99. Curses.attron(Curses.A_BOLD);
  100. }
  101. else {
  102. Curses.attroff(Curses.A_BOLD);
  103. }
  104. Curses.init_pair(idx, fgidx, bgidx);
  105. Curses.attron(Curses.COLOR_PAIR(idx));
  106. }
  107. else {
  108. printf("\x1B[%d;%d;%dm", bright, 30 + fgidx, 40 + bgidx);
  109. }
  110. }
  111. //will return the next character pressed
  112. Key consoleGetKey() {
  113. Key ret;
  114. ubyte[18] tmp;
  115. uint count;
  116. ret.code = Curses.getch();
  117. // Curses.move(0,0);
  118. // Console.put(" ");
  119. // Curses.move(0,0);
  120. // Console.put(ret.code, " ");
  121. if (ret.code != 0x1B) {
  122. // Not an escape sequence
  123. if (ret.code == Curses.KEY_MOUSE || ret.code == Curses.KEY_RESIZE || ret.code == Curses.KEY_EVENT || ret.code >= Curses.KEY_MAX) {
  124. return ret;
  125. }
  126. if (ret.code == 127 || ret.code == 8 || ret.code == 9 || ret.code == 13) {
  127. }
  128. // For ctrl+char
  129. else if (ret.code < 26) {
  130. ret.ctrl = true;
  131. ret.code = Key.A + ret.code - 1;
  132. //Curses.move(1,0);
  133. //Console.put(" ");
  134. //Curses.move(1,0);
  135. //Console.put("alt: ", ret.alt, " ctrl: ", ret.ctrl, " shift: ", ret.shift, " code: ", ret.code);
  136. return ret;
  137. }
  138. // For F5-F16:
  139. else if (ret.code >= 281 && ret.code <= 292) {
  140. ret.code -= 12;
  141. ret.shift = true;
  142. }
  143. else if (ret.code >= 293 && ret.code <= 304) {
  144. ret.code -= 24;
  145. ret.ctrl = true;
  146. }
  147. else if (ret.code >= 305 && ret.code <= 316) {
  148. ret.code -= 36;
  149. ret.ctrl = true;
  150. ret.shift = true;
  151. }
  152. else if (ret.code >= 317 && ret.code <= 328) {
  153. ret.code -= 48;
  154. ret.alt = true;
  155. }
  156. consoleTranslateKey(ret);
  157. //Curses.move(1,0);
  158. //Console.put(" ");
  159. //Curses.move(1,0);
  160. //Console.put("alt: ", ret.alt, " ctrl: ", ret.ctrl, " shift: ", ret.shift, " code: ", ret.code);
  161. return ret;
  162. }
  163. // Escape sequence...
  164. ret.code = Curses.getch();
  165. //Console.put(ret.code, " ");
  166. // Get extended commands
  167. if (ret.code == 0x1B) {
  168. // ESCAPE ESCAPE -> Escape
  169. ret.code = Key.Escape;
  170. }
  171. else if (ret.code == '[') {
  172. ret.code = Curses.getch();
  173. //Console.put(ret.code, " ");
  174. if (ret.code == '1') {
  175. ret.code = Curses.getch();
  176. //Console.put(ret.code, " ");
  177. if (ret.code == '~') {
  178. ret.code = Key.Home;
  179. }
  180. else if (ret.code == '#') {
  181. ret.code = Curses.getch();
  182. //Console.put(ret.code, " ");
  183. if (ret.code == '~') {
  184. ret.code = Key.F5;
  185. }
  186. else if (ret.code == ';') {
  187. ret.code = Curses.getch();
  188. //Console.put(ret.code, " ");
  189. if (ret.code == '2') {
  190. ret.shift = true;
  191. ret.code = Key.F5;
  192. }
  193. }
  194. }
  195. else if (ret.code == '7') {
  196. ret.code = Curses.getch();
  197. //Console.put(ret.code, " ");
  198. if (ret.code == '~') {
  199. ret.code = Key.F6;
  200. }
  201. else if (ret.code == ';') {
  202. ret.code = Curses.getch();
  203. //Console.put(ret.code, " ");
  204. if (ret.code == '2') {
  205. ret.shift = true;
  206. ret.code = Key.F6;
  207. }
  208. }
  209. }
  210. else if (ret.code == ';') {
  211. // Arrow Keys
  212. ret.code = Curses.getch();
  213. //Console.put(ret.code, " ");
  214. getModifiers(ret);
  215. //Console.put(ret.code, " ");
  216. if (ret.code == 'A') {
  217. ret.code = Key.Up;
  218. }
  219. else if (ret.code == 'B') {
  220. ret.code = Key.Down;
  221. }
  222. else if (ret.code == 'C') {
  223. ret.code = Key.Right;
  224. }
  225. else if (ret.code == 'D') {
  226. ret.code = Key.Left;
  227. }
  228. }
  229. else {
  230. ret.code = Curses.getch();
  231. //Console.put(ret.code, " ");
  232. }
  233. }
  234. else if (ret.code == '2') {
  235. ret.code = Curses.getch();
  236. //Console.put(ret.code, " ");
  237. if (ret.code == '~') {
  238. }
  239. else if (ret.code == ';') {
  240. // Alt + Insert
  241. ret.code = Curses.getch();
  242. //Console.put(ret.code, " ");
  243. getModifiers(ret);
  244. //Console.put(ret.code, " ");
  245. if (ret.code == '~') {
  246. ret.code = Key.Insert;
  247. }
  248. }
  249. }
  250. else if (ret.code == '3') {
  251. ret.code = Curses.getch();
  252. //Console.put(ret.code, " ");
  253. }
  254. else if (ret.code == '4') {
  255. ret.code = Curses.getch();
  256. //Console.put(ret.code, " ");
  257. }
  258. else if (ret.code == '5') {
  259. ret.code = Curses.getch();
  260. //Console.put(ret.code, " ");
  261. }
  262. else if (ret.code == '6') {
  263. ret.code = Curses.getch();
  264. //Console.put(ret.code, " ");
  265. }
  266. else {
  267. }
  268. }
  269. else if (ret.code == 'O') {
  270. ret.code = Curses.getch();
  271. // F1, F2, F3, F4
  272. if (ret.code == '1') {
  273. ret.code = Curses.getch();
  274. //Console.put(ret.code, " ");
  275. if (ret.code == ';') {
  276. ret.code = Curses.getch();
  277. //Console.put(ret.code, " ");
  278. getModifiers(ret);
  279. //Console.put(ret.code, " ");
  280. if (ret.code == 'P') {
  281. ret.code = Key.F1;
  282. }
  283. else if (ret.code == 'Q') {
  284. ret.code = Key.F2;
  285. }
  286. else if (ret.code == 'R') {
  287. ret.code = Key.F3;
  288. }
  289. else if (ret.code == 'S') {
  290. ret.code = Key.F4;
  291. }
  292. }
  293. }
  294. else if (ret.code == 'H') {
  295. ret.code = Key.Home;
  296. }
  297. else if (ret.code == 'F') {
  298. ret.code = Key.End;
  299. }
  300. else if (ret.code == 0x80) {
  301. ret.code = Key.F1;
  302. }
  303. else if (ret.code == 0x81) {
  304. ret.code = Key.F2;
  305. }
  306. else if (ret.code == 0x82) {
  307. ret.code = Key.F3;
  308. }
  309. else if (ret.code == 0x83) {
  310. ret.code = Key.F4;
  311. }
  312. }
  313. else {
  314. // Alt + Char
  315. ret.alt = true;
  316. consoleTranslateKey(ret);
  317. }
  318. //Curses.move(1,0);
  319. //Console.put(" ");
  320. //Curses.move(1,0);
  321. //Console.put("alt: ", ret.alt, " ctrl: ", ret.ctrl, " shift: ", ret.shift, " code: ", ret.code);
  322. return ret;
  323. }
  324. void getModifiers(ref Key key) {
  325. if (key.code == '2' || key.code == '4' || key.code == '6' || key.code == '8') {
  326. key.shift = true;
  327. }
  328. if (key.code == '3' || key.code == '4' || key.code == '7' || key.code == '8') {
  329. key.alt = true;
  330. }
  331. if (key.code == '5' || key.code == '6' || key.code == '7' || key.code == '8') {
  332. key.ctrl = true;
  333. }
  334. if (key.shift || key.alt || key.ctrl) {
  335. key.code = Curses.getch();
  336. }
  337. }
  338. void consoleTranslateKey(ref Key ky)
  339. {
  340. switch(ky.code) {
  341. case '~':
  342. case '!':
  343. case '@':
  344. case '#':
  345. case '$':
  346. case '%':
  347. case '^':
  348. case '&':
  349. case '*':
  350. case '(':
  351. case ')':
  352. case '_':
  353. case '+':
  354. case '{':
  355. case '}':
  356. case ':':
  357. case '"':
  358. case '<':
  359. case '>':
  360. case '|':
  361. case '?':
  362. ky.shift = true;
  363. break;
  364. default:
  365. if (ky.code >= 'A' && ky.code <= 'Z') {
  366. ky.shift = true;
  367. }
  368. else if (ky.code >= '0' && ky.code <= '9') {
  369. ky.shift = false;
  370. }
  371. break;
  372. }
  373. ky.code = keyTranslation[ky.code];
  374. }
  375. uint keyTranslation[Curses.KEY_MAX] = [
  376. ' ': Key.Space,
  377. '\n': Key.Return,
  378. '\r': Key.Return,
  379. '\t': Key.Tab,
  380. '\b': Key.Backspace,
  381. 127: Key.Backspace,
  382. 'a': Key.A,
  383. 'b': Key.B,
  384. 'c': Key.C,
  385. 'd': Key.D,
  386. 'e': Key.E,
  387. 'f': Key.F,
  388. 'g': Key.G,
  389. 'h': Key.H,
  390. 'i': Key.I,
  391. 'j': Key.J,
  392. 'k': Key.K,
  393. 'l': Key.L,
  394. 'm': Key.M,
  395. 'n': Key.N,
  396. 'o': Key.O,
  397. 'p': Key.P,
  398. 'q': Key.Q,
  399. 'r': Key.R,
  400. 's': Key.S,
  401. 't': Key.T,
  402. 'u': Key.U,
  403. 'v': Key.V,
  404. 'w': Key.W,
  405. 'x': Key.X,
  406. 'y': Key.Y,
  407. 'z': Key.Z,
  408. 'A': Key.A,
  409. 'B': Key.B,
  410. 'C': Key.C,
  411. 'D': Key.D,
  412. 'E': Key.E,
  413. 'F': Key.F,
  414. 'G': Key.G,
  415. 'H': Key.H,
  416. 'I': Key.I,
  417. 'J': Key.J,
  418. 'K': Key.K,
  419. 'L': Key.L,
  420. 'M': Key.M,
  421. 'N': Key.N,
  422. 'O': Key.O,
  423. 'P': Key.P,
  424. 'Q': Key.Q,
  425. 'R': Key.R,
  426. 'S': Key.S,
  427. 'T': Key.T,
  428. 'U': Key.U,
  429. 'V': Key.V,
  430. 'W': Key.W,
  431. 'X': Key.X,
  432. 'Y': Key.Y,
  433. 'Z': Key.Z,
  434. '0': Key.Zero,
  435. '1': Key.One,
  436. '2': Key.Two,
  437. '3': Key.Three,
  438. '4': Key.Four,
  439. '5': Key.Five,
  440. '6': Key.Six,
  441. '7': Key.Seven,
  442. '8': Key.Eight,
  443. '9': Key.Nine,
  444. '`': Key.SingleQuote,
  445. '~': Key.SingleQuote,
  446. '!': Key.One,
  447. '@': Key.Two,
  448. '#': Key.Three,
  449. '$': Key.Four,
  450. '%': Key.Five,
  451. '^': Key.Six,
  452. '&': Key.Seven,
  453. '*': Key.Eight,
  454. '(': Key.Nine,
  455. ')': Key.Zero,
  456. '-': Key.Minus,
  457. '_': Key.Minus,
  458. '=': Key.Equals,
  459. '+': Key.Equals,
  460. '[': Key.LeftBracket,
  461. '{': Key.LeftBracket,
  462. ']': Key.RightBracket,
  463. '}': Key.RightBracket,
  464. ';': Key.Semicolon,
  465. ':': Key.Semicolon,
  466. '\'': Key.Quote,
  467. '"': Key.Quote,
  468. ',': Key.Comma,
  469. '<': Key.Comma,
  470. '>': Key.Period,
  471. '.': Key.Period,
  472. '/': Key.Foreslash,
  473. '?': Key.Foreslash,
  474. '\\': Key.Backslash,
  475. '|': Key.Backslash,
  476. Curses.KEY_DOWN: Key.Down,
  477. Curses.KEY_UP: Key.Up,
  478. Curses.KEY_LEFT: Key.Left,
  479. Curses.KEY_RIGHT: Key.Right,
  480. Curses.KEY_HOME: Key.Home,
  481. Curses.KEY_BACKSPACE: Key.Backspace,
  482. Curses.KEY_DC: Key.Delete,
  483. Curses.KEY_F1: Key.F1,
  484. Curses.KEY_F2: Key.F2,
  485. Curses.KEY_F3: Key.F3,
  486. Curses.KEY_F4: Key.F4,
  487. Curses.KEY_F5: Key.F5,
  488. Curses.KEY_F6: Key.F6,
  489. Curses.KEY_F7: Key.F7,
  490. Curses.KEY_F8: Key.F8,
  491. Curses.KEY_F9: Key.F9,
  492. Curses.KEY_F10: Key.F10,
  493. Curses.KEY_F11: Key.F11,
  494. Curses.KEY_F12: Key.F12,
  495. Curses.KEY_F13: Key.F13,
  496. Curses.KEY_F14: Key.F14,
  497. Curses.KEY_F15: Key.F15,
  498. Curses.KEY_F16: Key.F16,
  499. Curses.KEY_NPAGE: Key.PageDown,
  500. Curses.KEY_PPAGE: Key.PageUp,
  501. Curses.KEY_ENTER: Key.Return,
  502. Curses.KEY_END: Key.End
  503. ];
  504. struct winsize {
  505. ushort ws_row;
  506. ushort ws_col;
  507. ushort ws_xpixel;
  508. ushort ws_ypixel;
  509. }
  510. //window size
  511. int m_width;
  512. int m_height;
  513. bool m_winsize_state;
  514. const auto TIOCGWINSZ = 0x5413;
  515. const auto SIGWINCH = 28;
  516. //position tracking
  517. int m_x;
  518. int m_y;
  519. winsize m_winsize_saved;
  520. winsize m_winsize_working;
  521. termios m_term_info_saved;
  522. termios m_term_info_working;
  523. //signal handler for terminal Size
  524. extern(C) void close_sig_handler(int signal) {
  525. // Djehuty.end(0);
  526. // for(int i=0; i<256; i++) {
  527. // printf("\x1B[48;5;%dma", i);
  528. // }
  529. ConsoleUninit();
  530. exit(0);
  531. }
  532. extern(C) void size_sig_handler(int signal) {
  533. ioctl(STDIN, TIOCGWINSZ, &m_winsize_working);
  534. if (m_width != m_winsize_working.ws_col || m_height != m_winsize_working.ws_row) {
  535. m_width = m_winsize_working.ws_col;
  536. m_height = m_winsize_working.ws_row;
  537. while (m_x >= m_width)
  538. {
  539. m_y++;
  540. m_x -= m_width;
  541. }
  542. if (m_y >= m_height) { m_y = m_height-1; }
  543. if (m_x < 0) { m_x = 0; }
  544. if (m_y < 0) { m_y = 0; }
  545. //reset (this will be retained when program exits)
  546. m_winsize_saved = m_winsize_working;
  547. //the window resized through the users actions, not through the class' resize()
  548. //therefore don't change it back to anything on exit
  549. m_winsize_state = false;
  550. }
  551. //fire Size event
  552. CuiApplication app = cast(CuiApplication)Djehuty.app;
  553. app.window.onResize();
  554. }
  555. void ConsoleInit() {
  556. printf("\x1B7");
  557. setlocale(LC_ALL, "");
  558. setlocale(LC_CTYPE, "");
  559. // setvbuf (stdout, null, _IONBF, 0);
  560. setlocale(LC_ALL, "");
  561. setlocale(LC_CTYPE, "");
  562. }
  563. void ConsoleUninit() {
  564. printf("\x1B[0m");
  565. }
  566. void ConsoleClear() {
  567. if (ApplicationController.instance.usingCurses) {
  568. Curses.clear();
  569. Curses.refresh();
  570. }
  571. else {
  572. printf("\x1B[2J\x1B[0;0H");
  573. }
  574. }
  575. void ConsoleSetRelative(int x, int y) {
  576. int newx;
  577. int newy;
  578. Curses.getyx(Curses.stdscr, m_y, m_x);
  579. newx = m_x + x;
  580. newy = m_y + y;
  581. Curses.move(newy, newx);
  582. }
  583. void ConsoleGetPosition(uint* x, uint* y) {
  584. Curses.getyx(Curses.stdscr, m_y, m_x);
  585. *x = m_x;
  586. *y = m_y;
  587. }
  588. void ConsoleSetPosition(uint x, uint y) {
  589. if (x >= m_width) { x = m_width-1; }
  590. if (y >= m_height) { y = m_height-1; }
  591. if (x < 0) { x = 0; }
  592. if (y < 0) { y = 0; }
  593. Curses.move(y,x);
  594. }
  595. void ConsoleHideCaret() {
  596. printf("\x1B[?25l");
  597. Curses.curs_set(0);
  598. }
  599. void ConsoleShowCaret() {
  600. printf("\x1B[?25h");
  601. Curses.curs_set(1);
  602. }
  603. void ConsoleSetHome() {
  604. if (ApplicationController.instance.usingCurses) {
  605. Curses.getyx(Curses.stdscr, m_y, m_x);
  606. m_x = 0;
  607. Curses.move(m_y, m_x);
  608. }
  609. else {
  610. printf("\x1B[0G");
  611. }
  612. }
  613. void ConsolePutString(char[] chrs) {
  614. chrs ~= '\0';
  615. Curses.getyx(Curses.stdscr, m_y, m_x);
  616. char[] utf8 = chrs;
  617. bool goBackOneLine = false;
  618. if (ApplicationController.instance.usingCurses) {
  619. for (uint i; i < utf8.length; i++) {
  620. if (utf8[i] == '\r' || utf8[i] == '\n' || utf8[i] == '\0') {
  621. if (i + m_x >= m_width) {
  622. i = m_width - m_x;
  623. goBackOneLine = true;
  624. }
  625. utf8[i] = '\0';
  626. Curses.wprintw(Curses.stdscr, "%s", &utf8[0]);
  627. if (goBackOneLine) {
  628. ConsoleSetPosition(m_width - 1, m_y);
  629. }
  630. Curses.refresh();
  631. return;
  632. }
  633. }
  634. }
  635. else {
  636. printf("%s", utf8.ptr);
  637. }
  638. }
  639. void ConsolePutChar(dchar chr) {
  640. dchar[] chrarray = [ chr, '\0' ];
  641. char[] chrs = Unicode.toUtf8(chrarray);
  642. if (ApplicationController.instance.usingCurses) {
  643. if (chr == '\r' || chr == '\n') {
  644. ConsoleSetRelative(0, 1);
  645. ConsoleSetHome();
  646. }
  647. else {
  648. Curses.wprintw(Curses.stdscr, "%s", chrs.ptr);
  649. Curses.refresh();
  650. }
  651. }
  652. else {
  653. printf("%s", chrs.ptr);
  654. }
  655. }
  656. void ConsoleGetSize(out uint width, out uint height) {
  657. Curses.getmaxyx(Curses.stdscr, m_height, m_width);
  658. width = m_width;
  659. height = m_height;
  660. }
  661. void ConsoleGetChar(out dchar chr, out uint code) {
  662. }