/** @file
  Virtural Keyboard Control Engine

  Copyright (c) 2012 - 2020, Intel Corporation. All rights reserved.<BR>
  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include "VirtualKeyboard.h"

/**
  Notify all keyboards pressed specific key need to hidden Virtual
  Keyboard.

  @param[in] KeyData          Keyboard pressed specific key data

  @retval EFI_SUCCESS         Success for the function.
  @retval EFI_NOT_FOUND       Not found Virtual Keyboard protocol.

**/
EFI_STATUS
EFIAPI
VkNotifyKeyCallback (
  IN EFI_KEY_DATA *KeyData
  )
{
  EFI_STATUS Status;
  VK_CONTEXT *VkContext;
  UINTN      MaxColumn;
  UINTN      MaxRow;
  UINT32     HorizontalPixel;
  UINT32     VerticalPixel;

  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkNotifyKeyCallback Start\n"));

  Status = gBS->LocateProtocol (&gEfiCallerIdGuid, NULL, (VOID **) &VkContext);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  gST->ConOut->QueryMode (
                 gST->ConOut,
                 gST->ConOut->Mode->Mode,
                 &MaxColumn,
                 &MaxRow
                 );
  HorizontalPixel = VkContext->GraphicsOutput->Mode->Info->HorizontalResolution;
  VerticalPixel   = VkContext->GraphicsOutput->Mode->Info->VerticalResolution;

  //
  // CursorRow and CursorColumn are started from 0, so need to add 2 not 1.
  //
  if (((UINTN)(gST->ConOut->Mode->CursorColumn * EFI_GLYPH_WIDTH) < VkContext->SimIconBackWidth) &&
      ((UINTN)((gST->ConOut->Mode->CursorRow + 2) * EFI_GLYPH_HEIGHT) > (VerticalPixel - VkContext->SimIconBackHeight))) {
    SaveVkIconBackgroundBltBuffer (VkContext, VkDisplayAttributeSimpleBottom);
  } else if (((UINTN)((gST->ConOut->Mode->CursorColumn + 2) * EFI_GLYPH_WIDTH) > (HorizontalPixel - VkContext->FullIconBackWidth)) &&
             ((UINTN)((gST->ConOut->Mode->CursorRow + 2) * EFI_GLYPH_HEIGHT) > (VerticalPixel - VkContext->FullIconBackHeight))){
    SaveVkIconBackgroundBltBuffer (VkContext, VkDisplayAttributeFullBottom);
  }

  //
  // Hide icon/keyboard to prevent screen scroll up.
  //
  if ((KeyData->Key.UnicodeChar         == CHAR_CARRIAGE_RETURN)    ||
      (KeyData->Key.ScanCode            == SCAN_ESC)                ||
      ((gST->ConOut->Mode->CursorColumn >= (INT32) (MaxColumn - 2)) &&
       (gST->ConOut->Mode->CursorRow    == (INT32) (MaxRow - 1)))) {
    HideVkBody (VkContext);
    HideVkIcon (VkContext);
    VkContext->IconReDrawCheck = 0;
  }

  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkNotifyKeyCallback End\n"));
  return EFI_SUCCESS;
}

/**
  Judge whether is a registed key.

  @param[in] RegsiteredData   A pointer to a buffer that is filled in with the keystroke
                              state data for the key that was registered.
  @param[in] InputData        A pointer to a buffer that is filled in with the keystroke
                              state data for the key that was pressed.

  @retval TRUE                Key be pressed matches a registered key.
  @retval FLASE               Match failed.

**/
BOOLEAN
IsKeyRegistered (
  IN EFI_KEY_DATA  *RegsiteredData,
  IN EFI_KEY_DATA  *InputData
  )
{
  ASSERT (RegsiteredData != NULL && InputData != NULL);

  if ((RegsiteredData->Key.ScanCode    != InputData->Key.ScanCode) ||
      (RegsiteredData->Key.UnicodeChar != InputData->Key.UnicodeChar)) {
    return FALSE;
  }

  //
  // Assume KeyShiftState/KeyToggleState = 0 in Registered key data means these state could be ignored.
  //
  if (RegsiteredData->KeyState.KeyShiftState != 0 &&
      RegsiteredData->KeyState.KeyShiftState != InputData->KeyState.KeyShiftState) {
    return FALSE;
  }
  if (RegsiteredData->KeyState.KeyToggleState != 0 &&
      RegsiteredData->KeyState.KeyToggleState != InputData->KeyState.KeyToggleState) {
    return FALSE;
  }

  return TRUE;
}

/**
  Pop the key from the keybuffer.

  @param[in, out] VkContext    Address of an VK_CONTEXT structure.
  @param[out]     KeyData      The EFI stand key infomation when code get the key will put
                               correct mapping key.

  @retval EFI_SUCCESS          The keystroke information was returned.
  @retval EFI_NOT_READY        The keystroke data is empty.

**/
EFI_STATUS
VkPopTheKey (
  IN OUT VK_CONTEXT   *VkContext,
     OUT EFI_KEY_DATA *KeyData OPTIONAL
  )
{
  EFI_STATUS Status;
  UINT8      IndexSt;
  UINT8      IndexEd;

  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkPopTheKey Start\n"));
  //
  // KeyBuffer
  // [         0          ]
  // [         1          ]<-*IndexSt/IndexEd
  // [         2          ]
  // [        ...         ]
  // [MAX_KEY_BUF_SIZE - 1]
  //
  Status  = EFI_SUCCESS;
  IndexSt = VkContext->KeyStartIndex;
  IndexEd = VkContext->KeyEndIndex;

  if (IndexEd == IndexSt) {
    DEBUG ((DEBUG_VK_KEYS, "ERROR - Keyboard buffer is empty.\n"));
    Status = EFI_NOT_READY;
    goto Error;
  }

  DEBUG ((DEBUG_VK_KEYS | DEBUG_INFO, "VkPopTheKey Unicode:    %08x\n", VkContext->Keybuffer[IndexSt].Key.UnicodeChar));
  DEBUG ((DEBUG_VK_KEYS | DEBUG_INFO, "VkPopTheKey ScanCode:   %08x\n", VkContext->Keybuffer[IndexSt].Key.ScanCode));
  DEBUG ((DEBUG_VK_KEYS | DEBUG_INFO, "VkPopTheKey ShiftState: %08x\n", VkContext->Keybuffer[IndexSt].KeyState.KeyShiftState));
  DEBUG ((DEBUG_VK_KEYS | DEBUG_INFO, "VkPopTheKey ToggleState:%08x\n", VkContext->Keybuffer[IndexSt].KeyState.KeyToggleState));

  if (KeyData != NULL) {
    *KeyData = VkContext->Keybuffer[IndexSt];
  }
  VkContext->KeyStartIndex = ++IndexSt % MAX_KEY_BUF_SIZE;

  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkPopTheKey Success, Status: %r\n", Status));
  goto End;

Error:
  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkPopTheKey Failed, Status: %r\n", Status));

End:
  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkPopTheKey End\n"));
  return Status;
}

