///////////////////////////////////////////////////////////////////////////// // 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 (); }