#include "stdafx.h" #include "sendkeys.h" /* * ----------------------------------------------------------------------------- * Copyright (c) 2004 lallous * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ----------------------------------------------------------------------------- The Original SendKeys copyright info --------------------------------------- SendKeys (sndkeys32.pas) routine for 32-bit Delphi. Written by Ken Henderson Copyright (c) 1995 Ken Henderson History ---------- 04/19/2004 * Initial version development 04/21/2004 * Added number of times specifier to special keys * Added {BEEP X Y} * Added {APPACTIVATE WindowTitle} * Added CarryDelay() and now delay works properly with all keys * Added SetDelay() method * Fixed code in AppActivate that allowed to pass both NULL windowTitle/windowClass 05/21/2004 * Fixed a bug in StringToVKey() that caused the search for RIGHTPAREN to be matched as RIGHT * Adjusted code so it compiles w/ VC6 05/24/2004 * Added unicode support Todo ------- * perhaps add mousecontrol: mousemove+mouse clicks * allow sending of normal keys multiple times as: {a 10} */ const WORD CSendKeys::VKKEYSCANSHIFTON = 0x01; const WORD CSendKeys::VKKEYSCANCTRLON = 0x02; const WORD CSendKeys::VKKEYSCANALTON = 0x04; const WORD CSendKeys::INVALIDKEY = 0xFFFF; const BYTE CSendKeys::ExtendedVKeys[MaxExtendedVKeys] = { VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT, VK_HOME, VK_END, VK_PRIOR, // PgUp VK_NEXT, // PgDn VK_INSERT, VK_DELETE, VK_CONTROL, VK_SHIFT, }; CSendKeys::CSendKeys() { m_nDelayNow = m_nDelayAlways = m_keyDownDelay = 0; } // Delphi port regexps: // --------------------- // search: .+Name:'([^']+)'.+vkey:([^\)]+)\) // replace: {"\1", \2} // // **If you add to this list, you must be sure to keep it sorted alphabetically // by Name because a binary search routine is used to scan it.** // CSendKeys::key_desc_t CSendKeys::KeyNames[CSendKeys::MaxSendKeysRecs] = { {_T("ADD"), VK_ADD}, {_T("APPS"), VK_APPS}, {_T("AT"), '@', true}, {_T("BACKSPACE"), VK_BACK}, {_T("BKSP"), VK_BACK}, {_T("BREAK"), VK_CANCEL}, {_T("BS"), VK_BACK}, {_T("CAPSLOCK"), VK_CAPITAL}, {_T("CARET"), '^', true}, {_T("CLEAR"), VK_CLEAR}, {_T("DECIMAL"), VK_DECIMAL}, {_T("DEL"), VK_DELETE}, {_T("DELETE"), VK_DELETE}, {_T("DIVIDE"), VK_DIVIDE}, {_T("DOWN"), VK_DOWN}, {_T("END"), VK_END}, {_T("ENTER"), VK_RETURN}, {_T("ESC"), VK_ESCAPE}, {_T("ESCAPE"), VK_ESCAPE}, {_T("F1"), VK_F1}, {_T("F10"), VK_F10}, {_T("F11"), VK_F11}, {_T("F12"), VK_F12}, {_T("F13"), VK_F13}, {_T("F14"), VK_F14}, {_T("F15"), VK_F15}, {_T("F16"), VK_F16}, {_T("F2"), VK_F2}, {_T("F3"), VK_F3}, {_T("F4"), VK_F4}, {_T("F5"), VK_F5}, {_T("F6"), VK_F6}, {_T("F7"), VK_F7}, {_T("F8"), VK_F8}, {_T("F9"), VK_F9}, {_T("HELP"), VK_HELP}, {_T("HOME"), VK_HOME}, {_T("INS"), VK_INSERT}, {_T("LEFT"), VK_LEFT}, {_T("LEFTBRACE"), '{', true}, {_T("LEFTPAREN"), '(', true}, {_T("LWIN"), VK_LWIN}, {_T("MULTIPLY"), VK_MULTIPLY}, {_T("NUMLOCK"), VK_NUMLOCK}, {_T("NUMPAD0"), VK_NUMPAD0}, {_T("NUMPAD1"), VK_NUMPAD1}, {_T("NUMPAD2"), VK_NUMPAD2}, {_T("NUMPAD3"), VK_NUMPAD3}, {_T("NUMPAD4"), VK_NUMPAD4}, {_T("NUMPAD5"), VK_NUMPAD5}, {_T("NUMPAD6"), VK_NUMPAD6}, {_T("NUMPAD7"), VK_NUMPAD7}, {_T("NUMPAD8"), VK_NUMPAD8}, {_T("NUMPAD9"), VK_NUMPAD9}, {_T("PERCENT"), '%', true}, {_T("PGDN"), VK_NEXT}, {_T("PGUP"), VK_PRIOR}, {_T("PLUS"), '+', true}, {_T("PRTSC"), VK_PRINT}, {_T("RIGHT"), VK_RIGHT}, {_T("RIGHTBRACE"), '}', true}, {_T("RIGHTPAREN"), ')', true}, {_T("RWIN"), VK_RWIN}, {_T("SCROLL"), VK_SCROLL}, {_T("SEPARATOR"), VK_SEPARATOR}, {_T("SNAPSHOT"), VK_SNAPSHOT}, {_T("SUBTRACT"), VK_SUBTRACT}, {_T("TAB"), VK_TAB}, {_T("TILDE"), '~', true}, {_T("UP"), VK_UP}, {_T("WIN"), VK_LWIN} }; // calls keybd_event() and waits, if needed, till the sent input is processed void CSendKeys::KeyboardEvent(BYTE VKey, BYTE ScanCode, LONG Flags) { keybd_event(VKey, ScanCode, Flags, 0); if (m_bWait) { MSG KeyboardMsg; while (::PeekMessage(&KeyboardMsg, 0, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)) { ::TranslateMessage(&KeyboardMsg); ::DispatchMessage(&KeyboardMsg); } } } // Checks whether the specified VKey is an extended key or not bool CSendKeys::IsVkExtended(BYTE VKey) { for (int i=0;i= 10) ) { // Define _WIN32_WINNT > 0x0400 // to compile INPUT input[2] = {0}; input[0].type = input[1].type = INPUT_KEYBOARD; input[0].ki.wVk = input[1].ki.wVk = VKey; input[1].ki.dwFlags = KEYEVENTF_KEYUP; ::SendInput(sizeof(input) / sizeof(INPUT), input, sizeof(INPUT)); } // Win95 else { KEYBOARDSTATE_t KeyboardState; NumState = GetKeyState(VK_NUMLOCK) & 1 ? true : false; GetKeyboardState(&KeyboardState[0]); if (NumState) KeyboardState[VK_NUMLOCK] &= ~1; else KeyboardState[VK_NUMLOCK] |= 1; SetKeyboardState(&KeyboardState[0]); } } } return; } // Get scancode ScanCode = LOBYTE(::MapVirtualKey(VKey, 0)); // Send keys for (Cnt=1; Cnt<=NumTimes; Cnt++) { // Carry needed delay ? if (bDelay) CarryDelay(); KeyboardEvent(VKey, ScanCode, IsVkExtended(VKey) ? KEYEVENTF_EXTENDEDKEY : 0); if(m_keyDownDelay > 0) { Sleep(m_keyDownDelay); } if (GenUpMsg) { SendKeyUp(VKey); } } } // Checks whether a bit is set bool CSendKeys::BitSet(BYTE BitTable, UINT BitMask) { return BitTable & BitMask ? true : false; } // Sends a single key void CSendKeys::SendKey(WORD MKey, WORD NumTimes, bool GenDownMsg) { // Send appropriate shift keys associated with the given VKey if (BitSet(HIBYTE(MKey), VKKEYSCANSHIFTON)) SendKeyDown(VK_SHIFT, 1, false); if (BitSet(HIBYTE(MKey), VKKEYSCANCTRLON)) SendKeyDown(VK_CONTROL, 1, false); if (BitSet(HIBYTE(MKey), VKKEYSCANALTON)) SendKeyDown(VK_MENU, 1, false); // Send the actual VKey SendKeyDown(LOBYTE(MKey), NumTimes, GenDownMsg, true); // toggle up shift keys if (BitSet(HIBYTE(MKey), VKKEYSCANSHIFTON)) SendKeyUp(VK_SHIFT); if (BitSet(HIBYTE(MKey), VKKEYSCANCTRLON)) SendKeyUp(VK_CONTROL); if (BitSet(HIBYTE(MKey), VKKEYSCANALTON)) SendKeyUp(VK_MENU); } // Implements a simple binary search to locate special key name strings WORD CSendKeys::StringToVKey(LPCTSTR KeyString, int &idx) { bool Found = false, Collided; int Bottom = 0, Top = MaxSendKeysRecs, Middle = (Bottom + Top) / 2; WORD retval = INVALIDKEY; idx = -1; do { Collided = (Bottom == Middle) || (Top == Middle); int cmp = _tcsnicmp(KeyNames[Middle].keyName, KeyString, _tcslen(KeyString)); if (cmp == 0) { Found = true; retval = KeyNames[Middle].VKey; idx = Middle; break; } else { if (cmp < 0) Bottom = Middle; else Top = Middle; Middle = (Bottom + Top) / 2; } } while (!(Found || Collided)); return retval; } // Releases all shift keys (keys that can be depressed while other keys are being pressed // If we are in a modifier group this function does nothing void CSendKeys::PopUpShiftKeys() { if (!m_bUsingParens) { if (m_bShiftDown) { SendKeyUp(VK_SHIFT); } if (m_bLShiftDown) { SendKeyUp(VK_LSHIFT); } if (m_bRShiftDown) { SendKeyUp(VK_RSHIFT); } if (m_bControlDown) { SendKeyUp(VK_CONTROL); } if (m_bLControlDown) { SendKeyUp(VK_LCONTROL); } if (m_bRControlDown) { SendKeyUp(VK_RCONTROL); } if (m_bAltDown) { SendKeyUp(VK_MENU); } if (m_bWinDown) { SendKeyUp(VK_LWIN); } m_bWinDown = m_bShiftDown = m_bLShiftDown = m_bRShiftDown = m_bControlDown = m_bLControlDown = m_bRControlDown = m_bAltDown = false; } } // Sends a key string bool CSendKeys::SendKeys(LPCTSTR KeysString, bool Wait) { WORD MKey; WORD NumTimes = 1; TCHAR KeyString[300] = {0}; bool retval = false; int keyIdx; LPTSTR pKey = (LPTSTR) KeysString; TCHAR ch; m_bWait = Wait; m_bWinDown = m_bShiftDown = m_bLShiftDown = m_bRShiftDown = m_bControlDown = m_bLControlDown = m_bRControlDown = m_bAltDown = m_bUsingParens = false; while (ch = *pKey) { switch (ch) { // begin modifier group case _TXCHAR('('): m_bUsingParens = true; break; // end modifier group case _TXCHAR(')'): m_bUsingParens = false; PopUpShiftKeys(); // pop all shift keys when we finish a modifier group close break; // ALT key case _TXCHAR('%'): m_bAltDown = true; SendKeyDown(VK_MENU, 1, false); break; // SHIFT key case _TXCHAR('+'): m_bShiftDown = true; SendKeyDown(VK_SHIFT, 1, false); break; // CTRL key case _TXCHAR('^'): m_bControlDown = true; SendKeyDown(VK_CONTROL, 1, false); break; // WINKEY (Left-WinKey) case '@': m_bWinDown = true; SendKeyDown(VK_LWIN, 1, false); break; // enter case _TXCHAR('~'): SendKeyDown(VK_RETURN, 1, true); PopUpShiftKeys(); break; // begin special keys case _TXCHAR('{'): { LPTSTR p = pKey+1; // skip past the beginning '{' size_t t; // find end of close while (*p && *p != _TXCHAR('}')) p++; t = p - pKey; // special key definition too big? if (t > sizeof(KeyString)) return false; // Take this KeyString into local buffer _tcsncpy(KeyString, pKey+1, t); KeyString[t-1] = _TXCHAR('\0'); keyIdx = -1; pKey += t; // skip to next keystring // Invalidate key MKey = INVALIDKEY; // sending arbitrary vkeys? if (_tcsnicmp(KeyString, _T("VKEY"), 4) == 0) { p = KeyString + 4; MKey = _ttoi(p); } else if (_tcsnicmp(KeyString, _T("BEEP"), 4) == 0) { p = KeyString + 4 + 1; LPTSTR p1 = p; DWORD frequency, delay; if ((p1 = _tcsstr(p, _T(" "))) != NULL) { *p1++ = _TXCHAR('\0'); frequency = _ttoi(p); delay = _ttoi(p1); ::Beep(frequency, delay); } } // Should activate a window? else if (_tcsnicmp(KeyString, _T("APPACTIVATE"), 11) == 0) { p = KeyString + 11 + 1; AppActivate(p); } // want to send/set delay? else if (_tcsnicmp(KeyString, _T("DELAY"), 5) == 0) { // Advance to parameters p = KeyString + 5; // set "sleep factor" if (*p == _TXCHAR('=')) m_nDelayAlways = _ttoi(p + 1); // Take number after the '=' character else // set "sleep now" m_nDelayNow = _ttoi(p); } // not command special keys, then process as keystring to VKey else { MKey = StringToVKey(KeyString, keyIdx); // Key found in table if (keyIdx != -1) { NumTimes = 1; // Does the key string have also count specifier? t = _tcslen(KeyNames[keyIdx].keyName); if (_tcslen(KeyString) > t) { p = KeyString + t; // Take the specified number of times NumTimes = _ttoi(p); } if (KeyNames[keyIdx].normalkey) MKey = ::VkKeyScan(KeyNames[keyIdx].VKey); } } // A valid key to send? if (MKey != INVALIDKEY) { if (MKey == VK_LCONTROL || MKey == VK_RCONTROL) { m_bLControlDown = (MKey == VK_LCONTROL); m_bRControlDown = (MKey == VK_RCONTROL); SendKeyDown(MKey, 1, false); } else if (MKey == VK_LSHIFT || MKey == VK_RSHIFT) { m_bLShiftDown = (MKey == VK_LSHIFT); m_bRShiftDown = (MKey == VK_RSHIFT); SendKeyDown(MKey, 1, false); } else { SendKey(MKey, NumTimes, true); PopUpShiftKeys(); } } } break; // a normal key was pressed default: // Get the VKey from the key MKey = ::VkKeyScan(ch); SendKey(MKey, 1, true); PopUpShiftKeys(); } pKey++; } m_bUsingParens = false; PopUpShiftKeys(); return true; } bool CSendKeys::AppActivate(HWND wnd) { if (wnd == NULL) return false; ::SendMessage(wnd, WM_SYSCOMMAND, SC_HOTKEY, (LPARAM) wnd); ::SendMessage(wnd, WM_SYSCOMMAND, SC_RESTORE, (LPARAM) wnd); ::ShowWindow(wnd, SW_SHOW); ::SetForegroundWindow(wnd); ::SetFocus(wnd); return true; } BOOL CALLBACK CSendKeys::enumwindowsProc(HWND hwnd, LPARAM lParam) { enumwindow_t *t = (enumwindow_t *) lParam; LPTSTR wtitle = 0, wclass = 0, str = t->str; if (!*str) str++; else { wtitle = str; str += _tcslen(str) + 1; } if (*str) wclass = str; bool bMatch(false); if (wclass) { TCHAR szClass[300]; if (::GetClassName(hwnd, szClass, sizeof(szClass))) bMatch |= (_tcsstr(szClass, wclass) != 0); } if (wtitle) { TCHAR szTitle[300]; if (::GetWindowText(hwnd, szTitle, sizeof(szTitle))) bMatch |= (_tcsstr(szTitle, wtitle) != 0); } if (bMatch) { t->hwnd = hwnd; return false; } return true; } // Searchs and activates a window given its title or class name bool CSendKeys::AppActivate(LPCTSTR WindowTitle, LPCTSTR WindowClass) { HWND w; w = ::FindWindow(WindowClass, WindowTitle); if (w == NULL) { // Must specify at least a criteria if (WindowTitle == NULL && WindowClass == NULL) return false; // << Code to serialize the windowtitle/windowclass in order to send to EnumWindowProc() size_t l1(0), l2(0); if (WindowTitle) l1 = _tcslen(WindowTitle); if (WindowClass) l2 = _tcslen(WindowClass); LPTSTR titleclass = new TCHAR [l1 + l2 + 5]; memset(titleclass, '\0', l1+l2+5); if (WindowTitle) _tcscpy(titleclass, WindowTitle); titleclass[l1] = 0; if (WindowClass) _tcscpy(titleclass+l1+1, WindowClass); // >> enumwindow_t t; t.hwnd = NULL; t.str = titleclass; ::EnumWindows(enumwindowsProc, (LPARAM) & t); w = t.hwnd; delete [] titleclass; } if (w == NULL) return false; return AppActivate(w); } // Carries the required delay and clears the m_nDelaynow value void CSendKeys::CarryDelay() { // Should we delay once? if (!m_nDelayNow) // should we delay always? m_nDelayNow = m_nDelayAlways; // No delay specified? if (m_nDelayNow) ::Sleep(m_nDelayNow); //::Beep(100, m_nDelayNow); // clear SleepNow m_nDelayNow = 0; } /* Test Binary search void CSendKeys::test() { WORD miss(0); for (int i=0;i