/**
  Push the event mapping key index to the input key buffer.

  @param[in, out] VkContext    Address of an VK_CONTEXT structure.
  @param[in]      Unicode      The font's unicode number.

  @retval EFI_SUCCESS          The maping key index did put the key buffer.

**/
EFI_STATUS
VkPushTheKey (
  IN OUT VK_CONTEXT *VkContext,
  IN     UINT16     Unicode
  )
{
  UINT8        IndexSt;
  UINT8        IndexEd;
  EFI_KEY_DATA KeyData;
  EFI_STATUS   Status;
  LIST_ENTRY   *Link;
  VK_NOTIFY    *CurrentNotify;

  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkPushTheKey Start\n"));
  //
  // KeyBuffer
  // [         0          ]
  // [         1          ]<-IndexSt/*IndexEd
  // [         2          ]
  // [        ...         ]
  // [MAX_KEY_BUF_SIZE - 1]
  //
  IndexSt = VkContext->KeyStartIndex;
  IndexEd = VkContext->KeyEndIndex;

  Status = EFI_SUCCESS;

  //
  // Check Keyboard Buffer if is full that will pop the first key
  //
  if (VkContext->IsSupportPartialKey &&
    ((VkContext->KeyEndIndex + 2) % MAX_KEY_BUF_SIZE) == IndexSt) {
    VkPopTheKey (VkContext, NULL);
  }

  if (((VkContext->KeyEndIndex + 1) % MAX_KEY_BUF_SIZE) == IndexSt) {
    VkPopTheKey (VkContext, NULL);
  }

  KeyData.Key.ScanCode             = SCAN_NULL;
  KeyData.Key.UnicodeChar          = CHAR_NULL;
  KeyData.KeyState.KeyShiftState   = EFI_SHIFT_STATE_VALID;
  KeyData.KeyState.KeyToggleState  = VkContext->KeyToggleState;
  KeyData.KeyState.KeyToggleState &= ~EFI_CAPS_LOCK_ACTIVE;
  KeyData.KeyState.KeyToggleState |= VkContext->IsCapsLockFlag ? EFI_CAPS_LOCK_ACTIVE : 0;

  switch (Unicode) {
  case VkKeyShift:
    VkContext->IsShiftKeyFlag = !VkContext->IsShiftKeyFlag;
    if (VkContext->PageNumber >= VkPage2 && VkContext->PageNumber <= VkPage3) {
      VkContext->IsShiftKeyFlag ? VkContext->PageNumber++ : VkContext->PageNumber--;
    }
    VkContext->IsRedrawUpdateUI = TRUE;
    break;

  case VkKeyTwoPage:
    switch (VkContext->PageNumber) {
    case VkPage0:
    case VkPage1:
      VkContext->IsShiftKeyFlag = FALSE;
      VkContext->PageNumber     = VkContext->IsShiftKeyFlag ? VkPage3 : VkPage2;
      break;

    case VkPage2:
    case VkPage3:
      VkContext->IsShiftKeyFlag = FALSE;
      VkContext->PageNumber     = VkContext->IsCapsLockFlag ? VkPage1 : VkPage0;
      break;

    default:
      break;
    }
    VkContext->IsRedrawUpdateUI = TRUE;
    break;

  case VkKeyCapslock:
    VkContext->IsCapsLockFlag = !VkContext->IsCapsLockFlag;
    if (VkContext->PageNumber >= VkPage0 && VkContext->PageNumber <= VkPage1) {
      VkContext->IsCapsLockFlag ? VkContext->PageNumber++ : VkContext->PageNumber--;
    }
    KeyData.KeyState.KeyToggleState &= ~EFI_CAPS_LOCK_ACTIVE;
    KeyData.KeyState.KeyToggleState |= VkContext->IsCapsLockFlag ? EFI_CAPS_LOCK_ACTIVE : 0;
    VkContext->IsRedrawUpdateUI      = TRUE;
    break;

  default:
    if ((Unicode & VkKeyScanMask) != 0) {
      KeyData.Key.ScanCode    = Unicode & ~VkKeyScanMask;
    } else {
      KeyData.Key.UnicodeChar = Unicode;
    }
    break;
  }

  if (KeyData.Key.ScanCode == SCAN_NULL && KeyData.Key.UnicodeChar == CHAR_NULL) {
    if (!VkContext->IsSupportPartialKey) {
      goto End;
    }
  }

  for (Link = GetFirstNode (&VkContext->NotifyList); !IsNull (&VkContext->NotifyList, Link); Link = GetNextNode (&VkContext->NotifyList, Link)) {
    CurrentNotify = CR (Link, VK_NOTIFY, NotifyEntry, VK_NOTIFY_SIGNATURE);
    //
    // The key notification function needs to run at TPL_CALLBACK
    // while current TPL is TPL_NOTIFY. It will be invoked in
    // VkKeyNotifyProcessHandler() which runs at TPL_CALLBACK.
    //
    if (IsKeyRegistered (&CurrentNotify->KeyData, &KeyData)) {
      VkContext->Keybuffer[IndexEd] = KeyData;
      VkContext->KeyEndIndex        = ++IndexEd % MAX_KEY_BUF_SIZE;
      gBS->SignalEvent (VkContext->KeyNotifyProcessEvent);
    }
  }
  VkContext->Keybuffer[IndexEd] = KeyData;
  VkContext->KeyEndIndex        = ++IndexEd % MAX_KEY_BUF_SIZE;

  DEBUG ((DEBUG_VK_TIMER_ENTRY_EXIT, "VkPushTheKey Success, Status: %r\n", Status));
  goto End;

End:
  DEBUG ((DEBUG_VK_TIMER_ENTRY_EXIT, "VkPushTheKey End\n"));
  return Status;
}

/**
  Process key notify.

  @param[in] Event                 Indicates the event that invoke this function.
  @param[in] Context               Indicates the calling context.

**/
VOID
EFIAPI
VkKeyNotifyProcessHandler (
  IN  EFI_EVENT Event,
  IN  VOID      *Context
  )
{
  EFI_STATUS   Status;
  VK_CONTEXT   *VkContext;
  EFI_KEY_DATA KeyData;
  LIST_ENTRY   *Link;
  LIST_ENTRY   *NotifyList;
  VK_NOTIFY    *CurrentNotify;
  EFI_TPL      OldTpl;

  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyNotifyProcessHandler Start\n"));
  VkContext = (VK_CONTEXT *) Context;
  OldTpl = gBS->RaiseTPL (TPL_NOTIFY);

  //
  // Invoke notification functions.
  //
  NotifyList = &VkContext->NotifyList;
  Status = VkPopTheKey (VkContext, &KeyData);
  if (EFI_ERROR (Status)) {
    goto Error;
  }

  for (Link = GetFirstNode (NotifyList); !IsNull (NotifyList, Link); Link = GetNextNode (NotifyList, Link)) {
    CurrentNotify = CR (Link, VK_NOTIFY, NotifyEntry, VK_NOTIFY_SIGNATURE);
    if (IsKeyRegistered (&CurrentNotify->KeyData, &KeyData)) {
      CurrentNotify->KeyNotificationFn (&KeyData);
    }
  }

  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyNotifyProcessHandler Success, Status: %r\n", Status));
  goto End;

Error:
  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyNotifyProcessHandler Failed, Status: %r\n", Status));

End:
  gBS->RestoreTPL (OldTpl);
  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyNotifyProcessHandler End\n"));
}

