From 0ee71d8fcfbdbc38b78fad5d42a0fe3abc43395e Mon Sep 17 00:00:00 2001 From: Mike Tolkachev Date: Tue, 27 May 2025 20:05:56 -0300 Subject: [PATCH 1/8] Switch f469-disco to fork with upgraded MicroPython --- .gitmodules | 3 ++- f469-disco | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 1066f1d7..508d6964 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,7 @@ [submodule "f469-disco"] path = f469-disco - url = https://github.com/diybitcoinhardware/f469-disco + url = https://github.com/miketlk/f469-disco.git + branch = micropython-upgrade [submodule "bootloader"] path = bootloader url = https://github.com/cryptoadvance/specter-bootloader \ No newline at end of file diff --git a/f469-disco b/f469-disco index db3ce3e9..620a7944 160000 --- a/f469-disco +++ b/f469-disco @@ -1 +1 @@ -Subproject commit db3ce3e918cf0fd36f076ecd86ef05240d7c3cef +Subproject commit 620a7944126bf9d0d53b755f534c2fd7facdb651 From 8c8c6dbaa3c9af8a32113d61eebd2c20095903b7 Mon Sep 17 00:00:00 2001 From: Mike Tolkachev Date: Sun, 8 Jun 2025 15:49:40 -0300 Subject: [PATCH 2/8] Update Makefile for Micropython v1.25 (Specter adaptation) Fix missing inclusion of custom MicroPython config in Unix build fix Makefile issues with clean and unix targets --- Makefile | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 18a6a8f2..eb147e48 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ mpy-cross: $(TARGET_DIR) $(MPY_DIR)/mpy-cross/Makefile make -C $(MPY_DIR)/mpy-cross \ DEBUG=$(DEBUG) \ CFLAGS_EXTRA="$(MPY_CFLAGS)" && \ - cp $(MPY_DIR)/mpy-cross/mpy-cross $(TARGET_DIR) + cp $(MPY_DIR)/mpy-cross/build/mpy-cross $(TARGET_DIR) # embed git metadata for firmware builds .PHONY: git-info @@ -39,13 +39,18 @@ git-info: disco: $(TARGET_DIR) mpy-cross $(MPY_DIR)/ports/stm32 git-info @echo Building firmware make -C $(MPY_DIR)/ports/stm32 \ - BOARD=$(BOARD) \ - FLAVOR=$(FLAVOR) \ - USE_DBOOT=$(USE_DBOOT) \ - USER_C_MODULES=$(USER_C_MODULES) \ - FROZEN_MANIFEST=$(FROZEN_MANIFEST_DISCO) \ - DEBUG=$(DEBUG) \ - CFLAGS_EXTRA="$(MPY_CFLAGS)" && \ + BOARD=$(BOARD) \ + FLAVOR=$(FLAVOR) \ + USE_DBOOT=$(USE_DBOOT) \ + USER_C_MODULES=$(USER_C_MODULES) \ + FROZEN_MANIFEST=$(FROZEN_MANIFEST_DISCO) \ + CFLAGS_EXTRA="-DMP_CONFIGFILE=\"\" $(MPY_CFLAGS)" + DEBUG=$(DEBUG) && \ + arm-none-eabi-objcopy -O binary \ + $(MPY_DIR)/ports/stm32/build-STM32F469DISC/firmware.elf \ + $(TARGET_DIR)/specter-diy.bin && \ + cp $(MPY_DIR)/ports/stm32/build-STM32F469DISC/firmware.hex \ + $(TARGET_DIR)/specter-diy.hex arm-none-eabi-objcopy -O binary \ $(MPY_DIR)/ports/stm32/build-STM32F469DISC/firmware.elf \ $(TARGET_DIR)/specter-diy.bin && \ @@ -76,8 +81,8 @@ unix: $(TARGET_DIR) mpy-cross $(MPY_DIR)/ports/unix git-info make -C $(MPY_DIR)/ports/unix \ USER_C_MODULES=$(USER_C_MODULES) \ FROZEN_MANIFEST=$(FROZEN_MANIFEST_UNIX) \ - CFLAGS_EXTRA="$(MPY_CFLAGS)" && \ - cp $(MPY_DIR)/ports/unix/micropython $(TARGET_DIR)/micropython_unix + CFLAGS_EXTRA="-DMP_CONFIGFILE=\"\" $(MPY_CFLAGS)" && \ + cp $(MPY_DIR)/ports/unix/build-standard/micropython $(TARGET_DIR)/micropython_unix simulate: unix $(TARGET_DIR)/micropython_unix simulate.py @@ -90,6 +95,7 @@ all: mpy-cross disco unix clean: rm -rf $(TARGET_DIR) make -C $(MPY_DIR)/mpy-cross clean + rm -rf $(MPY_DIR)/mpy-cross/build make -C $(MPY_DIR)/ports/unix \ USER_C_MODULES=$(USER_C_MODULES) \ FROZEN_MANIFEST=$(FROZEN_MANIFEST_UNIX) clean From 9626da08b815d6eb214a25a3c0ab6f15498ca700 Mon Sep 17 00:00:00 2001 From: Mike Tolkachev Date: Sun, 8 Jun 2025 15:50:31 -0300 Subject: [PATCH 3/8] Update f469-disco --- docs/build.md | 10 +++++++++- f469-disco | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/build.md b/docs/build.md index 7e71241b..c3374972 100644 --- a/docs/build.md +++ b/docs/build.md @@ -75,8 +75,16 @@ to avoid warnings being raised as errors. **MacOS**: ```sh -brew tap ArmMbed/homebrew-formulae brew install arm-none-eabi-gcc +brew install sdl2 +``` + +To make the SDL2 library available to your C/C++ toolchain, ensure that Homebrew’s include and library paths are added to the compiler and linker flags. If they are not already set, you can add the following lines to your shell profile (commonly `~/.zprofile` or `~/.bash_profile`) or configure them in another way before making the Unix build: + +```sh +export LDFLAGS="-L/opt/homebrew/lib $LDFLAGS" +export CPPFLAGS="-I/opt/homebrew/include $CPPFLAGS" +export CFLAGS="-I/opt/homebrew/include $CFLAGS" ``` On **Windows**: Install linux subsystem and follow Linux instructions. diff --git a/f469-disco b/f469-disco index 620a7944..a27e98b4 160000 --- a/f469-disco +++ b/f469-disco @@ -1 +1 @@ -Subproject commit 620a7944126bf9d0d53b755f534c2fd7facdb651 +Subproject commit a27e98b430dc2f7ebe7f3bd3b2ad003a0d150637 From 08a69a18c678d29d7f9fa3ffd95fbe1941f897ea Mon Sep 17 00:00:00 2001 From: Mike Tolkachev Date: Thu, 11 Sep 2025 13:32:48 -0300 Subject: [PATCH 4/8] Fix build doc for macOS: move SDL2 to the Simulator section Add VcXsrv to build doc and improve structure Update build instructions: avoid conflicts with Homebrew's includes --- docs/build.md | 58 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/docs/build.md b/docs/build.md index c3374972..fef821c6 100644 --- a/docs/build.md +++ b/docs/build.md @@ -61,53 +61,87 @@ You'll need to have [Nix](https://nixos.org/) installed as well. To compile the firmware for the board you will need `arm-none-eabi-gcc` compiler. -**Debian/Ubuntu**: +#### Debian/Ubuntu + ```sh sudo apt-get install build-essential gcc-arm-none-eabi binutils-arm-none-eabi gdb-multiarch openocd ``` -**Archlinux**: +#### Archlinux + ```sh sudo pacman -S arm-none-eabi-gcc arm-none-eabi-binutils openocd base-devel python-case ``` You might need change default gcc flag settings with `CFLAGS_EXTRA="-w"`. Export it or set the variable before of `make` to avoid warnings being raised as errors. -**MacOS**: +#### MacOS + ```sh brew install arm-none-eabi-gcc -brew install sdl2 ``` -To make the SDL2 library available to your C/C++ toolchain, ensure that Homebrew’s include and library paths are added to the compiler and linker flags. If they are not already set, you can add the following lines to your shell profile (commonly `~/.zprofile` or `~/.bash_profile`) or configure them in another way before making the Unix build: +#### Windows + +Install the Linux subsystem and follow Linux instructions. The recommended distribution as of this writing is **Ubuntu 22.04 LTS**. It can be installed using the following command in the **administrator's PowerShell**: ```sh -export LDFLAGS="-L/opt/homebrew/lib $LDFLAGS" -export CPPFLAGS="-I/opt/homebrew/include $CPPFLAGS" -export CFLAGS="-I/opt/homebrew/include $CFLAGS" +# Install Ubuntu 22.04 as WSL Linux environment +wsl --install -d Ubuntu-22.04 +# Start new instance of Ubuntu 22.04 (if multiple distros are present in system) +wsl -d Ubuntu-22.04 ``` -On **Windows**: Install linux subsystem and follow Linux instructions. +To install all the dependencies on Windows, make sure the **universe** repository is enabled and the package list is up to date. If not, here is a quick fix: + +```sh +# Uncomment all lines in /etc/apt/sources.list that begin with # deb +sudo sed -i 's/^# deb/deb/' /etc/apt/sources.list +# Enable universe repository +sudo add-apt-repository universe +# Update package list +sudo apt update +``` ### Prerequisities (Manually): Simulator You may need to install SDL2 library to simulate the screen of the device. -**Linux**: +#### Linux + ```sh sudo apt install libsdl2-dev ``` -**MacOS**: +#### MacOS + ```sh brew install sdl2 ``` -**Windows**: +To make the SDL2 library available to your C/C++ toolchain, ensure that Homebrew’s include and library paths are added to the compiler and linker flags. If they are not already set, you can add the following lines to your shell profile (commonly `~/.zprofile` or `~/.bash_profile`) or configure them in another way before making the Unix build: + +```sh +export LDFLAGS="-L/opt/homebrew/lib $LDFLAGS" +export CPPFLAGS="-idirafter /opt/homebrew/include $CPPFLAGS" +export CFLAGS="-idirafter /opt/homebrew/include $CFLAGS" +``` + +#### Windows + - `sudo apt install libsdl2-dev` on Linux side. - install and launch [Xming](https://sourceforge.net/projects/xming/) on Windows side - set `export DISPLAY=:0` on linux part +[VcXsrv](https://github.com/marchaesen/vcxsrv) is another viable option for running X11 applications on Windows. Here is an example environment that should be configured on the WSL side: + +```sh +export LIBGL_ALWAYS_INDIRECT=0 +export LIBGL_ALWAYS_SOFTWARE=true +export DISPLAY=127.0.0.1:0 +export XDG_RUNTIME_DIR=/mnt/wslg/runtime-dir +``` + ## Build All build commands might need a prefix like `nix develop -c` or need to be run from `nix develop` shell. If you use direnv, you don't need to do anything, apart from an initial `direnv allow`. From 3a8815c466f040fdf22d46c691dd0dfbd0fd7727 Mon Sep 17 00:00:00 2001 From: Mike Tolkachev Date: Tue, 16 Sep 2025 18:13:54 -0300 Subject: [PATCH 5/8] Update f469-disco --- f469-disco | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/f469-disco b/f469-disco index a27e98b4..5daf4082 160000 --- a/f469-disco +++ b/f469-disco @@ -1 +1 @@ -Subproject commit a27e98b430dc2f7ebe7f3bd3b2ad003a0d150637 +Subproject commit 5daf4082185f1f15c1f737ff3331729fcda4a7b8 From 23ef16c091bb5331aadaa7a567136201d52c3c45 Mon Sep 17 00:00:00 2001 From: Mike Tolkachev Date: Tue, 16 Sep 2025 18:23:11 -0300 Subject: [PATCH 6/8] Upgrade Docker container to Arm GNU Toolchain v14.3.rel1 and Python v3.9.23 --- Dockerfile | 55 ++++++++++++++++++++++++++++---------- docs/reproducible-build.md | 15 +++++++++++ 2 files changed, 56 insertions(+), 14 deletions(-) diff --git a/Dockerfile b/Dockerfile index 16ec1063..689cf916 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,23 +1,50 @@ -FROM python:3.9.15@sha256:b5f024fa682187ef9305a2b5d2c4bb583bef83356259669fc80273bb2222f5ed -ENV LANG=C.UTF-8 +# syntax=docker/dockerfile:1.6 +FROM python:3.9.23-bookworm@sha256:dc01447eea126f97459cbcb0e52a5863fcc84ff53462650ae5a28277c175f49d +ENV LANG=C.UTF-8 ARG DEBIAN_FRONTEND=noninteractive +ARG TOOLCHAIN_VER=14.3.rel1 +ARG TARGETARCH +ARG TARGET_DIR="/opt/arm-toolchain" + +# Minimal deps for download/verify/extract +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates curl xz-utils grep coreutils \ + && rm -rf /var/lib/apt/lists/* -# ARM Embedded Toolchain -# Integrity is checked using the MD5 checksum provided by ARM at https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads -RUN curl -sSfL -o arm-toolchain.tar.bz2 "https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2020q2/gcc-arm-none-eabi-9-2020-q2-update-x86_64-linux.tar.bz2?revision=05382cca-1721-44e1-ae19-1e7c3dc96118&rev=05382cca172144e1ae191e7c3dc96118&hash=3ACFE672E449EBA7A21773EE284A88BC7DFA5044" && \ - echo 2b9eeccc33470f9d3cda26983b9d2dc6 arm-toolchain.tar.bz2 > /tmp/arm-toolchain.md5 && \ - md5sum --check /tmp/arm-toolchain.md5 && rm /tmp/arm-toolchain.md5 && \ - tar xf arm-toolchain.tar.bz2 -C /opt && \ - rm arm-toolchain.tar.bz2 +# Default to Arm's blob storage mirror +ARG TOOLCHAIN_MIRROR=armkeil.blob.core.windows.net/developer/Files/downloads/gnu -# Adding GCC to PATH and defining rustup/cargo home directories -ENV PATH=/opt/gcc-arm-none-eabi-9-2020-q2-update/bin:$PATH +# Pin Arm GNU Toolchain per-arch with SHA256 sums +ENV TOOLCHAIN_SHA256_AMD64=8f6903f8ceb084d9227b9ef991490413014d991874a1e34074443c2a72b14dbd +ENV TOOLCHAIN_SHA256_ARM64=2d465847eb1d05f876270494f51034de9ace9abe87a4222d079f3360240184d3 -# Installing python requirements +# Arm GNU Toolchain (arm-none-eabi), host-aware, SHA-256 verified, cached download +RUN --mount=type=cache,target=/root/.cache \ + set -eux; \ + case "$TARGETARCH" in \ + amd64) host="x86_64"; TOOLCHAIN_SHA256="$TOOLCHAIN_SHA256_AMD64" ;; \ + arm64) host="aarch64"; TOOLCHAIN_SHA256="$TOOLCHAIN_SHA256_ARM64" ;; \ + *) echo "Unsupported arch: $TARGETARCH" && exit 1 ;; \ + esac; \ + file="arm-gnu-toolchain-${TOOLCHAIN_VER}-${host}-arm-none-eabi.tar.xz"; \ + url="https://${TOOLCHAIN_MIRROR}/${TOOLCHAIN_VER}/binrel/${file}"; \ + dest="/root/.cache/$file"; \ + if [ ! -f "$dest" ]; then \ + echo "Downloading $url"; \ + curl -fSL --retry 5 --retry-all-errors -C - -o "$dest" "$url"; \ + else \ + echo "Using cached $dest"; \ + fi; \ + echo "${TOOLCHAIN_SHA256} $dest" | sha256sum -c -; \ + tar -xJf "$dest" -C /opt; \ + ln -s /opt/arm-gnu-toolchain-${TOOLCHAIN_VER}-${host}-arm-none-eabi ${TARGET_DIR} + +ENV PATH="${TARGET_DIR}/bin:${PATH}" + +# Python deps COPY bootloader/tools/requirements.txt . -RUN pip3 install -r requirements.txt +RUN python -m pip install --no-cache-dir -r requirements.txt WORKDIR /app - CMD ["/usr/bin/env", "bash", "./build_firmware.sh"] diff --git a/docs/reproducible-build.md b/docs/reproducible-build.md index facc29ec..0028bcd9 100644 --- a/docs/reproducible-build.md +++ b/docs/reproducible-build.md @@ -45,3 +45,18 @@ For Apple M1 add a platform flag to the docker commands: docker build -t diy . --platform linux/x86_64 docker run --platform linux/amd64 -ti -v `pwd`:/app -e HOST_UID=$(id -u) -e HOST_GID=$(id -g) diy ``` + +# Simplified build without signing + +If you just need a HEX file without bootloader and signatures you can use this command: + +```sh +docker run -ti -v `pwd`:/app diy bash -c "make clean && make disco" +``` + +This will generate the binaries of the main firmware, which can be flashed into the Discovery board via ST-LINK or ROM bootloader. + +```txt +bin/specter-diy.bin +bin/specter-diy.hex +``` From 37bd7ec07af2b0451b974034c764da0008dd79e1 Mon Sep 17 00:00:00 2001 From: Mike Tolkachev Date: Mon, 13 Oct 2025 18:01:22 -0300 Subject: [PATCH 7/8] Update f469-disco --- .beads/issues.jsonl | 8 ++++++++ .gitmodules | 2 +- f469-disco | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 .beads/issues.jsonl diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl new file mode 100644 index 00000000..235dcd3f --- /dev/null +++ b/.beads/issues.jsonl @@ -0,0 +1,8 @@ +{"id":"specter-diy.new-6bg","title":"Fix C: Remove /qspi from sys.path in main.c","description":"Comment out /qspi and /qspi/lib from sys.path in stm32/main.c. Eliminates shadowing but users cant add modules to QSPI.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T18:52:46.875253+01:00","updated_at":"2025-12-19T19:27:55.312347+01:00","closed_at":"2025-12-19T19:27:55.312347+01:00","close_reason":"FAILED - CWD still /qspi, '' in sys.path still shadows"} +{"id":"specter-diy.new-9qr","title":"QSPI dirs shadow frozen modules after MicroPython upgrade","description":"New MicroPython uses .frozen as sys.path entry instead of absolute priority. CWD defaults to /qspi, so '' in sys.path finds /qspi/hosts before .frozen/hosts. Settings dirs shadow frozen modules. See .history/lvgl9-migration-lessons.md for details. Recommended fix: rename /qspi/hosts to /qspi/settings/hosts","status":"closed","priority":1,"issue_type":"bug","created_at":"2025-12-19T18:47:45.871674+01:00","updated_at":"2025-12-19T19:30:24.589192+01:00","closed_at":"2025-12-19T19:30:24.589192+01:00","close_reason":"RESOLVED: Fix D implemented - .frozen first in sys.path. Tested all 4 fixes: A(fail), B(works+migration), C(fail), D(works)","dependencies":[{"issue_id":"specter-diy.new-9qr","depends_on_id":"specter-diy.new-gxv","type":"blocks","created_at":"2025-12-19T18:52:53.130203+01:00","created_by":"daemon"},{"issue_id":"specter-diy.new-9qr","depends_on_id":"specter-diy.new-bkl","type":"blocks","created_at":"2025-12-19T18:52:53.240199+01:00","created_by":"daemon"},{"issue_id":"specter-diy.new-9qr","depends_on_id":"specter-diy.new-6bg","type":"blocks","created_at":"2025-12-19T18:52:53.350989+01:00","created_by":"daemon"},{"issue_id":"specter-diy.new-9qr","depends_on_id":"specter-diy.new-kp3","type":"blocks","created_at":"2025-12-19T18:52:53.460851+01:00","created_by":"daemon"}]} +{"id":"specter-diy.new-bkl","title":"Fix B: Rename settings dirs to /qspi/settings/*","description":"Change Host.SETTINGS_DIR and app paths from /qspi/hosts to /qspi/settings/hosts etc. Cleanest long-term fix.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T18:52:46.610554+01:00","updated_at":"2025-12-19T19:23:08.013163+01:00","closed_at":"2025-12-19T19:23:08.013163+01:00","close_reason":"SUCCESS - works but requires migrating old /qspi dirs"} +{"id":"specter-diy.new-dz2","title":"VFS filesystems not mounting (/flash, /qspi)","description":"sdram.RAMDevice.ioctl() returns None instead of 0 for init/erase. VfsFat.mkfs fails with 'can't convert NoneType to int'. Bug in f469-disco/usermods/sdram module.","status":"open","priority":1,"issue_type":"bug","created_at":"2025-12-19T19:49:19.007935+01:00","updated_at":"2025-12-19T19:53:28.074456+01:00"} +{"id":"specter-diy.new-gxv","title":"Fix A: Change CWD in boot.py to /flash","description":"Change os.chdir('/flash') in boot.py before pyb.main() to avoid /qspi being current dir. Already attempted, needs re-test.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T18:52:46.357046+01:00","updated_at":"2025-12-19T18:54:29.409761+01:00","closed_at":"2025-12-19T18:54:29.409761+01:00","close_reason":"Failed: moving CWD to /flash still shadows because /flash/keystore exists. The '' in sys.path always shadows if CWD has matching dirs."} +{"id":"specter-diy.new-kp3","title":"Fix D: Move .frozen first in sys.path in runtime.c","description":"Insert .frozen at index 0 instead of append in py/runtime.c. Frozen modules always win regardless of CWD.","status":"closed","priority":2,"issue_type":"task","created_at":"2025-12-19T18:52:47.141162+01:00","updated_at":"2025-12-19T19:15:17.619462+01:00","closed_at":"2025-12-19T19:15:17.619462+01:00","close_reason":"SUCCESS: Moving .frozen first in sys.path in runtime.c fixes the shadowing issue. Frozen modules now always take priority."} +{"id":"specter-diy.new-lp1","title":"QR scanner returns garbage data","description":"Scanner triggers/beeps/stops but data is binary garbage. PIN trigger mode at 9600 baud. May need different baud rate or scanner config.","status":"closed","priority":2,"issue_type":"bug","created_at":"2025-12-19T16:24:58.042665+01:00","updated_at":"2025-12-19T17:02:06.665561+01:00","closed_at":"2025-12-19T17:02:06.665561+01:00","close_reason":"Scanner works"} +{"id":"specter-diy.new-ok7","title":"Python code migration after MicroPython/LVGL 9.3.0 upgrade","description":"","status":"open","priority":1,"issue_type":"epic","created_at":"2025-12-19T16:24:40.788371+01:00","updated_at":"2025-12-19T16:24:40.788371+01:00","dependencies":[{"issue_id":"specter-diy.new-ok7","depends_on_id":"specter-diy.new-lp1","type":"blocks","created_at":"2025-12-19T16:25:03.594054+01:00","created_by":"daemon"}]} diff --git a/.gitmodules b/.gitmodules index 508d6964..b615da07 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "f469-disco"] path = f469-disco - url = https://github.com/miketlk/f469-disco.git + url = https://github.com/diybitcoinhardware/f469-disco.git branch = micropython-upgrade [submodule "bootloader"] path = bootloader diff --git a/f469-disco b/f469-disco index 5daf4082..2ccc2af1 160000 --- a/f469-disco +++ b/f469-disco @@ -1 +1 @@ -Subproject commit 5daf4082185f1f15c1f737ff3331729fcda4a7b8 +Subproject commit 2ccc2af1f8f0f015a064967a4628309f8c880d04 From 195a50cb358ad1bde6857a5213a80a7e383f5ec3 Mon Sep 17 00:00:00 2001 From: k9ert <117085+k9ert@users.noreply.github.com> Date: Fri, 19 Dec 2025 12:47:06 +0100 Subject: [PATCH 8/8] fix: LVGL 9.x migration for core GUI components an QR flows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - common.py: theme_default_init, style.set_* methods, screen_active() - core.py: display.init() before styles, screen_load() - async_gui.py: screen_active/load, delete_async - screen.py: align_to, style init/set methods - menu.py: align_to, obj replaces page, add_state - battery.py: transparent style via set_* methods - keyboard and qrcode - screen.py: align_to->align for parent, del_async->delete_async - specter.py: fix race in coro() - wait for scan to start - qr.py: verify baud rate switch, wait for popup close - hardwaretest.py: handle binary scan data gracefully - input.py - mnemonic.py - settings.py - transaction.py 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- boot/debug/hardwaretest.py | 6 +- src/gui/async_gui.py | 8 +- src/gui/common.py | 201 +++++++++++++++++---------------- src/gui/components/battery.py | 9 +- src/gui/components/keyboard.py | 31 ++--- src/gui/components/modal.py | 2 +- src/gui/components/qrcode.py | 54 +++++---- src/gui/core.py | 5 +- src/gui/decorators.py | 34 +++--- src/gui/screens/alert.py | 7 +- src/gui/screens/input.py | 22 ++-- src/gui/screens/menu.py | 16 +-- src/gui/screens/mnemonic.py | 36 +++--- src/gui/screens/progress.py | 6 +- src/gui/screens/prompt.py | 15 +-- src/gui/screens/qralert.py | 6 +- src/gui/screens/screen.py | 17 ++- src/gui/screens/settings.py | 14 +-- src/gui/screens/transaction.py | 36 +++--- src/gui/specter.py | 4 + src/hosts/qr.py | 4 + 21 files changed, 286 insertions(+), 247 deletions(-) diff --git a/boot/debug/hardwaretest.py b/boot/debug/hardwaretest.py index 6a80af2d..003801c8 100644 --- a/boot/debug/hardwaretest.py +++ b/boot/debug/hardwaretest.py @@ -52,7 +52,11 @@ async def main(self): await self.qr.enable() s = await self.qr.get_data() if s: - data = s.read().decode() + raw = s.read() + try: + data = raw.decode() + except: + data = repr(raw) await self.gui.alert("Here's what we scanned:", data) else: conn = get_connection() diff --git a/src/gui/async_gui.py b/src/gui/async_gui.py index 5d71d010..6a4cd282 100644 --- a/src/gui/async_gui.py +++ b/src/gui/async_gui.py @@ -46,10 +46,10 @@ def hide_loader(self): async def load_screen(self, scr): while self.background is not None: await asyncio.sleep_ms(10) - old_scr = lv.scr_act() - lv.scr_load(scr) + old_scr = lv.screen_active() + lv.screen_load(scr) self.scr = scr - old_scr.del_async() + old_scr.delete_async() async def open_popup(self, scr): # wait for another popup to finish @@ -57,7 +57,7 @@ async def open_popup(self, scr): await asyncio.sleep_ms(10) self.background = self.scr self.scr = scr - lv.scr_load(scr) + lv.screen_load(scr) async def close_popup(self): scr = self.background diff --git a/src/gui/common.py b/src/gui/common.py index 753d32b6..6b7a990f 100644 --- a/src/gui/common.py +++ b/src/gui/common.py @@ -1,6 +1,6 @@ """Some commonly used functions, like helpers""" import lvgl as lv -import qrcode +#import qrcode import math from micropython import const import gc @@ -14,125 +14,129 @@ def init_styles(dark=True): + # LVGL 9.x theme and style initialization + disp = lv.display_get_default() + if dark: - # Set theme - th = lv.theme_night_init(210, lv.font_roboto_22) - # adjusting theme - # background color cbg = lv.color_hex(0x192432) - # ctxt = lv.color_hex(0x7f8fa4) ctxt = lv.color_hex(0xFFFFFF) cbtnrel = lv.color_hex(0x506072) cbtnpr = lv.color_hex(0x405062) chl = lv.color_hex(0x313E50) + cprimary = lv.palette_main(lv.PALETTE.BLUE) + csecondary = lv.palette_main(lv.PALETTE.GREY) else: - # Set theme to light - # TODO: work in progress... - th = lv.theme_material_init(210, lv.font_roboto_22) - # adjusting theme - # background color cbg = lv.color_hex(0xEEEEEE) - # ctxt = lv.color_hex(0x7f8fa4) - ctxt = lv.color_hex(0) + ctxt = lv.color_hex(0x000000) cbtnrel = lv.color_hex(0x506072) cbtnpr = lv.color_hex(0x405062) chl = lv.color_hex(0x313E50) - th.style.label.sec.text.color = cbtnrel - th.style.scr.body.main_color = cbg - th.style.scr.body.grad_color = cbg - # text color - th.style.scr.text.color = ctxt - # buttons - # btn released - th.style.btn.rel.body.main_color = cbtnrel - th.style.btn.rel.body.grad_color = cbtnrel - th.style.btn.rel.body.shadow.width = 0 - th.style.btn.rel.body.border.width = 0 - th.style.btn.rel.body.radius = 10 - # btn pressed - lv.style_copy(th.style.btn.pr, th.style.btn.rel) - th.style.btn.pr.body.main_color = cbtnpr - th.style.btn.pr.body.grad_color = cbtnpr - # button map released - th.style.btnm.btn.rel.body.main_color = cbg - th.style.btnm.btn.rel.body.grad_color = cbg - th.style.btnm.btn.rel.body.radius = 0 - th.style.btnm.btn.rel.body.border.width = 0 - th.style.btnm.btn.rel.body.shadow.width = 0 - th.style.btnm.btn.rel.text.color = ctxt - # button map pressed - lv.style_copy(th.style.btnm.btn.pr, th.style.btnm.btn.rel) - th.style.btnm.btn.pr.body.main_color = chl - th.style.btnm.btn.pr.body.grad_color = chl - # button map toggled - lv.style_copy(th.style.btnm.btn.tgl_pr, th.style.btnm.btn.pr) - lv.style_copy(th.style.btnm.btn.tgl_rel, th.style.btnm.btn.pr) - # button map inactive - lv.style_copy(th.style.btnm.btn.ina, th.style.btnm.btn.rel) - th.style.btnm.btn.ina.text.opa = 80 - # button map background - th.style.btnm.bg.body.opa = 0 - th.style.btnm.bg.body.border.width = 0 - th.style.btnm.bg.body.shadow.width = 0 - # textarea - th.style.ta.oneline.body.opa = 0 - th.style.ta.oneline.body.border.width = 0 - th.style.ta.oneline.text.font = lv.font_roboto_28 - th.style.ta.oneline.text.color = ctxt - # slider - th.style.slider.knob.body.main_color = cbtnrel - th.style.slider.knob.body.grad_color = cbtnrel - th.style.slider.knob.body.radius = 5 - th.style.slider.knob.body.border.width = 0 - # page - th.style.page.bg.body.opa = 0 - th.style.page.scrl.body.opa = 0 - th.style.page.bg.body.border.width = 0 - th.style.page.bg.body.padding.left = 0 - th.style.page.bg.body.padding.right = 0 - th.style.page.bg.body.padding.top = 0 - th.style.page.bg.body.padding.bottom = 0 - th.style.page.scrl.body.border.width = 0 - th.style.page.scrl.body.padding.left = 0 - th.style.page.scrl.body.padding.right = 0 - th.style.page.scrl.body.padding.top = 0 - th.style.page.scrl.body.padding.bottom = 0 - - lv.theme_set_current(th) + cprimary = lv.palette_main(lv.PALETTE.BLUE) + csecondary = lv.palette_main(lv.PALETTE.GREY) + + # Initialize default theme + th = lv.theme_default_init(disp, cprimary, csecondary, dark, lv.font_montserrat_22) + disp.set_theme(th) + + # Store colors for later use + styles["cbg"] = cbg + styles["ctxt"] = ctxt + styles["cbtnrel"] = cbtnrel + styles["cbtnpr"] = cbtnpr + styles["chl"] = chl + + # Screen style + styles["scr"] = lv.style_t() + styles["scr"].init() + styles["scr"].set_bg_color(cbg) + styles["scr"].set_text_color(ctxt) + + # Button style + styles["btn"] = lv.style_t() + styles["btn"].init() + styles["btn"].set_bg_color(cbtnrel) + styles["btn"].set_shadow_width(0) + styles["btn"].set_border_width(0) + styles["btn"].set_radius(10) + + # Button pressed style + styles["btn_pressed"] = lv.style_t() + styles["btn_pressed"].init() + styles["btn_pressed"].set_bg_color(cbtnpr) + + # Button matrix styles + styles["btnm"] = lv.style_t() + styles["btnm"].init() + styles["btnm"].set_bg_color(cbg) + styles["btnm"].set_radius(0) + styles["btnm"].set_border_width(0) + styles["btnm"].set_shadow_width(0) + styles["btnm"].set_text_color(ctxt) + + styles["btnm_pressed"] = lv.style_t() + styles["btnm_pressed"].init() + styles["btnm_pressed"].set_bg_color(chl) + + styles["btnm_bg"] = lv.style_t() + styles["btnm_bg"].init() + styles["btnm_bg"].set_bg_opa(0) + styles["btnm_bg"].set_border_width(0) + styles["btnm_bg"].set_shadow_width(0) + + # Textarea style + styles["ta"] = lv.style_t() + styles["ta"].init() + styles["ta"].set_bg_opa(0) + styles["ta"].set_border_width(0) + styles["ta"].set_text_font(lv.font_montserrat_28) + styles["ta"].set_text_color(ctxt) + + # Slider knob style + styles["slider_knob"] = lv.style_t() + styles["slider_knob"].init() + styles["slider_knob"].set_bg_color(cbtnrel) + styles["slider_knob"].set_radius(5) + styles["slider_knob"].set_border_width(0) styles["theme"] = th - # Title style - just a default style with larger font + + # Title style styles["title"] = lv.style_t() - lv.style_copy(styles["title"], th.style.label.prim) - styles["title"].text.font = lv.font_roboto_28 - styles["title"].text.color = ctxt + styles["title"].init() + styles["title"].set_text_font(lv.font_montserrat_28) + styles["title"].set_text_color(ctxt) + # Hint style styles["hint"] = lv.style_t() - lv.style_copy(styles["hint"], th.style.label.sec) - styles["hint"].text.font = lv.font_roboto_16 + styles["hint"].init() + styles["hint"].set_text_font(lv.font_montserrat_16) + styles["hint"].set_text_color(csecondary) + # Small style styles["small"] = lv.style_t() - lv.style_copy(styles["small"], styles["hint"]) - styles["small"].text.color = ctxt + styles["small"].init() + styles["small"].set_text_font(lv.font_montserrat_16) + styles["small"].set_text_color(ctxt) + # Warning style styles["warning"] = lv.style_t() - lv.style_copy(styles["warning"], th.style.label.prim) - styles["warning"].text.color = lv.color_hex(0xFF9A00) + styles["warning"].init() + styles["warning"].set_text_color(lv.color_hex(0xFF9A00)) def add_label(text, y=PADDING, scr=None, style=None, width=None): """Helper functions that creates a title-styled label""" if width is None: width = HOR_RES - 2 * PADDING if scr is None: - scr = lv.scr_act() + scr = lv.screen_active() lbl = lv.label(scr) lbl.set_text(text) if style in styles: - lbl.set_style(0, styles[style]) - lbl.set_long_mode(lv.label.LONG.BREAK) + lbl.add_style(styles[style], 0) + lbl.set_long_mode(lv.label.LONG_MODE.WRAP) lbl.set_width(width) lbl.set_x((HOR_RES - width) // 2) - lbl.set_align(lv.label.ALIGN.CENTER) + lbl.set_style_text_align(lv.TEXT_ALIGN.CENTER, 0) lbl.set_y(y) return lbl @@ -140,21 +144,21 @@ def add_label(text, y=PADDING, scr=None, style=None, width=None): def add_button(text=None, callback=None, scr=None, y=700): """Helper function that creates a button with a text label""" if scr is None: - scr = lv.scr_act() - btn = lv.btn(scr) + scr = lv.screen_active() + btn = lv.button(scr) btn.set_width(HOR_RES - 2 * PADDING) btn.set_height(BTN_HEIGHT) if text is not None: lbl = lv.label(btn) lbl.set_text(text) - lbl.set_align(lv.label.ALIGN.CENTER) + lbl.center() - btn.align(scr, lv.ALIGN.IN_TOP_MID, 0, 0) + btn.align(lv.ALIGN.TOP_MID, 0, 0) btn.set_y(y) if callback is not None: - btn.set_event_cb(callback) + btn.add_event_cb(callback, lv.EVENT.CLICKED, None) return btn @@ -172,14 +176,17 @@ def align_button_pair(btn1, btn2): w = (HOR_RES - 3 * PADDING) // 2 btn1.set_width(w) btn2.set_width(w) + # Clear alignment and set explicit x positions + btn1.set_align(lv.ALIGN.DEFAULT) + btn2.set_align(lv.ALIGN.DEFAULT) btn1.set_x(PADDING) - btn2.set_x(HOR_RES // 2 + PADDING // 2) + btn2.set_x(PADDING + w + PADDING) def add_qrcode(text, y=QR_PADDING, scr=None, style=None, width=None): """Helper functions that creates a title-styled label""" if scr is None: - scr = lv.scr_act() + scr = lv.screen_active() if width is None: width = 350 @@ -188,7 +195,7 @@ def add_qrcode(text, y=QR_PADDING, scr=None, style=None, width=None): qr.set_text(text) qr.set_size(width) qr.set_text(text) - qr.align(scr, lv.ALIGN.IN_TOP_MID, 0, y) + qr.align_to(scr, lv.ALIGN.TOP_MID, 0, y) return qr diff --git a/src/gui/components/battery.py b/src/gui/components/battery.py index 29ca66e8..c16a367d 100644 --- a/src/gui/components/battery.py +++ b/src/gui/components/battery.py @@ -14,12 +14,15 @@ class Battery(lv.obj): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.set_style(lv.style_transp_tight) + # Make background transparent in LVGL 9.x + self.set_style_bg_opa(0, 0) + self.set_style_border_width(0, 0) + self.set_style_pad_all(0, 0) self.level = lv.label(self) self.level.set_recolor(True) self.icon = lv.label(self) self.charge = lv.label(self) - self.set_size(30,20) + self.set_size(30, 20) # self.bar = lv.bar(self) self.update() @@ -39,6 +42,6 @@ def update(self): self.icon.set_text(lv.SYMBOL.BATTERY_EMPTY) if self.CHARGING: self.charge.set_text(lv.SYMBOL.CHARGE) - self.charge.align(self.icon, lv.ALIGN.CENTER, 0, 0) + self.charge.align_to(self.icon, lv.ALIGN.CENTER, 0, 0) else: self.charge.set_text("") diff --git a/src/gui/components/keyboard.py b/src/gui/components/keyboard.py index fc6a0ac9..d8e1b725 100644 --- a/src/gui/components/keyboard.py +++ b/src/gui/components/keyboard.py @@ -4,18 +4,18 @@ from .theme import styles -class HintKeyboard(lv.btnm): +class HintKeyboard(lv.buttonmatrix): def __init__(self, scr, *args, **kwargs): super().__init__(scr, *args, **kwargs) - self.hint = lv.btn(scr) + self.hint = lv.button(scr) self.hint.set_size(50, 60) self.hint_lbl = lv.label(self.hint) self.hint_lbl.set_text(" ") - self.hint_lbl.set_style(0, styles["title"]) + self.hint_lbl.add_style(styles["title"], 0) self.hint_lbl.set_size(50, 60) - self.hint.set_hidden(True) + self.hint.add_flag(lv.obj.FLAG.HIDDEN) self.callback = None - super().set_event_cb(self.cb) + super().add_event_cb(self.cb, lv.EVENT.ALL, None) def set_event_cb(self, callback): self.callback = callback @@ -23,20 +23,21 @@ def set_event_cb(self, callback): def get_event_cb(self): return self.callback - def cb(self, obj, event): - if event == lv.EVENT.PRESSING: + def cb(self, event): + code = event.get_code() + obj = event.get_target() + if code == lv.EVENT.PRESSING: feed_touch() - c = obj.get_active_btn_text() + c = obj.get_selected_button_text() if c is not None and len(c) <= 2: - self.hint.set_hidden(False) + self.hint.clear_flag(lv.obj.FLAG.HIDDEN) self.hint_lbl.set_text(c) - point = lv.point_t() - indev = lv.indev_get_act() - lv.indev_get_point(indev, point) + indev = lv.indev_active() + point = indev.get_point() self.hint.set_pos(point.x - 25, point.y - 130) - elif event == lv.EVENT.RELEASED: - self.hint.set_hidden(True) + elif code == lv.EVENT.RELEASED: + self.hint.add_flag(lv.obj.FLAG.HIDDEN) if self.callback is not None: - self.callback(obj, event) + self.callback(obj, code) diff --git a/src/gui/components/modal.py b/src/gui/components/modal.py index 71193ddf..43d2b7ee 100644 --- a/src/gui/components/modal.py +++ b/src/gui/components/modal.py @@ -19,7 +19,7 @@ def __init__(self, parent, *args, **kwargs): self.mbox = lv.mbox(self) self.mbox.set_width(400) - self.mbox.align(None, lv.ALIGN.IN_TOP_MID, 0, 200) + self.mbox.align(lv.ALIGN.TOP_MID, 0, 200) def set_text(self, text): self.mbox.set_text(text) diff --git a/src/gui/components/qrcode.py b/src/gui/components/qrcode.py index 504a769d..ef6bd06a 100644 --- a/src/gui/components/qrcode.py +++ b/src/gui/components/qrcode.py @@ -1,6 +1,5 @@ import lvgl as lv import lvqr -import qrcode import math import gc import asyncio @@ -10,14 +9,22 @@ from qrencoder import QREncoder qr_style = lv.style_t() -qr_style.body.main_color = lv.color_hex(0xFFFFFF) -qr_style.body.grad_color = lv.color_hex(0xFFFFFF) -qr_style.body.opa = 255 -qr_style.text.opa = 255 -qr_style.text.color = lv.color_hex(0) -qr_style.text.line_space = 0 -qr_style.text.letter_space = 0 -qr_style.body.radius = 10 +qr_style.init() +qr_style.set_bg_color(lv.color_hex(0xFFFFFF)) +qr_style.set_bg_grad_color(lv.color_hex(0xFFFFFF)) +qr_style.set_bg_opa(255) +qr_style.set_text_opa(255) +qr_style.set_text_color(lv.color_hex(0)) +qr_style.set_text_line_space(0) +qr_style.set_text_letter_space(0) +qr_style.set_radius(10) + +# Transparent style (replacement for lv.style_transp_tight) +style_transp = lv.style_t() +style_transp.init() +style_transp.set_bg_opa(0) +style_transp.set_border_width(0) +style_transp.set_pad_all(0) QR_SIZES = [17, 32, 53, 78, 106, 154, 192, 230, 271, 367, 458, 586, 718, 858] BTNSIZE = 70 @@ -32,9 +39,11 @@ class QRCode(lv.obj): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) style = lv.style_t() - lv.style_copy(style, qr_style) - style.text.font = lv.font_roboto_16 - style.text.color = lv.color_hex(0x192432) + style.init() + style.set_bg_color(lv.color_hex(0xFFFFFF)) + style.set_bg_opa(255) + style.set_text_font(lv.font_roboto_16) + style.set_text_color(lv.color_hex(0x192432)) self.encoder = None self._autoplay = True @@ -50,9 +59,9 @@ def __init__(self, *args, **kwargs): self.create_playback_controls(style) self.note = lv.label(self) - self.note.set_style(0, style) + self.note.add_style(style, 0) self.note.set_text("") - self.note.set_align(lv.label.ALIGN.CENTER) + self.note.set_style_text_align(lv.TEXT_ALIGN.CENTER, 0) self.set_text(self._text) self.task = asyncio.create_task(self.animate()) @@ -66,14 +75,15 @@ def spacing(self): @spacing.setter def spacing(self, spacing): - qr_style = lv.style_t() - qr_style.body.border.width = spacing - self.qr.set_style(0, qr_style) + sp_style = lv.style_t() + sp_style.init() + sp_style.set_border_width(spacing) + self.qr.add_style(sp_style, 0) self._spacing = spacing def create_playback_controls(self, style): self.playback = lv.obj(self) - self.playback.set_style(lv.style_transp_tight) + self.playback.add_style(style_transp, 0) self.playback.set_size(480, BTNSIZE) self.playback.set_y(640) @@ -117,7 +127,7 @@ def create_playback_controls(self, style): def create_density_controls(self, style): self.controls = lv.obj(self) - self.controls.set_style(lv.style_transp_tight) + self.controls.add_style(style_transp, 0) self.controls.set_size(480, BTNSIZE) self.controls.set_y(740) plus = lv.btn(self.controls) @@ -136,8 +146,8 @@ def create_density_controls(self, style): lbl = lv.label(self.controls) lbl.set_text("QR code density") - lbl.set_style(0, style) - lbl.set_align(lv.label.ALIGN.CENTER) + lbl.add_style(style, 0) + lbl.set_style_text_align(lv.TEXT_ALIGN.CENTER, 0) lbl.align(self.controls, lv.ALIGN.CENTER, 0, 0) self.controls.set_hidden(True) @@ -290,7 +300,7 @@ def check_controls(self): def _set_text(self, text): # one bcur frame doesn't require checksum print(text) - self.set_style(qr_style) + self.add_style(qr_style, 0) self.qr.set_text(text) self.qr.align(self, lv.ALIGN.CENTER, 0, -100 if self.is_fullscreen else 0) self.note.align(self, lv.ALIGN.IN_BOTTOM_MID, 0, 0) diff --git a/src/gui/core.py b/src/gui/core.py index bba3fea3..4f68a21f 100644 --- a/src/gui/core.py +++ b/src/gui/core.py @@ -7,13 +7,14 @@ def init(blocking=True, dark=True): - # display.init(not blocking) + # Ensure display is initialized first (for LVGL 9.x) + display.init(not blocking) # Initialize the styles init_styles(dark=dark) scr = lv.obj() - lv.scr_load(scr) + lv.screen_load(scr) update() diff --git a/src/gui/decorators.py b/src/gui/decorators.py index 6cf54576..d441bdaa 100644 --- a/src/gui/decorators.py +++ b/src/gui/decorators.py @@ -9,33 +9,37 @@ def feed_touch(): and feeds it to random number pool """ point = lv.point_t() - indev = lv.indev_get_act() - lv.indev_get_point(indev, point) - # now we can take bytes([point.x % 256, point.y % 256]) - # and feed it into hash digest - t = time.ticks_cpu() - random_data = t.to_bytes(4, "big") + bytes([point.x % 256, point.y % 256]) - rng.feed(random_data) + indev = lv.indev_active() + if indev: + indev.get_point(point) + # now we can take bytes([point.x % 256, point.y % 256]) + # and feed it into hash digest + t = time.ticks_cpu() + random_data = t.to_bytes(4, "big") + bytes([point.x % 256, point.y % 256]) + rng.feed(random_data) def feed_rng(func): """Any callback will contribute to random number pool""" - - def wrapper(o, e): - if e == lv.EVENT.PRESSING: + # LVGL 9.x: callback receives event object + def wrapper(event): + code = event.get_code() + if code == lv.EVENT.PRESSING: feed_touch() - func(o, e) + func(event) return wrapper def on_release(func): """Handy decorator if you only care about click event""" - - def wrapper(o, e): - if e == lv.EVENT.PRESSING: + # LVGL 9.x: callback receives event object + # CLICKED = complete press+release cycle + def wrapper(event): + code = event.get_code() + if code == lv.EVENT.PRESSING: feed_touch() - elif e == lv.EVENT.RELEASED and func is not None: + elif code == lv.EVENT.CLICKED and func is not None: func() return wrapper diff --git a/src/gui/screens/alert.py b/src/gui/screens/alert.py index fd9f0fda..6cea561a 100644 --- a/src/gui/screens/alert.py +++ b/src/gui/screens/alert.py @@ -13,12 +13,13 @@ def __init__( obj = self.title if note is not None: self.note = add_label(note, scr=self, style="hint") - self.note.align(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 5) + self.note.align_to(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 5) obj = self.note - self.page = lv.page(self) + # LVGL 9.x: page replaced with scrollable obj + self.page = lv.obj(self) self.page.set_size(480, 600) self.message = add_label(message, scr=self.page) - self.page.align(obj, lv.ALIGN.OUT_BOTTOM_MID, 0, 0) + self.page.align_to(obj, lv.ALIGN.OUT_BOTTOM_MID, 0, 0) if button_text is not None: self.close_button = add_button(scr=self, callback=on_release(self.release)) diff --git a/src/gui/screens/input.py b/src/gui/screens/input.py index f28e5c34..a53975a7 100644 --- a/src/gui/screens/input.py +++ b/src/gui/screens/input.py @@ -123,13 +123,13 @@ def __init__( if note is not None: self.note = add_label(note, scr=self, style="hint") - self.note.align(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 5) + self.note.align_to(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 5) self.kb = HintKeyboard(self) self.kb.set_map(self.CHARSET) self.kb.set_width(HOR_RES) self.kb.set_height(int(VER_RES / 2.5)) - self.kb.align(self, lv.ALIGN.IN_BOTTOM_MID, 0, 0) + self.kb.align(lv.ALIGN.BOTTOM_MID, 0, 0) self.ta = lv.ta(self) self.ta.set_text(suggestion) @@ -145,7 +145,7 @@ def __init__( self.kb.set_event_cb(self.cb) self.warning = add_label("", scr=self, style="hint") - self.warning.align(self.ta, lv.ALIGN.OUT_BOTTOM_MID, 0, 10) + self.warning.align_to(self.ta, lv.ALIGN.OUT_BOTTOM_MID, 0, 10) def cb(self, obj, event): if event == lv.EVENT.RELEASED: @@ -215,14 +215,14 @@ def __init__(self, title="Enter your PIN code", note=None, get_word=None, subtit if subtitle is not None: lbl = add_label(subtitle, scr=self, style="hint") lbl.set_recolor(True) - lbl.align(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 10) + lbl.align_to(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 10) if note is not None: lbl = add_label(note, scr=self, style="hint") - lbl.align(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 90) + lbl.align_to(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 90) self.get_word = get_word if get_word is not None: self.words = add_label(get_word(b""), scr=self) - self.words.align(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 120) + self.words.align_to(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 120) btnm = lv.btnm(self) # shuffle numbers to make sure # no constant fingerprints left on screen @@ -237,7 +237,7 @@ def __init__(self, title="Enter your PIN code", note=None, get_word=None, subtit btnm.set_map(btnmap) btnm.set_width(HOR_RES) btnm.set_height(HOR_RES) - btnm.align(self, lv.ALIGN.IN_BOTTOM_MID, 0, -100) + btnm.align(lv.ALIGN.BOTTOM_MID, 0, -100) # increase font size style = lv.style_t() lv.style_copy(style, btnm.get_style(lv.btnm.STYLE.BTN_REL)) @@ -262,7 +262,7 @@ def __init__(self, title="Enter your PIN code", note=None, get_word=None, subtit self.pin.set_one_line(True) self.pin.set_text_align(lv.label.ALIGN.CENTER) self.pin.set_pwd_show_time(0) - self.pin.align(btnm, lv.ALIGN.OUT_TOP_MID, 0, -80) + self.pin.align_to(btnm, lv.ALIGN.OUT_TOP_MID, 0, -80) self.next_button = add_button(scr=self, callback=on_release(self.submit)) @@ -343,7 +343,7 @@ def __init__(self, title="Enter derivation path"): self.kb.set_map(self.PATH_CHARSET) self.kb.set_width(HOR_RES) self.kb.set_height(VER_RES // 2) - self.kb.align(self, lv.ALIGN.IN_BOTTOM_MID, 0, 0) + self.kb.align(lv.ALIGN.BOTTOM_MID, 0, 0) lbl = add_label("m/", style="title", scr=self) lbl.set_y(PADDING + 150) @@ -423,13 +423,13 @@ def __init__( self.title = add_label(title, scr=self, y=PADDING, style="title") self.note = add_label(note, scr=self, style="hint") - self.note.align(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 5) + self.note.align_to(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 5) self.kb = lv.btnm(self) self.kb.set_map(self.NUMERIC_CHARSET) self.kb.set_width(HOR_RES) self.kb.set_height(VER_RES // 2) - self.kb.align(self, lv.ALIGN.IN_BOTTOM_MID, 0, 0) + self.kb.align(lv.ALIGN.BOTTOM_MID, 0, 0) lbl = add_label('', style="title", scr=self) lbl.set_y(PADDING + 150) diff --git a/src/gui/screens/menu.py b/src/gui/screens/menu.py index e1e64363..8d9a89f2 100644 --- a/src/gui/screens/menu.py +++ b/src/gui/screens/menu.py @@ -13,9 +13,10 @@ def __init__( self.title = add_label(title, style="title", scr=self) if note is not None: self.note = add_label(note, style="hint", scr=self) - self.note.align(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 5) + self.note.align_to(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 5) y += self.note.get_height() - self.page = lv.page(self) + # LVGL 9.x: page replaced with scrollable obj + self.page = lv.obj(self) h = 800 - y - 20 self.page.set_size(480, h) self.page.set_y(y) @@ -32,15 +33,14 @@ def __init__( cb = None btn = add_button(text, cb, y=y, scr=self.page) if not enable: - btn.set_state(lv.btn.STATE.INA) - # color + btn.add_state(lv.STATE.DISABLED) + # color (LVGL 9.x style) if len(args) > 1: color = args[1] style = lv.style_t() - lv.style_copy(style, btn.get_style(lv.btn.STYLE.REL)) - style.body.main_color = lv.color_hex(color) - style.body.grad_color = lv.color_hex(color) - btn.set_style(lv.btn.STYLE.REL, style) + style.init() + style.set_bg_color(lv.color_hex(color)) + btn.add_style(style, 0) self.buttons.append(btn) y += 85 diff --git a/src/gui/screens/mnemonic.py b/src/gui/screens/mnemonic.py index d47eb7e6..12b3f363 100644 --- a/src/gui/screens/mnemonic.py +++ b/src/gui/screens/mnemonic.py @@ -14,10 +14,10 @@ def __init__(self, mnemonic="", title="Your recovery phrase:", note=None): self.title = add_label(title, scr=self, style="title") if note is not None: lbl = add_label(note, scr=self, style="hint") - lbl.align(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 5) + lbl.align_to(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 5) self.table = MnemonicTable(self) self.table.set_mnemonic(mnemonic) - self.table.align(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 30) + self.table.align_to(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 30) self.close_button = add_button(scr=self, callback=on_release(self.release)) @@ -29,16 +29,16 @@ def __init__(self, mnemonic="", title="Your recovery phrase:", note=None): super().__init__(title, message="", note=note) table = MnemonicTable(self) table.set_mnemonic(mnemonic) - table.align(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 50) + table.align_to(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 50) class ExportMnemonicScreen(MnemonicScreen): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.show_qr_btn = add_button(text="Show as QR code", scr=self, callback=on_release(self.select_qr)) - self.show_qr_btn.align(self.table, lv.ALIGN.OUT_BOTTOM_MID, 0, 10) + self.show_qr_btn.align_to(self.table, lv.ALIGN.OUT_BOTTOM_MID, 0, 10) self.save_sd_btn = add_button(text="Save to SD card (plaintext)", scr=self, callback=on_release(self.select_sd)) - self.save_sd_btn.align(self.show_qr_btn, lv.ALIGN.OUT_BOTTOM_MID, 0, 10) + self.save_sd_btn.align_to(self.show_qr_btn, lv.ALIGN.OUT_BOTTOM_MID, 0, 10) def select_sd(self): self.set_value(self.SD) @@ -59,7 +59,7 @@ def __init__( self.wordlist = wordlist mnemonic = generator(12) super().__init__(mnemonic, title, note) - self.table.align(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 50) + self.table.align_to(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 50) self.table.set_event_cb(self.on_word_click) # enable callbacks self.table.set_click(True) @@ -74,13 +74,13 @@ def __init__( # toggle switch 12-24 words lbl = lv.label(self) lbl.set_text("Use 24 words") - lbl.align(self.table, lv.ALIGN.OUT_BOTTOM_MID, 0, 40) + lbl.align_to(self.table, lv.ALIGN.OUT_BOTTOM_MID, 0, 40) lbl.set_x(120) self.switch_lbl = lbl self.switch = lv.sw(self) self.switch.off(lv.ANIM.OFF) - self.switch.align(lbl, lv.ALIGN.OUT_RIGHT_MID, 20, 0) + self.switch.align_to(lbl, lv.ALIGN.OUT_RIGHT_MID, 20, 0) def cb(): wordcount = 24 if self.switch.get_state() else 12 @@ -95,11 +95,11 @@ def cb(): self.kb.set_ctrl_map([lv.btnm.CTRL.TGL_ENABLE for i in range(11)]) self.kb.set_width(HOR_RES) self.kb.set_height(100) - self.kb.align(self.table, lv.ALIGN.OUT_BOTTOM_MID, 0, 5) + self.kb.align_to(self.table, lv.ALIGN.OUT_BOTTOM_MID, 0, 5) self.kb.set_hidden(True) self.instruction = add_label("Hint: click on any word above to edit it.", scr=self, style="hint") - self.instruction.align(self.kb, lv.ALIGN.OUT_BOTTOM_MID, 0, 15) + self.instruction.align_to(self.kb, lv.ALIGN.OUT_BOTTOM_MID, 0, 15) def on_word_click(self, obj, evt): @@ -173,7 +173,7 @@ def __init__( self, checker=None, lookup=None, fixer=None, title="Enter your recovery phrase" ): super().__init__("", title) - self.table.align(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 10) + self.table.align_to(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 10) self.checker = checker self.lookup = lookup @@ -231,7 +231,7 @@ def __init__( self.kb.set_btn_ctrl(self.BTN_DONE, lv.btnm.CTRL.INACTIVE) self.kb.set_width(HOR_RES) self.kb.set_height(260) - self.kb.align(self, lv.ALIGN.IN_BOTTOM_MID, 0, 0) + self.kb.align(lv.ALIGN.BOTTOM_MID, 0, 0) self.kb.set_event_cb(self.callback) self.fixer = fixer @@ -239,12 +239,12 @@ def __init__( self.fix_button = add_button("fix", on_release(self.fix_cb), self) self.fix_button.set_size(55, 30) # position it out of the screen but on correct y - self.fix_button.align(self.table, lv.ALIGN.OUT_BOTTOM_MID, -400, -38) + self.fix_button.align_to(self.table, lv.ALIGN.OUT_BOTTOM_MID, -400, -38) if lookup is not None: self.autocomplete.set_width(HOR_RES) self.autocomplete.set_height(50) - self.autocomplete.align(self.kb, lv.ALIGN.OUT_TOP_MID, 0, 0) + self.autocomplete.align_to(self.kb, lv.ALIGN.OUT_TOP_MID, 0, 0) words = lookup("", 4) + [""] self.autocomplete.set_map(words) self.autocomplete.set_event_cb(self.select_word) @@ -304,13 +304,13 @@ def check_buttons(self): # check if we can fix the mnemonic try: self.fixer(mnemonic) - self.fix_button.align(self.table, lv.ALIGN.OUT_BOTTOM_MID, x, y) + self.fix_button.align_to(self.table, lv.ALIGN.OUT_BOTTOM_MID, x, y) except: - self.fix_button.align( + self.fix_button.align_to( self.table, lv.ALIGN.OUT_BOTTOM_MID, -400, -38 ) else: - self.fix_button.align(self.table, lv.ALIGN.OUT_BOTTOM_MID, -400, -38) + self.fix_button.align_to(self.table, lv.ALIGN.OUT_BOTTOM_MID, -400, -38) def callback(self, obj, event): if event != lv.EVENT.RELEASED: @@ -383,4 +383,4 @@ def event_handler(obj, event): mbox.add_btns(btns) mbox.set_width(400) mbox.set_event_cb(event_handler) - mbox.align(None, lv.ALIGN.CENTER, 0, 0) + mbox.align(lv.ALIGN.CENTER, 0, 0) diff --git a/src/gui/screens/progress.py b/src/gui/screens/progress.py index c86800aa..3c369020 100644 --- a/src/gui/screens/progress.py +++ b/src/gui/screens/progress.py @@ -16,10 +16,10 @@ def __init__(self, title, message, button_text="Cancel"): self.start = 0 self.end = 30 self.arc.set_angles(self.start, self.end) - self.arc.align(self, lv.ALIGN.CENTER, 0, -150) - self.message.align(self.arc, lv.ALIGN.OUT_BOTTOM_MID, 0, 120) + self.arc.align(lv.ALIGN.CENTER, 0, -150) + self.message.align_to(self.arc, lv.ALIGN.OUT_BOTTOM_MID, 0, 120) self.progress = add_label("", scr=self, style="title") - self.progress.align(self.message, lv.ALIGN.OUT_BOTTOM_MID, 0, 30) + self.progress.align_to(self.message, lv.ALIGN.OUT_BOTTOM_MID, 0, 30) self.progress.set_recolor(True) def tick(self, d: int = 10): diff --git a/src/gui/screens/prompt.py b/src/gui/screens/prompt.py index 206bc83a..f9fd70b1 100644 --- a/src/gui/screens/prompt.py +++ b/src/gui/screens/prompt.py @@ -11,12 +11,13 @@ def __init__(self, title="Are you sure?", message="Make a choice", self.title = add_label(title, scr=self, style="title") if note is not None: self.note = add_label(note, scr=self, style="hint") - self.note.align(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 5) + self.note.align_to(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 5) obj = self.note - self.page = lv.page(self) + # LVGL 9.x: page replaced with scrollable obj + self.page = lv.obj(self) self.page.set_size(480, 600) self.message = add_label(message, scr=self.page) - self.page.align(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 0) + self.page.align_to(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 0) # Initialize an empty icon label. It will display nothing until a symbol is set. self.icon = lv.label(self) self.icon.set_text("") @@ -31,14 +32,14 @@ def __init__(self, title="Are you sure?", message="Make a choice", if warning: self.warning = add_label(warning, scr=self, style="warning") - # Display warning symbol in the icon label + # Display warning symbol in the icon label self.icon.set_text(lv.SYMBOL.WARNING) - + # Align warning text y_pos = self.cancel_button.get_y() - 60 # above the buttons x_pos = self.get_width() // 2 - self.warning.get_width() // 2 # in the center of the prompt self.warning.set_pos(x_pos, y_pos) - + # Align warning icon to the left of the title - self.icon.align(self.title, lv.ALIGN.IN_LEFT_MID, 90, 0) + self.icon.align_to(self.title, lv.ALIGN.LEFT_MID, 90, 0) diff --git a/src/gui/screens/qralert.py b/src/gui/screens/qralert.py index 18777665..550a4245 100644 --- a/src/gui/screens/qralert.py +++ b/src/gui/screens/qralert.py @@ -19,11 +19,11 @@ def __init__( qr_message = message super().__init__(title, message, button_text, note=note) self.qr = add_qrcode(qr_message, scr=self, width=qr_width) - self.qr.align(self.page, lv.ALIGN.IN_TOP_MID, 0, 20) - self.message.align(self.qr, lv.ALIGN.OUT_BOTTOM_MID, 0, 20) + self.qr.align_to(self.page, lv.ALIGN.TOP_MID, 0, 20) + self.message.align_to(self.qr, lv.ALIGN.OUT_BOTTOM_MID, 0, 20) if transcribe: btn = add_button("Toggle transcribe", on_release(self.toggle_transcribe), scr=self) - btn.align(self.message, lv.ALIGN.OUT_BOTTOM_MID, 0, 20) + btn.align_to(self.message, lv.ALIGN.OUT_BOTTOM_MID, 0, 20) def toggle_transcribe(self): self.qr.spacing = 0 if self.qr.spacing else 3 diff --git a/src/gui/screens/screen.py b/src/gui/screens/screen.py index 20e993b9..1a46b19e 100644 --- a/src/gui/screens/screen.py +++ b/src/gui/screens/screen.py @@ -22,18 +22,17 @@ def __init__(self): self.waiting = True self._value = None self.battery = Battery(self) - self.battery.align(self, lv.ALIGN.IN_TOP_RIGHT, -20, 10) + self.battery.align(lv.ALIGN.TOP_RIGHT, -20, 10) if type(self).network in type(self).COLORS: self.topbar = lv.obj(self) s = lv.style_t() - lv.style_copy(s, styles["theme"].style.btn.rel) - s.body.main_color = type(self).COLORS[type(self).network] - s.body.grad_color = type(self).COLORS[type(self).network] - s.body.opa = 200 - s.body.radius = 0 - s.body.border.width = 0 - self.topbar.set_style(s) + s.init() + s.set_bg_color(type(self).COLORS[type(self).network]) + s.set_bg_opa(200) + s.set_radius(0) + s.set_border_width(0) + self.topbar.add_style(s, 0) self.topbar.set_size(HOR_RES, 5) self.topbar.set_pos(0, 0) @@ -69,6 +68,6 @@ def show_loader(self, def hide_loader(self): if self.mbox is None: return - self.mbox.del_async() + self.mbox.delete_async() self.mbox = None update() diff --git a/src/gui/screens/settings.py b/src/gui/screens/settings.py index fe74796c..21ef3e48 100644 --- a/src/gui/screens/settings.py +++ b/src/gui/screens/settings.py @@ -9,7 +9,7 @@ def __init__(self, controls, title="Host setttings", note=None, controls_empty_t y = 40 if note is not None: self.note = add_label(note, style="hint", scr=self) - self.note.align(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 5) + self.note.align_to(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 5) y += self.note.get_height() self.controls = controls self.switches = [] @@ -22,9 +22,9 @@ def __init__(self, controls, title="Host setttings", note=None, controls_empty_t style="hint", ) switch = lv.sw(self.page) - switch.align(hint, lv.ALIGN.OUT_BOTTOM_MID, 0, 10) + switch.align_to(hint, lv.ALIGN.OUT_BOTTOM_MID, 0, 10) lbl = add_label(" OFF ON ", scr=self.page) - lbl.align(switch, lv.ALIGN.CENTER, 0, 0) + lbl.align_to(switch, lv.ALIGN.CENTER, 0, 0) if control.get("value", False): switch.on(lv.ANIM.OFF) self.switches.append(switch) @@ -44,7 +44,7 @@ def __init__(self, dev=False, usb=False, note=None): super().__init__("Device settings", "") if note is not None: self.note = add_label(note, style="hint", scr=self) - self.note.align(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 5) + self.note.align_to(self.title, lv.ALIGN.OUT_BOTTOM_MID, 0, 5) y = 70 usb_label = add_label("USB communication", y, scr=self.page) usb_hint = add_label( @@ -57,9 +57,9 @@ def __init__(self, dev=False, usb=False, note=None): style="hint", ) self.usb_switch = lv.sw(self.page) - self.usb_switch.align(usb_hint, lv.ALIGN.OUT_BOTTOM_MID, 0, 20) + self.usb_switch.align_to(usb_hint, lv.ALIGN.OUT_BOTTOM_MID, 0, 20) lbl = add_label(" OFF ON ", scr=self.page) - lbl.align(self.usb_switch, lv.ALIGN.CENTER, 0, 0) + lbl.align_to(self.usb_switch, lv.ALIGN.CENTER, 0, 0) if usb: self.usb_switch.on(lv.ANIM.OFF) @@ -86,7 +86,7 @@ def __init__(self, dev=False, usb=False, note=None): self.wipebtn = add_button( lv.SYMBOL.TRASH + " Wipe device", on_release(self.wipe), scr=self ) - self.wipebtn.align(self, lv.ALIGN.IN_BOTTOM_MID, 0, -140) + self.wipebtn.align(lv.ALIGN.BOTTOM_MID, 0, -140) style = lv.style_t() lv.style_copy(style, self.wipebtn.get_style(lv.btn.STYLE.REL)) style.body.main_color = lv.color_hex(0x951E2D) diff --git a/src/gui/screens/transaction.py b/src/gui/screens/transaction.py index 3a630c04..83364376 100644 --- a/src/gui/screens/transaction.py +++ b/src/gui/screens/transaction.py @@ -20,9 +20,9 @@ def __init__(self, title, meta): enable_inputs = enable_inputs or meta.get("issuance", False) or meta.get("reissuance", False) lbl = add_label("Show detailed information ", scr=self) - lbl.align(obj, lv.ALIGN.CENTER, 0, 0) + lbl.align_to(obj, lv.ALIGN.CENTER, 0, 0) self.details_sw = lv.sw(self) - self.details_sw.align(obj, lv.ALIGN.CENTER, 130, 0) + self.details_sw.align_to(obj, lv.ALIGN.CENTER, 130, 0) self.details_sw.set_event_cb(on_release(self.toggle_details)) if enable_inputs: self.details_sw.on(lv.ANIM.OFF) @@ -75,7 +75,7 @@ def __init__(self, title, meta): fee_txt = "%d satoshi" % (meta["fee"]) fee = add_label("Fee: " + fee_txt, scr=self.page) fee.set_style(0, style) - fee.align(obj, lv.ALIGN.OUT_BOTTOM_MID, 0, 30) + fee.align_to(obj, lv.ALIGN.OUT_BOTTOM_MID, 0, 30) obj = fee @@ -83,22 +83,22 @@ def __init__(self, title, meta): text = "WARNING!\n" + "\n".join(meta["warnings"]) self.warning = add_label(text, scr=self.page) self.warning.set_style(0, style_warning) - self.warning.align(obj, lv.ALIGN.OUT_BOTTOM_MID, 0, 30) + self.warning.align_to(obj, lv.ALIGN.OUT_BOTTOM_MID, 0, 30) lbl = add_label("%d INPUTS" % len(meta["inputs"]), scr=self.page2) - lbl.align(self.page2, lv.ALIGN.IN_TOP_MID, 0, 30) + lbl.align(lv.ALIGN.TOP_MID, 0, 30) obj = lbl for i, inp in enumerate(meta["inputs"]): idxlbl = lv.label(self.page2) idxlbl.set_text("%d:" % i) - idxlbl.align(lbl, lv.ALIGN.OUT_BOTTOM_MID, 0, 30) + idxlbl.align_to(lbl, lv.ALIGN.OUT_BOTTOM_MID, 0, 30) idxlbl.set_x(30) lbl = lv.label(self.page2) lbl.set_long_mode(lv.label.LONG.BREAK) lbl.set_width(380) valuetxt = "???" if inp["value"] == -1 else "%.8f" % (inp["value"]/1e8) lbl.set_text("%s %s from %s" % (valuetxt, inp.get("asset", self.default_asset), inp.get("label", "Unknown wallet"))) - lbl.align(idxlbl, lv.ALIGN.IN_TOP_LEFT, 0, 0) + lbl.align_to(idxlbl, lv.ALIGN.TOP_LEFT, 0, 0) lbl.set_x(60) if inp.get("sighash", ""): @@ -106,33 +106,33 @@ def __init__(self, title, meta): shlbl.set_long_mode(lv.label.LONG.BREAK) shlbl.set_width(380) shlbl.set_text(inp.get("sighash", "")) - shlbl.align(lbl, lv.ALIGN.OUT_BOTTOM_LEFT, 0, 5) + shlbl.align_to(lbl, lv.ALIGN.OUT_BOTTOM_LEFT, 0, 5) shlbl.set_x(60) shlbl.set_style(0, style_warning) lbl = shlbl obj = lbl lbl = add_label("%d OUTPUTS" % len(meta["outputs"]), scr=self.page2) - lbl.align(self.page2, lv.ALIGN.IN_TOP_MID, 0, 0) + lbl.align(lv.ALIGN.TOP_MID, 0, 0) lbl.set_y(obj.get_y() + obj.get_height() + 30) for i, out in enumerate(meta["outputs"]): idxlbl = lv.label(self.page2) idxlbl.set_text("%d:" % i) - idxlbl.align(lbl, lv.ALIGN.OUT_BOTTOM_MID, 0, 30) + idxlbl.align_to(lbl, lv.ALIGN.OUT_BOTTOM_MID, 0, 30) idxlbl.set_x(30) lbl = lv.label(self.page2) lbl.set_long_mode(lv.label.LONG.BREAK) lbl.set_width(380) valuetxt = "???" if out["value"] == -1 else "%.8f" % (out["value"]/1e8) lbl.set_text("%s %s to %s" % (valuetxt, out.get("asset", self.default_asset), out.get("label", ""))) - lbl.align(idxlbl, lv.ALIGN.IN_TOP_LEFT, 0, 0) + lbl.align_to(idxlbl, lv.ALIGN.TOP_LEFT, 0, 0) lbl.set_x(60) addrlbl = lv.label(self.page2) addrlbl.set_long_mode(lv.label.LONG.BREAK) addrlbl.set_width(380) addrlbl.set_text(format_addr(out["address"], words=4)) - addrlbl.align(lbl, lv.ALIGN.OUT_BOTTOM_LEFT, 0, 5) + addrlbl.align_to(lbl, lv.ALIGN.OUT_BOTTOM_LEFT, 0, 5) addrlbl.set_x(60) if out.get("label", ""): addrlbl.set_style(0, style_secondary) @@ -145,14 +145,14 @@ def __init__(self, title, meta): warning.set_align(lv.label.ALIGN.LEFT) warning.set_width(380) warning.set_style(0, self.style_warning) - warning.align(addrlbl, lv.ALIGN.OUT_BOTTOM_LEFT, 0, 10) + warning.align_to(addrlbl, lv.ALIGN.OUT_BOTTOM_LEFT, 0, 10) warning.set_x(60) lbl = warning if meta.get("fee"): idxlbl = lv.label(self.page2) idxlbl.set_text("Fee: " + fee_txt) - idxlbl.align(lbl, lv.ALIGN.OUT_BOTTOM_MID, 0, 30) + idxlbl.align_to(lbl, lv.ALIGN.OUT_BOTTOM_MID, 0, 30) idxlbl.set_x(30) self.toggle_details() @@ -171,11 +171,11 @@ def show_output(self, out, obj): lbl = add_label( "%s %s to" % (valuetxt, out.get("asset", self.default_asset)), style="title", scr=self.page ) - lbl.align(obj, lv.ALIGN.OUT_BOTTOM_MID, 0, 30) + lbl.align_to(obj, lv.ALIGN.OUT_BOTTOM_MID, 0, 30) obj = lbl if out.get("label", ""): lbl = add_label(out["label"], style="title", scr=self.page) - lbl.align(obj, lv.ALIGN.OUT_BOTTOM_MID, 0, 10) + lbl.align_to(obj, lv.ALIGN.OUT_BOTTOM_MID, 0, 10) obj = lbl if out.get("label", ""): txt = format_addr(out["address"], words=4) @@ -186,12 +186,12 @@ def show_output(self, out, obj): addr.set_style(0, self.style_secondary) else: addr.set_style(0, self.style) - addr.align(obj, lv.ALIGN.OUT_BOTTOM_MID, 0, 10) + addr.align_to(obj, lv.ALIGN.OUT_BOTTOM_MID, 0, 10) obj = addr if "warning" in out: text = "WARNING! %s" % out["warning"] warning = add_label(text, scr=self.page) warning.set_style(0, self.style_warning) - warning.align(obj, lv.ALIGN.OUT_BOTTOM_MID, 0, 10) + warning.align_to(obj, lv.ALIGN.OUT_BOTTOM_MID, 0, 10) obj = warning return obj \ No newline at end of file diff --git a/src/gui/specter.py b/src/gui/specter.py index 6f22a601..70d28632 100644 --- a/src/gui/specter.py +++ b/src/gui/specter.py @@ -60,6 +60,10 @@ async def coro(self, host, scr): - or host finishes processing Also updates progress screen """ + # Wait for scanning to start (or user cancel) + while not host.in_progress and scr.waiting: + await asyncio.sleep_ms(10) + # Wait for scanning to finish (or user cancel) while host.in_progress and scr.waiting: await asyncio.sleep_ms(30) scr.tick(5) diff --git a/src/hosts/qr.py b/src/hosts/qr.py index f4ab3728..2df2b7fb 100644 --- a/src/hosts/qr.py +++ b/src/hosts/qr.py @@ -826,6 +826,10 @@ async def get_data(self, raw=True, chunk_timeout=0.5): self, "Scanning...", "Point scanner to the QR code" ) stream = await self.scan(raw=raw, chunk_timeout=chunk_timeout) + # Wait for progress popup to close before returning + if self.manager is not None: + while self.manager.gui.background is not None: + await asyncio.sleep_ms(10) if stream is not None: return stream