From 5a906efcaa851abddb2a95019b2cf92e60fb4171 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 05:34:47 +0000 Subject: [PATCH 1/2] feat: show branch, commit, build number and build time in Settings > System x4-pro-light-sleep(-debug) had no CROSSINK_VERSION mapping in git_branch.py and fell back to "dev"; map them like x4-pro(-debug). git_branch.py now adds CROSSINK_GIT_BRANCH, CROSSINK_BUILD_NUMBER (CROSSINK_BUILD_NUMBER env, else commit count, empty on shallow clones) and CROSSINK_BUILD_TIME (UTC) through a build middleware scoped to src/util/BuildInfo.cpp, so the per-build timestamp only recompiles that file. The System footer adds a details line under the version and the list reserves room for it; /api/status reports the same fields. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01FQ85oMXFSkfR2wF4dx4hLz --- CHANGELOG.md | 2 + scripts/git_branch.py | 53 ++++++++++++++++++-- src/activities/settings/SettingsActivity.cpp | 34 +++++++++++-- src/network/CrossPointWebServer.cpp | 4 ++ src/util/BuildInfo.cpp | 19 +++++++ src/util/BuildInfo.h | 11 ++++ 6 files changed, 115 insertions(+), 8 deletions(-) create mode 100644 src/util/BuildInfo.cpp create mode 100644 src/util/BuildInfo.h diff --git a/CHANGELOG.md b/CHANGELOG.md index f0d7b2e155e..85f6c650ceb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - The OPDS browser shows how many books a category holds, in parentheses next to its arrow, when the catalog provides a count (a `thr:count` link attribute or a "12713 books" summary). Folder titles that start with a 📁 emoji show as "/name" instead, since the device fonts have no folder emoji. - On devices with PSRAM (Sticky, X4 Pro), the OPDS browser downloads the next page of a catalog in the background while you browse, and keeps pages you have visited in memory, so Next page, Previous page and Back open without waiting on the server. - The web file manager's image preview has previous and next buttons, and the left and right arrow keys, to step through the images in the current folder. Other file types are skipped, and stepping wraps around at either end. The preview also shows the image's position in the folder, its pixel dimensions, its file size and, when the file list has one, its modified date. +- The Settings > System footer shows the firmware's branch, commit (with `*` when built from uncommitted changes), build number and UTC build time under the version. The build number is the branch's commit count unless the build sets `CROSSINK_BUILD_NUMBER`, and is left out for shallow checkouts. The System list stops above the footer instead of running under it. The web status API reports the same fields. ### Changed @@ -32,6 +33,7 @@ ### Fixed +- X4 Pro light-sleep firmware shows its real version (for example `1.6.0-x4-pro`) instead of "dev" in Settings, on the boot screen, and in the web and OTA version checks. - Background Library indexing and the reader's next-page draw-ahead run at full CPU speed again instead of the lowest idle clock. - USB Drive no longer reads ahead into the sectors a computer is about to write, so copying files to the card is not slowed by background reads. - Nearby and KOReader position sync read the chapter layout of the orientation the book is read in, instead of whichever orientation the sync screen happened to use, so a book read in landscape lands on the right page. diff --git a/scripts/git_branch.py b/scripts/git_branch.py index b7683e61853..e49e873a94c 100644 --- a/scripts/git_branch.py +++ b/scripts/git_branch.py @@ -12,6 +12,7 @@ """ import configparser +import datetime import os import re import subprocess @@ -100,6 +101,44 @@ def get_git_dirty(project_dir): return 'unknown' +def get_build_number(project_dir): + # Batch/CI builds may pass their own number; otherwise use the commit count, + # which only means something when the full history is checked out. Shallow + # CI checkouts would all report 1, so they get no number at all. + number = os.environ.get('CROSSINK_BUILD_NUMBER') + if number: + return sanitize_version_component(number) + if run_git_value(project_dir, ['rev-parse', '--is-shallow-repository'], 'shallow state') != 'false': + return '' + number = run_git_value(project_dir, ['rev-list', '--count', 'HEAD'], 'build number') + return number if number.isdigit() else '' + + +def register_build_info(env, project_dir): + # The build time changes on every build. Defining it globally would change + # every compile command and force a full rebuild, so scope these defines to + # the one small file that exposes them. + branch = run_git_value( + project_dir, ['rev-parse', '--abbrev-ref', 'HEAD'], 'branch' + ) + if branch == 'HEAD': + branch = 'detached' + branch = re.sub(r'[^A-Za-z0-9._/-]+', '-', branch) or 'unknown' + build_time = datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%dT%H:%MZ') + defines = [ + ('CROSSINK_GIT_BRANCH', f'\\"{branch}\\"'), + ('CROSSINK_BUILD_NUMBER', f'\\"{get_build_number(project_dir)}\\"'), + ('CROSSINK_BUILD_TIME', f'\\"{build_time}\\"'), + ] + + def add_build_info_defines(node_env, node): + build_env = node_env.Clone() + build_env.Append(CPPDEFINES=defines) + return build_env.Object(node) + + env.AddBuildMiddleware(add_build_info_defines, '*src/util/BuildInfo.cpp') + + def _read_ini(project_dir): ini_path = os.path.join(project_dir, 'platformio.ini') local_ini_path = os.path.join(project_dir, 'platformio.local.ini') @@ -156,7 +195,12 @@ def get_hardware_version(project_dir, pioenv): if os.environ.get('CROSSINK_RELEASE_VERSION') else get_crossink_version(project_dir) ) - device_suffix = {'sticky': '-sticky', 'x4-pro': '-x4-pro', 'x4-classic': '-x4-classic'}[pioenv] + device_suffix = { + 'sticky': '-sticky', + 'x4-pro': '-x4-pro', + 'x4-pro-light-sleep': '-x4-pro', + 'x4-classic': '-x4-classic', + }[pioenv] return f'{base_version}{device_suffix}' @@ -172,7 +216,10 @@ def inject_version(env): ('CROSSINK_PIOENV', f'\\"{pioenv}\\"'), ]) - if pioenv in {'default', 'sticky', 'x4-pro', 'x4-classic'}: + if hasattr(env, 'AddBuildMiddleware'): + register_build_info(env, project_dir) + + if pioenv in {'default', 'sticky', 'x4-pro', 'x4-pro-light-sleep', 'x4-classic'}: version_string = get_hardware_version(project_dir, pioenv) if os.environ.get('CROSSINK_RC_HASH'): print(f'CrossInk RC build version: {version_string}') @@ -206,7 +253,7 @@ def inject_version(env): ]) print(f'CrossInk test build version: {ci_version}{suffix}') - elif pioenv in {'x4-pro-debug', 'x4-classic-debug'}: + elif pioenv in {'x4-pro-debug', 'x4-pro-light-sleep-debug', 'x4-classic-debug'}: branch = get_git_branch(project_dir) short_hash = get_git_short_hash(project_dir) ci_version = get_crossink_version(project_dir) diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index affd41a527f..24c506cc419 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -50,6 +50,7 @@ #include "components/UiAppHelpers.h" #include "components/icons/frontlightHeaderIcons.h" #include "fontIds.h" +#include "util/BuildInfo.h" #include "util/DictionaryRegistry.h" #include "util/FrontlightSchedule.h" @@ -148,12 +149,33 @@ std::string formatCompactDuration(const uint32_t seconds) { return buf; } +constexpr const char* systemVersionLabel = "CrossInk " CROSSINK_VERSION; + +// Space below the settings list for the System footer: the build details line +// plus the version on one line, or two when it has to wrap. +int systemVersionFooterReserve(const GfxRenderer& renderer, const int pageWidth, const ThemeMetrics& metrics) { + const int maxWidth = pageWidth - systemVersionFooterSideMargin * 2; + const int versionLines = renderer.getTextWidth(SMALL_FONT_ID, systemVersionLabel) <= maxWidth ? 1 : 2; + return metrics.verticalSpacing + systemVersionFooterBottomInset + + versionLines * renderer.getLineHeight(SMALL_FONT_ID); +} + void drawSystemVersionFooter(const GfxRenderer& renderer, const int pageWidth, const int pageHeight, const ThemeMetrics& metrics) { - const std::string label = "CrossInk " CROSSINK_VERSION; + const std::string label = systemVersionLabel; const int maxWidth = pageWidth - systemVersionFooterSideMargin * 2; - const int bottomLineY = + const int lineHeight = renderer.getLineHeight(SMALL_FONT_ID); + const int detailsLineY = pageHeight - metrics.buttonHintsHeight - metrics.verticalSpacing - systemVersionFooterBottomInset; + const int bottomLineY = detailsLineY - lineHeight; + + // Branch, commit ("*" marks uncommitted changes), build number and UTC build time. + char details[112]; + const char* buildNumber = BuildInfo::buildNumber(); + snprintf(details, sizeof(details), "%s %s%s%s%s %s", BuildInfo::gitBranch(), CROSSINK_GIT_SHA, + strcmp(CROSSINK_GIT_DIRTY, "1") == 0 ? "*" : "", buildNumber[0] != '\0' ? " #" : "", buildNumber, + BuildInfo::buildTime()); + drawCenteredTextLine(renderer, pageWidth, detailsLineY, renderer.truncatedText(SMALL_FONT_ID, details, maxWidth)); if (renderer.getTextWidth(SMALL_FONT_ID, label.c_str()) <= maxWidth) { drawCenteredTextLine(renderer, pageWidth, bottomLineY, label); @@ -182,7 +204,6 @@ void drawSystemVersionFooter(const GfxRenderer& renderer, const int pageWidth, c const std::string secondLine = lineBreak == std::string::npos ? "" : renderer.truncatedText(SMALL_FONT_ID, label.substr(lineBreak).c_str(), maxWidth); - const int lineHeight = renderer.getLineHeight(SMALL_FONT_ID); drawCenteredTextLine(renderer, pageWidth, bottomLineY - lineHeight, firstLine); drawCenteredTextLine(renderer, pageWidth, bottomLineY, secondLine); } @@ -1374,8 +1395,11 @@ void SettingsActivity::buildSettingsScreen(UiApp::ScreenType& screen) { // compact header geometry is in absolute screen coordinates. Overlap the // tab's top rule with the header's final underline pixel. const int tabTop = std::max(safe.y, CompactHeader::headerBottomY(metrics) - 1); - screen.setContentMargin( - fui::Insets{static_cast(tabTop - safe.y), 0, static_cast(metrics.buttonHintsHeight), 0}); + int bottomMargin = metrics.buttonHintsHeight; + if (!isFileBrowserView() && selectedCategoryIndex == 3) { + bottomMargin += systemVersionFooterReserve(renderer, renderer.getScreenWidth(), metrics); + } + screen.setContentMargin(fui::Insets{static_cast(tabTop - safe.y), 0, static_cast(bottomMargin), 0}); if (isFileBrowserView()) { screen.spacer(static_cast(metrics.verticalSpacing)); diff --git a/src/network/CrossPointWebServer.cpp b/src/network/CrossPointWebServer.cpp index 502da42e077..b716a5479ef 100644 --- a/src/network/CrossPointWebServer.cpp +++ b/src/network/CrossPointWebServer.cpp @@ -46,6 +46,7 @@ #include "html/js/jszip_minJs.generated.h" #include "util/BookCacheUtils.h" #include "util/BootReason.h" +#include "util/BuildInfo.h" #include "util/FontFamilyLabel.h" #include "util/StringUtils.h" @@ -776,6 +777,9 @@ void CrossPointWebServer::handleStatus() const { build["env"] = CROSSINK_PIOENV; build["gitSha"] = CROSSINK_GIT_SHA; build["gitDirty"] = CROSSINK_GIT_DIRTY; + build["gitBranch"] = BuildInfo::gitBranch(); + build["buildNumber"] = BuildInfo::buildNumber(); + build["buildTime"] = BuildInfo::buildTime(); #ifndef SIMULATOR const BoardConfig::BoardProfile& board = BoardConfig::ACTIVE; diff --git a/src/util/BuildInfo.cpp b/src/util/BuildInfo.cpp new file mode 100644 index 00000000000..37fd6d6dd07 --- /dev/null +++ b/src/util/BuildInfo.cpp @@ -0,0 +1,19 @@ +#include "BuildInfo.h" + +#ifndef CROSSINK_GIT_BRANCH +#define CROSSINK_GIT_BRANCH "unknown" +#endif + +#ifndef CROSSINK_BUILD_NUMBER +#define CROSSINK_BUILD_NUMBER "" +#endif + +#ifndef CROSSINK_BUILD_TIME +#define CROSSINK_BUILD_TIME "unknown" +#endif + +namespace BuildInfo { +const char* gitBranch() { return CROSSINK_GIT_BRANCH; } +const char* buildNumber() { return CROSSINK_BUILD_NUMBER; } +const char* buildTime() { return CROSSINK_BUILD_TIME; } +} // namespace BuildInfo diff --git a/src/util/BuildInfo.h b/src/util/BuildInfo.h new file mode 100644 index 00000000000..923b10be1c3 --- /dev/null +++ b/src/util/BuildInfo.h @@ -0,0 +1,11 @@ +#pragma once + +// Per-build provenance for the Settings > System footer. Kept out of +// AppVersion.h because scripts/git_branch.py defines these only for +// BuildInfo.cpp: the build time changes every build, and a global define would +// force a full rebuild each time. +namespace BuildInfo { +const char* gitBranch(); +const char* buildNumber(); +const char* buildTime(); +} // namespace BuildInfo From 1b6ccba232c3ee65e98823b021e64075cdb98e8b Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 08:07:42 +0000 Subject: [PATCH 2/2] fix: shorten the System footer build line to branch and commit The footer showed the full branch, build number and build time, which ran too long on device. It now shows " [*]": batch branches (test/combined-0927-b11) collapse to "b11", other branches drop their prefix folder. /api/status keeps the full branch, number and time. Co-Authored-By: Claude Opus 5.5 (1M context) Claude-Session: https://claude.ai/code/session_01FQ85oMXFSkfR2wF4dx4hLz --- CHANGELOG.md | 2 +- scripts/git_branch.py | 10 ++++++++++ src/activities/settings/SettingsActivity.cpp | 10 ++++------ src/util/BuildInfo.cpp | 5 +++++ src/util/BuildInfo.h | 2 ++ 5 files changed, 22 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85f6c650ceb..88a272e81a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ - The OPDS browser shows how many books a category holds, in parentheses next to its arrow, when the catalog provides a count (a `thr:count` link attribute or a "12713 books" summary). Folder titles that start with a 📁 emoji show as "/name" instead, since the device fonts have no folder emoji. - On devices with PSRAM (Sticky, X4 Pro), the OPDS browser downloads the next page of a catalog in the background while you browse, and keeps pages you have visited in memory, so Next page, Previous page and Back open without waiting on the server. - The web file manager's image preview has previous and next buttons, and the left and right arrow keys, to step through the images in the current folder. Other file types are skipped, and stepping wraps around at either end. The preview also shows the image's position in the folder, its pixel dimensions, its file size and, when the file list has one, its modified date. -- The Settings > System footer shows the firmware's branch, commit (with `*` when built from uncommitted changes), build number and UTC build time under the version. The build number is the branch's commit count unless the build sets `CROSSINK_BUILD_NUMBER`, and is left out for shallow checkouts. The System list stops above the footer instead of running under it. The web status API reports the same fields. +- The Settings > System footer shows the firmware's branch (the batch number, such as `b11`, for combined test builds) and commit, with `*` when built from uncommitted changes, under the version. The System list stops above the footer instead of running under it. The web status API reports the full branch, build number (the commit count unless the build sets `CROSSINK_BUILD_NUMBER`; left out for shallow checkouts) and UTC build time. ### Changed diff --git a/scripts/git_branch.py b/scripts/git_branch.py index e49e873a94c..70328a1f6dc 100644 --- a/scripts/git_branch.py +++ b/scripts/git_branch.py @@ -114,6 +114,15 @@ def get_build_number(project_dir): return number if number.isdigit() else '' +def short_branch_label(branch): + # Batch branches (test/combined-0927-b11) show as their batch number; other + # branches drop their prefix folder (claude/, feature/, fix/). + batch = re.search(r'combined-\d+-(b\d+)$', branch) + if batch: + return batch.group(1) + return branch.rsplit('/', 1)[-1] or 'unknown' + + def register_build_info(env, project_dir): # The build time changes on every build. Defining it globally would change # every compile command and force a full rebuild, so scope these defines to @@ -127,6 +136,7 @@ def register_build_info(env, project_dir): build_time = datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%dT%H:%MZ') defines = [ ('CROSSINK_GIT_BRANCH', f'\\"{branch}\\"'), + ('CROSSINK_GIT_BRANCH_SHORT', f'\\"{short_branch_label(branch)}\\"'), ('CROSSINK_BUILD_NUMBER', f'\\"{get_build_number(project_dir)}\\"'), ('CROSSINK_BUILD_TIME', f'\\"{build_time}\\"'), ] diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index 24c506cc419..f62b4a38570 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -169,12 +169,10 @@ void drawSystemVersionFooter(const GfxRenderer& renderer, const int pageWidth, c pageHeight - metrics.buttonHintsHeight - metrics.verticalSpacing - systemVersionFooterBottomInset; const int bottomLineY = detailsLineY - lineHeight; - // Branch, commit ("*" marks uncommitted changes), build number and UTC build time. - char details[112]; - const char* buildNumber = BuildInfo::buildNumber(); - snprintf(details, sizeof(details), "%s %s%s%s%s %s", BuildInfo::gitBranch(), CROSSINK_GIT_SHA, - strcmp(CROSSINK_GIT_DIRTY, "1") == 0 ? "*" : "", buildNumber[0] != '\0' ? " #" : "", buildNumber, - BuildInfo::buildTime()); + // Short branch and commit; "*" marks uncommitted changes. + char details[64]; + snprintf(details, sizeof(details), "%s %s%s", BuildInfo::shortBranch(), CROSSINK_GIT_SHA, + strcmp(CROSSINK_GIT_DIRTY, "1") == 0 ? "*" : ""); drawCenteredTextLine(renderer, pageWidth, detailsLineY, renderer.truncatedText(SMALL_FONT_ID, details, maxWidth)); if (renderer.getTextWidth(SMALL_FONT_ID, label.c_str()) <= maxWidth) { diff --git a/src/util/BuildInfo.cpp b/src/util/BuildInfo.cpp index 37fd6d6dd07..1e65c39c3b6 100644 --- a/src/util/BuildInfo.cpp +++ b/src/util/BuildInfo.cpp @@ -4,6 +4,10 @@ #define CROSSINK_GIT_BRANCH "unknown" #endif +#ifndef CROSSINK_GIT_BRANCH_SHORT +#define CROSSINK_GIT_BRANCH_SHORT CROSSINK_GIT_BRANCH +#endif + #ifndef CROSSINK_BUILD_NUMBER #define CROSSINK_BUILD_NUMBER "" #endif @@ -14,6 +18,7 @@ namespace BuildInfo { const char* gitBranch() { return CROSSINK_GIT_BRANCH; } +const char* shortBranch() { return CROSSINK_GIT_BRANCH_SHORT; } const char* buildNumber() { return CROSSINK_BUILD_NUMBER; } const char* buildTime() { return CROSSINK_BUILD_TIME; } } // namespace BuildInfo diff --git a/src/util/BuildInfo.h b/src/util/BuildInfo.h index 923b10be1c3..533dfd6ed15 100644 --- a/src/util/BuildInfo.h +++ b/src/util/BuildInfo.h @@ -6,6 +6,8 @@ // force a full rebuild each time. namespace BuildInfo { const char* gitBranch(); +// Batch number ("b11") for batch branches, else the branch without its prefix folder. +const char* shortBranch(); const char* buildNumber(); const char* buildTime(); } // namespace BuildInfo