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