Compare commits

..
Author SHA1 Message Date
jwinterm c65c12d328 release: wowlet 5.1.0
- macOS: first native signed + notarized build (new platform → minor bump)
- fix: stuck-tx detection + rebroadcast + re-relay on daemon sync
  (resolves the 'unconfirmed for days' field report)
- mining tab: top text layout fixes
2026-08-30 06:36:44 -04:00
jwinterm 38c63088b5 Merge macOS build lane: native signed+notarized Gitea build
Brings .gitea/workflows/build-macos.yml (brew-native compile, embedded
wownerod+Tor, static boost, bundled image codecs, Developer ID sign +
notarize) and the portable cmake tor/wownerod copy fixes.
2026-08-30 06:36:23 -04:00
jwinterm 3d38fdd135 ci(macos): bundle Qt image-plugin dylib deps (webp/sharpyuv/brotli) for clean-Mac runtime
macdeployqt bundles Qt frameworks but not the plain @rpath/*.dylib brew
deps of its image plugins, so a no-Homebrew Mac couldn't load webp. Add a
Qt-safe pass that resolves @rpath/*.dylib (never @rpath/*.framework) from
brew, copies them in, rewrites load paths, and fails the build if any
remain. Sign step now re-signs by Mach-O type (not exec bit) so the
install_name_tool-modified plugins get valid signatures.
2026-08-30 05:34:04 -04:00
jwinterm f2c3ce45a0 ci(macos): link boost statically (Boost_USE_STATIC_LIBS=ON)
The 100%-linked app dynamically referenced brew's libboost_*.dylib, which
macdeployqt failed to bundle — the app would fail to launch on any Mac
without Homebrew (notarization doesn't check runtime dylib resolution).
Static boost matches how guix/depends release builds work and removes the
runtime dependency entirely.
2026-08-30 05:08:01 -04:00
jwinterm 87d5977243 ci(macos): CPATH/LIBRARY_PATH to brew prefix so feather finds boost headers
monero's static libs built fine, but the feather target includes monero
headers pulling boost/variant.hpp, and clang doesn't search
/opt/homebrew/include by default (modern BoostConfig leaves
Boost_INCLUDE_DIRS empty).
2026-08-30 04:55:20 -04:00
jwinterm 9e6d85c246 ci(macos): disable Trezor (USE_DEVICE_TREZOR=OFF)
brew's protobuf pulls Abseil headers that require C++17, breaking the
feather compile (DEVICE_TREZOR_READY gates the protobuf link). Trezor
hardware does not support Wownero, so this removes a useless dependency
rather than working around it — the Linux lane only tolerates it because
its depends system pins an old protobuf.
2026-08-30 04:42:14 -04:00
jwinterm 49b3264b0e cmake: portable tor/wownerod copies — BSD cp lacks -u (exit 64 evaded the ret==1 check, silently shipping without embedded node/tor); fail on ANY nonzero cp exit 2026-08-29 14:38:24 -04:00
jwinterm 3cf58f0313 cmake: mkdir src/assets/tor before the TOR_DIR copy (untracked dir; BSD cp usage error evades the ret==1 check) 2026-08-29 14:28:09 -04:00
jwinterm 0f6892c123 ci(macos): ad-hoc sign tor before embedding — arm64 macOS SIGKILLs unsigned binaries, including the runtime-unpacked copy on user machines 2026-08-29 14:17:26 -04:00
jwinterm 1fedbf0faa ci(macos): ship libevent dylib next to tor (@executable_path linkage) 2026-08-29 14:06:15 -04:00
jwinterm 410df0c8ff ci(macos): extract wownerod into an explicit dir — find(1) does not descend the /tmp symlink 2026-08-29 13:55:16 -04:00
jwinterm 1853ebf558 ci(macos): disable pipefail around yes|brew (SIGPIPE 141 when brew asks nothing) 2026-08-29 13:47:37 -04:00
jwinterm a2b34f5ebc ci(macos): embed tor (expert bundle 15.0) — arms daemon --tx-proxy via HAS_TOR_BIN, parity with linux lane 2026-08-29 13:44:28 -04:00
jwinterm 27f694c4bd mining tab: fix top text layout — anchor conflict on sync value, oversized fonts, bezel overflow
- Sync Progress value had both left and horizontalCenter anchors (drifted center)
- 20pt labels in 30px rows collided; now 14pt matching the top-left monitors
- hashrate/uptime/height values auto-fit their pixel-art frames (HorizontalFit)
- 'uptime' -> 'Uptime'
2026-08-29 13:43:05 -04:00
jwinterm c7333a775c Merge fix/stuck-tx-detection: broadcast-failure detection, stuck-tx warning + rebroadcast, re-relay on daemon sync 2026-08-29 13:41:32 -04:00
jwinterm 8b846a8dc0 ci(macos): pin CMAKE_PREFIX_PATH to brew qt6 (qt@5 keg coexists unlinked on the runner) 2026-08-29 13:37:41 -04:00
jwinterm dc5f940801 ci(macos): pipe yes into brew — Homebrew 6 prompts for dependency installs regardless of NONINTERACTIVE 2026-08-29 13:27:05 -04:00
jwinterm 36d878e7bf ci(macos): also skip dependent upgrades in brew install 2026-08-29 13:16:13 -04:00
jwinterm a3509a6c3b Detect stuck outgoing transactions and re-relay after daemon restart
Field report: a send sat 'in mempool' for two days while the network had
never seen it (Tor down at send time defeats both the multi-broadcast and
the embedded daemon's exclusive --tx-proxy relay), locking the change.

Three additive changes, all strictly local (no txid is ever queried
against an external service):

- onMultiBroadcast now counts relay responses; if no node accepted the
  broadcast within 45s the user is told immediately and offered the
  existing rebroadcast path, instead of silence.
- A periodic local history scan warns once per outgoing tx that has been
  pending for more than 25 minutes, again offering rebroadcast (which
  already reconnects to a fresh node first).
- On DaemonManager::synced, still-pending outgoing txs are re-announced
  from the wallet's own tx cache; a restarted embedded node's pool has
  forgotten them.
2026-08-29 06:13:06 -04:00
5 changed files with 245 additions and 30 deletions
+89 -10
View File
@@ -35,8 +35,9 @@ jobs:
- name: install dependencies (brew)
run: |
export PATH="/opt/homebrew/bin:$PATH"
export NONINTERACTIVE=1 HOMEBREW_NO_ENV_HINTS=1
brew install --quiet qt libsodium libzip qrencode unbound cmake boost hidapi openssl expat protobuf pkgconfig zxing-cpp
set +o pipefail # yes(1) takes SIGPIPE when brew has nothing to ask
export NONINTERACTIVE=1 HOMEBREW_NO_ENV_HINTS=1 HOMEBREW_NO_INSTALL_UPGRADE=1
yes | brew install --quiet qt libsodium libzip qrencode unbound cmake boost hidapi openssl expat protobuf pkgconfig zxing-cpp
- name: fetch wownerod to embed
run: |
@@ -46,19 +47,48 @@ jobs:
arm64) WOWTAR=wownero-aarch64-apple-darwin11-v0.11.4.0.tar.bz2 ;;
*) WOWTAR=wownero-x86_64-apple-darwin11-v0.11.4.0.tar.bz2 ;;
esac
mkdir -p /tmp/wownerod-embed
mkdir -p /tmp/wownerod-embed /tmp/wow-extract
curl -L -o /tmp/wownero.tar.bz2 \
"https://github.com/wownero/wownero-codeberg-mirror/releases/download/v0.11.4.0/$WOWTAR"
tar xjf /tmp/wownero.tar.bz2 -C /tmp
find /tmp -name wownerod -type f -exec cp {} /tmp/wownerod-embed/ \;
# /tmp is a symlink on macOS — find(1) won't descend it as a bare argument
tar xjf /tmp/wownero.tar.bz2 -C /tmp/wow-extract
find /tmp/wow-extract -name wownerod -type f -exec cp {} /tmp/wownerod-embed/ \;
chmod +x /tmp/wownerod-embed/wownerod
ls -la /tmp/wownerod-embed
- name: fetch tor to embed
run: |
set -e
ARCH=$(uname -m)
case "$ARCH" in
arm64) TORTAR=tor-expert-bundle-macos-aarch64-15.0.tar.gz ;;
*) TORTAR=tor-expert-bundle-macos-x86_64-15.0.tar.gz ;;
esac
mkdir -p /tmp/teb /tmp/tor-embed
curl -L -o /tmp/teb.tgz "https://archive.torproject.org/tor-package-archive/torbrowser/15.0/$TORTAR"
tar xzf /tmp/teb.tgz -C /tmp/teb
cp /tmp/teb/tor/tor /tmp/teb/tor/libevent-*.dylib /tmp/tor-embed/
# tor ships UNSIGNED; arm64 macOS SIGKILLs unsigned binaries. Ad-hoc sign
# BEFORE embedding — the signature travels inside the file, so the copy
# the app unpacks at runtime on user machines is runnable too.
codesign -s - -f /tmp/tor-embed/tor /tmp/tor-embed/libevent-*.dylib
chmod +x /tmp/tor-embed/tor
# expert-bundle RELEASE number (15.0) is NOT the tor version — derive it
TORVER=$(/tmp/tor-embed/tor --version | grep -oE '[0-9]+(\.[0-9]+){3}' | head -1)
echo "TOR_VERSION=$TORVER" >> "$GITHUB_ENV"
echo "embedded tor version: $TORVER"
- name: build
run: |
export PATH="/opt/homebrew/bin:$PATH"
# clang does not search /opt/homebrew/include by default; the feather
# target pulls monero headers that include boost, and modern Boost's
# CMake config leaves Boost_INCLUDE_DIRS empty. CPATH/LIBRARY_PATH make
# every linked brew header/lib findable without per-dep flag surgery.
export CPATH="$(brew --prefix)/include"
export LIBRARY_PATH="$(brew --prefix)/lib"
mkdir -p build && cd build
cmake -DWITH_SCANNER=OFF -DWOWNEROD_DIR=/tmp/wownerod-embed ..
cmake -DWITH_SCANNER=OFF -DUSE_DEVICE_TREZOR=OFF -DBoost_USE_STATIC_LIBS=ON -DWOWNEROD_DIR=/tmp/wownerod-embed -DTOR_DIR=/tmp/tor-embed -DTOR_VERSION="$TOR_VERSION" -DCMAKE_PREFIX_PATH="$(brew --prefix qt)" ..
cmake --build . -j "$(sysctl -n hw.ncpu)"
- name: package app bundle
@@ -75,6 +105,52 @@ jobs:
cp ../src/assets/images/appicons/appicon.icns dist/Wowlet.app/Contents/Resources/
# bundle Qt frameworks/plugins so the app runs off-machine
/opt/homebrew/opt/qt/bin/macdeployqt dist/Wowlet.app
# macdeployqt bundles Qt frameworks but not the plain-dylib brew deps
# of Qt's image plugins (libwebp/libsharpyuv/libbrotli...), which are
# referenced as @rpath/lib*.dylib. Without them a clean (no-Homebrew)
# Mac can't load those image formats. Qt frameworks use a different
# ref form (@rpath/Qt*.framework/...), so keying strictly on
# `@rpath/*.dylib` bundles the stragglers WITHOUT touching Qt.
# Several passes converge (a bundled lib pulls in further deps).
APP=dist/Wowlet.app
FW="$APP/Contents/Frameworks"
mkdir -p "$FW"
for pass in 1 2 3 4; do
while IFS= read -r macho; do
file "$macho" | grep -q Mach-O || continue
chmod u+w "$macho"
otool -L "$macho" | awk 'NR>1{print $1}' | while IFS= read -r dep; do
case "$dep" in
@rpath/*.dylib)
name=$(basename "$dep")
if [ -f "$FW/$name" ]; then
install_name_tool -change "$dep" "@executable_path/../Frameworks/$name" "$macho" 2>/dev/null || true
continue
fi
real=$(find /opt/homebrew/lib /opt/homebrew/opt/*/lib -maxdepth 1 -name "$name" 2>/dev/null | head -1)
if [ -n "$real" ]; then
cp -n "$real" "$FW/$name"
chmod u+w "$FW/$name"
install_name_tool -id "@executable_path/../Frameworks/$name" "$FW/$name" 2>/dev/null || true
install_name_tool -change "$dep" "@executable_path/../Frameworks/$name" "$macho" 2>/dev/null || true
fi
;;
esac
done
done < <(find "$APP" -type f)
done
# Fail if any @rpath/*.dylib (non-Qt) remains unresolved in the bundle
leftover=$(find "$APP" -type f | while IFS= read -r m; do
file "$m" | grep -q Mach-O || continue
otool -L "$m" | awk 'NR>1{print $1}' | grep -E '^@rpath/[^/]+\.dylib$' || true
done | sort -u)
if [ -n "$leftover" ]; then
echo "UNBUNDLED @rpath dylibs remain:"; echo "$leftover"; exit 1
fi
echo "all plain-dylib deps bundled"
ls -la "$FW" | grep -iE "webp|sharpyuv|brotli" || true
ls -la dist/
- name: sign and notarize (skipped when secrets absent)
@@ -97,13 +173,16 @@ jobs:
security set-key-partition-list -S apple-tool:,apple: -s -k "$KCPW" "$KC" >/dev/null
security list-keychains -d user -s "$KC" login.keychain-db
# sign nested Mach-O executables first (embedded wownerod etc.),
# then frameworks, then the bundle — all hardened-runtime + timestamp
# Sign inside-out: every Mach-O NOT inside a .framework (embedded
# wownerod/tor, the bundled webp/sharpyuv/brotli dylibs, and the
# image-format plugins whose signatures install_name_tool just
# invalidated — keyed on `file`, not exec bit, since plugins/dylibs
# aren't +x), then each Qt framework as a unit, then the bundle.
SIGN() { codesign --force --options runtime --timestamp --keychain "$KC" \
--sign "$APPLE_SIGNING_IDENTITY" "$@"; }
find dist/Wowlet.app -type f -perm +111 ! -path "*/Frameworks/*" \
find dist/Wowlet.app -type f ! -path "*/*.framework/*" \
| while read -r f; do file "$f" | grep -q Mach-O && SIGN "$f" || true; done
find dist/Wowlet.app/Contents/Frameworks -maxdepth 1 \( -name "*.framework" -o -name "*.dylib" \) 2>/dev/null \
find dist/Wowlet.app/Contents/Frameworks -maxdepth 1 -name "*.framework" 2>/dev/null \
| while read -r fw; do SIGN "$fw"; done
SIGN dist/Wowlet.app
codesign --verify --deep --strict dist/Wowlet.app
+9 -5
View File
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.18)
project(feather
VERSION "5.0.3"
VERSION "5.1.0"
DESCRIPTION "A free Monero desktop wallet"
LANGUAGES CXX C ASM
)
@@ -148,15 +148,19 @@ if(TOR_DIR)
message(STATUS "Embedded Tor version: ${TOR_VERSION}")
# wowlet: the target dir is not tracked in git — create it (BSD cp's usage
# error exits 64, which the ret check below does not catch)
execute_process(COMMAND bash -c "mkdir -p ${CMAKE_CURRENT_SOURCE_DIR}/src/assets/tor")
# Always copy Tor when doing a reproducible build to prevent old versions from getting included
if (REPRODUCIBLE)
set(TOR_COPY_CMD "cp -a ${TOR_DIR}/* ${CMAKE_CURRENT_SOURCE_DIR}/src/assets/tor")
else()
set(TOR_COPY_CMD "cp -au ${TOR_DIR}/* ${CMAKE_CURRENT_SOURCE_DIR}/src/assets/tor")
set(TOR_COPY_CMD "cp -a ${TOR_DIR}/* ${CMAKE_CURRENT_SOURCE_DIR}/src/assets/tor") # wowlet: BSD cp has no -u (macOS: illegal option, exit 64, silent no-op)
endif()
execute_process(COMMAND bash -c "${TOR_COPY_CMD}" RESULT_VARIABLE ret)
if(ret EQUAL "1")
if(NOT ret EQUAL "0")
message(FATAL_ERROR "Tor copy failure: ${TOR_COPY_CMD}")
endif()
@@ -172,11 +176,11 @@ if(WOWNEROD_DIR)
if (REPRODUCIBLE)
set(WOWNEROD_COPY_CMD "cp -a ${WOWNEROD_DIR}/* ${CMAKE_CURRENT_SOURCE_DIR}/src/assets/wownerod")
else()
set(WOWNEROD_COPY_CMD "cp -au ${WOWNEROD_DIR}/* ${CMAKE_CURRENT_SOURCE_DIR}/src/assets/wownerod")
set(WOWNEROD_COPY_CMD "cp -a ${WOWNEROD_DIR}/* ${CMAKE_CURRENT_SOURCE_DIR}/src/assets/wownerod") # wowlet: BSD cp has no -u
endif()
execute_process(COMMAND bash -c "${WOWNEROD_COPY_CMD}" RESULT_VARIABLE ret)
if(ret EQUAL "1")
if(NOT ret EQUAL "0")
message(FATAL_ERROR "wownerod copy failure: ${WOWNEROD_COPY_CMD}")
endif()
+112
View File
@@ -542,6 +542,24 @@ void MainWindow::initWalletContext() {
connect(m_wallet, &Wallet::deviceError, this, &MainWindow::onDeviceError);
connect(m_wallet, &Wallet::multiBroadcast, this, &MainWindow::onMultiBroadcast);
// wowlet: count multi-broadcast relay results so a total failure (Tor not
// bootstrapped, every node unreachable) is surfaced instead of silent.
connect(m_rpc, &DaemonRpc::ApiResponse, this, [this](const DaemonRpc::DaemonResponse &resp){
if (resp.endpoint == DaemonRpc::Endpoint::SEND_RAW_TRANSACTION && resp.ok) {
m_broadcastBatchSuccesses++;
}
});
// wowlet: when the embedded daemon reports synced after a (re)start,
// re-announce any still-pending outgoing txs; a restarted pool has
// forgotten them.
connect(daemonManager(), &DaemonManager::synced, this, &MainWindow::relayPendingTransactions);
// wowlet: periodic local check for outgoing txs stuck unconfirmed.
m_stuckTxTimer = new QTimer(this);
connect(m_stuckTxTimer, &QTimer::timeout, this, &MainWindow::checkStuckTransactions);
m_stuckTxTimer->start(2 * 60 * 1000);
}
void MainWindow::menuToggleTabVisible(const QString &key){
@@ -760,6 +778,7 @@ void MainWindow::onSubtractFeeFromAmountEnabled(bool enabled) {
}
void MainWindow::onMultiBroadcast(const QMap<QString, QString> &txHexMap) {
m_broadcastBatchSuccesses = 0;
QMapIterator<QString, QString> i(txHexMap);
while (i.hasNext()) {
i.next();
@@ -770,6 +789,99 @@ void MainWindow::onMultiBroadcast(const QMap<QString, QString> &txHexMap) {
m_rpc->sendRawTransaction(i.value());
}
}
// wowlet: if not a single node accepted the relay, say so. The commit to
// the local daemon may still confirm on its own, but when Tor is down and
// the daemon runs with an exclusive --tx-proxy, nothing will ever leave
// this machine; the user should know within a minute, not after two days.
const QStringList txids = txHexMap.keys();
QTimer::singleShot(45 * 1000, this, [this, txids]{
if (m_broadcastBatchSuccesses > 0 || txids.isEmpty()) {
return;
}
QMessageBox box(this);
box.setWindowTitle("Transaction relay may have failed");
box.setText("No node accepted the transaction broadcast. If Tor is "
"still starting (or unavailable), the transaction may not "
"reach the network at all.");
box.setInformativeText("Rebroadcasting later is always safe: a "
"transaction can never be spent twice.");
QPushButton *retry = box.addButton("Rebroadcast now", QMessageBox::YesRole);
box.addButton("Later", QMessageBox::RejectRole);
box.exec();
if (box.clickedButton() == retry) {
this->onResendTransaction(txids[0]);
}
});
}
void MainWindow::checkStuckTransactions() {
// wowlet: a pending outgoing tx unconfirmed for this long has, in
// practice, not reached the network (Tor down at send time, embedded
// node restarted mid-relay). Local history scan only; no network calls,
// and no txid is ever looked up against any external service. The remedy
// reuses the existing rebroadcast path, which reconnects to a fresh node
// first.
constexpr qint64 kStuckSecs = 25 * 60;
if (m_wallet == nullptr || m_wallet->history() == nullptr) {
return;
}
TransactionHistory *history = m_wallet->history();
for (quint64 i = 0; i < history->count(); i++) {
const TransactionRow &row = history->transaction(static_cast<int>(i));
if (!row.pending || row.direction == TransactionRow::Direction_In) {
continue;
}
if (row.timestamp.secsTo(QDateTime::currentDateTime()) < kStuckSecs) {
continue;
}
if (m_stuckTxWarned.contains(row.hash)) {
continue;
}
m_stuckTxWarned.insert(row.hash);
QMessageBox box(this);
box.setWindowTitle("Transaction appears stuck");
box.setText(QString("An outgoing transaction has been unconfirmed for "
"more than %1 minutes:\n\n%2")
.arg(kStuckSecs / 60).arg(row.hash));
box.setInformativeText("It may never have reached the network. "
"Rebroadcasting is safe: a transaction can "
"never be spent twice, and your change stays "
"locked until this either confirms or fails.");
QPushButton *retry = box.addButton("Rebroadcast now", QMessageBox::YesRole);
box.addButton("Later", QMessageBox::RejectRole);
box.exec();
if (box.clickedButton() == retry) {
this->onResendTransaction(row.hash);
}
}
}
void MainWindow::relayPendingTransactions() {
// wowlet: rebuild the multi-broadcast map from the wallet's own tx cache
// for every pending outgoing tx and re-announce it. Strictly local
// sources; the announcement itself goes through the normal (Tor-routed)
// broadcast path.
if (m_wallet == nullptr || m_wallet->history() == nullptr) {
return;
}
TransactionHistory *history = m_wallet->history();
QMap<QString, QString> txHexMap;
for (quint64 i = 0; i < history->count(); i++) {
const TransactionRow &row = history->transaction(static_cast<int>(i));
if (!row.pending || row.direction == TransactionRow::Direction_In) {
continue;
}
QString txHex = m_wallet->getCacheTransaction(row.hash);
if (!txHex.isEmpty()) {
txHexMap[row.hash] = txHex;
}
}
if (txHexMap.isEmpty()) {
return;
}
qDebug() << QString("Re-relaying %1 pending transaction(s) after daemon sync").arg(txHexMap.size());
this->onMultiBroadcast(txHexMap);
}
void MainWindow::onSyncStatus(quint64 height, quint64 target, bool daemonSync) {
+14
View File
@@ -51,6 +51,9 @@ namespace Ui {
class MainWindow;
}
#include <QSet>
#include <QTimer>
class ToggleTab : QObject {
Q_OBJECT
@@ -214,12 +217,23 @@ private:
QIcon hardwareDevicePairedIcon();
QIcon hardwareDeviceUnpairedIcon();
// wowlet: stuck-transaction detection and pending re-relay. Both are
// strictly local: history rows and the wallet's own tx cache; no txid is
// ever queried against any external service.
void checkStuckTransactions();
void relayPendingTransactions();
QScopedPointer<Ui::MainWindow> ui;
WindowManager *m_windowManager;
Wallet *m_wallet = nullptr;
Nodes *m_nodes;
DaemonRpc *m_rpc;
// wowlet: see checkStuckTransactions / relayPendingTransactions
QTimer *m_stuckTxTimer = nullptr;
QSet<QString> m_stuckTxWarned;
int m_broadcastBatchSuccesses = 0;
SplashDialog *m_splashDialog = nullptr;
AccountSwitcherDialog *m_accountSwitcherDialog = nullptr;
TxPoolViewerDialog *m_txPoolViewerDialog = nullptr;
+21 -15
View File
@@ -134,10 +134,12 @@ Rectangle {
Text {
id: hashRateText
text: "-"
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.fill: parent
anchors.leftMargin: 4
font.pointSize: 16
verticalAlignment: Text.AlignVCenter
fontSizeMode: Text.HorizontalFit
minimumPointSize: 8
font.pointSize: 14
font.family: comicMono.name;
antialiasing: false
color: "#41FF00"
@@ -160,7 +162,7 @@ Rectangle {
Layout.fillHeight: true
Text {
text: "uptime"
text: "Uptime"
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 4
@@ -179,10 +181,12 @@ Rectangle {
Text {
id: miningUptime
text: "-"
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.fill: parent
anchors.leftMargin: 4
font.pointSize: 12
verticalAlignment: Text.AlignVCenter
fontSizeMode: Text.HorizontalFit
minimumPointSize: 8
font.pointSize: 14
font.family: comicMono.name;
antialiasing: false
color: "#41FF00"
@@ -270,7 +274,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 8
font.pointSize: 20
font.pointSize: 14
font.family: comicMonoBold.name;
color: "#41FF00"
antialiasing: false
@@ -285,10 +289,12 @@ Rectangle {
Text {
id: heightText
text: "-"
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.fill: parent
anchors.leftMargin: 8
font.pointSize: 18
verticalAlignment: Text.AlignVCenter
fontSizeMode: Text.HorizontalFit
minimumPointSize: 9
font.pointSize: 14
font.family: comicMonoBold.name;
color: "#41FF00"
antialiasing: false
@@ -311,7 +317,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: 8
font.pointSize: 20
font.pointSize: 14
font.family: comicMonoBold.name;
color: "#41FF00"
antialiasing: false
@@ -327,9 +333,9 @@ Rectangle {
id: syncPctText
text: "-"
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
font.pointSize: 18
anchors.left: parent.left
anchors.leftMargin: 8
font.pointSize: 14
font.family: comicMonoBold.name;
color: "#41FF00"
antialiasing: false