the back key: one C++ path, so a dialog closes too

A phone's back key did not close the settings dialog. A window-level QML
Shortcut never sees the key while a modal popup is up, and a desktop run says
so plainly: with the dialog open, Back left backLayer() at "settings", while
invokeMethod(win, "stepBack") closed it on the spot and Escape -- the popup's
own CloseOnEscape -- closed it too. So the key reaches the popup and never
reaches the shortcut map, which is why the drawer (no popup) closed fine and
hid the problem on a phone.

A Keys handler on the window's root item is no help either: nothing reached
it, the focused TextField keeps the key. So the key is read in C++, on the
application object but only for this window's events -- a filter installed on
the window itself was never called once, while the same key closed the drawer
through the shortcut, hence the application object.

With a layer open, the window's own backLayer() names it and stepBack() closes
it, press and release both taken; with nothing open the key is passed on
untouched, which is what leaves the window on a phone and does nothing on a
desktop. Main.qml keeps only those two functions: the Shortcut and
backHasLayer are gone, --uitest installs the same handler main() does, and the
back checks now drive the code the phone runs instead of a shortcut only the
test had.

The last check asks the phone's own question and can end the run without the
closing line -- the activity leaving takes the process with it, and every line
is flushed as it is written.

Desktop: 106 ok, 0 FAIL. Phone: 104 ok, 0 FAIL (two clipboard checks are
skipped on Android), and "after the last back: window visible 0".
This commit is contained in:
WHC
2026-10-11 21:44:33 +08:00
parent 81aeee7d1e
commit db8cdd7dd1
6 changed files with 201 additions and 0 deletions
+62
View File
@@ -37,6 +37,7 @@
#include "cst_agent.h"
#include "cst_attach.h"
#include "core/ToolProvider.h"
#include "ui/BackKey.h"
#include "ui/SessionImages.h"
#include "ui/ThemeIcons.h"
@@ -346,6 +347,9 @@ int runUiTest(AgentCore &core, const QString &size) {
std::printf("FAIL the root object is not a window\n");
return 1;
}
/* The same handler main() installs: every Back key check below goes through
* the code the phone runs, not through a Shortcut that only the test has. */
installBackKey(win, engine.rootObjects().value(0));
QTest::qWait(250);
/* --- the window opens on the session the engine already had ----------- */
@@ -1162,6 +1166,14 @@ int runUiTest(AgentCore &core, const QString &size) {
clickMouse(win, QStringLiteral("navButton"));
check(navPanel && navPanel->isVisible(),
QStringLiteral("drawer opens over the transcript"));
/* A phone closes the drawer with the back key, not with a second tap. */
QTest::keyClick(win, Qt::Key_Back);
QTest::qWait(120);
check(navPanel && !navPanel->isVisible(),
QStringLiteral("back closes the drawer"));
clickMouse(win, QStringLiteral("navButton"));
check(navPanel && navPanel->isVisible(),
QStringLiteral("and the drawer button opens it again"));
clickMouse(win, QStringLiteral("navButton"));
check(navPanel && !navPanel->isVisible(),
QStringLiteral("drawer closes again"));
@@ -1170,6 +1182,56 @@ int runUiTest(AgentCore &core, const QString &size) {
check(docked && docked->isVisible(),
QStringLiteral("widening again docks the sidebar back"));
/* --- the phone's back key closes what is open -------------------------- */
/* This one is last on purpose: on the offscreen platform a popup closed by a
* key leaves its modal overlay over the window -- the dialog says it is
* closed while the overlay still eats clicks. Nothing that clicks may come
* after it. */
{
QMetaObject::invokeMethod(win, "openSettings");
QTest::qWait(300);
QQuickItem *panel = item(win, QStringLiteral("settingsPanel"));
check(panel && panel->isVisible(),
QStringLiteral("back: the settings dialog is open to close"));
/* Which layer is open is asked of QML, not of an item's `visible`: the
* dialog's content item lives on after a close and still reports visible
* while it fades. backLayer() is what the Back key reads, so the test asks
* for exactly that. */
QVariant before;
QMetaObject::invokeMethod(win, "backLayer", Q_RETURN_ARG(QVariant, before));
check(before.toString() == QStringLiteral("settings"),
QStringLiteral("back: the settings layer is the one open"));
QTest::keyClick(win, Qt::Key_Back);
QTest::qWait(200);
QVariant after;
QMetaObject::invokeMethod(win, "backLayer", Q_RETURN_ARG(QVariant, after));
note(QStringLiteral("after back: layer \"%1\"").arg(after.toString()));
check(after.toString().isEmpty(),
QStringLiteral("back closes the dialog instead of the window"));
check(win->isVisible(), QStringLiteral("and the window is still there"));
/* With nothing open the key belongs to the platform: a phone leaves the
* window with it, so this side must not take it. On a desktop nothing
* happens and the window has to still be there; on a phone the window
* going away *is* the hand-over, so what is asked is that no layer was
* touched (whether the activity is already gone is the platform's own
* timing). This is the last thing the run does on a phone: the activity
* leaving takes the process with it, so the log can end on the check below
* without the closing line -- every line is flushed as it is written. */
QTest::keyClick(win, Qt::Key_Back);
QTest::qWait(120);
QVariant idle;
QMetaObject::invokeMethod(win, "backLayer", Q_RETURN_ARG(QVariant, idle));
#if defined(Q_OS_ANDROID)
note(QStringLiteral("after the last back: window visible %1")
.arg(win->isVisible() ? 1 : 0));
check(idle.toString().isEmpty(),
QStringLiteral("back with nothing open is left to the platform"));
#else
check(idle.toString().isEmpty() && win->isVisible(),
QStringLiteral("back with nothing open is left to the platform"));
#endif
}
/* A picture of the last state on request (see maybeShot). */
maybeShot(win, QString());