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