/*
  Name:             keybd_shift
  Copyright:        Copyright (C) SIL International.
  Documentation:    
  Description:      
  Create Date:      11 Dec 2009

  Modified Date:    9 Aug 2015
  Authors:          mcdurdin
  Related Files:    
  Dependencies:     

  Bugs:             
  Todo:             
  Notes:            
  History:          11 Dec 2009 - mcdurdin - I934 - x64 - Initial version
                    12 Mar 2010 - mcdurdin - I934 - x64 - Complete
                    12 Mar 2010 - mcdurdin - I2229 - Remove hints and warnings
                    31 Dec 2014 - mcdurdin - I4548 - V9.0 - When Alt is down, release of Ctrl, Shift is not detectable within TIP in some languages
                    09 Aug 2015 - mcdurdin - I4844 - Tidy up PostDummyKeyEvent calls
*/
#include "pch.h"
#include "kbd.h"

/**
  do_keybd_event adds a keyboard event into the keyboard event queue.

  Parameters: pInputs  array of INPUT structures which we will fill with our key events.
              n        pointer to current index into pInput, which we increment for each key
                       event we add

  This function mimics the keybd_event Windows API function, but instead of posting the event
  to the system keyboard event queue, it appends it to our internal event queue in pInputs.
  This allows us to send an atomic set of key events with SendInput.

  In this function, we map back the extended modifier keys. Now, the history of the PC keyboard
  means that this is a little weird. The original PC XT keyboard had Left and Right Shift keys,
  but only a single Ctrl and a single Alt key. This means, that the shift keys were each assigned
  their own scan code. However, Windows maps both keys to the same virtual key code (VK_SHIFT) for
  most purposes.

  When the Right Ctrl and Right Alt keys were introduced, they were differentiated from the original
  keys not by separate scan codes, but by an extended prefix byte (E0), together with the original
  scan code. Again, Windows does not differentiate with virtual keys (VK_CONTROL and VK_MENU).

  This means, to check for Left vs Right Shift, you need to test the scan code (2A and 36) 
  respectively. However, to test for Left vs Right Ctrl/Alt, you need to test the Extended bit in
  the flags for the message.

  There are a few places where VK_LSHIFT, VK_RSHIFT, VK_LCONTROL, VK_RCONTROL, VK_LMENU, and 
  VK_RMENU can be used, such as GetKeyState or MapVirtualKey. However, these virtual key codes
  should never be emitted and will never be received as a key event.

  To add an additional wrinkle to an already wrinkly story, Keyman (ab)uses the scan code property
  of key events to identify key events that it has synthesized, as opposed to those generated by
  the user or by another app (e.g. Remote Desktop scenarios). Instead of using the correct scan 
  code, it will stuff the key event with a 0xFF scan code, and replace that in its GetMessage 
  so the end application gets a look at the correct scan code. However, this will obviously not 
  work with Right Shift, which is differentiated from Left Shift _only_ by scan code. Fortunately,
  we can afford to not care about this -- it means a little extra flag setting behind the scenes
  as the right shift key is released and re-pressed during key events, but nothing too dramatic.
*/
void do_keybd_event(LPINPUT pInputs, int *n, BYTE vk, BYTE scan, DWORD flags, ULONG_PTR extraInfo) {   // I4548
  INPUT input;
  input.type = INPUT_KEYBOARD;

  switch(vk) {
  case VK_RCONTROL:
    flags |= KEYEVENTF_EXTENDEDKEY;
    /*fallthrough*/
  case VK_LCONTROL:
    vk = VK_CONTROL;
    break;

  case VK_RMENU:
    flags |= KEYEVENTF_EXTENDEDKEY;
    /*fallthrough*/
  case VK_LMENU:
    vk = VK_MENU;
    break;

  case VK_RSHIFT:
    scan = SCANCODE_RSHIFT; // from kbd.h
    /*fallthrough*/
  case VK_LSHIFT:
    vk = VK_SHIFT;
    break;
  }

  input.ki.wVk = vk;
  input.ki.wScan = scan;
  input.ki.dwFlags = flags;
  input.ki.time = 0;
  input.ki.dwExtraInfo = extraInfo;
  SendDebugMessageFormat(0, sdmAIDefault, 0, "do_keybd_event(n=%d, vk=%s, scan=%x, flags=%x)", *n, Debug_VirtualKey(vk), scan, flags);

  pInputs[*n] = input;
  (*n)++;
}

