Remove WiFi gate

This commit is contained in:
NeilClack
2026-08-18 23:37:44 -04:00
parent ec6b76618c
commit 8e930ef8d9
4 changed files with 266 additions and 266 deletions
+177 -165
View File
@@ -4,206 +4,218 @@
#include "app_state.h"
#include "home.h"
#include "provisioning.h"
#include "logger/logger.h"
#include "provisioning.h"
namespace
{
// How long audio fades out before the app tears down on quit. Only applied
// when audio is actually playing; otherwise quitting is immediate.
constexpr float kQuitFadeSeconds = 5.0f;
namespace {
// How long audio fades out before the app tears down on quit. Only applied
// when audio is actually playing; otherwise quitting is immediate.
constexpr float kQuitFadeSeconds = 5.0f;
} // anonymous namespace
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
{
// Initialize text logger
radiantwave::logger::Logger::instance().initialize();
radiantwave::logger::Logger::instance().info("System", "RadiantWave Kiosk starting up");
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) {
// Initialize text logger
radiantwave::logger::Logger::instance().initialize();
radiantwave::logger::Logger::instance().info(
"System", "RadiantWave Kiosk starting up");
if (!SDL_Init(SDL_INIT_VIDEO))
{
std::string err = SDL_GetError();
radiantwave::logger::Logger::instance().fatal("Graphics",
"Screen display initialization failed. Please verify graphics hardware and display connections (SDL_Init error: " + err + ")");
SDL_Log("Failed to initialize SDL: %s", err.c_str());
return SDL_APP_FAILURE;
}
if (!SDL_Init(SDL_INIT_VIDEO)) {
std::string err = SDL_GetError();
radiantwave::logger::Logger::instance().fatal(
"Graphics",
"Screen display initialization failed. Please verify graphics "
"hardware and display connections (SDL_Init error: " +
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
// compositor never has to scale the moving scroller (scaling moving text
// causes flicker).
state->window = SDL_CreateWindow("RadiantWave", 1920, 1080, SDL_WINDOW_FULLSCREEN);
if (!state->window)
{
SDL_Log("Failed to create window: %s", SDL_GetError());
delete state;
return SDL_APP_FAILURE;
}
// SDL_WINDOW_FULLSCREEN fills the desktop's native resolution, so the
// compositor never has to scale the moving scroller (scaling moving text
// causes flicker).
state->window =
SDL_CreateWindow("RadiantWave", 1920, 1080, SDL_WINDOW_FULLSCREEN);
if (!state->window) {
SDL_Log("Failed to create window: %s", SDL_GetError());
delete state;
return SDL_APP_FAILURE;
}
state->renderer = SDL_CreateRenderer(state->window, nullptr);
if (!state->renderer)
{
SDL_Log("Failed to create renderer: %s", SDL_GetError());
delete state;
return SDL_APP_FAILURE;
}
state->renderer = SDL_CreateRenderer(state->window, nullptr);
if (!state->renderer) {
SDL_Log("Failed to create renderer: %s", SDL_GetError());
delete state;
return SDL_APP_FAILURE;
}
// Present on the display's refresh. Without VSync the kiosk panel tears
// fast-scrolling text into horizontal bands that flicker as the text moves.
if (!SDL_SetRenderVSync(state->renderer, 1)) {
radiantwave::logger::Logger::instance().warning("Graphics",
"VSync could not be enabled; scrolling text may tear (VSync error: " + std::string(SDL_GetError()) + ")");
}
// Present on the display's refresh. Without VSync the kiosk panel tears
// fast-scrolling text into horizontal bands that flicker as the text moves.
if (!SDL_SetRenderVSync(state->renderer, 1)) {
radiantwave::logger::Logger::instance().warning(
"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
// layout code already targets this resolution, so everything renders at
// the correct size on any display — the GPU scales to native pixels.
if (!SDL_SetRenderLogicalPresentation(state->renderer, 1920, 1080, SDL_LOGICAL_PRESENTATION_STRETCH)) {
radiantwave::logger::Logger::instance().warning("Graphics",
"Could not set logical render size; layout may be wrong on HiDPI displays");
}
// Lock the renderer to a 1920x1080 logical coordinate space. All page
// layout code already targets this resolution, so everything renders at
// the correct size on any display — the GPU scales to native pixels.
// if (!SDL_SetRenderLogicalPresentation(state->renderer, 1920, 1080,
// SDL_LOGICAL_PRESENTATION_STRETCH)) {
// 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
if (!state->config.initialize()) {
radiantwave::logger::Logger::instance().warning("Config",
"Settings file could not be loaded. Default settings will be used (Config initialization failed at ~/.local/radiantwave/config.yaml)");
} else {
radiantwave::logger::Logger::instance().info("Config", "Configuration initialized successfully");
}
// Initialize configuration, database, and network manager
if (!state->config.initialize()) {
radiantwave::logger::Logger::instance().warning(
"Config", "Settings file could not be loaded. Default settings "
"will be used (Config initialization failed at "
"~/.local/radiantwave/config.yaml)");
} else {
radiantwave::logger::Logger::instance().info(
"Config", "Configuration initialized successfully");
}
if (!state->db.initialize()) {
radiantwave::logger::Logger::instance().error("Database",
"Database storage unavailable. App features may be limited (Database initialization failed at ~/.local/radiantwave/radiantwave.db)");
} else {
radiantwave::logger::Logger::instance().info("Database", "Database initialized and schema migrated");
}
if (!state->db.initialize()) {
radiantwave::logger::Logger::instance().error(
"Database", "Database storage unavailable. App features may be "
"limited (Database initialization failed at "
"~/.local/radiantwave/radiantwave.db)");
} else {
radiantwave::logger::Logger::instance().info(
"Database", "Database initialized and schema migrated");
}
if (!state->network.initialize()) {
radiantwave::logger::Logger::instance().warning("Network",
"WiFi network management is unavailable. Check if NetworkManager is running (D-Bus connection to org.freedesktop.NetworkManager failed)");
} else {
radiantwave::logger::Logger::instance().info("Network", "NetworkManager service connected");
}
if (!state->network.initialize()) {
radiantwave::logger::Logger::instance().warning(
"Network", "WiFi network management is unavailable. Check if "
"NetworkManager is running (D-Bus connection to "
"org.freedesktop.NetworkManager failed)");
} else {
radiantwave::logger::Logger::instance().info(
"Network", "NetworkManager service connected");
}
if (!state->audio.initialize(state->config)) {
radiantwave::logger::Logger::instance().warning("Audio",
"Audio playback is unavailable. Check sound hardware connections (SDL3_mixer initialization failed)");
} else {
radiantwave::logger::Logger::instance().info("Audio", "Audio subsystem initialized");
}
// Let playback logs report user-facing display names from the database.
state->audio.attachDatabase(state->db);
if (!state->audio.initialize(state->config)) {
radiantwave::logger::Logger::instance().warning(
"Audio", "Audio playback is unavailable. Check sound hardware "
"connections (SDL3_mixer initialization failed)");
} else {
radiantwave::logger::Logger::instance().info(
"Audio", "Audio subsystem initialized");
}
// 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
// internet is available, so downloads start automatically on connect.
// The downloaded files are reconciled into the database's audio_files table.
state->audioDownloader.start(state->db);
// Background download of audio assets. The worker keeps retrying until
// internet is available, so downloads start automatically on connect.
// The downloaded files are reconciled into the database's audio_files
// table.
state->audioDownloader.start(state->db);
// Background license validation. Home triggers a refresh whenever it is
// displayed; the result gates access to Play and the Proclamation options.
state->licenseTracker.start(state->db);
// Background license validation. Home triggers a refresh whenever it is
// displayed; the result gates access to Play and the Proclamation options.
state->licenseTracker.start(state->db);
state->config.onChanged([state](const radiantwave::config::AppConfig& new_config) {
SDL_Log("Live config update received: volume=%d, dynamic_audio=%d",
new_config.audio.volume, new_config.audio.dynamic_audio ? 1 : 0);
state->db.logEvent("INFO", "Config", "Live configuration hot-reloaded");
});
state->config.onChanged(
[state](const radiantwave::config::AppConfig &new_config) {
SDL_Log("Live config update received: volume=%d, dynamic_audio=%d",
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->currentMenuItem = (state->currentMenuItem + 1) % 2;
});
state->keybinds.bind(SDLK_DOWN, "menu_down", [state]() {
state->currentMenuItem = (state->currentMenuItem + 1) % 2;
});
state->keybinds.bind(SDLK_UP, "menu_up", [state]() {
state->currentMenuItem = (state->currentMenuItem + 1) % 2;
});
state->keybinds.bind(SDLK_DOWN, "menu_down", [state]() {
state->currentMenuItem = (state->currentMenuItem + 1) % 2;
});
// Drive the First-Run walkthrough: shows the WiFi page when the internet
// is unreachable, then the email and license pages when those are missing,
// finishing on Home. The internet check is a real connectivity probe
// (Ethernet, WiFi, or macOS all work), so it never traps a connected
// device on the WiFi page.
radiantwave::provisioning::begin(*state);
// Drive the First-Run walkthrough: shows the WiFi page when the internet
// is unreachable <DEPRECATED>, then the email and license pages when those
// are missing, finishing on Home. The internet check is a real connectivity
// probe (Ethernet, WiFi, or macOS all work), so it never traps a connected
// device on the WiFi page.
radiantwave::provisioning::begin(*state);
*appstate = state;
return SDL_APP_CONTINUE;
*appstate = state;
return SDL_APP_CONTINUE;
}
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
{
auto &state = *static_cast<AppState *>(appstate);
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) {
auto &state = *static_cast<AppState *>(appstate);
if (event->type == SDL_EVENT_QUIT)
{
state.quitRequested = true;
}
if (event->type == SDL_EVENT_QUIT) {
state.quitRequested = true;
}
if (!state.keybinds.handleEvent(*event)) {
state.pages.handleEvent(*event);
}
if (!state.keybinds.handleEvent(*event)) {
state.pages.handleEvent(*event);
}
if (state.quitRequested && state.quitFadeRemaining <= 0.0f)
{
// Graceful shutdown: start the audio fade-out and keep the loop
// running until it completes, then let SDL_AppQuit tear everything
// down. When nothing is playing there is nothing to fade and quitting
// is immediate.
if (state.audio.stopTrackFadeOut(kQuitFadeSeconds))
{
state.quitFadeRemaining = kQuitFadeSeconds;
return SDL_APP_CONTINUE;
}
return SDL_APP_SUCCESS;
}
if (state.quitRequested && state.quitFadeRemaining <= 0.0f) {
// Graceful shutdown: start the audio fade-out and keep the loop
// running until it completes, then let SDL_AppQuit tear everything
// down. When nothing is playing there is nothing to fade and quitting
// is immediate.
if (state.audio.stopTrackFadeOut(kQuitFadeSeconds)) {
state.quitFadeRemaining = kQuitFadeSeconds;
return SDL_APP_CONTINUE;
}
return SDL_APP_SUCCESS;
}
return SDL_APP_CONTINUE;
return SDL_APP_CONTINUE;
}
SDL_AppResult SDL_AppIterate(void *appstate)
{
auto &state = *static_cast<AppState *>(appstate);
SDL_AppResult SDL_AppIterate(void *appstate) {
auto &state = *static_cast<AppState *>(appstate);
Uint64 nowTicks = SDL_GetTicks();
static Uint64 lastTicks = nowTicks;
float dt = static_cast<float>(nowTicks - lastTicks) / 1000.0f;
if (dt > 0.1f) dt = 0.1f;
lastTicks = nowTicks;
Uint64 nowTicks = SDL_GetTicks();
static Uint64 lastTicks = nowTicks;
float dt = static_cast<float>(nowTicks - lastTicks) / 1000.0f;
if (dt > 0.1f)
dt = 0.1f;
lastTicks = nowTicks;
state.pages.update(dt);
state.pages.update(dt);
if (state.quitFadeRemaining > 0.0f)
{
state.quitFadeRemaining -= dt;
if (state.quitFadeRemaining <= 0.0f)
{
return SDL_APP_SUCCESS;
}
}
if (state.quitFadeRemaining > 0.0f) {
state.quitFadeRemaining -= dt;
if (state.quitFadeRemaining <= 0.0f) {
return SDL_APP_SUCCESS;
}
}
SDL_SetRenderDrawColor(state.renderer, 30, 30, 46, 255);
SDL_RenderClear(state.renderer);
SDL_SetRenderDrawColor(state.renderer, 30, 30, 46, 255);
SDL_RenderClear(state.renderer);
state.pages.render(state.renderer);
state.pages.render(state.renderer);
SDL_RenderPresent(state.renderer);
return SDL_APP_CONTINUE;
SDL_RenderPresent(state.renderer);
return SDL_APP_CONTINUE;
}
void SDL_AppQuit(void *appstate, SDL_AppResult result)
{
auto *state = static_cast<AppState *>(appstate);
if (state) {
state->audioDownloader.shutdown();
state->licenseTracker.shutdown();
state->audio.shutdown();
state->network.shutdown();
state->config.shutdown();
state->db.close();
SDL_DestroyRenderer(state->renderer);
SDL_DestroyWindow(state->window);
delete state;
}
SDL_Quit();
void SDL_AppQuit(void *appstate, SDL_AppResult result) {
auto *state = static_cast<AppState *>(appstate);
if (state) {
state->audioDownloader.shutdown();
state->licenseTracker.shutdown();
state->audio.shutdown();
state->network.shutdown();
state->config.shutdown();
state->db.close();
SDL_DestroyRenderer(state->renderer);
SDL_DestroyWindow(state->window);
delete state;
}
SDL_Quit();
}