/**
  Push the event mapping key index to the input key buffer.

  @param[in]  VkContext        Address of an VK_CONTEXT structure.
  @param[out] KeyItem          Key Item.
  @param[out] Index            Index of Key Item.
  @param[in]  TouchX           X position of finger touch.
  @param[in]  TouchY           Y position of finger touch.

  @retval TRUE                 Touch on Virtual Keyboard.
  @retval FALSE                Not touch on Virtual Keyboard.

**/
BOOLEAN
IsTouchVk (
  IN  VK_CONTEXT *VkContext,
  OUT VK_STRUCT  *KeyItem,
  OUT UINT32     *Index,
  IN  UINT32     TouchX,
  IN  UINT32     TouchY
  )
{
  for (*Index = 0; *Index < VkContext->NumOfKeysInfo; (*Index)++) {
    if (VkContext->KeyboardBodyPtr[*Index].DisStartX < TouchX &&
        VkContext->KeyboardBodyPtr[*Index].DisEndX   > TouchX &&
        VkContext->KeyboardBodyPtr[*Index].DisStartY < TouchY &&
        VkContext->KeyboardBodyPtr[*Index].DisEndY   > TouchY) {
      break;
    }
  }
  if (*Index != VkContext->NumOfKeysInfo) {
    *KeyItem = VkContext->KeyboardBodyPtr[*Index];
  }

  return *Index != VkContext->NumOfKeysInfo;
}

/**
  Call back function when ready to boot into OS.

  It will clear the Virtual Keyboard.

  @param[in] Event    Event whose notification function is being invoked.
  @param[in] Context  Pointer to the VkContext.

**/
VOID
EFIAPI
VkReadyToBootCallBack (
  IN EFI_EVENT Event,
  IN VOID      *Context
  )
{
  VK_CONTEXT                    *VkContext;
  VkContext = (VK_CONTEXT *)Context;

  HideVkBody (VkContext);
  VkContext->TargetKeyboardDisplay  = VkDisplayAttributeNone;
}

/**
  Timer event

  This routine is called at TPL_VK_SYNC.

  This routine polls for touch panel input.

  @param[in] Event    Event whose notification function is being invoked.
  @param[in] Context  Pointer to the notification function's context,
                      which is implementation-dependent. Context corresponds
                      to NotifyContext in CreateEventEx().

**/
VOID
EFIAPI
VkTimer (
  IN EFI_EVENT Event,
  IN VOID      *Context
  )
{
  EFI_ABSOLUTE_POINTER_PROTOCOL *AbsolutePointer;
  EFI_ABSOLUTE_POINTER_STATE    Point;
  EFI_STATUS                    Status;
  UINT32                        TouchX;
  UINT32                        TouchY;
  VK_CONTEXT                    *VkContext;
  UINT32                        Index;
  VK_STRUCT                     KeyItem;
  UINT32                        Font;

  DEBUG ((DEBUG_VK_TIMER_ENTRY_EXIT, "VkTimer Start\n"));
  VkContext = (VK_CONTEXT *)Context;

  //
  // Update keyboard UI layout
  //
  CheckIconCleared (VkContext);
  CheckScreenCleared (VkContext);
  CheckBackgroundChanged (VkContext);
  if (VkContext->IsRedrawUpdateUI) {
    HideVkBody (VkContext);
    DrawKeyboardLayout (VkContext);
    VkContext->IsRedrawUpdateUI = FALSE;
  }

  //
  // Error handle for invalid information
  //
  AbsolutePointer = VkContext->AbsolutePointer;
  Status = gBS->CheckEvent (AbsolutePointer->WaitForInput);
  if (EFI_ERROR (Status)) {
    if (Status != EFI_NOT_READY) {
      DEBUG ((DEBUG_VK_TIMER_ENTRY_EXIT | DEBUG_VK_POINTS, "ERROR - VkContext->AbsolutePointer->WaitForInput->CheckEvent failed! Status: %r\n", Status));
    }
    goto Error;
  }

  Status = AbsolutePointer->GetState (AbsolutePointer, &Point);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_VK_TIMER_ENTRY_EXIT | DEBUG_VK_POINTS, "ERROR - GetState failed, Status: %r\n", Status));
    goto Error;
  }

  if (VkContext->AbsolutePointer->Mode->AbsoluteMaxX <= Point.CurrentX) {
    DEBUG ((
      DEBUG_VK_TIMER_ENTRY_EXIT | DEBUG_VK_POINTS,
      "ERROR - X value exceeds maximum: X: 0x%016Lx, MaxX: 0x%016Lx\n",
      Point.CurrentX,
      VkContext->AbsolutePointer->Mode->AbsoluteMaxX
      ));
    Status = EFI_PROTOCOL_ERROR;
    goto Error;
  }
  if (VkContext->AbsolutePointer->Mode->AbsoluteMaxY <= Point.CurrentY) {
    DEBUG ((
      DEBUG_VK_TIMER_ENTRY_EXIT | DEBUG_VK_POINTS,
      "ERROR - Y value exceeds maximum: Y: 0x%016Lx, MaxY: 0x%016Lx\n",
      Point.CurrentY,
      VkContext->AbsolutePointer->Mode->AbsoluteMaxY
      ));
    Status = EFI_PROTOCOL_ERROR;
    goto Error;
  }

  //
  // Update the touch active status
  //
  VkContext->TouchActive = ((Point.ActiveButtons & EFI_ABSP_TouchActive) != 0) ? TRUE : FALSE;
  if (!VkContext->TouchActive) {
    VkContext->KeyPressed = FALSE;
  }

  //
  // On one Touch, multiple Reads happend and  this reads varying based on Key Press time.
  // Resulting in Multiple key press update on screen. This condition avoids KeyPressed skips
  // resulting due to faster key press and update only on valid key press.
  //
  if ((Point.CurrentX != VkContext->PreviousX) || (Point.CurrentY != VkContext->PreviousY)) {
     VkContext->KeyPressed = FALSE;
  }
  VkContext->PreviousX = Point.CurrentX;
  VkContext->PreviousY = Point.CurrentY;

  ConvertCoordinate (VkContext, Point, &TouchX, &TouchY);

  if (!VkContext->KeyPressed &&
      VkContext->TouchActive &&
      IsTouchVk (VkContext, &KeyItem, &Index, TouchX, TouchY)) {
    VkGetMappingFont (VkContext, KeyItem, &Font);
    DEBUG ((DEBUG_VK_TIMER_ENTRY_EXIT | DEBUG_INFO,                   "VK Touch event is trigger!\n" ));
    DEBUG ((DEBUG_VK_TIMER_ENTRY_EXIT | DEBUG_INFO | DEBUG_VK_POINTS, "TouchActive:            0x%04x  \n", VkContext->TouchActive));
    DEBUG ((DEBUG_VK_TIMER_ENTRY_EXIT | DEBUG_INFO | DEBUG_VK_POINTS, "CurrentKeyboardDisplay: 0x%04x  \n", VkContext->CurrentKeyboardDisplay));
    DEBUG ((DEBUG_VK_TIMER_ENTRY_EXIT | DEBUG_INFO | DEBUG_VK_POINTS, "X:                      0x%016Lx\n", Point.CurrentX));
    DEBUG ((DEBUG_VK_TIMER_ENTRY_EXIT | DEBUG_INFO | DEBUG_VK_POINTS, "Y:                      0x%016Lx\n", Point.CurrentY));
    DEBUG ((DEBUG_VK_TIMER_ENTRY_EXIT | DEBUG_INFO | DEBUG_VK_POINTS, "Z:                      0x%016Lx\n", Point.CurrentZ));
    DEBUG ((DEBUG_VK_TIMER_ENTRY_EXIT | DEBUG_INFO | DEBUG_VK_POINTS, "Buttons:                0x%08x  \n", Point.ActiveButtons));
    DEBUG ((DEBUG_VK_TIMER_ENTRY_EXIT | DEBUG_INFO | DEBUG_VK_POINTS, "MapKey Index:           0x%04x  \n", Index));
    DEBUG ((DEBUG_VK_TIMER_ENTRY_EXIT | DEBUG_INFO | DEBUG_VK_POINTS, "Key Unicode:            0x%08x  \n", Font));

    switch (Font) {
    //
    // Touch the small keyboard icon, show/hide the keyboard.
    //
    case VkKeyTypeMaximum:
      KeyboardLayoutHandler (VkContext, Index);
      break;

    //
    // Touch the key raw.
    //
    default:
      if (VkContext->CurrentKeyboardDisplay == VkDisplayAttributeNone) {
        break;
      }
      VkPushTheKey (VkContext, (UINT16) Font);
    }
    VkContext->KeyPressed = TRUE;
  }

  DEBUG ((DEBUG_VK_TIMER_ENTRY_EXIT, "VkTimer Success, Status: %r\n", Status));
  goto End;

