Compare commits
5
Commits
1.0
...
63265cc22f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63265cc22f | ||
|
|
9e73c82641 | ||
|
|
f2a40cd019 | ||
|
|
5caf1d6ef8 | ||
|
|
e17e881c00 |
@@ -0,0 +1,68 @@
|
|||||||
|
name: build-dev
|
||||||
|
on:
|
||||||
|
# Every push to dev: build, run the offline test suite, publish to the
|
||||||
|
# dev Attic cache.
|
||||||
|
push:
|
||||||
|
branches: [dev]
|
||||||
|
jobs:
|
||||||
|
nix-build:
|
||||||
|
runs-on: build-server
|
||||||
|
steps:
|
||||||
|
# actions/checkout@v4 is unreliable on host-mode runners (its files are
|
||||||
|
# copied into the workspace instead of the repo), so clone explicitly.
|
||||||
|
- name: Checkout
|
||||||
|
run: |
|
||||||
|
rm -rf ./* ./.[!.]* 2>/dev/null || true
|
||||||
|
git config --global http.extraheader "Authorization: token ${{ github.token }}"
|
||||||
|
git clone --branch "${{ gitea.ref_name }}" \
|
||||||
|
"${{ gitea.server_url }}/${{ gitea.repository }}.git" .
|
||||||
|
test -f flake.nix
|
||||||
|
git log --oneline -1
|
||||||
|
- name: Inspect workspace
|
||||||
|
run: |
|
||||||
|
pwd
|
||||||
|
ls -la
|
||||||
|
git rev-parse --show-toplevel 2>&1 || true
|
||||||
|
git remote -v 2>&1 || true
|
||||||
|
git describe --tags --always 2>&1 || true
|
||||||
|
test -f flake.nix && echo "flake.nix present" || echo "NO flake.nix"
|
||||||
|
# Dev builds fingerprint as the short commit hash.
|
||||||
|
# Persisted via GITHUB_ENV so the impure nix eval below reads it via
|
||||||
|
# builtins.getEnv.
|
||||||
|
- name: Compute version
|
||||||
|
run: |
|
||||||
|
echo "APP_VERSION=$(git rev-parse --short HEAD)" >> "$GITHUB_ENV"
|
||||||
|
# checks is keyed per-system (checks.<system>.default); nix build's
|
||||||
|
# #attr shorthand can't resolve it, so resolve the current system inline.
|
||||||
|
# --impure lets the flake read APP_VERSION via builtins.getEnv
|
||||||
|
# (pure evals always get the package defaults).
|
||||||
|
- name: Build package
|
||||||
|
run: |
|
||||||
|
nix build -L ".#packages.$(nix eval --impure --raw --expr 'builtins.currentSystem').default" \
|
||||||
|
--impure --out-link result-pkg
|
||||||
|
# The offline test suite runs in the check derivation's checkPhase.
|
||||||
|
# This is a separate build/output from the package itself.
|
||||||
|
- name: Build + offline test suite
|
||||||
|
run: |
|
||||||
|
nix build -L ".#checks.$(nix eval --impure --raw --expr 'builtins.currentSystem').default" \
|
||||||
|
--impure --out-link result-checks
|
||||||
|
- name: Live portal tests
|
||||||
|
env:
|
||||||
|
RWT_LIVE_ACCOUNT_EMAIL: ${{ secrets.RWT_LIVE_ACCOUNT_EMAIL }}
|
||||||
|
RWT_LIVE_LICENSE_EMAIL: ${{ secrets.RWT_LIVE_LICENSE_EMAIL }}
|
||||||
|
RWT_LIVE_LICENSE_KEY: ${{ secrets.RWT_LIVE_LICENSE_KEY }}
|
||||||
|
run: |
|
||||||
|
nix develop --command bash -c '
|
||||||
|
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug
|
||||||
|
cmake --build build --target test_api_client test_license_service
|
||||||
|
./build/test_api_client
|
||||||
|
./build/test_license_service
|
||||||
|
'
|
||||||
|
# Dev closure (the actual package, not the check output) lands in the
|
||||||
|
# dev cache.
|
||||||
|
- name: Push to Attic dev cache
|
||||||
|
env:
|
||||||
|
ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN_DEV }}
|
||||||
|
run: |
|
||||||
|
attic login radiantwave https://attic.radiantwavetech.com "$ATTIC_TOKEN"
|
||||||
|
attic push radiantwave:radiantwave-dev ./result-pkg
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
name: build
|
name: build-main
|
||||||
|
|
||||||
on:
|
on:
|
||||||
# Manual only for now. Select the branch to build when dispatching:
|
# Manual only. Dispatch against main: build, run the offline test suite,
|
||||||
# main -> APP_VERSION is the current git tag
|
# publish to the release Attic cache.
|
||||||
# dev -> APP_VERSION is the current commit hash
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -12,8 +11,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# actions/checkout@v4 is unreliable on host-mode runners (its files are
|
# actions/checkout@v4 is unreliable on host-mode runners (its files are
|
||||||
# copied into the workspace instead of the repo), so clone explicitly.
|
# copied into the workspace instead of the repo), so clone explicitly.
|
||||||
# A full clone (no --depth) is required so `git describe --tags` on main
|
# A full clone (no --depth) is required so `git describe --tags` resolves
|
||||||
# resolves the real nearest tag instead of falling back to a hash.
|
# the real nearest tag instead of falling back to a hash.
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
run: |
|
run: |
|
||||||
rm -rf ./* ./.[!.]* 2>/dev/null || true
|
rm -rf ./* ./.[!.]* 2>/dev/null || true
|
||||||
@@ -32,18 +31,12 @@ jobs:
|
|||||||
git describe --tags --always 2>&1 || true
|
git describe --tags --always 2>&1 || true
|
||||||
test -f flake.nix && echo "flake.nix present" || echo "NO flake.nix"
|
test -f flake.nix && echo "flake.nix present" || echo "NO flake.nix"
|
||||||
|
|
||||||
# Compute the version string baked into the binary's Home footer:
|
# Release builds report the current git tag.
|
||||||
# main reports the git tag, dev reports the commit hash fingerprint.
|
# Persisted via GITHUB_ENV so the impure nix eval below reads it via
|
||||||
# The key stays in the build step's environment (never echoed).
|
# builtins.getEnv.
|
||||||
- name: Compute version
|
- name: Compute version
|
||||||
id: version
|
|
||||||
run: |
|
run: |
|
||||||
if [ "${{ gitea.ref_name }}" = "main" ]; then
|
echo "APP_VERSION=$(git describe --tags --always)" >> "$GITHUB_ENV"
|
||||||
APP_VERSION="$(git describe --tags --always)"
|
|
||||||
else
|
|
||||||
APP_VERSION="$(git rev-parse --short HEAD)"
|
|
||||||
fi
|
|
||||||
echo "Branch: ${{ gitea.ref_name }} -> APP_VERSION=$APP_VERSION"
|
|
||||||
|
|
||||||
# checks is keyed per-system (checks.<system>.default); nix build's
|
# checks is keyed per-system (checks.<system>.default); nix build's
|
||||||
# #attr shorthand can't resolve it, so resolve the current system inline.
|
# #attr shorthand can't resolve it, so resolve the current system inline.
|
||||||
@@ -66,10 +59,10 @@ jobs:
|
|||||||
./build/test_license_service
|
./build/test_license_service
|
||||||
'
|
'
|
||||||
|
|
||||||
- name: Push to Attic cache
|
# Release closure lands in the release cache.
|
||||||
|
- name: Push to Attic release cache
|
||||||
|
env:
|
||||||
|
ATTIC_TOKEN: ${{ secrets.ATTIC_TOKEN_PROD }}
|
||||||
run: |
|
run: |
|
||||||
nix profile install github:zhaofengli/attic#attic-client \
|
attic login radiantwave https://attic.radiantwavetech.com "$ATTIC_TOKEN"
|
||||||
--extra-experimental-features "nix-command flakes"
|
attic push radiantwave:radiantwave ./result
|
||||||
|
|
||||||
attic login radiantwave https://attic.radiantwavetech.com "${{ secrets.ATTIC_TOKEN }}"
|
|
||||||
attic push radiantwave ./result-kiosk ./result-android ./result-commercial
|
|
||||||
@@ -37,7 +37,7 @@ struct AudioManifestEntry {
|
|||||||
// and keeps the database's audio_files table in sync with what is actually
|
// and keeps the database's audio_files table in sync with what is actually
|
||||||
// on disk.
|
// on disk.
|
||||||
//
|
//
|
||||||
// Fetches new_checksums.md5, compares each entry against the local file's MD5,
|
// Fetches checksums.md5, compares each entry against the local file's MD5,
|
||||||
// and re-downloads anything that is missing or whose checksum differs.
|
// and re-downloads anything that is missing or whose checksum differs.
|
||||||
// Files present on disk but absent from the manifest are deleted, and the
|
// Files present on disk but absent from the manifest are deleted, and the
|
||||||
// audio_files table is reconciled on every pass (and after each individual
|
// audio_files table is reconciled on every pass (and after each individual
|
||||||
@@ -68,7 +68,7 @@ public:
|
|||||||
|
|
||||||
static constexpr const char *AUDIO_BASE_URL =
|
static constexpr const char *AUDIO_BASE_URL =
|
||||||
"https://files.radiantwavetech.com/audio/";
|
"https://files.radiantwavetech.com/audio/";
|
||||||
static constexpr const char *MANIFEST_FILENAME = "new_checksums.md5";
|
static constexpr const char *MANIFEST_FILENAME = "checksums.md5";
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void run();
|
void run();
|
||||||
@@ -88,7 +88,7 @@ private:
|
|||||||
db::DatabaseManager *db_ = nullptr;
|
db::DatabaseManager *db_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parses new_checksums.md5 manifest text into entries. Blank lines and lines
|
// Parses checksums.md5 manifest text into entries. Blank lines and lines
|
||||||
// starting with '#' are ignored. Lines that don't match a known format are
|
// starting with '#' are ignored. Lines that don't match a known format are
|
||||||
// skipped. Returns false if no valid entries were found.
|
// skipped. Returns false if no valid entries were found.
|
||||||
bool parseManifest(const std::string &text,
|
bool parseManifest(const std::string &text,
|
||||||
|
|||||||
+41
-7
@@ -4,14 +4,48 @@
|
|||||||
|
|
||||||
namespace radiantwave {
|
namespace radiantwave {
|
||||||
|
|
||||||
inline void getLayoutSize(SDL_Renderer* renderer, int* w, int* h)
|
// Design resolution every page lays out against. The renderer's logical
|
||||||
|
// presentation (LETTERBOX) scales this fixed space onto whatever
|
||||||
|
// window/backbuffer the display actually provides.
|
||||||
|
inline constexpr int kDesignWidth = 1920;
|
||||||
|
inline constexpr int kDesignHeight = 1080;
|
||||||
|
|
||||||
|
// Returns the size pages should lay out against: the renderer's logical size
|
||||||
|
// when presentation is active, otherwise the real window size. The output
|
||||||
|
// params are always written — design constants are the last-resort fallback.
|
||||||
|
inline void getLayoutSize(SDL_Renderer *renderer, int *w, int *h)
|
||||||
{
|
{
|
||||||
SDL_RendererLogicalPresentation mode = SDL_LOGICAL_PRESENTATION_DISABLED;
|
if (!w || !h)
|
||||||
if (!SDL_GetRenderLogicalPresentation(renderer, w, h, &mode) ||
|
return;
|
||||||
mode == SDL_LOGICAL_PRESENTATION_DISABLED || *w <= 0 || *h <= 0)
|
*w = kDesignWidth;
|
||||||
{
|
*h = kDesignHeight;
|
||||||
SDL_GetWindowSize(SDL_GetRenderWindow(renderer), w, h);
|
|
||||||
}
|
if (!renderer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SDL_RendererLogicalPresentation mode = SDL_LOGICAL_PRESENTATION_DISABLED;
|
||||||
|
int logicalW = 0;
|
||||||
|
int logicalH = 0;
|
||||||
|
if (SDL_GetRenderLogicalPresentation(renderer, &logicalW, &logicalH,
|
||||||
|
&mode) &&
|
||||||
|
mode != SDL_LOGICAL_PRESENTATION_DISABLED && logicalW > 0 &&
|
||||||
|
logicalH > 0) {
|
||||||
|
*w = logicalW;
|
||||||
|
*h = logicalH;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Window *window = SDL_GetRenderWindow(renderer);
|
||||||
|
if (!window)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int windowW = 0;
|
||||||
|
int windowH = 0;
|
||||||
|
SDL_GetWindowSize(window, &windowW, &windowH);
|
||||||
|
if (windowW > 0 && windowH > 0) {
|
||||||
|
*w = windowW;
|
||||||
|
*h = windowH;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace radiantwave
|
} // namespace radiantwave
|
||||||
|
|||||||
+61
-10
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include "app_state.h"
|
#include "app_state.h"
|
||||||
#include "home.h"
|
#include "home.h"
|
||||||
|
#include "logical_size.h"
|
||||||
#include "logger/logger.h"
|
#include "logger/logger.h"
|
||||||
#include "provisioning.h"
|
#include "provisioning.h"
|
||||||
|
|
||||||
@@ -32,11 +33,26 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) {
|
|||||||
|
|
||||||
auto *state = new AppState{};
|
auto *state = new AppState{};
|
||||||
|
|
||||||
// SDL_WINDOW_FULLSCREEN fills the desktop's native resolution, so the
|
// Size the fullscreen window from the display's actual mode so the very
|
||||||
// compositor never has to scale the moving scroller (scaling moving text
|
// first buffer commit already matches what the compositor scanouts 1:1.
|
||||||
// causes flicker).
|
// Requesting a hardcoded size left the backbuffer at that size on
|
||||||
state->window =
|
// displays running a different mode, and frames reached the panel
|
||||||
SDL_CreateWindow("RadiantWave", 1920, 1080, SDL_WINDOW_FULLSCREEN);
|
// unscaled (quarter-size on 4K, cropped on smaller panels).
|
||||||
|
const SDL_DisplayMode *displayMode =
|
||||||
|
SDL_GetCurrentDisplayMode(SDL_GetPrimaryDisplay());
|
||||||
|
int windowWidth =
|
||||||
|
(displayMode && displayMode->w > 0) ? displayMode->w
|
||||||
|
: radiantwave::kDesignWidth;
|
||||||
|
int windowHeight =
|
||||||
|
(displayMode && displayMode->h > 0) ? displayMode->h
|
||||||
|
: radiantwave::kDesignHeight;
|
||||||
|
|
||||||
|
// Borderless desktop fullscreen: the compositor never has to scale the
|
||||||
|
// moving scroller (scaling moving text causes flicker). Deliberately no
|
||||||
|
// SDL_WINDOW_HIGH_PIXEL_DENSITY — kiosk panels are not HiDPI, so keeping
|
||||||
|
// the pixel buffer equal to the display mode keeps scanout 1:1.
|
||||||
|
state->window = SDL_CreateWindow("RadiantWave", windowWidth, windowHeight,
|
||||||
|
SDL_WINDOW_FULLSCREEN);
|
||||||
if (!state->window) {
|
if (!state->window) {
|
||||||
SDL_Log("Failed to create window: %s", SDL_GetError());
|
SDL_Log("Failed to create window: %s", SDL_GetError());
|
||||||
delete state;
|
delete state;
|
||||||
@@ -59,16 +75,38 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) {
|
|||||||
std::string(SDL_GetError()) + ")");
|
std::string(SDL_GetError()) + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock the renderer to a 1920x1080 logical coordinate space. All page
|
// All page layout targets the fixed design resolution; LETTERBOX maps it
|
||||||
// layout code already targets this resolution, so everything renders at
|
// onto the real window with aspect preserved (bars on non-16:9 panels).
|
||||||
// the correct size on any display — the GPU scales to native pixels.
|
if (!SDL_SetRenderLogicalPresentation(state->renderer,
|
||||||
if (!SDL_SetRenderLogicalPresentation(state->renderer, 1920, 1080,
|
radiantwave::kDesignWidth,
|
||||||
SDL_LOGICAL_PRESENTATION_STRETCH)) {
|
radiantwave::kDesignHeight,
|
||||||
|
SDL_LOGICAL_PRESENTATION_LETTERBOX)) {
|
||||||
radiantwave::logger::Logger::instance().warning(
|
radiantwave::logger::Logger::instance().warning(
|
||||||
"Graphics", "Could not set logical render size; layout may be "
|
"Graphics", "Could not set logical render size; layout may be "
|
||||||
"wrong on HiDPI displays");
|
"wrong on HiDPI displays");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Record how the requested mode, window, pixel buffer, and render
|
||||||
|
// output actually compare — this is the evidence trail for
|
||||||
|
// display-scaling problems on field units.
|
||||||
|
int windowW = 0, windowH = 0;
|
||||||
|
SDL_GetWindowSize(state->window, &windowW, &windowH);
|
||||||
|
int pixelW = 0, pixelH = 0;
|
||||||
|
SDL_GetWindowSizeInPixels(state->window, &pixelW, &pixelH);
|
||||||
|
int outputW = 0, outputH = 0;
|
||||||
|
SDL_GetRenderOutputSize(state->renderer, &outputW, &outputH);
|
||||||
|
radiantwave::logger::Logger::instance().info(
|
||||||
|
"Graphics",
|
||||||
|
"Requested " + std::to_string(windowWidth) + "x" +
|
||||||
|
std::to_string(windowHeight) + ", window " +
|
||||||
|
std::to_string(windowW) + "x" + std::to_string(windowH) +
|
||||||
|
", pixels " + std::to_string(pixelW) + "x" +
|
||||||
|
std::to_string(pixelH) + ", render output " +
|
||||||
|
std::to_string(outputW) + "x" + std::to_string(outputH) +
|
||||||
|
", presentation LETTERBOX");
|
||||||
|
}
|
||||||
|
|
||||||
SDL_HideCursor();
|
SDL_HideCursor();
|
||||||
|
|
||||||
// Initialize configuration, database, and network manager
|
// Initialize configuration, database, and network manager
|
||||||
@@ -157,6 +195,19 @@ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) {
|
|||||||
state.quitRequested = true;
|
state.quitRequested = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event->type == SDL_EVENT_WINDOW_RESIZED) {
|
||||||
|
radiantwave::logger::Logger::instance().info(
|
||||||
|
"Graphics", "Window resized to " +
|
||||||
|
std::to_string(event->window.data1) + "x" +
|
||||||
|
std::to_string(event->window.data2));
|
||||||
|
// Logical presentation persists across resizes; re-apply defensively
|
||||||
|
// in case backend state was reset underneath us.
|
||||||
|
SDL_SetRenderLogicalPresentation(state.renderer,
|
||||||
|
radiantwave::kDesignWidth,
|
||||||
|
radiantwave::kDesignHeight,
|
||||||
|
SDL_LOGICAL_PRESENTATION_LETTERBOX);
|
||||||
|
}
|
||||||
|
|
||||||
if (!state.keybinds.handleEvent(*event)) {
|
if (!state.keybinds.handleEvent(*event)) {
|
||||||
state.pages.handleEvent(*event);
|
state.pages.handleEvent(*event);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user