From 20964300946dab02f840fca5de4357902e63930e Mon Sep 17 00:00:00 2001 From: No0ne558 Date: Thu, 9 Apr 2026 13:39:03 -0700 Subject: [PATCH 1/7] chore(changelog): document vt_data auto-update fixes; respect fixed settings and non-server restarts (2026-04-09) --- docs/changelog.md | 11 ++++++++ main/data/manager.cc | 64 ++++++++++++++++++++++++++----------------- zone/settings_zone.cc | 2 ++ 3 files changed, 52 insertions(+), 25 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 2cf79e56..629c5ac9 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -7,6 +7,17 @@ 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` + - **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. diff --git a/main/data/manager.cc b/main/data/manager.cc index 8df45dcd..0392ac0f 100644 --- a/main/data/manager.cc +++ b/main/data/manager.cc @@ -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 @@ -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)) { @@ -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); } @@ -1919,9 +1923,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"; @@ -3560,8 +3572,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) diff --git a/zone/settings_zone.cc b/zone/settings_zone.cc index 5f73cd09..0f43d919 100644 --- a/zone/settings_zone.cc +++ b/zone/settings_zone.cc @@ -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; From 474afbff3c8f192edb8406993d6cb971a2cc9249 Mon Sep 17 00:00:00 2001 From: No0ne558 Date: Thu, 9 Apr 2026 13:49:10 -0700 Subject: [PATCH 2/7] docs(changelog): add default-employee creation note (2026-04-09) --- docs/changelog.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index 629c5ac9..b69c54c6 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -18,6 +18,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - 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. From 52526cc052536fe2ec2a9786f03b2c431ce1cc99 Mon Sep 17 00:00:00 2001 From: No0ne558 Date: Thu, 9 Apr 2026 13:50:10 -0700 Subject: [PATCH 3/7] chore: commit remaining changes (2026-04-09) --- main/data/manager.cc | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/main/data/manager.cc b/main/data/manager.cc index 0392ac0f..d1b47580 100644 --- a/main/data/manager.cc +++ b/main/data/manager.cc @@ -1321,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 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); From 6e76d972ee2248efaadd0c407568caac5fc98463 Mon Sep 17 00:00:00 2001 From: No0ne558 Date: Thu, 9 Apr 2026 13:58:04 -0700 Subject: [PATCH 4/7] fix(enum): fallback for std::to_underlying on older stdlibs (2026-04-09) --- src/utils/vt_enum_utils.hh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/utils/vt_enum_utils.hh b/src/utils/vt_enum_utils.hh index c6ca4983..ce8d7afe 100644 --- a/src/utils/vt_enum_utils.hh +++ b/src/utils/vt_enum_utils.hh @@ -27,6 +27,7 @@ #include #include #include // std::to_underlying (C++23) +#include namespace vt { @@ -132,7 +133,11 @@ std::optional IntToEnum(int value) { template requires std::is_enum_v constexpr auto EnumToUnderlying(E value) noexcept { - return std::to_underlying(value); + if constexpr (requires { std::to_underlying(value); }) { + return std::to_underlying(value); + } else { + return static_cast>(value); + } } /** From bec572ef71f9aae646a7595b6b93e3e50e669aa1 Mon Sep 17 00:00:00 2001 From: No0ne558 Date: Thu, 9 Apr 2026 14:03:22 -0700 Subject: [PATCH 5/7] ci: guard std::to_underlying with feature-test macro (2026-04-09) --- src/utils/vt_enum_utils.hh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/utils/vt_enum_utils.hh b/src/utils/vt_enum_utils.hh index ce8d7afe..6fa933c2 100644 --- a/src/utils/vt_enum_utils.hh +++ b/src/utils/vt_enum_utils.hh @@ -133,11 +133,11 @@ std::optional IntToEnum(int value) { template requires std::is_enum_v constexpr auto EnumToUnderlying(E value) noexcept { - if constexpr (requires { std::to_underlying(value); }) { - return std::to_underlying(value); - } else { - return static_cast>(value); - } +#if defined(__cpp_lib_to_underlying) + return std::to_underlying(value); +#else + return static_cast>(value); +#endif } /** From 0970e08650f8fab87476707f841a8974c638ef13 Mon Sep 17 00:00:00 2001 From: No0ne558 Date: Thu, 9 Apr 2026 14:14:43 -0700 Subject: [PATCH 6/7] ci: include so std::clamp is available (2026-04-09) --- src/utils/cpp23_utils.hh | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/cpp23_utils.hh b/src/utils/cpp23_utils.hh index ce7c21e5..84b3e1cb 100644 --- a/src/utils/cpp23_utils.hh +++ b/src/utils/cpp23_utils.hh @@ -27,6 +27,7 @@ #include #include #include +#include // std::clamp // Feature detection for C++20/C++23 features #ifdef __has_include From 4147b5ae1e9db2e459c70023ed36166674d50bd9 Mon Sep 17 00:00:00 2001 From: No0ne558 Date: Thu, 9 Apr 2026 14:26:27 -0700 Subject: [PATCH 7/7] ci: fallback to {fmt} or snprintf when std::format unavailable (2026-04-09) --- src/utils/cpp23_utils.hh | 129 +++++++++++++++++++++++++++------------ 1 file changed, 89 insertions(+), 40 deletions(-) diff --git a/src/utils/cpp23_utils.hh b/src/utils/cpp23_utils.hh index 84b3e1cb..6394d5d1 100644 --- a/src/utils/cpp23_utils.hh +++ b/src/utils/cpp23_utils.hh @@ -34,6 +34,9 @@ # if __has_include() # include # define VT_HAS_STD_FORMAT 1 +# elif __has_include() +# include +# define VT_HAS_FMTLIB 1 # endif #endif @@ -112,62 +115,32 @@ template // 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 [[nodiscard]] std::string format(std::format_string fmt, Args&&... args) { return std::format(fmt, std::forward(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 void format_to(std::string& str, std::format_string fmt, Args&&... args) { str.clear(); std::format_to(std::back_inserter(str), fmt, std::forward(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 [[nodiscard]] std::size_t format_to_buffer( - char* buffer, + char* buffer, std::size_t size, - std::format_string fmt, - Args&&... args) + std::format_string fmt, + Args&&... args) { if (size == 0) return 0; - + try { auto result = std::format_to_n(buffer, size - 1, fmt, std::forward(args)...); buffer[result.size < size ? result.size : size - 1] = '\0'; @@ -178,6 +151,82 @@ template } } +#elif defined(VT_HAS_FMTLIB) + +// Use {fmt} library as a compatible fallback. +template +[[nodiscard]] std::string format(fmt::format_string fmt_s, Args&&... args) { + return fmt::format(fmt_s, std::forward(args)...); +} + +template +void format_to(std::string& str, fmt::format_string fmt_s, Args&&... args) { + str.clear(); + fmt::format_to(std::back_inserter(str), fmt_s, std::forward(args)...); +} + +template +[[nodiscard]] std::size_t format_to_buffer( + char* buffer, + std::size_t size, + fmt::format_string fmt_s, + Args&&... args) +{ + if (size == 0) return 0; + + try { + auto result = fmt::format_to_n(buffer, size - 1, fmt_s, std::forward(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 + +template +[[nodiscard]] std::string format(const char* fmt_cstr, Args&&... args) { + // Estimate required size + int size = std::snprintf(nullptr, 0, fmt_cstr, std::forward(args)...); + if (size <= 0) return std::string(); + std::string out; + out.resize(static_cast(size)); + std::snprintf(out.data(), out.size() + 1, fmt_cstr, std::forward(args)...); + return out; +} + +template +void format_to(std::string& str, const char* fmt_cstr, Args&&... args) { + str = format(fmt_cstr, std::forward(args)...); +} + +template +[[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)...); + if (n < 0) { + buffer[0] = '\0'; + return 0; + } + if (static_cast(n) >= size) buffer[size - 1] = '\0'; + return static_cast(n); +} + +#endif + // ============================================================================ // Error Handling with std::expected // ============================================================================