Compare commits
4
Commits
ec6b76618c
...
1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b96631c374 | ||
|
|
39eb6d7bf7 | ||
|
|
3c9de0bee2 | ||
|
|
8e930ef8d9 |
@@ -65,3 +65,11 @@ jobs:
|
|||||||
./build/test_api_client
|
./build/test_api_client
|
||||||
./build/test_license_service
|
./build/test_license_service
|
||||||
'
|
'
|
||||||
|
|
||||||
|
- name: Push to Attic cache
|
||||||
|
run: |
|
||||||
|
nix profile install github:zhaofengli/attic#attic-client \
|
||||||
|
--extra-experimental-features "nix-command flakes"
|
||||||
|
|
||||||
|
attic login radiantwave https://attic.radiantwavetech.com "${{ secrets.ATTIC_TOKEN }}"
|
||||||
|
attic push radiantwave ./result-kiosk ./result-android ./result-commercial
|
||||||
|
|||||||
@@ -27,17 +27,17 @@ namespace radiantwave::audio {
|
|||||||
// honored, anything else falls back to the Hz parsed from the filename, so
|
// honored, anything else falls back to the Hz parsed from the filename, so
|
||||||
// the file's own name is always the safety net.
|
// the file's own name is always the safety net.
|
||||||
struct AudioManifestEntry {
|
struct AudioManifestEntry {
|
||||||
std::string filename;
|
std::string filename;
|
||||||
std::string display_name;
|
std::string display_name;
|
||||||
int solfeggio_frequency_hz = 0;
|
int solfeggio_frequency_hz = 0;
|
||||||
std::string md5;
|
std::string md5;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Downloads audio assets from files.radiantwavetech.com in the background
|
// Downloads audio assets from files.radiantwavetech.com in the background
|
||||||
// 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 checksums.md5, compares each entry against the local file's MD5,
|
// Fetches new_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
|
||||||
@@ -50,45 +50,49 @@ struct AudioManifestEntry {
|
|||||||
// checksums are picked up while running.
|
// checksums are picked up while running.
|
||||||
class AudioDownloader {
|
class AudioDownloader {
|
||||||
public:
|
public:
|
||||||
AudioDownloader() = default;
|
AudioDownloader() = default;
|
||||||
~AudioDownloader();
|
~AudioDownloader();
|
||||||
|
|
||||||
AudioDownloader(const AudioDownloader&) = delete;
|
AudioDownloader(const AudioDownloader &) = delete;
|
||||||
AudioDownloader& operator=(const AudioDownloader&) = delete;
|
AudioDownloader &operator=(const AudioDownloader &) = delete;
|
||||||
|
|
||||||
// Spawns the background thread. Safe to call repeatedly; only one worker
|
// Spawns the background thread. Safe to call repeatedly; only one worker
|
||||||
// runs at a time. The manifest is reconciled against `db` on every pass.
|
// runs at a time. The manifest is reconciled against `db` on every pass.
|
||||||
void start(db::DatabaseManager& db);
|
void start(db::DatabaseManager &db);
|
||||||
|
|
||||||
// Signals the worker to stop and joins the thread.
|
// Signals the worker to stop and joins the thread.
|
||||||
void shutdown();
|
void shutdown();
|
||||||
|
|
||||||
// Returns the current human-readable status for display (e.g. footer).
|
// Returns the current human-readable status for display (e.g. footer).
|
||||||
std::string statusString() const;
|
std::string statusString() const;
|
||||||
|
|
||||||
static constexpr const char* AUDIO_BASE_URL = "https://files.radiantwavetech.com/audio/";
|
static constexpr const char *AUDIO_BASE_URL =
|
||||||
static constexpr const char* MANIFEST_FILENAME = "checksums.md5";
|
"https://files.radiantwavetech.com/audio/";
|
||||||
|
static constexpr const char *MANIFEST_FILENAME = "new_checksums.md5";
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void run();
|
void run();
|
||||||
bool fetchManifest(std::vector<AudioManifestEntry>& out);
|
bool fetchManifest(std::vector<AudioManifestEntry> &out);
|
||||||
bool downloadAndVerify(const std::string& filename, const std::string& expectedMd5, const std::string& displayName);
|
bool downloadAndVerify(const std::string &filename,
|
||||||
|
const std::string &expectedMd5,
|
||||||
|
const std::string &displayName);
|
||||||
|
|
||||||
void setStatus(const std::string& msg);
|
void setStatus(const std::string &msg);
|
||||||
void wait(std::chrono::seconds seconds);
|
void wait(std::chrono::seconds seconds);
|
||||||
|
|
||||||
mutable std::mutex mutex_;
|
mutable std::mutex mutex_;
|
||||||
std::condition_variable cv_;
|
std::condition_variable cv_;
|
||||||
std::thread worker_;
|
std::thread worker_;
|
||||||
std::atomic<bool> stop_{false};
|
std::atomic<bool> stop_{false};
|
||||||
std::string status_;
|
std::string status_;
|
||||||
db::DatabaseManager* db_ = nullptr;
|
db::DatabaseManager *db_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parses checksums.md5 manifest text into entries. Blank lines and lines
|
// Parses new_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, std::vector<AudioManifestEntry>& out);
|
bool parseManifest(const std::string &text,
|
||||||
|
std::vector<AudioManifestEntry> &out);
|
||||||
|
|
||||||
// Maps a frequency value in Hz to its internal solfeggio frequency id using
|
// Maps a frequency value in Hz to its internal solfeggio frequency id using
|
||||||
// the same table the color palettes are seeded from (174/285->1, 396->2,
|
// the same table the color palettes are seeded from (174/285->1, 396->2,
|
||||||
@@ -100,9 +104,9 @@ int frequencyIdForHz(int hz);
|
|||||||
// "396Hz.wav" -> 396. Requires the digits to be followed by "Hz"
|
// "396Hz.wav" -> 396. Requires the digits to be followed by "Hz"
|
||||||
// (case-insensitive); returns 0 when the name has no frequency (e.g.
|
// (case-insensitive); returns 0 when the name has no frequency (e.g.
|
||||||
// "AlphaWaves.wav", "OriginalAudio.wav").
|
// "AlphaWaves.wav", "OriginalAudio.wav").
|
||||||
int hzFromFilename(const std::string& filename);
|
int hzFromFilename(const std::string &filename);
|
||||||
|
|
||||||
// Computes the lowercase MD5 hex of a file, or an empty string on failure.
|
// Computes the lowercase MD5 hex of a file, or an empty string on failure.
|
||||||
std::string computeFileMd5(const std::string& path);
|
std::string computeFileMd5(const std::string &path);
|
||||||
|
|
||||||
} // namespace radiantwave::audio
|
} // namespace radiantwave::audio
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "audio_settings_page.h"
|
#include "audio_settings_page.h"
|
||||||
#include "app_state.h"
|
#include "app_state.h"
|
||||||
|
#include "logical_size.h"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
@@ -477,7 +478,7 @@ void AudioSettingsPage::renderModal(SDL_Renderer* renderer, float fw, float fh,
|
|||||||
|
|
||||||
void AudioSettingsPage::render(SDL_Renderer* renderer) {
|
void AudioSettingsPage::render(SDL_Renderer* renderer) {
|
||||||
int winW, winH;
|
int winW, winH;
|
||||||
SDL_GetWindowSize(SDL_GetRenderWindow(renderer), &winW, &winH);
|
radiantwave::getLayoutSize(renderer, &winW, &winH);
|
||||||
float fw = static_cast<float>(winW);
|
float fw = static_cast<float>(winW);
|
||||||
float fh = static_cast<float>(winH);
|
float fh = static_cast<float>(winH);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "date_format_page.h"
|
#include "date_format_page.h"
|
||||||
#include "app_state.h"
|
#include "app_state.h"
|
||||||
#include "date_format.h"
|
#include "date_format.h"
|
||||||
|
#include "logical_size.h"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
@@ -131,7 +132,7 @@ void DateFormatPage::update(float dt)
|
|||||||
void DateFormatPage::render(SDL_Renderer* renderer)
|
void DateFormatPage::render(SDL_Renderer* renderer)
|
||||||
{
|
{
|
||||||
int winW, winH;
|
int winW, winH;
|
||||||
SDL_GetWindowSize(SDL_GetRenderWindow(renderer), &winW, &winH);
|
radiantwave::getLayoutSize(renderer, &winW, &winH);
|
||||||
float fw = static_cast<float>(winW);
|
float fw = static_cast<float>(winW);
|
||||||
float fh = static_cast<float>(winH);
|
float fh = static_cast<float>(winH);
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "db/db_keys.h"
|
#include "db/db_keys.h"
|
||||||
#include "provisioning.h"
|
#include "provisioning.h"
|
||||||
#include "validation.h"
|
#include "validation.h"
|
||||||
|
#include "logical_size.h"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -164,7 +165,7 @@ void EmailAddressPage::exitPage(AppState& state)
|
|||||||
void EmailAddressPage::render(SDL_Renderer* renderer)
|
void EmailAddressPage::render(SDL_Renderer* renderer)
|
||||||
{
|
{
|
||||||
int winW, winH;
|
int winW, winH;
|
||||||
SDL_GetWindowSize(SDL_GetRenderWindow(renderer), &winW, &winH);
|
radiantwave::getLayoutSize(renderer, &winW, &winH);
|
||||||
float fw = static_cast<float>(winW);
|
float fw = static_cast<float>(winW);
|
||||||
float fh = static_cast<float>(winH);
|
float fh = static_cast<float>(winH);
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -8,6 +8,7 @@
|
|||||||
#include "audio/audio_downloader.h"
|
#include "audio/audio_downloader.h"
|
||||||
#include "db/db_keys.h"
|
#include "db/db_keys.h"
|
||||||
#include "network/proclamation_sync.h"
|
#include "network/proclamation_sync.h"
|
||||||
|
#include "logical_size.h"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <SDL3_image/SDL_image.h>
|
#include <SDL3_image/SDL_image.h>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
@@ -114,7 +115,7 @@ void Home::update(float dt)
|
|||||||
void Home::render(SDL_Renderer *renderer)
|
void Home::render(SDL_Renderer *renderer)
|
||||||
{
|
{
|
||||||
int winW, winH;
|
int winW, winH;
|
||||||
SDL_GetWindowSize(SDL_GetRenderWindow(renderer), &winW, &winH);
|
radiantwave::getLayoutSize(renderer, &winW, &winH);
|
||||||
float fw = static_cast<float>(winW);
|
float fw = static_cast<float>(winW);
|
||||||
float fh = static_cast<float>(winH);
|
float fh = static_cast<float>(winH);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "license_blocked_page.h"
|
#include "license_blocked_page.h"
|
||||||
#include "app_state.h"
|
#include "app_state.h"
|
||||||
#include "home.h"
|
#include "home.h"
|
||||||
|
#include "logical_size.h"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
void LicenseBlockedPage::onPush(AppState& state)
|
void LicenseBlockedPage::onPush(AppState& state)
|
||||||
@@ -71,7 +72,7 @@ void LicenseBlockedPage::update(float dt)
|
|||||||
void LicenseBlockedPage::render(SDL_Renderer* renderer)
|
void LicenseBlockedPage::render(SDL_Renderer* renderer)
|
||||||
{
|
{
|
||||||
int winW, winH;
|
int winW, winH;
|
||||||
SDL_GetWindowSize(SDL_GetRenderWindow(renderer), &winW, &winH);
|
radiantwave::getLayoutSize(renderer, &winW, &winH);
|
||||||
float fw = static_cast<float>(winW);
|
float fw = static_cast<float>(winW);
|
||||||
float fh = static_cast<float>(winH);
|
float fh = static_cast<float>(winH);
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "network/license_service.h"
|
#include "network/license_service.h"
|
||||||
#include "provisioning.h"
|
#include "provisioning.h"
|
||||||
#include "validation.h"
|
#include "validation.h"
|
||||||
|
#include "logical_size.h"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
void LicenseKeyPage::onPush(AppState& state)
|
void LicenseKeyPage::onPush(AppState& state)
|
||||||
@@ -172,7 +173,7 @@ void LicenseKeyPage::exitPage(AppState& state)
|
|||||||
void LicenseKeyPage::render(SDL_Renderer* renderer)
|
void LicenseKeyPage::render(SDL_Renderer* renderer)
|
||||||
{
|
{
|
||||||
int winW, winH;
|
int winW, winH;
|
||||||
SDL_GetWindowSize(SDL_GetRenderWindow(renderer), &winW, &winH);
|
radiantwave::getLayoutSize(renderer, &winW, &winH);
|
||||||
float fw = static_cast<float>(winW);
|
float fw = static_cast<float>(winW);
|
||||||
float fh = static_cast<float>(winH);
|
float fh = static_cast<float>(winH);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "log_viewer_page.h"
|
#include "log_viewer_page.h"
|
||||||
#include "app_state.h"
|
#include "app_state.h"
|
||||||
#include "timezone_helper.h"
|
#include "timezone_helper.h"
|
||||||
|
#include "logical_size.h"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
@@ -162,7 +163,7 @@ void LogViewerPage::update(float dt) {
|
|||||||
|
|
||||||
void LogViewerPage::render(SDL_Renderer* renderer) {
|
void LogViewerPage::render(SDL_Renderer* renderer) {
|
||||||
int winW, winH;
|
int winW, winH;
|
||||||
SDL_GetWindowSize(SDL_GetRenderWindow(renderer), &winW, &winH);
|
radiantwave::getLayoutSize(renderer, &winW, &winH);
|
||||||
float fw = static_cast<float>(winW);
|
float fw = static_cast<float>(winW);
|
||||||
float fh = static_cast<float>(winH);
|
float fh = static_cast<float>(winH);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
namespace radiantwave {
|
||||||
|
|
||||||
|
inline void getLayoutSize(SDL_Renderer* renderer, int* w, int* h)
|
||||||
|
{
|
||||||
|
SDL_RendererLogicalPresentation mode = SDL_LOGICAL_PRESENTATION_DISABLED;
|
||||||
|
if (!SDL_GetRenderLogicalPresentation(renderer, w, h, &mode) ||
|
||||||
|
mode == SDL_LOGICAL_PRESENTATION_DISABLED || *w <= 0 || *h <= 0)
|
||||||
|
{
|
||||||
|
SDL_GetWindowSize(SDL_GetRenderWindow(renderer), w, h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace radiantwave
|
||||||
+177
-165
@@ -4,206 +4,218 @@
|
|||||||
|
|
||||||
#include "app_state.h"
|
#include "app_state.h"
|
||||||
#include "home.h"
|
#include "home.h"
|
||||||
#include "provisioning.h"
|
|
||||||
#include "logger/logger.h"
|
#include "logger/logger.h"
|
||||||
|
#include "provisioning.h"
|
||||||
|
|
||||||
namespace
|
namespace {
|
||||||
{
|
// How long audio fades out before the app tears down on quit. Only applied
|
||||||
// How long audio fades out before the app tears down on quit. Only applied
|
// when audio is actually playing; otherwise quitting is immediate.
|
||||||
// when audio is actually playing; otherwise quitting is immediate.
|
constexpr float kQuitFadeSeconds = 5.0f;
|
||||||
constexpr float kQuitFadeSeconds = 5.0f;
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
||||||
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
|
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) {
|
||||||
{
|
// Initialize text logger
|
||||||
// Initialize text logger
|
radiantwave::logger::Logger::instance().initialize();
|
||||||
radiantwave::logger::Logger::instance().initialize();
|
radiantwave::logger::Logger::instance().info(
|
||||||
radiantwave::logger::Logger::instance().info("System", "RadiantWave Kiosk starting up");
|
"System", "RadiantWave Kiosk starting up");
|
||||||
|
|
||||||
if (!SDL_Init(SDL_INIT_VIDEO))
|
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
||||||
{
|
std::string err = SDL_GetError();
|
||||||
std::string err = SDL_GetError();
|
radiantwave::logger::Logger::instance().fatal(
|
||||||
radiantwave::logger::Logger::instance().fatal("Graphics",
|
"Graphics",
|
||||||
"Screen display initialization failed. Please verify graphics hardware and display connections (SDL_Init error: " + err + ")");
|
"Screen display initialization failed. Please verify graphics "
|
||||||
SDL_Log("Failed to initialize SDL: %s", err.c_str());
|
"hardware and display connections (SDL_Init error: " +
|
||||||
return SDL_APP_FAILURE;
|
err + ")");
|
||||||
}
|
SDL_Log("Failed to initialize SDL: %s", err.c_str());
|
||||||
|
return SDL_APP_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
auto *state = new AppState{};
|
auto *state = new AppState{};
|
||||||
|
|
||||||
// SDL_WINDOW_FULLSCREEN fills the desktop's native resolution, so the
|
// SDL_WINDOW_FULLSCREEN fills the desktop's native resolution, so the
|
||||||
// compositor never has to scale the moving scroller (scaling moving text
|
// compositor never has to scale the moving scroller (scaling moving text
|
||||||
// causes flicker).
|
// causes flicker).
|
||||||
state->window = SDL_CreateWindow("RadiantWave", 1920, 1080, SDL_WINDOW_FULLSCREEN);
|
state->window =
|
||||||
if (!state->window)
|
SDL_CreateWindow("RadiantWave", 1920, 1080, SDL_WINDOW_FULLSCREEN);
|
||||||
{
|
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;
|
||||||
return SDL_APP_FAILURE;
|
return SDL_APP_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
state->renderer = SDL_CreateRenderer(state->window, nullptr);
|
state->renderer = SDL_CreateRenderer(state->window, nullptr);
|
||||||
if (!state->renderer)
|
if (!state->renderer) {
|
||||||
{
|
SDL_Log("Failed to create renderer: %s", SDL_GetError());
|
||||||
SDL_Log("Failed to create renderer: %s", SDL_GetError());
|
delete state;
|
||||||
delete state;
|
return SDL_APP_FAILURE;
|
||||||
return SDL_APP_FAILURE;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Present on the display's refresh. Without VSync the kiosk panel tears
|
// Present on the display's refresh. Without VSync the kiosk panel tears
|
||||||
// fast-scrolling text into horizontal bands that flicker as the text moves.
|
// fast-scrolling text into horizontal bands that flicker as the text moves.
|
||||||
if (!SDL_SetRenderVSync(state->renderer, 1)) {
|
if (!SDL_SetRenderVSync(state->renderer, 1)) {
|
||||||
radiantwave::logger::Logger::instance().warning("Graphics",
|
radiantwave::logger::Logger::instance().warning(
|
||||||
"VSync could not be enabled; scrolling text may tear (VSync error: " + std::string(SDL_GetError()) + ")");
|
"Graphics", "VSync could not be enabled; scrolling text may tear "
|
||||||
}
|
"(VSync error: " +
|
||||||
|
std::string(SDL_GetError()) + ")");
|
||||||
|
}
|
||||||
|
|
||||||
// Lock the renderer to a 1920x1080 logical coordinate space. All page
|
// Lock the renderer to a 1920x1080 logical coordinate space. All page
|
||||||
// layout code already targets this resolution, so everything renders at
|
// layout code already targets this resolution, so everything renders at
|
||||||
// the correct size on any display — the GPU scales to native pixels.
|
// the correct size on any display — the GPU scales to native pixels.
|
||||||
if (!SDL_SetRenderLogicalPresentation(state->renderer, 1920, 1080, SDL_LOGICAL_PRESENTATION_STRETCH)) {
|
if (!SDL_SetRenderLogicalPresentation(state->renderer, 1920, 1080,
|
||||||
radiantwave::logger::Logger::instance().warning("Graphics",
|
SDL_LOGICAL_PRESENTATION_STRETCH)) {
|
||||||
"Could not set logical render size; layout may be wrong on HiDPI displays");
|
radiantwave::logger::Logger::instance().warning(
|
||||||
}
|
"Graphics", "Could not set logical render size; layout may be "
|
||||||
|
"wrong on HiDPI displays");
|
||||||
|
}
|
||||||
|
|
||||||
SDL_HideCursor();
|
SDL_HideCursor();
|
||||||
|
|
||||||
// Initialize configuration, database, and network manager
|
// Initialize configuration, database, and network manager
|
||||||
if (!state->config.initialize()) {
|
if (!state->config.initialize()) {
|
||||||
radiantwave::logger::Logger::instance().warning("Config",
|
radiantwave::logger::Logger::instance().warning(
|
||||||
"Settings file could not be loaded. Default settings will be used (Config initialization failed at ~/.local/radiantwave/config.yaml)");
|
"Config", "Settings file could not be loaded. Default settings "
|
||||||
} else {
|
"will be used (Config initialization failed at "
|
||||||
radiantwave::logger::Logger::instance().info("Config", "Configuration initialized successfully");
|
"~/.local/radiantwave/config.yaml)");
|
||||||
}
|
} else {
|
||||||
|
radiantwave::logger::Logger::instance().info(
|
||||||
|
"Config", "Configuration initialized successfully");
|
||||||
|
}
|
||||||
|
|
||||||
if (!state->db.initialize()) {
|
if (!state->db.initialize()) {
|
||||||
radiantwave::logger::Logger::instance().error("Database",
|
radiantwave::logger::Logger::instance().error(
|
||||||
"Database storage unavailable. App features may be limited (Database initialization failed at ~/.local/radiantwave/radiantwave.db)");
|
"Database", "Database storage unavailable. App features may be "
|
||||||
} else {
|
"limited (Database initialization failed at "
|
||||||
radiantwave::logger::Logger::instance().info("Database", "Database initialized and schema migrated");
|
"~/.local/radiantwave/radiantwave.db)");
|
||||||
}
|
} else {
|
||||||
|
radiantwave::logger::Logger::instance().info(
|
||||||
|
"Database", "Database initialized and schema migrated");
|
||||||
|
}
|
||||||
|
|
||||||
if (!state->network.initialize()) {
|
if (!state->network.initialize()) {
|
||||||
radiantwave::logger::Logger::instance().warning("Network",
|
radiantwave::logger::Logger::instance().warning(
|
||||||
"WiFi network management is unavailable. Check if NetworkManager is running (D-Bus connection to org.freedesktop.NetworkManager failed)");
|
"Network", "WiFi network management is unavailable. Check if "
|
||||||
} else {
|
"NetworkManager is running (D-Bus connection to "
|
||||||
radiantwave::logger::Logger::instance().info("Network", "NetworkManager service connected");
|
"org.freedesktop.NetworkManager failed)");
|
||||||
}
|
} else {
|
||||||
|
radiantwave::logger::Logger::instance().info(
|
||||||
|
"Network", "NetworkManager service connected");
|
||||||
|
}
|
||||||
|
|
||||||
if (!state->audio.initialize(state->config)) {
|
if (!state->audio.initialize(state->config)) {
|
||||||
radiantwave::logger::Logger::instance().warning("Audio",
|
radiantwave::logger::Logger::instance().warning(
|
||||||
"Audio playback is unavailable. Check sound hardware connections (SDL3_mixer initialization failed)");
|
"Audio", "Audio playback is unavailable. Check sound hardware "
|
||||||
} else {
|
"connections (SDL3_mixer initialization failed)");
|
||||||
radiantwave::logger::Logger::instance().info("Audio", "Audio subsystem initialized");
|
} else {
|
||||||
}
|
radiantwave::logger::Logger::instance().info(
|
||||||
// Let playback logs report user-facing display names from the database.
|
"Audio", "Audio subsystem initialized");
|
||||||
state->audio.attachDatabase(state->db);
|
}
|
||||||
|
// Let playback logs report user-facing display names from the database.
|
||||||
|
state->audio.attachDatabase(state->db);
|
||||||
|
|
||||||
// Background download of audio assets. The worker keeps retrying until
|
// Background download of audio assets. The worker keeps retrying until
|
||||||
// internet is available, so downloads start automatically on connect.
|
// internet is available, so downloads start automatically on connect.
|
||||||
// The downloaded files are reconciled into the database's audio_files table.
|
// The downloaded files are reconciled into the database's audio_files
|
||||||
state->audioDownloader.start(state->db);
|
// table.
|
||||||
|
state->audioDownloader.start(state->db);
|
||||||
|
|
||||||
// Background license validation. Home triggers a refresh whenever it is
|
// Background license validation. Home triggers a refresh whenever it is
|
||||||
// displayed; the result gates access to Play and the Proclamation options.
|
// displayed; the result gates access to Play and the Proclamation options.
|
||||||
state->licenseTracker.start(state->db);
|
state->licenseTracker.start(state->db);
|
||||||
|
|
||||||
state->config.onChanged([state](const radiantwave::config::AppConfig& new_config) {
|
state->config.onChanged(
|
||||||
SDL_Log("Live config update received: volume=%d, dynamic_audio=%d",
|
[state](const radiantwave::config::AppConfig &new_config) {
|
||||||
new_config.audio.volume, new_config.audio.dynamic_audio ? 1 : 0);
|
SDL_Log("Live config update received: volume=%d, dynamic_audio=%d",
|
||||||
state->db.logEvent("INFO", "Config", "Live configuration hot-reloaded");
|
new_config.audio.volume,
|
||||||
});
|
new_config.audio.dynamic_audio ? 1 : 0);
|
||||||
|
state->db.logEvent("INFO", "Config",
|
||||||
|
"Live configuration hot-reloaded");
|
||||||
|
});
|
||||||
|
|
||||||
state->keybinds.bind(SDLK_UP, "menu_up", [state]() {
|
state->keybinds.bind(SDLK_UP, "menu_up", [state]() {
|
||||||
state->currentMenuItem = (state->currentMenuItem + 1) % 2;
|
state->currentMenuItem = (state->currentMenuItem + 1) % 2;
|
||||||
});
|
});
|
||||||
state->keybinds.bind(SDLK_DOWN, "menu_down", [state]() {
|
state->keybinds.bind(SDLK_DOWN, "menu_down", [state]() {
|
||||||
state->currentMenuItem = (state->currentMenuItem + 1) % 2;
|
state->currentMenuItem = (state->currentMenuItem + 1) % 2;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Drive the First-Run walkthrough: shows the WiFi page when the internet
|
// Drive the First-Run walkthrough: shows the WiFi page when the internet
|
||||||
// is unreachable, then the email and license pages when those are missing,
|
// is unreachable <DEPRECATED>, then the email and license pages when those
|
||||||
// finishing on Home. The internet check is a real connectivity probe
|
// are missing, finishing on Home. The internet check is a real connectivity
|
||||||
// (Ethernet, WiFi, or macOS all work), so it never traps a connected
|
// probe (Ethernet, WiFi, or macOS all work), so it never traps a connected
|
||||||
// device on the WiFi page.
|
// device on the WiFi page.
|
||||||
radiantwave::provisioning::begin(*state);
|
radiantwave::provisioning::begin(*state);
|
||||||
|
|
||||||
*appstate = state;
|
*appstate = state;
|
||||||
return SDL_APP_CONTINUE;
|
return SDL_APP_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
|
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) {
|
||||||
{
|
auto &state = *static_cast<AppState *>(appstate);
|
||||||
auto &state = *static_cast<AppState *>(appstate);
|
|
||||||
|
|
||||||
if (event->type == SDL_EVENT_QUIT)
|
if (event->type == SDL_EVENT_QUIT) {
|
||||||
{
|
state.quitRequested = true;
|
||||||
state.quitRequested = true;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (!state.keybinds.handleEvent(*event)) {
|
if (!state.keybinds.handleEvent(*event)) {
|
||||||
state.pages.handleEvent(*event);
|
state.pages.handleEvent(*event);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.quitRequested && state.quitFadeRemaining <= 0.0f)
|
if (state.quitRequested && state.quitFadeRemaining <= 0.0f) {
|
||||||
{
|
// Graceful shutdown: start the audio fade-out and keep the loop
|
||||||
// Graceful shutdown: start the audio fade-out and keep the loop
|
// running until it completes, then let SDL_AppQuit tear everything
|
||||||
// running until it completes, then let SDL_AppQuit tear everything
|
// down. When nothing is playing there is nothing to fade and quitting
|
||||||
// down. When nothing is playing there is nothing to fade and quitting
|
// is immediate.
|
||||||
// is immediate.
|
if (state.audio.stopTrackFadeOut(kQuitFadeSeconds)) {
|
||||||
if (state.audio.stopTrackFadeOut(kQuitFadeSeconds))
|
state.quitFadeRemaining = kQuitFadeSeconds;
|
||||||
{
|
return SDL_APP_CONTINUE;
|
||||||
state.quitFadeRemaining = kQuitFadeSeconds;
|
}
|
||||||
return SDL_APP_CONTINUE;
|
return SDL_APP_SUCCESS;
|
||||||
}
|
}
|
||||||
return SDL_APP_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
return SDL_APP_CONTINUE;
|
return SDL_APP_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_AppResult SDL_AppIterate(void *appstate)
|
SDL_AppResult SDL_AppIterate(void *appstate) {
|
||||||
{
|
auto &state = *static_cast<AppState *>(appstate);
|
||||||
auto &state = *static_cast<AppState *>(appstate);
|
|
||||||
|
|
||||||
Uint64 nowTicks = SDL_GetTicks();
|
Uint64 nowTicks = SDL_GetTicks();
|
||||||
static Uint64 lastTicks = nowTicks;
|
static Uint64 lastTicks = nowTicks;
|
||||||
float dt = static_cast<float>(nowTicks - lastTicks) / 1000.0f;
|
float dt = static_cast<float>(nowTicks - lastTicks) / 1000.0f;
|
||||||
if (dt > 0.1f) dt = 0.1f;
|
if (dt > 0.1f)
|
||||||
lastTicks = nowTicks;
|
dt = 0.1f;
|
||||||
|
lastTicks = nowTicks;
|
||||||
|
|
||||||
state.pages.update(dt);
|
state.pages.update(dt);
|
||||||
|
|
||||||
if (state.quitFadeRemaining > 0.0f)
|
if (state.quitFadeRemaining > 0.0f) {
|
||||||
{
|
state.quitFadeRemaining -= dt;
|
||||||
state.quitFadeRemaining -= dt;
|
if (state.quitFadeRemaining <= 0.0f) {
|
||||||
if (state.quitFadeRemaining <= 0.0f)
|
return SDL_APP_SUCCESS;
|
||||||
{
|
}
|
||||||
return SDL_APP_SUCCESS;
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_SetRenderDrawColor(state.renderer, 30, 30, 46, 255);
|
SDL_SetRenderDrawColor(state.renderer, 30, 30, 46, 255);
|
||||||
SDL_RenderClear(state.renderer);
|
SDL_RenderClear(state.renderer);
|
||||||
|
|
||||||
state.pages.render(state.renderer);
|
state.pages.render(state.renderer);
|
||||||
|
|
||||||
SDL_RenderPresent(state.renderer);
|
SDL_RenderPresent(state.renderer);
|
||||||
return SDL_APP_CONTINUE;
|
return SDL_APP_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SDL_AppQuit(void *appstate, SDL_AppResult result)
|
void SDL_AppQuit(void *appstate, SDL_AppResult result) {
|
||||||
{
|
auto *state = static_cast<AppState *>(appstate);
|
||||||
auto *state = static_cast<AppState *>(appstate);
|
if (state) {
|
||||||
if (state) {
|
state->audioDownloader.shutdown();
|
||||||
state->audioDownloader.shutdown();
|
state->licenseTracker.shutdown();
|
||||||
state->licenseTracker.shutdown();
|
state->audio.shutdown();
|
||||||
state->audio.shutdown();
|
state->network.shutdown();
|
||||||
state->network.shutdown();
|
state->config.shutdown();
|
||||||
state->config.shutdown();
|
state->db.close();
|
||||||
state->db.close();
|
SDL_DestroyRenderer(state->renderer);
|
||||||
SDL_DestroyRenderer(state->renderer);
|
SDL_DestroyWindow(state->window);
|
||||||
SDL_DestroyWindow(state->window);
|
delete state;
|
||||||
delete state;
|
}
|
||||||
}
|
SDL_Quit();
|
||||||
SDL_Quit();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "proclamation_options_page.h"
|
#include "proclamation_options_page.h"
|
||||||
#include "app_state.h"
|
#include "app_state.h"
|
||||||
#include "network/proclamation_sync.h"
|
#include "network/proclamation_sync.h"
|
||||||
|
#include "logical_size.h"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
@@ -552,7 +553,7 @@ void ProclamationOptionsPage::update(float dt)
|
|||||||
void ProclamationOptionsPage::render(SDL_Renderer* renderer)
|
void ProclamationOptionsPage::render(SDL_Renderer* renderer)
|
||||||
{
|
{
|
||||||
int winW, winH;
|
int winW, winH;
|
||||||
SDL_GetWindowSize(SDL_GetRenderWindow(renderer), &winW, &winH);
|
radiantwave::getLayoutSize(renderer, &winW, &winH);
|
||||||
float fw = static_cast<float>(winW);
|
float fw = static_cast<float>(winW);
|
||||||
float fh = static_cast<float>(winH);
|
float fh = static_cast<float>(winH);
|
||||||
|
|
||||||
|
|||||||
+61
-59
@@ -1,80 +1,82 @@
|
|||||||
#include "provisioning.h"
|
#include "provisioning.h"
|
||||||
#include "wifi_setup_page.h"
|
|
||||||
#include "email_address_page.h"
|
#include "email_address_page.h"
|
||||||
#include "license_key_page.h"
|
|
||||||
#include "home.h"
|
#include "home.h"
|
||||||
|
#include "license_key_page.h"
|
||||||
#include "logger/logger.h"
|
#include "logger/logger.h"
|
||||||
|
#include "wifi_setup_page.h"
|
||||||
|
|
||||||
namespace radiantwave::provisioning {
|
namespace radiantwave::provisioning {
|
||||||
|
|
||||||
// Push the page for a walkthrough step, or Home when the walkthrough is done.
|
// Push the page for a walkthrough step, or Home when the walkthrough is done.
|
||||||
static void pushStep(AppState& state, Step step)
|
static void pushStep(AppState &state, Step step) {
|
||||||
{
|
switch (step) {
|
||||||
switch (step) {
|
// case Step::Internet:
|
||||||
case Step::Internet:
|
// state.pages.push(state, std::make_unique<WiFiSetupPage>());
|
||||||
state.pages.push(state, std::make_unique<WiFiSetupPage>());
|
// break;
|
||||||
break;
|
case Step::Email:
|
||||||
case Step::Email:
|
state.pages.push(state, std::make_unique<EmailAddressPage>());
|
||||||
state.pages.push(state, std::make_unique<EmailAddressPage>());
|
break;
|
||||||
break;
|
case Step::License:
|
||||||
case Step::License:
|
state.pages.push(state, std::make_unique<LicenseKeyPage>());
|
||||||
state.pages.push(state, std::make_unique<LicenseKeyPage>());
|
break;
|
||||||
break;
|
case Step::Done:
|
||||||
case Step::Done:
|
state.provisioningMode = false;
|
||||||
state.provisioningMode = false;
|
state.pages.push(state, std::make_unique<Home>());
|
||||||
state.pages.push(state, std::make_unique<Home>());
|
break;
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void begin(AppState& state)
|
void begin(AppState &state) {
|
||||||
{
|
// Without the database there is nowhere to persist email/license, so the
|
||||||
// Without the database there is nowhere to persist email/license, so the
|
// walkthrough would trap the user on pages that can never be completed.
|
||||||
// walkthrough would trap the user on pages that can never be completed.
|
if (!state.db.isOpen()) {
|
||||||
if (!state.db.isOpen()) {
|
state.provisioningMode = false;
|
||||||
state.provisioningMode = false;
|
state.pages.push(state, std::make_unique<Home>());
|
||||||
state.pages.push(state, std::make_unique<Home>());
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const bool internetUp = state.network.hasInternet();
|
const bool internetUp = state.network.hasInternet();
|
||||||
const bool hasEmail = !state.db.getKV(radiantwave::db::KV_EMAIL_ADDRESS).empty();
|
const bool hasEmail =
|
||||||
const bool hasLicense = !state.db.getKV(radiantwave::db::KV_LICENSE_KEY).empty();
|
!state.db.getKV(radiantwave::db::KV_EMAIL_ADDRESS).empty();
|
||||||
|
const bool hasLicense =
|
||||||
|
!state.db.getKV(radiantwave::db::KV_LICENSE_KEY).empty();
|
||||||
|
|
||||||
if (indicatesFirstRun(internetUp, hasEmail, hasLicense)) {
|
if (indicatesFirstRun(internetUp, hasEmail, hasLicense)) {
|
||||||
// Placeholder: a brand-new device. Later this flag will launch the
|
// Placeholder: a brand-new device. Later this flag will launch the
|
||||||
// first-run welcome walkthrough (TOS, quick user guide, ...).
|
// first-run welcome walkthrough (TOS, quick user guide, ...).
|
||||||
state.firstRunWalkthrough = true;
|
state.firstRunWalkthrough = true;
|
||||||
radiantwave::logger::Logger::instance().info("Provisioning",
|
radiantwave::logger::Logger::instance().info(
|
||||||
"First-Run detected (no internet, no email, no license); "
|
"Provisioning",
|
||||||
"first-run welcome walkthrough is not yet implemented");
|
"First-Run detected (no internet, no email, no license); "
|
||||||
}
|
"first-run welcome walkthrough is not yet implemented");
|
||||||
|
}
|
||||||
|
|
||||||
state.walkthroughInternetUp = internetUp;
|
state.walkthroughInternetUp = internetUp;
|
||||||
state.provisioningMode = true;
|
state.provisioningMode = true;
|
||||||
pushStep(state, firstMissingStep(internetUp, hasEmail, hasLicense));
|
pushStep(state, firstMissingStep(internetUp, hasEmail, hasLicense));
|
||||||
}
|
}
|
||||||
|
|
||||||
void advance(AppState& state)
|
void advance(AppState &state) {
|
||||||
{
|
if (state.pages.depth() > 0) {
|
||||||
if (state.pages.depth() > 0) {
|
state.pages.pop(state);
|
||||||
state.pages.pop(state);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Internet is the first prerequisite and never gets "un-reached" once
|
// Internet is the first prerequisite and never gets "un-reached" once
|
||||||
// satisfied, so it is only re-probed when it was still missing (i.e. the
|
// satisfied, so it is only re-probed when it was still missing (i.e. the
|
||||||
// walkthrough is coming from the WiFi page).
|
// walkthrough is coming from the WiFi page).
|
||||||
bool internetUp = state.walkthroughInternetUp;
|
bool internetUp = state.walkthroughInternetUp;
|
||||||
if (!internetUp) {
|
if (!internetUp) {
|
||||||
internetUp = state.network.hasInternet();
|
internetUp = state.network.hasInternet();
|
||||||
state.walkthroughInternetUp = internetUp;
|
state.walkthroughInternetUp = internetUp;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool hasEmail = !state.db.getKV(radiantwave::db::KV_EMAIL_ADDRESS).empty();
|
const bool hasEmail =
|
||||||
const bool hasLicense = !state.db.getKV(radiantwave::db::KV_LICENSE_KEY).empty();
|
!state.db.getKV(radiantwave::db::KV_EMAIL_ADDRESS).empty();
|
||||||
|
const bool hasLicense =
|
||||||
|
!state.db.getKV(radiantwave::db::KV_LICENSE_KEY).empty();
|
||||||
|
|
||||||
state.provisioningMode = true;
|
state.provisioningMode = true;
|
||||||
pushStep(state, firstMissingStep(internetUp, hasEmail, hasLicense));
|
pushStep(state, firstMissingStep(internetUp, hasEmail, hasLicense));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace radiantwave::provisioning
|
} // namespace radiantwave::provisioning
|
||||||
|
|||||||
+12
-12
@@ -24,33 +24,33 @@
|
|||||||
namespace radiantwave::provisioning {
|
namespace radiantwave::provisioning {
|
||||||
|
|
||||||
// Prerequisite steps in walkthrough priority order.
|
// Prerequisite steps in walkthrough priority order.
|
||||||
enum class Step { Done, Internet, Email, License };
|
enum class Step { Done, Email, License };
|
||||||
|
|
||||||
// First unsatisfied step, given the current conditions.
|
// First unsatisfied step, given the current conditions.
|
||||||
inline Step firstMissingStep(bool internetUp, bool hasEmail, bool hasLicense)
|
inline Step firstMissingStep(bool internetUp, bool hasEmail, bool hasLicense) {
|
||||||
{
|
// if (!internetUp) return Step::Internet;
|
||||||
if (!internetUp) return Step::Internet;
|
if (!hasEmail)
|
||||||
if (!hasEmail) return Step::Email;
|
return Step::Email;
|
||||||
if (!hasLicense) return Step::License;
|
if (!hasLicense)
|
||||||
return Step::Done;
|
return Step::License;
|
||||||
|
return Step::Done;
|
||||||
}
|
}
|
||||||
|
|
||||||
// True when nothing is configured (no internet, no email, no license).
|
// True when nothing is configured (no internet, no email, no license).
|
||||||
// Signals a brand-new, never-provisioned device.
|
// Signals a brand-new, never-provisioned device.
|
||||||
inline bool indicatesFirstRun(bool internetUp, bool hasEmail, bool hasLicense)
|
inline bool indicatesFirstRun(bool internetUp, bool hasEmail, bool hasLicense) {
|
||||||
{
|
return !internetUp && !hasEmail && !hasLicense;
|
||||||
return !internetUp && !hasEmail && !hasLicense;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Startup walkthrough. Evaluates the prerequisites and pushes the first
|
// Startup walkthrough. Evaluates the prerequisites and pushes the first
|
||||||
// missing step's page (with provisioningMode set), or Home when everything
|
// missing step's page (with provisioningMode set), or Home when everything
|
||||||
// is satisfied. Home is always the end of the walkthrough.
|
// is satisfied. Home is always the end of the walkthrough.
|
||||||
void begin(AppState& state);
|
void begin(AppState &state);
|
||||||
|
|
||||||
// Called by a walkthrough page when the user finishes or exits it. Pops the
|
// Called by a walkthrough page when the user finishes or exits it. Pops the
|
||||||
// current page, re-evaluates the prerequisites, and pushes the next missing
|
// current page, re-evaluates the prerequisites, and pushes the next missing
|
||||||
// step's page (or Home). Internet is only re-probed when it was still
|
// step's page (or Home). Internet is only re-probed when it was still
|
||||||
// missing, so completing a step never blocks on a redundant check.
|
// missing, so completing a step never blocks on a redundant check.
|
||||||
void advance(AppState& state);
|
void advance(AppState &state);
|
||||||
|
|
||||||
} // namespace radiantwave::provisioning
|
} // namespace radiantwave::provisioning
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "proclamation_options_page.h"
|
#include "proclamation_options_page.h"
|
||||||
#include "scramble/fibonacci_scrambler.h"
|
#include "scramble/fibonacci_scrambler.h"
|
||||||
#include "utf8_utils.h"
|
#include "utf8_utils.h"
|
||||||
|
#include "logical_size.h"
|
||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <SDL3_ttf/SDL_ttf.h>
|
#include <SDL3_ttf/SDL_ttf.h>
|
||||||
@@ -35,7 +36,7 @@ void ScrollerPage::onPush(AppState &state)
|
|||||||
|
|
||||||
int winW = 0;
|
int winW = 0;
|
||||||
int winH = 0;
|
int winH = 0;
|
||||||
SDL_GetWindowSize(state.window, &winW, &winH);
|
radiantwave::getLayoutSize(state.renderer, &winW, &winH);
|
||||||
screenW_ = static_cast<float>(winW);
|
screenW_ = static_cast<float>(winW);
|
||||||
screenH_ = static_cast<float>(winH);
|
screenH_ = static_cast<float>(winH);
|
||||||
// Lines are capped at the full screen width; the widest Fibonacci lines are
|
// Lines are capped at the full screen width; the widest Fibonacci lines are
|
||||||
|
|||||||
+2
-1
@@ -10,6 +10,7 @@
|
|||||||
#include "email_address_page.h"
|
#include "email_address_page.h"
|
||||||
#include "license_key_page.h"
|
#include "license_key_page.h"
|
||||||
#include "date_format_page.h"
|
#include "date_format_page.h"
|
||||||
|
#include "logical_size.h"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <SDL3_image/SDL_image.h>
|
#include <SDL3_image/SDL_image.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -128,7 +129,7 @@ void Settings::update(float dt)
|
|||||||
void Settings::render(SDL_Renderer *renderer)
|
void Settings::render(SDL_Renderer *renderer)
|
||||||
{
|
{
|
||||||
int winW, winH;
|
int winW, winH;
|
||||||
SDL_GetWindowSize(SDL_GetRenderWindow(renderer), &winW, &winH);
|
radiantwave::getLayoutSize(renderer, &winW, &winH);
|
||||||
float fw = static_cast<float>(winW);
|
float fw = static_cast<float>(winW);
|
||||||
float fh = static_cast<float>(winH);
|
float fh = static_cast<float>(winH);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "timezone_page.h"
|
#include "timezone_page.h"
|
||||||
#include "app_state.h"
|
#include "app_state.h"
|
||||||
|
#include "logical_size.h"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
@@ -199,7 +200,7 @@ void TimeZonePage::update(float dt) {
|
|||||||
|
|
||||||
void TimeZonePage::render(SDL_Renderer* renderer) {
|
void TimeZonePage::render(SDL_Renderer* renderer) {
|
||||||
int winW, winH;
|
int winW, winH;
|
||||||
SDL_GetWindowSize(SDL_GetRenderWindow(renderer), &winW, &winH);
|
radiantwave::getLayoutSize(renderer, &winW, &winH);
|
||||||
float fw = static_cast<float>(winW);
|
float fw = static_cast<float>(winW);
|
||||||
float fh = static_cast<float>(winH);
|
float fh = static_cast<float>(winH);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "wifi_setup_page.h"
|
#include "wifi_setup_page.h"
|
||||||
#include "app_state.h"
|
#include "app_state.h"
|
||||||
#include "provisioning.h"
|
#include "provisioning.h"
|
||||||
|
#include "logical_size.h"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
@@ -301,7 +302,7 @@ void WiFiSetupPage::update(float dt) {
|
|||||||
|
|
||||||
void WiFiSetupPage::render(SDL_Renderer* renderer) {
|
void WiFiSetupPage::render(SDL_Renderer* renderer) {
|
||||||
int winW, winH;
|
int winW, winH;
|
||||||
SDL_GetWindowSize(SDL_GetRenderWindow(renderer), &winW, &winH);
|
radiantwave::getLayoutSize(renderer, &winW, &winH);
|
||||||
float fw = static_cast<float>(winW);
|
float fw = static_cast<float>(winW);
|
||||||
float fh = static_cast<float>(winH);
|
float fh = static_cast<float>(winH);
|
||||||
|
|
||||||
|
|||||||
+16
-30
@@ -1,51 +1,37 @@
|
|||||||
#include "test_harness.h"
|
|
||||||
#include "../src/provisioning.h"
|
#include "../src/provisioning.h"
|
||||||
|
#include "test_harness.h"
|
||||||
|
|
||||||
using radiantwave::provisioning::firstMissingStep;
|
using radiantwave::provisioning::firstMissingStep;
|
||||||
using radiantwave::provisioning::indicatesFirstRun;
|
using radiantwave::provisioning::indicatesFirstRun;
|
||||||
using radiantwave::provisioning::Step;
|
using radiantwave::provisioning::Step;
|
||||||
|
|
||||||
TEST(first_missing_step_all_satisfied)
|
TEST(first_missing_step_all_satisfied) {
|
||||||
{
|
ASSERT_EQ(firstMissingStep(true, true, true), Step::Done);
|
||||||
ASSERT_EQ(firstMissingStep(true, true, true), Step::Done);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(first_missing_step_no_internet)
|
TEST(first_missing_step_no_email) {
|
||||||
{
|
ASSERT_EQ(firstMissingStep(true, false, false), Step::Email);
|
||||||
// Internet is checked before email/license regardless of the others.
|
ASSERT_EQ(firstMissingStep(true, false, true), Step::Email);
|
||||||
ASSERT_EQ(firstMissingStep(false, false, false), Step::Internet);
|
|
||||||
ASSERT_EQ(firstMissingStep(false, true, true), Step::Internet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(first_missing_step_no_email)
|
TEST(first_missing_step_no_license) {
|
||||||
{
|
ASSERT_EQ(firstMissingStep(true, true, false), Step::License);
|
||||||
ASSERT_EQ(firstMissingStep(true, false, false), Step::Email);
|
|
||||||
ASSERT_EQ(firstMissingStep(true, false, true), Step::Email);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(first_missing_step_no_license)
|
TEST(first_run_all_missing) {
|
||||||
{
|
ASSERT_TRUE(indicatesFirstRun(false, false, false));
|
||||||
ASSERT_EQ(firstMissingStep(true, true, false), Step::License);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(first_run_all_missing)
|
TEST(first_run_has_internet) {
|
||||||
{
|
ASSERT_FALSE(indicatesFirstRun(true, false, false));
|
||||||
ASSERT_TRUE(indicatesFirstRun(false, false, false));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(first_run_has_internet)
|
TEST(first_run_has_email) {
|
||||||
{
|
ASSERT_FALSE(indicatesFirstRun(false, true, false));
|
||||||
ASSERT_FALSE(indicatesFirstRun(true, false, false));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(first_run_has_email)
|
TEST(first_run_has_license) {
|
||||||
{
|
ASSERT_FALSE(indicatesFirstRun(false, false, true));
|
||||||
ASSERT_FALSE(indicatesFirstRun(false, true, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(first_run_has_license)
|
|
||||||
{
|
|
||||||
ASSERT_FALSE(indicatesFirstRun(false, false, true));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RUN_ALL_TESTS()
|
RUN_ALL_TESTS()
|
||||||
|
|||||||
Reference in New Issue
Block a user