Qwidget/X11: Prevent Window from Beeing Activated/Focussed by Mouse Clicks

Make a floating QDockWidget unfocusable

I think I've found a better way to do it!
Just use this->setAttribute(Qt::WA_X11DoNotAcceptFocus); and voila!

Example:

MyVirtualKeyboard::MyVirtualKeyboard(QWidget *parent) :
QDockWidget(parent),
ui(new Ui::MyVirtualKeyboard)
{
ui->setupUi(this);
this->setAttribute(Qt::WA_X11DoNotAcceptFocus);
}

Qt blockSignals is not blocking first extra click

Digging into the dispatch mechanism of Qt shows what happens in which order. From this it becomes obvious why this strange behavior occurs.

From the backtrace I digged into g_main_context_dispatch of glib 2.0 used by Qt to dispatch events.

Within this function the events are dipatched in separate groups (queues). E.g. first all posted events, then all X11/Windows events, etc. if any.

Please note that a single mouse click, which consists of a press and release event results usually in two consectuive queues because they are processed so fast.

Means the "press" enters the queue, this queue, containing this SINGLE event ist processed more or less immediately and the "release" enters the next queue and is also processed immediately (always AFTER some posted events triggered by the program or so).

Only if the processing of something (like a qSleep(), takes longer, the queues may contain more than a single event per group.

I set three breakpoints and installed an eventfilter on mainwindow and the button. This way it was possible to see how everything interacts.

gdb$ info breakpoints

Num Type Disp Enb Address What
2 breakpoint keep y 0xb6d41db2 <g_main_context_dispatch+578>
breakpoint already hit 549 times
silent
p "dispatch"
continue

4 breakpoint keep y 0xb6d41bdd <g_main_context_dispatch+109>
breakpoint already hit 546 times
silent
p $ebp
continue

5 breakpoint keep y 0xb6d41bc9 <g_main_context_dispatch+89>
breakpoint already hit 551 times
silent
p "leaving"
continue

Note that I put "continue" in all break point commands. So the debugging did not block the app at any point.

The result for four consecutive clicks (three within the qSleep()) is as follows:

// Dispatch function entered, one queue with events available
$1528 = (void *) 0x1

// dispatching results in the pressEvent received by the button
$1529 = "dispatch"
"QPushButton(0x809d128) press"

// dispatch function left, the spontaneous event queue
// contained only the mouse press
$1530 = "leaving"

// again entering with events in 2 queues, no idea what for
$1531 = (void *) 0x2

// dispatching of both doesn't result in press or release events
$1532 = "dispatch"

$1533 = "dispatch"

$1534 = "leaving"

// Huh, another leaving, obviously no events in any queue
$1535 = "leaving"

// Once more dispatching with nothing of interest for us
$1536 = (void *) 0x1

$1537 = "dispatch"

$1538 = "leaving"

// here comes the queue containing the release event
// of the first click
$1539 = (void *) 0x1

// the dispatch results in the release event and the button
// triggers the doStuff() function.
$1540 = "dispatch"
"QPushButton(0x809d128) release"
false
0

// -----
// Now the qSleep() runs for 5 secs. I clicked 3 times.
// There is no way Qt can process the individual presses
//and releases. The window system buffers them until Qt has time.
// -----
// qSleep() finished, the signal for UNBLOCKING is emitted
// and the connected signal is enqueued in the posted events queue.
// -----

// leave the dispatching function
$1541 = "leaving"

// -----
// Now Qt receives the three remaining mouse clicks at once
// and puts ALL of them in a SINGLE spontaneous queue.

// enters the dispatching function, two queues contain events
$1542 = (void *) 0x2

// first queue dispatched, the one with the posted event
// unblocking occurs
$1543 = "dispatch"
"MainWindow(0xbfffe180) queued"
unblock()

// second queue dispatched,
// the one with the THREE press/release pairs !!!
$1544 = "dispatch"

// first press/release pair triggers button clicked signal
// and that in turn the signal blocking
"QPushButton(0x809d128) press"
"QPushButton(0x809d128) release"
false
1

// -----
// now the signals are blocked and qSleep() runs
// qSleep() finished and the signal for UNBLOCKING is emitted
// and the connected signal enqueued in the posted events queue.
// follwing two press/release pairs don't trigger the
// clicked signal (due to the blocking)
// -----

"QPushButton(0x809d128) press"
"QPushButton(0x809d128) release"
"QPushButton(0x809d128) press"
"QPushButton(0x809d128) release"

// leaving dispatch function
$1545 = "leaving"

// entering again the dispatch function with two queues
// containing events
$1546 = (void *) 0x2

// the unblocking
$1547 = "dispatch"
"MainWindow(0xbfffe180) queued"
unblock()

// and something unknown
$1548 = "dispatch"

$1549 = "leaving"

So it becomes obious why the posted unblocking interferes with the clicks. No chance to block all the clicks because qt handles the posted events (the unblocking) before the others. Only the en-bloc processing of the buffered clicks "seems" to work.

It's good to keep this in mind if blocking signals is used in conjunction with time consuming processing.

This also explains why my "hack" (see below) using QMetaObject::invokeMethod to invoke the SIGNAL works. It causes a redirection and needs two posted events. First for the SIGNAL (which is otherwise called immediately with emit()) and second for the SLOT. Only then unblocking occurs. By then the additional clicks have been dispatched while the button was still silenced:

1. click // dispatched, blocking

qSleep() // meanwhile clicking 3 times
// followed by enqueuing the SIGNAL
// followed by enqueuing the 3 clicks in a single queue

unblocking SIGNAL // dispatched, unblocks not yet

2.,3., and 4. click // dispatched, but button still blocked

unblocking SLOT // dispatched, finally unblocks

In my "solution" below, with uses the non-blocking local eventloop instead of the blocking qSleep(), the three clicks would have been processed immediately (instead of after the unblock has been enqueued) and no signal would have been emitted.


SOLUTION while keeping qSleep():

I solved the problem by using QMetaObject::invokeMethod() to invoke the SIGNAL:

QMetaObject::invokeMethod(this, "delayed_unblock", Qt::QueuedConnection);

instead of emit(delayed_unblock());


SOLUTION using a local event loop:

void MainWindow::doStuff()
{

qDebug() << btn->signalsBlocked();
qDebug() << num++;
btn->blockSignals(true);

QTimer t;
t.setSingleShot(true);
t.setInterval(5000);
QEventLoop loop;
connect(&t, SIGNAL(timeout()), &loop, SLOT(quit()));
t.start();
loop.exec(); // lets event processing happen nothing blocked (no mopuseclicks stuck in the windows system !?)

//QMetaObject::invokeMethod(this, "delayed_unblock", Qt::QueuedConnection);
emit(delayed_unblock());

}

SOLUTION using processEvents right after qSleep()

QApplication::processEvents(); seems to receive and dispatch the windows system events immediately. This also solves the problem.


Left following lines for historical reasons ; )

