2013-01-11 22:18:32 +01:00
|
|
|
/*
|
2015-04-13 12:42:18 -04:00
|
|
|
Licensed to the Software Freedom Conservancy (SFC) under one
|
|
|
|
|
or more contributor license agreements. See the NOTICE file
|
|
|
|
|
distributed with this work for additional information
|
|
|
|
|
regarding copyright ownership. The SFC licenses this file
|
|
|
|
|
to you under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
|
You may obtain a copy of the License at
|
2013-01-11 22:18:32 +01:00
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
|
limitations under the License.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "stdafx.h"
|
|
|
|
|
|
|
|
|
|
#include <ctime>
|
|
|
|
|
#include <string>
|
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
|
|
#include "interactions.h"
|
|
|
|
|
#include "interactions_common.h"
|
|
|
|
|
#include "logging.h"
|
|
|
|
|
#include "event_firing_thread.h"
|
|
|
|
|
|
2014-09-05 02:00:00 +00:00
|
|
|
#define ENULLPOINTER 22
|
|
|
|
|
|
2013-01-11 22:18:32 +01:00
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
|
|
#pragma data_seg(".LISTENER")
|
|
|
|
|
static bool pressed = false;
|
|
|
|
|
// The following booleans indicate whether any of the modifier keys are
|
|
|
|
|
// depressed. These booleans represent the state from the user's point of
|
|
|
|
|
// view - not if a modifier is depressed for the current key press (there
|
|
|
|
|
// is an explanation on why modifier keys have to be pressed and depressed
|
|
|
|
|
// for some keys below).
|
|
|
|
|
// Each modifier key can only be held down when sendKeys was called without
|
|
|
|
|
// calling releaseModifierKeys. The modifier key will only be released when
|
|
|
|
|
// sendKeys(Keys.NULL) is called or sendKeys() is called again (with the same
|
|
|
|
|
// modifier).
|
|
|
|
|
// Ordinarily, sendKeys would generate the following keys sequence when
|
|
|
|
|
// called with sendKeys("AB"):
|
|
|
|
|
// Shift_Down A_Down A_Up Shift_Up Shift_Down B_Down B_Up Shift_Up
|
|
|
|
|
// However, when sendKeys is called using the new Interactions API,
|
|
|
|
|
// the modifier keys are not released. So, we want the letters to be
|
|
|
|
|
// capitalized if Shift is pressed. The following calls:
|
|
|
|
|
// * sendKeyPress(SHIFT)
|
|
|
|
|
// * sendKeys("ab")
|
|
|
|
|
// * sendKeyRelease(SHIFT)
|
|
|
|
|
// Should generate the following events:
|
|
|
|
|
// Shift_Down A_Down A_Up B_Down B_Up Shift_Up
|
|
|
|
|
// With *capital* a and b. Using this boolean, we can tell if the shift key
|
|
|
|
|
// was held down by these calls and should generate upper-case chars.
|
|
|
|
|
static bool shiftPressed = false;
|
|
|
|
|
static bool controlPressed = false;
|
|
|
|
|
static bool altPressed = false;
|
|
|
|
|
static HHOOK hook = 0;
|
|
|
|
|
static HINSTANCE moduleHandle = NULL;
|
|
|
|
|
|
|
|
|
|
#pragma data_seg()
|
|
|
|
|
#pragma comment(linker, "/section:.LISTENER,rws")
|
|
|
|
|
|
|
|
|
|
// Left Mouse button pressed?
|
|
|
|
|
static bool leftMouseButtonPressed = false;
|
|
|
|
|
|
|
|
|
|
void backgroundUnicodeKeyPress(HWND ieWindow, wchar_t c, int pause)
|
|
|
|
|
{
|
|
|
|
|
pause = pause / 3;
|
|
|
|
|
|
|
|
|
|
// IE can crash if keyscan < 0. It's unclear this will
|
|
|
|
|
// do anything unless the correct keyboard layout is active,
|
|
|
|
|
// as the unicode character 'c' will already have its
|
|
|
|
|
// appropriate capitalization.
|
|
|
|
|
SHORT keyscan = VkKeyScanW(c);
|
|
|
|
|
if (keyscan < 0) {
|
|
|
|
|
keyscan = 0;
|
|
|
|
|
}
|
|
|
|
|
pressed = false;
|
|
|
|
|
PostMessage(ieWindow, WM_KEYDOWN, keyscan, 0);
|
|
|
|
|
PostMessage(ieWindow, WM_USER, 1234, 5678);
|
|
|
|
|
wait(pause);
|
|
|
|
|
|
|
|
|
|
// TODO: There must be a better way to tell when the keydown is processed
|
|
|
|
|
clock_t maxWait = clock() + 250;
|
|
|
|
|
while (!pressed && clock() < maxWait) {
|
|
|
|
|
wait(5);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PostMessage(ieWindow, WM_CHAR, c, 0);
|
|
|
|
|
|
|
|
|
|
wait(pause);
|
|
|
|
|
|
|
|
|
|
PostMessage(ieWindow, WM_KEYUP, keyscan, 0);
|
|
|
|
|
|
|
|
|
|
wait(pause);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sendModifierKeyDown(HWND hwnd, HKL layout, int modifierKeyCode,
|
|
|
|
|
BYTE keyboardState[256], int pause) {
|
|
|
|
|
keyboardState[modifierKeyCode] |= 0x80;
|
|
|
|
|
|
|
|
|
|
LPARAM modifierKey = 1 | MapVirtualKeyEx(modifierKeyCode, 0, layout) << 16;
|
|
|
|
|
if (!PostMessage(hwnd, WM_KEYDOWN, modifierKeyCode, modifierKey)) {
|
|
|
|
|
LOG(WARN) << "Modifier keydown failed: " << GetLastError();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wait(pause);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sendModifierKeyUp(HWND hwnd, HKL layout, int modifierKeyCode,
|
|
|
|
|
BYTE keyboardState[256], int pause) {
|
|
|
|
|
keyboardState[modifierKeyCode] &= ~0x80;
|
|
|
|
|
|
|
|
|
|
LPARAM modifierKey = 1 | MapVirtualKeyEx(modifierKeyCode, 0, layout) << 16;
|
|
|
|
|
modifierKey |= 0x3 << 30;
|
|
|
|
|
|
|
|
|
|
if (!PostMessage(hwnd, WM_KEYUP, modifierKeyCode, modifierKey)) {
|
|
|
|
|
LOG(WARN) << "Modifier keyup failed: " << GetLastError();
|
|
|
|
|
}
|
|
|
|
|
wait(pause);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sendModifierKeyDownIfNeeded(bool shouldSend, HWND hwnd, HKL layout,
|
|
|
|
|
int modifierKeyCode, BYTE keyboardState[256], int pause) {
|
|
|
|
|
|
|
|
|
|
if (shouldSend) {
|
|
|
|
|
sendModifierKeyDown(hwnd, layout, modifierKeyCode, keyboardState,
|
|
|
|
|
pause);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sendModifierKeyUpIfNeeded(bool shouldSend, HWND hwnd, HKL layout,
|
|
|
|
|
int modifierKeyCode, BYTE keyboardState[256], int pause) {
|
|
|
|
|
|
|
|
|
|
if (shouldSend) {
|
|
|
|
|
sendModifierKeyUp(hwnd, layout, modifierKeyCode, keyboardState,
|
|
|
|
|
pause);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool isShiftPressNeeded(WORD keyCode) {
|
|
|
|
|
return (keyCode & 0x0100) != 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool isControlPressNeeded(WORD keyCode) {
|
|
|
|
|
return (keyCode & 0x0200) != 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool isAltPressNeeded(WORD keyCode) {
|
|
|
|
|
return (keyCode & 0x0400) != 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LPARAM generateKeyMessageParam(UINT scanCode, bool extended)
|
|
|
|
|
{
|
|
|
|
|
LPARAM lparam = 1;
|
|
|
|
|
lparam |= scanCode << 16;
|
|
|
|
|
if (extended) {
|
|
|
|
|
lparam |= 1 << 24;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return lparam;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void backgroundKeyDown(HWND hwnd, HKL layout, BYTE keyboardState[256],
|
|
|
|
|
WORD keyCode, UINT scanCode, bool extended, int pause)
|
|
|
|
|
{
|
|
|
|
|
// For capital letters and symbols requiring the shift key to be pressed,
|
|
|
|
|
// A Shift key press must preceed. Unless the shift key is pressed - if
|
|
|
|
|
// shiftPressed is true, then a shift key-down was sent in the past.
|
|
|
|
|
sendModifierKeyDownIfNeeded(isShiftPressNeeded(keyCode) && (!shiftPressed), hwnd, layout,
|
|
|
|
|
VK_SHIFT, keyboardState, pause);
|
|
|
|
|
|
|
|
|
|
sendModifierKeyDownIfNeeded(isControlPressNeeded(keyCode) && (!controlPressed), hwnd, layout,
|
|
|
|
|
VK_CONTROL, keyboardState, pause);
|
|
|
|
|
|
|
|
|
|
sendModifierKeyDownIfNeeded(isAltPressNeeded(keyCode) && (!altPressed), hwnd, layout,
|
|
|
|
|
VK_MENU, keyboardState, pause);
|
|
|
|
|
|
|
|
|
|
// In order to produce an upper case character, the keyboard state should
|
|
|
|
|
// be modified. See the documentation of shiftPressed to understand why
|
|
|
|
|
// it's done only in this case.
|
|
|
|
|
if ((shiftPressed) || (isShiftPressNeeded(keyCode))) {
|
|
|
|
|
keyboardState[VK_SHIFT] |= 0x80;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
keyCode = LOBYTE(keyCode);
|
|
|
|
|
keyboardState[keyCode] |= 0x80;
|
|
|
|
|
|
|
|
|
|
SetKeyboardState(keyboardState);
|
|
|
|
|
|
|
|
|
|
LPARAM lparam = generateKeyMessageParam(scanCode, extended);
|
|
|
|
|
|
|
|
|
|
pressed = false;
|
|
|
|
|
if (!PostMessage(hwnd, WM_KEYDOWN, keyCode, lparam)) {
|
|
|
|
|
LOG(WARN) << "Key down failed: " << GetLastError();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PostMessage(hwnd, WM_USER, 1234, 5678);
|
|
|
|
|
|
|
|
|
|
// Listen out for the keypress event which IE synthesizes when IE
|
|
|
|
|
// processes the keydown message. Use a time out, just in case we
|
|
|
|
|
// have not got the logic right :)
|
|
|
|
|
|
|
|
|
|
clock_t maxWait = clock() + 5000;
|
|
|
|
|
while (!pressed) {
|
|
|
|
|
wait(5);
|
|
|
|
|
if (clock() >= maxWait) {
|
|
|
|
|
LOG(WARN) << "Timeout awaiting keypress: " << keyCode;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void backgroundKeyUp(HWND hwnd, HKL layout, BYTE keyboardState[256],
|
|
|
|
|
WORD keyCode, UINT scanCode, bool extended, int pause)
|
|
|
|
|
{
|
|
|
|
|
WORD origKeyCode = keyCode;
|
|
|
|
|
keyCode = LOBYTE(keyCode);
|
|
|
|
|
keyboardState[keyCode] &= ~0x80;
|
|
|
|
|
|
|
|
|
|
LPARAM lparam = generateKeyMessageParam(scanCode, extended);
|
|
|
|
|
lparam |= 0x3 << 30;
|
|
|
|
|
if (!PostMessage(hwnd, WM_KEYUP, keyCode, lparam)) {
|
|
|
|
|
LOG(WARN) << "Key up failed: " << GetLastError();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wait(pause);
|
|
|
|
|
|
|
|
|
|
sendModifierKeyUpIfNeeded(isShiftPressNeeded(origKeyCode) && (!shiftPressed), hwnd, layout,
|
|
|
|
|
VK_SHIFT, keyboardState, pause);
|
|
|
|
|
sendModifierKeyUpIfNeeded(isControlPressNeeded(origKeyCode) && (!controlPressed), hwnd, layout,
|
|
|
|
|
VK_CONTROL, keyboardState, pause);
|
|
|
|
|
sendModifierKeyUpIfNeeded(isAltPressNeeded(origKeyCode) && (!altPressed), hwnd, layout,
|
|
|
|
|
VK_MENU, keyboardState, pause);
|
|
|
|
|
|
|
|
|
|
// If Shift was held down, we should reset the keyboard state for it
|
|
|
|
|
// as well. See the comment in backgroundKeyDown on why it is set
|
|
|
|
|
// in the first place.
|
|
|
|
|
if ((shiftPressed) || (isShiftPressNeeded(origKeyCode))) {
|
|
|
|
|
keyboardState[VK_SHIFT] &= ~0x80;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SetKeyboardState(keyboardState);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
void backgroundKeyPress(HWND hwnd, HKL layout, BYTE keyboardState[256],
|
|
|
|
|
WORD keyCode, UINT scanCode, bool extended, int pause)
|
|
|
|
|
{
|
|
|
|
|
pause = pause / 3;
|
|
|
|
|
|
|
|
|
|
backgroundKeyDown(hwnd, layout, keyboardState, keyCode, scanCode, extended, pause);
|
|
|
|
|
backgroundKeyUp(hwnd, layout, keyboardState, keyCode, scanCode, extended, pause);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LRESULT CALLBACK GetMessageProc(int nCode, WPARAM wParam, LPARAM lParam)
|
|
|
|
|
{
|
|
|
|
|
if ((nCode == HC_ACTION) && (wParam == PM_REMOVE)) {
|
|
|
|
|
MSG* msg = reinterpret_cast<MSG*>(lParam);
|
|
|
|
|
if (msg->message == WM_USER && msg->wParam == 1234 && msg->lParam == 5678) {
|
|
|
|
|
pressed = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return CallNextHookEx(hook, nCode, wParam, lParam);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool isClearAllModifiersCode(wchar_t c)
|
|
|
|
|
{
|
|
|
|
|
return (c == 0xE000U);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool isShiftCode(wchar_t c)
|
|
|
|
|
{
|
|
|
|
|
return (c == 0xE008U); // shift (left)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool isControlCode(wchar_t c)
|
|
|
|
|
{
|
|
|
|
|
return (c == 0xE009U); // control (left)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool isAltCode(wchar_t c)
|
|
|
|
|
{
|
|
|
|
|
return (c == 0xE00AU); // alt (left)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool isModifierCharacter(wchar_t c)
|
|
|
|
|
{
|
|
|
|
|
return isClearAllModifiersCode(c) || isShiftCode(c) || isControlCode(c) ||
|
|
|
|
|
isAltCode(c);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// All the required information to post a keyboard event message.
|
|
|
|
|
struct KeySendingData {
|
|
|
|
|
HWND to_window;
|
|
|
|
|
HKL layout;
|
|
|
|
|
BYTE* keyboardState;
|
|
|
|
|
int pause_time;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void sendSingleModifierEventAndAdjustState(bool matchingModifier,
|
|
|
|
|
bool& modifierState, int modifierKeyCode, KeySendingData sendData)
|
|
|
|
|
{
|
|
|
|
|
if (!matchingModifier) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (modifierState) {
|
|
|
|
|
sendModifierKeyUp(sendData.to_window, sendData.layout,
|
|
|
|
|
modifierKeyCode, sendData.keyboardState, sendData.pause_time);
|
|
|
|
|
} else {
|
|
|
|
|
sendModifierKeyDown(sendData.to_window, sendData.layout,
|
|
|
|
|
modifierKeyCode, sendData.keyboardState, sendData.pause_time);
|
|
|
|
|
}
|
|
|
|
|
modifierState = !modifierState;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void postModifierReleaseMessages(bool releaseShift, bool releaseControl, bool releaseAlt,
|
|
|
|
|
KeySendingData sendData)
|
|
|
|
|
{
|
|
|
|
|
sendModifierKeyUpIfNeeded(releaseShift, sendData.to_window, sendData.layout, VK_SHIFT, sendData.keyboardState, sendData.pause_time);
|
|
|
|
|
sendModifierKeyUpIfNeeded(releaseControl, sendData.to_window, sendData.layout, VK_CONTROL, sendData.keyboardState, sendData.pause_time);
|
|
|
|
|
sendModifierKeyUpIfNeeded(releaseAlt, sendData.to_window, sendData.layout, VK_MENU, sendData.keyboardState, sendData.pause_time);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void sendModifierKeyEvent(wchar_t c, bool& shiftKey, bool& controlKey,
|
|
|
|
|
bool& altKey, KeySendingData sendData)
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
if (isClearAllModifiersCode(c)) {
|
|
|
|
|
postModifierReleaseMessages(shiftKey, controlKey, altKey, sendData);
|
|
|
|
|
|
|
|
|
|
shiftKey = controlKey = altKey = false;
|
|
|
|
|
} else {
|
|
|
|
|
sendSingleModifierEventAndAdjustState(isShiftCode(c), shiftKey, VK_SHIFT, sendData);
|
|
|
|
|
sendSingleModifierEventAndAdjustState(isControlCode(c), controlKey, VK_CONTROL, sendData);
|
|
|
|
|
sendSingleModifierEventAndAdjustState(isAltCode(c), altKey, VK_MENU, sendData);
|
|
|
|
|
if (isShiftCode(c)) {
|
|
|
|
|
updateShiftKeyState(shiftKey);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static HKL attachInputToIEThread(HWND directInputTo)
|
|
|
|
|
{
|
|
|
|
|
DWORD currThreadId = GetCurrentThreadId();
|
|
|
|
|
DWORD ieWinThreadId = GetWindowThreadProcessId(directInputTo, NULL);
|
|
|
|
|
|
|
|
|
|
GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCTSTR)
|
|
|
|
|
&sendKeys, &moduleHandle);
|
|
|
|
|
|
|
|
|
|
hook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC) &GetMessageProc,
|
|
|
|
|
moduleHandle, ieWinThreadId);
|
|
|
|
|
|
2015-04-17 15:31:06 -04:00
|
|
|
if (hook == NULL) {
|
|
|
|
|
LOGERR(WARN) << "Unable to set Windows hook. Individual keystrokes will be very slow";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Attach to the IE thread so we can send keys to it.
|
2013-01-11 22:18:32 +01:00
|
|
|
if (ieWinThreadId != currThreadId) {
|
|
|
|
|
AttachThreadInput(currThreadId, ieWinThreadId, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return GetKeyboardLayout(ieWinThreadId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void detachInputFromIEThread(HWND directInputTo)
|
|
|
|
|
{
|
|
|
|
|
DWORD currThreadId = GetCurrentThreadId();
|
|
|
|
|
DWORD ieWinThreadId = GetWindowThreadProcessId(directInputTo, NULL);
|
|
|
|
|
|
|
|
|
|
if (hook) {
|
|
|
|
|
UnhookWindowsHookEx(hook);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (moduleHandle) {
|
|
|
|
|
FreeLibrary(moduleHandle);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ieWinThreadId != currThreadId) {
|
|
|
|
|
AttachThreadInput(currThreadId, ieWinThreadId, false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C"
|
|
|
|
|
{
|
|
|
|
|
void sendKeys(WINDOW_HANDLE windowHandle, const wchar_t* value, int timePerKey)
|
|
|
|
|
{
|
|
|
|
|
if (!windowHandle) {
|
|
|
|
|
LOG(WARN) << "Window handle is invalid";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HWND directInputTo = static_cast<HWND>(windowHandle);
|
|
|
|
|
|
|
|
|
|
HKL layout = attachInputToIEThread(directInputTo);
|
|
|
|
|
BYTE keyboardState[256];
|
|
|
|
|
::ZeroMemory(keyboardState, sizeof(keyboardState));
|
|
|
|
|
|
|
|
|
|
bool controlKey = controlPressed;
|
|
|
|
|
bool shiftKey = shiftPressed;
|
|
|
|
|
bool altKey = altPressed;
|
|
|
|
|
KeySendingData sendData;
|
|
|
|
|
sendData.to_window = directInputTo;
|
|
|
|
|
sendData.layout = layout;
|
|
|
|
|
sendData.keyboardState = keyboardState;
|
|
|
|
|
sendData.pause_time = timePerKey;
|
|
|
|
|
|
|
|
|
|
for (const wchar_t *p = value; *p; ++p) {
|
|
|
|
|
const wchar_t c = *p;
|
|
|
|
|
|
|
|
|
|
bool extended = false;
|
|
|
|
|
|
|
|
|
|
UINT scanCode = 0;
|
|
|
|
|
WORD keyCode = 0;
|
|
|
|
|
|
|
|
|
|
if (isModifierCharacter(c)) {
|
|
|
|
|
sendModifierKeyEvent(c, shiftKey, controlKey, altKey, sendData);
|
|
|
|
|
shiftPressed = shiftKey;
|
|
|
|
|
controlPressed = controlKey;
|
|
|
|
|
altPressed = altKey;
|
|
|
|
|
continue;
|
|
|
|
|
} else if (c == 0xE001U) { // ^break
|
|
|
|
|
keyCode = VK_CANCEL;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
extended = true;
|
|
|
|
|
} else if (c == 0xE002U) { // help
|
|
|
|
|
keyCode = VK_HELP;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
} else if (c == 0xE003U) { // back space
|
|
|
|
|
keyCode = VK_BACK;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
} else if (c == 0xE004U) { // tab
|
|
|
|
|
keyCode = VK_TAB;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
} else if (c == 0xE005U) { // clear
|
|
|
|
|
keyCode = VK_CLEAR;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
} else if (c == 0xE006U) { // return
|
|
|
|
|
keyCode = VK_RETURN;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
} else if (c == 0xE007U) { // enter
|
|
|
|
|
keyCode = VK_RETURN;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
} else if (c == 0xE00BU) { // pause
|
|
|
|
|
keyCode = VK_PAUSE;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
extended = true;
|
|
|
|
|
} else if (c == 0xE00CU) { // escape
|
|
|
|
|
keyCode = VK_ESCAPE;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
} else if (c == 0xE00DU) { // space
|
|
|
|
|
keyCode = VK_SPACE;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
} else if (c == 0xE00EU) { // page up
|
|
|
|
|
keyCode = VK_PRIOR;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
extended = true;
|
|
|
|
|
} else if (c == 0xE00FU) { // page down
|
|
|
|
|
keyCode = VK_NEXT;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
extended = true;
|
|
|
|
|
} else if (c == 0xE010U) { // end
|
|
|
|
|
keyCode = VK_END;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
extended = true;
|
|
|
|
|
} else if (c == 0xE011U) { // home
|
|
|
|
|
keyCode = VK_HOME;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
extended = true;
|
|
|
|
|
} else if (c == 0xE012U) { // left arrow
|
|
|
|
|
keyCode = VK_LEFT;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
extended = true;
|
|
|
|
|
} else if (c == 0xE013U) { // up arrow
|
|
|
|
|
keyCode = VK_UP;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
extended = true;
|
|
|
|
|
} else if (c == 0xE014U) { // right arrow
|
|
|
|
|
keyCode = VK_RIGHT;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
extended = true;
|
|
|
|
|
} else if (c == 0xE015U) { // down arrow
|
|
|
|
|
keyCode = VK_DOWN;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
extended = true;
|
|
|
|
|
} else if (c == 0xE016U) { // insert
|
|
|
|
|
keyCode = VK_INSERT;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
extended = true;
|
|
|
|
|
} else if (c == 0xE017U) { // delete
|
|
|
|
|
keyCode = VK_DELETE;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
extended = true;
|
|
|
|
|
} else if (c == 0xE018U) { // semicolon
|
|
|
|
|
keyCode = VkKeyScanExW(L';', layout);
|
|
|
|
|
scanCode = MapVirtualKeyExW(LOBYTE(keyCode), 0, layout);
|
|
|
|
|
} else if (c == 0xE019U) { // equals
|
|
|
|
|
keyCode = VkKeyScanExW(L'=', layout);
|
|
|
|
|
scanCode = MapVirtualKeyExW(LOBYTE(keyCode), 0, layout);
|
|
|
|
|
} else if (c == 0xE01AU) { // numpad0
|
|
|
|
|
keyCode = VK_NUMPAD0;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
extended = true;
|
|
|
|
|
} else if (c == 0xE01BU) { // numpad1
|
|
|
|
|
keyCode = VK_NUMPAD1;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
extended = true;
|
|
|
|
|
} else if (c == 0xE01CU) { // numpad2
|
|
|
|
|
keyCode = VK_NUMPAD2;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
extended = true;
|
|
|
|
|
} else if (c == 0xE01DU) { // numpad3
|
|
|
|
|
keyCode = VK_NUMPAD3;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
extended = true;
|
|
|
|
|
} else if (c == 0xE01EU) { // numpad4
|
|
|
|
|
keyCode = VK_NUMPAD4;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
extended = true;
|
|
|
|
|
} else if (c == 0xE01FU) { // numpad5
|
|
|
|
|
keyCode = VK_NUMPAD5;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
extended = true;
|
|
|
|
|
} else if (c == 0xE020U) { // numpad6
|
|
|
|
|
keyCode = VK_NUMPAD6;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
extended = true;
|
|
|
|
|
} else if (c == 0xE021U) { // numpad7
|
|
|
|
|
keyCode = VK_NUMPAD7;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
extended = true;
|
|
|
|
|
} else if (c == 0xE022U) { // numpad8
|
|
|
|
|
keyCode = VK_NUMPAD8;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
extended = true;
|
|
|
|
|
} else if (c == 0xE023U) { // numpad9
|
|
|
|
|
keyCode = VK_NUMPAD9;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
extended = true;
|
|
|
|
|
} else if (c == 0xE024U) { // multiply
|
|
|
|
|
keyCode = VK_MULTIPLY;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
extended = true;
|
|
|
|
|
} else if (c == 0xE025U) { // add
|
|
|
|
|
keyCode = VK_ADD;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
extended = true;
|
|
|
|
|
} else if (c == 0xE026U) { // separator
|
|
|
|
|
keyCode = VkKeyScanExW(L',', layout);
|
|
|
|
|
scanCode = MapVirtualKeyExW(LOBYTE(keyCode), 0, layout);
|
|
|
|
|
} else if (c == 0xE027U) { // subtract
|
|
|
|
|
keyCode = VK_SUBTRACT;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
extended = true;
|
|
|
|
|
} else if (c == 0xE028U) { // decimal
|
|
|
|
|
keyCode = VK_DECIMAL;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
extended = true;
|
|
|
|
|
} else if (c == 0xE029U) { // divide
|
|
|
|
|
keyCode = VK_DIVIDE;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
extended = true;
|
|
|
|
|
} else if (c == 0xE031U) { // F1
|
|
|
|
|
keyCode = VK_F1;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
} else if (c == 0xE032U) { // F2
|
|
|
|
|
keyCode = VK_F2;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
} else if (c == 0xE033U) { // F3
|
|
|
|
|
keyCode = VK_F3;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
} else if (c == 0xE034U) { // F4
|
|
|
|
|
keyCode = VK_F4;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
} else if (c == 0xE035U) { // F5
|
|
|
|
|
keyCode = VK_F5;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
} else if (c == 0xE036U) { // F6
|
|
|
|
|
keyCode = VK_F6;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
} else if (c == 0xE037U) { // F7
|
|
|
|
|
keyCode = VK_F7;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
} else if (c == 0xE038U) { // F8
|
|
|
|
|
keyCode = VK_F8;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
} else if (c == 0xE039U) { // F9
|
|
|
|
|
keyCode = VK_F9;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
} else if (c == 0xE03AU) { // F10
|
|
|
|
|
keyCode = VK_F10;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
} else if (c == 0xE03BU) { // F11
|
|
|
|
|
keyCode = VK_F11;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
} else if (c == 0xE03CU) { // F12
|
|
|
|
|
keyCode = VK_F12;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
} else if (c == L'\n') { // line feed
|
|
|
|
|
keyCode = VK_RETURN;
|
|
|
|
|
scanCode = keyCode;
|
|
|
|
|
} else if (c == L'\r') { // carriage return
|
|
|
|
|
continue; // skip it
|
|
|
|
|
} else {
|
|
|
|
|
keyCode = VkKeyScanExW(c, layout);
|
|
|
|
|
scanCode = MapVirtualKeyExW(LOBYTE(keyCode), 0, layout);
|
|
|
|
|
if (!scanCode || (keyCode == 0xFFFFU)) {
|
|
|
|
|
LOG(WARN) << "No translation for key. Assuming unicode input: " << c;
|
|
|
|
|
backgroundUnicodeKeyPress(directInputTo, c, timePerKey);
|
|
|
|
|
continue; // bogus
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Note: There is *no* need to OR the keyCode with 0x0100 if
|
|
|
|
|
// shiftPressed is true. ORing the keyCode with these values is to
|
|
|
|
|
// indicate the backgroundKeyPress procedure that a modifier key
|
|
|
|
|
// press and release should be produced for this keyCode. However,
|
|
|
|
|
// when shiftPressed is true the events for the modifier were
|
|
|
|
|
// already generated by the sendKeyPress function.
|
|
|
|
|
if (shiftKey)
|
|
|
|
|
keyCode |= static_cast<WORD>(0x0100);
|
|
|
|
|
if (controlKey)
|
|
|
|
|
keyCode |= static_cast<WORD>(0x0200);
|
|
|
|
|
if (altKey)
|
|
|
|
|
keyCode |= static_cast<WORD>(0x0400);
|
|
|
|
|
|
|
|
|
|
int pause = timePerKey;
|
|
|
|
|
|
|
|
|
|
// Pause for control, alt, and shift generation: if we create these
|
|
|
|
|
// chars too fast, the target element may generated spurious chars.
|
|
|
|
|
|
|
|
|
|
if (keyCode & static_cast<WORD>(0x0100)) {
|
|
|
|
|
pause = (35 * 3); // uppercase char
|
|
|
|
|
} else if (shiftKey || controlKey || altKey) {
|
|
|
|
|
pause = (35 * 3); // shift|alt|ctrl
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
backgroundKeyPress(directInputTo, layout, keyboardState, keyCode, scanCode,
|
|
|
|
|
extended, pause);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
detachInputFromIEThread(directInputTo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void releaseModifierKeys(WINDOW_HANDLE windowHandle, int timePerKey)
|
|
|
|
|
{
|
|
|
|
|
if (!windowHandle) {
|
|
|
|
|
LOG(WARN) << "Window handle is invalid";
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HWND directInputTo = static_cast<HWND>(windowHandle);
|
|
|
|
|
|
|
|
|
|
HKL layout = attachInputToIEThread(directInputTo);
|
|
|
|
|
BYTE keyboardState[256];
|
|
|
|
|
::ZeroMemory(keyboardState, sizeof(keyboardState));
|
|
|
|
|
|
|
|
|
|
KeySendingData sendData;
|
|
|
|
|
|
|
|
|
|
sendData.to_window = directInputTo;
|
|
|
|
|
sendData.layout = layout;
|
|
|
|
|
sendData.keyboardState = keyboardState;
|
|
|
|
|
sendData.pause_time = 35;
|
|
|
|
|
|
|
|
|
|
if ((shiftPressed) || (controlPressed) || (altPressed)) {
|
|
|
|
|
postModifierReleaseMessages(shiftPressed, controlPressed, altPressed, sendData);
|
|
|
|
|
shiftPressed = false;
|
|
|
|
|
controlPressed = false;
|
|
|
|
|
altPressed = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
detachInputFromIEThread(directInputTo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool isSameThreadAs(HWND other)
|
|
|
|
|
{
|
|
|
|
|
DWORD currThreadId = GetCurrentThreadId();
|
|
|
|
|
DWORD winThreadId = GetWindowThreadProcessId(other, NULL);
|
|
|
|
|
|
|
|
|
|
return winThreadId == currThreadId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LRESULT clickAt(WINDOW_HANDLE handle, long x, long y, long button)
|
|
|
|
|
{
|
|
|
|
|
if (!handle) {
|
|
|
|
|
LOG(WARN) << "Window handle is invalid";
|
|
|
|
|
return ENULLPOINTER;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HWND directInputTo = (HWND) handle;
|
|
|
|
|
|
|
|
|
|
LRESULT result = mouseDownAt(handle, x, y, button);
|
|
|
|
|
if (result != 0) {
|
|
|
|
|
LOG(WARN) << "Mouse down did not succeed whilst clicking";
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return mouseUpAt(handle, x, y, button);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static LRESULT mouseDoubleClickDown(WINDOW_HANDLE directInputTo, long x, long y)
|
|
|
|
|
{
|
|
|
|
|
if (!directInputTo) {
|
|
|
|
|
LOG(WARN) << "Window handle is invalid";
|
|
|
|
|
return ENULLPOINTER;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!isSameThreadAs((HWND) directInputTo)) {
|
|
|
|
|
BOOL toReturn = PostMessage((HWND) directInputTo, WM_LBUTTONDBLCLK, MK_LBUTTON, MAKELONG(x, y));
|
|
|
|
|
|
|
|
|
|
// Wait until we know that the previous message has been processed
|
|
|
|
|
SendMessage((HWND) directInputTo, WM_USER, 0, 0);
|
|
|
|
|
return toReturn ? 0 : 1; // Because 0 means success.
|
|
|
|
|
} else {
|
|
|
|
|
return SendMessage((HWND) directInputTo, WM_LBUTTONDBLCLK, MK_LBUTTON, MAKELONG(x, y));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LRESULT doubleClickAt(WINDOW_HANDLE handle, long x, long y)
|
|
|
|
|
{
|
|
|
|
|
// A double click consists of the sequence
|
|
|
|
|
// 1: mouseDown
|
|
|
|
|
// 2: mouseUp
|
|
|
|
|
// 3: doubleClick
|
|
|
|
|
// 4: mouseUp
|
|
|
|
|
// Which is the equivalent to two clicks with the second mouseDown event
|
|
|
|
|
// is replaced by a doubleClick event.
|
|
|
|
|
|
|
|
|
|
if (!handle) {
|
|
|
|
|
LOG(WARN) << "Window handle is invalid";
|
|
|
|
|
return ENULLPOINTER;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LRESULT result = clickAt(handle, x, y, 0);
|
|
|
|
|
if (result != 0) {
|
|
|
|
|
LOG(WARN) << "Mouse down did not succeed whilst clicking";
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result = mouseDoubleClickDown(handle, x, y);
|
|
|
|
|
if (result != 0) {
|
|
|
|
|
LOG(WARN) << "Mouse down did not succeed whilst double clicking";
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return mouseUpAt(handle, x, y, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void fillEventData(long button, bool buttonDown, UINT *message, WPARAM *wparam)
|
|
|
|
|
{
|
|
|
|
|
if(WD_CLIENT_RIGHT_MOUSE_BUTTON == button) {
|
|
|
|
|
if(buttonDown) {
|
|
|
|
|
*message = WM_RBUTTONDOWN;
|
|
|
|
|
} else {
|
|
|
|
|
*message = WM_RBUTTONUP;
|
|
|
|
|
}
|
|
|
|
|
*wparam = MK_RBUTTON;
|
|
|
|
|
} else { // middle button support is declared in json wire protocol but it is not supported
|
|
|
|
|
leftMouseButtonPressed = buttonDown;
|
|
|
|
|
if(buttonDown) {
|
|
|
|
|
*message = WM_LBUTTONDOWN;
|
|
|
|
|
} else {
|
|
|
|
|
*message = WM_LBUTTONUP;
|
|
|
|
|
}
|
|
|
|
|
*wparam = MK_LBUTTON;
|
|
|
|
|
}
|
|
|
|
|
if (shiftPressed) {
|
|
|
|
|
*wparam |= MK_SHIFT;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LRESULT mouseDownAt(WINDOW_HANDLE directInputTo, long x, long y, long button)
|
|
|
|
|
{
|
|
|
|
|
if (!directInputTo) {
|
|
|
|
|
LOG(WARN) << "Window handle is invalid";
|
|
|
|
|
return ENULLPOINTER;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UINT message;
|
|
|
|
|
WPARAM wparam;
|
|
|
|
|
LRESULT returnValue;
|
|
|
|
|
|
|
|
|
|
fillEventData(button, true, &message, &wparam);
|
|
|
|
|
pausePersistentEventsFiring();
|
|
|
|
|
|
|
|
|
|
if (!isSameThreadAs((HWND) directInputTo)) {
|
|
|
|
|
BOOL toReturn = PostMessage((HWND) directInputTo, message, wparam, MAKELONG(x, y));
|
|
|
|
|
|
|
|
|
|
// Wait until we know that the previous message has been processed
|
|
|
|
|
SendMessage((HWND) directInputTo, WM_USER, 0, 0);
|
|
|
|
|
returnValue = toReturn ? 0 : 1; // Because 0 means success.
|
|
|
|
|
} else {
|
|
|
|
|
returnValue = SendMessage((HWND) directInputTo, message, wparam, MAKELONG(x, y));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Assume it's the left mouse button.
|
|
|
|
|
if(WD_CLIENT_RIGHT_MOUSE_BUTTON != button) {
|
|
|
|
|
updateLeftMouseButtonState(true);
|
|
|
|
|
}
|
|
|
|
|
resumePersistentEventsFiring();
|
|
|
|
|
|
|
|
|
|
return returnValue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LRESULT mouseUpAt(WINDOW_HANDLE directInputTo, long x, long y, long button)
|
|
|
|
|
{
|
|
|
|
|
if (!directInputTo) {
|
|
|
|
|
LOG(WARN) << "Window handle is invalid";
|
|
|
|
|
return ENULLPOINTER;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UINT message;
|
|
|
|
|
WPARAM wparam;
|
|
|
|
|
LRESULT returnValue;
|
|
|
|
|
|
|
|
|
|
fillEventData(button, false, &message, &wparam);
|
|
|
|
|
pausePersistentEventsFiring();
|
|
|
|
|
|
|
|
|
|
SendMessage((HWND) directInputTo, WM_MOUSEMOVE, (shiftPressed ? MK_SHIFT : 0), MAKELPARAM(x, y));
|
|
|
|
|
if (!isSameThreadAs((HWND) directInputTo)) {
|
|
|
|
|
BOOL toReturn = PostMessage((HWND) directInputTo, message, wparam, MAKELONG(x, y));
|
|
|
|
|
|
|
|
|
|
// Wait until we know that the previous message has been processed
|
|
|
|
|
SendMessage((HWND) directInputTo, WM_USER, 0, 0);
|
|
|
|
|
returnValue = toReturn ? 0 : 1; // Because 0 means success.
|
|
|
|
|
} else {
|
|
|
|
|
returnValue = SendMessage((HWND) directInputTo, message, wparam, MAKELONG(x, y));
|
|
|
|
|
}
|
|
|
|
|
// Assume it's the left mouse button.
|
|
|
|
|
if(WD_CLIENT_RIGHT_MOUSE_BUTTON != button) {
|
|
|
|
|
updateLeftMouseButtonState(false);
|
|
|
|
|
}
|
|
|
|
|
resumePersistentEventsFiring();
|
|
|
|
|
|
|
|
|
|
return returnValue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LRESULT mouseMoveTo(WINDOW_HANDLE handle, long duration, long fromX, long fromY, long toX, long toY)
|
|
|
|
|
{
|
|
|
|
|
if (!handle) {
|
|
|
|
|
LOG(WARN) << "Window handle is invalid";
|
|
|
|
|
return ENULLPOINTER;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pausePersistentEventsFiring();
|
|
|
|
|
|
|
|
|
|
HWND directInputTo = (HWND) handle;
|
|
|
|
|
long pointsDistance = distanceBetweenPoints(fromX, fromY, toX, toY);
|
|
|
|
|
const int stepSizeInPixels = 5;
|
|
|
|
|
int steps = pointsDistance / stepSizeInPixels;
|
|
|
|
|
|
|
|
|
|
long sleep = duration / max(steps, 1);
|
|
|
|
|
|
|
|
|
|
WPARAM buttonValue = (leftMouseButtonPressed ? MK_LBUTTON : 0);
|
|
|
|
|
if (shiftPressed) {
|
|
|
|
|
buttonValue |= MK_SHIFT;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < steps + 1; i++) {
|
|
|
|
|
//To avoid integer division rounding and cumulative floating point errors,
|
|
|
|
|
//calculate from scratch each time
|
|
|
|
|
int currentX = (int)(fromX + ((toX - fromX) * ((double)i) / steps));
|
|
|
|
|
int currentY = (int)(fromY + ((toY - fromY) * ((double)i) / steps));
|
|
|
|
|
SendMessage(directInputTo, WM_MOUSEMOVE, buttonValue, MAKELPARAM(currentX, currentY));
|
|
|
|
|
wait(sleep);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SendMessage(directInputTo, WM_MOUSEMOVE, buttonValue, MAKELPARAM(toX, toY));
|
|
|
|
|
resumePersistentEventsFiring(directInputTo, toX, toY, buttonValue);
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-21 10:58:08 -05:00
|
|
|
bool pending_input_events()
|
2013-01-11 22:18:32 +01:00
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|