#include #include #include #include #include #include static const size_t TERM_WIDTH=80; static const size_t TERM_HEIGHT=25; static size_t terminal_row; static size_t terminal_column; static uint8_t current_color; static uint16_t* term_buffer; static uint16_t* const vga_buffer = (uint16_t*) 0xB8000; void screen_initialize(void) { terminal_row = 0; terminal_column = 0; current_color = vga_color_set(LIGHT_GREY, BLACK); term_buffer = vga_buffer; for (size_t y = 0; y < TERM_HEIGHT; y++) { for(size_t x = 0; x < TERM_WIDTH; x++) { const size_t offset = y * TERM_WIDTH + x; term_buffer[offset] = vga_entry(' ', current_color); } } } void term_setcolor(enum vga_colors color) { current_color = color; } void term_putentryat (char c, uint8_t color, size_t x, size_t y) { const size_t offset = y * TERM_WIDTH + x; term_buffer[offset] = vga_entry(c, color); } void term_putchar(char c) { unsigned char uc = c; //Handle escaped characters, such as newline, and crtn. switch (uc) { case '\n': terminal_column = 0; terminal_row += 1; break; default: term_putentryat(uc, current_color, terminal_column, terminal_row); break; } if(++terminal_column == TERM_WIDTH) { terminal_column = 0; if(++terminal_row == TERM_HEIGHT){ term_scroll(false); terminal_row = 0; } } } void term_write(const char* data, size_t size) { for(size_t i = 0; i < size; i++) { //Begin handling ANSI escape codes. if(data[i] == 0x1B) { //The current character is ESC - the start of ANSI codes. term_writes("ANSI Code encountered: "); bool string_terminated = false; //Flag used in some of the escape codes switch(data[i+1]) { case 0x9b/*[*/: //CSI - Control Sequence Introducer (The most common one, hence it comes first) char* controlSequence; if(data[i+2] == NULL) { controlSequence = '\0'; for(size_t j = 0; j < (strlen(data) - (i+2); j++) { if(data[j] == ' ') break; controlSequence += data[j]; } handleControlSequence(controlSequence); //Send it off to our handler function to keep this part clean break; //Single shifts are not handled, so just print them and exit case 0x8e/*N*/: //SS2 - Single Shift Two term_writes("Single Shift Two\n"); break; case 0x8f/*O*/: //SS3 - Single Shift Three term_writes("Single Shift Three\n"); break; //Control Strings case 0x90/*P*/: //DCS - Device Control String term_writes("Device Control String"); string_terminated = false; break; case 0x9c/*\*/: //ST - String Terminator term_writes("String Terminator\n"); string_terminated = true; break; case 0x9d/*]*/: //OSC - Operating System Command term_writes("Operating System Command\n"); string_terminated = false; break; case 0x98/*X*/: //SOS - Start Of String term_writes("Start of String"); string_terminated = false; break; case 0x9e/*^*/: //PM - Privacy Message term_writes("Privacy Message\n"); break; case 0x9f/*_*/: //APC - Application Program Command term_writes("Application Program Command\n"); break; } } term_putchar(data[i]); } } void term_writes(const char* data) { term_write(data, strlen(data)); } void puts(const char* string) { term_write(string, strlen(string)); term_putchar('\n'); } void handleControlSequence(const char* sequence) { //Check for our failsafes if(sequence) { if(sequence != '\0') { int n = 0; //Default of the flag used for a few items bool nPresent = false; bool terminated = false; for(size_t i = 0; i < strlen(sequence); i++) { if(isDigit(sequence[i])) { nPresent = true; n = (n*10) + sequence[i]; } else if(n > 80) { n = 80; break; } } if(n == 0) n = 1; switch(sequence[i]) { case ';': terminated = true; int m = 0; for(size_t j = 0; j < strlen(sequence - i); j++) { if(sequence[j] == 'H' ||sequence[j] == 'f') break; if(isDigit(sequence[j])) { m = (m*10) + sequence[j]; } else if(j > 80) break; } if(m == 0) m = 1; cursor_pos(n, m); break; case 'A': //CUU - Cursor Up //cursor_pos(x, y) cursor_pos(terminal_column, terminal_row + n); break; case 'B': //CUD - Cursor Down cursor_pos(terminal_column, terminal_row - n); break; case 'C': //CUF - Cursor Forward cursor_pos(terminal_column + n, terminal_row); break; case 'D': //CUB - Cursor Back cursor_pos(terminal_column - n, terminal_row); break; case 'E': //CNL - Cursor Next Line cursor_pos(0, terminal_row + n); break; case 'F': //CPL - Cursor Previous Line cursor_pos(0, terminal_row - n); break; case 'G': //CHA - Cursor Horizontal Absolute cursor_pos(n, terminal_row); case 'J': //ED - Erase in Display //current cursor pos = y * width + x int pos = terminal_row * 80 + terminal_column; if(!nPresent) { for(pos < (25 * 80); pos++) { vga_buffer[pos] = '\0'; } n = 0; } else if(nPresent && n == 1) { for(pos > 0; pos--) { vga_buffer[pos] = '\0'; } } else if(n == 2 || n == 3) { for(int i = 0; i < (25*80); i++) { vga_buffer[0] = '\0'; } } break; case 'K': //EL - Erase in Line int pos = terminal_row * 80 + terminal_column; if(!nPresent) { //From cursor to end of line int endPos = (terminal_row + 1) * 80 - 1; //End of line = current row + 25 columns = current row + 1 for(pos < endPos; pos++) { vga_buffer[pos] = '\0'; } } else if(nPresent && n == 1) { //From cursor to start of line int endPos = terminal_row * 80; //Start of line = end of previous line + 1 == current line for(pos > endPos; pos--) { vga_buffer[pos] = '\0'; } } else if(nPresent && n == 2) { //Entire current line pos = terminal_row * 80; int endPos = (terminal_row + 1) * 80 - 1; for(pos < endPos; pos++) { vga_buffer[pos] = '\0'; } } break; case 'S': //SU - Scroll Up term_scroll(true); break; case 'T': //SD - Scroll Down term_scroll(false); break; } } } } } bool isDigit(const char c) { return c == '0' || c == '1' || c == '2' || c == '3' || c == '4' || c == '5' || c == '6' || c == '7' || c == '8' || c == '9'; } void term_scroll(bool down) { int current_pos; if(down) { current_pos = 25 * 80; //Start of the last line. } else { current_pos = 160; //Start of the second line. } unsigned char* term_buffer = (unsigned char*) vga_buffer; if(down) { //To scroll down, move every character into the one below it, or "pull" every character down while(current_pos > 80) { term_buffer[current_pos + 160] = vga_buffer[current_pos]; term_buffer[current_pos + 159] = vga_buffer[current_pos - 1]; current_pos -= 2; } else { while (current_pos <= 4000) { term_buffer[current_pos - 160 /*The character immediately below it*/] = vga_buffer[current_pos]; //Move each character up a line term_buffer[current_pos - 159 /*The color of the character below*/] = vga_buffer[current_pos + 1]; //As well as its color current_pos += 2; //Move to next char } } if(down) { current_pos = 0; //Start of first line for(current_pos < 80; current_pos++) { term_buffer[current_pos] = '\0'; current_pos += 2; } else { ; //Start of the last line //Wipe out the bottom line for(current_pos = 3840; current_pos <= 3920; i++) { term_buffer[current_pos] = 0; current_pos += 2; } } terminal_row = 24; //Start writing on the last line terminal_column = 0; }