Qt get mouse events outside of the application window - c++

First I'm not certain this is even possible without some sort of hacking of X.11 input but the discussions i'd seen online made me think it was possible.
Allow me to explain what I hope to do. I want a Qt application which will most likely just be a small window that sides on the screen sort of like a widget. The application does nothing until the user drags another application window over the top of it. The way I was hoping to detect this was to track the mouse and see if the left click is down and the mouse is over the Qt window and Qt is not the active window then do some action. However currently I havent been able to get mouse events when my Qt application is not the active window. I think some of these posts I linked refer to 'window' as a QWindow inside the QApp.
What I mean by window however is a X.11 Window, any application opened in X. My screenshots I hope highlight my current plight. I've attached my code as well and am happy to take any suggestions. Any other hacks that are known to help me achieve this I would also appreciate being informed of.
The red shows where my cursor has clicked, and the mouse event is recorded outside of the Qt window. This was triggered by the 'FocusOut' event however and is the last event I have managed to detect.
As we can see in the console, the mouse has moved but no events are caught. I really want to detect when the mouse crosses over onto the position the Qt App Window is at regardless of whether it is on top of another window or not.
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::MouseMove)
{
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
statusBar()->showMessage(QString("Mouse move (%1,%2)").arg(mouseEvent->pos().x()).arg(mouseEvent->pos().y()));
qDebug() << QString::number(mouseEvent->pos().x());
qDebug() << QString::number(mouseEvent->pos().y());
}
if (event->type() == QEvent::FocusOut)
{
QFocusEvent *focusEvent = static_cast<QFocusEvent*>(event);
focusEvent->accept();
qDebug()<<"event Filter Mouse Move111"<<QCursor::pos();
}
return false;
}
void MainWindow::initWindow()
{
//Makes the window frameless and always on top
//setWindowFlags(Qt::FramelessWindowHint|Qt::WindowStaysOnTopHint);
//Makes the window transparent
//setAttribute(Qt::WA_TranslucentBackground);
//Allows 'mouseMoved' events to be sent, not sure yet if this will be useful, I think we want mouseDragged
setMouseTracking(true);
grabMouse();
//setup this as an event filter for mouse events
qApp->installEventFilter(this);
}

Alright heres how I solved this problem. The event system in Qt, any application I assume, won't register events when the window is not active. However the process is obviously still running so data you can access while the window is active you can access whilst the window is no longer active.
Use a timed poll method to get the mouse position every n seconds
//Method used to hopefully track the mouse regardless of whether or not it is inside the active window
void MainWindow::pollMouse(unsigned long sec)
{
//Loop forever
while ( true )
{
QPoint mouseLoc = QCursor::pos();
qDebug() << "Mouse position global: x,y" << mouseLoc.x() << mouseLoc.y();
QThread::sleep(sec);
}
}

Related

Qt 5 Disabling Click-and-Hold

