| /* graphics.c - graphics mode support for GRUB */ |
| /* Implemented as a terminal type by Jeremy Katz <katzj@redhat.com> based |
| * on a patch by Paulo César Pereira de Andrade <pcpa@conectiva.com.br> |
| */ |
| /* |
| * GRUB -- GRand Unified Bootloader |
| * Copyright (C) 2001,2002 Red Hat, Inc. |
| * Portions copyright (C) 2000 Conectiva, Inc. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
| */ |
| |
| |
| |
| #ifdef SUPPORT_GRAPHICS |
| |
| #include <term.h> |
| #include <shared.h> |
| #include <graphics.h> |
| |
| #ifdef OVERLAY_LOGO |
| #include <logo.xbm> |
| #endif /* OVERLAY_LOGO */ |
| |
| int saved_videomode; |
| unsigned char *font8x16; |
| |
| int graphics_inited = 0; |
| |
| #define PALETTE_REDSHIFT 16 |
| #define PALETTE_GREENSHIFT 8 |
| #define PALETTE_COLORMASK 63 |
| |
| #define PALETTE_NCOLORS 16 |
| |
| #define PALETTE_RED(entry) ((entry) >> PALETTE_REDSHIFT) |
| #define PALETTE_GREEN(entry) (((entry) >> PALETTE_GREENSHIFT) & \ |
| PALETTE_COLORMASK) |
| #define PALETTE_BLUE(entry) ((entry) & PALETTE_COLORMASK) |
| |
| static char splashimage[64]; |
| static int splash_palette[PALETTE_NCOLORS]; |
| |
| #define HPIXELS 640 |
| #define VPIXELS 480 |
| #define HPIXELSPERBYTE 8 |
| |
| #define ROWBYTES (HPIXELS / HPIXELSPERBYTE) |
| #define SCREENBYTES (ROWBYTES * VPIXELS) |
| |
| #define VSHADOW VSHADOW1 |
| unsigned char VSHADOW1[SCREENBYTES]; |
| unsigned char VSHADOW2[SCREENBYTES]; |
| unsigned char VSHADOW4[SCREENBYTES]; |
| unsigned char VSHADOW8[SCREENBYTES]; |
| |
| static unsigned char *s1 = (unsigned char*)VSHADOW1; |
| static unsigned char *s2 = (unsigned char*)VSHADOW2; |
| static unsigned char *s4 = (unsigned char*)VSHADOW4; |
| static unsigned char *s8 = (unsigned char*)VSHADOW8; |
| |
| /* constants to define the viewable area */ |
| const int x0 = 0; |
| const int x1 = ROWBYTES; |
| const int y0 = 0; |
| const int y1 = 30; |
| |
| /* text buffer has to be kept around so that we can write things as we |
| * scroll and the like */ |
| unsigned short text[ROWBYTES * 30]; |
| |
| /* why do these have to be kept here? */ |
| int foreground = (63 << 16) | (63 << 8) | (63), background = 0, border = 0; |
| |
| |
| /* current position */ |
| static int fontx = 0; |
| static int fonty = 0; |
| |
| /* global state so that we don't try to recursively scroll or cursor */ |
| static int no_scroll = 0; |
| |
| /* color state */ |
| static int graphics_standard_color = A_NORMAL; |
| static int graphics_normal_color = A_NORMAL; |
| static int graphics_highlight_color = A_REVERSE; |
| static int graphics_current_color = A_NORMAL; |
| static color_state graphics_color_state = COLOR_STATE_STANDARD; |
| |
| |
| /* graphics local functions */ |
| static void graphics_setxy(int col, int row); |
| static void graphics_scroll(void); |
| static int read_image(char *); |
| |
| #ifdef OVERLAY_LOGO |
| static void draw_xbmlogo(void); |
| #endif /* OVERLAY_LOGO */ |
| |
| /* FIXME: where do these really belong? */ |
| static inline void outb(unsigned short port, unsigned char val) |
| { |
| __asm __volatile ("outb %0,%1"::"a" (val), "d" (port)); |
| } |
| |
| static void MapMask(int value) { |
| outb(0x3c4, 2); |
| outb(0x3c5, value); |
| } |
| |
| /* bit mask register */ |
| static void BitMask(int value) { |
| outb(0x3ce, 8); |
| outb(0x3cf, value); |
| } |
| |
| |
| /* Set the splash image */ |
| void graphics_set_splash(char *splashfile) { |
| grub_strcpy(splashimage, splashfile); |
| } |
| |
| /* Get the current splash image */ |
| char *graphics_get_splash(void) { |
| return splashimage; |
| } |
| |
| /* Initialize a vga16 graphics display with the palette based off of |
| * the image in splashimage. If the image doesn't exist, leave graphics |
| * mode. */ |
| int graphics_init() |
| { |
| int image_read, index, color; |
| |
| if (!graphics_inited) { |
| saved_videomode = set_videomode(0x12); |
| } |
| |
| font8x16 = (unsigned char*)graphics_get_font(); |
| |
| image_read = read_image(splashimage); |
| |
| /* |
| * Set VGA palette color 0 to be the system background color, 15 to be the |
| * system foreground color, and 17 to be the system border color. |
| * |
| * If the splashimage was read successfully, program the palette with |
| * its new colors; if not, set them to the background color. |
| */ |
| |
| graphics_set_palette(0, PALETTE_RED(background), PALETTE_GREEN(background), |
| PALETTE_BLUE(background)); |
| |
| for (index = 1; index < 15; index++) { |
| color = (image_read ? splash_palette[index] : background); |
| graphics_set_palette(index, PALETTE_RED(color), |
| PALETTE_GREEN(color), PALETTE_BLUE(color)); |
| } |
| |
| graphics_set_palette(15, PALETTE_RED(foreground), |
| PALETTE_GREEN(foreground), PALETTE_BLUE(foreground)); |
| |
| graphics_set_palette(0x11, PALETTE_RED(border), PALETTE_GREEN(border), |
| PALETTE_BLUE(border)); |
| |
| #ifdef OVERLAY_LOGO |
| draw_xbmlogo(); |
| #endif /* OVERLAY_LOGO */ |
| |
| graphics_inited = 1; |
| |
| /* make sure that the highlight color is set correctly */ |
| graphics_highlight_color = ((graphics_normal_color >> 4) | |
| ((graphics_normal_color & 0xf) << 4)); |
| |
| return 1; |
| } |
| |
| /* Leave graphics mode */ |
| void graphics_end(void) |
| { |
| if (graphics_inited) { |
| set_videomode(saved_videomode); |
| graphics_inited = 0; |
| } |
| } |
| |
| /* Print ch on the screen. Handle any needed scrolling or the like */ |
| void graphics_putchar(int ch) { |
| ch &= 0xff; |
| |
| graphics_cursor(0); |
| |
| if (ch == '\n') { |
| if (fonty + 1 < y1) |
| graphics_setxy(fontx, fonty + 1); |
| else |
| graphics_scroll(); |
| graphics_cursor(1); |
| return; |
| } else if (ch == '\r') { |
| graphics_setxy(x0, fonty); |
| graphics_cursor(1); |
| return; |
| } |
| |
| graphics_cursor(0); |
| |
| text[fonty * ROWBYTES + fontx] = ch; |
| text[fonty * ROWBYTES + fontx] &= 0x00ff; |
| if (graphics_current_color & 0xf0) |
| text[fonty * ROWBYTES + fontx] |= 0x100; |
| |
| graphics_cursor(0); |
| |
| if ((fontx + 1) >= x1) { |
| graphics_setxy(x0, fonty); |
| if (fonty + 1 < y1) |
| graphics_setxy(x0, fonty + 1); |
| else |
| graphics_scroll(); |
| } else { |
| graphics_setxy(fontx + 1, fonty); |
| } |
| |
| graphics_cursor(1); |
| } |
| |
| /* get the current location of the cursor */ |
| int graphics_getxy(void) { |
| return (fontx << 8) | fonty; |
| } |
| |
| void graphics_gotoxy(int x, int y) { |
| graphics_cursor(0); |
| |
| graphics_setxy(x, y); |
| |
| graphics_cursor(1); |
| } |
| |
| void graphics_cls(void) { |
| int i; |
| unsigned char *mem; |
| |
| graphics_cursor(0); |
| graphics_gotoxy(x0, y0); |
| |
| mem = (unsigned char*)VIDEOMEM; |
| |
| for (i = 0; i < ROWBYTES * 30; i++) |
| text[i] = ' '; |
| graphics_cursor(1); |
| |
| BitMask(0xff); |
| |
| /* plane 1 */ |
| MapMask(1); |
| grub_memcpy(mem, s1, SCREENBYTES); |
| |
| /* plane 2 */ |
| MapMask(2); |
| grub_memcpy(mem, s2, SCREENBYTES); |
| |
| /* plane 3 */ |
| MapMask(4); |
| grub_memcpy(mem, s4, SCREENBYTES); |
| |
| /* plane 4 */ |
| MapMask(8); |
| grub_memcpy(mem, s8, SCREENBYTES); |
| |
| MapMask(15); |
| } |
| |
| void graphics_setcolorstate (color_state state) { |
| switch (state) { |
| case COLOR_STATE_STANDARD: |
| graphics_current_color = graphics_standard_color; |
| break; |
| case COLOR_STATE_NORMAL: |
| graphics_current_color = graphics_normal_color; |
| break; |
| case COLOR_STATE_HIGHLIGHT: |
| graphics_current_color = graphics_highlight_color; |
| break; |
| default: |
| graphics_current_color = graphics_standard_color; |
| break; |
| } |
| |
| graphics_color_state = state; |
| } |
| |
| void graphics_setcolor (int normal_color, int highlight_color) { |
| graphics_normal_color = normal_color; |
| graphics_highlight_color = highlight_color; |
| |
| graphics_setcolorstate (graphics_color_state); |
| } |
| |
| int graphics_setcursor (int on) { |
| /* FIXME: we don't have a cursor in graphics */ |
| return 1; |
| } |
| |
| #ifdef OVERLAY_LOGO |
| static void draw_xbmlogo(void) |
| { |
| unsigned char mask; |
| unsigned xbm_index = 0, xbm_incr; |
| unsigned screenx, logox, logoy, fb_offset, fb_index; |
| |
| /* |
| * Place the logo such that the right hand side will be four pixels from |
| * the right hand edge of the screen and the bottom will be two pixels |
| * from the bottom edge. |
| */ |
| fb_offset = ((VPIXELS - 1) - logo_height - 2) * ROWBYTES; |
| xbm_incr = (logo_width / 8) + 1; |
| |
| for (logoy = 0; logoy < logo_height; logoy++) { |
| for (logox = 0, screenx = (HPIXELS - 1) - logo_width - 4; |
| logox < logo_width; logox++, screenx++) { |
| mask = 0x80 >> (screenx & 7); |
| fb_index = fb_offset + (screenx >> 3); |
| |
| /* |
| * If a bit is clear in the bitmap, draw it onto the |
| * framebuffer in the default foreground color. |
| */ |
| if ((logo_bits[xbm_index + (logox >> 3)] & |
| (1 << (logox & 7))) == 0) { |
| /* system default foreground color */ |
| s1[fb_index] |= mask; |
| s2[fb_index] |= mask; |
| s4[fb_index] |= mask; |
| s8[fb_index] |= mask; |
| } |
| } |
| |
| xbm_index += xbm_incr; |
| fb_offset += ROWBYTES; |
| } |
| } |
| #endif /* OVERLAY_LOGO */ |
| |
| /* |
| * Read in the splashscreen image and set the palette up appropriately. |
| * |
| * Format of splashscreen is an XPM (can be gzipped) with up to 15 colors and |
| * is assumed to be of the proper screen dimensions. |
| */ |
| static int read_image(char *s) |
| { |
| char buf[32], pal[16]; |
| unsigned char c, base, mask; |
| unsigned i, len, idx, colors, x, y, width, height; |
| |
| if (!grub_open(s)) |
| return 0; |
| |
| /* read XPM header - must match memcmp string PRECISELY. */ |
| if (!grub_read((char*)&buf, 10) || grub_memcmp(buf, "/* XPM */\n", 10)) { |
| errnum = ERR_NOTXPM; |
| grub_close(); |
| return 0; |
| } |
| |
| /* skip characters until we reach an initial '"' */ |
| while (grub_read(&c, 1)) { |
| if (c == '"') |
| break; |
| } |
| |
| /* skip whitespace */ |
| while (grub_read(&c, 1) && (c == ' ' || c == '\t')) |
| ; |
| |
| /* |
| * Format here should be four integers: |
| * |
| * Width Height NumberOfColors CharactersPerPixel |
| */ |
| i = 0; |
| width = c - '0'; |
| while (grub_read(&c, 1)) { |
| if (c >= '0' && c <= '9') |
| width = width * 10 + c - '0'; |
| else |
| break; |
| } |
| |
| /* skip whitespace to advance to next digit */ |
| while (grub_read(&c, 1) && (c == ' ' || c == '\t')) |
| ; |
| |
| height = c - '0'; |
| while (grub_read(&c, 1)) { |
| if (c >= '0' && c <= '9') |
| height = height * 10 + c - '0'; |
| else |
| break; |
| } |
| |
| /* skip whitespace to advance to next digit */ |
| while (grub_read(&c, 1) && (c == ' ' || c == '\t')) ; |
| |
| colors = c - '0'; |
| while (grub_read(&c, 1)) { |
| if (c >= '0' && c <= '9') |
| colors = colors * 10 + c - '0'; |
| else |
| break; |
| } |
| |
| /* eat rest of line - assumes chars per pixel is one */ |
| while (grub_read(&c, 1) && c != '"') |
| ; |
| |
| /* |
| * Parse the XPM palette - the format is: |
| * |
| * identifier colorspace #RRGGBB |
| * |
| * The identifier is simply a single character; the colorspace identifier |
| * is skipped as it's assumed to be "c" denoting RGB color. |
| * |
| * The six digits after the "#" are assumed to be a six digit RGB color |
| * identifier as defined in X11's rgb.txt file. |
| */ |
| for (i = 0, idx = 1; i < colors; i++) { |
| len = 0; |
| |
| while (grub_read(&c, 1) && c != '"') |
| ; |
| |
| grub_read(&c, 1); /* char */ |
| base = c; |
| grub_read(buf, 4); /* \t c # */ |
| |
| while (grub_read(&c, 1) && c != '"') { |
| if (len < sizeof(buf)) |
| buf[len++] = c; |
| } |
| |
| /* |
| * The RGB hex digits should be six characters in length. |
| * |
| * If the color field contains anything other than six |
| * characters, such as "None" to denote a transparent color, |
| * ignore it. |
| */ |
| if (len == 6) { |
| int r = ((hex(buf[0]) << 4) | hex(buf[1])) >> 2; |
| int g = ((hex(buf[2]) << 4) | hex(buf[3])) >> 2; |
| int b = ((hex(buf[4]) << 4) | hex(buf[5])) >> 2; |
| |
| if (idx > 14) { |
| errnum = ERR_TOOMANYCOLORS; |
| grub_close(); |
| return 0; |
| } |
| |
| pal[idx] = base; |
| splash_palette[idx++] = |
| ((r & PALETTE_COLORMASK) << PALETTE_REDSHIFT) | |
| ((g & PALETTE_COLORMASK) << PALETTE_GREENSHIFT) | |
| (b & PALETTE_COLORMASK); |
| } |
| } |
| |
| colors = idx - 1; /* actual number of colors used in XPM image */ |
| x = y = len = 0; |
| |
| /* clear (zero out) all four planes of the framebuffer */ |
| for (i = 0; i < SCREENBYTES; i++) |
| s1[i] = s2[i] = s4[i] = s8[i] = 0; |
| |
| /* parse the XPM data */ |
| while (y < height) { |
| /* exit on EOF, otherwise skip characters until an initial '"' */ |
| while (1) { |
| if (!grub_read(&c, 1)) { |
| errnum = ERR_CORRUPTXPM; |
| grub_close(); |
| return 0; |
| } |
| if (c == '"') |
| break; |
| } |
| |
| /* read characters until we hit an EOF or a terminating '"' */ |
| while (grub_read(&c, 1) && c != '"') { |
| int pixel = 0; |
| |
| /* |
| * Look up the specified pixel color in the palette; the |
| * pixel will not be drawn if its color cannot be found or |
| * if no colors were specified in the XPM image itself. |
| */ |
| for (i = 1; i <= colors; i++) |
| if (pal[i] == c) { |
| pixel = i; |
| break; |
| } |
| |
| /* |
| * A bit is set in each of the "planes" of the frame buffer to |
| * denote a pixel drawn in each color of the palette. |
| * |
| * The planes are a binary representation of the palette, so a |
| * pixel in color "1" of the palette would be denoted by setting a |
| * bit in plane "s1"; a pixel in color "15" of the palette would |
| * set the same bit in each of the four planes. |
| * |
| * Pixels are represented by set bits in a byte, in the order |
| * left-to-right (e.g. pixel 0 is 0x80, pixel 7 is 1.) |
| */ |
| if (pixel != 0) { |
| mask = 0x80 >> (x & 7); |
| |
| if (pixel & 1) |
| s1[len + (x >> 3)] |= mask; |
| if (pixel & 2) |
| s2[len + (x >> 3)] |= mask; |
| if (pixel & 4) |
| s4[len + (x >> 3)] |= mask; |
| if (pixel & 8) |
| s8[len + (x >> 3)] |= mask; |
| } |
| |
| /* |
| * Increment "x"; if we hit pixel HPIXELS, wrap to the start of the |
| * next horizontal line if we haven't yet reached the bottom of |
| * the screen. |
| */ |
| if (++x >= HPIXELS) { |
| x = 0; |
| |
| if (y++ < VPIXELS) |
| len += ROWBYTES; |
| else |
| break; |
| } |
| } |
| } |
| |
| grub_close(); |
| |
| return 1; |
| } |
| |
| /* Convert a character which is a hex digit to the appropriate integer */ |
| int hex(int v) |
| { |
| if (v >= 'A' && v <= 'F') |
| return (v - 'A' + 10); |
| if (v >= 'a' && v <= 'f') |
| return (v - 'a' + 10); |
| return (v - '0'); |
| } |
| |
| |
| /* move the graphics cursor location to col, row */ |
| static void graphics_setxy(int col, int row) { |
| if (col >= x0 && col < x1) { |
| fontx = col; |
| cursorX = col << 3; |
| } |
| if (row >= y0 && row < y1) { |
| fonty = row; |
| cursorY = row << 4; |
| } |
| } |
| |
| /* scroll the screen */ |
| static void graphics_scroll() { |
| int i, j; |
| |
| /* we don't want to scroll recursively... that would be bad */ |
| if (no_scroll) |
| return; |
| no_scroll = 1; |
| |
| /* move everything up a line */ |
| for (j = y0 + 1; j < y1; j++) { |
| graphics_gotoxy(x0, j - 1); |
| for (i = x0; i < x1; i++) { |
| graphics_putchar(text[j * ROWBYTES + i]); |
| } |
| } |
| |
| /* last line should be blank */ |
| graphics_gotoxy(x0, y1 - 1); |
| for (i = x0; i < x1; i++) |
| graphics_putchar(' '); |
| graphics_setxy(x0, y1 - 1); |
| |
| no_scroll = 0; |
| } |
| |
| void graphics_cursor(int set) { |
| unsigned char *pat, *mem, *ptr, chr[16 << 2]; |
| int i, ch, invert, offset; |
| |
| if (set && no_scroll) |
| return; |
| |
| offset = cursorY * ROWBYTES + fontx; |
| ch = text[fonty * ROWBYTES + fontx] & 0xff; |
| invert = (text[fonty * ROWBYTES + fontx] & 0xff00) != 0; |
| pat = font8x16 + (ch << 4); |
| |
| mem = (unsigned char*)VIDEOMEM + offset; |
| |
| if (!set) { |
| for (i = 0; i < 16; i++) { |
| unsigned char mask = pat[i]; |
| |
| if (!invert) { |
| chr[i ] = ((unsigned char*)VSHADOW1)[offset]; |
| chr[16 + i] = ((unsigned char*)VSHADOW2)[offset]; |
| chr[32 + i] = ((unsigned char*)VSHADOW4)[offset]; |
| chr[48 + i] = ((unsigned char*)VSHADOW8)[offset]; |
| |
| /* FIXME: if (shade) */ |
| if (1) { |
| if (ch == DISP_VERT || ch == DISP_LL || |
| ch == DISP_UR || ch == DISP_LR) { |
| unsigned char pmask = ~(pat[i] >> 1); |
| |
| chr[i ] &= pmask; |
| chr[16 + i] &= pmask; |
| chr[32 + i] &= pmask; |
| chr[48 + i] &= pmask; |
| } |
| if (i > 0 && ch != DISP_VERT) { |
| unsigned char pmask = ~(pat[i - 1] >> 1); |
| |
| chr[i ] &= pmask; |
| chr[16 + i] &= pmask; |
| chr[32 + i] &= pmask; |
| chr[48 + i] &= pmask; |
| if (ch == DISP_HORIZ || ch == DISP_UR || ch == DISP_LR) { |
| pmask = ~pat[i - 1]; |
| |
| chr[i ] &= pmask; |
| chr[16 + i] &= pmask; |
| chr[32 + i] &= pmask; |
| chr[48 + i] &= pmask; |
| } |
| } |
| } |
| chr[i ] |= mask; |
| chr[16 + i] |= mask; |
| chr[32 + i] |= mask; |
| chr[48 + i] |= mask; |
| |
| offset += ROWBYTES; |
| } |
| else { |
| chr[i ] = mask; |
| chr[16 + i] = mask; |
| chr[32 + i] = mask; |
| chr[48 + i] = mask; |
| } |
| } |
| } |
| else { |
| MapMask(15); |
| ptr = mem; |
| for (i = 0; i < 16; i++, ptr += ROWBYTES) { |
| cursorBuf[i] = pat[i]; |
| *ptr = ~pat[i]; |
| } |
| return; |
| } |
| |
| offset = 0; |
| for (i = 1; i < 16; i <<= 1, offset += 16) { |
| int j; |
| |
| MapMask(i); |
| ptr = mem; |
| for (j = 0; j < 16; j++, ptr += ROWBYTES) |
| *ptr = chr[j + offset]; |
| } |
| |
| MapMask(15); |
| } |
| |
| #endif /* SUPPORT_GRAPHICS */ |