Search code examples
c++qtwinapisendinput

Mouse pointer freezes after calling Sendinput, on minimize/maximize/close window buttons


I am building an application that receives mouse move/press/release commands from another computer and simulates them using the windows Sendinput function.

Everything works fine except the case where I click on the minimize or maximize or close buttons of the same window that generates the Sendinput calls.

In that case the mouse pointer freezes on top of the minimize button (or maximize or close) and the UI event loop seems to freeze completly. If I manually click somewhere on the screen the UI unfreezes and everything works again.

Here is a screenshot of what happens when I generate a sendinput call on top of the minimize button: enter image description here

This is the code I am using to simulate mouse move / press / release: Mouse.cpp

#include "Mouse.h"

#ifdef _WIN32
    #include <windows.h>
#endif

#ifdef _WIN32

void Mouse::moveToPoint(const int x, const int y)
{
    // get screen resolution
    HDC hDCScreen = GetDC(NULL);
    int horres = GetDeviceCaps(hDCScreen, HORZRES);
    int vertres = GetDeviceCaps(hDCScreen, VERTRES);
    ReleaseDC(NULL, hDCScreen);

    const int points_in_line = 65535;
    const double points_in_x_pixel = points_in_line / static_cast<double>(horres);
    const double points_in_y_pixel = points_in_line / static_cast<double>(vertres);

    INPUT event;
    event.type = INPUT_MOUSE;
    event.mi.dx = x * points_in_x_pixel; //+ 0.5;
    event.mi.dy = y * points_in_y_pixel; //+ 0.5;
    event.mi.mouseData = 0;
    event.mi.dwFlags =  MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_MOVE;
    event.mi.time = 0;
    event.mi.dwExtraInfo = 0;

    SendInput(1, &event, sizeof(event));
}

void Mouse::press(Qt::MouseButton button)
{
    DWORD btn = button == Qt::LeftButton  ? MOUSEEVENTF_LEFTDOWN   :
                button == Qt::RightButton ? MOUSEEVENTF_RIGHTDOWN  :
                button == Qt::MidButton   ? MOUSEEVENTF_MIDDLEDOWN : 0;

    INPUT event;
    event.type = INPUT_MOUSE;
    event.mi.dx = 0;
    event.mi.dy = 0;
    event.mi.mouseData = 0;
    event.mi.dwFlags = btn;
    event.mi.time = 0;
    event.mi.dwExtraInfo = 0;

    SendInput(1, &event, sizeof(event));
}

void Mouse::release(Qt::MouseButton button)
{
    DWORD btn = button == Qt::LeftButton  ? MOUSEEVENTF_LEFTUP   :
                button == Qt::RightButton ? MOUSEEVENTF_RIGHTUP  :
                button == Qt::MidButton   ? MOUSEEVENTF_MIDDLEUP : 0;

    INPUT event;
    event.type = INPUT_MOUSE;
    event.mi.dx = 0;
    event.mi.dy = 0;
    event.mi.mouseData = 0;
    event.mi.dwFlags = btn;
    event.mi.time = 0;
    event.mi.dwExtraInfo = 0;

    SendInput(1, &event, sizeof(event));
}

void Mouse::scroll(int value)
{
    INPUT event;
    event.type = INPUT_MOUSE;
    event.mi.dx = 0;
    event.mi.dy = 0;
    event.mi.mouseData = value * WHEEL_DELTA;
    event.mi.dwFlags = MOUSEEVENTF_WHEEL;
    event.mi.time = 0;
    event.mi.dwExtraInfo = 0;

    SendInput(1, &event, sizeof(event));
}

void Mouse::doubleClick(Qt::MouseButton button)
{
    press(button);
    release(button);

    std::chrono::milliseconds dura(100);
    std::this_thread::sleep_for(dura);

    press(button);
    release(button);
}
#endif

and the header file: Mouse.h

#ifndef MOUSE_H
#define MOUSE_H

#include <thread>
#include <QtGlobal>
#include <QtEvents>
#include <QDebug>
#include <iostream> //std::cout

class Mouse {
public:
    // fails if given coordinates are not in the screen's rect
    // Linux: fails if there is an opening X display error
    static void moveToPoint(const int x, const int y);

    static void doubleClick(Qt::MouseButton button);

    // fails if the given button is not Qt::LeftButton, Qt::RightButton or Qt::MiddleButton
    // Linux: fails if there is an opening X display error
    static void press(Qt::MouseButton button);
    static void release(Qt::MouseButton button);

    // Linux: fails if there is an opening X display error
    static void scroll(int value); //positive values for scrolling up, negative for scrolling down

private:
    static Qt::MouseButtons bp;
    static void selectArea(const int x, const int y);
};
#endif // MOUSE_H

What could be the cause of this strange behaviour?


Solution

  • As you've discovered, clicking on one of the caption buttons causes Windows to enter its own message loop waiting for the button release.

    While Windows is in this message loop, whichever message your own message loop is waiting for (to know when to release the button) isn't dispatched to you. You never receive the message to release the mouse button, so never inject that input and so the pointer seems frozen.

    The solution is to move your SendInput calls to a non-GUI thread.