Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1169 lines
24 KiB

/*++
Copyright (c) 1993 Microsoft Corporation
Module Name:
spkbd.c
Abstract:
Text setup keyboard support routines.
Author:
Ted Miller (tedm) 30-July-1993
Revision History:
--*/
#include "spprecmp.h"
#pragma hdrstop
#include <kbd.h>
#include <ntddkbd.h>
PKBDTABLES KeyboardTable;
HANDLE hKeyboard;
BOOLEAN KeyboardInitialized = FALSE;
BOOLEAN KbdLayoutInitialized = FALSE;
USHORT CurrentLEDs;
//
// Globals for async I/O calls
//
KEYBOARD_INDICATOR_PARAMETERS asyncKbdParams;
IO_STATUS_BLOCK asyncIoStatusBlock;
#define MAX_KEYBOARD_ITEMS 10
VOID
spkbdApcProcedure(
IN PVOID ApcContext,
IN PIO_STATUS_BLOCK IoStatusBlock,
IN ULONG Reserved
);
VOID
spkbdSetLEDs(
VOID
);
VOID
SpkbdInitialize(
VOID
);
VOID
SpkbdTerminate(
VOID
);
VOID
SpkbdLoadLayoutDll(
IN PVOID SifHandle,
IN PWSTR Directory
);
ULONG
SpkbdGetKeypress(
VOID
);
BOOLEAN
SpkbdIsKeyWaiting(
VOID
);
VOID
SpkbdDrain(
VOID
);
//
// Buffer for one character.
//
volatile ULONG KbdNextChar;
//
// The following are used in async calls to NtReadFile and so
// cannot be on the stack.
//
IO_STATUS_BLOCK IoStatusKeyboard;
KEYBOARD_INPUT_DATA KeyboardInputData[MAX_KEYBOARD_ITEMS];
LARGE_INTEGER DontCareLargeInteger;
//
// Current state of shift, control, alt keys.
//
USHORT ModifierBits = 0;
#define START_KEYBOARD_READ() \
\
ZwReadFile( \
hKeyboard, \
NULL, \
spkbdApcProcedure, \
NULL, \
&IoStatusKeyboard, \
KeyboardInputData, \
sizeof(KeyboardInputData), \
&DontCareLargeInteger, \
NULL \
)
VOID
SpInputInitialize(
VOID
)
/*++
Routine Description:
Initialize all input support. This includes
- opening the serial port and checking for a terminal.
- opening the keyboard device.
Arguments:
None.
Return Value:
None. Does not return if not successful.
--*/
{
SpkbdInitialize();
SpTermInitialize();
}
VOID
SpInputTerminate(
VOID
)
/*++
Routine Description:
Terminate all input support. This includes
- closing the serial port.
- closing the keyboard device.
Arguments:
None.
Return Value:
None.
--*/
{
SpkbdTerminate();
SpTermTerminate();
}
VOID
SpInputLoadLayoutDll(
IN PVOID SifHandle,
IN PWSTR Directory
)
{
SpkbdLoadLayoutDll(SifHandle, Directory);
}
ULONG
SpInputGetKeypress(
VOID
)
/*++
Routine Description:
Wait for a keypress and return it to the caller.
The return value will be an ASCII value (ie, not a scan code).
Arguments:
None.
Return Value:
ASCII value.
--*/
{
ULONG Tmp;
//
// If we are in upgrade graphics mode then
// switch to textmode
//
SpvidSwitchToTextmode();
while (TRUE) {
if (SpTermIsKeyWaiting()) {
Tmp = SpTermGetKeypress();
if (Tmp != 0) {
return Tmp;
}
}
if (SpkbdIsKeyWaiting()) {
return SpkbdGetKeypress();
}
}
}
BOOLEAN
SpInputIsKeyWaiting(
VOID
)
/*++
Routine Description:
Tell the caller if a keypress is waiting to be fetched by
a call to SpInputGetKeypress().
Arguments:
None.
Return Value:
TRUE is key waiting; FALSE otherwise.
--*/
{
return (SpTermIsKeyWaiting() || SpkbdIsKeyWaiting());
}
VOID
SpInputDrain(
VOID
)
{
SpTermDrain();
SpkbdDrain();
}
//
//
// Below here are all the functions for keyboard operations...
//
//
VOID
SpkbdInitialize(
VOID
)
/*++
Routine Description:
Initialize keyboard support. This includes
- opening the keyboard device.
Arguments:
None.
Return Value:
None. Does not return if not successful.
--*/
{
NTSTATUS Status;
OBJECT_ATTRIBUTES Attributes;
IO_STATUS_BLOCK IoStatusBlock;
UNICODE_STRING UnicodeString;
ASSERT(!KeyboardInitialized);
if(KeyboardInitialized) {
return;
}
//
// Open the keyboard.
//
RtlInitUnicodeString(&UnicodeString,DD_KEYBOARD_DEVICE_NAME_U L"0");
InitializeObjectAttributes(
&Attributes,
&UnicodeString,
OBJ_CASE_INSENSITIVE,
NULL,
NULL
);
Status = ZwCreateFile(
&hKeyboard,
GENERIC_READ | SYNCHRONIZE | FILE_READ_ATTRIBUTES,
&Attributes,
&IoStatusBlock,
NULL, // allocation size
FILE_ATTRIBUTE_NORMAL,
0, // no sharing
FILE_OPEN,
0,
NULL, // no EAs
0
);
if(!NT_SUCCESS(Status)) {
KdPrintEx((DPFLTR_SETUP_ID, DPFLTR_ERROR_LEVEL, "SETUP: NtOpenFile of " DD_KEYBOARD_DEVICE_NAME "0 returns %lx\n",Status));
SpFatalKbdError(SP_SCRN_KBD_OPEN_FAILED);
}
//
// Initialize LEDs.
//
//
// No NEC98 has NumLock and NumLock LED.
// Num keys must be act as Numlock alternated keys.
//
CurrentLEDs = (!IsNEC_98 ? 0 : KEYBOARD_NUM_LOCK_ON);
spkbdSetLEDs();
KeyboardInitialized = TRUE;
//
// Do not initialize keyboard input yet because we don't have a layout.
//
}
VOID
SpkbdTerminate(
VOID
)
/*++
Routine Description:
Terminate keyboard support. This includes
- closing the keyboard device.
Arguments:
None.
Return Value:
None.
--*/
{
NTSTATUS Status;
ASSERT(KeyboardInitialized);
if(KeyboardInitialized) {
Status = ZwClose(hKeyboard);
if(!NT_SUCCESS(Status)) {
KdPrintEx((DPFLTR_SETUP_ID, DPFLTR_ERROR_LEVEL, "SETUP: Unable to close " DD_KEYBOARD_DEVICE_NAME "0 (status = %lx)\n",Status));
}
KeyboardInitialized = FALSE;
}
}
VOID
SpkbdLoadLayoutDll(
IN PVOID SifHandle,
IN PWSTR Directory
)
{
PWSTR p,LayoutDll;
NTSTATUS Status;
//
// Determine layout name.
//
if(p = SpGetSectionKeyIndex(SifHandle,SIF_NLS,SIF_DEFAULTLAYOUT,1)) {
LayoutDll = p;
} else {
p = SpGetSectionKeyIndex(SifHandle,SIF_NLS,SIF_DEFAULTLAYOUT,0);
if(!p) {
SpFatalSifError(SifHandle,SIF_NLS,SIF_DEFAULTLAYOUT,0,0);
}
LayoutDll = SpGetSectionKeyIndex(SifHandle,SIF_KEYBOARDLAYOUTFILES,p,0);
if(!LayoutDll) {
SpFatalSifError(SifHandle,SIF_KEYBOARDLAYOUTFILES,p,0,0);
}
}
SpDisplayStatusText(SP_STAT_LOADING_KBD_LAYOUT,DEFAULT_STATUS_ATTRIBUTE,LayoutDll);
//
// Bugcheck if we can't load the layout dll, because we won't be able
// to put up a screen and ask the user to hit f3, etc.
//
Status = SpLoadKbdLayoutDll(Directory,LayoutDll,&KeyboardTable);
if(!NT_SUCCESS(Status)) {
KdPrintEx((DPFLTR_SETUP_ID, DPFLTR_ERROR_LEVEL, "SETUP: Unable to load layout dll %ws (%lx)\n",LayoutDll,Status));
SpFatalKbdError(SP_SCRN_KBD_LAYOUT_FAILED, LayoutDll);
}
//
// Erase status text line.
//
SpDisplayStatusOptions(DEFAULT_STATUS_ATTRIBUTE,0);
//
// Now that we've loaded the layout, we can start accepting keyboard input.
//
START_KEYBOARD_READ();
KbdLayoutInitialized = TRUE;
}
ULONG
SpkbdGetKeypress(
VOID
)
/*++
Routine Description:
Wait for a keypress and return it to the caller.
The return value will be an ASCII value (ie, not a scan code).
Arguments:
None.
Return Value:
ASCII value.
--*/
{
ULONG k;
//
// Shouldn't be calling this until we have loaded a keyboard layout.
//
ASSERT(KeyboardTable);
//
// Wait for the user to press a key.
//
while(!KbdNextChar) {
;
}
k = KbdNextChar;
KbdNextChar = 0;
return(k);
}
BOOLEAN
SpkbdIsKeyWaiting(
VOID
)
/*++
Routine Description:
Tell the caller if a keypress is waiting to be fetched by
a call to SpkbdGetKeypress().
Arguments:
None.
Return Value:
TRUE is key waiting; FALSE otherwise.
--*/
{
//
// Shouldn't be calling this until we have loaded a keyboard layout.
//
ASSERT(KeyboardTable);
return((BOOLEAN)(KbdNextChar != 0));
}
VOID
SpkbdDrain(
VOID
)
/*++
Routine Description:
Drain the keyboard buffer, throwing away any keystrokes
in the buffer waiting to be fetched.
Arguments:
None.
Return Value:
TRUE is key waiting; FALSE otherwise.
--*/
{
ASSERT(KeyboardTable);
KbdNextChar = 0;
}
ULONG
spkbdScanCodeToChar(
IN UCHAR Prefix,
IN USHORT ScanCode,
IN BOOLEAN Break
);
VOID
spkbdApcProcedure(
IN PVOID ApcContext,
IN PIO_STATUS_BLOCK IoStatusBlock,
IN ULONG Reserved
)
/*++
Routine Description:
Async Procedure Call routine for keyboard reads. The I/O
system will call this routine when the keyboard class driver
wants to return some data to us.
Arguments:
Return Value:
None.
--*/
{
UCHAR bPrefix;
PKEYBOARD_INPUT_DATA pkei;
ULONG k;
UNREFERENCED_PARAMETER(ApcContext);
UNREFERENCED_PARAMETER(Reserved);
for(pkei = KeyboardInputData;
(PUCHAR)pkei < (PUCHAR)KeyboardInputData + IoStatusBlock->Information;
pkei++)
{
if(pkei->Flags & KEY_E0) {
bPrefix = 0xE0;
} else if (pkei->Flags & KEY_E1) {
bPrefix = 0xE1;
} else {
bPrefix = 0;
}
k = spkbdScanCodeToChar(
bPrefix,
pkei->MakeCode,
(BOOLEAN)((pkei->Flags & KEY_BREAK) != 0)
);
if(k) {
KbdNextChar = k;
}
}
//
// Keyboard might have been terminated.
//
if(KeyboardInitialized) {
START_KEYBOARD_READ();
}
}
WCHAR SavedDeadChar = 0;
UCHAR AltNumpadAccum = 0;
struct {
BYTE CursorKey;
BYTE NumberKey;
BYTE Value;
} NumpadCursorToNumber[] = { { VK_INSERT, VK_NUMPAD0, 0 },
{ VK_END , VK_NUMPAD1, 1 },
{ VK_DOWN , VK_NUMPAD2, 2 },
{ VK_NEXT , VK_NUMPAD3, 3 },
{ VK_LEFT , VK_NUMPAD4, 4 },
{ VK_CLEAR , VK_NUMPAD5, 5 },
{ VK_RIGHT , VK_NUMPAD6, 6 },
{ VK_HOME , VK_NUMPAD7, 7 },
{ VK_UP , VK_NUMPAD8, 8 },
{ VK_PRIOR , VK_NUMPAD9, 9 },
{ VK_DELETE, VK_DECIMAL, 10 }, // no value.
{ 0 , 0 , 0 }
};
ULONG
spkbdScanCodeToChar(
IN UCHAR Prefix,
IN USHORT ScanCode,
IN BOOLEAN Break
)
{
USHORT VKey = 0;
PVSC_VK VscVk;
PVK_TO_WCHAR_TABLE pVKT;
PVK_TO_WCHARS1 pVK;
USHORT Modifier;
USHORT ModBits,ModNum;
WCHAR deadChar;
ScanCode &= 0x7f;
if(Prefix == 0) {
if(ScanCode < KeyboardTable->bMaxVSCtoVK) {
//
// Index directly into non-prefix scan code table.
//
VKey = KeyboardTable->pusVSCtoVK[ScanCode];
if(VKey == 0) {
KdPrintEx((DPFLTR_SETUP_ID, DPFLTR_ERROR_LEVEL, "SETUP: Unknown scan code 0x%x\n",ScanCode));
return (0);
}
} else {
KdPrintEx((DPFLTR_SETUP_ID, DPFLTR_ERROR_LEVEL, "SETUP: Unknown scan code 0x%x\n",ScanCode));
return(0);
}
} else {
if(Prefix == 0xe0) {
//
// Ignore the SHIFT keystrokes generated by the hardware
//
if((ScanCode == SCANCODE_LSHIFT) || (ScanCode == SCANCODE_RSHIFT)) {
return(0);
}
VscVk = KeyboardTable->pVSCtoVK_E0;
} else if(Prefix == 0xe1) {
VscVk = KeyboardTable->pVSCtoVK_E1;
} else {
KdPrintEx((DPFLTR_SETUP_ID, DPFLTR_ERROR_LEVEL, "SETUP: Unknown keyboard scan prefix 0x%x\n",Prefix));
return(0);
}
while(VscVk->Vk) {
if(VscVk->Vsc == ScanCode) {
VKey = VscVk->Vk;
break;
}
VscVk++;
}
if(VKey == 0) {
KdPrintEx((DPFLTR_SETUP_ID, DPFLTR_ERROR_LEVEL, "SETUP: Unknown keyboard scan prefix/code 0x%x/0x%x\n",Prefix,ScanCode));
return(0);
}
}
//
// VirtualKey --> modifier bits. This translation is also
// mapped out in the pCharModifiers field in the keyboard layout
// table but that seems redundant.
//
Modifier = 0;
switch(VKey & 0xff) {
case VK_LSHIFT:
case VK_RSHIFT:
Modifier = KBDSHIFT;
break;
case VK_LCONTROL:
case VK_RCONTROL:
Modifier = KBDCTRL;
break;
case VK_RMENU:
//
// AltGr ==> control+alt modifier.
//
if(KeyboardTable->fLocaleFlags & KLLF_ALTGR) {
Modifier = KBDCTRL;
}
// fall through
case VK_LMENU:
Modifier |= KBDALT;
break;
}
if(Break) {
//
// Key is being released.
// If it's not a modifer, ignore it.
//
if(!Modifier) {
return(0);
}
//
// Key being released is a modifier.
//
ModifierBits &= ~Modifier;
//
// If it's ALT going up and we have a numpad key being entered,
// return it.
//
if((Modifier & KBDALT) && AltNumpadAccum) {
WCHAR UnicodeChar;
RtlOemToUnicodeN(
&UnicodeChar,
sizeof(UnicodeChar),
NULL,
&AltNumpadAccum,
1
);
AltNumpadAccum = 0;
return(UnicodeChar);
}
return(0);
} else {
if(Modifier) {
//
// Key is being pressed and is a modifier.
//
ModifierBits |= Modifier;
//
// If ALT is going down, reset alt+numpad value.
//
if(Modifier & KBDALT) {
AltNumpadAccum = 0;
}
return(0);
}
}
//
// If we get here, we've got a non-modifier key being made (pressed).
// If the previous key was a dead key, the user gets only
// one try to get a valid second half.
//
deadChar = SavedDeadChar;
SavedDeadChar = 0;
//
// Special processing if the key is a numeric keypad key.
//
if(VKey & KBDNUMPAD) {
int i;
for(i=0; NumpadCursorToNumber[i].CursorKey; i++) {
if(NumpadCursorToNumber[i].CursorKey == (BYTE)VKey) {
//
// Key is a numeric keypad key. If ALT (and only alt) is down,
// then we have an alt+numpad code being entered.
//
if(((ModifierBits & ~KBDALT) == 0) && (NumpadCursorToNumber[i].Value < 10)) {
AltNumpadAccum = (AltNumpadAccum * 10) + NumpadCursorToNumber[i].Value;
}
//
// If numlock is on, translate the key from cursor movement
// to a number key.
//
if(CurrentLEDs & KEYBOARD_NUM_LOCK_ON) {
VKey = NumpadCursorToNumber[i].NumberKey;
}
break;
}
}
}
//
// We need to filter out keystrokes that we know are not part of any
// character set here.
//
if((!deadChar)) {
switch(VKey & 0xff) {
case VK_CAPITAL:
if(CurrentLEDs & KEYBOARD_CAPS_LOCK_ON) {
CurrentLEDs &= ~KEYBOARD_CAPS_LOCK_ON;
} else {
CurrentLEDs |= KEYBOARD_CAPS_LOCK_ON;
}
spkbdSetLEDs();
return(0);
case VK_NUMLOCK:
if(CurrentLEDs & KEYBOARD_NUM_LOCK_ON) {
CurrentLEDs &= ~KEYBOARD_NUM_LOCK_ON;
} else {
CurrentLEDs |= KEYBOARD_NUM_LOCK_ON;
}
spkbdSetLEDs();
return(0);
case VK_PRIOR:
return(KEY_PAGEUP);
case VK_NEXT:
return(KEY_PAGEDOWN);
case VK_UP:
return(KEY_UP);
case VK_DOWN:
return(KEY_DOWN);
case VK_LEFT:
return(KEY_LEFT);
case VK_RIGHT:
return(KEY_RIGHT);
case VK_HOME:
return(KEY_HOME);
case VK_END:
return(KEY_END);
case VK_INSERT:
return(KEY_INSERT);
case VK_DELETE:
return(KEY_DELETE);
case VK_F1:
return(KEY_F1);
case VK_F2:
return(KEY_F2);
case VK_F3:
return(KEY_F3);
case VK_F4:
return(KEY_F4);
case VK_F5:
return(KEY_F5);
case VK_F6:
return(KEY_F6);
case VK_F7:
return(KEY_F7);
case VK_F8:
return(KEY_F8);
case VK_F9:
return(KEY_F9);
case VK_F10:
return(KEY_F10);
case VK_F11:
return(KEY_F11);
case VK_F12:
return(KEY_F12);
}
}
//
// We think the character is probably a 'real' character.
// Scan through all the shift-state tables until a matching Virtual
// Key is found.
//
for(pVKT = KeyboardTable->pVkToWcharTable; pVKT->pVkToWchars; pVKT++) {
pVK = pVKT->pVkToWchars;
while(pVK->VirtualKey) {
if(pVK->VirtualKey == (BYTE)VKey) {
goto VK_Found;
}
pVK = (PVK_TO_WCHARS1)((PBYTE)pVK + pVKT->cbSize);
}
}
//
// Key is not valid with requested modifiers.
//
return(0);
VK_Found:
ModBits = ModifierBits;
//
// If CapsLock affects this key and it is on: toggle SHIFT state
// only if no other state is on.
// (CapsLock doesn't affect SHIFT state if Ctrl or Alt are down).
//
if((pVK->Attributes & CAPLOK) && ((ModBits & ~KBDSHIFT) == 0)
&& (CurrentLEDs & KEYBOARD_CAPS_LOCK_ON))
{
ModBits ^= KBDSHIFT;
}
//
// Get the modification number.
//
if(ModBits > KeyboardTable->pCharModifiers->wMaxModBits) {
return(0); // invalid keystroke
}
ModNum = KeyboardTable->pCharModifiers->ModNumber[ModBits];
if(ModNum == SHFT_INVALID) {
return(0); // invalid keystroke
}
if(ModNum >= pVKT->nModifications) {
//
// Key is not valid with current modifiers.
// Could still be a control char that we can convert directly.
//
if((ModBits == KBDCTRL) || (ModBits == (KBDCTRL | KBDSHIFT))) {
if(((UCHAR)VKey >= 'A') && ((UCHAR)VKey <= 'Z')) {
return((ULONG)VKey & 0x1f);
}
}
return(0); // invalid keystroke
}
if(pVK->wch[ModNum] == WCH_NONE) {
return(0);
}
if((pVK->wch[ModNum] == WCH_DEAD)) {
if(!deadChar) {
//
// Remember the current dead character, whose value follows
// the current entry in the modifier mapping table.
//
SavedDeadChar = ((PVK_TO_WCHARS1)((PUCHAR)pVK + pVKT->cbSize))->wch[ModNum];
}
return(0);
}
//
// The keyboard layout table contains some dead key mappings.
// If previous key was a dead key, attempt to compose it with the
// current character by scanning the keyboard layout table for a match.
//
if(deadChar) {
ULONG chr;
PDEADKEY DeadKeyEntry;
chr = MAKELONG(pVK->wch[ModNum],deadChar);
if(DeadKeyEntry = KeyboardTable->pDeadKey) {
while(DeadKeyEntry->dwBoth) {
if(DeadKeyEntry->dwBoth == chr) {
//
// Got a match. Return the composed character.
//
return((ULONG)DeadKeyEntry->wchComposed);
}
DeadKeyEntry++;
}
}
//
// If we get here, then the previous key was a dead char,
// but the current key could not be composed with it.
// So return nothing. Note that the dead key has been forgotten.
//
return(0);
}
return((ULONG)pVK->wch[ModNum]);
}
VOID
spkbdSetLEDs(
VOID
)
{
asyncKbdParams.UnitId = 0;
asyncKbdParams.LedFlags = CurrentLEDs;
ZwDeviceIoControlFile(
hKeyboard,
NULL,
NULL,
NULL,
&asyncIoStatusBlock,
IOCTL_KEYBOARD_SET_INDICATORS,
&asyncKbdParams,
sizeof(asyncKbdParams),
NULL,
0
);
}
VOID
SpSelectAndLoadLayoutDll(
IN PWSTR Directory,
IN PVOID SifHandle,
IN BOOLEAN ShowStatus
)
/*++
Routine Description:
Allows the user to select a keyboard layout DLL and loads it.
Arguments:
Directory - The setup startup directory
SifHandle - Handle to txtsetup.sif
ShowStatus - Whether status should be displayed or not
Return Value:
None
--*/
{
ULONG SelectedLayout;
ULONG DefLayout = -1;
PWSTR TempPtr = 0;
PWSTR LayoutDll = 0;
NTSTATUS Status;
//
// Get the default layout (index)
//
TempPtr = SpGetSectionKeyIndex(SifHandle, SIF_NLS, SIF_DEFAULTLAYOUT, 0);
if(!TempPtr) {
SpFatalSifError(SifHandle, SIF_NLS, SIF_DEFAULTLAYOUT, 0, 0);
}
DefLayout = SpGetKeyIndex(SifHandle, SIF_KEYBOARDLAYOUTDESC, TempPtr);
if(DefLayout == -1) {
SpFatalSifError(SifHandle, SIF_NLS, SIF_DEFAULTLAYOUT, 0, 0);
}
SelectedLayout = -1;
//
// Let the user select the layout which he wants
//
if (SpSelectSectionItem(SifHandle, SIF_KEYBOARDLAYOUTDESC,
SP_SELECT_KBDLAYOUT, DefLayout, &SelectedLayout)) {
//
// Load the layout if its not already loaded
//
if (DefLayout != SelectedLayout) {
//
// get the key
//
TempPtr = SpGetKeyName(SifHandle, SIF_KEYBOARDLAYOUTDESC, SelectedLayout);
if (TempPtr) {
//
// get the KDB layout dll name
//
LayoutDll = SpGetSectionKeyIndex(SifHandle, SIF_KEYBOARDLAYOUTFILES,
TempPtr, 0);
if (LayoutDll) {
if (ShowStatus) {
SpDisplayStatusText(SP_STAT_LOADING_KBD_LAYOUT,
DEFAULT_STATUS_ATTRIBUTE, LayoutDll);
}
//
// Load the new KDB layout dll
//
Status = SpLoadKbdLayoutDll(Directory, LayoutDll, &KeyboardTable);
}
else
Status = STATUS_INVALID_PARAMETER;
if (!NT_SUCCESS(Status)) {
CLEAR_ENTIRE_SCREEN();
SpFatalKbdError(SP_SCRN_KBD_LAYOUT_FAILED, LayoutDll);
} else {
if (ShowStatus) {
//
// Erase status text line.
//
SpDisplayStatusOptions(DEFAULT_STATUS_ATTRIBUTE, 0);
}
//
// Now that we've loaded the layout,
// we can start accepting keyboard input.
//
START_KEYBOARD_READ();
KbdLayoutInitialized = TRUE;
}
} else {
CLEAR_ENTIRE_SCREEN();
SpFatalSifError(SifHandle, SIF_KEYBOARDLAYOUTDESC, 0, 0, 0);
}
}
}
}