diff --git a/src/BlockchainBackend.cpp b/src/BlockchainBackend.cpp index 05d7744..dba6cb5 100644 --- a/src/BlockchainBackend.cpp +++ b/src/BlockchainBackend.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -205,9 +206,28 @@ void BlockchainBackend::refreshAccounts() const LogosResult result = toLogosResult(m_blockchainClient->invokeRemoteMethod( BLOCKCHAIN_MODULE_NAME, "wallet_get_known_addresses")); + if (!result.success) { + qWarning() << "refreshAccounts: failed:" << result.error.toString(); + return; + } + + // The SDK marshals the JSON array into a QVariantList; rely on toList() + // rather than canConvert() (which is unreliable for a + // QVariantList under Qt6), and fall back to toStringList() for the rare + // case where the value already arrives as a QStringList. QStringList list; - if (result.success && result.value.canConvert()) + const QVariantList items = result.value.toList(); + if (!items.isEmpty()) { + for (const QVariant& item : items) { + const QString addr = item.toString(); + if (!addr.isEmpty()) + list << addr; + } + } else { list = result.value.toStringList(); + } + + qDebug() << "refreshAccounts: loaded" << list.size() << "addresses"; m_accountsModel->setAddresses(list); @@ -346,6 +366,14 @@ void BlockchainBackend::clearLogs() void BlockchainBackend::copyToClipboard(QString text) { - if (QGuiApplication::clipboard()) - QGuiApplication::clipboard()->setText(text); + // The backend runs in a non-GUI ViewModuleHost subprocess, where there is + // no QGuiApplication and accessing the clipboard segfaults. Clipboard is + // handled QML-side (see BlockchainView.copyText); guard here so any stray + // call is a no-op rather than a crash. + if (!qobject_cast(QCoreApplication::instance())) { + qWarning() << "copyToClipboard: no GUI application; ignoring"; + return; + } + if (QClipboard* clipboard = QGuiApplication::clipboard()) + clipboard->setText(text); } diff --git a/src/qml/BlockchainView.qml b/src/qml/BlockchainView.qml index cca4ace..695e975 100644 --- a/src/qml/BlockchainView.qml +++ b/src/qml/BlockchainView.qml @@ -40,6 +40,23 @@ Rectangle { readonly property var accountsModel: logos.model("blockchain_ui", "accounts") readonly property var logModel: logos.model("blockchain_ui", "logs") + // Clipboard must be handled here in the UI-host (GUI) process. The backend + // .rep source runs in a separate, non-GUI ViewModuleHost subprocess where + // QGuiApplication::clipboard() segfaults (process exits with code 11), so + // we copy from QML via a hidden TextEdit instead of calling the backend. + function copyText(text) { + clipboardHelper.text = text || "" + clipboardHelper.selectAll() + clipboardHelper.copy() + clipboardHelper.deselect() + clipboardHelper.text = "" + } + + TextEdit { + id: clipboardHelper + visible: false + } + QtObject { id: _d function getStatusString(s) { @@ -228,7 +245,7 @@ Rectangle { ) } onCopyToClipboard: (text) => { - if (root.backend) root.backend.copyToClipboard(text) + root.copyText(text) } onTransferRequested: function(fromKeyHex, toKeyHex, amount) { if (!root.backend) return @@ -246,6 +263,7 @@ Rectangle { function(error) { walletView.setLeaderClaimResult("Error: " + error) } ) } + onRefreshAccountsRequested: if (root.backend) root.backend.refreshAccounts() } Item { @@ -260,7 +278,7 @@ Rectangle { logModel: root.logModel onClearRequested: if (root.backend) root.backend.clearLogs() onCopyToClipboard: (text) => { - if (root.backend) root.backend.copyToClipboard(text) + root.copyText(text) } } } @@ -291,7 +309,7 @@ Rectangle { ) } onCopyToClipboard: (text) => { - if (root.backend) root.backend.copyToClipboard(text) + root.copyText(text) } } } diff --git a/src/qml/views/LogsView.qml b/src/qml/views/LogsView.qml index 3ed4d18..f0a4372 100644 --- a/src/qml/views/LogsView.qml +++ b/src/qml/views/LogsView.qml @@ -70,7 +70,9 @@ Control { delegate: ItemDelegate{ width: ListView.view.width contentItem: LogosText { - text: model.text + // The remoted log model can briefly report an undefined + // "text" role while the replica syncs — coerce to "". + text: model.text || "" font.pixelSize: Theme.typography.secondaryText wrapMode: Text.Wrap } @@ -78,7 +80,7 @@ Control { color: hovered ? Theme.palette.background: "transparent" radius: 2 } - onClicked: root.copyToClipboard(model.text) + onClicked: root.copyToClipboard(model.text || "") } LogosText { diff --git a/src/qml/views/WalletView.qml b/src/qml/views/WalletView.qml index 8fe2164..345ea9e 100644 --- a/src/qml/views/WalletView.qml +++ b/src/qml/views/WalletView.qml @@ -16,6 +16,7 @@ RowLayout { property string lastBalanceErrorAddress: "" signal getBalanceRequested(string addressHex) + signal refreshAccountsRequested() signal transferRequested(string fromKeyHex, string toKeyHex, string amount) signal claimLeaderRewardsRequested() signal copyToClipboard(string text) @@ -48,10 +49,19 @@ RowLayout { anchors.margins: Theme.spacing.large spacing: Theme.spacing.large - LogosText { - text: qsTr("Accounts") - font.pixelSize: Theme.typography.secondaryText - font.bold: true + RowLayout { + Layout.fillWidth: true + LogosText { + text: qsTr("Accounts") + font.pixelSize: Theme.typography.secondaryText + font.bold: true + } + Item { Layout.fillWidth: true } + LogosButton { + text: qsTr("Refresh") + padding: Theme.spacing.small + onClicked: root.refreshAccountsRequested() + } } LogosText {