wowlet: pixel-art mining tab — port mining.qml (Qt6) + 25 assets, MiningWidget/MiningBackend hosting QQuickWidget, wired to embedded-node solo miner (hashrate/sync feedback); adds Qt6 Quick

This commit is contained in:
jwinterm
2026-06-18 18:51:05 -04:00
parent ccd53f59af
commit e0c06d6526
40 changed files with 903 additions and 2 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
- name: install dependencies
run: apt -y install curl bzip2 git cmake build-essential ccache libssl-dev libunbound-dev libboost-all-dev libzmq3-dev
libqrencode-dev qt6-base-dev qt6-svg-dev qt6-websockets-dev qt6-multimedia-dev
qt6-wayland-dev libzip-dev libsodium-dev libgcrypt20-dev libx11-xcb-dev
qt6-wayland-dev qt6-declarative-dev libzip-dev libsodium-dev libgcrypt20-dev libx11-xcb-dev
protobuf-compiler libprotobuf-dev libhidapi-dev libzxing-dev libusb-dev
libusb-1.0-0-dev
- name: configure git
+7 -1
View File
@@ -13,6 +13,9 @@ set(QT_COMPONENTS
Svg
WebSockets
SvgWidgets
Quick
Qml
QuickWidgets
)
if (WITH_SCANNER)
@@ -31,7 +34,7 @@ if (CHECK_UPDATES)
add_subdirectory(openpgp)
endif()
qt_add_resources(RESOURCES assets.qrc assets_tor.qrc assets_wownerod.qrc assets_docs.qrc)
qt_add_resources(RESOURCES assets.qrc assets_tor.qrc assets_wownerod.qrc assets_docs.qrc mining.qrc)
# Compile source files (.h/.cpp)
file(GLOB SOURCE_FILES
@@ -287,6 +290,9 @@ target_link_libraries(feather PRIVATE
Qt::Svg
Qt::WebSockets
Qt::SvgWidgets
Qt::Quick
Qt::Qml
Qt::QuickWidgets
Threads::Threads
${QRENCODE_LIBRARY}
${POLYSEED_LIBRARY}
+6
View File
@@ -10,6 +10,8 @@
#include <QThread>
#include <QCheckBox>
#include "widgets/MiningWidget.h"
#include "constants.h"
#include "dialog/AddressCheckerIndexDialog.h"
#include "dialog/BalanceDialog.h"
@@ -248,6 +250,10 @@ void MainWindow::initWidgets() {
ui->tabWidget->setCurrentIndex(this->findTab("Send"));
});
// [Mining] — wowlet: the pixel-art mining tab (mining.qml + embedded-node solo miner)
m_miningWidget = new MiningWidget(m_wallet, this);
ui->tabWidget->addTab(m_miningWidget, "Mining");
// [Notes]
ui->notes->setPlainText(m_wallet->getCacheAttribute("wallet.notes"));
connect(ui->notes, &QPlainTextEdit::textChanged, [this] {
+2
View File
@@ -64,6 +64,7 @@ public:
};
class WindowManager;
class MiningWidget;
class MainWindow : public QMainWindow
{
Q_OBJECT
@@ -225,6 +226,7 @@ private:
WalletUnlockWidget *m_walletUnlockWidget = nullptr;
ContactsWidget *m_contactsWidget = nullptr;
MiningWidget *m_miningWidget = nullptr;
HistoryWidget *m_historyWidget = nullptr;
SendWidget *m_sendWidget = nullptr;
ReceiveWidget *m_receiveWidget = nullptr;
Binary file not shown.
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

+730
View File
@@ -0,0 +1,730 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
Rectangle {
id: root
color: "#181725"
anchors.fill: parent
property variant buffer: []
property int bufferMaxLength: 12
// state: 0:idle 1:startup 2:syncing 3:mining
signal startMining();
signal stopMining();
Component.onCompleted: {
calcAvailableHeightConsoleLines();
}
onHeightChanged: {
calcAvailableHeightConsoleLines();
}
function calcAvailableHeightConsoleLines() {
var max_lines = parseInt(textContainer.height / 20);
if(root.bufferMaxLength != max_lines && max_lines >= 4)
root.bufferMaxLength = max_lines;
}
// width: 980
// height: 480
Column {
FontLoader { id: comicMono; source: "qrc:/fonts/ComicMono.ttf" }
FontLoader { id: comicMonoBold; source: "qrc:/fonts/ComicMono-Bold.ttf" }
}
AnimatedImage {
//visible: mining.daemonMiningState === 0
source: "qrc:/mining/bg1.gif"
fillMode: Image.PreserveAspectCrop
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.top: parent.top
anchors.bottomMargin: 92
anchors.topMargin: 112
}
Image {
source: "qrc:/mining/overlay.png"
fillMode: Image.PreserveAspectCrop
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.top: parent.top
anchors.bottomMargin: 92
anchors.topMargin: 112
smooth: false
}
Image {
id: miningGradient
opacity: 0.0
source: "qrc:/mining/mining_gradient.png"
fillMode: Image.PreserveAspectCrop
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.top: parent.top
anchors.bottomMargin: 92
anchors.topMargin: 112
smooth: false
states: [
State { when: mining.daemonMiningState !== 0;
PropertyChanges { target: miningGradient; opacity: 1.0 }},
State { when: mining.daemonMiningState === 0;
PropertyChanges { target: miningGradient; opacity: 0.0 }}
]
transitions: [ Transition { NumberAnimation { property: "opacity"; duration: 750}} ]
}
ColumnLayout {
width: parent.width
height: parent.height
spacing: 0
RowLayout {
spacing: 0
Layout.fillWidth: true
Layout.preferredHeight: 128
Image {
source: "qrc:/mining/topleft.png"
Layout.preferredWidth: 435
Layout.preferredHeight: 128
smooth: false
// top-left monitors
ColumnLayout {
width: 102
height: 74
anchors.left: parent.left
anchors.leftMargin: 14
anchors.top: parent.top
anchors.topMargin: 42
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.fillHeight: true
Text {
text: "Hashrate"
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 4
font.pointSize: 14
font.family: comicMonoBold.name;
antialiasing: false
color: "#41FF00"
}
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.fillHeight: true
Text {
id: hashRateText
text: "-"
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 4
font.pointSize: 16
font.family: comicMono.name;
antialiasing: false
color: "#41FF00"
}
}
}
ColumnLayout {
width: 102
height: 74
anchors.left: parent.left
anchors.leftMargin: 142
anchors.top: parent.top
anchors.topMargin: 42
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.fillHeight: true
Text {
text: "uptime"
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 4
font.pointSize: 14
font.family: comicMonoBold.name;
antialiasing: false
color: "#41FF00"
}
}
Rectangle {
color: "transparent"
Layout.fillWidth: true
Layout.fillHeight: true
Text {
id: miningUptime
text: "-"
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 4
font.pointSize: 12
font.family: comicMono.name;
antialiasing: false
color: "#41FF00"
}
}
}
AnimatedImage {
visible: mining.daemonMiningState !== 0
source: "qrc:/mining/mining.webp"
fillMode: Image.PreserveAspectCrop
width: 115
height: 86
anchors.left: parent.left
anchors.leftMargin: 263
anchors.top: parent.top
anchors.topMargin: 34
}
}
ColumnLayout {
Layout.fillWidth: true
Layout.preferredHeight: 128
spacing: 0
Image {
source: "qrc:/mining/warning.png"
Layout.fillWidth: true
Layout.preferredHeight: 15
fillMode: Image.TileHorizontally
smooth: false
}
Image {
source: "qrc:/mining/topright_bar.png"
Layout.fillWidth: true
Layout.preferredHeight: 4
fillMode: Image.TileHorizontally
smooth: false
}
RowLayout {
spacing: 0
Layout.fillHeight: true
Layout.preferredHeight: 102
Image {
Layout.preferredWidth: 5
Layout.preferredHeight: parent.height
source: "qrc:/mining/topright_left.png"
smooth: false
}
Image {
Layout.fillWidth: true
Layout.preferredHeight: parent.height
source: "qrc:/mining/topright_middle.png"
fillMode: Image.TileHorizontally
smooth: false
RowLayout {
anchors.top: parent.top
anchors.left: parent.left
anchors.leftMargin: 6
anchors.right: parent.right
anchors.rightMargin: 8
anchors.topMargin: 12
height: 78
spacing: 16
ColumnLayout {
Layout.minimumWidth: 200
Layout.maximumWidth: 260
Layout.fillHeight: true
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: "transparent"
Text {
text: "Block Height"
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 8
font.pointSize: 20
font.family: comicMonoBold.name;
color: "#41FF00"
antialiasing: false
}
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: "transparent"
Text {
id: heightText
text: "-"
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 8
font.pointSize: 18
font.family: comicMonoBold.name;
color: "#41FF00"
antialiasing: false
}
}
}
ColumnLayout {
Layout.minimumWidth: 200
Layout.maximumWidth: 260
Layout.fillHeight: true
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: "transparent"
Text {
text: "Sync Progress"
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 8
font.pointSize: 20
font.family: comicMonoBold.name;
color: "#41FF00"
antialiasing: false
}
}
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: "transparent"
Text {
id: syncPctText
text: "-"
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
font.pointSize: 18
font.family: comicMonoBold.name;
color: "#41FF00"
antialiasing: false
}
}
}
Item {
Layout.fillHeight: true
Layout.fillWidth: true
}
}
}
Image {
Layout.preferredWidth: 5
Layout.preferredHeight: parent.height
source: "qrc:/mining/topright_right.png"
smooth: false
}
}
Item {
Layout.preferredHeight: 7 // 15 + 4 + 102 + 7 = 128
Layout.fillWidth: true
}
}
}
RowLayout {
spacing: 0
//Layout.preferredHeight: 128
Layout.fillHeight: true
Layout.fillWidth: true
Image {
Layout.preferredWidth: 6
Layout.fillHeight: true
source: "qrc:/mining/r_left.png"
fillMode: Image.TileVertically
smooth: false
}
Item {
// text container
Layout.fillWidth: true
Layout.fillHeight: true
Rectangle {
id: textContainer
color: "transparent"
height: parent.height - 20
width: parent.width - 32
clip: true
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
Text {
id: cons
anchors.margins: 4
anchors.fill: parent
text: {
if(mining.daemonMiningState === 0) {
return "Press the pick-axe to start mining!";
} else {
return "";
}
}
font.pointSize: 14
font.family: comicMono.name;
wrapMode: Text.WordWrap
color: "white"
}
}
}
Image {
Layout.preferredWidth: 6
Layout.fillHeight: true
source: "qrc:/mining/r_right.png"
fillMode: Image.TileVertically
smooth: false
}
}
RowLayout {
spacing: 0
Layout.preferredHeight: 140
Layout.fillWidth: true
Image {
Layout.preferredWidth: 306
Layout.preferredHeight: 140
source: "qrc:/mining/lowerleft.png"
smooth: false
AnimatedImage {
source: mining.daemonMiningState === 0 ? "qrc:/assets/images/dog_sitting.gif" : "qrc:/assets/images/dog_running.gif"
width: 80
height: 60
anchors.bottom: parent.bottom
anchors.bottomMargin: 22
anchors.left: parent.left
anchors.leftMargin: 22
MouseArea {
anchors.fill: parent
hoverEnabled: true
onEntered: {
showBubble();
}
onExited: {
hideBubble();
}
}
}
}
Image {
Layout.fillWidth: true
Layout.preferredHeight: 140
source: "qrc:/mining/lower_repeat.png"
fillMode: Image.TileHorizontally
smooth: false
}
Image {
Layout.preferredWidth: 236
Layout.preferredHeight: 140
source: "qrc:/mining/bottom_center_console.png"
smooth: false
Rectangle {
// middle console clock
anchors.left: parent.left
anchors.leftMargin: 100
anchors.top: parent.top
anchors.topMargin: 8
color: "transparent"
width: 54
height: 16
Text {
id: clock
text: ""
antialiasing: false
font.pointSize: 9
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
font.family: comicMonoBold.name;
color: "#41FF00";
Component.onCompleted: {
root.setClock();
}
}
}
Image {
source: "qrc:/mining/intel.png"
width: 100
height: 100
fillMode: Image.Pad
smooth: false
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
}
}
Image {
Layout.fillWidth: true
Layout.preferredHeight: 140
source: "qrc:/mining/lower_repeat.png"
fillMode: Image.TileHorizontally
smooth: false
}
Image {
Layout.preferredWidth: 308
Layout.preferredHeight: 140
source: "qrc:/mining/lowerright.png"
smooth: false
Rectangle {
// lower-right button container
color: "transparent"
width: 106
height: 100
anchors.right: parent.right
anchors.rightMargin: 11
anchors.top: parent.top
anchors.topMargin: 34
Image {
id: imgAxe
visible: mining.daemonMiningState === 0
source: "qrc:/mining/axe.png"
width: 73
height: 75
smooth: false
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
}
AnimatedImage {
visible: mining.daemonMiningState !== 0
source: "qrc:/mining/elmo.gif"
width: 106
height: 100
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
Text {
id: stopMiningBtn
visible: true
text: "Stop Mining"
font.pointSize: 10
z: parent.z + 1
color: "black"
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 28
font.family: comicMonoBold.name;
antialiasing: false
}
}
MouseArea {
anchors.fill: parent
z: parent.z + 1
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
if(mining.daemonMiningState === 0) {
root.startMining();
root.calcAvailableHeightConsoleLines();
}
else
root.stopMining();
}
onEntered: {
imgAxe.height = 64
imgAxe.width = 64
}
onExited: {
imgAxe.height = 75
imgAxe.width = 73
}
}
}
}
}
}
Image {
id: bubble
visible: false
source: "qrc:/mining/bubble.png"
width: 200
height: 60
anchors.bottom: parent.bottom
anchors.bottomMargin: 64
anchors.left: parent.left
anchors.leftMargin: 48
Rectangle {
anchors.top: parent.top
anchors.topMargin: 6
anchors.left: parent.left
anchors.leftMargin: 10
height: 26
color: "transparent"
width: 183
z: parent.z + 1
Text {
id: bubbleText
text: ""
color: "black"
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
font.pointSize: 14
font.family: comicMonoBold.name;
}
}
}
Timer {
id: setClockTimer
interval: 1000*60
running: true
repeat: true
onTriggered: setClock()
}
Timer {
id: dogBubbleTimer
interval: 1000*30
running: true
repeat: true
onTriggered: {
if(Math.random() >= 0.5) return;
if(bubble.visible) return;
root.dogBubbleRemoval.stop();
root.dogBubbleRemoval.start();
var msg = root.bubbleMessage();
bubbleText.text = msg;
bubble.visible = true;
}
}
Timer {
id: dogBubbleRemoval
interval: 2500
running: false
repeat: false
onTriggered: bubble.visible = false;
}
function setClock() {
var now = new Date();
var hours = now.getHours();
var minutes = ('0'+now.getMinutes()).slice(-2);
clock.text = hours + ":" + minutes;
}
function resetComponents() {
hashRateText.text = "-";
miningUptime.text = "-";
syncPctText.text = "-";
heightText.text = "-";
}
function consoleAppend(line) {
if(root.buffer.length >= root.bufferMaxLength)
root.buffer.shift()
root.buffer.push(line);
cons.text = "";
for(var i = 0; i != root.bufferMaxLength; i++) {
if(root.buffer[i])
cons.text += root.buffer[i] + "\n";
}
}
Connections {
target: mining
function onDaemonOutput(line) {
root.consoleAppend(line);
}
function onSyncStatus(from, to, pct) {
syncPctText.text = pct + "%";
heightText.text = from + "/" + to;
}
function onUptimeChanged(uptime) {
miningUptime.text = uptime;
}
function onHashrate(hashrate) {
hashRateText.text = hashrate;
}
}
function showBubble() {
if(bubble.visible) return;
var msg = root.bubbleMessage();
bubbleText.text = msg;
bubble.visible = true;
}
function hideBubble() {
bubble.visible = false;
}
function bubbleMessage() {
var active = ["such work!", "mining WOW!", "woof woof!", "I am tired!", "mining :@", "weeeee", "blocks everywhere!", "wooohoo", "working, twerkin'", "looking4blocks", "mining blocks"];
var inactive = ["doing nothing!", "ZzZZzzZ", "wen mining?!", "ETA mining?!", "zZZzzZZz", "omg so bored", "so bored!!", "much idle, many zZz", "lets go!", "i'm ready!"];
var syncing = ["wen 1gbit", "syncin'", "zZzz", "ETA sync ready?!", "downloading blocks", "fetching blocks"];
var msg = "";
if(mining.daemonMiningState === 0) {
return inactive[Math.floor(Math.random()*inactive.length)];
} else if (mining.daemonMiningState === 2) {
return syncing[Math.floor(Math.random()*syncing.length)];
} else {
return active[Math.floor(Math.random()*active.length)];
}
}
}
+4
View File
@@ -125,6 +125,10 @@ bool Wallet::isMining() {
return m_walletImpl->isMining();
}
double Wallet::miningHashRate() {
return m_walletImpl->miningHashRate();
}
QString Wallet::errorString() const {
return QString::fromStdString(m_walletImpl->errorString());
}
+1
View File
@@ -131,6 +131,7 @@ public:
bool startMining(quint32 threads = 1);
bool stopMining();
bool isMining();
double miningHashRate();
//! return true if wallet is connected to a node
bool isConnected() const;
+1
View File
@@ -61,6 +61,7 @@ void signal_handler(int signum) {
int main(int argc, char *argv[])
{
Q_INIT_RESOURCE(assets);
Q_INIT_RESOURCE(mining);
#if defined(Q_OS_LINUX) && defined(STACK_TRACE)
::signal(SIGSEGV, &signal_handler);
+34
View File
@@ -0,0 +1,34 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="/">
<file alias="qml/mining.qml">assets/qml/mining.qml</file>
<file alias="fonts/ComicMono.ttf">assets/fonts/ComicMono.ttf</file>
<file alias="fonts/ComicMono-Bold.ttf">assets/fonts/ComicMono-Bold.ttf</file>
<file alias="assets/images/dog_sitting.gif">assets/images/dog_sitting.gif</file>
<file alias="assets/images/dog_running.gif">assets/images/dog_running.gif</file>
<file alias="mining/amd.png">assets/images/mining/amd.png</file>
<file alias="mining/axe.png">assets/images/mining/axe.png</file>
<file alias="mining/bg1.gif">assets/images/mining/bg1.gif</file>
<file alias="mining/bottom_center_console.png">assets/images/mining/bottom_center_console.png</file>
<file alias="mining/bubble.png">assets/images/mining/bubble.png</file>
<file alias="mining/elmo.gif">assets/images/mining/elmo.gif</file>
<file alias="mining/intel.png">assets/images/mining/intel.png</file>
<file alias="mining/lowerleft_btn.png">assets/images/mining/lowerleft_btn.png</file>
<file alias="mining/lowerleft_circle.png">assets/images/mining/lowerleft_circle.png</file>
<file alias="mining/lowerleft.png">assets/images/mining/lowerleft.png</file>
<file alias="mining/lower_repeat.png">assets/images/mining/lower_repeat.png</file>
<file alias="mining/lowerright.png">assets/images/mining/lowerright.png</file>
<file alias="mining/mining_gradient.png">assets/images/mining/mining_gradient.png</file>
<file alias="mining/mining.webp">assets/images/mining/mining.webp</file>
<file alias="mining/overlay.png">assets/images/mining/overlay.png</file>
<file alias="mining/r_bottom.png">assets/images/mining/r_bottom.png</file>
<file alias="mining/r_left.png">assets/images/mining/r_left.png</file>
<file alias="mining/r_right.png">assets/images/mining/r_right.png</file>
<file alias="mining/solo.png">assets/images/mining/solo.png</file>
<file alias="mining/topleft.png">assets/images/mining/topleft.png</file>
<file alias="mining/topright_bar.png">assets/images/mining/topright_bar.png</file>
<file alias="mining/topright_left.png">assets/images/mining/topright_left.png</file>
<file alias="mining/topright_middle.png">assets/images/mining/topright_middle.png</file>
<file alias="mining/topright_right.png">assets/images/mining/topright_right.png</file>
<file alias="mining/warning.png">assets/images/mining/warning.png</file>
</qresource>
</RCC>
+64
View File
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: The Monero Project
#include "MiningWidget.h"
#include <algorithm>
#include <QVBoxLayout>
#include <QQmlContext>
#include <QThread>
MiningWidget::MiningWidget(Wallet *wallet, QWidget *parent)
: QWidget(parent)
, m_wallet(wallet)
, m_quickWidget(new QQuickWidget(this))
, m_backend(new MiningBackend(this))
, m_timer(new QTimer(this))
{
auto *layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->addWidget(m_quickWidget);
m_quickWidget->rootContext()->setContextProperty("mining", m_backend);
m_quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
m_quickWidget->setSource(QUrl("qrc:/qml/mining.qml"));
if (auto *root = m_quickWidget->rootObject()) {
connect(root, SIGNAL(startMining()), this, SLOT(onStartMining()));
connect(root, SIGNAL(stopMining()), this, SLOT(onStopMining()));
}
connect(m_timer, &QTimer::timeout, this, &MiningWidget::poll);
m_timer->start(2000);
}
void MiningWidget::onStartMining() {
int threads = std::max(1, QThread::idealThreadCount() / 2);
if (m_wallet->startMining(threads))
m_backend->setState(3);
}
void MiningWidget::onStopMining() {
m_wallet->stopMining();
m_backend->setState(0);
}
void MiningWidget::poll() {
bool mining = m_wallet->isMining();
m_backend->setState(mining ? 3 : 0);
if (mining) {
double hr = m_wallet->miningHashRate();
QString hrStr = hr >= 1000 ? QString("%1 kH/s").arg(hr / 1000.0, 0, 'f', 2)
: QString("%1 H/s").arg(hr, 0, 'f', 0);
emit m_backend->hashrate(hrStr);
}
quint64 from = m_wallet->blockChainHeight();
quint64 to = m_wallet->daemonBlockChainTargetHeight();
if (to > 0) {
int pct = static_cast<int>(100.0 * static_cast<double>(from) / static_cast<double>(to));
emit m_backend->syncStatus(static_cast<int>(from), static_cast<int>(to), pct);
}
}
+53
View File
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: The Monero Project
#ifndef WOWLET_MININGWIDGET_H
#define WOWLET_MININGWIDGET_H
#include <QWidget>
#include <QTimer>
#include <QQuickWidget>
#include "libwalletqt/Wallet.h"
// Context object exposed to mining.qml as `mining`. The QML reads daemonMiningState and reacts to
// the hashrate/syncStatus/uptimeChanged/daemonOutput signals.
class MiningBackend : public QObject {
Q_OBJECT
Q_PROPERTY(int daemonMiningState READ daemonMiningState NOTIFY daemonMiningStateChanged)
public:
explicit MiningBackend(QObject *parent = nullptr) : QObject(parent) {}
int daemonMiningState() const { return m_state; }
void setState(int s) { if (m_state != s) { m_state = s; emit daemonMiningStateChanged(); } }
signals:
void daemonMiningStateChanged();
void daemonOutput(const QString &line);
void syncStatus(int from, int to, int pct);
void uptimeChanged(const QString &uptime);
void hashrate(const QString &hashrate);
private:
int m_state = 0; // 0:idle 1:startup 2:syncing 3:mining
};
// wowlet: the pixel-art mining tab. Hosts mining.qml and wires its pick-axe to the wallet's solo
// miner (RandomWOW via the embedded node), feeding back hashrate + sync status.
class MiningWidget : public QWidget {
Q_OBJECT
public:
explicit MiningWidget(Wallet *wallet, QWidget *parent = nullptr);
private slots:
void onStartMining();
void onStopMining();
void poll();
private:
Wallet *m_wallet;
QQuickWidget *m_quickWidget;
MiningBackend *m_backend;
QTimer *m_timer;
};
#endif //WOWLET_MININGWIDGET_H