/*++ 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 #include 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); } } } }