Files
MentOS/mentos/src/io/video.c
T

432 lines
10 KiB
C

/// @file video.c
/// @brief Video functions and costants.
/// @copyright (c) 2014-2022 This file is distributed under the MIT License.
/// See LICENSE.md for details.
#include "io/port_io.h"
#include "io/video.h"
#include "io/vga/vga.h"
#include "stdbool.h"
#include "ctype.h"
#include "string.h"
#include "stdio.h"
#define HEIGHT 25 ///< The height of the
#define WIDTH 80 ///< The width of the
#define W2 (WIDTH * 2) ///< The width of the
#define TOTAL_SIZE (HEIGHT * WIDTH * 2) ///< The total size of the screen.
#define ADDR (char *)0xB8000U ///< The address of the
#define STORED_PAGES 3 ///< The number of stored pages.
/// @brief Stores the association between ANSI colors and pure VIDEO colors.
struct ansi_color_map_t {
/// The ANSI color number.
uint8_t ansi_color;
/// The VIDEO color number.
uint8_t video_color;
}
/// @brief The mapping.
ansi_color_map[] = {
{ 30, 0 },
{ 31, 4 },
{ 32, 2 },
{ 33, 6 },
{ 34, 1 },
{ 35, 5 },
{ 36, 3 },
{ 37, 7 },
{ 90, 8 },
{ 91, 12 },
{ 92, 10 },
{ 93, 14 },
{ 94, 9 },
{ 95, 13 },
{ 96, 11 },
{ 97, 15 },
{ 40, 0 },
{ 41, 4 },
{ 42, 2 },
{ 43, 6 },
{ 44, 1 },
{ 45, 5 },
{ 46, 3 },
{ 47, 7 },
{ 100, 8 },
{ 101, 12 },
{ 102, 10 },
{ 103, 14 },
{ 104, 9 },
{ 105, 13 },
{ 106, 11 },
{ 107, 15 },
{ 0, 0 }
};
/// Pointer to a position of the screen writer.
char *pointer = ADDR;
/// The current color.
unsigned char color = 7;
/// Used to write on the escape_buffer. If -1, we are not parsing an escape sequence.
int escape_index = -1;
/// Used to store an escape sequence.
char escape_buffer[256];
/// Buffer where we store the upper scroll history.
char upper_buffer[STORED_PAGES * TOTAL_SIZE] = { 0 };
/// Buffer where we store the lower scroll history.
char original_page[TOTAL_SIZE] = { 0 };
/// Determines if the screen is currently scrolled.
int scrolled_page = 0;
/// @brief Get the current column number.
/// @return The column number.
static inline unsigned __get_x()
{
return ((pointer - ADDR) % (WIDTH * 2)) / 2;
}
/// @brief Get the current row number.
/// @return The row number.
static inline unsigned __get_y()
{
return (pointer - ADDR) / (WIDTH * 2);
}
/// @brief Draws the given character.
/// @param c The character to draw.
static inline void __draw_char(char c)
{
for (char *ptr = (ADDR + TOTAL_SIZE + (WIDTH * 2)); ptr > pointer; ptr -= 2) {
*(ptr) = *(ptr - 2);
*(ptr + 1) = *(ptr - 1);
}
*(pointer++) = c;
*(pointer++) = color;
}
/// @brief Sets the provided ansi code.
/// @param ansi_code The ansi code describing background and foreground color.
static inline void __set_color(uint8_t ansi_code)
{
struct ansi_color_map_t *it = ansi_color_map;
while (it->ansi_color != 0) {
if (ansi_code == it->ansi_color) {
if (((ansi_code >= 30) && (ansi_code <= 37)) || ((ansi_code >= 90) && (ansi_code <= 97))) {
color = (color & 0xF0U) | it->video_color;
} else {
color = (color & 0x0FU) | (it->video_color << 4U);
}
break;
}
++it;
}
}
/// @brief Moves the cursor backward.
/// @param erase If 1 also erase the character.
/// @param amount How many times we move backward.
static inline void __move_cursor_backward(int erase, int amount)
{
for (int i = 0; i < amount; ++i) {
// Bring back the pointer.
pointer -= 2;
if (erase) {
strcpy(pointer, pointer + 2);
}
}
video_set_cursor_auto();
}
/// @brief Moves the cursor forward.
/// @param erase If 1 also erase the character.
/// @param amount How many times we move forward.
static inline void __move_cursor_forward(int erase, int amount)
{
for (int i = 0; i < amount; ++i) {
// Bring forward the pointer.
if (erase) {
__draw_char(' ');
} else {
pointer += 2;
}
}
video_set_cursor_auto();
}
/// @brief Issue the vide to move the cursor to the given position.
/// @param x The x coordinate.
/// @param y The y coordinate.
static inline void __video_set_cursor(unsigned int x, unsigned int y)
{
uint32_t position = x * WIDTH + y;
// Cursor LOW port to vga INDEX register.
outportb(0x3D4, 0x0F);
outportb(0x3D5, (uint8_t)(position & 0xFFU));
// Cursor HIGH port to vga INDEX register.
outportb(0x3D4, 0x0E);
outportb(0x3D5, (uint8_t)((position >> 8U) & 0xFFU));
}
void video_init()
{
video_clear();
}
void video_update()
{
#ifndef VGA_TEXT_MODE
if (vga_is_enabled())
vga_update();
#endif
}
void video_putc(int c)
{
// ESCAPE SEQUENCES
if (c == '\033') {
escape_index = 0;
return;
}
if (escape_index >= 0) {
if ((escape_index == 0) && (c == '[')) {
return;
}
escape_buffer[escape_index++] = c;
escape_buffer[escape_index] = 0;
if (isalpha(c)) {
escape_buffer[--escape_index] = 0;
if (c == 'C') {
__move_cursor_forward(false, atoi(escape_buffer));
} else if (c == 'D') {
__move_cursor_backward(false, atoi(escape_buffer));
} else if (c == 'm') {
__set_color(atoi(escape_buffer));
} else if (c == 'J') {
video_clear();
}
escape_index = -1;
}
return;
}
#ifndef VGA_TEXT_MODE
if (vga_is_enabled()) {
vga_putc(c);
return;
}
#endif
// == NORMAL CHARACTERS =======================================================================
// If the character is '\n' go the new line.
if (c == '\n') {
video_new_line();
//video_shift_one_line_down();
} else if (c == '\b') {
__move_cursor_backward(true, 1);
} else if (c == '\r') {
video_cartridge_return();
} else if (c == 127) {
strcpy(pointer, pointer + 2);
} else if ((c >= 0x20) && (c <= 0x7E)) {
__draw_char(c);
} else {
return;
}
video_shift_one_line_up();
video_set_cursor_auto();
}
void video_puts(const char *str)
{
#ifndef VGA_TEXT_MODE
if (vga_is_enabled()) {
vga_puts(str);
return;
}
#endif
while ((*str) != 0) {
video_putc((*str++));
}
}
void video_set_cursor_auto()
{
#ifndef VGA_TEXT_MODE
if (vga_is_enabled())
return;
#endif
__video_set_cursor(((pointer - ADDR) / 2U) / WIDTH, ((pointer - ADDR) / 2U) % WIDTH);
}
void video_move_cursor(unsigned int x, unsigned int y)
{
#ifndef VGA_TEXT_MODE
if (vga_is_enabled()) {
vga_move_cursor(x, y);
return;
}
#endif
pointer = ADDR + ((y * WIDTH * 2) + (x * 2));
video_set_cursor_auto();
}
void video_get_cursor_position(unsigned int *x, unsigned int *y)
{
#ifndef VGA_TEXT_MODE
if (vga_is_enabled()) {
vga_get_cursor_position(x, y);
return;
}
#endif
if (x)
*x = __get_x();
if (y)
*y = __get_y();
}
void video_get_screen_size(unsigned int *width, unsigned int *height)
{
#ifndef VGA_TEXT_MODE
if (vga_is_enabled()) {
vga_get_screen_size(width, height);
return;
}
#endif
if (width)
*width = WIDTH;
if (height)
*height = HEIGHT;
}
void video_clear()
{
#ifndef VGA_TEXT_MODE
if (vga_is_enabled()) {
vga_clear_screen();
return;
}
#endif
memset(upper_buffer, 0, STORED_PAGES * TOTAL_SIZE);
memset(ADDR, 0, TOTAL_SIZE);
}
void video_new_line()
{
#ifndef VGA_TEXT_MODE
if (vga_is_enabled()) {
vga_new_line();
return;
}
#endif
pointer = ADDR + ((pointer - ADDR) / W2 + 1) * W2;
video_shift_one_line_up();
video_set_cursor_auto();
}
void video_cartridge_return()
{
#ifndef VGA_TEXT_MODE
if (vga_is_enabled()) {
vga_new_line();
return;
}
#endif
pointer = ADDR + ((pointer - ADDR) / W2 - 1) * W2;
video_new_line();
video_shift_one_line_up();
video_set_cursor_auto();
}
void video_shift_one_line_up(void)
{
if (pointer >= ADDR + TOTAL_SIZE) {
// Move the upper buffer up by one line.
for (int row = 0; row < (STORED_PAGES * HEIGHT); ++row) {
memcpy(upper_buffer + W2 * row, upper_buffer + W2 * (row + 1), 2 * WIDTH);
}
// Copy the first line on the screen inside the last line of the upper buffer.
memcpy(upper_buffer + (TOTAL_SIZE * STORED_PAGES - W2), ADDR, 2 * WIDTH);
// Move the screen up by one line.
for (int row = 0; row < HEIGHT; ++row) {
memcpy(ADDR + (W2 * row), ADDR + (W2 * (row + 1)), 2 * WIDTH);
}
// Update the pointer.
pointer = ADDR + ((pointer - ADDR) / W2 - 1) * W2;
}
}
void video_shift_one_page_up()
{
if (scrolled_page > 0) {
// Decrese the number of scrolled pages, and compute which page must be loaded.
int page_to_load = (STORED_PAGES - (--scrolled_page));
// If we have reached 0, restore the original page.
if (scrolled_page == 0)
memcpy(ADDR, original_page, TOTAL_SIZE);
else
memcpy(ADDR, upper_buffer + (page_to_load * TOTAL_SIZE), TOTAL_SIZE);
}
}
void video_shift_one_page_down(void)
{
if (scrolled_page < STORED_PAGES) {
// Increase the number of scrolled pages, and compute which page must be loaded.
int page_to_load = (STORED_PAGES - (++scrolled_page));
// If we are loading the first history page, save the original.
if (scrolled_page == 1)
memcpy(original_page, ADDR, TOTAL_SIZE);
// Load the specific page.
memcpy(ADDR, upper_buffer + (page_to_load * TOTAL_SIZE), TOTAL_SIZE);
}
}
#if 0
void video_scroll_up()
{
if (is_scrolled)
return;
char *ptr = memory = pointer;
for (unsigned int it = 0; it < TOTAL_SIZE; it++) {
downbuffer[y][x] = *ptr;
*ptr++ = upbuffer[y][x];
}
is_scrolled = true;
stored_x = video_get_column();
stored_y = video_get_line();
video_move_cursor(width, height);
}
void video_scroll_down()
{
char *ptr = memory;
// If PAGEUP hasn't been pressed, it's useless to go down, there is nothing.
if (!is_scrolled) {
return;
}
for (uint32_t y = 0; y < height; y++) {
for (uint32_t x = 0; x < width * 2; x++) {
*ptr++ = downbuffer[y][x];
}
}
is_scrolled = false;
video_move_cursor(stored_x, stored_y);
stored_x = 0;
stored_y = 0;
}
#endif