More work on actual VESA console and menu. Shadowing still doesn't work
authorH. Peter Anvin <hpa@zytor.com>
Thu, 31 Aug 2006 22:48:26 +0000 (15:48 -0700)
committerH. Peter Anvin <hpa@zytor.com>
Thu, 31 Aug 2006 22:48:26 +0000 (15:48 -0700)
quite right, however.

com32/include/console.h
com32/lib/Makefile
com32/lib/sys/vesa/alphatbl.pl [deleted file]
com32/lib/sys/vesa/drawtxt.c
com32/lib/sys/vesa/initvesa.c
com32/lib/sys/vesa/video.h
com32/lib/sys/vesacon_write.c
com32/lib/sys/vesaserial_write.c [new file with mode: 0644]
com32/modules/Makefile
com32/modules/vesamenu.c [new file with mode: 0644]
com32/samples/vesa.c

index 3b08e25..a5e8ca2 100644 (file)
@@ -55,4 +55,9 @@ extern const struct output_dev dev_ansicon_w;
 /* ANSI plus serial port */
 extern const struct output_dev dev_ansiserial_w;
 
+/* VESA graphics console (output only) */
+extern const struct output_dev dev_vesacon_w;
+/* VESA plus serial port */
+extern const struct output_dev dev_vesaserial_w;
+
 #endif /* _CONSOLE_H */
index 05b2279..e7f4ad7 100644 (file)
@@ -33,8 +33,8 @@ LIBOBJS = \
        sys/null_read.o sys/null_write.o sys/serial_write.o             \
        sys/ansicon_write.o sys/ansiserial_write.o                      \
        \
-       sys/vesa/initvesa.o sys/vesa/alphatbl.o sys/vesa/drawtxt.o \
-       sys/vesa/background.o \
+       sys/vesacon_write.o sys/vesaserial_write.o                      \
+       sys/vesa/initvesa.o sys/vesa/drawtxt.o  sys/vesa/background.o   \
        \
        pci/cfgtype.o                                                   \
        pci/readb.o pci/readw.o pci/readl.o pci/readbios.o              \
@@ -80,11 +80,6 @@ install: all
        -rm -rf $(INSTALLROOT)$(COM32DIR)/include
        cp -r ../include $(INSTALLROOT)$(COM32DIR)
 
-sys/vesa/alphatbl.o: sys/vesa/alphatbl.c
-
-sys/vesa/alphatbl.c: sys/vesa/alphatbl.pl
-       $(PERL) $< > $@ || ( rm -f $@ ; exit 1 )
-
 # This code is performance critical, and doesn't compile well with -Os
 sys/vesa/drawtxt.o: sys/vesa/drawtxt.c
        $(CC) $(CFLAGS) -O3 -c -o $@ $<
diff --git a/com32/lib/sys/vesa/alphatbl.pl b/com32/lib/sys/vesa/alphatbl.pl
deleted file mode 100755 (executable)
index e49ff3e..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-#!/usr/bin/perl
-#
-# Compute the alpha-blending table for 256 possible (R, G, B) value
-# compared with the 4-bit intensity value from the character attribute
-#
-
-#
-# Configurable parameters...
-#
-@text_intensity = (0.0, 0.3333, 0.6667, 1.0);
-$text_alpha = 0.5;
-$input_gamma  = 1.7;
-$text_gamma   = 1.7;
-$output_gamma = 1.7;
-
-sub ungamma($$) {
-    my($v, $gamma) = @_;
-
-    return $v**$gamma;
-}
-
-sub gamma($$) {
-    my($v, $gamma) = @_;
-    return $v**(1/$gamma);
-}
-
-print "unsigned char __vesacon_alpha_tbl[256][4] = {\n";
-
-for ($i = 0; $i <= 255; $i++) {
-    $ival = ungamma($i/255, $input_gamma);
-
-    $intro = "\t{";
-
-    for ($j = 0; $j < 4; $j++) {
-       $v = ($ival*(1-$text_alpha)) +
-           ungamma($text_intensity[$j], $text_gamma)*$text_alpha;
-
-       $d = int(gamma($v,$output_gamma)*255+0.5);
-
-       $d = 0   if ($d < 0);
-       $d = 255 if ($d > 255);
-
-       printf "%s%3d", $intro, $d;
-       $intro = ', ';
-    }
-    print "},\n";
-}
-print "};\n";
index b121e67..f2c7951 100644 (file)
@@ -174,10 +174,70 @@ static void vesacon_update_characters(int row, int col, int nrows, int ncols)
   }
 }
 
-void vesacon_write_at(int row, int col, const char *str, uint8_t attr, int rev)
+/* Fill a number of characters... */
+static inline struct vesa_char *vesacon_fill(struct vesa_char *ptr,
+                                            struct vesa_char fill,
+                                            unsigned int count)
+{
+  asm volatile("cld; rep; stosl"
+              : "+D" (ptr), "+c" (count)
+              : "a" (fill)
+              : "memory");
+
+  return ptr;
+}
+
+/* Erase a region of the screen */
+void __vesacon_erase(int x0, int y0, int x1, int y1, uint8_t attr, int rev)
+{
+  int y;
+  struct vesa_char *ptr = &__vesacon_text_display
+    [(y0+1)*(TEXT_PIXEL_COLS/FONT_WIDTH+2)+(x0+1)];
+  struct vesa_char fill = {
+    .ch = ' ',
+    .attr = attr,
+    .sha = rev
+  };
+  int ncols = x1-x0+1;
+
+  for (y = y0; y <= y1; y++) {
+    vesacon_fill(ptr, fill, ncols);
+    ptr += TEXT_PIXEL_COLS/FONT_WIDTH+2;
+  }
+
+  vesacon_update_characters(y0, x0, y1-y0+1, ncols);
+}
+
+/* Scroll the screen up */
+void __vesacon_scroll_up(int nrows, uint8_t attr, int rev)
+{
+  struct vesa_char *fromptr = &__vesacon_text_display
+      [nrows*(TEXT_PIXEL_COLS/FONT_WIDTH+2)];
+  struct vesa_char *toptr = __vesacon_text_display;
+  int dword_count = nrows*(TEXT_PIXEL_COLS/FONT_WIDTH+2);
+  struct vesa_char fill = {
+    .ch   = ' ',
+    .attr = attr,
+    .sha  = rev,
+  };
+
+  asm volatile("cld ; rep ; movsl"
+              : "+D" (toptr), "+S" (fromptr), "+c" (dword_count));
+
+  dword_count = (__vesacon_text_rows-nrows)*(TEXT_PIXEL_COLS/FONT_WIDTH+2);
+  vesacon_fill(toptr, fill, dword_count);
+  
+  vesacon_update_characters(0, 0, __vesacon_text_rows,
+                           TEXT_PIXEL_COLS/FONT_WIDTH);
+}
+
+/* Draw text at a specific area of the screen */
+void __vesacon_write_at(int x, int y, const char *str,
+                       uint8_t attr, int rev)
 {
   int n = 0;
-  struct vesa_char *ptr = &__vesacon_text_display[(row+1)*(TEXT_PIXEL_COLS/FONT_WIDTH+2)+(col+1)];
+  struct vesa_char *ptr = &__vesacon_text_display
+    [(y+1)*(TEXT_PIXEL_COLS/FONT_WIDTH+2)+(x+1)];
 
   while (*str) {
     ptr->ch   = *str;
@@ -189,6 +249,19 @@ void vesacon_write_at(int row, int col, const char *str, uint8_t attr, int rev)
     ptr++;
   }
 
-  vesacon_update_characters(row, col, 1, n);
+  vesacon_update_characters(y, x, 1, n);
+}
+
+/* Draw one character text at a specific area of the screen */
+void __vesacon_write_char(int x, int y, char ch, uint8_t attr, int rev)
+{
+  struct vesa_char *ptr = &__vesacon_text_display
+    [(y+1)*(TEXT_PIXEL_COLS/FONT_WIDTH+2)+(x+1)];
+
+  ptr->ch   = ch;
+  ptr->attr = attr;
+  ptr->sha  = rev;
+
+  vesacon_update_characters(y, x, 1, 1);
 }
 
