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