I'm trying to solve an issue in a Qt5 application that's caused by a touchscreen driver I wrote a couple years ago. The problem being that my touchscreen driver generates mouse press events to simulate touch behavior. This was because at the time I wrote it, my team was using an outdated Linux OS that didn't have a native touch driver.
The problem is that when I touch the +/- buttons (the scroll buttons that increase/decrease the slider by a single tick, or cause it to rapidly scroll if held), the slider rapidly slides up/down as if I had clicked and held the button. This is because my driver uses QApplication::postEvent() to dispatch the mouse event from a separate thread, and the delay in between touch and release is just long enough for it to register a click-and-hold type event, though the release event doesn't stop the sliding.
I would rewrite the application to use the now available native touch driver, but it doesn't offer enough control to be able to do what my driver does (my driver can generate left, right, or middle mouse events, whereas the native driver only provides left).
I tried rewriting the driver to use QApplication::sendEvent() instead, but that was causing a segmentation fault that I couldn't figure out the cause of.
So is there a way I can disable the "click-and-hold" type behavior on the QSlider itself? So that I can still tap the buttons, but they'll only increase/decrease by a single tick at a time?
EDIT: Here's an example of how I'm generating the "touch" events via my driver.
void InputHandler::touchPress(TouchPoint * tp, QWidget * widget) {
QPoint screenPos(tp->cx(), tp->cy());
QWidget * target = widget;
if(target == NULL) target = QApplication::widgetAt(screenPos);
if(target != NULL) {
QPoint local = target->mapFromGlobal(screenPos);
QMouseEvent * press = new QMouseEvent(QEvent::MouseButtonPress, local, screenPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
QApplication::postEvent(target, press);
DBG(" -- touch press on: " << typeid(*widget).name());
// Check to see if the target is a QLineEdit or QComboBox, and if so, set focus on it.
QLineEdit * lineEdit = dynamic_cast<QLineEdit*>(target);
QComboBox * comboBox = dynamic_cast<QComboBox*>(target);
if(lineEdit) lineEdit->setFocus(Qt::MouseFocusReason);
if(comboBox) comboBox->setFocus(Qt::MouseFocusReason);
}
}
EDIT 2: So a coworker played around with the slider and pointed out that he pressed the +/- buttons, causing it to slide, and then he clicks the "reset" button we have to reset all controls on the form, the slider would reset and then continue sliding, indicating that the touch press was never released. He then would click the +/- buttons normally with the mouse and reset again and it would stop. So even though the logs indicate that the mousePressEvent and mouseReleaseEvent methods are being triggered by my events, they don't seem to have the same behavior as using the mouse.
Any ideas?
EDIT 3: If it helps, I added an eventFilter and printed out every event type that the QSlider receives, from when I first touch the screen to when I release. The events received are:
Mouse Press (2)
Tooltip Change (184)
Paint (12)
Mouse Move (5)
Mouse Move (5)
Mouse Release (3)
But then after the release event, I get spammed in the logs with QEvent::Timer events, which does not happen when I simply click with the mouse, as opposed to touching the slider with the touchscreen. If I then tap somewhere else, drag the slider, or click the slider with the actual mouse, these timer events stop.
Why is my generated QMouseEvent causing these QEvent::Timer events to be generated when I tap the slider with the touchscreen, but not with the mouse?
A minimal example to generate mouse press/release events that are correctly handled by QSlider is:
#include <QApplication>
#include <QMainWindow>
#include <QMouseEvent>
#include <QSlider>
#include <QTimer>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QMainWindow m;
QSlider *slider = new QSlider();
m.setCentralWidget(slider);
m.resize(100, 100);
m.show();
QTimer::singleShot(1000, [&](){
a.postEvent(slider, new QMouseEvent(QEvent::MouseButtonPress, QPoint(50,50), m.mapToGlobal(QPoint(50,50)), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier));
a.postEvent(slider, new QMouseEvent(QEvent::MouseButtonRelease, QPoint(50,50), m.mapToGlobal(QPoint(50,50)), Qt::LeftButton, Qt::NoButton, Qt::NoModifier));
});
return a.exec();
}
This example decreases the slider value with 1 tick after 1 second.
Note that it is important to pass Qt::NoButton as buttons parameters to the QEvent::MouseButtonRelease event, otherwise the slider will continue moving to the current mouse position (see QAbstractSlider::setRepeatAction), as QSlider thinks the button is still pressed. I assume this behaviour is what you call 'press-and-hold'.

Qt transfer mouse move events to new window

I'm handling mouse events (through an event filter) on a QTabBar to detect when the user clicks and drags a tab to tear it off. When this happens, I remove the current widget from the QTabWidget and create a new top level widget that I add it to so that it's detached and floating, just like when you tear off a tab in Chrome. This new floating window is a custom frameless widget I made that has a custom titlebar that I handle mouse events on to allow the user to drag the window around the desktop.
The problem I'm having is that when you click and drag the tab to pull it off and the new top level window is created, I can't seem to get the application to continue dragging the new window without the user clicking and dragging on my titlebar. I'd like for the original drag motion to just transfer to the new widget so that the use can keep dragging it until he releases the mouse button.
I've tried creating a "fake" QMouseEvent to pass to my title bar (by calling QCoreApplication::sendEvent(object, event) to make it think it's been clicked on, but it doesn't receive any mouse move events unless you actually click on it. I'm open to other ideas.
Update: I added some debugging statements and it looks like once I detach the tab and create the new floating window, the QMainWindow continues to receive the mouse move events until I release the mouse button. I'll try adding some code to forward these mouse events on to the new floating window, but that feels kinda hacky.
Correction: The QMainWindow is not receiving the mouse move events, an object named "MainWindowWindow" is, which is a QWidgetWindow that I guess is a private type used to manage top level windows?
Ok, I got it working, but I don't like it. As I said in my correction, once the tab is detached, the QWidgetWindow starts receiving the mouse move events. This is a private type that's not exposed via the API.
What I ended up doing was installing an event filter on the application and looking for mouse events on an object that inherits from QWidgetWindow. I then directly call new drag() and endDrag() methods that I added to my floating window class. In my eventFilter method, it looks like this.
if (watched->inherits("QWidgetWindow"))
{
if (floater) // <- the floating window that was recently detached
{
if (event->type() == QEvent::MouseMove)
{
floater->drag(QCursor::pos()); // <-- Pass global cursor pos
}
else if (event->type() == QEvent::MouseRelease)
{
floater->endDrag();
floater = nullptr;
}
}
}
Like I said, if feels dirty because I A) am checking for events on a private type that I'm not really supposed to know about, and B) telling another window to drag around when it has code to do this itself. But it works, and I've spent enough time working on this. If someone has a more elegant solution, I'm happy to hear it.

