blob: 1744788da81ba422fa8f121a43aaf231e9329c3a [file] [log] [blame]
that1964d192016-01-07 00:41:03 +01001/*
2 Copyright 2016 _that/TeamWin
3 This file is part of TWRP/TeamWin Recovery Project.
4
5 TWRP is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 TWRP is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with TWRP. If not, see <http://www.gnu.org/licenses/>.
17*/
18
19// terminal.cpp - GUITerminal object
20
21#include <stdio.h>
22#include <stdlib.h>
23#include <string.h>
24#include <fcntl.h>
25#include <unistd.h>
26#include <termio.h>
27
28#include <string>
29#include <cctype>
30#include <linux/input.h>
thata17f1752016-01-11 01:07:28 +010031#include <sys/wait.h>
that1964d192016-01-07 00:41:03 +010032
33extern "C" {
34#include "../twcommon.h"
that1964d192016-01-07 00:41:03 +010035}
Ethan Yonkerfbb43532015-12-28 21:54:50 +010036#include "../minuitwrp/minui.h"
that1964d192016-01-07 00:41:03 +010037
38#include "rapidxml.hpp"
39#include "objects.hpp"
40
41#if 0
42#define debug_printf printf
43#else
44#define debug_printf(...)
45#endif
46
47extern int g_pty_fd; // in gui.cpp where the select is
48
49/*
50Pseudoterminal handler.
51*/
52class Pseudoterminal
53{
54public:
55 Pseudoterminal() : fdMaster(0), pid(0)
56 {
57 }
58
59 bool started() const { return pid > 0; }
60
61 bool start()
62 {
63 fdMaster = getpt();
64 if (fdMaster < 0) {
65 LOGERR("Error %d on getpt()\n", errno);
66 return false;
67 }
68
69 if (unlockpt(fdMaster) != 0) {
70 LOGERR("Error %d on unlockpt()\n", errno);
71 return false;
72 }
73
74 pid = fork();
75 if (pid < 0) {
76 LOGERR("fork failed for pty, error %d\n", errno);
77 close(fdMaster);
78 pid = 0;
79 return false;
80 }
81 else if (pid) {
82 // child started, now someone needs to periodically read from fdMaster
83 // and write it to the terminal
84 // this currently works through gui.cpp calling terminal_pty_read below
85 g_pty_fd = fdMaster;
86 return true;
87 }
88 else {
89 int fdSlave = open(ptsname(fdMaster), O_RDWR);
90 close(fdMaster);
91 runSlave(fdSlave);
92 }
93 // we can't get here
94 LOGERR("impossible error in pty\n");
95 return false;
96 }
97
98 void runSlave(int fdSlave)
99 {
100 dup2(fdSlave, 0); // PTY becomes standard input (0)
101 dup2(fdSlave, 1); // PTY becomes standard output (1)
102 dup2(fdSlave, 2); // PTY becomes standard error (2)
103
104 // Now the original file descriptor is useless
105 close(fdSlave);
106
107 // Make the current process a new session leader
108 if (setsid() == (pid_t)-1)
109 LOGERR("setsid failed: %d\n", errno);
110
111 // As the child is a session leader, set the controlling terminal to be the slave side of the PTY
112 // (Mandatory for programs like the shell to make them manage correctly their outputs)
113 ioctl(0, TIOCSCTTY, 1);
114
115 execl("/sbin/sh", "sh", NULL);
116 _exit(127);
117 }
118
119 int read(char* buffer, size_t size)
120 {
121 if (!started()) {
122 LOGERR("someone tried to read from pty, but it was not started\n");
123 return -1;
124 }
125 int rc = ::read(fdMaster, buffer, size);
126 debug_printf("pty read: %d bytes\n", rc);
127 if (rc < 0) {
thata17f1752016-01-11 01:07:28 +0100128 // assume child has died (usual errno when shell exits seems to be EIO == 5)
129 if (errno != EIO)
130 LOGERR("pty read failed: %d\n", errno);
131 stop();
that1964d192016-01-07 00:41:03 +0100132 }
133 return rc;
134 }
135
136 int write(const char* buffer, size_t size)
137 {
138 if (!started()) {
139 LOGERR("someone tried to write to pty, but it was not started\n");
140 return -1;
141 }
142 int rc = ::write(fdMaster, buffer, size);
143 debug_printf("pty write: %d bytes -> %d\n", size, rc);
144 if (rc < 0) {
thata17f1752016-01-11 01:07:28 +0100145 LOGERR("pty write failed: %d\n", errno);
that1964d192016-01-07 00:41:03 +0100146 // assume child has died
thata17f1752016-01-11 01:07:28 +0100147 stop();
that1964d192016-01-07 00:41:03 +0100148 }
149 return rc;
150 }
151
152 template<size_t n>
153 inline int write(const char (&literal)[n])
154 {
155 return write(literal, n-1);
156 }
157
158 void resize(int xChars, int yChars, int w, int h)
159 {
160 struct winsize ws;
161 ws.ws_row = yChars;
162 ws.ws_col = xChars;
163 ws.ws_xpixel = w;
164 ws.ws_ypixel = h;
165 if (ioctl(fdMaster, TIOCSWINSZ, &ws) < 0)
166 LOGERR("failed to set window size, error %d\n", errno);
167 }
168
thata17f1752016-01-11 01:07:28 +0100169 void stop()
170 {
171 if (!started()) {
172 LOGERR("someone tried to stop pty, but it was not started\n");
173 return;
174 }
175 close(fdMaster);
176 g_pty_fd = fdMaster = -1;
177 int status;
178 waitpid(pid, &status, WNOHANG); // avoid zombies but don't hang if the child is still alive and we got here due to some error
179 pid = 0;
180 }
181
that1964d192016-01-07 00:41:03 +0100182private:
183 int fdMaster;
thata17f1752016-01-11 01:07:28 +0100184 pid_t pid;
that1964d192016-01-07 00:41:03 +0100185};
186
187// UTF-8 decoder
188// Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
189// See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
190
191const uint32_t UTF8_ACCEPT = 0;
192const uint32_t UTF8_REJECT = 1;
193
194static const uint8_t utf8d[] = {
195 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f
196 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f
197 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 40..5f
198 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 60..7f
199 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, // 80..9f
200 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, // a0..bf
201 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // c0..df
202 0xa,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x4,0x3,0x3, // e0..ef
203 0xb,0x6,0x6,0x6,0x5,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8,0x8, // f0..ff
204 0x0,0x1,0x2,0x3,0x5,0x8,0x7,0x1,0x1,0x1,0x4,0x6,0x1,0x1,0x1,0x1, // s0..s0
205 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,1, // s1..s2
206 1,2,1,1,1,1,1,2,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1, // s3..s4
207 1,2,1,1,1,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,3,1,1,1,1,1,1, // s5..s6
208 1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8
209};
210
211uint32_t inline utf8decode(uint32_t* state, uint32_t* codep, uint32_t byte)
212{
213 uint32_t type = utf8d[byte];
214
215 *codep = (*state != UTF8_ACCEPT) ?
216 (byte & 0x3fu) | (*codep << 6) :
217 (0xff >> type) & (byte);
218
219 *state = utf8d[256 + *state*16 + type];
220 return *state;
221}
222// end of UTF-8 decoder
223
224// Append a UTF-8 codepoint to string s
225size_t utf8add(std::string& s, uint32_t cp)
226{
227 if (cp < 0x7f) {
228 s += cp;
229 return 1;
230 }
231 else if (cp < 0x7ff) {
232 s += (0xc0 | (cp >> 6));
233 s += (0x80 | (cp & 0x3f));
234 return 2;
235 }
236 else if (cp < 0xffff) {
237 s += (0xe0 | (cp >> 12));
238 s += (0x80 | ((cp >> 6) & 0x3f));
239 s += (0x80 | (cp & 0x3f));
240 return 3;
241 }
242 else if (cp < 0x1fffff) {
243 s += (0xf0 | (cp >> 18));
244 s += (0x80 | ((cp >> 12) & 0x3f));
245 s += (0x80 | ((cp >> 6) & 0x3f));
246 s += (0x80 | (cp & 0x3f));
247 return 4;
248 }
249 return 0;
250}
251
252/*
253TerminalEngine is the terminal back-end, dealing with the text buffer and attributes
254and with communicating with the pty.
255It does not care about visual things like rendering, fonts, windows etc.
256The idea is that 0 to n GUITerminal instances (e.g. on different pages) can connect
257to one TerminalEngine to interact with the terminal, and that the TerminalEngine
258survives things like page changes or even theme reloads.
259*/
260class TerminalEngine
261{
262public:
263#if 0 // later
264 struct Attributes
265 {
266 COLOR fgcolor; // TODO: what about palette?
267 COLOR bgcolor;
268 // could add bold, underline, blink, etc.
269 };
270
271 struct AttributeRange
272 {
273 size_t start; // start position inside text (in bytes)
274 Attributes a;
275 };
276#endif
277 typedef uint32_t CodePoint; // Unicode code point
278
279 // A line of text, optimized for rendering and storage in the buffer
280 struct Line
281 {
282 std::string text; // in UTF-8 format
283// std::vector<AttributeRange> attrs;
284 Line() {}
285 size_t utf8forward(size_t start) const
286 {
287 if (start >= text.size())
288 return start;
289 uint32_t u8state = 0, u8cp = 0;
290 size_t i = start;
291 uint32_t rc;
292 do {
293 rc = utf8decode(&u8state, &u8cp, (unsigned char)text[i]);
294 ++i;
295 } while (rc != UTF8_ACCEPT && rc != UTF8_REJECT && i < text.size());
296 return i;
297 }
298
299 std::string substr(size_t start, size_t n) const
300 {
301 size_t i = 0;
302 for (; start && i < text.size(); i = utf8forward(i))
303 --start;
304 size_t s = i;
305 for (; n && i < text.size(); i = utf8forward(i))
306 --n;
307 return text.substr(s, i - s);
308 }
309 size_t length() const
310 {
311 size_t n = 0;
312 for (size_t i = 0; i < text.size(); i = utf8forward(i))
313 ++n;
314 return n;
315 }
316 };
317
318 // A single character cell with a Unicode code point
319 struct Cell
320 {
321 Cell() : cp(' ') {}
322 Cell(CodePoint cp) : cp(cp) {}
323 CodePoint cp;
324// Attributes a;
325 };
326
327 // A line of text, optimized for editing single characters
328 struct UnpackedLine
329 {
330 std::vector<Cell> cells;
331 void eraseFrom(size_t x)
332 {
333 if (cells.size() > x)
334 cells.erase(cells.begin() + x, cells.end());
335 }
336
337 void eraseTo(size_t x)
338 {
339 if (x > 0)
340 cells.erase(cells.begin(), cells.begin() + x);
341 }
342 };
343
344 TerminalEngine()
345 {
346 // the default size will be overwritten by the GUI window when the size is known
347 width = 40;
348 height = 10;
349
350 clear();
351 updateCounter = 0;
352 state = kStateGround;
353 utf8state = utf8codepoint = 0;
354 }
355
356 void setSize(int xChars, int yChars, int w, int h)
357 {
358 width = xChars;
359 height = yChars;
360 if (pty.started())
361 pty.resize(width, height, w, h);
362 debug_printf("setSize: %d*%d chars, %d*%d pixels\n", xChars, yChars, w, h);
363 }
364
365 void initPty()
366 {
367 if (!pty.started())
368 {
369 pty.start();
370 pty.resize(width, height, 0, 0);
371 }
372 }
373
374 void readPty()
375 {
376 char buffer[1024];
377 int rc = pty.read(buffer, sizeof(buffer));
378 debug_printf("readPty: %d bytes\n", rc);
379 if (rc < 0)
380 output("\r\nChild process exited.\r\n"); // TODO: maybe exit terminal here
381 else
382 for (int i = 0; i < rc; ++i)
383 output(buffer[i]);
384 }
385
386 void clear()
387 {
388 cursorX = cursorY = 0;
389 lines.clear();
390 setY(0);
391 unpackLine(0);
that0d0d5222017-03-14 20:58:08 +0100392 linewrap = false;
that1964d192016-01-07 00:41:03 +0100393 ++updateCounter;
394 }
395
396 void output(const char *buf)
397 {
398 for (const char* p = buf; *p; ++p)
399 output(*p);
400 }
401
402 void output(const char ch)
403 {
404 char debug[2]; debug[0] = ch; debug[1] = 0;
405 debug_printf("output: %d %s\n", (int)ch, (ch >= ' ' && ch < 127) ? debug : ch == 27 ? "esc" : "");
406 if (ch < 32) {
407 // always process control chars, even after incomplete UTF-8 fragments
408 processC0(ch);
409 if (utf8state != UTF8_ACCEPT)
410 {
411 debug_printf("Terminal: incomplete UTF-8 fragment before control char ignored, codepoint=%u ch=%d\n", utf8codepoint, (int)ch);
412 utf8state = UTF8_ACCEPT;
413 }
414 return;
415 }
416 uint32_t rc = utf8decode(&utf8state, &utf8codepoint, (unsigned char)ch);
417 if (rc == UTF8_ACCEPT)
418 processCodePoint(utf8codepoint);
419 else if (rc == UTF8_REJECT) {
420 debug_printf("Terminal: invalid UTF-8 sequence ignored, codepoint=%u ch=%d\n", utf8codepoint, (int)ch);
421 utf8state = UTF8_ACCEPT;
422 }
423 // else we need to read more bytes to assemble a codepoint
424 }
425
426 bool inputChar(int ch)
427 {
428 debug_printf("inputChar: %d\n", ch);
that1964d192016-01-07 00:41:03 +0100429 initPty(); // reinit just in case it died before
430 // encode the char as UTF-8 and send it to the pty
431 std::string c;
432 utf8add(c, (uint32_t)ch);
433 pty.write(c.c_str(), c.size());
434 return true;
435 }
436
437 bool inputKey(int key)
438 {
439 debug_printf("inputKey: %d\n", key);
440 switch (key)
441 {
442 case KEY_UP: pty.write("\e[A"); break;
443 case KEY_DOWN: pty.write("\e[B"); break;
444 case KEY_RIGHT: pty.write("\e[C"); break;
445 case KEY_LEFT: pty.write("\e[D"); break;
446 case KEY_HOME: pty.write("\eOH"); break;
447 case KEY_END: pty.write("\eOF"); break;
448 case KEY_INSERT: pty.write("\e[2~"); break;
449 case KEY_DELETE: pty.write("\e[3~"); break;
450 case KEY_PAGEUP: pty.write("\e[5~"); break;
451 case KEY_PAGEDOWN: pty.write("\e[6~"); break;
452 // TODO: other keys
453 default:
454 return false;
455 }
456 return true;
457 }
458
459 size_t getLinesCount() const { return lines.size(); }
460 const Line& getLine(size_t n) { if (unpackedY == n) packLine(); return lines[n]; }
461 int getCursorX() const { return cursorX; }
462 int getCursorY() const { return cursorY; }
463 int getUpdateCounter() const { return updateCounter; }
464
465 void setX(int x)
466 {
467 x = min(width, max(x, 0));
468 cursorX = x;
that0d0d5222017-03-14 20:58:08 +0100469 linewrap = false;
that1964d192016-01-07 00:41:03 +0100470 ++updateCounter;
471 }
472
473 void setY(int y)
474 {
475 //y = min(height, max(y, 0));
476 y = max(y, 0);
477 cursorY = y;
that0d0d5222017-03-14 20:58:08 +0100478 linewrap = false;
that1964d192016-01-07 00:41:03 +0100479 while (lines.size() <= (size_t) y)
480 lines.push_back(Line());
481 ++updateCounter;
482 }
483
484 void up(int n = 1) { setY(cursorY - n); }
485 void down(int n = 1) { setY(cursorY + n); }
486 void left(int n = 1) { setX(cursorX - n); }
487 void right(int n = 1) { setX(cursorX + n); }
488
489private:
490 void packLine()
491 {
492 std::string& s = lines[unpackedY].text;
493 s.clear();
494 for (size_t i = 0; i < unpackedLine.cells.size(); ++i) {
495 Cell& c = unpackedLine.cells[i];
496 utf8add(s, c.cp);
497 // later: if attributes changed, add attributes
498 }
499 }
500
501 void unpackLine(size_t y)
502 {
503 uint32_t u8state = 0, u8cp = 0;
504 std::string& s = lines[y].text;
505 unpackedLine.cells.clear();
Matt Mowera8a89d12016-12-30 18:10:37 -0600506 for (size_t i = 0; i < s.size(); ++i) {
that1964d192016-01-07 00:41:03 +0100507 uint32_t rc = utf8decode(&u8state, &u8cp, (unsigned char)s[i]);
508 if (rc == UTF8_ACCEPT)
509 unpackedLine.cells.push_back(Cell(u8cp));
510 }
511 if (unpackedLine.cells.size() < (size_t)width)
512 unpackedLine.cells.resize(width);
513 unpackedY = y;
514 }
515
516 void ensureUnpacked(size_t y)
517 {
518 if (unpackedY != y)
519 {
520 packLine();
521 unpackLine(y);
522 }
523 }
524
525 void processC0(char ch)
526 {
527 switch (ch)
528 {
529 case 7: // BEL
530 DataManager::Vibrate("tw_button_vibrate");
531 break;
532 case 8: // BS
533 left();
534 break;
535 case 9: // HT
536 // TODO: this might be totally wrong
537 right();
538 while (cursorX % 8 != 0 && cursorX < width)
539 right();
540 break;
541 case 10: // LF
542 case 11: // VT
543 case 12: // FF
544 down();
545 break;
546 case 13: // CR
547 setX(0);
548 break;
549 case 24: // CAN
550 case 26: // SUB
551 state = kStateGround;
552 ctlseq.clear();
553 break;
554 case 27: // ESC
555 state = kStateEsc;
556 ctlseq.clear();
557 break;
558 }
559 }
560
561 void processCodePoint(CodePoint cp)
562 {
563 ++updateCounter;
564 debug_printf("codepoint: %u\n", cp);
565 if (cp == 0x9b) // CSI
566 {
567 state = kStateCsi;
568 ctlseq.clear();
569 return;
570 }
571 switch (state)
572 {
573 case kStateGround:
574 processChar(cp);
575 break;
576 case kStateEsc:
577 processEsc(cp);
578 break;
579 case kStateCsi:
580 processControlSequence(cp);
581 break;
582 }
583 }
584
585 void processChar(CodePoint cp)
586 {
that0d0d5222017-03-14 20:58:08 +0100587 if (linewrap) {
588 down();
589 setX(0);
590 }
that1964d192016-01-07 00:41:03 +0100591 ensureUnpacked(cursorY);
592 // extend unpackedLine if needed, write ch into cell
593 if (unpackedLine.cells.size() <= (size_t)cursorX)
594 unpackedLine.cells.resize(cursorX+1);
595 unpackedLine.cells[cursorX].cp = cp;
596
that0d0d5222017-03-14 20:58:08 +0100597 right(); // also bumps updateCounter
598
that1964d192016-01-07 00:41:03 +0100599 if (cursorX >= width)
that0d0d5222017-03-14 20:58:08 +0100600 linewrap = true;
that1964d192016-01-07 00:41:03 +0100601 }
602
603 void processEsc(CodePoint cp)
604 {
605 switch (cp) {
606 case 'c': // TODO: Reset
607 break;
608 case 'D': // Line feed
609 down();
610 break;
611 case 'E': // Newline
612 setX(0);
613 down();
614 break;
615 case '[': // CSI
616 state = kStateCsi;
617 ctlseq.clear();
618 break;
619 case ']': // TODO: OSC state
620 default:
621 state = kStateGround;
622 }
623 }
624
625 void processControlSequence(CodePoint cp)
626 {
627 if (cp >= 0x40 && cp <= 0x7e) {
628 ctlseq += cp;
629 execControlSequence(ctlseq);
630 ctlseq.clear();
631 state = kStateGround;
632 return;
633 }
634 if (isdigit(cp) || cp == ';' /* || (ch >= 0x3c && ch <= 0x3f) */) {
635 ctlseq += cp;
636 // state = kStateCsiParam;
637 return;
638 }
639 }
640
641 static int parseArg(std::string& s, int defaultvalue)
642 {
643 if (s.empty() || !isdigit(s[0]))
644 return defaultvalue;
645 int value = atoi(s.c_str());
646 size_t pos = s.find(';');
647 s.erase(0, pos != std::string::npos ? pos+1 : std::string::npos);
648 return value;
649 }
650
651 void execControlSequence(std::string ctlseq)
652 {
653 // assert(!ctlseq.empty());
654 if (ctlseq == "6n") {
655 // CPR - cursor position report
656 char answer[20];
657 sprintf(answer, "\e[%d;%dR", cursorY, cursorX);
658 pty.write(answer, strlen(answer));
659 return;
660 }
661 char f = *ctlseq.rbegin();
662 // if (f == '?') ... private mode
663 switch (f)
664 {
665 // case '@': // ICH - insert character
666 case 'A': // CUU - cursor up
667 up(parseArg(ctlseq, 1));
668 break;
669 case 'B': // CUD - cursor down
670 case 'e': // VPR - line position forward
671 down(parseArg(ctlseq, 1));
672 break;
673 case 'C': // CUF - cursor right
674 case 'a': // HPR - character position forward
675 right(parseArg(ctlseq, 1));
676 break;
677 case 'D': // CUB - cursor left
678 left(parseArg(ctlseq, 1));
679 break;
680 case 'E': // CNL - cursor next line
681 down(parseArg(ctlseq, 1));
682 setX(0);
683 break;
684 case 'F': // CPL - cursor preceding line
685 up(parseArg(ctlseq, 1));
686 setX(0);
687 break;
688 case 'G': // CHA - cursor character absolute
689 setX(parseArg(ctlseq, 1)-1);
690 break;
691 case 'H': // CUP - cursor position
692 // TODO: consider scrollback area
693 setY(parseArg(ctlseq, 1)-1);
694 setX(parseArg(ctlseq, 1)-1);
695 break;
696 case 'J': // ED - erase in page
697 {
698 int param = parseArg(ctlseq, 0);
699 ensureUnpacked(cursorY);
700 switch (param) {
701 default:
702 case 0:
703 unpackedLine.eraseFrom(cursorX);
704 if (lines.size() > (size_t)cursorY+1)
705 lines.erase(lines.begin() + cursorY+1, lines.end());
706 break;
707 case 1:
708 unpackedLine.eraseTo(cursorX);
709 if (cursorY > 0) {
710 lines.erase(lines.begin(), lines.begin() + cursorY-1);
711 cursorY = 0;
712 }
713 break;
714 case 2: // clear
715 case 3: // clear incl scrollback
716 clear();
717 break;
718 }
719 }
720 break;
721 case 'K': // EL - erase in line
722 {
723 int param = parseArg(ctlseq, 0);
724 ensureUnpacked(cursorY);
725 switch (param) {
726 default:
727 case 0:
728 unpackedLine.eraseFrom(cursorX);
729 break;
730 case 1:
731 unpackedLine.eraseTo(cursorX);
732 break;
733 case 2:
734 unpackedLine.cells.clear();
735 break;
736 }
737 }
738 break;
739 // case 'L': // IL - insert line
740
741 default:
742 debug_printf("unknown ctlseq: '%s'\n", ctlseq.c_str());
743 break;
744 }
745 }
746
747private:
748 int cursorX, cursorY; // 0-based, char based. TODO: decide how to handle scrollback
that0d0d5222017-03-14 20:58:08 +0100749 bool linewrap; // true to put next character into next line
that1964d192016-01-07 00:41:03 +0100750 int width, height; // window size in chars
751 std::vector<Line> lines; // the text buffer
752 UnpackedLine unpackedLine; // current line for editing
753 size_t unpackedY; // number of current line
754 int updateCounter; // changes whenever terminal could require redraw
755
756 Pseudoterminal pty;
757 enum { kStateGround, kStateEsc, kStateCsi } state;
758
759 // for accumulating a full UTF-8 character from individual bytes
760 uint32_t utf8state;
761 uint32_t utf8codepoint;
762
763 // for accumulating a control sequence after receiving CSI
764 std::string ctlseq;
765};
766
767// The one and only terminal engine for now
768TerminalEngine gEngine;
769
770void terminal_pty_read()
771{
772 gEngine.readPty();
773}
774
775
776GUITerminal::GUITerminal(xml_node<>* node) : GUIScrollList(node)
777{
778 allowSelection = false; // terminal doesn't support list item selections
779 lastCondition = false;
780
781 if (!node) {
Matt Mowera8a89d12016-12-30 18:10:37 -0600782 mRenderX = 0;
783 mRenderY = 0;
784 mRenderW = gr_fb_width();
785 mRenderH = gr_fb_height();
that1964d192016-01-07 00:41:03 +0100786 }
787
788 engine = &gEngine;
789 updateCounter = 0;
790}
791
792int GUITerminal::Update(void)
793{
Matt Mowera8a89d12016-12-30 18:10:37 -0600794 if (!isConditionTrue()) {
that1964d192016-01-07 00:41:03 +0100795 lastCondition = false;
796 return 0;
797 }
798
799 if (lastCondition == false) {
800 lastCondition = true;
801 // we're becoming visible, so we might need to resize the terminal content
802 InitAndResize();
803 }
804
805 if (updateCounter != engine->getUpdateCounter()) {
806 // try to keep the cursor in view
807 SetVisibleListLocation(engine->getCursorY());
808 updateCounter = engine->getUpdateCounter();
809 }
810
811 GUIScrollList::Update();
812
813 if (mUpdate) {
814 mUpdate = 0;
815 if (Render() == 0)
816 return 2;
817 }
818 return 0;
819}
820
821// NotifyTouch - Notify of a touch event
822// Return 0 on success, >0 to ignore remainder of touch, and <0 on error
823int GUITerminal::NotifyTouch(TOUCH_STATE state, int x, int y)
824{
Matt Mowera8a89d12016-12-30 18:10:37 -0600825 if (!isConditionTrue())
that1964d192016-01-07 00:41:03 +0100826 return -1;
827
828 // TODO: grab focus correctly
829 // TODO: fix focus handling in PageManager and GUIInput
830 SetInputFocus(1);
831 debug_printf("Terminal: SetInputFocus\n");
832 return GUIScrollList::NotifyTouch(state, x, y);
833 // TODO later: allow cursor positioning by touch (simulate mouse click?)
834 // http://stackoverflow.com/questions/5966903/how-to-get-mousemove-and-mouseclick-in-bash
835 // will likely not work with Busybox anyway
836}
837
838int GUITerminal::NotifyKey(int key, bool down)
839{
thatd4725ca2016-01-19 00:15:21 +0100840 if (!HasInputFocus)
841 return 1;
that1964d192016-01-07 00:41:03 +0100842 if (down)
843 if (engine->inputKey(key))
844 mUpdate = 1;
845 return 0;
846}
847
848// character input
849int GUITerminal::NotifyCharInput(int ch)
850{
851 if (engine->inputChar(ch))
852 mUpdate = 1;
853 return 0;
854}
855
856size_t GUITerminal::GetItemCount()
857{
858 return engine->getLinesCount();
859}
860
861void GUITerminal::RenderItem(size_t itemindex, int yPos, bool selected)
862{
863 const TerminalEngine::Line& line = engine->getLine(itemindex);
864
865 gr_color(mFontColor.red, mFontColor.green, mFontColor.blue, mFontColor.alpha);
866 // later: handle attributes here
867
868 // render text
869 const char* text = line.text.c_str();
870 gr_textEx_scaleW(mRenderX, yPos, text, mFont->GetResource(), mRenderW, TOP_LEFT, 0);
871
872 if (itemindex == (size_t) engine->getCursorY()) {
873 // render cursor
874 int cursorX = engine->getCursorX();
875 std::string leftOfCursor = line.substr(0, cursorX);
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100876 int x = gr_ttf_measureEx(leftOfCursor.c_str(), mFont->GetResource());
that1964d192016-01-07 00:41:03 +0100877 // note that this single character can be a UTF-8 sequence
878 std::string atCursor = (size_t)cursorX < line.length() ? line.substr(cursorX, 1) : " ";
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100879 int w = gr_ttf_measureEx(atCursor.c_str(), mFont->GetResource());
that1964d192016-01-07 00:41:03 +0100880 gr_color(mFontColor.red, mFontColor.green, mFontColor.blue, mFontColor.alpha);
881 gr_fill(mRenderX + x, yPos, w, actualItemHeight);
882 gr_color(mBackgroundColor.red, mBackgroundColor.green, mBackgroundColor.blue, mBackgroundColor.alpha);
883 gr_textEx_scaleW(mRenderX + x, yPos, atCursor.c_str(), mFont->GetResource(), mRenderW, TOP_LEFT, 0);
884 }
885}
886
887void GUITerminal::NotifySelect(size_t item_selected)
888{
889 // do nothing - terminal ignores selections
890}
891
892void GUITerminal::InitAndResize()
893{
894 // make sure the shell is started
895 engine->initPty();
896 // send window resize
Ethan Yonkerfbb43532015-12-28 21:54:50 +0100897 int charWidth = gr_ttf_measureEx("N", mFont->GetResource());
that1964d192016-01-07 00:41:03 +0100898 engine->setSize(mRenderW / charWidth, GetDisplayItemCount(), mRenderW, mRenderH);
899}
900
901void GUITerminal::SetPageFocus(int inFocus)
902{
Ethan Yonkerec0b4322016-03-31 14:04:14 -0500903 if (inFocus && isConditionTrue()) {
904 // TODO: grab focus correctly, this hack grabs focus and insists that the terminal be the focus regardless of other elements
905 // It's highly unlikely that there will be any other visible input elements on the page anyway...
906 SetInputFocus(1);
that1964d192016-01-07 00:41:03 +0100907 InitAndResize();
Ethan Yonkerec0b4322016-03-31 14:04:14 -0500908 }
that1964d192016-01-07 00:41:03 +0100909}