Skip to content
Merged
16 changes: 16 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
## [Unreleased]

### Fixed
- **Auto-Update vt_data: Prevent automatic updates when disabled (2026-04-09)**
- Fixed unexpected vt_data downloads/updates triggered when restarting or shutting down from non-server displays even when "Auto-Update vt_data on Startup" is OFF.
- Root Cause: early startup autoupdate script executed based on `.viewtouch_config.autoupdate` before the authoritative fixed settings were consulted; restart flows that use the command file + vtrestart could relaunch `vt_main` which ran the script regardless of the UI toggle.
- Solution:
- Startup autoupdate now requires both `.viewtouch_config.autoupdate` and the fixed settings file (`/usr/viewtouch/dat/settings.dat`) `auto_update_vt_data` flag to be true before running the update script.
- `FindVTData()` now respects `auto_update_vt_data` and will not download `vt_data` when auto-update is disabled.
- The UI switch persists immediately (`settings->Save()`), ensuring restarts honor the user's preference.
- Added logging when autoupdate is suppressed and when restart origin is recorded for auditing.
- Impact: Restarting or shutting down from any display no longer triggers automatic vt_data updates when the setting is OFF.
- Files modified: `main/data/manager.cc`, `zone/settings_zone.cc`

- **Default Employees: Only create preset employees when missing (2026-04-09)**
- Fixed issue where preset/default employees were created every startup even when `employee.dat` already existed.
- Solution: Only create default/preset employees when `employee.dat` is missing.
- Files modified: `main/data/manager.cc`

- **Dialog: Fix crash when opening/using Add Comment dialog (2026-04-08)**
- Prevents a segmentation fault when opening or submitting the Add Comment dialog caused by an uninitialized `key` pointer in `GetTextDialog`.
- `GetTextDialog` constructors now initialize all entries of `key[]` to `nullptr` to avoid dereferencing uninitialized pointers.
Expand Down
80 changes: 51 additions & 29 deletions main/data/manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -667,8 +667,21 @@ int main(int argc, genericChar* argv[])
vt::Logger::info("Using default data path: {}", VIEWTOUCH_PATH "/dat");
MasterSystem->SetDataPath(VIEWTOUCH_PATH "/dat");
}
// Respect fixed settings file when deciding whether to run startup update script
bool fixed_auto_update_allowed = true;
std::string fixed_settings_path = std::string(VIEWTOUCH_PATH) + "/dat/settings.dat";
if (fs::exists(fixed_settings_path)) {
Settings temp_settings;
if (temp_settings.Load(fixed_settings_path.c_str()) == 0) {
fixed_auto_update_allowed = temp_settings.auto_update_vt_data;
if (!fixed_auto_update_allowed)
ReportError(GlobalTranslate("Startup autoupdate suppressed by fixed settings"));
} else {
ReportError(GlobalTranslate("Warning: Could not load fixed settings file for startup update decision"));
}
}
// Check for updates from server if not disabled
if (autoupdate)
if (autoupdate && fixed_auto_update_allowed)
{
ReportError(GlobalTranslate("Automatic check for updates..."));
unlink(VIEWTOUCH_UPDATE_COMMAND); // out with the old
Expand All @@ -681,26 +694,12 @@ int main(int argc, genericChar* argv[])
// Check if vt_data exists locally first
bool vt_data_updated = false;

// Check if auto-update is enabled by loading settings from a fixed location
// to ensure consistency across all displays
bool auto_update_enabled = true; // Default to enabled for backward compatibility

std::string fixed_settings_path = std::string(VIEWTOUCH_PATH) + "/dat/settings.dat";

if (fs::exists(fixed_settings_path)) {
Settings temp_settings;
if (temp_settings.Load(fixed_settings_path.c_str()) == 0) {
auto_update_enabled = temp_settings.auto_update_vt_data;
if (!auto_update_enabled) {
ReportError(GlobalTranslate("Auto-update of vt_data is disabled in settings"));
} else {
ReportError(GlobalTranslate("Auto-update of vt_data is enabled in settings"));
}
} else {
ReportError(GlobalTranslate("Warning: Could not load settings file, defaulting to auto-update enabled"));
}
// Determine whether vt_data auto-update is enabled (reuse earlier fixed setting)
bool auto_update_enabled = fixed_auto_update_allowed;
if (!auto_update_enabled) {
ReportError(GlobalTranslate("Auto-update of vt_data is disabled in settings"));
} else {
ReportError(GlobalTranslate("Warning: Settings file not found, defaulting to auto-update enabled"));
ReportError(GlobalTranslate("Auto-update of vt_data is enabled in settings"));
}

if (!fs::exists(SYSTEM_DATA_FILE)) {
Expand Down Expand Up @@ -948,7 +947,12 @@ void UserSignal1(int /*my_signal*/)
}
}

// Exit immediately to trigger restart
// Log origin of restart if available, then exit to trigger restart
std::string origin = displaystr.data();
if (!origin.empty())
ReportError(std::string("UserSignal1: Restart requested from display: ") + origin);
else
ReportError("UserSignal1: Restart requested (unknown display)");
ReportError("UserSignal1: Exiting for restart");
exit(0);
}
Expand Down Expand Up @@ -1317,11 +1321,19 @@ int StartSystem(int my_use_net)
// set developer key (this should be done somewhere else)
sys->user_db.developer->key = settings->developer_key;