Error:
  DEBUG ((DEBUG_VK_TIMER_ENTRY_EXIT, "VkTimer Failed, Status: %r\n", Status));

End:
  DEBUG ((DEBUG_VK_TIMER_ENTRY_EXIT, "VkTimer End\n"));
  return;
}

/**
  VkTouchWaitForKey - SignalEvent when the keybuffer has keys.

  @param[in] Event    Event whose notification function is being invoked.
  @param[in] Context  Pointer to the notification function's context,
                      which is implementation-dependent.  Context corresponds
                      to NotifyContext in CreateEventEx().

**/
VOID
EFIAPI
VkTouchWaitForKey (
  IN EFI_EVENT Event,
  IN VOID      *Context
  )
{
  VK_CONTEXT *VkContext;

  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkTouchWaitForKey Start\n"));
  VkContext = (VK_CONTEXT*) Context;

  if (VkContext->KeyStartIndex != VkContext->KeyEndIndex) {
    DEBUG ((DEBUG_VK_KEYS, "Signal VkTouchWaitForKey\n"));
    gBS->SignalEvent (Event);
  }

  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkTouchWaitForKey End\n"));
}

/**
  Get image file data from BIOS image through specific ImageId.

  @param[in] VkContext      Address of an VK_CONTEXT structure.
  @param[in] ImageId        Guid to get specific image file used.

  @retval EFI_IMAGE_INPUT   Image data on BIOS image.

**/
EFI_IMAGE_INPUT*
VkGetImage (
  IN VK_CONTEXT   *VkContext,
  IN EFI_IMAGE_ID ImageId
  )
{
  EFI_STATUS      Status;
  EFI_IMAGE_INPUT Image;
  EFI_IMAGE_INPUT *VkImage;


  Status = VkContext->HiiImageEx->GetImageEx (
                                    VkContext->HiiImageEx,
                                    VkContext->HiiHandle,
                                    ImageId,
                                    &Image
                                    );
  if (EFI_ERROR (Status)) {
    ASSERT_EFI_ERROR (Status);
  }

  VkImage = AllocateCopyPool (sizeof (EFI_IMAGE_INPUT), &Image);
  if (VkImage == NULL) {
    ASSERT_EFI_ERROR (EFI_OUT_OF_RESOURCES);
    return NULL;
  }

  return VkImage;
}

/**
  Dump partial debug message on VkContext.

  @param[in] VkContext        Address of an VK_CONTEXT structure.

**/
VOID
VkDumpContext (
  IN VK_CONTEXT *VkContext
  )
{
  DEBUG ((
    DEBUG_VK_ROUTINE_ENTRY_EXIT,
    "VkContext->Signature: 0x%016x\n",
    VkContext->Signature
    ));
  DEBUG ((
    DEBUG_VK_ROUTINE_ENTRY_EXIT,
    "VkContext->Controller: 0x%016x\n",
    VkContext->Controller
    ));
  DEBUG ((
    DEBUG_VK_ROUTINE_ENTRY_EXIT | DEBUG_VK_GRAPHICS_INFO,
    "VkContext->GraphicsOutput->Mode->MaxMode: 0x%08x\n",
    VkContext->GraphicsOutput->Mode->MaxMode
    ));
  DEBUG ((
    DEBUG_VK_ROUTINE_ENTRY_EXIT | DEBUG_VK_GRAPHICS_INFO,
    "VkContext->GraphicsOutput->Mode->Mode: 0x%08x\n",
    VkContext->GraphicsOutput->Mode->Mode
    ));
  DEBUG ((
    DEBUG_VK_ROUTINE_ENTRY_EXIT | DEBUG_VK_GRAPHICS_INFO,
    "VkContext->GraphicsOutput->Mode->Info->HorizontalResolution: 0x%08x\n",
    VkContext->GraphicsOutput->Mode->Info->HorizontalResolution
    ));
  DEBUG ((
    DEBUG_VK_ROUTINE_ENTRY_EXIT | DEBUG_VK_GRAPHICS_INFO,
    "VkContext->GraphicsOutput->Mode->Info->VerticalResolution: 0x%08x\n",
    VkContext->GraphicsOutput->Mode->Info->VerticalResolution
    ));
  DEBUG ((
    DEBUG_VK_ROUTINE_ENTRY_EXIT | DEBUG_VK_POINTS,
    "VkContext->AbsolutePointer->Mode X: 0x%016Lx - 0x%016Lx\n",
    VkContext->AbsolutePointer->Mode->AbsoluteMinX,
    VkContext->AbsolutePointer->Mode->AbsoluteMaxX
    ));
  DEBUG ((
    DEBUG_VK_ROUTINE_ENTRY_EXIT | DEBUG_VK_POINTS,
    "VkContext->AbsolutePointer->Mode Y: 0x%016Lx - 0x%016Lx\n",
    VkContext->AbsolutePointer->Mode->AbsoluteMinY,
    VkContext->AbsolutePointer->Mode->AbsoluteMaxY
    ));
  DEBUG ((
    DEBUG_VK_ROUTINE_ENTRY_EXIT | DEBUG_VK_POINTS,
    "VkContext->AbsolutePointer->Mode Z: 0x%016Lx - 0x%016Lx\n",
    VkContext->AbsolutePointer->Mode->AbsoluteMinZ,
    VkContext->AbsolutePointer->Mode->AbsoluteMaxZ
    ));
  DEBUG ((
    DEBUG_VK_ROUTINE_ENTRY_EXIT | DEBUG_VK_POINTS,
    "VkContext->AbsolutePointer->Mode->Attributes: 0x%08x\n",
    VkContext->AbsolutePointer->Mode->Attributes
    ));
}