index 7d29427..022fb32 100644 (file)
@@ -45,7 +45,7 @@ struct vesa_info __vesa_info;
 
 struct vesa_char *__vesacon_text_display;
 
-int __vesacon_font_height;
+int __vesacon_font_height, __vesacon_text_rows;
 uint8_t __vesacon_graphics_font[FONT_MAX_CHARS][FONT_MAX_HEIGHT];
 
 uint32_t __vesacon_background[VIDEO_Y_SIZE][VIDEO_X_SIZE];
@@ -159,6 +159,7 @@ static int vesacon_set_mode(void)
   rom_font = MK_PTR(rm.es, rm.ebp.w[0]);
   __vesacon_font_height = 16;
   unpack_font((uint8_t *)__vesacon_graphics_font, rom_font, 16);
+  __vesacon_text_rows = (VIDEO_Y_SIZE-2*VIDEO_BORDER)/__vesacon_font_height;
 
   /* Now set video mode */
   rm.eax.w[0] = 0x4F02;                /* Set SVGA video mode */
@@ -201,78 +202,11 @@ static int init_text_display(void)
 
 int __vesacon_init(void)
 {
-  int i, j, r, g, b, n;
-  const int step = 8;
-
-  /* Fill the background image with a test pattern */
-  for (i = 0; i < VIDEO_Y_SIZE; i++) {
-    r = g = b = n = 0;
-
-    for (j = 0; j < VIDEO_X_SIZE; j++) {
-      switch (n) {
-      case 0:
-       r += step;
-       if (r >= 255) {
-         r = 255;
-         n++;
-       }
-       break;
-      case 1:
-       g += step;
-       if (g >= 255) {
-         g = 255;
-         n++;
-       }
-       break;
-      case 2:
-       r -= step;
-       if (r <= 0) {
-         r = 0;
-         n++;
-       }
-       break;
-      case 3:
-       b += step;
-       if (b >= 255) {
-         b = 255;
-         n++;
-       }
-       break;
-      case 4:
-       g -= step;
-       if (g <= 0) {
-         g = 0;
-         n++;
-       }
-       break;
-      case 5:
-       r += step;
-       if (r >= 255) {
-         r = 255;
-         n++;
-       }
-       break;
-      case 6:
-       g += step;
-       if (g >= 255) {
-         g = 255;
-         n++;
-       }
-       break;
-      case 7:
-       r -= step;
-       if (r < 0) {
-         r = 0;
-         n = 0;
-       }
-       g = b = r;
-       break;
-      }
-      __vesacon_background[i][j] = (r << 16) + (g << 8) + b;
-    }
-  }
+  int rv;
 
-  vesacon_set_mode();
+  rv = vesacon_set_mode();
+  if (rv)
+    return rv;
 
   init_text_display();
 
index 9237a85..82b14a4 100644 (file)
@@ -53,14 +53,19 @@ struct vesa_char {
 
 extern struct vesa_char *__vesacon_text_display;
 
-extern int __vesacon_font_height;
+extern int __vesacon_font_height, __vesacon_text_rows;
 extern uint8_t __vesacon_graphics_font[FONT_MAX_CHARS][FONT_MAX_HEIGHT];
 extern uint32_t __vesacon_background[VIDEO_Y_SIZE][VIDEO_X_SIZE];
 extern uint32_t __vesacon_shadowfb[VIDEO_Y_SIZE][VIDEO_X_SIZE];
 
 extern unsigned char __vesacon_alpha_tbl[256][4];
 
-extern int __vesacon_init_background(void);
+int __vesacon_init_background(void);
 int vesacon_load_background(const char *);
+int __vesacon_init(void);
+void __vesacon_erase(int, int, int, int, uint8_t, int);
+void __vesacon_scroll_up(int, uint8_t, int);
+void __vesacon_write_at(int, int, const char *, uint8_t, int);
+void __vesacon_write_char(int, int, char, uint8_t, int);
 
 #endif /* LIB_SYS_VESA_VIDEO_H */
index 2b0fb70..d759a82 100644 (file)
@@ -28,7 +28,8 @@
 /*
  * vesacon_write.c
  *
- * Write to the screen mapped using VESA BIOS extensions (VBE)
+ * Write to the screen using ANSI control codes (about as capable as
+ * DOS' ANSI.SYS.)
  */
 
 #include <errno.h>
 #include <minmax.h>
 #include <klibc/compiler.h>
 #include "file.h"
+#include "vesa/video.h"
 
 struct curxy {
   uint8_t x, y;
 } __attribute__((packed));
-#define BIOS_CURXY ((struct curxy *)0x450) /* Array for each page */
-#define BIOS_ROWS (*(uint8_t *)0x484)      /* Minus one; if zero use 24 (= 25 lines) */
-#define BIOS_COLS (*(uint16_t *)0x44A)
-#define BIOS_PAGE (*(uint8_t *)0x462)
 
 enum ansi_state {
   st_init,                     /* Normal (no ESC seen) */
@@ -71,6 +69,7 @@ struct term_state {
   int pvt;                     /* Private code? */
   int nparms;                  /* Number of parameters seen */
   int parms[MAX_PARMS];
+  struct curxy xy;
 };
 
 static const struct term_state default_state =
@@ -90,6 +89,7 @@ static const struct term_state default_state =
   .state = st_init,
   .pvt = 0,
   .nparms = 0,
+  .xy = { 0, 0 },
 };
 
 static struct term_state st;
@@ -99,66 +99,67 @@ static const char decvt_to_cp437[] =
   { 0004, 0261, 0007, 0007, 0007, 0007, 0370, 0361, 0007, 0007, 0331, 0277, 0332, 0300, 0305, 0304,
     0304, 0304, 0137, 0137, 0303, 0264, 0301, 0302, 0263, 0363, 0362, 0343, 0330, 0234, 0007, 00 };
 
+/* Reference counter to the screen, to keep track of if we need reinitialization. */
+static int vesacon_counter = 0;
+
 /* Common setup */
-static void vesacon_init(void)
+int __vesacon_open(struct file_info *fp)
 {
   static com32sys_t ireg;      /* Auto-initalized to all zero */
   com32sys_t oreg;
 
-  /* Initial state */
-  memcpy(&st, &default_state, sizeof st);
-
-  /* Are we disabled? */
-  ireg.eax.w[0] = 0x000b;
-  __intcall(0x22, &ireg, &oreg);
+  (void)fp;
 
-  if ( (signed char)oreg.ebx.b[1] < 0 ) {
-    st.disabled = 1;
-    return;
+  if (!vesacon_counter) {
+    /* Initial state */
+    memcpy(&st, &default_state, sizeof st);
+    
+    /* Are we disabled? */
+    ireg.eax.w[0] = 0x000b;
+    __intcall(0x22, &ireg, &oreg);
+    
+    if ( (signed char)oreg.ebx.b[1] < 0 ) {
+      st.disabled = 1;
+    } else {
+      /* Switch mode */
+      if (__vesacon_init())
+       return EIO;
+    }
   }
 
-  /* Force text mode */
-  ireg.eax.w[0] = 0x0005;
-  __intcall(0x22, &ireg, NULL);
+  vesacon_counter++;
+  return 0;
+}
+
+int __vesacon_close(struct file_info *fp)
+{
+  (void)fp;
 
-  /* Get cursor shape */
-  ireg.eax.b[1] = 0x03;
-  ireg.ebx.b[1] = BIOS_PAGE;
-  __intcall(0x10, &ireg, &oreg);
-  st.cursor_type = oreg.ecx.w[0];
+  vesacon_counter--;
+  return 0;
 }
 
 /* Erase a region of the screen */
 static void vesacon_erase(int x0, int y0, int x1, int y1)
 {
-  static com32sys_t ireg;
-
-  ireg.eax.w[0] = 0x0600;      /* Clear window */
-  ireg.ebx.b[1] = st.attr;     /* Fill with current attribute */
-  ireg.ecx.b[0] = x0;
-  ireg.ecx.b[1] = y0;
-  ireg.edx.b[0] = x1;
-  ireg.edx.b[1] = y1;
-  __intcall(0x10, &ireg, NULL);
+  __vesacon_erase(x0, y0, x1, y1, st.attr,
+                 st.reverse ? SHADOW_ALL : SHADOW_NORMAL);
 }
 
 /* Show or hide the cursor */
 static void showcursor(int yes)
 {
-  static com32sys_t ireg;
-
-  ireg.eax.b[1] = 0x01;
-  ireg.ecx.w[0] = yes ? st.cursor_type : 0x2020;
-  __intcall(0x10, &ireg, NULL);
+  (void)yes;
+  /* Do something here */
 }
 
 static void vesacon_putchar(int ch)
 {
   static com32sys_t ireg;
-  const int rows  = BIOS_ROWS ? BIOS_ROWS+1 : 25;
-  const int cols  = BIOS_COLS;
-  const int page  = BIOS_PAGE;
-  struct curxy xy = BIOS_CURXY[page];
+  const int rows  = __vesacon_text_rows;
+  const int cols  = VIDEO_X_SIZE/FONT_WIDTH;
+  const int page  = 0;
+  struct curxy xy = st.xy;
 
   switch ( st.state ) {
   case st_init:
@@ -201,12 +202,8 @@ static void vesacon_putchar(int ch)
        if ( st.vtgraphics && (ch & 0xe0) == 0x60 )
          ch = decvt_to_cp437[ch - 0x60];
 
-       ireg.eax.b[1] = 0x09;
-       ireg.eax.b[0] = ch;
-       ireg.ebx.b[1] = page;
-       ireg.ebx.b[0] = st.attr;
-       ireg.ecx.w[0] = 1;
-       __intcall(0x10, &ireg, NULL);
+       __vesacon_write_char(xy.x, xy.y, ch, st.attr,
+                            st.reverse ? SHADOW_ALL : SHADOW_NORMAL);
        xy.x++;
       }
       break;
@@ -492,20 +489,12 @@ static void vesacon_putchar(int ch)
   }
   while ( xy.y >= rows ) {
     xy.y--;
-    ireg.eax.w[0] = 0x0601;
-    ireg.ebx.b[1] = st.attr;
-    ireg.ecx.w[0] = 0;
-    ireg.edx.b[1] = rows-1;
-    ireg.edx.b[0] = cols-1;
-    __intcall(0x10, &ireg, NULL); /* Scroll */
+    __vesacon_scroll_up(1, st.attr, st.reverse ? SHADOW_ALL : SHADOW_NORMAL);
   }
 
   /* Update cursor position */
-  ireg.eax.b[1] = 0x02;
-  ireg.ebx.b[1] = page;
-  ireg.edx.b[1] = xy.y;
-  ireg.edx.b[0] = xy.x;
-  __intcall(0x10, &ireg, NULL);
+  /* vesacon_set_cursor(xy.x, xy.y); */
+  st.xy = xy;
 }
 
 
@@ -532,6 +521,6 @@ const struct output_dev dev_vesacon_w = {
   .flags      = __DEV_TTY | __DEV_OUTPUT,
   .fileflags  = O_WRONLY | O_CREAT | O_TRUNC | O_APPEND,
   .write      = __vesacon_write,
-  .close      = NULL,
-  .init       = vesacon_init,
+  .close      = __vesacon_close,
+  .open       = __vesacon_open,
 };
diff --git a/com32/lib/sys/vesaserial_write.c b/com32/lib/sys/vesaserial_write.c
new file mode 100644 (file)
index 0000000..da09a68
--- /dev/null
@@ -0,0 +1,58 @@
+/* ----------------------------------------------------------------------- *
+ *
+ *   Copyright 2004 H. Peter Anvin - All Rights Reserved
+ *
+ *   Permission is hereby granted, free of charge, to any person
+ *   obtaining a copy of this software and associated documentation
+ *   files (the "Software"), to deal in the Software without
+ *   restriction, including without limitation the rights to use,
+ *   copy, modify, merge, publish, distribute, sublicense, and/or
+ *   sell copies of the Software, and to permit persons to whom
+ *   the Software is furnished to do so, subject to the following
+ *   conditions:
+ *
+ *   The above copyright notice and this permission notice shall
+ *   be included in all copies or substantial portions of the Software.
+ *
+ *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ *   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ *   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ *   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ *   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ *   OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * ----------------------------------------------------------------------- */
+
+/*
+ * vesaserial_write.c
+ *
+ * Write to both to the VESA console and the serial port
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <com32.h>
+#include <minmax.h>
+#include "file.h"
+
+extern int __vesacon_open(void);
+extern int __vesacon_close(struct file_info *);
+extern ssize_t __vesacon_write(struct file_info *, const void *, size_t);
+extern ssize_t __serial_write(struct file_info *, const void *, size_t);
+
+static ssize_t __vesaserial_write(struct file_info *fp, const void *buf, size_t count)
+{
+  __vesacon_write(fp, buf, count);
+  return __serial_write(fp, buf, count);
+}
+
+const struct output_dev dev_vesaserial_w = {
+  .dev_magic  = __DEV_MAGIC,
+  .flags      = __DEV_TTY | __DEV_OUTPUT,
+  .fileflags  = O_WRONLY | O_CREAT | O_TRUNC | O_APPEND,
+  .write      = __vesaserial_write,
+  .close      = __vesacon_close,
+  .open       = __vesacon_open,
+};
index fc8c861..971842e 100644 (file)
@@ -44,7 +44,7 @@ AUXDIR   = $(LIBDIR)/syslinux
 INCDIR   = /usr/include
 COM32DIR = $(AUXDIR)/com32
 
-MODULES          = chain.c32 menu.c32 ethersel.c32 mboot.c32 dmitest.c32
+MODULES          = chain.c32 menu.c32 vesamenu.c32 ethersel.c32 mboot.c32 dmitest.c32
 TESTFILES = menu.lnx
 
 all: $(MODULES) $(TESTFILES)
@@ -82,6 +82,9 @@ dmitest.elf : dmitest.o dmi_utils.o dmi.o $(LIBS)
 menu.elf : menu.o readconfig.o $(LIBS)
        $(LD) $(LDFLAGS) -o $@ $^
 
+vesamenu.elf : vesamenu.o readconfig.o $(LIBS)
+       $(LD) $(LDFLAGS) -o $@ $^
+
 menu.lnx : menu.lo readconfig.lo $(LNXLIBS)
        $(CC) $(LNXLDFLAGS) -o $@ $^
 
diff --git a/com32/modules/vesamenu.c b/com32/modules/vesamenu.c
new file mode 100644 (file)
index 0000000..f20fc78
--- /dev/null
@@ -0,0 +1,856 @@
+/* ----------------------------------------------------------------------- *
+ *
+ *   Copyright 2004-2006 H. Peter Anvin - All Rights Reserved
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, Inc., 53 Temple Place Ste 330,
+ *   Boston MA 02111-1307, USA; either version 2 of the License, or
+ *   (at your option) any later version; incorporated herein by reference.
+ *
+ * ----------------------------------------------------------------------- */
+
+/*
+ * menu.c
+ *
+ * Simple menu system which displays a list and allows the user to select
+ * a command line and/or edit it.
+ */
+
+#define _GNU_SOURCE            /* Needed for asprintf() on Linux */
+#include <ctype.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <console.h>
+#include <getkey.h>
+#include <minmax.h>
+#include <setjmp.h>
+#include <limits.h>
+#include <sha1.h>
+#include <base64.h>
+#ifdef __COM32__
+#include <com32.h>
+#endif
+
+#include "menu.h"
+
+struct menu_attrib {
+  const char *border;          /* Border area */
+  const char *title;           /* Title bar */
+  const char *unsel;           /* Unselected menu item */
+  const char *hotkey;          /* Unselected hotkey */
+  const char *sel;             /* Selected */
+  const char *hotsel;          /* Selected hotkey */
+  const char *scrollbar;       /* Scroll bar */
+  const char *tabmsg;          /* Press [Tab] message */
+  const char *cmdmark;         /* Command line marker */
+  const char *cmdline;         /* Command line */
+  const char *screen;          /* Rest of the screen */
+  const char *pwdborder;       /* Password box border */
+  const char *pwdheader;       /* Password box header */
+  const char *pwdentry;                /* Password box contents */
+  const char *timeout_msg;     /* Timeout message */
+  const char *timeout;         /* Timeout counter */
+};
+
+static const struct menu_attrib default_attrib = {
+  .border      = "\033[0;30;44m",
+  .title       = "\033[1;36;44m",
+  .unsel        = "\033[0;37;44m",
+  .hotkey       = "\033[1;37;44m",
+  .sel          = "\033[0;7;37;40m",
+  .hotsel       = "\033[1;7;37;40m",
+  .scrollbar    = "\033[0;30;44m",
+  .tabmsg      = "\033[0;31;40m",
+  .cmdmark     = "\033[1;36;40m",
+  .cmdline     = "\033[0;37;40m",
+  .screen      = "\033[0;37;40m",
+  .pwdborder   = "\033[0;30;47m",
+  .pwdheader    = "\033[0;31;47m",
+  .pwdentry     = "\033[0;30;47m",
+  .timeout_msg  = "\033[0;37;40m",
+  .timeout      = "\033[1;37;40m",
+};
+
+static const struct menu_attrib *menu_attrib = &default_attrib;
+
+struct menu_parameter mparm[] = {
+  { "width", 80 },
+  { "margin", 10 },
+  { "passwordmargin", 3 },
+  { "rows", 12 },
+  { "tabmsgrow", 18 },
+  { "cmdlinerow", 18 },
+  { "endrow", 24 },
+  { "passwordrow", 11 },
+  { "timeoutrow", 20 },
+  { NULL, 0 }
+};
+
+#define WIDTH          mparm[0].value
+#define MARGIN         mparm[1].value
+#define PASSWD_MARGIN  mparm[2].value
+#define MENU_ROWS      mparm[3].value
+#define TABMSG_ROW     mparm[4].value
+#define CMDLINE_ROW    mparm[5].value
+#define END_ROW                mparm[6].value
+#define PASSWD_ROW     mparm[7].value
+#define TIMEOUT_ROW    mparm[8].value
+
+static char *
+pad_line(const char *text, int align, int width)
+{
+  static char buffer[MAX_CMDLINE_LEN];
+  int n, p;
+
+  if ( width >= (int) sizeof buffer )
+    return NULL;               /* Can't do it */
+
+  n = strlen(text);
+  if ( n >= width )
+    n = width;
+
+  memset(buffer, ' ', width);
+  buffer[width] = 0;
+  p = ((width-n)*align)>>1;
+  memcpy(buffer+p, text, n);
+
+  return buffer;
+}
+
+/* Display an entry, with possible hotkey highlight.  Assumes
+   that the current attribute is the non-hotkey one, and will
+   guarantee that as an exit condition as well. */
+static void
+display_entry(const struct menu_entry *entry, const char *attrib,
+             const char *hotattrib, int width)
+{
+  const char *p = entry->displayname;
+
+  while ( width ) {
+    if ( *p ) {
+      if ( *p == '^' ) {
+       p++;
+       if ( *p && ((unsigned char)*p & ~0x20) == entry->hotkey ) {
+         fputs(hotattrib, stdout);
+         putchar(*p++);
+         fputs(attrib, stdout);
+         width--;
+       }
+      } else {
+       putchar(*p++);
+       width--;
+      }
+    } else {
+      putchar(' ');
+      width--;
+    }
+  }
+}
+
+static void
+draw_row(int y, int sel, int top, int sbtop, int sbbot)
+{
+  int i = (y-4)+top;
+
+  printf("\033[%d;%dH%s\016x\017%s ",
+        y, MARGIN+1, menu_attrib->border,
+        (i == sel) ? menu_attrib->sel : menu_attrib->unsel);
+
+  if ( i >= nentries ) {
+    fputs(pad_line("", 0, WIDTH-2*MARGIN-4), stdout);
+  } else {
+    display_entry(&menu_entries[i],
+                 (i == sel) ? menu_attrib->sel : menu_attrib->unsel,
+                 (i == sel) ? menu_attrib->hotsel : menu_attrib->hotkey,
+                 WIDTH-2*MARGIN-4);
+  }
+
+  if ( nentries <= MENU_ROWS ) {
+    printf(" %s\016x\017", menu_attrib->border);
+  } else if ( sbtop > 0 ) {
+    if ( y >= sbtop && y <= sbbot )
+      printf(" %s\016a\017", menu_attrib->scrollbar);
+    else
+      printf(" %s\016x\017", menu_attrib->border);
+  } else {
+    putchar(' ');              /* Don't modify the scrollbar */
+  }
+}
+
+static int
+passwd_compare(const char *passwd, const char *entry)
+{
+  const char *p;
+  SHA1_CTX ctx;
+  unsigned char sha1[20], pwdsha1[20];
+
+  if ( passwd[0] != '$' )      /* Plaintext passwd, yuck! */
+    return !strcmp(entry, passwd);
+
+  if ( strncmp(passwd, "$4$", 3) )
+    return 0;                  /* Only SHA-1 passwds supported */
+
+  SHA1Init(&ctx);
+
+  if ( (p = strchr(passwd+3, '$')) ) {
+    SHA1Update(&ctx, passwd+3, p-(passwd+3));
+    p++;
+  } else {
+    p = passwd+3;              /* Assume no salt */
+  }
+
+  SHA1Update(&ctx, entry, strlen(entry));
+  SHA1Final(sha1, &ctx);
+
+  memset(pwdsha1, 0, 20);
+  unbase64(pwdsha1, 20, p);
+
+  return !memcmp(sha1, pwdsha1, 20);
+}
+
+static jmp_buf timeout_jump;
+
+static int mygetkey(clock_t timeout)
+{
+  clock_t t0, t;
+  clock_t tto, to;
+  int key;
+
+  if ( !totaltimeout )
+    return get_key(stdin, timeout);
+
+  for (;;) {
+    tto = min(totaltimeout, INT_MAX);
+    to = timeout ? min(tto, timeout) : tto;
+
+    t0 = times(NULL);
+    key = get_key(stdin, to);
+    t = times(NULL) - t0;
+
+    if ( totaltimeout <= t )
+      longjmp(timeout_jump, 1);
+
+    totaltimeout -= t;
+
+    if ( key != KEY_NONE )
+      return key;
+
+    if ( timeout ) {
+      if ( timeout <= t )
+       return KEY_NONE;
+
+      timeout -= t;
+    }
+  }
+}
+
+static int
+ask_passwd(const char *menu_entry)
+{
+  static const char title[] = "Password required";
+  char user_passwd[WIDTH], *p;
+  int done;
+  int key;
+  int x;
+
+  printf("\033[%d;%dH%s\016l", PASSWD_ROW, PASSWD_MARGIN+1,
+        menu_attrib->pwdborder);
+  for ( x = 2 ; x <= WIDTH-2*PASSWD_MARGIN-1 ; x++ )
+    putchar('q');
+
+  printf("k\033[%d;%dHx", PASSWD_ROW+1, PASSWD_MARGIN+1);
+  for ( x = 2 ; x <= WIDTH-2*PASSWD_MARGIN-1 ; x++ )
+    putchar(' ');
+
+  printf("x\033[%d;%dHm", PASSWD_ROW+2, PASSWD_MARGIN+1);
+  for ( x = 2 ; x <= WIDTH-2*PASSWD_MARGIN-1 ; x++ )
+    putchar('q');
+
+  printf("j\017\033[%d;%dH%s %s \033[%d;%dH%s",
+        PASSWD_ROW, (WIDTH-((int)sizeof(title)+1))/2,
+        menu_attrib->pwdheader, title,
+        PASSWD_ROW+1, PASSWD_MARGIN+3, menu_attrib->pwdentry);
+
+  /* Actually allow user to type a password, then compare to the SHA1 */
+  done = 0;
+  p = user_passwd;
+
+  while ( !done ) {
+    key = mygetkey(0);
+
+    switch ( key ) {
+    case KEY_ENTER:
+    case KEY_CTRL('J'):
+      done = 1;
+      break;
+
+    case KEY_ESC:
+    case KEY_CTRL('C'):
+      p = user_passwd;         /* No password entered */
+      done = 1;
+      break;
+
+    case KEY_BACKSPACE:
+    case KEY_DEL:
+    case KEY_DELETE:
+      if ( p > user_passwd ) {
+       printf("\b \b");
+       p--;
+      }
+      break;
+
+    case KEY_CTRL('U'):
+      while ( p > user_passwd ) {
+       printf("\b \b");
+       p--;
+      }
+      break;
+
+    default:
+      if ( key >= ' ' && key <= 0xFF &&
+          (p-user_passwd) < WIDTH-2*PASSWD_MARGIN-5 ) {
+       *p++ = key;
+       putchar('*');
+      }
+      break;
+    }
+  }
+
+  if ( p == user_passwd )
+    return 0;                  /* No password entered */
+
+  *p = '\0';
+
+  return (menu_master_passwd && passwd_compare(menu_master_passwd, user_passwd))
+    || (menu_entry && passwd_compare(menu_entry, user_passwd));
+}
+
+
+static void
+draw_menu(int sel, int top, int edit_line)
+{
+  int x, y;
+  int sbtop = 0, sbbot = 0;
+
+  if ( nentries > MENU_ROWS ) {
+    int sblen = MENU_ROWS*MENU_ROWS/nentries;
+    sbtop = (MENU_ROWS-sblen+1)*top/(nentries-MENU_ROWS+1);
+    sbbot = sbtop + sblen - 1;
+
+    sbtop += 4;  sbbot += 4;   /* Starting row of scrollbar */
+  }
+
+  printf("\033[1;%dH%s\016l", MARGIN+1, menu_attrib->border);
+  for ( x = 2 ; x <= WIDTH-2*MARGIN-1 ; x++ )
+    putchar('q');
+
+  printf("k\033[2;%dH%sx\017%s %s %s\016x",
+        MARGIN+1,
+        menu_attrib->border,
+        menu_attrib->title,
+        pad_line(menu_title, 1, WIDTH-2*MARGIN-4),
+        menu_attrib->border);
+
+  printf("\033[3;%dH%st", MARGIN+1, menu_attrib->border);
+  for ( x = 2 ; x <= WIDTH-2*MARGIN-1 ; x++ )
+    putchar('q');
+  fputs("u\017", stdout);
+
+  for ( y = 4 ; y < 4+MENU_ROWS ; y++ )
+    draw_row(y, sel, top, sbtop, sbbot);
+
+  printf("\033[%d;%dH%s\016m", y, MARGIN+1, menu_attrib->border);
+  for ( x = 2 ; x <= WIDTH-2*MARGIN-1 ; x++ )
+    putchar('q');
+  fputs("j\017", stdout);
+
+  if ( edit_line && allowedit && !menu_master_passwd )
+    printf("%s\033[%d;1H%s", menu_attrib->tabmsg, TABMSG_ROW,
+          pad_line("Press [Tab] to edit options", 1, WIDTH));
+
+  printf("%s\033[%d;1H", menu_attrib->screen, END_ROW);
+}
+
+static const char *
+edit_cmdline(char *input, int top)
+{
+  static char cmdline[MAX_CMDLINE_LEN];
+  int key, len, prev_len, cursor;
+  int redraw = 1;              /* We enter with the menu already drawn */
+
+  strncpy(cmdline, input, MAX_CMDLINE_LEN);
+  cmdline[MAX_CMDLINE_LEN-1] = '\0';
+
+  len = cursor = strlen(cmdline);
+  prev_len = 0;
+
+  for (;;) {
+    if ( redraw > 1 ) {
+      /* Clear and redraw whole screen */
+      /* Enable ASCII on G0 and DEC VT on G1; do it in this order
+        to avoid confusing the Linux console */
+      printf("\033e\033%%@\033)0\033(B%s\033[?25l\033[2J", menu_attrib->screen);
+      draw_menu(-1, top, 1);
+      prev_len = 0;
+    }
+
+    if ( redraw > 0 ) {
+      /* Redraw the command line */
+      printf("\033[?25l\033[%d;1H%s> %s%s",
+            CMDLINE_ROW, menu_attrib->cmdmark,
+            menu_attrib->cmdline, pad_line(cmdline, 0, prev_len));
+      printf("%s\033[%d;3H%s\033[?25h",
+            menu_attrib->cmdline, CMDLINE_ROW, pad_line(cmdline, 0, cursor));
+      prev_len = len;
+      redraw = 0;
+    }
+
+    key = mygetkey(0);
+
+    switch( key ) {
+    case KEY_CTRL('L'):
+      redraw = 2;
+      break;
+
+    case KEY_ENTER:
+    case KEY_CTRL('J'):
+      return cmdline;
+
+    case KEY_ESC:
+    case KEY_CTRL('C'):
+      return NULL;
+
+    case KEY_BACKSPACE:
+    case KEY_DEL:
+      if ( cursor ) {
+       memmove(cmdline+cursor-1, cmdline+cursor, len-cursor+1);
+       len--;
+       cursor--;
+       redraw = 1;
+      }
+      break;
+
+    case KEY_CTRL('D'):
+    case KEY_DELETE:
+      if ( cursor < len ) {
+       memmove(cmdline+cursor, cmdline+cursor+1, len-cursor);
+       len--;
+       redraw = 1;
+      }
+      break;
+
+    case KEY_CTRL('U'):
+      if ( len ) {
+       len = cursor = 0;
+       cmdline[len] = '\0';
+       redraw = 1;
+      }
+      break;
+
+    case KEY_CTRL('W'):
+      if ( cursor ) {
+       int prevcursor = cursor;
+
+       while ( cursor && my_isspace(cmdline[cursor-1]) )
+         cursor--;
+
+       while ( cursor && !my_isspace(cmdline[cursor-1]) )
+         cursor--;
+
+       memmove(cmdline+cursor, cmdline+prevcursor, len-prevcursor+1);
+       len -= (cursor-prevcursor);
+       redraw = 1;
+      }
+      break;
+
+    case KEY_LEFT:
+    case KEY_CTRL('B'):
+      if ( cursor ) {
+       cursor--;
+       redraw = 1;
+      }
+      break;
+
+    case KEY_RIGHT:
+    case KEY_CTRL('F'):
+      if ( cursor < len ) {
+       putchar(cmdline[cursor++]);
+      }
+      break;
+
+    case KEY_CTRL('K'):
+      if ( cursor < len ) {
+       cmdline[len = cursor] = '\0';
+       redraw = 1;
+      }
+      break;
+
+    case KEY_HOME:
+    case KEY_CTRL('A'):
+      if ( cursor ) {
+       cursor = 0;
+       redraw = 1;
+      }
+      break;
+
+    case KEY_END:
+    case KEY_CTRL('E'):
+      if ( cursor != len ) {
+       cursor = len;
+       redraw = 1;
+      }
+      break;
+
+    default:
+      if ( key >= ' ' && key <= 0xFF && len < MAX_CMDLINE_LEN-1 ) {
+       if ( cursor == len ) {
+         cmdline[len] = key;
+         cmdline[++len] = '\0';
+         cursor++;
+         putchar(key);
+         prev_len++;
+       } else {
+         memmove(cmdline+cursor+1, cmdline+cursor, len-cursor+1);
+         cmdline[cursor++] = key;
+         len++;
+         redraw = 1;
+       }
+      }
+      break;
+    }
+  }
+}
+
+static void
+clear_screen(void)
+{
+  printf("\033e\033%%@\033)0\033(B%s\033[?25l\033[2J", menu_attrib->screen);
+}
+
+static inline int
+shift_is_held(void)
+{
+  uint8_t shift_bits = *(uint8_t *)0x417;
+
+  return !!(shift_bits & 0x5d);        /* Caps/Scroll/Alt/Shift */
+}
+
+static const char *
+run_menu(void)
+{
+  int key;
+  int done = 0;
+  volatile int entry = defentry, prev_entry = -1;
+  int top = 0, prev_top = -1;
+  int clear = 1, to_clear;
+  const char *cmdline = NULL;
+  volatile clock_t key_timeout, timeout_left, this_timeout;
+
+  /* Note: for both key_timeout and timeout == 0 means no limit */
+  timeout_left = key_timeout = timeout;
+
+  /* If we're in shiftkey mode, exit immediately unless a shift key is pressed */
+  if ( shiftkey && !shift_is_held() ) {
+    return menu_entries[defentry].cmdline;
+  }
+
+  /* Handle both local and global timeout */
+  if ( setjmp(timeout_jump) ) {
+    entry = defentry;
+
+    if ( top < 0 || top < entry-MENU_ROWS+1 )
+      top = max(0, entry-MENU_ROWS+1);
+    else if ( top > entry || top > max(0,nentries-MENU_ROWS) )
+      top = min(entry, max(0,nentries-MENU_ROWS));
+
+    draw_menu(ontimeout ? -1 : entry, top, 1);
+    cmdline = ontimeout ? ontimeout : menu_entries[entry].cmdline;
+    done = 1;
+  }
+
+  while ( !done ) {
+    if ( entry < 0 )
+      entry = 0;
+    else if ( entry >= nentries )
+      entry = nentries-1;
+
+    if ( top < 0 || top < entry-MENU_ROWS+1 )
+      top = max(0, entry-MENU_ROWS+1);
+    else if ( top > entry || top > max(0,nentries-MENU_ROWS) )
+      top = min(entry, max(0,nentries-MENU_ROWS));
+
+    /* Start with a clear screen */
+    if ( clear ) {
+      /* Clear and redraw whole screen */
+      /* Enable ASCII on G0 and DEC VT on G1; do it in this order
+        to avoid confusing the Linux console */
+      clear_screen();
+      clear = 0;
+      prev_entry = prev_top = -1;
+    }
+
+    if ( top != prev_top ) {
+      draw_menu(entry, top, 1);
+    } else if ( entry != prev_entry ) {
+      draw_row(prev_entry-top+4, entry, top, 0, 0);
+      draw_row(entry-top+4, entry, top, 0, 0);
+    }
+
+    prev_entry = entry;  prev_top = top;
+
+    /* Cursor movement cancels timeout */
+    if ( entry != defentry )
+      key_timeout = 0;
+
+    if ( key_timeout ) {
+      int tol = timeout_left/CLK_TCK;
+      int nc = snprintf(NULL, 0, " Automatic boot in %d seconds ", tol);
+      printf("\033[%d;%dH%s Automatic boot in %s%d%s seconds ",
+            TIMEOUT_ROW, 1+((WIDTH-nc)>>1),
+            menu_attrib->timeout_msg,
+            menu_attrib->timeout, tol,
+            menu_attrib->timeout_msg);
+      to_clear = 1;
+    } else {
+      to_clear = 0;
+    }
+
+    this_timeout = min(min(key_timeout, timeout_left), CLK_TCK);
+    key = mygetkey(this_timeout);
+
+    if ( key != KEY_NONE ) {
+      timeout_left = key_timeout;
+      if ( to_clear )
+       printf("\033[%d;1H%s\033[K", TIMEOUT_ROW, menu_attrib->screen);
+    }
+
+    switch ( key ) {
+    case KEY_NONE:             /* Timeout */
+      /* This is somewhat hacky, but this at least lets the user
+        know what's going on, and still deals with "phantom inputs"
+        e.g. on serial ports.
+
+        Warning: a timeout will boot the default entry without any
+        password! */
+      if ( key_timeout ) {
+       if ( timeout_left <= this_timeout )
+         longjmp(timeout_jump, 1);
+
+       timeout_left -= this_timeout;
+      }
+      break;
+
+    case KEY_CTRL('L'):
+      clear = 1;
+      break;
+
+    case KEY_ENTER:
+    case KEY_CTRL('J'):
+      key_timeout = 0;         /* Cancels timeout */
+      if ( menu_entries[entry].passwd ) {
+       clear = 1;
+       done = ask_passwd(menu_entries[entry].passwd);
+      } else {
+       done = 1;
+      }
+      cmdline = menu_entries[entry].cmdline;
+      break;
+
+    case KEY_UP:
+    case KEY_CTRL('P'):
+      if ( entry > 0 ) {
+       entry--;
+       if ( entry < top )
+         top -= MENU_ROWS;
+      }
+      break;
+
+    case KEY_DOWN:
+    case KEY_CTRL('N'):
+      if ( entry < nentries-1 ) {
+       entry++;
+       if ( entry >= top+MENU_ROWS )
+         top += MENU_ROWS;
+      }
+      break;
+
+    case KEY_PGUP:
+    case KEY_LEFT:
+    case KEY_CTRL('B'):
+    case '<':
+      entry -= MENU_ROWS;
+      top   -= MENU_ROWS;
+      break;
+
+    case KEY_PGDN:
+    case KEY_RIGHT:
+    case KEY_CTRL('F'):
+    case '>':
+    case ' ':
+      entry += MENU_ROWS;
+      top   += MENU_ROWS;
+      break;
+
+    case '-':
+      entry--;
+      top--;
+      break;
+
+    case '+':
+      entry++;
+      top++;
+      break;
+
+    case KEY_CTRL('A'):
+    case KEY_HOME:
+      top = entry = 0;
+      break;
+
+    case KEY_CTRL('E'):
+    case KEY_END:
+      entry = nentries - 1;
+      top = max(0, nentries-MENU_ROWS);
+      break;
+
+    case KEY_TAB:
+      if ( allowedit ) {
+       int ok = 1;
+
+       key_timeout = 0;        /* Cancels timeout */
+       draw_row(entry-top+4, -1, top, 0, 0);
+
+       if ( menu_master_passwd ) {
+         ok = ask_passwd(NULL);
+         clear_screen();
+         draw_menu(-1, top, 0);
+       } else {
+         /* Erase [Tab] message */
+         printf("\033[%d;1H%s\033[K", TABMSG_ROW, menu_attrib->screen);
+       }
+
+       if ( ok ) {
+         cmdline = edit_cmdline(menu_entries[entry].cmdline, top);
+         done = !!cmdline;
+         clear = 1;            /* In case we hit [Esc] and done is null */
+       } else {
+         draw_row(entry-top+4, entry, top, 0, 0);
+       }
+      }
+      break;
+    case KEY_CTRL('C'):                /* Ctrl-C */
+    case KEY_ESC:              /* Esc */
+      if ( allowedit ) {
+       done = 1;
+       clear = 1;
+       key_timeout = 0;
+
+       draw_row(entry-top+4, -1, top, 0, 0);
+
+       if ( menu_master_passwd )
+         done = ask_passwd(NULL);
+      }
+      break;
+    default:
+      if ( key > 0 && key < 0xFF ) {
+       key &= ~0x20;           /* Upper case */
+       if ( menu_hotkeys[key] ) {
+         key_timeout = 0;
+         entry = menu_hotkeys[key] - menu_entries;
+         /* Should we commit at this point? */
+       }
+      }
+      break;
+    }
+  }
+
+  printf("\033[?25h");         /* Show cursor */
+
+  /* Return the label name so localboot and ipappend work */
+  return cmdline;
+}
+
+
+static void
+execute(const char *cmdline)
+{
+#ifdef __COM32__
+  com32sys_t ireg;
+  const char *p;
+  char *q = __com32.cs_bounce;
+  const char *kernel, *args;
+
+  memset(&ireg, 0, sizeof ireg);
+
+  kernel = q;
+  p = cmdline;
+  while ( *p && !my_isspace(*p) ) {
+    *q++ = *p++;
+  }
+  *q++ = '\0';
+
+  args = q;
+  while ( *p && my_isspace(*p) )
+    p++;
+
+  strcpy(q, p);
+
+  if ( !strcmp(kernel, ".localboot") ) {
+    ireg.eax.w[0] = 0x0014;    /* Local boot */
+    ireg.edx.w[0] = strtoul(args, NULL, 0);
+  } else {
+    ireg.eax.w[0] = 0x0016;    /* Run kernel image */
+    ireg.esi.w[0] = OFFS(kernel);
+    ireg.ds       = SEG(kernel);
+    ireg.ebx.w[0] = OFFS(args);
+    ireg.es       = SEG(args);
+    /* ireg.ecx.l    = 0; */           /* We do ipappend "manually" */
+    /* ireg.edx.l    = 0; */
+  }
+
+  __intcall(0x22, &ireg, NULL);
+
+  /* If this returns, something went bad; return to menu */
+#else
+  /* For testing... */
+  printf("\n\033[0m>>> %s\n", cmdline);
+  exit(0);
+#endif
+}
+
+static void __attribute__((destructor)) console_cleanup(void)
+{
+  /* For the serial console, be nice and clean up */
+  fputs("\033[0m\033[20l", stdout);
+}
+
+int vesacon_load_background(const char *filename);
+
+int main(int argc, char *argv[])
+{
+  const char *cmdline;
+
+  (void)argc;
+
+  /* console_ansi_raw(); */
+  openconsole(&dev_rawcon_r, &dev_vesaserial_w);
+  vesacon_load_background("stacy.png");
+
+  parse_config(argv[1]);
+
+  if ( !nentries ) {
+    fputs("No LABEL entries found in configuration file!\n", stdout);
+    return 1;                  /* Error! */
+  }
+
+  for(;;) {
+    cmdline = run_menu();
+
+    printf("\033[?25h\033[%d;1H\033[0m", END_ROW);
+    if ( cmdline )
+      execute(cmdline);
+    else
+      return 0;                        /* Exit */
+  }
+}
index 6d148ae..159a55d 100644 (file)
@@ -18,7 +18,7 @@ int main(void)
   
   for (attr = 0; attr < 256; attr++) {
     snprintf(attr_buf, sizeof attr_buf, " [%02X] ", attr);
-    vesacon_write_at(row, col, attr_buf, attr, attr & 3);
+    __vesacon_write_at(row, col, attr_buf, attr, attr & 3);
     row++;
     if (row >= 29) {
       row = 0;