diff --git a/stand/common/gfx_fb.c b/stand/common/gfx_fb.c index 08c0f59505f2..2aed8775a540 100644 --- a/stand/common/gfx_fb.c +++ b/stand/common/gfx_fb.c @@ -1,2686 +1,2679 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright 2020 Toomas Soome * Copyright 2019 OmniOS Community Edition (OmniOSce) Association. * Copyright 2020 RackTop Systems, Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ /* * The workhorse here is gfxfb_blt(). It is implemented to mimic UEFI * GOP Blt, and allows us to fill the rectangle on screen, copy * rectangle from video to buffer and buffer to video and video to video. * Such implementation does allow us to have almost identical implementation * for both BIOS VBE and UEFI. * * ALL pixel data is assumed to be 32-bit BGRA (byte order Blue, Green, Red, * Alpha) format, this allows us to only handle RGB data and not to worry * about mixing RGB with indexed colors. * Data exchange between memory buffer and video will translate BGRA * and native format as following: * * 32-bit to/from 32-bit is trivial case. * 32-bit to/from 24-bit is also simple - we just drop the alpha channel. * 32-bit to/from 16-bit is more complicated, because we nee to handle * data loss from 32-bit to 16-bit. While reading/writing from/to video, we * need to apply masks of 16-bit color components. This will preserve * colors for terminal text. For 32-bit truecolor PMG images, we need to * translate 32-bit colors to 15/16 bit colors and this means data loss. * There are different algorithms how to perform such color space reduction, * we are currently using bitwise right shift to reduce color space and so far * this technique seems to be sufficient (see also gfx_fb_putimage(), the * end of for loop). * 32-bit to/from 8-bit is the most troublesome because 8-bit colors are * indexed. From video, we do get color indexes, and we do translate * color index values to RGB. To write to video, we again need to translate * RGB to color index. Additionally, we need to translate between VGA and * console colors. * * Our internal color data is represented using BGRA format. But the hardware * used indexed colors for 8-bit colors (0-255) and for this mode we do * need to perform translation to/from BGRA and index values. * * - paletteentry RGB <-> index - * BGRA BUFFER <----/ \ - VIDEO * \ / * - RGB (16/24/32) - * * To perform index to RGB translation, we use palette table generated * from when we set up 8-bit mode video. We cannot read palette data from * the hardware, because not all hardware supports reading it. * * BGRA to index is implemented in rgb_to_color_index() by searching * palette array for closest match of RBG values. * * Note: In 8-bit mode, We do store first 16 colors to palette registers * in VGA color order, this serves two purposes; firstly, * if palette update is not supported, we still have correct 16 colors. * Secondly, the kernel does get correct 16 colors when some other boot * loader is used. However, the palette map for 8-bit colors is using * console color ordering - this does allow us to skip translation * from VGA colors to console colors, while we are reading RGB data. */ #include #include #include #include #include #include #include #include #include #include #include #if defined(EFI) #include #include #else #include #endif /* VGA text mode does use bold font. */ #if !defined(VGA_8X16_FONT) #define VGA_8X16_FONT "/boot/fonts/8x16b.fnt" #endif #if !defined(DEFAULT_8X16_FONT) #define DEFAULT_8X16_FONT "/boot/fonts/8x16.fnt" #endif /* * Must be sorted by font size in descending order */ font_list_t fonts = STAILQ_HEAD_INITIALIZER(fonts); #define DEFAULT_FONT_DATA font_data_8x16 extern vt_font_bitmap_data_t font_data_8x16; teken_gfx_t gfx_state = { 0 }; static struct { unsigned char r; /* Red percentage value. */ unsigned char g; /* Green percentage value. */ unsigned char b; /* Blue percentage value. */ } color_def[NCOLORS] = { {0, 0, 0}, /* black */ {50, 0, 0}, /* dark red */ {0, 50, 0}, /* dark green */ {77, 63, 0}, /* dark yellow */ {20, 40, 64}, /* dark blue */ {50, 0, 50}, /* dark magenta */ {0, 50, 50}, /* dark cyan */ {75, 75, 75}, /* light gray */ {18, 20, 21}, /* dark gray */ {100, 0, 0}, /* light red */ {0, 100, 0}, /* light green */ {100, 100, 0}, /* light yellow */ {45, 62, 81}, /* light blue */ {100, 0, 100}, /* light magenta */ {0, 100, 100}, /* light cyan */ {100, 100, 100}, /* white */ }; uint32_t cmap[NCMAP]; /* * Between console's palette and VGA's one: * - blue and red are swapped (1 <-> 4) * - yellow and cyan are swapped (3 <-> 6) */ const int cons_to_vga_colors[NCOLORS] = { 0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15 }; static const int vga_to_cons_colors[NCOLORS] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; struct text_pixel *screen_buffer; #if defined(EFI) static EFI_GRAPHICS_OUTPUT_BLT_PIXEL *GlyphBuffer; #else static struct paletteentry *GlyphBuffer; #endif static size_t GlyphBufferSize; static bool insert_font(char *, FONT_FLAGS); static int font_set(struct env_var *, int, const void *); static void * allocate_glyphbuffer(uint32_t, uint32_t); static void gfx_fb_cursor_draw(teken_gfx_t *, const teken_pos_t *, bool); /* * Initialize gfx framework. */ void gfx_framework_init(void) { /* * Setup font list to have builtin font. */ (void) insert_font(NULL, FONT_BUILTIN); } static uint8_t * gfx_get_fb_address(void) { return (ptov((uint32_t)gfx_state.tg_fb.fb_addr)); } /* * Utility function to parse gfx mode line strings. */ bool gfx_parse_mode_str(char *str, int *x, int *y, int *depth) { char *p, *end; errno = 0; p = str; *x = strtoul(p, &end, 0); if (*x == 0 || errno != 0) return (false); if (*end != 'x') return (false); p = end + 1; *y = strtoul(p, &end, 0); if (*y == 0 || errno != 0) return (false); if (*end != 'x') { *depth = -1; /* auto select */ } else { p = end + 1; *depth = strtoul(p, &end, 0); if (*depth == 0 || errno != 0 || *end != '\0') return (false); } return (true); } static uint32_t rgb_color_map(uint8_t index, uint32_t rmax, int roffset, uint32_t gmax, int goffset, uint32_t bmax, int boffset) { uint32_t color, code, gray, level; if (index < NCOLORS) { #define CF(_f, _i) ((_f ## max * color_def[(_i)]._f / 100) << _f ## offset) return (CF(r, index) | CF(g, index) | CF(b, index)); #undef CF } #define CF(_f, _c) ((_f ## max & _c) << _f ## offset) /* 6x6x6 color cube */ if (index > 15 && index < 232) { uint32_t red, green, blue; for (red = 0; red < 6; red++) { for (green = 0; green < 6; green++) { for (blue = 0; blue < 6; blue++) { code = 16 + (red * 36) + (green * 6) + blue; if (code != index) continue; red = red ? (red * 40 + 55) : 0; green = green ? (green * 40 + 55) : 0; blue = blue ? (blue * 40 + 55) : 0; color = CF(r, red); color |= CF(g, green); color |= CF(b, blue); return (color); } } } } /* colors 232-255 are a grayscale ramp */ for (gray = 0; gray < 24; gray++) { level = (gray * 10) + 8; code = 232 + gray; if (code == index) break; } return (CF(r, level) | CF(g, level) | CF(b, level)); #undef CF } /* * Support for color mapping. * For 8, 24 and 32 bit depth, use mask size 8. * 15/16 bit depth needs to use mask size from mode, * or we will lose color information from 32-bit to 15/16 bit translation. */ uint32_t gfx_fb_color_map(uint8_t index) { int rmask, gmask, bmask; int roff, goff, boff, bpp; roff = ffs(gfx_state.tg_fb.fb_mask_red) - 1; goff = ffs(gfx_state.tg_fb.fb_mask_green) - 1; boff = ffs(gfx_state.tg_fb.fb_mask_blue) - 1; bpp = roundup2(gfx_state.tg_fb.fb_bpp, 8) >> 3; if (bpp == 2) rmask = gfx_state.tg_fb.fb_mask_red >> roff; else rmask = 0xff; if (bpp == 2) gmask = gfx_state.tg_fb.fb_mask_green >> goff; else gmask = 0xff; if (bpp == 2) bmask = gfx_state.tg_fb.fb_mask_blue >> boff; else bmask = 0xff; return (rgb_color_map(index, rmask, 16, gmask, 8, bmask, 0)); } /* * Get indexed color from RGB. This function is used to write data to video * memory when the adapter is set to use indexed colors. * Since UEFI does only support 32-bit colors, we do not implement it for * UEFI because there is no need for it and we do not have palette array * for UEFI. */ static uint8_t rgb_to_color_index(uint8_t r, uint8_t g, uint8_t b) { #if !defined(EFI) uint32_t color, best, dist, k; int diff; color = 0; best = 255 * 255 * 255; for (k = 0; k < NCMAP; k++) { diff = r - pe8[k].Red; dist = diff * diff; diff = g - pe8[k].Green; dist += diff * diff; diff = b - pe8[k].Blue; dist += diff * diff; /* Exact match, exit the loop */ if (dist == 0) break; if (dist < best) { color = k; best = dist; } } if (k == NCMAP) k = color; return (k); #else (void) r; (void) g; (void) b; return (0); #endif } int generate_cons_palette(uint32_t *palette, int format, uint32_t rmax, int roffset, uint32_t gmax, int goffset, uint32_t bmax, int boffset) { int i; switch (format) { case COLOR_FORMAT_VGA: for (i = 0; i < NCOLORS; i++) palette[i] = cons_to_vga_colors[i]; for (; i < NCMAP; i++) palette[i] = i; break; case COLOR_FORMAT_RGB: for (i = 0; i < NCMAP; i++) palette[i] = rgb_color_map(i, rmax, roffset, gmax, goffset, bmax, boffset); break; default: return (ENODEV); } return (0); } static void gfx_mem_wr1(uint8_t *base, size_t size, uint32_t o, uint8_t v) { if (o >= size) return; *(uint8_t *)(base + o) = v; } static void gfx_mem_wr2(uint8_t *base, size_t size, uint32_t o, uint16_t v) { if (o >= size) return; *(uint16_t *)(base + o) = v; } static void gfx_mem_wr4(uint8_t *base, size_t size, uint32_t o, uint32_t v) { if (o >= size) return; *(uint32_t *)(base + o) = v; } static int gfxfb_blt_fill(void *BltBuffer, uint32_t DestinationX, uint32_t DestinationY, uint32_t Width, uint32_t Height) { #if defined(EFI) EFI_GRAPHICS_OUTPUT_BLT_PIXEL *p; #else struct paletteentry *p; #endif uint32_t data, bpp, pitch, y, x; int roff, goff, boff; size_t size; off_t off; uint8_t *destination; if (BltBuffer == NULL) return (EINVAL); if (DestinationY + Height > gfx_state.tg_fb.fb_height) return (EINVAL); if (DestinationX + Width > gfx_state.tg_fb.fb_width) return (EINVAL); if (Width == 0 || Height == 0) return (EINVAL); p = BltBuffer; roff = ffs(gfx_state.tg_fb.fb_mask_red) - 1; goff = ffs(gfx_state.tg_fb.fb_mask_green) - 1; boff = ffs(gfx_state.tg_fb.fb_mask_blue) - 1; if (gfx_state.tg_fb.fb_bpp == 8) { data = rgb_to_color_index(p->Red, p->Green, p->Blue); } else { data = (p->Red & (gfx_state.tg_fb.fb_mask_red >> roff)) << roff; data |= (p->Green & (gfx_state.tg_fb.fb_mask_green >> goff)) << goff; data |= (p->Blue & (gfx_state.tg_fb.fb_mask_blue >> boff)) << boff; } bpp = roundup2(gfx_state.tg_fb.fb_bpp, 8) >> 3; pitch = gfx_state.tg_fb.fb_stride * bpp; destination = gfx_get_fb_address(); size = gfx_state.tg_fb.fb_size; for (y = DestinationY; y < Height + DestinationY; y++) { off = y * pitch + DestinationX * bpp; for (x = 0; x < Width; x++) { switch (bpp) { case 1: gfx_mem_wr1(destination, size, off, (data < NCOLORS) ? cons_to_vga_colors[data] : data); break; case 2: gfx_mem_wr2(destination, size, off, data); break; case 3: gfx_mem_wr1(destination, size, off, (data >> 16) & 0xff); gfx_mem_wr1(destination, size, off + 1, (data >> 8) & 0xff); gfx_mem_wr1(destination, size, off + 2, data & 0xff); break; case 4: gfx_mem_wr4(destination, size, off, data); break; default: return (EINVAL); } off += bpp; } } return (0); } static int gfxfb_blt_video_to_buffer(void *BltBuffer, uint32_t SourceX, uint32_t SourceY, uint32_t DestinationX, uint32_t DestinationY, uint32_t Width, uint32_t Height, uint32_t Delta) { #if defined(EFI) EFI_GRAPHICS_OUTPUT_BLT_PIXEL *p; #else struct paletteentry *p; #endif uint32_t x, sy, dy; uint32_t bpp, pitch, copybytes; off_t off; uint8_t *source, *destination, *sb; uint8_t rm, rp, gm, gp, bm, bp; bool bgra; if (BltBuffer == NULL) return (EINVAL); if (SourceY + Height > gfx_state.tg_fb.fb_height) return (EINVAL); if (SourceX + Width > gfx_state.tg_fb.fb_width) return (EINVAL); if (Width == 0 || Height == 0) return (EINVAL); if (Delta == 0) Delta = Width * sizeof (*p); bpp = roundup2(gfx_state.tg_fb.fb_bpp, 8) >> 3; pitch = gfx_state.tg_fb.fb_stride * bpp; copybytes = Width * bpp; rp = ffs(gfx_state.tg_fb.fb_mask_red) - 1; gp = ffs(gfx_state.tg_fb.fb_mask_green) - 1; bp = ffs(gfx_state.tg_fb.fb_mask_blue) - 1; rm = gfx_state.tg_fb.fb_mask_red >> rp; gm = gfx_state.tg_fb.fb_mask_green >> gp; bm = gfx_state.tg_fb.fb_mask_blue >> bp; /* If FB pixel format is BGRA, we can use direct copy. */ bgra = bpp == 4 && ffs(rm) - 1 == 8 && rp == 16 && ffs(gm) - 1 == 8 && gp == 8 && ffs(bm) - 1 == 8 && bp == 0; for (sy = SourceY, dy = DestinationY; dy < Height + DestinationY; sy++, dy++) { off = sy * pitch + SourceX * bpp; source = gfx_get_fb_address() + off; destination = (uint8_t *)BltBuffer + dy * Delta + DestinationX * sizeof (*p); if (bgra) { bcopy(source, destination, copybytes); } else { for (x = 0; x < Width; x++) { uint32_t c = 0; p = (void *)(destination + x * sizeof (*p)); sb = source + x * bpp; switch (bpp) { case 1: c = *sb; break; case 2: c = *(uint16_t *)sb; break; case 3: c = sb[0] << 16 | sb[1] << 8 | sb[2]; break; case 4: c = *(uint32_t *)sb; break; default: return (EINVAL); } if (bpp == 1) { *(uint32_t *)p = gfx_fb_color_map( (c < 16) ? vga_to_cons_colors[c] : c); } else { p->Red = (c >> rp) & rm; p->Green = (c >> gp) & gm; p->Blue = (c >> bp) & bm; p->Reserved = 0; } } } } return (0); } static int gfxfb_blt_buffer_to_video(void *BltBuffer, uint32_t SourceX, uint32_t SourceY, uint32_t DestinationX, uint32_t DestinationY, uint32_t Width, uint32_t Height, uint32_t Delta) { #if defined(EFI) EFI_GRAPHICS_OUTPUT_BLT_PIXEL *p; #else struct paletteentry *p; #endif uint32_t x, sy, dy; uint32_t bpp, pitch, copybytes; off_t off; uint8_t *source, *destination; uint8_t rm, rp, gm, gp, bm, bp; bool bgra; if (BltBuffer == NULL) return (EINVAL); if (DestinationY + Height > gfx_state.tg_fb.fb_height) return (EINVAL); if (DestinationX + Width > gfx_state.tg_fb.fb_width) return (EINVAL); if (Width == 0 || Height == 0) return (EINVAL); if (Delta == 0) Delta = Width * sizeof (*p); bpp = roundup2(gfx_state.tg_fb.fb_bpp, 8) >> 3; pitch = gfx_state.tg_fb.fb_stride * bpp; copybytes = Width * bpp; rp = ffs(gfx_state.tg_fb.fb_mask_red) - 1; gp = ffs(gfx_state.tg_fb.fb_mask_green) - 1; bp = ffs(gfx_state.tg_fb.fb_mask_blue) - 1; rm = gfx_state.tg_fb.fb_mask_red >> rp; gm = gfx_state.tg_fb.fb_mask_green >> gp; bm = gfx_state.tg_fb.fb_mask_blue >> bp; /* If FB pixel format is BGRA, we can use direct copy. */ bgra = bpp == 4 && ffs(rm) - 1 == 8 && rp == 16 && ffs(gm) - 1 == 8 && gp == 8 && ffs(bm) - 1 == 8 && bp == 0; for (sy = SourceY, dy = DestinationY; sy < Height + SourceY; sy++, dy++) { off = dy * pitch + DestinationX * bpp; destination = gfx_get_fb_address() + off; if (bgra) { source = (uint8_t *)BltBuffer + sy * Delta + SourceX * sizeof (*p); bcopy(source, destination, copybytes); } else { for (x = 0; x < Width; x++) { uint32_t c; p = (void *)((uint8_t *)BltBuffer + sy * Delta + (SourceX + x) * sizeof (*p)); if (bpp == 1) { c = rgb_to_color_index(p->Red, p->Green, p->Blue); } else { c = (p->Red & rm) << rp | (p->Green & gm) << gp | (p->Blue & bm) << bp; } off = x * bpp; switch (bpp) { case 1: gfx_mem_wr1(destination, copybytes, off, (c < 16) ? cons_to_vga_colors[c] : c); break; case 2: gfx_mem_wr2(destination, copybytes, off, c); break; case 3: gfx_mem_wr1(destination, copybytes, off, (c >> 16) & 0xff); gfx_mem_wr1(destination, copybytes, off + 1, (c >> 8) & 0xff); gfx_mem_wr1(destination, copybytes, off + 2, c & 0xff); break; case 4: gfx_mem_wr4(destination, copybytes, x * bpp, c); break; default: return (EINVAL); } } } } return (0); } static int gfxfb_blt_video_to_video(uint32_t SourceX, uint32_t SourceY, uint32_t DestinationX, uint32_t DestinationY, uint32_t Width, uint32_t Height) { uint32_t bpp, copybytes; int pitch; uint8_t *source, *destination; off_t off; if (SourceY + Height > gfx_state.tg_fb.fb_height) return (EINVAL); if (SourceX + Width > gfx_state.tg_fb.fb_width) return (EINVAL); if (DestinationY + Height > gfx_state.tg_fb.fb_height) return (EINVAL); if (DestinationX + Width > gfx_state.tg_fb.fb_width) return (EINVAL); if (Width == 0 || Height == 0) return (EINVAL); bpp = roundup2(gfx_state.tg_fb.fb_bpp, 8) >> 3; pitch = gfx_state.tg_fb.fb_stride * bpp; copybytes = Width * bpp; off = SourceY * pitch + SourceX * bpp; source = gfx_get_fb_address() + off; off = DestinationY * pitch + DestinationX * bpp; destination = gfx_get_fb_address() + off; if ((uintptr_t)destination > (uintptr_t)source) { source += Height * pitch; destination += Height * pitch; pitch = -pitch; } while (Height-- > 0) { bcopy(source, destination, copybytes); source += pitch; destination += pitch; } return (0); } int gfxfb_blt(void *BltBuffer, GFXFB_BLT_OPERATION BltOperation, uint32_t SourceX, uint32_t SourceY, uint32_t DestinationX, uint32_t DestinationY, uint32_t Width, uint32_t Height, uint32_t Delta) { int rv; #if defined(EFI) EFI_STATUS status; EFI_GRAPHICS_OUTPUT *gop = gfx_state.tg_private; /* * We assume Blt() does work, if not, we will need to build * exception list case by case. */ if (gop != NULL) { switch (BltOperation) { case GfxFbBltVideoFill: status = gop->Blt(gop, BltBuffer, EfiBltVideoFill, SourceX, SourceY, DestinationX, DestinationY, Width, Height, Delta); break; case GfxFbBltVideoToBltBuffer: status = gop->Blt(gop, BltBuffer, EfiBltVideoToBltBuffer, SourceX, SourceY, DestinationX, DestinationY, Width, Height, Delta); break; case GfxFbBltBufferToVideo: status = gop->Blt(gop, BltBuffer, EfiBltBufferToVideo, SourceX, SourceY, DestinationX, DestinationY, Width, Height, Delta); break; case GfxFbBltVideoToVideo: status = gop->Blt(gop, BltBuffer, EfiBltVideoToVideo, SourceX, SourceY, DestinationX, DestinationY, Width, Height, Delta); break; default: status = EFI_INVALID_PARAMETER; break; } switch (status) { case EFI_SUCCESS: rv = 0; break; case EFI_INVALID_PARAMETER: rv = EINVAL; break; case EFI_DEVICE_ERROR: default: rv = EIO; break; } return (rv); } #endif switch (BltOperation) { case GfxFbBltVideoFill: rv = gfxfb_blt_fill(BltBuffer, DestinationX, DestinationY, Width, Height); break; case GfxFbBltVideoToBltBuffer: rv = gfxfb_blt_video_to_buffer(BltBuffer, SourceX, SourceY, DestinationX, DestinationY, Width, Height, Delta); break; case GfxFbBltBufferToVideo: rv = gfxfb_blt_buffer_to_video(BltBuffer, SourceX, SourceY, DestinationX, DestinationY, Width, Height, Delta); break; case GfxFbBltVideoToVideo: rv = gfxfb_blt_video_to_video(SourceX, SourceY, DestinationX, DestinationY, Width, Height); break; default: rv = EINVAL; break; } return (rv); } void gfx_bitblt_bitmap(teken_gfx_t *state, const uint8_t *glyph, const teken_attr_t *a, uint32_t alpha, bool cursor) { uint32_t width, height; uint32_t fgc, bgc, bpl, cc, o; int bpp, bit, byte; bool invert = false; bpp = 4; /* We only generate BGRA */ width = state->tg_font.vf_width; height = state->tg_font.vf_height; bpl = (width + 7) / 8; /* Bytes per source line. */ fgc = a->ta_fgcolor; bgc = a->ta_bgcolor; if (a->ta_format & TF_BOLD) fgc |= TC_LIGHT; if (a->ta_format & TF_BLINK) bgc |= TC_LIGHT; fgc = gfx_fb_color_map(fgc); bgc = gfx_fb_color_map(bgc); if (a->ta_format & TF_REVERSE) invert = !invert; if (cursor) invert = !invert; if (invert) { uint32_t tmp; tmp = fgc; fgc = bgc; bgc = tmp; } alpha = alpha << 24; fgc |= alpha; bgc |= alpha; for (uint32_t y = 0; y < height; y++) { for (uint32_t x = 0; x < width; x++) { byte = y * bpl + x / 8; bit = 0x80 >> (x % 8); o = y * width * bpp + x * bpp; cc = glyph[byte] & bit ? fgc : bgc; gfx_mem_wr4(state->tg_glyph, state->tg_glyph_size, o, cc); } } } /* * Draw prepared glyph on terminal point p. */ static void gfx_fb_printchar(teken_gfx_t *state, const teken_pos_t *p) { unsigned x, y, width, height; width = state->tg_font.vf_width; height = state->tg_font.vf_height; x = state->tg_origin.tp_col + p->tp_col * width; y = state->tg_origin.tp_row + p->tp_row * height; gfx_fb_cons_display(x, y, width, height, state->tg_glyph); } /* * Store char with its attribute to buffer and put it on screen. */ void gfx_fb_putchar(void *arg, const teken_pos_t *p, teken_char_t c, const teken_attr_t *a) { teken_gfx_t *state = arg; const uint8_t *glyph; int idx; idx = p->tp_col + p->tp_row * state->tg_tp.tp_col; if (idx >= state->tg_tp.tp_col * state->tg_tp.tp_row) return; /* remove the cursor */ if (state->tg_cursor_visible) gfx_fb_cursor_draw(state, &state->tg_cursor, false); screen_buffer[idx].c = c; screen_buffer[idx].a = *a; glyph = font_lookup(&state->tg_font, c, a); gfx_bitblt_bitmap(state, glyph, a, 0xff, false); gfx_fb_printchar(state, p); /* display the cursor */ if (state->tg_cursor_visible) { const teken_pos_t *c; c = teken_get_cursor(&state->tg_teken); gfx_fb_cursor_draw(state, c, true); } } void gfx_fb_fill(void *arg, const teken_rect_t *r, teken_char_t c, const teken_attr_t *a) { teken_gfx_t *state = arg; const uint8_t *glyph; teken_pos_t p; struct text_pixel *row; /* remove the cursor */ if (state->tg_cursor_visible) gfx_fb_cursor_draw(state, &state->tg_cursor, false); glyph = font_lookup(&state->tg_font, c, a); gfx_bitblt_bitmap(state, glyph, a, 0xff, false); for (p.tp_row = r->tr_begin.tp_row; p.tp_row < r->tr_end.tp_row; p.tp_row++) { row = &screen_buffer[p.tp_row * state->tg_tp.tp_col]; for (p.tp_col = r->tr_begin.tp_col; p.tp_col < r->tr_end.tp_col; p.tp_col++) { row[p.tp_col].c = c; row[p.tp_col].a = *a; gfx_fb_printchar(state, &p); } } /* display the cursor */ if (state->tg_cursor_visible) { const teken_pos_t *c; c = teken_get_cursor(&state->tg_teken); gfx_fb_cursor_draw(state, c, true); } } static void gfx_fb_cursor_draw(teken_gfx_t *state, const teken_pos_t *p, bool on) { const uint8_t *glyph; int idx; idx = p->tp_col + p->tp_row * state->tg_tp.tp_col; if (idx >= state->tg_tp.tp_col * state->tg_tp.tp_row) return; glyph = font_lookup(&state->tg_font, screen_buffer[idx].c, &screen_buffer[idx].a); gfx_bitblt_bitmap(state, glyph, &screen_buffer[idx].a, 0xff, on); gfx_fb_printchar(state, p); state->tg_cursor = *p; } void gfx_fb_cursor(void *arg, const teken_pos_t *p) { teken_gfx_t *state = arg; #if defined(EFI) EFI_TPL tpl; tpl = BS->RaiseTPL(TPL_NOTIFY); #endif /* Switch cursor off in old location and back on in new. */ if (state->tg_cursor_visible) { gfx_fb_cursor_draw(state, &state->tg_cursor, false); gfx_fb_cursor_draw(state, p, true); } #if defined(EFI) BS->RestoreTPL(tpl); #endif } void gfx_fb_param(void *arg, int cmd, unsigned int value) { teken_gfx_t *state = arg; const teken_pos_t *c; switch (cmd) { case TP_SETLOCALCURSOR: /* * 0 means normal (usually block), 1 means hidden, and * 2 means blinking (always block) for compatibility with * syscons. We don't support any changes except hiding, * so must map 2 to 0. */ value = (value == 1) ? 0 : 1; /* FALLTHROUGH */ case TP_SHOWCURSOR: c = teken_get_cursor(&state->tg_teken); gfx_fb_cursor_draw(state, c, true); if (value != 0) state->tg_cursor_visible = true; else state->tg_cursor_visible = false; break; default: /* Not yet implemented */ break; } } bool is_same_pixel(struct text_pixel *px1, struct text_pixel *px2) { if (px1->c != px2->c) return (false); /* Is there image stored? */ if ((px1->a.ta_format & TF_IMAGE) || (px2->a.ta_format & TF_IMAGE)) return (false); if (px1->a.ta_format != px2->a.ta_format) return (false); if (px1->a.ta_fgcolor != px2->a.ta_fgcolor) return (false); if (px1->a.ta_bgcolor != px2->a.ta_bgcolor) return (false); return (true); } static void gfx_fb_copy_area(teken_gfx_t *state, const teken_rect_t *s, const teken_pos_t *d) { uint32_t sx, sy, dx, dy, width, height; width = state->tg_font.vf_width; height = state->tg_font.vf_height; sx = state->tg_origin.tp_col + s->tr_begin.tp_col * width; sy = state->tg_origin.tp_row + s->tr_begin.tp_row * height; dx = state->tg_origin.tp_col + d->tp_col * width; dy = state->tg_origin.tp_row + d->tp_row * height; width *= (s->tr_end.tp_col - s->tr_begin.tp_col + 1); (void) gfxfb_blt(NULL, GfxFbBltVideoToVideo, sx, sy, dx, dy, width, height, 0); } static void gfx_fb_copy_line(teken_gfx_t *state, int ncol, teken_pos_t *s, teken_pos_t *d) { teken_rect_t sr; teken_pos_t dp; unsigned soffset, doffset; bool mark = false; int x; soffset = s->tp_col + s->tp_row * state->tg_tp.tp_col; doffset = d->tp_col + d->tp_row * state->tg_tp.tp_col; for (x = 0; x < ncol; x++) { if (is_same_pixel(&screen_buffer[soffset + x], &screen_buffer[doffset + x])) { if (mark) { gfx_fb_copy_area(state, &sr, &dp); mark = false; } } else { screen_buffer[doffset + x] = screen_buffer[soffset + x]; if (mark) { /* update end point */ sr.tr_end.tp_col = s->tp_col + x;; } else { /* set up new rectangle */ mark = true; sr.tr_begin.tp_col = s->tp_col + x; sr.tr_begin.tp_row = s->tp_row; sr.tr_end.tp_col = s->tp_col + x; sr.tr_end.tp_row = s->tp_row; dp.tp_col = d->tp_col + x; dp.tp_row = d->tp_row; } } } if (mark) { gfx_fb_copy_area(state, &sr, &dp); } } void gfx_fb_copy(void *arg, const teken_rect_t *r, const teken_pos_t *p) { teken_gfx_t *state = arg; unsigned doffset, soffset; teken_pos_t d, s; int nrow, ncol, y; /* Has to be signed - >= 0 comparison */ /* * Copying is a little tricky. We must make sure we do it in * correct order, to make sure we don't overwrite our own data. */ nrow = r->tr_end.tp_row - r->tr_begin.tp_row; ncol = r->tr_end.tp_col - r->tr_begin.tp_col; if (p->tp_row + nrow > state->tg_tp.tp_row || p->tp_col + ncol > state->tg_tp.tp_col) return; soffset = r->tr_begin.tp_col + r->tr_begin.tp_row * state->tg_tp.tp_col; doffset = p->tp_col + p->tp_row * state->tg_tp.tp_col; /* remove the cursor */ if (state->tg_cursor_visible) gfx_fb_cursor_draw(state, &state->tg_cursor, false); /* * Copy line by line. */ if (doffset <= soffset) { s = r->tr_begin; d = *p; for (y = 0; y < nrow; y++) { s.tp_row = r->tr_begin.tp_row + y; d.tp_row = p->tp_row + y; gfx_fb_copy_line(state, ncol, &s, &d); } } else { for (y = nrow - 1; y >= 0; y--) { s.tp_row = r->tr_begin.tp_row + y; d.tp_row = p->tp_row + y; gfx_fb_copy_line(state, ncol, &s, &d); } } /* display the cursor */ if (state->tg_cursor_visible) { const teken_pos_t *c; c = teken_get_cursor(&state->tg_teken); gfx_fb_cursor_draw(state, c, true); } } /* * Implements alpha blending for RGBA data, could use pixels for arguments, * but byte stream seems more generic. * The generic alpha blending is: * blend = alpha * fg + (1.0 - alpha) * bg. * Since our alpha is not from range [0..1], we scale appropriately. */ static uint8_t alpha_blend(uint8_t fg, uint8_t bg, uint8_t alpha) { uint16_t blend, h, l; /* trivial corner cases */ if (alpha == 0) return (bg); if (alpha == 0xFF) return (fg); blend = (alpha * fg + (0xFF - alpha) * bg); /* Division by 0xFF */ h = blend >> 8; l = blend & 0xFF; if (h + l >= 0xFF) h++; return (h); } /* * Implements alpha blending for RGBA data, could use pixels for arguments, * but byte stream seems more generic. * The generic alpha blending is: * blend = alpha * fg + (1.0 - alpha) * bg. * Since our alpha is not from range [0..1], we scale appropriately. */ static void bitmap_cpy(void *dst, void *src, uint32_t size) { #if defined(EFI) EFI_GRAPHICS_OUTPUT_BLT_PIXEL *ps, *pd; #else struct paletteentry *ps, *pd; #endif uint32_t i; uint8_t a; ps = src; pd = dst; /* * we only implement alpha blending for depth 32. */ for (i = 0; i < size; i ++) { a = ps[i].Reserved; pd[i].Red = alpha_blend(ps[i].Red, pd[i].Red, a); pd[i].Green = alpha_blend(ps[i].Green, pd[i].Green, a); pd[i].Blue = alpha_blend(ps[i].Blue, pd[i].Blue, a); pd[i].Reserved = a; } } static void * allocate_glyphbuffer(uint32_t width, uint32_t height) { size_t size; size = sizeof (*GlyphBuffer) * width * height; if (size != GlyphBufferSize) { free(GlyphBuffer); GlyphBuffer = malloc(size); if (GlyphBuffer == NULL) return (NULL); GlyphBufferSize = size; } return (GlyphBuffer); } void gfx_fb_cons_display(uint32_t x, uint32_t y, uint32_t width, uint32_t height, void *data) { #if defined(EFI) EFI_GRAPHICS_OUTPUT_BLT_PIXEL *buf; #else struct paletteentry *buf; #endif size_t size; size = width * height * sizeof(*buf); /* * Common data to display is glyph, use preallocated * glyph buffer. */ if (gfx_state.tg_glyph_size != GlyphBufferSize) (void) allocate_glyphbuffer(width, height); if (size == GlyphBufferSize) buf = GlyphBuffer; else buf = malloc(size); if (buf == NULL) return; if (gfxfb_blt(buf, GfxFbBltVideoToBltBuffer, x, y, 0, 0, width, height, 0) == 0) { bitmap_cpy(buf, data, width * height); (void) gfxfb_blt(buf, GfxFbBltBufferToVideo, 0, 0, x, y, width, height, 0); } if (buf != GlyphBuffer) free(buf); } /* * Public graphics primitives. */ static int isqrt(int num) { int res = 0; int bit = 1 << 30; /* "bit" starts at the highest power of four <= the argument. */ while (bit > num) bit >>= 2; while (bit != 0) { if (num >= res + bit) { num -= res + bit; res = (res >> 1) + bit; } else { res >>= 1; } bit >>= 2; } return (res); } /* set pixel in framebuffer using gfx coordinates */ void gfx_fb_setpixel(uint32_t x, uint32_t y) { uint32_t c; const teken_attr_t *ap; if (gfx_state.tg_fb_type == FB_TEXT) return; ap = teken_get_curattr(&gfx_state.tg_teken); if (ap->ta_format & TF_REVERSE) { c = ap->ta_bgcolor; if (ap->ta_format & TF_BLINK) c |= TC_LIGHT; } else { c = ap->ta_fgcolor; if (ap->ta_format & TF_BOLD) c |= TC_LIGHT; } c = gfx_fb_color_map(c); if (x >= gfx_state.tg_fb.fb_width || y >= gfx_state.tg_fb.fb_height) return; gfxfb_blt(&c, GfxFbBltVideoFill, 0, 0, x, y, 1, 1, 0); } /* * draw rectangle in framebuffer using gfx coordinates. * The function is borrowed from vt_fb.c */ void gfx_fb_drawrect(uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2, uint32_t fill) { uint32_t x, y; if (gfx_state.tg_fb_type == FB_TEXT) return; for (y = y1; y <= y2; y++) { if (fill || (y == y1) || (y == y2)) { for (x = x1; x <= x2; x++) gfx_fb_setpixel(x, y); } else { gfx_fb_setpixel(x1, y); gfx_fb_setpixel(x2, y); } } } void gfx_fb_line(uint32_t x0, uint32_t y0, uint32_t x1, uint32_t y1, uint32_t wd) { int dx, sx, dy, sy; int err, e2, x2, y2, ed, width; if (gfx_state.tg_fb_type == FB_TEXT) return; width = wd; sx = x0 < x1? 1 : -1; sy = y0 < y1? 1 : -1; dx = x1 > x0? x1 - x0 : x0 - x1; dy = y1 > y0? y1 - y0 : y0 - y1; err = dx + dy; ed = dx + dy == 0 ? 1: isqrt(dx * dx + dy * dy); for (;;) { gfx_fb_setpixel(x0, y0); e2 = err; x2 = x0; if ((e2 << 1) >= -dx) { /* x step */ e2 += dy; y2 = y0; while (e2 < ed * width && (y1 != (uint32_t)y2 || dx > dy)) { y2 += sy; gfx_fb_setpixel(x0, y2); e2 += dx; } if (x0 == x1) break; e2 = err; err -= dy; x0 += sx; } if ((e2 << 1) <= dy) { /* y step */ e2 = dx-e2; while (e2 < ed * width && (x1 != (uint32_t)x2 || dx < dy)) { x2 += sx; gfx_fb_setpixel(x2, y0); e2 += dy; } if (y0 == y1) break; err += dx; y0 += sy; } } } /* * quadratic Bézier curve limited to gradients without sign change. */ void gfx_fb_bezier(uint32_t x0, uint32_t y0, uint32_t x1, uint32_t y1, uint32_t x2, uint32_t y2, uint32_t wd) { int sx, sy, xx, yy, xy, width; int dx, dy, err, curvature; int i; if (gfx_state.tg_fb_type == FB_TEXT) return; width = wd; sx = x2 - x1; sy = y2 - y1; xx = x0 - x1; yy = y0 - y1; curvature = xx*sy - yy*sx; if (sx*sx + sy*sy > xx*xx+yy*yy) { x2 = x0; x0 = sx + x1; y2 = y0; y0 = sy + y1; curvature = -curvature; } if (curvature != 0) { xx += sx; sx = x0 < x2? 1 : -1; xx *= sx; yy += sy; sy = y0 < y2? 1 : -1; yy *= sy; xy = (xx*yy) << 1; xx *= xx; yy *= yy; if (curvature * sx * sy < 0) { xx = -xx; yy = -yy; xy = -xy; curvature = -curvature; } dx = 4 * sy * curvature * (x1 - x0) + xx - xy; dy = 4 * sx * curvature * (y0 - y1) + yy - xy; xx += xx; yy += yy; err = dx + dy + xy; do { for (i = 0; i <= width; i++) gfx_fb_setpixel(x0 + i, y0); if (x0 == x2 && y0 == y2) return; /* last pixel -> curve finished */ y1 = 2 * err < dx; if (2 * err > dy) { x0 += sx; dx -= xy; dy += yy; err += dy; } if (y1 != 0) { y0 += sy; dy -= xy; dx += xx; err += dx; } } while (dy < dx); /* gradient negates -> algorithm fails */ } gfx_fb_line(x0, y0, x2, y2, width); } /* * draw rectangle using terminal coordinates and current foreground color. */ void gfx_term_drawrect(uint32_t ux1, uint32_t uy1, uint32_t ux2, uint32_t uy2) { int x1, y1, x2, y2; int xshift, yshift; int width, i; uint32_t vf_width, vf_height; teken_rect_t r; if (gfx_state.tg_fb_type == FB_TEXT) return; vf_width = gfx_state.tg_font.vf_width; vf_height = gfx_state.tg_font.vf_height; width = vf_width / 4; /* line width */ xshift = (vf_width - width) / 2; yshift = (vf_height - width) / 2; /* Shift coordinates */ if (ux1 != 0) ux1--; if (uy1 != 0) uy1--; ux2--; uy2--; /* mark area used in terminal */ r.tr_begin.tp_col = ux1; r.tr_begin.tp_row = uy1; r.tr_end.tp_col = ux2 + 1; r.tr_end.tp_row = uy2 + 1; term_image_display(&gfx_state, &r); /* * Draw horizontal lines width points thick, shifted from outer edge. */ x1 = (ux1 + 1) * vf_width + gfx_state.tg_origin.tp_col; y1 = uy1 * vf_height + gfx_state.tg_origin.tp_row + yshift; x2 = ux2 * vf_width + gfx_state.tg_origin.tp_col; gfx_fb_drawrect(x1, y1, x2, y1 + width, 1); y2 = uy2 * vf_height + gfx_state.tg_origin.tp_row; y2 += vf_height - yshift - width; gfx_fb_drawrect(x1, y2, x2, y2 + width, 1); /* * Draw vertical lines width points thick, shifted from outer edge. */ x1 = ux1 * vf_width + gfx_state.tg_origin.tp_col + xshift; y1 = uy1 * vf_height + gfx_state.tg_origin.tp_row; y1 += vf_height; y2 = uy2 * vf_height + gfx_state.tg_origin.tp_row; gfx_fb_drawrect(x1, y1, x1 + width, y2, 1); x1 = ux2 * vf_width + gfx_state.tg_origin.tp_col; x1 += vf_width - xshift - width; gfx_fb_drawrect(x1, y1, x1 + width, y2, 1); /* Draw upper left corner. */ x1 = ux1 * vf_width + gfx_state.tg_origin.tp_col + xshift; y1 = uy1 * vf_height + gfx_state.tg_origin.tp_row; y1 += vf_height; x2 = ux1 * vf_width + gfx_state.tg_origin.tp_col; x2 += vf_width; y2 = uy1 * vf_height + gfx_state.tg_origin.tp_row + yshift; for (i = 0; i <= width; i++) gfx_fb_bezier(x1 + i, y1, x1 + i, y2 + i, x2, y2 + i, width-i); /* Draw lower left corner. */ x1 = ux1 * vf_width + gfx_state.tg_origin.tp_col; x1 += vf_width; y1 = uy2 * vf_height + gfx_state.tg_origin.tp_row; y1 += vf_height - yshift; x2 = ux1 * vf_width + gfx_state.tg_origin.tp_col + xshift; y2 = uy2 * vf_height + gfx_state.tg_origin.tp_row; for (i = 0; i <= width; i++) gfx_fb_bezier(x1, y1 - i, x2 + i, y1 - i, x2 + i, y2, width-i); /* Draw upper right corner. */ x1 = ux2 * vf_width + gfx_state.tg_origin.tp_col; y1 = uy1 * vf_height + gfx_state.tg_origin.tp_row + yshift; x2 = ux2 * vf_width + gfx_state.tg_origin.tp_col; x2 += vf_width - xshift - width; y2 = uy1 * vf_height + gfx_state.tg_origin.tp_row; y2 += vf_height; for (i = 0; i <= width; i++) gfx_fb_bezier(x1, y1 + i, x2 + i, y1 + i, x2 + i, y2, width-i); /* Draw lower right corner. */ x1 = ux2 * vf_width + gfx_state.tg_origin.tp_col; y1 = uy2 * vf_height + gfx_state.tg_origin.tp_row; y1 += vf_height - yshift; x2 = ux2 * vf_width + gfx_state.tg_origin.tp_col; x2 += vf_width - xshift - width; y2 = uy2 * vf_height + gfx_state.tg_origin.tp_row; for (i = 0; i <= width; i++) gfx_fb_bezier(x1, y1 - i, x2 + i, y1 - i, x2 + i, y2, width-i); } int gfx_fb_putimage(png_t *png, uint32_t ux1, uint32_t uy1, uint32_t ux2, uint32_t uy2, uint32_t flags) { #if defined(EFI) EFI_GRAPHICS_OUTPUT_BLT_PIXEL *p; #else struct paletteentry *p; #endif uint8_t *data; uint32_t i, j, x, y, fheight, fwidth; int rs, gs, bs; uint8_t r, g, b, a; bool scale = false; bool trace = false; teken_rect_t rect; trace = (flags & FL_PUTIMAGE_DEBUG) != 0; if (gfx_state.tg_fb_type == FB_TEXT) { if (trace) printf("Framebuffer not active.\n"); return (1); } if (png->color_type != PNG_TRUECOLOR_ALPHA) { if (trace) printf("Not truecolor image.\n"); return (1); } if (ux1 > gfx_state.tg_fb.fb_width || uy1 > gfx_state.tg_fb.fb_height) { if (trace) printf("Top left coordinate off screen.\n"); return (1); } if (png->width > UINT16_MAX || png->height > UINT16_MAX) { if (trace) printf("Image too large.\n"); return (1); } if (png->width < 1 || png->height < 1) { if (trace) printf("Image too small.\n"); return (1); } /* * If 0 was passed for either ux2 or uy2, then calculate the missing * part of the bottom right coordinate. */ scale = true; if (ux2 == 0 && uy2 == 0) { /* Both 0, use the native resolution of the image */ ux2 = ux1 + png->width; uy2 = uy1 + png->height; scale = false; } else if (ux2 == 0) { /* Set ux2 from uy2/uy1 to maintain aspect ratio */ ux2 = ux1 + (png->width * (uy2 - uy1)) / png->height; } else if (uy2 == 0) { /* Set uy2 from ux2/ux1 to maintain aspect ratio */ uy2 = uy1 + (png->height * (ux2 - ux1)) / png->width; } if (ux2 > gfx_state.tg_fb.fb_width || uy2 > gfx_state.tg_fb.fb_height) { if (trace) printf("Bottom right coordinate off screen.\n"); return (1); } fwidth = ux2 - ux1; fheight = uy2 - uy1; /* * If the original image dimensions have been passed explicitly, * disable scaling. */ if (fwidth == png->width && fheight == png->height) scale = false; if (ux1 == 0) { /* * No top left X co-ordinate (real coordinates start at 1), * place as far right as it will fit. */ ux2 = gfx_state.tg_fb.fb_width - gfx_state.tg_origin.tp_col; ux1 = ux2 - fwidth; } if (uy1 == 0) { /* * No top left Y co-ordinate (real coordinates start at 1), * place as far down as it will fit. */ uy2 = gfx_state.tg_fb.fb_height - gfx_state.tg_origin.tp_row; uy1 = uy2 - fheight; } if (ux1 >= ux2 || uy1 >= uy2) { if (trace) printf("Image dimensions reversed.\n"); return (1); } if (fwidth < 2 || fheight < 2) { if (trace) printf("Target area too small\n"); return (1); } if (trace) printf("Image %ux%u -> %ux%u @%ux%u\n", png->width, png->height, fwidth, fheight, ux1, uy1); rect.tr_begin.tp_col = ux1 / gfx_state.tg_font.vf_width; rect.tr_begin.tp_row = uy1 / gfx_state.tg_font.vf_height; rect.tr_end.tp_col = (ux1 + fwidth) / gfx_state.tg_font.vf_width; rect.tr_end.tp_row = (uy1 + fheight) / gfx_state.tg_font.vf_height; /* * mark area used in terminal */ if (!(flags & FL_PUTIMAGE_NOSCROLL)) term_image_display(&gfx_state, &rect); if ((flags & FL_PUTIMAGE_BORDER)) gfx_fb_drawrect(ux1, uy1, ux2, uy2, 0); data = malloc(fwidth * fheight * sizeof(*p)); p = (void *)data; if (data == NULL) { if (trace) printf("Out of memory.\n"); return (1); } /* * Build image for our framebuffer. */ /* Helper to calculate the pixel index from the source png */ #define GETPIXEL(xx, yy) (((yy) * png->width + (xx)) * png->bpp) /* * For each of the x and y directions, calculate the number of pixels * in the source image that correspond to a single pixel in the target. * Use fixed-point arithmetic with 16-bits for each of the integer and * fractional parts. */ const uint32_t wcstep = ((png->width - 1) << 16) / (fwidth - 1); const uint32_t hcstep = ((png->height - 1) << 16) / (fheight - 1); rs = 8 - (fls(gfx_state.tg_fb.fb_mask_red) - ffs(gfx_state.tg_fb.fb_mask_red) + 1); gs = 8 - (fls(gfx_state.tg_fb.fb_mask_green) - ffs(gfx_state.tg_fb.fb_mask_green) + 1); bs = 8 - (fls(gfx_state.tg_fb.fb_mask_blue) - ffs(gfx_state.tg_fb.fb_mask_blue) + 1); uint32_t hc = 0; for (y = 0; y < fheight; y++) { uint32_t hc2 = (hc >> 9) & 0x7f; uint32_t hc1 = 0x80 - hc2; uint32_t offset_y = hc >> 16; uint32_t offset_y1 = offset_y + 1; uint32_t wc = 0; for (x = 0; x < fwidth; x++) { uint32_t wc2 = (wc >> 9) & 0x7f; uint32_t wc1 = 0x80 - wc2; uint32_t offset_x = wc >> 16; uint32_t offset_x1 = offset_x + 1; /* Target pixel index */ j = y * fwidth + x; if (!scale) { i = GETPIXEL(x, y); r = png->image[i]; g = png->image[i + 1]; b = png->image[i + 2]; a = png->image[i + 3]; } else { uint8_t pixel[4]; uint32_t p00 = GETPIXEL(offset_x, offset_y); uint32_t p01 = GETPIXEL(offset_x, offset_y1); uint32_t p10 = GETPIXEL(offset_x1, offset_y); uint32_t p11 = GETPIXEL(offset_x1, offset_y1); /* * Given a 2x2 array of pixels in the source * image, combine them to produce a single * value for the pixel in the target image. * Each column of pixels is combined using * a weighted average where the top and bottom * pixels contribute hc1 and hc2 respectively. * The calculation for bottom pixel pB and * top pixel pT is: * (pT * hc1 + pB * hc2) / (hc1 + hc2) * Once the values are determined for the two * columns of pixels, then the columns are * averaged together in the same way but using * wc1 and wc2 for the weightings. * * Since hc1 and hc2 are chosen so that * hc1 + hc2 == 128 (and same for wc1 + wc2), * the >> 14 below is a quick way to divide by * (hc1 + hc2) * (wc1 + wc2) */ for (i = 0; i < 4; i++) pixel[i] = ( (png->image[p00 + i] * hc1 + png->image[p01 + i] * hc2) * wc1 + (png->image[p10 + i] * hc1 + png->image[p11 + i] * hc2) * wc2) >> 14; r = pixel[0]; g = pixel[1]; b = pixel[2]; a = pixel[3]; } if (trace) printf("r/g/b: %x/%x/%x\n", r, g, b); /* * Rough colorspace reduction for 15/16 bit colors. */ p[j].Red = r >> rs; p[j].Green = g >> gs; p[j].Blue = b >> bs; p[j].Reserved = a; wc += wcstep; } hc += hcstep; } gfx_fb_cons_display(ux1, uy1, fwidth, fheight, data); free(data); return (0); } /* * Reset font flags to FONT_AUTO. */ void reset_font_flags(void) { struct fontlist *fl; STAILQ_FOREACH(fl, &fonts, font_next) { fl->font_flags = FONT_AUTO; } } static vt_font_bitmap_data_t * set_font(teken_unit_t *rows, teken_unit_t *cols, teken_unit_t h, teken_unit_t w) { vt_font_bitmap_data_t *font = NULL; struct fontlist *fl; unsigned height = h; unsigned width = w; /* * First check for manually loaded font. */ STAILQ_FOREACH(fl, &fonts, font_next) { if (fl->font_flags == FONT_MANUAL) { font = fl->font_data; if (font->vfbd_font == NULL && fl->font_load != NULL && fl->font_name != NULL) { font = fl->font_load(fl->font_name); } if (font == NULL || font->vfbd_font == NULL) font = NULL; break; } } if (font != NULL) { - *rows = (height - BORDER_PIXELS) / font->vfbd_height; - *cols = (width - BORDER_PIXELS) / font->vfbd_width; + *rows = height / font->vfbd_height; + *cols = width / font->vfbd_width; return (font); } /* * Find best font for these dimensions, or use default - * - * A 1 pixel border is the absolute minimum we could have - * as a border around the text window (BORDER_PIXELS = 2), - * however a slightly larger border not only looks better - * but for the fonts currently statically built into the - * emulator causes much better font selection for the - * normal range of screen resolutions. */ STAILQ_FOREACH(fl, &fonts, font_next) { font = fl->font_data; - if ((((*rows * font->vfbd_height) + BORDER_PIXELS) <= height) && - (((*cols * font->vfbd_width) + BORDER_PIXELS) <= width)) { + if ((*rows * font->vfbd_height <= height) && + (*cols * font->vfbd_width <= width)) { if (font->vfbd_font == NULL || fl->font_flags == FONT_RELOAD) { if (fl->font_load != NULL && fl->font_name != NULL) { font = fl->font_load(fl->font_name); } if (font == NULL) continue; } - *rows = (height - BORDER_PIXELS) / font->vfbd_height; - *cols = (width - BORDER_PIXELS) / font->vfbd_width; + *rows = height / font->vfbd_height; + *cols = width / font->vfbd_width; break; } font = NULL; } if (font == NULL) { /* * We have fonts sorted smallest last, try it before * falling back to builtin. */ fl = STAILQ_LAST(&fonts, fontlist, font_next); if (fl != NULL && fl->font_load != NULL && fl->font_name != NULL) { font = fl->font_load(fl->font_name); } if (font == NULL) font = &DEFAULT_FONT_DATA; - *rows = (height - BORDER_PIXELS) / font->vfbd_height; - *cols = (width - BORDER_PIXELS) / font->vfbd_width; + *rows = height / font->vfbd_height; + *cols = width / font->vfbd_width; } return (font); } static void cons_clear(void) { char clear[] = { '\033', 'c' }; /* Reset terminal */ teken_input(&gfx_state.tg_teken, clear, sizeof(clear)); gfx_state.tg_functions->tf_param(&gfx_state, TP_SHOWCURSOR, 0); } void setup_font(teken_gfx_t *state, teken_unit_t height, teken_unit_t width) { vt_font_bitmap_data_t *font_data; teken_pos_t *tp = &state->tg_tp; char env[8]; int i; /* * set_font() will select a appropriate sized font for * the number of rows and columns selected. If we don't * have a font that will fit, then it will use the * default builtin font and adjust the rows and columns * to fit on the screen. */ font_data = set_font(&tp->tp_row, &tp->tp_col, height, width); if (font_data == NULL) panic("out of memory"); for (i = 0; i < VFNT_MAPS; i++) { state->tg_font.vf_map[i] = font_data->vfbd_font->vf_map[i]; state->tg_font.vf_map_count[i] = font_data->vfbd_font->vf_map_count[i]; } state->tg_font.vf_bytes = font_data->vfbd_font->vf_bytes; state->tg_font.vf_height = font_data->vfbd_font->vf_height; state->tg_font.vf_width = font_data->vfbd_font->vf_width; snprintf(env, sizeof (env), "%ux%u", state->tg_font.vf_width, state->tg_font.vf_height); env_setenv("screen.font", EV_VOLATILE | EV_NOHOOK, env, font_set, env_nounset); } /* Binary search for the glyph. Return 0 if not found. */ static uint16_t font_bisearch(const vfnt_map_t *map, uint32_t len, teken_char_t src) { unsigned min, mid, max; min = 0; max = len - 1; /* Empty font map. */ if (len == 0) return (0); /* Character below minimal entry. */ if (src < map[0].vfm_src) return (0); /* Optimization: ASCII characters occur very often. */ if (src <= map[0].vfm_src + map[0].vfm_len) return (src - map[0].vfm_src + map[0].vfm_dst); /* Character above maximum entry. */ if (src > map[max].vfm_src + map[max].vfm_len) return (0); /* Binary search. */ while (max >= min) { mid = (min + max) / 2; if (src < map[mid].vfm_src) max = mid - 1; else if (src > map[mid].vfm_src + map[mid].vfm_len) min = mid + 1; else return (src - map[mid].vfm_src + map[mid].vfm_dst); } return (0); } /* * Return glyph bitmap. If glyph is not found, we will return bitmap * for the first (offset 0) glyph. */ uint8_t * font_lookup(const struct vt_font *vf, teken_char_t c, const teken_attr_t *a) { uint16_t dst; size_t stride; /* Substitute bold with normal if not found. */ if (a->ta_format & TF_BOLD) { dst = font_bisearch(vf->vf_map[VFNT_MAP_BOLD], vf->vf_map_count[VFNT_MAP_BOLD], c); if (dst != 0) goto found; } dst = font_bisearch(vf->vf_map[VFNT_MAP_NORMAL], vf->vf_map_count[VFNT_MAP_NORMAL], c); found: stride = howmany(vf->vf_width, 8) * vf->vf_height; return (&vf->vf_bytes[dst * stride]); } static int load_mapping(int fd, struct vt_font *fp, int n) { size_t i, size; ssize_t rv; vfnt_map_t *mp; if (fp->vf_map_count[n] == 0) return (0); size = fp->vf_map_count[n] * sizeof(*mp); mp = malloc(size); if (mp == NULL) return (ENOMEM); fp->vf_map[n] = mp; rv = read(fd, mp, size); if (rv < 0 || (size_t)rv != size) { free(fp->vf_map[n]); fp->vf_map[n] = NULL; return (EIO); } for (i = 0; i < fp->vf_map_count[n]; i++) { mp[i].vfm_src = be32toh(mp[i].vfm_src); mp[i].vfm_dst = be16toh(mp[i].vfm_dst); mp[i].vfm_len = be16toh(mp[i].vfm_len); } return (0); } static int builtin_mapping(struct vt_font *fp, int n) { size_t size; struct vfnt_map *mp; if (n >= VFNT_MAPS) return (EINVAL); if (fp->vf_map_count[n] == 0) return (0); size = fp->vf_map_count[n] * sizeof(*mp); mp = malloc(size); if (mp == NULL) return (ENOMEM); fp->vf_map[n] = mp; memcpy(mp, DEFAULT_FONT_DATA.vfbd_font->vf_map[n], size); return (0); } /* * Load font from builtin or from file. * We do need special case for builtin because the builtin font glyphs * are compressed and we do need to uncompress them. * Having single load_font() for both cases will help us to simplify * font switch handling. */ static vt_font_bitmap_data_t * load_font(char *path) { int fd, i; uint32_t glyphs; struct font_header fh; struct fontlist *fl; vt_font_bitmap_data_t *bp; struct vt_font *fp; size_t size; ssize_t rv; /* Get our entry from the font list. */ STAILQ_FOREACH(fl, &fonts, font_next) { if (strcmp(fl->font_name, path) == 0) break; } if (fl == NULL) return (NULL); /* Should not happen. */ bp = fl->font_data; if (bp->vfbd_font != NULL && fl->font_flags != FONT_RELOAD) return (bp); fd = -1; /* * Special case for builtin font. * Builtin font is the very first font we load, we do not have * previous loads to be released. */ if (fl->font_flags == FONT_BUILTIN) { if ((fp = calloc(1, sizeof(struct vt_font))) == NULL) return (NULL); fp->vf_width = DEFAULT_FONT_DATA.vfbd_width; fp->vf_height = DEFAULT_FONT_DATA.vfbd_height; fp->vf_bytes = malloc(DEFAULT_FONT_DATA.vfbd_uncompressed_size); if (fp->vf_bytes == NULL) { free(fp); return (NULL); } bp->vfbd_uncompressed_size = DEFAULT_FONT_DATA.vfbd_uncompressed_size; bp->vfbd_compressed_size = DEFAULT_FONT_DATA.vfbd_compressed_size; if (lz4_decompress(DEFAULT_FONT_DATA.vfbd_compressed_data, fp->vf_bytes, DEFAULT_FONT_DATA.vfbd_compressed_size, DEFAULT_FONT_DATA.vfbd_uncompressed_size, 0) != 0) { free(fp->vf_bytes); free(fp); return (NULL); } for (i = 0; i < VFNT_MAPS; i++) { fp->vf_map_count[i] = DEFAULT_FONT_DATA.vfbd_font->vf_map_count[i]; if (builtin_mapping(fp, i) != 0) goto free_done; } bp->vfbd_font = fp; return (bp); } fd = open(path, O_RDONLY); if (fd < 0) return (NULL); size = sizeof(fh); rv = read(fd, &fh, size); if (rv < 0 || (size_t)rv != size) { bp = NULL; goto done; } if (memcmp(fh.fh_magic, FONT_HEADER_MAGIC, sizeof(fh.fh_magic)) != 0) { bp = NULL; goto done; } if ((fp = calloc(1, sizeof(struct vt_font))) == NULL) { bp = NULL; goto done; } for (i = 0; i < VFNT_MAPS; i++) fp->vf_map_count[i] = be32toh(fh.fh_map_count[i]); glyphs = be32toh(fh.fh_glyph_count); fp->vf_width = fh.fh_width; fp->vf_height = fh.fh_height; size = howmany(fp->vf_width, 8) * fp->vf_height * glyphs; bp->vfbd_uncompressed_size = size; if ((fp->vf_bytes = malloc(size)) == NULL) goto free_done; rv = read(fd, fp->vf_bytes, size); if (rv < 0 || (size_t)rv != size) goto free_done; for (i = 0; i < VFNT_MAPS; i++) { if (load_mapping(fd, fp, i) != 0) goto free_done; } /* * Reset builtin flag now as we have full font loaded. */ if (fl->font_flags == FONT_BUILTIN) fl->font_flags = FONT_AUTO; /* * Release previously loaded entries. We can do this now, as * the new font is loaded. Note, there can be no console * output till the new font is in place and teken is notified. * We do need to keep fl->font_data for glyph dimensions. */ STAILQ_FOREACH(fl, &fonts, font_next) { if (fl->font_data->vfbd_font == NULL) continue; for (i = 0; i < VFNT_MAPS; i++) free(fl->font_data->vfbd_font->vf_map[i]); free(fl->font_data->vfbd_font->vf_bytes); free(fl->font_data->vfbd_font); fl->font_data->vfbd_font = NULL; } bp->vfbd_font = fp; bp->vfbd_compressed_size = 0; done: if (fd != -1) close(fd); return (bp); free_done: for (i = 0; i < VFNT_MAPS; i++) free(fp->vf_map[i]); free(fp->vf_bytes); free(fp); bp = NULL; goto done; } struct name_entry { char *n_name; SLIST_ENTRY(name_entry) n_entry; }; SLIST_HEAD(name_list, name_entry); /* Read font names from index file. */ static struct name_list * read_list(char *fonts) { struct name_list *nl; struct name_entry *np; char *dir, *ptr; char buf[PATH_MAX]; int fd, len; dir = strdup(fonts); if (dir == NULL) return (NULL); ptr = strrchr(dir, '/'); *ptr = '\0'; fd = open(fonts, O_RDONLY); if (fd < 0) return (NULL); nl = malloc(sizeof(*nl)); if (nl == NULL) { close(fd); return (nl); } SLIST_INIT(nl); while ((len = fgetstr(buf, sizeof (buf), fd)) >= 0) { if (*buf == '#' || *buf == '\0') continue; if (bcmp(buf, "MENU", 4) == 0) continue; if (bcmp(buf, "FONT", 4) == 0) continue; ptr = strchr(buf, ':'); if (ptr == NULL) continue; else *ptr = '\0'; np = malloc(sizeof(*np)); if (np == NULL) { close(fd); return (nl); /* return what we have */ } if (asprintf(&np->n_name, "%s/%s", dir, buf) < 0) { free(np); close(fd); return (nl); /* return what we have */ } SLIST_INSERT_HEAD(nl, np, n_entry); } close(fd); return (nl); } /* * Read the font properties and insert new entry into the list. * The font list is built in descending order. */ static bool insert_font(char *name, FONT_FLAGS flags) { struct font_header fh; struct fontlist *fp, *previous, *entry, *next; size_t size; ssize_t rv; int fd; char *font_name; font_name = NULL; if (flags == FONT_BUILTIN) { /* * We only install builtin font once, while setting up * initial console. Since this will happen very early, * we assume asprintf will not fail. Once we have access to * files, the builtin font will be replaced by font loaded * from file. */ if (!STAILQ_EMPTY(&fonts)) return (false); fh.fh_width = DEFAULT_FONT_DATA.vfbd_width; fh.fh_height = DEFAULT_FONT_DATA.vfbd_height; (void) asprintf(&font_name, "%dx%d", DEFAULT_FONT_DATA.vfbd_width, DEFAULT_FONT_DATA.vfbd_height); } else { fd = open(name, O_RDONLY); if (fd < 0) return (false); rv = read(fd, &fh, sizeof(fh)); close(fd); if (rv < 0 || (size_t)rv != sizeof(fh)) return (false); if (memcmp(fh.fh_magic, FONT_HEADER_MAGIC, sizeof(fh.fh_magic)) != 0) return (false); font_name = strdup(name); } if (font_name == NULL) return (false); /* * If we have an entry with the same glyph dimensions, replace * the file name and mark us. We only support unique dimensions. */ STAILQ_FOREACH(entry, &fonts, font_next) { if (fh.fh_width == entry->font_data->vfbd_width && fh.fh_height == entry->font_data->vfbd_height) { free(entry->font_name); entry->font_name = font_name; entry->font_flags = FONT_RELOAD; return (true); } } fp = calloc(sizeof(*fp), 1); if (fp == NULL) { free(font_name); return (false); } fp->font_data = calloc(sizeof(*fp->font_data), 1); if (fp->font_data == NULL) { free(font_name); free(fp); return (false); } fp->font_name = font_name; fp->font_flags = flags; fp->font_load = load_font; fp->font_data->vfbd_width = fh.fh_width; fp->font_data->vfbd_height = fh.fh_height; if (STAILQ_EMPTY(&fonts)) { STAILQ_INSERT_HEAD(&fonts, fp, font_next); return (true); } previous = NULL; size = fp->font_data->vfbd_width * fp->font_data->vfbd_height; STAILQ_FOREACH(entry, &fonts, font_next) { vt_font_bitmap_data_t *bd; bd = entry->font_data; /* Should fp be inserted before the entry? */ if (size > bd->vfbd_width * bd->vfbd_height) { if (previous == NULL) { STAILQ_INSERT_HEAD(&fonts, fp, font_next); } else { STAILQ_INSERT_AFTER(&fonts, previous, fp, font_next); } return (true); } next = STAILQ_NEXT(entry, font_next); if (next == NULL || size > next->font_data->vfbd_width * next->font_data->vfbd_height) { STAILQ_INSERT_AFTER(&fonts, entry, fp, font_next); return (true); } previous = entry; } return (true); } static int font_set(struct env_var *ev __unused, int flags __unused, const void *value) { struct fontlist *fl; char *eptr; unsigned long x = 0, y = 0; /* * Attempt to extract values from "XxY" string. In case of error, * we have unmaching glyph dimensions and will just output the * available values. */ if (value != NULL) { x = strtoul(value, &eptr, 10); if (*eptr == 'x') y = strtoul(eptr + 1, &eptr, 10); } STAILQ_FOREACH(fl, &fonts, font_next) { if (fl->font_data->vfbd_width == x && fl->font_data->vfbd_height == y) break; } if (fl != NULL) { /* Reset any FONT_MANUAL flag. */ reset_font_flags(); /* Mark this font manually loaded */ fl->font_flags = FONT_MANUAL; cons_update_mode(gfx_state.tg_fb_type != FB_TEXT); return (CMD_OK); } printf("Available fonts:\n"); STAILQ_FOREACH(fl, &fonts, font_next) { printf(" %dx%d\n", fl->font_data->vfbd_width, fl->font_data->vfbd_height); } return (CMD_OK); } void bios_text_font(bool use_vga_font) { if (use_vga_font) (void) insert_font(VGA_8X16_FONT, FONT_MANUAL); else (void) insert_font(DEFAULT_8X16_FONT, FONT_MANUAL); } void autoload_font(bool bios) { struct name_list *nl; struct name_entry *np; nl = read_list("/boot/fonts/INDEX.fonts"); if (nl == NULL) return; while (!SLIST_EMPTY(nl)) { np = SLIST_FIRST(nl); SLIST_REMOVE_HEAD(nl, n_entry); if (insert_font(np->n_name, FONT_AUTO) == false) printf("failed to add font: %s\n", np->n_name); free(np->n_name); free(np); } /* * If vga text mode was requested, load vga.font (8x16 bold) font. */ if (bios) { bios_text_font(true); } (void) cons_update_mode(gfx_state.tg_fb_type != FB_TEXT); } COMMAND_SET(load_font, "loadfont", "load console font from file", command_font); static int command_font(int argc, char *argv[]) { int i, c, rc; struct fontlist *fl; vt_font_bitmap_data_t *bd; bool list; list = false; optind = 1; optreset = 1; rc = CMD_OK; while ((c = getopt(argc, argv, "l")) != -1) { switch (c) { case 'l': list = true; break; case '?': default: return (CMD_ERROR); } } argc -= optind; argv += optind; if (argc > 1 || (list && argc != 0)) { printf("Usage: loadfont [-l] | [file.fnt]\n"); return (CMD_ERROR); } if (list) { STAILQ_FOREACH(fl, &fonts, font_next) { printf("font %s: %dx%d%s\n", fl->font_name, fl->font_data->vfbd_width, fl->font_data->vfbd_height, fl->font_data->vfbd_font == NULL? "" : " loaded"); } return (CMD_OK); } /* Clear scren */ cons_clear(); if (argc == 1) { char *name = argv[0]; if (insert_font(name, FONT_MANUAL) == false) { printf("loadfont error: failed to load: %s\n", name); return (CMD_ERROR); } (void) cons_update_mode(gfx_state.tg_fb_type != FB_TEXT); return (CMD_OK); } if (argc == 0) { /* * Walk entire font list, release any loaded font, and set * autoload flag. The font list does have at least the builtin * default font. */ STAILQ_FOREACH(fl, &fonts, font_next) { if (fl->font_data->vfbd_font != NULL) { bd = fl->font_data; /* * Note the setup_font() is releasing * font bytes. */ for (i = 0; i < VFNT_MAPS; i++) free(bd->vfbd_font->vf_map[i]); free(fl->font_data->vfbd_font); fl->font_data->vfbd_font = NULL; fl->font_data->vfbd_uncompressed_size = 0; fl->font_flags = FONT_AUTO; } } (void) cons_update_mode(gfx_state.tg_fb_type != FB_TEXT); } return (rc); } bool gfx_get_edid_resolution(struct vesa_edid_info *edid, edid_res_list_t *res) { struct resolution *rp, *p; /* * Walk detailed timings tables (4). */ if ((edid->display.supported_features & EDID_FEATURE_PREFERRED_TIMING_MODE) != 0) { /* Walk detailed timing descriptors (4) */ for (int i = 0; i < DET_TIMINGS; i++) { /* * Reserved value 0 is not used for display decriptor. */ if (edid->detailed_timings[i].pixel_clock == 0) continue; if ((rp = malloc(sizeof(*rp))) == NULL) continue; rp->width = GET_EDID_INFO_WIDTH(edid, i); rp->height = GET_EDID_INFO_HEIGHT(edid, i); if (rp->width > 0 && rp->width <= EDID_MAX_PIXELS && rp->height > 0 && rp->height <= EDID_MAX_LINES) TAILQ_INSERT_TAIL(res, rp, next); else free(rp); } } /* * Walk standard timings list (8). */ for (int i = 0; i < STD_TIMINGS; i++) { /* Is this field unused? */ if (edid->standard_timings[i] == 0x0101) continue; if ((rp = malloc(sizeof(*rp))) == NULL) continue; rp->width = HSIZE(edid->standard_timings[i]); switch (RATIO(edid->standard_timings[i])) { case RATIO1_1: rp->height = HSIZE(edid->standard_timings[i]); if (edid->header.version > 1 || edid->header.revision > 2) { rp->height = rp->height * 10 / 16; } break; case RATIO4_3: rp->height = HSIZE(edid->standard_timings[i]) * 3 / 4; break; case RATIO5_4: rp->height = HSIZE(edid->standard_timings[i]) * 4 / 5; break; case RATIO16_9: rp->height = HSIZE(edid->standard_timings[i]) * 9 / 16; break; } /* * Create resolution list in decreasing order, except keep * first entry (preferred timing mode). */ TAILQ_FOREACH(p, res, next) { if (p->width * p->height < rp->width * rp->height) { /* Keep preferred mode first */ if (TAILQ_FIRST(res) == p) TAILQ_INSERT_AFTER(res, p, rp, next); else TAILQ_INSERT_BEFORE(p, rp, next); break; } if (TAILQ_NEXT(p, next) == NULL) { TAILQ_INSERT_TAIL(res, rp, next); break; } } } return (!TAILQ_EMPTY(res)); } diff --git a/stand/i386/libi386/vidconsole.c b/stand/i386/libi386/vidconsole.c index e17885cb7b0c..f94ed2d26712 100644 --- a/stand/i386/libi386/vidconsole.c +++ b/stand/i386/libi386/vidconsole.c @@ -1,1238 +1,1238 @@ /*- * Copyright (c) 1998 Michael Smith (msmith@freebsd.org) * Copyright (c) 1997 Kazutaka YOKOTA (yokota@zodiac.mech.utsunomiya-u.ac.jp) * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * Id: probe_keyboard.c,v 1.13 1997/06/09 05:10:55 bde Exp */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include "vbe.h" #include #include "libi386.h" #if KEYBOARD_PROBE static int probe_keyboard(void); #endif static void vidc_probe(struct console *cp); static int vidc_init(int arg); static void vidc_putchar(int c); static int vidc_getchar(void); static int vidc_ischar(void); static void cons_draw_frame(teken_attr_t *); static int vidc_started; static uint16_t *vgatext; static tf_bell_t vidc_cons_bell; static tf_cursor_t vidc_text_cursor; static tf_putchar_t vidc_text_putchar; static tf_fill_t vidc_text_fill; static tf_copy_t vidc_text_copy; static tf_param_t vidc_text_param; static tf_respond_t vidc_cons_respond; static teken_funcs_t tf = { .tf_bell = vidc_cons_bell, .tf_cursor = vidc_text_cursor, .tf_putchar = vidc_text_putchar, .tf_fill = vidc_text_fill, .tf_copy = vidc_text_copy, .tf_param = vidc_text_param, .tf_respond = vidc_cons_respond, }; static teken_funcs_t tfx = { .tf_bell = vidc_cons_bell, .tf_cursor = gfx_fb_cursor, .tf_putchar = gfx_fb_putchar, .tf_fill = gfx_fb_fill, .tf_copy = gfx_fb_copy, .tf_param = gfx_fb_param, .tf_respond = vidc_cons_respond, }; #define KEYBUFSZ 10 static uint8_t keybuf[KEYBUFSZ]; /* keybuf for extended codes */ struct console vidconsole = { .c_name = "vidconsole", .c_desc = "internal video/keyboard", .c_flags = 0, .c_probe = vidc_probe, .c_init = vidc_init, .c_out = vidc_putchar, .c_in = vidc_getchar, .c_ready = vidc_ischar }; /* * This function is used to mark a rectangular image area so the scrolling * will know we need to copy the data from there. */ void term_image_display(teken_gfx_t *state, const teken_rect_t *r) { teken_pos_t p; int idx; if (screen_buffer == NULL) return; for (p.tp_row = r->tr_begin.tp_row; p.tp_row < r->tr_end.tp_row; p.tp_row++) { for (p.tp_col = r->tr_begin.tp_col; p.tp_col < r->tr_end.tp_col; p.tp_col++) { idx = p.tp_col + p.tp_row * state->tg_tp.tp_col; if (idx >= state->tg_tp.tp_col * state->tg_tp.tp_row) return; screen_buffer[idx].a.ta_format |= TF_IMAGE; } } } static void vidc_text_set_cursor(teken_unit_t row, teken_unit_t col, bool visible) { uint16_t addr; uint8_t msl, s, e; msl = vga_get_crtc(VGA_REG_BASE, VGA_CRTC_MAX_SCAN_LINE) & 0x1f; s = vga_get_crtc(VGA_REG_BASE, VGA_CRTC_CURSOR_START) & 0xC0; e = vga_get_crtc(VGA_REG_BASE, VGA_CRTC_CURSOR_END); if (visible == true) { addr = row * TEXT_COLS + col; vga_set_crtc(VGA_REG_BASE, VGA_CRTC_CURSOR_LOC_HIGH, addr >> 8); vga_set_crtc(VGA_REG_BASE, VGA_CRTC_CURSOR_LOC_LOW, addr & 0xff); e = msl; } else { s |= (1<<5); } vga_set_crtc(VGA_REG_BASE, VGA_CRTC_CURSOR_START, s); vga_set_crtc(VGA_REG_BASE, VGA_CRTC_CURSOR_END, e); } static void vidc_text_get_cursor(teken_unit_t *row, teken_unit_t *col) { uint16_t addr; addr = (vga_get_crtc(VGA_REG_BASE, VGA_CRTC_CURSOR_LOC_HIGH) << 8) + vga_get_crtc(VGA_REG_BASE, VGA_CRTC_CURSOR_LOC_LOW); *row = addr / TEXT_COLS; *col = addr % TEXT_COLS; } /* * Not implemented. */ static void vidc_cons_bell(void *s __unused) { } static void vidc_text_cursor(void *s __unused, const teken_pos_t *p) { teken_unit_t row, col; if (p->tp_col == TEXT_COLS) col = p->tp_col - 1; else col = p->tp_col; if (p->tp_row == TEXT_ROWS) row = p->tp_row - 1; else row = p->tp_row; vidc_text_set_cursor(row, col, true); } /* * Binary searchable table for Unicode to CP437 conversion. */ struct unicp437 { uint16_t unicode_base; uint8_t cp437_base; uint8_t length; }; static const struct unicp437 cp437table[] = { { 0x0020, 0x20, 0x5e }, { 0x00a0, 0x20, 0x00 }, { 0x00a1, 0xad, 0x00 }, { 0x00a2, 0x9b, 0x00 }, { 0x00a3, 0x9c, 0x00 }, { 0x00a5, 0x9d, 0x00 }, { 0x00a7, 0x15, 0x00 }, { 0x00aa, 0xa6, 0x00 }, { 0x00ab, 0xae, 0x00 }, { 0x00ac, 0xaa, 0x00 }, { 0x00b0, 0xf8, 0x00 }, { 0x00b1, 0xf1, 0x00 }, { 0x00b2, 0xfd, 0x00 }, { 0x00b5, 0xe6, 0x00 }, { 0x00b6, 0x14, 0x00 }, { 0x00b7, 0xfa, 0x00 }, { 0x00ba, 0xa7, 0x00 }, { 0x00bb, 0xaf, 0x00 }, { 0x00bc, 0xac, 0x00 }, { 0x00bd, 0xab, 0x00 }, { 0x00bf, 0xa8, 0x00 }, { 0x00c4, 0x8e, 0x01 }, { 0x00c6, 0x92, 0x00 }, { 0x00c7, 0x80, 0x00 }, { 0x00c9, 0x90, 0x00 }, { 0x00d1, 0xa5, 0x00 }, { 0x00d6, 0x99, 0x00 }, { 0x00dc, 0x9a, 0x00 }, { 0x00df, 0xe1, 0x00 }, { 0x00e0, 0x85, 0x00 }, { 0x00e1, 0xa0, 0x00 }, { 0x00e2, 0x83, 0x00 }, { 0x00e4, 0x84, 0x00 }, { 0x00e5, 0x86, 0x00 }, { 0x00e6, 0x91, 0x00 }, { 0x00e7, 0x87, 0x00 }, { 0x00e8, 0x8a, 0x00 }, { 0x00e9, 0x82, 0x00 }, { 0x00ea, 0x88, 0x01 }, { 0x00ec, 0x8d, 0x00 }, { 0x00ed, 0xa1, 0x00 }, { 0x00ee, 0x8c, 0x00 }, { 0x00ef, 0x8b, 0x00 }, { 0x00f0, 0xeb, 0x00 }, { 0x00f1, 0xa4, 0x00 }, { 0x00f2, 0x95, 0x00 }, { 0x00f3, 0xa2, 0x00 }, { 0x00f4, 0x93, 0x00 }, { 0x00f6, 0x94, 0x00 }, { 0x00f7, 0xf6, 0x00 }, { 0x00f8, 0xed, 0x00 }, { 0x00f9, 0x97, 0x00 }, { 0x00fa, 0xa3, 0x00 }, { 0x00fb, 0x96, 0x00 }, { 0x00fc, 0x81, 0x00 }, { 0x00ff, 0x98, 0x00 }, { 0x0192, 0x9f, 0x00 }, { 0x0393, 0xe2, 0x00 }, { 0x0398, 0xe9, 0x00 }, { 0x03a3, 0xe4, 0x00 }, { 0x03a6, 0xe8, 0x00 }, { 0x03a9, 0xea, 0x00 }, { 0x03b1, 0xe0, 0x01 }, { 0x03b4, 0xeb, 0x00 }, { 0x03b5, 0xee, 0x00 }, { 0x03bc, 0xe6, 0x00 }, { 0x03c0, 0xe3, 0x00 }, { 0x03c3, 0xe5, 0x00 }, { 0x03c4, 0xe7, 0x00 }, { 0x03c6, 0xed, 0x00 }, { 0x03d5, 0xed, 0x00 }, { 0x2010, 0x2d, 0x00 }, { 0x2014, 0x2d, 0x00 }, { 0x2018, 0x60, 0x00 }, { 0x2019, 0x27, 0x00 }, { 0x201c, 0x22, 0x00 }, { 0x201d, 0x22, 0x00 }, { 0x2022, 0x07, 0x00 }, { 0x203c, 0x13, 0x00 }, { 0x207f, 0xfc, 0x00 }, { 0x20a7, 0x9e, 0x00 }, { 0x20ac, 0xee, 0x00 }, { 0x2126, 0xea, 0x00 }, { 0x2190, 0x1b, 0x00 }, { 0x2191, 0x18, 0x00 }, { 0x2192, 0x1a, 0x00 }, { 0x2193, 0x19, 0x00 }, { 0x2194, 0x1d, 0x00 }, { 0x2195, 0x12, 0x00 }, { 0x21a8, 0x17, 0x00 }, { 0x2202, 0xeb, 0x00 }, { 0x2208, 0xee, 0x00 }, { 0x2211, 0xe4, 0x00 }, { 0x2212, 0x2d, 0x00 }, { 0x2219, 0xf9, 0x00 }, { 0x221a, 0xfb, 0x00 }, { 0x221e, 0xec, 0x00 }, { 0x221f, 0x1c, 0x00 }, { 0x2229, 0xef, 0x00 }, { 0x2248, 0xf7, 0x00 }, { 0x2261, 0xf0, 0x00 }, { 0x2264, 0xf3, 0x00 }, { 0x2265, 0xf2, 0x00 }, { 0x2302, 0x7f, 0x00 }, { 0x2310, 0xa9, 0x00 }, { 0x2320, 0xf4, 0x00 }, { 0x2321, 0xf5, 0x00 }, { 0x2500, 0xc4, 0x00 }, { 0x2502, 0xb3, 0x00 }, { 0x250c, 0xda, 0x00 }, { 0x2510, 0xbf, 0x00 }, { 0x2514, 0xc0, 0x00 }, { 0x2518, 0xd9, 0x00 }, { 0x251c, 0xc3, 0x00 }, { 0x2524, 0xb4, 0x00 }, { 0x252c, 0xc2, 0x00 }, { 0x2534, 0xc1, 0x00 }, { 0x253c, 0xc5, 0x00 }, { 0x2550, 0xcd, 0x00 }, { 0x2551, 0xba, 0x00 }, { 0x2552, 0xd5, 0x00 }, { 0x2553, 0xd6, 0x00 }, { 0x2554, 0xc9, 0x00 }, { 0x2555, 0xb8, 0x00 }, { 0x2556, 0xb7, 0x00 }, { 0x2557, 0xbb, 0x00 }, { 0x2558, 0xd4, 0x00 }, { 0x2559, 0xd3, 0x00 }, { 0x255a, 0xc8, 0x00 }, { 0x255b, 0xbe, 0x00 }, { 0x255c, 0xbd, 0x00 }, { 0x255d, 0xbc, 0x00 }, { 0x255e, 0xc6, 0x01 }, { 0x2560, 0xcc, 0x00 }, { 0x2561, 0xb5, 0x00 }, { 0x2562, 0xb6, 0x00 }, { 0x2563, 0xb9, 0x00 }, { 0x2564, 0xd1, 0x01 }, { 0x2566, 0xcb, 0x00 }, { 0x2567, 0xcf, 0x00 }, { 0x2568, 0xd0, 0x00 }, { 0x2569, 0xca, 0x00 }, { 0x256a, 0xd8, 0x00 }, { 0x256b, 0xd7, 0x00 }, { 0x256c, 0xce, 0x00 }, { 0x2580, 0xdf, 0x00 }, { 0x2584, 0xdc, 0x00 }, { 0x2588, 0xdb, 0x00 }, { 0x258c, 0xdd, 0x00 }, { 0x2590, 0xde, 0x00 }, { 0x2591, 0xb0, 0x02 }, { 0x25a0, 0xfe, 0x00 }, { 0x25ac, 0x16, 0x00 }, { 0x25b2, 0x1e, 0x00 }, { 0x25ba, 0x10, 0x00 }, { 0x25bc, 0x1f, 0x00 }, { 0x25c4, 0x11, 0x00 }, { 0x25cb, 0x09, 0x00 }, { 0x25d8, 0x08, 0x00 }, { 0x25d9, 0x0a, 0x00 }, { 0x263a, 0x01, 0x01 }, { 0x263c, 0x0f, 0x00 }, { 0x2640, 0x0c, 0x00 }, { 0x2642, 0x0b, 0x00 }, { 0x2660, 0x06, 0x00 }, { 0x2663, 0x05, 0x00 }, { 0x2665, 0x03, 0x01 }, { 0x266a, 0x0d, 0x00 }, { 0x266c, 0x0e, 0x00 } }; static uint8_t vga_get_cp437(teken_char_t c) { int min, mid, max; min = 0; max = (sizeof(cp437table) / sizeof(struct unicp437)) - 1; if (c < cp437table[0].unicode_base || c > cp437table[max].unicode_base + cp437table[max].length) return ('?'); while (max >= min) { mid = (min + max) / 2; if (c < cp437table[mid].unicode_base) max = mid - 1; else if (c > cp437table[mid].unicode_base + cp437table[mid].length) min = mid + 1; else return (c - cp437table[mid].unicode_base + cp437table[mid].cp437_base); } return ('?'); } static void vidc_text_printchar(teken_gfx_t *state, const teken_pos_t *p) { int idx; uint8_t attr; struct text_pixel *px; teken_color_t fg, bg, tmp; struct cgatext { uint8_t ch; uint8_t attr; } *addr; idx = p->tp_col + p->tp_row * state->tg_tp.tp_col; px = &screen_buffer[idx]; fg = teken_256to16(px->a.ta_fgcolor); bg = teken_256to16(px->a.ta_bgcolor); if (px->a.ta_format & TF_BOLD) fg |= TC_LIGHT; if (px->a.ta_format & TF_BLINK) bg |= TC_LIGHT; if (px->a.ta_format & TF_REVERSE) { tmp = fg; fg = bg; bg = tmp; } attr = (cmap[bg & 0xf] << 4) | cmap[fg & 0xf]; addr = (struct cgatext *)vgatext; addr[idx].ch = vga_get_cp437(px->c); addr[idx].attr = attr; } static void vidc_text_putchar(void *s, const teken_pos_t *p, teken_char_t c, const teken_attr_t *a) { teken_gfx_t *state = s; int attr, idx; idx = p->tp_col + p->tp_row * state->tg_tp.tp_col; if (idx >= state->tg_tp.tp_col * state->tg_tp.tp_row) return; screen_buffer[idx].c = c; screen_buffer[idx].a = *a; vidc_text_printchar(state, p); } static void vidc_text_fill(void *arg, const teken_rect_t *r, teken_char_t c, const teken_attr_t *a) { teken_gfx_t *state = arg; teken_pos_t p; teken_unit_t row, col; vidc_text_get_cursor(&row, &col); vidc_text_set_cursor(row, col, false); for (p.tp_row = r->tr_begin.tp_row; p.tp_row < r->tr_end.tp_row; p.tp_row++) for (p.tp_col = r->tr_begin.tp_col; p.tp_col < r->tr_end.tp_col; p.tp_col++) vidc_text_putchar(state, &p, c, a); vidc_text_set_cursor(row, col, true); } static void vidc_text_copy(void *ptr, const teken_rect_t *r, const teken_pos_t *p) { teken_gfx_t *state = ptr; int srow, drow; int nrow, ncol, x, y; /* Has to be signed - >= 0 comparison */ teken_pos_t d, s; teken_unit_t row, col; /* * Copying is a little tricky. We must make sure we do it in * correct order, to make sure we don't overwrite our own data. */ nrow = r->tr_end.tp_row - r->tr_begin.tp_row; ncol = r->tr_end.tp_col - r->tr_begin.tp_col; vidc_text_get_cursor(&row, &col); vidc_text_set_cursor(row, col, false); if (p->tp_row < r->tr_begin.tp_row) { /* Copy from bottom to top. */ for (y = 0; y < nrow; y++) { d.tp_row = p->tp_row + y; s.tp_row = r->tr_begin.tp_row + y; drow = d.tp_row * state->tg_tp.tp_col; srow = s.tp_row * state->tg_tp.tp_col; for (x = 0; x < ncol; x++) { d.tp_col = p->tp_col + x; s.tp_col = r->tr_begin.tp_col + x; if (!is_same_pixel( &screen_buffer[d.tp_col + drow], &screen_buffer[s.tp_col + srow])) { screen_buffer[d.tp_col + drow] = screen_buffer[s.tp_col + srow]; vidc_text_printchar(state, &d); } } } } else { /* Copy from top to bottom. */ if (p->tp_col < r->tr_begin.tp_col) { /* Copy from right to left. */ for (y = nrow - 1; y >= 0; y--) { d.tp_row = p->tp_row + y; s.tp_row = r->tr_begin.tp_row + y; drow = d.tp_row * state->tg_tp.tp_col; srow = s.tp_row * state->tg_tp.tp_col; for (x = 0; x < ncol; x++) { d.tp_col = p->tp_col + x; s.tp_col = r->tr_begin.tp_col + x; if (!is_same_pixel( &screen_buffer[d.tp_col + drow], &screen_buffer[s.tp_col + srow])) { screen_buffer[d.tp_col + drow] = screen_buffer[s.tp_col + srow]; vidc_text_printchar(state, &d); } } } } else { /* Copy from left to right. */ for (y = nrow - 1; y >= 0; y--) { d.tp_row = p->tp_row + y; s.tp_row = r->tr_begin.tp_row + y; drow = d.tp_row * state->tg_tp.tp_col; srow = s.tp_row * state->tg_tp.tp_col; for (x = ncol - 1; x >= 0; x--) { d.tp_col = p->tp_col + x; s.tp_col = r->tr_begin.tp_col + x; if (!is_same_pixel( &screen_buffer[d.tp_col + drow], &screen_buffer[s.tp_col + srow])) { screen_buffer[d.tp_col + drow] = screen_buffer[s.tp_col + srow]; vidc_text_printchar(state, &d); } } } } } vidc_text_set_cursor(row, col, true); } static void vidc_text_param(void *arg, int cmd, unsigned int value) { teken_gfx_t *state = arg; teken_unit_t row, col; switch (cmd) { case TP_SETLOCALCURSOR: /* * 0 means normal (usually block), 1 means hidden, and * 2 means blinking (always block) for compatibility with * syscons. We don't support any changes except hiding, * so must map 2 to 0. */ value = (value == 1) ? 0 : 1; /* FALLTHROUGH */ case TP_SHOWCURSOR: vidc_text_get_cursor(&row, &col); if (value != 0) { vidc_text_set_cursor(row, col, true); state->tg_cursor_visible = true; } else { vidc_text_set_cursor(row, col, false); state->tg_cursor_visible = false; } break; default: /* Not yet implemented */ break; } } /* * Not implemented. */ static void vidc_cons_respond(void *s __unused, const void *buf __unused, size_t len __unused) { } static void vidc_probe(struct console *cp) { /* look for a keyboard */ #if KEYBOARD_PROBE if (probe_keyboard()) #endif { cp->c_flags |= C_PRESENTIN; } /* XXX for now, always assume we can do BIOS screen output */ cp->c_flags |= C_PRESENTOUT; } static bool color_name_to_teken(const char *name, int *val) { if (strcasecmp(name, "black") == 0) { *val = TC_BLACK; return (true); } if (strcasecmp(name, "red") == 0) { *val = TC_RED; return (true); } if (strcasecmp(name, "green") == 0) { *val = TC_GREEN; return (true); } if (strcasecmp(name, "brown") == 0) { *val = TC_BROWN; return (true); } if (strcasecmp(name, "blue") == 0) { *val = TC_BLUE; return (true); } if (strcasecmp(name, "magenta") == 0) { *val = TC_MAGENTA; return (true); } if (strcasecmp(name, "cyan") == 0) { *val = TC_CYAN; return (true); } if (strcasecmp(name, "white") == 0) { *val = TC_WHITE; return (true); } return (false); } static int vidc_set_colors(struct env_var *ev, int flags, const void *value) { int val = 0; char buf[2]; const void *evalue; const teken_attr_t *ap; teken_attr_t a; if (value == NULL) return (CMD_OK); if (color_name_to_teken(value, &val)) { snprintf(buf, sizeof (buf), "%d", val); evalue = buf; } else { char *end; errno = 0; val = (int)strtol(value, &end, 0); if (errno != 0 || *end != '\0') { printf("Allowed values are either ansi color name or " "number from range [0-7].\n"); return (CMD_OK); } evalue = value; } ap = teken_get_defattr(&gfx_state.tg_teken); a = *ap; if (strcmp(ev->ev_name, "teken.fg_color") == 0) { /* is it already set? */ if (ap->ta_fgcolor == val) return (CMD_OK); a.ta_fgcolor = val; } if (strcmp(ev->ev_name, "teken.bg_color") == 0) { /* is it already set? */ if (ap->ta_bgcolor == val) return (CMD_OK); a.ta_bgcolor = val; } /* Improve visibility */ if (a.ta_bgcolor == TC_WHITE) a.ta_bgcolor |= TC_LIGHT; teken_set_defattr(&gfx_state.tg_teken, &a); cons_draw_frame(&a); env_setenv(ev->ev_name, flags | EV_NOHOOK, evalue, NULL, NULL); teken_input(&gfx_state.tg_teken, "\e[2J", 4); return (CMD_OK); } static int env_screen_nounset(struct env_var *ev __unused) { if (gfx_state.tg_fb_type == FB_TEXT) return (0); return (EPERM); } static int vidc_load_palette(void) { int i, roff, goff, boff, rc; if (pe8 == NULL) pe8 = calloc(sizeof(*pe8), NCMAP); if (pe8 == NULL) return (ENOMEM); /* Generate VGA colors */ roff = ffs(gfx_state.tg_fb.fb_mask_red) - 1; goff = ffs(gfx_state.tg_fb.fb_mask_green) - 1; boff = ffs(gfx_state.tg_fb.fb_mask_blue) - 1; rc = generate_cons_palette((uint32_t *)pe8, COLOR_FORMAT_RGB, gfx_state.tg_fb.fb_mask_red >> roff, roff, gfx_state.tg_fb.fb_mask_green >> goff, goff, gfx_state.tg_fb.fb_mask_blue >> boff, boff); if (rc == 0) { for (i = 0; i < NCMAP; i++) { int idx; if (i < NCOLORS) idx = cons_to_vga_colors[i]; else idx = i; rc = vbe_set_palette(&pe8[i], idx); if (rc != 0) break; } } return (rc); } static void cons_draw_frame(teken_attr_t *a) { teken_attr_t attr = *a; teken_color_t fg = a->ta_fgcolor; attr.ta_fgcolor = attr.ta_bgcolor; teken_set_defattr(&gfx_state.tg_teken, &attr); gfx_fb_drawrect(0, 0, gfx_state.tg_fb.fb_width, gfx_state.tg_origin.tp_row, 1); gfx_fb_drawrect(0, gfx_state.tg_fb.fb_height - gfx_state.tg_origin.tp_row - 1, gfx_state.tg_fb.fb_width, gfx_state.tg_fb.fb_height, 1); gfx_fb_drawrect(0, gfx_state.tg_origin.tp_row, gfx_state.tg_origin.tp_col, gfx_state.tg_fb.fb_height - gfx_state.tg_origin.tp_row - 1, 1); gfx_fb_drawrect( gfx_state.tg_fb.fb_width - gfx_state.tg_origin.tp_col - 1, gfx_state.tg_origin.tp_row, gfx_state.tg_fb.fb_width, gfx_state.tg_fb.fb_height, 1); attr.ta_fgcolor = fg; teken_set_defattr(&gfx_state.tg_teken, &attr); } /* * Binary searchable table for CP437 to Unicode conversion. */ struct cp437uni { uint8_t cp437_base; uint16_t unicode_base; uint8_t length; }; static const struct cp437uni cp437unitable[] = { { 0, 0x0000, 0 }, { 1, 0x263A, 1 }, { 3, 0x2665, 1 }, { 5, 0x2663, 0 }, { 6, 0x2660, 0 }, { 7, 0x2022, 0 }, { 8, 0x25D8, 0 }, { 9, 0x25CB, 0 }, { 10, 0x25D9, 0 }, { 11, 0x2642, 0 }, { 12, 0x2640, 0 }, { 13, 0x266A, 1 }, { 15, 0x263C, 0 }, { 16, 0x25BA, 0 }, { 17, 0x25C4, 0 }, { 18, 0x2195, 0 }, { 19, 0x203C, 0 }, { 20, 0x00B6, 0 }, { 21, 0x00A7, 0 }, { 22, 0x25AC, 0 }, { 23, 0x21A8, 0 }, { 24, 0x2191, 0 }, { 25, 0x2193, 0 }, { 26, 0x2192, 0 }, { 27, 0x2190, 0 }, { 28, 0x221F, 0 }, { 29, 0x2194, 0 }, { 30, 0x25B2, 0 }, { 31, 0x25BC, 0 }, { 32, 0x0020, 0x5e }, { 127, 0x2302, 0 }, { 128, 0x00C7, 0 }, { 129, 0x00FC, 0 }, { 130, 0x00E9, 0 }, { 131, 0x00E2, 0 }, { 132, 0x00E4, 0 }, { 133, 0x00E0, 0 }, { 134, 0x00E5, 0 }, { 135, 0x00E7, 0 }, { 136, 0x00EA, 1 }, { 138, 0x00E8, 0 }, { 139, 0x00EF, 0 }, { 140, 0x00EE, 0 }, { 141, 0x00EC, 0 }, { 142, 0x00C4, 1 }, { 144, 0x00C9, 0 }, { 145, 0x00E6, 0 }, { 146, 0x00C6, 0 }, { 147, 0x00F4, 0 }, { 148, 0x00F6, 0 }, { 149, 0x00F2, 0 }, { 150, 0x00FB, 0 }, { 151, 0x00F9, 0 }, { 152, 0x00FF, 0 }, { 153, 0x00D6, 0 }, { 154, 0x00DC, 0 }, { 155, 0x00A2, 1 }, { 157, 0x00A5, 0 }, { 158, 0x20A7, 0 }, { 159, 0x0192, 0 }, { 160, 0x00E1, 0 }, { 161, 0x00ED, 0 }, { 162, 0x00F3, 0 }, { 163, 0x00FA, 0 }, { 164, 0x00F1, 0 }, { 165, 0x00D1, 0 }, { 166, 0x00AA, 0 }, { 167, 0x00BA, 0 }, { 168, 0x00BF, 0 }, { 169, 0x2310, 0 }, { 170, 0x00AC, 0 }, { 171, 0x00BD, 0 }, { 172, 0x00BC, 0 }, { 173, 0x00A1, 0 }, { 174, 0x00AB, 0 }, { 175, 0x00BB, 0 }, { 176, 0x2591, 2 }, { 179, 0x2502, 0 }, { 180, 0x2524, 0 }, { 181, 0x2561, 1 }, { 183, 0x2556, 0 }, { 184, 0x2555, 0 }, { 185, 0x2563, 0 }, { 186, 0x2551, 0 }, { 187, 0x2557, 0 }, { 188, 0x255D, 0 }, { 189, 0x255C, 0 }, { 190, 0x255B, 0 }, { 191, 0x2510, 0 }, { 192, 0x2514, 0 }, { 193, 0x2534, 0 }, { 194, 0x252C, 0 }, { 195, 0x251C, 0 }, { 196, 0x2500, 0 }, { 197, 0x253C, 0 }, { 198, 0x255E, 1 }, { 200, 0x255A, 0 }, { 201, 0x2554, 0 }, { 202, 0x2569, 0 }, { 203, 0x2566, 0 }, { 204, 0x2560, 0 }, { 205, 0x2550, 0 }, { 206, 0x256C, 0 }, { 207, 0x2567, 1 }, { 209, 0x2564, 1 }, { 211, 0x2559, 0 }, { 212, 0x2558, 0 }, { 213, 0x2552, 1 }, { 215, 0x256B, 0 }, { 216, 0x256A, 0 }, { 217, 0x2518, 0 }, { 218, 0x250C, 0 }, { 219, 0x2588, 0 }, { 220, 0x2584, 0 }, { 221, 0x258C, 0 }, { 222, 0x2590, 0 }, { 223, 0x2580, 0 }, { 224, 0x03B1, 0 }, { 225, 0x00DF, 0 }, { 226, 0x0393, 0 }, { 227, 0x03C0, 0 }, { 228, 0x03A3, 0 }, { 229, 0x03C3, 0 }, { 230, 0x00B5, 0 }, { 231, 0x03C4, 0 }, { 232, 0x03A6, 0 }, { 233, 0x0398, 0 }, { 234, 0x03A9, 0 }, { 235, 0x03B4, 0 }, { 236, 0x221E, 0 }, { 237, 0x03C6, 0 }, { 238, 0x03B5, 0 }, { 239, 0x2229, 0 }, { 240, 0x2261, 0 }, { 241, 0x00B1, 0 }, { 242, 0x2265, 0 }, { 243, 0x2264, 0 }, { 244, 0x2320, 1 }, { 246, 0x00F7, 0 }, { 247, 0x2248, 0 }, { 248, 0x00B0, 0 }, { 249, 0x2219, 0 }, { 250, 0x00B7, 0 }, { 251, 0x221A, 0 }, { 252, 0x207F, 0 }, { 253, 0x00B2, 0 }, { 254, 0x25A0, 0 }, { 255, 0x00A0, 0 } }; static uint16_t vga_cp437_to_uni(uint8_t c) { int min, mid, max; min = 0; max = (sizeof(cp437unitable) / sizeof(struct cp437uni)) - 1; while (max >= min) { mid = (min + max) / 2; if (c < cp437unitable[mid].cp437_base) max = mid - 1; else if (c > cp437unitable[mid].cp437_base + cp437unitable[mid].length) min = mid + 1; else return (c - cp437unitable[mid].cp437_base + cp437unitable[mid].unicode_base); } return ('?'); } /* * install font for text mode */ static void vidc_install_font(void) { uint8_t reg[7]; const uint8_t *from; uint8_t volatile *to; uint16_t c; int i, j, s; int bpc, f_offset; teken_attr_t a = { 0 }; /* We can only program VGA registers. */ if (!vbe_is_vga()) return; if (gfx_state.tg_fb_type != FB_TEXT) return; /* Sync-reset the sequencer registers */ vga_set_seq(VGA_REG_BASE, VGA_SEQ_RESET, VGA_SEQ_RST_NAR); reg[0] = vga_get_seq(VGA_REG_BASE, VGA_SEQ_MAP_MASK); reg[1] = vga_get_seq(VGA_REG_BASE, VGA_SEQ_CLOCKING_MODE); reg[2] = vga_get_seq(VGA_REG_BASE, VGA_SEQ_MEMORY_MODE); reg[3] = vga_get_grc(VGA_REG_BASE, VGA_GC_READ_MAP_SELECT); reg[4] = vga_get_grc(VGA_REG_BASE, VGA_GC_MODE); reg[5] = vga_get_grc(VGA_REG_BASE, VGA_GC_MISCELLANEOUS); reg[6] = vga_get_atr(VGA_REG_BASE, VGA_AC_MODE_CONTROL); /* Screen off */ vga_set_seq(VGA_REG_BASE, VGA_SEQ_CLOCKING_MODE, reg[1] | VGA_SEQ_CM_SO); /* * enable write to plane2, since fonts * could only be loaded into plane2 */ vga_set_seq(VGA_REG_BASE, VGA_SEQ_MAP_MASK, VGA_SEQ_MM_EM2); /* * sequentially access data in the bit map being * selected by MapMask register (index 0x02) */ vga_set_seq(VGA_REG_BASE, VGA_SEQ_MEMORY_MODE, 0x07); /* Sync-reset ended, and allow the sequencer to operate */ vga_set_seq(VGA_REG_BASE, VGA_SEQ_RESET, VGA_SEQ_RST_SR | VGA_SEQ_RST_NAR); /* * select plane 2 on Read Mode 0 */ vga_set_grc(VGA_REG_BASE, VGA_GC_READ_MAP_SELECT, 0x02); /* * system addresses sequentially access data, follow * Memory Mode register bit 2 in the sequencer */ vga_set_grc(VGA_REG_BASE, VGA_GC_MODE, 0x00); /* * set range of host memory addresses decoded by VGA * hardware -- A0000h-BFFFFh (128K region) */ vga_set_grc(VGA_REG_BASE, VGA_GC_MISCELLANEOUS, 0x00); /* * This assumes 8x16 characters, which yield the traditional 80x25 * screen. */ bpc = 16; s = 0; /* font slot, maybe should use tunable there. */ f_offset = s * 8 * 1024; for (i = 0; i < 256; i++) { c = vga_cp437_to_uni(i); from = font_lookup(&gfx_state.tg_font, c, &a); to = (unsigned char *)ptov(VGA_MEM_BASE) + f_offset + i * 0x20; for (j = 0; j < bpc; j++) *to++ = *from++; } vga_set_atr(VGA_REG_BASE, VGA_AC_MODE_CONTROL, reg[6]); /* Sync-reset the sequencer registers */ vga_set_seq(VGA_REG_BASE, VGA_SEQ_RESET, VGA_SEQ_RST_NAR); vga_set_seq(VGA_REG_BASE, VGA_SEQ_MAP_MASK, reg[0]); vga_set_seq(VGA_REG_BASE, VGA_SEQ_MEMORY_MODE, reg[2]); /* Sync-reset ended, and allow the sequencer to operate */ vga_set_seq(VGA_REG_BASE, VGA_SEQ_RESET, VGA_SEQ_RST_SR | VGA_SEQ_RST_NAR); /* restore graphic registers */ vga_set_grc(VGA_REG_BASE, VGA_GC_READ_MAP_SELECT, reg[3]); vga_set_grc(VGA_REG_BASE, VGA_GC_MODE, reg[4]); vga_set_grc(VGA_REG_BASE, VGA_GC_MISCELLANEOUS, (reg[5] & 0x03) | 0x0c); /* Screen on */ vga_set_seq(VGA_REG_BASE, VGA_SEQ_CLOCKING_MODE, reg[1] & 0xdf); } bool cons_update_mode(bool use_gfx_mode) { const teken_attr_t *a; teken_attr_t attr; char env[10], *ptr; int format, roff, goff, boff; gfx_state.tg_tp.tp_row = TEXT_ROWS; gfx_state.tg_tp.tp_col = TEXT_COLS; if (use_gfx_mode) { setup_font(&gfx_state, gfx_state.tg_fb.fb_height, gfx_state.tg_fb.fb_width); /* Point of origin in pixels. */ gfx_state.tg_origin.tp_row = (gfx_state.tg_fb.fb_height - (gfx_state.tg_tp.tp_row * gfx_state.tg_font.vf_height)) / 2; gfx_state.tg_origin.tp_col = (gfx_state.tg_fb.fb_width - (gfx_state.tg_tp.tp_col * gfx_state.tg_font.vf_width)) / 2; gfx_state.tg_glyph_size = gfx_state.tg_font.vf_height * gfx_state.tg_font.vf_width * 4; free(gfx_state.tg_glyph); gfx_state.tg_glyph = malloc(gfx_state.tg_glyph_size); if (gfx_state.tg_glyph == NULL) return (false); gfx_state.tg_functions = &tfx; snprintf(env, sizeof (env), "%d", gfx_state.tg_fb.fb_height); env_setenv("screen.height", EV_VOLATILE | EV_NOHOOK, env, env_noset, env_screen_nounset); snprintf(env, sizeof (env), "%d", gfx_state.tg_fb.fb_width); env_setenv("screen.width", EV_VOLATILE | EV_NOHOOK, env, env_noset, env_screen_nounset); snprintf(env, sizeof (env), "%d", gfx_state.tg_fb.fb_bpp); env_setenv("screen.depth", EV_VOLATILE | EV_NOHOOK, env, env_noset, env_screen_nounset); } else { /* Trigger loading of 8x16 font. */ setup_font(&gfx_state, - 16 * gfx_state.tg_fb.fb_height + BORDER_PIXELS, - 8 * gfx_state.tg_fb.fb_width + BORDER_PIXELS); + 16 * gfx_state.tg_fb.fb_height, + 8 * gfx_state.tg_fb.fb_width); gfx_state.tg_functions = &tf; /* ensure the following are not set for text mode */ unsetenv("screen.height"); unsetenv("screen.width"); unsetenv("screen.depth"); vidc_install_font(); } free(screen_buffer); screen_buffer = malloc(gfx_state.tg_tp.tp_row * gfx_state.tg_tp.tp_col * sizeof(*screen_buffer)); if (screen_buffer == NULL) return (false); teken_init(&gfx_state.tg_teken, gfx_state.tg_functions, &gfx_state); if (gfx_state.tg_ctype == CT_INDEXED) format = COLOR_FORMAT_VGA; else format = COLOR_FORMAT_RGB; roff = ffs(gfx_state.tg_fb.fb_mask_red) - 1; goff = ffs(gfx_state.tg_fb.fb_mask_green) - 1; boff = ffs(gfx_state.tg_fb.fb_mask_blue) - 1; (void) generate_cons_palette(cmap, format, gfx_state.tg_fb.fb_mask_red >> roff, roff, gfx_state.tg_fb.fb_mask_green >> goff, goff, gfx_state.tg_fb.fb_mask_blue >> boff, boff); if (gfx_state.tg_ctype == CT_INDEXED && use_gfx_mode) vidc_load_palette(); teken_set_winsize(&gfx_state.tg_teken, &gfx_state.tg_tp); a = teken_get_defattr(&gfx_state.tg_teken); attr = *a; /* * On first run, we set up the vidc_set_colors() * callback. If the env is already set, we * pick up fg and bg color values from the environment. */ ptr = getenv("teken.fg_color"); if (ptr != NULL) { attr.ta_fgcolor = strtol(ptr, NULL, 10); ptr = getenv("teken.bg_color"); attr.ta_bgcolor = strtol(ptr, NULL, 10); teken_set_defattr(&gfx_state.tg_teken, &attr); } else { snprintf(env, sizeof(env), "%d", attr.ta_fgcolor); env_setenv("teken.fg_color", EV_VOLATILE, env, vidc_set_colors, env_nounset); snprintf(env, sizeof(env), "%d", attr.ta_bgcolor); env_setenv("teken.bg_color", EV_VOLATILE, env, vidc_set_colors, env_nounset); } /* Improve visibility */ if (attr.ta_bgcolor == TC_WHITE) attr.ta_bgcolor |= TC_LIGHT; teken_set_defattr(&gfx_state.tg_teken, &attr); snprintf(env, sizeof (env), "%u", (unsigned)gfx_state.tg_tp.tp_row); setenv("LINES", env, 1); snprintf(env, sizeof (env), "%u", (unsigned)gfx_state.tg_tp.tp_col); setenv("COLUMNS", env, 1); /* Draw frame around terminal area. */ cons_draw_frame(&attr); /* Erase display, this will also fill our screen buffer. */ teken_input(&gfx_state.tg_teken, "\e[2J", 4); gfx_state.tg_functions->tf_param(&gfx_state, TP_SHOWCURSOR, 1); return (true); } static int vidc_init(int arg) { const teken_attr_t *a; int val; char env[8]; if (vidc_started && arg == 0) return (0); vidc_started = 1; vbe_init(); /* * Check Miscellaneous Output Register (Read at 3CCh, Write at 3C2h) * for bit 1 (Input/Output Address Select), which means * color/graphics adapter. */ if (vga_get_reg(VGA_REG_BASE, VGA_GEN_MISC_OUTPUT_R) & VGA_GEN_MO_IOA) vgatext = (uint16_t *)PTOV(VGA_TXT_BASE); else vgatext = (uint16_t *)PTOV(VGA_MEM_BASE + VGA_MEM_SIZE); /* set 16bit colors */ val = vga_get_atr(VGA_REG_BASE, VGA_AC_MODE_CONTROL); val &= ~VGA_AC_MC_BI; val &= ~VGA_AC_MC_ELG; vga_set_atr(VGA_REG_BASE, VGA_AC_MODE_CONTROL, val); #if defined(FRAMEBUFFER_MODE) val = vbe_default_mode(); /* if val is not legal VBE mode, use text mode */ if (VBE_VALID_MODE(val)) { if (vbe_set_mode(val) != 0) bios_set_text_mode(VGA_TEXT_MODE); } #endif gfx_framework_init(); if (!cons_update_mode(VBE_VALID_MODE(vbe_get_mode()))) return (1); for (int i = 0; i < 10 && vidc_ischar(); i++) (void) vidc_getchar(); return (0); /* XXX reinit? */ } void vidc_biosputchar(int c) { v86.ctl = 0; v86.addr = 0x10; v86.eax = 0xe00 | (c & 0xff); v86.ebx = 0x7; v86int(); } static void vidc_putchar(int c) { unsigned char ch = c; if (screen_buffer != NULL) teken_input(&gfx_state.tg_teken, &ch, sizeof (ch)); else vidc_biosputchar(c); } static int vidc_getchar(void) { int i, c; for (i = 0; i < KEYBUFSZ; i++) { if (keybuf[i] != 0) { c = keybuf[i]; keybuf[i] = 0; return (c); } } if (vidc_ischar()) { v86.ctl = 0; v86.addr = 0x16; v86.eax = 0x0; v86int(); if ((v86.eax & 0xff) != 0) { return (v86.eax & 0xff); } /* extended keys */ switch (v86.eax & 0xff00) { case 0x4800: /* up */ keybuf[0] = '['; keybuf[1] = 'A'; return (0x1b); /* esc */ case 0x4b00: /* left */ keybuf[0] = '['; keybuf[1] = 'D'; return (0x1b); /* esc */ case 0x4d00: /* right */ keybuf[0] = '['; keybuf[1] = 'C'; return (0x1b); /* esc */ case 0x5000: /* down */ keybuf[0] = '['; keybuf[1] = 'B'; return (0x1b); /* esc */ default: return (-1); } } else { return (-1); } } static int vidc_ischar(void) { int i; for (i = 0; i < KEYBUFSZ; i++) { if (keybuf[i] != 0) { return (1); } } v86.ctl = V86_FLAGS; v86.addr = 0x16; v86.eax = 0x100; v86int(); return (!V86_ZR(v86.efl)); } #if KEYBOARD_PROBE #define PROBE_MAXRETRY 5 #define PROBE_MAXWAIT 400 #define IO_DUMMY 0x84 #define IO_KBD 0x060 /* 8042 Keyboard */ /* selected defines from kbdio.h */ #define KBD_STATUS_PORT 4 /* status port, read */ #define KBD_DATA_PORT 0 /* data port, read/write * also used as keyboard command * and mouse command port */ #define KBDC_ECHO 0x00ee #define KBDS_ANY_BUFFER_FULL 0x0001 #define KBDS_INPUT_BUFFER_FULL 0x0002 #define KBD_ECHO 0x00ee /* 7 microsec delay necessary for some keyboard controllers */ static void delay7(void) { /* * I know this is broken, but no timer is available yet at this stage... * See also comments in `delay1ms()'. */ inb(IO_DUMMY); inb(IO_DUMMY); inb(IO_DUMMY); inb(IO_DUMMY); inb(IO_DUMMY); inb(IO_DUMMY); } /* * This routine uses an inb to an unused port, the time to execute that * inb is approximately 1.25uS. This value is pretty constant across * all CPU's and all buses, with the exception of some PCI implentations * that do not forward this I/O address to the ISA bus as they know it * is not a valid ISA bus address, those machines execute this inb in * 60 nS :-(. * */ static void delay1ms(void) { int i = 800; while (--i >= 0) (void) inb(0x84); } /* * We use the presence/absence of a keyboard to determine whether the internal * console can be used for input. * * Perform a simple test on the keyboard; issue the ECHO command and see * if the right answer is returned. We don't do anything as drastic as * full keyboard reset; it will be too troublesome and take too much time. */ static int probe_keyboard(void) { int retry = PROBE_MAXRETRY; int wait; int i; while (--retry >= 0) { /* flush any noise */ while (inb(IO_KBD + KBD_STATUS_PORT) & KBDS_ANY_BUFFER_FULL) { delay7(); inb(IO_KBD + KBD_DATA_PORT); delay1ms(); } /* wait until the controller can accept a command */ for (wait = PROBE_MAXWAIT; wait > 0; --wait) { if (((i = inb(IO_KBD + KBD_STATUS_PORT)) & (KBDS_INPUT_BUFFER_FULL | KBDS_ANY_BUFFER_FULL)) == 0) break; if (i & KBDS_ANY_BUFFER_FULL) { delay7(); inb(IO_KBD + KBD_DATA_PORT); } delay1ms(); } if (wait <= 0) continue; /* send the ECHO command */ outb(IO_KBD + KBD_DATA_PORT, KBDC_ECHO); /* wait for a response */ for (wait = PROBE_MAXWAIT; wait > 0; --wait) { if (inb(IO_KBD + KBD_STATUS_PORT) & KBDS_ANY_BUFFER_FULL) break; delay1ms(); } if (wait <= 0) continue; delay7(); i = inb(IO_KBD + KBD_DATA_PORT); #ifdef PROBE_KBD_BEBUG printf("probe_keyboard: got 0x%x.\n", i); #endif if (i == KBD_ECHO) { /* got the right answer */ return (1); } } return (0); } #endif /* KEYBOARD_PROBE */ diff --git a/stand/lua/drawer.lua b/stand/lua/drawer.lua index 6062d7e87a03..eb9b18117cd3 100644 --- a/stand/lua/drawer.lua +++ b/stand/lua/drawer.lua @@ -1,518 +1,518 @@ -- -- SPDX-License-Identifier: BSD-2-Clause-FreeBSD -- -- Copyright (c) 2015 Pedro Souza -- Copyright (c) 2018 Kyle Evans -- All rights reserved. -- -- Redistribution and use in source and binary forms, with or without -- modification, are permitted provided that the following conditions -- are met: -- 1. Redistributions of source code must retain the above copyright -- notice, this list of conditions and the following disclaimer. -- 2. Redistributions in binary form must reproduce the above copyright -- notice, this list of conditions and the following disclaimer in the -- documentation and/or other materials provided with the distribution. -- -- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND -- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -- ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE -- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -- SUCH DAMAGE. -- -- $FreeBSD$ -- local color = require("color") local config = require("config") local core = require("core") local screen = require("screen") local drawer = {} local fbsd_brand local none local menu_name_handlers local branddefs local logodefs local brand_position local logo_position local menu_position local frame_size local default_shift local shift local function menuEntryName(drawing_menu, entry) local name_handler = menu_name_handlers[entry.entry_type] if name_handler ~= nil then return name_handler(drawing_menu, entry) end if type(entry.name) == "function" then return entry.name() end return entry.name end local function processFile(gfxname) if gfxname == nil then return false, "Missing filename" end local ret = try_include('gfx-' .. gfxname) if ret == nil then return false, "Failed to include gfx-" .. gfxname end -- Legacy format if type(ret) ~= "table" then return true end for gfxtype, def in pairs(ret) do if gfxtype == "brand" then drawer.addBrand(gfxname, def) elseif gfxtype == "logo" then drawer.addLogo(gfxname, def) else return false, "Unknown graphics type '" .. gfxtype .. "'" end end return true end local function getBranddef(brand) if brand == nil then return nil end -- Look it up local branddef = branddefs[brand] -- Try to pull it in if branddef == nil then local res, err = processFile(brand) if not res then -- This fallback should go away after FreeBSD 13. try_include('brand-' .. brand) -- If the fallback also failed, print whatever error -- we encountered in the original processing. if branddefs[brand] == nil then print(err) return nil end end branddef = branddefs[brand] end return branddef end local function getLogodef(logo) if logo == nil then return nil end -- Look it up local logodef = logodefs[logo] -- Try to pull it in if logodef == nil then local res, err = processFile(logo) if not res then -- This fallback should go away after FreeBSD 13. try_include('logo-' .. logo) -- If the fallback also failed, print whatever error -- we encountered in the original processing. if logodefs[logo] == nil then print(err) return nil end end logodef = logodefs[logo] end return logodef end local function draw(x, y, logo) for i = 1, #logo do screen.setcursor(x, y + i - 1) printc(logo[i]) end end local function drawmenu(menudef) local x = menu_position.x local y = menu_position.y x = x + shift.x y = y + shift.y -- print the menu and build the alias table local alias_table = {} local entry_num = 0 local menu_entries = menudef.entries local effective_line_num = 0 if type(menu_entries) == "function" then menu_entries = menu_entries() end for _, e in ipairs(menu_entries) do -- Allow menu items to be conditionally visible by specifying -- a visible function. if e.visible ~= nil and not e.visible() then goto continue end effective_line_num = effective_line_num + 1 if e.entry_type ~= core.MENU_SEPARATOR then entry_num = entry_num + 1 screen.setcursor(x, y + effective_line_num) printc(entry_num .. ". " .. menuEntryName(menudef, e)) -- fill the alias table alias_table[tostring(entry_num)] = e if e.alias ~= nil then for _, a in ipairs(e.alias) do alias_table[a] = e end end else screen.setcursor(x, y + effective_line_num) printc(menuEntryName(menudef, e)) end ::continue:: end return alias_table end local function defaultframe() if core.isSerialConsole() then return "ascii" end return "double" end local function drawframe() local x = menu_position.x - 3 local y = menu_position.y - 1 local w = frame_size.w local h = frame_size.h local framestyle = loader.getenv("loader_menu_frame") or defaultframe() local framespec = drawer.frame_styles[framestyle] -- If we don't have a framespec for the current frame style, just don't -- draw a box. if framespec == nil then return false end local hl = framespec.horizontal local vl = framespec.vertical local tl = framespec.top_left local bl = framespec.bottom_left local tr = framespec.top_right local br = framespec.bottom_right x = x + shift.x y = y + shift.y if core.isFramebufferConsole() and loader.term_drawrect ~= nil then loader.term_drawrect(x, y, x + w, y + h) return true end screen.setcursor(x, y); printc(tl) screen.setcursor(x, y + h); printc(bl) screen.setcursor(x + w, y); printc(tr) screen.setcursor(x + w, y + h); printc(br) screen.setcursor(x + 1, y) for _ = 1, w - 1 do printc(hl) end screen.setcursor(x + 1, y + h) for _ = 1, w - 1 do printc(hl) end for i = 1, h - 1 do screen.setcursor(x, y + i) printc(vl) screen.setcursor(x + w, y + i) printc(vl) end return true end local function drawbox() local x = menu_position.x - 3 local y = menu_position.y - 1 local w = frame_size.w local menu_header = loader.getenv("loader_menu_title") or "Welcome to FreeBSD" local menu_header_align = loader.getenv("loader_menu_title_align") local menu_header_x x = x + shift.x y = y + shift.y if drawframe(x, y, w) == false then return end if menu_header_align ~= nil then menu_header_align = menu_header_align:lower() if menu_header_align == "left" then -- Just inside the left border on top menu_header_x = x + 1 elseif menu_header_align == "right" then -- Just inside the right border on top menu_header_x = x + w - #menu_header end end if menu_header_x == nil then menu_header_x = x + (w // 2) - (#menu_header // 2) end screen.setcursor(menu_header_x, y) printc(menu_header) end local function drawbrand() local x = tonumber(loader.getenv("loader_brand_x")) or brand_position.x local y = tonumber(loader.getenv("loader_brand_y")) or brand_position.y local branddef = getBranddef(loader.getenv("loader_brand")) if branddef == nil then branddef = getBranddef(drawer.default_brand) end local graphic = branddef.graphic x = x + shift.x y = y + shift.y if core.isFramebufferConsole() and loader.term_putimage ~= nil and branddef.image ~= nil then - if loader.term_putimage(branddef.image, 0, 0, 0, 7, 0) + if loader.term_putimage(branddef.image, 1, 1, 0, 7, 0) then return true end end draw(x, y, graphic) end local function drawlogo() local x = tonumber(loader.getenv("loader_logo_x")) or logo_position.x local y = tonumber(loader.getenv("loader_logo_y")) or logo_position.y local logo = loader.getenv("loader_logo") local colored = color.isEnabled() local logodef = getLogodef(logo) if logodef == nil or logodef.graphic == nil or (not colored and logodef.requires_color) then -- Choose a sensible default if colored then logodef = getLogodef(drawer.default_color_logodef) else logodef = getLogodef(drawer.default_bw_logodef) end -- Something has gone terribly wrong. if logodef == nil then logodef = getLogodef(drawer.default_fallback_logodef) end end if logodef ~= nil and logodef.graphic == none then shift = logodef.shift else shift = default_shift end x = x + shift.x y = y + shift.y if logodef ~= nil and logodef.shift ~= nil then x = x + logodef.shift.x y = y + logodef.shift.y end if core.isFramebufferConsole() and loader.term_putimage ~= nil and logodef.image ~= nil then local y1 = 15 if logodef.image_rl ~= nil then y1 = logodef.image_rl end if loader.term_putimage(logodef.image, x, y, 0, y + y1, 0) then return true end end draw(x, y, logodef.graphic) end local function drawitem(func) local console = loader.getenv("console") local c for c in string.gmatch(console, "%w+") do loader.setenv("console", c) func() end loader.setenv("console", console) end fbsd_brand = { " ______ ____ _____ _____ ", " | ____| | _ \\ / ____| __ \\ ", " | |___ _ __ ___ ___ | |_) | (___ | | | |", " | ___| '__/ _ \\/ _ \\| _ < \\___ \\| | | |", " | | | | | __/ __/| |_) |____) | |__| |", " | | | | | | || | | |", " |_| |_| \\___|\\___||____/|_____/|_____/ " } none = {""} menu_name_handlers = { -- Menu name handlers should take the menu being drawn and entry being -- drawn as parameters, and return the name of the item. -- This is designed so that everything, including menu separators, may -- have their names derived differently. The default action for entry -- types not specified here is to use entry.name directly. [core.MENU_SEPARATOR] = function(_, entry) if entry.name ~= nil then if type(entry.name) == "function" then return entry.name() end return entry.name end return "" end, [core.MENU_CAROUSEL_ENTRY] = function(_, entry) local carid = entry.carousel_id local caridx = config.getCarouselIndex(carid) local choices = entry.items if type(choices) == "function" then choices = choices() end if #choices < caridx then caridx = 1 end return entry.name(caridx, choices[caridx], choices) end, } branddefs = { -- Indexed by valid values for loader_brand in loader.conf(5). Valid -- keys are: graphic (table depicting graphic) ["fbsd"] = { graphic = fbsd_brand, image = "/boot/images/freebsd-brand-rev.png", }, ["none"] = { graphic = none, }, } logodefs = { -- Indexed by valid values for loader_logo in loader.conf(5). Valid keys -- are: requires_color (boolean), graphic (table depicting graphic), and -- shift (table containing x and y). ["tribute"] = { graphic = fbsd_brand, }, ["tributebw"] = { graphic = fbsd_brand, }, ["none"] = { graphic = none, shift = {x = 17, y = 0}, }, } brand_position = {x = 2, y = 1} logo_position = {x = 46, y = 4} menu_position = {x = 5, y = 10} frame_size = {w = 42, h = 13} default_shift = {x = 0, y = 0} shift = default_shift -- Module exports drawer.default_brand = 'fbsd' drawer.default_color_logodef = 'orb' drawer.default_bw_logodef = 'orbbw' -- For when things go terribly wrong; this def should be present here in the -- drawer module in case it's a filesystem issue. drawer.default_fallback_logodef = 'none' -- These should go away after FreeBSD 13; only available for backwards -- compatibility with old logo- files. function drawer.addBrand(name, def) branddefs[name] = def end function drawer.addLogo(name, def) logodefs[name] = def end drawer.frame_styles = { -- Indexed by valid values for loader_menu_frame in loader.conf(5). -- All of the keys appearing below must be set for any menu frame style -- added to drawer.frame_styles. ["ascii"] = { horizontal = "-", vertical = "|", top_left = "+", bottom_left = "+", top_right = "+", bottom_right = "+", }, ["single"] = { horizontal = "\xE2\x94\x80", vertical = "\xE2\x94\x82", top_left = "\xE2\x94\x8C", bottom_left = "\xE2\x94\x94", top_right = "\xE2\x94\x90", bottom_right = "\xE2\x94\x98", }, ["double"] = { horizontal = "\xE2\x95\x90", vertical = "\xE2\x95\x91", top_left = "\xE2\x95\x94", bottom_left = "\xE2\x95\x9A", top_right = "\xE2\x95\x97", bottom_right = "\xE2\x95\x9D", }, } function drawer.drawscreen(menudef) -- drawlogo() must go first. -- it determines the positions of other elements drawitem(drawlogo) drawitem(drawbrand) drawitem(drawbox) return drawmenu(menudef) end return drawer diff --git a/sys/sys/font.h b/sys/sys/font.h index e09b2112959d..969a9bce4e6d 100644 --- a/sys/sys/font.h +++ b/sys/sys/font.h @@ -1,123 +1,122 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2009, 2013 The FreeBSD Foundation * All rights reserved. * * This software was developed by Ed Schouten under sponsorship from the * FreeBSD Foundation. * * Portions of this software were developed by Oleksandr Rybalko * under sponsorship from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #ifndef _SYS_FONT_H_ #define _SYS_FONT_H_ #include /* * Fonts. * * Fonts support normal and bold weights, and single and double width glyphs. * Mapping tables are used to map Unicode points to glyphs. They are sorted by * code point, and vtfont_lookup() uses this to perform a binary search. Each * font has four mapping tables: two weights times two halves (left/single, * right). When a character is not present in a bold map the glyph from the * normal map is used. When no glyph is available, it uses glyph 0, which is * normally equal to U+FFFD. */ enum vfnt_map_type { VFNT_MAP_NORMAL = 0, /* Normal font. */ VFNT_MAP_NORMAL_RIGHT, /* Normal font right hand. */ VFNT_MAP_BOLD, /* Bold font. */ VFNT_MAP_BOLD_RIGHT, /* Bold font right hand. */ VFNT_MAPS /* Number of maps. */ }; struct font_info { int32_t fi_checksum; uint32_t fi_width; uint32_t fi_height; uint32_t fi_bitmap_size; uint32_t fi_map_count[VFNT_MAPS]; }; struct vfnt_map { uint32_t vfm_src; uint16_t vfm_dst; uint16_t vfm_len; } __packed; typedef struct vfnt_map vfnt_map_t; struct vt_font { vfnt_map_t *vf_map[VFNT_MAPS]; uint8_t *vf_bytes; uint32_t vf_height; uint32_t vf_width; uint32_t vf_map_count[VFNT_MAPS]; uint32_t vf_refcount; }; typedef struct vt_font_bitmap_data { uint32_t vfbd_width; uint32_t vfbd_height; uint32_t vfbd_compressed_size; uint32_t vfbd_uncompressed_size; uint8_t *vfbd_compressed_data; struct vt_font *vfbd_font; } vt_font_bitmap_data_t; typedef enum { FONT_AUTO, /* This font is loaded by software */ FONT_MANUAL, /* This font is loaded manually by user */ FONT_BUILTIN, /* This font was built in at compile time */ FONT_RELOAD /* This font is marked to be re-read from file */ } FONT_FLAGS; struct fontlist { char *font_name; FONT_FLAGS font_flags; vt_font_bitmap_data_t *font_data; vt_font_bitmap_data_t *(*font_load)(char *); STAILQ_ENTRY(fontlist) font_next; }; -#define BORDER_PIXELS 10 /* space from screen border */ typedef STAILQ_HEAD(font_list, fontlist) font_list_t; #define FONT_HEADER_MAGIC "VFNT0002" struct font_header { uint8_t fh_magic[8]; uint8_t fh_width; uint8_t fh_height; uint16_t fh_pad; uint32_t fh_glyph_count; uint32_t fh_map_count[VFNT_MAPS]; } __packed; #endif /* !_SYS_FONT_H_ */