/**
  Start the virtual keyboard driver

  This routine allocates the necessary resources for the driver.

  This routine is called by VirtualKeyboardDriverStart to complete the driver
  initialization.

  @param[in, out] VkContext        Address of an VK_CONTEXT structure
  @param[in]      Controller       Handle of device to work with.

  @retval EFI_SUCCESS              Driver API properly initialized

**/
EFI_STATUS
VkApiStart (
  IN OUT VK_CONTEXT *VkContext,
  IN EFI_HANDLE     Controller
  )
{
  EFI_STATUS                        Status;
  EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *SimpleEx;
  EFI_KEY_DATA                      KeyData;
  EFI_HANDLE                        NotifyHandle;
  EFI_EVENT                         ReadyToBootEvent;

  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkApiStart Start\n"));

  Status = gBS->CreateEvent (
                  EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  VkKeyNotifyProcessHandler,
                  VkContext,
                  &VkContext->KeyNotifyProcessEvent
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT | DEBUG_ERROR, "ERROR - Failed to create VkContext->KeyNotifyProcessEvent, Status: %r\n", Status));
    goto Error;
  }

  Status = gBS->CreateEvent (
                  EVT_NOTIFY_WAIT,
                  TPL_NOTIFY,
                  VkTouchWaitForKey,
                  VkContext,
                  &(VkContext->SimpleTextIn.WaitForKey)
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT | DEBUG_ERROR, "ERROR - Failed to create VkContext->SimpleTextIn.WaitForKey, Status: %r\n", Status));
    goto Error;
  }

  Status = gBS->CreateEvent (
                  EVT_NOTIFY_WAIT,
                  TPL_NOTIFY,
                  VkTouchWaitForKey,
                  VkContext,
                  &(VkContext->SimpleTextInEx.WaitForKeyEx)
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT | DEBUG_ERROR, "ERROR - Failed to create VkContext->SimpleTextInEx.WaitForKeyEx, Status %r\n", Status));
    goto Error;
  }

  Status = gBS->CreateEvent (
                  EVT_TIMER | EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  VkTimer,
                  (VOID *)VkContext,
                  &(VkContext->TimerEvent)
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT | DEBUG_ERROR, "ERROR - Failed to create the timer event, Status: %r\n", Status));
    goto Error;
  }

  Status = gBS->SetTimer (
                  VkContext->TimerEvent,
                  TimerPeriodic,
                  (UINT64) VK_POLL_INTERVAL
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT | DEBUG_ERROR, "ERROR - Failed to set the timer event, Status: %r\n", Status));
    goto Error;
  }

  //
  // Create event to clear keyboard before boot into OS.
  //
  Status = EfiCreateEventReadyToBootEx (
             TPL_CALLBACK,
             VkReadyToBootCallBack,
             (VOID *)VkContext,
             &ReadyToBootEvent
             );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT | DEBUG_ERROR, "ERROR - Failed to create ReadyToBootEvent, Status: %r\n", Status));
    goto Error;
  }

  Status = gBS->LocateProtocol (
                  &gEfiHiiImageExProtocolGuid,
                  NULL,
                  (VOID **) &VkContext->HiiImageEx
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT | DEBUG_ERROR, "ERROR - Failed to open HiiImageEx protocol, Status: %r\n", Status));
    goto Error;
  }

  //
  // Initialize VkContext
  //
  VkContext->Signature                          = VK_SIGNATURE;
  VkContext->Controller                         = Controller;
  VkContext->SimpleTextIn.Reset                 = VkKeyboardReset;
  VkContext->SimpleTextIn.ReadKeyStroke         = VkKeyboardReadKeyStroke;
  VkContext->SimpleTextInEx.Reset               = VkKeyboardResetEx;
  VkContext->SimpleTextInEx.ReadKeyStrokeEx     = VkKeyboardReadKeyStrokeEx;
  VkContext->SimpleTextInEx.SetState            = VkKeyboardSetState;
  VkContext->SimpleTextInEx.RegisterKeyNotify   = VkKeyboardRegisterKeyNotify;
  VkContext->SimpleTextInEx.UnregisterKeyNotify = VkKeyboardUnregisterKeyNotify;
  VkContext->IsIconShowed                       = FALSE;
  VkContext->IsBackgroundChanged                = FALSE;
  VkContext->PageNumber                         = 0;
  VkContext->CurrentKeyboardDisplay             = VkDisplayAttributeNone;
  VkContext->TargetKeyboardDisplay              = VkDisplayAttributeNone;
  VkContext->VkBodyBackgroundBltBuffer          = NULL;
  VkContext->VkBodyCompoundBltBuffer            = NULL;
  VkContext->VkBodyBltSize                      = 0;
  VkContext->VkBodyBltStartX                    = 0;
  VkContext->VkBodyBltStartY                    = 0;
  VkContext->VkBodyBltHeight                    = 0;
  VkContext->VkBodyBltWidth                     = 0;
  VkContext->IconBltBuffer                      = NULL;
  VkContext->IconBltSize                        = 0;
  VkContext->IconReDrawCheck                    = 0;
  VkContext->FullIconUpdatedFlag                = FALSE;
  VkContext->SimIconUpdatedFlag                 = FALSE;
  VkContext->KeyStartIndex                      = 0;
  VkContext->KeyEndIndex                        = 0;
  VkContext->KeyToggleState                     = EFI_TOGGLE_STATE_VALID;
  VkContext->SmallIcon                          = VkGetImage (VkContext, IMAGE_TOKEN (IMG_VK_SIMPLEICON));
  VkContext->FullIcon                           = VkGetImage (VkContext, IMAGE_TOKEN (IMG_VK_FULLICON));
  VkContext->SimKeyBody                         = VkGetImage (VkContext, IMAGE_TOKEN (IMG_VK_SIMPLEKEYBOARD));
  VkContext->DigKeyBody                         = VkGetImage (VkContext, IMAGE_TOKEN (IMG_VK_DIGITKEYBOARD));
  VkContext->CapLeKeyBody                       = VkGetImage (VkContext, IMAGE_TOKEN (IMG_VK_CAPITALLETTERKEYBOARD));

  InitializeListHead (&VkContext->NotifyList);

  Status = SetCharacterPosition (VkContext, 800, 600);
  ASSERT_EFI_ERROR (Status);

  Status = DrawKeyboardLayout (VkContext);
  ASSERT_EFI_ERROR (Status);

  Status = HideVkBody (VkContext);
  ASSERT_EFI_ERROR (Status);

  Status = HideVkIcon (VkContext);
  ASSERT_EFI_ERROR (Status);

  DEBUG_CODE (
    VkDumpContext (VkContext);
  );

  Status = gBS->HandleProtocol (
                  gST->ConsoleInHandle,
                  &gEfiSimpleTextInputExProtocolGuid,
                  (VOID **)&SimpleEx
                  );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT | DEBUG_ERROR, "ERROR - Failed to found ConIn Protocol, Status: %r\n", Status));
    goto Error;
  }

  KeyData.KeyState.KeyToggleState = 0;
  KeyData.KeyState.KeyShiftState  = 0;
  KeyData.Key.ScanCode            = SCAN_ESC;
  KeyData.Key.UnicodeChar         = CHAR_NULL;
  NotifyHandle                    = NULL;
  Status = SimpleEx->RegisterKeyNotify (
                       SimpleEx,
                       &KeyData,
                       VkNotifyKeyCallback,
                       &NotifyHandle
                       );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT | DEBUG_ERROR, "ERROR - Failed to register 'Esc', Status: %r\n", Status));
    goto Error;
  }

  KeyData.KeyState.KeyToggleState = 0;
  KeyData.KeyState.KeyShiftState  = 0;
  KeyData.Key.ScanCode            = SCAN_NULL;
  KeyData.Key.UnicodeChar         = CHAR_CARRIAGE_RETURN;
  Status = SimpleEx->RegisterKeyNotify (
                       SimpleEx,
                       &KeyData,
                       VkNotifyKeyCallback,
                       &NotifyHandle
                       );
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT | DEBUG_ERROR, "ERROR - Failed to register 'Enter', Status: %r\n", Status));
    goto Error;
  }

  for (KeyData.Key.UnicodeChar = L' '; KeyData.Key.UnicodeChar <= L'~'; KeyData.Key.UnicodeChar++) {
    Status = SimpleEx->RegisterKeyNotify (
                         SimpleEx,
                         &KeyData,
                         VkNotifyKeyCallback,
                         &NotifyHandle
                         );
    if (EFI_ERROR (Status)) {
      DEBUG ((
        DEBUG_VK_ROUTINE_ENTRY_EXIT | DEBUG_ERROR,
        "ERROR - Failed to register '%c', Status: %r\n",
        KeyData.Key.UnicodeChar,
        Status
        ));
      break;
    }
  }
  if (EFI_ERROR(Status)) {
    goto Error;
  }

  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkApiStart Success, Status: %r\n", Status));
  goto End;

