# Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: colin@gibibit.com-20080719214640-ixudxurtvb5v5zpy # target_branch: ../trunk-clean # testament_sha1: 7597022c488e88b1cb28dfd2082bb898b6411899 # timestamp: 2008-07-19 14:56:49 -0700 # source_branch: http://grub.gibibit.com/bzr/trunk-clean # base_revision_id: colin@gibibit.com-20080719214342-aw2lim5mylg2crpc # # Begin patch === modified file '.bzrignore' --- .bzrignore 2008-07-19 21:43:42 +0000 +++ .bzrignore 2008-07-19 21:44:04 +0000 @@ -7,3 +7,5 @@ ./conf/sparc64-ieee1275.mk ./conf/i386-coreboot.mk ./build* +./util/fonttool/build +./util/fonttool/fonttool.iws === modified file 'ChangeLog' --- ChangeLog 2008-07-18 15:11:35 +0000 +++ ChangeLog 2008-07-19 21:39:45 +0000 @@ -332,6 +332,73 @@ * disk/ata.c: Use named constants for status bits. +2008-07-04 Colin D Bennett + + High resolution timer support. Implemented for i386 CPU using TSC. + Extracted generic grub_millisleep() so it's linked in only as needed. + This requires a Pentium compatible CPU; currently the code does not + check for this (so it will fail on 386 and 486 machines). + + * conf/i386-efi.rmk: Added TSC high resolution time module, link with + generic grub_millisleep() function. + + * conf/i386-pc.rmk: Likewise. + + * conf/sparc64-ieee1275.rmk: Add kern/generic/millisleep.c and + kern/generic/get_time_ms.c to kernel, to use generic time functions. + + * conf/powerpc-ieee1275.rmk: Add kern/generic/millisleep.c to kernel, + to use generic grub_millisleep() function. + + * conf/i386-linuxbios.rmk: Added kern/generic/get_time_ms.c to the + kernel. + + * kern/generic/get_time_ms.c (grub_get_time_ms): New file. Platform + independent implementation of grub_get_time_ms() using the RTC that + can be linked into a platform's kernel when it does not implement its + own specialized grub_get_time_ms() function. + + * kern/generic/millisleep.c (grub_millisleep): New file. Extracted + from grub_millisleep_generic() in kern/misc.c and renamed. Changed it + to use grub_get_time_ms() instead of grub_get_rtc() for better + precision on when high resolution time is available. + + * kern/misc.c (grub_millisleep_generic): Deleted. Moved to + kern/generic/millisleep.c so that it is only included in the kernel + image when a platform does not define a specialized version. + + * commands/sleep.c (grub_interruptible_millisleep): Uses + grub_get_time_ms() instead of grub_get_rtc() to stay in sync with + grub_millisleep() from kern/generic/millisleep.c. + + * include/grub/i386/tsc.h (grub_get_tsc): New file. Inline function + grub_get_tsc() uses x86 RDTSC instruction (available on Pentium+ CPUs) + to read the counter value for the TSC. + (grub_tsc_calibrate): Declare this function for grub_machine_init(). + + * kern/i386/tsc.c (grub_get_time_ms): x86 TSC support providing a high + resolution clock. + (grub_tsc_calibrate): New function to calibrate the TSC using RTC. + + * include/grub/time.h (grub_get_time_ms): Added grub_get_time_ms() + function to return the current time in millseconds since the epoch. + This supports higher resolution time than grub_get_rtc() on some + platforms such as i386-pc, where the RTC has only about 1/18 s + precision but a higher precision timer such as the TSC is available. + + * kern/i386/efi/init.c (grub_millisleep): Deleted. Don't define + grub_millisleep() -- it just called grub_millisleep_generic() but now + it is linked to kern/generic/millisleep.c for the implementation. + + * kern/sparc64/ieee1275/init.c (grub_millisleep): Deleted. + + * kern/i386/pc/init.c (grub_machine_init): Call grub_tsc_calibrate(). + (grub_millisleep): Deleted. + + * kern/ieee1275/init.c (grub_millisleep): Deleted. + (grub_get_rtc): Now calls grub_get_time_ms(), which does the real + work. + 2008-07-04 Pavel Roskin * kern/i386/linuxbios/init.c (grub_machine_init): Cast addr to === modified file 'Makefile.in' --- Makefile.in 2008-07-17 14:05:12 +0000 +++ Makefile.in 2008-07-19 21:39:24 +0000 @@ -85,6 +85,39 @@ YACC = @YACC@ UNIFONT_HEX = @UNIFONT_HEX@ +### Pretty output control ### +# Set up compiler and linker commands that either is quiet (does not print +# the command line being executed) or verbose (print the command line). +_CC := $(CC) +_TARGET_CC := $(TARGET_CC) +_STRIP := $(STRIP) +_GENMODSRC := sh $(srcdir)/genmodsrc.sh +ifeq ($(V),1) + override V_PREFIX := + override CC = $(_CC) + override TARGET_CC = $(_CC) + override STRIP = $(_STRIP) + override GENMODSRC = $(_GENMODSRC) + override INFO_GENCMDLIST = + override INFO_GENFSLIST = + override INFO_GENPARTMAPLIST = + override INFO_GEN_FINAL_COMMAND_LIST = + override INFO_GEN_FINAL_FS_LIST = + override INFO_GEN_FINAL_PARTMAP_LIST = +else + override V_PREFIX := @ + override CC = @echo "COMPILE $<"; $(_CC) + override TARGET_CC = @echo "COMPILE(TARGET) $<"; $(_TARGET_CC) + override STRIP = @echo "STRIP $@"; $(_STRIP) + override GENMODSRC = @echo "GENMODSRC $@"; $(_GENMODSRC) + override INFO_GENCMDLIST = @echo "GENCMDLIST $@" + override INFO_GENFSLIST = @echo "GENFSLIST $@" + override INFO_GENPARTMAPLIST = @echo "GENPARTMAPLIST $@" + override INFO_GEN_FINAL_COMMAND_LIST = @echo "GENCMDLIST[final] $@" + override INFO_GEN_FINAL_FS_LIST = @echo "GENFSLIST[final] $@" + override INFO_GEN_FINAL_PARTMAP_LIST = @echo "GENPARTMAPLIST[final] $@" +endif + # Options. enable_grub_emu = @enable_grub_emu@ enable_grub_fstest = @enable_grub_fstest@ @@ -132,13 +165,16 @@ || (rm -f $@; exit 1) command.lst: $(COMMANDFILES) - cat $^ /dev/null | sort > $@ + $(INFO_GEN_FINAL_COMMAND_LIST) + $(V_PREFIX)cat $^ /dev/null | sort > $@ fs.lst: $(FSFILES) - cat $^ /dev/null | sort > $@ + $(INFO_GEN_FINAL_FS_LIST) + $(V_PREFIX)cat $^ /dev/null | sort > $@ partmap.lst: $(PARTMAPFILES) - cat $^ /dev/null | sort > $@ + $(INFO_GEN_FINAL_PARTMAP_LIST) + $(V_PREFIX)cat $^ /dev/null | sort > $@ ifeq (, $(UNIFONT_HEX)) else === modified file 'commands/i386/pc/vbeinfo.c' --- commands/i386/pc/vbeinfo.c 2007-07-21 22:32:33 +0000 +++ commands/i386/pc/vbeinfo.c 2008-07-09 14:39:39 +0000 @@ -18,6 +18,7 @@ */ #include +#include #include #include #include @@ -48,12 +49,22 @@ grub_err_t err; char *modevar; - grub_printf ("List of compatible video modes:\n"); - err = grub_vbe_probe (&controller_info); if (err != GRUB_ERR_NONE) return err; + int row = 0; + + grub_printf ("VBE info: version: %d.%d OEM software rev: %d.%d\n", + controller_info.version >> 8, + controller_info.version & 0xFF, + controller_info.oem_software_rev >> 8, + controller_info.oem_software_rev & 0xFF); + row++; + grub_printf (" total memory: %d KiB\n", + (controller_info.total_memory << 16) / 1024); + row++; + /* Because the information on video modes is stored in a temporary place, it is better to copy it to somewhere safe. */ p = video_mode_list = real2pm (controller_info.video_mode_ptr); @@ -67,6 +78,10 @@ grub_memcpy (saved_video_mode_list, video_mode_list, video_mode_list_size); + grub_printf ("List of compatible video modes:\n"); + grub_printf ("Legend: P=Packed pixel, D=Direct color, " + "mask/pos=R/G/B/reserved\n"); + /* Walk through all video modes listed. */ for (p = saved_video_mode_list; *p != 0xFFFF; p++) { @@ -103,10 +118,10 @@ switch (mode_info_tmp.memory_model) { case 0x04: - memory_model = "Packed Pixel"; + memory_model = "Packed"; break; case 0x06: - memory_model = "Direct Color"; + memory_model = "Direct"; break; default: @@ -116,12 +131,31 @@ if (! memory_model) continue; - grub_printf ("0x%03x: %d x %d x %d bpp (%s)\n", - mode, + grub_printf ("0x%03x: %4dx%4dx%2d %s", + mode, mode_info_tmp.x_resolution, mode_info_tmp.y_resolution, mode_info_tmp.bits_per_pixel, - memory_model); + memory_model); + if (memory_model[0] == 'D') + grub_printf (" mask: %d/%d/%d/%d pos: %d/%d/%d/%d", + mode_info_tmp.red_mask_size, + mode_info_tmp.green_mask_size, + mode_info_tmp.blue_mask_size, + mode_info_tmp.rsvd_mask_size, + mode_info_tmp.red_field_position, + mode_info_tmp.green_field_position, + mode_info_tmp.blue_field_position, + mode_info_tmp.rsvd_field_position); + grub_printf ("\n"); + + if (++row >= 25) + { + grub_printf ("-- More --"); + grub_getkey (); + grub_printf ("\r \r"); + row = 0; + } } grub_free (saved_video_mode_list); === modified file 'commands/sleep.c' --- commands/sleep.c 2008-05-16 20:55:29 +0000 +++ commands/sleep.c 2008-07-04 16:55:48 +0000 @@ -43,15 +43,15 @@ grub_printf ("%d ", n); } -/* Based on grub_millisleep() from kern/misc.c. */ +/* Based on grub_millisleep() from kern/generic/millisleep.c. */ static int grub_interruptible_millisleep (grub_uint32_t ms) { - grub_uint32_t end_at; - - end_at = grub_get_rtc () + grub_div_roundup (ms * GRUB_TICKS_PER_SECOND, 1000); - - while (grub_get_rtc () < end_at) + grub_uint64_t start; + + start = grub_get_time_ms (); + + while (grub_get_time_ms () - start < ms) if (grub_checkkey () >= 0 && GRUB_TERM_ASCII_CHAR (grub_getkey ()) == GRUB_TERM_ESC) return 1; === modified file 'commands/videotest.c' --- commands/videotest.c 2007-07-21 22:32:33 +0000 +++ commands/videotest.c 2008-07-19 18:25:18 +0000 @@ -17,6 +17,7 @@ */ #include +#include #include #include #include @@ -26,15 +27,38 @@ #include #include #include - -static grub_err_t -grub_cmd_videotest (struct grub_arg_list *state __attribute__ ((unused)), - int argc __attribute__ ((unused)), - char **args __attribute__ ((unused))) -{ +#include +#include +#include /* for grub_vbe_bios_set_display_start () test */ +/* Option array indices. */ +#define ARGINDEX_TEST_TIME 0 +#define ARGINDEX_DOUBLE_BUF 1 + +static const struct grub_arg_option arg_options[] = { + {"time", 't', 0, "Time to run each test, in seconds.", 0, ARG_TYPE_INT}, + {"dbuf", 'd', 0, "Use double buffered graphics.", 0, ARG_TYPE_NONE}, + {0, 0, 0, 0, 0, 0} +}; + +#define DEFAULT_TEST_TIME 5 + +/* Command options -- populated base on command line arguments. */ +struct videotest_options +{ + int test_time; + int double_buffering; +}; + + +static void +basic_video_test (struct videotest_options *vt_opts) +{ + int mode_type = GRUB_VIDEO_MODE_TYPE_INDEX_COLOR; + if (vt_opts->double_buffering) + mode_type |= GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED; if (grub_video_setup (1024, 768, GRUB_VIDEO_MODE_TYPE_INDEX_COLOR) != GRUB_ERR_NONE) - return grub_errno; + return; grub_getkey (); @@ -44,7 +68,8 @@ unsigned int width; unsigned int height; int i; - struct grub_font_glyph glyph; + grub_font_t font; + struct grub_font_glyph *glyph; struct grub_video_render_target *text_layer; grub_video_color_t palette[16]; @@ -65,8 +90,15 @@ color = grub_video_map_rgb (0, 255, 255); grub_video_fill_rect (color, 100, 100, 100, 100); - grub_font_get_glyph ('*', &glyph); - grub_video_blit_glyph (&glyph, color, 200 ,0); + font = grub_font_get ("Helvetica Bold 14"); + if (! font) + { + grub_error (GRUB_ERR_BAD_FONT, "No font loaded."); + return; + } + + glyph = grub_font_get_glyph (font, '*'); + grub_video_blit_glyph (glyph, color, 200 ,0); grub_video_set_viewport (x + 150, y + 150, width - 150 * 2, height - 150 * 2); @@ -77,18 +109,18 @@ color = grub_video_map_rgb (255, 255, 255); - grub_font_get_glyph ('A', &glyph); - grub_video_blit_glyph (&glyph, color, 16, 16); - grub_font_get_glyph ('B', &glyph); - grub_video_blit_glyph (&glyph, color, 16 * 2, 16); + glyph = grub_font_get_glyph (font, 'A'); + grub_video_blit_glyph (glyph, color, 16, 16); + glyph = grub_font_get_glyph (font, 'B'); + grub_video_blit_glyph (glyph, color, 16 * 2, 16); - grub_font_get_glyph ('*', &glyph); + glyph = grub_font_get_glyph (font, '*'); for (i = 0; i < 16; i++) { color = grub_video_map_color (i); palette[i] = color; - grub_video_blit_glyph (&glyph, color, 16 + i * 16, 32); + grub_video_blit_glyph (glyph, color, 16 + i * 16, 32); } grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY); @@ -101,30 +133,1076 @@ 0, 0, width, height); } + grub_video_swap_buffers (); + grub_getkey (); + + /* Test VBE set display start address. */ + /* This should scroll the screen, first vertically, + * then horizontally. The horizontal scrolling seems to + * only have a resolution of about 16 pixels on my VIA Mini-ITX. */ + int vbestatus; + int vbeok = 0; + int vbeerr = 0; + for (i = 0; i < 50; i++) + { + vbestatus = grub_vbe_bios_set_display_start (0, i); + if (vbestatus == GRUB_VBE_STATUS_OK) + vbeok++; + else + vbeerr++; + } + + + grub_getkey (); + + for (i = 0; i < 50; i++) + { + vbestatus = grub_vbe_bios_set_display_start (i, 50); + if (vbestatus == GRUB_VBE_STATUS_OK) + vbeok++; + else + vbeerr++; + } + grub_getkey (); grub_video_delete_render_target (text_layer); - - grub_video_restore (); - - for (i = 0; i < 16; i++) - grub_printf("color %d: %08x\n", i, palette[i]); - - grub_errno = GRUB_ERR_NONE; + grub_video_restore (); + + grub_printf ("VBE set_display_start status: %d\n", vbestatus); + grub_printf ("ok: %d\n", vbeok); + grub_printf ("errors: %d\n", vbeerr); + + grub_errno = GRUB_ERR_NONE; +} + + + +/** + * Simple opaque image blit test. + * Returns the error status in grub_errno. + */ +static void +bitmap_demo (struct videotest_options *vt_opts) +{ + int mode_type = GRUB_VIDEO_MODE_TYPE_RGB; + if (vt_opts->double_buffering) + mode_type |= GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED; + if (grub_video_setup (1024, 768, mode_type) != GRUB_ERR_NONE) + return; + + grub_video_rect_t view; + grub_video_get_viewport ((unsigned *) &view.x, (unsigned *) &view.y, + (unsigned *) &view.width, + (unsigned *) &view.height); + + grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY); + const int bm1width = 500, bm1height = 400; + struct grub_video_bitmap *bitmap1; + + if (grub_video_bitmap_create (&bitmap1, bm1width, bm1height, + GRUB_VIDEO_BLIT_FORMAT_RGB_888) + != GRUB_ERR_NONE) + return; + + int offset = 0; + int x; + int y; + grub_uint8_t *data = grub_video_bitmap_get_data (bitmap1); + for (y = 0; y < bm1height; y++) + { + for (x = 0; x < bm1width; x++) + { + data[offset++] = x ^ y; /* red */ + data[offset++] = (x * 3) ^ (y * 3); /* green */ + data[offset++] = (x * 2) ^ (y * 2); /* blue */ + } + } + + /* Blit the entire bitmap in the center of the screen. */ + grub_video_blit_bitmap (bitmap1, + GRUB_VIDEO_BLIT_REPLACE, + view.x + (view.width - bm1width) / 2, + view.y + (view.height - bm1height) / 2, + 0, 0, bm1width, bm1height); + grub_video_swap_buffers (); + grub_getkey (); + + /* Blit more copies of the bitmap. */ + /* Upper left. */ + grub_video_blit_bitmap (bitmap1, GRUB_VIDEO_BLIT_REPLACE, + view.x, view.y, 0, 0, bm1width, bm1height); + /* Upper right. */ + grub_video_blit_bitmap (bitmap1, + GRUB_VIDEO_BLIT_REPLACE, + view.x + view.width - bm1width, + view.y, 0, 0, bm1width, bm1height); + /* Lower left. */ + grub_video_blit_bitmap (bitmap1, + GRUB_VIDEO_BLIT_REPLACE, + view.x, + view.y + view.height - bm1height, + 0, 0, bm1width, bm1height); + /* Lower right. */ + grub_video_blit_bitmap (bitmap1, + GRUB_VIDEO_BLIT_REPLACE, + view.x + view.width - bm1width, + view.y + view.height - bm1height, + 0, 0, bm1width, bm1height); + grub_video_swap_buffers (); + grub_getkey (); + + int clearbg = 0; /* Boolean flag: whether to fill background. */ + grub_video_color_t bgcolor = grub_video_map_rgb (16, 16, 96); + + /* Animate the image sliding in. End when a key is pressed. */ + int vscale = 1000; + int velocityx = -5000; + int velocityy = -8000; + int positionx = 100; + int positiony = 300; + + grub_uint32_t frame_count = 0; + grub_uint32_t start_time = grub_get_time_ms (); + + while (grub_checkkey () == -1) + { + /* If the time limit option is set, then check if it's exceeded. */ + if (vt_opts->test_time != 0 + && grub_get_time_ms () >= start_time + vt_opts->test_time * 1000) + break; + + int newx = positionx + velocityx / vscale; + int newy = positiony + velocityy / vscale; + + /* Check collision w/ left */ + if (newx < view.x && velocityx < 0) + { + velocityx = -velocityx; + newx = positionx + velocityx / vscale; + } + /* Check collision w/ right */ + if (newx + bm1width > view.x + view.width && velocityx > 0) + { + velocityx = -velocityx; + newx = positionx + velocityx / vscale; + } + /* Check collision w/ top */ + if (newy < 0 && velocityy < 0) + { + velocityy = -velocityy; + newy = positiony + velocityy / vscale; + } + /* Check collision w/ bottom */ + if (newy + bm1height > view.y + view.height && velocityy > 0) + { + velocityy = -velocityy; + newy = positiony + velocityy / vscale; + } + + positionx = newx; + positiony = newy; + + if (clearbg) + grub_video_fill_rect (bgcolor, view.x, view.y, view.width, + view.height); + + grub_video_blit_bitmap (bitmap1, + GRUB_VIDEO_BLIT_REPLACE, + view.x + positionx, + view.y + positiony, 0, 0, bm1width, bm1height); + grub_video_swap_buffers (); + frame_count++; + + /* Acceleration due to gravity... + * note that the y coordinate is increasing downward. */ + velocityy += vscale / 7; + } + + /* Calculate average frame rate. */ + grub_uint32_t duration = grub_get_time_ms () - start_time; + grub_uint32_t fps_x10 = 10 * 1000 * frame_count / duration; + + /* Eat the keystroke. */ + if (grub_checkkey () != -1) + grub_getkey (); + + grub_video_bitmap_destroy (bitmap1); + grub_video_restore (); + + grub_printf ("Average frame rate: %d.%d fps\n", fps_x10 / 10, fps_x10 % 10); + + grub_errno = GRUB_ERR_NONE; +} + + + +/* Configuration settings for a benchmark run. */ +struct benchmark_config +{ + int width; + int height; + unsigned int mode_type; + int use_rgba_bitmaps; +}; + +/* Macro to make the benchmark_configs[] declaration more concise. */ +#define MODE_TYPE_RGB(bpp) (GRUB_VIDEO_MODE_TYPE_RGB \ + | ((bpp) << GRUB_VIDEO_MODE_TYPE_DEPTH_POS)) + +/* The video configurations to use for the benchmark. */ +static struct benchmark_config benchmark_configs[] = { + {320, 200, MODE_TYPE_RGB (0), 0}, + {640, 480, MODE_TYPE_RGB (0), 0}, + {1024, 768, MODE_TYPE_RGB (0), 0}, + {1024, 768, MODE_TYPE_RGB (0), 1}, +}; + +#define NUM_BENCHMARK_CONFIGS (sizeof(benchmark_configs) \ + / sizeof(benchmark_configs[0])) + +struct benchmark_result +{ + /* If set to 1, the test was able to run successfully; + * 0 means there was an error. */ + int test_passed; + + /* Bits per pixel for the video mode used. */ + int bpp; + + /* All fps are in fps * 10 to achieve one decimal place. */ + /* Set to 0 to indicate the test could not be run. */ + grub_int32_t fill_fps; + grub_int32_t blit_fps; + grub_int32_t blend_fps; +}; + +#define BENCHMARK_RESULT_FPS_SCALE 10 + +static void +move_rectangle_one_step (int *x, int *y, + int width, int height, + int *vx, int *vy, const grub_video_rect_t * bounds) +{ + int newx = *x + *vx; + int newy = *y + *vy; + + /* Check collision w/ left */ + if (newx < bounds->x && *vx < 0) + { + *vx = -*vx; + newx = *x + *vx; + } + /* Check collision w/ right */ + if (newx + width > bounds->x + bounds->width && *vx > 0) + { + *vx = -*vx; + newx = *x + *vx; + } + /* Check collision w/ top */ + if (newy < 0 && *vy < 0) + { + *vy = -*vy; + newy = *y + *vy; + } + /* Check collision w/ bottom */ + if (newy + height > bounds->y + bounds->height && *vy > 0) + { + *vy = -*vy; + newy = *y + *vy; + } + + *x = newx; + *y = newy; +} + +/** + * Run the benchmark test for a particular video mode, which is specified + * by ``*config``. The results of the test are stored in ``*result``. + */ +static void +do_benchmark (const struct benchmark_config *config, + struct benchmark_result *result, + struct videotest_options *vt_opts) +{ + struct grub_video_mode_info modeinfo; + + result->test_passed = 0; + result->fill_fps = 0; + result->blit_fps = 0; + result->blend_fps = 0; + result->bpp = 0; + + int mode_type = config->mode_type; + if (vt_opts->double_buffering) + mode_type |= GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED; + if (grub_video_setup (config->width, config->height, + mode_type) != GRUB_ERR_NONE) + return; + + if (grub_video_get_info (&modeinfo) == GRUB_ERR_NONE) + result->bpp = modeinfo.bpp; + + /* Full screen bitmap blit test. */ + + grub_video_rect_t view; + grub_video_get_viewport ((unsigned *) &view.x, (unsigned *) &view.y, + (unsigned *) &view.width, + (unsigned *) &view.height); + + grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY); + + + /* For measuring timing. */ + grub_uint32_t frame_count; + grub_uint64_t start_time; + grub_uint64_t desired_stop_time; + grub_uint32_t duration; + + + /*** FILL TEST ***/ + + /* Alternates between 0 and 1 to change the color. */ + int color_flag = 0; + grub_video_color_t fillcolors[2]; + fillcolors[0] = grub_video_map_rgb (0, 0, 255); + fillcolors[1] = grub_video_map_rgb (255, 255, 0); + + frame_count = 0; + start_time = grub_get_time_ms (); + desired_stop_time = start_time + vt_opts->test_time * 1000; + + while (grub_get_time_ms () < desired_stop_time && grub_checkkey () == -1) + { + grub_video_fill_rect (fillcolors[color_flag], + view.x, view.y, view.width, view.height); + grub_video_swap_buffers (); + color_flag ^= 1; + frame_count++; + } + if (grub_checkkey () != -1) + grub_getkey (); /* Eat the keypress, if there was one. */ + + /* Calculate average frame rate. */ + duration = grub_get_time_ms () - start_time; + if (duration == 0) + { + result->fill_fps = 0; + } + else + { + result->fill_fps = + (BENCHMARK_RESULT_FPS_SCALE * 1000 + * frame_count / duration); + } + + + /*** BLIT TEST ***/ + + /* Generate two bitmaps, the same size as the screen. */ + const int bitmapwidth = view.width, bitmapheight = view.height; + struct grub_video_bitmap *bitmap1; + struct grub_video_bitmap *bitmap2; + enum grub_video_blit_format bitmap_format = config->use_rgba_bitmaps + ? GRUB_VIDEO_BLIT_FORMAT_RGBA_8888 : GRUB_VIDEO_BLIT_FORMAT_RGB_888; + + if (grub_video_bitmap_create (&bitmap1, bitmapwidth, bitmapheight, + bitmap_format) != GRUB_ERR_NONE) + return; + + if (grub_video_bitmap_create (&bitmap2, bitmapwidth, bitmapheight, + bitmap_format) != GRUB_ERR_NONE) + { + grub_video_bitmap_destroy (bitmap1); + return; + } + + int offset; + int x; + int y; + grub_uint8_t *data; + + offset = 0; + data = grub_video_bitmap_get_data (bitmap1); + for (y = 0; y < bitmapheight; y++) + { + for (x = 0; x < bitmapwidth; x++) + { + data[offset++] = x ^ y; /* red */ + data[offset++] = (x * 3) ^ (y * 3); /* green */ + data[offset++] = (x * 2) ^ (y * 2); /* blue */ + if (config->use_rgba_bitmaps) + data[offset++] = 255; + } + } + + offset = 0; + data = grub_video_bitmap_get_data (bitmap2); + for (y = 0; y < bitmapheight; y++) + { + for (x = 0; x < bitmapwidth; x++) + { + data[offset++] = x + y; /* red */ + data[offset++] = x * x + y * y; /* green */ + data[offset++] = x * x / 4 + y * y / 4; /* blue */ + if (config->use_rgba_bitmaps) + data[offset++] = 255; + } + } + + + /* Now do the blit test, alternating between the two bitmaps. */ + frame_count = 0; + start_time = grub_get_time_ms (); + desired_stop_time = start_time + vt_opts->test_time * 1000; + + int cur = 0; /* Which bitmap to draw this frame. */ + while (grub_get_time_ms () < desired_stop_time && grub_checkkey () == -1) + { + struct grub_video_bitmap *current_bitmap = cur == 0 ? bitmap1 : bitmap2; + grub_video_blit_bitmap (current_bitmap, + GRUB_VIDEO_BLIT_REPLACE, + view.x, view.y, 0, 0, bitmapwidth, + bitmapheight); + grub_video_swap_buffers (); + frame_count++; + cur ^= 1; + } + if (grub_checkkey () != -1) + grub_getkey (); /* Eat the keypress, if there was once. */ + + /* Calculate average frame rate. */ + duration = grub_get_time_ms () - start_time; + if (duration == 0) + { + result->blit_fps = 0; + } + else + { + result->blit_fps = + (BENCHMARK_RESULT_FPS_SCALE * 1000 + * frame_count / duration); + } + + grub_video_bitmap_destroy (bitmap2); + grub_video_bitmap_destroy (bitmap1); + + + + /*** BLEND TEST ***/ + + /* Generate two bitmaps, with alpha translucency. */ + const int bbw = view.width * 2 / 3; + const int bbh = view.height * 2 / 3; + + if (grub_video_bitmap_create (&bitmap1, bbw, bbh, + GRUB_VIDEO_BLIT_FORMAT_RGBA_8888) != + GRUB_ERR_NONE) + return; + + if (grub_video_bitmap_create (&bitmap2, bbw, bbh, + GRUB_VIDEO_BLIT_FORMAT_RGBA_8888) != + GRUB_ERR_NONE) + { + grub_video_bitmap_destroy (bitmap1); + return; + } + + offset = 0; + data = grub_video_bitmap_get_data (bitmap1); + for (y = 0; y < bbh; y++) + { + for (x = 0; x < bbw; x++) + { + /* Calculate a to be increasing away from the center. */ + int dx = 256 * (x - bbw / 2) / (bbw / 2); + int dy = 256 * (y - bbh / 2) / (bbh / 2); + int a = dx * dx + dy * dy; + /* range for a = 0 .. 2*(256^2) = 2*2^16 = 2^17 */ + a >>= 17 - 8; /* Make range 0..256. */ + if (a > 255) + a = 255; + + data[offset++] = x ^ y; /* red */ + data[offset++] = (x * 3) ^ (y * 3); /* green */ + data[offset++] = (x * 2) ^ (y * 2); /* blue */ + data[offset++] = 255 - a; + } + } + + offset = 0; + data = grub_video_bitmap_get_data (bitmap2); + for (y = 0; y < bbh; y++) + { + for (x = 0; x < bbw; x++) + { + data[offset++] = x + y; /* red */ + data[offset++] = x * x + y * y; /* green */ + data[offset++] = x * x / 4 + y * y / 4; /* blue */ + data[offset++] = 255; + } + } + + frame_count = 0; + start_time = grub_get_time_ms (); + desired_stop_time = start_time + vt_opts->test_time * 1000; + + + grub_video_color_t bgcolor = grub_video_map_rgb (80, 80, 80); + + /* Bitmap locations. */ + int b1x = 0; + int b1y = 0; + int b2x = view.width - bbw; + int b2y = 0; + /* Bitmap velocities. */ + int b1vx = 8; + int b1vy = 12; + int b2vx = -10; + int b2vy = 9; + + while (grub_get_time_ms () < desired_stop_time && grub_checkkey () == -1) + { + move_rectangle_one_step (&b1x, &b1y, bbw, bbh, &b1vx, &b1vy, &view); + move_rectangle_one_step (&b2x, &b2y, bbw, bbh, &b2vx, &b2vy, &view); + grub_video_fill_rect (bgcolor, view.x, view.y, view.width, view.height); + grub_video_blit_bitmap (bitmap2, + GRUB_VIDEO_BLIT_BLEND, + b2x, b2y, 0, 0, bbw, bbh); + grub_video_blit_bitmap (bitmap1, + GRUB_VIDEO_BLIT_BLEND, + b1x, b1y, 0, 0, bbw, bbh); + grub_video_swap_buffers (); + frame_count++; + cur ^= 1; + } + if (grub_checkkey () != -1) + grub_getkey (); /* Eat the keypress, if there was once. */ + + /* Calculate average frame rate. */ + duration = grub_get_time_ms () - start_time; + if (duration == 0) + { + result->blend_fps = 0; + } + else + { + result->blend_fps = + (BENCHMARK_RESULT_FPS_SCALE * 1000 + * frame_count / duration); + } + + grub_video_bitmap_destroy (bitmap2); + grub_video_bitmap_destroy (bitmap1); + + grub_video_restore (); + result->test_passed = 1; +} + +/** + * Run a benchmark test in a series of video modes. + * The results are reported in tabular form. This will be helpful to + * determine how effective various optimizations are. + */ +static void +benchmark_test (struct videotest_options *vt_opts) +{ + unsigned int i; + struct benchmark_result results[NUM_BENCHMARK_CONFIGS]; + + /* Set option default values. */ + if (vt_opts->test_time == 0) + vt_opts->test_time = DEFAULT_TEST_TIME; + + /* Run benchmarks. */ + for (i = 0; i < NUM_BENCHMARK_CONFIGS; i++) + { + grub_error_push (); + do_benchmark (&benchmark_configs[i], &results[i], vt_opts); + } + + grub_print_error (); + + /* Display results. */ + grub_printf ("Benchmark results (in frames/s):\n"); + grub_printf ("(W=Width, H=Height, B=Bits per pixel,\n" + " T=Mode Type, A=Bitmap Alpha)\n"); + grub_printf (" W H B T A FILL BLIT BLEND\n"); + for (i = 0; i < NUM_BENCHMARK_CONFIGS; i++) + { + struct benchmark_config *c = &benchmark_configs[i]; + struct benchmark_result *r = &results[i]; + + if (r->test_passed) + { + grub_printf ("%4dx%4d %2d %d %d: %4d.%d %4d.%d %4d.%d\n", + c->width, c->height, r->bpp, + c->mode_type & 0x0F, + c->use_rgba_bitmaps, + r->fill_fps / BENCHMARK_RESULT_FPS_SCALE, + r->fill_fps % BENCHMARK_RESULT_FPS_SCALE, + r->blit_fps / BENCHMARK_RESULT_FPS_SCALE, + r->blit_fps % BENCHMARK_RESULT_FPS_SCALE, + r->blend_fps / BENCHMARK_RESULT_FPS_SCALE, + r->blend_fps % BENCHMARK_RESULT_FPS_SCALE); + } + else + { + grub_printf ("%4dx%4d %2d %d Not supported.\n", + c->width, c->height, + ((c->mode_type & GRUB_VIDEO_MODE_TYPE_DEPTH_MASK) + >> GRUB_VIDEO_MODE_TYPE_DEPTH_POS), + c->mode_type & 0x0F); + } + } + + grub_errno = GRUB_ERR_NONE; +} + + +/** + * Test time functions. + */ +static void +clock_test (struct videotest_options *vt_opts) +{ + int mode_type = GRUB_VIDEO_MODE_TYPE_RGB; + if (vt_opts->double_buffering) + mode_type |= GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED; + if (grub_video_setup (640, 480, mode_type) != GRUB_ERR_NONE) + return; + + grub_video_rect_t view; + grub_video_get_viewport ((unsigned *) &view.x, (unsigned *) &view.y, + (unsigned *) &view.width, + (unsigned *) &view.height); + + grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY); + + /* Draw a progress bar that animates in sync with time. */ + + grub_video_rect_t bar_frame; + bar_frame.width = view.width - 2 * view.width / 10; + bar_frame.height = view.height / 20; + bar_frame.x = view.x + view.width / 10; + bar_frame.y = view.y + view.height - bar_frame.height - view.height / 10; + + grub_video_color_t bgcolor = grub_video_map_rgb (50, 50, 50); + grub_video_color_t framecolor = grub_video_map_rgb (255, 255, 255); + grub_video_color_t barbgcolor = grub_video_map_rgb (0, 0, 128); + grub_video_color_t barcolor = grub_video_map_rgb (100, 100, 255); + + grub_uint32_t frame_count; + grub_uint64_t start_time; + grub_uint64_t barstart; + grub_uint32_t barlength = 1000; + + grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY); + + frame_count = 0; + start_time = grub_get_time_ms (); + barstart = grub_get_time_ms (); + + while (grub_checkkey () == -1) + { + grub_uint64_t now; + grub_uint32_t bartime; + now = grub_get_time_ms (); + /* If the time limit option is set, then check if it's exceeded. */ + if (vt_opts->test_time != 0 + && now >= start_time + vt_opts->test_time * 1000) + break; + bartime = now - barstart; + if (bartime > barlength) + { + barstart = grub_get_time_ms (); /* Start over. */ + bartime = barlength; + } + + /* Clear screen. */ + grub_video_fill_rect (bgcolor, view.x, view.y, view.width, view.height); + + /* Border. */ + grub_video_fill_rect (framecolor, + bar_frame.x - 1, bar_frame.y - 1, + bar_frame.width + 2, bar_frame.height + 2); + + /* Bar background. */ + int barwidth = bar_frame.width * bartime / barlength; + grub_video_fill_rect (barbgcolor, bar_frame.x + barwidth, + bar_frame.y, bar_frame.width - barwidth, + bar_frame.height); + /* Bar foreground. */ + grub_video_fill_rect (barcolor, bar_frame.x, bar_frame.y, + barwidth, bar_frame.height); + grub_video_swap_buffers (); + frame_count++; + } + + grub_uint32_t duration = grub_get_time_ms () - start_time; + grub_uint32_t fps_x10 = 10 * 1000 * frame_count / duration; + + if (grub_checkkey () != -1) + grub_getkey (); /* Eat the keypress, if there was one. */ + grub_video_restore (); + grub_printf ("Average frame rate: %d.%d fps\n", fps_x10 / 10, fps_x10 % 10); +} + + +/** + * Test double buffering. + */ +static void +doublebuf_test (struct videotest_options *vt_opts) +{ + int mode_type = GRUB_VIDEO_MODE_TYPE_RGB; + if (vt_opts->double_buffering) + mode_type |= GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED; + if (grub_video_setup (640, 480, mode_type) != GRUB_ERR_NONE) + return; + + grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY); + + grub_video_color_t red = grub_video_map_rgb (255, 50, 50); + grub_video_color_t yellow = grub_video_map_rgb (255, 255, 0); + grub_video_color_t green = grub_video_map_rgb (20, 255, 20); + grub_video_color_t blue = grub_video_map_rgb (50, 50, 255); + grub_video_color_t black = grub_video_map_rgb (0, 0, 0); + grub_video_color_t bgcolor = grub_video_map_rgb (255, 255, 255); + + grub_video_fill_rect (bgcolor, 0, 0, 640, 480); + grub_video_fill_rect (red, 100, 100, 200, 200); + grub_video_swap_buffers (); + grub_getkey (); + + grub_video_fill_rect (bgcolor, 0, 0, 640, 480); + grub_video_fill_rect (yellow, 120, 120, 200, 200); + grub_video_swap_buffers (); + grub_getkey (); + + grub_video_fill_rect (bgcolor, 0, 0, 640, 480); + grub_video_fill_rect (green, 140, 140, 200, 200); + grub_video_swap_buffers (); + grub_getkey (); + + grub_video_fill_rect (bgcolor, 0, 0, 640, 480); + grub_video_fill_rect (blue, 160, 160, 200, 200); + grub_video_swap_buffers (); + grub_getkey (); + + grub_video_fill_rect (bgcolor, 0, 0, 640, 480); + grub_video_fill_rect (black, 180, 180, 200, 200); + grub_video_swap_buffers (); + grub_getkey (); + + grub_video_restore (); +} + + +/** + * Test text rendering. + */ +static void +text_test (struct videotest_options *vt_opts) +{ + grub_video_color_t color; + const char *s; + int view_x; + int view_y; + int view_width; + int view_height; + int xpos; + int ypos; + int i; + grub_font_t font1; + grub_font_t font2; + grub_font_t font3; + + int mode_type = GRUB_VIDEO_MODE_TYPE_RGB; + if (vt_opts->double_buffering) + mode_type |= GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED; + if (grub_video_setup (1024, 768, mode_type) != GRUB_ERR_NONE) + return; + + grub_video_get_viewport ((unsigned int *) &view_x, + (unsigned int *) &view_y, + (unsigned int *) &view_width, + (unsigned int *) &view_height); + grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY); + + if (!(font1 = grub_font_get ("New Century Schoolbook 24")) + || !(font2 = grub_font_get ("Helvetica Bold 14")) + || !(font3 = grub_font_get ("Helvetica 10"))) + { + grub_video_restore (); + grub_error (GRUB_ERR_BAD_FONT, "No font loaded."); + return; + } + + color = grub_video_map_rgb (0, 0, 0); + grub_video_fill_rect (color, 0, 0, view_width, view_height); + + color = grub_video_map_rgb (255, 0, 0); + grub_video_fill_rect (color, 0, 0, 100, 100); + + color = grub_video_map_rgb (0, 255, 255); + grub_video_fill_rect (color, 100, 100, 100, 100); + + color = grub_video_map_rgb (255, 255, 255); + + xpos = 10; + ypos = 30; + s = "Hello, World!"; + for (i = 0; i < 40; i++) + { + if (xpos + grub_font_get_string_width (font1, s) >= view_width) + { + /* The string will wrap; go to the beginning of the next line. */ + xpos = 10; + ypos += (grub_font_get_descent (font1) + + grub_font_get_ascent (font1)); + } + grub_video_draw_string ("Hello, World!", + font1, grub_video_map_rgb (255, 255, 0), + view_x + xpos, view_y + ypos); + + xpos += grub_font_get_string_width (font1, s); + } + + xpos = 300; + ypos = 450; + grub_video_draw_string (grub_font_get_name (font1), + font1, grub_video_map_rgb (255, 255, 255), + view_x + xpos, view_y + ypos); + ypos += grub_font_get_descent (font1) + grub_font_get_ascent (font2) + 2; + grub_video_draw_string (grub_font_get_name (font2), + font2, grub_video_map_rgb (255, 255, 255), + view_x + xpos, view_y + ypos); + ypos += grub_font_get_descent (font2) + grub_font_get_ascent (font3) + 2; + grub_video_draw_string (grub_font_get_name (font3), + font3, grub_video_map_rgb (255, 255, 255), + view_x + xpos, view_y + ypos); + + grub_video_swap_buffers (); + grub_getkey (); + grub_video_restore (); + grub_errno = GRUB_ERR_NONE; +} + + +/** + * Test bitmap scaling. + */ +static void +scale_test (struct videotest_options *vt_opts) +{ + int mode_type = GRUB_VIDEO_MODE_TYPE_RGB; + if (vt_opts->double_buffering) + mode_type |= GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED; + if (grub_video_setup (1024, 768, mode_type) != GRUB_ERR_NONE) + return; + + grub_errno = GRUB_ERR_NONE; + grub_video_rect_t view; + grub_video_get_viewport ((unsigned *) &view.x, (unsigned *) &view.y, + (unsigned *) &view.width, + (unsigned *) &view.height); + + grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY); + + grub_font_t font; + if (!(font = grub_font_get ("Helvetica 10"))) + { + grub_video_restore (); + grub_error (GRUB_ERR_BAD_FONT, "No font loaded."); + return; + } + + grub_video_color_t color; + + int text_y = 0; + int text_height = 25; + + color = grub_video_map_rgb (44, 44, 200); + grub_video_fill_rect (color, view.x, view.y, view.width, view.height); + color = grub_video_map_rgb (255, 255, 255); + + grub_video_draw_string ("Loading image", + font, color, 10, text_y += text_height); + + enum grub_video_bitmap_scale_method scale_method = + GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST; + const char *bitmap_name = "/boot/images/wallpaper.tga"; + struct grub_video_bitmap *bitmap; + grub_video_bitmap_load (&bitmap, bitmap_name); + if (grub_errno != GRUB_ERR_NONE) + { + grub_video_draw_string ("Error loading bitmap", + font, color, 10, text_y += text_height); + } + else + { + grub_video_draw_string ("Original image", + font, color, 10, text_y += text_height); + grub_video_blit_bitmap (bitmap, GRUB_VIDEO_BLIT_BLEND, + 400, text_y - 10, + 0, 0, grub_video_bitmap_get_width (bitmap), + grub_video_bitmap_get_height (bitmap)); + + struct grub_video_bitmap *bitmap2; + if (grub_video_bitmap_create_scaled (&bitmap2, 40, 40, + bitmap, + scale_method) + != GRUB_ERR_NONE) + { + grub_video_draw_string ("Error scaling down bitmap", + font, color, 10, text_y += text_height); + } + else + { + grub_video_draw_string ("Scaled down version", + font, color, 10, text_y += text_height); + grub_video_blit_bitmap (bitmap2, GRUB_VIDEO_BLIT_BLEND, + 400, text_y + 100, + 0, 0, grub_video_bitmap_get_width (bitmap2), + grub_video_bitmap_get_height (bitmap2)); + grub_video_bitmap_destroy (bitmap2); + } + + struct grub_video_bitmap *bitmap3; + if (grub_video_bitmap_create_scaled (&bitmap3, 500, 300, bitmap, + scale_method) + != GRUB_ERR_NONE) + { + grub_video_draw_string ("Error scaling up bitmap", + font, color, 10, text_y += text_height); + } + else + { + grub_video_draw_string ("Scaled up version", + font, color, 10, text_y += text_height); + grub_video_blit_bitmap (bitmap3, GRUB_VIDEO_BLIT_BLEND, + 400, text_y + 50, + 0, 0, grub_video_bitmap_get_width (bitmap3), + grub_video_bitmap_get_height (bitmap3)); + grub_video_bitmap_destroy (bitmap3); + } + } + + grub_video_swap_buffers (); + grub_getkey (); + grub_video_bitmap_destroy (bitmap); + grub_video_restore (); +} + + +/** Print a list of the available tests. */ +static void list_tests (void); + +/** + * Video test command. Takes an argument specifying the test to run. + */ +static grub_err_t +grub_cmd_videotest (struct grub_arg_list *state, int argc, char **args) +{ + int i; + struct videotest_options vt_opts; + /* Pointer to the test function. */ + void (*test_func) (struct videotest_options *) = NULL; + + vt_opts.test_time = + state[ARGINDEX_TEST_TIME].set + ? grub_strtoul (state[ARGINDEX_TEST_TIME].arg, 0, 0) : 0; + vt_opts.double_buffering = state[ARGINDEX_DOUBLE_BUF].set; + + /* Parse command line arguments to determine the test to run. */ + for (i = 0; i < argc; i++) + { + char *arg = args[i]; + if (grub_strcmp (arg, "list") == 0) + { + list_tests (); + return GRUB_ERR_NONE; + } + else if (grub_strcmp (arg, "basic") == 0) + { + test_func = basic_video_test; + } + else if (grub_strcmp (arg, "bench") == 0) + { + test_func = benchmark_test; + } + else if (grub_strcmp (arg, "bitmaps") == 0) + { + test_func = bitmap_demo; + } + else if (grub_strcmp (arg, "clock") == 0) + { + test_func = clock_test; + } + else if (grub_strcmp (arg, "doublebuf") == 0) + { + test_func = doublebuf_test; + } + else if (grub_strcmp (arg, "text") == 0) + { + test_func = text_test; + } + else if (grub_strcmp (arg, "scale") == 0) + { + test_func = scale_test; + } + else + { + grub_printf ("Error: Unknown test `%s'\n", arg); + grub_errno = GRUB_ERR_BAD_ARGUMENT; + return grub_errno; + } + } + + if (test_func == NULL) + { + grub_printf ("Usage: videotest TESTNAME Run a test.\n"); + grub_printf (" videotest list List available tests.\n"); + grub_printf ("\n"); + list_tests (); + } + else + { + test_func (&vt_opts); + } + return grub_errno; } -GRUB_MOD_INIT(videotest) +static void +list_tests (void) +{ + grub_printf ("Available tests\n"); + grub_printf ("===============\n"); + grub_printf ("basic Basic video test with filled rectangles,\n"); + grub_printf (" offscreen rendering targets, some text.\n"); + grub_printf ("bench Run a performance benchmark.\n"); + grub_printf ("bitmaps Test generating and blitting bitmaps.\n"); + grub_printf ("clock Test time functions w/ animated progress bar.\n"); + grub_printf ("doublebuf Test double buffering.\n"); + grub_printf ("text Test text rendering.\n"); + grub_printf ("scale Test image scaling.\n"); + grub_printf ("\n"); +} + + +GRUB_MOD_INIT (videotest) { grub_register_command ("videotest", grub_cmd_videotest, GRUB_COMMAND_FLAG_BOTH, - "videotest", - "Test video subsystem", - 0); + "videotest TEST", + "Run the specified video subsystem test.", + arg_options); } -GRUB_MOD_FINI(videotest) +GRUB_MOD_FINI (videotest) { grub_unregister_command ("videotest"); } === modified file 'conf/common.rmk' --- conf/common.rmk 2008-07-11 19:09:14 +0000 +++ conf/common.rmk 2008-07-19 21:46:40 +0000 @@ -269,6 +269,7 @@ cmp.mod cat.mod help.mod font.mod search.mod \ loopback.mod fs_uuid.mod configfile.mod echo.mod \ terminfo.mod test.mod blocklist.mod hexdump.mod \ + gfxmenu.mod \ read.mod sleep.mod loadenv.mod # For hello.mod. @@ -276,6 +277,11 @@ hello_mod_CFLAGS = $(COMMON_CFLAGS) hello_mod_LDFLAGS = $(COMMON_LDFLAGS) +# For gfxmenu.mod. +gfxmenu_mod_SOURCES = gfxmenu/gfxmenu.c gfxmenu/widget-box.c +gfxmenu_mod_CFLAGS = $(COMMON_CFLAGS) +gfxmenu_mod_LDFLAGS = $(COMMON_LDFLAGS) + # For boot.mod. boot_mod_SOURCES = commands/boot.c boot_mod_CFLAGS = $(COMMON_CFLAGS) @@ -312,7 +318,7 @@ help_mod_LDFLAGS = $(COMMON_LDFLAGS) # For font.mod. -font_mod_SOURCES = font/manager.c +font_mod_SOURCES = font/font_cmd.c font/font.c font/loader.c font_mod_CFLAGS = $(COMMON_CFLAGS) font_mod_LDFLAGS = $(COMMON_LDFLAGS) === modified file 'conf/i386-efi.rmk' --- conf/i386-efi.rmk 2008-07-17 08:50:26 +0000 +++ conf/i386-efi.rmk 2008-07-19 21:39:45 +0000 @@ -84,7 +84,9 @@ kern/misc.c kern/mm.c kern/loader.c kern/rescue.c kern/term.c \ kern/i386/dl.c kern/i386/efi/init.c kern/parser.c kern/partition.c \ kern/env.c symlist.c kern/efi/efi.c kern/efi/init.c kern/efi/mm.c \ - term/efi/console.c disk/efi/efidisk.c + term/efi/console.c disk/efi/efidisk.c \ + kern/i386/tsc.c \ + kern/generic/millisleep.c kernel_mod_HEADERS = arg.h boot.h cache.h device.h disk.h dl.h elf.h elfload.h \ env.h err.h file.h fs.h kernel.h loader.h misc.h mm.h net.h parser.h \ partition.h pc_partition.h rescue.h symbol.h term.h time.h types.h \ === modified file 'conf/i386-pc.rmk' --- conf/i386-pc.rmk 2008-07-13 00:55:15 +0000 +++ conf/i386-pc.rmk 2008-07-19 21:46:40 +0000 @@ -43,12 +43,16 @@ kern/disk.c kern/dl.c kern/file.c kern/fs.c kern/err.c \ kern/misc.c kern/mm.c kern/loader.c kern/rescue.c kern/term.c \ kern/i386/dl.c kern/i386/pc/init.c kern/parser.c kern/partition.c \ + kern/i386/tsc.c \ + kern/generic/millisleep.c \ kern/env.c \ + kern/menu_viewer.c \ term/i386/pc/console.c \ symlist.c kernel_img_HEADERS = arg.h boot.h cache.h device.h disk.h dl.h elf.h elfload.h \ env.h err.h file.h fs.h kernel.h loader.h misc.h mm.h net.h parser.h \ partition.h pc_partition.h rescue.h symbol.h term.h time.h types.h \ + menu_viewer.h \ machine/biosdisk.h machine/boot.h machine/console.h machine/init.h \ machine/memory.h machine/loader.h machine/vga.h machine/vbe.h machine/kernel.h kernel_img_CFLAGS = $(COMMON_CFLAGS) @@ -246,7 +250,7 @@ play_mod_LDFLAGS = $(COMMON_LDFLAGS) # For video.mod. -video_mod_SOURCES = video/video.c +video_mod_SOURCES = video/video.c video/setmode.c video_mod_CFLAGS = $(COMMON_CFLAGS) video_mod_LDFLAGS = $(COMMON_LDFLAGS) @@ -261,7 +265,10 @@ videotest_mod_LDFLAGS = $(COMMON_LDFLAGS) # For bitmap.mod -bitmap_mod_SOURCES = video/bitmap.c +bitmap_mod_SOURCES = video/bitmap.c \ + video/bitmap_scale_nn.c \ + video/bitmap_scale_bilinear.c \ + video/bitmap_scale.c bitmap_mod_CFLAGS = $(COMMON_CFLAGS) bitmap_mod_LDFLAGS = $(COMMON_LDFLAGS) === modified file 'conf/powerpc-ieee1275.rmk' --- conf/powerpc-ieee1275.rmk 2008-06-19 04:14:16 +0000 +++ conf/powerpc-ieee1275.rmk 2008-07-03 04:19:16 +0000 @@ -85,6 +85,7 @@ kern/ieee1275/init.c term/ieee1275/ofconsole.c \ kern/ieee1275/openfw.c disk/ieee1275/ofdisk.c \ kern/parser.c kern/partition.c kern/env.c kern/powerpc/dl.c \ + kern/generic/millisleep.c \ symlist.c kern/powerpc/cache.S kernel_elf_HEADERS = grub/powerpc/ieee1275/ieee1275.h kernel_elf_CFLAGS = $(COMMON_CFLAGS) === modified file 'conf/sparc64-ieee1275.rmk' --- conf/sparc64-ieee1275.rmk 2008-06-19 00:04:59 +0000 +++ conf/sparc64-ieee1275.rmk 2008-07-03 14:16:34 +0000 @@ -73,6 +73,7 @@ kern/rescue.c kern/term.c term/ieee1275/ofconsole.c \ kern/sparc64/ieee1275/openfw.c disk/ieee1275/ofdisk.c \ kern/partition.c kern/env.c kern/sparc64/dl.c symlist.c \ + kern/generic/millisleep.c kern/generic/get_time_ms.c \ kern/sparc64/cache.S kern/parser.c kernel_elf_HEADERS = grub/sparc64/ieee1275/ieee1275.h kernel_elf_CFLAGS = $(COMMON_CFLAGS) @@ -195,7 +196,7 @@ cat_mod_LDFLAGS = $(COMMON_LDFLAGS) # For font.mod. -font_mod_SOURCES = font/manager.c +font_mod_SOURCES = font/font_cmd.c font/font.c font/loader.c font_mod_CFLAGS = $(COMMON_CFLAGS) font_mod_LDFLAGS = $(COMMON_LDFLAGS) === modified file 'config.h.in' --- config.h.in 2008-07-13 00:55:15 +0000 +++ config.h.in 2008-07-19 21:46:40 +0000 @@ -113,10 +113,37 @@ /* Number of bits in a file offset, on hosts where this is settable. */ #undef _FILE_OFFSET_BITS +/* Define for large files, on AIX-style hosts. */ +#undef _LARGE_FILES + +/* Define to 1 if on MINIX. */ +#undef _MINIX + +/* Define to 2 if the system does not provide POSIX.1 features except with + this defined. */ +#undef _POSIX_1_SOURCE + +/* Define to 1 if you need to in order for `stat' and other things to work. */ +#undef _POSIX_SOURCE + +/* Enable extensions on AIX 3, Interix. */ +#ifndef _ALL_SOURCE +# undef _ALL_SOURCE +#endif /* Enable GNU extensions on systems that have them. */ #ifndef _GNU_SOURCE # undef _GNU_SOURCE #endif +/* Enable threading extensions on Solaris. */ +#ifndef _POSIX_PTHREAD_SEMANTICS +# undef _POSIX_PTHREAD_SEMANTICS +#endif +/* Enable extensions on HP NonStop. */ +#ifndef _TANDEM_SOURCE +# undef _TANDEM_SOURCE +#endif +/* Enable general extensions on Solaris. */ +#ifndef __EXTENSIONS__ +# undef __EXTENSIONS__ +#endif -/* Define for large files, on AIX-style hosts. */ -#undef _LARGE_FILES === added file 'font/font.c' --- font/font.c 1970-01-01 00:00:00 +0000 +++ font/font.c 2008-07-03 14:12:08 +0000 @@ -0,0 +1,92 @@ +/* font.c - Font functions. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2008 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +grub_font_t +grub_font_get (const char *font_name) +{ + struct font_node *node; + + for (node = grub_font_list; node; node = node->next) + { + grub_font_t font = node->value; + if (grub_strcmp (font->name, font_name) == 0) + return font; + } + + /* If no font by that name is found, return the first font in the list + * as a fallback. */ + return grub_font_list->value; +} + +const char * +grub_font_get_name (grub_font_t font) +{ + return font->name; +} + +int +grub_font_get_max_char_width (grub_font_t font) +{ + return font->max_char_width; +} + +int +grub_font_get_max_char_height (grub_font_t font) +{ + return font->max_char_height; +} + +int +grub_font_get_ascent (grub_font_t font) +{ + return font->ascent; +} + +int +grub_font_get_descent (grub_font_t font) +{ + return font->descent; +} + +int +grub_font_get_string_width (grub_font_t font, const char *str) +{ + int i; + int width; + struct grub_font_glyph *glyph; + grub_size_t len; + + len = grub_strlen (str); + + for (i = 0, width = 0; i < len; i++) + { + glyph = grub_font_get_glyph (font, str[i]); + width += glyph->device_width; + } + + return width; +} === added file 'font/font_cmd.c' --- font/font_cmd.c 1970-01-01 00:00:00 +0000 +++ font/font_cmd.c 2008-07-03 14:12:08 +0000 @@ -0,0 +1,75 @@ +/* font_cmd.c - Font command definition. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2003,2005,2006,2007 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include + +static grub_err_t +loadfont_command (struct grub_arg_list *state __attribute__ ((unused)), + int argc, + char **args) +{ + if (argc == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "no font specified"); + + while (argc--) + if (grub_font_load (*args++) != 0) + return GRUB_ERR_BAD_FONT; + + return GRUB_ERR_NONE; +} + +static grub_err_t +lsfonts_command (struct grub_arg_list *state __attribute__ ((unused)), + int argc __attribute__ ((unused)), + char **args __attribute__ ((unused))) +{ + struct font_node *node; + + grub_printf ("Loaded fonts:\n"); + for (node = grub_font_list; node; node = node->next) + { + grub_font_t font = node->value; + grub_printf ("%s\n", font->name); + } + + return GRUB_ERR_NONE; +} + +GRUB_MOD_INIT(font_manager) +{ + grub_font_loader_init (); + + grub_register_command ("loadfont", loadfont_command, GRUB_COMMAND_FLAG_BOTH, + "loadfont FILE...", + "Specify one or more font files to load.", 0); + + grub_register_command ("lsfonts", lsfonts_command, GRUB_COMMAND_FLAG_BOTH, + "lsfonts", + "List the loaded fonts.", 0); +} + +GRUB_MOD_FINI(font_manager) +{ + /* Should this free fonts, unknown_glyph, etc.? Freeing fonts could + * be a Bad Thing if there are still references to any of them. */ + + grub_unregister_command ("loadfont"); +} + === added file 'font/loader.c' --- font/loader.c 1970-01-01 00:00:00 +0000 +++ font/loader.c 2008-07-03 14:12:08 +0000 @@ -0,0 +1,696 @@ +/* loader.c - Functions to handle loading fonts and glyphs from files. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2003,2005,2006,2007,2008 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +//#include +//#include +#include +#include +#include + +/* Definition of font registry. */ +struct font_node *grub_font_list; + +static int +register_font (grub_font_t font); + +static void +free_font (grub_font_t font); + +static void +remove_font (grub_font_t font); + +struct font_file_section +{ + grub_file_t file; /* The file this section is in. */ + char name[4]; /* FOURCC name of the section. */ + grub_uint32_t length; /* Length of the section contents. */ + int eof; /* Set by open_section() on EOF. */ +}; + +/* Font file format constants. */ +static const char pff2_magic[4] = { 'P', 'F', 'F', '2' }; +static const char section_names_file[4] = { 'F', 'I', 'L', 'E' }; +static const char section_names_font_name[4] = { 'N', 'A', 'M', 'E' }; +static const char section_names_max_char_width[4] = { 'M', 'A', 'X', 'W' }; +static const char section_names_max_char_height[4] = { 'M', 'A', 'X', 'H' }; +static const char section_names_ascent[4] = { 'A', 'S', 'C', 'E' }; +static const char section_names_descent[4] = { 'D', 'E', 'S', 'C' }; +static const char section_names_char_index[4] = { 'C', 'H', 'I', 'X' }; +static const char section_names_data[4] = { 'D', 'A', 'T', 'A' }; + +/* Replace unknown glyphs with a rounded question mark. */ +static grub_uint8_t unknown_glyph_bitmap[] = +{ + /* 76543210 */ + 0x7C, /* ooooo */ + 0x82, /* o o */ + 0xBA, /* o ooo o */ + 0xAA, /* o o o o */ + 0xAA, /* o o o o */ + 0x8A, /* o o o */ + 0x9A, /* o oo o */ + 0x92, /* o o o */ + 0x92, /* o o o */ + 0x92, /* o o o */ + 0x92, /* o o o */ + 0x82, /* o o */ + 0x92, /* o o o */ + 0x82, /* o o */ + 0x7C, /* ooooo */ + 0x00 /* */ +}; + +static struct grub_font_glyph *unknown_glyph; + +void +grub_font_loader_init (void) +{ + unknown_glyph = grub_malloc(sizeof(struct grub_font_glyph) + + sizeof(unknown_glyph_bitmap)); + if (! unknown_glyph) + return; + + unknown_glyph->width = 8; + unknown_glyph->height = 16; + unknown_glyph->offset_x = 0; + unknown_glyph->offset_y = 0; + unknown_glyph->device_width = 8; + grub_memcpy(unknown_glyph->bitmap, + unknown_glyph_bitmap, sizeof(unknown_glyph_bitmap)); +} + +/* + * Open the next section in the file. + * + * On success, the section name is stored in section->name and the length in + * section->length, and 0 is returned. On failure, 1 is returned and + * grub_errno is set approriately with an error message. + * + * If 1 is returned due to being at the end of the file, then section->eof is + * set to 1; otherwise, section->eof is set to 0. + */ +static int +open_section (grub_file_t file, struct font_file_section *section) +{ + grub_ssize_t retval; + grub_uint32_t raw_length; + + section->file = file; + section->eof = 0; + + /* Read the FOURCC section name. */ + retval = grub_file_read (file, section->name, 4); + if (retval >= 0 && retval < 4) + { + section->eof = 1; + return 1; /* EOF encountered. */ + } + else if (retval < 0) + { + grub_error (GRUB_ERR_BAD_FONT, + "Font format error: can't read section name"); + return 1; /* Read error. */ + } + + /* Read the big-endian 32-bit section length. */ + retval = grub_file_read (file, (char *) &raw_length, 4); + if (retval >= 0 && retval < 4) + { + section->eof = 1; + return 1; /* EOF encountered. */ + } + else if (retval < 0) + { + grub_error (GRUB_ERR_BAD_FONT, + "Font format error: can't read section length"); + return 1; /* Read error. */ + } + + /* Convert byte-order and store in *length. */ + section->length = grub_be_to_cpu32 (raw_length); + + return 0; +} + +/* Size in bytes of each character index (CHIX section) + * entry in the font file. */ +#define FONT_CHAR_INDEX_ENTRY_SIZE (4 + 1 + 4) + +/* + * Load the character index (CHIX) section contents from the font file. This + * presumes that the position of FILE is positioned immediately after the + * section length for the CHIX section (i.e., at the start of the section + * contents). Returns 0 upon success, nonzero for failure (in which case + * grub_errno is set appropriately). + */ +static int +load_font_index (grub_file_t file, grub_uint32_t sect_length, struct + grub_font *font) +{ + unsigned i; + +#if FONT_DEBUG >= 2 + grub_printf("load_font_index(sect_length=%d)\n", sect_length); +#endif + + /* Sanity check: ensure section length is divisible by the entry size. */ + if (sect_length % FONT_CHAR_INDEX_ENTRY_SIZE != 0) + { + grub_error (GRUB_ERR_BAD_FONT, + "Font file format error: character index length %d " + "is not a multiple of the entry size %d", + sect_length, FONT_CHAR_INDEX_ENTRY_SIZE); + return 1; /* Invalid index section length. */ + } + + /* Calculate the number of characters. */ + font->num_chars = sect_length / FONT_CHAR_INDEX_ENTRY_SIZE; + + /* Allocate the character index array. */ + font->char_index = grub_malloc (font->num_chars + * sizeof (struct char_index_entry)); + if (!font->char_index) + return 1; /* Error allocating memory. */ + +#if FONT_DEBUG >= 2 + grub_printf("num_chars=%d)\n", font->num_chars); +#endif + + /* Load the character index data from the file. */ + for (i = 0; i < font->num_chars; i++) + { + struct char_index_entry *entry = &font->char_index[i]; + + /* Read code point value; convert to native byte order. */ + if (grub_file_read (file, (char *) &entry->code, 4) != 4) + return 1; + entry->code = grub_be_to_cpu32 (entry->code); + + /* Read storage flags byte. */ + if (grub_file_read (file, (char *) &entry->storage_flags, 1) != 1) + return 1; + + /* Read glyph data offset; convert to native byte order. */ + if (grub_file_read (file, (char *) &entry->offset, 4) != 4) + return 1; + entry->offset = grub_be_to_cpu32 (entry->offset); + + /* No glyph loaded. Will be loaded on demand and cached thereafter. */ + entry->glyph = 0; + +#if FONT_DEBUG >= 5 + if (i < 10) /* Print the 1st 10 characters. */ + grub_printf("c=%d o=%d\n", entry->code, entry->offset); +#endif + } + + return 0; /* Index loaded OK. */ +} + +/* + * Read the contents of the specified section as a string, which is + * allocated on the heap. Returns 0 if there is an error. + */ +static char * +read_section_as_string (struct font_file_section *section) +{ + char *str; + grub_ssize_t ret; + + str = grub_malloc (section->length + 1); + if (!str) + return 0; + + ret = grub_file_read (section->file, str, section->length); + if (ret < 0 || ret != (grub_ssize_t) section->length) + { + grub_free (str); + return 0; + } + + str[section->length] = '\0'; + return str; +} + +/* + * Read the contents of the current section as a 16-bit integer value, + * which is stored into *VALUE. Returns 0 upon success, nonzero upon failure. + */ +static int +read_section_as_short (struct font_file_section *section, grub_int16_t *value) +{ + grub_uint16_t raw_value; + + if (section->length != 2) + { + grub_error (GRUB_ERR_BAD_FONT, + "Font file format error: section %c%c%c%c length " + "is %d but should be 2", + section->name[0], section->name[1], + section->name[2], section->name[3], + section->length); + return 1; /* An error occurred. */ + } + if (grub_file_read (section->file, (char *) &raw_value, 2) != 2) + return 1; /* An error occurred. */ + + *value = grub_be_to_cpu16 (raw_value); + return 0; /* Successfully read the value. */ +} + +/* + * Load a font and add it to the beginning of the global font list. + * Returns: 0 upon success; nonzero upon failure. + */ +int +grub_font_load (const char *filename) +{ + grub_file_t file = 0; + struct font_file_section section; + char magic[4]; + grub_font_t font = 0; + +#if FONT_DEBUG >= 1 + grub_printf("add_font(%s)\n", filename); +#endif + + file = grub_file_open (filename); + if (!file) + goto fail; + +#if FONT_DEBUG >= 3 + grub_printf("file opened\n"); +#endif + + /* Read the FILE section. It indicates the file format. */ + if (open_section (file, §ion) != 0) + goto fail; + +#if FONT_DEBUG >= 3 + grub_printf("opened FILE section\n"); +#endif + if (grub_memcmp (section.name, section_names_file, 4) != 0) + { + grub_error (GRUB_ERR_BAD_FONT, + "Font file format error: 1st section must be FILE"); + goto fail; + } + +#if FONT_DEBUG >= 3 + grub_printf("section name ok\n"); +#endif + if (section.length != 4) + { + grub_error (GRUB_ERR_BAD_FONT, + "Font file format error (file type ID length is %d " + "but should be 4)", section.length); + goto fail; + } + +#if FONT_DEBUG >= 3 + grub_printf("section length ok\n"); +#endif + /* Check the file format type code. */ + if (grub_file_read (file, magic, 4) != 4) + goto fail; + +#if FONT_DEBUG >= 3 + grub_printf("read magic ok\n"); +#endif + + if (grub_memcmp (magic, pff2_magic, 4) != 0) + { + grub_error (GRUB_ERR_BAD_FONT, "Invalid font magic %x %x %x %x", + magic[0], magic[1], magic[2], magic[3]); + goto fail; + } + +#if FONT_DEBUG >= 3 + grub_printf("compare magic ok\n"); +#endif + + /* Allocate the font object. */ + font = (grub_font_t) grub_malloc (sizeof (struct grub_font)); + if (!font) + goto fail; + + font->file = file; + font->name = 0; + font->ascent = 0; + font->descent = 0; + font->max_char_width = 0; + font->max_char_height = 0; + font->num_chars = 0; + font->char_index = 0; + +#if FONT_DEBUG >= 3 + grub_printf("allocate font ok; loading font info\n"); +#endif + + /* Load the font information. */ + while (1) + { + if (open_section (file, §ion) != 0) + { + if (section.eof) + break; /* Done reading the font file. */ + else + goto fail; + } + +#if FONT_DEBUG >= 2 + grub_printf("opened section %c%c%c%c ok\n", + section.name[0], section.name[1], + section.name[2], section.name[3]); +#endif + + if (grub_memcmp (section.name, section_names_font_name, 4) == 0) + { + font->name = read_section_as_string (§ion); + if (!font->name) + goto fail; + } + else if (grub_memcmp (section.name, section_names_max_char_width, 4) == 0) + { + if (read_section_as_short (§ion, &font->max_char_width) != 0) + goto fail; + } + else if (grub_memcmp (section.name, section_names_max_char_height, 4) == 0) + { + if (read_section_as_short (§ion, &font->max_char_height) != 0) + goto fail; + } + else if (grub_memcmp (section.name, section_names_ascent, 4) == 0) + { + if (read_section_as_short (§ion, &font->ascent) != 0) + goto fail; + } + else if (grub_memcmp (section.name, section_names_descent, 4) == 0) + { + if (read_section_as_short (§ion, &font->descent) != 0) + goto fail; + } + else if (grub_memcmp (section.name, section_names_char_index, 4) == 0) + { + /* Load the font index. */ + if (load_font_index (file, section.length, font) != 0) + goto fail; + } + else if (grub_memcmp (section.name, section_names_data, 4) == 0) + { + /* When the DATA section marker is reached, we stop reading. */ + break; + } + else + { + /* Unhandled section type, simply skip past it. */ +#if FONT_DEBUG >= 3 + grub_printf("Unhandled section type, skipping.\n"); +#endif + if ((int) grub_file_seek (file, + grub_file_tell (file) + section.length) + == -1) + goto fail; + } + } + + if (!font->name) + { + grub_printf ("Note: Font has no name.\n"); + font->name = grub_strdup ("Unknown"); + } + +#if FONT_DEBUG >= 1 + grub_printf ("Loaded font `%s'.\n" + "Ascent=%d Descent=%d MaxW=%d MaxH=%d Number of characters=%d.\n", + font->name, + font->ascent, font->descent, + font->max_char_width, font->max_char_height, + font->num_chars); +#endif + + if (font->max_char_width == 0 + || font->max_char_height == 0 + || font->num_chars == 0 + || font->char_index == 0 + || font->ascent == 0 + || font->descent == 0) + { + grub_error (GRUB_ERR_BAD_FONT, + "Invalid font file: missing some required data."); + goto fail; + } + + /* Add the font to the global font registry. */ + if (register_font (font) != 0) + goto fail; + + return 0; /* Font loaded ok. */ + +fail: + free_font (font); + return 1; /* Failed to load font. */ +} + +/* + * Read a 16-bit big-endian integer from FILE, convert it to native byte + * order, and store it in *VALUE. + * Returns 0 on success, 1 on failure. + */ +static int +read_be_uint16 (grub_file_t file, grub_uint16_t * value) +{ + if (grub_file_read (file, (char *) value, 2) != 2) + return 1; + *value = grub_be_to_cpu16 (*value); + return 0; +} + +static int +read_be_int16 (grub_file_t file, grub_int16_t * value) +{ + /* For the signed integer version, use the same code as for unsigned. */ + return read_be_uint16 (file, (grub_uint16_t *) value); +} + +/* + * Return a pointer to the character index entry for the glyph corresponding to + * the codepoint CODE in the font FONT. If not found, return zero. + */ +static struct char_index_entry * +find_glyph (const grub_font_t font, grub_uint32_t code) +{ + grub_uint32_t i; + grub_uint32_t len = font->num_chars; + struct char_index_entry *table = font->char_index; + + /* Do a linear search. */ + for (i = 0; i < len; i++) + { + if (table[i].code == code) + return &table[i]; + } + + return 0; /* No entry found for code point CODE. */ +} + +static struct grub_font_glyph * +grub_font_get_glyph_internal (grub_font_t font, grub_uint32_t code) +{ + struct char_index_entry *index_entry; + + index_entry = find_glyph (font, code); + if (index_entry) + { + struct grub_font_glyph *glyph = 0; + grub_uint16_t width; + grub_uint16_t height; + grub_int16_t xoff; + grub_int16_t yoff; + grub_int16_t dwidth; + int len; + + if (index_entry->glyph) + return index_entry->glyph; /* Return cached glyph. */ + + /* Make sure we can find glyphs for error messages. Push active + error message to error stack and reset error message. */ + grub_error_push (); + + grub_file_seek (font->file, index_entry->offset); + + /* Read the glyph width, height, and baseline. */ + if (read_be_uint16(font->file, &width) != 0 + || read_be_uint16(font->file, &height) != 0 + || read_be_int16(font->file, &xoff) != 0 + || read_be_int16(font->file, &yoff) != 0 + || read_be_int16(font->file, &dwidth) != 0) + { + //remove_font (font); + return 0; + } + + len = (width * height + 7) / 8; + glyph = grub_malloc (sizeof (struct grub_font_glyph) + len); + if (! glyph) + { + //remove_font (font); + return 0; + } + + glyph->font = font; + glyph->width = width; + glyph->height = height; + glyph->offset_x = xoff; + glyph->offset_y = yoff; + glyph->device_width = dwidth; + + /* Don't try to read empty bitmaps (e.g., space characters). */ + if (len != 0) + { + if (grub_file_read (font->file, (char *) glyph->bitmap, len) != len) + { + //remove_font (font); + return 0; + } + } + + /* Restore old error message. */ + grub_error_pop (); + + /* Cache the glyph. */ + index_entry->glyph = glyph; + + return glyph; /* Glyph loaded ok. */ + } + + return 0; +} + +/* Get the glyph for FONT corresponding to the Unicode code point CODE. + * Returns a pointer to an glyph indicating there is no glyph available + * if CODE does not exist in the font. The glyphs are cached once loaded. */ +struct grub_font_glyph * +grub_font_get_glyph (grub_font_t font, grub_uint32_t code) +{ + struct grub_font_glyph *glyph; + glyph = grub_font_get_glyph_internal (font, code); + if (glyph == 0) + glyph = unknown_glyph; + return glyph; +} + +/* Get a glyph corresponding to the codepoint CODE. If no glyph is available + * for CODE in the available fonts, then a glyph representing an unknown + * character is returned. This function never returns NULL. + * The returned glyph is owned by the font manager and should not be freed + * by the caller. The glyphs are cached. */ +struct grub_font_glyph * +grub_font_get_glyph_any (grub_uint32_t code) +{ + struct font_node *node; + /* Keep track of next node, in case there's an I/O error in + * grub_font_get_glyph() and the font is removed from the list. */ + struct font_node *next; + + for (node = grub_font_list; node; node = next) + { + grub_font_t font; + struct grub_font_glyph *glyph; + + font = node->value; + next = node->next; + + glyph = grub_font_get_glyph_internal (font, code); + if (glyph) + return glyph; + } + + /* Uggh... Glyph was not found in any font. */ + return unknown_glyph; /* Failed to load glyph. */ +} + +/* + * Free the memory used by a font. + * This should not be called if the font has been made available to + * users (once it is added to the global font list), since there would + * be the possibility of a dangling pointer. + */ +static void +free_font (grub_font_t font) +{ + if (font) + { + if (font->file) + grub_file_close (font->file); + if (font->name) + grub_free (font->name); + if (font->char_index) + grub_free (font->char_index); + grub_free (font); + } +} + +/* + * Add FONT to the global font registry. + * Returns 0 upon success, nonzero on failure (the font was not registered). + */ +static int +register_font (grub_font_t font) +{ + struct font_node *node = 0; + + node = grub_malloc (sizeof (struct font_node)); + if (!node) + return 1; /* Error. */ + + node->value = font; + node->next = grub_font_list; + grub_font_list = node; + + return 0; /* Success. */ +} + +/* + * Remove the font from the global font list. We don't actually free the + * font's memory since users could be holding references to the font. + */ +static void +remove_font (grub_font_t font) +{ + struct font_node **nextp, *cur; + + for (nextp = &grub_font_list, cur = *nextp; + cur; + nextp = &cur->next, cur = cur->next) + { + if (cur->value == font) + { + *nextp = cur->next; + + /* Free the node, but not the font itself. */ + grub_free (cur); + + return; + } + } +} + === removed file 'font/manager.c' --- font/manager.c 2008-02-10 11:04:38 +0000 +++ font/manager.c 1970-01-01 00:00:00 +0000 @@ -1,282 +0,0 @@ -/* - * GRUB -- GRand Unified Bootloader - * Copyright (C) 2003,2005,2006,2007 Free Software Foundation, Inc. - * - * GRUB is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * GRUB is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with GRUB. If not, see . - */ - -#include -#include -#include -#include -#include -#include -#include - -struct entry -{ - grub_uint32_t code; - grub_uint32_t offset; -}; - -struct font -{ - struct font *next; - grub_file_t file; - grub_uint32_t num; - struct entry table[0]; -}; - -static struct font *font_list; - -/* Fill unknown glyph's with rounded question mark. */ -static grub_uint8_t unknown_glyph[16] = -{ /* 76543210 */ - 0x7C, /* ooooo */ - 0x82, /* o o */ - 0xBA, /* o ooo o */ - 0xAA, /* o o o o */ - 0xAA, /* o o o o */ - 0x8A, /* o o o */ - 0x9A, /* o oo o */ - 0x92, /* o o o */ - 0x92, /* o o o */ - 0x92, /* o o o */ - 0x92, /* o o o */ - 0x82, /* o o */ - 0x92, /* o o o */ - 0x82, /* o o */ - 0x7C, /* ooooo */ - 0x00 /* */ -}; - -static int -add_font (const char *filename) -{ - grub_file_t file = 0; - char magic[4]; - grub_uint32_t num, i; - struct font *font = 0; - - file = grub_file_open (filename); - if (! file) - goto fail; - - if (grub_file_read (file, magic, 4) != 4) - goto fail; - - if (grub_memcmp (magic, GRUB_FONT_MAGIC, 4) != 0) - { - grub_error (GRUB_ERR_BAD_FONT, "invalid font magic"); - goto fail; - } - - if (grub_file_read (file, (char *) &num, 4) != 4) - goto fail; - - num = grub_le_to_cpu32 (num); - font = (struct font *) grub_malloc (sizeof (struct font) - + sizeof (struct entry) * num); - if (! font) - goto fail; - - font->file = file; - font->num = num; - - for (i = 0; i < num; i++) - { - grub_uint32_t code, offset; - - if (grub_file_read (file, (char *) &code, 4) != 4) - goto fail; - - if (grub_file_read (file, (char *) &offset, 4) != 4) - goto fail; - - font->table[i].code = grub_le_to_cpu32 (code); - font->table[i].offset = grub_le_to_cpu32 (offset); - } - - font->next = font_list; - font_list = font; - - return 1; - - fail: - if (font) - grub_free (font); - - if (file) - grub_file_close (file); - - return 0; -} - -static void -remove_font (struct font *font) -{ - struct font **p, *q; - - for (p = &font_list, q = *p; q; p = &(q->next), q = q->next) - if (q == font) - { - *p = q->next; - - grub_file_close (font->file); - grub_free (font); - - break; - } -} - -/* Return the offset of the glyph corresponding to the codepoint CODE - in the font FONT. If no found, return zero. */ -static grub_uint32_t -find_glyph (const struct font *font, grub_uint32_t code) -{ - grub_uint32_t start = 0; - grub_uint32_t end = font->num - 1; - const struct entry *table = font->table; - - /* This shouldn't happen. */ - if (font->num == 0) - return 0; - - /* Do a binary search. */ - while (start <= end) - { - grub_uint32_t i = (start + end) / 2; - - if (table[i].code < code) - start = i + 1; - else if (table[i].code > code) - end = i - 1; - else - return table[i].offset; - } - - return 0; -} - -/* Set the glyph to something stupid. */ -static void -fill_with_default_glyph (grub_font_glyph_t glyph) -{ - unsigned i; - - /* Use pre-defined pattern to fill unknown glyphs. */ - for (i = 0; i < 16; i++) - glyph->bitmap[i] = unknown_glyph[i]; - - glyph->char_width = 1; - glyph->width = glyph->char_width * 8; - glyph->height = 16; - glyph->baseline = (16 * 3) / 4; -} - -/* Get a glyph corresponding to the codepoint CODE. Always fill glyph - information with something, even if no glyph is found. */ -int -grub_font_get_glyph (grub_uint32_t code, - grub_font_glyph_t glyph) -{ - struct font *font; - grub_uint8_t bitmap[32]; - - /* FIXME: It is necessary to cache glyphs! */ - - restart: - for (font = font_list; font; font = font->next) - { - grub_uint32_t offset; - - offset = find_glyph (font, code); - if (offset) - { - grub_uint32_t w; - int len; - - /* Make sure we can find glyphs for error messages. Push active - error message to error stack and reset error message. */ - grub_error_push (); - - grub_file_seek (font->file, offset); - if ((len = grub_file_read (font->file, (char *) &w, sizeof (w))) - != sizeof (w)) - { - remove_font (font); - goto restart; - } - - w = grub_le_to_cpu32 (w); - if (w != 1 && w != 2) - { - /* grub_error (GRUB_ERR_BAD_FONT, "invalid width"); */ - remove_font (font); - goto restart; - } - - if (grub_file_read (font->file, (char *) bitmap, w * 16) - != (grub_ssize_t) w * 16) - { - remove_font (font); - goto restart; - } - - /* Fill glyph with information. */ - grub_memcpy (glyph->bitmap, bitmap, w * 16); - - glyph->char_width = w; - glyph->width = glyph->char_width * 8; - glyph->height = 16; - glyph->baseline = (16 * 3) / 4; - - /* Restore old error message. */ - grub_error_pop (); - - return 1; - } - } - - /* Uggh... No font was found. */ - fill_with_default_glyph (glyph); - return 0; -} - -static grub_err_t -font_command (struct grub_arg_list *state __attribute__ ((unused)), - int argc __attribute__ ((unused)), - char **args __attribute__ ((unused))) -{ - if (argc == 0) - return grub_error (GRUB_ERR_BAD_ARGUMENT, "no font specified"); - - while (argc--) - if (! add_font (*args++)) - return 1; - - return 0; -} - -GRUB_MOD_INIT(font_manager) -{ - grub_register_command ("font", font_command, GRUB_COMMAND_FLAG_BOTH, - "font FILE...", - "Specify one or more font files to display.", 0); -} - -GRUB_MOD_FINI(font_manager) -{ - grub_unregister_command ("font"); -} === modified file 'genmk.rb' --- genmk.rb 2008-07-02 18:03:23 +0000 +++ genmk.rb 2008-07-04 21:40:36 +0000 @@ -125,7 +125,7 @@ $(TARGET_CC) $(TARGET_CPPFLAGS) $(TARGET_CFLAGS) $(#{prefix}_CFLAGS) -c -o $@ $< #{mod_src}: moddep.lst genmodsrc.sh - sh $(srcdir)/genmodsrc.sh '#{mod_name}' $< > $@ || (rm -f $@; exit 1) + $(GENMODSRC) '#{mod_name}' $< > $@ || (rm -f $@; exit 1) ifneq ($(#{prefix}_EXPORTS),no) #{defsym}: #{pre_obj} @@ -157,18 +157,21 @@ PARTMAPFILES += #{partmap} #{command}: #{src} $(#{src}_DEPENDENCIES) gencmdlist.sh - set -e; \ - $(TARGET_CC) -I#{dir} -I$(srcdir)/#{dir} $(TARGET_CPPFLAGS) $(TARGET_#{flag}) $(#{prefix}_#{flag}) -E $< \ + $(INFO_GENCMDLIST) + $(V_PREFIX)set -e; \ + $(_TARGET_CC) -I#{dir} -I$(srcdir)/#{dir} $(TARGET_CPPFLAGS) $(TARGET_#{flag}) $(#{prefix}_#{flag}) -E $< \ | sh $(srcdir)/gencmdlist.sh #{symbolic_name} > $@ || (rm -f $@; exit 1) #{fs}: #{src} $(#{src}_DEPENDENCIES) genfslist.sh - set -e; \ - $(TARGET_CC) -I#{dir} -I$(srcdir)/#{dir} $(TARGET_CPPFLAGS) $(TARGET_#{flag}) $(#{prefix}_#{flag}) -E $< \ + $(INFO_GENFSLIST) + $(V_PREFIX)set -e; \ + $(_TARGET_CC) -I#{dir} -I$(srcdir)/#{dir} $(TARGET_CPPFLAGS) $(TARGET_#{flag}) $(#{prefix}_#{flag}) -E $< \ | sh $(srcdir)/genfslist.sh #{symbolic_name} > $@ || (rm -f $@; exit 1) #{partmap}: #{src} $(#{src}_DEPENDENCIES) genpartmaplist.sh - set -e; \ - $(TARGET_CC) -I#{dir} -I$(srcdir)/#{dir} $(TARGET_CPPFLAGS) $(TARGET_#{flag}) $(#{prefix}_#{flag}) -E $< \ + $(INFO_GENPARTMAPLIST) + $(V_PREFIX)set -e; \ + $(_TARGET_CC) -I#{dir} -I$(srcdir)/#{dir} $(TARGET_CPPFLAGS) $(TARGET_#{flag}) $(#{prefix}_#{flag}) -E $< \ | sh $(srcdir)/genpartmaplist.sh #{symbolic_name} > $@ || (rm -f $@; exit 1) === added directory 'gfxmenu' === added file 'gfxmenu/gfxmenu.c' --- gfxmenu/gfxmenu.c 1970-01-01 00:00:00 +0000 +++ gfxmenu/gfxmenu.c 2008-07-19 19:56:06 +0000 @@ -0,0 +1,506 @@ +/* gfxmenu.c - Graphical menu interface. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2008 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct menu_item +{ + const char *name; + struct grub_video_bitmap *icon; + grub_menu_entry_t entry; +} +menu_item_t; + +static const int icon_width = 32; +static const int icon_height = 32; +static grub_video_rect_t screen; +static grub_font_t title_font; +static grub_font_t item_font; +static grub_font_t status_font; +static grub_video_color_t title_color; +static grub_video_color_t item_color; +static grub_video_color_t status_color; +static grub_video_color_t status_bg_color; +static struct grub_video_bitmap *background_image; +static const char title_text[] = "GRUB 2 Boot Menu"; +static int num_items; +static menu_item_t *items; +static int selected_item_index; +static grub_gfxmenu_box_t menu_box; +static grub_gfxmenu_box_t selected_item_box; + +static grub_err_t +menu_init (grub_menu_t menu) +{ + num_items = menu->size; + if (num_items < 1) + return grub_error (GRUB_ERR_MENU, "Empty menu"); + + items = grub_malloc (num_items * sizeof (*items)); + if (!items) + return grub_errno; + + int i; + grub_menu_entry_t cur; + for (i = 0, cur = menu->entry_list; i < num_items; i++, cur = cur->next) + { + items[i].name = cur->title; + items[i].icon = 0; /* TODO match w/ patterns to choose an icon */ + items[i].entry = cur; + } + + /* + items[0].name = "Ubuntu 8.10"; + grub_video_bitmap_load (&img, "/boot/images/icon-ubuntu.tga"); + if (grub_errno == GRUB_ERR_NONE) + { + grub_video_bitmap_create_scaled (&items[0].icon, icon_width, + icon_height, img, + GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST); + if (grub_errno != GRUB_ERR_NONE) + return; + grub_video_bitmap_destroy (img); + } + */ + + return GRUB_ERR_NONE; +} + +static void +menu_destroy (void) +{ + if (items) + { + int i; + for (i = 0; i < num_items; i++) + { + if (items[i].icon) + { + grub_video_bitmap_destroy (items[i].icon); + items[i].icon = 0; + } + } + grub_free (items); + items = 0; + } +} + +static grub_err_t +style_init (void) +{ + if (!(title_font = grub_font_get ("Helvetica Bold 24")) + || !(item_font = grub_font_get ("Helvetica Bold 12")) + || !(status_font = grub_font_get ("Helvetica 10"))) + return grub_errno; + + title_color = grub_video_map_rgb (0, 0, 0); + item_color = grub_video_map_rgb (0, 0, 0); + status_color = grub_video_map_rgb (255, 255, 255); + status_bg_color = grub_video_map_rgba (0, 0, 0, 112); + + struct grub_video_bitmap *img; + grub_video_bitmap_load (&img, "/boot/images/bg-protomenu1.tga"); + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; + + grub_video_bitmap_create_scaled (&background_image, screen.width, + screen.height, img, + GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST); + if (grub_errno != GRUB_ERR_NONE) + return grub_errno; + grub_video_bitmap_destroy (img); + + menu_box = grub_gfxmenu_create_box ("/boot/images/menubox_", ".tga"); + if (menu_box == 0) + return grub_error (GRUB_ERR_MENU, "Unable to create menu box"); + + selected_item_box = grub_gfxmenu_create_box ("/boot/images/select_blue_", ".tga"); + if (selected_item_box == 0) + return grub_error (GRUB_ERR_MENU, "Unable to create selection box"); + + return GRUB_ERR_NONE; +} + +static void +style_destroy (void) +{ + if (menu_box) + { + menu_box->destroy(menu_box); + menu_box = 0; + } + if (selected_item_box) + { + selected_item_box->destroy(selected_item_box); + selected_item_box = 0; + } +} + +static void +draw_background (void) +{ + grub_video_blit_bitmap (background_image, GRUB_VIDEO_BLIT_REPLACE, + 0, 0, 0, 0, + grub_video_bitmap_get_width (background_image), + grub_video_bitmap_get_height (background_image)); +} + +static void +draw_menu (void) +{ + int boxpad = 14; + int icon_text_space = 4; + int item_vspace = 16; + + int ascent = grub_font_get_ascent (item_font); + int descent = grub_font_get_descent (item_font); + int item_height = 34; + + grub_video_rect_t r; + r.width = screen.width * 4 / 5; + /* Set the menu box height to fit the items. */ + r.height = (item_height * num_items + + item_vspace * (num_items - 1) + + 2 * boxpad); + r.x = (screen.width - r.width) / 2; + r.y = (screen.height - r.height) / 2; + menu_box->set_content_size (menu_box, r.width, r.height); + + int menu_box_left_pad = menu_box->get_left_pad (menu_box); + int menu_box_top_pad = menu_box->get_top_pad (menu_box); + menu_box->draw (menu_box, r.x - menu_box_left_pad, r.y - menu_box_top_pad); + + int item_top = r.y + boxpad; + int item_left = r.x + boxpad; + int i; + + for (i = 0; i < num_items; i++) + { + if (i == selected_item_index) + { + selected_item_box->set_content_size (selected_item_box, + r.width - 2 * boxpad, + item_height); + int leftpad = selected_item_box->get_left_pad (selected_item_box); + int toppad = selected_item_box->get_top_pad (selected_item_box); + selected_item_box->draw (selected_item_box, + item_left - leftpad, + item_top - toppad); + } + + if (items[i].icon) + grub_video_blit_bitmap (items[i].icon, GRUB_VIDEO_BLIT_BLEND, + item_left, + item_top + (item_height - icon_height) / 2, + 0, 0, icon_width, icon_height); + + grub_video_draw_string (items[i].name, item_font, item_color, + item_left + icon_width + icon_text_space, + (item_top + (item_height - (ascent + descent)) + / 2 + ascent)); + + item_top += item_height + item_vspace; + } +} + +static void +draw_title (void) +{ + /* Center the title. */ + int title_width = grub_font_get_string_width (title_font, title_text); + int x = (screen.width - title_width) / 2; + int y = 40 + grub_font_get_ascent (title_font); + grub_video_draw_string (title_text, title_font, title_color, x, y); +} + +static void +draw_status (void) +{ + int descent = grub_font_get_descent (status_font); + int ascent = grub_font_get_ascent (status_font); + int vpad = 5; + int textheight = descent + ascent + 1; + int h = 2 * vpad + 2 * textheight; + + grub_video_fill_rect (status_bg_color, 0, screen.height - h, + screen.width, screen.height - 1); + + int texty = screen.height - h + vpad + ascent; + grub_video_draw_string ("Select an item with the arrow keys and " + "press Enter to boot.", + status_font, status_color, 30, texty); + texty += textheight; + grub_video_draw_string ("Press: 'c' for command line; 't' to switch to " + "non-graphical menu.", + status_font, status_color, 30, texty); +} + +static void +draw_menu_screen (void) +{ + grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY); + draw_background (); + draw_menu (); + draw_title (); + draw_status (); +} + +static grub_err_t +set_graphics_mode (void) +{ + const char *doublebuf_str = grub_env_get ("doublebuffering"); + int doublebuf_flags = + (doublebuf_str && doublebuf_str[0] == 'n') + ? 0 + : GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED; + + const char *modestr = grub_env_get ("gfxmode"); + if (grub_video_setup_preferred_mode (modestr, doublebuf_flags, 640, 480) + != GRUB_ERR_NONE) + return grub_errno; + + return GRUB_ERR_NONE; +} + +static grub_err_t +set_text_mode (void) +{ + return grub_video_restore (); +} + +static int term_target_width; +static int term_target_height; +static struct grub_video_render_target *term_target; +static grub_gfxmenu_box_t term_box; +static grub_term_t term_original; + +static void +repaint_terminal (int x __attribute ((unused)), + int y __attribute ((unused)), + int width __attribute ((unused)), + int height __attribute ((unused))) +{ + draw_menu_screen (); + grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY); + + int termx = screen.x + screen.width / 10; + int termy = screen.y + screen.height / 10; + + if (term_box) + term_box->draw (term_box, + termx - term_box->get_left_pad (term_box), + termy - term_box->get_top_pad (term_box)); + + grub_video_blit_render_target (term_target, GRUB_VIDEO_BLIT_REPLACE, + termx, termy, + 0, 0, term_target_width, term_target_height); + grub_video_swap_buffers (); +} + +static void init_terminal (void) +{ + term_original = grub_term_get_current (); + + term_target_width = screen.width * 8 / 10; + term_target_height = screen.height * 8 / 10; + + grub_video_create_render_target (&term_target, + term_target_width, + term_target_height, + GRUB_VIDEO_MODE_TYPE_RGB + | GRUB_VIDEO_MODE_TYPE_ALPHA); + if (grub_errno != GRUB_ERR_NONE) + return; + + grub_gfxterm_init_window (term_target, 0, 0, + term_target_width, term_target_height, + "Fixed 10", 16); + if (grub_errno != GRUB_ERR_NONE) + return; + + term_box = grub_gfxmenu_create_box ("/boot/images/select_blue_", ".tga"); + term_box->set_content_size (term_box, term_target_width, term_target_height); + + grub_gfxterm_set_repaint_callback (repaint_terminal); + grub_term_set_current (grub_gfxterm_get_term ()); +} + +static void destroy_terminal (void) +{ + term_box->destroy (term_box); + term_box = 0; + grub_gfxterm_destroy_window (); + grub_gfxterm_set_repaint_callback (0); + grub_video_delete_render_target (term_target); + grub_term_set_current (term_original); +} + +static void +run_terminal (void) +{ + grub_cmdline_run (1); +} + +static grub_err_t +show_menu (grub_menu_t menu, int nested __attribute__ ((unused))) +{ + set_graphics_mode (); + grub_error_push (); + grub_video_get_viewport ((unsigned *) &screen.x, + (unsigned *) &screen.y, + (unsigned *) &screen.width, + (unsigned *) &screen.height); + + /* Clear the screen; there may be garbage left over in video memory, and + loading the menu style (particularly the background) can take a while. */ + grub_video_fill_rect (grub_video_map_rgb (100, 100, 100), + screen.x, screen.y, screen.width, screen.height); + grub_video_swap_buffers (); + + init_terminal (); + + if (menu_init (menu) != GRUB_ERR_NONE) + goto menufail; + + if (style_init () != GRUB_ERR_NONE) + goto stylefail; + + selected_item_index = 0; + + int done = 0; + while (!done && !grub_menu_viewer_should_return ()) + { + draw_menu_screen (); + grub_video_swap_buffers (); + + int c = GRUB_TERM_ASCII_CHAR (grub_getkey ()); + if (c == 'j' || c == 14) + { + selected_item_index++; + if (selected_item_index >= num_items) + selected_item_index = 0; + } + else if (c == 'k' || c == 16) + { + selected_item_index--; + if (selected_item_index < 0) + selected_item_index = num_items - 1; + } + else if (c == '\r' || c == '\n' || c == 6) + { + if (selected_item_index >=0 && selected_item_index < num_items) + { + menu_item_t *item = &items[selected_item_index]; + if (item->entry != 0) + { + /* Currently we switch back to text mode by restoring + the original terminal before executing the menu entry. + It is hard to make it work when executing a menu entry + that switches video modes -- it using gfxterm in a + window, the repaint callback seems to crash GRUB. */ + /* TODO: Determine if this works when 'gfxterm' was set as + the current terminal before invoking the gfxmenu. */ + destroy_terminal (); + + grub_menu_execute_entry (item->entry); + if (grub_errno != GRUB_ERR_NONE) + grub_wait_after_message (); + + if (set_graphics_mode () != GRUB_ERR_NONE) + done = 1; + else + init_terminal (); + } + } + } + else if (c == 'c') + { + run_terminal (); + if (grub_errno != GRUB_ERR_NONE) + goto fail; + } + else if (c == 't') + { + /* The write hook for 'menuviewer' will cause + * grub_menu_viewer_should_return to return nonzero. */ + grub_env_set ("menuviewer", "terminal"); + } + else if (nested && c == GRUB_TERM_ESC) + { + done = 1; + } + } + +fail: + style_destroy (); +stylefail: + menu_destroy (); +menufail: + set_text_mode (); + destroy_terminal (); + + grub_print_error (); + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_cmd_gfxmenu (struct grub_arg_list *state __attribute__ ((unused)), + int argc __attribute__ ((unused)), + char **args __attribute__ ((unused))) +{ + grub_menu_t menu = grub_env_get_data_slot ("menu"); + if (!menu) + return grub_error (GRUB_ERR_MENU, "No menu context"); + + return show_menu (menu, 1); +} + +static struct grub_menu_viewer menu_viewer = +{ + .name = "gfxmenu", + .show_menu = show_menu +}; + +GRUB_MOD_INIT (gfxmenu) +{ + (void) mod; /* To stop warning. */ + grub_menu_viewer_register (&menu_viewer); + grub_register_command ("gfxmenu", + grub_cmd_gfxmenu, GRUB_COMMAND_FLAG_BOTH, + "gfxmenu", "Show graphical menu interface", 0); +} + +GRUB_MOD_FINI (gfxmenu) +{ + grub_unregister_command ("gfxmenu"); +} === added file 'gfxmenu/widget-box.c' --- gfxmenu/widget-box.c 1970-01-01 00:00:00 +0000 +++ gfxmenu/widget-box.c 2008-07-19 18:25:18 +0000 @@ -0,0 +1,244 @@ +/* widget_box.c - Pixmap-stylized box widget. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2008 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +enum box_pixmaps +{ + BOX_PIXMAP_NW, BOX_PIXMAP_NE, BOX_PIXMAP_SE, BOX_PIXMAP_SW, + BOX_PIXMAP_N, BOX_PIXMAP_E, BOX_PIXMAP_S, BOX_PIXMAP_W, + BOX_PIXMAP_CENTER +}; + +static const char *box_pixmap_names[] = { + /* Corners: */ + "nw", "ne", "se", "sw", + /* Sides: */ + "n", "e", "s", "w", + /* Center: */ + "c" +}; + +#define BOX_NUM_PIXMAPS (sizeof(box_pixmap_names)/sizeof(*box_pixmap_names)) + +static void +draw (grub_gfxmenu_box_t self, int x, int y) +{ + int height_n; + int height_s; + int height_e; + int height_w; + int width_n; + int width_s; + int width_e; + int width_w; + unsigned i; + + /* Don't try to draw if any pixmaps are null, except the center. */ + for (i = 0; i < BOX_NUM_PIXMAPS; i++) + { + if (i != BOX_PIXMAP_CENTER && self->scaled_pixmaps[i] == 0) + return; + } + + height_n = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_N]); + height_s = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_S]); + height_e = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_E]); + height_w = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_W]); + width_n = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_N]); + width_s = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_S]); + width_e = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_E]); + width_w = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_W]); + + /* Draw sides. */ + grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_N], + GRUB_VIDEO_BLIT_BLEND, + x + width_w, y, 0, 0, width_n, height_n); + grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_S], + GRUB_VIDEO_BLIT_BLEND, + x + width_w, y + height_n + height_w, + 0, 0, width_s, height_s); + grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_E], + GRUB_VIDEO_BLIT_BLEND, + x + width_w + width_n, y + height_n, + 0, 0, width_e, height_e); + grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_W], + GRUB_VIDEO_BLIT_BLEND, + x, y + height_n, 0, 0, width_w, height_w); + + /* Draw corners. */ + grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_NW], + GRUB_VIDEO_BLIT_BLEND, + x, y, 0, 0, width_w, height_n); + grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_NE], + GRUB_VIDEO_BLIT_BLEND, + x + width_w + width_n, y, + 0, 0, width_e, height_n); + grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_SE], + GRUB_VIDEO_BLIT_BLEND, + x + width_w + width_n, y + height_n + height_e, + 0, 0, width_e, height_s); + grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_SW], + GRUB_VIDEO_BLIT_BLEND, + x, y + height_n + height_w, + 0, 0, width_w, height_s); + + /* Draw center. */ + if (self->scaled_pixmaps[BOX_PIXMAP_CENTER]) + grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_CENTER], + GRUB_VIDEO_BLIT_BLEND, + x + width_w, y + height_n, + 0, 0, self->width, self->height); +} + +static void +scale_pixmap (grub_gfxmenu_box_t self, int i, int w, int h) +{ + struct grub_video_bitmap **scaled = &self->scaled_pixmaps[i]; + struct grub_video_bitmap *raw = self->raw_pixmaps[i]; + + if (raw == 0) + return; + + if (w == -1) + w = grub_video_bitmap_get_width (raw); + if (h == -1) + h = grub_video_bitmap_get_height (raw); + + if (*scaled == 0 + || ((int) grub_video_bitmap_get_width (*scaled) != w) + || ((int) grub_video_bitmap_get_height (*scaled) != h)) + { + if (*scaled) + { + grub_video_bitmap_destroy (*scaled); + *scaled = 0; + } + grub_video_bitmap_create_scaled (scaled, w, h, raw, + GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST); + } +} + +static void +set_content_size (grub_gfxmenu_box_t self, + int width, int height) +{ + self->width = width; + self->height = height; + /* Resize sides to match the width and height. */ + /* It is assumed that the corners width/height match the adjacent sides. */ + + /* Resize N and S sides to match width. */ + scale_pixmap(self, BOX_PIXMAP_N, width, -1); + scale_pixmap(self, BOX_PIXMAP_S, width, -1); + + /* Resize E and W sides to match height. */ + scale_pixmap(self, BOX_PIXMAP_E, -1, height); + scale_pixmap(self, BOX_PIXMAP_W, -1, height); + + /* Don't scale the corners--they are assumed to match the sides. */ + scale_pixmap(self, BOX_PIXMAP_NW, -1, -1); + scale_pixmap(self, BOX_PIXMAP_SW, -1, -1); + scale_pixmap(self, BOX_PIXMAP_NE, -1, -1); + scale_pixmap(self, BOX_PIXMAP_SE, -1, -1); + + /* Scale the center area. */ + scale_pixmap(self, BOX_PIXMAP_CENTER, width, height); +} + +static int +get_left_pad (grub_gfxmenu_box_t self) +{ + return grub_video_bitmap_get_width (self->raw_pixmaps[BOX_PIXMAP_W]); +} + +static int +get_top_pad (grub_gfxmenu_box_t self) +{ + return grub_video_bitmap_get_height (self->raw_pixmaps[BOX_PIXMAP_N]); +} + +static void +destroy (grub_gfxmenu_box_t self) +{ + unsigned i; + for (i = 0; i < BOX_NUM_PIXMAPS; i++) + { + if (self->raw_pixmaps[i]) + grub_video_bitmap_destroy(self->raw_pixmaps[i]); + self->raw_pixmaps[i] = 0; + + if (self->scaled_pixmaps[i]) + grub_video_bitmap_destroy(self->scaled_pixmaps[i]); + self->scaled_pixmaps[i] = 0; + } + grub_free (self->raw_pixmaps); + self->raw_pixmaps = 0; + grub_free (self->scaled_pixmaps); + self->scaled_pixmaps = 0; + + grub_free (self); /* Free self: must be the last step! */ +} + + +grub_gfxmenu_box_t +grub_gfxmenu_create_box (const char *pixmaps_prefix, + const char *pixmaps_suffix) +{ + char path[200]; + unsigned i; + grub_gfxmenu_box_t box; + + box = (grub_gfxmenu_box_t) grub_malloc (sizeof (*box)); + if (!box) + return 0; + box->width = 0; + box->height = 0; + box->raw_pixmaps = + (struct grub_video_bitmap **) + grub_malloc (BOX_NUM_PIXMAPS * sizeof (struct grub_video_bitmap *)); + box->scaled_pixmaps = + (struct grub_video_bitmap **) + grub_malloc (BOX_NUM_PIXMAPS * sizeof (struct grub_video_bitmap *)); + + for (i = 0; i < BOX_NUM_PIXMAPS; i++) + { + /* TODO XXX dynamically allocate PATH, making sure it's large enough */ + grub_sprintf (path, "%s%s%s", + pixmaps_prefix, box_pixmap_names[i], pixmaps_suffix); + grub_video_bitmap_load (&box->raw_pixmaps[i], path); + box->scaled_pixmaps[i] = 0; + } + + box->draw = draw; + box->set_content_size = set_content_size; + box->get_left_pad = get_left_pad; + box->get_top_pad = get_top_pad; + box->destroy = destroy; + + return box; +} + === added file 'include/grub/bitmap_scale.h' --- include/grub/bitmap_scale.h 1970-01-01 00:00:00 +0000 +++ include/grub/bitmap_scale.h 2008-07-03 14:27:43 +0000 @@ -0,0 +1,54 @@ +/* bitmap_scale.h - Bitmap scaling functions. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2006,2007 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#ifndef GRUB_BITMAP_SCALE_HEADER +#define GRUB_BITMAP_SCALE_HEADER 1 + +#include +#include +#include + +enum grub_video_bitmap_scale_method +{ + /* Choose the fastest interpolation algorithm. */ + GRUB_VIDEO_BITMAP_SCALE_METHOD_FASTEST, + /* Choose the highest quality interpolation algorithm. */ + GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST, + /* Nearest neighbor interpolation algorithm. */ + GRUB_VIDEO_BITMAP_SCALE_METHOD_NEAREST, + /* Bilinear interpolation algorithm. */ + GRUB_VIDEO_BITMAP_SCALE_METHOD_BILINEAR +}; + +grub_err_t +grub_video_bitmap_scale_nn (struct grub_video_bitmap *dst, + struct grub_video_bitmap *src); + +grub_err_t +grub_video_bitmap_scale_bilinear (struct grub_video_bitmap *dst, + struct grub_video_bitmap *src); + +grub_err_t +grub_video_bitmap_create_scaled (struct grub_video_bitmap **dst, + int dst_width, int dst_height, + struct grub_video_bitmap *src, + enum + grub_video_bitmap_scale_method scale_method); + +#endif /* ! GRUB_BITMAP_SCALE_HEADER */ === modified file 'include/grub/font.h' --- include/grub/font.h 2007-07-21 22:32:33 +0000 +++ include/grub/font.h 2008-07-03 14:17:31 +0000 @@ -1,6 +1,6 @@ /* * GRUB -- GRand Unified Bootloader - * Copyright (C) 2003,2007 Free Software Foundation, Inc. + * Copyright (C) 2003,2007,2008 Free Software Foundation, Inc. * * GRUB is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -21,33 +21,85 @@ #include -#define GRUB_FONT_MAGIC "PPF\x7f" +/* Forward declaration of opaque structure grub_font. + * Users only pass struct grub_font pointers to the font module functions, + * and do not have knowledge of the structure contents. */ +struct grub_font; + +/* Font type used to access font functions. */ +typedef struct grub_font *grub_font_t; + struct grub_font_glyph { - /* Glyph width in pixels. */ - grub_uint8_t width; - - /* Glyph height in pixels. */ - grub_uint8_t height; - - /* Glyph width in characters. */ - grub_uint8_t char_width; - - /* Glyph baseline position in pixels (from up). */ - grub_uint8_t baseline; - - /* Glyph bitmap data array of bytes in ((width + 7) / 8) * height. - Bitmap is formulated by height scanlines, each scanline having - width number of pixels. Pixels are coded as bits, value 1 meaning - of opaque pixel and 0 is transparent. If width does not fit byte - boundary, it will be padded with 0 to make it fit. */ - grub_uint8_t bitmap[32]; + /* Reference to the font this glyph belongs to. */ + grub_font_t font; + + /* Glyph bitmap width in pixels. */ + grub_uint16_t width; + + /* Glyph bitmap height in pixels. */ + grub_uint16_t height; + + /* Glyph bitmap x offset in pixels. Add to screen coordinate. */ + grub_int16_t offset_x; + + /* Glyph bitmap y offset in pixels. Subtract from screen coordinate. */ + grub_int16_t offset_y; + + /* Number of pixels to advance to start the next character. */ + grub_uint16_t device_width; + + /* Row-major order, packed bits (no padding; rows can break within a byte). + * The length of the array is (width * height + 7) / 8. Within a + * byte, the most significant bit is the first (leftmost/uppermost) pixel. + * Pixels are coded as bits, value 1 meaning of opaque pixel and 0 is + * transparent. If the length of the array does not fit byte boundary, it + * will be padded with 0 bits to make it fit. */ + grub_uint8_t bitmap[0]; }; -typedef struct grub_font_glyph *grub_font_glyph_t; - -int grub_font_get_glyph (grub_uint32_t code, - grub_font_glyph_t glyph); + +/****** font/font.c ******/ + +/* Get the font that has the specified name. Font names are in the form + * "Family Name Bold Italic 14", where Bold and Italic are optional. + * If no font matches the name specified, the most recently loaded font + * is returned as a fallback. */ +grub_font_t grub_font_get (const char *font_name); + +const char *grub_font_get_name (grub_font_t font); + +int grub_font_get_max_char_width (grub_font_t font); + +int grub_font_get_max_char_height (grub_font_t font); + +int grub_font_get_ascent (grub_font_t font); + +int grub_font_get_descent (grub_font_t font); + +int grub_font_get_string_width (grub_font_t font, const char *str); + + +/****** font/loader.c ******/ + +/* + * Load a font and add it to the beginning of the global font list. + * Returns: 0 upon success; nonzero upon failure. + */ +int grub_font_load (const char *filename); + +/* Get the glyph for FONT corresponding to the Unicode code point CODE. + * Returns a pointer to an glyph indicating there is no glyph available + * if CODE does not exist in the font. The glyphs are cached once loaded. */ +struct grub_font_glyph *grub_font_get_glyph (grub_font_t font, + grub_uint32_t code); + +/* Get a glyph corresponding to the codepoint CODE. If no glyph is available + * for CODE in the available fonts, then a glyph representing an unknown + * character is returned. This function never returns NULL. + * The returned glyph is owned by the font manager and should not be freed + * by the caller. The glyphs are cached. */ +struct grub_font_glyph *grub_font_get_glyph_any (grub_uint32_t code); #endif /* ! GRUB_FONT_HEADER */ === added file 'include/grub/font_internal.h' --- include/grub/font_internal.h 1970-01-01 00:00:00 +0000 +++ include/grub/font_internal.h 2008-07-03 14:16:11 +0000 @@ -0,0 +1,71 @@ +/* font_internal.h - Font declarations for use internally by the font module. + * Users of the font module should not include this header. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2003,2005,2006,2007,2008 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#ifndef GRUB_FONT_INTERNAL_HEADER +#define GRUB_FONT_INTERNAL_HEADER 1 + +#include +#include +#include +#include +#include +#include +#include + +#define FONT_DEBUG 0 + +struct char_index_entry +{ + grub_uint32_t code; + grub_uint8_t storage_flags; + grub_uint32_t offset; + struct grub_font_glyph *glyph; /* Glyph if loaded, or null. */ +}; + +struct grub_font +{ + char *name; + grub_file_t file; + short max_char_width; + short max_char_height; + short ascent; + short descent; + grub_uint32_t num_chars; + struct char_index_entry *char_index; +}; + +struct font_node +{ + struct font_node *next; + struct grub_font *value; +}; + +extern struct font_node *grub_font_list; + + +/****** loader.c ******/ + +/* Initialize the font loader module. */ +void +grub_font_loader_init (void); + + +#endif /* ! GRUB_FONT_INTERNAL_HEADER */ + === added file 'include/grub/gfxterm.h' --- include/grub/gfxterm.h 1970-01-01 00:00:00 +0000 +++ include/grub/gfxterm.h 2008-07-19 19:56:06 +0000 @@ -0,0 +1,41 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2006,2007,2008 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#ifndef GRUB_GFXTERM_HEADER +#define GRUB_GFXTERM_HEADER 1 + +#include +#include +#include +#include + +grub_err_t +grub_gfxterm_init_window (struct grub_video_render_target *target, + int x, int y, int width, int height, + const char *font_name, int border_width); + +void grub_gfxterm_destroy_window (void); + +grub_term_t grub_gfxterm_get_term (void); + +typedef void (*grub_gfxterm_repaint_callback_t)(int x, int y, + int width, int height); + +void grub_gfxterm_set_repaint_callback (grub_gfxterm_repaint_callback_t func); + +#endif /* ! GRUB_GFXTERM_HEADER */ === added file 'include/grub/gfxwidgets.h' --- include/grub/gfxwidgets.h 1970-01-01 00:00:00 +0000 +++ include/grub/gfxwidgets.h 2008-07-19 18:25:18 +0000 @@ -0,0 +1,47 @@ +/* gfxwidgets.h - Widgets for the graphical menu (gfxmenu). */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2008 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#ifndef GRUB_GFXWIDGETS_HEADER +#define GRUB_GFXWIDGETS_HEADER 1 + +#include + +typedef struct grub_gfxmenu_box *grub_gfxmenu_box_t; + +struct grub_gfxmenu_box +{ + /* The size of the content. */ + int width; + int height; + + struct grub_video_bitmap **raw_pixmaps; + struct grub_video_bitmap **scaled_pixmaps; + + void (*draw) (grub_gfxmenu_box_t self, int x, int y); + void (*set_content_size) (grub_gfxmenu_box_t self, + int width, int height); + int (*get_left_pad) (grub_gfxmenu_box_t self); + int (*get_top_pad) (grub_gfxmenu_box_t self); + void (*destroy) (grub_gfxmenu_box_t self); +}; + +grub_gfxmenu_box_t grub_gfxmenu_create_box (const char *pixmaps_prefix, + const char *pixmaps_suffix); + +#endif /* ! GRUB_GFXWIDGETS_HEADER */ === modified file 'include/grub/i386/pc/vbeblit.h' --- include/grub/i386/pc/vbeblit.h 2007-07-21 22:32:33 +0000 +++ include/grub/i386/pc/vbeblit.h 2008-07-09 17:27:53 +0000 @@ -25,55 +25,93 @@ struct grub_video_i386_vbeblit_info; void -grub_video_i386_vbeblit_R8G8B8A8_R8G8B8A8 (struct grub_video_i386_vbeblit_info *dst, - struct grub_video_i386_vbeblit_info *src, - int x, int y, int width, int height, - int offset_x, int offset_y); - -void -grub_video_i386_vbeblit_R8G8B8X8_R8G8B8X8 (struct grub_video_i386_vbeblit_info *dst, - struct grub_video_i386_vbeblit_info *src, - int x, int y, int width, int height, - int offset_x, int offset_y); - -void -grub_video_i386_vbeblit_R8G8B8_R8G8B8A8 (struct grub_video_i386_vbeblit_info *dst, - struct grub_video_i386_vbeblit_info *src, - int x, int y, int width, int height, - int offset_x, int offset_y); - -void -grub_video_i386_vbeblit_R8G8B8_R8G8B8X8 (struct grub_video_i386_vbeblit_info *dst, - struct grub_video_i386_vbeblit_info *src, - int x, int y, int width, int height, - int offset_x, int offset_y); - -void -grub_video_i386_vbeblit_index_R8G8B8A8 (struct grub_video_i386_vbeblit_info *dst, - struct grub_video_i386_vbeblit_info *src, - int x, int y, int width, int height, - int offset_x, int offset_y); - -void -grub_video_i386_vbeblit_index_R8G8B8X8 (struct grub_video_i386_vbeblit_info *dst, - struct grub_video_i386_vbeblit_info *src, - int x, int y, int width, int height, - int offset_x, int offset_y); - -void -grub_video_i386_vbeblit_R8G8B8A8_R8G8B8 (struct grub_video_i386_vbeblit_info *dst, - struct grub_video_i386_vbeblit_info *src, - int x, int y, int width, int height, - int offset_x, int offset_y); - -void -grub_video_i386_vbeblit_R8G8B8_R8G8B8 (struct grub_video_i386_vbeblit_info *dst, - struct grub_video_i386_vbeblit_info *src, - int x, int y, int width, int height, - int offset_x, int offset_y); - -void -grub_video_i386_vbeblit_index_R8G8B8 (struct grub_video_i386_vbeblit_info *dst, +grub_video_i386_vbeblit_BGRX8888_RGBX8888 (struct grub_video_i386_vbeblit_info *dst, + struct grub_video_i386_vbeblit_info *src, + int x, int y, int width, int height, + int offset_x, int offset_y); + +void +grub_video_i386_vbeblit_BGRA8888_RGB888 (struct grub_video_i386_vbeblit_info *dst, + struct grub_video_i386_vbeblit_info *src, + int x, int y, int width, int height, + int offset_x, int offset_y); + +void +grub_video_i386_vbeblit_BGR888_RGB888 (struct grub_video_i386_vbeblit_info *dst, + struct grub_video_i386_vbeblit_info *src, + int x, int y, int width, int height, + int offset_x, int offset_y); + +void +grub_video_i386_vbeblit_BGRA8888_RGBA8888 (struct grub_video_i386_vbeblit_info *dst, + struct grub_video_i386_vbeblit_info *src, + int x, int y, int width, int height, + int offset_x, int offset_y); + +void +grub_video_i386_vbeblit_BGR888_RGBA8888 (struct grub_video_i386_vbeblit_info *dst, + struct grub_video_i386_vbeblit_info *src, + int x, int y, int width, int height, + int offset_x, int offset_y); + +void +grub_video_i386_vbeblit_BGR888_RGBX8888 (struct grub_video_i386_vbeblit_info *dst, + struct grub_video_i386_vbeblit_info *src, + int x, int y, int width, int height, + int offset_x, int offset_y); + +void +grub_video_i386_vbeblit_RGBA8888_RGBA8888 (struct grub_video_i386_vbeblit_info *dst, + struct grub_video_i386_vbeblit_info *src, + int x, int y, int width, int height, + int offset_x, int offset_y); + +/* Direct copy for compatible 32 bpp blit formats. + * (RGBA8888->RGBA8888, BGRA8888->BGRA8888, etc.) */ +void +grub_video_i386_vbeblit_direct32_copy (struct grub_video_i386_vbeblit_info *dst, + struct grub_video_i386_vbeblit_info *src, + int x, int y, int width, int height, + int offset_x, int offset_y); + +void +grub_video_i386_vbeblit_RGB888_RGBA8888 (struct grub_video_i386_vbeblit_info *dst, + struct grub_video_i386_vbeblit_info *src, + int x, int y, int width, int height, + int offset_x, int offset_y); + +void +grub_video_i386_vbeblit_RGB888_RGBX8888 (struct grub_video_i386_vbeblit_info *dst, + struct grub_video_i386_vbeblit_info *src, + int x, int y, int width, int height, + int offset_x, int offset_y); + +void +grub_video_i386_vbeblit_index_RGBA8888 (struct grub_video_i386_vbeblit_info *dst, + struct grub_video_i386_vbeblit_info *src, + int x, int y, int width, int height, + int offset_x, int offset_y); + +void +grub_video_i386_vbeblit_index_RGBX8888 (struct grub_video_i386_vbeblit_info *dst, + struct grub_video_i386_vbeblit_info *src, + int x, int y, int width, int height, + int offset_x, int offset_y); + +void +grub_video_i386_vbeblit_RGBA8888_RGB888 (struct grub_video_i386_vbeblit_info *dst, + struct grub_video_i386_vbeblit_info *src, + int x, int y, int width, int height, + int offset_x, int offset_y); + +void +grub_video_i386_vbeblit_RGB888_RGB888 (struct grub_video_i386_vbeblit_info *dst, + struct grub_video_i386_vbeblit_info *src, + int x, int y, int width, int height, + int offset_x, int offset_y); + +void +grub_video_i386_vbeblit_index_RGB888 (struct grub_video_i386_vbeblit_info *dst, struct grub_video_i386_vbeblit_info *src, int x, int y, int width, int height, int offset_x, int offset_y); === modified file 'include/grub/i386/pc/vbefill.h' --- include/grub/i386/pc/vbefill.h 2007-07-21 22:32:33 +0000 +++ include/grub/i386/pc/vbefill.h 2008-07-03 13:49:18 +0000 @@ -25,14 +25,14 @@ struct grub_video_i386_vbeblit_info; void -grub_video_i386_vbefill_R8G8B8A8 (struct grub_video_i386_vbeblit_info *dst, +grub_video_i386_vbefill_direct32 (struct grub_video_i386_vbeblit_info *dst, grub_video_color_t color, int x, int y, int width, int height); void -grub_video_i386_vbefill_R8G8B8 (struct grub_video_i386_vbeblit_info *dst, - grub_video_color_t color, int x, int y, - int width, int height); +grub_video_i386_vbefill_direct24 (struct grub_video_i386_vbeblit_info *dst, + grub_video_color_t color, int x, int y, + int width, int height); void grub_video_i386_vbefill_index (struct grub_video_i386_vbeblit_info *dst, === added file 'include/grub/i386/tsc.h' --- include/grub/i386/tsc.h 1970-01-01 00:00:00 +0000 +++ include/grub/i386/tsc.h 2008-07-04 17:55:21 +0000 @@ -0,0 +1,43 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2008 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#ifndef KERNEL_CPU_TSC_HEADER +#define KERNEL_CPU_TSC_HEADER 1 + +#include + +/* Read the TSC value, which increments with each CPU clock cycle. */ +static __inline grub_uint64_t +grub_get_tsc (void) +{ + grub_uint32_t lo, hi; + + /* The CPUID instruction is a 'serializing' instruction, and + avoids out-of-order execution of the RDTSC instruction. */ + __asm__ __volatile__ ("xorl %%eax, %%eax\n\t" + "cpuid":::"%rax", "%rbx", "%rcx", "%rdx"); + /* Read TSC value. We cannot use "=A", since this would use + %rax on x86_64. */ + __asm__ __volatile__ ("rdtsc":"=a" (lo), "=d" (hi)); + + return (((grub_uint64_t) hi) << 32) | lo; +} + +void grub_tsc_calibrate (void); + +#endif /* ! KERNEL_CPU_TSC_HEADER */ === added file 'include/grub/menu.h' --- include/grub/menu.h 1970-01-01 00:00:00 +0000 +++ include/grub/menu.h 2008-07-19 18:40:28 +0000 @@ -0,0 +1,51 @@ +/* menu.h - Menu and menu entry model declarations. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2002,2003,2005,2006,2007,2008 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#ifndef GRUB_MENU_HEADER +#define GRUB_MENU_HEADER 1 + +/* The menu entry. */ +struct grub_menu_entry +{ + /* The title name. */ + const char *title; + + /* The commands associated with this menu entry. */ + struct grub_script *commands; + + /* The sourcecode of the menu entry, used by the editor. */ + const char *sourcecode; + + /* The next element. */ + struct grub_menu_entry *next; +}; +typedef struct grub_menu_entry *grub_menu_entry_t; + +/* The menu. */ +struct grub_menu +{ + /* The size of a menu. */ + int size; + + /* The list of menu entries. */ + grub_menu_entry_t entry_list; +}; +typedef struct grub_menu *grub_menu_t; + +#endif /* GRUB_MENU_HEADER */ === added file 'include/grub/menu_viewer.h' --- include/grub/menu_viewer.h 1970-01-01 00:00:00 +0000 +++ include/grub/menu_viewer.h 2008-07-19 19:07:47 +0000 @@ -0,0 +1,48 @@ +/* menu_viewer.h - Interface to menu viewer implementations. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2002,2003,2005,2007,2008 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#ifndef GRUB_MENU_VIEWER_HEADER +#define GRUB_MENU_VIEWER_HEADER 1 + +#include +#include +#include +#include + +struct grub_menu_viewer +{ + /* The menu viewer name. */ + const char *name; + + grub_err_t (*show_menu) (grub_menu_t menu, int nested); + + struct grub_menu_viewer *next; +}; +typedef struct grub_menu_viewer *grub_menu_viewer_t; + +void EXPORT_FUNC(grub_menu_viewer_init) (void); + +void EXPORT_FUNC(grub_menu_viewer_register) (grub_menu_viewer_t viewer); + +grub_err_t EXPORT_FUNC(grub_menu_viewer_show_menu) (grub_menu_t menu, int nested); + +/* Return nonzero iff the menu viewer should clean up and return ASAP. */ +int EXPORT_FUNC(grub_menu_viewer_should_return) (void); + +#endif /* GRUB_MENU_VIEWER_HEADER */ === modified file 'include/grub/normal.h' --- include/grub/normal.h 2008-03-26 12:01:02 +0000 +++ include/grub/normal.h 2008-07-19 19:24:29 +0000 @@ -25,6 +25,7 @@ #include #include #include +#include /* The maximum size of a command-line. */ #define GRUB_MAX_CMDLINE 1600 @@ -84,34 +85,6 @@ }; typedef struct grub_command *grub_command_t; -/* The menu entry. */ -struct grub_menu_entry -{ - /* The title name. */ - const char *title; - - /* The commands associated with this menu entry. */ - struct grub_script *commands; - - /* The sourcecode of the menu entry, used by the editor. */ - const char *sourcecode; - - /* The next element. */ - struct grub_menu_entry *next; -}; -typedef struct grub_menu_entry *grub_menu_entry_t; - -/* The menu. */ -struct grub_menu -{ - /* The size of a menu. */ - int size; - - /* The list of menu entries. */ - grub_menu_entry_t entry_list; -}; -typedef struct grub_menu *grub_menu_t; - /* This is used to store the names of filesystem modules for auto-loading. */ struct grub_fs_module_list { @@ -123,10 +96,12 @@ /* To exit from the normal mode. */ extern grub_jmp_buf grub_exit_env; +extern struct grub_menu_viewer grub_normal_terminal_menu_viewer; + void grub_enter_normal_mode (const char *config); void grub_normal_execute (const char *config, int nested); -void grub_menu_run (grub_menu_t menu, int nested); void grub_menu_entry_run (grub_menu_entry_t entry); +void grub_menu_execute_entry(grub_menu_entry_t entry); void grub_cmdline_run (int nested); int grub_cmdline_get (const char *prompt, char cmdline[], unsigned max_len, int echo_char, int readline); === modified file 'include/grub/time.h' --- include/grub/time.h 2007-10-22 19:02:16 +0000 +++ include/grub/time.h 2008-07-03 04:19:16 +0000 @@ -1,6 +1,6 @@ /* * GRUB -- GRand Unified Bootloader - * Copyright (C) 2007 Free Software Foundation, Inc. + * Copyright (C) 2007, 2008 Free Software Foundation, Inc. * * GRUB is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,12 +19,13 @@ #ifndef KERNEL_TIME_HEADER #define KERNEL_TIME_HEADER 1 +#include #include #include #include void EXPORT_FUNC(grub_millisleep) (grub_uint32_t ms); -void EXPORT_FUNC(grub_millisleep_generic) (grub_uint32_t ms); +grub_uint64_t EXPORT_FUNC(grub_get_time_ms) (void); static __inline void grub_sleep (grub_uint32_t s) === modified file 'include/grub/video.h' --- include/grub/video.h 2008-01-01 12:02:07 +0000 +++ include/grub/video.h 2008-07-19 19:31:46 +0000 @@ -21,6 +21,7 @@ #include #include +#include /* Video color in hardware dependent format. Users should not assume any specific coding format. */ @@ -31,40 +32,41 @@ struct grub_video_render_target; /* Forward declarations for used data structures. */ -struct grub_font_glyph; struct grub_video_bitmap; /* Defines used to describe video mode or rendering target. */ -#define GRUB_VIDEO_MODE_TYPE_ALPHA 0x00000008 -#define GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED 0x00000004 +#define GRUB_VIDEO_MODE_TYPE_ALPHA 0x00000020 +#define GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED 0x00000010 +#define GRUB_VIDEO_MODE_TYPE_1BIT_BITMAP 0x00000004 #define GRUB_VIDEO_MODE_TYPE_INDEX_COLOR 0x00000002 #define GRUB_VIDEO_MODE_TYPE_RGB 0x00000001 /* Defines used to mask flags. */ -#define GRUB_VIDEO_MODE_TYPE_COLOR_MASK 0x00000003 +#define GRUB_VIDEO_MODE_TYPE_COLOR_MASK 0x0000000F /* Defines used to specify requested bit depth. */ #define GRUB_VIDEO_MODE_TYPE_DEPTH_MASK 0x0000ff00 #define GRUB_VIDEO_MODE_TYPE_DEPTH_POS 8 -/* Defined predefined render targets. */ -#define GRUB_VIDEO_RENDER_TARGET_DISPLAY ((struct grub_video_render_target *) 0) -#define GRUB_VIDEO_RENDER_TARGET_FRONT_BUFFER ((struct grub_video_render_target *) 0) -#define GRUB_VIDEO_RENDER_TARGET_BACK_BUFFER ((struct grub_video_render_target *) 1) +/* Predefined render target: */ +/* The render target that client code should render to. */ +#define GRUB_VIDEO_RENDER_TARGET_DISPLAY \ + ((struct grub_video_render_target *) 0) + /* Defined blitting formats. */ enum grub_video_blit_format { - /* Follow exactly field & mask information. */ - GRUB_VIDEO_BLIT_FORMAT_RGBA, - /* Make optimization assumption. */ - GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8, - /* Follow exactly field & mask information. */ - GRUB_VIDEO_BLIT_FORMAT_RGB, - /* Make optimization assumption. */ - GRUB_VIDEO_BLIT_FORMAT_R8G8B8, + GRUB_VIDEO_BLIT_FORMAT_RGBA, /* General: Use fields & masks. */ + GRUB_VIDEO_BLIT_FORMAT_RGBA_8888, /* Optimized format. */ + GRUB_VIDEO_BLIT_FORMAT_BGRA_8888, /* Optimized format. */ + GRUB_VIDEO_BLIT_FORMAT_RGB, /* General: Use fields & masks. */ + GRUB_VIDEO_BLIT_FORMAT_RGB_888, /* Optimized format. */ + GRUB_VIDEO_BLIT_FORMAT_BGR_888, /* Optimized format. */ /* When needed, decode color or just use value as is. */ - GRUB_VIDEO_BLIT_FORMAT_INDEXCOLOR + GRUB_VIDEO_BLIT_FORMAT_INDEXCOLOR, + /* Two color bitmap; bits packed: rows are not padded to byte boundary. */ + GRUB_VIDEO_BLIT_FORMAT_1BIT_PACKED }; /* Define blitting operators. */ @@ -127,6 +129,18 @@ /* What is location of reserved color bits. In Index Color mode, this is 0. */ unsigned int reserved_field_pos; + + /* For 1-bit bitmaps, the background color. Used for bits = 0. */ + grub_uint8_t bg_red; + grub_uint8_t bg_green; + grub_uint8_t bg_blue; + grub_uint8_t bg_alpha; + + /* For 1-bit bitmaps, the foreground color. Used for bits = 1. */ + grub_uint8_t fg_red; + grub_uint8_t fg_green; + grub_uint8_t fg_blue; + grub_uint8_t fg_alpha; }; struct grub_video_palette_data @@ -137,6 +151,16 @@ grub_uint8_t a; /* Reserved bits value (0-255). */ }; +/* A 2D rectangle type. */ +struct grub_video_rect +{ + int x; + int y; + int width; + int height; +}; +typedef struct grub_video_rect grub_video_rect_t; + struct grub_video_adapter { /* The video adapter name. */ @@ -183,6 +207,10 @@ grub_err_t (*blit_glyph) (struct grub_font_glyph *glyph, grub_video_color_t color, int x, int y); + grub_err_t (*draw_string) (const char *str, grub_font_t font, + grub_video_color_t color, + int left_x, int baseline_y); + grub_err_t (*blit_bitmap) (struct grub_video_bitmap *bitmap, enum grub_video_blit_operators oper, int x, int y, int offset_x, int offset_y, @@ -255,6 +283,10 @@ grub_err_t grub_video_blit_glyph (struct grub_font_glyph *glyph, grub_video_color_t color, int x, int y); +grub_err_t grub_video_draw_string (const char *str, grub_font_t font, + grub_video_color_t color, + int left_x, int baseline_y); + grub_err_t grub_video_blit_bitmap (struct grub_video_bitmap *bitmap, enum grub_video_blit_operators oper, int x, int y, int offset_x, int offset_y, @@ -269,6 +301,9 @@ grub_err_t grub_video_scroll (grub_video_color_t color, int dx, int dy); +/* Swap the pages referred to by the front buffer and back buffer render + * targets, and the page previously referred to by the back buffer is made + * visible on the display. */ grub_err_t grub_video_swap_buffers (void); grub_err_t grub_video_create_render_target (struct grub_video_render_target **result, @@ -282,4 +317,21 @@ grub_err_t grub_video_get_active_render_target (struct grub_video_render_target **target); + +/* Defined in video/setmode.c */ + +/* Set the video mode based on the preferred modes specified in MODE_LIST in + the form: x[x][;...] + + For example: 640x480;800x600x8;400x300x32 + + If MODE_LIST is null, or no modes in it are usable, then DEFAULT_WIDTH and + DEFAULT_HEIGHT are used to set the mode. The MODE_FLAGS argument determines + the video mode flags such as double buffering that are used. */ + +grub_err_t +grub_video_setup_preferred_mode (const char *mode_list, int mode_flags, + int default_width, int default_height); + + #endif /* ! GRUB_VIDEO_HEADER */ === added directory 'kern/generic' === added file 'kern/generic/get_time_ms.c' --- kern/generic/get_time_ms.c 1970-01-01 00:00:00 +0000 +++ kern/generic/get_time_ms.c 2008-07-04 16:55:48 +0000 @@ -0,0 +1,37 @@ +/* get_time_ms.c - generic time implementation -- using platform RTC. + * The generic implementation of these functions can be used for architectures + * or platforms that do not have a more specialized implementation. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2008 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include + +/* Calculate the time in milliseconds since the epoch based on the RTC. */ +grub_uint64_t +grub_get_time_ms (void) +{ + /* By dimensional analysis: + + 1000 ms N rtc ticks 1 s + ------- * ----------- * ----------- = 1000*N/T ms + 1 s 1 T rtc ticks + */ + grub_uint64_t ticks_ms_per_sec = ((grub_uint64_t) 1000) * grub_get_rtc (); + return grub_divmod64 (ticks_ms_per_sec, GRUB_TICKS_PER_SECOND, 0); +} === added file 'kern/generic/millisleep.c' --- kern/generic/millisleep.c 1970-01-01 00:00:00 +0000 +++ kern/generic/millisleep.c 2008-07-04 16:55:48 +0000 @@ -0,0 +1,39 @@ +/* millisleep.c - generic millisleep function. + * The generic implementation of these functions can be used for architectures + * or platforms that do not have a more specialized implementation. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 1999,2000,2001,2002,2003,2004,2005,2006,2007,2008 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include + +void +grub_millisleep (grub_uint32_t ms) +{ + grub_uint64_t start; + + start = grub_get_time_ms (); + + /* Instead of setting an end time and looping while the current time is + less than that, comparing the elapsed sleep time with the desired sleep + time handles the (unlikely!) case that the timer would wrap around + during the sleep. */ + + while (grub_get_time_ms () - start < ms) + grub_cpu_idle (); +} === modified file 'kern/i386/efi/init.c' --- kern/i386/efi/init.c 2007-10-22 18:59:33 +0000 +++ kern/i386/efi/init.c 2008-07-04 17:55:21 +0000 @@ -25,18 +25,13 @@ #include #include #include -#include - -void -grub_millisleep (grub_uint32_t ms) -{ - grub_millisleep_generic (ms); -} +#include void grub_machine_init (void) { grub_efi_init (); + grub_tsc_calibrate (); } void === modified file 'kern/i386/pc/init.c' --- kern/i386/pc/init.c 2008-06-15 17:21:16 +0000 +++ kern/i386/pc/init.c 2008-07-04 18:03:26 +0000 @@ -30,6 +30,7 @@ #include #include #include +#include struct mem_region { @@ -46,12 +47,6 @@ grub_size_t grub_os_area_size; grub_size_t grub_lower_mem, grub_upper_mem; -void -grub_millisleep (grub_uint32_t ms) -{ - grub_millisleep_generic (ms); -} - void grub_arch_sync_caches (void *address __attribute__ ((unused)), grub_size_t len __attribute__ ((unused))) @@ -231,6 +226,8 @@ if (! grub_os_area_addr) grub_fatal ("no upper memory"); + + grub_tsc_calibrate (); } void === added file 'kern/i386/tsc.c' --- kern/i386/tsc.c 1970-01-01 00:00:00 +0000 +++ kern/i386/tsc.c 2008-07-04 17:55:21 +0000 @@ -0,0 +1,89 @@ +/* kern/i386/tsc.c - x86 TSC time source implementation + * Requires Pentium or better x86 CPU that supports the RDTSC instruction. + * This module uses the RTC (via grub_get_rtc()) to calibrate the TSC to + * real time. + * + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2008 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include + +/* Calibrated reference for TSC=0. This defines the time since the epoch in + milliseconds that TSC=0 refers to. */ +static grub_uint64_t tsc_boot_time; + +/* Calibrated TSC rate. (In TSC ticks per millisecond.) */ +static grub_uint64_t tsc_ticks_per_ms; + + +/* Declared in . */ +grub_uint64_t +grub_get_time_ms (void) +{ + return tsc_boot_time + grub_divmod64 (grub_get_tsc (), tsc_ticks_per_ms, 0); +} + + +/* How many RTC ticks to use for calibration loop. (>= 1) */ +#define CALIBRATION_TICKS 2 + +/* Calibrate the TSC based on the RTC. */ +void +grub_tsc_calibrate (void) +{ + /* First calbrate the TSC rate (relative, not absolute time). */ + grub_uint64_t start_tsc; + grub_uint64_t end_tsc; + grub_uint32_t initial_tick; + grub_uint32_t start_tick; + grub_uint32_t end_tick; + + /* Wait for the start of the next tick; + we'll base out timing off this edge. */ + initial_tick = grub_get_rtc (); + do + { + start_tick = grub_get_rtc (); + } + while (start_tick == initial_tick); + start_tsc = grub_get_tsc (); + + /* Wait for the start of the next tick. This will + be the end of the 1-tick period. */ + do + { + end_tick = grub_get_rtc (); + } + while (end_tick - start_tick < CALIBRATION_TICKS); + end_tsc = grub_get_tsc (); + + tsc_ticks_per_ms = + grub_divmod64 (grub_divmod64 + (end_tsc - start_tsc, end_tick - start_tick, 0) + * GRUB_TICKS_PER_SECOND, 1000, 0); + + /* Reference the TSC zero (boot time) to the epoch to + get an absolute real time reference. */ + grub_uint64_t ms_since_boot = grub_divmod64 (end_tsc, tsc_ticks_per_ms, 0); + grub_uint64_t mstime_now = grub_divmod64 ((grub_uint64_t) 1000 * end_tick, + GRUB_TICKS_PER_SECOND, + 0); + tsc_boot_time = mstime_now - ms_since_boot; +} === modified file 'kern/ieee1275/init.c' --- kern/ieee1275/init.c 2008-07-04 02:01:55 +0000 +++ kern/ieee1275/init.c 2008-07-04 19:43:25 +0000 @@ -47,12 +47,6 @@ extern char _end[]; void -grub_millisleep (grub_uint32_t ms) -{ - grub_millisleep_generic (ms); -} - -void grub_exit (void) { grub_ieee1275_exit (); @@ -260,8 +254,8 @@ grub_console_fini (); } -grub_uint32_t -grub_get_rtc (void) +grub_uint64_t +grub_get_time_ms (void) { grub_uint32_t msecs = 0; @@ -270,6 +264,12 @@ return msecs; } +grub_uint32_t +grub_get_rtc (void) +{ + return grub_get_time_ms (); +} + grub_addr_t grub_arch_modules_addr (void) { === added file 'kern/menu_viewer.c' --- kern/menu_viewer.c 1970-01-01 00:00:00 +0000 +++ kern/menu_viewer.c 2008-07-19 19:07:47 +0000 @@ -0,0 +1,99 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2002,2003,2005,2007 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include + +/* The list of menu viewers. */ +static grub_menu_viewer_t menu_viewer_list; + +static int should_return; +static int menu_viewer_changed; + +void +grub_menu_viewer_register (grub_menu_viewer_t viewer) +{ + viewer->next = menu_viewer_list; + menu_viewer_list = viewer; +} + +static grub_menu_viewer_t get_current_menu_viewer (void) +{ + const char *selected_name = grub_env_get ("menuviewer"); + + /* If none selected, pick the last registered one. */ + if (selected_name == 0) + return menu_viewer_list; + + grub_menu_viewer_t cur; + for (cur = menu_viewer_list; cur; cur = cur->next) + { + if (grub_strcmp (cur->name, selected_name) == 0) + return cur; + } + + /* Fall back to the first entry (or null). */ + return menu_viewer_list; +} + +grub_err_t +grub_menu_viewer_show_menu (grub_menu_t menu, int nested) +{ + grub_err_t err; + int repeat = 0; + do + { + repeat = 0; + menu_viewer_changed = 0; + grub_menu_viewer_t cur = get_current_menu_viewer (); + if (!cur) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "No menu viewer available."); + + should_return = 0; + err = cur->show_menu (menu, nested); + if (menu_viewer_changed) + repeat = 1; + } + while (repeat); + return err; +} + +int +grub_menu_viewer_should_return (void) +{ + return should_return; +} + +static char * +menuviewer_write_hook (struct grub_env_var *var __attribute__ ((unused)), + const char *val) +{ + menu_viewer_changed = 1; + should_return = 1; + return grub_strdup (val); +} + +void +grub_menu_viewer_init (void) +{ + grub_register_variable_hook ("menuviewer", 0, menuviewer_write_hook); +} + === modified file 'kern/misc.c' --- kern/misc.c 2008-06-15 23:42:48 +0000 +++ kern/misc.c 2008-07-04 18:03:26 +0000 @@ -23,7 +23,6 @@ #include #include #include -#include void * grub_memmove (void *dest, const void *src, grub_size_t n) @@ -1018,17 +1017,6 @@ return p - dest; } -void -grub_millisleep_generic (grub_uint32_t ms) -{ - grub_uint32_t end_at; - - end_at = grub_get_rtc () + grub_div_roundup (ms * GRUB_TICKS_PER_SECOND, 1000); - - while (grub_get_rtc () < end_at) - grub_cpu_idle (); -} - /* Abort GRUB. This function does not return. */ void grub_abort (void) === modified file 'kern/sparc64/ieee1275/init.c' --- kern/sparc64/ieee1275/init.c 2007-10-22 18:59:33 +0000 +++ kern/sparc64/ieee1275/init.c 2008-07-04 17:55:21 +0000 @@ -66,12 +66,6 @@ /* Never reached. */ } -void -grub_millisleep (grub_uint32_t ms) -{ - grub_millisleep_generic (ms); -} - int grub_ieee1275_test_flag (enum grub_ieee1275_flag flag) { === modified file 'normal/main.c' --- normal/main.c 2008-02-02 16:48:52 +0000 +++ normal/main.c 2008-07-19 19:07:47 +0000 @@ -28,6 +28,7 @@ #include #include #include +#include grub_jmp_buf grub_exit_env; @@ -476,7 +477,7 @@ if (menu && menu->size) { - grub_menu_run (menu, nested); + grub_menu_viewer_show_menu (menu, nested); if (nested) free_menu (menu); } @@ -519,6 +520,8 @@ if (mod) grub_dl_ref (mod); + grub_menu_viewer_register (&grub_normal_terminal_menu_viewer); + grub_set_history (GRUB_DEFAULT_HISTORY_SIZE); /* Register a command "normal" for the rescue mode. */ @@ -535,6 +538,8 @@ /* This registers some built-in commands. */ grub_command_init (); + + grub_menu_viewer_init (); } GRUB_MOD_FINI(normal) === modified file 'normal/menu.c' --- normal/menu.c 2008-02-09 11:00:19 +0000 +++ normal/menu.c 2008-07-19 19:24:29 +0000 @@ -24,6 +24,7 @@ #include #include #include +#include static grub_uint8_t grub_color_menu_normal; static grub_uint8_t grub_color_menu_highlight; @@ -364,7 +365,7 @@ if (timeout > 0) print_timeout (timeout, offset, 0); - while (1) + while (!grub_menu_viewer_should_return ()) { int c; timeout = get_timeout (); @@ -468,6 +469,10 @@ } goto refresh; + case 't': + grub_env_set ("menuviewer", "protomenu"); + goto refresh; + default: break; } @@ -476,13 +481,14 @@ } } - /* Never reach here. */ + /* Exit menu without activating an item. This occurs if the user presses + * 't', switching to the graphical menu viewer. */ return -1; } /* Run a menu entry. */ -static void -run_menu_entry (grub_menu_entry_t entry) +void +grub_menu_execute_entry(grub_menu_entry_t entry) { grub_script_execute (entry->commands); @@ -491,8 +497,8 @@ grub_command_execute ("boot", 0); } -void -grub_menu_run (grub_menu_t menu, int nested) +static grub_err_t +show_menu (grub_menu_t menu, int nested) { while (1) { @@ -513,7 +519,7 @@ grub_printf (" Booting \'%s\'\n\n", e->title); - run_menu_entry (e); + grub_menu_execute_entry (e); /* Deal with a fallback entry. */ /* FIXME: Multiple fallback entries like GRUB Legacy. */ @@ -526,7 +532,7 @@ e = get_entry (menu, fallback_entry); grub_env_unset ("fallback"); grub_printf ("\n Falling back to \'%s\'\n\n", e->title); - run_menu_entry (e); + grub_menu_execute_entry (e); } if (grub_errno != GRUB_ERR_NONE) @@ -537,4 +543,12 @@ grub_wait_after_message (); } } + + return GRUB_ERR_NONE; } + +struct grub_menu_viewer grub_normal_terminal_menu_viewer = +{ + .name = "terminal", + .show_menu = show_menu +}; === modified file 'term/gfxterm.c' --- term/gfxterm.c 2008-01-01 12:02:07 +0000 +++ term/gfxterm.c 2008-07-19 19:56:06 +0000 @@ -28,14 +28,12 @@ #include #include #include +#include #include +#include #define DEFAULT_VIDEO_WIDTH 640 #define DEFAULT_VIDEO_HEIGHT 480 -#define DEFAULT_VIDEO_FLAGS 0 - -#define DEFAULT_CHAR_WIDTH 8 -#define DEFAULT_CHAR_HEIGHT 16 #define DEFAULT_BORDER_WIDTH 10 @@ -91,6 +89,9 @@ unsigned int cursor_y; int cursor_state; + /* Font settings. */ + grub_font_t font; + /* Terminal color settings. */ grub_uint8_t standard_color_setting; grub_uint8_t normal_color_setting; @@ -107,10 +108,20 @@ struct grub_colored_char *text_buffer; }; +static int refcount; +static struct grub_video_render_target *render_target; +static grub_video_rect_t window; static struct grub_virtual_screen virtual_screen; +static grub_gfxterm_repaint_callback_t repaint_callback; + +static grub_err_t init_window (struct grub_video_render_target *target, + int x, int y, int width, int height, + const char *font_name, int border_width); + +static void destroy_window (void); + static grub_dl_t my_mod; -static struct grub_video_mode_info mode_info; static struct grub_video_render_target *text_layer; @@ -170,18 +181,25 @@ static grub_err_t grub_virtual_screen_setup (unsigned int x, unsigned int y, - unsigned int width, unsigned int height) + unsigned int width, unsigned int height, + const char *font_name) { /* Free old virtual screen. */ grub_virtual_screen_free (); /* Initialize with default data. */ + virtual_screen.font = grub_font_get (font_name); + if (!virtual_screen.font) + return grub_error (GRUB_ERR_BAD_FONT, + "No font loaded."); virtual_screen.width = width; virtual_screen.height = height; virtual_screen.offset_x = x; virtual_screen.offset_y = y; - virtual_screen.char_width = DEFAULT_CHAR_WIDTH; - virtual_screen.char_height = DEFAULT_CHAR_HEIGHT; + virtual_screen.char_width = + grub_font_get_max_char_width (virtual_screen.font); + virtual_screen.char_height = + grub_font_get_max_char_height (virtual_screen.font); virtual_screen.cursor_x = 0; virtual_screen.cursor_y = 0; virtual_screen.cursor_state = 1; @@ -227,281 +245,129 @@ } static grub_err_t +init_window (struct grub_video_render_target *target, + int x, int y, int width, int height, + const char *font_name, int border_width) +{ + /* Clean up any prior instance. */ + destroy_window (); + + /* Create virtual screen. */ + if (grub_virtual_screen_setup (border_width, border_width, + width - 2 * border_width, + height - 2 * border_width, + font_name) + != GRUB_ERR_NONE) + { + return grub_errno; + } + + /* Set the render target. */ + render_target = target; + + /* Set window bounds. */ + window.x = x; + window.y = y; + window.width = width; + window.height = height; + + /* Mark whole window as dirty. */ + dirty_region_reset (); + dirty_region_add (0, 0, width, height); + + return (grub_errno = GRUB_ERR_NONE); +} + +grub_err_t +grub_gfxterm_init_window (struct grub_video_render_target *target, + int x, int y, int width, int height, + const char *font_name, int border_width) +{ + grub_errno = GRUB_ERR_NONE; + if (refcount++ == 0) + init_window (target, x, y, width, height, font_name, border_width); + return grub_errno; +} + +static grub_err_t grub_gfxterm_init (void) { - char *modevar; - int width = DEFAULT_VIDEO_WIDTH; - int height = DEFAULT_VIDEO_HEIGHT; - int depth = -1; - int flags = DEFAULT_VIDEO_FLAGS; - grub_video_color_t color; + /* If gfxterm has already been initialized by calling the init_window + function, then leave it alone when it is set as the current terminal. */ + if (refcount++ != 0) + return GRUB_ERR_NONE; /* Parse gfxmode environment variable if set. */ - modevar = grub_env_get ("gfxmode"); - if (modevar) - { - char *tmp; - char *next_mode; - char *current_mode; - char *param; - char *value; - int mode_found = 0; - - /* Take copy of env.var. as we don't want to modify that. */ - tmp = grub_strdup (modevar); - modevar = tmp; - - if (grub_errno != GRUB_ERR_NONE) - return grub_errno; - - /* Initialize next mode. */ - next_mode = modevar; - - /* Loop until all modes has been tested out. */ - while (next_mode != NULL) - { - /* Use last next_mode as current mode. */ - tmp = next_mode; - - /* Reset video mode settings. */ - width = DEFAULT_VIDEO_WIDTH; - height = DEFAULT_VIDEO_HEIGHT; - depth = -1; - flags = DEFAULT_VIDEO_FLAGS; - - /* Save position of next mode and separate modes. */ - next_mode = grub_strchr(next_mode, ';'); - if (next_mode) - { - *next_mode = 0; - next_mode++; - } - - /* Skip whitespace. */ - while (grub_isspace (*tmp)) - tmp++; - - /* Initialize token holders. */ - current_mode = tmp; - param = tmp; - value = NULL; - - /* Parse x[x]*/ - - /* Find width value. */ - value = param; - param = grub_strchr(param, 'x'); - if (param == NULL) - { - grub_err_t rc; - - /* First setup error message. */ - rc = grub_error (GRUB_ERR_BAD_ARGUMENT, - "Invalid mode: %s\n", - current_mode); - - /* Free memory before returning. */ - grub_free (modevar); - - return rc; - } - - *param = 0; - param++; - - width = grub_strtoul (value, 0, 0); - if (grub_errno != GRUB_ERR_NONE) - { - grub_err_t rc; - - /* First setup error message. */ - rc = grub_error (GRUB_ERR_BAD_ARGUMENT, - "Invalid mode: %s\n", - current_mode); - - /* Free memory before returning. */ - grub_free (modevar); - - return rc; - } - - /* Find height value. */ - value = param; - param = grub_strchr(param, 'x'); - if (param == NULL) - { - height = grub_strtoul (value, 0, 0); - if (grub_errno != GRUB_ERR_NONE) - { - grub_err_t rc; - - /* First setup error message. */ - rc = grub_error (GRUB_ERR_BAD_ARGUMENT, - "Invalid mode: %s\n", - current_mode); - - /* Free memory before returning. */ - grub_free (modevar); - - return rc; - } - } - else - { - /* We have optional color depth value. */ - *param = 0; - param++; - - height = grub_strtoul (value, 0, 0); - if (grub_errno != GRUB_ERR_NONE) - { - grub_err_t rc; - - /* First setup error message. */ - rc = grub_error (GRUB_ERR_BAD_ARGUMENT, - "Invalid mode: %s\n", - current_mode); - - /* Free memory before returning. */ - grub_free (modevar); - - return rc; - } - - /* Convert color depth value. */ - value = param; - depth = grub_strtoul (value, 0, 0); - if (grub_errno != GRUB_ERR_NONE) - { - grub_err_t rc; - - /* First setup error message. */ - rc = grub_error (GRUB_ERR_BAD_ARGUMENT, - "Invalid mode: %s\n", - current_mode); - - /* Free memory before returning. */ - grub_free (modevar); - - return rc; - } - } - - /* Try out video mode. */ - - /* If we have 8 or less bits, then assume that it is indexed color mode. */ - if ((depth <= 8) && (depth != -1)) - flags |= GRUB_VIDEO_MODE_TYPE_INDEX_COLOR; - - /* We have more than 8 bits, then assume that it is RGB color mode. */ - if (depth > 8) - flags |= GRUB_VIDEO_MODE_TYPE_RGB; - - /* If user requested specific depth, forward that information to driver. */ - if (depth != -1) - flags |= (depth << GRUB_VIDEO_MODE_TYPE_DEPTH_POS) - & GRUB_VIDEO_MODE_TYPE_DEPTH_MASK; - - /* Try to initialize requested mode. Ignore any errors. */ - grub_error_push (); - if (grub_video_setup (width, height, flags) != GRUB_ERR_NONE) - { - grub_error_pop (); - continue; - } - - /* Figure out what mode we ended up. */ - if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE) - { - /* Couldn't get video mode info, restore old mode and continue to next one. */ - grub_error_pop (); - - grub_video_restore (); - continue; - } - - /* Restore state of error stack. */ - grub_error_pop (); - - /* Mode found! Exit loop. */ - mode_found = 1; - break; - } - - /* Free memory. */ - grub_free (modevar); - - if (!mode_found) - return grub_error (GRUB_ERR_BAD_ARGUMENT, - "No suitable mode found."); - } - else - { - /* No gfxmode variable set, use defaults. */ - - /* If we have 8 or less bits, then assume that it is indexed color mode. */ - if ((depth <= 8) && (depth != -1)) - flags |= GRUB_VIDEO_MODE_TYPE_INDEX_COLOR; - - /* We have more than 8 bits, then assume that it is RGB color mode. */ - if (depth > 8) - flags |= GRUB_VIDEO_MODE_TYPE_RGB; - - /* If user requested specific depth, forward that information to driver. */ - if (depth != -1) - flags |= (depth << GRUB_VIDEO_MODE_TYPE_DEPTH_POS) - & GRUB_VIDEO_MODE_TYPE_DEPTH_MASK; - - /* Initialize user requested mode. */ - if (grub_video_setup (width, height, flags) != GRUB_ERR_NONE) - return grub_errno; - - /* Figure out what mode we ended up. */ - if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE) - { - grub_video_restore (); - return grub_errno; - } + const char *modevar = grub_env_get ("gfxmode"); + if (grub_video_setup_preferred_mode (modevar, 0, + DEFAULT_VIDEO_WIDTH, + DEFAULT_VIDEO_HEIGHT) + != GRUB_ERR_NONE) + return grub_errno; + + /* Figure out what mode we ended up. */ + struct grub_video_mode_info mode_info; + if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE) + { + grub_video_restore (); + return grub_errno; } /* Make sure screen is black. */ - color = grub_video_map_rgb (0, 0, 0); - grub_video_fill_rect (color, 0, 0, mode_info.width, mode_info.height); + grub_video_fill_rect (grub_video_map_rgb (0, 0, 0), + 0, 0, mode_info.width, mode_info.height); bitmap = 0; + /* Select the font to use. */ + char *font_name = grub_env_get ("gfxterm_font"); + if (!font_name) + font_name = ""; /* Allow fallback to any font. */ + /* Leave borders for virtual screen. */ - width = mode_info.width - (2 * DEFAULT_BORDER_WIDTH); - height = mode_info.height - (2 * DEFAULT_BORDER_WIDTH); - - /* Create virtual screen. */ - if (grub_virtual_screen_setup (DEFAULT_BORDER_WIDTH, DEFAULT_BORDER_WIDTH, - width, height) != GRUB_ERR_NONE) + if (init_window (GRUB_VIDEO_RENDER_TARGET_DISPLAY, + 0, 0, mode_info.width, mode_info.height, + font_name, + DEFAULT_BORDER_WIDTH) != GRUB_ERR_NONE) { grub_video_restore (); return grub_errno; } - /* Mark whole screen as dirty. */ - dirty_region_reset (); - dirty_region_add (0, 0, mode_info.width, mode_info.height); - return (grub_errno = GRUB_ERR_NONE); } +static void +destroy_window (void) +{ + if (bitmap) + { + grub_video_bitmap_destroy (bitmap); + bitmap = 0; + } + + repaint_callback = 0; + grub_virtual_screen_free (); +} + +void +grub_gfxterm_destroy_window (void) +{ + if (--refcount == 0) + destroy_window (); +} + static grub_err_t grub_gfxterm_fini (void) { - if (bitmap) + /* Don't destroy an explicitly initialized terminal instance when it is + unset as the current terminal. */ + if (--refcount == 0) { - grub_video_bitmap_destroy (bitmap); - bitmap = 0; + destroy_window (); + grub_video_restore (); } - grub_virtual_screen_free (); - - grub_video_restore (); - - return GRUB_ERR_NONE; + return (grub_errno = GRUB_ERR_NONE); } static void @@ -509,9 +375,15 @@ unsigned int width, unsigned int height) { grub_video_color_t color; - - grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY); - + grub_video_rect_t saved_view; + + grub_video_set_active_render_target (render_target); + /* Save viewport and set it to our window. */ + grub_video_get_viewport ((unsigned *) &saved_view.x, + (unsigned *) &saved_view.y, + (unsigned *) &saved_view.width, + (unsigned *) &saved_view.height); + grub_video_set_viewport (window.x, window.y, window.width, window.height); if (bitmap) { @@ -578,6 +450,14 @@ y - virtual_screen.offset_y, width, height); } + + /* Restore saved viewport. */ + grub_video_set_viewport (saved_view.x, saved_view.y, + saved_view.width, saved_view.height); + grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY); + + if (repaint_callback) + repaint_callback (x, y, width, height); } static void @@ -661,11 +541,12 @@ write_char (void) { struct grub_colored_char *p; - struct grub_font_glyph glyph; + struct grub_font_glyph *glyph; grub_video_color_t color; grub_video_color_t bgcolor; unsigned int x; unsigned int y; + int ascent; /* Find out active character. */ p = (virtual_screen.text_buffer @@ -675,7 +556,8 @@ p -= p->index; /* Get glyph for character. */ - grub_font_get_glyph (p->code, &glyph); + glyph = grub_font_get_glyph (virtual_screen.font, p->code); + ascent = grub_font_get_ascent (virtual_screen.font); color = p->fg_color; bgcolor = p->bg_color; @@ -685,13 +567,13 @@ /* Render glyph to text layer. */ grub_video_set_active_render_target (text_layer); - grub_video_fill_rect (bgcolor, x, y, glyph.width, glyph.height); - grub_video_blit_glyph (&glyph, color, x, y); + grub_video_fill_rect (bgcolor, x, y, glyph->width, glyph->height); + grub_video_blit_glyph (glyph, color, x, y + ascent); grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY); /* Mark character to be drawn. */ dirty_region_add (virtual_screen.offset_x + x, virtual_screen.offset_y + y, - glyph.width, glyph.height); + glyph->width, glyph->height); } static void @@ -705,7 +587,8 @@ /* Determine cursor properties and position on text layer. */ x = virtual_screen.cursor_x * virtual_screen.char_width; - y = ((virtual_screen.cursor_y + 1) * virtual_screen.char_height) - 3; + y = (virtual_screen.cursor_y * virtual_screen.char_height + + grub_font_get_ascent (virtual_screen.font)); width = virtual_screen.char_width; height = 2; @@ -769,7 +652,16 @@ dirty_region_add_virtualscreen (); } else - { + { + grub_video_rect_t saved_view; + grub_video_set_active_render_target (render_target); + /* Save viewport and set it to our window. */ + grub_video_get_viewport ((unsigned *) &saved_view.x, + (unsigned *) &saved_view.y, + (unsigned *) &saved_view.width, + (unsigned *) &saved_view.height); + grub_video_set_viewport (window.x, window.y, window.width, window.height); + /* Clear new border area. */ grub_video_fill_rect (color, virtual_screen.offset_x, virtual_screen.offset_y, @@ -778,10 +670,18 @@ /* Scroll physical screen. */ grub_video_scroll (color, 0, -virtual_screen.char_height); + /* Restore saved viewport. */ + grub_video_set_viewport (saved_view.x, saved_view.y, + saved_view.width, saved_view.height); + grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY); + /* Draw cursor if visible. */ if (virtual_screen.cursor_state) write_cursor (); } + + if (repaint_callback) + repaint_callback (window.x, window.y, window.width, window.height); } static void @@ -822,14 +722,18 @@ } else { - struct grub_font_glyph glyph; + struct grub_font_glyph *glyph; struct grub_colored_char *p; + unsigned char_width; /* Get properties of the character. */ - grub_font_get_glyph (c, &glyph); + glyph = grub_font_get_glyph (virtual_screen.font, c); + + /* TODO [CDB] Fix wide characters. Bi-width font? */ + char_width = 1; /* If we are about to exceed line length, wrap to next line. */ - if (virtual_screen.cursor_x + glyph.char_width > virtual_screen.columns) + if (virtual_screen.cursor_x + char_width > virtual_screen.columns) grub_putchar ('\n'); /* Find position on virtual screen, and fill information. */ @@ -839,18 +743,18 @@ p->code = c; p->fg_color = virtual_screen.fg_color; p->bg_color = virtual_screen.bg_color; - p->width = glyph.char_width - 1; + p->width = char_width - 1; p->index = 0; /* If we have large glyph, add fixup info. */ - if (glyph.char_width > 1) + if (char_width > 1) { unsigned i; - for (i = 1; i < glyph.char_width; i++) + for (i = 1; i < char_width; i++) { p[i].code = ' '; - p[i].width = glyph.char_width - 1; + p[i].width = char_width - 1; p[i].index = i; } } @@ -859,7 +763,7 @@ write_char (); /* Make sure we scroll screen when needed and wrap line correctly. */ - virtual_screen.cursor_x += glyph.char_width; + virtual_screen.cursor_x += char_width; if (virtual_screen.cursor_x >= virtual_screen.columns) { virtual_screen.cursor_x = 0; @@ -877,13 +781,17 @@ } static grub_ssize_t -grub_gfxterm_getcharwidth (grub_uint32_t c) +grub_gfxterm_getcharwidth (grub_uint32_t c __attribute__((unused))) { - struct grub_font_glyph glyph; - - grub_font_get_glyph (c, &glyph); - - return glyph.char_width; +#if 0 + struct grub_font_glyph *glyph; + + glyph = grub_font_get_glyph (c); + + return glyph->char_width; +#else + return 1; /* TODO [CDB] Fix wide characters. */ +#endif } static grub_uint16_t @@ -945,7 +853,8 @@ /* Clear text layer. */ grub_video_set_active_render_target (text_layer); color = virtual_screen.bg_color; - grub_video_fill_rect (color, 0, 0, mode_info.width, mode_info.height); + grub_video_fill_rect (color, 0, 0, + virtual_screen.width, virtual_screen.height); grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY); /* Mark virtual screen to be redrawn. */ @@ -1014,8 +923,23 @@ dirty_region_redraw (); } +void +grub_gfxterm_set_repaint_callback (grub_gfxterm_repaint_callback_t func) +{ + repaint_callback = func; +} + +/* Option array indices. */ +#define BACKGROUND_CMD_ARGINDEX_MODE 0 + +static const struct grub_arg_option background_image_cmd_options[] = { + {"mode", 'm', 0, "Background image mode (`stretch', `normal').", 0, + ARG_TYPE_STRING}, + {0, 0, 0, 0, 0, 0} +}; + static grub_err_t -grub_gfxterm_background_image_cmd (struct grub_arg_list *state __attribute__ ((unused)), +grub_gfxterm_background_image_cmd (struct grub_arg_list *state, int argc, char **args) { @@ -1031,7 +955,7 @@ /* Mark whole screen as dirty. */ dirty_region_reset (); - dirty_region_add (0, 0, mode_info.width, mode_info.height); + dirty_region_add (0, 0, window.width, window.height); } /* If filename was provided, try to load that. */ @@ -1042,16 +966,40 @@ if (grub_errno != GRUB_ERR_NONE) return grub_errno; + /* Determine if the bitmap should be scaled to fit the screen. */ + if (!state[BACKGROUND_CMD_ARGINDEX_MODE].set + || grub_strcmp (state[BACKGROUND_CMD_ARGINDEX_MODE].arg, + "stretch") == 0) + { + if (window.width != (int) grub_video_bitmap_get_width (bitmap) + || window.height != (int) grub_video_bitmap_get_height (bitmap)) + { + struct grub_video_bitmap *scaled_bitmap; + grub_video_bitmap_create_scaled (&scaled_bitmap, + window.width, + window.height, + bitmap, + GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST); + if (grub_errno == GRUB_ERR_NONE) + { + /* Replace the original bitmap with the scaled one. */ + grub_video_bitmap_destroy (bitmap); + bitmap = scaled_bitmap; + } + } + } + + /* If bitmap was loaded correctly, display it. */ if (bitmap) { /* Determine bitmap dimensions. */ bitmap_width = grub_video_bitmap_get_width (bitmap); - bitmap_height = grub_video_bitmap_get_width (bitmap); + bitmap_height = grub_video_bitmap_get_height (bitmap); /* Mark whole screen as dirty. */ dirty_region_reset (); - dirty_region_add (0, 0, mode_info.width, mode_info.height); + dirty_region_add (0, 0, window.width, window.height); } } @@ -1082,9 +1030,16 @@ .next = 0 }; +grub_term_t +grub_gfxterm_get_term (void) +{ + return &grub_video_term; +} + GRUB_MOD_INIT(term_gfxterm) { my_mod = mod; + refcount = 0; grub_term_register (&grub_video_term); grub_register_command ("background_image", @@ -1092,7 +1047,7 @@ GRUB_COMMAND_FLAG_BOTH, "background_image", "Load background image for active terminal", - 0); + background_image_cmd_options); } GRUB_MOD_FINI(term_gfxterm) === modified file 'term/i386/pc/vesafb.c' --- term/i386/pc/vesafb.c 2007-12-30 08:52:06 +0000 +++ term/i386/pc/vesafb.c 2008-07-03 14:12:08 +0000 @@ -250,10 +250,11 @@ break; default: - return grub_font_get_glyph (code, bitmap, width); + return grub_font_get_glyph_any (code, bitmap, width); } } + /* TODO [CDB] This is wrong for the new font module. Should it be fixed? */ if (bitmap) grub_memcpy (bitmap, vga_font + code * virtual_screen.char_height, === modified file 'term/i386/pc/vga.c' --- term/i386/pc/vga.c 2008-01-21 15:48:27 +0000 +++ term/i386/pc/vga.c 2008-07-03 14:12:08 +0000 @@ -65,6 +65,7 @@ static struct colored_char text_buf[TEXT_WIDTH * TEXT_HEIGHT]; static unsigned char saved_map_mask; static int page = 0; +static grub_font_t font = 0; #define SEQUENCER_ADDR_PORT 0x3C4 #define SEQUENCER_DATA_PORT 0x3C5 @@ -161,6 +162,9 @@ saved_map_mask = get_map_mask (); set_map_mask (0x0f); set_start_address (PAGE_OFFSET (page)); + font = grub_font_get (""); /* Choose any font, for now. */ + if (!font) + return grub_error (GRUB_ERR_BAD_FONT, "No font loaded."); return GRUB_ERR_NONE; } @@ -185,7 +189,7 @@ write_char (void) { struct colored_char *p = text_buf + xpos + ypos * TEXT_WIDTH; - struct grub_font_glyph glyph; + struct grub_font_glyph *glyph; unsigned char *mem_base; unsigned plane; @@ -194,7 +198,7 @@ p -= p->index; /* Get glyph for character. */ - grub_font_get_glyph (p->code, &glyph); + glyph = grub_font_get_glyph (font, p->code); for (plane = 0x01; plane <= 0x08; plane <<= 1) { @@ -210,17 +214,21 @@ { unsigned i; - for (i = 0; i < glyph.char_width && offset < 32; i++) + unsigned char_width = 1; /* TODO [CDB] Figure out wide characters. */ + /* TODO [CDB] Re-implement glyph drawing for vga module. */ +#if 0 + for (i = 0; i < char_width && offset < 32; i++) { unsigned char fg_mask, bg_mask; - fg_mask = (p->fg_color & plane) ? glyph.bitmap[offset] : 0; - bg_mask = (p->bg_color & plane) ? ~(glyph.bitmap[offset]) : 0; + fg_mask = (p->fg_color & plane) ? glyph->bitmap[offset] : 0; + bg_mask = (p->bg_color & plane) ? ~(glyph->bitmap[offset]) : 0; offset++; if (check_vga_mem (mem + i)) mem[i] = (fg_mask | bg_mask); } +#endif /* 0 */ } } @@ -320,36 +328,37 @@ } else { - struct grub_font_glyph glyph; + struct grub_font_glyph *glyph; struct colored_char *p; + unsigned char_width = 1; - grub_font_get_glyph(c, &glyph); + glyph = grub_font_get_glyph(font, c); - if (xpos + glyph.char_width > TEXT_WIDTH) + if (xpos + char_width > TEXT_WIDTH) grub_putchar ('\n'); p = text_buf + xpos + ypos * TEXT_WIDTH; p->code = c; p->fg_color = fg_color; p->bg_color = bg_color; - p->width = glyph.char_width - 1; + p->width = char_width - 1; p->index = 0; - if (glyph.char_width > 1) + if (char_width > 1) { unsigned i; - for (i = 1; i < glyph.char_width; i++) + for (i = 1; i < char_width; i++) { p[i].code = ' '; - p[i].width = glyph.char_width - 1; + p[i].width = char_width - 1; p[i].index = i; } } write_char (); - xpos += glyph.char_width; + xpos += char_width; if (xpos >= TEXT_WIDTH) { xpos = 0; @@ -381,11 +390,16 @@ static grub_ssize_t grub_vga_getcharwidth (grub_uint32_t c) { +#if 0 struct grub_font_glyph glyph; - grub_font_get_glyph (c, &glyph); + glyph = grub_font_get_glyph (c); return glyph.char_width; +#else + /* TODO [CDB] Glyph wide characters? vga? */ + return 1; +#endif } static grub_uint16_t === added directory 'util/fonttool' === added file 'util/fonttool/build.xml' --- util/fonttool/build.xml 1970-01-01 00:00:00 +0000 +++ util/fonttool/build.xml 2008-07-03 14:08:39 +0000 @@ -0,0 +1,23 @@ + + GRUB 2 font tool. + + + + + + + + + + + + + + + + + + + + === added file 'util/fonttool/fonttool.iml' --- util/fonttool/fonttool.iml 1970-01-01 00:00:00 +0000 +++ util/fonttool/fonttool.iml 2008-07-03 14:08:39 +0000 @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + === added file 'util/fonttool/fonttool.ipr' --- util/fonttool/fonttool.ipr 1970-01-01 00:00:00 +0000 +++ util/fonttool/fonttool.ipr 2008-07-03 14:08:39 +0000 @@ -0,0 +1,306 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + === added directory 'util/fonttool/src' === added directory 'util/fonttool/src/org' === added directory 'util/fonttool/src/org/gnu' === added directory 'util/fonttool/src/org/gnu/grub' === added directory 'util/fonttool/src/org/gnu/grub/fonttool' === added file 'util/fonttool/src/org/gnu/grub/fonttool/BDFFont.java' --- util/fonttool/src/org/gnu/grub/fonttool/BDFFont.java 1970-01-01 00:00:00 +0000 +++ util/fonttool/src/org/gnu/grub/fonttool/BDFFont.java 2008-07-03 14:08:39 +0000 @@ -0,0 +1,24 @@ +package org.gnu.grub.fonttool; + +import java.awt.image.BufferedImage; +import java.util.HashMap; +import java.util.Map; + +public class BDFFont { + private Map glyphs = new HashMap(); + private int height = 0; + + void putGlyph(char ch, BufferedImage glyph) { + glyphs.put(ch, glyph); + if (glyph.getHeight() > height) + height = glyph.getHeight(); + } + + public BufferedImage getGlyph(char c) { + return glyphs.get(c); + } + + public int getHeight() { + return height; + } +} === added file 'util/fonttool/src/org/gnu/grub/fonttool/BDFLoader.java' --- util/fonttool/src/org/gnu/grub/fonttool/BDFLoader.java 1970-01-01 00:00:00 +0000 +++ util/fonttool/src/org/gnu/grub/fonttool/BDFLoader.java 2008-07-03 14:10:25 +0000 @@ -0,0 +1,254 @@ +package org.gnu.grub.fonttool; + +import java.io.*; +import java.util.StringTokenizer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class BDFLoader { + private final BufferedReader in; + private final Font font; + private int maxCharWidth; + private int maxCharHeight; + + BDFLoader(BufferedReader in) { + this.in = in; + this.font = new Font(); + this.maxCharWidth = 0; + this.maxCharHeight = 0; + } + + public static boolean isBDFFile(String filename) { + DataInputStream in = null; + try { + in = new DataInputStream(new FileInputStream(filename)); + final String signature = "STARTFONT "; + byte[] b = new byte[signature.length()]; + in.readFully(b); + in.close(); + + String s = new String(b, "US-ASCII"); + return signature.equals(s); + } catch (IOException e) { + if (in != null) { + try { + in.close(); + } catch (IOException e1) { + // Ignore. + } + } + return false; + } + } + + public static Font loadFontResource(String resourceName) throws IOException { + InputStream in = BDFLoader.class.getClassLoader().getResourceAsStream(resourceName); + if (in == null) + throw new FileNotFoundException("Font resource " + resourceName + " not found"); + return loadFontFromStream(in); + } + + public static Font loadFontFile(String filename) throws IOException { + InputStream in = new FileInputStream(filename); + return loadFontFromStream(in); + } + + private static Font loadFontFromStream(InputStream inStream) throws IOException { + BufferedReader in; + try { + in = new BufferedReader(new InputStreamReader(inStream, "ISO-8859-1")); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("Encoding not supported: " + e.getMessage(), e); + } + return new BDFLoader(in).loadFont(); + } + + private Font loadFont() throws IOException { + loadFontInfo(); + while (loadChar()) { + /* Loop. */ + } + + font.setMaxCharWidth(maxCharWidth); + font.setMaxCharHeight(maxCharHeight); + return font; + } + + private void loadFontInfo() throws IOException { + final Pattern stringSettingPattern = Pattern.compile("^(\\w+)\\s+\"([^\"]+)\"$"); + String line; + // Load the global font information that appears before CHARS. + final int UNSET = Integer.MIN_VALUE; + font.setAscent(UNSET); + font.setDescent(UNSET); + font.setFamily("Unknown"); + font.setBold(false); + font.setItalic(false); + font.setPointSize(Font.UNKNOWN_POINT_SIZE); + do { + line = in.readLine(); + if (line == null) + throw new IOException("BDF format error: end of file while " + + "reading global font information"); + + StringTokenizer st = new StringTokenizer(line); + if (st.hasMoreTokens()) { + String name = st.nextToken(); + if (name.equals("FONT_ASCENT")) { + if (!st.hasMoreTokens()) + throw new IOException("BDF format error: " + + "no tokens after " + name); + font.setAscent(Integer.parseInt(st.nextToken())); + } else if (name.equals("FONT_DESCENT")) { + if (!st.hasMoreTokens()) + throw new IOException("BDF format error: " + + "no tokens after " + name); + font.setDescent(Integer.parseInt(st.nextToken())); + } else if (name.equals("POINT_SIZE")) { + if (!st.hasMoreTokens()) + throw new IOException("BDF format error: " + + "no tokens after " + name); + // Divide by 10, since it is stored X10. + font.setPointSize(Integer.parseInt(st.nextToken()) / 10); + } else if (name.equals("FAMILY_NAME")) { + Matcher matcher = stringSettingPattern.matcher(line); + if (!matcher.matches()) + throw new IOException("BDF format error: " + + "line doesn't match string " + + "setting pattern: " + line); + font.setFamily(matcher.group(2)); + } else if (name.equals("WEIGHT_NAME")) { + Matcher matcher = stringSettingPattern.matcher(line); + if (!matcher.matches()) + throw new IOException("BDF format error: " + + "line doesn't match string " + + "setting pattern: " + line); + String weightName = matcher.group(2); + font.setBold("bold".equalsIgnoreCase(weightName)); + } else if (name.equals("SLANT")) { + Matcher matcher = stringSettingPattern.matcher(line); + if (!matcher.matches()) + throw new IOException("BDF format error: " + + "line doesn't match string " + + "setting pattern: " + line); + String slantType = matcher.group(2); + font.setItalic(!"R".equalsIgnoreCase(slantType)); + } else if (name.equals("CHARS")) { + // This is the end of the global font information and + // the beginning of the character definitions. + break; + } else { + // Skip other fields. + } + } + } while (true); + + if (font.getAscent() == UNSET) + throw new IOException("BDF format error: no FONT_ASCENT property"); + if (font.getDescent() == UNSET) + throw new IOException("BDF format error: no FONT_DESCENT property"); + } + + private boolean loadChar() throws IOException { + String line; + // Find start of character + do { + line = in.readLine(); + if (line == null) + return false; + StringTokenizer st = new StringTokenizer(line); + if (st.hasMoreTokens() && st.nextToken().equals("STARTCHAR")) { + if (!st.hasMoreTokens()) + throw new IOException("BDF format error: no character name after STARTCHAR"); + break; + } + } while (true); + + // Find properties + final int UNSET = Integer.MIN_VALUE; + int codePoint = UNSET; + int bbx = UNSET; + int bby = UNSET; + int bbox = UNSET; + int bboy = UNSET; + int dwidth = UNSET; + do { + line = in.readLine(); + if (line == null) + return false; + StringTokenizer st = new StringTokenizer(line); + if (st.hasMoreTokens()) { + String field = st.nextToken(); + if (field.equals("ENCODING")) { + if (!st.hasMoreTokens()) + throw new IOException("BDF format error: no encoding # after ENCODING"); + String codePointStr = st.nextToken(); + codePoint = Integer.parseInt(codePointStr); + } else if (field.equals("BBX")) { + if (!st.hasMoreTokens()) + throw new IOException("BDF format error: no tokens after BBX"); + bbx = Integer.parseInt(st.nextToken()); + bby = Integer.parseInt(st.nextToken()); + bbox = Integer.parseInt(st.nextToken()); + bboy = Integer.parseInt(st.nextToken()); + } else if (field.equals("DWIDTH")) { + if (!st.hasMoreTokens()) + throw new IOException("BDF format error: no tokens after DWIDTH"); + dwidth = Integer.parseInt(st.nextToken()); + int dwidthY = Integer.parseInt(st.nextToken()); + // The DWIDTH Y value should be zero for any normal font. + if (dwidthY != 0) { + throw new IOException("BDF format error: dwidth Y value" + + "is nonzero (" + dwidthY + ") " + + "for char " + codePoint + "."); + } + } else if (field.equals("BITMAP")) { + break; // now read the bitmap + } + } + } while (true); + + if (codePoint == UNSET) + throw new IOException("BDF format error: " + + "no code point set"); + if (bbx == UNSET || bby == UNSET) + throw new IOException("BDF format error: " + + "bbx/bby missing: " + bbx + ", " + bby + + " for char " + codePoint); + + if (bbox == UNSET || bboy == UNSET) + throw new IOException("BDF format error: " + + "bbox/bboy missing: " + bbox + ", " + bboy + + " for char " + codePoint); + + if (dwidth == UNSET) + throw new IOException("BDF format error: " + + "dwidth missing for char " + codePoint); + + final int glyphWidth = bbx; + final int glyphHeight = bby; + if (glyphWidth > maxCharWidth) + maxCharWidth = glyphWidth; + if (glyphHeight > maxCharHeight) + maxCharHeight = glyphHeight; + + // Read the bitmap + Glyph glyph = new Glyph(codePoint, glyphWidth, glyphHeight, bbox, bboy, dwidth); + for (int y = 0; y < glyphHeight; y++) { + line = in.readLine(); + if (line == null) + return false; + for (int b = 0; b < line.length(); b++) { + int v = Integer.parseInt(Character.toString(line.charAt(b)), 16); + for (int x = b * 4, i = 0; i < 4 && x < glyphWidth; x++, i++) { + boolean set = (v & 0x8) != 0; + v <<= 1; + glyph.setPixel(x, y, set); + } + } + } + + font.putGlyph(codePoint, glyph); + return true; + } +} === added file 'util/fonttool/src/org/gnu/grub/fonttool/CharDefs.java' --- util/fonttool/src/org/gnu/grub/fonttool/CharDefs.java 1970-01-01 00:00:00 +0000 +++ util/fonttool/src/org/gnu/grub/fonttool/CharDefs.java 2008-07-03 14:10:25 +0000 @@ -0,0 +1,135 @@ +package org.gnu.grub.fonttool; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.TreeMap; + +class CharDefs { + private boolean debug = "1".equals(System.getProperty("fonttool.debug")); + private TreeMap glyphs; + private ByteArrayOutputStream charDefsData; + private int maxCharWidth; + private int maxCharHeight; + private HashMap charIndex; + + public CharDefs(Font font) { + this.glyphs = font.getGlyphs(); + this.charIndex = null; + this.charDefsData = null; + + calculateMaxSizes(); + } + + private void calculateMaxSizes() { + maxCharWidth = 0; + maxCharHeight = 0; + for (Glyph glyph : glyphs.values()) { + final int w = glyph.getWidth(); + final int h = glyph.getHeight(); + if (w > maxCharWidth) + maxCharWidth = w; + if (h > maxCharHeight) + maxCharHeight = h; + } + } + + void buildDefinitions() { + charIndex = new HashMap(); + HashMap charDefIndex = new HashMap(); + charDefsData = new ByteArrayOutputStream(); + DataOutputStream charDefs = new DataOutputStream(charDefsData); + try { + // Loop through all the glyphs, writing the glyph data to the + // in-memory byte stream, collapsing duplicate glyphs, and + // constructing index information. + for (Glyph glyph : glyphs.values()) { + CharDef charDef = new CharDef(glyph.getWidth(), + glyph.getHeight(), + glyph.getBitmap()); + + if (charDefIndex.containsKey(charDef)) { + // Use already-written glyph. + if (debug) + System.out.printf("Duplicate glyph for character U+%04X%n", + glyph.getCodePoint()); + final int charOffset = charDefIndex.get(charDef).intValue(); + final CharStorageInfo info = + new CharStorageInfo(glyph.getCodePoint(), charOffset); + charIndex.put(glyph.getCodePoint(), info); + } else { + // Write glyph data. + final int charOffset = charDefs.size(); + final CharStorageInfo info = + new CharStorageInfo(glyph.getCodePoint(), charOffset); + charIndex.put(glyph.getCodePoint(), info); + + charDefIndex.put(charDef, (long) charOffset); + + charDefs.writeShort(glyph.getWidth()); + charDefs.writeShort(glyph.getHeight()); + charDefs.writeShort(glyph.getBbox()); + charDefs.writeShort(glyph.getBboy()); + charDefs.writeShort(glyph.getDeviceWidth()); + charDefs.write(glyph.getBitmap()); + } + } + } catch (IOException e) { + throw new RuntimeException("Error writing to in-memory byte stream", e); + } + } + + public int getMaxCharWidth() { + return maxCharWidth; + } + + public int getMaxCharHeight() { + return maxCharHeight; + } + + public HashMap getCharIndex() { + if (charIndex == null) throw new IllegalStateException(); + return charIndex; + } + + public byte[] getDefinitionData() { + if (charDefsData == null) throw new IllegalStateException(); + return charDefsData.toByteArray(); + } + + + private static class CharDef { + private final int width; + private final int height; + private final byte[] data; + + public CharDef(int width, int height, byte[] data) { + this.width = width; + this.height = height; + this.data = data; + } + + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + CharDef charDef = (CharDef) o; + + if (height != charDef.height) return false; + if (width != charDef.width) return false; + if (!Arrays.equals(data, charDef.data)) return false; + + return true; + } + + public int hashCode() { + int result; + result = width; + result = 31 * result + height; + result = 31 * result + Arrays.hashCode(data); + return result; + } + } +} === added file 'util/fonttool/src/org/gnu/grub/fonttool/CharMatrix.java' --- util/fonttool/src/org/gnu/grub/fonttool/CharMatrix.java 1970-01-01 00:00:00 +0000 +++ util/fonttool/src/org/gnu/grub/fonttool/CharMatrix.java 2008-07-03 14:10:25 +0000 @@ -0,0 +1,139 @@ +package org.gnu.grub.fonttool; + +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionAdapter; +import java.util.HashMap; +import java.util.Map; +import javax.swing.*; + +public class CharMatrix extends JComponent { + private Font charFont; + private int zoom = 1; + private int startingChar; + private Map glyphLocations = new HashMap(); + private GlyphSelectionListener glyphSelectionListener; + private Point mouseLocation; + + public interface GlyphSelectionListener { + void glyphSelected(int codePoint); + } + + public CharMatrix() { + addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent e) { + int codePoint = getCodePointForGlyphAt(e.getPoint()); + if (codePoint != -1) { + if (glyphSelectionListener != null) + glyphSelectionListener.glyphSelected(codePoint); + } + } + + public void mouseExited(MouseEvent e) { + mouseLocation = null; + repaint(); + } + }); + + addMouseMotionListener(new MouseMotionAdapter() { + public void mouseMoved(MouseEvent e) { + mouseLocation = e.getPoint(); + repaint(); + } + }); + } + + public void setGlyphSelectionListener(GlyphSelectionListener listener) { + this.glyphSelectionListener = listener; + } + + public Font getCharFont() { + return charFont; + } + + public void setCharFont(Font font) { + this.charFont = font; + repaint(); + } + + public int getZoom() { + return zoom; + } + + public void setZoom(int zoom) { + this.zoom = zoom; + repaint(); + } + + public int getStartingChar() { + return startingChar; + } + + public void setStartingChar(int startingChar) { + this.startingChar = startingChar; + repaint(); + } + + public int getCodePointForGlyphAt(Point p) { + for (Rectangle rectangle : glyphLocations.keySet()) { + if (rectangle.contains(p)) + return glyphLocations.get(rectangle); + } + return -1; + } + + protected void paintComponent(Graphics g) { + glyphLocations.clear(); + + if (charFont == null) + return; + + int w = getWidth(); + int h = getHeight(); + int maxCharWidth = charFont.getMaxCharWidth(); + int maxCharHeight = charFont.getMaxCharHeight(); + int space = 2; + int charsPerRow = w / (maxCharWidth + space); + int rows = h / (maxCharHeight + space); + + Rectangle highlightBounds = null; + int codePoint = startingChar; + int y = 3 + charFont.getAscent(); // Point y to the baseline. + for (int row = 0; row < rows; row++) { + int x = 3; + for (int col = 0; col < charsPerRow; col++) { + Glyph glyph = charFont.getGlyph(codePoint); + + if (glyph != null) { + Rectangle charBounds = + CharView.drawGlyph(g, charFont, glyph, x, y, zoom, getForeground()); + // Expand the bounding box for a bigger clickable area. + final int boundsPad = 1; + charBounds.x -= boundsPad; + charBounds.y -= boundsPad; + charBounds.width += 2 * boundsPad; + charBounds.height += 2 * boundsPad; + glyphLocations.put(charBounds, codePoint); + if (mouseLocation != null && charBounds.contains(mouseLocation)) { + highlightBounds = charBounds; + } + } + x += maxCharWidth + space; + codePoint++; + } + + y += maxCharHeight + space; + } + + if (highlightBounds != null) { + g.setColor(Color.blue); + Graphics2D g2 = (Graphics2D) g; + g2.setStroke(new BasicStroke(3f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 2f)); + g2.draw(new Rectangle(highlightBounds.x - 2, highlightBounds.y - 2, + highlightBounds.width + 3, highlightBounds.height + 3)); + g2.setStroke(new BasicStroke(1f)); + } + + } +} === added file 'util/fonttool/src/org/gnu/grub/fonttool/CharStorageInfo.java' --- util/fonttool/src/org/gnu/grub/fonttool/CharStorageInfo.java 1970-01-01 00:00:00 +0000 +++ util/fonttool/src/org/gnu/grub/fonttool/CharStorageInfo.java 2008-07-03 14:08:39 +0000 @@ -0,0 +1,19 @@ +package org.gnu.grub.fonttool; + +class CharStorageInfo { + private final int codePoint; + private final int fileOffset; + + public CharStorageInfo(int codePoint, int fileOffset) { + this.codePoint = codePoint; + this.fileOffset = fileOffset; + } + + public int getCodePoint() { + return codePoint; + } + + public int getFileOffset() { + return fileOffset; + } +} === added file 'util/fonttool/src/org/gnu/grub/fonttool/CharView.java' --- util/fonttool/src/org/gnu/grub/fonttool/CharView.java 1970-01-01 00:00:00 +0000 +++ util/fonttool/src/org/gnu/grub/fonttool/CharView.java 2008-07-03 14:10:25 +0000 @@ -0,0 +1,101 @@ +package org.gnu.grub.fonttool; + +import java.awt.*; +import java.util.Formatter; +import javax.swing.*; + +public class CharView extends JComponent { + private Font charFont = null; + private Glyph glyph = null; + private int zoom = 1; + + public Font getCharFont() { + return charFont; + } + + public void setCharFont(Font charFont) { + this.charFont = charFont; + } + + public Glyph getGlyph() { + return glyph; + } + + public void setGlyph(Glyph glyph) { + this.glyph = glyph; + repaint(); + } + + public void setZoom(int zoom) { + this.zoom = zoom; + } + + public Dimension getPreferredSize() { + if (glyph == null) + return new Dimension(1, 1); + return new Dimension(glyph.getWidth() * zoom, glyph.getHeight() * zoom); + } + + protected void paintComponent(Graphics g) { + if (glyph == null) { + // Draw a gray 'X' indicating no character selected. + g.setColor(Color.gray); + g.drawLine(0, 0, getWidth() - 1, getHeight() - 1); + g.drawLine(getWidth(), 0, 0, getHeight() - 1); + return; + } + + final int x0 = (getWidth() - glyph.getWidth() * zoom) / 2; + final int y0 = getHeight() / 2; + drawGlyph(g, charFont, glyph, x0, y0, zoom, getForeground()); + + g.setFont(new java.awt.Font("SansSerif", java.awt.Font.PLAIN, 12)); + FontMetrics fm = g.getFontMetrics(); + Formatter f = new Formatter(); + f.format("U+%04X", glyph.getCodePoint()); + String str = f.toString(); + g.drawString(str, + (getWidth() - fm.stringWidth(str)) / 2, + getHeight() - fm.getHeight()); + } + + /** + * Draw the specified glyph with its baseline at y0 and the horizontal + * start at x0. + */ + static Rectangle drawGlyph(Graphics g, Font font, Glyph glyph, int startX, int baselineY, int zoom, Color fg) { + // Adjust (x0, y0) so that it puts the bitmap at the right location. + final int offsetX = zoom * glyph.getBbox(); + final int offsetY = zoom * glyph.getBboy(); + final int bitmapLeft = startX + offsetX; + final int bitmapBottom = baselineY - offsetY; + final int bitmapTop = bitmapBottom - zoom * glyph.getHeight(); + + // Draw a gray box around the glyph bounds. + g.setColor(Color.lightGray); + g.drawRect(bitmapLeft - 1, bitmapTop - 1, + glyph.getWidth() * zoom + 1, zoom * glyph.getHeight() + 1); + + // Draw the baseline + g.setColor(new Color(110, 110, 255)); + g.drawLine(startX, baselineY, startX + zoom * glyph.getDeviceWidth(), baselineY); + + // Draw the glyph. + g.setColor(fg); + byte[] bitmap = glyph.getBitmap(); + int bitIndex = 0; + for (int y = 0; y < glyph.getHeight(); y++) { + for (int x = 0; x < glyph.getWidth(); x++, bitIndex++) { + int byteIndex = bitIndex / 8; + int bitShift = bitIndex % 8; + byte b = bitmap[byteIndex]; + boolean bitValue = (b & (0x80 >>> bitShift)) != 0; + if (bitValue) { + g.fillRect(bitmapLeft + x * zoom, bitmapTop + y * zoom, zoom, zoom); + } + } + } + + return new Rectangle(bitmapLeft, bitmapTop, zoom * glyph.getWidth(), zoom * glyph.getHeight()); + } +} === added file 'util/fonttool/src/org/gnu/grub/fonttool/Converter.java' --- util/fonttool/src/org/gnu/grub/fonttool/Converter.java 1970-01-01 00:00:00 +0000 +++ util/fonttool/src/org/gnu/grub/fonttool/Converter.java 2008-07-03 14:08:39 +0000 @@ -0,0 +1,78 @@ +package org.gnu.grub.fonttool; + +import java.io.IOException; + +/** + * Program to convert BDF fonts into PFF2 fonts for use with GRUB. + */ +public class Converter { + public static void main(String[] args) { + if (args.length < 1) { + printUsageAndExit(); + } + + String in = null; + String out = null; + + try { + for (String arg : args) { + if (arg.startsWith("--")) { + String option; + String value; + int equalsPos = arg.indexOf('='); + if (equalsPos < 0) { + option = arg.substring(2); + value = null; + } else { + option = arg.substring(2, equalsPos); + value = arg.substring(equalsPos + 1); + } + + if ("in".equals(option)) { + if (value == null) + throw new CommandLineException(option + " option requires a value."); + in = value; + } else if ("out".equals(option)) { + if (value == null) + throw new CommandLineException(option + " option requires a value."); + out = value; + } + } else { + throw new CommandLineException("Non-option argument `" + arg + "'."); + } + } + if (in == null || out == null) { + throw new CommandLineException("Both --in=X and --out=Y must be specified."); + } + } catch (CommandLineException e) { + System.err.println("Error: " + e.getMessage()); + System.exit(1); + } + + try { + // Read BDF. + Font font = BDFLoader.loadFontFile(in); + + // Write PFF2. + new PFF2Writer(out).writeFont(font); + } catch (IOException e) { + System.err.println("I/O error converting font: " + e); + e.printStackTrace(); + System.exit(1); + } + } + + private static class CommandLineException extends Exception { + public CommandLineException(String message) { + super(message); + } + } + + private static void printUsageAndExit() { + System.err.println("GNU GRUB Font Conversion Tool"); + System.err.println(); + System.err.println("Usage: Converter --in=IN.bdf --out=OUT.pf2"); + System.err.println(); + System.exit(1); + } +} === added file 'util/fonttool/src/org/gnu/grub/fonttool/FileFormatException.java' --- util/fonttool/src/org/gnu/grub/fonttool/FileFormatException.java 1970-01-01 00:00:00 +0000 +++ util/fonttool/src/org/gnu/grub/fonttool/FileFormatException.java 2008-07-03 14:08:39 +0000 @@ -0,0 +1,9 @@ +package org.gnu.grub.fonttool; + +import java.io.IOException; + +public class FileFormatException extends IOException { + public FileFormatException(String msg) { + super(msg); + } +} === added file 'util/fonttool/src/org/gnu/grub/fonttool/Font.java' --- util/fonttool/src/org/gnu/grub/fonttool/Font.java 1970-01-01 00:00:00 +0000 +++ util/fonttool/src/org/gnu/grub/fonttool/Font.java 2008-07-03 14:10:25 +0000 @@ -0,0 +1,106 @@ +package org.gnu.grub.fonttool; + +import java.util.TreeMap; + +public class Font { + public static final int UNKNOWN_POINT_SIZE = -1; + + private TreeMap glyphs; + private String family; + private boolean bold; + private boolean italic; + private int pointSize; + private int maxCharWidth; + private int maxCharHeight; + private int ascent; + private int descent; + + public Font() { + glyphs = new TreeMap(); + } + + public String getFamily() { + return family; + } + + public void setFamily(String family) { + this.family = family; + } + + public boolean isBold() { + return bold; + } + + public void setBold(boolean bold) { + this.bold = bold; + } + + public boolean isItalic() { + return italic; + } + + public void setItalic(boolean italic) { + this.italic = italic; + } + + public int getPointSize() { + return pointSize; + } + + public void setPointSize(int pointSize) { + this.pointSize = pointSize; + } + + public int getMaxCharWidth() { + return maxCharWidth; + } + + public void setMaxCharWidth(int maxCharWidth) { + this.maxCharWidth = maxCharWidth; + } + + public int getMaxCharHeight() { + return maxCharHeight; + } + + public void setMaxCharHeight(int maxCharHeight) { + this.maxCharHeight = maxCharHeight; + } + + public int getAscent() { + return ascent; + } + + public void setAscent(int ascent) { + this.ascent = ascent; + } + + public int getDescent() { + return descent; + } + + public void setDescent(int descent) { + this.descent = descent; + } + + public void putGlyph(int codePoint, Glyph glyph) { + glyphs.put(codePoint, glyph); + } + + public TreeMap getGlyphs() { + return glyphs; + } + + public Glyph getGlyph(int codePoint) { + return glyphs.get(codePoint); + } + + public String getStandardName() { + StringBuilder name = new StringBuilder(getFamily()); + if (isBold()) name.append(" Bold"); + if (isItalic()) name.append(" Italic"); + name.append(' '); + name.append(getPointSize()); + return name.toString(); + } +} === added file 'util/fonttool/src/org/gnu/grub/fonttool/Glyph.java' --- util/fonttool/src/org/gnu/grub/fonttool/Glyph.java 1970-01-01 00:00:00 +0000 +++ util/fonttool/src/org/gnu/grub/fonttool/Glyph.java 2008-07-03 14:10:25 +0000 @@ -0,0 +1,83 @@ +package org.gnu.grub.fonttool; + +public class Glyph { + private final int codePoint; + private final int width; + private final int height; + + // These define the amounts to shift the character bitmap by + // before drawing it. See + // http://www.adobe.com/devnet/font/pdfs/5005.BDF_Spec.pdf + // and + // http://www.linuxbabble.com/documentation/x/bdf/ + // for explanatory figures. + private final int bbox; + private final int bboy; + + // Number of pixels to advance horizontally from this character's origin + // to the origin of the next character. + private final int deviceWidth; + + // Row-major order, no padding. Rows can break within a byte. + // MSb is first (leftmost/uppermost) pixel. + private final byte[] bitmap; + + public Glyph(int codePoint, int width, int height, int bbox, int bboy, int deviceWidth) { + this(codePoint, width, height, bbox, bboy, deviceWidth, + new byte[(width * height + 7) / 8]); + } + + public Glyph(int codePoint, int width, int height, + int bbox, int bboy, int deviceWidth, + byte[] bitmap) { + this.codePoint = codePoint; + this.width = width; + this.height = height; + this.bboy = bboy; + this.bbox = bbox; + this.deviceWidth = deviceWidth; + this.bitmap = bitmap; + } + + public void setPixel(int x, int y, boolean value) { + if (x < 0 || y < 0 || x >= width || y >= height) + throw new IllegalArgumentException( + "Invalid pixel location (" + x + ", " + y + ") for " + + width + "x" + height + " glyph"); + + int bitIndex = y * width + x; + int byteIndex = bitIndex / 8; + int bitPos = bitIndex % 8; + int v = value ? 0x80 >>> bitPos : 0; + int mask = ~(0x80 >>> bitPos); + bitmap[byteIndex] = (byte) ((bitmap[byteIndex] & mask) | v); + } + + public int getCodePoint() { + return codePoint; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public int getBbox() { + return bbox; + } + + public int getBboy() { + return bboy; + } + + public int getDeviceWidth() { + return deviceWidth; + } + + public byte[] getBitmap() { + return bitmap; + } +} === added file 'util/fonttool/src/org/gnu/grub/fonttool/PFF2Loader.java' --- util/fonttool/src/org/gnu/grub/fonttool/PFF2Loader.java 1970-01-01 00:00:00 +0000 +++ util/fonttool/src/org/gnu/grub/fonttool/PFF2Loader.java 2008-07-03 14:10:25 +0000 @@ -0,0 +1,203 @@ +package org.gnu.grub.fonttool; + +import java.io.EOFException; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.charset.Charset; + +/** + * Load a PFF2 font completely into memory as an instance of Font. + */ +public class PFF2Loader { + private static final int debug + = Integer.parseInt(System.getProperty("fonttool.debug", "0")); + + private RandomAccessFile f; + private Font font; + + private PFF2Loader(String filename) throws FileNotFoundException { + f = new RandomAccessFile(filename, "r"); + font = new Font(); + } + + public static boolean isPFF2File(String filename) { + RandomAccessFile f = null; + boolean isFont = false; + try { + f = new RandomAccessFile(filename, "r"); + byte[] sectionNameBytes = new byte[4]; + f.readFully(sectionNameBytes); + String sectionName = new String(sectionNameBytes, "US-ASCII"); + if (sectionName.equals(PFF2Sections.FILE)) { + final String expectedType = "PFF2"; + int len = f.readInt(); + if (len == expectedType.length()) { + byte[] typeBytes = new byte[len]; + f.readFully(typeBytes); + String type = new String(typeBytes, "US-ASCII"); + if (type.equals(expectedType)) { + isFont = true; + } + } + } + f.close(); + } catch (IOException e) { + if (f != null) { + try { + f.close(); + } catch (IOException e1) { + // Ignore. + } + } + } + + return isFont; + } + + public static Font loadFontFile(String filename) throws IOException { + PFF2Loader loader = new PFF2Loader(filename); + loader.loadFont(); + return loader.getFont(); + } + + private void loadFont() throws IOException { + String s; + s = openSection(); + if (!PFF2Sections.FILE.equals(s)) + throw new FileFormatException( + "First PFF2 section must be FILE but is `" + s + "'."); + + s = readCompleteSectionAsString(); + if (!"PFF2".equals(s)) + throw new FileFormatException("Unsupported file type `" + s + "'."); + + font.setFamily("Unnamed"); + font.setPointSize(Font.UNKNOWN_POINT_SIZE); + try { + if (debug >= 1) System.out.println("Loading font information..."); + boolean doneLoadingInfo = false; + while (!doneLoadingInfo) { + s = openSection(); + int sectionLength = f.readInt(); + if (debug >= 2) + System.out.println("Section: " + s + " length: " + sectionLength); + if (s.equals(PFF2Sections.MAX_CHAR_WIDTH)) { + int maxCharWidth = readSectionContentsAsUShort(sectionLength); + font.setMaxCharWidth(maxCharWidth); + } else if (s.equals(PFF2Sections.MAX_CHAR_HEIGHT)) { + int maxCharHeight = readSectionContentsAsUShort(sectionLength); + font.setMaxCharHeight(maxCharHeight); + } else if (s.equals(PFF2Sections.FONT_POINT_SIZE)) { + int pointSize = readSectionContentsAsUShort(sectionLength); + font.setPointSize(pointSize); + } else if (s.equals(PFF2Sections.FONT_ASCENT)) { + int ascent = readSectionContentsAsUShort(sectionLength); + font.setAscent(ascent); + } else if (s.equals(PFF2Sections.FONT_DESCENT)) { + int descent = readSectionContentsAsUShort(sectionLength); + font.setDescent(descent); + } else if (s.equals(PFF2Sections.FONT_NAME)) { + String fontName = readSectionContentsAsString(sectionLength); + if (debug >= 1) + System.out.println("Loaded font: " + fontName); + } else if (s.equals(PFF2Sections.FONT_FAMILY)) { + font.setFamily(readSectionContentsAsString(sectionLength)); + } else if (s.equals(PFF2Sections.FONT_WEIGHT)) { + String weightString = readSectionContentsAsString(sectionLength); + font.setBold(weightString.equalsIgnoreCase("bold")); + } else if (s.equals(PFF2Sections.FONT_SLANT)) { + String slantString = readSectionContentsAsString(sectionLength); + font.setItalic(slantString.equalsIgnoreCase("italic")); + } else if (s.equals(PFF2Sections.CHAR_INDEX)) { + loadCharsFromIndex(sectionLength); + } else if (s.equals(PFF2Sections.REMAINDER_IS_DATA)) { + doneLoadingInfo = true; + } else { + // If the section is not handled, then we just skip it. + if (f.skipBytes(sectionLength) != sectionLength) { + System.err.println("Warning: Could not skip " + + sectionLength + + " bytes for section `" + s + "'."); + } + } + } + } catch (EOFException eofException) { + // End of file; done reading font. + } + f.close(); + } + + private void loadCharsFromIndex(int sectionLength) throws IOException { + long indexSectionEnd = f.getFilePointer() + sectionLength; + try { + while (f.getFilePointer() < indexSectionEnd) { + int codePoint = f.readInt(); + int storageFlags = f.readUnsignedByte(); + int offset = f.readInt(); + + if ((storageFlags & 0x07) == 0) { + Glyph g = loadUncompressedGlyph(codePoint, offset); + font.putGlyph(codePoint, g); + } + } + } catch (EOFException eofException) { + // End of file; simple return. + } + } + + private Glyph loadUncompressedGlyph(int codePoint, int fileOffset) throws IOException { + if (debug >= 3) + System.out.printf("Loading uncompressed glyph U+%04X at %d%n", codePoint, fileOffset); + + long savedPos = f.getFilePointer(); + + f.seek(fileOffset); + int width = f.readUnsignedShort(); + int height = f.readUnsignedShort(); + int offsetX = f.readShort(); + int offsetY = f.readShort(); + int deviceWidth = f.readShort(); + if (debug >= 3) System.out.println(" Size=" + width + "x" + height + + " offset=(" + offsetX + ", " + offsetY + + ") dWidth=" + deviceWidth); + byte[] bitmap = new byte[(width * height + 7) / 8]; + f.readFully(bitmap); + + f.seek(savedPos); + return new Glyph(codePoint, width, height, + offsetX, offsetY, deviceWidth, bitmap); + } + + private int readSectionContentsAsUShort(int sectionLength) throws IOException { + if (sectionLength != 2) + throw new FileFormatException( + "For uint16 section, expected 2 bytes but length is " + + sectionLength + "."); + return f.readUnsignedShort(); + } + + private String openSection() throws IOException { + byte[] b = new byte[4]; + f.readFully(b); + return new String(b, Charset.forName("US-ASCII")); + } + + private String readCompleteSectionAsString() throws IOException { + int n = f.readInt(); + return readSectionContentsAsString(n); + } + + private String readSectionContentsAsString(int n) throws IOException { + StringBuilder buf = new StringBuilder(); + while (n-- != 0) { + char c = (char) (f.readByte() & 0xFF); + buf.append(c); + } + return buf.toString(); + } + + public Font getFont() { + return font; + } +} === added file 'util/fonttool/src/org/gnu/grub/fonttool/PFF2Sections.java' --- util/fonttool/src/org/gnu/grub/fonttool/PFF2Sections.java 1970-01-01 00:00:00 +0000 +++ util/fonttool/src/org/gnu/grub/fonttool/PFF2Sections.java 2008-07-03 14:10:25 +0000 @@ -0,0 +1,19 @@ +package org.gnu.grub.fonttool; + +/** + * Section name constants for the PFF2 file format. + */ +public class PFF2Sections { + static final String FILE = "FILE"; + static final String FONT_NAME = "NAME"; + static final String FONT_FAMILY = "FAMI"; + static final String FONT_WEIGHT = "WEIG"; + static final String FONT_SLANT = "SLAN"; + static final String FONT_POINT_SIZE = "PTSZ"; + static final String MAX_CHAR_WIDTH = "MAXW"; + static final String MAX_CHAR_HEIGHT = "MAXH"; + static final String FONT_ASCENT = "ASCE"; + static final String FONT_DESCENT = "DESC"; + static final String CHAR_INDEX = "CHIX"; + static final String REMAINDER_IS_DATA = "DATA"; +} === added file 'util/fonttool/src/org/gnu/grub/fonttool/PFF2Writer.java' --- util/fonttool/src/org/gnu/grub/fonttool/PFF2Writer.java 1970-01-01 00:00:00 +0000 +++ util/fonttool/src/org/gnu/grub/fonttool/PFF2Writer.java 2008-07-03 14:10:25 +0000 @@ -0,0 +1,133 @@ +package org.gnu.grub.fonttool; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; + +// TODO Add DEFLATE compressed blocks of characters. +public class PFF2Writer { + private RandomAccessFile f; + private String currentSection; + private long currentSectionStart; + + public PFF2Writer(String filename) throws FileNotFoundException { + this.f = new RandomAccessFile(filename, "rw"); + this.currentSection = null; + this.currentSectionStart = -1; + } + + public void writeFont(Font font) throws IOException { + // Write file type ID header. + writeSection(PFF2Sections.FILE, "PFF2"); + + writeSection(PFF2Sections.FONT_NAME, font.getStandardName()); + writeSection(PFF2Sections.FONT_FAMILY, font.getFamily()); + writeSection(PFF2Sections.FONT_WEIGHT, font.isBold() ? "bold" : "normal"); + writeSection(PFF2Sections.FONT_SLANT, font.isItalic() ? "italic" : "normal"); + if (font.getPointSize() != Font.UNKNOWN_POINT_SIZE) + writeShortSection(PFF2Sections.FONT_POINT_SIZE, font.getPointSize()); + + // Construct character definitions. + CharDefs charDefs = new CharDefs(font); + charDefs.buildDefinitions(); + + // Write max character width and height metrics. + writeShortSection(PFF2Sections.MAX_CHAR_WIDTH, charDefs.getMaxCharWidth()); + writeShortSection(PFF2Sections.MAX_CHAR_HEIGHT, charDefs.getMaxCharHeight()); + writeShortSection(PFF2Sections.FONT_ASCENT, font.getAscent()); + writeShortSection(PFF2Sections.FONT_DESCENT, font.getDescent()); + + // Write character index with pointers to the character definitions. + beginSection(PFF2Sections.CHAR_INDEX); + + // Determine the size of the index, so we can properly refer to the + // character definition offset in the index. The actual number of + // bytes written is compared to the calculated value to ensure we + // are correct. + final int indexStart = (int) f.getFilePointer(); + final int calculatedIndexLength = + font.getGlyphs().size() * (4 + 1 + 4); + final int charDefStart = indexStart + calculatedIndexLength + 8; + + for (CharStorageInfo storageInfo : charDefs.getCharIndex().values()) { + f.writeInt(storageInfo.getCodePoint()); + f.writeByte(0); // Storage flags: bits 1..0 = 00b : uncompressed. + f.writeInt(charDefStart + storageInfo.getFileOffset()); + } + + final int indexEnd = (int) f.getFilePointer(); + if (indexEnd - indexStart != calculatedIndexLength) { + throw new RuntimeException("Incorrect index length calculated, calc=" + + calculatedIndexLength + + " actual=" + (indexEnd - indexStart)); + } + endSection(PFF2Sections.CHAR_INDEX); + + f.writeBytes(PFF2Sections.REMAINDER_IS_DATA); + f.writeInt(-1); // Data takes up the rest of the file. + f.write(charDefs.getDefinitionData()); + + f.close(); + } + + private void beginSection(String sectionName) throws IOException { + verifyOkToBeginSection(sectionName); + + f.writeBytes(sectionName); + f.writeInt(-1); // Placeholder for the section length. + currentSection = sectionName; + currentSectionStart = f.getFilePointer(); + } + + private void endSection(String sectionName) throws IOException { + verifyOkToEndSection(sectionName); + + long sectionEnd = f.getFilePointer(); + long sectionLength = sectionEnd - currentSectionStart; + f.seek(currentSectionStart - 4); + f.writeInt((int) sectionLength); + f.seek(sectionEnd); + currentSection = null; + currentSectionStart = -1; + } + + private void verifyOkToBeginSection(String sectionName) { + if (sectionName.length() != 4) + throw new IllegalArgumentException( + "Section names must be 4 characters: `" + sectionName + "'."); + if (currentSection != null) + throw new IllegalStateException( + "Attempt to start `" + sectionName + + "' section before ending the previous section `" + + currentSection + "'."); + } + + private void verifyOkToEndSection(String sectionName) { + if (sectionName.length() != 4) + throw new IllegalStateException("Invalid section name '" + sectionName + + "'; must be 4 characters."); + if (currentSection == null) + throw new IllegalStateException( + "Attempt to end section `" + sectionName + + "' when no section active."); + if (!sectionName.equals(currentSection)) + throw new IllegalStateException( + "Attempt to end `" + sectionName + + "' section during active section `" + + currentSection + "'."); + } + + private void writeSection(String sectionName, String contents) throws IOException { + verifyOkToBeginSection(sectionName); + f.writeBytes(sectionName); + f.writeInt(contents.length()); + f.writeBytes(contents); + } + + private void writeShortSection(String sectionName, int value) throws IOException { + verifyOkToBeginSection(sectionName); + f.writeBytes(sectionName); + f.writeInt(2); + f.writeShort(value); + } +} === added file 'util/fonttool/src/org/gnu/grub/fonttool/Viewer.java' --- util/fonttool/src/org/gnu/grub/fonttool/Viewer.java 1970-01-01 00:00:00 +0000 +++ util/fonttool/src/org/gnu/grub/fonttool/Viewer.java 2008-07-03 14:10:25 +0000 @@ -0,0 +1,135 @@ +package org.gnu.grub.fonttool; + +import java.io.File; +import java.io.IOException; +import javax.swing.*; + +/** + * Program to view fonts. + *

