From: Endres Puschner <code@e7p.de>
To: grub-devel@gnu.org
Subject: Horizontal boot menu
Date: Sun, 10 Apr 2011 21:09:34 +0200 [thread overview]
Message-ID: <4DA2006E.1060209@e7p.de> (raw)
[-- Attachment #1: Type: text/plain, Size: 1447 bytes --]
Hi, grub-developers,
I implemented a complete horizontal extension to the boot menu and some
other little features and fixes for extending the gfxmenu boot menu.
I would just describe my added theme parameters for the boot_menu control:
- item_width: Optionally specifying a width for each item in
horizontal lists, but could also be implemented to work with vertical lists.
- item_icon_padding: Padding between icon and box or between icon and
other item if there is no item_box.
- horizontal: boolean, defining this boot_list as horizontal.
- item_pixmap_style: like selected_item_pixmap_style but for non
selected entries.
- item_pixmap_icon_only: boolean, selected and unselected pixmaps are
only shown around the icons and not around the whole item.
The scrollbars must be manually turned around to be in horizontal mode,
I have uploaded a demo theme as an example:
http://data.e7p.de/grub-horizontal-theme.png
http://data.e7p.de/grub-horizontal-theme.zip
I fixed a bug in the icon-section. The first group in any menuentry was
skipped while looking for icons before, so only icons for the second
class would be searched.
The normal menu is also modified as I wanted to allow to use right/left
keys while navigating through the menu while it is horizontal. At
menu_init, a new environment variable (orientation) can be set by the
used menu module to horizontal for obvious reasons.
Best regards,
Endres
[-- Attachment #2: grub_horizontal1.diff --]
[-- Type: text/x-patch, Size: 20000 bytes --]
=== modified file 'grub-core/gfxmenu/gui_list.c'
--- grub-core/gfxmenu/gui_list.c 2010-10-16 20:16:52 +0000
+++ grub-core/gfxmenu/gui_list.c 2011-04-10 18:20:42 +0000
@@ -23,6 +23,7 @@
#include <grub/gui_string_util.h>
#include <grub/gfxmenu_view.h>
#include <grub/gfxwidgets.h>
+#include <grub/env.h>
struct grub_gui_list_impl
{
@@ -35,9 +36,11 @@
int icon_width;
int icon_height;
+ int item_width;
int item_height;
int item_padding;
int item_icon_space;
+ int item_icon_padding;
int item_spacing;
grub_font_t item_font;
grub_font_t selected_item_font;
@@ -53,14 +56,19 @@
grub_gfxmenu_box_t scrollbar_thumb;
int scrollbar_width;
+ int horizontal;
+
int first_shown_index;
int need_to_recreate_boxes;
char *theme_dir;
char *menu_box_pattern;
char *selected_item_box_pattern;
+ char *item_box_pattern;
grub_gfxmenu_box_t menu_box;
grub_gfxmenu_box_t selected_item_box;
+ grub_gfxmenu_box_t item_box;
+ int box_icon_only;
grub_gfxmenu_icon_manager_t icon_manager;
@@ -91,15 +99,27 @@
get_num_shown_items (list_impl_t self)
{
int boxpad = self->item_padding;
- int item_vspace = self->item_spacing;
+ int item_space = self->item_spacing;
int item_height = self->item_height;
+ int item_width = self->item_width;
grub_gfxmenu_box_t box = self->menu_box;
int box_top_pad = box->get_top_pad (box);
int box_bottom_pad = box->get_bottom_pad (box);
-
- return (self->bounds.height + item_vspace - 2 * boxpad
- - box_top_pad - box_bottom_pad) / (item_height + item_vspace);
+ int box_left_pad = box->get_left_pad (box);
+ int box_right_pad = box->get_right_pad (box);
+
+ grub_gfxmenu_box_t itembox = self->item_box;
+
+ if(self->horizontal)
+ return (self->bounds.width + item_space - 2 * boxpad
+ - box_left_pad - box_right_pad) / (grub_max(item_width,
+ self->icon_width + 2 * self->item_icon_padding) + item_space
+ + itembox->get_left_pad(itembox) + itembox->get_right_pad(itembox));
+ else
+ return (self->bounds.height + item_space - 2 * boxpad
+ - box_top_pad - box_bottom_pad) / (item_height + item_space
+ + itembox->get_top_pad(itembox) + itembox->get_bottom_pad(itembox));
}
static int
@@ -115,6 +135,10 @@
self->selected_item_box_pattern,
self->theme_dir);
+ grub_gui_recreate_box (&self->item_box,
+ self->item_box_pattern,
+ self->theme_dir);
+
self->need_to_recreate_boxes = 0;
}
@@ -183,29 +207,48 @@
static void
draw_scrollbar (list_impl_t self,
int value, int extent, int min, int max,
- int rightx, int topy, int height)
+ int x1, int y1, int length)
{
+ /* x1 and y1 have now their special meanings:
+ * if vertical: x1 = right, y1 = top
+ * if horizontal: x1 = left, y1 = bottom */
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);
+ if(self->horizontal == 0) {
+ int tracktop = y1 + frame->get_top_pad (frame);
+ int tracklen = length - 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,
+ x1 - (self->scrollbar_width + frame_horizontal_pad),
+ y1);
+ thumb->draw (thumb,
+ x1 - (self->scrollbar_width - frame->get_right_pad (frame)),
+ thumby);
+ } else {
+ int trackleft = x1 + frame->get_left_pad (frame);
+ int tracklen = length - frame_horizontal_pad;
+ frame->set_content_size (frame, tracklen, self->scrollbar_width);
+ int thumbx = trackleft + tracklen * (value - min) / (max - min);
+ int thumbwidth = tracklen * extent / (max - min) + 1;
+ thumb->set_content_size (thumb,
+ thumbwidth - (thumb->get_left_pad (thumb)
+ + thumb->get_right_pad (thumb)),
+ self->scrollbar_width - frame_vertical_pad);
+ frame->draw (frame, x1,
+ y1 - (self->scrollbar_width + frame_vertical_pad));
+ thumb->draw (thumb, thumbx,
+ y1 - (self->scrollbar_width - frame->get_bottom_pad (frame)));
+ }
}
/* Draw the list of items. */
@@ -217,24 +260,44 @@
int boxpad = self->item_padding;
int icon_text_space = self->item_icon_space;
- int item_vspace = self->item_spacing;
+ int item_space = self->item_spacing;
+
+ int horizontal = self->horizontal;
int ascent = grub_font_get_ascent (self->item_font);
int descent = grub_font_get_descent (self->item_font);
+ int icon_width = self->icon_width + 2 * self->item_icon_padding;
+ int icon_height = self->icon_height + 2 * self->item_icon_padding;
+ int item_width = icon_width; // TODO: for both horizontal and vertical
int item_height = self->item_height;
-
+
make_selected_item_visible (self);
grub_gfxmenu_box_t selbox = self->selected_item_box;
+ grub_gfxmenu_box_t itembox = self->item_box;
int sel_leftpad = selbox->get_left_pad (selbox);
int sel_toppad = selbox->get_top_pad (selbox);
- int item_top = sel_toppad;
+ int item_leftpad = selbox->get_left_pad (itembox);
+ int item_toppad = selbox->get_top_pad (itembox);
+ int max_leftpad = grub_max(sel_leftpad, item_leftpad);
+ int max_toppad = grub_max(sel_toppad, item_toppad);
+
+ int item_left = max_leftpad;
+ int item_top = max_toppad;
+
int menu_index;
int visible_index;
struct grub_video_rect oviewport;
grub_video_get_viewport (&oviewport.x, &oviewport.y,
&oviewport.width, &oviewport.height);
+ if (horizontal) {
+ int new_width = grub_min(num_shown_items, self->view->menu->size)
+ * (grub_max(icon_width,item_width) + 2 * item_leftpad + item_space)
+ - item_space - 1 + 2 * grub_abs(sel_leftpad-item_leftpad) + 2 * boxpad;
+ oviewport.x += grub_max(0, self->bounds.width - new_width) / 2 - grub_abs(sel_leftpad-item_leftpad) / 2;
+ oviewport.width = new_width;
+ }
grub_video_set_viewport (oviewport.x + boxpad,
oviewport.y + boxpad,
oviewport.width - 2 * boxpad,
@@ -246,20 +309,45 @@
{
int is_selected = (menu_index == self->view->selected);
- if (is_selected)
- {
- selbox->set_content_size (selbox, oviewport.width - 2 * boxpad - 2,
- item_height - 1);
- selbox->draw (selbox, 0,
- item_top - sel_toppad);
- }
+ if (is_selected) {
+ if (horizontal == 0)
+ selbox->set_content_size (selbox,
+ (self->box_icon_only?icon_width:
+ (int)(oviewport.width - 2 * boxpad - 2)),
+ item_height - 1);
+ else
+ selbox->set_content_size (selbox, item_width - 1,
+ (self->box_icon_only?icon_height:
+ (int)(oviewport.height - 2 * boxpad - 2)));
+ selbox->draw (selbox, item_left - sel_leftpad, item_top - sel_toppad);
+ } else if (itembox) {
+ if (horizontal == 0)
+ itembox->set_content_size (itembox,
+ (self->box_icon_only?icon_width:
+ (int)(oviewport.width - 2 * boxpad - 2)),
+ item_height - 1);
+ else
+ itembox->set_content_size (itembox, item_width - 1,
+ (self->box_icon_only?icon_height:
+ (int)(oviewport.height - 2 * boxpad - 2)));
+ itembox->draw (itembox, item_left - item_leftpad, item_top - item_toppad);
+ }
struct grub_video_bitmap *icon;
- if ((icon = get_item_icon (self, menu_index)) != 0)
- grub_video_blit_bitmap (icon, GRUB_VIDEO_BLIT_BLEND,
- sel_leftpad,
- item_top + (item_height - self->icon_height) / 2,
- 0, 0, self->icon_width, self->icon_height);
+ if ((icon = get_item_icon (self, menu_index)) != 0) {
+ if (horizontal == 0)
+ grub_video_blit_bitmap (icon, GRUB_VIDEO_BLIT_BLEND,
+ item_left + self->item_icon_padding,
+ item_top + self->item_icon_padding +
+ (item_height - self->icon_height) / 2,
+ 0, 0, self->icon_width, self->icon_height);
+ else
+ grub_video_blit_bitmap (icon, GRUB_VIDEO_BLIT_BLEND,
+ item_left + self->item_icon_padding +
+ (item_width - icon_width) / 2,
+ item_top + self->item_icon_padding,
+ 0, 0, self->icon_width, self->icon_height);
+ }
const char *item_title =
grub_menu_get_entry (self->view->menu, menu_index)->title;
@@ -271,15 +359,24 @@
((is_selected && self->selected_item_color_set)
? self->selected_item_color
: self->item_color);
- grub_font_draw_string (item_title,
- font,
- grub_gui_map_color (text_color),
- sel_leftpad + self->icon_width + icon_text_space,
- (item_top + (item_height - (ascent + descent))
- / 2 + ascent));
-
- item_top += item_height + item_vspace;
+ if (horizontal == 0) {
+ grub_font_draw_string (item_title, font,
+ grub_gui_map_color (text_color),
+ icon_width + icon_text_space,
+ item_top + (item_height - (ascent + descent)) / 2 + ascent);
+
+ item_top += item_height + item_space;
+ } else {
+ grub_font_draw_string (item_title, font,
+ grub_gui_map_color (text_color),
+ (item_left + (item_width -
+ grub_font_get_string_width(font, item_title)) / 2),
+ item_top + item_toppad + icon_height + icon_text_space + ascent);
+
+ item_left += item_width + item_space + 2 * item_leftpad;
+ }
}
+
grub_video_set_viewport (oviewport.x,
oviewport.y,
oviewport.width,
@@ -332,10 +429,13 @@
draw_scrollbar (self,
self->first_shown_index, num_shown_items,
0, self->view->menu->size,
- self->bounds.width - box_right_pad
- + self->scrollbar_width,
- box_top_pad,
- self->bounds.height - box_top_pad - box_bottom_pad);
+ (self->horizontal?box_left_pad:
+ (int)(self->bounds.width - box_right_pad + self->scrollbar_width)),
+ (!self->horizontal?box_top_pad:
+ (int)(self->bounds.height - box_bottom_pad - self->scrollbar_width)),
+ (self->horizontal?
+ (self->bounds.width - box_left_pad - box_right_pad):
+ (self->bounds.height - box_top_pad - box_bottom_pad)));
}
grub_gui_restore_viewport (&vpsave);
@@ -377,7 +477,7 @@
if (check_boxes (self))
{
int boxpad = self->item_padding;
- int item_vspace = self->item_spacing;
+ int item_space = self->item_spacing;
int item_height = self->item_height;
int num_items = 3;
@@ -386,24 +486,48 @@
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);
- unsigned width_s;
+
+ unsigned sel_size;
grub_gfxmenu_box_t selbox = self->selected_item_box;
int sel_toppad = selbox->get_top_pad (selbox);
-
- *width = grub_font_get_string_width (self->item_font, "Typical OS");
- width_s = grub_font_get_string_width (self->selected_item_font,
- "Typical OS");
- if (*width < width_s)
- *width = width_s;
-
- *width += 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 + sel_toppad);
+ int sel_bottompad = selbox->get_bottom_pad (selbox);
+
+ grub_gfxmenu_box_t itembox = self->item_box;
+ int item_toppad = itembox->get_top_pad (itembox);
+ int item_bottompad = itembox->get_bottom_pad (itembox);
+
+ unsigned max_toppad = grub_max(sel_toppad, item_toppad);
+ unsigned max_bottompad = grub_max(sel_bottompad, item_bottompad);
+
+ if (self->horizontal == 0) {
+ *width = grub_font_get_string_width (self->item_font, "Typical OS");
+ sel_size = grub_font_get_string_width (self->selected_item_font,
+ "Typical OS");
+ if (*width < sel_size) *width = sel_size;
+
+ *width += 2 * boxpad + box_left_pad + box_right_pad;
+
+ /* Set the menu box height to fit the items. */
+ *height = (item_height * num_items
+ + item_space * (num_items - 1)
+ + 2 * boxpad
+ + box_top_pad + box_bottom_pad + sel_toppad);
+ } else {
+ *height = grub_font_get_ascent (self->item_font)
+ - grub_font_get_descent (self->item_font);
+ sel_size = grub_font_get_ascent (self->selected_item_font)
+ - grub_font_get_descent (self->selected_item_font);
+ if (*height < sel_size && self->selected_item_font) *height = sel_size;
+ if (*height + self->item_icon_space < max_bottompad) *height = max_bottompad;
+ else *height += max_bottompad;
+
+ *height += self->icon_height + box_top_pad + box_bottom_pad
+ + (2 * (boxpad + self->item_icon_padding))
+ + max_toppad + self->item_icon_space;
+ /* Set the menu box width to fit the items. ie: set to full width */
+ *width = self->bounds.width;
+ }
}
else
{
@@ -458,6 +582,10 @@
self->icon_width,
self->icon_height);
}
+ else if (grub_strcmp (name, "item_width") == 0)
+ {
+ self->item_width = grub_strtol (value, 0, 10);
+ }
else if (grub_strcmp (name, "item_height") == 0)
{
self->item_height = grub_strtol (value, 0, 10);
@@ -470,6 +598,10 @@
{
self->item_icon_space = grub_strtol (value, 0, 10);
}
+ else if (grub_strcmp (name, "item_icon_padding") == 0)
+ {
+ self->item_icon_padding = grub_strtol (value, 0, 10);
+ }
else if (grub_strcmp (name, "item_spacing") == 0)
{
self->item_spacing = grub_strtol (value, 0, 10);
@@ -490,6 +622,16 @@
grub_free (self->selected_item_box_pattern);
self->selected_item_box_pattern = value ? grub_strdup (value) : 0;
}
+ else if (grub_strcmp (name, "item_pixmap_style") == 0)
+ {
+ self->need_to_recreate_boxes = 1;
+ grub_free (self->item_box_pattern);
+ self->item_box_pattern = value ? grub_strdup (value) : 0;
+ }
+ else if (grub_strcmp (name, "item_pixmap_icon_only") == 0)
+ {
+ self->box_icon_only = grub_strcmp (value, "false") != 0;
+ }
else if (grub_strcmp (name, "scrollbar_frame") == 0)
{
self->need_to_recreate_scrollbar = 1;
@@ -524,6 +666,11 @@
else
self->id = 0;
}
+ else if (grub_strcmp (name, "horizontal") == 0)
+ {
+ self->horizontal = grub_strcmp (value, "false") != 0;
+ if(self->horizontal) grub_env_set("orientation", "horizontal");
+ }
return grub_errno;
}
@@ -578,9 +725,11 @@
self->icon_width = 32;
self->icon_height = 32;
+ self->item_width = 42; // Only applies to horizontal lists TODO: do for both and appropriate default value
self->item_height = 42;
self->item_padding = 14;
self->item_icon_space = 4;
+ self->item_icon_padding = 0;
self->item_spacing = 16;
self->item_font = default_font;
self->selected_item_font = 0; /* Default to using the item_font. */
@@ -604,6 +753,10 @@
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->item_box = grub_gfxmenu_create_box (0, 0);
+ self->box_icon_only = 0;
+
+ self->horizontal = 0;
self->icon_manager = grub_gfxmenu_icon_manager_new ();
if (! self->icon_manager)
=== modified file 'grub-core/gfxmenu/icon_manager.c'
--- grub-core/gfxmenu/icon_manager.c 2009-11-21 16:48:05 +0000
+++ grub-core/gfxmenu/icon_manager.c 2011-04-09 13:10:49 +0000
@@ -257,7 +257,7 @@
/* Try each class in succession. */
icon = 0;
- for (c = entry->classes->next; c && ! icon; c = c->next)
+ for (c = entry->classes; c && ! icon; c = c->next)
icon = get_icon_by_class (mgr, c->name);
return icon;
}
=== modified file 'grub-core/normal/menu.c'
--- grub-core/normal/menu.c 2011-04-08 10:12:02 +0000
+++ grub-core/normal/menu.c 2011-04-10 18:13:14 +0000
@@ -349,6 +349,8 @@
struct grub_term_output *term;
int gfxmenu = 0;
+ grub_env_set("orientation", "vertical");
+
FOR_ACTIVE_TERM_OUTPUTS(term)
if (grub_strcmp (term->name, "gfxterm") == 0)
{
@@ -580,22 +582,6 @@
menu_set_chosen_entry (current_entry);
break;
- case GRUB_TERM_KEY_UP:
- case GRUB_TERM_CTRL | 'p':
- case '^':
- if (current_entry > 0)
- current_entry--;
- menu_set_chosen_entry (current_entry);
- break;
-
- case GRUB_TERM_CTRL | 'n':
- case GRUB_TERM_KEY_DOWN:
- case 'v':
- if (current_entry < menu->size - 1)
- current_entry++;
- menu_set_chosen_entry (current_entry);
- break;
-
case GRUB_TERM_CTRL | 'g':
case GRUB_TERM_KEY_PPAGE:
if (current_entry < GRUB_MENU_PAGE_SIZE)
@@ -614,14 +600,6 @@
menu_set_chosen_entry (current_entry);
break;
- case '\n':
- case '\r':
- case GRUB_TERM_KEY_RIGHT:
- case GRUB_TERM_CTRL | 'f':
- menu_fini ();
- *auto_boot = 0;
- return current_entry;
-
case '\e':
if (nested)
{
@@ -656,6 +634,62 @@
*auto_boot = 0;
return i;
}
+
+ if (grub_strcmp(grub_env_get("orientation"), "horizontal") == 0) {
+ switch(c) {
+ case GRUB_TERM_KEY_LEFT:
+ case GRUB_TERM_CTRL | 'p':
+ case '<':
+ if (current_entry > 0)
+ current_entry--;
+ menu_set_chosen_entry (current_entry);
+ break;
+
+ case GRUB_TERM_CTRL | 'n':
+ case GRUB_TERM_KEY_RIGHT:
+ case '>':
+ if (current_entry < menu->size - 1)
+ current_entry++;
+ menu_set_chosen_entry (current_entry);
+ break;
+
+ case '\n':
+ case '\r':
+ case GRUB_TERM_CTRL | 'f':
+ menu_fini ();
+ *auto_boot = 0;
+ return current_entry;
+
+ }
+ } else {
+ switch(c) {
+ case GRUB_TERM_KEY_UP:
+ case GRUB_TERM_CTRL | 'p':
+ case '^':
+ if (current_entry > 0)
+ current_entry--;
+ menu_set_chosen_entry (current_entry);
+ break;
+
+ case GRUB_TERM_CTRL | 'n':
+ case GRUB_TERM_KEY_DOWN:
+ case 'v':
+ if (current_entry < menu->size - 1)
+ current_entry++;
+ menu_set_chosen_entry (current_entry);
+ break;
+
+ case '\n':
+ case '\r':
+ case GRUB_TERM_KEY_RIGHT:
+ case GRUB_TERM_CTRL | 'f':
+ menu_fini ();
+ *auto_boot = 0;
+ return current_entry;
+
+ }
+ }
+
}
break;
}
next reply other threads:[~2011-04-10 19:09 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2011-04-10 19:09 Endres Puschner [this message]
2011-05-31 23:56 ` Horizontal boot menu Vladimir 'φ-coder/phcoder' Serbinenko
2011-06-01 16:15 ` Endres Puschner
2011-06-02 4:09 ` Vladimir 'φ-coder/phcoder' Serbinenko
-- strict thread matches above, loose matches on Subject: below --
2013-05-31 7:02 horizontal " kiran ps
2013-05-31 14:49 ` Lennart Sorensen
2013-05-31 17:42 ` kiran ps
2013-06-01 21:22 ` Gerard Butler
2013-06-07 7:00 ` kiran ps
2013-06-07 7:06 ` kiran ps
2013-06-07 16:26 ` Andrey Borzenkov
2013-06-07 16:30 ` Vladimir 'φ-coder/phcoder' Serbinenko
2013-06-08 7:30 ` Gerard Butler
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=4DA2006E.1060209@e7p.de \
--to=code@e7p.de \
--cc=grub-devel@gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.