Qt filter out mouse presses

I am writing a software for an embedded linux device running qt4.8 with a touch screen.
Right now I am having issues with the touch screen being too sensitive to touch and I would get 2 touches on the screen at once.
What I want to do is filter out any touches after the first after a period of time.
What I have so far is I have installed the eventfilter and listening for mouse events:
qApp->installEventFilter(this);
...
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
switch(event->type()){
case QEvent::MouseButtonPress:{
if(timer.isActive()) return true;
timer.start();
}
}
The problem I am having is that when going between pages/menu's in my application the GUI will lag and block the system for longer than my timer. So when the page finally loads, by this time my timer is finished and the 2nd unwanted mouse press is processed...
Is there a better way to do this? Emiting a signal to trigger the time AFTER the new page/menu is loaded?; Threading out the eventfilter to handle mouse presses in a different thread?
Thank you.

How can I selectively make QWidget accept focus from a mouse click?

I'm creating an application that provides a visual representation of nodes and lines connecting them together. Both nodes and lines are represented by custom QWidgets, WidgetNode and WidgetLine, say.
I've implemented WidgetLine as a transparent widget that is large enough to contain the start and end point of the line, and a custom function to draw the line itself.
I would like it so that if the user clicks on or right next to the line then the WidgetLine receives focus, but if they click further away from the line (but still over the rectangular area covered by WidgetLine's geometry) then the click is completely ignored by WidgetLine and passed on to the widget below.
I first tried doing this with a custom focusInEvent() function on WidgetLine, but found the mouse clicks weren't propagating below. I then tried setting the focus policy to Qt::NoFocus and using a custom mousePressEvent() using setFocus() to manually set the focus when appropriate, but the mouse events are still not being propagated to widgets above even when I call ignore() on them.
Finally, I've tried installing an event filter to reject mouse events, with this event filter function
bool WidgetLineFilter::eventFilter(QObject* object, QEvent* event)
{
assert(object == mCord);
if (event->type() == QEvent::MouseButtonPress)
{
QMouseEvent* e = dynamic_cast<QMouseEvent*>(event);
assert(e);
if (e)
{
QPoint mouseRelativeToParent = mCord->mapToParent(e->pos());
// calculate distance of mouse click to patch cord
QLineF line(mCord->line());
float distance = distanceFromPointToLine(QVector2D(line.p1()), QVector2D(line.p2()), QVector2D(mouseRelativeToParent));
qDebug() << distance;
const float distanceThreshold = 2.f;
if (distance < distanceThreshold)
{
qDebug() << "consuming mouse click for focus";
mCord->setFocus(Qt::MouseFocusReason);
return true;
}
else{
qDebug() << "mousepressevent too far for focus";
return QObject::eventFilter(object, event);
}
}
}
return false;
}
But this still does not propagate the mouse events to the parent on the "mousepressevent too far for focus" case. I've also tried returning false and true from here, and calling ignore on e but the widgets below are not receiving the click.
(NB the above approaches do work in the sense that WidgetLine only gets focus at the right time, it's just the widgets below that aren't receiving the press events when it doesn't get focus.)
Any ideas on how to fix this?
Store the mouse pos in a global var. Have all of your widgets have enter/leave events like the following and use that to check what widget you're in / near when you run the func.
void QGLWidget::enterEvent(QEvent *)
{
setFocus();
}
void QGLWidget::leaveEvent(QEvent *)
{
clearFocus();
}
In the end, I created an event filter to selectively intercept mouse events and installed it on the base window and recursively on every single child widget of the base window (installing it on new child widgets when they are created). This filter calls the base window with each mouse press event which then iterates through each WidgetLine, testing if they should be selected by this mouse press and setting focus on them if they should. If they all test false then the filter releases the event, otherwise the filter consumes it.
WidgetLine's are then set to be transparent for mouse events with
setAttribute(Qt::WA_TransparentForMouseEvents);
It's messier than it ought to be to achieve this but does the trick.

Qt user resize event ends (stops)

I have a QWidget and i need to do some actions (refresh a picture in widget) when resize event ends. How can i catch this action?
I need to catch moment when user ENDs all his resize actions by releasing mouse button. It is not a good practice in my application to refresh image every pixel resized. It should calls only when mouse released and resize actions ends.
I am just tried to reimplement QMouseReleaseEvent to catch it, but it do not works when user presses on the border of widget to resize it. It means does not working in our situation.
Then i was tried to create my own QSizeGrip and insert it on the bottom of my widget, but reimplemented event QMouseReleaseEvent again did not work in it. Event did not generates any time user released mouse. I do not know why.
Anybody can help me with that problem?
Thanks in advance.
The timeout method is a decent idea, but if the user is resizing and then pauses for longer than the timer's interval then you end up not getting the true "user is done resizing the window" event. Setting the interval longer makes that situation less likely, but by doing that, you end up having a long delay between the time the user finished resizing and the time your function gets called. In my search for a solution, I found quite a few people solving it using the timer method, so apparently it's reliable enough for some use cases, but I think it's a bit hacky.
I like mhstnsc's idea, so after implementing it, I decided to add some code here that might be of use to someone trying to do something similar.
You could easily adapt it to catch the "user is done moving the window" event by making a m_bUserIsMoving flag and overriding "void MainWindow::moveEvent(QMoveEvent* pEvent)". I'm using it to save a config file whenever the user finishes resizing or moving the window, so that the last position will always be saved even if the app is killed in an unclean manner.
// constructor
MainWindow::MainWindow(QWidget* pParent, Qt::WindowFlags flags) : QMainWindow(pParent, flags)
{
m_bUserIsResizing = false;
qApp->installEventFilter(this);
}
// this will be called when any event in the application occurs
bool MainWindow::eventFilter(QObject* pObj, QEvent* pEvent)
{
// We need to check for both types of mouse release, because it can vary on which type happens when resizing.
if ((pEvent->type() == QEvent::MouseButtonRelease) || (pEvent->type() == QEvent::NonClientAreaMouseButtonRelease)) {
QMouseEvent* pMouseEvent = dynamic_cast<QMouseEvent*>(pEvent);
if ((pMouseEvent->button() == Qt::MouseButton::LeftButton) && m_bUserIsResizing) {
printf("Gotcha!\n");
m_bUserIsResizing = false; // reset user resizing flag
}
}
return QObject::eventFilter(pObj, pEvent); // pass it on without eating it
}
// override from QWidget that triggers whenever the user resizes the window
void MainWindow::resizeEvent(QResizeEvent* pEvent) { m_bUserIsResizing = true; }
It's slightly more complicated than the timer, but more robust.
I've do it in this way:
inherit my class from QWidget
define private variable int timerId = 0
overload QWidget::resizeEvent and QObject::timerEvent
void MapLoader::resizeEvent(QResizeEvent *){
if (timerId){
killTimer(timerId);
timerId = 0;
}
timerId = startTimer(5000/*delay beetween ends of resize and your action*/);
}
void MapLoader::timerEvent(QTimerEvent *te){
/*your actions here*/
killTimer(te->timerId());
timerId = 0;
}
Mouse events on windows decoration are managed by the underlying window system, this is why you can't catch them as you tried.
I had the same issue once, the solution I chose was to (re)start a singleshot QTimer on each resize event, and only process the update after the timer interval elapsed. Not very sexy but I did not find any other workaround..
Another way would be to install an event filter for the app and get all the events of the application, trap mouse press and mouse release and do not update the window in between .
"Installing an event filter on QCoreApplication::instance(). Such an event filter is able to process all events for all widgets, so it's just as powerful as reimplementing notify(); furthermore, it's possible to have more than one application-global event filter. Global event filters even see mouse events for disabled widgets. Note that application event filters are only called for objects that live in the main thread."
My Qt app uses image windows and does complex layered rebuilds, which can take some time even on a very fast machine. So not having the window redraw with every change in the window frame size was important to me so the response to the window frame resize would not be laggy.
So I solved it this way:
In my image window, I have enabled mouse tracking:
setMouseTracking(true);
Then, in the window class, I have a boolean, puntme; this is set when a resize event is caught:
bool puntme;
Then, in the mousemove event:
void imgWindow::mouseMoveEvent(QMouseEvent* event)
{
if (puntme)
{
puntme = false;
needRebuild = true;
update();
}
...
Basically, what this does is as soon as the user moves the mouse over the window -- which is a pretty natural thing for them to do if they were just resizing it -- then the window redraws with the new size. It doesn't happen during the resize, because Qt isn't forwarding move moves then.
Instead, during the resize, I just scale up an already existing bitmap, which gives a crude approximation of the change in scale with necessarily handling the actual newly more-or-less available resolution.
Worst case, user resizes, moves away from the window, and leaves the crudely scaled bitmap in place until the come back to it, at which point it will duly update to the actual new displayed bitmap == scale/size conditions.
There's no perfect way - what is really needed here is for Qt to provide (user has stopped resizing window" message, but in lieu of that, this has been working well for me.