// // key.c // // Handles all keyboard input messages sent to the server. // // Copyright (C) 2001 Microsoft Corporation // // Author: a-devjen (Devin Jenson) // #include "apihandl.h" #include "tclient2.h" // Average number of characters per word #define AVG_CHARS_PER_WORD 6 // Max WPM before setting delay to 0 #define MAX_WORDS_PER_MIN 1000 // This macro calculates the delay between each character // (in milliseconds) according to the specified WPM #define CALC_DELAY_BETWEEN_CHARS(WPM) \ (60000 / (WPM * AVG_CHARS_PER_WORD)) // This constant simply defines the default delay // between each character according to the default // words per minute specified in TCLIENT2.H static const UINT DEFAULT_DELAY_BETWEEN_CHARS = CALC_DELAY_BETWEEN_CHARS(T2_DEFAULT_WORDS_PER_MIN); // // The Foucher Formula // (Defining MS. Per Char) // // 60000 // --------- // WPM * CPW // // Internal Helper Function Prototypes static LPARAM KeyGenParam(UINT Message, BOOL IsAltDown, int vKeyCode); static BOOL KeyCharToVirt(WCHAR KeyChar, int *vKeyCode, BOOL *RequiresShift); static BOOL KeyVirtToChar(int vKeyCode, WCHAR *KeyChar); static LPCSTR KeySendVirtMessage(HANDLE Connection, UINT Message, int vKeyCode); static LPCSTR KeySendCharMessage(HANDLE Connection, UINT Message, WCHAR KeyChar); // Macros to quick return the specified keystate #define ISALTDOWN ((TSAPIHANDLE *)Connection)->IsAltDown #define ISSHIFTDOWN ((TSAPIHANDLE *)Connection)->IsShiftDown #define ISCTRLDOWN ((TSAPIHANDLE *)Connection)->IsCtrlDown /* R - Specifies the repeat count for the current message. The value is the number of times the keystroke is autorepeated as a result of the user holding down the key. If the keystroke is held long enough, multiple messages are sent. However, the repeat count is not cumulative. This value is always 1 for WM_SYSKEYUP and WM_KEYUP. S - Specifies the scan code. The value depends on the original equipment manufacturer (OEM). E - Specifies whether the key is an extended key, such as the right-hand ALT and CTRL keys that appear on an enhanced 101- or 102-key keyboard. The value is 1 if it is an extended key; otherwise, it is 0. X - Reserved; do not use. C - Specifies the context code. The value is always 0 for a WM_KEYDOWN and WM_KEYUP message or if the message is posted to the active window because no window has the keyboard focus. For WM_SYSKEYDOWN and WM_SYSKEYUP, the value is 1 if the ALT key is down while the message is activated. P - Specifies the previous key state. The value is 1 if the key is down before the message is sent, or it is zero if the key is up. The value is always 1 for a WM_KEYUP or WM_SYSKEYUP message. T - Specifies the transition state. The value is always 0 for a WM_KEYDOWN or WM_SYSKEYDOWN message, and 1 for a WM_KEYUP or a WM_SYSKEYUP message. TPCXXXXESSSSSSSSRRRRRRRRRRRRRRRR 10987654321098765432109876543210 | | | | 30 20 10 0 */ // KeyGenParam // // Based on the specified information, this generates // a valid LPARAM as documented by Windows keyboard // messages. Quick documentation is provided above. // // Returns the generated LPARAM, no failure code. static LPARAM KeyGenParam( UINT Message, BOOL IsAltDown, int vKeyCode) { // Set the repeat count for all key messages (1) DWORD Param = 0x00000001; // Set zBit 0-15 (WORD) as value 1 // Next set the OEM Scan code for the virtual key code DWORD ScanCode = (DWORD)((BYTE)MapVirtualKey((UINT)vKeyCode, 0)); if (ScanCode != 0) Param |= (ScanCode << 16); // Set zBits 16-23 // Set the extended flag for extended keys switch (vKeyCode) { // Add more keys here case VK_DELETE: case VK_LWIN: case VK_RWIN: Param |= 0x01000000; break; } // Is the ALT Key down for SYS_ messages? if (IsAltDown) Param |= 0x20000000; // Enable zBit 29 // Set the previous key and transition state if (Message == WM_SYSKEYUP || Message == WM_KEYUP) Param |= 0xC0000000; // Enable zBit 30 and 31 // Convert the DWORD to an LPARAM and return return (LPARAM)Param; } // KeyCharToVirt // // Maps the specified character to a virtual key code. // This will also specify whether or not shift is required for // the virtual key code have the same effect. For example: // the letter 'K'. Sending VK_K is not enough shift must be // down to get the capital 'K'. // // Returns TRUE of the data has been successfully // translated, FALSE otherwise. If FALSE, pointers have // been unmodified. static BOOL KeyCharToVirt( WCHAR KeyChar, int *vKeyCode, BOOL *RequiresShift) { // Get the key data SHORT ScanData = VkKeyScanW(KeyChar); // Check Success/Failure of API call if (LOBYTE(ScanData) == -1 && HIBYTE(ScanData) == -1) return FALSE; // Set the data if (vKeyCode != NULL) *vKeyCode = LOBYTE(ScanData); if (RequiresShift != NULL) *RequiresShift = (HIBYTE(ScanData) & 0x01); return TRUE; } // KeyVirtToChar // // Translates the specified virtual key code into a character. // There are some virtual key codes that do not have // character representations, such as the arrow keys. In this // case, the function fails. // // Returns TRUE if the virtual key code was successfully // translated, FALSE otherwise. If FALSE, the KeyChar // pointer has not been modified. static BOOL KeyVirtToChar( int vKeyCode, WCHAR *KeyChar) { // Use Win32 API to map, two is the value used for // vKey->Char, no type is defined for easier // readability there :-( [as of Jan 2001] UINT Result = MapVirtualKeyW((UINT)vKeyCode, 2); if (Result == 0) return FALSE; // Set the data if (KeyChar != NULL) *KeyChar = (WCHAR)Result; return TRUE; } // KeySendVirtMessage // // This routine corrects and sends a key message to the server. // WM_SYSKEY messages are converted over to WM_KEY messages so // that they can be transferred over the wire correctly - they // still work though. An LPARAM is automatically generated. // Support is only implemented for the messages: // WM_KEYDOWN and WM_KEYUP (and the SYS versions). // // The return value is a string specifying an error if one // occured, otherwise the process was successful and NULL // is returned. static LPCSTR KeySendVirtMessage( HANDLE Connection, UINT Message, int vKeyCode) { // Wait until the user unpauses the script (if paused) T2WaitForPauseInput(Connection); // Filter out invalid messages, and convert SYS messages switch (Message) { case WM_SYSKEYDOWN: case WM_KEYDOWN: Message = WM_KEYDOWN; break; case WM_SYSKEYUP: case WM_KEYUP: Message = WM_KEYUP; break; default: return "Unsupported keyboard message"; } // Trigger specific actions for special virtual key codes switch (vKeyCode) { // CTRL Key message case VK_CONTROL: case VK_LCONTROL: case VK_RCONTROL: { switch (Message) { case WM_KEYDOWN: // Flag CTRL as down if (ISCTRLDOWN == TRUE) return NULL; ISCTRLDOWN = TRUE; break; case WM_KEYUP: // Flag CTRL as up if (ISCTRLDOWN == FALSE) return NULL; ISCTRLDOWN = FALSE; break; } } // SHIFT Key message case VK_SHIFT: case VK_LSHIFT: case VK_RSHIFT: { switch (Message) { case WM_KEYDOWN: // Flag SHIFT as down if (ISSHIFTDOWN == TRUE) return NULL; ISSHIFTDOWN = TRUE; break; case WM_KEYUP: // Flag SHIFT as up if (ISSHIFTDOWN == FALSE) return NULL; ISSHIFTDOWN = FALSE; break; } break; } // ALT Key message case VK_MENU: case VK_LMENU: case VK_RMENU: { switch (Message) { case WM_KEYDOWN: // Flag ALT as down if (ISALTDOWN == TRUE) return NULL; ISALTDOWN = TRUE; break; case WM_KEYUP: // Flag ALT as up if (ISALTDOWN == FALSE) return NULL; ISALTDOWN = FALSE; break; } break; } } // Send the message over the wire return T2SendData(Connection, Message, (WPARAM)vKeyCode, KeyGenParam(Message, ISALTDOWN, vKeyCode)); } // KeySendCharMessage // // This routine automatically translates a charater into its // virtual key code counterpart and passes the code onto the // KeySendVirtMessage. Shift reliant characters will NOT // have the SHIFT key automatically be sent over as well, // because this would be more difficult to manage the shift key // locally. // // Only WM_KEYDOWN and WM_KEYUP messages (and their SYS versions) // are supported. Unsupported keyboard messages will cause the // routine to fail. // // The return value is a string specifying an error if one // occured, otherwise the process was successful and NULL // is returned. static LPCSTR KeySendCharMessage(HANDLE Connection, UINT Message, WCHAR KeyChar) { // Wait until the user unpauses the script (if paused) T2WaitForPauseInput(Connection); // Only the following messages are supported if (Message == WM_KEYDOWN || Message == WM_KEYUP || Message == WM_SYSKEYDOWN || Message == WM_SYSKEYUP) { int vKeyCode = 0; // Translate the character to a virtual key code if (KeyCharToVirt(KeyChar, &vKeyCode, NULL) == FALSE) return "Failed to map character to a virtual key code"; // Submit the virtual key code return KeySendVirtMessage(Connection, Message, vKeyCode); } // WM_CHAR and WM_SYSCHAR is not used over this tclient connection return "Unsupported keyboard message"; } // T2SetDefaultWPM // // Every TCLIENT connection has it's own special properties, and this // includes the "Words Per Minute" property. The property represents // the default speed in which TCLIENT is to type text to the server. // The scripter can override this by using the optional parameter // after TypeText() to indicate the temporary words per minute. // // This routine sets the default words per minute as described above. // // The return value is NULL if successful, otherwise a string // describing the error. TSAPI LPCSTR T2SetDefaultWPM(HANDLE Connection, DWORD WordsPerMinute) { // Validate the handle if (T2IsHandle(Connection) == FALSE) return "Not a valid connection"; // If WPM is set to 0, use the default if (WordsPerMinute == 0) { WordsPerMinute = T2_DEFAULT_WORDS_PER_MIN; ((TSAPIHANDLE *)Connection)->DelayPerChar = DEFAULT_DELAY_BETWEEN_CHARS; } // If WPM is suggested to be insanely high, set the delay to // 0 instead of trying to calculate it else if (WordsPerMinute > MAX_WORDS_PER_MIN) ((TSAPIHANDLE *)Connection)->DelayPerChar = 0; // Otherwise, calculate the words per minute into // the delay value used in Sleep() calls else ((TSAPIHANDLE *)Connection)->DelayPerChar = CALC_DELAY_BETWEEN_CHARS(WordsPerMinute); // Record the words per minute if the user wants this value back ((TSAPIHANDLE *)Connection)->WordsPerMinute = WordsPerMinute; return NULL; } // T2GetDefaultWPM // // Every TCLIENT connection has it's own special properties, and this // includes the "Words Per Minute" property. The property represents // the default speed in which TCLIENT is to type text to the server. // The scripter can override this by using the optional parameter // after TypeText() to indicate the temporary words per minute. // // This routine retrieves the current default words per minute // as described above. // // The return value is NULL if successful, otherwise a string // describing the error. TSAPI LPCSTR T2GetDefaultWPM(HANDLE Connection, DWORD *WordsPerMinute) { // Validate the handle if (T2IsHandle(Connection) == FALSE) return "Not a valid connection"; __try { // Attempt to set a value at the specified pointer *WordsPerMinute = ((TSAPIHANDLE *)Connection)->WordsPerMinute; } __except (EXCEPTION_EXECUTE_HANDLER) { _ASSERT(FALSE); // Nope, it failed. return "Invalid WordsPerMinute pointer"; } return NULL; } // // Keyboard Routines // // These functions are used to make keyboard handling easier. // They come in two flavors, character version, and // virtual keycode version. The virtual keycode functions // are prefixed with a "V" after the T2 to indicate so. // // T2KeyAlt // // Allows for easy ability to do an ALT + Key combonation. // For example, ALT-F in a typical Windows application will // open up the File menu. // // Returns NULL if successful, otherwise a string describing // the failure. // Character Version TSAPI LPCSTR T2KeyAlt(HANDLE Connection, WCHAR KeyChar) { // Don't validate the connection handle here, // it is done within T2KeyDown, and no reason // two double check it. // First press the ALT key LPCSTR Result = T2VKeyDown(Connection, VK_MENU); if (Result != NULL) return Result; // Be realistic T2WaitForLatency(Connection); // Next press and release the specified custom key Result = T2KeyPress(Connection, KeyChar); // Be realistic T2WaitForLatency(Connection); // Finally, let up on the ALT key T2VKeyUp(Connection, VK_MENU); return Result; } // Virtual Key Code Version TSAPI LPCSTR T2VKeyAlt(HANDLE Connection, INT vKeyCode) { // Don't validate the connection handle here, // it is done within T2VKeyDown, and no reason // two double check it. // First press the ALT key LPCSTR Result = T2VKeyDown(Connection, VK_MENU); if (Result != NULL) return Result; // Be realistic T2WaitForLatency(Connection); // Next press and release the specified custom key Result = T2VKeyPress(Connection, vKeyCode); // Be realistic T2WaitForLatency(Connection); // Finally, let up on the ALT key T2VKeyUp(Connection, VK_MENU); return Result; } // T2KeyCtrl // // Allows for easy ability to do a CTRL + Key combonation. // For example, CTRL-C in a typical Windows application will // copy a selected item to the clipboard. // // Returns NULL if successful, otherwise a string describing // the failure. // Character Version TSAPI LPCSTR T2KeyCtrl(HANDLE Connection, WCHAR KeyChar) { // Don't validate the connection handle here, // it is done within T2KeyDown, and no reason // two double check it. // First press the CTRL key LPCSTR Result = T2VKeyDown(Connection, VK_CONTROL); if (Result != NULL) return Result; // Be realistic T2WaitForLatency(Connection); // Next press and release the specified custom key Result = T2KeyPress(Connection, KeyChar); // Be realistic T2WaitForLatency(Connection); // Finally, let up on the CTRL key T2VKeyUp(Connection, VK_CONTROL); return Result; } // Virtual Key Code Version TSAPI LPCSTR T2VKeyCtrl(HANDLE Connection, INT vKeyCode) { // Don't validate the connection handle here, // it is done within T2VKeyDown, and no reason // two double check it. // First press the CTRL key LPCSTR Result = T2VKeyDown(Connection, VK_CONTROL); if (Result != NULL) return Result; // Be realistic T2WaitForLatency(Connection); // Next press and release the specified custom key Result = T2VKeyPress(Connection, vKeyCode); // Be realistic T2WaitForLatency(Connection); // Finally, let up on the CTRL key T2VKeyUp(Connection, VK_CONTROL); return Result; } // T2KeyDown // // Presses and a key down, and holds it down until // T2KeyUp is called. This is useful for holding down SHIFT // to type several letters in caps, etc. NOTE: For character // versions of this function, SHIFT will NOT automatically be // pressed. If you do a T2KeyDown(hCon, L'T'), a lowercase // key may be the output! You will need to manually press the // SHIFT key using T2VKeyDown(hCon, VK_SHIFT). Remember, // these are low level calls. TypeText() on the other hand, // will automatically press/release SHIFT as needed. // // Returns NULL if successful, otherwise a string describing // the failure. // Character Version TSAPI LPCSTR T2KeyDown(HANDLE Connection, WCHAR KeyChar) { // Validate the handle if (T2IsHandle(Connection) == FALSE) return "Not a valid connection"; // Simply send the WM_KEYDOWN message over the wire return KeySendCharMessage(Connection, WM_KEYDOWN, KeyChar); } // Virtual Key Code Version TSAPI LPCSTR T2VKeyDown(HANDLE Connection, INT vKeyCode) { // Validate the handle if (T2IsHandle(Connection) == FALSE) return "Not a valid connection"; // Simply send the WM_KEYDOWN message over the wire return KeySendVirtMessage(Connection, WM_KEYDOWN, vKeyCode); } // T2KeyPress // // Presses and releases a key. NOTE: For character // versions of this function, SHIFT will NOT automatically be // pressed. If you do a T2KeyDown(hCon, L'T'), a lowercase // key may be the output! You will need to manually press the // SHIFT key using T2VKeyDown(hCon, VK_SHIFT). Remember, // these are low level calls. TypeText() on the other hand, // will automatically press/release SHIFT as needed. // // Returns NULL if successful, otherwise a string describing // the failure. // Character Version TSAPI LPCSTR T2KeyPress(HANDLE Connection, WCHAR KeyChar) { // Don't validate the connection handle here, // it is done within T2VKeyDown, and no reason // two double check it. // First press the key LPCSTR Result = T2KeyDown(Connection, KeyChar); if (Result != NULL) return Result; // Be realistic T2WaitForLatency(Connection); // Then release it return T2KeyUp(Connection, KeyChar); } // Virtual Key Code Version TSAPI LPCSTR T2VKeyPress(HANDLE Connection, INT vKeyCode) { // Don't validate the connection handle here, // it is done within T2VKeyDown, and no reason // two double check it. // First press the key LPCSTR Result = T2VKeyDown(Connection, vKeyCode); if (Result != NULL) return Result; // Be realistic T2WaitForLatency(Connection); // Then release it return T2VKeyUp(Connection, vKeyCode); } // T2KeyUp // // Releases a key that has been pressed by the T2KeyDown // function. If the key is not down, behavior is undefined. // // Returns NULL if successful, otherwise a string describing // the failure. // Character Version TSAPI LPCSTR T2KeyUp(HANDLE Connection, WCHAR KeyChar) { // Simply send the WM_KEYUP message over the wire return KeySendCharMessage(Connection, WM_KEYUP, KeyChar); } // Virtual Key Code Version TSAPI LPCSTR T2VKeyUp(HANDLE Connection, INT vKeyCode) { // Simply send the WM_KEYUP message over the wire return KeySendVirtMessage(Connection, WM_KEYUP, vKeyCode); } // T2TypeText // // This handy function enumerates each character specified in the text // and sends the required key messages over the wire to end up with // the proper result. The shift key is automatically pressed/depressed // as needed for capital letters and acts as real user action. // Additionally, the speed of typed text is indicated by the // (optional) WordsPerMinute parameter. If WordsPerMinute is 0 (zero), // the default WordsPerMinute for the handle is used. // // Returns NULL if successful, otherwise a string describing // the failure. TSAPI LPCSTR T2TypeText(HANDLE Connection, LPCWSTR Text, UINT WordsPerMin) { LPCSTR Result = NULL; int vKeyCode = 0; BOOL RequiresShift = FALSE; UINT DelayBetweenChars; BOOL ShiftToggler; // Validate the handle if (T2IsHandle(Connection) == FALSE) return "Not a valid connection"; // First get the default delay between each character DelayBetweenChars = ((TSAPIHANDLE *)Connection)->DelayPerChar; // Get the current state of the shift key ShiftToggler = ISSHIFTDOWN; // If specified, use the custom WordsPerMinute if (WordsPerMin > 0) DelayBetweenChars = CALC_DELAY_BETWEEN_CHARS(WordsPerMin); // Enter the exception clause in case we have some bad // Text pointer, and we attempt to continue forever... __try { // Loop between each character until a null character is hit for (; *Text != 0; ++Text) { // First get the key code associated with the current character if (KeyCharToVirt(*Text, &vKeyCode, &RequiresShift) == FALSE) { // This should never happen, but Roseanne is still being // aired on channel 11, so you never know... _ASSERT(FALSE); return "Failed to map a character to a virtual key code"; } // Press shift if we need if (RequiresShift == TRUE && ShiftToggler == FALSE) Result = KeySendVirtMessage(Connection, WM_KEYDOWN, VK_SHIFT); // Release shift if we need else if (RequiresShift == FALSE && ShiftToggler == TRUE) Result = KeySendVirtMessage(Connection, WM_KEYUP, VK_SHIFT); // Set the current shift state now ShiftToggler = RequiresShift; // We are not using T2VKeyPress() here because we need want // to prevent as many performance hits as possible, and in this // case we would be checking the Connection handle over and // over, in addition to making and new stacks etc, // which is quite pointless. // Press the current key down Result = KeySendVirtMessage(Connection, WM_KEYDOWN, vKeyCode); if (Result != NULL) return Result; // Release the current key Result = KeySendVirtMessage(Connection, WM_KEYUP, vKeyCode); if (Result != NULL) return Result; // Note we are not releasing shift if it is down, this is because // the next key may be caps as well, and normal users don't // press and release shift for each character (assuming they // didn't use the caps key...) // Delay for the specified amount of time to get accurate // count for WordsPerMinute Sleep(DelayBetweenChars); } // We are done going through all the text. If we still have // shift down, release it at this point. if (ShiftToggler == TRUE) return KeySendVirtMessage(Connection, WM_KEYUP, VK_SHIFT); } __except(EXCEPTION_EXECUTE_HANDLER) { // Uhm, shouldn't happen? _ASSERT(FALSE); return "Exception occured"; } return NULL; }