Error:
  VkApiStop (VkContext);
  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkApiStart Failed, Status: %r\n", Status));

End:
  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkApiStart End\n"));
  return Status;
}

/**
  Stop the virtual keyboard driver

  This routine releases the resources allocated by VKApiStart.

  This routine is called by VirtualKeyboardDriverStop to initiate the driver
  shutdown.

  @param[in] VkContext        Address of an VK_CONTEXT structure

**/
VOID
VkApiStop (
  IN VK_CONTEXT *VkContext
  )
{
  EFI_STATUS Status;
  VK_NOTIFY  *NotifyNode;
  LIST_ENTRY *NotifyList;

  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkApiStop Start\n"));

  Status = gBS->SetTimer (VkContext->TimerEvent, TimerCancel, 0);
  ASSERT_EFI_ERROR (Status);

  if (VkContext->KeyNotifyProcessEvent != NULL) {
    Status = gBS->CloseEvent (VkContext->KeyNotifyProcessEvent);
    ASSERT_EFI_ERROR (Status);
    VkContext->KeyNotifyProcessEvent = NULL;
  }

  if (VkContext->TimerEvent != NULL) {
    Status = gBS->CloseEvent (VkContext->TimerEvent);
    ASSERT_EFI_ERROR (Status);
    VkContext->TimerEvent = NULL;
  }

  if (VkContext->SimpleTextIn.WaitForKey != NULL) {
    Status = gBS->CloseEvent (VkContext->SimpleTextIn.WaitForKey);
    ASSERT_EFI_ERROR (Status);
    VkContext->SimpleTextIn.WaitForKey = NULL;
  }

  if (VkContext->SimpleTextInEx.WaitForKeyEx != NULL) {
    Status = gBS->CloseEvent (VkContext->SimpleTextInEx.WaitForKeyEx);
    ASSERT_EFI_ERROR (Status);
    VkContext->SimpleTextInEx.WaitForKeyEx = NULL;
  }

  NotifyList = &VkContext->NotifyList;
  if (NotifyList != NULL) {
    while (!IsListEmpty (NotifyList)) {
      NotifyNode = CR (NotifyList->ForwardLink, VK_NOTIFY, NotifyEntry, VK_NOTIFY_SIGNATURE);
      RemoveEntryList (NotifyList->ForwardLink);
      gBS->FreePool (NotifyNode);
    }
  }

  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkApiStop End\n"));
}

/**
  Reset the input device and optionally run diagnostics

  @param[in] This                 Protocol instance pointer.
  @param[in] ExtendedVerification Driver may perform diagnostics on reset.

  @retval EFI_SUCCESS             The device was reset.
  @retval EFI_DEVICE_ERROR        The device is not functioning properly and could not be reset.

**/
EFI_STATUS
EFIAPI
VkKeyboardReset (
  IN EFI_SIMPLE_TEXT_INPUT_PROTOCOL *This,
  IN BOOLEAN                        ExtendedVerification
  )
{
  EFI_STATUS Status;

  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyboardReset Start\n"));
  Status = EFI_SUCCESS;
  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyboardReset End\n"));
  return Status;
}

