1/*
2 * Console.cpp - Mimicing the console driver
3 * Based on the console driver.
4 *
5 * Copyright 2005 Michael Lotz. All rights reserved.
6 * Distributed under the MIT License.
7 *
8 * Copyright 2005, Axel D��rfler, axeld@pinc-software.de. All rights reserved.
9 * Distributed under the terms of the MIT License.
10 *
11 * Copyright 2001-2002, Travis Geiselbrecht. All rights reserved.
12 * Distributed under the terms of the NewOS License.
13 */
14
15#include "Console.h"
16#include "ViewBuffer.h"
17#include <stdio.h>
18
19Console::Console(ViewBuffer *output)
20	:	fState(CONSOLE_STATE_NORMAL),
21		fOutput(output)
22{
23	fOutput->GetSize(&fColumns, &fLines);
24	fOutput->SetResizeCallback(&ResizeCallback, this);
25	ResetConsole();
26	GotoXY(0, 0);
27	SaveCursor(true);
28	fOutput->Clear(0x0f);
29}
30
31
32Console::~Console()
33{
34}
35
36
37void
38Console::ResetConsole()
39{
40	fAttr = 0x0f;
41	fScrollTop = 0;
42	fScrollBottom = fLines - 1;
43	fBrightAttr = true;
44	fReverseAttr = false;
45}
46
47
48void
49Console::ResizeCallback(int32 width, int32 height, void *data)
50{
51	Console *console = (Console *)data;
52
53	console->fColumns = width;
54	console->fLines = height;
55	console->SetScrollRegion(console->fScrollTop, height - 1);
56}
57
58
59void
60Console::SetScrollRegion(int top, int bottom)
61{
62	if (top < 0)
63		top = 0;
64	if (bottom >= fLines)
65		bottom = fLines - 1;
66	if (top > bottom)
67		return;
68
69	fScrollTop = top;
70	fScrollBottom = bottom;
71}
72
73
74void
75Console::ScrollUp()
76{
77	// see if cursor is outside of scroll region
78	if (fY < fScrollTop || fY > fScrollBottom)
79		return;
80
81	if (fY - fScrollTop > 1) {
82		// move the screen up one
83		fOutput->Blit(0, fScrollTop + 1, fColumns, fY - fScrollTop, 0, fScrollTop);
84	}
85
86	// clear the bottom line
87	fOutput->FillGlyph(0, fY, fColumns, 1, ' ', fAttr);
88}
89
90
91void
92Console::ScrollDown()
93{
94	// see if cursor is outside of scroll region
95	if (fY < fScrollTop || fY > fScrollBottom)
96		return;
97
98	if (fScrollBottom - fY > 1) {
99		// move the screen down one
100		fOutput->Blit(0, fY, fColumns, fScrollBottom - fY, 0, fY + 1);
101	}
102
103	// clear the top line
104	fOutput->FillGlyph(0, fY, fColumns, 1, ' ', fAttr);
105}
106
107
108void
109Console::LineFeed()
110{
111	if (fY == fScrollBottom) {
112 		// we hit the bottom of our scroll region
113 		ScrollUp();
114	} else if (fY < fScrollBottom) {
115		fY++;
116	}
117}
118
119
120void
121Console::RLineFeed()
122{
123	if (fY == fScrollTop) {
124 		// we hit the top of our scroll region
125 		ScrollDown();
126	} else if (fY > fScrollTop) {
127		fY--;
128	}
129}
130
131
132void
133Console::CariageReturn()
134{
135	fX = 0;
136}
137
138
139void
140Console::Delete()
141{
142	if (fX > 0) {
143		fX--;
144	} else if (fY > 0) {
145        fY--;
146        fX = fColumns - 1;
147    } else {
148        ScrollDown();
149        fY--;
150        fX = fColumns - 1;
151        return;
152    }
153
154	fOutput->PutGlyph(fX, fY, ' ', fAttr);
155}
156
157
158void
159Console::Tab()
160{
161	fX = (fX + TAB_SIZE) & ~TAB_MASK;
162	if (fX >= fColumns) {
163		fX -= fColumns;
164		LineFeed();
165	}
166}
167
168
169void
170Console::EraseLine(erase_line_mode mode)
171{
172	switch (mode) {
173		case LINE_ERASE_WHOLE:
174			fOutput->FillGlyph(0, fY, fColumns, 1, ' ', fAttr);
175			break;
176		case LINE_ERASE_LEFT:
177			fOutput->FillGlyph(0, fY, fX + 1, 1, ' ', fAttr);
178			break;
179		case LINE_ERASE_RIGHT:
180			fOutput->FillGlyph(fX, fY, fColumns - fX, 1, ' ', fAttr);
181			break;
182		default:
183			return;
184	}
185}
186
187
188void
189Console::EraseScreen(erase_screen_mode mode)
190{
191	switch (mode) {
192		case SCREEN_ERASE_WHOLE:
193			fOutput->Clear(fAttr);
194			break;
195		case SCREEN_ERASE_UP:
196			fOutput->FillGlyph(0, 0, fColumns, fY + 1, ' ', fAttr);
197			break;
198		case SCREEN_ERASE_DOWN:
199			fOutput->FillGlyph(fY, 0, fColumns, fLines - fY, ' ', fAttr);
200			break;
201		default:
202			return;
203	}
204}
205
206
207void
208Console::SaveCursor(bool save_attrs)
209{
210	fSavedX = fX;
211	fSavedY = fY;
212
213	if (save_attrs)
214		fSavedAttr = fAttr;
215}
216
217
218void
219Console::RestoreCursor(bool restore_attrs)
220{
221	fX = fSavedX;
222	fY = fSavedY;
223
224	if (restore_attrs)
225		fAttr = fSavedAttr;
226}
227
228
229void
230Console::UpdateCursor(int x, int y)
231{
232	fOutput->MoveCursor(x, y);
233}
234
235
236void
237Console::GotoXY(int new_x, int new_y)
238{
239	if (new_x >= fColumns)
240		new_x = fColumns - 1;
241	if (new_x < 0)
242		new_x = 0;
243	if (new_y >= fLines)
244		new_y = fLines - 1;
245	if (new_y < 0)
246		new_y = 0;
247
248	fX = new_x;
249	fY = new_y;
250}
251
252
253void
254Console::PutChar(const char c)
255{
256	fOutput->PutGlyph(fX, fY, c, fAttr);
257	if (++fX >= fColumns) {
258		CariageReturn();
259		LineFeed();
260	}
261}
262
263
264void
265Console::SetVT100Attributes(int32 *args, int32 argCount)
266{
267	if (argCount == 0) {
268		// that's the default (attributes off)
269		argCount++;
270		args[0] = 0;
271	}
272
273	for (int32 i = 0; i < argCount; i++) {
274		switch (args[i]) {
275			case 0: // reset
276				fAttr = 0x0f;
277				fBrightAttr = true;
278				fReverseAttr = false;
279				break;
280			case 1: // bright
281				fBrightAttr = true;
282				fAttr |= 0x08; // set the bright bit
283				break;
284			case 2: // dim
285				fBrightAttr = false;
286				fAttr &= ~0x08; // unset the bright bit
287				break;
288			case 4: // underscore we can't do
289				break;
290			case 5: // blink
291				fAttr |= 0x80; // set the blink bit
292				break;
293			case 7: // reverse
294				fReverseAttr = true;
295				fAttr = ((fAttr & BMASK) >> 4) | ((fAttr & FMASK) << 4);
296				if (fBrightAttr)
297					fAttr |= 0x08;
298				break;
299			case 8: // hidden?
300				break;
301
302			/* foreground colors */
303			case 30: fAttr = (fAttr & ~FMASK) | 0 | (fBrightAttr ? 0x08 : 0); break; // black
304			case 31: fAttr = (fAttr & ~FMASK) | 4 | (fBrightAttr ? 0x08 : 0); break; // red
305			case 32: fAttr = (fAttr & ~FMASK) | 2 | (fBrightAttr ? 0x08 : 0); break; // green
306			case 33: fAttr = (fAttr & ~FMASK) | 6 | (fBrightAttr ? 0x08 : 0); break; // yellow
307			case 34: fAttr = (fAttr & ~FMASK) | 1 | (fBrightAttr ? 0x08 : 0); break; // blue
308			case 35: fAttr = (fAttr & ~FMASK) | 5 | (fBrightAttr ? 0x08 : 0); break; // magenta
309			case 36: fAttr = (fAttr & ~FMASK) | 3 | (fBrightAttr ? 0x08 : 0); break; // cyan
310			case 37: fAttr = (fAttr & ~FMASK) | 7 | (fBrightAttr ? 0x08 : 0); break; // white
311
312			/* background colors */
313			case 40: fAttr = (fAttr & ~BMASK) | (0 << 4); break; // black
314			case 41: fAttr = (fAttr & ~BMASK) | (4 << 4); break; // red
315			case 42: fAttr = (fAttr & ~BMASK) | (2 << 4); break; // green
316			case 43: fAttr = (fAttr & ~BMASK) | (6 << 4); break; // yellow
317			case 44: fAttr = (fAttr & ~BMASK) | (1 << 4); break; // blue
318			case 45: fAttr = (fAttr & ~BMASK) | (5 << 4); break; // magenta
319			case 46: fAttr = (fAttr & ~BMASK) | (3 << 4); break; // cyan
320			case 47: fAttr = (fAttr & ~BMASK) | (7 << 4); break; // white
321		}
322	}
323}
324
325
326bool
327Console::ProcessVT100Command(const char c, bool seen_bracket, int32 *args, int32 argCount)
328{
329	bool ret = true;
330
331	if (seen_bracket) {
332		switch(c) {
333			case 'H': /* set cursor position */
334			case 'f': {
335				int32 row = argCount > 0 ? args[0] : 1;
336				int32 col = argCount > 1 ? args[1] : 1;
337				if (row > 0)
338					row--;
339				if (col > 0)
340					col--;
341				GotoXY(col, row);
342				break;
343			}
344			case 'A': { /* move up */
345				int32 deltay = argCount > 0 ? -args[0] : -1;
346				if (deltay == 0)
347					deltay = -1;
348				GotoXY(fX, fY + deltay);
349				break;
350			}
351			case 'e':
352			case 'B': { /* move down */
353				int32 deltay = argCount > 0 ? args[0] : 1;
354				if (deltay == 0)
355					deltay = 1;
356				GotoXY(fX, fY + deltay);
357				break;
358			}
359			case 'D': { /* move left */
360				int32 deltax = argCount > 0 ? -args[0] : -1;
361				if (deltax == 0)
362					deltax = -1;
363				GotoXY(fX + deltax, fY);
364				break;
365			}
366			case 'a':
367			case 'C': { /* move right */
368				int32 deltax = argCount > 0 ? args[0] : 1;
369				if (deltax == 0)
370					deltax = 1;
371				GotoXY(fX + deltax, fY);
372				break;
373			}
374			case '`':
375			case 'G': { /* set X position */
376				int32 newx = argCount > 0 ? args[0] : 1;
377				if (newx > 0)
378					newx--;
379				GotoXY(newx, fY);
380				break;
381			}
382			case 'd': { /* set y position */
383				int32 newy = argCount > 0 ? args[0] : 1;
384				if (newy > 0)
385					newy--;
386				GotoXY(fX, newy);
387				break;
388			}
389			case 's': /* save current cursor */
390				SaveCursor(false);
391				break;
392			case 'u': /* restore cursor */
393				RestoreCursor(false);
394				break;
395			case 'r': { /* set scroll region */
396				int32 low = argCount > 0 ? args[0] : 1;
397				int32 high = argCount > 1 ? args[1] : fLines;
398				if (low <= high)
399					SetScrollRegion(low - 1, high - 1);
400				break;
401			}
402			case 'L': { /* scroll virtual down at cursor */
403				int32 lines = argCount > 0 ? args[0] : 1;
404				while (lines > 0) {
405					ScrollDown();
406					lines--;
407				}
408				break;
409			}
410			case 'M': { /* scroll virtual up at cursor */
411				int32 lines = argCount > 0 ? args[0] : 1;
412				while (lines > 0) {
413					ScrollUp();
414					lines--;
415				}
416				break;
417			}
418			case 'K':
419				if (argCount == 0 || args[0] == 0) {
420					// erase to end of line
421					EraseLine(LINE_ERASE_RIGHT);
422				} else if (argCount > 0) {
423					if (args[0] == 1)
424						EraseLine(LINE_ERASE_LEFT);
425					else if (args[0] == 2)
426						EraseLine(LINE_ERASE_WHOLE);
427				}
428				break;
429			case 'J':
430				if (argCount == 0 || args[0] == 0) {
431					// erase to end of screen
432					EraseScreen(SCREEN_ERASE_DOWN);
433				} else if (argCount > 0) {
434					if (args[0] == 1)
435						EraseScreen(SCREEN_ERASE_UP);
436					else if (args[0] == 2)
437						EraseScreen(SCREEN_ERASE_WHOLE);
438				}
439				break;
440			case 'm':
441				if (argCount >= 0)
442					SetVT100Attributes(args, argCount);
443				break;
444			default:
445				ret = false;
446		}
447	} else {
448		switch (c) {
449			case 'c':
450				ResetConsole();
451				break;
452			case 'D':
453				RLineFeed();
454				break;
455			case 'M':
456				LineFeed();
457				break;
458			case '7':
459				SaveCursor(true);
460				break;
461			case '8':
462				RestoreCursor(true);
463				break;
464			default:
465				ret = false;
466		}
467	}
468
469	return ret;
470}
471
472
473void
474Console::Write(const void *buf, size_t len)
475{
476	UpdateCursor(-1, -1); // hide the cursor
477
478	const char *c;
479	size_t pos = 0;
480
481	while (pos < len) {
482		c = &((const char *)buf)[pos++];
483
484		switch (fState) {
485			case CONSOLE_STATE_NORMAL:
486				// just output the stuff
487				switch (*c) {
488					case '\n':
489						LineFeed();
490						break;
491					case '\r':
492						CariageReturn();
493						break;
494					case 0x8: // backspace
495						Delete();
496						break;
497					case '\t':
498						Tab();
499						break;
500					case '\a':
501						// beep
502						//printf("<BEEP>\n");
503						break;
504					case '\0':
505						break;
506					case 0x1b:
507						// escape character
508						fArgCount = -1;
509						fState = CONSOLE_STATE_GOT_ESCAPE;
510						break;
511					default:
512						PutChar(*c);
513				}
514				break;
515			case CONSOLE_STATE_GOT_ESCAPE:
516				// look for either commands with no argument, or the '[' character
517				switch (*c) {
518					case '[':
519						fState = CONSOLE_STATE_SEEN_BRACKET;
520						break;
521					default:
522						fArgs[fArgCount] = 0;
523						ProcessVT100Command(*c, false, fArgs, fArgCount + 1);
524						fState = CONSOLE_STATE_NORMAL;
525				}
526				break;
527			case CONSOLE_STATE_SEEN_BRACKET:
528				switch (*c) {
529					case '0'...'9':
530						fArgCount = 0;
531						fArgs[fArgCount] = *c - '0';
532						fState = CONSOLE_STATE_PARSING_ARG;
533						break;
534					case '?':
535						// private DEC mode parameter follows - we ignore those anyway
536						// ToDo: check if it was really used in combination with a mode command
537						break;
538					default:
539						ProcessVT100Command(*c, true, fArgs, fArgCount + 1);
540						fState = CONSOLE_STATE_NORMAL;
541				}
542				break;
543			case CONSOLE_STATE_NEW_ARG:
544				switch (*c) {
545					case '0'...'9':
546						fArgCount++;
547						if (fArgCount == MAX_ARGS) {
548							fState = CONSOLE_STATE_NORMAL;
549							break;
550						}
551						fArgs[fArgCount] = *c - '0';
552						fState = CONSOLE_STATE_PARSING_ARG;
553						break;
554					default:
555						ProcessVT100Command(*c, true, fArgs, fArgCount + 1);
556						fState = CONSOLE_STATE_NORMAL;
557				}
558				break;
559			case CONSOLE_STATE_PARSING_ARG:
560				// parse args
561				switch (*c) {
562					case '0'...'9':
563						fArgs[fArgCount] *= 10;
564						fArgs[fArgCount] += *c - '0';
565						break;
566					case ';':
567						fState = CONSOLE_STATE_NEW_ARG;
568						break;
569					default:
570						ProcessVT100Command(*c, true, fArgs, fArgCount + 1);
571						fState = CONSOLE_STATE_NORMAL;
572				}
573			}
574	}
575
576	UpdateCursor(fX, fY); // show it again
577}
578