[x11] Force FocusIn events for already focused windows
Depending on the current focus stealing prevention level, it's possible for kwin to call XSetInputFocus() on a window that already has the input focus. In which case, we won't receive the corresponding FocusIn event and the client will remain inactive from kwin's perspective even though it isn't. In order to work around this issue, we can move the input focus to the null window. By doing so, it's guaranteed that we're going to receive the matching FocusIn event for the client. This commit indirectly fixes a bug where fullscreen games are displayed below panels.
This commit is contained in:
parent
6a42eccfec
commit
a0c4a8e766
2 changed files with 72 additions and 0 deletions
|
@ -57,6 +57,7 @@ private Q_SLOTS:
|
|||
void testCaptionWmName();
|
||||
void testCaptionMultipleWindows();
|
||||
void testFullscreenWindowGroups();
|
||||
void testActivateFocusedWindow();
|
||||
};
|
||||
|
||||
void X11ClientTest::initTestCase()
|
||||
|
@ -626,5 +627,71 @@ void X11ClientTest::testFullscreenWindowGroups()
|
|||
QTRY_COMPARE(client->layer(), ActiveLayer);
|
||||
}
|
||||
|
||||
void X11ClientTest::testActivateFocusedWindow()
|
||||
{
|
||||
// The window manager may call XSetInputFocus() on a window that already has focus, in which
|
||||
// case no FocusIn event will be generated and the window won't be marked as active. This test
|
||||
// verifies that we handle that subtle case properly.
|
||||
|
||||
QScopedPointer<xcb_connection_t, XcbConnectionDeleter> connection(xcb_connect(nullptr, nullptr));
|
||||
QVERIFY(!xcb_connection_has_error(connection.data()));
|
||||
|
||||
QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
|
||||
QVERIFY(windowCreatedSpy.isValid());
|
||||
|
||||
const QRect windowGeometry(0, 0, 100, 200);
|
||||
xcb_size_hints_t hints;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
|
||||
xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
|
||||
|
||||
// Create the first test window.
|
||||
const xcb_window_t window1 = xcb_generate_id(connection.data());
|
||||
xcb_create_window(connection.data(), XCB_COPY_FROM_PARENT, window1, rootWindow(),
|
||||
windowGeometry.x(), windowGeometry.y(),
|
||||
windowGeometry.width(), windowGeometry.height(),
|
||||
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
|
||||
xcb_icccm_set_wm_normal_hints(connection.data(), window1, &hints);
|
||||
xcb_change_property(connection.data(), XCB_PROP_MODE_REPLACE, window1,
|
||||
atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &window1);
|
||||
xcb_map_window(connection.data(), window1);
|
||||
xcb_flush(connection.data());
|
||||
QVERIFY(windowCreatedSpy.wait());
|
||||
X11Client *client1 = windowCreatedSpy.first().first().value<X11Client *>();
|
||||
QVERIFY(client1);
|
||||
QCOMPARE(client1->windowId(), window1);
|
||||
QCOMPARE(client1->isActive(), true);
|
||||
|
||||
// Create the second test window.
|
||||
const xcb_window_t window2 = xcb_generate_id(connection.data());
|
||||
xcb_create_window(connection.data(), XCB_COPY_FROM_PARENT, window2, rootWindow(),
|
||||
windowGeometry.x(), windowGeometry.y(),
|
||||
windowGeometry.width(), windowGeometry.height(),
|
||||
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
|
||||
xcb_icccm_set_wm_normal_hints(connection.data(), window2, &hints);
|
||||
xcb_change_property(connection.data(), XCB_PROP_MODE_REPLACE, window2,
|
||||
atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &window2);
|
||||
xcb_map_window(connection.data(), window2);
|
||||
xcb_flush(connection.data());
|
||||
QVERIFY(windowCreatedSpy.wait());
|
||||
X11Client *client2 = windowCreatedSpy.last().first().value<X11Client *>();
|
||||
QVERIFY(client2);
|
||||
QCOMPARE(client2->windowId(), window2);
|
||||
QCOMPARE(client2->isActive(), true);
|
||||
|
||||
// When the second test window is destroyed, the window manager will attempt to activate the
|
||||
// next client in the focus chain, which is the first window.
|
||||
xcb_set_input_focus(connection.data(), XCB_INPUT_FOCUS_POINTER_ROOT, window1, XCB_CURRENT_TIME);
|
||||
xcb_destroy_window(connection.data(), window2);
|
||||
xcb_flush(connection.data());
|
||||
QVERIFY(Test::waitForWindowDestroyed(client2));
|
||||
QVERIFY(client1->isActive());
|
||||
|
||||
// Destroy the first test window.
|
||||
xcb_destroy_window(connection.data(), window1);
|
||||
xcb_flush(connection.data());
|
||||
QVERIFY(Test::waitForWindowDestroyed(client1));
|
||||
}
|
||||
|
||||
WAYLANDTEST_MAIN(X11ClientTest)
|
||||
#include "x11_client_test.moc"
|
||||
|
|
|
@ -2059,6 +2059,11 @@ void X11Client::setOnAllActivities(bool on)
|
|||
*/
|
||||
void X11Client::takeFocus()
|
||||
{
|
||||
// Force a FocusIn event if the window is already focused but inactive.
|
||||
Xcb::CurrentInput currentInput;
|
||||
if (!currentInput.isNull() && currentInput.window() == window())
|
||||
workspace()->focusToNull();
|
||||
|
||||
if (rules()->checkAcceptFocus(info->input()))
|
||||
m_client.focus();
|
||||
else
|
||||
|
|
Loading…
Reference in a new issue