/**
  Resets the input device hardware.

  The Reset() function resets the input device hardware. As part
  of initialization process, the firmware/device will make a quick
  but reasonable attempt to verify that the device is functioning.
  If the ExtendedVerification flag is TRUE the firmware may take
  an extended amount of time to verify the device is operating on
  reset. Otherwise the reset operation is to occur as quickly as
  possible. The hardware verification process is not defined by
  this specification and is left up to the platform firmware or
  driver to implement.

  @param[in] This                 A pointer to the EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL instance.

  @param[in] ExtendedVerification Indicates that the driver may perform a more exhaustive
                                  verification operation of the device during reset.

  @retval EFI_SUCCESS             The device was reset.
  @retval EFI_DEVICE_ERROR        The device is not functioning correctly and could not be reset.

**/
EFI_STATUS
EFIAPI
VkKeyboardResetEx (
  IN EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This,
  IN BOOLEAN                           ExtendedVerification
  )
{
  EFI_STATUS Status;
  VK_CONTEXT *VkContext;

  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyboardResetEx Start\n"));

  Status = EFI_SUCCESS;
  if (This == NULL) {
    Status = EFI_INVALID_PARAMETER;
    goto Error;
  }

  VkContext = VK_CONTEXT_FROM_SIMPLETEXTINEX_PROTOCOL (This);

  Status = VkContext->SimpleTextIn.Reset (&VkContext->SimpleTextIn, ExtendedVerification);
  if (EFI_ERROR (Status)) {
    DEBUG ((DEBUG_VK_KEYS, "ERROR - Failed to VK reset, Status: %r\n", Status));
    goto Error;
  }

  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyboardResetEx Success, Status: %r\n", Status));
  goto End;

Error:
  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyboardResetEx Failed, Status: %r\n", Status));

End:
  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyboardResetEx End\n"));
  return Status;
}

/**
  Reads the next keystroke from the input device. The WaitForKey Event can
  be used to test for existence of a keystroke via WaitForEvent () call.

  @param[in]  This         Protocol instance pointer.
  @param[out] Key          Driver may perform diagnostics on reset.

  @retval EFI_SUCCESS      The keystroke information was returned.
  @retval EFI_NOT_READY    There was no keystroke data available.
  @retval EFI_DEVICE_ERROR The keystroke information was not returned due to
                           hardware errors.

**/
EFI_STATUS
EFIAPI
VkKeyboardReadKeyStroke (
  IN  EFI_SIMPLE_TEXT_INPUT_PROTOCOL *This,
  OUT EFI_INPUT_KEY                  *Key
  )
{
  EFI_STATUS    Status;
  VK_CONTEXT    *VkContext;
  EFI_KEY_DATA  TmpKeyData;
  EFI_TPL       OldTpl;

  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyboardReadKeyStroke Start\n"));

  OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
  if (This == NULL || Key == NULL) {
    Status = EFI_INVALID_PARAMETER;
    goto Error;
  }

  VkContext = VK_CONTEXT_FROM_PROTOCOL (This);

  Status = VkKeyboardReadKeyStrokeEx (&VkContext->SimpleTextInEx, &TmpKeyData);
  if (EFI_ERROR (Status)) {
    goto Error;
  }
  *Key = TmpKeyData.Key;


  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyboardReadKeyStroke Success, Status: %r\n", Status));
  goto End;

Error:
  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyboardReadKeyStroke Failed, Status: %r\n", Status));

End:
  gBS->RestoreTPL (OldTpl);
  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyboardReadKeyStroke End\n"));
  return Status;
}

/**
  Reads the next keystroke from the input device.

  @param[in]  This               Protocol instance pointer.
  @param[out] KeyData            A pointer to a buffer that is filled in with the keystroke
                                 state data for the key that was pressed.

  @retval EFI_SUCCESS            The keystroke information was returned.
  @retval EFI_NOT_READY          There was no keystroke data available.
  @retval EFI_INVALID_PARAMETER  This or KeyData is NULL.

**/
EFI_STATUS
EFIAPI
VkKeyboardReadKeyStrokeEx (
  IN  EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This,
  OUT EFI_KEY_DATA                      *KeyData
  )
{
  VK_CONTEXT        *VkContext;
  EFI_STATUS        Status;
  EFI_TPL           OldTpl;
  EFI_KEY_DATA      TmpKeyData;

  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyboardReadKeyStrokeEx Start\n"));

  OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
  if (This == NULL || KeyData == NULL) {
    Status = EFI_INVALID_PARAMETER;
    goto Error;
  }

  VkContext = VK_CONTEXT_FROM_SIMPLETEXTINEX_PROTOCOL (This);

  while (TRUE) {
    Status = VkPopTheKey (VkContext, &TmpKeyData);
    if (EFI_ERROR (Status)) {
      DEBUG ((DEBUG_VK_KEYS, "ERROR - Failed to VkPopTheKey check whether keybuffer is empty, Status: %r\n", Status));
      goto Error;
    }
    if (TmpKeyData.Key.ScanCode != SCAN_NULL || TmpKeyData.Key.UnicodeChar != CHAR_NULL) {
      break;
    }
  }
  *KeyData = TmpKeyData;

  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyboardReadKeyStrokeEx Success, Status: %r\n", Status));
  goto End;

Error:
  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyboardReadKeyStrokeEx Failed, Status: %r\n", Status));

End:
  gBS->RestoreTPL (OldTpl);
  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyboardReadKeyStrokeEx End\n"));
  return Status;
}

/**
  Set certain state for the input device.

  @param[in] This                 Protocol instance pointer.
  @param[in] KeyToggleState       A pointer to the EFI_KEY_TOGGLE_STATE to set the
                                  state for the input device.

  @retval EFI_SUCCESS             The device state was set appropriately.
  @retval EFI_INVALID_PARAMETER   This or KeyToggleState is NULL.

**/
EFI_STATUS
EFIAPI
VkKeyboardSetState (
  IN EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This,
  IN EFI_KEY_TOGGLE_STATE              *KeyToggleState
  )
{
  EFI_STATUS Status;
  VK_CONTEXT *VkContext;

  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyboardSetState Start\n"));

  Status = EFI_SUCCESS;
  if (This == NULL || KeyToggleState == NULL) {
    Status = EFI_INVALID_PARAMETER;
    goto Error;
  }

  VkContext = VK_CONTEXT_FROM_SIMPLETEXTINEX_PROTOCOL (This);

  VkContext->KeyToggleState      = *KeyToggleState;
  VkContext->IsCapsLockFlag      = (*KeyToggleState & EFI_CAPS_LOCK_ACTIVE) == EFI_CAPS_LOCK_ACTIVE;
  VkContext->IsSupportPartialKey = (*KeyToggleState & EFI_KEY_STATE_EXPOSED) == EFI_KEY_STATE_EXPOSED;

  if (VkContext->PageNumber >= VkPage0 && VkContext->PageNumber <= VkPage1) {
    VkContext->PageNumber = VkContext->IsCapsLockFlag ? VkPage1 : VkPage0;
  }

  HideVkBody (VkContext);
  DrawKeyboardLayout (VkContext);

  DEBUG ((DEBUG_VK_KEYS | DEBUG_INFO, "VkContext->KeyToggleState:      %02x\n", VkContext->KeyToggleState));
  DEBUG ((DEBUG_VK_KEYS | DEBUG_INFO, "VkContext->IsCapsLockFlag:      %02x\n", VkContext->IsCapsLockFlag));
  DEBUG ((DEBUG_VK_KEYS | DEBUG_INFO, "VkContext->IsSupportPartialKey: %02x\n", VkContext->IsSupportPartialKey));

  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyboardSetState Success, Status: %r\n", Status));
  goto End;

Error:
  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyboardSetState Failed, Status: %r\n", Status));

End:
  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyboardSetState End\n"));
  return Status;
}