Because the doc of Qt 5.6 tells us that "Signals emitted while being blocked are not buffered." Thinking of it .. nothing is emitted at all because qSleep() blocks the app. completely. So it should be the case that Qt doesn't get a grip of the clicked mouse buttons at all (which is still stuck in Windows or X11) before the qSleep() finishes. And it should be due to the windows system that clicks are buffered. The first of that clicks is processed after everything else, including unblocking, after the timer finished. For the remaining clicks the signals are again blocked by then. (@thuga explains that well).

How to pass X11 events to QDialog

You can receive XEvents through:

  • a filter function set with QAbstractEventDispatcher::instance()->setEventFilter() which will receive all XEvents.
  • a filter function set with qApp->setEventFilter() which will only receive events targeted at the application.
  • a reimplementation of the virtual function QApplication::x11EventFilter
  • a reimplementation of the virtual function QWidget::x11Event for your top level window(s) (child widgets don't receive XEvents).

in that order. If any of these functions returns true for any event, the next function won't receive that event.

Some events can also be filtered by Qt between these functions, for example QWidget::x11Event doesn't receive XKeyEvents (which are filtered by the QInputContext::x11FilterEvent function of the widget which has keyboard focus).

For more details, you should look at Qt sources: QEventDispatcher_x11.cpp and the function QApplication::x11ProcessEvent in QApplication_x11.cpp

So for the most part, if you reimplement only the x11Event function in your QDialog derived class, you should already receive most XEvent. And if you want your child widgets to receive them too, you could call manually their x11Event functions from your reimplementation of QDialog::x11Event.



Related Topics



Leave a reply



Submit