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