From 8adc256ffe53a7cace75d74bf6ac5d173e8b59fc Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Thu, 29 Oct 2026 03:14:00 +0800 Subject: [PATCH 01/10] test(qt): expose non-ASCII text rejection through real receiver (refs #147) --- CMakeLists.txt | 18 ++++++ src/qt/unicode_input_test.cpp | 109 ++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 src/qt/unicode_input_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c1ceabb4..edfa8822 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -403,5 +403,23 @@ if(TERMFORGE_BUILD_QT_SHELL) "${PROJECT_SOURCE_DIR}/src/qt/run_smoke.py" "$") set_tests_properties(qt_cpp_shell PROPERTIES TIMEOUT 35 ENVIRONMENT "QT_QPA_PLATFORM=offscreen") + + add_executable(qt_unicode_input_test src/qt/unicode_input_test.cpp) + set_target_properties(qt_unicode_input_test PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/qt-unicode-bin/$") + termforge_qt_entry(qt_unicode_input_test) + target_link_libraries(qt_unicode_input_test PRIVATE Qt6::Test) + add_dependencies(qt_unicode_input_test shell_input_client) + add_custom_command(TARGET qt_unicode_input_test POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "$" + "$/platforms/" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "$" "$/" + VERBATIM) + add_test(NAME qt_unicode_input COMMAND "${Python3_EXECUTABLE}" + "${PROJECT_SOURCE_DIR}/src/qt/run_smoke.py" "$") + set_tests_properties(qt_unicode_input PROPERTIES TIMEOUT 35 + ENVIRONMENT "QT_QPA_PLATFORM=offscreen") endif() endif() diff --git a/src/qt/unicode_input_test.cpp b/src/qt/unicode_input_test.cpp new file mode 100644 index 00000000..9b000f6a --- /dev/null +++ b/src/qt/unicode_input_test.cpp @@ -0,0 +1,109 @@ +// Public Qt key events, fixed ConPTY, and an independent ReadConsoleW receiver. +#include "terminal_widget.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using termforge::qt::SessionModel; +using termforge::qt::TerminalWidget; + +static void require(bool condition, const char* message) { + if (!condition) throw std::runtime_error(message); +} +template +static void await(Predicate predicate, const char* message) { + QElapsedTimer timer; timer.start(); + while (!predicate() && timer.elapsed() < 5000) { + QApplication::processEvents(QEventLoop::AllEvents, 20); + QTest::qWait(5); + } + require(predicate(), message); +} +static bool has_text(const termforge::Screen& screen, std::string_view text) { + for (const auto& row : screen.lines) { + std::string line; + for (const auto& cell : row) if (cell.width) line += cell.text; + if (line.find(text) != std::string::npos) return true; + } + return false; +} +static bool text_event(QWidget& target, const QString& text) { + QKeyEvent event(QEvent::KeyPress, Qt::Key_unknown, Qt::NoModifier, text); + event.setAccepted(false); + QApplication::sendEvent(&target, &event); + return event.isAccepted(); +} +static QByteArray received(const QString& path) { + QFile file(path); + require(file.open(QIODevice::ReadOnly), "receiver output is missing"); + return file.readAll(); +} +static void finish(SessionModel& model) { + // EOT belongs only to this receiver protocol, not a Qt Ctrl-D contract. + require(model.send(std::string(1, '\x04')), "receiver EOT was rejected"); + await([&] { return model.finished(); }, "receiver did not finish"); + require(model.natural_exit() && model.exit_code() == 0 && model.output_eof() && + model.cleanup_verified() && model.active_processes_after() == 0, + "receiver did not exit naturally with verified Job cleanup"); +} +static void focus(TerminalWidget& terminal) { + terminal.show(); terminal.activateWindow(); terminal.setFocus(); + QApplication::processEvents(); + require(terminal.hasFocus(), "key event target has no focus"); +} +static QJsonObject unicode_text(const QString& receiver, const QString& directory) { + const QString path = directory + QStringLiteral("/unicode.bin"); + SessionModel model; + TerminalWidget terminal(model); + focus(terminal); + require(model.start(receiver.toStdWString(), (QStringLiteral("\"") + path + QLatin1Char('"')).toStdWString(), + directory.toStdWString()), "receiver could not start"); + await([&] { return has_text(model.screen(), "TF_KEY_READY"); }, "receiver did not become ready"); + text_event(terminal, QStringLiteral("L")); + text_event(terminal, QString::fromUtf16(u"\u4e2d\u6587")); + text_event(terminal, QStringLiteral("R")); + finish(model); + const auto actual = received(path); + const auto expected = QByteArray::fromHex("4ce4b8ade6968752"); + std::cerr << "unicode expected=" << expected.toHex().constData() << " actual=" + << actual.toHex().constData() << '\n'; + require(actual == expected, "Qt text did not reach the receiver as exact UTF-8"); + return {{"case", "unicode-text"}, {"received_hex", QString::fromLatin1(actual.toHex())}, + {"exit_code", static_cast(model.exit_code())}, {"job_active", 0}}; +} +int main(int argc, char** argv) { + SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); + _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); + char gate[3]{}; DWORD count = 0; BOOL owned = FALSE; + if (!ReadFile(GetStdHandle(STD_INPUT_HANDLE), gate, sizeof(gate), &count, nullptr) || + count != sizeof(gate) || std::string_view(gate, count) != "GO\n" || + !IsProcessInJob(GetCurrentProcess(), nullptr, &owned) || !owned) { + std::cerr << "Qt Unicode test requires its owned Job supervisor\n"; return 2; + } + qInstallMessageHandler([](QtMsgType, const QMessageLogContext&, const QString& message) { + std::cerr << message.toStdString() << '\n' << std::flush; + }); + QApplication app(argc, argv); + QApplication::setQuitOnLastWindowClosed(false); + try { + QTemporaryDir workspace; + require(workspace.isValid(), "owned receiver workspace could not be created"); + const auto receiver = QCoreApplication::applicationDirPath() + QStringLiteral("/shell_input_client.exe"); + require(QFile::exists(receiver), "independent receiver is missing"); + QJsonArray cases{unicode_text(receiver, workspace.path())}; + const QJsonObject result{{"schema", "termforge-qt-unicode-input-v1"}, {"cases", cases}, + {"ime", false}, {"presented_done", QJsonValue()}}; + std::cout << QJsonDocument(result).toJson(QJsonDocument::Compact).constData() << '\n'; + return 0; + } catch (const std::exception& error) { + std::cerr << "Qt Unicode input: " << error.what() << '\n'; return 1; + } +} From cb3933813edf8e627450aa0e6c4e9dd3179d600d Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Thu, 29 Oct 2026 03:15:47 +0800 Subject: [PATCH 02/10] feat(qt): encode key text with owned UTF-16 state (refs #147) --- src/qt/terminal_widget.hpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/qt/terminal_widget.hpp b/src/qt/terminal_widget.hpp index fd3a209c..58d00240 100644 --- a/src/qt/terminal_widget.hpp +++ b/src/qt/terminal_widget.hpp @@ -2,6 +2,7 @@ #pragma once #include "terminal_session.hpp" +#include "../../experiments/shell/keyboard.hpp" #include #include #include @@ -17,6 +18,7 @@ namespace termforge::qt { class TerminalWidget final : public QWidget { SessionModel& model_; + tf_shell::KeyboardEncoder keyboard_; static constexpr int margin = 8; int cell_width_ = 9; int cell_height_ = 19; @@ -41,31 +43,29 @@ class TerminalWidget final : public QWidget { return QWidget::event(event); } void keyPressEvent(QKeyEvent* event) override { - QByteArray bytes; - if (event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_C) bytes = QByteArray(1, '\x03'); + std::string bytes; + auto next = keyboard_; + if (event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_C) bytes = std::string(1, '\x03'); else if (event->modifiers().testFlag(Qt::ControlModifier) || event->modifiers().testFlag(Qt::AltModifier)) { QWidget::keyPressEvent(event); return; } else { switch (event->key()) { case Qt::Key_Return: case Qt::Key_Enter: bytes = "\r"; break; - case Qt::Key_Backspace: bytes = QByteArray(1, '\x7f'); break; + case Qt::Key_Backspace: bytes = std::string(1, '\x7f'); break; case Qt::Key_Tab: bytes = "\t"; break; case Qt::Key_Up: bytes = "\x1b[A"; break; case Qt::Key_Down: bytes = "\x1b[B"; break; case Qt::Key_Right: bytes = "\x1b[C"; break; case Qt::Key_Left: bytes = "\x1b[D"; break; default: - for (const auto character : event->text()) { - if (character.unicode() < 0x20 || character.unicode() > 0x7e) { - QWidget::keyPressEvent(event); return; - } - } - bytes = event->text().toLatin1(); + if (event->text().isEmpty()) { QWidget::keyPressEvent(event); return; } + for (const auto character : event->text()) bytes += next.character(character.unicode()); } } - if (bytes.isEmpty()) { QWidget::keyPressEvent(event); return; } - model_.send(bytes); - event->accept(); + if (bytes.empty() || model_.send(bytes)) { + keyboard_ = next; + event->accept(); + } else event->ignore(); } void paintEvent(QPaintEvent*) override { QPainter painter(this); From 39e3c2c5f28f1faf46f6f6b370d0e66d907efb8f Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Thu, 29 Oct 2026 03:16:21 +0800 Subject: [PATCH 03/10] test(qt): pin scalar sequences and pending Return boundary (refs #147) --- src/qt/unicode_input_test.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/qt/unicode_input_test.cpp b/src/qt/unicode_input_test.cpp index 9b000f6a..cc220159 100644 --- a/src/qt/unicode_input_test.cpp +++ b/src/qt/unicode_input_test.cpp @@ -69,10 +69,25 @@ static QJsonObject unicode_text(const QString& receiver, const QString& director await([&] { return has_text(model.screen(), "TF_KEY_READY"); }, "receiver did not become ready"); text_event(terminal, QStringLiteral("L")); text_event(terminal, QString::fromUtf16(u"\u4e2d\u6587")); + text_event(terminal, QString::fromUtf16(u"e\u0301\U0001f600")); + text_event(terminal, QString(QChar(0xd83d))); + text_event(terminal, QString(QChar(0xde00))); + text_event(terminal, QString::fromUtf16(u"\U0001f469\u200d\U0001f4bb1\ufe0f\u20e3")); + text_event(terminal, QString(QChar(0xdc00))); + text_event(terminal, QString(QChar(0xd800)) + QLatin1Char('x')); + text_event(terminal, QString(QChar(0xd800)) + QChar(0xd83d)); + text_event(terminal, QString(QChar(0xde00))); + text_event(terminal, QString(QChar(0xd800))); + QTest::keyClick(&terminal, Qt::Key_Return); + text_event(terminal, QString(QChar(0xdc00))); text_event(terminal, QStringLiteral("R")); finish(model); const auto actual = received(path); - const auto expected = QByteArray::fromHex("4ce4b8ade6968752"); + // Literal UTF-8: Chinese, NFD, non-BMP whole/split, ZWJ/keycap, malformed, + // and a pending half followed by Return and an orphan low surrogate. + const auto expected = QByteArray::fromHex( + "4ce4b8ade6968765cc81f09f9880f09f9880f09f91a9e2808df09f92bb31efb88fe283a3" + "efbfbdefbfbd78efbfbdf09f9880efbfbd0defbfbd52"); std::cerr << "unicode expected=" << expected.toHex().constData() << " actual=" << actual.toHex().constData() << '\n'; require(actual == expected, "Qt text did not reach the receiver as exact UTF-8"); From 7320fca7fa73b68e21063cbc891522c75f51b558 Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Thu, 29 Oct 2026 03:18:07 +0800 Subject: [PATCH 04/10] fix(qt): end pending scalar before normal control keys (refs #147) --- src/qt/terminal_widget.hpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/qt/terminal_widget.hpp b/src/qt/terminal_widget.hpp index 58d00240..0853c4a7 100644 --- a/src/qt/terminal_widget.hpp +++ b/src/qt/terminal_widget.hpp @@ -45,6 +45,7 @@ class TerminalWidget final : public QWidget { void keyPressEvent(QKeyEvent* event) override { std::string bytes; auto next = keyboard_; + bool character_text = false; if (event->modifiers() == Qt::ControlModifier && event->key() == Qt::Key_C) bytes = std::string(1, '\x03'); else if (event->modifiers().testFlag(Qt::ControlModifier) || event->modifiers().testFlag(Qt::AltModifier)) { QWidget::keyPressEvent(event); return; @@ -59,9 +60,16 @@ class TerminalWidget final : public QWidget { case Qt::Key_Left: bytes = "\x1b[D"; break; default: if (event->text().isEmpty()) { QWidget::keyPressEvent(event); return; } - for (const auto character : event->text()) bytes += next.character(character.unicode()); + character_text = true; + for (const auto character : event->text()) { + bytes += next.character(character.unicode()); + // InputQueue rejects this whole packet. Bound temporary + // encoding too, without changing the committed encoder. + if (bytes.size() > 1024) break; + } } } + if (!character_text) bytes = next.finish() + bytes; if (bytes.empty() || model_.send(bytes)) { keyboard_ = next; event->accept(); From dca412fb1f1e463b558dec163faf8791d8af5a3c Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Thu, 29 Oct 2026 03:18:09 +0800 Subject: [PATCH 05/10] test(qt): require focus isolation and atomic packet rejection (refs #147) --- src/qt/unicode_input_test.cpp | 50 ++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/src/qt/unicode_input_test.cpp b/src/qt/unicode_input_test.cpp index cc220159..4b154b1d 100644 --- a/src/qt/unicode_input_test.cpp +++ b/src/qt/unicode_input_test.cpp @@ -1,12 +1,16 @@ // Public Qt key events, fixed ConPTY, and an independent ReadConsoleW receiver. #include "terminal_widget.hpp" +#include "session_tabs.hpp" #include +#include #include #include #include #include #include #include +#include +#include #include #include #include @@ -94,6 +98,50 @@ static QJsonObject unicode_text(const QString& receiver, const QString& director return {{"case", "unicode-text"}, {"received_hex", QString::fromLatin1(actual.toHex())}, {"exit_code", static_cast(model.exit_code())}, {"job_active", 0}}; } +static QJsonObject focus_and_packet_rejection(const QString& receiver, const QString& directory) { + const QString path = directory + QStringLiteral("/focus-packet.bin"); + SessionModel model; + TerminalWidget terminal(model); + focus(terminal); + require(model.start(receiver.toStdWString(), (QStringLiteral("\"") + path + QLatin1Char('"')).toStdWString(), + directory.toStdWString()), "focus receiver could not start"); + await([&] { return has_text(model.screen(), "TF_KEY_READY"); }, "focus receiver did not become ready"); + require(text_event(terminal, QStringLiteral("F")), "ASCII prefix was rejected"); + require(text_event(terminal, QString(QChar(0xd83d))), "pending high was rejected"); + QLineEdit other; + other.show(); other.activateWindow(); other.setFocus(); QApplication::processEvents(); + require(other.hasFocus() && !terminal.hasFocus(), "focus did not leave terminal"); + require(!text_event(terminal, QStringLiteral("LEAK")), "unfocused target accepted synthetic text"); + focus(terminal); + require(text_event(terminal, QString(QChar(0xde00))), "orphan low after focus was rejected"); + require(text_event(terminal, QString(QChar(0xd83d))), "hide high was rejected"); + terminal.hide(); QApplication::processEvents(); + require(!text_event(terminal, QStringLiteral("HIDDEN")), "hidden target accepted text"); + focus(terminal); + require(text_event(terminal, QString(QChar(0xde00))), "orphan low after hide was rejected"); + // Exactly 1024 encoded bytes are accepted as one packet. + require(text_event(terminal, QString(1020, QLatin1Char('x')) + QString::fromUtf16(u"\U0001f600")), + "exact packet limit was rejected"); + require(text_event(terminal, QString(QChar(0xd83d))), "overflow high was rejected"); + // The attempted completion plus 1021 ASCII bytes is 1025 bytes. On failure + // the held high must survive, and none of that attempted packet may arrive. + require(!text_event(terminal, QString(QChar(0xde00)) + QString(1021, QLatin1Char('z'))), + "oversized packet was accepted"); + require(!model.error().isEmpty(), "input queue rejection has no visible model error"); + require(text_event(terminal, QString(QChar(0xde00))), "valid retry after packet rejection failed"); + require(text_event(terminal, QString(QChar(0))), "embedded NUL was rejected"); + require(text_event(terminal, QStringLiteral("R")), "ASCII suffix was rejected"); + finish(model); + const auto expected = QByteArray::fromHex("46efbfbdefbfbd") + QByteArray(1020, 'x') + + QByteArray::fromHex("f09f9880f09f98800052"); + const auto actual = received(path); + require(actual == expected, "focus/reset or whole-packet rejection changed receiver bytes"); + require(!text_event(terminal, QString(QChar(0xd83d))), "finished target accepted pending high"); + return {{"case", "focus-hide-packet-rejection"}, {"received_bytes", actual.size()}, + {"received_sha256", QString::fromLatin1(QCryptographicHash::hash(actual, QCryptographicHash::Sha256).toHex())}, + {"exact_packet_bytes", 1024}, {"rejected_packet_bytes", 1025}, + {"exit_code", static_cast(model.exit_code())}, {"job_active", 0}}; +} int main(int argc, char** argv) { SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); @@ -113,7 +161,7 @@ int main(int argc, char** argv) { require(workspace.isValid(), "owned receiver workspace could not be created"); const auto receiver = QCoreApplication::applicationDirPath() + QStringLiteral("/shell_input_client.exe"); require(QFile::exists(receiver), "independent receiver is missing"); - QJsonArray cases{unicode_text(receiver, workspace.path())}; + QJsonArray cases{unicode_text(receiver, workspace.path()), focus_and_packet_rejection(receiver, workspace.path())}; const QJsonObject result{{"schema", "termforge-qt-unicode-input-v1"}, {"cases", cases}, {"ime", false}, {"presented_done", QJsonValue()}}; std::cout << QJsonDocument(result).toJson(QJsonDocument::Compact).constData() << '\n'; From c4734bd8731d3fe0a2c2d7874d0141dd1152bf00 Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Thu, 29 Oct 2026 03:20:01 +0800 Subject: [PATCH 06/10] fix(qt): reset uncommitted input on focus and target loss (refs #147) --- src/qt/session_tabs.hpp | 2 +- src/qt/terminal_session.hpp | 5 ++++- src/qt/terminal_widget.hpp | 18 +++++++++++++++++- src/qt/unicode_input_test.cpp | 4 ++-- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/qt/session_tabs.hpp b/src/qt/session_tabs.hpp index 4a87d1b4..4aebc765 100644 --- a/src/qt/session_tabs.hpp +++ b/src/qt/session_tabs.hpp @@ -24,7 +24,7 @@ class SessionPage final : public QWidget { layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(&terminal_); model_.on_changed = [this] { - terminal_.update(); + terminal_.refresh_session(); if (on_changed) on_changed(); if (retired_ && model_.finished()) deleteLater(); }; diff --git a/src/qt/terminal_session.hpp b/src/qt/terminal_session.hpp index 0f5e089f..1284bdf6 100644 --- a/src/qt/terminal_session.hpp +++ b/src/qt/terminal_session.hpp @@ -184,7 +184,10 @@ class SessionModel final : public QObject { return succeeded; } bool send(const std::string& bytes) { - if (!running() || !input_) return false; + if (!running() || !input_) { + remember_error(QStringLiteral("Terminal input is unavailable")); + notify(); return false; + } try { if (input_->send(bytes)) return true; remember_error(QStringLiteral("Terminal input queue is full or unavailable")); diff --git a/src/qt/terminal_widget.hpp b/src/qt/terminal_widget.hpp index 0853c4a7..9e64be61 100644 --- a/src/qt/terminal_widget.hpp +++ b/src/qt/terminal_widget.hpp @@ -32,10 +32,13 @@ class TerminalWidget final : public QWidget { return palette[index >= 0 && index < 16 ? static_cast(index) : foreground ? 7 : 0]; } void closeEvent(QCloseEvent* event) override { + keyboard_.reset(); model_.close(); QWidget::closeEvent(event); } bool event(QEvent* event) override { + if (event->type() == QEvent::FocusOut || event->type() == QEvent::Hide) + keyboard_.reset(); // QWidget normally consumes Tab for focus traversal before keyPressEvent. if (event->type() == QEvent::KeyPress && static_cast(event)->key() == Qt::Key_Tab) { keyPressEvent(static_cast(event)); return event->isAccepted(); @@ -43,6 +46,15 @@ class TerminalWidget final : public QWidget { return QWidget::event(event); } void keyPressEvent(QKeyEvent* event) override { + if (!isVisible() || !hasFocus()) { + keyboard_.reset(); event->ignore(); return; + } + if (!model_.running()) { + keyboard_.reset(); + // Report the unavailable target even for an uncommitted high unit. + model_.send(std::string{}); + event->ignore(); return; + } std::string bytes; auto next = keyboard_; bool character_text = false; @@ -129,9 +141,13 @@ class TerminalWidget final : public QWidget { setFocusPolicy(Qt::StrongFocus); setAttribute(Qt::WA_InputMethodEnabled, false); setFixedSize(sizeHint()); - model_.on_changed = [this] { update(); }; + model_.on_changed = [this] { refresh_session(); }; } ~TerminalWidget() override { model_.on_changed = {}; } + void refresh_session() { + if (!model_.running()) keyboard_.reset(); + update(); + } QSize sizeHint() const override { return {margin * 2 + SessionModel::fixed_columns * cell_width_, margin * 3 + (SessionModel::fixed_rows + 1) * cell_height_}; diff --git a/src/qt/unicode_input_test.cpp b/src/qt/unicode_input_test.cpp index 4b154b1d..9a47de67 100644 --- a/src/qt/unicode_input_test.cpp +++ b/src/qt/unicode_input_test.cpp @@ -59,7 +59,7 @@ static void finish(SessionModel& model) { "receiver did not exit naturally with verified Job cleanup"); } static void focus(TerminalWidget& terminal) { - terminal.show(); terminal.activateWindow(); terminal.setFocus(); + terminal.show(); QApplication::setActiveWindow(terminal.window()); terminal.setFocus(); QApplication::processEvents(); require(terminal.hasFocus(), "key event target has no focus"); } @@ -109,7 +109,7 @@ static QJsonObject focus_and_packet_rejection(const QString& receiver, const QSt require(text_event(terminal, QStringLiteral("F")), "ASCII prefix was rejected"); require(text_event(terminal, QString(QChar(0xd83d))), "pending high was rejected"); QLineEdit other; - other.show(); other.activateWindow(); other.setFocus(); QApplication::processEvents(); + other.show(); QApplication::setActiveWindow(&other); other.setFocus(); QApplication::processEvents(); require(other.hasFocus() && !terminal.hasFocus(), "focus did not leave terminal"); require(!text_event(terminal, QStringLiteral("LEAK")), "unfocused target accepted synthetic text"); focus(terminal); From 2bdcea3161f415f49d96bc0f30e5e72794c8c72f Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Thu, 29 Oct 2026 03:20:02 +0800 Subject: [PATCH 07/10] test(qt): verify independent tab receivers and unavailable targets (refs #147) --- CMakeLists.txt | 2 +- src/qt/unicode_input_test.cpp | 111 +++++++++++++++++++++++++++++++++- 2 files changed, 110 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index edfa8822..fc091a58 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -420,6 +420,6 @@ if(TERMFORGE_BUILD_QT_SHELL) add_test(NAME qt_unicode_input COMMAND "${Python3_EXECUTABLE}" "${PROJECT_SOURCE_DIR}/src/qt/run_smoke.py" "$") set_tests_properties(qt_unicode_input PROPERTIES TIMEOUT 35 - ENVIRONMENT "QT_QPA_PLATFORM=offscreen") + ENVIRONMENT "QT_QPA_PLATFORM=offscreen;TERMFORGE_TEST_ARTIFACTS=${PROJECT_BINARY_DIR}/test-artifacts") endif() endif() diff --git a/src/qt/unicode_input_test.cpp b/src/qt/unicode_input_test.cpp index 9a47de67..f89a1bd4 100644 --- a/src/qt/unicode_input_test.cpp +++ b/src/qt/unicode_input_test.cpp @@ -3,6 +3,7 @@ #include "session_tabs.hpp" #include #include +#include #include #include #include @@ -142,6 +143,104 @@ static QJsonObject focus_and_packet_rejection(const QString& receiver, const QSt {"exact_packet_bytes", 1024}, {"rejected_packet_bytes", 1025}, {"exit_code", static_cast(model.exit_code())}, {"job_active", 0}}; } +static QJsonObject normal_keys(const QString& receiver, const QString& directory) { + const QString path = directory + QStringLiteral("/normal-keys.bin"); + SessionModel model; + TerminalWidget terminal(model); + focus(terminal); + require(model.start(receiver.toStdWString(), (QStringLiteral("\"") + path + QLatin1Char('"')).toStdWString(), + directory.toStdWString()), "normal-key receiver could not start"); + await([&] { return has_text(model.screen(), "TF_KEY_READY"); }, "normal-key receiver did not become ready"); + QTest::keyClicks(&terminal, QStringLiteral("Ab9")); + for (int key : {Qt::Key_Return, Qt::Key_Tab, Qt::Key_Backspace}) QTest::keyClick(&terminal, static_cast(key)); + QTest::keyClick(&terminal, Qt::Key_C, Qt::ControlModifier); + for (int key : {Qt::Key_Up, Qt::Key_Down, Qt::Key_Right, Qt::Key_Left}) + QTest::keyClick(&terminal, static_cast(key)); + QKeyEvent unsupported(QEvent::KeyPress, Qt::Key_D, Qt::ControlModifier, QString(QChar(4))); + unsupported.setAccepted(false); QApplication::sendEvent(&terminal, &unsupported); + require(!unsupported.isAccepted(), "Qt Ctrl-D became a supported product action"); + finish(model); + const auto actual = received(path); + require(actual == QByteArray::fromHex("4162390d097f031b5b411b5b421b5b431b5b44"), + "existing normal-mode key bytes changed"); + return {{"case", "normal-keys"}, {"received_hex", QString::fromLatin1(actual.toHex())}, + {"exit_code", 0}, {"job_active", 0}}; +} +static QJsonObject tab_and_close_isolation(const QString& receiver, const QString& directory) { + const auto argument = [&](const QString& name) { + return (QStringLiteral("\"") + directory + QLatin1Char('/') + name + QLatin1Char('"')).toStdWString(); + }; + termforge::qt::SessionTabs window(receiver.toStdWString(), argument(QStringLiteral("tab-a.bin")), directory.toStdWString()); + window.show(); QApplication::setActiveWindow(&window); QApplication::processEvents(); + auto* first = window.current_session(); + require(first != nullptr, "first receiver tab is missing"); + await([&] { return has_text(first->model().screen(), "TF_KEY_READY"); }, "first receiver tab did not become ready"); + require(text_event(first->terminal(), QStringLiteral("A")), "first tab prefix was rejected"); + require(text_event(first->terminal(), QString(QChar(0xd83d))), "first tab pending high was rejected"); + auto* second = window.new_session(receiver.toStdWString(), argument(QStringLiteral("tab-b.bin")), directory.toStdWString()); + require(second && second != first, "second independent receiver tab is missing"); + await([&] { return has_text(second->model().screen(), "TF_KEY_READY"); }, "second receiver tab did not become ready"); + require(!text_event(first->terminal(), QStringLiteral("WRONG")), "hidden tab accepted text"); + require(text_event(second->terminal(), QString(QChar(0xde00)) + QLatin1Char('B')), "second tab low was rejected"); + require(text_event(second->terminal(), QString(QChar(0xd83d))), "second tab pending high was rejected"); + auto* bar = window.findChild(); + require(bar && bar->count() == 2, "receiver tab bar is missing"); + QTest::mouseClick(bar, Qt::LeftButton, Qt::NoModifier, bar->tabRect(0).center()); + QApplication::processEvents(); + require(first->terminal().hasFocus(), "tab switch lost receiver focus"); + require(text_event(first->terminal(), QString(QChar(0xde00)) + QLatin1Char('C')), "first tab low was rejected"); + QTest::mouseClick(bar, Qt::LeftButton, Qt::NoModifier, bar->tabRect(1).center()); + QApplication::processEvents(); + require(text_event(second->terminal(), QString(QChar(0xde00)) + QLatin1Char('D')), "second tab return low was rejected"); + finish(second->model()); + require(received(directory + QStringLiteral("/tab-b.bin")) == QByteArray::fromHex("efbfbd42efbfbd44"), + "second tab received another target's pending surrogate or text"); + // Closing a third target with a pending half cannot commit into its peer. + auto* closing = window.new_session(receiver.toStdWString(), argument(QStringLiteral("tab-close.bin")), directory.toStdWString()); + await([&] { return has_text(closing->model().screen(), "TF_KEY_READY"); }, "closing receiver tab did not become ready"); + require(text_event(closing->terminal(), QString(QChar(0xd83d))), "closing tab high was rejected"); + closing->terminal().close(); + require(!text_event(closing->terminal(), QString(QChar(0xde00))), "closed target accepted text"); + await([&] { return closing->model().finished(); }, "closed receiver did not finish"); + require(closing->model().cleanup_verified() && closing->model().active_processes_after() == 0 && + !closing->model().natural_exit(), "closed receiver cleanup was not verified cancellation"); + QTest::mouseClick(bar, Qt::LeftButton, Qt::NoModifier, bar->tabRect(0).center()); + QApplication::processEvents(); + require(text_event(first->terminal(), QString(QChar(0xde00)) + QLatin1Char('E')), "surviving receiver stopped accepting text"); + finish(first->model()); + const auto first_bytes = received(directory + QStringLiteral("/tab-a.bin")); + require(first_bytes == QByteArray::fromHex("41efbfbd43efbfbd45"), "first receiver bytes leaked across tab boundaries"); + window.close(); + return {{"case", "tab-close-isolation"}, {"first_hex", QString::fromLatin1(first_bytes.toHex())}, + {"second_hex", "efbfbd42efbfbd44"}, {"natural_receivers", 2}, + {"cancelled_receiver", 1}, {"job_active", 0}}; +} +static QJsonObject unavailable_targets(const QString& receiver, const QString& directory) { + const QString path = directory + QStringLiteral("/late-start.bin"); + SessionModel model; + TerminalWidget terminal(model); + focus(terminal); + require(!text_event(terminal, QString(QChar(0xd83d))) && !model.error().isEmpty(), + "unstarted target accepted text or hid its rejection"); + require(model.start(receiver.toStdWString(), (QStringLiteral("\"") + path + QLatin1Char('"')).toStdWString(), + directory.toStdWString()), "late-start receiver could not start"); + await([&] { return has_text(model.screen(), "TF_KEY_READY"); }, "late-start receiver did not become ready"); + require(text_event(terminal, QString(QChar(0xde00)) + QLatin1Char('R')), "valid target failed after unavailable rejection"); + finish(model); + require(received(path) == QByteArray::fromHex("efbfbd52"), "unavailable target polluted later encoder state"); + require(!text_event(terminal, QStringLiteral("AFTER")), "ended target accepted text"); + SessionModel failed; + TerminalWidget error_terminal(failed); + focus(error_terminal); + require(!failed.start((directory + QStringLiteral("/missing-receiver.exe")).toStdWString(), L""), + "missing receiver unexpectedly started"); + await([&] { return failed.finished(); }, "failed target cleanup did not finish"); + require(!text_event(error_terminal, QString(QChar(0xd83d))) && !failed.error().isEmpty() && + failed.cleanup_verified() && failed.active_processes_after() == 0, + "failed target accepted text or lost its error/cleanup"); + return {{"case", "unavailable-targets"}, {"received_hex", "efbfbd52"}, + {"unstarted_rejected", true}, {"ended_rejected", true}, {"failed_rejected", true}, {"job_active", 0}}; +} int main(int argc, char** argv) { SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); @@ -157,13 +256,21 @@ int main(int argc, char** argv) { QApplication app(argc, argv); QApplication::setQuitOnLastWindowClosed(false); try { - QTemporaryDir workspace; + const auto artifacts = qEnvironmentVariable("TERMFORGE_TEST_ARTIFACTS"); + if (!artifacts.isEmpty()) require(QDir().mkpath(artifacts), "artifact directory could not be created"); + QTemporaryDir workspace(artifacts.isEmpty() ? QString() : artifacts + QStringLiteral("/qt-unicode-XXXXXX")); require(workspace.isValid(), "owned receiver workspace could not be created"); + if (!artifacts.isEmpty()) workspace.setAutoRemove(false); const auto receiver = QCoreApplication::applicationDirPath() + QStringLiteral("/shell_input_client.exe"); require(QFile::exists(receiver), "independent receiver is missing"); - QJsonArray cases{unicode_text(receiver, workspace.path()), focus_and_packet_rejection(receiver, workspace.path())}; + QJsonArray cases{unicode_text(receiver, workspace.path()), focus_and_packet_rejection(receiver, workspace.path()), + normal_keys(receiver, workspace.path()), tab_and_close_isolation(receiver, workspace.path()), + unavailable_targets(receiver, workspace.path())}; const QJsonObject result{{"schema", "termforge-qt-unicode-input-v1"}, {"cases", cases}, {"ime", false}, {"presented_done", QJsonValue()}}; + QFile saved(workspace.path() + QStringLiteral("/sample.json")); + require(saved.open(QIODevice::WriteOnly) && saved.write(QJsonDocument(result).toJson()) > 0, + "receiver sample could not be retained"); std::cout << QJsonDocument(result).toJson(QJsonDocument::Compact).constData() << '\n'; return 0; } catch (const std::exception& error) { From 1e16a0caebd2219e0b9de1c7bd3faa638a7b9baf Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Thu, 29 Oct 2026 03:23:37 +0800 Subject: [PATCH 08/10] docs(qt): preserve Unicode receiver red-green provenance (refs #147) --- .../2026-10-29-qt-unicode-input/README.md | 41 +++++ .../focused-runtime.json | 169 ++++++++++++++++++ .../focused-validation.json | 148 +++++++++++++++ .../red-runtime.json | 165 +++++++++++++++++ 4 files changed, 523 insertions(+) create mode 100644 experiments/shell/results/2026-10-29-qt-unicode-input/README.md create mode 100644 experiments/shell/results/2026-10-29-qt-unicode-input/focused-runtime.json create mode 100644 experiments/shell/results/2026-10-29-qt-unicode-input/focused-validation.json create mode 100644 experiments/shell/results/2026-10-29-qt-unicode-input/red-runtime.json diff --git a/experiments/shell/results/2026-10-29-qt-unicode-input/README.md b/experiments/shell/results/2026-10-29-qt-unicode-input/README.md new file mode 100644 index 00000000..de47b7f2 --- /dev/null +++ b/experiments/shell/results/2026-10-29-qt-unicode-input/README.md @@ -0,0 +1,41 @@ +# Qt 基础 Unicode 文本输入的限定验证 + +执行项 [#147](https://github.com/JavaZeroo/termforge/issues/147),父项 [#30](https://github.com/JavaZeroo/termforge/issues/30)。正式 C++ / Qt Widgets / 固定 Microsoft ConPTY 路线将 `QKeyEvent.text()` 的 UTF-16 单元交给本项目既有 `KeyboardEncoder`;每个 widget 独立保存未完成的高代理项。临时编码器处理整条事件,只有整包进入有界输入队列后才提交状态。包过限或队列拒绝保留此前状态;失焦、隐藏、关闭及目标不可用清除未提交的半个标量。 + +未配对单元使用 U+FFFD,控制键结束此前未完成标量,焦点变化丢弃未提交单元;这些是**现有 TermForge 编码策略**。本批没有运行 WindTerm,不宣称这些边界已经与原版匹配。 + +## 公共边界与原始接收 + +测试通过公开 Qt key/focus/hide/tab 动作进入正式 widget/session,再由固定 ConPTY 启动独立 `shell_input_client.exe`。该现有夹具用 `ReadConsoleW` 读取原始单元,通过 Windows UTF-8 API 导出文件;字面预期不调用被测编码器。只在夹具结束时由 Session 直接发送 EOT(4),不把它解释为产品已支持 Qt Ctrl-D。输入回显或 `send()` 返回成功均不是接收 oracle。 + +五组场景: + +| 场景 | 实际接收与边界 | +|---|---| +| Unicode 文本 | 中文、NFD `e + U+0301`、整事件与跨事件的 U+1F600、固定 woman-technologist ZWJ/keycap、孤立低代理项、连续高代理项和 Return 边界;58B 精确 UTF-8 | +| 焦点/隐藏与包拒绝 | 显式 Qt 焦点移交、隐藏后恢复均使低代理项独立替换;1024B 接受,1025B 整包拒绝且不污染已持有高代理项;嵌入 NUL;最终 1037B | +| 原有正常模式按键 | ASCII、Return、Tab、Backspace、Ctrl-C 和四方向键的19B字面预期;Qt Ctrl-D 仍拒绝 | +| 标签与关闭 | 两个真实标签各有独立接收文件;隐藏目标拒绝输入,切换丢弃未完成单元;第三个目标带半个代理项关闭后只取消自身,存活目标继续接收 | +| 不可用目标 | 未启动目标拒绝后可以正常启动,孤立低代理项不能复用失败输入;已结束和启动失败目标拒绝,错误及回收可见 | + +Qt 层实际拒绝场景使用现有单包上限。16KiB 总队列容量沿既有 `InputQueue` 公共夹具单独验证,没有通过随机洪流制造全队列现场,也没有增加生产测试钩子。不同配置的检查不拼成一条原生运行。 + +## 已固定的红与针对性绿 + +- 原始 test-only 源 `8adc256ffe53a7cace75d74bf6ac5d173e8b59fc`,测试 EXE SHA-256 `06d1b14692d08e672517409846fca27821514ca94e3d0163bd3f5b9943ba5dd8`:预期 `4ce4b8ade6968752`,独立接收端只收到 `4c52`;接收子进程正常结束,测试按字节不一致失败,外层 Job active=0/total=4,未超时。[原39文件运行时身份](red-runtime.json)保留。 +- 扩展反例源 `39e3c2c` 显示高代理项越过 Return 后与低代理项错误拼接;按既有编码器 finish 策略修复。源 `dca412f` 还固定未聚焦目标接受合成文本的反例。 +- 焦点夹具最初仅调用 `activateWindow()`,在 offscreen 平台重获焦点失败;该次失败保留在本地原日志。当前夹具改用公开 `QApplication::setActiveWindow` 并检查实际 Qt 焦点,不能据此声明 Windows 物理输入焦点已验收。 +- 针对性源 `2bdcea3161f415f49d96bc0f30e5e72794c8c72f` 的五组场景通过;外层 Job active=0/total=17/terminated=0,测试根正常 exit=0。6个自然结束接收文件和1个取消目标分别记录。可审查的[源/原日志哈希与离线接收重算](focused-validation.json)、[40文件运行时](focused-runtime.json)与最终联合源码验证分开保留。 + +原等待边界不变:单阶段5秒,监督器主 `communicate` 25秒,CTest35秒,失败清理仍由原监督器/Session处理;这些是诊断期限,不是性能指标或完整进程生命周期承诺。运行使用 Qt 6.10.3、固定 ConPTY 1.25.260930003、VS2022/MSVC19.42 与 app-local CRT14.51;运行时清单绑定所有实际文件。 + +## 复验与限制 + +按 [Qt 构建说明](../../../../../src/qt/README.md)配置后: + +```powershell +cmake --build build/qt-cpp --config Release --target qt_unicode_input_test +ctest --test-dir build/qt-cpp -C Release -R '^qt_unicode_input$' --no-tests=error --verbose +``` + +每次产生独立自有临时目录;合成接收文件及 stdout/stderr 留在本地,公开仅源码、输入字面量和脱敏身份/标量摘要。没有新第三方库,也没有修改核心字素、实验后端或开启 `WA_InputMethodEnabled`。Qt offscreen 与人工 `QKeyEvent` 不等于真实 OS IME、物理键盘、跨 DPI、字形/宽度/整形或实际呈现;这些及 WindTerm 对照、应用键盘模式和性能仍未知。本切片不验收完整 #30 或 R2。 diff --git a/experiments/shell/results/2026-10-29-qt-unicode-input/focused-runtime.json b/experiments/shell/results/2026-10-29-qt-unicode-input/focused-runtime.json new file mode 100644 index 00000000..07c8a839 --- /dev/null +++ b/experiments/shell/results/2026-10-29-qt-unicode-input/focused-runtime.json @@ -0,0 +1,169 @@ +{ + "schema": "termforge-windows-runtime-v1", + "executable": "qt_unicode_input_test.exe", + "architecture": "windows-x64", + "msvc_runtime_version": "14.51.36247.0", + "msvc_runtime_manifest_sha256": "154176c8b4ae6dd57fce453e2c2002ff484344b39451fa9880b07db4794f3758", + "files": { + "D3Dcompiler_47.dll": { + "bytes": 4669440, + "sha256": "0f89c19165fd83a05e39bea601a06007e6a8814d55e2adf913902926231f843e" + }, + "LICENSE-MICROSOFT-VC-REDIST.docx": { + "bytes": 39553, + "sha256": "08651651a7602fc7c0e2763de0fde1ff9f868df2780597cd1775ee9d6441c783" + }, + "NOTICE.txt": { + "bytes": 563, + "sha256": "510781c94971ea19adae55d2c42207d942b0646deee29d5c401f189cc25d415f" + }, + "Qt6Core.dll": { + "bytes": 10250040, + "sha256": "b712b4754588e89f855dc0aa087d6304b2fd21b0e0639d1cdeb9cb969bd5fb72" + }, + "Qt6Gui.dll": { + "bytes": 9514296, + "sha256": "03ba47a2025036a04f5548bce242788ac21edf551e78d5b42e1b87beb799904f" + }, + "Qt6Network.dll": { + "bytes": 1773880, + "sha256": "203f8ddf83c015ce56752ebe7a1986472be7dbb95c20e13ac8e1d741d9ac93d1" + }, + "Qt6Test.dll": { + "bytes": 380216, + "sha256": "9d0473552b332aa378c60da05af97d741fcce06b0fc4dd68e36d885015eb5187" + }, + "Qt6Widgets.dll": { + "bytes": 6577464, + "sha256": "61ddd0316a731b8dbabaf2924cd0d47b756461e37329b4f535902cf450416127" + }, + "assets/fonts/OFL.txt": { + "bytes": 4395, + "sha256": "50ab8dd54680d3473f649c9db86fece88434d097c7834475c1c72d2f8c429215" + }, + "assets/fonts/README.md": { + "bytes": 3091, + "sha256": "760a714ad011733e0b93f3c25298190ff5fa493a9e40ad38d347922a355d538f" + }, + "assets/fonts/RobotoMono-Regular.ttf": { + "bytes": 125748, + "sha256": "af0bff7599c3df3831755c16e39b3c496df74b8c8d8a1161b14dc8461be17cb4" + }, + "concrt140.dll": { + "bytes": 374200, + "sha256": "54716f0738af891f283d213b5c8d11b25896bb8ee3097d301eae718560cf974e" + }, + "conpty-backend/LICENSE": { + "bytes": 1095, + "sha256": "ad0cf28f3381ca9bb0bf101d127402d44c17bfa0991e1a00bff7ae6679e9dada" + }, + "conpty-backend/OpenConsole.exe": { + "bytes": 1089376, + "sha256": "3d66b23d0a71bb8eed2b77edc8b9df9bf54ce6c8fb74c863a30e760997f80586" + }, + "conpty-backend/conpty.dll": { + "bytes": 119608, + "sha256": "feeef341d891643c62d30b6b07800bc70f0bc148f44cb8c3bee84aa557ae805a" + }, + "conpty-backend/manifest.json": { + "bytes": 1467, + "sha256": "e75e522a3355c0a66c6d60cafa02413a83d8757262747f650b77c959c09ed439" + }, + "dxcompiler.dll": { + "bytes": 14019128, + "sha256": "2bc16dc5ec85315f89e2f10a482d46dde94a65d6b2d7077f4c49c77e10222acc" + }, + "dxil.dll": { + "bytes": 1502760, + "sha256": "0e90c12c1c6aa1e09d025322a815f9dd80da7d1e331f1df585c7fcbbf44b40d3" + }, + "generic/qtuiotouchplugin.dll": { + "bytes": 102712, + "sha256": "69498d05870aa583c31159f9350e3d8a598b895f6525de7b6fa382395ad27b9d" + }, + "icuuc.dll": { + "bytes": 36864, + "sha256": "59d134ddc15ae0594f0a328979f8201662fc82634ddb7c537b156e7cf871f5eb" + }, + "imageformats/qgif.dll": { + "bytes": 47928, + "sha256": "ded464ef782f41862f391bc5b21c54d58f6aee670fc9f10a2d909ae5b0ba09a7" + }, + "imageformats/qico.dll": { + "bytes": 45880, + "sha256": "0c2abdf95d778bfed9863b8be817dc6cffe2dacdd1a85b2117b4b4ccb1a08e66" + }, + "imageformats/qjpeg.dll": { + "bytes": 577848, + "sha256": "cd965015cce4638361d7bc01193d92a041a227d46d0759ec25d2a1a8608a6b40" + }, + "msvcp140.dll": { + "bytes": 643512, + "sha256": "7c26614e1d733892c2deac7e245ce115504b1d80592dd0a01b08e3e5a55f89ca" + }, + "msvcp140_1.dll": { + "bytes": 35768, + "sha256": "206c931bf90fdad8816de3b5e2ef80b2bcaa9406c89ecc05fe6fddffe251e982" + }, + "msvcp140_2.dll": { + "bytes": 274872, + "sha256": "d50d7883f20d1dc6191768d3746f52dd9cac89c346ffaed5be1f110c2f34a838" + }, + "msvcp140_atomic_wait.dll": { + "bytes": 57792, + "sha256": "3d0cbfaa1bf3eecf5a3f4491d2960ee803cb994f30292c6adc4a07c498f60e2b" + }, + "msvcp140_codecvt_ids.dll": { + "bytes": 31160, + "sha256": "8a65c7596ef2e6938731f5a1058e7e40145b6d97967cc649231a076b9a608d78" + }, + "networkinformation/qnetworklistmanager.dll": { + "bytes": 70968, + "sha256": "4bc26d5b613c507634fd10f07fe031fcbd4f02157d23b9eb1407de4dd6da8278" + }, + "platforms/qoffscreen.dll": { + "bytes": 113976, + "sha256": "a35f87f597b91121cfc0ef39dcb0a5e7ad2876850fbfaf6377edf80eaa067bfd" + }, + "platforms/qwindows.dll": { + "bytes": 997688, + "sha256": "cafd5bfdef3d89d176ee26725c8d4759fd6cb007645b645a4cbf6386364a3f36" + }, + "qt_unicode_input_test.exe": { + "bytes": 203776, + "sha256": "c1f0472583cc8e3b83fb286925e9006fe7334bca1bfbfa0a94b2f3bc3655605d" + }, + "shell_input_client.exe": { + "bytes": 41984, + "sha256": "3aae0923441b7c27ff687824e780c9689d77f2c98796ec8bf12693d00ae1b10e" + }, + "styles/qmodernwindowsstyle.dll": { + "bytes": 221496, + "sha256": "c279e7a97b21fcd7acdfde88cacb8baa119f563282e09c241acb3edee2bc9bbf" + }, + "tls/qcertonlybackend.dll": { + "bytes": 103224, + "sha256": "99d00b37bb2835c539366e4018a58e0ddad21e1c648a3f44f56335a0c1d34765" + }, + "tls/qschannelbackend.dll": { + "bytes": 262968, + "sha256": "b7035c3689b93f55b48112c3e85cf4b8550fefde3f7a044df2784c29330e6940" + }, + "vccorlib140.dll": { + "bytes": 350648, + "sha256": "09f93d5ff96ae09767f3e1af3cdd43a5a58f2598654fc63089429bb6f9f4737f" + }, + "vcruntime140.dll": { + "bytes": 178616, + "sha256": "d1f4225df2cd877dbf130d5668a021dce3f94118455ff5ec952061c30afc9ce7" + }, + "vcruntime140_1.dll": { + "bytes": 50112, + "sha256": "a7146c08f89fe5b04541ab507cdb59ff7b44534d4ba3c668a426c6450a03434e" + }, + "vcruntime140_threads.dll": { + "bytes": 38840, + "sha256": "40f39e8cee5f5a531b4010e18b807e8fe265b908dc58f959ecb7ebc6ea6cb11a" + } + } +} diff --git a/experiments/shell/results/2026-10-29-qt-unicode-input/focused-validation.json b/experiments/shell/results/2026-10-29-qt-unicode-input/focused-validation.json new file mode 100644 index 00000000..dc1b4e7a --- /dev/null +++ b/experiments/shell/results/2026-10-29-qt-unicode-input/focused-validation.json @@ -0,0 +1,148 @@ +{ + "schema": "termforge-qt-unicode-focused-v1", + "source_commit": "2bdcea3161f415f49d96bc0f30e5e72794c8c72f", + "source_tree": "58f1193e86da69971f82c4abc8cbfdb1602dd41b", + "sources": { + "CMakeLists.txt": { + "bytes": 25814, + "sha256": "6a49540a1173b26cda3703a745870898a2071cc1c047a42b8eb9484a2cd559f7" + }, + "src/qt/unicode_input_test.cpp": { + "bytes": 17771, + "sha256": "5ac07aa9661f10c128b56f44bd9059daa126bef06f62d8bf5a7f592d59115b41" + }, + "src/qt/terminal_widget.hpp": { + "bytes": 7851, + "sha256": "c74daa211f2bf7cdd42d72c2cdd194921754436b13b3d2b35ebd39aca26720a8" + }, + "src/qt/terminal_session.hpp": { + "bytes": 9557, + "sha256": "bf3816ec703aa91d66505d1dfa77dcfda6e574fa2c2b020ebec657b4927ab98e" + }, + "src/qt/session_tabs.hpp": { + "bytes": 7013, + "sha256": "bdd7843ee051bd6efecb08311b623c671d54fc5e1d2302b8f9d2a731c49749df" + }, + "src/qt/run_smoke.py": { + "bytes": 4287, + "sha256": "25f4d6adec4e9bfad77698300c44d9162511ac1ebb722993d5a52cdb8a863983" + }, + "experiments/shell/keyboard.hpp": { + "bytes": 3979, + "sha256": "0790ddd0a3b755de5fc22a7bff5c3421b91c19b78dcd7bc0af9de22fa471c3b3" + }, + "experiments/shell/queues.hpp": { + "bytes": 7224, + "sha256": "9087944a7749176789a489bce0522c55764461342b59c138611743b6947845b6" + }, + "experiments/shell/input_client.cpp": { + "bytes": 11706, + "sha256": "67ebb343978ac6822f8feaafa7510f9ee325737fce91b3d2aed8385deea6a969" + }, + "experiments/conpty/session.hpp": { + "bytes": 11072, + "sha256": "7b3934715948a05c1996bcccb02ef2eb58ea11baf26bf97c2e728a52d58becda" + }, + "experiments/conpty/backend.hpp": { + "bytes": 6815, + "sha256": "5ee3ab95dc4b9aefce23ff7900cda2ecdd9cd09d1b1044074de2f5e572c31489" + }, + "experiments/core/terminal.hpp": { + "bytes": 12270, + "sha256": "ed3c030611a6df38df26057a8c2834f2808cd05306e73aead79927b1f76fe5ca" + }, + "experiments/core/screen.hpp": { + "bytes": 17242, + "sha256": "04c19f09ebc0c468efbef8dd4c9da8f4bcd44e94000a87c56239756c0df1b474" + }, + "third_party/qt/manifest.json": { + "bytes": 5806, + "sha256": "2796294f254f7fe0370be99d2de8f7745734c7352ac2648b1f8d9a8fab5ad2cd" + }, + "third_party/conpty/manifest.json": { + "bytes": 1467, + "sha256": "e75e522a3355c0a66c6d60cafa02413a83d8757262747f650b77c959c09ed439" + }, + "third_party/msvc_runtime/manifest.json": { + "bytes": 4123, + "sha256": "154176c8b4ae6dd57fce453e2c2002ff484344b39451fa9880b07db4794f3758" + } + }, + "red": { + "source_commit": "8adc256ffe53a7cace75d74bf6ac5d173e8b59fc", + "expected_hex": "4ce4b8ade6968752", + "actual_hex": "4c52", + "ctest_exit": 8, + "native_exit": 1, + "receiver_exit": 0, + "job_active": 0, + "job_total": 4, + "timed_out": false, + "runtime_manifest": { + "bytes": 5873, + "sha256": "21f47a361fdb01889eb11eeeb86a1faf21f49bea7ae0487c490f71c47da8a48c" + }, + "log": { + "bytes": 1970, + "sha256": "fec2d8a5bba939e23f87a804ee356535378982d5d1edd0852eba73e222a276c9" + } + }, + "focused": { + "ctest_exit": 0, + "native_exit": 0, + "job_active": 0, + "job_total": 17, + "timed_out": false, + "runtime_manifest": { + "bytes": 6007, + "sha256": "ca3fec6250f109e860a2111cbaa49b9c26ccd2bcdd6779ce43c1d48fbdf6488a" + }, + "log": { + "bytes": 2894, + "sha256": "ed48ca36530620c166a4037ccec7beade380b116ab97d703ba1bc6c733ce7de7" + }, + "receivers": { + "unicode.bin": { + "bytes": 58, + "sha256": "c3138debbb91d7dc8a74324283da18b89ed1c1a9a52efc8b5caf7e77a92df2ce" + }, + "focus-packet.bin": { + "bytes": 1037, + "sha256": "97f22206016f52617da7b671f20fea22d85b36bee4d66c04cf3a174264b8d862" + }, + "normal-keys.bin": { + "bytes": 19, + "sha256": "aa5b356f76c764945f6dfea4430b774b68201b2095f14d7613832290b0f461e7" + }, + "tab-a.bin": { + "bytes": 9, + "sha256": "fa27960a63a077e4b40f1de30e99f489921da201cd72c2284f5bb26579eb008a" + }, + "tab-b.bin": { + "bytes": 8, + "sha256": "6a324e7924ac17fb25d6444b73b49137d96c23c98ec45069d28a048ca5de1dcf" + }, + "late-start.bin": { + "bytes": 4, + "sha256": "9719ab72356050600c22e4b79b4200066150c6bac592a0630b4ba8f07f189080" + } + }, + "cancelled_receiver": { + "output_bytes": 0, + "natural_exit": false, + "cleanup_verified": true + } + }, + "deadlines": { + "per_gate_ms": 5000, + "supervisor_communicate_seconds": 25, + "ctest_seconds": 35 + }, + "full_validation": null, + "limits": [ + "synthetic QKeyEvent, not physical keyboard or OS IME", + "receiver ReadConsoleW and independent UTF8 export only", + "glyph, width, shaping, presented and performance unknown", + "Qt queue refusal exercised by single-packet limit; existing InputQueue total-capacity fixture remains separate" + ] +} diff --git a/experiments/shell/results/2026-10-29-qt-unicode-input/red-runtime.json b/experiments/shell/results/2026-10-29-qt-unicode-input/red-runtime.json new file mode 100644 index 00000000..f9c51d72 --- /dev/null +++ b/experiments/shell/results/2026-10-29-qt-unicode-input/red-runtime.json @@ -0,0 +1,165 @@ +{ + "schema": "termforge-windows-runtime-v1", + "executable": "qt_unicode_input_test.exe", + "architecture": "windows-x64", + "msvc_runtime_version": "14.51.36247.0", + "msvc_runtime_manifest_sha256": "154176c8b4ae6dd57fce453e2c2002ff484344b39451fa9880b07db4794f3758", + "files": { + "D3Dcompiler_47.dll": { + "bytes": 4669440, + "sha256": "0f89c19165fd83a05e39bea601a06007e6a8814d55e2adf913902926231f843e" + }, + "LICENSE-MICROSOFT-VC-REDIST.docx": { + "bytes": 39553, + "sha256": "08651651a7602fc7c0e2763de0fde1ff9f868df2780597cd1775ee9d6441c783" + }, + "NOTICE.txt": { + "bytes": 563, + "sha256": "510781c94971ea19adae55d2c42207d942b0646deee29d5c401f189cc25d415f" + }, + "Qt6Core.dll": { + "bytes": 10250040, + "sha256": "b712b4754588e89f855dc0aa087d6304b2fd21b0e0639d1cdeb9cb969bd5fb72" + }, + "Qt6Gui.dll": { + "bytes": 9514296, + "sha256": "03ba47a2025036a04f5548bce242788ac21edf551e78d5b42e1b87beb799904f" + }, + "Qt6Network.dll": { + "bytes": 1773880, + "sha256": "203f8ddf83c015ce56752ebe7a1986472be7dbb95c20e13ac8e1d741d9ac93d1" + }, + "Qt6Widgets.dll": { + "bytes": 6577464, + "sha256": "61ddd0316a731b8dbabaf2924cd0d47b756461e37329b4f535902cf450416127" + }, + "assets/fonts/OFL.txt": { + "bytes": 4395, + "sha256": "50ab8dd54680d3473f649c9db86fece88434d097c7834475c1c72d2f8c429215" + }, + "assets/fonts/README.md": { + "bytes": 3091, + "sha256": "760a714ad011733e0b93f3c25298190ff5fa493a9e40ad38d347922a355d538f" + }, + "assets/fonts/RobotoMono-Regular.ttf": { + "bytes": 125748, + "sha256": "af0bff7599c3df3831755c16e39b3c496df74b8c8d8a1161b14dc8461be17cb4" + }, + "concrt140.dll": { + "bytes": 374200, + "sha256": "54716f0738af891f283d213b5c8d11b25896bb8ee3097d301eae718560cf974e" + }, + "conpty-backend/LICENSE": { + "bytes": 1095, + "sha256": "ad0cf28f3381ca9bb0bf101d127402d44c17bfa0991e1a00bff7ae6679e9dada" + }, + "conpty-backend/OpenConsole.exe": { + "bytes": 1089376, + "sha256": "3d66b23d0a71bb8eed2b77edc8b9df9bf54ce6c8fb74c863a30e760997f80586" + }, + "conpty-backend/conpty.dll": { + "bytes": 119608, + "sha256": "feeef341d891643c62d30b6b07800bc70f0bc148f44cb8c3bee84aa557ae805a" + }, + "conpty-backend/manifest.json": { + "bytes": 1467, + "sha256": "e75e522a3355c0a66c6d60cafa02413a83d8757262747f650b77c959c09ed439" + }, + "dxcompiler.dll": { + "bytes": 14019128, + "sha256": "2bc16dc5ec85315f89e2f10a482d46dde94a65d6b2d7077f4c49c77e10222acc" + }, + "dxil.dll": { + "bytes": 1502760, + "sha256": "0e90c12c1c6aa1e09d025322a815f9dd80da7d1e331f1df585c7fcbbf44b40d3" + }, + "generic/qtuiotouchplugin.dll": { + "bytes": 102712, + "sha256": "69498d05870aa583c31159f9350e3d8a598b895f6525de7b6fa382395ad27b9d" + }, + "icuuc.dll": { + "bytes": 36864, + "sha256": "59d134ddc15ae0594f0a328979f8201662fc82634ddb7c537b156e7cf871f5eb" + }, + "imageformats/qgif.dll": { + "bytes": 47928, + "sha256": "ded464ef782f41862f391bc5b21c54d58f6aee670fc9f10a2d909ae5b0ba09a7" + }, + "imageformats/qico.dll": { + "bytes": 45880, + "sha256": "0c2abdf95d778bfed9863b8be817dc6cffe2dacdd1a85b2117b4b4ccb1a08e66" + }, + "imageformats/qjpeg.dll": { + "bytes": 577848, + "sha256": "cd965015cce4638361d7bc01193d92a041a227d46d0759ec25d2a1a8608a6b40" + }, + "msvcp140.dll": { + "bytes": 643512, + "sha256": "7c26614e1d733892c2deac7e245ce115504b1d80592dd0a01b08e3e5a55f89ca" + }, + "msvcp140_1.dll": { + "bytes": 35768, + "sha256": "206c931bf90fdad8816de3b5e2ef80b2bcaa9406c89ecc05fe6fddffe251e982" + }, + "msvcp140_2.dll": { + "bytes": 274872, + "sha256": "d50d7883f20d1dc6191768d3746f52dd9cac89c346ffaed5be1f110c2f34a838" + }, + "msvcp140_atomic_wait.dll": { + "bytes": 57792, + "sha256": "3d0cbfaa1bf3eecf5a3f4491d2960ee803cb994f30292c6adc4a07c498f60e2b" + }, + "msvcp140_codecvt_ids.dll": { + "bytes": 31160, + "sha256": "8a65c7596ef2e6938731f5a1058e7e40145b6d97967cc649231a076b9a608d78" + }, + "networkinformation/qnetworklistmanager.dll": { + "bytes": 70968, + "sha256": "4bc26d5b613c507634fd10f07fe031fcbd4f02157d23b9eb1407de4dd6da8278" + }, + "platforms/qoffscreen.dll": { + "bytes": 113976, + "sha256": "a35f87f597b91121cfc0ef39dcb0a5e7ad2876850fbfaf6377edf80eaa067bfd" + }, + "platforms/qwindows.dll": { + "bytes": 997688, + "sha256": "cafd5bfdef3d89d176ee26725c8d4759fd6cb007645b645a4cbf6386364a3f36" + }, + "qt_unicode_input_test.exe": { + "bytes": 143872, + "sha256": "06d1b14692d08e672517409846fca27821514ca94e3d0163bd3f5b9943ba5dd8" + }, + "shell_input_client.exe": { + "bytes": 41984, + "sha256": "3aae0923441b7c27ff687824e780c9689d77f2c98796ec8bf12693d00ae1b10e" + }, + "styles/qmodernwindowsstyle.dll": { + "bytes": 221496, + "sha256": "c279e7a97b21fcd7acdfde88cacb8baa119f563282e09c241acb3edee2bc9bbf" + }, + "tls/qcertonlybackend.dll": { + "bytes": 103224, + "sha256": "99d00b37bb2835c539366e4018a58e0ddad21e1c648a3f44f56335a0c1d34765" + }, + "tls/qschannelbackend.dll": { + "bytes": 262968, + "sha256": "b7035c3689b93f55b48112c3e85cf4b8550fefde3f7a044df2784c29330e6940" + }, + "vccorlib140.dll": { + "bytes": 350648, + "sha256": "09f93d5ff96ae09767f3e1af3cdd43a5a58f2598654fc63089429bb6f9f4737f" + }, + "vcruntime140.dll": { + "bytes": 178616, + "sha256": "d1f4225df2cd877dbf130d5668a021dce3f94118455ff5ec952061c30afc9ce7" + }, + "vcruntime140_1.dll": { + "bytes": 50112, + "sha256": "a7146c08f89fe5b04541ab507cdb59ff7b44534d4ba3c668a426c6450a03434e" + }, + "vcruntime140_threads.dll": { + "bytes": 38840, + "sha256": "40f39e8cee5f5a531b4010e18b807e8fe265b908dc58f959ecb7ebc6ea6cb11a" + } + } +} From 2b6d988914b04a2a05520f0b67c08c30b6e2b43b Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Thu, 29 Oct 2026 04:44:03 +0800 Subject: [PATCH 09/10] ci(qt): cover Unicode receiver with fixed runtime identities (refs #147) --- .github/workflows/qt-cpp.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/qt-cpp.yml b/.github/workflows/qt-cpp.yml index ae3e7bd7..cf5d64d4 100644 --- a/.github/workflows/qt-cpp.yml +++ b/.github/workflows/qt-cpp.yml @@ -42,7 +42,7 @@ jobs: "-DTERMFORGE_CONPTY_BACKEND_DIR=$dependencyRoot/conpty" ` "-DTERMFORGE_MSVC_RUNTIME_DIR=$dependencyRoot/crt" - name: Build Qt product and independent smoke - run: cmake --build build/qt-cpp --config Release --target termforge_qt_shell qt_cpp_shell_smoke qt_cpp_resize_smoke --parallel 2 + run: cmake --build build/qt-cpp --config Release --target termforge_qt_shell qt_cpp_shell_smoke qt_cpp_resize_smoke qt_unicode_input_test --parallel 2 - name: Record complete path-free runtime manifests run: | python tools/msvc_runtime.py manifest --directory build/qt-cpp/qt-bin/Release --executable termforge_qt_shell.exe @@ -51,6 +51,8 @@ jobs: if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } python tools/msvc_runtime.py manifest --directory build/qt-cpp/qt-resize-bin/Release --executable qt_cpp_resize_smoke.exe if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + python tools/msvc_runtime.py manifest --directory build/qt-cpp/qt-unicode-bin/Release --executable qt_unicode_input_test.exe + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - name: Preserve runtime file identities without binaries if: always() uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 @@ -62,8 +64,9 @@ jobs: build/qt-cpp/qt-bin/Release/runtime-manifest.json build/qt-cpp/qt-smoke-bin/Release/runtime-manifest.json build/qt-cpp/qt-resize-bin/Release/runtime-manifest.json + build/qt-cpp/qt-unicode-bin/Release/runtime-manifest.json - name: Run Qt offscreen smoke in its owned Job - run: ctest --test-dir build/qt-cpp -C Release -R '^qt_cpp_(shell|resize)$' --no-tests=error --verbose + run: ctest --test-dir build/qt-cpp -C Release -R '^(qt_cpp_(shell|resize)|qt_unicode_input)$' --no-tests=error --verbose # Keep this configuration independent from the default/system-backend and # Alacritty reference jobs. SDKs, runtime folders and raw logs are not # publication artifacts; this job does not produce a release package. From 0bdcfb2d81b61ea0a91efd02be99a84935840187 Mon Sep 17 00:00:00 2001 From: JavaZeroo <71128095+JavaZeroo@users.noreply.github.com> Date: Thu, 29 Oct 2026 04:58:41 +0800 Subject: [PATCH 10/10] docs(qt): publish fixed Unicode and resize integration validation (refs #147) --- docs/EVIDENCE.md | 1 + docs/FEATURES.md | 2 +- .../2026-10-29-qt-unicode-input/README.md | 12 +- .../final-input-runtime.json | 169 +++++++ .../final-product-runtime.json | 157 +++++++ .../final-resize-runtime.json | 165 +++++++ .../final-shell-runtime.json | 165 +++++++ .../validation.json | 439 ++++++++++++++++++ src/qt/README.md | 10 +- 9 files changed, 1114 insertions(+), 6 deletions(-) create mode 100644 experiments/shell/results/2026-10-29-qt-unicode-input/final-input-runtime.json create mode 100644 experiments/shell/results/2026-10-29-qt-unicode-input/final-product-runtime.json create mode 100644 experiments/shell/results/2026-10-29-qt-unicode-input/final-resize-runtime.json create mode 100644 experiments/shell/results/2026-10-29-qt-unicode-input/final-shell-runtime.json create mode 100644 experiments/shell/results/2026-10-29-qt-unicode-input/validation.json diff --git a/docs/EVIDENCE.md b/docs/EVIDENCE.md index 3bbcfe52..651282a6 100644 --- a/docs/EVIDENCE.md +++ b/docs/EVIDENCE.md @@ -10,6 +10,7 @@ |---|---| | 自主 C++ 限定 1049 协议 | [固定结果](../experiments/core/results/2026-10-29-cpp-1049/README.md)、[验证身份](../experiments/core/results/2026-10-29-cpp-1049/validation.json):文字/光标/颜色及 pending-wrap、重复进入与读者失效;55项中Qt1项分列,13条内部skip及旧负例判据失败保留。未验收非颜色属性/全网格背景、其他模式、真实TUI/IME/DPI、呈现或性能 | | 自主 C++ 双缓冲存储与文档 epoch 基础 | [固定结果](../experiments/core/results/2026-10-29-buffer-state-r2/README.md)、[验证身份](../experiments/core/results/2026-10-29-buffer-state-r2/validation.json):拥有状态、共享 ID、隐藏帧 resize 回滚与跨缓冲句柄失效;Qt/candidate 关闭配置的完整 50 项及 12 条内部 skip 分列。未接 private CSI,不是原版模式、Qt 产品路线或完整 #27 验收 | +| Qt 基础 Unicode 文本字节 | [红绿与联合验证](../experiments/shell/results/2026-10-29-qt-unicode-input/README.md)、[固定联合身份](../experiments/shell/results/2026-10-29-qt-unicode-input/validation.json):公开Qt事件到独立ReadConsoleW接收端,整/分事件代理项、焦点/隐藏/关闭、1024B整包与NUL、两目标隔离。2b6d源码一次61项分列Qt3/其余58,13内部skip、旧红例与27原件保留;不是实际IME、字素/字形、原生Unicode窗口、DPI或性能验收 | | Qt cmd 标签与退出前回收 | [固定结果](../experiments/shell/results/2026-10-28-qt-tabs/README.md)、[验证身份](../experiments/shell/results/2026-10-28-qt-tabs/validation.json):双 cmd 隔离、失败页、关闭与四个实际事件循环边界;Qt 1 项和其他 49 项诊断及内部跳过分列,旧中断轮不拼接 | | Qt 基础动态网格与真实 child 尺寸 | [固定结果](../experiments/shell/results/2026-10-29-qt-resize/README.md)、[原冻结与查询](../experiments/shell/results/2026-10-29-qt-resize/validation.json)、[平台区域反例与夹具修订](../experiments/shell/results/2026-10-29-qt-resize/platform-area-validation.json)、[0ff 隔离原生补充](../experiments/shell/results/2026-10-29-qt-resize/native-validation.json):GUI/core、child、错误和字体度量红绿;678 原生极窄失败及 135 条 child 回复保留;0ff 新离屏 57 项与冻结包一次原生成功分列,原生保留全部 40 条 CSBI、实际顶层区域 120px→11 列及 root/Job 回收。两次离屏均分开 Qt 2 项、其余 55 项诊断及 13 条内部 skip;退出竞态旧尺寸保留;原生窗口仅 DPI 96,不是原版 reflow、真实不同 DPI、物理呈现或旧 system 失败修复 | | Qt app-local 固定微软 CRT 与隔离 Windows 加载 | [固定结果](../experiments/shell/results/2026-10-29-qt-crt-deploy/README.md)、[完整运行时与验证身份](../experiments/shell/results/2026-10-29-qt-crt-deploy/validation.json):原 `cf8` 52 文件首次加载失败与仅追加 CRT/原许可后的成功分列;新源码构建、原生窗口、有限 cmd/关闭、offscreen 及 Job 清理独立记录。不是完整发行、跨 DPI/IME 或物理呈现验收 | diff --git a/docs/FEATURES.md b/docs/FEATURES.md index bf9b36fa..f1f13379 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -23,7 +23,7 @@ | 新路线迁移入口 | 当前状态与边界 | 目标差距 | 路标 | |---|---|---|---| -| 自主 C++ / Qt / 固定微软 ConPTY | **实现中,未验收**:[显式 Qt 入口](../src/qt/README.md)提供默认 80×24、随可用区域变化的 cmd 标签、基本 ASCII/16 色及各会话独立有界队列;[基础动态尺寸验证](../experiments/shell/results/2026-10-29-qt-resize/README.md)分离窗口/核心/transport 与真实 child,覆盖极小、重复、隐藏页和关闭交叉,并复验原标签算术及事件循环回收。678 原生极窄失败保留;0ff 仅修订字面子区域/顶层实际区域夹具,另有[冻结包一次隔离原生成功](../experiments/shell/results/2026-10-29-qt-resize/native-validation.json):实际顶层区域、core/transport/child 一致,窗口仅 DPI 96。两次离屏完整 57 项分别绑定源码;Qt 2 项与其余 55 项既有/独立诊断、13 条内部 skip 分列 | 分屏、其他 shell、工作区恢复、完整 resize/reflow、历史/选择/Find、Unicode/IME、真实 DPI、应用兼容与性能仍需实现或迁移;物理裁剪不恢复丢弃正文,完整 WindTerm 标签/尺寸行为对照未完成;当前不是可发布开发包 | R2 | +| 自主 C++ / Qt / 固定微软 ConPTY | **实现中,未验收**:[显式 Qt 入口](../src/qt/README.md)提供默认 80×24、随可用区域变化的 cmd 标签、基本 ASCII 与[限定 Unicode 文本字节](../experiments/shell/results/2026-10-29-qt-unicode-input/README.md)、16 色及各会话独立有界队列;[基础动态尺寸验证](../experiments/shell/results/2026-10-29-qt-resize/README.md)分离窗口/核心/transport 与真实 child,覆盖极小、重复、隐藏页和关闭交叉,并复验原标签算术及事件循环回收。678 原生极窄失败保留;0ff 仅修订字面子区域/顶层实际区域夹具,另有[冻结包一次隔离原生成功](../experiments/shell/results/2026-10-29-qt-resize/native-validation.json):实际顶层区域、core/transport/child 一致,窗口仅 DPI 96。旧尺寸结果保留各自源码;[Unicode 联合源码的一次完整61项](../experiments/shell/results/2026-10-29-qt-unicode-input/validation.json)分开 Qt3项与其余58项既有/独立诊断,13条内部skip保留 | 分屏、其他 shell、工作区恢复、完整 resize/reflow、历史/选择/Find、完整 Unicode 字素/绘制与 IME、真实 DPI、应用兼容与性能仍需实现或迁移;物理裁剪不恢复丢弃正文,完整 WindTerm 标签/尺寸行为对照未完成;当前不是可发布开发包 | R2 | 自主 C++ 核心已有 [#130 双缓冲存储基础](../experiments/core/results/2026-10-29-buffer-state-r2/README.md),并接入 [#135 限定 1049 协议](../experiments/core/results/2026-10-29-cpp-1049/README.md):strict/recovery 识别 `?1049h/l`,重复进入归首格、清旧备用文本和复位写入颜色,返回恢复主屏文字/光标/颜色及 pending-wrap。共享行 ID、双帧物理 resize 与逻辑 epoch 防止旧位置、选择、搜索及 viewport 复活,包括清屏后重写相同文字。原版完整网格/背景、非颜色属性及其他模式仍未知;本地55项中 Qt1项与其余54项诊断分列,13条内部跳过保留,不表示完整 [#27](https://github.com/JavaZeroo/termforge/issues/27)、真实 TUI/Qt 交互或系统剪贴板已验收。 diff --git a/experiments/shell/results/2026-10-29-qt-unicode-input/README.md b/experiments/shell/results/2026-10-29-qt-unicode-input/README.md index de47b7f2..0e11187b 100644 --- a/experiments/shell/results/2026-10-29-qt-unicode-input/README.md +++ b/experiments/shell/results/2026-10-29-qt-unicode-input/README.md @@ -2,7 +2,7 @@ 执行项 [#147](https://github.com/JavaZeroo/termforge/issues/147),父项 [#30](https://github.com/JavaZeroo/termforge/issues/30)。正式 C++ / Qt Widgets / 固定 Microsoft ConPTY 路线将 `QKeyEvent.text()` 的 UTF-16 单元交给本项目既有 `KeyboardEncoder`;每个 widget 独立保存未完成的高代理项。临时编码器处理整条事件,只有整包进入有界输入队列后才提交状态。包过限或队列拒绝保留此前状态;失焦、隐藏、关闭及目标不可用清除未提交的半个标量。 -未配对单元使用 U+FFFD,控制键结束此前未完成标量,焦点变化丢弃未提交单元;这些是**现有 TermForge 编码策略**。本批没有运行 WindTerm,不宣称这些边界已经与原版匹配。 +未配对单元使用 U+FFFD,已支持的控制/导航键结束此前未完成标量,焦点变化丢弃未提交单元;这些是**现有 TermForge 编码策略**。本批没有运行 WindTerm,不宣称这些边界已经与原版匹配。 ## 公共边界与原始接收 @@ -29,6 +29,16 @@ Qt 层实际拒绝场景使用现有单包上限。16KiB 总队列容量沿既 原等待边界不变:单阶段5秒,监督器主 `communicate` 25秒,CTest35秒,失败清理仍由原监督器/Session处理;这些是诊断期限,不是性能指标或完整进程生命周期承诺。运行使用 Qt 6.10.3、固定 ConPTY 1.25.260930003、VS2022/MSVC19.42 与 app-local CRT14.51;运行时清单绑定所有实际文件。 +## 联合源码与一次完整回归 + +动态尺寸 PR148 合入 main `05f0065f4eb19ce5a76065d18f2247bcc731e285` 后,本分支以 `5d7cb5c` 合并:保留可扩展布局、resize/字体/DPR 事件与独立编码器的焦点、隐藏、关闭和结束清理。执行源码固定为 `2b6d988914b04a2a05520f0b67c08c30b6e2b43b`,tree `46e358272e6f1c99ef5d65ffcb007a3ce41b1ac9`;后续公开文档和 JSON 不算执行源码。[联合验证身份](validation.json)与上述旧红例、旧针对性绿例分别保留。 + +在 Windows 11 x64 build26200/UBR9711、MSVC19.42.34433.0、Python3.12.10 下使用固定 Qt6.10.3、ConPTY1.25.260930003 与 app-local CRT14.51.36247.0。先运行 Qt 三项针对性检查 **3/3**,随后唯一一次完整 CTest **61/61、exit0、307.06s**。其中正式 Qt 为 `qt_cpp_shell`、`qt_unicode_input`、`qt_cpp_resize` 三项,其余58项属于既有默认/system或独立诊断;**13条内部 unittest skip** 全部保留,包含私有 window station 的 OpenClipboard5,不能写成 CF 验收。时长仅为测试运行记录,不是性能基线。 + +两次 Unicode 检查各产生六个自然结束接收文件与一个取消目标的空文件,全部逐字核对字面预期。7397个执行输入及运行文件在完整回归后重算零变化:2817源码、4358 SDK、2 ConPTY、12 CRT、38产品目录、40原smoke、40 resize、41 Unicode、49旧诊断文件;这些目录计数包含各自 `runtime-manifest.json`。另外27份旧原件和已有公开红绿证据未变。四份实际运行时清单分别为[产品](final-product-runtime.json)、[原 smoke](final-shell-runtime.json)、[resize](final-resize-runtime.json)、[Unicode](final-input-runtime.json);其 `files` 字段不包含清单自身。 + +专用 Qt CI 新增 Unicode 测试目标、完整运行时身份与三项测试选择。当前公开文件由同一批原件离线派生,普通与 `-O` 解释器导出逐字一致;没有重新运行原版、Qt windows-plugin、guest 或第二次完整矩阵。旧 #80/#81/#144/#145 原因仍未知,这次通过不构成因果修复。原生缩放证据仍只绑定其原0ff源码,不转记为本次 Unicode 原生验收。 + ## 复验与限制 按 [Qt 构建说明](../../../../../src/qt/README.md)配置后: diff --git a/experiments/shell/results/2026-10-29-qt-unicode-input/final-input-runtime.json b/experiments/shell/results/2026-10-29-qt-unicode-input/final-input-runtime.json new file mode 100644 index 00000000..e2b96933 --- /dev/null +++ b/experiments/shell/results/2026-10-29-qt-unicode-input/final-input-runtime.json @@ -0,0 +1,169 @@ +{ + "schema": "termforge-windows-runtime-v1", + "executable": "qt_unicode_input_test.exe", + "architecture": "windows-x64", + "msvc_runtime_version": "14.51.36247.0", + "msvc_runtime_manifest_sha256": "154176c8b4ae6dd57fce453e2c2002ff484344b39451fa9880b07db4794f3758", + "files": { + "D3Dcompiler_47.dll": { + "bytes": 4669440, + "sha256": "0f89c19165fd83a05e39bea601a06007e6a8814d55e2adf913902926231f843e" + }, + "LICENSE-MICROSOFT-VC-REDIST.docx": { + "bytes": 39553, + "sha256": "08651651a7602fc7c0e2763de0fde1ff9f868df2780597cd1775ee9d6441c783" + }, + "NOTICE.txt": { + "bytes": 563, + "sha256": "510781c94971ea19adae55d2c42207d942b0646deee29d5c401f189cc25d415f" + }, + "Qt6Core.dll": { + "bytes": 10250040, + "sha256": "b712b4754588e89f855dc0aa087d6304b2fd21b0e0639d1cdeb9cb969bd5fb72" + }, + "Qt6Gui.dll": { + "bytes": 9514296, + "sha256": "03ba47a2025036a04f5548bce242788ac21edf551e78d5b42e1b87beb799904f" + }, + "Qt6Network.dll": { + "bytes": 1773880, + "sha256": "203f8ddf83c015ce56752ebe7a1986472be7dbb95c20e13ac8e1d741d9ac93d1" + }, + "Qt6Test.dll": { + "bytes": 380216, + "sha256": "9d0473552b332aa378c60da05af97d741fcce06b0fc4dd68e36d885015eb5187" + }, + "Qt6Widgets.dll": { + "bytes": 6577464, + "sha256": "61ddd0316a731b8dbabaf2924cd0d47b756461e37329b4f535902cf450416127" + }, + "assets/fonts/OFL.txt": { + "bytes": 4395, + "sha256": "50ab8dd54680d3473f649c9db86fece88434d097c7834475c1c72d2f8c429215" + }, + "assets/fonts/README.md": { + "bytes": 3091, + "sha256": "760a714ad011733e0b93f3c25298190ff5fa493a9e40ad38d347922a355d538f" + }, + "assets/fonts/RobotoMono-Regular.ttf": { + "bytes": 125748, + "sha256": "af0bff7599c3df3831755c16e39b3c496df74b8c8d8a1161b14dc8461be17cb4" + }, + "concrt140.dll": { + "bytes": 374200, + "sha256": "54716f0738af891f283d213b5c8d11b25896bb8ee3097d301eae718560cf974e" + }, + "conpty-backend/LICENSE": { + "bytes": 1095, + "sha256": "ad0cf28f3381ca9bb0bf101d127402d44c17bfa0991e1a00bff7ae6679e9dada" + }, + "conpty-backend/OpenConsole.exe": { + "bytes": 1089376, + "sha256": "3d66b23d0a71bb8eed2b77edc8b9df9bf54ce6c8fb74c863a30e760997f80586" + }, + "conpty-backend/conpty.dll": { + "bytes": 119608, + "sha256": "feeef341d891643c62d30b6b07800bc70f0bc148f44cb8c3bee84aa557ae805a" + }, + "conpty-backend/manifest.json": { + "bytes": 1467, + "sha256": "e75e522a3355c0a66c6d60cafa02413a83d8757262747f650b77c959c09ed439" + }, + "dxcompiler.dll": { + "bytes": 14019128, + "sha256": "2bc16dc5ec85315f89e2f10a482d46dde94a65d6b2d7077f4c49c77e10222acc" + }, + "dxil.dll": { + "bytes": 1502760, + "sha256": "0e90c12c1c6aa1e09d025322a815f9dd80da7d1e331f1df585c7fcbbf44b40d3" + }, + "generic/qtuiotouchplugin.dll": { + "bytes": 102712, + "sha256": "69498d05870aa583c31159f9350e3d8a598b895f6525de7b6fa382395ad27b9d" + }, + "icuuc.dll": { + "bytes": 36864, + "sha256": "59d134ddc15ae0594f0a328979f8201662fc82634ddb7c537b156e7cf871f5eb" + }, + "imageformats/qgif.dll": { + "bytes": 47928, + "sha256": "ded464ef782f41862f391bc5b21c54d58f6aee670fc9f10a2d909ae5b0ba09a7" + }, + "imageformats/qico.dll": { + "bytes": 45880, + "sha256": "0c2abdf95d778bfed9863b8be817dc6cffe2dacdd1a85b2117b4b4ccb1a08e66" + }, + "imageformats/qjpeg.dll": { + "bytes": 577848, + "sha256": "cd965015cce4638361d7bc01193d92a041a227d46d0759ec25d2a1a8608a6b40" + }, + "msvcp140.dll": { + "bytes": 643512, + "sha256": "7c26614e1d733892c2deac7e245ce115504b1d80592dd0a01b08e3e5a55f89ca" + }, + "msvcp140_1.dll": { + "bytes": 35768, + "sha256": "206c931bf90fdad8816de3b5e2ef80b2bcaa9406c89ecc05fe6fddffe251e982" + }, + "msvcp140_2.dll": { + "bytes": 274872, + "sha256": "d50d7883f20d1dc6191768d3746f52dd9cac89c346ffaed5be1f110c2f34a838" + }, + "msvcp140_atomic_wait.dll": { + "bytes": 57792, + "sha256": "3d0cbfaa1bf3eecf5a3f4491d2960ee803cb994f30292c6adc4a07c498f60e2b" + }, + "msvcp140_codecvt_ids.dll": { + "bytes": 31160, + "sha256": "8a65c7596ef2e6938731f5a1058e7e40145b6d97967cc649231a076b9a608d78" + }, + "networkinformation/qnetworklistmanager.dll": { + "bytes": 70968, + "sha256": "4bc26d5b613c507634fd10f07fe031fcbd4f02157d23b9eb1407de4dd6da8278" + }, + "platforms/qoffscreen.dll": { + "bytes": 113976, + "sha256": "a35f87f597b91121cfc0ef39dcb0a5e7ad2876850fbfaf6377edf80eaa067bfd" + }, + "platforms/qwindows.dll": { + "bytes": 997688, + "sha256": "cafd5bfdef3d89d176ee26725c8d4759fd6cb007645b645a4cbf6386364a3f36" + }, + "qt_unicode_input_test.exe": { + "bytes": 212480, + "sha256": "e911b76e8c303dd1c1ac8d7ab225e1cf9a3e913fd960d889119d44304c85851c" + }, + "shell_input_client.exe": { + "bytes": 41984, + "sha256": "924fc4f11fe8e416f48d9deaecbf520e2899839fec6eedeb162f0e5805b59f06" + }, + "styles/qmodernwindowsstyle.dll": { + "bytes": 221496, + "sha256": "c279e7a97b21fcd7acdfde88cacb8baa119f563282e09c241acb3edee2bc9bbf" + }, + "tls/qcertonlybackend.dll": { + "bytes": 103224, + "sha256": "99d00b37bb2835c539366e4018a58e0ddad21e1c648a3f44f56335a0c1d34765" + }, + "tls/qschannelbackend.dll": { + "bytes": 262968, + "sha256": "b7035c3689b93f55b48112c3e85cf4b8550fefde3f7a044df2784c29330e6940" + }, + "vccorlib140.dll": { + "bytes": 350648, + "sha256": "09f93d5ff96ae09767f3e1af3cdd43a5a58f2598654fc63089429bb6f9f4737f" + }, + "vcruntime140.dll": { + "bytes": 178616, + "sha256": "d1f4225df2cd877dbf130d5668a021dce3f94118455ff5ec952061c30afc9ce7" + }, + "vcruntime140_1.dll": { + "bytes": 50112, + "sha256": "a7146c08f89fe5b04541ab507cdb59ff7b44534d4ba3c668a426c6450a03434e" + }, + "vcruntime140_threads.dll": { + "bytes": 38840, + "sha256": "40f39e8cee5f5a531b4010e18b807e8fe265b908dc58f959ecb7ebc6ea6cb11a" + } + } +} diff --git a/experiments/shell/results/2026-10-29-qt-unicode-input/final-product-runtime.json b/experiments/shell/results/2026-10-29-qt-unicode-input/final-product-runtime.json new file mode 100644 index 00000000..2e5b297d --- /dev/null +++ b/experiments/shell/results/2026-10-29-qt-unicode-input/final-product-runtime.json @@ -0,0 +1,157 @@ +{ + "schema": "termforge-windows-runtime-v1", + "executable": "termforge_qt_shell.exe", + "architecture": "windows-x64", + "msvc_runtime_version": "14.51.36247.0", + "msvc_runtime_manifest_sha256": "154176c8b4ae6dd57fce453e2c2002ff484344b39451fa9880b07db4794f3758", + "files": { + "D3Dcompiler_47.dll": { + "bytes": 4669440, + "sha256": "0f89c19165fd83a05e39bea601a06007e6a8814d55e2adf913902926231f843e" + }, + "LICENSE-MICROSOFT-VC-REDIST.docx": { + "bytes": 39553, + "sha256": "08651651a7602fc7c0e2763de0fde1ff9f868df2780597cd1775ee9d6441c783" + }, + "NOTICE.txt": { + "bytes": 563, + "sha256": "510781c94971ea19adae55d2c42207d942b0646deee29d5c401f189cc25d415f" + }, + "Qt6Core.dll": { + "bytes": 10250040, + "sha256": "b712b4754588e89f855dc0aa087d6304b2fd21b0e0639d1cdeb9cb969bd5fb72" + }, + "Qt6Gui.dll": { + "bytes": 9514296, + "sha256": "03ba47a2025036a04f5548bce242788ac21edf551e78d5b42e1b87beb799904f" + }, + "Qt6Network.dll": { + "bytes": 1773880, + "sha256": "203f8ddf83c015ce56752ebe7a1986472be7dbb95c20e13ac8e1d741d9ac93d1" + }, + "Qt6Widgets.dll": { + "bytes": 6577464, + "sha256": "61ddd0316a731b8dbabaf2924cd0d47b756461e37329b4f535902cf450416127" + }, + "assets/fonts/OFL.txt": { + "bytes": 4395, + "sha256": "50ab8dd54680d3473f649c9db86fece88434d097c7834475c1c72d2f8c429215" + }, + "assets/fonts/README.md": { + "bytes": 3091, + "sha256": "760a714ad011733e0b93f3c25298190ff5fa493a9e40ad38d347922a355d538f" + }, + "assets/fonts/RobotoMono-Regular.ttf": { + "bytes": 125748, + "sha256": "af0bff7599c3df3831755c16e39b3c496df74b8c8d8a1161b14dc8461be17cb4" + }, + "concrt140.dll": { + "bytes": 374200, + "sha256": "54716f0738af891f283d213b5c8d11b25896bb8ee3097d301eae718560cf974e" + }, + "conpty-backend/LICENSE": { + "bytes": 1095, + "sha256": "ad0cf28f3381ca9bb0bf101d127402d44c17bfa0991e1a00bff7ae6679e9dada" + }, + "conpty-backend/OpenConsole.exe": { + "bytes": 1089376, + "sha256": "3d66b23d0a71bb8eed2b77edc8b9df9bf54ce6c8fb74c863a30e760997f80586" + }, + "conpty-backend/conpty.dll": { + "bytes": 119608, + "sha256": "feeef341d891643c62d30b6b07800bc70f0bc148f44cb8c3bee84aa557ae805a" + }, + "conpty-backend/manifest.json": { + "bytes": 1467, + "sha256": "e75e522a3355c0a66c6d60cafa02413a83d8757262747f650b77c959c09ed439" + }, + "dxcompiler.dll": { + "bytes": 14019128, + "sha256": "2bc16dc5ec85315f89e2f10a482d46dde94a65d6b2d7077f4c49c77e10222acc" + }, + "dxil.dll": { + "bytes": 1502760, + "sha256": "0e90c12c1c6aa1e09d025322a815f9dd80da7d1e331f1df585c7fcbbf44b40d3" + }, + "generic/qtuiotouchplugin.dll": { + "bytes": 102712, + "sha256": "69498d05870aa583c31159f9350e3d8a598b895f6525de7b6fa382395ad27b9d" + }, + "icuuc.dll": { + "bytes": 36864, + "sha256": "59d134ddc15ae0594f0a328979f8201662fc82634ddb7c537b156e7cf871f5eb" + }, + "imageformats/qgif.dll": { + "bytes": 47928, + "sha256": "ded464ef782f41862f391bc5b21c54d58f6aee670fc9f10a2d909ae5b0ba09a7" + }, + "imageformats/qico.dll": { + "bytes": 45880, + "sha256": "0c2abdf95d778bfed9863b8be817dc6cffe2dacdd1a85b2117b4b4ccb1a08e66" + }, + "imageformats/qjpeg.dll": { + "bytes": 577848, + "sha256": "cd965015cce4638361d7bc01193d92a041a227d46d0759ec25d2a1a8608a6b40" + }, + "msvcp140.dll": { + "bytes": 643512, + "sha256": "7c26614e1d733892c2deac7e245ce115504b1d80592dd0a01b08e3e5a55f89ca" + }, + "msvcp140_1.dll": { + "bytes": 35768, + "sha256": "206c931bf90fdad8816de3b5e2ef80b2bcaa9406c89ecc05fe6fddffe251e982" + }, + "msvcp140_2.dll": { + "bytes": 274872, + "sha256": "d50d7883f20d1dc6191768d3746f52dd9cac89c346ffaed5be1f110c2f34a838" + }, + "msvcp140_atomic_wait.dll": { + "bytes": 57792, + "sha256": "3d0cbfaa1bf3eecf5a3f4491d2960ee803cb994f30292c6adc4a07c498f60e2b" + }, + "msvcp140_codecvt_ids.dll": { + "bytes": 31160, + "sha256": "8a65c7596ef2e6938731f5a1058e7e40145b6d97967cc649231a076b9a608d78" + }, + "networkinformation/qnetworklistmanager.dll": { + "bytes": 70968, + "sha256": "4bc26d5b613c507634fd10f07fe031fcbd4f02157d23b9eb1407de4dd6da8278" + }, + "platforms/qwindows.dll": { + "bytes": 997688, + "sha256": "cafd5bfdef3d89d176ee26725c8d4759fd6cb007645b645a4cbf6386364a3f36" + }, + "styles/qmodernwindowsstyle.dll": { + "bytes": 221496, + "sha256": "c279e7a97b21fcd7acdfde88cacb8baa119f563282e09c241acb3edee2bc9bbf" + }, + "termforge_qt_shell.exe": { + "bytes": 163328, + "sha256": "29bdf5e19b14482aac53636dfb493e0126bb29b1c45a19d350c3f31e4e572976" + }, + "tls/qcertonlybackend.dll": { + "bytes": 103224, + "sha256": "99d00b37bb2835c539366e4018a58e0ddad21e1c648a3f44f56335a0c1d34765" + }, + "tls/qschannelbackend.dll": { + "bytes": 262968, + "sha256": "b7035c3689b93f55b48112c3e85cf4b8550fefde3f7a044df2784c29330e6940" + }, + "vccorlib140.dll": { + "bytes": 350648, + "sha256": "09f93d5ff96ae09767f3e1af3cdd43a5a58f2598654fc63089429bb6f9f4737f" + }, + "vcruntime140.dll": { + "bytes": 178616, + "sha256": "d1f4225df2cd877dbf130d5668a021dce3f94118455ff5ec952061c30afc9ce7" + }, + "vcruntime140_1.dll": { + "bytes": 50112, + "sha256": "a7146c08f89fe5b04541ab507cdb59ff7b44534d4ba3c668a426c6450a03434e" + }, + "vcruntime140_threads.dll": { + "bytes": 38840, + "sha256": "40f39e8cee5f5a531b4010e18b807e8fe265b908dc58f959ecb7ebc6ea6cb11a" + } + } +} diff --git a/experiments/shell/results/2026-10-29-qt-unicode-input/final-resize-runtime.json b/experiments/shell/results/2026-10-29-qt-unicode-input/final-resize-runtime.json new file mode 100644 index 00000000..5cb6a4eb --- /dev/null +++ b/experiments/shell/results/2026-10-29-qt-unicode-input/final-resize-runtime.json @@ -0,0 +1,165 @@ +{ + "schema": "termforge-windows-runtime-v1", + "executable": "qt_cpp_resize_smoke.exe", + "architecture": "windows-x64", + "msvc_runtime_version": "14.51.36247.0", + "msvc_runtime_manifest_sha256": "154176c8b4ae6dd57fce453e2c2002ff484344b39451fa9880b07db4794f3758", + "files": { + "D3Dcompiler_47.dll": { + "bytes": 4669440, + "sha256": "0f89c19165fd83a05e39bea601a06007e6a8814d55e2adf913902926231f843e" + }, + "LICENSE-MICROSOFT-VC-REDIST.docx": { + "bytes": 39553, + "sha256": "08651651a7602fc7c0e2763de0fde1ff9f868df2780597cd1775ee9d6441c783" + }, + "NOTICE.txt": { + "bytes": 563, + "sha256": "510781c94971ea19adae55d2c42207d942b0646deee29d5c401f189cc25d415f" + }, + "Qt6Core.dll": { + "bytes": 10250040, + "sha256": "b712b4754588e89f855dc0aa087d6304b2fd21b0e0639d1cdeb9cb969bd5fb72" + }, + "Qt6Gui.dll": { + "bytes": 9514296, + "sha256": "03ba47a2025036a04f5548bce242788ac21edf551e78d5b42e1b87beb799904f" + }, + "Qt6Network.dll": { + "bytes": 1773880, + "sha256": "203f8ddf83c015ce56752ebe7a1986472be7dbb95c20e13ac8e1d741d9ac93d1" + }, + "Qt6Test.dll": { + "bytes": 380216, + "sha256": "9d0473552b332aa378c60da05af97d741fcce06b0fc4dd68e36d885015eb5187" + }, + "Qt6Widgets.dll": { + "bytes": 6577464, + "sha256": "61ddd0316a731b8dbabaf2924cd0d47b756461e37329b4f535902cf450416127" + }, + "assets/fonts/OFL.txt": { + "bytes": 4395, + "sha256": "50ab8dd54680d3473f649c9db86fece88434d097c7834475c1c72d2f8c429215" + }, + "assets/fonts/README.md": { + "bytes": 3091, + "sha256": "760a714ad011733e0b93f3c25298190ff5fa493a9e40ad38d347922a355d538f" + }, + "assets/fonts/RobotoMono-Regular.ttf": { + "bytes": 125748, + "sha256": "af0bff7599c3df3831755c16e39b3c496df74b8c8d8a1161b14dc8461be17cb4" + }, + "concrt140.dll": { + "bytes": 374200, + "sha256": "54716f0738af891f283d213b5c8d11b25896bb8ee3097d301eae718560cf974e" + }, + "conpty-backend/LICENSE": { + "bytes": 1095, + "sha256": "ad0cf28f3381ca9bb0bf101d127402d44c17bfa0991e1a00bff7ae6679e9dada" + }, + "conpty-backend/OpenConsole.exe": { + "bytes": 1089376, + "sha256": "3d66b23d0a71bb8eed2b77edc8b9df9bf54ce6c8fb74c863a30e760997f80586" + }, + "conpty-backend/conpty.dll": { + "bytes": 119608, + "sha256": "feeef341d891643c62d30b6b07800bc70f0bc148f44cb8c3bee84aa557ae805a" + }, + "conpty-backend/manifest.json": { + "bytes": 1467, + "sha256": "e75e522a3355c0a66c6d60cafa02413a83d8757262747f650b77c959c09ed439" + }, + "dxcompiler.dll": { + "bytes": 14019128, + "sha256": "2bc16dc5ec85315f89e2f10a482d46dde94a65d6b2d7077f4c49c77e10222acc" + }, + "dxil.dll": { + "bytes": 1502760, + "sha256": "0e90c12c1c6aa1e09d025322a815f9dd80da7d1e331f1df585c7fcbbf44b40d3" + }, + "generic/qtuiotouchplugin.dll": { + "bytes": 102712, + "sha256": "69498d05870aa583c31159f9350e3d8a598b895f6525de7b6fa382395ad27b9d" + }, + "icuuc.dll": { + "bytes": 36864, + "sha256": "59d134ddc15ae0594f0a328979f8201662fc82634ddb7c537b156e7cf871f5eb" + }, + "imageformats/qgif.dll": { + "bytes": 47928, + "sha256": "ded464ef782f41862f391bc5b21c54d58f6aee670fc9f10a2d909ae5b0ba09a7" + }, + "imageformats/qico.dll": { + "bytes": 45880, + "sha256": "0c2abdf95d778bfed9863b8be817dc6cffe2dacdd1a85b2117b4b4ccb1a08e66" + }, + "imageformats/qjpeg.dll": { + "bytes": 577848, + "sha256": "cd965015cce4638361d7bc01193d92a041a227d46d0759ec25d2a1a8608a6b40" + }, + "msvcp140.dll": { + "bytes": 643512, + "sha256": "7c26614e1d733892c2deac7e245ce115504b1d80592dd0a01b08e3e5a55f89ca" + }, + "msvcp140_1.dll": { + "bytes": 35768, + "sha256": "206c931bf90fdad8816de3b5e2ef80b2bcaa9406c89ecc05fe6fddffe251e982" + }, + "msvcp140_2.dll": { + "bytes": 274872, + "sha256": "d50d7883f20d1dc6191768d3746f52dd9cac89c346ffaed5be1f110c2f34a838" + }, + "msvcp140_atomic_wait.dll": { + "bytes": 57792, + "sha256": "3d0cbfaa1bf3eecf5a3f4491d2960ee803cb994f30292c6adc4a07c498f60e2b" + }, + "msvcp140_codecvt_ids.dll": { + "bytes": 31160, + "sha256": "8a65c7596ef2e6938731f5a1058e7e40145b6d97967cc649231a076b9a608d78" + }, + "networkinformation/qnetworklistmanager.dll": { + "bytes": 70968, + "sha256": "4bc26d5b613c507634fd10f07fe031fcbd4f02157d23b9eb1407de4dd6da8278" + }, + "platforms/qoffscreen.dll": { + "bytes": 113976, + "sha256": "a35f87f597b91121cfc0ef39dcb0a5e7ad2876850fbfaf6377edf80eaa067bfd" + }, + "platforms/qwindows.dll": { + "bytes": 997688, + "sha256": "cafd5bfdef3d89d176ee26725c8d4759fd6cb007645b645a4cbf6386364a3f36" + }, + "qt_cpp_resize_smoke.exe": { + "bytes": 219648, + "sha256": "b73e2157963c759a06bf1e600f4b198f46748ad535542353cdef46fbc0d380e8" + }, + "styles/qmodernwindowsstyle.dll": { + "bytes": 221496, + "sha256": "c279e7a97b21fcd7acdfde88cacb8baa119f563282e09c241acb3edee2bc9bbf" + }, + "tls/qcertonlybackend.dll": { + "bytes": 103224, + "sha256": "99d00b37bb2835c539366e4018a58e0ddad21e1c648a3f44f56335a0c1d34765" + }, + "tls/qschannelbackend.dll": { + "bytes": 262968, + "sha256": "b7035c3689b93f55b48112c3e85cf4b8550fefde3f7a044df2784c29330e6940" + }, + "vccorlib140.dll": { + "bytes": 350648, + "sha256": "09f93d5ff96ae09767f3e1af3cdd43a5a58f2598654fc63089429bb6f9f4737f" + }, + "vcruntime140.dll": { + "bytes": 178616, + "sha256": "d1f4225df2cd877dbf130d5668a021dce3f94118455ff5ec952061c30afc9ce7" + }, + "vcruntime140_1.dll": { + "bytes": 50112, + "sha256": "a7146c08f89fe5b04541ab507cdb59ff7b44534d4ba3c668a426c6450a03434e" + }, + "vcruntime140_threads.dll": { + "bytes": 38840, + "sha256": "40f39e8cee5f5a531b4010e18b807e8fe265b908dc58f959ecb7ebc6ea6cb11a" + } + } +} diff --git a/experiments/shell/results/2026-10-29-qt-unicode-input/final-shell-runtime.json b/experiments/shell/results/2026-10-29-qt-unicode-input/final-shell-runtime.json new file mode 100644 index 00000000..0deea328 --- /dev/null +++ b/experiments/shell/results/2026-10-29-qt-unicode-input/final-shell-runtime.json @@ -0,0 +1,165 @@ +{ + "schema": "termforge-windows-runtime-v1", + "executable": "qt_cpp_shell_smoke.exe", + "architecture": "windows-x64", + "msvc_runtime_version": "14.51.36247.0", + "msvc_runtime_manifest_sha256": "154176c8b4ae6dd57fce453e2c2002ff484344b39451fa9880b07db4794f3758", + "files": { + "D3Dcompiler_47.dll": { + "bytes": 4669440, + "sha256": "0f89c19165fd83a05e39bea601a06007e6a8814d55e2adf913902926231f843e" + }, + "LICENSE-MICROSOFT-VC-REDIST.docx": { + "bytes": 39553, + "sha256": "08651651a7602fc7c0e2763de0fde1ff9f868df2780597cd1775ee9d6441c783" + }, + "NOTICE.txt": { + "bytes": 563, + "sha256": "510781c94971ea19adae55d2c42207d942b0646deee29d5c401f189cc25d415f" + }, + "Qt6Core.dll": { + "bytes": 10250040, + "sha256": "b712b4754588e89f855dc0aa087d6304b2fd21b0e0639d1cdeb9cb969bd5fb72" + }, + "Qt6Gui.dll": { + "bytes": 9514296, + "sha256": "03ba47a2025036a04f5548bce242788ac21edf551e78d5b42e1b87beb799904f" + }, + "Qt6Network.dll": { + "bytes": 1773880, + "sha256": "203f8ddf83c015ce56752ebe7a1986472be7dbb95c20e13ac8e1d741d9ac93d1" + }, + "Qt6Test.dll": { + "bytes": 380216, + "sha256": "9d0473552b332aa378c60da05af97d741fcce06b0fc4dd68e36d885015eb5187" + }, + "Qt6Widgets.dll": { + "bytes": 6577464, + "sha256": "61ddd0316a731b8dbabaf2924cd0d47b756461e37329b4f535902cf450416127" + }, + "assets/fonts/OFL.txt": { + "bytes": 4395, + "sha256": "50ab8dd54680d3473f649c9db86fece88434d097c7834475c1c72d2f8c429215" + }, + "assets/fonts/README.md": { + "bytes": 3091, + "sha256": "760a714ad011733e0b93f3c25298190ff5fa493a9e40ad38d347922a355d538f" + }, + "assets/fonts/RobotoMono-Regular.ttf": { + "bytes": 125748, + "sha256": "af0bff7599c3df3831755c16e39b3c496df74b8c8d8a1161b14dc8461be17cb4" + }, + "concrt140.dll": { + "bytes": 374200, + "sha256": "54716f0738af891f283d213b5c8d11b25896bb8ee3097d301eae718560cf974e" + }, + "conpty-backend/LICENSE": { + "bytes": 1095, + "sha256": "ad0cf28f3381ca9bb0bf101d127402d44c17bfa0991e1a00bff7ae6679e9dada" + }, + "conpty-backend/OpenConsole.exe": { + "bytes": 1089376, + "sha256": "3d66b23d0a71bb8eed2b77edc8b9df9bf54ce6c8fb74c863a30e760997f80586" + }, + "conpty-backend/conpty.dll": { + "bytes": 119608, + "sha256": "feeef341d891643c62d30b6b07800bc70f0bc148f44cb8c3bee84aa557ae805a" + }, + "conpty-backend/manifest.json": { + "bytes": 1467, + "sha256": "e75e522a3355c0a66c6d60cafa02413a83d8757262747f650b77c959c09ed439" + }, + "dxcompiler.dll": { + "bytes": 14019128, + "sha256": "2bc16dc5ec85315f89e2f10a482d46dde94a65d6b2d7077f4c49c77e10222acc" + }, + "dxil.dll": { + "bytes": 1502760, + "sha256": "0e90c12c1c6aa1e09d025322a815f9dd80da7d1e331f1df585c7fcbbf44b40d3" + }, + "generic/qtuiotouchplugin.dll": { + "bytes": 102712, + "sha256": "69498d05870aa583c31159f9350e3d8a598b895f6525de7b6fa382395ad27b9d" + }, + "icuuc.dll": { + "bytes": 36864, + "sha256": "59d134ddc15ae0594f0a328979f8201662fc82634ddb7c537b156e7cf871f5eb" + }, + "imageformats/qgif.dll": { + "bytes": 47928, + "sha256": "ded464ef782f41862f391bc5b21c54d58f6aee670fc9f10a2d909ae5b0ba09a7" + }, + "imageformats/qico.dll": { + "bytes": 45880, + "sha256": "0c2abdf95d778bfed9863b8be817dc6cffe2dacdd1a85b2117b4b4ccb1a08e66" + }, + "imageformats/qjpeg.dll": { + "bytes": 577848, + "sha256": "cd965015cce4638361d7bc01193d92a041a227d46d0759ec25d2a1a8608a6b40" + }, + "msvcp140.dll": { + "bytes": 643512, + "sha256": "7c26614e1d733892c2deac7e245ce115504b1d80592dd0a01b08e3e5a55f89ca" + }, + "msvcp140_1.dll": { + "bytes": 35768, + "sha256": "206c931bf90fdad8816de3b5e2ef80b2bcaa9406c89ecc05fe6fddffe251e982" + }, + "msvcp140_2.dll": { + "bytes": 274872, + "sha256": "d50d7883f20d1dc6191768d3746f52dd9cac89c346ffaed5be1f110c2f34a838" + }, + "msvcp140_atomic_wait.dll": { + "bytes": 57792, + "sha256": "3d0cbfaa1bf3eecf5a3f4491d2960ee803cb994f30292c6adc4a07c498f60e2b" + }, + "msvcp140_codecvt_ids.dll": { + "bytes": 31160, + "sha256": "8a65c7596ef2e6938731f5a1058e7e40145b6d97967cc649231a076b9a608d78" + }, + "networkinformation/qnetworklistmanager.dll": { + "bytes": 70968, + "sha256": "4bc26d5b613c507634fd10f07fe031fcbd4f02157d23b9eb1407de4dd6da8278" + }, + "platforms/qoffscreen.dll": { + "bytes": 113976, + "sha256": "a35f87f597b91121cfc0ef39dcb0a5e7ad2876850fbfaf6377edf80eaa067bfd" + }, + "platforms/qwindows.dll": { + "bytes": 997688, + "sha256": "cafd5bfdef3d89d176ee26725c8d4759fd6cb007645b645a4cbf6386364a3f36" + }, + "qt_cpp_shell_smoke.exe": { + "bytes": 219648, + "sha256": "0d3903717dc7195127ce7f711ad23bc45b6689498235edee7dfca82baf767eca" + }, + "styles/qmodernwindowsstyle.dll": { + "bytes": 221496, + "sha256": "c279e7a97b21fcd7acdfde88cacb8baa119f563282e09c241acb3edee2bc9bbf" + }, + "tls/qcertonlybackend.dll": { + "bytes": 103224, + "sha256": "99d00b37bb2835c539366e4018a58e0ddad21e1c648a3f44f56335a0c1d34765" + }, + "tls/qschannelbackend.dll": { + "bytes": 262968, + "sha256": "b7035c3689b93f55b48112c3e85cf4b8550fefde3f7a044df2784c29330e6940" + }, + "vccorlib140.dll": { + "bytes": 350648, + "sha256": "09f93d5ff96ae09767f3e1af3cdd43a5a58f2598654fc63089429bb6f9f4737f" + }, + "vcruntime140.dll": { + "bytes": 178616, + "sha256": "d1f4225df2cd877dbf130d5668a021dce3f94118455ff5ec952061c30afc9ce7" + }, + "vcruntime140_1.dll": { + "bytes": 50112, + "sha256": "a7146c08f89fe5b04541ab507cdb59ff7b44534d4ba3c668a426c6450a03434e" + }, + "vcruntime140_threads.dll": { + "bytes": 38840, + "sha256": "40f39e8cee5f5a531b4010e18b807e8fe265b908dc58f959ecb7ebc6ea6cb11a" + } + } +} diff --git a/experiments/shell/results/2026-10-29-qt-unicode-input/validation.json b/experiments/shell/results/2026-10-29-qt-unicode-input/validation.json new file mode 100644 index 00000000..4e26fadf --- /dev/null +++ b/experiments/shell/results/2026-10-29-qt-unicode-input/validation.json @@ -0,0 +1,439 @@ +{ + "schema": "termforge-qt-unicode-final-validation-v1", + "issue": 147, + "execution_source_commit": "2b6d988914b04a2a05520f0b67c08c30b6e2b43b", + "execution_source_tree": "46e358272e6f1c99ef5d65ffcb007a3ce41b1ac9", + "base_main": "05f0065f4eb19ce5a76065d18f2247bcc731e285", + "configuration": { + "architecture": "windows-x64", + "build": "Release", + "qt_shell": true, + "candidate_adapter": false, + "qt_platform": "offscreen", + "formal_qt_ctest_entries": 3, + "legacy_or_independent_ctest_entries": 58 + }, + "environment": { + "windows_version": "10.0.26200", + "windows_build": "26200", + "windows_ubr": 9711, + "python_version_number": "3.12.10", + "python_exe_sha256": "0b471133e110cfb53a061cad528ce8e517d7b9ac41a0a396c39ad795a487fc14", + "compiler": "MSVC", + "compiler_version": "19.42.34433.0", + "compiler_exe_identity": { + "bytes": 862832, + "sha256": "42fd88579adc0e2418a0a71ec9adcfcf677d39ff2724c9d6ac9d901264812bf2" + }, + "host_isolated_for_performance": false + }, + "dependencies": { + "qt_version": "6.10.3", + "qt_sdk_identity": { + "files": 4358, + "tree_sha256": "a35ab6fe76b62e4d9bd6d3c6b0d476c63583070cea5c9118ef5bb02042ffe266" + }, + "conpty_version": "1.25.260930003", + "msvc_runtime_version": "14.51.36247.0" + }, + "sources": { + "CMakeLists.txt": { + "bytes": 27190, + "sha256": "c5d295a4cb74693d43cff471ec24537fdbaf47fdde65bc026a61bdbf15189a35" + }, + ".github/workflows/qt-cpp.yml": { + "bytes": 3728, + "sha256": "de9cb0fc494050498678ea8202f7eb8f436a2ee4798757410bb405c8fc79493e" + }, + "src/qt/unicode_input_test.cpp": { + "bytes": 17771, + "sha256": "5ac07aa9661f10c128b56f44bd9059daa126bef06f62d8bf5a7f592d59115b41" + }, + "src/qt/terminal_widget.hpp": { + "bytes": 9125, + "sha256": "282d6c6fa94f6d268749ee839c30e6faad27376616bdf738790427dcc5d919cc" + }, + "src/qt/terminal_session.hpp": { + "bytes": 14100, + "sha256": "0000e06e91b042d1841c31864ab9e464008e0d2a024fdbcaa697b0e48d30555e" + }, + "src/qt/session_tabs.hpp": { + "bytes": 7007, + "sha256": "df3634a37f07a73f92033f29392a7624275a2ce3e4db293dcce01408b9148b07" + }, + "src/qt/run_smoke.py": { + "bytes": 4287, + "sha256": "25f4d6adec4e9bfad77698300c44d9162511ac1ebb722993d5a52cdb8a863983" + }, + "src/qt/main.cpp": { + "bytes": 1137, + "sha256": "4af7c6722ba6847d90d3d9938849aa3b69a8de749c043a278c8725dc46011796" + }, + "src/qt/shell_smoke.cpp": { + "bytes": 25287, + "sha256": "86022250a4c5d50824e112d6748c56bb549cb055c0737288f8a3c88f3733c8d0" + }, + "src/qt/resize_smoke.cpp": { + "bytes": 30667, + "sha256": "d98b438723a88753b9f084abac941cf2683e90b4f71fd771fa01b6293166983a" + }, + "src/qt/resize_fixture.hpp": { + "bytes": 10084, + "sha256": "a855779e06b45b2c68daf413108172ee2d51bcb75fd55911263505d7dc6be300" + }, + "experiments/core/terminal.hpp": { + "bytes": 12270, + "sha256": "ed3c030611a6df38df26057a8c2834f2808cd05306e73aead79927b1f76fe5ca" + }, + "experiments/core/screen.hpp": { + "bytes": 17242, + "sha256": "04c19f09ebc0c468efbef8dd4c9da8f4bcd44e94000a87c56239756c0df1b474" + }, + "experiments/core/unicode_width.hpp": { + "bytes": 32445, + "sha256": "d48bf60090a9c6a4263bb46ac587b13814b52c0dbe9d2649cee20e832a1e1926" + }, + "experiments/shell/keyboard.hpp": { + "bytes": 3979, + "sha256": "0790ddd0a3b755de5fc22a7bff5c3421b91c19b78dcd7bc0af9de22fa471c3b3" + }, + "experiments/shell/queues.hpp": { + "bytes": 7224, + "sha256": "9087944a7749176789a489bce0522c55764461342b59c138611743b6947845b6" + }, + "experiments/shell/input_client.cpp": { + "bytes": 11706, + "sha256": "67ebb343978ac6822f8feaafa7510f9ee325737fce91b3d2aed8385deea6a969" + }, + "experiments/conpty/session.hpp": { + "bytes": 11072, + "sha256": "7b3934715948a05c1996bcccb02ef2eb58ea11baf26bf97c2e728a52d58becda" + }, + "experiments/conpty/backend.hpp": { + "bytes": 6815, + "sha256": "5ee3ab95dc4b9aefce23ff7900cda2ecdd9cd09d1b1044074de2f5e572c31489" + }, + "third_party/qt/manifest.json": { + "bytes": 5806, + "sha256": "2796294f254f7fe0370be99d2de8f7745734c7352ac2648b1f8d9a8fab5ad2cd" + }, + "third_party/conpty/manifest.json": { + "bytes": 1467, + "sha256": "e75e522a3355c0a66c6d60cafa02413a83d8757262747f650b77c959c09ed439" + }, + "third_party/msvc_runtime/manifest.json": { + "bytes": 4123, + "sha256": "154176c8b4ae6dd57fce453e2c2002ff484344b39451fa9880b07db4794f3758" + } + }, + "frozen_inputs": { + "files": 7397, + "group_counts": { + "source": 2817, + "qt_sdk": 4358, + "conpty_input": 2, + "crt_input": 12, + "product": 38, + "qt_shell": 40, + "qt_resize": 40, + "qt_unicode": 41, + "legacy_bin": 49 + }, + "snapshot": { + "bytes": 1407270, + "sha256": "3e2a25dd1a91580437f23c31889ad5917ea4b96c0ddd4615605e3c590935ce8b" + }, + "post_audit": { + "bytes": 21799, + "sha256": "aea7a0fa4758506ee998ed3370fac67546fa837e75a2322380c78cb49e6aef80" + }, + "changed_files": 0, + "group_tree_sha256": { + "source": "3286f59386f06a35a43a93b0133e16bf3876303fba4cfff750b8e8e85340c9eb", + "qt_sdk": "b3d0eba18ace779669332e81038782d4aa8f181bfab3f4b0281a1f153bdc7384", + "conpty_input": "1ba1fa3c75dbfcc2dd57a24b37b53313113bc5556ee804f93726c8532208c13c", + "crt_input": "164755e4c347ecfe5d25ce8fe82f313e647b4bde8857a845396d78d04a5c2a39", + "product": "834a2639b21e8c53de839819fbea8800c4485118c90190844943b27f9f92f728", + "qt_shell": "bb58045dcace918440ff7726fa711d9e174444b0e6c59864c5f841093e5f60cf", + "qt_resize": "c1b08834497d6568d1c3c619cb973d15225ff9a9331c020e99878e92d3c0d438", + "qt_unicode": "e49782a4c38ff897cef1b9d1961d57764ad93fa75461c825650055a6facc6816", + "legacy_bin": "d24204dd2a7c58136d67e405c65437206227d219eef351bfa6f1053fd6b380f4" + } + }, + "runtime_manifests": [ + { + "group": "product", + "executable": "termforge_qt_shell.exe", + "manifest": "final-product-runtime.json", + "manifest_identity": { + "bytes": 5576, + "sha256": "afdf04c2e07e901f10b76d7fe3fdde4ec7763b43e606b037e59cfa1e66fe2362" + }, + "files_excluding_manifest": 37 + }, + { + "group": "qt_shell", + "executable": "qt_cpp_shell_smoke.exe", + "manifest": "final-shell-runtime.json", + "manifest_identity": { + "bytes": 5857, + "sha256": "5be718f5183612269412498ac00940f74b22009cfdf4201ae7208d4f478ea9eb" + }, + "files_excluding_manifest": 39 + }, + { + "group": "qt_resize", + "executable": "qt_cpp_resize_smoke.exe", + "manifest": "final-resize-runtime.json", + "manifest_identity": { + "bytes": 5859, + "sha256": "adcfb69b99d98250675a063ea096394a688a4f1ef3b77e567a07a4bba7f61037" + }, + "files_excluding_manifest": 39 + }, + { + "group": "qt_unicode", + "executable": "qt_unicode_input_test.exe", + "manifest": "final-input-runtime.json", + "manifest_identity": { + "bytes": 6007, + "sha256": "efee180b19114bd28c3b36947edff1c7c43e3527832c1da75718ee8a407f5910" + }, + "files_excluding_manifest": 40 + } + ], + "focused": { + "ctest_passed": 3, + "ctest_failed": 0, + "real_seconds": 13.53, + "log": { + "bytes": 26677, + "sha256": "5c3dadcf7271b086b772ce4f9dac030396cdb3302815fcc19a70920cb61aac4b" + } + }, + "full": { + "maximum_attempts": 1, + "actual_attempts": 1, + "exit_code": 0, + "ctest_passed": 61, + "ctest_failed": 0, + "real_seconds": 307.06, + "internal_unittest_skips": [ + { + "test": "setUpClass (test_candidate_compare.CandidateComparisonTests)", + "reason": "optional Alacritty binary is not built; core-candidate workflow runs these checks" + }, + { + "test": "setUpClass (test_capability_matrix.CapabilityMatrixTests)", + "reason": "optional Alacritty binary is not built; core-candidate workflow runs these checks" + }, + { + "test": "setUpClass (test_fuzz_matrix.FuzzMatrixTests)", + "reason": "optional Alacritty binary is not built; core-candidate workflow runs these checks" + }, + { + "test": "setUpClass (test_real_app_matrix.RealApplicationMatrixTests)", + "reason": "optional real application capture dependencies are not built" + }, + { + "test": "test_actual_system_copy_paste_and_shortcuts_twice (experiments.shell.test_actions.ActionWindowTest.test_actual_system_copy_paste_and_shortcuts_twice)", + "reason": "actual system Copy/Paste requires a disposable hosted VM" + }, + { + "test": "test_suspended_target_is_reclaimed_if_job_assignment_fails (test_ui_r1.EvidenceBoundaryTests.test_suspended_target_is_reclaimed_if_job_assignment_fails)", + "reason": "opt-in pinned reference failure injection" + }, + { + "test": "test_mouse_selection_to_real_system_clipboard (experiments.shell.test_shell.ShellTests.test_mouse_selection_to_real_system_clipboard)", + "reason": "system copy UI gate requires a disposable hosted VM" + }, + { + "test": "test_system_clipboard_product_read_errors_cancel_and_close (experiments.shell.test_shell.ShellTests.test_system_clipboard_product_read_errors_cancel_and_close)", + "reason": "product clipboard read gate requires a disposable hosted VM" + }, + { + "test": "test_system_clipboard_shortcut_to_window_confirmation_and_receiver (experiments.shell.test_shell.ShellTests.test_system_clipboard_shortcut_to_window_confirmation_and_receiver)", + "reason": "real system clipboard UI gate requires a disposable hosted VM" + }, + { + "test": "test_owned_clipboard_contention_preserves_write_and_read_failures (experiments.shell.test_selection_bytes.SelectionBytesWindowTest.test_owned_clipboard_contention_preserves_write_and_read_failures)", + "reason": "system CF requires a disposable hosted VM" + }, + { + "test": "test_real_system_copy_shortcuts_for_styled_fixture_twice (experiments.shell.test_selection_bytes.SelectionBytesWindowTest.test_real_system_copy_shortcuts_for_styled_fixture_twice)", + "reason": "system CF requires a disposable hosted VM" + }, + { + "test": "test_real_system_copy_and_edit_context_twice (experiments.shell.test_selection_context.SelectionContextWindowTest.test_real_system_copy_and_edit_context_twice)", + "reason": "real CF/EDIT Copy requires a disposable hosted VM" + }, + { + "test": "test_real_system_clipboard (experiments.shell.test_paste.PasteTests.test_real_system_clipboard)", + "reason": "Private window station OpenClipboard denied (5); system API gate remains unverified. Private evidence path omitted." + } + ], + "qt_supervisors": [ + { + "test": "qt_cpp_shell", + "sample_schema": "termforge-qt-cpp-smoke-v1", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 26, + "terminated": 0 + } + }, + { + "test": "qt_unicode_input", + "sample_schema": "termforge-qt-unicode-input-v1", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 17, + "terminated": 0 + } + }, + { + "test": "qt_cpp_resize", + "sample_schema": "termforge-qt-resize-smoke-v1", + "exit_code": 0, + "timed_out": false, + "job_after": { + "active": 0, + "total": 27, + "terminated": 0 + } + } + ] + }, + "receivers": [ + { + "stage": "focused", + "files": { + "focus-packet.bin": { + "bytes": 1037, + "sha256": "97f22206016f52617da7b671f20fea22d85b36bee4d66c04cf3a174264b8d862" + }, + "late-start.bin": { + "bytes": 4, + "sha256": "9719ab72356050600c22e4b79b4200066150c6bac592a0630b4ba8f07f189080" + }, + "normal-keys.bin": { + "bytes": 19, + "sha256": "aa5b356f76c764945f6dfea4430b774b68201b2095f14d7613832290b0f461e7" + }, + "tab-a.bin": { + "bytes": 9, + "sha256": "fa27960a63a077e4b40f1de30e99f489921da201cd72c2284f5bb26579eb008a" + }, + "tab-b.bin": { + "bytes": 8, + "sha256": "6a324e7924ac17fb25d6444b73b49137d96c23c98ec45069d28a048ca5de1dcf" + }, + "tab-close.bin": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "unicode.bin": { + "bytes": 58, + "sha256": "c3138debbb91d7dc8a74324283da18b89ed1c1a9a52efc8b5caf7e77a92df2ce" + } + }, + "sample": { + "bytes": 1409, + "sha256": "2d4c2678601ecff45995d7c6bad7a0677debcde837a9d549b6172e704285fca4" + } + }, + { + "stage": "full", + "files": { + "focus-packet.bin": { + "bytes": 1037, + "sha256": "97f22206016f52617da7b671f20fea22d85b36bee4d66c04cf3a174264b8d862" + }, + "late-start.bin": { + "bytes": 4, + "sha256": "9719ab72356050600c22e4b79b4200066150c6bac592a0630b4ba8f07f189080" + }, + "normal-keys.bin": { + "bytes": 19, + "sha256": "aa5b356f76c764945f6dfea4430b774b68201b2095f14d7613832290b0f461e7" + }, + "tab-a.bin": { + "bytes": 9, + "sha256": "fa27960a63a077e4b40f1de30e99f489921da201cd72c2284f5bb26579eb008a" + }, + "tab-b.bin": { + "bytes": 8, + "sha256": "6a324e7924ac17fb25d6444b73b49137d96c23c98ec45069d28a048ca5de1dcf" + }, + "tab-close.bin": { + "bytes": 0, + "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "unicode.bin": { + "bytes": 58, + "sha256": "c3138debbb91d7dc8a74324283da18b89ed1c1a9a52efc8b5caf7e77a92df2ce" + } + }, + "sample": { + "bytes": 1409, + "sha256": "2d4c2678601ecff45995d7c6bad7a0677debcde837a9d549b6172e704285fca4" + } + } + ], + "preserved_prior_files": { + "count": 27, + "snapshot": { + "bytes": 4453, + "sha256": "3208a8156d09598c36ea574d794da16badbda903d86411d621010f2a2b6eae12" + }, + "changed_files": 0 + }, + "raw_evidence": { + "02-configure.log": { + "bytes": 1239, + "sha256": "027cb72e74f839ebf045d751db04aeb30575ef6f1a82096b2cf0a9051395efa3" + }, + "03-build.log": { + "bytes": 27829, + "sha256": "b55d2604120657a41ded9d31fb520abbe27be6a2d2f4955ca19ca541c89828b4" + }, + "05-focused-ctest.log": { + "bytes": 26677, + "sha256": "5c3dadcf7271b086b772ce4f9dac030396cdb3302815fcc19a70920cb61aac4b" + }, + "06-full-ctest.log": { + "bytes": 178210, + "sha256": "a0f2d00f69ab9e0dea8c96907f8fed73dcac36aab1c91bcae516e62a4eedeb9d" + }, + "LastTest.log": { + "bytes": 181833, + "sha256": "bdaa671286062d477e86ff982967f73e1438fbf499e8f9d516bc447da5b5a147" + }, + "00-preserved-prior-files.json": { + "bytes": 4453, + "sha256": "3208a8156d09598c36ea574d794da16badbda903d86411d621010f2a2b6eae12" + }, + "01-inputs-before.json": { + "bytes": 1374299, + "sha256": "296e897bd9a11cb07d2bddf708b8d9864968627210f84e7ebf64cf7de191bd21" + }, + "04-run-inputs-before.json": { + "bytes": 1407270, + "sha256": "3e2a25dd1a91580437f23c31889ad5917ea4b96c0ddd4615605e3c590935ce8b" + } + }, + "limitations": [ + "Public synthetic Qt events and an independent ReadConsoleW receiver, not physical keyboard or OS IME.", + "No native windows-plugin or VM execution in this final slice; previous resize native evidence retains its own source.", + "UTF-8 receive-byte equality does not verify grapheme width, glyph shaping, fallback or all application input modes.", + "Six receiver sessions naturally finish; the separate closing target is cancelled. Empty cancelled bytes are not a natural-exit proof.", + "Qt total-queue saturation is not forced; actual Qt packet-limit rejection and existing independent InputQueue tests remain separate.", + "The 58 legacy/independent entries and 13 internal skips do not establish complete Qt, clipboard or R2 acceptance.", + "Old CI unknowns #80/#81/#144/#145 remain open; this single green run does not establish their root causes.", + "Timing is diagnostic test duration, not a performance baseline or approved tolerance. presented_done remains null." + ] +} diff --git a/src/qt/README.md b/src/qt/README.md index ec46a43e..517b0921 100644 --- a/src/qt/README.md +++ b/src/qt/README.md @@ -1,6 +1,6 @@ # C++ / Qt 本地会话迁移入口 -按 [ADR-0008](../../docs/adr/0008-cpp-core-qt-widgets.md)采用自主 C++ 核心和 Qt Widgets,Windows 进程由[固定微软 ConPTY](../../third_party/conpty/README.md)承载。当前入口提供默认 80×24、随窗口可用区域变化的本地 cmd 标签、基本 ASCII 按键、16 色绘制、启动错误与关闭回收;不是完整 R2 或 WindTerm 界面。 +按 [ADR-0008](../../docs/adr/0008-cpp-core-qt-widgets.md)采用自主 C++ 核心和 Qt Widgets,Windows 进程由[固定微软 ConPTY](../../third_party/conpty/README.md)承载。当前入口提供默认 80×24、随窗口可用区域变化的本地 cmd 标签、基础 ASCII 与限定 Unicode 按键文本传输、16 色绘制、启动错误与关闭回收;不是完整 R2 或 WindTerm 界面。 现有 C++ 核心的[能力限制](../../experiments/core/README.md)继续有效。Qt 尚未迁移原 Win32 的完整工作区、分屏、历史浏览、选择/复制、Find、完整键盘/IME 或会话恢复;完整 resize/reflow 仍需按目标调查和实现,也不能继承 Alacritty 分支的通过结果。 @@ -21,9 +21,9 @@ cmake -S . -B build/qt-cpp -G "Visual Studio 17 2022" -A x64 ` -DQt6_DIR="/lib/cmake/Qt6" ` -DTERMFORGE_CONPTY_BACKEND_DIR="" ` -DTERMFORGE_MSVC_RUNTIME_DIR="" -cmake --build build/qt-cpp --config Release --target termforge_qt_shell qt_cpp_shell_smoke qt_cpp_resize_smoke +cmake --build build/qt-cpp --config Release --target termforge_qt_shell qt_cpp_shell_smoke qt_cpp_resize_smoke qt_unicode_input_test build/qt-cpp/qt-bin/Release/termforge_qt_shell.exe -ctest --test-dir build/qt-cpp -C Release -R '^qt_cpp_(shell|resize)$' --output-on-failure +ctest --test-dir build/qt-cpp -C Release -R '^(qt_cpp_(shell|resize)|qt_unicode_input)$' --output-on-failure ``` 固定 ConPTY/CRT 输入缺失或篡改时构建明确失败;实际工具集更新到固定 CRT 之外时须重新审查依赖。产品中的 shell 启动失败保留可关闭的错误窗口。随目录保留 Qt 动态运行库、平台插件、`conpty-backend/`、`assets/fonts/`、十个固定 CRT DLL 及其原许可/NOTICE;每次构建重新校验并部署 CRT,不运行安装器、不修改 System32。最终目录用 `tools/msvc_runtime.py manifest` 记录全部文件。当前只是本地构建输出,Qt/第三方的完整分发审计及产品打包尚未完成。 @@ -40,6 +40,8 @@ Parser/Screen 仅在 Qt 线程修改;ConPTY reader 向有界输出队列写入 字面网格测试使用可见父窗口内的终端子区域;独立顶层测试保留一列请求并根据原生事件处理后的实际可用区域核对 core 和 child,记录平台调整差异。大 child 的资源上限验证保留 owner 裁剪范围。旧 678 离屏通过后的原生极窄失败、新夹具离屏验证及[0ff 冻结包的一次隔离原生成功](../../experiments/shell/results/2026-10-29-qt-resize/native-validation.json)分别保留;后者只观察到 DPI 96,不代替不同 DPI 或完整产品验收。 -该测试使用 Qt offscreen 平台和本项目独立取得的 OFL Roboto Mono 字体;没有物理键盘、实际不同 DPI、Windows 原生窗口上屏或用户呈现时间验收,`presented_done=null`。两会话隔离不代表八会话、IME 或完整工作区验收,测试耗时不是终端性能指标。 +`qt_unicode_input` 通过公开 Qt 文本/焦点/隐藏/标签动作,将 UTF-16 整事件及跨事件代理项经每目标 `KeyboardEncoder` 交给固定 ConPTY,独立 `ReadConsoleW` 接收端核对中文、NFD、非BMP、固定ZWJ/keycap、NUL与原有正常模式按键的精确UTF-8。整包1024B上限与拒绝反馈、失焦/关闭/不可用目标的状态隔离有[红绿与联合源码验证](../../experiments/shell/results/2026-10-29-qt-unicode-input/README.md);未配对单元及焦点清理是当前TermForge策略,尚无原版对照。此项不启用 Qt IME,也不证明相应字形、字素宽度或原始物理按键已验收。 + +以上离屏测试使用 Qt offscreen 平台和本项目独立取得的 OFL Roboto Mono 字体;没有物理键盘、实际不同 DPI、Windows 原生窗口上屏或用户呈现时间验收,`presented_done=null`。两会话隔离不代表八会话、IME 或完整工作区验收,测试耗时不是终端性能指标。 构建开关默认关闭只是从旧原型向新入口迁移的状态;正式路线已确认,旧构建保留为回归对照。Qt 文字与图形资源均独立取得,不使用 WindTerm 的专有字体、图标或主题。