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
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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] {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 440 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
@@ -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)];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||