Skip to content

Commit c2b912b

Browse files
committed
Merge branch 'master' into claude/automate-language-updates-vrO5D
2 parents 05660ae + 8985040 commit c2b912b

48 files changed

Lines changed: 2218 additions & 287 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/main.yml

Lines changed: 184 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -673,6 +673,11 @@ jobs:
673673
echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
674674
echo "#define LICENSE" >> secret.h
675675
676+
- name: Prepare Android signing key
677+
run: |
678+
printf '%s' "${{ secrets.SIGNING_KEY }}" | base64 --decode > ${{ github.workspace }}/src/android/ci-signing-key.jks
679+
echo "CI_ANDROID_SIGNING_KEYSTORE=${{ github.workspace }}/src/android/ci-signing-key.jks" >> $GITHUB_ENV
680+
676681
- name: Build qthttpserver for Android
677682
run: |
678683
cd src/qthttpserver
@@ -699,6 +704,24 @@ jobs:
699704
- name: Build APK (not usable for production due to unpatched QT library)
700705
run: cd src; androiddeployqt --input android-qdomyos-zwift-deployment-settings.json --output ${{ github.workspace }}/output/android/ --android-platform android-31 --gradle --aab
701706

707+
- name: Re-sign APK for stable App Links fingerprint
708+
run: |
709+
APK_PATH="${{ github.workspace }}/output/android/build/outputs/apk/debug/android-debug.apk"
710+
SIGNED_APK_PATH="${{ github.workspace }}/output/android/build/outputs/apk/debug/android-debug-signed.apk"
711+
APKSIGNER=$(find /usr/local/lib/android/sdk/build-tools -name apksigner | sort | tail -n 1)
712+
if [ -z "$APKSIGNER" ]; then
713+
echo "apksigner not found"
714+
exit 1
715+
fi
716+
"$APKSIGNER" sign \
717+
--ks "$CI_ANDROID_SIGNING_KEYSTORE" \
718+
--ks-key-alias "${{ secrets.ALIAS }}" \
719+
--ks-pass "pass:${{ secrets.KEY_STORE_PASSWORD }}" \
720+
--key-pass "pass:${{ secrets.KEY_PASSWORD }}" \
721+
--out "$SIGNED_APK_PATH" \
722+
"$APK_PATH"
723+
mv "$SIGNED_APK_PATH" "$APK_PATH"
724+
702725
- name: Show ccache stats
703726
if: always()
704727
run: ccache --show-stats
@@ -1375,9 +1398,6 @@ jobs:
13751398
- name: Set up QEMU
13761399
uses: docker/setup-qemu-action@v3
13771400

