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