+ * Supports PFF2 and BDF format font files. + */ +public class Viewer { + public static void main(String[] args) { + if (args.length < 1) { + printUsageAndExit(); + } + + boolean verbose = false; + String format = null; + String filename = null; + + try { + for (String arg : args) { + if (arg.startsWith("--")) { + String option; + String value; + int equalsPos = arg.indexOf('='); + if (equalsPos < 0) { + option = arg.substring(2); + value = null; + } else { + option = arg.substring(2, equalsPos); + value = arg.substring(equalsPos + 1); + } + + if ("format".equals(option)) { + if (value == null) + throw new CommandLineException(option + " option requires a value."); + format = value; + } else if ("verbose".equals(option)) { + verbose = true; + } + } else { + if (filename == null) { + filename = arg; + } else { + throw new CommandLineException( + "Extra argument after filename: `" + arg + "'."); + } + } + } + if (filename == null) { + throw new CommandLineException("A filename must be specified."); + } + if (!(format == null || "bdf".equals(format) || "pff2".equals(format))) { + throw new CommandLineException("Invalid format: must be `bdf' or `pff2'."); + } + } catch (CommandLineException e) { + System.err.println("Error: " + e.getMessage()); + System.exit(1); + } + + if (!new File(filename).exists()) { + System.err.println("Error: File `" + filename + "' not found."); + System.exit(1); + } + + if (format == null) { + // Autodetect the format. + if (PFF2Loader.isPFF2File(filename)) + format = "pff2"; + else if (BDFLoader.isBDFFile(filename)) + format = "bdf"; + else { + System.err.println("Error: Unable to detect font file format " + + "for `" + filename + "'."); + System.exit(1); + } + if (verbose) + System.out.println("Detected file format `" + format + "'."); + } + + Font font = null; + try { + if ("bdf".equals(format)) { + if (verbose) + System.out.println("Loading BDF font."); + font = BDFLoader.loadFontFile(filename); + } else if ("pff2".equals(format)) { + if (verbose) + System.out.println("Loading PFF2 font."); + font = PFF2Loader.loadFontFile(filename); + } else { + System.err.println("Error: Bad format `" + format + "'"); + System.exit(1); + } + } catch (IOException e) { + System.err.println("I/O error loading font: " + e); + e.printStackTrace(); + System.exit(1); + } + + if (font == null) { + System.err.println("Error: Font not loaded."); + System.exit(1); + } + + if (verbose) { + System.out.println("Loaded font: '" + font.getStandardName() + "'"); + } + + ViewerFrame f = new ViewerFrame(font); + f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + f.setLocationRelativeTo(null); // Center the frame on screen. + f.setVisible(true); + } + + private static class CommandLineException extends Exception { + public CommandLineException(String message) { + super(message); + } + } + + private static void printUsageAndExit() { + System.err.println("GNU GRUB Font Viewer"); + System.err.println(); + System.err.println("Usage: Viewer [--format=FORMAT] FILE"); + System.err.println(" --format=bdf The font is in BDF format."); + System.err.println(" --format=pff2 The font is in PFF2 format."); + System.err.println(" If no --format option is given, the type will be"); + System.err.println(" automatically detected."); + System.err.println(); + System.exit(1); + } +} === added file 'util/fonttool/src/org/gnu/grub/fonttool/ViewerFrame.java' --- util/fonttool/src/org/gnu/grub/fonttool/ViewerFrame.java 1970-01-01 00:00:00 +0000 +++ util/fonttool/src/org/gnu/grub/fonttool/ViewerFrame.java 2008-07-03 14:08:39 +0000 @@ -0,0 +1,61 @@ +package org.gnu.grub.fonttool; + +import java.awt.*; +import javax.swing.*; +import javax.swing.border.BevelBorder; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + +public class ViewerFrame extends JFrame { + private final Font font; + + private JLabel startingCodePointLabel; + private JSlider startingCodePointSlider; + private CharMatrix charMatrix; + private CharView charView; + private JLabel statusBar; + + public ViewerFrame(Font font) { + this.font = font; + + createComponents(); + buildUI(); + setSize(800, 600); + } + + private void createComponents() { + startingCodePointLabel = new JLabel("Starting Code Point"); + startingCodePointSlider = new JSlider(JSlider.HORIZONTAL, 0, 65535, 0); + startingCodePointSlider.addChangeListener(new ChangeListener() { + public void stateChanged(ChangeEvent e) { + charMatrix.setStartingChar(startingCodePointSlider.getValue()); + } + }); + charMatrix = new CharMatrix(); + charMatrix.setCharFont(font); + charMatrix.setGlyphSelectionListener(new CharMatrix.GlyphSelectionListener() { + public void glyphSelected(int codePoint) { + charView.setGlyph(font.getGlyph(codePoint)); + } + }); + charView = new CharView(); + charView.setZoom(3); + charView.setBorder(new BevelBorder(BevelBorder.RAISED)); + statusBar = new JLabel(); + } + + private void buildUI() { + JComponent topPanel = Box.createVerticalBox(); + topPanel.add(startingCodePointLabel); + topPanel.add(startingCodePointSlider); + + JComponent charPanel = new JPanel(new GridLayout(2, 1)); + charPanel.add(charMatrix); + charPanel.add(charView); + + setLayout(new BorderLayout()); + add(topPanel, BorderLayout.NORTH); + add(charPanel, BorderLayout.CENTER); + add(statusBar, BorderLayout.SOUTH); + } +} === added directory 'util/fonttool/test' === added directory 'util/fonttool/test/org' === added directory 'util/fonttool/test/org/gnu' === added directory 'util/fonttool/test/org/gnu/grub' === added directory 'util/fonttool/test/org/gnu/grub/fonttool' === added file 'util/fonttool/test/org/gnu/grub/fonttool/TestGlyph.java' --- util/fonttool/test/org/gnu/grub/fonttool/TestGlyph.java 1970-01-01 00:00:00 +0000 +++ util/fonttool/test/org/gnu/grub/fonttool/TestGlyph.java 2008-07-03 14:10:25 +0000 @@ -0,0 +1,110 @@ +package org.gnu.grub.fonttool; + +import org.junit.Assert; +import org.junit.Test; + +public class TestGlyph { + @Test + public void testConstructor3x5() { + Glyph g = new Glyph(65, 3, 5, 0, 0, 0); + + // Should be empty upon construction. + assertBitmapEquals("..." + + "..." + + "..." + + "..." + + "...", + g.getBitmap()); + + // Should have ceil(3 * 5 / 8) = ceil(15 / 8) = ceil(1 + 7/8) = 2 bytes. + Assert.assertEquals("Wrong bitmap array size.", + 2, g.getBitmap().length); + } + + @Test + public void testConstructor5x5() { + Glyph g = new Glyph(65, 5, 5, 0, 0, 0); + + // Should be empty upon construction. + assertBitmapEquals("....." + + "....." + + "....." + + "....." + + ".....", + g.getBitmap()); + + // Should have ceil(5 * 5 / 8) = ceil(25 / 8) = ceil(3 + 1/8) = 4 bytes. + Assert.assertEquals("Wrong bitmap array size.", + 4, g.getBitmap().length); + } + + @Test + public void testSetPixel() { + Glyph g = new Glyph(65, 3, 5, 0, 0, 0); + + g.setPixel(0, 0, true); + Assert.assertEquals(0x80, g.getBitmap()[0] & 0xFF); + Assert.assertEquals(0x00, g.getBitmap()[1] & 0xFF); + assertBitmapEquals("X.." + + "..." + + "..." + + "..." + + "...", + g.getBitmap()); + + g.setPixel(2, 1, true); + Assert.assertEquals(0x84, g.getBitmap()[0] & 0xFF); + Assert.assertEquals(0x00, g.getBitmap()[1] & 0xFF); + assertBitmapEquals("X.." + + "..X" + + "..." + + "..." + + "...", + g.getBitmap()); + } + + static void assertBitmapEquals(String expectedBitmap, byte[] actualBitmap) { + Assert.assertNotNull("expected bitmap null", expectedBitmap); + Assert.assertNotNull("actual bitmap null", actualBitmap); + StringBuilder errors = new StringBuilder(); + + int bit = 7; + int byteIndex = -1; + for (int bitIndex = 0; bitIndex < expectedBitmap.length(); bitIndex++) { + char c = expectedBitmap.charAt(bitIndex); + boolean expectedBit; + if (c == '.') { + expectedBit = false; + } else if (c == 'X') { + expectedBit = true; + } else { + throw new IllegalArgumentException("Bad character '" + c + "' in expected bitmap string"); + } + + byteIndex = bitIndex / 8; + byte b = actualBitmap[byteIndex]; + boolean actualBit = (b & (1 << bit)) != 0; + if (actualBit != expectedBit) { + errors.append("[").append(bitIndex) + .append("] wrong (it is ") + .append(actualBit ? "1" : "0").append("). "); + } + + if (bit == 0) { + bit = 7; + } else { + bit--; + } + } + + if (byteIndex != expectedBitmap.length() / 8) { + errors.append("Last byte index wrong, was ") + .append(byteIndex).append(" but expected ") + .append(expectedBitmap.length() - 1); + } + + if (errors.length() != 0) { + Assert.fail("Bitmap wrong: " + errors); + } + } +} === modified file 'util/i386/pc/grub-mkrescue.in' --- util/i386/pc/grub-mkrescue.in 2008-07-12 14:40:50 +0000 +++ util/i386/pc/grub-mkrescue.in 2008-07-19 21:39:24 +0000 @@ -71,7 +71,7 @@ --modules=*) modules=`echo "$option" | sed 's/--modules=//'` ;; --overlay=*) - overlay=`echo "$option" | sed 's/--overlay=//'` ;; + overlay=${overlay}${overlay:+ }`echo "$option" | sed 's/--overlay=//'` ;; --pkglibdir=*) input_dir=`echo "$option" | sed 's/--pkglibdir=//'` ;; --grub-mkimage=*) @@ -124,9 +124,10 @@ echo "insmod $i" done > ${aux_dir}/boot/grub/grub.cfg -if test "x$overlay" = x ; then : ; else - cp -dpR ${overlay}/* ${aux_dir}/ -fi +for d in ${overlay}; do + echo "Overlaying $d" + cp -vdpR ${d}/* ${aux_dir}/ +done if [ "x${image_type}" = xfloppy -o "x${emulation}" = xfloppy ] ; then # build memdisk === modified file 'video/bitmap.c' --- video/bitmap.c 2007-07-21 22:32:33 +0000 +++ video/bitmap.c 2008-07-03 13:49:18 +0000 @@ -78,7 +78,7 @@ switch (blit_format) { - case GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8: + case GRUB_VIDEO_BLIT_FORMAT_RGBA_8888: mode_info->mode_type = GRUB_VIDEO_MODE_TYPE_RGB | GRUB_VIDEO_MODE_TYPE_ALPHA; mode_info->bpp = 32; @@ -94,7 +94,7 @@ mode_info->reserved_field_pos = 24; break; - case GRUB_VIDEO_BLIT_FORMAT_R8G8B8: + case GRUB_VIDEO_BLIT_FORMAT_RGB_888: mode_info->mode_type = GRUB_VIDEO_MODE_TYPE_RGB; mode_info->bpp = 24; mode_info->bytes_per_pixel = 3; === added file 'video/bitmap_scale.c' --- video/bitmap_scale.c 1970-01-01 00:00:00 +0000 +++ video/bitmap_scale.c 2008-07-03 14:27:43 +0000 @@ -0,0 +1,99 @@ +/* bitmap_scale.c - Bitmap scaling interface. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2006,2007 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +/* + * This function creates a new scaled version of the bitmap SRC. The new + * bitmap has dimensions DST_WIDTH by DST_HEIGHT. The scaling algorithm + * is given by SCALE_METHOD. If an error is encountered, the return code is + * not equal to GRUB_ERR_NONE, and the bitmap DST is either not created, or + * it is destroyed before this function returns. + * + * Supports only direct color modes which have components separated + * into bytes (e.g., RGBA 8:8:8:8 or BGR 8:8:8 true color). + * But because of this simplifying assumption, the implementation is + * greatly simplified. + */ +grub_err_t +grub_video_bitmap_create_scaled (struct grub_video_bitmap **dst, + int dst_width, int dst_height, + struct grub_video_bitmap *src, + enum grub_video_bitmap_scale_method + scale_method) +{ + + /* Verify the simplifying assumptions. */ + if (src == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "null src bitmap in grub_video_bitmap_create_scaled"); + if (src->mode_info.red_field_pos % 8 != 0 + || src->mode_info.green_field_pos % 8 != 0 + || src->mode_info.blue_field_pos % 8 != 0 + || src->mode_info.reserved_field_pos % 8 != 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "src format not supported for scale"); + if (src->mode_info.width == 0 || src->mode_info.height == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "bitmap has a zero dimension"); + if (dst_width <= 0 || dst_height <= 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "requested to scale to a size w/ a zero dimension"); + if (src->mode_info.bytes_per_pixel * 8 != src->mode_info.bpp) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "bitmap to scale has inconsistent Bpp and bpp"); + + /* Create the new bitmap. */ + grub_err_t ret; + ret = grub_video_bitmap_create (dst, dst_width, dst_height, + src->mode_info.blit_format); + if (ret != GRUB_ERR_NONE) + return ret; /* Error. */ + + switch (scale_method) + { + case GRUB_VIDEO_BITMAP_SCALE_METHOD_FASTEST: + case GRUB_VIDEO_BITMAP_SCALE_METHOD_NEAREST: + ret = grub_video_bitmap_scale_nn (*dst, src); + break; + case GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST: + case GRUB_VIDEO_BITMAP_SCALE_METHOD_BILINEAR: + ret = grub_video_bitmap_scale_bilinear (*dst, src); + break; + default: + ret = grub_error (GRUB_ERR_BAD_ARGUMENT, "Invalid scale_method value"); + break; + } + + if (ret == GRUB_ERR_NONE) + { + /* Success: *dst is now a pointer to the scaled bitmap. */ + return GRUB_ERR_NONE; + } + else + { + /* Destroy the bitmap and return the error code. */ + grub_video_bitmap_destroy (*dst); + return ret; + } +} === added file 'video/bitmap_scale_bilinear.c' --- video/bitmap_scale_bilinear.c 1970-01-01 00:00:00 +0000 +++ video/bitmap_scale_bilinear.c 2008-07-03 14:28:15 +0000 @@ -0,0 +1,145 @@ +/* bitmap_scale_bilinear.c - Bilinear image scaling algorithm. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2006,2007 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +/* + * Copy the bitmap SRC to the bitmap DST, scaling the bitmap to fit the + * dimensions of DST. This function uses the bilinear interpolation algorithm + * to interpolate the pixels. + * + * Supports only direct color modes which have components separated + * into bytes (e.g., RGBA 8:8:8:8 or BGR 8:8:8 true color). + * But because of this simplifying assumption, the implementation is + * greatly simplified. + */ +grub_err_t +grub_video_bitmap_scale_bilinear (struct grub_video_bitmap *dst, + struct grub_video_bitmap *src) +{ + /* Verify the simplifying assumptions. */ + if (dst == 0 || src == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "null bitmap in scale func"); + if (dst->mode_info.red_field_pos % 8 != 0 + || dst->mode_info.green_field_pos % 8 != 0 + || dst->mode_info.blue_field_pos % 8 != 0 + || dst->mode_info.reserved_field_pos % 8 != 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "dst format not supported"); + if (src->mode_info.red_field_pos % 8 != 0 + || src->mode_info.green_field_pos % 8 != 0 + || src->mode_info.blue_field_pos % 8 != 0 + || src->mode_info.reserved_field_pos % 8 != 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "src format not supported"); + if (dst->mode_info.red_field_pos != src->mode_info.red_field_pos + || dst->mode_info.red_mask_size != src->mode_info.red_mask_size + || dst->mode_info.green_field_pos != src->mode_info.green_field_pos + || dst->mode_info.green_mask_size != src->mode_info.green_mask_size + || dst->mode_info.blue_field_pos != src->mode_info.blue_field_pos + || dst->mode_info.blue_mask_size != src->mode_info.blue_mask_size + || dst->mode_info.reserved_field_pos != + src->mode_info.reserved_field_pos + || dst->mode_info.reserved_mask_size != + src->mode_info.reserved_mask_size) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "dst and src not compatible"); + if (dst->mode_info.bytes_per_pixel != src->mode_info.bytes_per_pixel) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "dst and src not compatible"); + if (dst->mode_info.width == 0 || dst->mode_info.height == 0 + || src->mode_info.width == 0 || src->mode_info.height == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "bitmap has a zero dimension"); + + grub_uint8_t *ddata = dst->data; + grub_uint8_t *sdata = src->data; + int dw = dst->mode_info.width; + int dh = dst->mode_info.height; + int sw = src->mode_info.width; + int sh = src->mode_info.height; + int dstride = dst->mode_info.pitch; + int sstride = src->mode_info.pitch; + /* bytes_per_pixel is the same for both src and dst. */ + int bytes_per_pixel = dst->mode_info.bytes_per_pixel; + + int dy; + for (dy = 0; dy < dh; dy++) + { + int dx; + for (dx = 0; dx < dw; dx++) + { + grub_uint8_t *dptr; + grub_uint8_t *sptr; + int sx; + int sy; + int comp; + + /* Compute the source coordinate that the destination coordinate + * maps to. Note: sx/sw = dx/dw => sx = sw*dx/dw. */ + sx = sw * dx / dw; + sy = sh * dy / dh; + + /* Get the address of the pixels in src and dst. */ + dptr = ddata + dy * dstride + dx * bytes_per_pixel; + sptr = sdata + sy * sstride + sx * bytes_per_pixel; + + /* If we have enough space to do so, use bilinear interpolation. + * Otherwise, fall back to nearest neighbor for this pixel. */ + if (sx < sw - 1 && sy < sh - 1) + { + /* Do bilinear interpolation. */ + + /* Fixed-point .8 numbers representing the fraction of the + * distance in the x (u) and y (v) direction within the + * box of 4 pixels in the source. */ + int u = (256 * sw * dx / dw) - (sx * 256); + int v = (256 * sh * dy / dh) - (sy * 256); + + for (comp = 0; comp < bytes_per_pixel; comp++) + { + /* Get the component's values for the + * 4 source corner pixels. */ + grub_uint8_t f00 = sptr[comp]; + grub_uint8_t f10 = sptr[comp + bytes_per_pixel]; + grub_uint8_t f01 = sptr[comp + sstride]; + grub_uint8_t f11 = sptr[comp + sstride + bytes_per_pixel]; + + /* Do linear interpolations along the top and bottom + * rows of the box. */ + grub_uint8_t f0y = (256 - v) * f00 / 256 + v * f01 / 256; + grub_uint8_t f1y = (256 - v) * f10 / 256 + v * f11 / 256; + + /* Interpolate vertically. */ + grub_uint8_t fxy = (256 - u) * f0y / 256 + u * f1y / 256; + + dptr[comp] = fxy; + } + } + else + { + /* Fall back to nearest neighbor interpolation. */ + /* Copy the pixel color value. */ + for (comp = 0; comp < bytes_per_pixel; comp++) + dptr[comp] = sptr[comp]; + } + } + } + return GRUB_ERR_NONE; +} === added file 'video/bitmap_scale_nn.c' --- video/bitmap_scale_nn.c 1970-01-01 00:00:00 +0000 +++ video/bitmap_scale_nn.c 2008-07-03 14:27:43 +0000 @@ -0,0 +1,109 @@ +/* bitmap_scale_nn.c - Nearest neighbor image scaling algorithm. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2006,2007 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +/* + * Copy the bitmap SRC to the bitmap DST, scaling the bitmap to fit the + * dimensions of DST. This function uses the nearest neighbor algorithm to + * interpolate the pixels. + * + * Supports only direct color modes which have components separated + * into bytes (e.g., RGBA 8:8:8:8 or BGR 8:8:8 true color). + * But because of this simplifying assumption, the implementation is + * greatly simplified. + */ +grub_err_t +grub_video_bitmap_scale_nn (struct grub_video_bitmap *dst, + struct grub_video_bitmap *src) +{ + /* Verify the simplifying assumptions. */ + if (dst == 0 || src == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "null bitmap in scale_nn"); + if (dst->mode_info.red_field_pos % 8 != 0 + || dst->mode_info.green_field_pos % 8 != 0 + || dst->mode_info.blue_field_pos % 8 != 0 + || dst->mode_info.reserved_field_pos % 8 != 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "dst format not supported"); + if (src->mode_info.red_field_pos % 8 != 0 + || src->mode_info.green_field_pos % 8 != 0 + || src->mode_info.blue_field_pos % 8 != 0 + || src->mode_info.reserved_field_pos % 8 != 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "src format not supported"); + if (dst->mode_info.red_field_pos != src->mode_info.red_field_pos + || dst->mode_info.red_mask_size != src->mode_info.red_mask_size + || dst->mode_info.green_field_pos != src->mode_info.green_field_pos + || dst->mode_info.green_mask_size != src->mode_info.green_mask_size + || dst->mode_info.blue_field_pos != src->mode_info.blue_field_pos + || dst->mode_info.blue_mask_size != src->mode_info.blue_mask_size + || dst->mode_info.reserved_field_pos != + src->mode_info.reserved_field_pos + || dst->mode_info.reserved_mask_size != + src->mode_info.reserved_mask_size) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "dst and src not compatible"); + if (dst->mode_info.bytes_per_pixel != src->mode_info.bytes_per_pixel) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "dst and src not compatible"); + if (dst->mode_info.width == 0 || dst->mode_info.height == 0 + || src->mode_info.width == 0 || src->mode_info.height == 0) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "bitmap has a zero dimension"); + + grub_uint8_t *ddata = dst->data; + grub_uint8_t *sdata = src->data; + int dw = dst->mode_info.width; + int dh = dst->mode_info.height; + int sw = src->mode_info.width; + int sh = src->mode_info.height; + int dstride = dst->mode_info.pitch; + int sstride = src->mode_info.pitch; + /* bytes_per_pixel is the same for both src and dst. */ + int bytes_per_pixel = dst->mode_info.bytes_per_pixel; + + int dy; + for (dy = 0; dy < dh; dy++) + { + int dx; + for (dx = 0; dx < dw; dx++) + { + grub_uint8_t *dptr; + grub_uint8_t *sptr; + int sx; + int sy; + int comp; + + /* Compute the source coordinate that the destination coordinate + * maps to. Note: sx/sw = dx/dw => sx = sw*dx/dw. */ + sx = sw * dx / dw; + sy = sh * dy / dh; + + /* Get the address of the pixels in src and dst. */ + dptr = ddata + dy * dstride + dx * bytes_per_pixel; + sptr = sdata + sy * sstride + sx * bytes_per_pixel; + + /* Copy the pixel color value. */ + for (comp = 0; comp < bytes_per_pixel; comp++) + dptr[comp] = sptr[comp]; + } + } + return GRUB_ERR_NONE; +} === modified file 'video/i386/pc/vbe.c' --- video/i386/pc/vbe.c 2008-01-21 15:48:27 +0000 +++ video/i386/pc/vbe.c 2008-07-19 19:42:37 +0000 @@ -63,6 +63,7 @@ static struct { struct grub_video_render_target render_target; + int is_double_buffered; /* Is the video mode double buffered? */ unsigned int bytes_per_scan_line; unsigned int bytes_per_pixel; @@ -77,6 +78,24 @@ static grub_uint32_t mode_in_use = 0x55aa; static grub_uint16_t *mode_list; +static struct +{ + grub_size_t page_size; /* The size of a page in bytes. */ + + /* For page flipping strategy. */ + int displayed_page; /* The page # that is the front buffer. */ + int render_page; /* The page # that is the back buffer. */ + + /* For blit strategy. */ + grub_uint8_t *offscreen_buffer; + + /* Virtual functions. */ + int (*update_screen) (void); + int (*destroy) (void); +} doublebuf_state; + +static void double_buffering_init (void); + static void * real2pm (grub_vbe_farptr_t ptr) { @@ -376,6 +395,7 @@ /* Reset frame buffer and render target variables. */ grub_memset (&framebuffer, 0, sizeof(framebuffer)); render_target = &framebuffer.render_target; + grub_memset (&doublebuf_state, 0, sizeof(doublebuf_state)); return GRUB_ERR_NONE; } @@ -391,6 +411,9 @@ /* TODO: Decide, is this something we want to do. */ return grub_errno; + if (doublebuf_state.destroy) + doublebuf_state.destroy(); + /* TODO: Free any resources allocated by driver. */ grub_free (mode_list); mode_list = 0; @@ -533,9 +556,13 @@ render_target->viewport.width = active_mode_info.x_resolution; render_target->viewport.height = active_mode_info.y_resolution; - /* Set framebuffer pointer and mark it as non allocated. */ + /* Mark framebuffer memory as non allocated. */ render_target->is_allocated = 0; - render_target->data = framebuffer.ptr; + + /* Set up double buffering information. */ + framebuffer.is_double_buffered = + ((mode_type & GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED) != 0); + double_buffering_init (); /* Copy default palette to initialize emulated palette. */ for (i = 0; @@ -556,6 +583,166 @@ return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no matching mode found."); } +/* + Set framebuffer render target page and display the proper page, based on + `doublebuf_state.render_page' and `doublebuf_state.displayed_page', + respectively. + + Returns 0 upon success, nonzero upon failure. + */ +static int +doublebuf_pageflipping_commit (void) +{ + /* Set the render target's data pointer to the start of the render_page. */ + framebuffer.render_target.data = + ((char *) framebuffer.ptr) + + doublebuf_state.page_size * doublebuf_state.render_page; + + /* Tell the video adapter to display the new front page. */ + int display_start_line = + framebuffer.render_target.mode_info.height + * doublebuf_state.displayed_page; + + grub_vbe_status_t vbe_err = + grub_vbe_bios_set_display_start (0, display_start_line); + if (vbe_err != GRUB_VBE_STATUS_OK) + return 1; + + return 0; +} + +static int +doublebuf_pageflipping_update_screen (void) +{ + /* Swap the page numbers in the framebuffer struct. */ + int new_displayed_page = doublebuf_state.render_page; + doublebuf_state.render_page = doublebuf_state.displayed_page; + doublebuf_state.displayed_page = new_displayed_page; + + return doublebuf_pageflipping_commit (); +} + +static int +doublebuf_pageflipping_destroy (void) +{ + doublebuf_state.update_screen = 0; + doublebuf_state.destroy = 0; + return 0; +} + +static int +doublebuf_pageflipping_init (void) +{ + doublebuf_state.page_size = + framebuffer.bytes_per_scan_line * render_target->mode_info.height; + + /* Get video RAM size in bytes. */ + grub_size_t vram_size = controller_info.total_memory << 16; + + if (2 * doublebuf_state.page_size > vram_size) + return 1; /* Not enough video memory for 2 pages. */ + + doublebuf_state.displayed_page = 0; + doublebuf_state.render_page = 1; + + doublebuf_state.update_screen = doublebuf_pageflipping_update_screen; + doublebuf_state.destroy = doublebuf_pageflipping_destroy; + + /* Set the framebuffer memory data pointer and display the right page. */ + if (doublebuf_pageflipping_commit () != GRUB_ERR_NONE) + return 1; /* Unable to set the display start. */ + + framebuffer.render_target.mode_info.mode_type + |= GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED; + return 0; +} + +static int +doublebuf_blit_update_screen (void) +{ + grub_memcpy (framebuffer.ptr, + doublebuf_state.offscreen_buffer, + doublebuf_state.page_size); + return 0; +} + +static int +doublebuf_blit_destroy (void) +{ + grub_free (doublebuf_state.offscreen_buffer); + doublebuf_state.offscreen_buffer = 0; + + doublebuf_state.update_screen = 0; + doublebuf_state.destroy = 0; + return 0; +} + +static int +doublebuf_blit_init (void) +{ + doublebuf_state.page_size = + framebuffer.bytes_per_scan_line * render_target->mode_info.height; + + doublebuf_state.offscreen_buffer = (grub_uint8_t *) + grub_malloc (doublebuf_state.page_size); + if (doublebuf_state.offscreen_buffer == 0) + return 1; /* Error. */ + + framebuffer.render_target.data = doublebuf_state.offscreen_buffer; + doublebuf_state.update_screen = doublebuf_blit_update_screen; + doublebuf_state.destroy = doublebuf_blit_destroy; + + framebuffer.render_target.mode_info.mode_type + |= GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED; + return 0; +} + +static int +doublebuf_null_update_screen (void) +{ + return 0; +} + +static int +doublebuf_null_destroy (void) +{ + doublebuf_state.update_screen = 0; + doublebuf_state.destroy = 0; + return 0; +} + +static int +doublebuf_null_init (void) +{ + framebuffer.render_target.data = framebuffer.ptr; + doublebuf_state.update_screen = doublebuf_null_update_screen; + doublebuf_state.destroy = doublebuf_null_destroy; + + framebuffer.render_target.mode_info.mode_type + &= ~GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED; + return 0; +} + +/* Select the best double buffering mode available. */ +static void +double_buffering_init (void) +{ + if (doublebuf_state.destroy) + doublebuf_state.destroy(); + + if (framebuffer.is_double_buffered) + { + if (doublebuf_pageflipping_init () == 0) + return; + + if (doublebuf_blit_init () == 0) + return; + } + + /* Fall back to no double buffering. */ + doublebuf_null_init (); +} + static grub_err_t grub_video_vbe_get_info (struct grub_video_mode_info *mode_info) { @@ -708,6 +895,16 @@ return minindex; } + else if ((render_target->mode_info.mode_type + & GRUB_VIDEO_MODE_TYPE_1BIT_BITMAP) != 0) + { + if (red == render_target->mode_info.fg_red + && green == render_target->mode_info.fg_green + && blue == render_target->mode_info.fg_blue) + return 1; + else + return 0; + } else { grub_uint32_t value; @@ -737,6 +934,17 @@ /* No alpha available in index color modes, just use same value as in only RGB modes. */ return grub_video_vbe_map_rgb (red, green, blue); + else if ((render_target->mode_info.mode_type + & GRUB_VIDEO_MODE_TYPE_1BIT_BITMAP) != 0) + { + if (red == render_target->mode_info.fg_red + && green == render_target->mode_info.fg_green + && blue == render_target->mode_info.fg_blue + && alpha == render_target->mode_info.fg_alpha) + return 1; + else + return 0; + } else { grub_uint32_t value; @@ -797,6 +1005,24 @@ *alpha = framebuffer.palette[color].a; return; } + else if ((mode_info->mode_type + & GRUB_VIDEO_MODE_TYPE_1BIT_BITMAP) != 0) + { + if (color & 1) + { + *red = mode_info->fg_red; + *green = mode_info->fg_green; + *blue = mode_info->fg_blue; + *alpha = mode_info->fg_alpha; + } + else + { + *red = mode_info->bg_red; + *green = mode_info->bg_green; + *blue = mode_info->bg_blue; + *alpha = mode_info->bg_alpha; + } + } else { grub_uint32_t tmp; @@ -876,17 +1102,24 @@ target.data = render_target->data; /* Try to figure out more optimized version. */ - if (target.mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8) - { - grub_video_i386_vbefill_R8G8B8A8 (&target, color, x, y, - width, height); - return GRUB_ERR_NONE; - } - - if (target.mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8) - { - grub_video_i386_vbefill_R8G8B8 (&target, color, x, y, - width, height); + if (target.mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGRA_8888) + { + grub_video_i386_vbefill_direct32 (&target, color, x, y, + width, height); + return GRUB_ERR_NONE; + } + + if (target.mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGBA_8888) + { + grub_video_i386_vbefill_direct32 (&target, color, x, y, + width, height); + return GRUB_ERR_NONE; + } + + if (target.mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGB_888) + { + grub_video_i386_vbefill_direct24 (&target, color, x, y, + width, height); return GRUB_ERR_NONE; } @@ -903,76 +1136,6 @@ return GRUB_ERR_NONE; } -// TODO: Remove this method and replace with bitmap based glyphs -static grub_err_t -grub_video_vbe_blit_glyph (struct grub_font_glyph * glyph, - grub_video_color_t color, int x, int y) -{ - struct grub_video_i386_vbeblit_info target; - unsigned int width; - unsigned int charwidth; - unsigned int height; - unsigned int i; - unsigned int j; - unsigned int x_offset = 0; - unsigned int y_offset = 0; - - /* Make sure there is something to do. */ - if (x >= (int)render_target->viewport.width) - return GRUB_ERR_NONE; - - if (y >= (int)render_target->viewport.height) - return GRUB_ERR_NONE; - - /* Calculate glyph dimensions. */ - width = ((glyph->width + 7) / 8) * 8; - charwidth = width; - height = glyph->height; - - if (x + (int)width < 0) - return GRUB_ERR_NONE; - - if (y + (int)height < 0) - return GRUB_ERR_NONE; - - /* Do not allow drawing out of viewport. */ - if (x < 0) - { - width += x; - x_offset = (unsigned int)-x; - x = 0; - } - if (y < 0) - { - height += y; - y_offset = (unsigned int)-y; - y = 0; - } - - if ((x + width) > render_target->viewport.width) - width = render_target->viewport.width - x; - if ((y + height) > render_target->viewport.height) - height = render_target->viewport.height - y; - - /* Add viewport offset. */ - x += render_target->viewport.x; - y += render_target->viewport.y; - - /* Use vbeblit_info to encapsulate rendering. */ - target.mode_info = &render_target->mode_info; - target.data = render_target->data; - - /* Draw glyph. */ - for (j = 0; j < height; j++) - for (i = 0; i < width; i++) - if ((glyph->bitmap[((i + x_offset) / 8) - + (j + y_offset) * (charwidth / 8)] - & (1 << ((charwidth - (i + x_offset) - 1) % 8)))) - set_pixel (&target, x+i, y+j, color); - - return GRUB_ERR_NONE; -} - /* NOTE: This function assumes that given coordinates are within bounds of handled data. */ static void @@ -985,19 +1148,35 @@ if (oper == GRUB_VIDEO_BLIT_REPLACE) { /* Try to figure out more optimized version for replace operator. */ - if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8) + if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGBA_8888) { - if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8) - { - grub_video_i386_vbeblit_R8G8B8X8_R8G8B8X8 (target, source, + if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGBA_8888) + { + grub_video_i386_vbeblit_direct32_copy (target, source, + x, y, width, height, + offset_x, offset_y); + return; + } + + if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGRA_8888) + { + grub_video_i386_vbeblit_BGRX8888_RGBX8888 (target, source, x, y, width, height, offset_x, offset_y); return; } - if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8) - { - grub_video_i386_vbeblit_R8G8B8_R8G8B8X8 (target, source, + if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGR_888) + { + grub_video_i386_vbeblit_BGR888_RGBX8888 (target, source, + x, y, width, height, + offset_x, offset_y); + return; + } + + if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGB_888) + { + grub_video_i386_vbeblit_RGB888_RGBX8888 (target, source, x, y, width, height, offset_x, offset_y); return; @@ -1005,26 +1184,42 @@ if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_INDEXCOLOR) { - grub_video_i386_vbeblit_index_R8G8B8X8 (target, source, + grub_video_i386_vbeblit_index_RGBX8888 (target, source, x, y, width, height, offset_x, offset_y); return; } } - if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8) + if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGB_888) { - if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8) - { - grub_video_i386_vbeblit_R8G8B8A8_R8G8B8 (target, source, - x, y, width, height, - offset_x, offset_y); - return; - } - - if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8) - { - grub_video_i386_vbeblit_R8G8B8_R8G8B8 (target, source, + if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGRA_8888) + { + grub_video_i386_vbeblit_BGRA8888_RGB888 (target, source, + x, y, width, height, + offset_x, offset_y); + return; + } + + if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGBA_8888) + { + grub_video_i386_vbeblit_RGBA8888_RGB888 (target, source, + x, y, width, height, + offset_x, offset_y); + return; + } + + if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGR_888) + { + grub_video_i386_vbeblit_BGR888_RGB888 (target, source, + x, y, width, height, + offset_x, offset_y); + return; + } + + if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGB_888) + { + grub_video_i386_vbeblit_RGB888_RGB888 (target, source, x, y, width, height, offset_x, offset_y); return; @@ -1032,13 +1227,24 @@ if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_INDEXCOLOR) { - grub_video_i386_vbeblit_index_R8G8B8 (target, source, + grub_video_i386_vbeblit_index_RGB888 (target, source, x, y, width, height, offset_x, offset_y); return; } } + if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGRA_8888) + { + if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGRA_8888) + { + grub_video_i386_vbeblit_direct32_copy (target, source, + x, y, width, height, + offset_x, offset_y); + return; + } + } + if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_INDEXCOLOR) { if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_INDEXCOLOR) @@ -1057,19 +1263,35 @@ else { /* Try to figure out more optimized blend operator. */ - if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8) + if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGBA_8888) { - if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8) - { - grub_video_i386_vbeblit_R8G8B8A8_R8G8B8A8 (target, source, - x, y, width, height, - offset_x, offset_y); - return; - } - - if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8) - { - grub_video_i386_vbeblit_R8G8B8_R8G8B8A8 (target, source, + if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGRA_8888) + { + grub_video_i386_vbeblit_BGRA8888_RGBA8888 (target, source, + x, y, width, height, + offset_x, offset_y); + return; + } + + if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGBA_8888) + { + grub_video_i386_vbeblit_RGBA8888_RGBA8888 (target, source, + x, y, width, height, + offset_x, offset_y); + return; + } + + if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGR_888) + { + grub_video_i386_vbeblit_BGR888_RGBA8888 (target, source, + x, y, width, height, + offset_x, offset_y); + return; + } + + if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGB_888) + { + grub_video_i386_vbeblit_RGB888_RGBA8888 (target, source, x, y, width, height, offset_x, offset_y); return; @@ -1077,26 +1299,42 @@ if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_INDEXCOLOR) { - grub_video_i386_vbeblit_index_R8G8B8A8 (target, source, + grub_video_i386_vbeblit_index_RGBA8888 (target, source, x, y, width, height, offset_x, offset_y); return; } } - if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8) + if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGB_888) { - if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8) - { - grub_video_i386_vbeblit_R8G8B8A8_R8G8B8 (target, source, - x, y, width, height, - offset_x, offset_y); - return; - } - - if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_R8G8B8) - { - grub_video_i386_vbeblit_R8G8B8_R8G8B8 (target, source, + if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGRA_8888) + { + grub_video_i386_vbeblit_BGRA8888_RGB888 (target, source, + x, y, width, height, + offset_x, offset_y); + return; + } + + if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGBA_8888) + { + grub_video_i386_vbeblit_RGBA8888_RGB888 (target, source, + x, y, width, height, + offset_x, offset_y); + return; + } + + if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_BGR_888) + { + grub_video_i386_vbeblit_BGR888_RGB888 (target, source, + x, y, width, height, + offset_x, offset_y); + return; + } + + if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_RGB_888) + { + grub_video_i386_vbeblit_RGB888_RGB888 (target, source, x, y, width, height, offset_x, offset_y); return; @@ -1104,7 +1342,7 @@ if (target->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_INDEXCOLOR) { - grub_video_i386_vbeblit_index_R8G8B8 (target, source, + grub_video_i386_vbeblit_index_RGB888 (target, source, x, y, width, height, offset_x, offset_y); return; @@ -1219,6 +1457,77 @@ return GRUB_ERR_NONE; } +/* + * Draw the specified glyph at (x, y). The y coordinate designates the + * baseline of the character, while the x coordinate designates the left + * side location of the character. + */ +static grub_err_t +grub_video_vbe_blit_glyph (struct grub_font_glyph *glyph, + grub_video_color_t color, + int left_x, int baseline_y) +{ + struct grub_video_bitmap glyph_bitmap; + + /* Don't try to draw empty glyphs (U+0020, etc.). */ + if (glyph->width == 0 || glyph->height == 0) + return GRUB_ERR_NONE; + + glyph_bitmap.mode_info.width = glyph->width; + glyph_bitmap.mode_info.height = glyph->height; + glyph_bitmap.mode_info.mode_type = + (1 << GRUB_VIDEO_MODE_TYPE_DEPTH_POS) + | GRUB_VIDEO_MODE_TYPE_1BIT_BITMAP; + glyph_bitmap.mode_info.blit_format = GRUB_VIDEO_BLIT_FORMAT_1BIT_PACKED; + glyph_bitmap.mode_info.bpp = 1; + glyph_bitmap.mode_info.bytes_per_pixel = 0; /* Really 1 bit per pixel. */ + glyph_bitmap.mode_info.pitch = glyph->width; /* Packed densely as bits. */ + glyph_bitmap.mode_info.number_of_colors = 2; + glyph_bitmap.mode_info.bg_red = 0; + glyph_bitmap.mode_info.bg_green = 0; + glyph_bitmap.mode_info.bg_blue = 0; + glyph_bitmap.mode_info.bg_alpha = 0; + grub_video_vbe_unmap_color(color, + &glyph_bitmap.mode_info.fg_red, + &glyph_bitmap.mode_info.fg_green, + &glyph_bitmap.mode_info.fg_blue, + &glyph_bitmap.mode_info.fg_alpha); + glyph_bitmap.data = glyph->bitmap; + + int bitmap_left = left_x + glyph->offset_x; + int bitmap_bottom = baseline_y - glyph->offset_y; + int bitmap_top = bitmap_bottom - glyph->height; + + return grub_video_vbe_blit_bitmap (&glyph_bitmap, GRUB_VIDEO_BLIT_BLEND, + bitmap_left, bitmap_top, + 0, 0, + glyph->width, glyph->height); +} + +static grub_err_t +grub_video_vbe_draw_string (const char *str, grub_font_t font, + grub_video_color_t color, + int left_x, int baseline_y) +{ + grub_size_t len; + grub_size_t i; + int x; + struct grub_font_glyph *glyph; + + len = grub_strlen (str); + x = left_x; + for (i = 0; i < len; i++) + { + glyph = grub_font_get_glyph (font, str[i]); + if (grub_video_vbe_blit_glyph (glyph, color, x, baseline_y) + != GRUB_ERR_NONE) + return grub_errno; + x += glyph->device_width; + } + + return GRUB_ERR_NONE; +} + static grub_err_t grub_video_vbe_blit_render_target (struct grub_video_render_target *source, enum grub_video_blit_operators oper, @@ -1403,10 +1712,13 @@ return GRUB_ERR_NONE; } -static grub_err_t +static grub_err_t grub_video_vbe_swap_buffers (void) { - /* TODO: Implement buffer swapping. */ + if (doublebuf_state.update_screen () != 0) + return grub_error (GRUB_ERR_INVALID_COMMAND, + "Double buffer update failed"); + return GRUB_ERR_NONE; } @@ -1505,17 +1817,13 @@ static grub_err_t grub_video_vbe_set_active_render_target (struct grub_video_render_target *target) { - if (target == GRUB_VIDEO_RENDER_TARGET_FRONT_BUFFER) + if (target == GRUB_VIDEO_RENDER_TARGET_DISPLAY) { render_target = &framebuffer.render_target; - + return GRUB_ERR_NONE; } - if (target == GRUB_VIDEO_RENDER_TARGET_BACK_BUFFER) - return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, - "double buffering not implemented yet."); - if (! target->data) return grub_error (GRUB_ERR_BAD_ARGUMENT, "invalid render target given."); @@ -1551,6 +1859,7 @@ .unmap_color = grub_video_vbe_unmap_color, .fill_rect = grub_video_vbe_fill_rect, .blit_glyph = grub_video_vbe_blit_glyph, + .draw_string = grub_video_vbe_draw_string, .blit_bitmap = grub_video_vbe_blit_bitmap, .blit_render_target = grub_video_vbe_blit_render_target, .scroll = grub_video_vbe_scroll, === modified file 'video/i386/pc/vbeblit.c' --- video/i386/pc/vbeblit.c 2008-01-01 12:02:07 +0000 +++ video/i386/pc/vbeblit.c 2008-07-09 17:27:53 +0000 @@ -35,7 +35,343 @@ #include void -grub_video_i386_vbeblit_R8G8B8A8_R8G8B8A8 (struct grub_video_i386_vbeblit_info *dst, +grub_video_i386_vbeblit_BGRX8888_RGBX8888 (struct grub_video_i386_vbeblit_info + *dst, + struct grub_video_i386_vbeblit_info + *src, int x, int y, int width, + int height, int offset_x, + int offset_y) +{ + int i; + int j; + grub_uint8_t *srcptr; + grub_uint8_t *dstptr; + unsigned srcrowskip; + unsigned dstrowskip; + + /* Calculate the number of bytes to advance from the end of one line + * to the beginning of the next line. */ + srcrowskip = + src->mode_info->pitch - src->mode_info->bytes_per_pixel * width; + dstrowskip = + dst->mode_info->pitch - dst->mode_info->bytes_per_pixel * width; + + srcptr = (grub_uint8_t *) get_data_ptr (src, offset_x, offset_y); + dstptr = (grub_uint8_t *) get_data_ptr (dst, x, y); + for (j = 0; j < height; j++) + { + for (i = 0; i < width; i++) + { + grub_uint8_t r = *srcptr++; + grub_uint8_t g = *srcptr++; + grub_uint8_t b = *srcptr++; + grub_uint8_t a = *srcptr++; + + *dstptr++ = b; + *dstptr++ = g; + *dstptr++ = r; + *dstptr++ = a; + } + srcptr += srcrowskip; + dstptr += dstrowskip; + } +} + +void +grub_video_i386_vbeblit_BGRA8888_RGB888 (struct grub_video_i386_vbeblit_info + *dst, + struct grub_video_i386_vbeblit_info + *src, int x, int y, int width, + int height, int offset_x, + int offset_y) +{ + int i; + int j; + grub_uint8_t *srcptr; + grub_uint8_t *dstptr; + unsigned srcrowskip; + unsigned dstrowskip; + + /* Calculate the number of bytes to advance from the end of one line + * to the beginning of the next line. */ + srcrowskip = + src->mode_info->pitch - src->mode_info->bytes_per_pixel * width; + dstrowskip = + dst->mode_info->pitch - dst->mode_info->bytes_per_pixel * width; + + srcptr = (grub_uint8_t *) get_data_ptr (src, offset_x, offset_y); + dstptr = (grub_uint8_t *) get_data_ptr (dst, x, y); + for (j = 0; j < height; j++) + { + for (i = 0; i < width; i++) + { + grub_uint8_t r = *srcptr++; + grub_uint8_t g = *srcptr++; + grub_uint8_t b = *srcptr++; + + *dstptr++ = b; + *dstptr++ = g; + *dstptr++ = r; + *dstptr++ = 255; /* Alpha component: Set opaque. */ + } + srcptr += srcrowskip; + dstptr += dstrowskip; + } +} + +void +grub_video_i386_vbeblit_BGR888_RGB888 (struct grub_video_i386_vbeblit_info + *dst, + struct grub_video_i386_vbeblit_info + *src, int x, int y, int width, + int height, int offset_x, + int offset_y) +{ + int i; + int j; + grub_uint8_t *srcptr; + grub_uint8_t *dstptr; + unsigned srcrowskip; + unsigned dstrowskip; + + /* Calculate the number of bytes to advance from the end of one line + * to the beginning of the next line. */ + srcrowskip = + src->mode_info->pitch - src->mode_info->bytes_per_pixel * width; + dstrowskip = + dst->mode_info->pitch - dst->mode_info->bytes_per_pixel * width; + + srcptr = (grub_uint8_t *) get_data_ptr (src, offset_x, offset_y); + dstptr = (grub_uint8_t *) get_data_ptr (dst, x, y); + for (j = 0; j < height; j++) + { + for (i = 0; i < width; i++) + { + grub_uint8_t r = *srcptr++; + grub_uint8_t g = *srcptr++; + grub_uint8_t b = *srcptr++; + + *dstptr++ = b; + *dstptr++ = g; + *dstptr++ = r; + } + srcptr += srcrowskip; + dstptr += dstrowskip; + } +} + +void +grub_video_i386_vbeblit_BGRA8888_RGBA8888 (struct grub_video_i386_vbeblit_info *dst, + struct grub_video_i386_vbeblit_info *src, + int x, int y, int width, int height, + int offset_x, int offset_y) +{ + grub_uint32_t *srcptr; + grub_uint32_t *dstptr; + unsigned srcrowskip; + unsigned dstrowskip; + int i; + int j; + + /* Calculate the number of bytes to advance from the end of one line + * to the beginning of the next line. */ + srcrowskip = + src->mode_info->pitch - src->mode_info->bytes_per_pixel * width; + dstrowskip = + dst->mode_info->pitch - dst->mode_info->bytes_per_pixel * width; + + srcptr = (grub_uint32_t *) get_data_ptr (src, offset_x, offset_y); + dstptr = (grub_uint32_t *) get_data_ptr (dst, x, y); + + for (j = 0; j < height; j++) + { + for (i = 0; i < width; i++) + { + grub_uint32_t color; + unsigned int sr; + unsigned int sg; + unsigned int sb; + unsigned int a; + unsigned int dr; + unsigned int dg; + unsigned int db; + + color = *srcptr++; + + a = color >> 24; + + if (a == 0) + { + /* Skip transparent source pixels. */ + dstptr++; + continue; + } + + sr = (color >> 0) & 0xFF; + sg = (color >> 8) & 0xFF; + sb = (color >> 16) & 0xFF; + + if (a == 255) + { + /* Opaque pixel shortcut. */ + dr = sr; + dg = sg; + db = sb; + } + else + { + /* General pixel color blending. */ + color = *dstptr; + + dr = (color >> 16) & 0xFF; + dr = (dr * (255 - a) + sr * a) / 255; + dg = (color >> 8) & 0xFF; + dg = (dg * (255 - a) + sg * a) / 255; + db = (color >> 0) & 0xFF; + db = (db * (255 - a) + sb * a) / 255; + } + + color = (a << 24) | (dr << 16) | (dg << 8) | db; + + *dstptr++ = color; + } + + srcptr = (grub_uint32_t *) (((grub_uint8_t *) srcptr) + srcrowskip); + dstptr = (grub_uint32_t *) (((grub_uint8_t *) dstptr) + dstrowskip); + } +} + +void +grub_video_i386_vbeblit_BGR888_RGBA8888 (struct grub_video_i386_vbeblit_info *dst, + struct grub_video_i386_vbeblit_info *src, + int x, int y, int width, int height, + int offset_x, int offset_y) +{ + grub_uint32_t *srcptr; + grub_uint8_t *dstptr; + unsigned srcrowskip; + unsigned dstrowskip; + int i; + int j; + + /* Calculate the number of bytes to advance from the end of one line + * to the beginning of the next line. */ + srcrowskip = + src->mode_info->pitch - src->mode_info->bytes_per_pixel * width; + dstrowskip = + dst->mode_info->pitch - dst->mode_info->bytes_per_pixel * width; + + srcptr = (grub_uint32_t *) get_data_ptr (src, offset_x, offset_y); + dstptr = (grub_uint8_t *) get_data_ptr (dst, x, y); + + for (j = 0; j < height; j++) + { + for (i = 0; i < width; i++) + { + grub_uint32_t color; + unsigned int sr; + unsigned int sg; + unsigned int sb; + unsigned int a; + unsigned int dr; + unsigned int dg; + unsigned int db; + + color = *srcptr++; + + a = color >> 24; + + if (a == 0) + { + /* Skip transparent source pixels. */ + dstptr += 3; + continue; + } + + sr = (color >> 0) & 0xFF; + sg = (color >> 8) & 0xFF; + sb = (color >> 16) & 0xFF; + + if (a == 255) + { + /* Opaque pixel shortcut. */ + dr = sr; + dg = sg; + db = sb; + } + else + { + /* General pixel color blending. */ + color = *dstptr; + + db = dstptr[0]; + db = (db * (255 - a) + sb * a) / 255; + dg = dstptr[1]; + dg = (dg * (255 - a) + sg * a) / 255; + dr = dstptr[2]; + dr = (dr * (255 - a) + sr * a) / 255; + } + + *dstptr++ = db; + *dstptr++ = dg; + *dstptr++ = dr; + } + + srcptr = (grub_uint32_t *) (((grub_uint8_t *) srcptr) + srcrowskip); + dstptr += dstrowskip; + } +} + +void +grub_video_i386_vbeblit_BGR888_RGBX8888 (struct grub_video_i386_vbeblit_info *dst, + struct grub_video_i386_vbeblit_info *src, + int x, int y, int width, int height, + int offset_x, int offset_y) +{ + grub_uint32_t *srcptr; + grub_uint8_t *dstptr; + unsigned srcrowskip; + unsigned dstrowskip; + int i; + int j; + + /* Calculate the number of bytes to advance from the end of one line + * to the beginning of the next line. */ + srcrowskip = + src->mode_info->pitch - src->mode_info->bytes_per_pixel * width; + dstrowskip = + dst->mode_info->pitch - dst->mode_info->bytes_per_pixel * width; + + srcptr = (grub_uint32_t *) get_data_ptr (src, offset_x, offset_y); + dstptr = (grub_uint8_t *) get_data_ptr (dst, x, y); + + for (j = 0; j < height; j++) + { + for (i = 0; i < width; i++) + { + grub_uint32_t color; + grub_uint8_t sr; + grub_uint8_t sg; + grub_uint8_t sb; + + color = *srcptr++; + + sr = (color >> 0) & 0xFF; + sg = (color >> 8) & 0xFF; + sb = (color >> 16) & 0xFF; + + *dstptr++ = sb; + *dstptr++ = sg; + *dstptr++ = sr; + } + + srcptr = (grub_uint32_t *) (((grub_uint8_t *) srcptr) + srcrowskip); + dstptr += dstrowskip; + } +} + +void +grub_video_i386_vbeblit_RGBA8888_RGBA8888 (struct grub_video_i386_vbeblit_info *dst, struct grub_video_i386_vbeblit_info *src, int x, int y, int width, int height, int offset_x, int offset_y) @@ -101,10 +437,10 @@ } void -grub_video_i386_vbeblit_R8G8B8X8_R8G8B8X8 (struct grub_video_i386_vbeblit_info *dst, - struct grub_video_i386_vbeblit_info *src, - int x, int y, int width, int height, - int offset_x, int offset_y) +grub_video_i386_vbeblit_direct32_copy (struct grub_video_i386_vbeblit_info *dst, + struct grub_video_i386_vbeblit_info *src, + int x, int y, int width, int height, + int offset_x, int offset_y) { int j; grub_uint32_t *srcptr; @@ -126,7 +462,7 @@ } void -grub_video_i386_vbeblit_R8G8B8_R8G8B8A8 (struct grub_video_i386_vbeblit_info *dst, +grub_video_i386_vbeblit_RGB888_RGBA8888 (struct grub_video_i386_vbeblit_info *dst, struct grub_video_i386_vbeblit_info *src, int x, int y, int width, int height, int offset_x, int offset_y) @@ -193,7 +529,7 @@ } void -grub_video_i386_vbeblit_R8G8B8_R8G8B8X8 (struct grub_video_i386_vbeblit_info *dst, +grub_video_i386_vbeblit_RGB888_RGBX8888 (struct grub_video_i386_vbeblit_info *dst, struct grub_video_i386_vbeblit_info *src, int x, int y, int width, int height, int offset_x, int offset_y) @@ -231,7 +567,7 @@ } void -grub_video_i386_vbeblit_index_R8G8B8A8 (struct grub_video_i386_vbeblit_info *dst, +grub_video_i386_vbeblit_index_RGBA8888 (struct grub_video_i386_vbeblit_info *dst, struct grub_video_i386_vbeblit_info *src, int x, int y, int width, int height, int offset_x, int offset_y) @@ -295,7 +631,7 @@ } void -grub_video_i386_vbeblit_index_R8G8B8X8 (struct grub_video_i386_vbeblit_info *dst, +grub_video_i386_vbeblit_index_RGBX8888 (struct grub_video_i386_vbeblit_info *dst, struct grub_video_i386_vbeblit_info *src, int x, int y, int width, int height, int offset_x, int offset_y) @@ -332,7 +668,7 @@ } void -grub_video_i386_vbeblit_R8G8B8A8_R8G8B8 (struct grub_video_i386_vbeblit_info *dst, +grub_video_i386_vbeblit_RGBA8888_RGB888 (struct grub_video_i386_vbeblit_info *dst, struct grub_video_i386_vbeblit_info *src, int x, int y, int width, int height, int offset_x, int offset_y) @@ -368,7 +704,7 @@ } void -grub_video_i386_vbeblit_R8G8B8_R8G8B8 (struct grub_video_i386_vbeblit_info *dst, +grub_video_i386_vbeblit_RGB888_RGB888 (struct grub_video_i386_vbeblit_info *dst, struct grub_video_i386_vbeblit_info *src, int x, int y, int width, int height, int offset_x, int offset_y) @@ -393,7 +729,7 @@ } void -grub_video_i386_vbeblit_index_R8G8B8 (struct grub_video_i386_vbeblit_info *dst, +grub_video_i386_vbeblit_index_RGB888 (struct grub_video_i386_vbeblit_info *dst, struct grub_video_i386_vbeblit_info *src, int x, int y, int width, int height, int offset_x, int offset_y) === modified file 'video/i386/pc/vbefill.c' --- video/i386/pc/vbefill.c 2007-07-21 22:32:33 +0000 +++ video/i386/pc/vbefill.c 2008-07-03 13:49:18 +0000 @@ -34,51 +34,63 @@ #include void -grub_video_i386_vbefill_R8G8B8A8 (struct grub_video_i386_vbeblit_info *dst, +grub_video_i386_vbefill_direct32 (struct grub_video_i386_vbeblit_info *dst, grub_video_color_t color, int x, int y, int width, int height) { int i; int j; grub_uint32_t *dstptr; - - /* We do not need to worry about data being out of bounds - as we assume that everything has been checked before. */ + grub_size_t rowskip; + + /* Calculate the number of bytes to advance from the end of one line + * to the beginning of the next line. */ + rowskip = dst->mode_info->pitch - dst->mode_info->bytes_per_pixel * width; + + /* Get the start address. */ + dstptr = (grub_uint32_t *) grub_video_vbe_get_video_ptr (dst, x, y); for (j = 0; j < height; j++) { - dstptr = (grub_uint32_t *)grub_video_vbe_get_video_ptr (dst, x, y + j); - for (i = 0; i < width; i++) *dstptr++ = color; + + /* Advance the dest pointer to the right location on the next line. */ + dstptr = (grub_uint32_t *) (((char *) dstptr) + rowskip); } } void -grub_video_i386_vbefill_R8G8B8 (struct grub_video_i386_vbeblit_info *dst, - grub_video_color_t color, int x, int y, - int width, int height) +grub_video_i386_vbefill_direct24 (struct grub_video_i386_vbeblit_info *dst, + grub_video_color_t color, int x, int y, + int width, int height) { int i; int j; + grub_size_t rowskip; grub_uint8_t *dstptr; - grub_uint8_t fillr = (grub_uint8_t)((color >> 0) & 0xFF); - grub_uint8_t fillg = (grub_uint8_t)((color >> 8) & 0xFF); - grub_uint8_t fillb = (grub_uint8_t)((color >> 16) & 0xFF); + grub_uint8_t fill0 = (grub_uint8_t)((color >> 0) & 0xFF); + grub_uint8_t fill1 = (grub_uint8_t)((color >> 8) & 0xFF); + grub_uint8_t fill2 = (grub_uint8_t)((color >> 16) & 0xFF); - /* We do not need to worry about data being out of bounds - as we assume that everything has been checked before. */ + /* Calculate the number of bytes to advance from the end of one line + * to the beginning of the next line. */ + rowskip = dst->mode_info->pitch - dst->mode_info->bytes_per_pixel * width; + + /* Get the start address. */ + dstptr = (grub_uint8_t *) grub_video_vbe_get_video_ptr (dst, x, y); for (j = 0; j < height; j++) { - dstptr = (grub_uint8_t *)grub_video_vbe_get_video_ptr (dst, x, y + j); - for (i = 0; i < width; i++) { - *dstptr++ = fillr; - *dstptr++ = fillg; - *dstptr++ = fillb; + *dstptr++ = fill0; + *dstptr++ = fill1; + *dstptr++ = fill2; } + + /* Advance the dest pointer to the right location on the next line. */ + dstptr += rowskip; } } @@ -89,18 +101,24 @@ { int i; int j; + grub_size_t rowskip; grub_uint8_t *dstptr; grub_uint8_t fill = (grub_uint8_t)color & 0xFF; - /* We do not need to worry about data being out of bounds - as we assume that everything has been checked before. */ + /* Calculate the number of bytes to advance from the end of one line + * to the beginning of the next line. */ + rowskip = dst->mode_info->pitch - dst->mode_info->bytes_per_pixel * width; + + /* Get the start address. */ + dstptr = (grub_uint8_t *) grub_video_vbe_get_video_ptr (dst, x, y); for (j = 0; j < height; j++) { - dstptr = (grub_uint8_t *)grub_video_vbe_get_video_ptr (dst, x, y + j); - for (i = 0; i < width; i++) *dstptr++ = fill; + + /* Advance the dest pointer to the right location on the next line. */ + dstptr += rowskip; } } @@ -112,9 +130,6 @@ int i; int j; - /* We do not need to worry about data being out of bounds - as we assume that everything has been checked before. */ - for (j = 0; j < height; j++) for (i = 0; i < width; i++) set_pixel (dst, x+i, y+j, color); === modified file 'video/i386/pc/vbeutil.c' --- video/i386/pc/vbeutil.c 2007-07-21 22:32:33 +0000 +++ video/i386/pc/vbeutil.c 2008-07-03 14:10:25 +0000 @@ -52,6 +52,11 @@ + y * source->mode_info->pitch + x; break; + + /* case 1: */ + /* For 1-bit bitmaps, addressing needs to be done at the bit level + * and it doesn't make sense, in general, to ask for a pointer + * to a particular pixel's data. */ } return ptr; @@ -86,6 +91,17 @@ color = *(grub_uint8_t *)get_data_ptr (source, x, y); break; + case 1: + if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_1BIT_PACKED) + { + int bit_index = y * source->mode_info->width + x; + grub_uint8_t *ptr = (grub_uint8_t *)source->data + + bit_index / 8; + int bit_pos = 7 - bit_index % 8; + color = (*ptr >> bit_pos) & 0x01; + } + break; + default: break; } @@ -143,6 +159,17 @@ } break; + case 1: + if (source->mode_info->blit_format == GRUB_VIDEO_BLIT_FORMAT_1BIT_PACKED) + { + int bit_index = y * source->mode_info->width + x; + grub_uint8_t *ptr = (grub_uint8_t *)source->data + + bit_index / 8; + int bit_pos = 7 - bit_index % 8; + *ptr = (*ptr & ~(1 << bit_pos)) | ((color & 0x01) << bit_pos); + } + break; + default: break; } === modified file 'video/readers/jpeg.c' --- video/readers/jpeg.c 2008-01-22 13:50:38 +0000 +++ video/readers/jpeg.c 2008-07-03 13:49:18 +0000 @@ -542,7 +542,7 @@ if (grub_video_bitmap_create (data->bitmap, data->image_width, data->image_height, - GRUB_VIDEO_BLIT_FORMAT_R8G8B8)) + GRUB_VIDEO_BLIT_FORMAT_RGB_888)) return grub_errno; data->bit_mask = 0x0; === modified file 'video/readers/png.c' --- video/readers/png.c 2008-03-31 11:00:48 +0000 +++ video/readers/png.c 2008-07-03 13:49:18 +0000 @@ -231,7 +231,7 @@ { if (grub_video_bitmap_create (data->bitmap, data->image_width, data->image_height, - GRUB_VIDEO_BLIT_FORMAT_R8G8B8)) + GRUB_VIDEO_BLIT_FORMAT_RGB_888)) return grub_errno; data->bpp = 3; } @@ -239,7 +239,7 @@ { if (grub_video_bitmap_create (data->bitmap, data->image_width, data->image_height, - GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8)) + GRUB_VIDEO_BLIT_FORMAT_RGBA_8888)) return grub_errno; data->bpp = 4; } === modified file 'video/readers/tga.c' --- video/readers/tga.c 2007-12-30 08:52:06 +0000 +++ video/readers/tga.c 2008-07-03 13:49:18 +0000 @@ -397,7 +397,7 @@ { grub_video_bitmap_create (bitmap, header.image_width, header.image_height, - GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8); + GRUB_VIDEO_BLIT_FORMAT_RGBA_8888); if (grub_errno != GRUB_ERR_NONE) { grub_file_close (file); @@ -420,7 +420,7 @@ { grub_video_bitmap_create (bitmap, header.image_width, header.image_height, - GRUB_VIDEO_BLIT_FORMAT_R8G8B8); + GRUB_VIDEO_BLIT_FORMAT_RGB_888); if (grub_errno != GRUB_ERR_NONE) { grub_file_close (file); === added file 'video/setmode.c' --- video/setmode.c 1970-01-01 00:00:00 +0000 +++ video/setmode.c 2008-07-19 19:31:46 +0000 @@ -0,0 +1,249 @@ +/* video/setmode.c - Smart video mode selection based on preferences. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2006,2007,2008 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include + +/* Set the video mode based on the preferred modes specified in MODE_LIST in + the form: x[x][;...] + + For example: 640x480;800x600x8;400x300x32 + + If MODE_LIST is null, or no modes in it are usable, then DEFAULT_WIDTH and + DEFAULT_HEIGHT are used to set the mode. The MODE_FLAGS argument determines + the video mode flags such as double buffering that are used. */ + +grub_err_t +grub_video_setup_preferred_mode (const char *mode_list, int mode_flags, + int default_width, int default_height) +{ + int mode_found = 0; + + if (mode_list != NULL) + { + /* Take copy of mode_list as we don't want tat. */ + char *const modes_copy = grub_strdup (mode_list); + if (modes_copy == NULL) + return grub_errno; + + /* Initialize next mode. */ + char *next_mode = modes_copy; + + /* Loop until all modes has been tested out. */ + while ((next_mode != NULL) && !mode_found) + { + /* Use last next_mode as current mode. */ + char *tmp = next_mode; + + int width = -1; + int height = -1; + int depth = -1; + + /* Save position of next mode and separate modes. */ + next_mode = grub_strchr(next_mode, ';'); + if (next_mode) + { + *next_mode = 0; + next_mode++; + } + + /* Skip whitespace. */ + while (grub_isspace (*tmp)) + tmp++; + + /* Initialize token holders. */ + char *current_mode = tmp; + char *param = tmp; + char *value = NULL; + + /* Parse x[x]*/ + + /* Find width value. */ + value = param; + param = grub_strchr(param, 'x'); + if (param == NULL) + { + grub_err_t rc; + + /* First setup error message. */ + rc = grub_error (GRUB_ERR_BAD_ARGUMENT, + "Invalid mode: %s\n", + current_mode); + + /* Free memory before returning. */ + grub_free (modes_copy); + + return rc; + } + + *param = 0; + param++; + + width = grub_strtoul (value, 0, 0); + if (grub_errno != GRUB_ERR_NONE) + { + grub_err_t rc; + + /* First setup error message. */ + rc = grub_error (GRUB_ERR_BAD_ARGUMENT, + "Invalid mode: %s\n", + current_mode); + + /* Free memory before returning. */ + grub_free (modes_copy); + + return rc; + } + + /* Find height value. */ + value = param; + param = grub_strchr(param, 'x'); + if (param == NULL) + { + height = grub_strtoul (value, 0, 0); + if (grub_errno != GRUB_ERR_NONE) + { + grub_err_t rc; + + /* First setup error message. */ + rc = grub_error (GRUB_ERR_BAD_ARGUMENT, + "Invalid mode: %s\n", + current_mode); + + /* Free memory before returning. */ + grub_free (modes_copy); + + return rc; + } + } + else + { + /* We have optional color depth value. */ + *param = 0; + param++; + + height = grub_strtoul (value, 0, 0); + if (grub_errno != GRUB_ERR_NONE) + { + grub_err_t rc; + + /* First setup error message. */ + rc = grub_error (GRUB_ERR_BAD_ARGUMENT, + "Invalid mode: %s\n", + current_mode); + + /* Free memory before returning. */ + grub_free (modes_copy); + + return rc; + } + + /* Convert color depth value. */ + value = param; + depth = grub_strtoul (value, 0, 0); + if (grub_errno != GRUB_ERR_NONE) + { + grub_err_t rc; + + /* First setup error message. */ + rc = grub_error (GRUB_ERR_BAD_ARGUMENT, + "Invalid mode: %s\n", + current_mode); + + /* Free memory before returning. */ + grub_free (modes_copy); + + return rc; + } + } + + /* Try out video mode. */ + + int flags = mode_flags; + /* If we have <= 8 bits, assume it is an indexed color mode. */ + if ((depth <= 8) && (depth != -1)) + flags |= GRUB_VIDEO_MODE_TYPE_INDEX_COLOR; + + /* We have > 8 bits; assume that it is RGB color mode. */ + if (depth > 8) + flags |= GRUB_VIDEO_MODE_TYPE_RGB; + + /* If user requested specific depth, pass the request to driver. */ + if (depth != -1) + flags |= (depth << GRUB_VIDEO_MODE_TYPE_DEPTH_POS) + & GRUB_VIDEO_MODE_TYPE_DEPTH_MASK; + + /* Try to initialize requested mode. Ignore any errors. */ + grub_error_push (); + if (grub_video_setup (width, height, flags) != GRUB_ERR_NONE) + { + grub_error_pop (); + continue; + } + + /* Figure out what mode we ended up. */ + struct grub_video_mode_info mode_info; + if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE) + { + /* Couldn't get video mode info, restore old mode + and continue to next one. */ + grub_error_pop (); + + grub_video_restore (); + continue; + } + + /* Restore state of error stack. */ + grub_error_pop (); + + /* Mode found! Exit loop. */ + mode_found = 1; + } + + /* Free memory. */ + grub_free (modes_copy); + } + + if (!mode_found) + { + /* No gfxmode variable set, or no listed mode was supported. + Use the caller-specified defaults. */ + int flags = mode_flags | GRUB_VIDEO_MODE_TYPE_RGB; + + /* Initialize user requested mode. */ + if (grub_video_setup (default_width, default_height, flags) + != GRUB_ERR_NONE) + return grub_errno; + + /* Figure out what mode we ended up. */ + struct grub_video_mode_info mode_info; + if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE) + grub_video_restore (); + else + mode_found = 1; + } + + if (!mode_found) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "No suitable mode found."); + + return (grub_errno = GRUB_ERR_NONE); +} === modified file 'video/video.c' --- video/video.c 2008-01-01 12:02:07 +0000 +++ video/video.c 2008-07-09 16:50:11 +0000 @@ -152,15 +152,22 @@ if (mode_info->bpp == 32) { if ((mode_info->red_mask_size == 8) + && (mode_info->red_field_pos == 16) + && (mode_info->green_mask_size == 8) + && (mode_info->green_field_pos == 8) + && (mode_info->blue_mask_size == 8) + && (mode_info->blue_field_pos == 0)) + { + return GRUB_VIDEO_BLIT_FORMAT_BGRA_8888; + } + if ((mode_info->red_mask_size == 8) && (mode_info->red_field_pos == 0) && (mode_info->green_mask_size == 8) && (mode_info->green_field_pos == 8) && (mode_info->blue_mask_size == 8) - && (mode_info->blue_field_pos == 16) - && (mode_info->reserved_mask_size == 8) - && (mode_info->reserved_field_pos == 24)) + && (mode_info->blue_field_pos == 16)) { - return GRUB_VIDEO_BLIT_FORMAT_R8G8B8A8; + return GRUB_VIDEO_BLIT_FORMAT_RGBA_8888; } } @@ -168,13 +175,22 @@ if (mode_info->bpp == 24) { if ((mode_info->red_mask_size == 8) + && (mode_info->red_field_pos == 16) + && (mode_info->green_mask_size == 8) + && (mode_info->green_field_pos == 8) + && (mode_info->blue_mask_size == 8) + && (mode_info->blue_field_pos == 0)) + { + return GRUB_VIDEO_BLIT_FORMAT_BGR_888; + } + if ((mode_info->red_mask_size == 8) && (mode_info->red_field_pos == 0) && (mode_info->green_mask_size == 8) && (mode_info->green_field_pos == 8) && (mode_info->blue_mask_size == 8) && (mode_info->blue_field_pos == 16)) { - return GRUB_VIDEO_BLIT_FORMAT_R8G8B8; + return GRUB_VIDEO_BLIT_FORMAT_RGB_888; } } @@ -305,6 +321,19 @@ return grub_video_adapter_active->blit_glyph (glyph, color, x, y); } +/* Draw string to screen using specified color and font. */ +grub_err_t +grub_video_draw_string (const char *str, grub_font_t font, + grub_video_color_t color, + int left_x, int baseline_y) +{ + if (! grub_video_adapter_active) + return grub_error (GRUB_ERR_BAD_DEVICE, "No video mode activated"); + + return grub_video_adapter_active->draw_string (str, font, color, + left_x, baseline_y); +} + /* Blit bitmap to screen. */ grub_err_t grub_video_blit_bitmap (struct grub_video_bitmap *bitmap, # Begin bundle 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 /i7kinChIclOvjQ=