1378-
- name: Set up Docker Buildx
1379-
uses: docker/setup-buildx-action@v3
1380-
13811401
- name: Secrets
13821402
run: |
13831403
cd src
@@ -1393,32 +1413,41 @@ jobs:
13931413
cd ..
13941414
13951415
- name: Build for Raspberry Pi
1396-
uses: docker://arm32v7/debian:bullseye-20241016
1397-
with:
1398-
args: >
1416+
run: |
1417+
docker run --rm --platform linux/arm/v6 \
1418+
-v ${{ github.workspace }}:/workspace -w /workspace \
1419+
balenalib/raspberry-pi-debian:bullseye-build \
13991420
bash -c "
1400-
set -ex &&
1401-
for i in 1 2 3; do apt-get update && break || sleep 5; done &&
1402-
for i in 1 2 3; do apt-get install -y --fix-missing build-essential git cmake qtbase5-dev qtbase5-private-dev qtchooser qt5-qmake qtbase5-dev-tools qttools5-dev-tools libqt5svg5-dev qtmultimedia5-dev libqt5charts5-dev qtpositioning5-dev qtconnectivity5-dev libqt5websockets5-dev libqt5texttospeech5-dev libqt5bluetooth5 libqt5networkauth5-dev qml-module-qtlocation qml-module-qtpositioning qtlocation5-dev libqt5quickcontrols2-5 qtquickcontrols2-5-dev qml-module-qtquick-controls2 qtbase5-dev libqt5sql5-sqlite libqt5sql5 libqt5sql5-mysql libqt5sql5-psql && break || sleep 5; done &&
1403-
export QT_SELECT=qt5 &&
1404-
export PATH=/usr/lib/qt5/bin:$PATH &&
1405-
cd /github/workspace &&
1406-
sed -i '/QtHttpServer/d' qdomyos-zwift.pro &&
1407-
find src -type f \( -name '*.cpp' -o -name '*.h' \) -exec sed -i 's/#include <QtHttpServer/\/\/#include <QtHttpServer/' {} + &&
1408-
find src -type f \( -name '*.cpp' -o -name '*.h' \) -exec sed -i 's/QHttpServer/\/\/QHttpServer/' {} + &&
1409-
cat qdomyos-zwift.pro &&
1410-
qmake &&
1411-
make -j$(nproc)
1421+
set -ex &&
1422+
for i in 1 2 3; do apt-get update && break || sleep 5; done &&
1423+
for i in 1 2 3; do apt-get install -y --fix-missing build-essential git cmake qtbase5-dev qtbase5-private-dev qtchooser qt5-qmake qtbase5-dev-tools qttools5-dev-tools libqt5svg5-dev qtmultimedia5-dev libqt5charts5-dev qtpositioning5-dev qtconnectivity5-dev libqt5websockets5-dev libqt5texttospeech5-dev libqt5bluetooth5 libqt5networkauth5-dev qml-module-qtlocation qml-module-qtpositioning qtlocation5-dev libqt5quickcontrols2-5 qtquickcontrols2-5-dev qml-module-qtquick-controls2 qtbase5-dev libqt5sql5-sqlite libqt5sql5 libqt5sql5-mysql libqt5sql5-psql && break || sleep 5; done &&
1424+
export QT_SELECT=qt5 &&
1425+
export PATH=/usr/lib/qt5/bin:\$PATH &&
1426+
sed -i '/QtHttpServer/d' qdomyos-zwift.pro &&
1427+
find src -type f \( -name '*.cpp' -o -name '*.h' \) -exec sed -i 's/#include <QtHttpServer/\/\/#include <QtHttpServer/' {} + &&
1428+
find src -type f \( -name '*.cpp' -o -name '*.h' \) -exec sed -i 's/QHttpServer/\/\/QHttpServer/' {} + &&
1429+
cat qdomyos-zwift.pro &&
1430+
qmake &&
1431+
make -j\$(nproc)
14121432
"
14131433
14141434
- name: Rename binary
1415-
run: mv src/qdomyos-zwift src/qdomyos-zwift-32bit
1416-
1435+
run: mv src/qdomyos-zwift src/qdomyos-zwift-armv6hf
1436+
1437+
- name: Verify 32-bit binary targets ARMv6
1438+
run: |
1439+
ARCH=$(readelf -A src/qdomyos-zwift-armv6hf | grep 'Tag_CPU_arch:' | awk '{print $2}')
1440+
echo "Binary CPU architecture: $ARCH"
1441+
if [ "$ARCH" != "v6" ]; then
1442+
echo "::error::Binary targets $ARCH but must target v6 for Pi Zero W compatibility"
1443+
exit 1
1444+
fi
1445+
14171446
- name: Archive Raspberry Pi binary
14181447
uses: actions/upload-artifact@v4
14191448
with:
1420-
name: raspberry-pi-binary
1421-
path: src/qdomyos-zwift-32bit
1449+
name: raspberry-pi-binary-armv6hf
1450+
path: src/qdomyos-zwift-armv6hf
14221451