/**
  Register a notification function for a particular keystroke for the input device.

  @param[in]  This                        Protocol instance pointer.
  @param[in]  KeyData                     A pointer to a buffer that is filled in with the keystroke
                                          information data for the key that was pressed.
  @param[in]  KeyNotificationFunction     Points to the function to be called when the key
                                          sequence is typed specified by KeyData.
  @param[out] NotifyHandle                Points to the unique handle assigned to the registered notification.

  @retval EFI_SUCCESS                     The notification function was registered successfully.
  @retval EFI_OUT_OF_RESOURCES            Unable to allocate resources for necessary data structures.
  @retval EFI_INVALID_PARAMETER           KeyData or NotifyHandle or KeyNotificationFunction is NULL.

**/
EFI_STATUS
EFIAPI
VkKeyboardRegisterKeyNotify (
  IN  EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This,
  IN  EFI_KEY_DATA                      *KeyData,
  IN  EFI_KEY_NOTIFY_FUNCTION           KeyNotificationFunction,
  OUT EFI_HANDLE                        *NotifyHandle
  )
{
  EFI_STATUS Status;
  EFI_TPL    OldTpl;
  VK_CONTEXT *VkContext;
  LIST_ENTRY *Link;
  VK_NOTIFY  *CurrentNotify;
  VK_NOTIFY  *NewNotify;

  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyboardRegisterKeyNotify Start\n"));

  //
  // Enter critical section
  //
  OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
  Status = EFI_SUCCESS;
  if (This == NULL || KeyData == NULL || NotifyHandle == NULL || KeyNotificationFunction == NULL) {
    Status = EFI_INVALID_PARAMETER;
    goto Error;
  }
  VkContext = VK_CONTEXT_FROM_SIMPLETEXTINEX_PROTOCOL (This);


  //
  // Return EFI_SUCCESS if the (KeyData, NotificationFunction) is already registered.
  //
  for (Link = VkContext->NotifyList.ForwardLink; Link != &VkContext->NotifyList; Link = Link->ForwardLink) {
    CurrentNotify = CR (Link, VK_NOTIFY, NotifyEntry, VK_NOTIFY_SIGNATURE);
    if (IsKeyRegistered (&CurrentNotify->KeyData, KeyData)) {
      if (CurrentNotify->KeyNotificationFn == KeyNotificationFunction) {
        *NotifyHandle = CurrentNotify;
        Status = EFI_SUCCESS;
        goto End;
      }
    }
  }

  //
  // Allocate resource to save the notification function
  //
  NewNotify = (VK_NOTIFY *) AllocateZeroPool (sizeof (VK_NOTIFY));
  if (NewNotify == NULL) {
    Status = EFI_OUT_OF_RESOURCES;
    goto Error;
  }

  NewNotify->Signature         = VK_NOTIFY_SIGNATURE;
  NewNotify->KeyNotificationFn = KeyNotificationFunction;
  CopyMem (&NewNotify->KeyData, KeyData, sizeof (EFI_KEY_DATA));
  InsertTailList (&VkContext->NotifyList, &NewNotify->NotifyEntry);

  *NotifyHandle = NewNotify;
  Status        = EFI_SUCCESS;

  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyboardRegisterKeyNotify Success, Status: %r\n", Status));
  goto End;

Error:
  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyboardRegisterKeyNotify Failed, Status: %r\n", Status));

End:
  //
  // Leave critical section and return
  //
  gBS->RestoreTPL (OldTpl);
  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyboardRegisterKeyNotify End\n"));
  return Status;
}

/**
  Remove a registered notification function from a particular keystroke.

  @param[in] This                Protocol instance pointer.
  @param[in] NotificationHandle  The handle of the notification function being unregistered.

  @retval EFI_SUCCESS            The notification function was unregistered successfully.
  @retval EFI_INVALID_PARAMETER  The NotificationHandle is invalid

**/
EFI_STATUS
EFIAPI
VkKeyboardUnregisterKeyNotify (
  IN EFI_SIMPLE_TEXT_INPUT_EX_PROTOCOL *This,
  IN EFI_HANDLE                        NotificationHandle
  )
{
  EFI_STATUS Status;
  VK_CONTEXT *VkContext;
  EFI_TPL    OldTpl;
  LIST_ENTRY *Link;
  VK_NOTIFY  *CurrentNotify;
  BOOLEAN    IsFindNotifyHandle;

  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyboardUnregisterKeyNotify Start\n"));

  //
  // Enter critical section
  //
  OldTpl = gBS->RaiseTPL (TPL_NOTIFY);
  Status = EFI_SUCCESS;
  if (This == NULL || NotificationHandle == NULL) {
    Status = EFI_INVALID_PARAMETER;
    goto Error;
  }
  VkContext = VK_CONTEXT_FROM_SIMPLETEXTINEX_PROTOCOL (This);

  IsFindNotifyHandle = FALSE;
  for (Link = VkContext->NotifyList.ForwardLink; Link != &VkContext->NotifyList; Link = Link->ForwardLink) {
    CurrentNotify = CR (Link, VK_NOTIFY, NotifyEntry, VK_NOTIFY_SIGNATURE);
    if (CurrentNotify == NotificationHandle) {
      //
      // Remove the notification function from NotifyList and free resources
      //
      RemoveEntryList (&CurrentNotify->NotifyEntry);

      gBS->FreePool (CurrentNotify);
      IsFindNotifyHandle = TRUE;
      break;
    }
  }

  if (!IsFindNotifyHandle) {
    Status = EFI_INVALID_PARAMETER;
    goto Error;
  }

  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyboardUnregisterKeyNotify Success, Status: %r\n", Status));
  goto End;

Error:
  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyboardUnregisterKeyNotify Failed, Status: %r\n", Status));

End:
  //
  // Leave critical section and return
  //
  gBS->RestoreTPL (OldTpl);
  DEBUG ((DEBUG_VK_ROUTINE_ENTRY_EXIT, "VkKeyboardUnregisterKeyNotify End\n"));
  return Status;
}
