Saturday 26 January 2013

Using the SHIFT key to modify the behaviour of a grub4dos menu entry


If you have linux in your grub4dos menu.lst, you often need two entries, one for normal mode and one for safe mode so that you can boot on 'difficult' hardware.

This solution allows you to just have one menu entry but you can hold down SHIFT when you hit Enter to use safe mode parameters.

For this you need the bios utility on your drive (in this case in the root of the boot drive).You could also test for Ctrl or Alt and some other keys.

Note some lines are long and run over - use cut and paste!

title Run Linux\nPress SHIFT+ENTER for safe mode 
/bios int=0x16 eax=0x00000200 > (md)0x300+1
cat --skip=12 --length=2 (md)0x300+1 | set /a n=0x > nul 
set /a n=%n% & 0x03 > nul && if %n%>=1 echo SHIFT PRESSED! 
#LShift=01,RShift=02,CTRL=04,ALT=08,SCROLL=10,NUM=20,CAPS=40,INS=80 
#set /a n=%n% & 0x04 > nul && if %n%>=1 echo CTRL PRESSED! 
set sf=
if %n%>=1 set sf=acpi=off irqpoll noapic noapm nodma nomce nolapic nosmp 
kernel /casper/vmlinuz file=/cdrom/preseed/ubuntu.seed boot=casper %sf% splash 
initrd /casper/initrd.img



Bios Int 16 ah=2 reference here

NOTE: was...
/bios int=0x16 eax=0x00000200 > (md)0x300+1 && cat --skip=12 --length=2 (md)0x300+1 | set /a n=0x > nul

the 2nd half of the line is only executed if the BIOS returns non-zero. So && should not be used in this case.

8 comments:

  1. Thanks, this works almost perfectly. The only change I would recommend is to add a "set n=0" at the beginning of every entry, before the "/bios..." line, to reset shift status, otherwise it would consider shift always being pressed, even if it wasn't, after the first time it is pressed.

    ReplyDelete
  2. this only detects the current status - it does not set it

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. Yes I know, what I meant was this:

    In my test case, for simplicity, I tried loading different menus from the main menu, loading either "menu1" if shift wasn't pressed when pressing enter or "menu2" if shift was pressed. It correctly loaded "menu1" when not pressing shift and loaded "menu2" when shift was pressed, but after pressing shift once, thus loading "menu2", then going back to the main menu and trying the same, it would always load "menu2", regardless of whether I was pressing shift or not, because the variable %n% was already set.

    What I meant was you should re-set n=0 at the beginning of the entry to prevent this. Maybe that wasn't supposed to happen but it was happening.

    ReplyDelete
  5. Sorry, I still don't understand.???
    In my code example, n is always set to the value returned by the BIOS before the value of n is tested. Can you email me your test menus so I can try to figure out what is happening?

    ReplyDelete
  6. My test case is very simple. Three menus, one main menu with the shift-switch entry, and two menus each with an entry that loads back the main menu. I'm testing with the latest grub4dos (2013-02-02).

    Grub loads main.lst:
    title Test Menu Entry
    /bios int=0x16 eax=0x00000200 > (md)0x300+1 && cat --skip=12 --length=2 (md)0x300+1 | set /a n=0x > nul
    set /a n=%n% & 0x03 > nul && if %n%>=1 echo SHIFT PRESSED!
    set sf=menu1
    if %n%>=1 set sf=menu2
    configfile /%sf%.lst

    menu1.lst:
    title In Menu 1 Back to Main Menu
    configfile /main.lst

    menu2.lst:
    title In Menu 2 Back to Main Menu
    configfile /main.lst

    Steps:
    1) Hit enter at "Test Menu Entry", menu1.lst loads as expected.
    2) Go back to the main menu, hit shift+enter, successfully loads menu2.lst.
    3) Go back to the main menu, after this, hitting enter again on "Test Menu Entry", without shift, it will still load menu2.lst and echo "SHIFT PRESSED!" as if shift was pressed when it wasn't.

    To fix this behavior I changed main.lst to:
    title Test Menu Entry
    set n=0
    /bios int=0x16 etc...

    With this it correctly re-assumes shift not being pressed every time, after having pressed it once.

    ReplyDelete
  7. OK - I see the problem - it works under QEMU OK but not on real hardware! The issue is with the && in the first line! I have seen this behaviour before. To fix the issue, simply split the line into two like this:

    /bios int=0x16 eax=0x00000200 > (md)0x300+1
    cat --skip=12 --length=2 (md)0x300+1 | set /a n=0x > nul

    ReplyDelete
  8. Yep, that fixed it. Thanks for looking into it, now it works perfectly.

    ReplyDelete