diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bfc34848..cb401ab5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index eca618a0..ef35f7ae 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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} diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index ed4db546..ade16e49 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -10,6 +10,8 @@ #include #include +#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] { diff --git a/src/MainWindow.h b/src/MainWindow.h index c44270d3..1a4f7ba3 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -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; diff --git a/src/assets/fonts/ComicMono-Bold.ttf b/src/assets/fonts/ComicMono-Bold.ttf new file mode 100644 index 00000000..e03f41e7 Binary files /dev/null and b/src/assets/fonts/ComicMono-Bold.ttf differ diff --git a/src/assets/fonts/ComicMono.ttf b/src/assets/fonts/ComicMono.ttf new file mode 100644 index 00000000..9bc7354e Binary files /dev/null and b/src/assets/fonts/ComicMono.ttf differ diff --git a/src/assets/images/dog_running.gif b/src/assets/images/dog_running.gif new file mode 100644 index 00000000..433ee586 Binary files /dev/null and b/src/assets/images/dog_running.gif differ diff --git a/src/assets/images/dog_sitting.gif b/src/assets/images/dog_sitting.gif new file mode 100644 index 00000000..f773aeed Binary files /dev/null and b/src/assets/images/dog_sitting.gif differ diff --git a/src/assets/images/mining/amd.png b/src/assets/images/mining/amd.png new file mode 100644 index 00000000..a75f5976 Binary files /dev/null and b/src/assets/images/mining/amd.png differ diff --git a/src/assets/images/mining/axe.png b/src/assets/images/mining/axe.png new file mode 100644 index 00000000..7321bfde Binary files /dev/null and b/src/assets/images/mining/axe.png differ diff --git a/src/assets/images/mining/bg1.gif b/src/assets/images/mining/bg1.gif new file mode 100644 index 00000000..356669c9 Binary files /dev/null and b/src/assets/images/mining/bg1.gif differ diff --git a/src/assets/images/mining/bottom_center_console.png b/src/assets/images/mining/bottom_center_console.png new file mode 100644 index 00000000..cf492e0a Binary files /dev/null and b/src/assets/images/mining/bottom_center_console.png differ diff --git a/src/assets/images/mining/bubble.png b/src/assets/images/mining/bubble.png new file mode 100644 index 00000000..6681c187 Binary files /dev/null and b/src/assets/images/mining/bubble.png differ diff --git a/src/assets/images/mining/elmo.gif b/src/assets/images/mining/elmo.gif new file mode 100644 index 00000000..bbecfadf Binary files /dev/null and b/src/assets/images/mining/elmo.gif differ diff --git a/src/assets/images/mining/intel.png b/src/assets/images/mining/intel.png new file mode 100644 index 00000000..909df591 Binary files /dev/null and b/src/assets/images/mining/intel.png differ diff --git a/src/assets/images/mining/lower_repeat.png b/src/assets/images/mining/lower_repeat.png new file mode 100644 index 00000000..9b8cf16c Binary files /dev/null and b/src/assets/images/mining/lower_repeat.png differ diff --git a/src/assets/images/mining/lowerleft.png b/src/assets/images/mining/lowerleft.png new file mode 100644 index 00000000..97db4c34 Binary files /dev/null and b/src/assets/images/mining/lowerleft.png differ diff --git a/src/assets/images/mining/lowerleft_btn.png b/src/assets/images/mining/lowerleft_btn.png new file mode 100644 index 00000000..2e2f275e Binary files /dev/null and b/src/assets/images/mining/lowerleft_btn.png differ diff --git a/src/assets/images/mining/lowerleft_circle.png b/src/assets/images/mining/lowerleft_circle.png new file mode 100644 index 00000000..7baa63fa Binary files /dev/null and b/src/assets/images/mining/lowerleft_circle.png differ diff --git a/src/assets/images/mining/lowerright.png b/src/assets/images/mining/lowerright.png new file mode 100644 index 00000000..c9f2faff Binary files /dev/null and b/src/assets/images/mining/lowerright.png differ diff --git a/src/assets/images/mining/mining.webp b/src/assets/images/mining/mining.webp new file mode 100644 index 00000000..b11fe4a8 Binary files /dev/null and b/src/assets/images/mining/mining.webp differ diff --git a/src/assets/images/mining/mining_gradient.png b/src/assets/images/mining/mining_gradient.png new file mode 100644 index 00000000..f7422fe3 Binary files /dev/null and b/src/assets/images/mining/mining_gradient.png differ diff --git a/src/assets/images/mining/overlay.png b/src/assets/images/mining/overlay.png new file mode 100644 index 00000000..be971466 Binary files /dev/null and b/src/assets/images/mining/overlay.png differ diff --git a/src/assets/images/mining/r_bottom.png b/src/assets/images/mining/r_bottom.png new file mode 100644 index 00000000..8fa382f1 Binary files /dev/null and b/src/assets/images/mining/r_bottom.png differ diff --git a/src/assets/images/mining/r_left.png b/src/assets/images/mining/r_left.png new file mode 100644 index 00000000..b498e85d Binary files /dev/null and b/src/assets/images/mining/r_left.png differ diff --git a/src/assets/images/mining/r_right.png b/src/assets/images/mining/r_right.png new file mode 100644 index 00000000..8f56bfe1 Binary files /dev/null and b/src/assets/images/mining/r_right.png differ diff --git a/src/assets/images/mining/solo.png b/src/assets/images/mining/solo.png new file mode 100644 index 00000000..499e73b4 Binary files /dev/null and b/src/assets/images/mining/solo.png differ diff --git a/src/assets/images/mining/topleft.png b/src/assets/images/mining/topleft.png new file mode 100644 index 00000000..c4ddea10 Binary files /dev/null and b/src/assets/images/mining/topleft.png differ diff --git a/src/assets/images/mining/topright_bar.png b/src/assets/images/mining/topright_bar.png new file mode 100644 index 00000000..988f19c7 Binary files /dev/null and b/src/assets/images/mining/topright_bar.png differ diff --git a/src/assets/images/mining/topright_left.png b/src/assets/images/mining/topright_left.png new file mode 100644 index 00000000..3251de85 Binary files /dev/null and b/src/assets/images/mining/topright_left.png differ diff --git a/src/assets/images/mining/topright_middle.png b/src/assets/images/mining/topright_middle.png new file mode 100644 index 00000000..c5909869 Binary files /dev/null and b/src/assets/images/mining/topright_middle.png differ diff --git a/src/assets/images/mining/topright_right.png b/src/assets/images/mining/topright_right.png new file mode 100644 index 00000000..3602e7ca Binary files /dev/null and b/src/assets/images/mining/topright_right.png differ diff --git a/src/assets/images/mining/warning.png b/src/assets/images/mining/warning.png new file mode 100644 index 00000000..29745cbe Binary files /dev/null and b/src/assets/images/mining/warning.png differ diff --git a/src/assets/qml/mining.qml b/src/assets/qml/mining.qml new file mode 100644 index 00000000..65245a1b --- /dev/null +++ b/src/assets/qml/mining.qml @@ -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)]; + } + } +} diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index 55c684cd..81109fd7 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -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()); } diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index 3931aadf..0cbfefeb 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -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; diff --git a/src/main.cpp b/src/main.cpp index c2302d0e..00df4ce3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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); diff --git a/src/mining.qrc b/src/mining.qrc new file mode 100644 index 00000000..c4349d48 --- /dev/null +++ b/src/mining.qrc @@ -0,0 +1,34 @@ + + + assets/qml/mining.qml + assets/fonts/ComicMono.ttf + assets/fonts/ComicMono-Bold.ttf + assets/images/dog_sitting.gif + assets/images/dog_running.gif + assets/images/mining/amd.png + assets/images/mining/axe.png + assets/images/mining/bg1.gif + assets/images/mining/bottom_center_console.png + assets/images/mining/bubble.png + assets/images/mining/elmo.gif + assets/images/mining/intel.png + assets/images/mining/lowerleft_btn.png + assets/images/mining/lowerleft_circle.png + assets/images/mining/lowerleft.png + assets/images/mining/lower_repeat.png + assets/images/mining/lowerright.png + assets/images/mining/mining_gradient.png + assets/images/mining/mining.webp + assets/images/mining/overlay.png + assets/images/mining/r_bottom.png + assets/images/mining/r_left.png + assets/images/mining/r_right.png + assets/images/mining/solo.png + assets/images/mining/topleft.png + assets/images/mining/topright_bar.png + assets/images/mining/topright_left.png + assets/images/mining/topright_middle.png + assets/images/mining/topright_right.png + assets/images/mining/warning.png + + diff --git a/src/widgets/MiningWidget.cpp b/src/widgets/MiningWidget.cpp new file mode 100644 index 00000000..58139486 --- /dev/null +++ b/src/widgets/MiningWidget.cpp @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: The Monero Project + +#include "MiningWidget.h" + +#include + +#include +#include +#include + +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(100.0 * static_cast(from) / static_cast(to)); + emit m_backend->syncStatus(static_cast(from), static_cast(to), pct); + } +} diff --git a/src/widgets/MiningWidget.h b/src/widgets/MiningWidget.h new file mode 100644 index 00000000..f08de31d --- /dev/null +++ b/src/widgets/MiningWidget.h @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: The Monero Project + +#ifndef WOWLET_MININGWIDGET_H +#define WOWLET_MININGWIDGET_H + +#include +#include +#include + +#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