/////////////////////////////////////////////////////////////////////////////
// Name: mousecontrol.cpp
// Purpose: cross platform mouse control api
// Author: Cesar Mauri Loba (cesar at crea-si dot com)
// Modified by:
// Created:
// Copyright: (C) 2008-14 Cesar Mauri Loba - CREA Software Systems
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
/////////////////////////////////////////////////////////////////////////////
// TODO: under X11. Test XTest extensions enabled
//if(!XTestQueryExtension(display, &ev, &er, &ma, &mi))
//{
//fprintf(stderr, "XTest extension not supported on server.\n");
//exit(1);
//}
#include
#include
#include "simplelog.h"
#if defined(WIN32)
//
// Windows
//
#define _WIN32_WINNT 0x0500
#include
#define MOUSE_EXTRA_INFO 69
inline float roundf(float x) { return (x-floorf(x))>0.5 ? ceilf(x) : floorf(x); }
#else // WIN32
//
// Linux
//
#include
#include
#include
#include
#endif // WIN32
#include
#include
#include "mousecontrol.h"
// Mouse actions
#define MOUSE_MOVE_ABS 0x8000 // Absolute motion
#define MOUSE_MOVE_REL 0x0000 // Relative motion
#define MOUSE_LEFTDOWN 0x0002 // Left button down
#define MOUSE_LEFTUP 0x0004 // Left button up
#define MOUSE_RIGHTDOWN 0x0008 // Right button down
#define MOUSE_RIGHTUP 0x0010 // Right button up
#define MOUSE_MIDDLEDOWN 0x0020 // Middle button down
#define MOUSE_MIDDLEUP 0x0040 // Middle button up
static
void sleep_milliseconds(unsigned ms)
{
if (!ms) return;
#ifdef WIN32
Sleep (ms);
#else
usleep (ms * 1000);
#endif
}
CMouseControl::CMouseControl (void* pDisplay)
: m_enabledRestrictedWorkingArea(false)
, m_enabledWrapPointer(false)
#if !defined(WIN32)
, m_closeDisplay(false)
#endif
{
m_leftPercent= m_rightPercent= m_topPercent= m_bottomPercent= 1.0f;
// Under Windows display parameter is ignored
#if defined(WIN32)
assert (pDisplay== NULL);
#else
if (pDisplay) m_pDisplay= pDisplay;
else {
m_pDisplay= XOpenDisplay(NULL);
m_closeDisplay= true;
assert (m_pDisplay);
if (!m_pDisplay) throw std::runtime_error("mousecontrol: cannot open display");
}
#endif
// Read screen size
OnDisplayChanged ();
m_VirtualXIni= m_VirtualYIni= 0;
m_VirtualHeight= (float) m_ScreenHeight;
m_VirtualWidth= (float) m_ScreenWidth;
m_fDx= m_fDy= 1.0f;
m_minDeltaThreshold= 0.0f;
m_actualMotionWeight= 1.0f;
m_dxant= 0.0f; m_dyant= 0.0f;
for (int i= 0; i< ACCEL_ARRAY_SIZE; i++) m_accelArray[i]= (float) 1;
m_sendActionWait= 0;
}
CMouseControl::~CMouseControl()
{
#if !defined(WIN32)
if (m_closeDisplay) XCloseDisplay(static_cast(m_pDisplay));
#endif
}
void CMouseControl::GetScreenSize()
{
#if defined(WIN32)
DEVMODE devMode;
// Get screen size. TODO: test with multiple displays
BOOL retval= EnumDisplaySettings(NULL, ENUM_CURRENT_SETTINGS, &devMode);
if (!retval) { exit (-1); }
m_ScreenHeight= devMode.dmPelsHeight;
m_ScreenWidth= devMode.dmPelsWidth;
#else // Linux
m_ScreenHeight=
DisplayHeight (static_cast(m_pDisplay), DefaultScreen (static_cast(m_pDisplay)));
m_ScreenWidth=
DisplayWidth (static_cast(m_pDisplay), DefaultScreen (static_cast(m_pDisplay)));
#endif
slog_write (SLOG_PRIO_DEBUG, "current screen size: %d, %d\n", m_ScreenWidth, m_ScreenHeight);
}
void CMouseControl::GetPointerLocation (int& x, int& y)
{
#if defined(WIN32)
CURSORINFO pci;
pci.cbSize= sizeof(CURSORINFO);
pci.ptScreenPos.x= 0;
pci.ptScreenPos.y= 0;
GetCursorInfo(&pci);
x= (int) pci.ptScreenPos.x;
y= (int) pci.ptScreenPos.y;
#else // Linux
Window root, child;
int rootX, rootY;
unsigned int xstate;
Window rootWin=
RootWindow (static_cast(m_pDisplay), DefaultScreen (static_cast(m_pDisplay)));
XQueryPointer(
static_cast(m_pDisplay), rootWin, &root, &child, &rootX, &rootY, &x, &y, &xstate );
#endif
}
void CMouseControl::OnDisplayChanged ()
{
GetScreenSize();
RecomputeWorkingArea ();
}
void CMouseControl::SetAbsVirtualResolution (float xIni, float yIni, float width, float height)
{
assert (xIni>= 0 && yIni>= 0 && width> 0 && height> 0);
m_VirtualXIni= xIni;
m_VirtualYIni= yIni;
m_VirtualWidth= width;
m_VirtualHeight= height;
}
void CMouseControl::SetWorkingArea (float leftPercent, float rightPercent, float topPercent, float bottomPercent)
{
SetTopPercent (topPercent);
SetLeftPercent (leftPercent);
SetRightPercent (rightPercent);
SetBottomPercent (bottomPercent);
}
void CMouseControl::RecomputeWorkingArea ()
{
m_minScreenX= (m_ScreenWidth -
(int) ((float) m_ScreenWidth * m_leftPercent)) / 2;
m_minScreenY= (m_ScreenHeight -
(int) ((float) m_ScreenHeight * m_topPercent)) / 2;
m_maxScreenX= (m_ScreenWidth - 1) - (m_ScreenWidth -
(int) ((float) m_ScreenWidth * m_rightPercent)) / 2;
m_maxScreenY= (m_ScreenHeight - 1) - (m_ScreenHeight -
(int) ((float) m_ScreenHeight * m_bottomPercent)) / 2;
}
void CMouseControl::SetRelAcceleration2 (long delta0, float factor0,
long delta1, float factor1)
{
assert (delta0> 2 &&delta1> 2);
assert (factor0> 0.0f && factor1> 0.0f);
if (delta0>= ACCEL_ARRAY_SIZE) delta0= ACCEL_ARRAY_SIZE;
if (delta1>= ACCEL_ARRAY_SIZE) delta1= ACCEL_ARRAY_SIZE;
int i;
for (i= 0; i< delta0; i++) m_accelArray[i]= 1;
for (;i< delta1; i++) m_accelArray[i]= factor0;
float j= 0;
for (;i< ACCEL_ARRAY_SIZE; i++) {
m_accelArray[i]= factor0 * factor1 + j;
j+= 0.1f;
}
}
bool CMouseControl::EnforceWorkingAreaLimits (int &x, int &y)
{
bool retval= false;
if (x< m_minScreenX) { x= m_minScreenX; retval= true; }
else if (x> m_maxScreenX) { x= m_maxScreenX; retval= true; }
if (y< m_minScreenY) { y= m_minScreenY; retval= true; }
else if (y> m_maxScreenY) { y= m_maxScreenY; retval= true; }
return retval;
}
void CMouseControl::Virt2Fis (float virtX, float virtY, float &fisX, float &fisY)
{
float fFisX, fFisY, fVirtWidth, fVirtHeight;
fFisX= virtX - m_VirtualXIni;
fFisY= virtY - m_VirtualYIni;
fVirtWidth= m_VirtualWidth;
fVirtHeight= m_VirtualHeight;
if (fFisX< 0.0f) fFisX= 0.0f;
else if (fFisX>= fVirtWidth) fFisX= 1.0f;
else fFisX/= fVirtWidth;
if (fFisY< 0.0f) fFisY= 0.0f;
else if (fFisY>= fVirtHeight) fFisY= 1.0f;
else fFisY/= fVirtHeight;
// To integer
fisX= fFisX * (float) (m_ScreenWidth - 1);
fisY= fFisY * (float) (m_ScreenHeight - 1);
}
void CMouseControl::MovePointerAbs (float x, float y)
{
// OnDisplayChanged ();
int iX, iY;
float fisX, fisY, dx, dy;
Virt2Fis (x, y, fisX, fisY);
GetPointerLocation (iX, iY);
dx= fisX - (float) iX;
dy= fisY - (float) iY;
// Low-pass filter
dx= dx * (1.0f - m_actualMotionWeight) + m_dxant * m_actualMotionWeight;
dy= dy * (1.0f - m_actualMotionWeight) + m_dyant * m_actualMotionWeight;
m_dxant= dx; m_dyant= dy;
// Map to screen coordinates
iX= iX + (int) dx;
iY= iY + (int) dy;
EnforceWorkingAreaLimits (iX, iY);
DoMovePointerAbs (iX, iY);
}
float CMouseControl::MovePointerRel (float dx, float dy, int* dxRes, int* dyRes)
{
OnDisplayChanged ();
// Apply factors
dx*= m_fDx;
dy*= m_fDy;
// Low-pass filter
dx= dx * (1.0f - m_actualMotionWeight) + m_dxant * m_actualMotionWeight;
dy= dy * (1.0f - m_actualMotionWeight) + m_dyant * m_actualMotionWeight;
m_dxant= dx; m_dyant= dy;
// Acceleration
float distance= (float) ::sqrt (dx * dx + dy * dy);
unsigned int iAccelArray= (unsigned int) (distance + 0.5f);
if (iAccelArray>= ACCEL_ARRAY_SIZE) iAccelArray= ACCEL_ARRAY_SIZE - 1;
dx*= m_accelArray[iAccelArray];
dy*= m_accelArray[iAccelArray];
// Apply delta threshold
if (-m_minDeltaThreshold < dx && dx < m_minDeltaThreshold) dx= 0.0f;
if (-m_minDeltaThreshold < dy && dy < m_minDeltaThreshold) dy= 0.0f;
int idx= (int) roundf(dx);
int idy= (int) roundf(dy);
int mouseX, mouseY;
if (m_enabledRestrictedWorkingArea && !m_enabledWrapPointer) {
GetPointerLocation (mouseX, mouseY);
if (mouseX + idx< m_minScreenX) idx= m_minScreenX - mouseX;
else if (mouseX + idx > m_maxScreenX) idx= m_maxScreenX - mouseX;
if (mouseY + idy < m_minScreenY) idy= m_minScreenY - mouseY;
else if (mouseY + idy > m_maxScreenY) idy= m_maxScreenY - mouseY;
}
if (m_enabledWrapPointer) {
int minWrapX= 0;
int maxWrapX= m_ScreenWidth;
int minWrapY= 0;
int maxWrapY= m_ScreenHeight;
if (m_enabledRestrictedWorkingArea) {
minWrapX= m_minScreenX;
maxWrapX= m_maxScreenX;
minWrapY= m_minScreenY;
maxWrapY= m_maxScreenY;
}
GetPointerLocation(mouseX, mouseY);
if (mouseX + idx < minWrapX) {
idx -= mouseX - minWrapX;
DoMovePointerAbs(maxWrapX, mouseY);
}
if (mouseX + idx > maxWrapX) {
idx -= maxWrapX - mouseX;
DoMovePointerAbs(minWrapX, mouseY);
}
if (mouseY + idy < minWrapY) {
idy -= mouseY - minWrapY;
DoMovePointerAbs(mouseX, maxWrapY);
}
if (mouseY + idy > maxWrapY) {
idy -= maxWrapY - mouseY;
DoMovePointerAbs(mouseX, minWrapY);
}
}
DoMovePointerRel (idx, idy);
if (dxRes) *dxRes= idx;
if (dyRes) *dyRes= idy;
return (float) sqrt((double)(idx * idx + idy * idy));
}
void CMouseControl::DoMovePointerAbs (long x, long y)
{
SendMouseCommand (x, y, MOUSE_MOVE_ABS);
}
void CMouseControl::DoMovePointerRel (long dx, long dy)
{
SendMouseCommand (dx, dy, MOUSE_MOVE_REL);
}
// Send a mouse control system command
void CMouseControl::SendMouseCommand (long x, long y, int flags)
{
#if defined(WIN32)
INPUT ip;
// Allways define MOUSEEVENTF_MOVE flag, other flags are coincident
// with native flags under windows
flags|= MOUSEEVENTF_MOVE;
if (flags & MOUSE_MOVE_ABS) {
// Normalize absolute motion
x= (x * 65535) / (m_ScreenWidth - 1);
y= (y * 65535) / (m_ScreenHeight - 1);
}
/* Need to swap buttons? */
if (flags & (MOUSE_LEFTDOWN | MOUSE_LEFTUP | MOUSE_RIGHTDOWN | MOUSE_RIGHTUP) &&
::GetSystemMetrics(SM_SWAPBUTTON)) {
if (flags & MOUSE_LEFTDOWN) {
flags &= ~MOUSE_LEFTDOWN;
flags |= MOUSE_RIGHTDOWN;
}
else if (flags & MOUSE_RIGHTDOWN) {
flags &= ~MOUSE_RIGHTDOWN;
flags |= MOUSE_LEFTDOWN;
}
if (flags & MOUSE_LEFTUP) {
flags &= ~MOUSE_LEFTUP;
flags |= MOUSE_RIGHTUP;
}
else if (flags & MOUSE_RIGHTUP) {
flags &= ~MOUSE_RIGHTUP;
flags |= MOUSE_LEFTUP;
}
}
ip.type = INPUT_MOUSE;
ip.mi.dwFlags = flags;
ip.mi.mouseData = 0;
ip.mi.dwExtraInfo = MOUSE_EXTRA_INFO;
ip.mi.time = 0;
ip.mi.dx= x;
ip.mi.dy= y;
SendInput(1, &ip, sizeof(ip));
#else
if (flags== MOUSE_MOVE_ABS)
// Absolute motion
XTestFakeMotionEvent(
static_cast(m_pDisplay),
DefaultScreen(static_cast(m_pDisplay)),
x, y,
CurrentTime
);
else if (flags== MOUSE_MOVE_REL)
// Relative motion
XTestFakeRelativeMotionEvent(static_cast(m_pDisplay), x, y, CurrentTime);
else
{
// Button press
bool is_press= false;
unsigned int button= 0;;
switch (flags)
{
case MOUSE_LEFTDOWN: // Left button down
button= 1; is_press= true;
break;
case MOUSE_LEFTUP: // Left button up
button= 1; is_press= false;
break;
case MOUSE_RIGHTDOWN: // Right button down
button= 3; is_press= true;
break;
case MOUSE_RIGHTUP: // Right button up
button= 3; is_press= false;
break;
case MOUSE_MIDDLEDOWN: // Middle button down
button= 2; is_press= true;
break;
case MOUSE_MIDDLEUP: // Middle button up
button= 2; is_press= false;
break;
default:
assert (false);
}
XTestFakeButtonEvent(static_cast(m_pDisplay), button, is_press, CurrentTime);
}
XFlush (static_cast(m_pDisplay));
#endif
}
void CMouseControl::CenterPointer ()
{
DoMovePointerAbs(m_ScreenWidth/2, m_ScreenHeight/2);
}
void CMouseControl::LeftDown ()
{
SendMouseCommand (0, 0, MOUSE_LEFTDOWN);
}
void CMouseControl::LeftUp ()
{
SendMouseCommand (0, 0, MOUSE_LEFTUP);
}
void CMouseControl::MiddleDown ()
{
SendMouseCommand (0, 0, MOUSE_MIDDLEDOWN);
}
void CMouseControl::MiddleUp ()
{
SendMouseCommand (0, 0, MOUSE_MIDDLEUP);
}
void CMouseControl::RightDown ()
{
SendMouseCommand (0, 0, MOUSE_RIGHTDOWN);
}
void CMouseControl::RightUp ()
{
SendMouseCommand (0, 0, MOUSE_RIGHTUP);
}
void CMouseControl::LeftClick ()
{
LeftDown ();
sleep_milliseconds(m_sendActionWait);
LeftUp ();
}
void CMouseControl::MiddleClick ()
{
MiddleDown ();
sleep_milliseconds(m_sendActionWait);
MiddleUp ();
}
void CMouseControl::RightClick ()
{
RightDown ();
sleep_milliseconds(m_sendActionWait);
RightUp ();
}
void CMouseControl::LeftDblClick ()
{
LeftClick ();
sleep_milliseconds(m_sendActionWait);
LeftClick ();
}