// Create default users if settings file was just created
if (settings_just_created)
// Create default users only if no employee database file exists
{
ReportLoader("Creating Default Users");
CreateDefaultUsers(sys, settings);
std::array<genericChar, STRLENGTH> user_db_path{};
sys->FullPath(MASTER_USER_DB, user_db_path.data());
if (!DoesFileExist(user_db_path.data()))
{
ReportLoader("Creating Default Users");
CreateDefaultUsers(sys, settings);
}
else
{
ReportLoader("Skipping Default Users creation; employee database exists");
}
}

vt_safe_string::safe_format(msg.data(), msg.size(), "%s OK", MASTER_USER_DB);
Expand Down Expand Up @@ -1919,9 +1931,17 @@ int FindVTData(InputDataFile *infile)
if (infile->Open(vt_data_path, version) == 0)
return version;

// Only download if we don't have any vt_data file anywhere
// This prevents overwriting existing files when offline
if (!fs::exists(SYSTEM_DATA_FILE) && !fs::exists(vt_data_path)) {
// Only download if we don't have any vt_data file anywhere AND auto-update is allowed
// This prevents overwriting existing files when offline or when auto-update is disabled
bool allow_download = true;
if (MasterSystem != nullptr) {
if (MasterSystem->settings.auto_update_vt_data == 0) {
allow_download = false;
}
}
if (!allow_download) {
fprintf(stderr, "Auto-update disabled by settings, skipping vt_data download in FindVTData\n");
} else if (!fs::exists(SYSTEM_DATA_FILE) && !fs::exists(vt_data_path)) {
// download to official location and then try to read again
// Try both HTTPS and HTTP for reliable downloads on Raspberry Pi
const std::string vtdata_url = "www.viewtouch.com/vt_data";
Expand Down Expand Up @@ -3560,8 +3580,10 @@ int RunUserCommand()
AllowLogins = 0;
else if (strcmp(key.data(), "allowlogin") == 0)
AllowLogins = 1;
else if (strcmp(key.data(), "exitsystem") == 0)
else if (strcmp(key.data(), "exitsystem") == 0) {
exit_system = 1;
ReportError("RunUserCommand: External command requested system exit");
}
else if (strcmp(key.data(), "endday") == 0)
endday = RunEndDay();
else if (strcmp(key.data(), "runmacros") == 0)
Expand Down
130 changes: 90 additions & 40 deletions src/utils/cpp23_utils.hh
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,16 @@
#include <system_error>
#include <stdexcept>
#include <type_traits>
#include <algorithm> // std::clamp

// Feature detection for C++20/C++23 features
#ifdef __has_include
# if __has_include(<format>)
# include <format>
# define VT_HAS_STD_FORMAT 1
# elif __has_include(<fmt/format.h>)
# include <fmt/format.h>
# define VT_HAS_FMTLIB 1
# endif
#endif

Expand Down Expand Up @@ -111,62 +115,32 @@ template<typename E>
// String Formatting (C++20 std::format, enhanced in C++23)
// ============================================================================

/**
* @brief Type-safe string formatting using std::format
*
* Modern replacement for sprintf/snprintf with compile-time format checking.
*
* @param fmt Format string
* @param args Arguments to format
* @return Formatted string
*
* Example:
* auto str = format("Account {} of {}", account_no, total);
* auto price = format("Price: ${:.2f}", 19.99);
*/
// String formatting utilities: provide implementations using std::format (C++20/23)
// or fall back to {fmt} if available. If neither is present, provide a
// minimal snprintf-based fallback for simple formatting.

#if defined(VT_HAS_STD_FORMAT)

template<typename... Args>
[[nodiscard]] std::string format(std::format_string<Args...> fmt, Args&&... args) {
return std::format(fmt, std::forward<Args>(args)...);
}

/**
* @brief Format to an existing string (more efficient, no allocation)
*
* @param str Output string (will be cleared and reused)
* @param fmt Format string
* @param args Arguments to format
*/
template<typename... Args>
void format_to(std::string& str, std::format_string<Args...> fmt, Args&&... args) {
str.clear();
std::format_to(std::back_inserter(str), fmt, std::forward<Args>(args)...);
}

/**
* @brief Format with fixed-size buffer (for stack allocation)
*
* Safe replacement for snprintf() with automatic truncation.
* Returns number of characters that would have been written (like snprintf).
*
* @param buffer Output buffer
* @param size Buffer size
* @param fmt Format string
* @param args Arguments to format
* @return Number of characters written (excluding null terminator)
*
* Example:
* char buffer[256];
* format_to_buffer(buffer, sizeof(buffer), "Account {}", acct_num);
*/
template<typename... Args>
[[nodiscard]] std::size_t format_to_buffer(
char* buffer,
char* buffer,
std::size_t size,
std::format_string<Args...> fmt,
Args&&... args)
std::format_string<Args...> fmt,
Args&&... args)
{
if (size == 0) return 0;

try {
auto result = std::format_to_n(buffer, size - 1, fmt, std::forward<Args>(args)...);
buffer[result.size < size ? result.size : size - 1] = '\0';
Expand All @@ -177,6 +151,82 @@ template<typename... Args>
}
}

#elif defined(VT_HAS_FMTLIB)

// Use {fmt} library as a compatible fallback.
template<typename... Args>
[[nodiscard]] std::string format(fmt::format_string<Args...> fmt_s, Args&&... args) {
return fmt::format(fmt_s, std::forward<Args>(args)...);
}

template<typename... Args>
void format_to(std::string& str, fmt::format_string<Args...> fmt_s, Args&&... args) {
str.clear();
fmt::format_to(std::back_inserter(str), fmt_s, std::forward<Args>(args)...);
}

template<typename... Args>
[[nodiscard]] std::size_t format_to_buffer(
char* buffer,
std::size_t size,
fmt::format_string<Args...> fmt_s,
Args&&... args)
{
if (size == 0) return 0;

try {
auto result = fmt::format_to_n(buffer, size - 1, fmt_s, std::forward<Args>(args)...);
buffer[result.size < size ? result.size : size - 1] = '\0';
return result.size;
} catch (...) {
buffer[0] = '\0';
return 0;
}
}

#else

// Minimal fallback: use snprintf-style formatting. This does NOT support
// Python-style formatting ("{}") and is a degraded fallback for older
// compilers without std::format or {fmt}. It keeps the code compiling but
// callers should prefer environments with std::format or fmt available.
#include <cstdio>

template<typename... Args>
[[nodiscard]] std::string format(const char* fmt_cstr, Args&&... args) {
// Estimate required size
int size = std::snprintf(nullptr, 0, fmt_cstr, std::forward<Args>(args)...);
if (size <= 0) return std::string();
std::string out;
out.resize(static_cast<std::size_t>(size));
std::snprintf(out.data(), out.size() + 1, fmt_cstr, std::forward<Args>(args)...);
return out;
}

template<typename... Args>
void format_to(std::string& str, const char* fmt_cstr, Args&&... args) {
str = format(fmt_cstr, std::forward<Args>(args)...);
}

template<typename... Args>
[[nodiscard]] std::size_t format_to_buffer(
char* buffer,
std::size_t size,
const char* fmt_cstr,
Args&&... args)
{
if (size == 0) return 0;
int n = std::snprintf(buffer, size, fmt_cstr, std::forward<Args>(args)...);
if (n < 0) {
buffer[0] = '\0';
return 0;
}
if (static_cast<std::size_t>(n) >= size) buffer[size - 1] = '\0';
return static_cast<std::size_t>(n);
}

#endif

// ============================================================================
// Error Handling with std::expected
// ============================================================================
Expand Down
5 changes: 5 additions & 0 deletions src/utils/vt_enum_utils.hh
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <optional>
#include <vector>
#include <utility> // std::to_underlying (C++23)
#include <type_traits>

namespace vt {

Expand Down Expand Up @@ -132,7 +133,11 @@ std::optional<E> IntToEnum(int value) {
template<typename E>
requires std::is_enum_v<E>
constexpr auto EnumToUnderlying(E value) noexcept {
#if defined(__cpp_lib_to_underlying)
return std::to_underlying(value);
#else
return static_cast<std::underlying_type_t<E>>(value);
#endif
}

/**
Expand Down
2 changes: 2 additions & 0 deletions zone/settings_zone.cc
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,8 @@ SignalResult SwitchZone::Touch(Terminal *term, int /*tx*/, int /*ty*/)
break;
case SWITCH_AUTO_UPDATE_VT_DATA:
settings->auto_update_vt_data ^= 1;
// Persist change immediately so startup/restart reads the updated preference
settings->Save();
break;
case SWITCH_BUTTON_IMAGES:
settings->show_button_images_default ^= 1;
Expand Down
Loading