Replacement for XEvent queue inspection in FocusOut event case

Instead of inspecting the XEvent queue we create a Timer with a
singleshot of 0 msec to move the setActive(false) call to the end of the
event handling. In case there is a matching FocusIn event this will be
handled before the timer fired and can cancel the timer.
This commit is contained in:
Martin Gräßlin 2013-08-05 09:48:14 +02:00
parent b90c2c0e6a
commit 330003cdee
3 changed files with 40 additions and 51 deletions

View file

@ -136,6 +136,7 @@ Client::Client()
, m_menuAvailable(false)
#endif
, m_decoInputExtent()
, m_focusOutTimer(nullptr)
{
// TODO: Do all as initialization
syncRequest.counter = syncRequest.alarm = XCB_NONE;
@ -2489,6 +2490,13 @@ bool Client::decorationHasAlpha() const
}
}
void Client::cancelFocusOutTimer()
{
if (m_focusOutTimer) {
m_focusOutTimer->stop();
}
}
} // namespace
#include "client.moc"

View file

@ -645,6 +645,8 @@ public:
template <typename T>
void print(T &stream) const;
void cancelFocusOutTimer();
public Q_SLOTS:
void closeWindow();
void updateCaption();
@ -987,6 +989,8 @@ private:
#endif
Xcb::Window m_decoInputExtent;
QPoint input_offset;
QTimer *m_focusOutTimer;
};
/**

View file

@ -1359,6 +1359,9 @@ void Client::focusInEvent(xcb_focus_in_event_t *e)
return; // we don't care
if (!isShown(false) || !isOnCurrentDesktop()) // we unmapped it, but it got focus meanwhile ->
return; // activateNextClient() already transferred focus elsewhere
workspace()->forEachClient([](Client *client) {
client->cancelFocusOutTimer();
});
// check if this client is in should_get_focus list or if activation is allowed
bool activate = workspace()->allowClientActivation(this, -1U, true);
workspace()->gotFocusIn(this); // remove from should_get_focus list
@ -1370,52 +1373,6 @@ void Client::focusInEvent(xcb_focus_in_event_t *e)
}
}
// When a client loses focus, FocusOut events are usually immediatelly
// followed by FocusIn events for another client that gains the focus
// (unless the focus goes to another screen, or to the nofocus widget).
// Without this check, the former focused client would have to be
// deactivated, and after that, the new one would be activated, with
// a short time when there would be no active client. This can cause
// flicker sometimes, e.g. when a fullscreen is shown, and focus is transferred
// from it to its transient, the fullscreen would be kept in the Active layer
// at the beginning and at the end, but not in the middle, when the active
// client would be temporarily none (see Client::belongToLayer() ).
// Therefore, the events queue is checked, whether it contains the matching
// FocusIn event, and if yes, deactivation of the previous client will
// be skipped, as activation of the new one will automatically deactivate
// previously active client.
static bool follows_focusin = false;
static bool follows_focusin_failed = false;
static Bool predicate_follows_focusin(Display*, XEvent* e, XPointer arg)
{
Q_UNUSED(arg)
if (follows_focusin || follows_focusin_failed)
return False;
if (e->type == FocusIn && workspace()->findClient(WindowMatchPredicate(e->xfocus.window))) {
// found FocusIn
follows_focusin = true;
return False;
}
// events that may be in the queue before the FocusIn event that's being
// searched for
if (e->type == FocusIn || e->type == FocusOut || e->type == KeymapNotify)
return False;
follows_focusin_failed = true; // a different event - stop search
return False;
}
static bool check_follows_focusin(Client* c)
{
follows_focusin = follows_focusin_failed = false;
XEvent dummy;
// XCheckIfEvent() is used to make the search non-blocking, the predicate
// always returns False, so nothing is removed from the events queue.
// XPeekIfEvent() would block.
XCheckIfEvent(display(), &dummy, predicate_follows_focusin, (XPointer)c);
return follows_focusin;
}
void Client::focusOutEvent(xcb_focus_out_event_t *e)
{
if (e->event != window())
@ -1430,11 +1387,31 @@ void Client::focusOutEvent(xcb_focus_out_event_t *e)
return; // hack for motif apps like netscape
if (QApplication::activePopupWidget())
return;
#warning Port for XCheckIfEvent is needed, see documentation above
#if KWIN_QT5_PORTING
if (!check_follows_focusin(this))
#endif
setActive(false);
// When a client loses focus, FocusOut events are usually immediatelly
// followed by FocusIn events for another client that gains the focus
// (unless the focus goes to another screen, or to the nofocus widget).
// Without this check, the former focused client would have to be
// deactivated, and after that, the new one would be activated, with
// a short time when there would be no active client. This can cause
// flicker sometimes, e.g. when a fullscreen is shown, and focus is transferred
// from it to its transient, the fullscreen would be kept in the Active layer
// at the beginning and at the end, but not in the middle, when the active
// client would be temporarily none (see Client::belongToLayer() ).
// Therefore the setActive(false) call is moved to the end of the current
// event queue. If there is a matching FocusIn event in the current queue
// this will be processed before the setActive(false) call and the activation
// of the Client which gained FocusIn will automatically deactivate the
// previously active client.
if (!m_focusOutTimer) {
m_focusOutTimer = new QTimer(this);
m_focusOutTimer->setSingleShot(true);
m_focusOutTimer->setInterval(0);
connect(m_focusOutTimer, &QTimer::timeout, [this]() {
setActive(false);
});
}
m_focusOutTimer->start();
}
// performs _NET_WM_MOVERESIZE