/**
  keybd_sendprefix pushes a dummy keystroke into the keyboard event queue.

  Parameters: pInputs  array of INPUT structures which we will fill with our key events.
              n        pointer to current index into pInput, which we increment for each key
                       event we add

  The keybd_sendprefix call is needed, for example, if the user presses Alt+F which matches a rule 
  in the current Keyman keyboard, because without the dummy prefix key, faking the release of the 
  Alt key will activate the mainmenu of the current application.
*/
void keybd_sendprefix(LPINPUT pInputs, int *n)
{
  SendDebugMessageFormat(0, sdmAIDefault, 0, "keybd_sendprefix: sending prefix down+up");
  do_keybd_event(pInputs, n, (BYTE) Globals::get_vk_prefix(), SCAN_FLAG_KEYMAN_KEY_EVENT, 0, 0);   // I4548   // I4844
  do_keybd_event(pInputs, n, (BYTE) Globals::get_vk_prefix(), SCAN_FLAG_KEYMAN_KEY_EVENT, KEYEVENTF_KEYUP, 0);   // I4548   // I4844
}

/**
  keybd_shift_release records the current keyboard state and then releases any modifier
  keys. If a modifier key must be released, it first sends a dummy prefix key to prevent
  isolated modifier key actions such as Alt opening up a menu.

  Parameters: pInputs  array of INPUT structures which we will fill with our key events.
              n        pointer to current index into pInput, which we increment for each key
                       event we add
              kbd      pointer to keyboard state (256 byte array), in which we will store
                       the initial modifier state for later restoration by keybd_shift_reset
*/
void keybd_shift_release(LPINPUT pInputs, int *n, LPBYTE const kbd) {
  const BYTE modifiers[6] = { VK_LMENU, VK_RMENU, VK_LCONTROL, VK_RCONTROL, VK_LSHIFT, VK_RSHIFT };
  BOOL hasSentPrefix = FALSE;

  for (int i = 0; i < _countof(modifiers); i++) {
    if (kbd[modifiers[i]] & 0x80) {
      if (!hasSentPrefix) {
        keybd_sendprefix(pInputs, n);
        hasSentPrefix = TRUE;
      }
      SendDebugMessageFormat(0, sdmAIDefault, 0, "keybd_shift_release: sending keyup vkey=%s", Debug_VirtualKey(modifiers[i]));
      do_keybd_event(pInputs, n, modifiers[i], SCAN_FLAG_KEYMAN_KEY_EVENT, KEYEVENTF_KEYUP, 0);
    }
  }
}

/**
  keybd_shift_reset returns the modifiers to their original pressed state and, if any modifier
  key presses are emitted, emits also a dummy 'prefix' keystroke in order to prevent default 
  modifier actions such as Alt opening up a menu.

  Parameters: pInputs  array of INPUT structures which we will fill with our key events.
              n        pointer to current index into pInput, which we increment for each key
                       event we add
              kbd      pointer to keyboard state (256 byte array), previously set by 
                       keybd_shift_release
*/
void keybd_shift_reset(LPINPUT pInputs, int *n, LPBYTE const kbd) {
  const BYTE modifiers[6] = { VK_LMENU, VK_RMENU, VK_LCONTROL, VK_RCONTROL, VK_LSHIFT, VK_RSHIFT };
  BOOL needsPrefix = FALSE;

  for (int i = 0; i < _countof(modifiers); i++) {
    if (kbd[modifiers[i]] & 0x80) {
      SendDebugMessageFormat(0, sdmAIDefault, 0, "keybd_shift_reset: sending keydown vkey=%s", Debug_VirtualKey(modifiers[i]));
      do_keybd_event(pInputs, n, modifiers[i], SCAN_FLAG_KEYMAN_KEY_EVENT, 0, 0);
      needsPrefix = TRUE;
    }
  }

  if (needsPrefix) {
    keybd_sendprefix(pInputs, n);
  }
}

/**
  keybd_shift evaluates the current keyboard modifier state and queues key events in order to
  initially set modifiers to "up" and, after the output key events are queued, resets the modifiers
  to their initial state.

  Parameters: pInputs  array of INPUT structures which we will fill with our key events.
              n        pointer to current index into pInput, which we increment for each key
                       event we add
              isReset  are we clearing or resetting the modifier state?
              kbd      pointer to keyboard state (256 byte array) that owner must maintain but
                       that we will fill

  There must be enough space in pInputs to contain 6 x up + 6 x down + 2 prefix-down + 2 prefix-up event = 16 events,
  to support both the clear and reset calls.
*/
void keybd_shift(LPINPUT pInputs, int *n, BOOL isReset, LPBYTE const kbd) {
  if (isReset) {
    keybd_shift_reset(pInputs, n, kbd);
  } else {
    keybd_shift_release(pInputs, n, kbd);
  }
}