14231452
raspberry-pi-build-and-image-64bit:
14241453
runs-on: ubuntu-22.04-arm
@@ -1443,30 +1472,141 @@ jobs:
14431472
echo "#define LICENSE" >> secret.h
14441473
cd ..
14451474
1446-
- name: Install Qt5 build dependencies
1447-
run: |
1448-
sudo apt-get update
1449-
sudo apt-get install -y build-essential git cmake qtbase5-dev qtbase5-private-dev qtchooser qt5-qmake qtbase5-dev-tools qttools5-dev-tools libqt5svg5-dev qtmultimedia5-dev libqt5charts5-dev qtpositioning5-dev qtconnectivity5-dev libqt5websockets5-dev libqt5texttospeech5-dev libqt5bluetooth5 libqt5networkauth5-dev qml-module-qtlocation qml-module-qtpositioning qtlocation5-dev libqt5quickcontrols2-5 qtquickcontrols2-5-dev qml-module-qtquick-controls2 qtbase5-dev libqt5sql5-sqlite libqt5sql5 libqt5sql5-mysql libqt5sql5-psql
1450-
14511475
- name: Build for Raspberry Pi 64-bit
1452-
run: |
1453-
export QT_SELECT=qt5
1454-
export PATH=/usr/lib/qt5/bin:$PATH
1455-
sed -i '/QtHttpServer/d' qdomyos-zwift.pro
1456-
find src -type f \( -name '*.cpp' -o -name '*.h' \) -exec sed -i 's/#include <QtHttpServer/\/\/#include <QtHttpServer/' {} +
1457-
find src -type f \( -name '*.cpp' -o -name '*.h' \) -exec sed -i 's/QHttpServer/\/\/QHttpServer/' {} +
1458-
cat qdomyos-zwift.pro
1459-
qmake
1460-
make -j$(nproc)
1476+
uses: docker://arm64v8/debian:bullseye-20241016
1477+
with:
1478+
args: >
1479+
bash -c "
1480+
set -ex &&
1481+
for i in 1 2 3; do apt-get update && break || sleep 5; done &&
1482+
for i in 1 2 3; do apt-get install -y --fix-missing build-essential git cmake qtbase5-dev qtbase5-private-dev qtchooser qt5-qmake qtbase5-dev-tools qttools5-dev-tools libqt5svg5-dev qtmultimedia5-dev libqt5charts5-dev qtpositioning5-dev qtconnectivity5-dev libqt5websockets5-dev libqt5texttospeech5-dev libqt5bluetooth5 libqt5networkauth5-dev qml-module-qtlocation qml-module-qtpositioning qtlocation5-dev libqt5quickcontrols2-5 qtquickcontrols2-5-dev qml-module-qtquick-controls2 qtbase5-dev libqt5sql5-sqlite libqt5sql5 libqt5sql5-mysql libqt5sql5-psql && break || sleep 5; done &&
1483+
export QT_SELECT=qt5 &&
1484+
export PATH=/usr/lib/qt5/bin:$PATH &&
1485+
cd /github/workspace &&
1486+
sed -i '/QtHttpServer/d' qdomyos-zwift.pro &&
1487+
find src -type f \( -name '*.cpp' -o -name '*.h' \) -exec sed -i 's/#include <QtHttpServer/\/\/#include <QtHttpServer/' {} + &&
1488+
find src -type f \( -name '*.cpp' -o -name '*.h' \) -exec sed -i 's/QHttpServer/\/\/QHttpServer/' {} + &&
1489+
cat qdomyos-zwift.pro &&
1490+
qmake &&
1491+
make -j$(nproc)
1492+
"
14611493
14621494
- name: Rename binary
1463-
run: mv src/qdomyos-zwift src/qdomyos-zwift-64bit
1495+
run: mv src/qdomyos-zwift src/qdomyos-zwift-aarch64
1496+
1497+
- name: Verify 64-bit binary glibc compatibility
1498+
run: |
1499+
MAX_GLIBC=$(readelf -V src/qdomyos-zwift-aarch64 | grep -oP 'GLIBC_\K[0-9.]+' | sort -V | tail -1)
1500+
echo "Max required GLIBC version: $MAX_GLIBC"
1501+
if dpkg --compare-versions "$MAX_GLIBC" gt "2.31"; then
1502+
echo "::error::Binary requires GLIBC $MAX_GLIBC but Raspberry Pi OS Bullseye only has 2.31"
1503+
exit 1
1504+
fi
14641505
14651506
- name: Archive Raspberry Pi 64bit binary
14661507
uses: actions/upload-artifact@v4
14671508
with:
1468-
name: raspberry-pi-binary-64bit
1469-
path: src/qdomyos-zwift-64bit
1509+
name: raspberry-pi-binary-aarch64
1510+
path: src/qdomyos-zwift-aarch64
1511+
1512+
raspberry-pi-smoke-test:
1513+
runs-on: ubuntu-22.04-arm
1514+
needs: [raspberry-pi-build, raspberry-pi-build-and-image-64bit]
1515+
1516+
steps:
1517+
- name: Download armv6hf binary
1518+
uses: actions/download-artifact@v4
1519+
with:
1520+
name: raspberry-pi-binary-armv6hf
1521+
path: bin32
1522+
1523+
- name: Download aarch64 binary
1524+
uses: actions/download-artifact@v4
1525+
with:
1526+
name: raspberry-pi-binary-aarch64
1527+
path: bin64
1528+
1529+
- name: Set up QEMU
1530+
uses: docker/setup-qemu-action@v3
1531+
1532+
- name: ELF metadata checks for armv6hf binary (Pi Zero W)
1533+
timeout-minutes: 5
1534+
run: |
1535+
chmod +x bin32/qdomyos-zwift-armv6hf
1536+
file bin32/qdomyos-zwift-armv6hf
1537+
readelf -A bin32/qdomyos-zwift-armv6hf | grep -E 'Tag_CPU|Tag_FP|Tag_ABI'
1538+
1539+
# Check CPU architecture is ARMv6
1540+
ARCH=$(readelf -A bin32/qdomyos-zwift-armv6hf | grep 'Tag_CPU_arch:' | awk '{print $2}')
1541+
if [ "$ARCH" != "v6" ]; then
1542+
echo "::error::Binary targets $ARCH, expected v6"
1543+
exit 1
1544+
fi
1545+
echo "PASS: CPU architecture is ARMv6"
1546+
1547+
# Check hard-float ABI
1548+
VFP=$(readelf -A bin32/qdomyos-zwift-armv6hf | grep 'Tag_ABI_VFP_args:' | awk '{print $2}')
1549+
if [ "$VFP" != "VFP" ]; then
1550+
echo "::error::Binary does not use hard-float ABI (Tag_ABI_VFP_args: $VFP)"
1551+
exit 1
1552+
fi
1553+
echo "PASS: Hard-float ABI (VFP registers)"
1554+
1555+
# Check dynamic linker / interpreter path
1556+
INTERP=$(readelf -l bin32/qdomyos-zwift-armv6hf | grep 'interpreter:' | grep -oP '/[^\]]+')
1557+
if [ "$INTERP" != "/lib/ld-linux-armhf.so.3" ]; then
1558+
echo "::error::Wrong interpreter: $INTERP, expected /lib/ld-linux-armhf.so.3"
1559+
exit 1
1560+
fi
1561+
echo "PASS: Interpreter is /lib/ld-linux-armhf.so.3"
1562+
1563+
echo "SUCCESS: All armv6hf ELF metadata checks passed"
1564+
1565+
- name: Runtime smoke test armv6hf binary
1566+
timeout-minutes: 10
1567+
continue-on-error: true
1568+
run: |
1569+
docker run --rm --platform linux/arm/v6 \
1570+
-v ${{ github.workspace }}/bin32:/app \
1571+
balenalib/raspberry-pi-debian:bullseye-run \
1572+
bash -c "
1573+
for i in 1 2 3; do apt-get update && break || sleep 5; done
1574+
for i in 1 2 3; do apt-get install -y --no-install-recommends \
1575+
libqt5core5a libqt5bluetooth5 libqt5charts5 libqt5widgets5 \
1576+
libqt5network5 libqt5websockets5 libqt5xml5 libqt5sql5 \
1577+
libqt5multimedia5 libqt5positioning5 libqt5qml5 libqt5quick5 \
1578+
libqt5texttospeech5 libqt5networkauth5 libqt5gui5 || true && break || sleep 5; done
1579+
OUTPUT=\$(/app/qdomyos-zwift-armv6hf -no-gui -smoke-test 2>&1) || true
1580+
echo \"\$OUTPUT\"
1581+
if echo \"\$OUTPUT\" | grep -q 'SMOKE_OK'; then
1582+
echo 'SUCCESS: armv6hf binary runs and Qt runtime loads'
1583+
else
1584+
echo '::error::armv6hf runtime smoke test failed - SMOKE_OK not found'
1585+
exit 1
1586+
fi
1587+
"
1588+
1589+
- name: Runtime smoke test aarch64 binary (ARM64 Bullseye - Pi 3/4/5)
1590+
timeout-minutes: 10
1591+
run: |
1592+
chmod +x bin64/qdomyos-zwift-aarch64
1593+
docker run --rm -v ${{ github.workspace }}/bin64:/app \
1594+
arm64v8/debian:bullseye-20241016 \
1595+
bash -c "
1596+
apt-get update && apt-get install -y --no-install-recommends \
1597+
libqt5core5a libqt5bluetooth5 libqt5charts5 libqt5widgets5 \
1598+
libqt5network5 libqt5websockets5 libqt5xml5 libqt5sql5 \
1599+
libqt5multimedia5 libqt5positioning5 libqt5qml5 libqt5quick5 \
1600+
libqt5texttospeech5 libqt5networkauth5 libqt5gui5 || true
1601+
OUTPUT=\$(/app/qdomyos-zwift-aarch64 -no-gui -smoke-test 2>&1) || true
1602+
echo \"\$OUTPUT\"
1603+
if echo \"\$OUTPUT\" | grep -q 'SMOKE_OK'; then
1604+
echo 'SUCCESS: aarch64 binary runs on ARM64 Bullseye'
1605+
else
1606+
echo '::error::aarch64 runtime smoke test failed - SMOKE_OK not found'
1607+
exit 1
1608+
fi
1609+
"
14701610
14711611
window-msvc2022-build:
14721612
runs-on: windows-latest
@@ -1985,7 +2125,7 @@ jobs:
19852125
permissions: write-all
19862126
runs-on: ubuntu-22.04
19872127
if: github.event_name == 'schedule'
1988-
needs: [window-msvc2019-build, window-msvc2022-build, window-build, android-build, raspberry-pi-build, nordictrack-build, peloton-bike-plus-build, peloton-bike-build, raspberry-pi-build-and-image-64bit]
2128+
needs: [window-msvc2019-build, window-msvc2022-build, window-build, android-build, raspberry-pi-build, nordictrack-build, peloton-bike-plus-build, peloton-bike-build, raspberry-pi-build-and-image-64bit, raspberry-pi-smoke-test]
19892129
steps:
19902130
- name: Checkout code
19912131
uses: actions/checkout@v2
@@ -2026,7 +2166,8 @@ jobs:
20262166
- **nordictrack-rower-android-trial**: Nordictrack Rower build for iFIT2 Tablets
20272167
- **peloton-bike-plus-android-trial**: Peloton Bike Plus build with Grupetto backend
20282168
- **peloton-bike-android-trial**: Peloton Bike build with Grupetto backend
2029-
- **raspberry-pi-binary**: Raspberry Pi build
2169+
- **raspberry-pi-binary-armv6hf**: Raspberry Pi 32-bit (ARMv6 hard-float, Pi Zero W compatible)
2170+
- **raspberry-pi-binary-aarch64**: Raspberry Pi 64-bit (AArch64, Pi 3/4/5)
20302171
20312172
__Please help us improve QZ by reporting any issues you encounter!__ :wink:
20322173
files: |
@@ -2043,5 +2184,5 @@ jobs:
20432184
nordictrack-rower-android-trial/android-debug-nordictrack-rower.apk
20442185
peloton-bike-plus-android-trial/android-debug-peloton-bike-plus.apk
20452186
peloton-bike-android-trial/android-debug-peloton-bike.apk
2046-
raspberry-pi-binary/qdomyos-zwift-32bit
2047-
raspberry-pi-binary/qdomyos-zwift-64bit
2187+
raspberry-pi-binary-armv6hf/qdomyos-zwift-armv6hf
2188+
raspberry-pi-binary-aarch64/qdomyos-zwift-aarch64

