# Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: colin@gibibit.com-20080818142101-ko4kkni8u61e510j # target_branch: file:///home/cdb/grub/repo/trunk-clean/ # testament_sha1: 9ef85bcbc96d0f5c165e2cdd1996f1c1be6a6e53 # timestamp: 2008-08-18 08:47:00 -0700 # source_branch: http://grub.gibibit.com/bzr/trunk-clean # base_revision_id: colin@gibibit.com-20080818142050-zqm84a9aewr81xtj # # 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-08-18 12:46:31 +0000 +++ ChangeLog 2008-08-18 14:21:01 +0000 @@ -667,7 +667,6 @@ (memdisk_mod_SOURCES, memdisk_mod_CFLAGS) (memdisk_mod_LDFLAGS): New variables. * conf/i386-coreboot.rmk: Likewise. - * conf/i386-ieee1275.rmk: Likewise. 2008-08-02 Robert Millan @@ -800,6 +799,72 @@ * normal/main.c (get_line): Fix buffer overflow bug. +2008-07-28 Colin D Bennett + + High resolution timer support. Implemented for x86 CPUs using TSC. + Extracted generic grub_millisleep() so it's linked in only as needed. + This requires a Pentium compatible CPU; if the RDTSC instruction is + not supported, then it falls back on the generic grub_get_time_ms() + implementation that uses the machine's RTC. + + * conf/i386-efi.rmk: Added new source files to kernel_elf_SOURCES. + + * conf/i386-pc.rmk: Likewise. + + * conf/sparc64-ieee1275.rmk: Likewise. + + * conf/powerpc-ieee1275.rmk: Likewise. + + + * kern/generic/rtc_get_time_ms.c: New file. + + * kern/generic/millisleep.c: New file. + + * kern/misc.c (grub_millisleep_generic): Removed. + + * 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. New inline + function. + (grub_cpu_is_cpuid_supported): New inline function. + (grub_cpu_is_tsc_supported): New inline function. + (grub_tsc_init): New function prototype. + (grub_tsc_get_time_ms): New function prototype. + + * kern/i386/tsc.c (grub_get_time_ms): New file. New function. + (calibrate_tsc): New static function. + (grub_tsc_init): New function. + + * include/grub/time.h (grub_millisleep_generic): Removed. + (grub_get_time_ms): New function. + (grub_install_get_time_ms): New function. + + * kern/time.c (grub_get_time_ms): New function. + (grub_install_get_time_ms): New function. + + * kern/i386/efi/init.c (grub_millisleep): Removed. + (grub_machine_init): Call grub_tsc_init. + + * kern/i386/linuxbios/init.c (grub_machine_init): Install the RTC + get_time_ms() implementation. + + * kern/sparc64/ieee1275/init.c (grub_millisleep): Removed. + (ieee1275_get_time_ms): New function. + (grub_machine_init): Install get_time_ms() implementation. + + * kern/i386/pc/init.c (grub_machine_init): Call grub_tsc_init(). + (grub_millisleep): Removed. + + * kern/ieee1275/init.c (grub_millisleep): Removed. + (grub_machine_init): Install ieee1275_get_time_ms() implementation. + (ieee1275_get_time_ms): New static function. + (grub_get_rtc): Now calls ieee1275_get_time_ms(), which does the real + work. + + * conf/i386-ieee1275.rmk: Likewise. + 2008-07-28 Robert Millan * partmap/apple.c (GRUB_APPLE_HEADER_MAGIC): New macro. === modified file 'Makefile.in' --- Makefile.in 2008-08-09 11:30:26 +0000 +++ Makefile.in 2008-08-11 14:43:03 +0000 @@ -90,6 +90,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@ @@ -138,13 +171,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/videotest.c' --- commands/videotest.c 2007-07-21 22:32:33 +0000 +++ commands/videotest.c 2008-08-11 15:02:14 +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/grub/themes/winter/without-leaves.png"; + 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-08-14 17:59:33 +0000 +++ conf/common.rmk 2008-08-16 17:35:05 +0000 @@ -287,6 +287,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 crc.mod # For hello.mod. @@ -294,6 +295,27 @@ hello_mod_CFLAGS = $(COMMON_CFLAGS) hello_mod_LDFLAGS = $(COMMON_LDFLAGS) +# For gfxmenu.mod. +gfxmenu_mod_SOURCES = \ + gfxmenu/gfxmenu.c \ + gfxmenu/model.c \ + gfxmenu/view.c \ + gfxmenu/icon_manager.c \ + gfxmenu/theme_loader.c \ + gfxmenu/widget-box.c \ + gfxmenu/gui_canvas.c \ + gfxmenu/gui_circular_progress.c \ + gfxmenu/gui_box.c \ + gfxmenu/gui_label.c \ + gfxmenu/gui_list.c \ + gfxmenu/gui_image.c \ + gfxmenu/gui_progress_bar.c \ + gfxmenu/gui_util.c \ + gfxmenu/gui_string_util.c \ + gfxmenu/named_colors.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) @@ -330,7 +352,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) @@ -395,7 +417,7 @@ crc_mod_LDFLAGS = $(COMMON_LDFLAGS) # Misc. -pkglib_MODULES += gzio.mod bufio.mod elf.mod +pkglib_MODULES += gzio.mod bufio.mod elf.mod trig.mod # For elf.mod. elf_mod_SOURCES = kern/elf.c @@ -411,3 +433,8 @@ bufio_mod_SOURCES = io/bufio.c bufio_mod_CFLAGS = $(COMMON_CFLAGS) bufio_mod_LDFLAGS = $(COMMON_LDFLAGS) + +# For trig.mod. +trig_mod_SOURCES = lib/trig.c +trig_mod_CFLAGS = $(COMMON_CFLAGS) +trig_mod_LDFLAGS = $(COMMON_LDFLAGS) === modified file 'conf/i386-pc.rmk' --- conf/i386-pc.rmk 2008-08-18 12:46:31 +0000 +++ conf/i386-pc.rmk 2008-08-18 14:21:01 +0000 @@ -49,11 +49,13 @@ kern/generic/rtc_get_time_ms.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 machine/pxe.h @@ -253,7 +255,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) @@ -268,7 +270,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/sparc64-ieee1275.rmk' --- conf/sparc64-ieee1275.rmk 2008-08-05 10:54:37 +0000 +++ conf/sparc64-ieee1275.rmk 2008-08-07 23:48:19 +0000 @@ -196,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 'docs/gfxmenu-theme-example.txt' --- docs/gfxmenu-theme-example.txt 1970-01-01 00:00:00 +0000 +++ docs/gfxmenu-theme-example.txt 2008-08-14 21:15:21 +0000 @@ -0,0 +1,128 @@ +# GRUB gfxmenu theme "winter". +# Uses background image from: +# http://www.cyberpunkcafe.com/e107_plugins/autogallery/autogallery.php?show=1.Open%20Source%20Wallpaper +# "without-leaves.png" was called "Without Leafs in Winter.png" + +lua-script: "winter.lua" +title-text: "" +title-font: "Helvetica Bold 18" +status-font: "Helvetica 8" +terminal-font: "Fixed 9" +title-color: "40, 40, 40" +status-color: "#FFF" +status-bg-color: "0, 166, 183, 128" +desktop-image: "without-leaves.png" +desktop-color: "0, 154, 183" +terminal-box: "terminal_*.png" + ++ boot_menu { + position = (120, 60) + preferred_size = (400, -1) + item_font = "Helvetica Bold 14" + selected_item_font = "Helvetica Bold 14" + item_color = "0, 0, 0" + selected_item_color = "203, 251, 255" + menu_pixmap_style = "menu_*.png" + selected_item_pixmap_style = "select_*.png" + icon_width = 44 + icon_height = 44 + item_height = 32 + item_padding = 0 + item_icon_space = 3 + item_spacing = 11 +} + +# You can add text at arbitrary locations on the screen. +# The specification within the "+label {...}" block is free-form, +# so you can use as much or as little white space as you like. + ++ label { + position = (170, 50) + font = "smoothansi 13" + color = "0,0,128" + text = "This is the Winter theme ... brought to you by GRUB!" +} + +# Show the text alignment supported by labels. ++ vbox { + position = (220, 347) + preferred_size = (200, -1) # A preferred size of -1 means automatic. + + label { text="Text alignment demo" align="center" font="aqui 11" } + + label { text="Left" align="left" font="cure 11" } + + label { text="Center" align="center" font="cure 11" } + + label { text="Right" align="right" font="cure 11" } +} + ++ vbox { + position = (580, 10) + + label { text="GNU" font="gelly 11" color="0, 0, 0" } + + label { text="GRUB" font="aqui 11" color="0, 0, 0" } + + label { text="boot loader" font="cure 11" color="0, 0, 0" } +} + ++ hbox { + position = (80, 10) + + label { text="GNU" font="gelly 11" color="0, 0, 0" } + + label { text="GRUB" font="aqui 11" color="0, 0, 0" } + + label { text="boot loader" font="cure 11" color="0, 0, 0" } +} + +# Demonstration of a compound layout: boxes within boxes. ++ hbox +{ + position = (480, 3) + + + vbox + { + # Note: We can't just use 'size' to set the image's size, + # since the vbox will resize the component according to its + # preferred size, which for images is the native image size. + + + image { file="/boot/grub/themes/icons/ubuntu.png" + preferred_size = (20, 20) } + + image { file="/boot/grub/themes/icons/gentoo.png" + preferred_size = (20, 20) } + } + + + vbox + { + + label { text="GRand" font="cure 11" color=#99F } + + label { text="Unified" font="cure 11" color=#BBF } + + label { text="Bootloader" font="cure 11" color=#DDF } + } +} + +# By defining a 'progress_bar' type component with an ID of '__timeout__', +# the progress bar will be used to display the time remaining before an +# the default entry is automatically booted. ++ progress_bar +{ + id = "__timeout__" + position = (80, 393) + preferred_size = (500, 24) + font = "cure 11" + text_color = #000 + fg_color = #CCF + bg_color = #66B + border_color = #006 + show_text = false +} + +# Although the progress_bar component is normally used to indicate the +# time remaining, it's also possible to create other components with an ID +# of '__timeout__'. All components with and ID of 'timeout_bar' will have +# the following properties set based on the timeout value: +# text, value, start, end, visible. +# In this case, we have set 'show_text=false' on the progress bar, and use +# the following label's 'text' property to display the message. ++ label +{ + id = "__timeout__" + position = (80, 420) + preferred_size = (500, 24) + font = "lime 11" + color = #117 + align = "center" +} + + === 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-29 14:51:29 +0000 @@ -0,0 +1,689 @@ +/* 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 + +/* 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_buffile_open (filename, 1024); + 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 + grub_off_t section_end = grub_file_tell (file) + section.length; + if ((int) grub_file_seek (file, section_end) == -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-08-01 03:06:55 +0000 +++ font/manager.c 1970-01-01 00:00:00 +0000 @@ -1,283 +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 -#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_buffile_open (filename, 0); - 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-28 21:35:40 +0000 +++ genmk.rb 2008-08-03 03:57:46 +0000 @@ -126,7 +126,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} @@ -158,18 +158,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 file 'gentrigtables.py' --- gentrigtables.py 1970-01-01 00:00:00 +0000 +++ gentrigtables.py 2008-08-15 23:59:38 +0000 @@ -0,0 +1,59 @@ +#!/usr/bin/python +# Script to generate trigonometric function tables. +# By Colin D. Bennett , 2008-08-15. +# +# 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 . + +from math import * +from sys import stdout + +def write(x): + stdout.write(x) + +def writeTable(arr, name): + indent = ' ' * 4 + write("short ") + write(name) + write("[] =\n{\n") + write(indent) + for i in range(len(arr)): + if i != 0: + write(",") + if i % 10 == 0: + write("\n") + write(indent) + write("%d" % arr[i]) + write("\n};\n") + +def main(): + sintab = [] + costab = [] + for i in range(256): + # Convert to an angle in 1/256 of a circle. + x = i * 2 * pi / 256 + sintab.append(int(round(sin(x) * 16384))) + costab.append(int(round(cos(x) * 16384))) + + write("#define TRIG_ANGLE_MAX 256\n") + write("#define TRIG_FRACTION_SCALE 16384\n") + writeTable(sintab, "sintab") + writeTable(costab, "costab") + +if __name__ == "__main__": + main() + +# vim:ai et sw=4 ts=4 === added directory 'gfxmenu' === added file 'gfxmenu/gfxmenu.c' --- gfxmenu/gfxmenu.c 1970-01-01 00:00:00 +0000 +++ gfxmenu/gfxmenu.c 2008-08-15 23:59:38 +0000 @@ -0,0 +1,231 @@ +/* gfxmenu.c - Graphical menu interface controller. */ +/* + * 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 +#include +#include + +static void switch_to_text_menu (void) +{ + grub_env_set ("menuviewer", "terminal"); +} + +static void +process_key_press (int c, + grub_gfxmenu_model_t model, + grub_gfxmenu_view_t view, + int nested, + int *should_exit) +{ + /* When a key is pressed, stop the timeout. */ + grub_gfxmenu_model_clear_timeout (model); + + if (c == 'j' || c == GRUB_TERM_DOWN) + { + int i = grub_gfxmenu_model_get_selected_index (model); + int num_items = grub_gfxmenu_model_get_num_entries (model); + if (i < num_items - 1) + { + i++; + grub_gfxmenu_model_set_selected_index (model, i); + } + } + else if (c == 'k' || c == GRUB_TERM_UP) + { + int i = grub_gfxmenu_model_get_selected_index (model); + if (i > 0) + { + i--; + grub_gfxmenu_model_set_selected_index (model, i); + } + } + else if (c == '\r' || c == '\n' || c == GRUB_TERM_RIGHT) + { + int selected = grub_gfxmenu_model_get_selected_index (model); + int num_entries = grub_gfxmenu_model_get_num_entries (model); + if (selected >= 0 && selected < num_entries) + { + grub_menu_entry_t entry = + grub_gfxmenu_model_get_entry (model, selected); + grub_gfxmenu_view_execute_entry (view, entry); + } + } + else if (c == 'c') + { + grub_gfxmenu_view_run_terminal (view); + if (grub_errno != GRUB_ERR_NONE) + *should_exit = 1; + } + else if (c == 't') + { + /* The write hook for 'menuviewer' will cause + * grub_menu_viewer_should_return to return nonzero. */ + switch_to_text_menu (); + *should_exit = 1; + } + else if (c == '1') + { + grub_gfxmenu_view_load_theme (view, + "/boot/grub/themes/proto/theme.txt"); + } + else if (c == '2') + { + grub_gfxmenu_view_load_theme (view, + "/boot/grub/themes/winter/theme.txt"); + } + else if (c == '3') + { + grub_gfxmenu_view_load_theme (view, + "/boot/grub/themes/ubuntu1/theme.txt"); + } + else if (c == '4') + { + grub_gfxmenu_view_load_theme (view, + "/boot/grub/themes/ubuntu2/theme.txt"); + } + else if (nested && c == GRUB_TERM_ESC) + { + *should_exit = 1; + } +} + +static void +handle_key_events (grub_gfxmenu_model_t model, + grub_gfxmenu_view_t view, + int nested, + int *should_exit) +{ + while (!*should_exit && grub_checkkey () != -1) + { + int key = grub_getkey (); + int c = GRUB_TERM_ASCII_CHAR (key); + process_key_press (c, model, view, nested, should_exit); + } +} + +static grub_err_t +show_menu (grub_menu_t menu, int nested) +{ + grub_gfxmenu_model_t model; + + model = grub_gfxmenu_model_new (menu); + if (! model) + { + grub_print_error (); + grub_printf ("Initializing menu data for graphical menu failed;\n" + "falling back to terminal based menu.\n"); + grub_wait_after_message (); + switch_to_text_menu (); + return grub_errno; + } + + grub_gfxmenu_view_t view; + + /* Create the view. */ + const char *theme_path = grub_env_get ("theme"); + if (! theme_path) + theme_path = "/boot/grub/themes/proto/theme.txt"; + + view = grub_gfxmenu_view_new (theme_path, model); + if (! view) + { + grub_print_error (); + grub_printf ("Starting graphical menu failed;\n" + "falling back to terminal based menu.\n"); + grub_wait_after_message (); + grub_gfxmenu_model_destroy (model); + switch_to_text_menu (); + return grub_errno; + } + + /* Initially select the default menu entry. */ + int default_index = grub_menu_get_default_entry_index (menu); + grub_gfxmenu_model_set_selected_index (model, default_index); + + /* Start the timer to execute the default entry. */ + grub_gfxmenu_model_set_timeout (model); + + /* Main event loop. */ + int exit_requested = 0; + while (!exit_requested && !grub_menu_viewer_should_return ()) + { + if (grub_gfxmenu_model_timeout_expired (model)) + { + grub_gfxmenu_model_clear_timeout (model); + int i = grub_gfxmenu_model_get_selected_index (model); + grub_menu_entry_t e = grub_gfxmenu_model_get_entry (model, i); + grub_gfxmenu_view_execute_with_fallback (view, e); + continue; + } + + grub_gfxmenu_view_draw (view); + grub_video_swap_buffers (); + handle_key_events (model, view, nested, &exit_requested); + } + + grub_gfxmenu_view_destroy (view); + grub_gfxmenu_model_destroy (model); + + return grub_errno; +} + +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/gui_box.c' --- gfxmenu/gui_box.c 1970-01-01 00:00:00 +0000 +++ gfxmenu/gui_box.c 2008-08-16 17:35:05 +0000 @@ -0,0 +1,367 @@ +/* gui_box.c - GUI container that stack components. */ +/* + * 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 + +struct component_node +{ + grub_gui_component_t component; + struct component_node *next; + struct component_node *prev; +}; + +typedef struct grub_gui_box *grub_gui_box_t; + +typedef void (*layout_func_t) (grub_gui_box_t self, int modify_layout, + int *width, int *height); + +struct grub_gui_box +{ + struct grub_gui_container_ops *container; + + grub_gui_container_t parent; + grub_video_rect_t bounds; + char *id; + int preferred_width; + int preferred_height; + + /* Doubly linked list of components with dummy head & tail nodes. */ + struct component_node chead; + struct component_node ctail; + + /* The layout function: differs for vertical and horizontal boxes. */ + layout_func_t layout_func; +}; + +static void +box_destroy (void *vself) +{ + grub_gui_box_t self = vself; + struct component_node *cur; + struct component_node *next; + for (cur = self->chead.next; cur != &self->ctail; cur = next) + { + /* Copy the 'next' pointer, since we need it for the next iteration, + and we're going to free the memory it is stored in. */ + next = cur->next; + /* Destroy the child component. */ + cur->component->ops->destroy (cur->component); + /* Free the linked list node. */ + grub_free (cur); + } + grub_free (self); +} + +static const char * +box_get_id (void *vself) +{ + grub_gui_box_t self = vself; + return self->id; +} + +static int +box_is_instance (void *vself __attribute__((unused)), const char *type) +{ + return (grub_strcmp (type, "component") == 0 + || grub_strcmp (type, "container") == 0); +} + +static void +layout_horizontally (grub_gui_box_t self, int modify_layout, + int *width, int *height) +{ + /* Start at the left (chead) and set the x coordinates as we go right. */ + /* All components have their width set to the box's width. */ + + struct component_node *cur; + int x = 0; + if (height) + *height = 0; + for (cur = self->chead.next; cur != &self->ctail; cur = cur->next) + { + grub_gui_component_t c = cur->component; + grub_video_rect_t r; + + c->ops->get_preferred_size (c, &r.width, &r.height); + + /* Check and possibly update the maximum width, if non-null. */ + if (height && r.height > *height) + *height = r.height; + + /* Set the component's bounds, if the flag is set. */ + if (modify_layout) + { + r.x = x; + r.y = 0; + /* Width comes from the component's preferred size. */ + r.height = self->bounds.height; + c->ops->set_bounds (c, &r); + } + + x += r.width; + } + + /* Return the sum of the children's preferred widths. */ + if (width) + *width = x; +} + +static void +layout_vertically (grub_gui_box_t self, int modify_layout, + int *width, int *height) +{ + /* Start at the top (chead) and set the y coordinates as we go down. */ + /* All components have their width set to the vbox's width. */ + + struct component_node *cur; + int y = 0; + if (width) + *width = 0; + for (cur = self->chead.next; cur != &self->ctail; cur = cur->next) + { + grub_gui_component_t c = cur->component; + grub_video_rect_t r; + + c->ops->get_preferred_size (c, &r.width, &r.height); + + /* Check and possibly update the maximum width, if non-null. */ + if (width && r.width > *width) + *width = r.width; + + /* Set the component's bounds, if the flag is set. */ + if (modify_layout) + { + r.x = 0; + r.y = y; + r.width = self->bounds.width; + /* Height comes from the component's preferred size. */ + c->ops->set_bounds (c, &r); + } + + y += r.height; + } + + /* Return the sum of the children's preferred heights. */ + if (height) + *height = y; +} + +static void +box_paint (void *vself) +{ + grub_gui_box_t self = vself; + struct component_node *cur; + grub_video_rect_t vpsave; + + grub_gui_set_viewport (&self->bounds, &vpsave); + for (cur = self->chead.next; cur != &self->ctail; cur = cur->next) + { + grub_gui_component_t comp = cur->component; + comp->ops->paint (comp); + } + grub_gui_restore_viewport (&vpsave); +} + +static void +box_set_parent (void *vself, grub_gui_container_t parent) +{ + grub_gui_box_t self = vself; + self->parent = parent; +} + +static grub_gui_container_t +box_get_parent (void *vself) +{ + grub_gui_box_t self = vself; + return self->parent; +} + +static void +box_set_bounds (void *vself, const grub_video_rect_t *bounds) +{ + grub_gui_box_t self = vself; + self->bounds = *bounds; + self->layout_func (self, 1, 0, 0); /* Relayout the children. */ +} + +static void +box_get_bounds (void *vself, grub_video_rect_t *bounds) +{ + grub_gui_box_t self = vself; + *bounds = self->bounds; +} + +/* The box's preferred size is based on the preferred sizes + of its children. */ +static void +box_get_preferred_size (void *vself, int *width, int *height) +{ + grub_gui_box_t self = vself; + self->layout_func (self, 0, width, height); /* Just calculate the size. */ + + /* Allow preferred dimensions to override the computed dimensions. */ + if (self->preferred_width >= 0) + *width = self->preferred_width; + if (self->preferred_height >= 0) + *height = self->preferred_height; +} + +static void +box_set_property (void *vself, const char *name, const char *value) +{ + grub_gui_box_t self = vself; + if (grub_strcmp (name, "id") == 0) + { + grub_free (self->id); + if (value) + self->id = grub_strdup (value); + else + self->id = 0; + } + else if (grub_strcmp (name, "preferred_size") == 0) + { + int w; + int h; + if (grub_gui_parse_2_tuple (value, &w, &h)) + { + self->preferred_width = w; + self->preferred_height = h; + } + } +} + +static void +box_add (void *vself, grub_gui_component_t comp) +{ + grub_gui_box_t self = vself; + struct component_node *node; + node = grub_malloc (sizeof (*node)); + if (! node) + return; /* Note: probably should handle the error. */ + node->component = comp; + /* Insert the node before the tail. */ + node->prev = self->ctail.prev; + node->prev->next = node; + node->next = &self->ctail; + node->next->prev = node; + + comp->ops->set_parent (comp, (grub_gui_container_t) self); + self->layout_func (self, 1, 0, 0); /* Relayout the children. */ +} + +static void +box_remove (void *vself, grub_gui_component_t comp) +{ + grub_gui_box_t self = vself; + struct component_node *cur; + for (cur = self->chead.next; cur != &self->ctail; cur = cur->next) + { + if (cur->component == comp) + { + /* Unlink 'cur' from the list. */ + cur->prev->next = cur->next; + cur->next->prev = cur->prev; + /* Free the node's memory (but don't destroy the component). */ + grub_free (cur); + /* Must not loop again, since 'cur' would be dereferenced! */ + return; + } + } +} + +static void +box_iterate_children (void *vself, + grub_gui_component_callback cb, void *userdata) +{ + grub_gui_box_t self = vself; + struct component_node *cur; + for (cur = self->chead.next; cur != &self->ctail; cur = cur->next) + cb (cur->component, userdata); +} + +static struct grub_gui_container_ops box_ops = +{ + .component = + { + .destroy = box_destroy, + .get_id = box_get_id, + .is_instance = box_is_instance, + .paint = box_paint, + .set_parent = box_set_parent, + .get_parent = box_get_parent, + .set_bounds = box_set_bounds, + .get_bounds = box_get_bounds, + .get_preferred_size = box_get_preferred_size, + .set_property = box_set_property + }, + .add = box_add, + .remove = box_remove, + .iterate_children = box_iterate_children +}; + +/* Box constructor. Specify the appropriate layout function to create + a horizontal or vertical stacking box. */ +static grub_gui_box_t +box_new (layout_func_t layout_func) +{ + grub_gui_box_t box; + box = grub_malloc (sizeof (*box)); + if (! box) + return 0; + box->container = &box_ops; + box->parent = 0; + box->bounds.x = 0; + box->bounds.y = 0; + box->bounds.width = 0; + box->bounds.height = 0; + box->id = 0; + box->preferred_width = -1; + box->preferred_height = -1; + box->chead.component = 0; + box->chead.prev = 0; + box->chead.next = &box->ctail; + box->ctail.component = 0; + box->ctail.prev = &box->chead; + box->ctail.next = 0; + box->layout_func = layout_func; + return box; +} + +/* Create a new container that stacks its child components horizontally, + from left to right. Each child get a width corresponding to its + preferred width. The height of each child is set the maximum of the + preferred heights of all children. */ +grub_gui_container_t +grub_gui_hbox_new (void) +{ + return (grub_gui_container_t) box_new (layout_horizontally); +} + +/* Create a new container that stacks its child components verticallyj, + from top to bottom. Each child get a height corresponding to its + preferred height. The width of each child is set the maximum of the + preferred widths of all children. */ +grub_gui_container_t +grub_gui_vbox_new (void) +{ + return (grub_gui_container_t) box_new (layout_vertically); +} === added file 'gfxmenu/gui_canvas.c' --- gfxmenu/gui_canvas.c 1970-01-01 00:00:00 +0000 +++ gfxmenu/gui_canvas.c 2008-08-16 17:35:05 +0000 @@ -0,0 +1,264 @@ +/* gui_canvas.c - GUI container allowing manually placed components. */ +/* + * 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 + +// TODO Add layering so that components can be properly overlaid. + +struct component_node +{ + grub_gui_component_t component; + struct component_node *next; +}; + +struct grub_gui_canvas +{ + struct grub_gui_container_ops *container; + + grub_gui_container_t parent; + grub_video_rect_t bounds; + char *id; + int preferred_width; + int preferred_height; + /* Component list (dummy head node). */ + struct component_node components; +}; + +typedef struct grub_gui_canvas *grub_gui_canvas_t; + +static void +canvas_destroy (void *vself) +{ + grub_gui_canvas_t self = vself; + struct component_node *cur; + struct component_node *next; + for (cur = self->components.next; cur; cur = next) + { + /* Copy the 'next' pointer, since we need it for the next iteration, + and we're going to free the memory it is stored in. */ + next = cur->next; + /* Destroy the child component. */ + cur->component->ops->destroy (cur->component); + /* Free the linked list node. */ + grub_free (cur); + } + grub_free (self); +} + +static const char * +canvas_get_id (void *vself) +{ + grub_gui_canvas_t self = vself; + return self->id; +} + +static int +canvas_is_instance (void *vself __attribute__((unused)), const char *type) +{ + return (grub_strcmp (type, "component") == 0 + || grub_strcmp (type, "container") == 0); +} + +static void +canvas_paint (void *vself) +{ + grub_gui_canvas_t self = vself; + struct component_node *cur; + grub_video_rect_t vpsave; + + grub_gui_set_viewport (&self->bounds, &vpsave); + for (cur = self->components.next; cur; cur = cur->next) + { + int pw; + int ph; + grub_video_rect_t r; + grub_gui_component_t comp; + + comp = cur->component; + + /* Give the child its preferred size. */ + comp->ops->get_preferred_size (comp, &pw, &ph); + comp->ops->get_bounds (comp, &r); + if (r.width != pw || r.height != ph) + { + r.width = pw; + r.height = ph; + comp->ops->set_bounds (comp, &r); + } + + /* Paint the child. */ + comp->ops->paint (comp); + } + grub_gui_restore_viewport (&vpsave); +} + +static void +canvas_set_parent (void *vself, grub_gui_container_t parent) +{ + grub_gui_canvas_t self = vself; + self->parent = parent; +} + +static grub_gui_container_t +canvas_get_parent (void *vself) +{ + grub_gui_canvas_t self = vself; + return self->parent; +} + +static void +canvas_set_bounds (void *vself, const grub_video_rect_t *bounds) +{ + grub_gui_canvas_t self = vself; + self->bounds = *bounds; +} + +static void +canvas_get_bounds (void *vself, grub_video_rect_t *bounds) +{ + grub_gui_canvas_t self = vself; + *bounds = self->bounds; +} + +static void +canvas_get_preferred_size (void *vself, int *width, int *height) +{ + grub_gui_canvas_t self = vself; + *width = 0; + *height = 0; + + /* Allow preferred dimensions to override the empty dimensions. */ + if (self->preferred_width >= 0) + *width = self->preferred_width; + if (self->preferred_height >= 0) + *height = self->preferred_height; +} + +static void +canvas_set_property (void *vself, const char *name, const char *value) +{ + grub_gui_canvas_t self = vself; + if (grub_strcmp (name, "id") == 0) + { + grub_free (self->id); + if (value) + self->id = grub_strdup (value); + else + self->id = 0; + } + else if (grub_strcmp (name, "preferred_size") == 0) + { + int w; + int h; + if (grub_gui_parse_2_tuple (value, &w, &h)) + { + self->preferred_width = w; + self->preferred_height = h; + } + } +} + +static void +canvas_add (void *vself, grub_gui_component_t comp) +{ + grub_gui_canvas_t self = vself; + struct component_node *node; + node = grub_malloc (sizeof (*node)); + if (! node) + return; /* Note: probably should handle the error. */ + node->component = comp; + node->next = self->components.next; + self->components.next = node; + comp->ops->set_parent (comp, (grub_gui_container_t) self); +} + +static void +canvas_remove (void *vself, grub_gui_component_t comp) +{ + grub_gui_canvas_t self = vself; + struct component_node *cur; + struct component_node *prev; + prev = &self->components; + for (cur = self->components.next; cur; prev = cur, cur = cur->next) + { + if (cur->component == comp) + { + /* Unlink 'cur' from the list. */ + prev->next = cur->next; + /* Free the node's memory (but don't destroy the component). */ + grub_free (cur); + /* Must not loop again, since 'cur' would be dereferenced! */ + return; + } + } +} + +static void +canvas_iterate_children (void *vself, + grub_gui_component_callback cb, void *userdata) +{ + grub_gui_canvas_t self = vself; + struct component_node *cur; + for (cur = self->components.next; cur; cur = cur->next) + cb (cur->component, userdata); +} + +static struct grub_gui_container_ops canvas_ops = +{ + .component = + { + .destroy = canvas_destroy, + .get_id = canvas_get_id, + .is_instance = canvas_is_instance, + .paint = canvas_paint, + .set_parent = canvas_set_parent, + .get_parent = canvas_get_parent, + .set_bounds = canvas_set_bounds, + .get_bounds = canvas_get_bounds, + .get_preferred_size = canvas_get_preferred_size, + .set_property = canvas_set_property + }, + .add = canvas_add, + .remove = canvas_remove, + .iterate_children = canvas_iterate_children +}; + +grub_gui_container_t +grub_gui_canvas_new (void) +{ + grub_gui_canvas_t canvas; + canvas = grub_malloc (sizeof (*canvas)); + if (! canvas) + return 0; + canvas->container = &canvas_ops; + canvas->parent = 0; + canvas->bounds.x = 0; + canvas->bounds.y = 0; + canvas->bounds.width = 0; + canvas->bounds.height = 0; + canvas->id = 0; + canvas->preferred_width = -1; + canvas->preferred_height = -1; + canvas->components.component = 0; + canvas->components.next = 0; + return (grub_gui_container_t) canvas; +} === added file 'gfxmenu/gui_circular_progress.c' --- gfxmenu/gui_circular_progress.c 1970-01-01 00:00:00 +0000 +++ gfxmenu/gui_circular_progress.c 2008-08-16 17:35:05 +0000 @@ -0,0 +1,339 @@ +/* gui_circular_process.c - GUI circular progress indicator component. */ +/* + * 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 + +struct grub_gui_circular_progress +{ + struct grub_gui_component_ops *circprog_ops; + + grub_gui_container_t parent; + grub_video_rect_t bounds; + char *id; + int preferred_width; + int preferred_height; + int visible; + int start; + int end; + int value; + int num_ticks; + int start_angle; + int ticks_disappear; + char *theme_dir; + int need_to_load_pixmaps; + char *center_file; + char *tick_file; + struct grub_video_bitmap *center_bitmap; + struct grub_video_bitmap *tick_bitmap; +}; + +typedef struct grub_gui_circular_progress *circular_progress_t; + +static void +circprog_destroy (void *vself) +{ + circular_progress_t self = vself; + grub_free (self); +} + +static const char * +circprog_get_id (void *vself) +{ + circular_progress_t self = vself; + return self->id; +} + +static int +circprog_is_instance (void *vself __attribute__((unused)), const char *type) +{ + return grub_strcmp (type, "component") == 0; +} + +static struct grub_video_bitmap * +load_bitmap (const char *dir, const char *file) +{ + struct grub_video_bitmap *bitmap; + char *abspath; + + /* Check arguments. */ + if (! dir || ! file) + return 0; + + /* Resolve to an absolute path. */ + abspath = grub_resolve_relative_path (dir, file); + if (! abspath) + return 0; + + /* Load the image. */ + grub_errno = GRUB_ERR_NONE; + grub_video_bitmap_load (&bitmap, abspath); + grub_errno = GRUB_ERR_NONE; + + grub_free (abspath); + return bitmap; +} + +static int +check_pixmaps (circular_progress_t self) +{ + if (self->need_to_load_pixmaps) + { + if (self->center_bitmap) + grub_video_bitmap_destroy (self->center_bitmap); + self->center_bitmap = load_bitmap (self->theme_dir, self->center_file); + self->tick_bitmap = load_bitmap (self->theme_dir, self->tick_file); + self->need_to_load_pixmaps = 0; + } + + return (self->center_bitmap != 0 && self->tick_bitmap != 0); +} + +static void +circprog_paint (void *vself) +{ + circular_progress_t self = vself; + + if (! self->visible) + return; + if (! check_pixmaps (self)) + return; + + grub_video_rect_t vpsave; + grub_gui_set_viewport (&self->bounds, &vpsave); + + int width = self->bounds.width; + int height = self->bounds.height; + int center_width = grub_video_bitmap_get_width (self->center_bitmap); + int center_height = grub_video_bitmap_get_height (self->center_bitmap); + int tick_width = grub_video_bitmap_get_width (self->tick_bitmap); + int tick_height = grub_video_bitmap_get_height (self->tick_bitmap); + grub_video_blit_bitmap (self->center_bitmap, GRUB_VIDEO_BLIT_BLEND, + (width - center_width) / 2, + (height - center_height) / 2, 0, 0, + center_width, center_height); + + int radius = width / 2 - tick_width / 2 - 1; + int nticks = (self->num_ticks + * (self->value - self->start) + / (self->end - self->start)); + int tick_begin; + int tick_end; + /* Do ticks appear or disappear as the value approached the end? */ + if (self->ticks_disappear) + { + tick_begin = nticks; + tick_end = self->num_ticks - 1; + } + else + { + tick_begin = 0; + tick_end = nticks - 1; + } + + int i; + for (i = tick_begin; i < tick_end; i++) + { + int x; + int y; + int angle; + + /* Calculate the location of the tick. */ + angle = self->start_angle + i * GRUB_TRIG_ANGLE_MAX / self->num_ticks; + x = width / 2 + (grub_cos (angle) * radius / GRUB_TRIG_FRACTION_SCALE); + y = height / 2 + (grub_sin (angle) * radius / GRUB_TRIG_FRACTION_SCALE); + + /* Adjust (x,y) so the tick is centered. */ + x -= tick_width / 2; + y -= tick_height / 2; + + /* Draw the tick. */ + grub_video_blit_bitmap (self->tick_bitmap, GRUB_VIDEO_BLIT_BLEND, + x, y, 0, 0, tick_width, tick_height); + } + + grub_gui_restore_viewport (&vpsave); +} + +static void +circprog_set_parent (void *vself, grub_gui_container_t parent) +{ + circular_progress_t self = vself; + self->parent = parent; +} + +static grub_gui_container_t +circprog_get_parent (void *vself) +{ + circular_progress_t self = vself; + return self->parent; +} + +static void +circprog_set_bounds (void *vself, const grub_video_rect_t *bounds) +{ + circular_progress_t self = vself; + self->bounds = *bounds; +} + +static void +circprog_get_bounds (void *vself, grub_video_rect_t *bounds) +{ + circular_progress_t self = vself; + *bounds = self->bounds; +} + +static void +circprog_get_preferred_size (void *vself, int *width, int *height) +{ + circular_progress_t self = vself; + + *width = 0; + *height = 0; + + /* Allow preferred dimensions to override the circprog dimensions. */ + if (self->preferred_width >= 0) + *width = self->preferred_width; + if (self->preferred_height >= 0) + *height = self->preferred_height; +} + +static void +circprog_set_property (void *vself, const char *name, const char *value) +{ + circular_progress_t self = vself; + if (grub_strcmp (name, "value") == 0) + { + self->value = grub_strtol (value, 0, 10); + } + else if (grub_strcmp (name, "start") == 0) + { + self->start = grub_strtol (value, 0, 10); + } + else if (grub_strcmp (name, "end") == 0) + { + self->end = grub_strtol (value, 0, 10); + } + else if (grub_strcmp (name, "num_ticks") == 0) + { + self->num_ticks = grub_strtol (value, 0, 10); + } + else if (grub_strcmp (name, "start_angle") == 0) + { + self->start_angle = grub_strtol (value, 0, 10); + } + else if (grub_strcmp (name, "ticks_disappear") == 0) + { + self->ticks_disappear = grub_strcmp (value, "false") != 0; + } + else if (grub_strcmp (name, "center_bitmap") == 0) + { + self->need_to_load_pixmaps = 1; + grub_free (self->center_file); + self->center_file = value ? grub_strdup (value) : 0; + } + else if (grub_strcmp (name, "tick_bitmap") == 0) + { + self->need_to_load_pixmaps = 1; + grub_free (self->tick_file); + self->tick_file = value ? grub_strdup (value) : 0; + } + else if (grub_strcmp (name, "theme_dir") == 0) + { + self->need_to_load_pixmaps = 1; + grub_free (self->theme_dir); + self->theme_dir = value ? grub_strdup (value) : 0; + } + else if (grub_strcmp (name, "preferred_size") == 0) + { + int w; + int h; + if (grub_gui_parse_2_tuple (value, &w, &h)) + { + self->preferred_width = w; + self->preferred_height = h; + } + } + else if (grub_strcmp (name, "visible") == 0) + { + self->visible = grub_strcmp (value, "false") != 0; + } + else if (grub_strcmp (name, "id") == 0) + { + grub_free (self->id); + if (value) + self->id = grub_strdup (value); + else + self->id = 0; + } +} + +static struct grub_gui_component_ops circprog_ops = +{ + .destroy = circprog_destroy, + .get_id = circprog_get_id, + .is_instance = circprog_is_instance, + .paint = circprog_paint, + .set_parent = circprog_set_parent, + .get_parent = circprog_get_parent, + .set_bounds = circprog_set_bounds, + .get_bounds = circprog_get_bounds, + .get_preferred_size = circprog_get_preferred_size, + .set_property = circprog_set_property +}; + +grub_gui_component_t +grub_gui_circular_progress_new (void) +{ + circular_progress_t self; + self = grub_malloc (sizeof (*self)); + if (! self) + return 0; + self->circprog_ops = &circprog_ops; + self->parent = 0; + self->bounds.x = 0; + self->bounds.y = 0; + self->bounds.width = 0; + self->bounds.height = 0; + self->id = 0; + self->preferred_width = -1; + self->preferred_height = -1; + self->visible = 1; + self->start = 0; + self->end = 0; + self->value = 0; + self->num_ticks = 64; + self->start_angle = -64; + self->ticks_disappear = 0; + + self->theme_dir = 0; + self->need_to_load_pixmaps = 0; + self->center_file = 0; + self->tick_file = 0; + self->center_bitmap = 0; + self->tick_bitmap = 0; + + return (grub_gui_component_t) self; +} === added file 'gfxmenu/gui_image.c' --- gfxmenu/gui_image.c 1970-01-01 00:00:00 +0000 +++ gfxmenu/gui_image.c 2008-08-16 17:35:05 +0000 @@ -0,0 +1,262 @@ +/* gui_image.c - GUI component to display an image. */ +/* + * 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 + +struct grub_gui_image +{ + struct grub_gui_component_ops *image; + + grub_gui_container_t parent; + grub_video_rect_t bounds; + char *id; + int preferred_width; + int preferred_height; + struct grub_video_bitmap *raw_bitmap; + struct grub_video_bitmap *bitmap; +}; + +typedef struct grub_gui_image *grub_gui_image_t; + +static void +image_destroy (void *vself) +{ + grub_gui_image_t self = vself; + + /* Free the scaled bitmap, unless it's a reference to the raw bitmap. */ + if (self->bitmap && (self->bitmap != self->raw_bitmap)) + grub_video_bitmap_destroy (self->bitmap); + if (self->raw_bitmap) + grub_video_bitmap_destroy (self->raw_bitmap); + + grub_free (self); +} + +static const char * +image_get_id (void *vself) +{ + grub_gui_image_t self = vself; + return self->id; +} + +static int +image_is_instance (void *vself __attribute__((unused)), const char *type) +{ + return grub_strcmp (type, "component") == 0; +} + +static void +image_paint (void *vself) +{ + grub_gui_image_t self = vself; + if (! self->bitmap) + return; + grub_video_rect_t vpsave; + grub_gui_set_viewport (&self->bounds, &vpsave); + grub_video_blit_bitmap (self->bitmap, GRUB_VIDEO_BLIT_BLEND, + 0, 0, 0, 0, + grub_video_bitmap_get_width (self->bitmap), + grub_video_bitmap_get_height (self->bitmap)); + grub_gui_restore_viewport (&vpsave); +} + +static void +image_set_parent (void *vself, grub_gui_container_t parent) +{ + grub_gui_image_t self = vself; + self->parent = parent; +} + +static grub_gui_container_t +image_get_parent (void *vself) +{ + grub_gui_image_t self = vself; + return self->parent; +} + +static void +rescale_image (grub_gui_image_t self) +{ + if (! self->raw_bitmap) + { + if (self->bitmap) + { + grub_video_bitmap_destroy (self->bitmap); + self->bitmap = 0; + } + return; + } + + unsigned width = self->bounds.width; + unsigned height = self->bounds.height; + + if (self->bitmap + && (grub_video_bitmap_get_width (self->bitmap) == width) + && (grub_video_bitmap_get_height (self->bitmap) == height)) + return; /* Nothing to do; already the right size. */ + + /* Free any old scaled bitmap, + unless it's a reference to the raw bitmap. */ + if (self->bitmap && (self->bitmap != self->raw_bitmap)) + grub_video_bitmap_destroy (self->bitmap); + + /* Create a scaled bitmap, unless the requested size is the same + as the raw size -- in that case a reference is made. */ + if (grub_video_bitmap_get_width (self->raw_bitmap) == width + && grub_video_bitmap_get_height (self->raw_bitmap) == height) + { + self->bitmap = self->raw_bitmap; + return; + } + + /* Create the scaled bitmap. */ + grub_video_bitmap_create_scaled (&self->bitmap, + width, + height, + self->raw_bitmap, + GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST); + grub_errno = GRUB_ERR_NONE; /* Catch any errors. */ +} + +static void +image_set_bounds (void *vself, const grub_video_rect_t *bounds) +{ + grub_gui_image_t self = vself; + self->bounds = *bounds; + rescale_image (self); +} + +static void +image_get_bounds (void *vself, grub_video_rect_t *bounds) +{ + grub_gui_image_t self = vself; + *bounds = self->bounds; +} + +static void +image_get_preferred_size (void *vself, int *width, int *height) +{ + grub_gui_image_t self = vself; + + if (self->raw_bitmap) + { + *width = grub_video_bitmap_get_width (self->raw_bitmap); + *height = grub_video_bitmap_get_height (self->raw_bitmap); + } + else + { + *width = 0; + *height = 0; + } + + /* Allow preferred dimensions to override the image dimensions. */ + if (self->preferred_width >= 0) + *width = self->preferred_width; + if (self->preferred_height >= 0) + *height = self->preferred_height; +} + +static void +load_image (grub_gui_image_t self, const char *path) +{ + struct grub_video_bitmap *bitmap; + grub_errno = GRUB_ERR_NONE; + if (grub_video_bitmap_load (&bitmap, path) != GRUB_ERR_NONE) + { + /* We should log the error somehow. */ + grub_errno = GRUB_ERR_NONE; + return; + } + if (self->bitmap && (self->bitmap != self->raw_bitmap)) + grub_video_bitmap_destroy (self->bitmap); + if (self->raw_bitmap) + grub_video_bitmap_destroy (self->raw_bitmap); + + self->raw_bitmap = bitmap; + rescale_image (self); +} + +static void +image_set_property (void *vself, const char *name, const char *value) +{ + grub_gui_image_t self = vself; + if (grub_strcmp (name, "file") == 0) + { + load_image (self, value); + } + else if (grub_strcmp (name, "preferred_size") == 0) + { + int w; + int h; + if (grub_gui_parse_2_tuple (value, &w, &h)) + { + self->preferred_width = w; + self->preferred_height = h; + } + } + else if (grub_strcmp (name, "id") == 0) + { + grub_free (self->id); + if (value) + self->id = grub_strdup (value); + else + self->id = 0; + } +} + +static struct grub_gui_component_ops image_ops = +{ + .destroy = image_destroy, + .get_id = image_get_id, + .is_instance = image_is_instance, + .paint = image_paint, + .set_parent = image_set_parent, + .get_parent = image_get_parent, + .set_bounds = image_set_bounds, + .get_bounds = image_get_bounds, + .get_preferred_size = image_get_preferred_size, + .set_property = image_set_property +}; + +grub_gui_component_t +grub_gui_image_new (void) +{ + grub_gui_image_t image; + image = grub_malloc (sizeof (*image)); + if (! image) + return 0; + image->image = &image_ops; + image->parent = 0; + image->bounds.x = 0; + image->bounds.y = 0; + image->bounds.width = 0; + image->bounds.height = 0; + image->id = 0; + image->preferred_width = -1; + image->preferred_height = -1; + image->raw_bitmap = 0; + image->bitmap = 0; + return (grub_gui_component_t) image; +} + === added file 'gfxmenu/gui_label.c' --- gfxmenu/gui_label.c 1970-01-01 00:00:00 +0000 +++ gfxmenu/gui_label.c 2008-08-16 17:35:05 +0000 @@ -0,0 +1,247 @@ +/* gui_label.c - GUI component to display a line of text. */ +/* + * 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 + +static const char *align_options[] = +{ + "left", + "center", + "right", + 0 +}; + +enum align_mode { + align_left, + align_center, + align_right +}; + +struct grub_gui_label +{ + struct grub_gui_component_ops *label; + + grub_gui_container_t parent; + grub_video_rect_t bounds; + char *id; + int preferred_width; + int preferred_height; + int visible; + char *text; + grub_font_t font; + grub_gui_color_t color; + enum align_mode align; +}; + +typedef struct grub_gui_label *grub_gui_label_t; + +static void +label_destroy (void *vself) +{ + grub_gui_label_t self = vself; + grub_free (self->text); + grub_free (self); +} + +static const char * +label_get_id (void *vself) +{ + grub_gui_label_t self = vself; + return self->id; +} + +static int +label_is_instance (void *vself __attribute__((unused)), const char *type) +{ + return grub_strcmp (type, "component") == 0; +} + +static void +label_paint (void *vself) +{ + grub_gui_label_t self = vself; + + if (! self->visible) + return; + + /* Calculate the starting x coordinate. */ + int left_x; + if (self->align == align_left) + left_x = 0; + else if (self->align == align_center) + left_x = ((self->bounds.width + - grub_font_get_string_width (self->font, self->text)) + ) / 2; + else if (self->align == align_right) + left_x = (self->bounds.width + - grub_font_get_string_width (self->font, self->text)); + else + return; /* Invalid alignment. */ + + grub_video_rect_t vpsave; + grub_gui_set_viewport (&self->bounds, &vpsave); + grub_video_draw_string (self->text, + self->font, + grub_gui_map_color (self->color), + left_x, + grub_font_get_ascent (self->font)); + grub_gui_restore_viewport (&vpsave); +} + +static void +label_set_parent (void *vself, grub_gui_container_t parent) +{ + grub_gui_label_t self = vself; + self->parent = parent; +} + +static grub_gui_container_t +label_get_parent (void *vself) +{ + grub_gui_label_t self = vself; + return self->parent; +} + +static void +label_set_bounds (void *vself, const grub_video_rect_t *bounds) +{ + grub_gui_label_t self = vself; + self->bounds = *bounds; +} + +static void +label_get_bounds (void *vself, grub_video_rect_t *bounds) +{ + grub_gui_label_t self = vself; + *bounds = self->bounds; +} + +static void +label_get_preferred_size (void *vself, int *width, int *height) +{ + grub_gui_label_t self = vself; + *width = grub_font_get_string_width (self->font, self->text); + *height = (grub_font_get_ascent (self->font) + + grub_font_get_descent (self->font)); + + /* Allow preferred dimensions to override the computed dimensions. */ + if (self->preferred_width >= 0) + *width = self->preferred_width; + if (self->preferred_height >= 0) + *height = self->preferred_height; +} + +static void +label_set_property (void *vself, const char *name, const char *value) +{ + grub_gui_label_t self = vself; + if (grub_strcmp (name, "text") == 0) + { + grub_free (self->text); + if (! value) + value = ""; + self->text = grub_strdup (value); + } + else if (grub_strcmp (name, "font") == 0) + { + self->font = grub_font_get (value); + } + else if (grub_strcmp (name, "color") == 0) + { + self->color = grub_gui_parse_color (value); + } + else if (grub_strcmp (name, "align") == 0) + { + int i; + for (i = 0; align_options[i]; i++) + { + if (grub_strcmp (align_options[i], value) == 0) + { + self->align = i; /* Set the alignment mode. */ + break; + } + } + } + else if (grub_strcmp (name, "visible") == 0) + { + self->visible = grub_strcmp (value, "false") != 0; + } + else if (grub_strcmp (name, "preferred_size") == 0) + { + int w; + int h; + if (grub_gui_parse_2_tuple (value, &w, &h)) + { + self->preferred_width = w; + self->preferred_height = h; + } + } + else if (grub_strcmp (name, "id") == 0) + { + grub_free (self->id); + if (value) + self->id = grub_strdup (value); + else + self->id = 0; + } +} + +static struct grub_gui_component_ops label_ops = +{ + .destroy = label_destroy, + .get_id = label_get_id, + .is_instance = label_is_instance, + .paint = label_paint, + .set_parent = label_set_parent, + .get_parent = label_get_parent, + .set_bounds = label_set_bounds, + .get_bounds = label_get_bounds, + .get_preferred_size = label_get_preferred_size, + .set_property = label_set_property +}; + +grub_gui_component_t +grub_gui_label_new (void) +{ + grub_gui_label_t label; + label = grub_malloc (sizeof (*label)); + if (! label) + return 0; + label->label = &label_ops; + label->parent = 0; + label->bounds.x = 0; + label->bounds.y = 0; + label->bounds.width = 0; + label->bounds.height = 0; + label->id = 0; + label->preferred_width = -1; + label->preferred_height = -1; + label->visible = 1; + label->text = grub_strdup (""); + label->font = grub_font_get ("Helvetica 10"); + label->color.red = 0; + label->color.green = 0; + label->color.blue = 0; + label->color.alpha = 255; + label->align = align_left; + return (grub_gui_component_t) label; +} === added file 'gfxmenu/gui_list.c' --- gfxmenu/gui_list.c 1970-01-01 00:00:00 +0000 +++ gfxmenu/gui_list.c 2008-08-16 17:35:05 +0000 @@ -0,0 +1,624 @@ +/* gui_list.c - GUI component to display a selectable list of items. */ +/* + * 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 + +struct grub_gui_list_impl +{ + struct grub_gui_list_ops *list_ops; + + grub_gui_container_t parent; + grub_video_rect_t bounds; + char *id; + int preferred_width; + int preferred_height; + int visible; + + int icon_width; + int icon_height; + int item_height; + int item_padding; + int item_icon_space; + int item_spacing; + grub_font_t item_font; + grub_font_t selected_item_font; + grub_gui_color_t item_color; + int selected_item_color_set; + grub_gui_color_t selected_item_color; + + int draw_scrollbar; + int need_to_recreate_scrollbar; + char *scrollbar_frame_pattern; + char *scrollbar_thumb_pattern; + grub_gfxmenu_box_t scrollbar_frame; + grub_gfxmenu_box_t scrollbar_thumb; + int scrollbar_width; + + int min_items_shown; + int max_items_shown; + int first_shown_index; + + int need_to_recreate_boxes; + char *theme_dir; + char *menu_box_pattern; + char *selected_item_box_pattern; + grub_gfxmenu_box_t menu_box; + grub_gfxmenu_box_t selected_item_box; + + grub_gfxmenu_icon_manager_t icon_manager; + grub_gfxmenu_model_t menu; +}; + +typedef struct grub_gui_list_impl *list_impl_t; + +static void +list_destroy (void *vself) +{ + list_impl_t self = vself; + + grub_free (self->theme_dir); + grub_free (self->menu_box_pattern); + grub_free (self->selected_item_box_pattern); + if (self->menu_box) + self->menu_box->destroy (self->menu_box); + if (self->selected_item_box) + self->selected_item_box->destroy (self->selected_item_box); + if (self->icon_manager) + grub_gfxmenu_icon_manager_destroy (self->icon_manager); + + grub_free (self); +} + +static int +get_num_shown_items (list_impl_t self) +{ + int n = grub_gfxmenu_model_get_num_entries (self->menu); + if (self->min_items_shown != -1 && n < self->min_items_shown) + n = self->min_items_shown; + if (self->max_items_shown != -1 && n > self->max_items_shown) + n = self->max_items_shown; + return n; +} + +static int +check_boxes (list_impl_t self) +{ + if (self->need_to_recreate_boxes) + { + grub_gui_recreate_box (&self->menu_box, + self->menu_box_pattern, + self->theme_dir); + + grub_gui_recreate_box (&self->selected_item_box, + self->selected_item_box_pattern, + self->theme_dir); + + self->need_to_recreate_boxes = 0; + } + + return (self->menu_box != 0 && self->selected_item_box != 0); +} + +static int +check_scrollbar (list_impl_t self) +{ + if (self->need_to_recreate_scrollbar) + { + grub_gui_recreate_box (&self->scrollbar_frame, + self->scrollbar_frame_pattern, + self->theme_dir); + + grub_gui_recreate_box (&self->scrollbar_thumb, + self->scrollbar_thumb_pattern, + self->theme_dir); + + self->need_to_recreate_scrollbar = 0; + } + + return (self->scrollbar_frame != 0 && self->scrollbar_thumb != 0); +} + +static const char * +list_get_id (void *vself) +{ + list_impl_t self = vself; + return self->id; +} + +static int +list_is_instance (void *vself __attribute__((unused)), const char *type) +{ + return (grub_strcmp (type, "component") == 0 + || grub_strcmp (type, "list") == 0); +} + +static struct grub_video_bitmap * +get_item_icon (list_impl_t self, int item_index) +{ + grub_menu_entry_t entry; + entry = grub_gfxmenu_model_get_entry (self->menu, item_index); + if (! entry) + return 0; + + return grub_gfxmenu_icon_manager_get_icon (self->icon_manager, entry); +} + +static void +make_selected_item_visible (list_impl_t self) +{ + int selected_index = grub_gfxmenu_model_get_selected_index (self->menu); + if (selected_index < 0) + return; /* No item is selected. */ + int num_shown_items = get_num_shown_items (self); + int last_shown_index = self->first_shown_index + (num_shown_items - 1); + if (selected_index < self->first_shown_index) + self->first_shown_index = selected_index; + else if (selected_index > last_shown_index) + self->first_shown_index = selected_index - (num_shown_items - 1); +} + +/* Draw a scrollbar on the menu. */ +static void +draw_scrollbar (list_impl_t self, + int value, int extent, int min, int max, + int rightx, int topy, int height) +{ + grub_gfxmenu_box_t frame = self->scrollbar_frame; + grub_gfxmenu_box_t thumb = self->scrollbar_thumb; + int frame_vertical_pad = (frame->get_top_pad (frame) + + frame->get_bottom_pad (frame)); + int frame_horizontal_pad = (frame->get_left_pad (frame) + + frame->get_right_pad (frame)); + int tracktop = topy + frame->get_top_pad (frame); + int tracklen = height - frame_vertical_pad; + frame->set_content_size (frame, self->scrollbar_width, tracklen); + int thumby = tracktop + tracklen * (value - min) / (max - min); + int thumbheight = tracklen * extent / (max - min) + 1; + thumb->set_content_size (thumb, + self->scrollbar_width - frame_horizontal_pad, + thumbheight - (thumb->get_top_pad (thumb) + + thumb->get_bottom_pad (thumb))); + frame->draw (frame, + rightx - (self->scrollbar_width + frame_horizontal_pad), + topy); + thumb->draw (thumb, + rightx - (self->scrollbar_width - frame->get_right_pad (frame)), + thumby); +} + +/* Draw the list of items. */ +static void +draw_menu (list_impl_t self) +{ + if (! self->menu_box || ! self->selected_item_box) + return; + + int boxpad = self->item_padding; + int icon_text_space = self->item_icon_space; + int item_vspace = self->item_spacing; + + int ascent = grub_font_get_ascent (self->item_font); + int descent = grub_font_get_descent (self->item_font); + int item_height = self->item_height; + + int total_num_items = grub_gfxmenu_model_get_num_entries (self->menu); + int num_shown_items = get_num_shown_items (self); + grub_gfxmenu_box_t box = self->menu_box; + int width = self->bounds.width; + int height = self->bounds.height; + + int box_left_pad = box->get_left_pad (box); + int box_top_pad = box->get_top_pad (box); + int box_right_pad = box->get_right_pad (box); + int box_bottom_pad = box->get_bottom_pad (box); + + box->set_content_size (box, + width - box_left_pad - box_right_pad, + height - box_top_pad - box_bottom_pad); + + box->draw (box, 0, 0); + + make_selected_item_visible (self); + + int drawing_scrollbar = (self->draw_scrollbar + && (num_shown_items < total_num_items) + && check_scrollbar (self)); + + int scrollbar_h_space = drawing_scrollbar ? self->scrollbar_width : 0; + + int item_top = box_top_pad + boxpad; + int item_left = box_left_pad + boxpad; + int menu_index; + int visible_index; + + for (visible_index = 0, menu_index = self->first_shown_index; + visible_index < num_shown_items && menu_index < total_num_items; + visible_index++, menu_index++) + { + int is_selected = + (menu_index == grub_gfxmenu_model_get_selected_index (self->menu)); + + if (is_selected) + { + grub_gfxmenu_box_t selbox = self->selected_item_box; + int sel_leftpad = selbox->get_left_pad (selbox); + int sel_toppad = selbox->get_top_pad (selbox); + selbox->set_content_size (selbox, + (width - 2 * boxpad + - box_left_pad - box_right_pad + - scrollbar_h_space), + item_height); + selbox->draw (selbox, + item_left - sel_leftpad, + item_top - sel_toppad); + } + + struct grub_video_bitmap *icon; + if ((icon = get_item_icon (self, menu_index)) != 0) + grub_video_blit_bitmap (icon, GRUB_VIDEO_BLIT_BLEND, + item_left, + item_top + (item_height - self->icon_height) / 2, + 0, 0, self->icon_width, self->icon_height); + + const char *item_title = + grub_gfxmenu_model_get_entry_title (self->menu, menu_index); + grub_font_t font = + (is_selected && self->selected_item_font + ? self->selected_item_font + : self->item_font); + grub_gui_color_t text_color = + ((is_selected && self->selected_item_color_set) + ? self->selected_item_color + : self->item_color); + grub_video_draw_string (item_title, + font, + grub_gui_map_color (text_color), + item_left + self->icon_width + icon_text_space, + (item_top + (item_height - (ascent + descent)) + / 2 + ascent)); + + item_top += item_height + item_vspace; + } + + if (drawing_scrollbar) + draw_scrollbar (self, + self->first_shown_index, num_shown_items, + 0, total_num_items, + width - box_right_pad + self->scrollbar_width, + box_top_pad + boxpad, + height - box_top_pad - box_bottom_pad); +} + +static void +list_paint (void *vself) +{ + list_impl_t self = vself; + + if (! self->visible) + return; + + check_boxes (self); + + grub_video_rect_t vpsave; + grub_gui_set_viewport (&self->bounds, &vpsave); + draw_menu (self); + grub_gui_restore_viewport (&vpsave); +} + +static void +list_set_parent (void *vself, grub_gui_container_t parent) +{ + list_impl_t self = vself; + self->parent = parent; +} + +static grub_gui_container_t +list_get_parent (void *vself) +{ + list_impl_t self = vself; + return self->parent; +} + +static void +list_set_bounds (void *vself, const grub_video_rect_t *bounds) +{ + list_impl_t self = vself; + self->bounds = *bounds; +} + +static void +list_get_bounds (void *vself, grub_video_rect_t *bounds) +{ + list_impl_t self = vself; + *bounds = self->bounds; +} + +static void +list_get_preferred_size (void *vself, int *width, int *height) +{ + list_impl_t self = vself; + + if (check_boxes (self)) + { + int boxpad = self->item_padding; + int item_vspace = self->item_spacing; + int item_height = self->item_height; + int num_items = get_num_shown_items (self); + + grub_gfxmenu_box_t box = self->menu_box; + int box_left_pad = box->get_left_pad (box); + int box_top_pad = box->get_top_pad (box); + int box_right_pad = box->get_right_pad (box); + int box_bottom_pad = box->get_bottom_pad (box); + + *width = 400 + 2 * boxpad + box_left_pad + box_right_pad; + + /* Set the menu box height to fit the items. */ + *height = (item_height * num_items + + item_vspace * (num_items - 1) + + 2 * boxpad + + box_top_pad + box_bottom_pad); + } + else + { + *width = 0; + *height = 0; + } + + /* Allow preferred dimensions to override the computed dimensions. */ + if (self->preferred_width >= 0) + *width = self->preferred_width; + if (self->preferred_height >= 0) + *height = self->preferred_height; +} + +static void +list_set_property (void *vself, const char *name, const char *value) +{ + list_impl_t self = vself; + if (grub_strcmp (name, "item_font") == 0) + { + self->item_font = grub_font_get (value); + } + else if (grub_strcmp (name, "selected_item_font") == 0) + { + if (! value || grub_strcmp (value, "inherit") == 0) + self->selected_item_font = 0; + else + self->selected_item_font = grub_font_get (value); + } + else if (grub_strcmp (name, "item_color") == 0) + { + self->item_color = grub_gui_parse_color (value); + } + else if (grub_strcmp (name, "selected_item_color") == 0) + { + if (! value || grub_strcmp (value, "inherit") == 0) + { + self->selected_item_color_set = 0; + } + else + { + self->selected_item_color_set = 1; + self->selected_item_color = grub_gui_parse_color (value); + } + } + else if (grub_strcmp (name, "icon_width") == 0) + { + self->icon_width = grub_strtol (value, 0, 10); + grub_gfxmenu_icon_manager_set_icon_size (self->icon_manager, + self->icon_width, + self->icon_height); + } + else if (grub_strcmp (name, "icon_height") == 0) + { + self->icon_height = grub_strtol (value, 0, 10); + grub_gfxmenu_icon_manager_set_icon_size (self->icon_manager, + self->icon_width, + self->icon_height); + } + else if (grub_strcmp (name, "item_height") == 0) + { + self->item_height = grub_strtol (value, 0, 10); + } + else if (grub_strcmp (name, "item_padding") == 0) + { + self->item_padding = grub_strtol (value, 0, 10); + } + else if (grub_strcmp (name, "item_icon_space") == 0) + { + self->item_icon_space = grub_strtol (value, 0, 10); + } + else if (grub_strcmp (name, "item_spacing") == 0) + { + self->item_spacing = grub_strtol (value, 0, 10); + } + else if (grub_strcmp (name, "visible") == 0) + { + self->visible = grub_strcmp (value, "false") != 0; + } + else if (grub_strcmp (name, "menu_pixmap_style") == 0) + { + self->need_to_recreate_boxes = 1; + grub_free (self->menu_box_pattern); + self->menu_box_pattern = value ? grub_strdup (value) : 0; + } + else if (grub_strcmp (name, "selected_item_pixmap_style") == 0) + { + self->need_to_recreate_boxes = 1; + grub_free (self->selected_item_box_pattern); + self->selected_item_box_pattern = value ? grub_strdup (value) : 0; + } + else if (grub_strcmp (name, "scrollbar_frame") == 0) + { + self->need_to_recreate_scrollbar = 1; + grub_free (self->scrollbar_frame_pattern); + self->scrollbar_frame_pattern = value ? grub_strdup (value) : 0; + } + else if (grub_strcmp (name, "scrollbar_thumb") == 0) + { + self->need_to_recreate_scrollbar = 1; + grub_free (self->scrollbar_thumb_pattern); + self->scrollbar_thumb_pattern = value ? grub_strdup (value) : 0; + } + else if (grub_strcmp (name, "scrollbar_width") == 0) + { + self->scrollbar_width = grub_strtol (value, 0, 10); + } + else if (grub_strcmp (name, "scrollbar") == 0) + { + self->draw_scrollbar = grub_strcmp (value, "false") != 0; + } + else if (grub_strcmp (name, "min_items_shown") == 0) + { + self->min_items_shown = grub_strtol (value, 0, 10); + } + else if (grub_strcmp (name, "max_items_shown") == 0) + { + self->max_items_shown = grub_strtol (value, 0, 10); + } + else if (grub_strcmp (name, "theme_dir") == 0) + { + self->need_to_recreate_boxes = 1; + grub_free (self->theme_dir); + self->theme_dir = value ? grub_strdup (value) : 0; + } + else if (grub_strcmp (name, "preferred_size") == 0) + { + int w; + int h; + if (grub_gui_parse_2_tuple (value, &w, &h)) + { + self->preferred_width = w; + self->preferred_height = h; + } + } + else if (grub_strcmp (name, "id") == 0) + { + grub_free (self->id); + if (value) + self->id = grub_strdup (value); + else + self->id = 0; + } +} + +/* Set necessary information that the gfxmenu view provides. */ +static void +list_set_view_info (void *vself, + const char *theme_path, + grub_gfxmenu_model_t menu) +{ + list_impl_t self = vself; + grub_gfxmenu_icon_manager_set_theme_path (self->icon_manager, theme_path); + self->menu = menu; +} + +static struct grub_gui_list_ops list_ops = +{ + .component_ops = + { + .destroy = list_destroy, + .get_id = list_get_id, + .is_instance = list_is_instance, + .paint = list_paint, + .set_parent = list_set_parent, + .get_parent = list_get_parent, + .set_bounds = list_set_bounds, + .get_bounds = list_get_bounds, + .get_preferred_size = list_get_preferred_size, + .set_property = list_set_property + }, + .set_view_info = list_set_view_info +}; + +grub_gui_component_t +grub_gui_list_new (void) +{ + list_impl_t self; + grub_font_t default_font; + grub_gui_color_t default_fg_color; + grub_gui_color_t default_bg_color; + + self = grub_malloc (sizeof (*self)); + if (! self) + return 0; + + self->list_ops = &list_ops; + self->parent = 0; + self->bounds.x = 0; + self->bounds.y = 0; + self->bounds.width = 0; + self->bounds.height = 0; + self->id = 0; + self->preferred_width = -1; + self->preferred_height = -1; + self->visible = 1; + + default_font = grub_font_get ("Helvetica 12"); + default_fg_color = grub_gui_color_rgb (0, 0, 0); + default_bg_color = grub_gui_color_rgb (255, 255, 255); + + self->icon_width = 32; + self->icon_height = 32; + self->item_height = 42; + self->item_padding = 14; + self->item_icon_space = 4; + self->item_spacing = 16; + self->item_font = default_font; + self->selected_item_font = 0; /* Default to using the item_font. */ + self->item_color = default_fg_color; + self->selected_item_color_set = 0; /* Default to using the item_color. */ + self->selected_item_color = default_fg_color; + + self->draw_scrollbar = 1; + self->need_to_recreate_scrollbar = 1; + self->scrollbar_frame = 0; + self->scrollbar_thumb = 0; + self->scrollbar_frame_pattern = 0; + self->scrollbar_thumb_pattern = 0; + self->scrollbar_width = 16; + + self->min_items_shown = -1; + self->max_items_shown = -1; + self->first_shown_index = 0; + + self->need_to_recreate_boxes = 0; + self->theme_dir = 0; + self->menu_box_pattern = 0; + self->selected_item_box_pattern = 0; + self->menu_box = grub_gfxmenu_create_box (0, 0); + self->selected_item_box = grub_gfxmenu_create_box (0, 0); + + self->icon_manager = grub_gfxmenu_icon_manager_new (); + if (! self->icon_manager) + { + self->list_ops->component_ops.destroy (self); + return 0; + } + grub_gfxmenu_icon_manager_set_icon_size (self->icon_manager, + self->icon_width, + self->icon_height); + return (grub_gui_component_t) self; +} === added file 'gfxmenu/gui_progress_bar.c' --- gfxmenu/gui_progress_bar.c 1970-01-01 00:00:00 +0000 +++ gfxmenu/gui_progress_bar.c 2008-08-16 17:35:05 +0000 @@ -0,0 +1,378 @@ +/* gui_progress_bar.c - GUI progress bar component. */ +/* + * 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 + +struct grub_gui_progress_bar +{ + struct grub_gui_component_ops *progress_bar; + + grub_gui_container_t parent; + grub_video_rect_t bounds; + char *id; + int preferred_width; + int preferred_height; + int visible; + int start; + int end; + int value; + int show_text; + char *text; + grub_font_t font; + grub_gui_color_t text_color; + grub_gui_color_t border_color; + grub_gui_color_t bg_color; + grub_gui_color_t fg_color; + + char *theme_dir; + int need_to_recreate_pixmaps; + char *bar_pattern; + char *highlight_pattern; + grub_gfxmenu_box_t bar_box; + grub_gfxmenu_box_t highlight_box; +}; + +typedef struct grub_gui_progress_bar *grub_gui_progress_bar_t; + +static void +progress_bar_destroy (void *vself) +{ + grub_gui_progress_bar_t self = vself; + grub_free (self); +} + +static const char * +progress_bar_get_id (void *vself) +{ + grub_gui_progress_bar_t self = vself; + return self->id; +} + +static int +progress_bar_is_instance (void *vself __attribute__((unused)), const char *type) +{ + return grub_strcmp (type, "component") == 0; +} + +static int +check_pixmaps (grub_gui_progress_bar_t self) +{ + if (self->need_to_recreate_pixmaps) + { + grub_gui_recreate_box (&self->bar_box, + self->bar_pattern, + self->theme_dir); + + grub_gui_recreate_box (&self->highlight_box, + self->highlight_pattern, + self->theme_dir); + + self->need_to_recreate_pixmaps = 0; + } + + return (self->bar_box != 0 && self->highlight_box != 0); +} + +static void +draw_filled_rect_bar (grub_gui_progress_bar_t self) +{ + /* Set the progress bar's frame. */ + grub_video_rect_t f; + f.x = 1; + f.y = 1; + f.width = self->bounds.width - 2; + f.height = self->bounds.height - 2; + + /* Border. */ + grub_video_fill_rect (grub_gui_map_color (self->border_color), + f.x - 1, f.y - 1, + f.width + 2, f.height + 2); + + /* Bar background. */ + int barwidth = (f.width + * (self->value - self->start) + / (self->end - self->start)); + grub_video_fill_rect (grub_gui_map_color (self->bg_color), + f.x + barwidth, f.y, + f.width - barwidth, f.height); + + /* Bar foreground. */ + grub_video_fill_rect (grub_gui_map_color (self->fg_color), + f.x, f.y, + barwidth, f.height); +} + +static void +draw_pixmap_bar (grub_gui_progress_bar_t self) +{ + grub_gfxmenu_box_t bar = self->bar_box; + grub_gfxmenu_box_t hl = self->highlight_box; + int w = self->bounds.width; + int h = self->bounds.height; + int bar_l_pad = bar->get_left_pad (bar); + int bar_r_pad = bar->get_right_pad (bar); + int bar_t_pad = bar->get_top_pad (bar); + int bar_b_pad = bar->get_bottom_pad (bar); + int bar_h_pad = bar_l_pad + bar_r_pad; + int bar_v_pad = bar_t_pad + bar_b_pad; + int tracklen = w - bar_h_pad; + int trackheight = h - bar_v_pad; + bar->set_content_size (bar, tracklen, trackheight); + + int barwidth = (tracklen + * (self->value - self->start) + / (self->end - self->start)); + hl->set_content_size (hl, barwidth, h - bar_v_pad); + + bar->draw (bar, 0, 0); + hl->draw (hl, bar_l_pad, bar_t_pad); +} + +static void +draw_text (grub_gui_progress_bar_t self) +{ + const char *text = self->text; + if (text && self->show_text) + { + grub_font_t font = self->font; + grub_video_color_t text_color = grub_gui_map_color (self->text_color); + int width = self->bounds.width; + int height = self->bounds.height; + + /* Center the text. */ + int text_width = grub_font_get_string_width (font, text); + int x = (width - text_width) / 2; + int y = ((height - grub_font_get_descent (font)) / 2 + + grub_font_get_ascent (font) / 2); + grub_video_draw_string (text, font, text_color, x, y); + } +} + +static void +progress_bar_paint (void *vself) +{ + grub_gui_progress_bar_t self = vself; + if (! self->visible) + return; + + grub_video_rect_t vpsave; + grub_gui_set_viewport (&self->bounds, &vpsave); + + if (check_pixmaps (self)) + draw_pixmap_bar (self); + else + draw_filled_rect_bar (self); + + draw_text (self); + + grub_gui_restore_viewport (&vpsave); +} + +static void +progress_bar_set_parent (void *vself, grub_gui_container_t parent) +{ + grub_gui_progress_bar_t self = vself; + self->parent = parent; +} + +static grub_gui_container_t +progress_bar_get_parent (void *vself) +{ + grub_gui_progress_bar_t self = vself; + return self->parent; +} + +static void +progress_bar_set_bounds (void *vself, const grub_video_rect_t *bounds) +{ + grub_gui_progress_bar_t self = vself; + self->bounds = *bounds; +} + +static void +progress_bar_get_bounds (void *vself, grub_video_rect_t *bounds) +{ + grub_gui_progress_bar_t self = vself; + *bounds = self->bounds; +} + +static void +progress_bar_get_preferred_size (void *vself, int *width, int *height) +{ + grub_gui_progress_bar_t self = vself; + + *width = 200; + *height = 28; + + /* Allow preferred dimensions to override the progress_bar dimensions. */ + if (self->preferred_width >= 0) + *width = self->preferred_width; + if (self->preferred_height >= 0) + *height = self->preferred_height; +} + +static void +progress_bar_set_property (void *vself, const char *name, const char *value) +{ + grub_gui_progress_bar_t self = vself; + if (grub_strcmp (name, "value") == 0) + { + self->value = grub_strtol (value, 0, 10); + } + else if (grub_strcmp (name, "start") == 0) + { + self->start = grub_strtol (value, 0, 10); + } + else if (grub_strcmp (name, "end") == 0) + { + self->end = grub_strtol (value, 0, 10); + } + else if (grub_strcmp (name, "text") == 0) + { + grub_free (self->text); + if (! value) + value = ""; + self->text = grub_strdup (value); + } + else if (grub_strcmp (name, "font") == 0) + { + self->font = grub_font_get (value); + } + else if (grub_strcmp (name, "text_color") == 0) + { + self->text_color = grub_gui_parse_color (value); + } + else if (grub_strcmp (name, "border_color") == 0) + { + self->border_color = grub_gui_parse_color (value); + } + else if (grub_strcmp (name, "bg_color") == 0) + { + self->bg_color = grub_gui_parse_color (value); + } + else if (grub_strcmp (name, "fg_color") == 0) + { + self->fg_color = grub_gui_parse_color (value); + } + else if (grub_strcmp (name, "bar_style") == 0) + { + self->need_to_recreate_pixmaps = 1; + grub_free (self->bar_pattern); + self->bar_pattern = value ? grub_strdup (value) : 0; + } + else if (grub_strcmp (name, "highlight_style") == 0) + { + self->need_to_recreate_pixmaps = 1; + grub_free (self->highlight_pattern); + self->highlight_pattern = value ? grub_strdup (value) : 0; + } + else if (grub_strcmp (name, "theme_dir") == 0) + { + self->need_to_recreate_pixmaps = 1; + grub_free (self->theme_dir); + self->theme_dir = value ? grub_strdup (value) : 0; + } + else if (grub_strcmp (name, "preferred_size") == 0) + { + int w; + int h; + if (grub_gui_parse_2_tuple (value, &w, &h)) + { + self->preferred_width = w; + self->preferred_height = h; + } + } + else if (grub_strcmp (name, "visible") == 0) + { + self->visible = grub_strcmp (value, "false") != 0; + } + else if (grub_strcmp (name, "show_text") == 0) + { + self->show_text = grub_strcmp (value, "false") != 0; + } + else if (grub_strcmp (name, "id") == 0) + { + grub_free (self->id); + if (value) + self->id = grub_strdup (value); + else + self->id = 0; + } +} + +static struct grub_gui_component_ops progress_bar_ops = +{ + .destroy = progress_bar_destroy, + .get_id = progress_bar_get_id, + .is_instance = progress_bar_is_instance, + .paint = progress_bar_paint, + .set_parent = progress_bar_set_parent, + .get_parent = progress_bar_get_parent, + .set_bounds = progress_bar_set_bounds, + .get_bounds = progress_bar_get_bounds, + .get_preferred_size = progress_bar_get_preferred_size, + .set_property = progress_bar_set_property +}; + +grub_gui_component_t +grub_gui_progress_bar_new (void) +{ + grub_gui_progress_bar_t self; + self = grub_malloc (sizeof (*self)); + if (! self) + return 0; + self->progress_bar = &progress_bar_ops; + self->parent = 0; + self->bounds.x = 0; + self->bounds.y = 0; + self->bounds.width = 0; + self->bounds.height = 0; + self->id = 0; + self->preferred_width = -1; + self->preferred_height = -1; + self->visible = 1; + self->start = 0; + self->end = 0; + self->value = 0; + self->show_text = 1; + self->text = grub_strdup (""); + self->font = grub_font_get ("Helvetica 10"); + grub_gui_color_t black = { .red = 0, .green = 0, .blue = 0, .alpha = 255 }; + grub_gui_color_t gray = { .red = 128, .green = 128, .blue = 128, .alpha = 255 }; + grub_gui_color_t lightgray = { .red = 200, .green = 200, .blue = 200, .alpha = 255 }; + self->text_color = black; + self->border_color = black; + self->bg_color = gray; + self->fg_color = lightgray; + + self->theme_dir = 0; + self->need_to_recreate_pixmaps = 0; + self->bar_pattern = 0; + self->highlight_pattern = 0; + self->bar_box = 0; + self->highlight_box = 0; + + return (grub_gui_component_t) self; +} === added file 'gfxmenu/gui_string_util.c' --- gfxmenu/gui_string_util.c 1970-01-01 00:00:00 +0000 +++ gfxmenu/gui_string_util.c 2008-08-16 17:35:05 +0000 @@ -0,0 +1,348 @@ +/* gui_string_util.c - String utilities used by the GUI system. */ +/* + * 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 + +/* Create a new NUL-terminated string on the heap as a substring of BUF. + The range of buf included is the half-open interval [START,END). + The index START is inclusive, END is exclusive. */ +char * +grub_new_substring (const char *buf, + grub_size_t start, grub_size_t end) +{ + if (end < start) + return 0; + grub_size_t len = end - start; + char *s = grub_malloc (len + 1); + if (! s) + return 0; + grub_memcpy (s, buf + start, len); + s[len] = '\0'; + return s; +} + +/* Eliminate "." and ".." path elements from PATH. A new heap-allocated + string is returned. */ +static char * +canonicalize_path (const char *path) +{ + int i; + const char *p; + char *newpath = 0; + + /* Count the path components in path. */ + int components = 1; + for (p = path; *p; p++) + if (*p == '/') + components++; + + char **path_array = grub_malloc (components * sizeof (*path_array)); + if (! path_array) + return 0; + + /* Initialize array elements to NULL pointers; in case once of the + allocations fails, the cleanup code can just call grub_free() for all + pointers in the array. */ + for (i = 0; i < components; i++) + path_array[i] = 0; + + /* Parse the path into path_array. */ + p = path; + for (i = 0; i < components && p; i++) + { + /* Find the end of the path element. */ + const char *end = grub_strchr (p, '/'); + if (!end) + end = p + grub_strlen (p); + + /* Copy the element. */ + path_array[i] = grub_new_substring (p, 0, end - p); + if (!path_array[i]) + goto cleanup; + + /* Advance p to point to the start of the next element, or NULL. */ + if (*end) + p = end + 1; + else + p = 0; + } + + /* Eliminate '.' and '..' elements from the path array. */ + int newpath_length = 0; + for (i = components - 1; i >= 0; --i) + { + if (! grub_strcmp (path_array[i], ".")) + { + grub_free (path_array[i]); + path_array[i] = 0; + } + else if (! grub_strcmp (path_array[i], "..") + && i > 0) + { + /* Delete the '..' and the prior path element. */ + grub_free (path_array[i]); + path_array[i] = 0; + --i; + grub_free (path_array[i]); + path_array[i] = 0; + } + else + { + newpath_length += grub_strlen (path_array[i]) + 1; + } + } + + /* Construct a new path string. */ + newpath = grub_malloc (newpath_length + 1); + if (! newpath) + goto cleanup; + + newpath[0] = '\0'; + char *newpath_end = newpath; + int first = 1; + for (i = 0; i < components; i++) + { + char *element = path_array[i]; + if (element) + { + /* For all components but the first, prefix with a slash. */ + if (! first) + newpath_end = grub_stpcpy (newpath_end, "/"); + newpath_end = grub_stpcpy (newpath_end, element); + first = 0; + } + } + +cleanup: + for (i = 0; i < components; i++) + grub_free (path_array[i]); + grub_free (path_array); + + return newpath; +} + +/* Return a new heap-allocated string representing to absolute path + to the file referred to by PATH. If PATH is an absolute path, then + the returned path is a copy of PATH. If PATH is a relative path, then + BASE is with PATH used to construct the absolute path. */ +char * +grub_resolve_relative_path (const char *base, const char *path) +{ + char *abspath; + char *canonpath; + char *p; + + /* If PATH is an absolute path, then just use it as is. */ + if (path[0] == '/' || path[0] == '(') + return canonicalize_path (path); + + abspath = grub_malloc (grub_strlen (base) + grub_strlen (path) + 1); + if (!abspath) + return 0; + + /* Concatenate BASE and PATH. + Note that BASE is expected to have a trailing slash. */ + p = grub_stpcpy (abspath, base); + grub_stpcpy (p, path); + + canonpath = canonicalize_path (abspath); + if (!canonpath) + return abspath; + + grub_free (abspath); + return canonpath; +} + +/* Get the path of the directory where the file at FILE_PATH is located. + FILE_PATH should refer to a file, not a directory. The returned path + includes a trailing slash. + This does not handle GRUB "(hd0,0)" paths properly yet since it only + looks at slashes. */ +char * +grub_get_dirname (const char *file_path) +{ + int i; + int last_slash; + + last_slash = -1; + for (i = grub_strlen (file_path) - 1; i >= 0; --i) + { + if (file_path[i] == '/') + { + last_slash = i; + break; + } + } + if (last_slash == -1) + return grub_strdup ("/"); + + return grub_new_substring (file_path, 0, last_slash + 1); +} + +static __inline int +isxdigit (char c) +{ + return ((c >= '0' && c <= '9') + || (c >= 'a' && c <= 'f') + || (c >= 'A' && c <= 'F')); +} + +static int +parse_hex_color_component (const char *s, unsigned start, unsigned end) +{ + unsigned len; + char buf[3]; + + len = end - start; + /* Check the limits so we don't overrun the buffer. */ + if (len < 1 || len > 2) + return 0; + + if (len == 1) + { + buf[0] = s[start]; /* Get the first and only hex digit. */ + buf[1] = buf[0]; /* Duplicate the hex digit. */ + } + else if (len == 2) + { + buf[0] = s[start]; + buf[1] = s[start + 1]; + } + + buf[2] = '\0'; + + return grub_strtoul (buf, 0, 16); +} + +/* Parse a color string of the form "r, g, b", "#RGB", "#RGBA", + "#RRGGBB", or "#RRGGBBAA". */ +grub_gui_color_t +grub_gui_parse_color (const char *s) +{ + grub_gui_color_t c; + + /* Skip whitespace. */ + while (*s && grub_isspace (*s)) + s++; + + if (*s == '#') + { + /* HTML-style. Number if hex digits: + [6] #RRGGBB [3] #RGB + [8] #RRGGBBAA [4] #RGBA */ + + s++; /* Skip the '#'. */ + /* Count the hexits to determine the format. */ + int hexits = 0; + const char *end = s; + while (isxdigit (*end)) + { + end++; + hexits++; + } + + /* Parse the color components based on the format. */ + if (hexits == 3 || hexits == 4) + { + c.red = parse_hex_color_component (s, 0, 1); + c.green = parse_hex_color_component (s, 1, 2); + c.blue = parse_hex_color_component (s, 2, 3); + if (hexits == 4) + c.alpha = parse_hex_color_component (s, 3, 4); + else + c.alpha = 255; + } + else if (hexits == 6 || hexits == 8) + { + c.red = parse_hex_color_component (s, 0, 2); + c.green = parse_hex_color_component (s, 2, 4); + c.blue = parse_hex_color_component (s, 4, 6); + if (hexits == 8) + c.alpha = parse_hex_color_component (s, 6, 8); + else + c.alpha = 255; + } + else + goto fail; + } + else if (grub_isdigit (*s)) + { + /* Comma separated decimal values. */ + c.red = grub_strtoul (s, 0, 0); + if ((s = grub_strchr (s, ',')) == 0) + goto fail; + s++; + c.green = grub_strtoul (s, 0, 0); + if ((s = grub_strchr (s, ',')) == 0) + goto fail; + s++; + c.blue = grub_strtoul (s, 0, 0); + if ((s = grub_strchr (s, ',')) == 0) + c.alpha = 255; + else + { + s++; + c.alpha = grub_strtoul (s, 0, 0); + } + } + else + { + if (! grub_gui_get_named_color (s, &c)) + goto fail; + } + + return c; + +fail: + c.red = 0; + c.green = 0; + c.blue = 0; + c.alpha = 255; + return c; +} + +/* Parse a value in the form "(x, y)", storing the first element (x) into + *PX and the second element (y) into *PY. + Returns 1 if successful, 0 if failed to parse. */ +int +grub_gui_parse_2_tuple (const char *s, int *px, int *py) +{ + int x; + int y; + + while (*s && grub_isspace (*s)) + s++; + if (*s != '(') + return 0; + s++; /* Skip the opening parentheses. */ + if (! *s) + return 0; + x = grub_strtol (s, 0, 10); + if ((s = grub_strchr (s, ',')) == 0) + return 0; + s++; /* Skip the element separator (the comma). */ + y = grub_strtol (s, 0, 10); + + *px = x; + *py = y; + return 1; +} === added file 'gfxmenu/gui_util.c' --- gfxmenu/gui_util.c 1970-01-01 00:00:00 +0000 +++ gfxmenu/gui_util.c 2008-08-16 17:35:05 +0000 @@ -0,0 +1,101 @@ +/* gui_util.c - GUI utility 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 + + +struct find_by_id_state +{ + const char *match_id; + grub_gui_component_callback match_callback; + void *match_userdata; +}; + +static void +find_by_id_recursively (grub_gui_component_t component, void *userdata) +{ + struct find_by_id_state *state; + const char *id; + + state = (struct find_by_id_state *) userdata; + id = component->ops->get_id (component); + if (id && grub_strcmp (id, state->match_id) == 0) + state->match_callback (component, state->match_userdata); + + if (component->ops->is_instance (component, "container")) + { + grub_gui_container_t container; + container = (grub_gui_container_t) component; + container->ops->iterate_children (container, + find_by_id_recursively, + state); + } +} + +void +grub_gui_find_by_id (grub_gui_component_t root, + const char *id, + grub_gui_component_callback cb, + void *userdata) +{ + struct find_by_id_state state; + state.match_id = id; + state.match_callback = cb; + state.match_userdata = userdata; + find_by_id_recursively (root, &state); +} + + +struct iterate_recursively_state +{ + grub_gui_component_callback callback; + void *userdata; +}; + +static +void iterate_recursively_cb (grub_gui_component_t component, void *userdata) +{ + struct iterate_recursively_state *state; + + state = (struct iterate_recursively_state *) userdata; + state->callback (component, state->userdata); + + if (component->ops->is_instance (component, "container")) + { + grub_gui_container_t container; + container = (grub_gui_container_t) component; + container->ops->iterate_children (container, + iterate_recursively_cb, + state); + } +} + +void +grub_gui_iterate_recursively (grub_gui_component_t root, + grub_gui_component_callback cb, + void *userdata) +{ + struct iterate_recursively_state state; + state.callback = cb; + state.userdata = userdata; + iterate_recursively_cb (root, &state); +} === added file 'gfxmenu/icon_manager.c' --- gfxmenu/icon_manager.c 1970-01-01 00:00:00 +0000 +++ gfxmenu/icon_manager.c 2008-08-16 17:35:05 +0000 @@ -0,0 +1,254 @@ +/* icon_manager.c - gfxmenu icon manager. */ +/* + * 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 + +/* Currently hard coded to '.png' extension. */ +static const char icon_extension[] = ".png"; + +typedef struct icon_entry +{ + char *class_name; + struct grub_video_bitmap *bitmap; + struct icon_entry *next; +} *icon_entry_t; + +struct grub_gfxmenu_icon_manager +{ + char *theme_path; + int icon_width; + int icon_height; + + /* Icon cache: linked list w/ dummy head node. */ + struct icon_entry cache; +}; + + +/* Create a new icon manager and return a point to it. */ +grub_gfxmenu_icon_manager_t +grub_gfxmenu_icon_manager_new (void) +{ + grub_gfxmenu_icon_manager_t mgr; + mgr = grub_malloc (sizeof (*mgr)); + if (! mgr) + return 0; + + mgr->theme_path = 0; + mgr->icon_width = 0; + mgr->icon_height = 0; + + /* Initialize the dummy head node. */ + mgr->cache.class_name = 0; + mgr->cache.bitmap = 0; + mgr->cache.next = 0; + + return mgr; +} + +/* Destroy the icon manager MGR, freeing all resources used by it. + +Note: Any bitmaps returned by grub_gfxmenu_icon_manager_get_icon() +are destroyed and must not be used by the caller after this function +is called. */ +void +grub_gfxmenu_icon_manager_destroy (grub_gfxmenu_icon_manager_t mgr) +{ + grub_gfxmenu_icon_manager_clear_cache (mgr); + grub_free (mgr->theme_path); + grub_free (mgr); +} + +/* Clear the icon cache. */ +void +grub_gfxmenu_icon_manager_clear_cache (grub_gfxmenu_icon_manager_t mgr) +{ + icon_entry_t cur; + icon_entry_t next; + for (cur = mgr->cache.next; cur; cur = next) + { + next = cur->next; + grub_free (cur->class_name); + grub_video_bitmap_destroy (cur->bitmap); + grub_free (cur); + } + mgr->cache.next = 0; +} + +/* Set the theme path. If the theme path is changed, the icon cache + is cleared. */ +void +grub_gfxmenu_icon_manager_set_theme_path (grub_gfxmenu_icon_manager_t mgr, + const char *path) +{ + /* Clear the cache if the theme path has changed. */ + if (((mgr->theme_path == 0) != (path == 0)) + || (grub_strcmp (mgr->theme_path, path) != 0)) + grub_gfxmenu_icon_manager_clear_cache (mgr); + + grub_free (mgr->theme_path); + mgr->theme_path = path ? grub_strdup (path) : 0; +} + +/* Set the icon size. When icons are requested from the icon manager, + they are scaled to this size before being returned. If the size is + changed, the icon cache is cleared. */ +void +grub_gfxmenu_icon_manager_set_icon_size (grub_gfxmenu_icon_manager_t mgr, + int width, int height) +{ + /* If the width or height is changed, we must clear the cache, since the + scaled bitmaps are stored in the cache. */ + if (width != mgr->icon_width || height != mgr->icon_height) + grub_gfxmenu_icon_manager_clear_cache (mgr); + + mgr->icon_width = width; + mgr->icon_height = height; +} + +/* Try to load an icon for the specified CLASS_NAME in the directory DIR. + Returns 0 if the icon could not be loaded, or returns a pointer to a new + bitmap if it was successful. */ +static struct grub_video_bitmap * +try_loading_icon (grub_gfxmenu_icon_manager_t mgr, + const char *dir, const char *class_name) +{ + char *path = grub_malloc (grub_strlen (dir) + + grub_strlen (class_name) + + grub_strlen (icon_extension) + + 1); + if (! path) + return 0; + + grub_strcpy (path, dir); + grub_strcat (path, class_name); + grub_strcat (path, icon_extension); + + struct grub_video_bitmap *raw_bitmap; + grub_video_bitmap_load (&raw_bitmap, path); + grub_free (path); + grub_errno = GRUB_ERR_NONE; /* Critical to clear the error!! */ + if (! raw_bitmap) + return 0; + + struct grub_video_bitmap *scaled_bitmap; + grub_video_bitmap_create_scaled (&scaled_bitmap, + mgr->icon_width, mgr->icon_height, + raw_bitmap, + GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST); + grub_video_bitmap_destroy (raw_bitmap); + if (! scaled_bitmap) + return 0; + + return scaled_bitmap; +} + +/* Get the icon for the specified class CLASS_NAME. If an icon for + CLASS_NAME already exists in the cache, then a reference to the cached + bitmap is returned. If it is not cached, then it is loaded and cached. + If no icon could be could for CLASS_NAME, then 0 is returned. */ +static struct grub_video_bitmap * +get_icon_by_class (grub_gfxmenu_icon_manager_t mgr, const char *class_name) +{ + /* First check the icon cache. */ + icon_entry_t entry; + for (entry = mgr->cache.next; entry; entry = entry->next) + { + if (grub_strcmp (entry->class_name, class_name) == 0) + return entry->bitmap; + } + + if (! mgr->theme_path) + return 0; + + /* Otherwise, we search for an icon to load. */ + char *theme_dir = grub_get_dirname (mgr->theme_path); + char *icons_dir; + struct grub_video_bitmap *icon; + icon = 0; + /* First try the theme's own icons, from "grub/themes/NAME/icons/" */ + icons_dir = grub_resolve_relative_path (theme_dir, "icons/"); + if (icons_dir) + { + icon = try_loading_icon (mgr, icons_dir, class_name); + grub_free (icons_dir); + } + if (! icon) + { + /* If the theme doesn't have an appropriate icon, check in + "grub/themes/icons". */ + /* TODO use GRUB prefix "/icons" */ + icons_dir = grub_resolve_relative_path (theme_dir, "../icons/"); + if (icons_dir) + { + icon = try_loading_icon (mgr, icons_dir, class_name); + grub_free (icons_dir); + } + } + grub_free (theme_dir); + + /* No icon was found. */ + /* This should probably be noted in the cache, so that a search is not + performed each time an icon for CLASS_NAME is requested. */ + if (! icon) + return 0; + + /* Insert a new cache entry for this icon. */ + entry = grub_malloc (sizeof (*entry)); + if (! entry) + { + grub_video_bitmap_destroy (icon); + return 0; + } + entry->class_name = grub_strdup (class_name); + entry->bitmap = icon; + entry->next = mgr->cache.next; + mgr->cache.next = entry; /* Link it into the cache. */ + return entry->bitmap; +} + +/* Get the best available icon for ENTRY. Beginning with the first class + listed in the menu entry and proceeding forward, an icon for each class + is searched for. The first icon found is returned. The returned icon + is scaled to the size specified by + grub_gfxmenu_icon_manager_set_icon_size(). + + Note: Bitmaps returned by this function are destroyed when the + icon manager is destroyed. + */ +struct grub_video_bitmap * +grub_gfxmenu_icon_manager_get_icon (grub_gfxmenu_icon_manager_t mgr, + grub_menu_entry_t entry) +{ + struct grub_menu_entry_class *c; + struct grub_video_bitmap *icon; + + /* Try each class in succession. */ + icon = 0; + for (c = entry->classes->next; c && ! icon; c = c->next) + icon = get_icon_by_class (mgr, c->name); + return icon; +} === added file 'gfxmenu/model.c' --- gfxmenu/model.c 1970-01-01 00:00:00 +0000 +++ gfxmenu/model.c 2008-08-12 02:55:06 +0000 @@ -0,0 +1,191 @@ +/* model.c - Graphical menu interface MVC model. */ +/* + * 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 + +/* Model type definition. */ +struct grub_gfxmenu_model +{ + grub_menu_t menu; + int num_entries; + grub_menu_entry_t *entries; + int selected_entry_index; + int timeout_set; + grub_uint64_t timeout_start; + grub_uint64_t timeout_at; +}; + + +grub_gfxmenu_model_t +grub_gfxmenu_model_new (grub_menu_t menu) +{ + grub_gfxmenu_model_t model; + + model = grub_malloc (sizeof (*model)); + if (! model) + return 0; + + model->menu = menu; + model->num_entries = menu->size; + model->entries = 0; + model->selected_entry_index = 0; + model->timeout_set = 0; + model->timeout_at = 0; + if (model->num_entries > 0) + { + model->entries = grub_malloc (model->num_entries + * sizeof (*model->entries)); + if (! model->entries) + goto fail_and_free; + + int i; + grub_menu_entry_t cur; + for (i = 0, cur = menu->entry_list; + i < model->num_entries; + i++, cur = cur->next) + { + model->entries[i] = cur; + } + } + + return model; + +fail_and_free: + grub_free (model->entries); + grub_free (model); + return 0; +} + +void +grub_gfxmenu_model_destroy (grub_gfxmenu_model_t model) +{ + if (! model) + return; + + grub_free (model->entries); + model->entries = 0; + + grub_free (model); +} + +grub_menu_t +grub_gfxmenu_model_get_menu (grub_gfxmenu_model_t model) +{ + return model->menu; +} + +void +grub_gfxmenu_model_set_timeout (grub_gfxmenu_model_t model) +{ + int timeout_sec = grub_menu_get_timeout (); + if (timeout_sec >= 0) + { + model->timeout_start = grub_get_time_ms (); + model->timeout_at = model->timeout_start + timeout_sec * 1000; + model->timeout_set = 1; + } + else + { + model->timeout_set = 0; + } +} + +void +grub_gfxmenu_model_clear_timeout (grub_gfxmenu_model_t model) +{ + model->timeout_set = 0; + grub_menu_set_timeout (-1); +} + +int +grub_gfxmenu_model_get_timeout_ms (grub_gfxmenu_model_t model) +{ + if (!model->timeout_set) + return -1; + + return model->timeout_at - model->timeout_start; +} + +int +grub_gfxmenu_model_get_timeout_remaining_ms (grub_gfxmenu_model_t model) +{ + if (!model->timeout_set) + return -1; + + return model->timeout_at - grub_get_time_ms (); +} + +int +grub_gfxmenu_model_timeout_expired (grub_gfxmenu_model_t model) +{ + if (model->timeout_set + && grub_get_time_ms () >= model->timeout_at) + return 1; + + return 0; +} + +int +grub_gfxmenu_model_get_num_entries (grub_gfxmenu_model_t model) +{ + return model->num_entries; +} + +int +grub_gfxmenu_model_get_selected_index (grub_gfxmenu_model_t model) +{ + return model->selected_entry_index; +} + +void +grub_gfxmenu_model_set_selected_index (grub_gfxmenu_model_t model, int index) +{ + model->selected_entry_index = index; +} + +const char * +grub_gfxmenu_model_get_entry_title (grub_gfxmenu_model_t model, int index) +{ + if (index < 0 || index >= model->num_entries) + { + grub_error (GRUB_ERR_OUT_OF_RANGE, "invalid menu index"); + return 0; + } + + return model->entries[index]->title; +} + +grub_menu_entry_t +grub_gfxmenu_model_get_entry (grub_gfxmenu_model_t model, int index) +{ + if (index < 0 || index >= model->num_entries) + { + grub_error (GRUB_ERR_OUT_OF_RANGE, "invalid menu index"); + return 0; + } + + return model->entries[index]; +} === added file 'gfxmenu/named_colors.c' --- gfxmenu/named_colors.c 1970-01-01 00:00:00 +0000 +++ gfxmenu/named_colors.c 2008-08-16 17:35:05 +0000 @@ -0,0 +1,209 @@ +/* named_colors.c - Named color values. */ +/* + * 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 + +struct named_color +{ + const char *name; + grub_gui_color_t color; +}; + +/* + Named color list generated from the list of SVG color keywords from + , + processed through the following Perl command: + perl -ne 'chomp;split;print "{ \"$_[0]\", RGB_COLOR($_[2]) },\n"' + */ + +#define RGB_COLOR(r,g,b) {.red = r, .green = g, .blue = b, .alpha = 255} + +static struct named_color named_colors[] = +{ + { "aliceblue", RGB_COLOR(240,248,255) }, + { "antiquewhite", RGB_COLOR(250,235,215) }, + { "aqua", RGB_COLOR(0,255,255) }, + { "aquamarine", RGB_COLOR(127,255,212) }, + { "azure", RGB_COLOR(240,255,255) }, + { "beige", RGB_COLOR(245,245,220) }, + { "bisque", RGB_COLOR(255,228,196) }, + { "black", RGB_COLOR(0,0,0) }, + { "blanchedalmond", RGB_COLOR(255,235,205) }, + { "blue", RGB_COLOR(0,0,255) }, + { "blueviolet", RGB_COLOR(138,43,226) }, + { "brown", RGB_COLOR(165,42,42) }, + { "burlywood", RGB_COLOR(222,184,135) }, + { "cadetblue", RGB_COLOR(95,158,160) }, + { "chartreuse", RGB_COLOR(127,255,0) }, + { "chocolate", RGB_COLOR(210,105,30) }, + { "coral", RGB_COLOR(255,127,80) }, + { "cornflowerblue", RGB_COLOR(100,149,237) }, + { "cornsilk", RGB_COLOR(255,248,220) }, + { "crimson", RGB_COLOR(220,20,60) }, + { "cyan", RGB_COLOR(0,255,255) }, + { "darkblue", RGB_COLOR(0,0,139) }, + { "darkcyan", RGB_COLOR(0,139,139) }, + { "darkgoldenrod", RGB_COLOR(184,134,11) }, + { "darkgray", RGB_COLOR(169,169,169) }, + { "darkgreen", RGB_COLOR(0,100,0) }, + { "darkgrey", RGB_COLOR(169,169,169) }, + { "darkkhaki", RGB_COLOR(189,183,107) }, + { "darkmagenta", RGB_COLOR(139,0,139) }, + { "darkolivegreen", RGB_COLOR(85,107,47) }, + { "darkorange", RGB_COLOR(255,140,0) }, + { "darkorchid", RGB_COLOR(153,50,204) }, + { "darkred", RGB_COLOR(139,0,0) }, + { "darksalmon", RGB_COLOR(233,150,122) }, + { "darkseagreen", RGB_COLOR(143,188,143) }, + { "darkslateblue", RGB_COLOR(72,61,139) }, + { "darkslategray", RGB_COLOR(47,79,79) }, + { "darkslategrey", RGB_COLOR(47,79,79) }, + { "darkturquoise", RGB_COLOR(0,206,209) }, + { "darkviolet", RGB_COLOR(148,0,211) }, + { "deeppink", RGB_COLOR(255,20,147) }, + { "deepskyblue", RGB_COLOR(0,191,255) }, + { "dimgray", RGB_COLOR(105,105,105) }, + { "dimgrey", RGB_COLOR(105,105,105) }, + { "dodgerblue", RGB_COLOR(30,144,255) }, + { "firebrick", RGB_COLOR(178,34,34) }, + { "floralwhite", RGB_COLOR(255,250,240) }, + { "forestgreen", RGB_COLOR(34,139,34) }, + { "fuchsia", RGB_COLOR(255,0,255) }, + { "gainsboro", RGB_COLOR(220,220,220) }, + { "ghostwhite", RGB_COLOR(248,248,255) }, + { "gold", RGB_COLOR(255,215,0) }, + { "goldenrod", RGB_COLOR(218,165,32) }, + { "gray", RGB_COLOR(128,128,128) }, + { "green", RGB_COLOR(0,128,0) }, + { "greenyellow", RGB_COLOR(173,255,47) }, + { "grey", RGB_COLOR(128,128,128) }, + { "honeydew", RGB_COLOR(240,255,240) }, + { "hotpink", RGB_COLOR(255,105,180) }, + { "indianred", RGB_COLOR(205,92,92) }, + { "indigo", RGB_COLOR(75,0,130) }, + { "ivory", RGB_COLOR(255,255,240) }, + { "khaki", RGB_COLOR(240,230,140) }, + { "lavender", RGB_COLOR(230,230,250) }, + { "lavenderblush", RGB_COLOR(255,240,245) }, + { "lawngreen", RGB_COLOR(124,252,0) }, + { "lemonchiffon", RGB_COLOR(255,250,205) }, + { "lightblue", RGB_COLOR(173,216,230) }, + { "lightcoral", RGB_COLOR(240,128,128) }, + { "lightcyan", RGB_COLOR(224,255,255) }, + { "lightgoldenrodyellow", RGB_COLOR(250,250,210) }, + { "lightgray", RGB_COLOR(211,211,211) }, + { "lightgreen", RGB_COLOR(144,238,144) }, + { "lightgrey", RGB_COLOR(211,211,211) }, + { "lightpink", RGB_COLOR(255,182,193) }, + { "lightsalmon", RGB_COLOR(255,160,122) }, + { "lightseagreen", RGB_COLOR(32,178,170) }, + { "lightskyblue", RGB_COLOR(135,206,250) }, + { "lightslategray", RGB_COLOR(119,136,153) }, + { "lightslategrey", RGB_COLOR(119,136,153) }, + { "lightsteelblue", RGB_COLOR(176,196,222) }, + { "lightyellow", RGB_COLOR(255,255,224) }, + { "lime", RGB_COLOR(0,255,0) }, + { "limegreen", RGB_COLOR(50,205,50) }, + { "linen", RGB_COLOR(250,240,230) }, + { "magenta", RGB_COLOR(255,0,255) }, + { "maroon", RGB_COLOR(128,0,0) }, + { "mediumaquamarine", RGB_COLOR(102,205,170) }, + { "mediumblue", RGB_COLOR(0,0,205) }, + { "mediumorchid", RGB_COLOR(186,85,211) }, + { "mediumpurple", RGB_COLOR(147,112,219) }, + { "mediumseagreen", RGB_COLOR(60,179,113) }, + { "mediumslateblue", RGB_COLOR(123,104,238) }, + { "mediumspringgreen", RGB_COLOR(0,250,154) }, + { "mediumturquoise", RGB_COLOR(72,209,204) }, + { "mediumvioletred", RGB_COLOR(199,21,133) }, + { "midnightblue", RGB_COLOR(25,25,112) }, + { "mintcream", RGB_COLOR(245,255,250) }, + { "mistyrose", RGB_COLOR(255,228,225) }, + { "moccasin", RGB_COLOR(255,228,181) }, + { "navajowhite", RGB_COLOR(255,222,173) }, + { "navy", RGB_COLOR(0,0,128) }, + { "oldlace", RGB_COLOR(253,245,230) }, + { "olive", RGB_COLOR(128,128,0) }, + { "olivedrab", RGB_COLOR(107,142,35) }, + { "orange", RGB_COLOR(255,165,0) }, + { "orangered", RGB_COLOR(255,69,0) }, + { "orchid", RGB_COLOR(218,112,214) }, + { "palegoldenrod", RGB_COLOR(238,232,170) }, + { "palegreen", RGB_COLOR(152,251,152) }, + { "paleturquoise", RGB_COLOR(175,238,238) }, + { "palevioletred", RGB_COLOR(219,112,147) }, + { "papayawhip", RGB_COLOR(255,239,213) }, + { "peachpuff", RGB_COLOR(255,218,185) }, + { "peru", RGB_COLOR(205,133,63) }, + { "pink", RGB_COLOR(255,192,203) }, + { "plum", RGB_COLOR(221,160,221) }, + { "powderblue", RGB_COLOR(176,224,230) }, + { "purple", RGB_COLOR(128,0,128) }, + { "red", RGB_COLOR(255,0,0) }, + { "rosybrown", RGB_COLOR(188,143,143) }, + { "royalblue", RGB_COLOR(65,105,225) }, + { "saddlebrown", RGB_COLOR(139,69,19) }, + { "salmon", RGB_COLOR(250,128,114) }, + { "sandybrown", RGB_COLOR(244,164,96) }, + { "seagreen", RGB_COLOR(46,139,87) }, + { "seashell", RGB_COLOR(255,245,238) }, + { "sienna", RGB_COLOR(160,82,45) }, + { "silver", RGB_COLOR(192,192,192) }, + { "skyblue", RGB_COLOR(135,206,235) }, + { "slateblue", RGB_COLOR(106,90,205) }, + { "slategray", RGB_COLOR(112,128,144) }, + { "slategrey", RGB_COLOR(112,128,144) }, + { "snow", RGB_COLOR(255,250,250) }, + { "springgreen", RGB_COLOR(0,255,127) }, + { "steelblue", RGB_COLOR(70,130,180) }, + { "tan", RGB_COLOR(210,180,140) }, + { "teal", RGB_COLOR(0,128,128) }, + { "thistle", RGB_COLOR(216,191,216) }, + { "tomato", RGB_COLOR(255,99,71) }, + { "turquoise", RGB_COLOR(64,224,208) }, + { "violet", RGB_COLOR(238,130,238) }, + { "wheat", RGB_COLOR(245,222,179) }, + { "white", RGB_COLOR(255,255,255) }, + { "whitesmoke", RGB_COLOR(245,245,245) }, + { "yellow", RGB_COLOR(255,255,0) }, + { "yellowgreen", RGB_COLOR(154,205,50) }, + { 0, { 0, 0, 0, 0 } } /* Terminator. */ +}; + +/* Get the color named NAME. If the color was found, returns 1 and + stores the color into *COLOR. If the color was not found, returns 0 and + does not modify *COLOR. */ +int +grub_gui_get_named_color (const char *name, + grub_gui_color_t *color) +{ + int i; + for (i = 0; named_colors[i].name; i++) + { + if (grub_strcmp (named_colors[i].name, name) == 0) + { + *color = named_colors[i].color; + return 1; + } + } + return 0; +} === added file 'gfxmenu/theme_loader.c' --- gfxmenu/theme_loader.c 1970-01-01 00:00:00 +0000 +++ gfxmenu/theme_loader.c 2008-08-16 17:35:05 +0000 @@ -0,0 +1,609 @@ +/* theme_loader.c - Theme file loader for 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Construct a new box widget using ABSPATTERN to find the pixmap files for + it, storing the new box instance at *BOXPTR. + PATTERN should be of the form: "(hd0,0)/somewhere/style*.png". + The '*' then gets substituted with the various pixmap names that the + box uses. + + Returns zero on failure, nonzero on success. + */ +static int +recreate_box_absolute (grub_gfxmenu_box_t *boxptr, const char *abspattern) +{ + char *prefix; + char *suffix; + char *star; + grub_gfxmenu_box_t box; + + star = grub_strchr (abspattern, '*'); + if (! star) + return 0; + + /* Prefix: Get the part before the '*'. */ + prefix = grub_malloc (star - abspattern + 1); + if (! prefix) + return 0; + + grub_memcpy (prefix, abspattern, star - abspattern); + prefix[star - abspattern] = '\0'; + + /* Suffix: Everything after the '*' is the suffix. */ + suffix = star + 1; + + box = grub_gfxmenu_create_box (prefix, suffix); + grub_free (prefix); + if (! box) + return 0; + + if (*boxptr) + (*boxptr)->destroy (*boxptr); + *boxptr = box; + return 1; +} + + +/* Construct a new box widget using PATTERN to find the pixmap files for it, + storing the new widget at *BOXPTR. PATTERN should be of the form: + "somewhere/style*.png". The '*' then gets substituted with the various + pixmap names that the widget uses. + + Important! The value of *BOXPTR must be initialized! It must either + (1) Be 0 (a NULL pointer), or + (2) Be a pointer to a valid 'grub_gfxmenu_box_t' instance. + In this case, the previous instance is destroyed. + + Returns zero on failure, nonzero on success. + */ +int +grub_gui_recreate_box (grub_gfxmenu_box_t *boxptr, + const char *pattern, const char *theme_dir) +{ + char *abspattern; + int success; + + /* Check arguments. */ + if (! pattern || ! theme_dir) + return 0; + + /* Resolve to an absolute path. */ + abspattern = grub_resolve_relative_path (theme_dir, pattern); + if (! abspattern) + return 0; + + /* Create the box. */ + success = recreate_box_absolute (boxptr, abspattern); + grub_free (abspattern); + return success; +} + +/* Set the specified property NAME on the view to the given string VALUE. + This function takes ownership of both NAME and VALUE, so the caller + should pass pointers to new heap-allocated strings. */ +static void +theme_set_string (grub_gfxmenu_view_t view, + const char *name, + const char *value, + const char *theme_dir) +{ + if (! grub_strcmp ("title-font", name)) + view->title_font = grub_font_get (value); + else if (! grub_strcmp ("message-font", name)) + view->message_font = grub_font_get (value); + else if (! grub_strcmp ("terminal-font", name)) + { + grub_free (view->terminal_font_name); + view->terminal_font_name = grub_strdup (value); + } + else if (! grub_strcmp ("title-color", name)) + view->title_color = grub_gui_parse_color (value); + else if (! grub_strcmp ("message-color", name)) + view->message_color = grub_gui_parse_color (value); + else if (! grub_strcmp ("message-bg-color", name)) + view->message_bg_color = grub_gui_parse_color (value); + else if (! grub_strcmp ("desktop-image", name)) + { + struct grub_video_bitmap *raw_bitmap; + struct grub_video_bitmap *scaled_bitmap; + char *path; + path = grub_resolve_relative_path (theme_dir, value); + if (! path) + return; + grub_errno = GRUB_ERR_NONE; + if (grub_video_bitmap_load (&raw_bitmap, path) != GRUB_ERR_NONE) + { + grub_errno = GRUB_ERR_NONE; + grub_free (path); + return; + } + grub_free(path); + grub_video_bitmap_create_scaled (&scaled_bitmap, + view->screen.width, + view->screen.height, + raw_bitmap, + GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST); + grub_video_bitmap_destroy (raw_bitmap); + if (!scaled_bitmap) + return; + + grub_video_bitmap_destroy (view->desktop_image); + view->desktop_image = scaled_bitmap; + } + else if (! grub_strcmp ("desktop-color", name)) + view->desktop_color = grub_gui_parse_color (value); + else if (! grub_strcmp ("terminal-box", name)) + grub_gui_recreate_box (&view->terminal_box, value, theme_dir); + else if (! grub_strcmp ("title-text", name)) + { + grub_free (view->title_text); + view->title_text = grub_strdup (value); + } +} + +struct parsebuf +{ + char *buf; + int pos; + int len; + char *theme_dir; + grub_gfxmenu_view_t view; +}; + +static void +advance_to_next_line (struct parsebuf *p) +{ + /* Eat characters up to the newline. */ + while (p->pos < p->len && p->buf[p->pos] != '\n') + p->pos++; + p->pos++; /* Eat the newline. */ +} + +static void +skip_whitespace (struct parsebuf *p) +{ + while (p->pos < p->len + && (p->buf[p->pos] == ' ' + || p->buf[p->pos] == '\t' + || p->buf[p->pos] == '\r' + || p->buf[p->pos] == '\n' + || p->buf[p->pos] == '\f')) + p->pos++; +} + +static int +read_char (struct parsebuf *p) +{ + if (p->pos < p->len) + return p->buf[p->pos++]; + else + return -1; +} + +static char * +read_identifier (struct parsebuf *p) +{ + int start; + int end; + + skip_whitespace (p); + start = p->pos; + while (p->pos < p->len + && (grub_isalpha(p->buf[p->pos]) + || grub_isdigit(p->buf[p->pos]) + || p->buf[p->pos] == '_' + || p->buf[p->pos] == '-')) + p->pos++; + end = p->pos; + if (end - start < 1) + return 0; + + return grub_new_substring (p->buf, start, end); +} + +static char * +read_expression (struct parsebuf *p) +{ + int start; + int end; + + skip_whitespace (p); + if (p->pos < p->len && p->buf[p->pos] == '"') + { + /* Read as a quoted string. */ + /* The quotation marks are not included in the expression value. */ + p->pos++; + start = p->pos; + while (p->pos < p->len + && p->buf[p->pos] != '"') + p->pos++; + end = p->pos; + if (p->pos < p->len) + p->pos++; /* Skip the terminating quotation mark. */ + } + else if (p->pos < p->len && p->buf[p->pos] == '(') + { + /* Read as a parenthesized string -- for tuples/coordinates. */ + /* The parentheses are included in the expression value. */ + start = p->pos; + while (p->pos < p->len) + { + char c = p->buf[p->pos]; + p->pos++; + if (c == ')') + break; + } + end = p->pos; + } + else if (p->pos < p->len) + { + /* Read as a single word -- for numeric values. */ + start = p->pos; + while (p->pos < p->len + && ! grub_isspace (p->buf[p->pos])) + p->pos++; + end = p->pos; + } + else + return 0; + + return grub_new_substring (p->buf, start, end); +} + +/* Read a GUI object specification from the theme file. + Any components created will be added to the GUI container PARENT. + Returns 0 on success, nonzero on failure. */ +static int +read_object (struct parsebuf *p, grub_gui_container_t parent) +{ + char *name; + name = read_identifier (p); + if (! name) + goto fail; + + grub_gui_component_t component = 0; + if (grub_strcmp (name, "label") == 0) + { + component = grub_gui_label_new (); + } + else if (grub_strcmp (name, "image") == 0) + { + component = grub_gui_image_new (); + } + else if (grub_strcmp (name, "vbox") == 0) + { + component = (grub_gui_component_t) grub_gui_vbox_new (); + } + else if (grub_strcmp (name, "hbox") == 0) + { + component = (grub_gui_component_t) grub_gui_hbox_new (); + } + else if (grub_strcmp (name, "canvas") == 0) + { + component = (grub_gui_component_t) grub_gui_canvas_new (); + } + else if (grub_strcmp (name, "progress_bar") == 0) + { + component = grub_gui_progress_bar_new (); + } + else if (grub_strcmp (name, "circular_progress") == 0) + { + component = grub_gui_circular_progress_new (); + } + else if (grub_strcmp (name, "boot_menu") == 0) + { + component = grub_gui_list_new (); + } + else + { + /* Unknown type; ignore. */ + goto fail; /* We should actually parse the structure. */ + } + + if (component) + { + grub_video_rect_t r = { .x=0, .y=0, .width=-1, .height=-1 }; + component->ops->set_bounds (component, &r); + parent->ops->add (parent, component); + } + + skip_whitespace (p); + if (read_char (p) != '{') + goto fail; + + while (p->pos < p->len) + { + skip_whitespace (p); + + /* Check whether the end has been encountered. */ + if (p->pos < p->len + && p->buf[p->pos] == '}') + { + p->pos++; /* Skip the closing brace. */ + break; + } + + if (p->pos < p->len + && p->buf[p->pos] == '#') + { + /* Skip comments. */ + advance_to_next_line (p); + continue; + } + + if (p->pos < p->len + && p->buf[p->pos] == '+') + { + p->pos++; /* Skip the '+'. */ + /* Check whether this component is a container. */ + if (component->ops->is_instance (component, "container")) + { + /* Read the sub-object recursively and add it as a child. */ + if (read_object (p, (grub_gui_container_t) component) != 0) + goto fail; + /* After reading the sub-object, resume parsing, expecting + another property assignment or sub-object definition. */ + continue; + } + else + { + /* error ("Attempted to add object to non-container.") */ + goto fail; + } + } + + char *property; + property = read_identifier (p); + if (! property) + goto fail; + + skip_whitespace (p); + if (read_char (p) != '=') + { + grub_free (property); + goto fail; + } + skip_whitespace (p); + + char *value; + value = read_expression (p); + if (! value) + { + grub_free (property); + goto fail; + } + + /* Handle the property value. */ + if (grub_strcmp (property, "position") == 0) + { + /* Special case for position value. */ + int x; + int y; + + if (grub_gui_parse_2_tuple (value, &x, &y)) + { + grub_video_rect_t r; + component->ops->get_bounds (component, &r); + r.x = x; + r.y = y; + component->ops->set_bounds (component, &r); + } + } + else if (grub_strcmp (property, "size") == 0) + { + /* Special case for size value. */ + int w; + int h; + + if (grub_gui_parse_2_tuple (value, &w, &h)) + { + grub_video_rect_t r; + component->ops->get_bounds (component, &r); + r.width = w; + r.height = h; + component->ops->set_bounds (component, &r); + } + } + else + { + /* General property handling. */ + component->ops->set_property (component, property, value); + } + + grub_free (value); + grub_free (property); + } + + /* Set the object's size to its preferred size unless the user has + explicitly specified the size. */ + grub_video_rect_t r; + component->ops->get_bounds (component, &r); + if (r.width == -1 || r.height == -1) + { + component->ops->get_preferred_size (component, &r.width, &r.height); + component->ops->set_bounds (component, &r); + } + + return 0; + +fail: + grub_free (name); + + /* Once the opening brace has been found, we always jump past the closing + brace if an error is encountered. */ + while (p->pos < p->len + && p->buf[p->pos] != '}') + p->pos++; + return 1; +} + +static void +read_property (struct parsebuf *p) +{ + char *name; + + name = read_identifier (p); /* Read the property name. */ + if (! name) + goto next_line; + + skip_whitespace (p); /* Skip whitespace before separator. */ + + if (read_char (p) != ':') /* Read separator. */ + goto next_line; + + skip_whitespace (p); /* Skip whitespace after separator. */ + + /* Get the value based on its type. */ + if (p->pos < p->len && p->buf[p->pos] == '"') + { + /* String value. (e.g., '"My string"') */ + char *value = read_expression (p); + if (! value) + goto next_line; + theme_set_string (p->view, name, value, p->theme_dir); + grub_free (value); + } + +next_line: + grub_free (name); + advance_to_next_line (p); +} + +struct theme_info +{ + const char *theme_dir; + const char *theme_path; +}; + +static void +update_theme_info_visit (grub_gui_component_t component, void *userdata) +{ + struct theme_info *info; + info = userdata; + component->ops->set_property (component, "theme_dir", info->theme_dir); + component->ops->set_property (component, "theme_path", info->theme_path); +} + +/* Update any boot menu components with the current menu model and + theme path. */ +static void +update_component_tree_theme_info (grub_gui_component_t root, + const char *theme_dir, + const char *theme_path) +{ + struct theme_info info; + info.theme_dir = theme_dir; + info.theme_path = theme_path; + grub_gui_iterate_recursively (root, update_theme_info_visit, &info); +} + +/* Set properties on the view based on settings from the specified + theme file. Returns nonzero on success, zero on failure. */ +int +grub_gfxmenu_view_load_theme (grub_gfxmenu_view_t view, const char *theme_path) +{ + grub_file_t file; + struct parsebuf p; + + p.view = view; + p.theme_dir = grub_get_dirname (theme_path); + + file = grub_file_open (theme_path); + if (!file) + { + grub_free (p.theme_dir); + return 0; + } + + p.len = grub_file_size (file); + p.buf = grub_malloc (p.len); + if (! p.buf) + { + grub_file_close (file); + grub_free (p.theme_dir); + return 0; + } + if (grub_file_read (file, p.buf, p.len) != p.len) + { + grub_free (p.buf); + grub_file_close (file); + grub_free (p.theme_dir); + return 0; + } + + if (view->canvas) + view->canvas->ops->component.destroy (view->canvas); + + view->canvas = grub_gui_canvas_new (); + ((grub_gui_component_t) view->canvas) + ->ops->set_bounds ((grub_gui_component_t) view->canvas, + &view->screen); + + p.pos = 0; + while (p.pos < p.len) + { + /* Skip comments (lines beginning with #). */ + if (p.pos < p.len && p.buf[p.pos] == '#') + { + advance_to_next_line (&p); + continue; + } + + /* Get name. */ + /* Find the first non-whitespace character. */ + skip_whitespace (&p); + + /* Handle the content. */ + if (p.buf[p.pos] == '+') + { + p.pos++; /* Skip the '+'. */ + read_object (&p, view->canvas); + } + else + { + read_property (&p); + } + } + + /* Set the new theme path. */ + grub_free (view->theme_path); + view->theme_path = grub_strdup (theme_path); + update_component_tree_theme_info ((grub_gui_component_t) view->canvas, + p.theme_dir, + theme_path); + + grub_free (p.buf); + grub_file_close (file); + grub_free (p.theme_dir); + return 1; +} === added file 'gfxmenu/view.c' --- gfxmenu/view.c 1970-01-01 00:00:00 +0000 +++ gfxmenu/view.c 2008-08-16 17:35:05 +0000 @@ -0,0 +1,505 @@ +/* view.c - Graphical menu interface MVC view. */ +/* + * 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 +#include +#include +#include +#include +#include +#include + +/* The component ID identifying GUI components to be updated as the timeout + status changes. */ +#define TIMEOUT_COMPONENT_ID "__timeout__" + +static void init_terminal (grub_gfxmenu_view_t view); +static void destroy_terminal (void); +static grub_err_t set_graphics_mode (void); +static grub_err_t set_text_mode (void); + +/* Create a new view object, loading the theme specified by THEME_PATH and + associating MODEL with the view. */ +grub_gfxmenu_view_t +grub_gfxmenu_view_new (const char *theme_path, grub_gfxmenu_model_t model) +{ + grub_gfxmenu_view_t view; + + view = grub_malloc (sizeof (*view)); + if (! view) + return 0; + + set_graphics_mode (); + grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY); + grub_video_get_viewport ((unsigned *) &view->screen.x, + (unsigned *) &view->screen.y, + (unsigned *) &view->screen.width, + (unsigned *) &view->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 (0, 0, 0), + view->screen.x, view->screen.y, + view->screen.width, view->screen.height); + grub_video_swap_buffers (); + + grub_font_t default_font; + grub_gui_color_t default_fg_color; + grub_gui_color_t default_bg_color; + + default_font = grub_font_get ("Helvetica 12"); + default_fg_color = grub_gui_color_rgb (0, 0, 0); + default_bg_color = grub_gui_color_rgb (255, 255, 255); + + view->model = model; + view->canvas = 0; + + view->title_font = default_font; + view->message_font = default_font; + view->terminal_font_name = grub_strdup ("Fixed 10"); + view->title_color = default_fg_color; + view->message_color = default_bg_color; + view->message_bg_color = default_fg_color; + view->desktop_image = 0; + view->desktop_color = default_bg_color; + view->terminal_box = grub_gfxmenu_create_box (0, 0); + view->title_text = grub_strdup ("GRUB Boot Menu"); + view->progress_message_text = 0; + view->theme_path = 0; + + if (! grub_gfxmenu_view_load_theme (view, theme_path)) + { + grub_gfxmenu_view_destroy (view); + return 0; + } + + init_terminal (view); + + return view; +} + +/* Destroy the view object. All used memory is freed. */ +void +grub_gfxmenu_view_destroy (grub_gfxmenu_view_t view) +{ + grub_video_bitmap_destroy (view->desktop_image); + if (view->terminal_box) + view->terminal_box->destroy (view->terminal_box); + grub_free (view->terminal_font_name); + grub_free (view->title_text); + grub_free (view->progress_message_text); + grub_free (view->theme_path); + if (view->canvas) + view->canvas->ops->component.destroy (view->canvas); + grub_free (view); + + set_text_mode (); + destroy_terminal (); +} + +/* Sets MESSAGE as the progress message for the view. + MESSAGE can be 0, in which case no message is displayed. */ +static void +set_progress_message (grub_gfxmenu_view_t view, const char *message) +{ + grub_free (view->progress_message_text); + if (message) + view->progress_message_text = grub_strdup (message); + else + view->progress_message_text = 0; +} + +static void +draw_background (grub_gfxmenu_view_t view) +{ + if (view->desktop_image) + { + struct grub_video_bitmap *img = view->desktop_image; + grub_video_blit_bitmap (img, GRUB_VIDEO_BLIT_REPLACE, + view->screen.x, view->screen.y, 0, 0, + grub_video_bitmap_get_width (img), + grub_video_bitmap_get_height (img)); + } + else + { + grub_video_fill_rect (grub_gui_map_color (view->desktop_color), + view->screen.x, view->screen.y, + view->screen.width, view->screen.height); + } +} + +static void +draw_title (grub_gfxmenu_view_t view) +{ + if (! view->title_text) + return; + + /* Center the title. */ + int title_width = grub_font_get_string_width (view->title_font, + view->title_text); + int x = (view->screen.width - title_width) / 2; + int y = 40 + grub_font_get_ascent (view->title_font); + grub_video_draw_string (view->title_text, + view->title_font, + grub_gui_map_color (view->title_color), + x, y); +} + +struct progress_value_data +{ + const char *visible; + const char *start; + const char *end; + const char *value; + const char *text; +}; + +static void +update_timeout_visit (grub_gui_component_t component, + void *userdata) +{ + struct progress_value_data *pv; + pv = (struct progress_value_data *) userdata; + component->ops->set_property (component, "visible", pv->visible); + component->ops->set_property (component, "start", pv->start); + component->ops->set_property (component, "end", pv->end); + component->ops->set_property (component, "value", pv->value); + component->ops->set_property (component, "text", pv->text); +} + +static void +update_timeout (grub_gfxmenu_view_t view) +{ + char startbuf[20]; + char valuebuf[20]; + char msgbuf[120]; + + int timeout = grub_gfxmenu_model_get_timeout_ms (view->model); + int remaining = grub_gfxmenu_model_get_timeout_remaining_ms (view->model); + struct progress_value_data pv; + + pv.visible = timeout > 0 ? "true" : "false"; + grub_sprintf (startbuf, "%d", -timeout); + pv.start = startbuf; + pv.end = "0"; + grub_sprintf (valuebuf, "%d", remaining > 0 ? -remaining : 0); + pv.value = valuebuf; + + int seconds_remaining_rounded_up = (remaining + 999) / 1000; + grub_sprintf (msgbuf, + "The highlighted entry will be booted automatically in %d s.", + seconds_remaining_rounded_up); + pv.text = msgbuf; + + grub_gui_find_by_id ((grub_gui_component_t) view->canvas, + TIMEOUT_COMPONENT_ID, update_timeout_visit, &pv); +} + +static void +update_menu_visit (grub_gui_component_t component, + void *userdata) +{ + grub_gfxmenu_view_t view; + view = userdata; + if (component->ops->is_instance (component, "list")) + { + grub_gui_list_t list = (grub_gui_list_t) component; + list->ops->set_view_info (list, view->theme_path, view->model); + } +} + +/* Update any boot menu components with the current menu model and + theme path. */ +static void +update_menu_components (grub_gfxmenu_view_t view) +{ + grub_gui_iterate_recursively ((grub_gui_component_t) view->canvas, + update_menu_visit, view); +} + +static void +draw_message (grub_gfxmenu_view_t view) +{ + char *text = view->progress_message_text; + if (! text) + return; + + grub_font_t font = view->message_font; + grub_video_color_t color = grub_gui_map_color (view->message_color); + + /* Set the timeout bar's frame. */ + grub_video_rect_t f; + f.width = view->screen.width * 4 / 5; + f.height = 50; + f.x = view->screen.x + (view->screen.width - f.width) / 2; + f.y = view->screen.y + view->screen.height - 90 - 20 - f.height; + + /* Border. */ + grub_video_fill_rect (color, + f.x-1, f.y-1, f.width+2, f.height+2); + /* Fill. */ + grub_video_fill_rect (grub_gui_map_color (view->message_bg_color), + f.x, f.y, f.width, f.height); + + /* Center the text. */ + int text_width = grub_font_get_string_width (font, text); + int x = f.x + (f.width - text_width) / 2; + int y = (f.y + (f.height - grub_font_get_descent (font)) / 2 + + grub_font_get_ascent (font) / 2); + grub_video_draw_string (text, font, color, x, y); +} + + +void +grub_gfxmenu_view_draw (grub_gfxmenu_view_t view) +{ + grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY); + update_timeout (view); + update_menu_components (view); + + draw_background (view); + if (view->canvas) + view->canvas->ops->component.paint (view->canvas); + draw_title (view); + draw_message (view); +} + +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 int term_initialized; +static grub_term_t term_original; +static grub_gfxmenu_view_t term_view; + +static void +repaint_terminal (int x __attribute ((unused)), + int y __attribute ((unused)), + int width __attribute ((unused)), + int height __attribute ((unused))) +{ + if (! term_view) + return; + + grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY); + grub_gfxmenu_view_draw (term_view); + grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY); + + int termx = term_view->screen.x + + term_view->screen.width * (10 - 7) / 10 / 2; + int termy = term_view->screen.y + + term_view->screen.height * (10 - 7) / 10 / 2; + + grub_gfxmenu_box_t term_box = term_view->terminal_box; + if (term_box) + { + term_box->set_content_size (term_box, + term_target_width, term_target_height); + + 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 (grub_gfxmenu_view_t view) +{ + term_original = grub_term_get_current (); + + term_target_width = view->screen.width * 7 / 10; + term_target_height = view->screen.height * 7 / 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; + + /* Note: currently there is no API for changing the gfxterm font + on the fly, so whatever font the initially loaded theme specifies + will be permanent. */ + grub_gfxterm_init_window (term_target, 0, 0, + term_target_width, term_target_height, + view->terminal_font_name, 3); + if (grub_errno != GRUB_ERR_NONE) + return; + term_initialized = 1; + + /* XXX: store static pointer to the 'view' object so the repaint callback can access it. */ + term_view = view; + grub_gfxterm_set_repaint_callback (repaint_terminal); + grub_term_set_current (grub_gfxterm_get_term ()); +} + +static void destroy_terminal (void) +{ + term_view = 0; + if (term_initialized) + grub_gfxterm_destroy_window (); + grub_gfxterm_set_repaint_callback (0); + if (term_target) + grub_video_delete_render_target (term_target); + if (term_original) + grub_term_set_current (term_original); +} + + +static void +notify_booting (void *userdata, grub_menu_entry_t entry) +{ + grub_gfxmenu_view_t view = (grub_gfxmenu_view_t) userdata; + + char *s = grub_malloc (100 + grub_strlen (entry->title)); + if (!s) + return; + + grub_sprintf (s, "Booting '%s'", entry->title); + set_progress_message (view, s); + grub_free (s); + grub_gfxmenu_view_draw (view); + grub_video_swap_buffers (); +} + +static void +notify_fallback (void *userdata, grub_menu_entry_t entry) +{ + grub_gfxmenu_view_t view = (grub_gfxmenu_view_t) userdata; + + char *s = grub_malloc (100 + grub_strlen (entry->title)); + if (!s) + return; + + grub_sprintf (s, "Falling back to '%s'", entry->title); + set_progress_message (view, s); + grub_free (s); + grub_gfxmenu_view_draw (view); + grub_video_swap_buffers (); +} + +static void +notify_execution_failure (void *userdata __attribute__ ((unused))) +{ +} + + +static struct grub_menu_execute_callback execute_callback = +{ + .notify_booting = notify_booting, + .notify_fallback = notify_fallback, + .notify_failure = notify_execution_failure +}; + +int +grub_gfxmenu_view_execute_with_fallback (grub_gfxmenu_view_t view, + grub_menu_entry_t entry) +{ + grub_menu_execute_with_fallback (grub_gfxmenu_model_get_menu (view->model), + entry, &execute_callback, (void *) view); + + if (set_graphics_mode () != GRUB_ERR_NONE) + return 0; /* Failure. */ + + /* If we returned, there was a failure. */ + set_progress_message (view, + "Unable to automatically boot. " + "Press SPACE to continue."); + grub_gfxmenu_view_draw (view); + grub_video_swap_buffers (); + while (GRUB_TERM_ASCII_CHAR(grub_getkey ()) != ' ') + { + /* Wait for SPACE to be pressed. */ + } + + set_progress_message (view, 0); /* Clear the message. */ + + return 1; /* Ok. */ +} + +int +grub_gfxmenu_view_execute_entry (grub_gfxmenu_view_t view, + grub_menu_entry_t entry) +{ + /* 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 (entry); + if (grub_errno != GRUB_ERR_NONE) + grub_wait_after_message (); + + if (set_graphics_mode () != GRUB_ERR_NONE) + return 0; /* Failure. */ + + init_terminal (view); + return 1; /* Ok. */ +} + +void +grub_gfxmenu_view_run_terminal (grub_gfxmenu_view_t view __attribute__((unused))) +{ + grub_cmdline_run (1); +} === added file 'gfxmenu/widget-box.c' --- gfxmenu/widget-box.c 1970-01-01 00:00:00 +0000 +++ gfxmenu/widget-box.c 2008-08-15 16:49:18 +0000 @@ -0,0 +1,271 @@ +/* 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 int +get_height (struct grub_video_bitmap *bitmap) +{ + if (bitmap) + return grub_video_bitmap_get_height (bitmap); + else + return 0; +} + +static int +get_width (struct grub_video_bitmap *bitmap) +{ + if (bitmap) + return grub_video_bitmap_get_width (bitmap); + else + return 0; +} + +static void +blit (grub_gfxmenu_box_t self, int pixmap_index, int x, int y) +{ + struct grub_video_bitmap *bitmap; + bitmap = self->scaled_pixmaps[pixmap_index]; + if (! bitmap) + return; + grub_video_blit_bitmap (bitmap, GRUB_VIDEO_BLIT_BLEND, + x, y, 0, 0, + grub_video_bitmap_get_width (bitmap), + grub_video_bitmap_get_height (bitmap)); +} + +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; + + height_n = get_height (self->scaled_pixmaps[BOX_PIXMAP_N]); + height_s = get_height (self->scaled_pixmaps[BOX_PIXMAP_S]); + height_e = get_height (self->scaled_pixmaps[BOX_PIXMAP_E]); + height_w = get_height (self->scaled_pixmaps[BOX_PIXMAP_W]); + width_n = get_width (self->scaled_pixmaps[BOX_PIXMAP_N]); + width_s = get_width (self->scaled_pixmaps[BOX_PIXMAP_S]); + width_e = get_width (self->scaled_pixmaps[BOX_PIXMAP_E]); + width_w = get_width (self->scaled_pixmaps[BOX_PIXMAP_W]); + + /* Draw sides. */ + blit (self, BOX_PIXMAP_N, x + width_w, y); + blit (self, BOX_PIXMAP_S, x + width_w, y + height_n + self->content_height); + blit (self, BOX_PIXMAP_E, x + width_w + self->content_width, y + height_n); + blit (self, BOX_PIXMAP_W, x, y + height_n); + + /* Draw corners. */ + blit (self, BOX_PIXMAP_NW, x, y); + blit (self, BOX_PIXMAP_NE, x + width_w + self->content_width, y); + blit (self, BOX_PIXMAP_SE, + x + width_w + self->content_width, + y + height_n + self->content_height); + blit (self, BOX_PIXMAP_SW, x, y + height_n + self->content_height); + + /* Draw center. */ + blit (self, BOX_PIXMAP_CENTER, x + width_w, y + height_n); +} + +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->content_width = width; + self->content_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 get_width (self->raw_pixmaps[BOX_PIXMAP_W]); +} + +static int +get_top_pad (grub_gfxmenu_box_t self) +{ + return get_height (self->raw_pixmaps[BOX_PIXMAP_N]); +} + +static int +get_right_pad (grub_gfxmenu_box_t self) +{ + return get_width (self->raw_pixmaps[BOX_PIXMAP_E]); +} + +static int +get_bottom_pad (grub_gfxmenu_box_t self) +{ + return get_height (self->raw_pixmaps[BOX_PIXMAP_S]); +} + +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! */ +} + + +/* Create a new box. If PIXMAPS_PREFIX and PIXMAPS_SUFFIX are both non-null, + then an attempt is made to load the north, south, east, west, northwest, + northeast, southeast, southwest, and center pixmaps. + If either PIXMAPS_PREFIX or PIXMAPS_SUFFIX is 0, then no pixmaps are + loaded, and the box has zero-width borders and is drawn transparent. */ +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->content_width = 0; + box->content_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++) + { + if (pixmaps_prefix && pixmaps_suffix) + { + /* TODO XXX dynamically allocate PATH, ensure it's large enough */ + grub_sprintf (path, "%s%s%s", + pixmaps_prefix, box_pixmap_names[i], pixmaps_suffix); + grub_errno = GRUB_ERR_NONE; + grub_video_bitmap_load (&box->raw_pixmaps[i], path); + grub_errno = GRUB_ERR_NONE; /* Clear any potential error. */ + } + else + { + box->raw_pixmaps[i] = 0; + } + 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->get_right_pad = get_right_pad; + box->get_bottom_pad = get_bottom_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/gfxmenu_model.h' --- include/grub/gfxmenu_model.h 1970-01-01 00:00:00 +0000 +++ include/grub/gfxmenu_model.h 2008-07-26 23:37:04 +0000 @@ -0,0 +1,59 @@ +/* gfxmenu_model.h - gfxmenu model 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 . + */ + +#ifndef GRUB_GFXMENU_MODEL_HEADER +#define GRUB_GFXMENU_MODEL_HEADER 1 + +#include + +struct grub_gfxmenu_model; /* Forward declaration of opaque type. */ +typedef struct grub_gfxmenu_model *grub_gfxmenu_model_t; + + +grub_gfxmenu_model_t grub_gfxmenu_model_new (grub_menu_t menu); + +void grub_gfxmenu_model_destroy (grub_gfxmenu_model_t model); + +grub_menu_t grub_gfxmenu_model_get_menu (grub_gfxmenu_model_t model); + +void grub_gfxmenu_model_set_timeout (grub_gfxmenu_model_t model); + +void grub_gfxmenu_model_clear_timeout (grub_gfxmenu_model_t model); + +int grub_gfxmenu_model_get_timeout_ms (grub_gfxmenu_model_t model); + +int grub_gfxmenu_model_get_timeout_remaining_ms (grub_gfxmenu_model_t model); + +int grub_gfxmenu_model_timeout_expired (grub_gfxmenu_model_t model); + +int grub_gfxmenu_model_get_num_entries (grub_gfxmenu_model_t model); + +int grub_gfxmenu_model_get_selected_index (grub_gfxmenu_model_t model); + +void grub_gfxmenu_model_set_selected_index (grub_gfxmenu_model_t model, + int index); + +const char *grub_gfxmenu_model_get_entry_title (grub_gfxmenu_model_t model, + int index); + +grub_menu_entry_t grub_gfxmenu_model_get_entry (grub_gfxmenu_model_t model, + int index); + +#endif /* GRUB_GFXMENU_MODEL_HEADER */ + === added file 'include/grub/gfxmenu_view.h' --- include/grub/gfxmenu_view.h 1970-01-01 00:00:00 +0000 +++ include/grub/gfxmenu_view.h 2008-08-15 14:46:37 +0000 @@ -0,0 +1,91 @@ +/* gfxmenu_view.h - gfxmenu view 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 . + */ + +#ifndef GRUB_GFXMENU_VIEW_HEADER +#define GRUB_GFXMENU_VIEW_HEADER 1 + +#include +#include +#include +#include +#include + +struct grub_gfxmenu_view; /* Forward declaration of opaque type. */ +typedef struct grub_gfxmenu_view *grub_gfxmenu_view_t; + + +grub_gfxmenu_view_t grub_gfxmenu_view_new (const char *theme_path, + grub_gfxmenu_model_t model); + +void grub_gfxmenu_view_destroy (grub_gfxmenu_view_t view); + +/* Set properties on the view based on settings from the specified + theme file. */ +int grub_gfxmenu_view_load_theme (grub_gfxmenu_view_t view, + const char *theme_path); + +int +grub_gui_recreate_box (grub_gfxmenu_box_t *boxptr, + const char *pattern, const char *theme_dir); + +void grub_gfxmenu_view_draw (grub_gfxmenu_view_t view); + +int grub_gfxmenu_view_execute_with_fallback (grub_gfxmenu_view_t view, + grub_menu_entry_t entry); + +int grub_gfxmenu_view_execute_entry (grub_gfxmenu_view_t view, + grub_menu_entry_t entry); + +void grub_gfxmenu_view_run_terminal (grub_gfxmenu_view_t view); + + + +/* Implementation details -- this should not be used outside of the + view itself. */ + +#include +#include +#include +#include +#include + +/* Definition of the private representation of the view. */ +struct grub_gfxmenu_view +{ + grub_video_rect_t screen; + + grub_font_t title_font; + grub_font_t message_font; + char *terminal_font_name; + grub_gui_color_t title_color; + grub_gui_color_t message_color; + grub_gui_color_t message_bg_color; + struct grub_video_bitmap *desktop_image; + grub_gui_color_t desktop_color; + grub_gfxmenu_box_t terminal_box; + char *title_text; + char *progress_message_text; + char *theme_path; + + grub_gui_container_t canvas; + + grub_gfxmenu_model_t model; +}; + +#endif /* ! GRUB_GFXMENU_VIEW_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-08-15 04:57:08 +0000 @@ -0,0 +1,49 @@ +/* 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 content_width; + int content_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); + int (*get_right_pad) (grub_gfxmenu_box_t self); + int (*get_bottom_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 */ === added file 'include/grub/gui.h' --- include/grub/gui.h 1970-01-01 00:00:00 +0000 +++ include/grub/gui.h 2008-08-16 17:35:05 +0000 @@ -0,0 +1,165 @@ +/* gui.h - GUI components header file. */ +/* + * 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 + +#ifndef GRUB_GUI_H +#define GRUB_GUI_H 1 + +/* A representation of a color. Unlike grub_video_color_t, this + representation is independent of any video mode specifics. */ +typedef struct grub_gui_color +{ + grub_uint8_t red; + grub_uint8_t green; + grub_uint8_t blue; + grub_uint8_t alpha; +} grub_gui_color_t; + +typedef struct grub_gui_component *grub_gui_component_t; +typedef struct grub_gui_container *grub_gui_container_t; +typedef struct grub_gui_list *grub_gui_list_t; + +typedef void (*grub_gui_component_callback) (grub_gui_component_t component, + void *userdata); + +/* Component interface. */ + +struct grub_gui_component_ops +{ + void (*destroy) (void *self); + const char * (*get_id) (void *self); + int (*is_instance) (void *self, const char *type); + void (*paint) (void *self); + void (*set_parent) (void *self, grub_gui_container_t parent); + grub_gui_container_t (*get_parent) (void *self); + void (*set_bounds) (void *self, const grub_video_rect_t *bounds); + void (*get_bounds) (void *self, grub_video_rect_t *bounds); + void (*get_preferred_size) (void *self, int *width, int *height); + void (*set_property) (void *self, const char *name, const char *value); +}; + +struct grub_gui_container_ops +{ + struct grub_gui_component_ops component; + void (*add) (void *self, grub_gui_component_t comp); + void (*remove) (void *self, grub_gui_component_t comp); + void (*iterate_children) (void *self, + grub_gui_component_callback cb, void *userdata); +}; + +struct grub_gui_list_ops +{ + struct grub_gui_component_ops component_ops; + void (*set_view_info) (void *self, + const char *theme_path, + grub_gfxmenu_model_t menu); +}; + +struct grub_gui_component +{ + struct grub_gui_component_ops *ops; +}; + +struct grub_gui_container +{ + struct grub_gui_container_ops *ops; +}; + +struct grub_gui_list +{ + struct grub_gui_list_ops *ops; +}; + + +/* Interfaces to concrete component classes. */ + +grub_gui_container_t grub_gui_canvas_new (void); +grub_gui_container_t grub_gui_vbox_new (void); +grub_gui_container_t grub_gui_hbox_new (void); +grub_gui_component_t grub_gui_label_new (void); +grub_gui_component_t grub_gui_image_new (void); +grub_gui_component_t grub_gui_progress_bar_new (void); +grub_gui_component_t grub_gui_list_new (void); +grub_gui_component_t grub_gui_circular_progress_new (void); + +/* Manipulation functions. */ + +/* Visit all components with the specified ID. */ +void grub_gui_find_by_id (grub_gui_component_t root, + const char *id, + grub_gui_component_callback cb, + void *userdata); + +/* Visit all components. */ +void grub_gui_iterate_recursively (grub_gui_component_t root, + grub_gui_component_callback cb, + void *userdata); + +/* Helper functions. */ + +static __inline void +grub_gui_save_viewport (grub_video_rect_t *r) +{ + grub_video_get_viewport ((unsigned *) &r->x, + (unsigned *) &r->y, + (unsigned *) &r->width, + (unsigned *) &r->height); +} + +static __inline void +grub_gui_restore_viewport (const grub_video_rect_t *r) +{ + grub_video_set_viewport (r->x, r->y, r->width, r->height); +} + +/* Set a new viewport relative the the current one, saving the current + viewport in OLD so it can be later restored. */ +static __inline void +grub_gui_set_viewport (const grub_video_rect_t *r, grub_video_rect_t *old) +{ + grub_gui_save_viewport (old); + grub_video_set_viewport (old->x + r->x, + old->y + r->y, + r->width, + r->height); +} + +static __inline grub_gui_color_t +grub_gui_color_rgb (int r, int g, int b) +{ + grub_gui_color_t c; + c.red = r; + c.green = g; + c.blue = b; + c.alpha = 255; + return c; +} + +static __inline grub_video_color_t +grub_gui_map_color (grub_gui_color_t c) +{ + return grub_video_map_rgba (c.red, c.green, c.blue, c.alpha); +} + +#endif /* ! GRUB_GUI_H */ === added file 'include/grub/gui_string_util.h' --- include/grub/gui_string_util.h 1970-01-01 00:00:00 +0000 +++ include/grub/gui_string_util.h 2008-08-16 17:35:05 +0000 @@ -0,0 +1,39 @@ +/* gui_string_util.h - String utilities for the 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 . + */ + +#ifndef GRUB_GUI_STRING_UTIL_HEADER +#define GRUB_GUI_STRING_UTIL_HEADER 1 + +#include +#include + +char *grub_new_substring (const char *buf, + grub_size_t start, grub_size_t end); + +char *grub_resolve_relative_path (const char *base, const char *path); + +char *grub_get_dirname (const char *file_path); + +int grub_gui_get_named_color (const char *name, grub_gui_color_t *color); + +grub_gui_color_t grub_gui_parse_color (const char *s); + +int grub_gui_parse_2_tuple (const char *s, int *px, int *py); + +#endif /* GRUB_GUI_STRING_UTIL_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, === modified file 'include/grub/i386/types.h' --- include/grub/i386/types.h 2007-07-21 22:32:33 +0000 +++ include/grub/i386/types.h 2008-08-11 14:36:33 +0000 @@ -25,6 +25,51 @@ /* The size of long. */ #define GRUB_TARGET_SIZEOF_LONG 4 +/* The native word size in bits. */ +#define __WORDSIZE 32 + +/* These assume 8-bit `char's, 16-bit `short int's, + and 32-bit `int's and `long int's. */ + +/* Number of bits in a `char'. */ +#define CHAR_BIT 8 + +/* Minimum and maximum values a `signed char' can hold. */ +#define SCHAR_MIN (-128) +#define SCHAR_MAX 127 + +/* Maximum value an `unsigned char' can hold. (Minimum is 0.) */ +#define UCHAR_MAX 255 + +/* Minimum and maximum values a `char' can hold. */ +#ifdef __CHAR_UNSIGNED__ +# define CHAR_MIN 0 +# define CHAR_MAX UCHAR_MAX +#else +# define CHAR_MIN SCHAR_MIN +# define CHAR_MAX SCHAR_MAX +#endif + +/* Minimum and maximum values for signed short int. */ +#define SHRT_MIN (-32768) +#define SHRT_MAX 32767 + +/* Maximum value for unsigned short int. (Minimum is 0.) */ +#define USHRT_MAX 65535 + +/* Mininum and maximum values for signed int. */ +#define INT_MIN (-INT_MAX - 1) +#define INT_MAX 2147483647 +#define UINT_MAX 4294967295U + +/* Mininum and maximum values for signed long. */ +#if __WORDSIZE == 64 +# define LONG_MAX 9223372036854775807L +#else +# define LONG_MAX 2147483647L +#endif +#define LONG_MIN (-LONG_MAX - 1L) + /* i386 is little-endian. */ #undef GRUB_TARGET_WORDS_BIGENDIAN === added file 'include/grub/icon_manager.h' --- include/grub/icon_manager.h 1970-01-01 00:00:00 +0000 +++ include/grub/icon_manager.h 2008-08-14 16:32:41 +0000 @@ -0,0 +1,41 @@ +/* icon_manager.h - gfxmenu icon manager. */ +/* + * 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_ICON_MANAGER_HEADER +#define GRUB_ICON_MANAGER_HEADER 1 + +#include +#include + +/* Forward declaration of opaque structure handle type. */ +typedef struct grub_gfxmenu_icon_manager *grub_gfxmenu_icon_manager_t; + +grub_gfxmenu_icon_manager_t grub_gfxmenu_icon_manager_new (void); +void grub_gfxmenu_icon_manager_destroy (grub_gfxmenu_icon_manager_t mgr); +void grub_gfxmenu_icon_manager_clear_cache (grub_gfxmenu_icon_manager_t mgr); +void grub_gfxmenu_icon_manager_set_theme_path (grub_gfxmenu_icon_manager_t mgr, + const char *path); +void grub_gfxmenu_icon_manager_set_icon_size (grub_gfxmenu_icon_manager_t mgr, + int width, int height); +struct grub_video_bitmap * +grub_gfxmenu_icon_manager_get_icon (grub_gfxmenu_icon_manager_t mgr, + grub_menu_entry_t entry); + +#endif /* GRUB_ICON_MANAGER_HEADER */ + === added file 'include/grub/menu.h' --- include/grub/menu.h 1970-01-01 00:00:00 +0000 +++ include/grub/menu.h 2008-08-03 02:14:40 +0000 @@ -0,0 +1,63 @@ +/* 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 + +struct grub_menu_entry_class +{ + char *name; + struct grub_menu_entry_class *next; +}; + +/* The menu entry. */ +struct grub_menu_entry +{ + /* The title name. */ + const char *title; + + /* The classes associated with the menu entry: + used to choose an icon or other style attributes. + This is a dummy head node for the linked list, so for an entry E, + E.classes->next is the first class if it is not NULL. */ + struct grub_menu_entry_class *classes; + + /* 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/misc.h' --- include/grub/misc.h 2008-07-04 01:12:54 +0000 +++ include/grub/misc.h 2008-08-11 14:36:33 +0000 @@ -52,11 +52,14 @@ char *EXPORT_FUNC(grub_strstr) (const char *haystack, const char *needle); int EXPORT_FUNC(grub_iswordseparator) (int c); int EXPORT_FUNC(grub_isspace) (int c); +int EXPORT_FUNC(grub_iscntrl) (int c); int EXPORT_FUNC(grub_isprint) (int c); int EXPORT_FUNC(grub_isalpha) (int c); +int EXPORT_FUNC(grub_isalnum) (int c); int EXPORT_FUNC(grub_isgraph) (int c); int EXPORT_FUNC(grub_isdigit) (int c); int EXPORT_FUNC(grub_tolower) (int c); +long EXPORT_FUNC(grub_strtol) (const char *str, char **end, int base); unsigned long EXPORT_FUNC(grub_strtoul) (const char *str, char **end, int base); unsigned long long EXPORT_FUNC(grub_strtoull) (const char *str, char **end, int base); char *EXPORT_FUNC(grub_strdup) (const char *s); === modified file 'include/grub/normal.h' --- include/grub/normal.h 2008-03-26 12:01:02 +0000 +++ include/grub/normal.h 2008-08-03 04:00:22 +0000 @@ -25,6 +25,7 @@ #include #include #include +#include /* The maximum size of a command-line. */ #define GRUB_MAX_CMDLINE 1600 @@ -39,7 +40,7 @@ #define GRUB_COMMAND_FLAG_TITLE 0x4 /* Don't print the command on booting. */ #define GRUB_COMMAND_FLAG_NO_ECHO 0x8 -/* Don't print the command on booting. */ +/* Pass arguments to the command without parsing options. */ #define GRUB_COMMAND_FLAG_NO_ARG_PARSE 0x10 /* Not loaded yet. Used for auto-loading. */ #define GRUB_COMMAND_FLAG_NOT_LOADED 0x20 @@ -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,27 @@ /* To exit from the normal mode. */ extern grub_jmp_buf grub_exit_env; +extern struct grub_menu_viewer grub_normal_terminal_menu_viewer; + +typedef struct grub_menu_execute_callback +{ + void (*notify_booting) (void *userdata, grub_menu_entry_t entry); + void (*notify_fallback) (void *userdata, grub_menu_entry_t entry); + void (*notify_failure) (void *userdata); +} +*grub_menu_execute_callback_t; + 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_execute_with_fallback (grub_menu_t menu, + grub_menu_entry_t entry, + grub_menu_execute_callback_t callback, + void *callback_data); void grub_menu_entry_run (grub_menu_entry_t entry); +int grub_menu_get_default_entry_index (grub_menu_t menu); +int grub_menu_get_timeout (void); +void grub_menu_set_timeout (int timeout); +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); === added file 'include/grub/trig.h' --- include/grub/trig.h 1970-01-01 00:00:00 +0000 +++ include/grub/trig.h 2008-08-15 23:59:38 +0000 @@ -0,0 +1,44 @@ +/* trig.h - Trigonometric function support. */ +/* + * 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_TRIG_HEADER +#define GRUB_TRIG_HEADER 1 + +#define GRUB_TRIG_ANGLE_MAX 256 +#define GRUB_TRIG_ANGLE_MASK 255 +#define GRUB_TRIG_FRACTION_SCALE 16384 + +extern short grub_trig_sintab[]; +extern short grub_trig_costab[]; + +static __inline int +grub_sin (int x) +{ + x &= GRUB_TRIG_ANGLE_MASK; + return grub_trig_sintab[x]; +} + +static __inline int +grub_cos (int x) +{ + x &= GRUB_TRIG_ANGLE_MASK; + return grub_trig_costab[x]; +} + +#endif /* ! GRUB_TRIG_HEADER */ === 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 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-08-14 17:46:47 +0000 +++ kern/misc.c 2008-08-16 00:14:43 +0000 @@ -363,6 +363,12 @@ } int +grub_iscntrl (int c) +{ + return (c >= 0x00 && c <= 0x1F) || c == 0x7F; +} + +int grub_isprint (int c) { return (c >= ' ' && c <= '~'); @@ -375,6 +381,12 @@ } int +grub_isalnum (int c) +{ + return grub_isalpha (c) || grub_isdigit (c); +} + +int grub_isdigit (int c) { return (c >= '0' && c <= '9'); @@ -395,6 +407,41 @@ return c; } +long +grub_strtol (const char *str, char **end, int base) +{ + int negative = 0; + + while (*str && grub_isspace (*str)) + str++; + + if (*str == '-') + { + negative = 1; + str++; + } + + unsigned long long magnitude; + magnitude = grub_strtoull (str, end, base); + if (negative) + { + if (magnitude > -((long long) LONG_MIN)) + { + grub_error (GRUB_ERR_OUT_OF_RANGE, "negative overflow"); + return LONG_MIN; + } + return -((long long) magnitude); + } + else + { + if (magnitude > LONG_MAX) + { + grub_error (GRUB_ERR_OUT_OF_RANGE, "positive overflow"); + return LONG_MAX; + } + return (long) magnitude; + } +} unsigned long grub_strtoul (const char *str, char **end, int base) === added file 'lib/trig.c' --- lib/trig.c 1970-01-01 00:00:00 +0000 +++ lib/trig.c 2008-08-15 23:59:38 +0000 @@ -0,0 +1,83 @@ +/* trig.c - Trigonometric table definitions. */ +/* + * 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 + +/* These tables were generated with `gentrigtables.py'. */ + +short grub_trig_sintab[] = +{ + 0,402,804,1205,1606,2006,2404,2801,3196,3590, + 3981,4370,4756,5139,5520,5897,6270,6639,7005,7366, + 7723,8076,8423,8765,9102,9434,9760,10080,10394,10702, + 11003,11297,11585,11866,12140,12406,12665,12916,13160,13395, + 13623,13842,14053,14256,14449,14635,14811,14978,15137,15286, + 15426,15557,15679,15791,15893,15986,16069,16143,16207,16261, + 16305,16340,16364,16379,16384,16379,16364,16340,16305,16261, + 16207,16143,16069,15986,15893,15791,15679,15557,15426,15286, + 15137,14978,14811,14635,14449,14256,14053,13842,13623,13395, + 13160,12916,12665,12406,12140,11866,11585,11297,11003,10702, + 10394,10080,9760,9434,9102,8765,8423,8076,7723,7366, + 7005,6639,6270,5897,5520,5139,4756,4370,3981,3590, + 3196,2801,2404,2006,1606,1205,804,402,0,-402, + -804,-1205,-1606,-2006,-2404,-2801,-3196,-3590,-3981,-4370, + -4756,-5139,-5520,-5897,-6270,-6639,-7005,-7366,-7723,-8076, + -8423,-8765,-9102,-9434,-9760,-10080,-10394,-10702,-11003,-11297, + -11585,-11866,-12140,-12406,-12665,-12916,-13160,-13395,-13623,-13842, + -14053,-14256,-14449,-14635,-14811,-14978,-15137,-15286,-15426,-15557, + -15679,-15791,-15893,-15986,-16069,-16143,-16207,-16261,-16305,-16340, + -16364,-16379,-16384,-16379,-16364,-16340,-16305,-16261,-16207,-16143, + -16069,-15986,-15893,-15791,-15679,-15557,-15426,-15286,-15137,-14978, + -14811,-14635,-14449,-14256,-14053,-13842,-13623,-13395,-13160,-12916, + -12665,-12406,-12140,-11866,-11585,-11297,-11003,-10702,-10394,-10080, + -9760,-9434,-9102,-8765,-8423,-8076,-7723,-7366,-7005,-6639, + -6270,-5897,-5520,-5139,-4756,-4370,-3981,-3590,-3196,-2801, + -2404,-2006,-1606,-1205,-804,-402 +}; + +short grub_trig_costab[] = +{ + 16384,16379,16364,16340,16305,16261,16207,16143,16069,15986, + 15893,15791,15679,15557,15426,15286,15137,14978,14811,14635, + 14449,14256,14053,13842,13623,13395,13160,12916,12665,12406, + 12140,11866,11585,11297,11003,10702,10394,10080,9760,9434, + 9102,8765,8423,8076,7723,7366,7005,6639,6270,5897, + 5520,5139,4756,4370,3981,3590,3196,2801,2404,2006, + 1606,1205,804,402,0,-402,-804,-1205,-1606,-2006, + -2404,-2801,-3196,-3590,-3981,-4370,-4756,-5139,-5520,-5897, + -6270,-6639,-7005,-7366,-7723,-8076,-8423,-8765,-9102,-9434, + -9760,-10080,-10394,-10702,-11003,-11297,-11585,-11866,-12140,-12406, + -12665,-12916,-13160,-13395,-13623,-13842,-14053,-14256,-14449,-14635, + -14811,-14978,-15137,-15286,-15426,-15557,-15679,-15791,-15893,-15986, + -16069,-16143,-16207,-16261,-16305,-16340,-16364,-16379,-16384,-16379, + -16364,-16340,-16305,-16261,-16207,-16143,-16069,-15986,-15893,-15791, + -15679,-15557,-15426,-15286,-15137,-14978,-14811,-14635,-14449,-14256, + -14053,-13842,-13623,-13395,-13160,-12916,-12665,-12406,-12140,-11866, + -11585,-11297,-11003,-10702,-10394,-10080,-9760,-9434,-9102,-8765, + -8423,-8076,-7723,-7366,-7005,-6639,-6270,-5897,-5520,-5139, + -4756,-4370,-3981,-3590,-3196,-2801,-2404,-2006,-1606,-1205, + -804,-402,0,402,804,1205,1606,2006,2404,2801, + 3196,3590,3981,4370,4756,5139,5520,5897,6270,6639, + 7005,7366,7723,8076,8423,8765,9102,9434,9760,10080, + 10394,10702,11003,11297,11585,11866,12140,12406,12665,12916, + 13160,13395,13623,13842,14053,14256,14449,14635,14811,14978, + 15137,15286,15426,15557,15679,15791,15893,15986,16069,16143, + 16207,16261,16305,16340,16364,16379 +}; + === modified file 'normal/main.c' --- normal/main.c 2008-07-29 14:07:47 +0000 +++ normal/main.c 2008-08-03 04:00:22 +0000 @@ -28,6 +28,7 @@ #include #include #include +#include grub_jmp_buf grub_exit_env; @@ -147,14 +148,132 @@ grub_env_unset_data_slot ("menu"); } +static void +free_menu_entry_classes (struct grub_menu_entry_class *head) +{ + /* Free all the classes. */ + while (head) + { + struct grub_menu_entry_class *next; + + grub_free (head->name); + next = head->next; + grub_free (head); + head = next; + } +} + +/* The tag that can be added to a menu entry's title to specify a class + for the UI to use in selecting an icon or other visual attributes. */ +static const char entry_class_attr_tag[] = "|class="; + +#define ENTRY_ATTR_SEPARATOR_CHAR ',' + +/* Parse and strip a possible "class" attribute in the title. + This code is not designed to support other attributes than "class" + in the title since, we are planning to use a better method of + specifying this information in the future. The parameter TITLE is + modified by storing a '\0' at the appropriate location to strip the + class information, if it exists. The class list is stored into *HEAD. */ +static struct grub_menu_entry_class * +get_classes_from_entry_title (char *title) +{ + struct grub_menu_entry_class *head; + char *attr_start; + + head = grub_malloc (sizeof (struct grub_menu_entry_class)); + if (! head) + return 0; + head->name = 0; + head->next = 0; + + attr_start = grub_strstr (title, entry_class_attr_tag); + if (attr_start) + { + struct grub_menu_entry_class *tail; + const char *p; + + /* Trim the properties off of the title. */ + *attr_start = '\0'; + + /* Move the pointer to the beginning of the first class name. */ + attr_start += grub_strlen (entry_class_attr_tag); + + tail = head; + p = attr_start; + while (p && *p) + { + const char *q; + const char *end; + const char *next_start; + + /* Skip any leading whitespace. */ + while (*p && grub_isspace (*p)) + p++; + + /* Find the comma terminating this one ... */ + q = grub_strchr (p, ENTRY_ATTR_SEPARATOR_CHAR); + /* ... or if it's the last one, find the '\0' terminator. */ + if (q) + { + end = q - 1; + next_start = q + 1; + } + else + { + /* For the last class, extend it to the end. */ + end = p + grub_strlen (p); + next_start = 0; + } + + /* Trim any trailing whitespace. */ + while (end > p && grub_isspace (*end)) + end--; + + grub_size_t len = end - p + 1; + /* Copy the class name into a new string. */ + char *class_name = grub_malloc (len + 1); + if (! class_name) + { + free_menu_entry_classes (head); + return 0; + } + grub_memcpy (class_name, p, len); + class_name[len] = '\0'; + + /* Create a new class and add it at the tail of the list. */ + struct grub_menu_entry_class *new_class; + new_class = grub_malloc (sizeof (struct grub_menu_entry_class)); + if (! new_class) + { + grub_free (class_name); + free_menu_entry_classes (head); + return 0; + } + /* Fill in the new class node. */ + new_class->name = class_name; + new_class->next = 0; + /* Link the tail to it, and make it the new tail. */ + tail->next = new_class; + tail = new_class; + + /* Advance the character pointer. */ + p = next_start; + } + } + + return head; +} + grub_err_t grub_normal_menu_addentry (const char *title, struct grub_script *script, const char *sourcecode) { - const char *menutitle; + char *menutitle; const char *menusourcecode; grub_menu_t menu; grub_menu_entry_t *last; + struct grub_menu_entry_class *classes; menu = grub_env_get_data_slot("menu"); if (! menu) @@ -173,6 +292,14 @@ return grub_errno; } + classes = get_classes_from_entry_title (menutitle); + if (! classes) + { + grub_free ((void *) menutitle); + grub_free ((void *) menusourcecode); + return grub_errno; + } + /* Add the menu entry at the end of the list. */ while (*last) last = &(*last)->next; @@ -180,6 +307,7 @@ *last = grub_malloc (sizeof (**last)); if (! *last) { + free_menu_entry_classes (classes); grub_free ((void *) menutitle); grub_free ((void *) menusourcecode); return grub_errno; @@ -187,6 +315,7 @@ (*last)->commands = script; (*last)->title = menutitle; + (*last)->classes = classes; (*last)->next = 0; (*last)->sourcecode = menusourcecode; @@ -476,7 +605,7 @@ if (menu && menu->size) { - grub_menu_run (menu, nested); + grub_menu_viewer_show_menu (menu, nested); if (nested) free_menu (menu); } @@ -519,6 +648,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 +666,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-08-17 09:30:25 +0000 +++ normal/menu.c 2008-08-18 14:21:01 +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; @@ -241,8 +242,8 @@ /* Return the current timeout. If the variable "timeout" is not set or invalid, return -1. */ -static int -get_timeout (void) +int +grub_menu_get_timeout (void) { char *val; int timeout; @@ -269,8 +270,8 @@ } /* Set current timeout in the variable "timeout". */ -static void -set_timeout (int timeout) +void +grub_menu_set_timeout (int timeout) { /* Ignore TIMEOUT if it is zero, because it will be unset really soon. */ if (timeout > 0) @@ -308,6 +309,57 @@ return entry; } +/* Get the default menu entry index. */ +int +grub_menu_get_default_entry_index (grub_menu_t menu) +{ + int i = get_entry_number ("default"); + + /* If DEFAULT_ENTRY is not within the menu entries, fall back to + the first entry. */ + if (i < 0 || i >= menu->size) + i = 0; + + return i; +} + +/* Get the first entry number from the variable NAME, which is a + space-separated list of nonnegative integers. The entry number which + is returned is stripped from the value of NAME. */ +static int +get_and_remove_first_entry_number (const char *name) +{ + char *val; + char *tail; + int entry; + + val = grub_env_get (name); + if (! val) + return -1; + + grub_error_push (); + + entry = (int) grub_strtoul (val, &tail, 0); + + if (grub_errno == GRUB_ERR_NONE) + { + /* Skip whitespace to find the next digit. */ + while (*tail && grub_isspace (*tail)) + tail++; + grub_env_set (name, tail); + } + else + { + grub_env_unset (name); + grub_errno = GRUB_ERR_NONE; + entry = -1; + } + + grub_error_pop (); + + return entry; +} + static void print_timeout (int timeout, int offset, int second_stage) { @@ -332,15 +384,10 @@ first = 0; - default_entry = get_entry_number ("default"); - - /* If DEFAULT_ENTRY is not within the menu entries, fall back to - the first entry. */ - if (default_entry < 0 || default_entry >= menu->size) - default_entry = 0; + default_entry = grub_menu_get_default_entry_index (menu); /* If timeout is 0, drawing is pointless (and ugly). */ - if (get_timeout () == 0) + if (grub_menu_get_timeout () == 0) return default_entry; offset = default_entry; @@ -359,15 +406,15 @@ print_entries (menu, first, offset); grub_refresh (); - timeout = get_timeout (); + timeout = grub_menu_get_timeout (); if (timeout > 0) print_timeout (timeout, offset, 0); - while (1) + while (!grub_menu_viewer_should_return ()) { int c; - timeout = get_timeout (); + timeout = grub_menu_get_timeout (); if (timeout > 0) { @@ -377,7 +424,7 @@ if (current_time - saved_time >= 1000) { timeout--; - set_timeout (timeout); + grub_menu_set_timeout (timeout); saved_time = current_time; print_timeout (timeout, offset, 1); } @@ -484,6 +531,10 @@ } goto refresh; + case 't': + grub_env_set ("menuviewer", "protomenu"); + goto refresh; + default: break; } @@ -492,13 +543,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); @@ -507,15 +559,80 @@ grub_command_execute ("boot", 0); } +/* Execute ENTRY from the menu MENU, falling back to entries specified + in the environment variable "fallback" if it fails. CALLBACK is a + pointer to a struct of function pointers which are used to allow the + caller provide feedback to the user. */ void -grub_menu_run (grub_menu_t menu, int nested) +grub_menu_execute_with_fallback (grub_menu_t menu, + grub_menu_entry_t entry, + grub_menu_execute_callback_t callback, + void *callback_data) +{ + int fallback_entry; + + callback->notify_booting (callback_data, entry); + + grub_menu_execute_entry (entry); + + /* Deal with fallback entries. */ + while ((fallback_entry = get_and_remove_first_entry_number ("fallback")) + >= 0) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + + entry = get_entry (menu, fallback_entry); + callback->notify_fallback (callback_data, entry); + grub_menu_execute_entry (entry); + } + + if (grub_errno != GRUB_ERR_NONE) + callback->notify_failure (callback_data); +} + +static void +notify_booting (void *userdata __attribute__((unused)), + grub_menu_entry_t entry) +{ + grub_printf (" Booting \'%s\'\n\n", entry->title); +} + +static void +notify_fallback (void *userdata __attribute__((unused)), + grub_menu_entry_t entry) +{ + grub_printf ("\n Falling back to \'%s\'\n\n", entry->title); + grub_millisleep (2000); +} + +static void +notify_execution_failure (void *userdata __attribute__((unused))) +{ + if (grub_errno != GRUB_ERR_NONE) + { + grub_print_error (); + grub_errno = GRUB_ERR_NONE; + + grub_wait_after_message (); + } +} + +static struct grub_menu_execute_callback execution_callback = +{ + .notify_booting = notify_booting, + .notify_fallback = notify_fallback, + .notify_failure = notify_execution_failure +}; + +static grub_err_t +show_menu (grub_menu_t menu, int nested) { while (1) { int boot_entry; grub_menu_entry_t e; - int fallback_entry; - + boot_entry = run_menu (menu, nested); if (boot_entry < 0) break; @@ -523,34 +640,18 @@ e = get_entry (menu, boot_entry); if (! e) continue; /* Menu is empty. */ - + grub_cls (); grub_setcursor (1); - grub_printf (" Booting \'%s\'\n\n", e->title); - - run_menu_entry (e); - - /* Deal with a fallback entry. */ - /* FIXME: Multiple fallback entries like GRUB Legacy. */ - fallback_entry = get_entry_number ("fallback"); - if (fallback_entry >= 0) - { - grub_print_error (); - grub_errno = GRUB_ERR_NONE; - - 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); - } - - if (grub_errno != GRUB_ERR_NONE) - { - grub_print_error (); - grub_errno = GRUB_ERR_NONE; - - grub_wait_after_message (); - } + grub_menu_execute_with_fallback (menu, e, &execution_callback, 0); } + + 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 file 'util/fonttool/makepf2.sh' --- util/fonttool/makepf2.sh 1970-01-01 00:00:00 +0000 +++ util/fonttool/makepf2.sh 2008-08-07 23:50:20 +0000 @@ -0,0 +1,17 @@ +#!/bin/sh + +# This script will generate GRUB 2 PFF2 font files out of BDF. +# You need the GRUB font utilities "Convert" program for this. + +DIR=$( dirname $0 ) + +for font in "$@" +do + out=`basename "${font}" | cut -d'.' -f1` + out="${out}.pf2" + echo "Converting ${font} -> ${out} ..." + java -cp "${DIR}/build/fonttool.jar" \ + org.gnu.grub.fonttool.Converter \ + --in="${font}" \ + --out="${out}" +done === 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-08-03 01:30:02 +0000 @@ -0,0 +1,101 @@ +/* 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) +{ + *dst = 0; + + /* 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); + *dst = 0; + 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-08-01 03:06:55 +0000 +++ video/readers/jpeg.c 2008-08-03 03:57:46 +0000 @@ -1,20 +1,20 @@ /* - * 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 . - */ +* 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 @@ -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-08-01 03:06:55 +0000 +++ video/readers/png.c 2008-08-03 04:00:22 +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 2008-08-01 03:06:55 +0000 +++ video/readers/tga.c 2008-08-03 03:57:46 +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 IyBCYXphYXIgcmV2aXNpb24gYnVuZGxlIHY0CiMKQlpoOTFBWSZTWRugVaMC6iR/gH////////// //////////9i6H7wV7IMBQD2pANsSokJUtH3vun1j66izXvsRAfPsujQCnofRwfbQF93er33Onvm 72+8U+6O756uhVWY0AkAFGmjR8tje8dvC71wADsNdVWRu47QL73lAAAAAOee7x1bvPvaGd1NwoC1 gKAAEu18dPeLzzXfXz29vHntwV8npus9tPPvvOtvVy9eeTzvtPqAAt9fI6d2+m+b5r67sgAAABQA vHuOp774A+gFNUHd73nhADIPS7DR0AAkANBJQNNd2DT73uL5r7tVpwa12bsRvQOkLvp7vdfei7Lo bje4176zXTLuzefW9BXtjrzb0vaaXr6cx7VfY3vbnbro+7t3Xfe7vX3t3e69B27vr0727vnvZu1S +3cvc7u+70N3esdx6ve9teVDi762Z9zTJ9V333b13vdK1CtNQvt3Fu9fTvnavqtO87PePqHSmmNb sHpvZ7m3N33z2u8b1o8vs+Ej7hnz5Pe97256uA0OHWVXPe8+n07kgKoN6Z111RAQda330+u++9b2 7sttFsBpPgPR5UDp9PXCvS8d70xkOnvYKUFUevQchtgV8AB6qj0NW+z3dgDoW+2T1rVHjoVLbVe2 6vB4Im+nfN3zu3PTfexd66u5wO9dcj7593rpXppffee9nAmgyWdu9vo9tY77buzwE9tnXtx2++++ I9tFAPrvgR4HbiPZp8De+DofRo3Zte93tb3d9nPa7um22nOZzY3XuG7aZa3jnXu93l1nnvcAFA+D 75HwgCnfHXTvYHfd9nvXc1SKinSgDuaqXuwPeFmBb7c7Zexo+2B2MLr7voAAYfPeb7NAPje6154H ez0NBztQusLW82PTResp2w0dd8wA69Bw772loA8vnnaHbUXsDvYL2ByBR0006N7BdgAD0+No+gAa 3m27oTZbds6ddCujoO7Docgo8ffd2wegAd1w9fQA+x32XD1Qtjuw2w7rXXXDd3NG2AAAU+MHoKHt mfbvhoNG03rfYZEh6aCh6AB0Dy8fIAN77daAGvOZyHro0HRSQDoBoBvpwADu3fbx1kzY0aKLYbY0 PWXl92fEGgA30cAA9s8VWLuwobsB0bYkddKA6lQ8FdPtjj1my2fbvay7bPOr7s8HqxZSfI7vdttv t94LzLsM+w972+roAAAaAAAEQ77PeaqskmItapalJtqLeurqdWUbtu1m7Qt06HbYgTW0bXc5ybbY JUsuu6LCy9uVe1eXDXcDdjbal7b22td33B497UVdvsuXJtT2rubu7sNjZ21bm4bm3NbaUO1xrmpT kzrLmRzu3docVjeT69voAApQC2BxPfXSXe7drAvcYWvdZiAar3Z2vOaQ23GDlcmcFPe7fQAoKAA9 K767lAo+zI7wTXru5y4BcuBgTdt1VrwB9FUACmT2PTgejj2HkNx9N5g3feeoAAA6p5F2WlYCdLUg SCvoGdwAAPoAPeofc+8sPoN8z7q6+zpzyGj0poLfVW3xzJdMdn1uN27sN72XLbnb0CFdteJ63efP gAAAAA+gU+31Xx3azDQ33267cafZ942+vvtLTs333eRbNbojvZWuU2BhKcR007o74vLZIve6qvef X1fAAASX2bWFsV9Puy9z6UAoAA7svYADdjmiur7uld2+8cj3t0yna+7o7GWzA8unC7UddQOQKkkl XQZ2EkQACQooB1uA2tjRQlbNQc2KbWGg4UTurFUlGNlSr56l7j1PXbuzZVAKoXW06cgklIhQXNbK GnOiEzAl2Zceurtvd0cy7ijJCpoYu9u94bcwmxa2gN2oZxoq4sR2cNAdKUvezkMmnu3VGzu0BoCm rsEuazHboNAKCgDRy2G6u19mnmwy9GRK6ZoGg2wLbNtHIwnSrMK7u25112LHWx1rduy5bgKGrLDV tqdYDobp2MFJQNhw+9wB01e51O1NzY2tguqu7qohFRABI++99rPexS93dd533u7z3unvL7dvvr3v vdx98qD5rVoUMJGMOy2rrnG0baw3Ddd3txJXob0Hd683bp0otN3G1pCbZX3t0282O6OTkSbNmPr2 549Xru3RkCj3TKoN3rk3gLd2wkBnt247u02UFnONttlsyO51OlsttmkRmKWYppQZN3dOws4KuKCe m9jJCnga7kgZrT33fQqgAvZ8840+Xb733sxXQAVUGsEoQABCEBAIBCYEyNJpT9NTaEaFPRqemozU 0yGQHojI0bUASmgIRBEETCTAlT8Qp7IJNqGnqDeqNNAaA0HqAD1AAANASCRCEBEaBMgm0CaBDTCm aGknptNU8ifompo9T2qek09NQ2U2UfqTTyIEKREEJoCaACGQE0aGhPJPU0waTE1T1NmUxBlP0aRi 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