CLAUDE.md renamed to AGENTS.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
# CLAUDE.md
2-
3-
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4-
51
## Project Overview
62

73
QDomyos-Zwift is a Qt-based application that bridges fitness equipment (treadmills, bikes, ellipticals, rowers) with virtual training platforms like Zwift. It acts as a Bluetooth intermediary, connecting physical equipment to fitness apps while providing enhanced features like Peloton integration, power zone training, and workout programs.
@@ -470,4 +466,4 @@ These are typically updated via Xcode IDE rather than manually editing files.
470466
## Additional Memories
471467

472468
- When adding a new setting in QML (setting-tiles.qml), you must:
473-
* Add the property at the END of the properties list
469+
* Add the property at the END of the properties list

src/WebPelotonAuth.qml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,30 @@ Item {
1414
height: parent.height
1515
width: parent.width
1616
visible: true
17+
property bool callbackHandled: false
1718

1819
// Signal to notify the parent stack when we want to go back
1920
signal goBack()
2021

2122
WebView {
23+
id: pelotonWebView
2224
anchors.fill: parent
2325
height: parent.height
2426
width: parent.width
2527
visible: !rootItem.pelotonPopupVisible
2628
url: rootItem.getPelotonAuthUrl
29+
30+
onLoadingChanged: {
31+
if (callbackHandled || !loadRequest.url) {
32+
return;
33+
}
34+
35+
var currentUrl = loadRequest.url.toString()
36+
if (currentUrl.indexOf("https://www.qzfitness.com/peloton/callback") === 0) {
37+
callbackHandled = true
38+
rootItem.handleOAuthCallbackFromQml(currentUrl)
39+
}
40+
}
2741
}
2842

2943
Popup {
@@ -33,6 +47,8 @@ Item {
3347
onEnabledChanged: { if(rootItem.pelotonPopupVisible) popupPelotonConnectedWeb.open() }
3448
onClosed: {
3549
rootItem.pelotonPopupVisible = false;
50+
callbackHandled = false;
51+
pelotonWebView.url = "";
3652
// Attempt to go back to the previous view after the popup is closed
3753
goBack();
3854
}
@@ -76,5 +92,6 @@ Item {
7692
// Component is being completed
7793
Component.onCompleted: {
7894
console.log("WebPelotonAuth loaded")
95+
callbackHandled = false
7996
}
8097
}

src/android/AndroidManifest.xml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<?xml version="1.0"?>
2-
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionName="2.20.27" android:versionCode="1299" android:installLocation="auto">
2+
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionName="2.20.30" android:versionCode="1310" android:installLocation="auto">
33
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
44
Remove the comment if you do not require these default permissions. -->
55
<!-- %%INSERT_PERMISSIONS -->
@@ -18,6 +18,15 @@
1818
<intent-filter>
1919
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
2020
</intent-filter>
21+
<intent-filter android:autoVerify="true">
22+
<action android:name="android.intent.action.VIEW" />
23+
<category android:name="android.intent.category.DEFAULT" />
24+
<category android:name="android.intent.category.BROWSABLE" />
25+
<data
26+
android:scheme="https"
27+
android:host="www.qzfitness.com"
28+
android:pathPrefix="/peloton/callback" />
29+
</intent-filter>
2130
<meta-data
2231
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
2332
android:resource="@xml/device_filter" />

0 commit comments

Comments
 (0)