From 7e4327826f2c2acd900fc7d6bbf4038dbdfdfdd0 Mon Sep 17 00:00:00 2001 From: Marius Scheffel Date: Sat, 14 Mar 2026 10:44:43 +0100 Subject: [PATCH 1/5] added quirks for koboMonza --- run.sh | 17 +++++++++++++++++ src/kobo/diag_main.cpp | 21 ++++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/run.sh b/run.sh index 5a3e7d0..a862547 100644 --- a/run.sh +++ b/run.sh @@ -76,12 +76,29 @@ exec >>"${LOG}" 2>&1 hydrate_probe_env +# Clara Colour (spaColour/393): snow protocol, needs mirror_x if [ -z "${APP_TOUCH_MIRROR_X}" ] && \ { [ "${APP_CODENAME}" = "spaColour" ] || [ "${APP_PRODUCT_ID}" = "393" ]; } && \ [ "${APP_TOUCH_PROTOCOL}" = "snow" ]; then APP_TOUCH_MIRROR_X=1 fi +# Libra Colour (monza/390): standard multitouch, needs mirror_y (not mirror_x) +if [ -z "${APP_TOUCH_MIRROR_Y}" ] && \ + { [ "${APP_CODENAME}" = "monza" ] || [ "${APP_PRODUCT_ID}" = "390" ]; }; then + APP_TOUCH_MIRROR_Y=1 +fi + +# Elipsa 2E (condor/389): standard multitouch, needs mirror_y (not mirror_x) +if [ -z "${APP_TOUCH_MIRROR_Y}" ] && \ + { [ "${APP_CODENAME}" = "condor" ] || [ "${APP_PRODUCT_ID}" = "389" ]; }; then + APP_TOUCH_MIRROR_Y=1 +fi + +if [ -z "${APP_TOUCH_MIRROR_X}" ]; then + APP_TOUCH_MIRROR_X=0 +fi + if [ -z "${APP_TOUCH_MIRROR_Y}" ]; then APP_TOUCH_MIRROR_Y=0 fi diff --git a/src/kobo/diag_main.cpp b/src/kobo/diag_main.cpp index 8cea924..b76a482 100644 --- a/src/kobo/diag_main.cpp +++ b/src/kobo/diag_main.cpp @@ -96,11 +96,30 @@ skeets_input_protocol_t detect_input_protocol() { int run_input_diag() { std::fprintf(stderr, "rewrite diag: input.begin\n"); + // Query actual framebuffer dimensions instead of hardcoding a single device's resolution. + int fb_width = 0; + int fb_height = 0; + { + skeets_framebuffer_t probe_fb; + std::string fb_err; + if (skeets_framebuffer_open(probe_fb, &fb_err)) { + fb_width = probe_fb.info.screen_width; + fb_height = probe_fb.info.screen_height; + skeets_framebuffer_close(probe_fb); + std::fprintf(stderr, "rewrite diag: input.detected_fb=%dx%d\n", fb_width, fb_height); + } else { + fb_width = 1072; + fb_height = 1448; + std::fprintf(stderr, "rewrite diag: input.fb_probe_failed=%s (using %dx%d fallback)\n", + fb_err.c_str(), fb_width, fb_height); + } + } + skeets_input_t input; input.debug_raw_events = true; input.raw_event_log_budget = 64; std::string error_message; - if (!skeets_input_open(input, 1072, 1448, detect_input_protocol(), &error_message)) { + if (!skeets_input_open(input, fb_width, fb_height, detect_input_protocol(), &error_message)) { std::fprintf(stderr, "rewrite diag: input.open_failed=%s\n", error_message.c_str()); return 5; } From a278c9b737b22acd7542c6d71473454e0c7ddd10 Mon Sep 17 00:00:00 2001 From: Marius Scheffel Date: Sat, 14 Mar 2026 11:06:34 +0100 Subject: [PATCH 2/5] Add diagnostic logging for config.ini write failures When login succeeds but config.ini is not written, the errors were silently ignored. This adds comprehensive logging to: - config_save(): log file open failures with errno and strerror - save_session() in bootstrap.cpp: log config path and save result - persist_session() in main.cpp: log config path and save result - skeets_ensure_data_dirs(): log directory paths being created This will help diagnose issues like: - Permission problems on /mnt/onboard/.adds/sKeets/ - Read-only filesystem mounts - Directory creation failures --- src/kobo/bootstrap.cpp | 18 +++++++++++++++--- src/kobo/main.cpp | 16 +++++++++++++--- src/util/config.cpp | 25 +++++++++++++++++++------ src/util/paths.cpp | 17 +++++++++++++++-- 4 files changed, 62 insertions(+), 14 deletions(-) diff --git a/src/kobo/bootstrap.cpp b/src/kobo/bootstrap.cpp index a983375..047da51 100644 --- a/src/kobo/bootstrap.cpp +++ b/src/kobo/bootstrap.cpp @@ -91,8 +91,14 @@ bool has_saved_session(const Bsky::Session& session) { } void save_session(const Bsky::Session& session) { - config_t* config = config_open(skeets_config_path()); - if (!config) return; + const char* config_path = skeets_config_path(); + std::fprintf(stderr, "bootstrap: save_session: config_path='%s'\n", config_path); + + config_t* config = config_open(config_path); + if (!config) { + std::fprintf(stderr, "bootstrap: save_session: config_open returned NULL for '%s'\n", config_path); + return; + } config_set_str(config, "handle", session.handle.c_str()); config_set_str(config, "access_jwt", session.access_jwt.c_str()); @@ -100,7 +106,13 @@ void save_session(const Bsky::Session& session) { config_set_str(config, "did", session.did.c_str()); config_set_str(config, "pds_url", session.pds_url.c_str()); config_set_str(config, "appview_url", session.appview_url.c_str()); - config_save(config); + + int result = config_save(config); + if (result != 0) { + std::fprintf(stderr, "bootstrap: save_session: config_save FAILED for '%s' (result=%d)\n", config_path, result); + } else { + std::fprintf(stderr, "bootstrap: save_session: config_save succeeded for '%s'\n", config_path); + } config_free(config); } diff --git a/src/kobo/main.cpp b/src/kobo/main.cpp index 6a19313..18b1aa6 100644 --- a/src/kobo/main.cpp +++ b/src/kobo/main.cpp @@ -1569,15 +1569,25 @@ void clear_saved_session(skeets_app_t& app) { } void persist_session(skeets_app_t& app, const Bsky::Session& session) { - config_t* config = config_open(skeets_config_path()); - if (config) { + const char* config_path = skeets_config_path(); + std::fprintf(stderr, "persist_session: config_path='%s'\n", config_path); + + config_t* config = config_open(config_path); + if (!config) { + std::fprintf(stderr, "persist_session: config_open returned NULL for '%s'\n", config_path); + } else { config_set_str(config, "handle", session.handle.c_str()); config_set_str(config, "access_jwt", session.access_jwt.c_str()); config_set_str(config, "refresh_jwt", session.refresh_jwt.c_str()); config_set_str(config, "did", session.did.c_str()); config_set_str(config, "pds_url", session.pds_url.c_str()); config_set_str(config, "appview_url", session.appview_url.c_str()); - config_save(config); + int result = config_save(config); + if (result != 0) { + std::fprintf(stderr, "persist_session: config_save FAILED (result=%d)\n", result); + } else { + std::fprintf(stderr, "persist_session: config_save succeeded\n"); + } config_free(config); } app.bootstrap.session = session; diff --git a/src/util/config.cpp b/src/util/config.cpp index b5cd400..b48e439 100644 --- a/src/util/config.cpp +++ b/src/util/config.cpp @@ -1,9 +1,10 @@ #include "config.h" #include "str.h" -#include -#include -#include +#include +#include +#include +#include #define CONFIG_MAX_ENTRIES 128 @@ -57,13 +58,25 @@ void config_free(config_t *cfg) { } int config_save(config_t *cfg) { - if (!cfg) return -1; + if (!cfg) { + std::fprintf(stderr, "config_save: cfg is NULL\n"); + return -1; + } FILE *f = fopen(cfg->path, "w"); - if (!f) return -1; + if (!f) { + std::fprintf(stderr, "config_save: failed to open '%s' for writing: %s (errno=%d)\n", + cfg->path, std::strerror(errno), errno); + return -1; + } fprintf(f, "# sKeets config\n"); for (int i = 0; i < cfg->count; i++) fprintf(f, "%s=%s\n", cfg->entries[i].key, cfg->entries[i].value); - fclose(f); + if (fclose(f) != 0) { + std::fprintf(stderr, "config_save: fclose failed for '%s': %s (errno=%d)\n", + cfg->path, std::strerror(errno), errno); + return -1; + } + std::fprintf(stderr, "config_save: successfully wrote %d entries to '%s'\n", cfg->count, cfg->path); return 0; } diff --git a/src/util/paths.cpp b/src/util/paths.cpp index f497366..c43ee59 100644 --- a/src/util/paths.cpp +++ b/src/util/paths.cpp @@ -1,5 +1,6 @@ #include "paths.h" +#include #include #include #include @@ -84,8 +85,20 @@ const char *skeets_cache_dir() { int skeets_ensure_data_dirs() { const path_state_t &state = paths(); - if (!mkdirs(state.data_dir)) return -1; - if (!mkdirs(state.cache_dir)) return -1; + fprintf(stderr, "paths: ensuring data directories exist\n"); + fprintf(stderr, "paths: data_dir='%s'\n", state.data_dir.c_str()); + fprintf(stderr, "paths: config_path='%s'\n", state.config_path.c_str()); + fprintf(stderr, "paths: cache_dir='%s'\n", state.cache_dir.c_str()); + + if (!mkdirs(state.data_dir)) { + fprintf(stderr, "paths: FAILED to create data_dir='%s'\n", state.data_dir.c_str()); + return -1; + } + if (!mkdirs(state.cache_dir)) { + fprintf(stderr, "paths: FAILED to create cache_dir='%s'\n", state.cache_dir.c_str()); + return -1; + } + fprintf(stderr, "paths: data directories ready\n"); return 0; } From 931550ea8e5ba8c2bda60a36a1068d4052b1677f Mon Sep 17 00:00:00 2001 From: Marius Scheffel Date: Sat, 14 Mar 2026 11:34:52 +0100 Subject: [PATCH 3/5] Add fsync and extensive bootstrap logging for config durability The config.ini was not persisting across reboots because: 1. fclose() doesn't guarantee data reaches persistent storage on embedded devices 2. Forced reboot (run.sh reboots after app exit) could truncate unwritten data Changes: - config_save: Add fflush() + fsync() before fclose() to ensure durability - config_save: Log all error cases including fsync failures - load_saved_session: Add logging for config path and loaded session details - skeets_run_bootstrap: Add step-by-step logging for session restore flow This will also help diagnose the issue where second run shows 'No fonts' and exits immediately without attempting session restore. --- src/kobo/bootstrap.cpp | 36 ++++++++++++++++++++++++++++++++++-- src/util/config.cpp | 18 ++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/kobo/bootstrap.cpp b/src/kobo/bootstrap.cpp index 047da51..419de8d 100644 --- a/src/kobo/bootstrap.cpp +++ b/src/kobo/bootstrap.cpp @@ -70,9 +70,15 @@ bool file_exists(const char* path) { } Bsky::Session load_saved_session() { + const char* config_path = skeets_config_path(); + std::fprintf(stderr, "bootstrap: load_saved_session: config_path='%s'\n", config_path); + Bsky::Session session; - config_t* config = config_open(skeets_config_path()); - if (!config) return session; + config_t* config = config_open(config_path); + if (!config) { + std::fprintf(stderr, "bootstrap: load_saved_session: config_open returned NULL\n"); + return session; + } session.handle = config_get_str(config, "handle", ""); session.access_jwt = config_get_str(config, "access_jwt", ""); @@ -82,6 +88,14 @@ Bsky::Session load_saved_session() { session.appview_url = config_get_str(config, "appview_url", config_get_str(config, "appview", kDefaultAppView)); + + std::fprintf(stderr, "bootstrap: load_saved_session: loaded handle='%s' did='%s' pds='%s' has_access=%zu has_refresh=%zu\n", + session.handle.empty() ? "(empty)" : session.handle.c_str(), + session.did.empty() ? "(empty)" : session.did.c_str(), + session.pds_url.empty() ? "(empty)" : session.pds_url.c_str(), + session.access_jwt.size(), + session.refresh_jwt.size()); + config_free(config); return session; } @@ -155,16 +169,22 @@ skeets_bootstrap_result_t make_error_result(const std::string& error_message, bo } // namespace skeets_bootstrap_result_t skeets_run_bootstrap() { + std::fprintf(stderr, "bootstrap: skeets_run_bootstrap: starting\n"); + if (skeets_ensure_data_dirs() != 0) { + std::fprintf(stderr, "bootstrap: skeets_run_bootstrap: failed to create data dirs\n"); return make_error_result("Failed to create rewrite data directories", false); } + std::fprintf(stderr, "bootstrap: skeets_run_bootstrap: loading saved session\n"); Bsky::Session saved_session = load_saved_session(); Bsky::Session restored_session; std::string saved_session_error; if (has_saved_session(saved_session)) { + std::fprintf(stderr, "bootstrap: skeets_run_bootstrap: found saved session, attempting restore\n"); if (restore_saved_session_with_retry(saved_session, restored_session, saved_session_error)) { + std::fprintf(stderr, "bootstrap: skeets_run_bootstrap: session restore succeeded\n"); save_session(restored_session); skeets_bootstrap_result_t result; result.state = skeets_bootstrap_state_t::session_restored; @@ -175,21 +195,31 @@ skeets_bootstrap_result_t skeets_run_bootstrap() { result.used_saved_session = true; return result; } + std::fprintf(stderr, "bootstrap: skeets_run_bootstrap: session restore failed: %s\n", saved_session_error.c_str()); + } else { + std::fprintf(stderr, "bootstrap: skeets_run_bootstrap: no saved session found\n"); } + std::fprintf(stderr, "bootstrap: skeets_run_bootstrap: checking for login.txt\n"); bool found_login_file = false; const skeets_login_txt_t login = read_login_txt(found_login_file); if (!found_login_file) { + std::fprintf(stderr, "bootstrap: skeets_run_bootstrap: no login.txt found\n"); if (has_saved_session(saved_session)) { + std::fprintf(stderr, "bootstrap: skeets_run_bootstrap: returning error (had session but restore failed)\n"); return make_error_result(saved_session_error.empty() ? "Saved session resume failed" : saved_session_error, false); } + std::fprintf(stderr, "bootstrap: skeets_run_bootstrap: returning waiting for login\n"); return make_waiting_result(); } + std::fprintf(stderr, "bootstrap: skeets_run_bootstrap: login.txt found, handle='%s'\n", login.handle.c_str()); if (login.handle.empty() || login.password.empty()) { + std::fprintf(stderr, "bootstrap: skeets_run_bootstrap: login.txt missing handle or password\n"); return make_error_result("login.txt is missing required handle or password fields", true); } + std::fprintf(stderr, "bootstrap: skeets_run_bootstrap: attempting createSession\n"); bool created = false; std::string error_message; Bsky::Session created_session; @@ -206,6 +236,7 @@ skeets_bootstrap_result_t skeets_run_bootstrap() { return make_error_result(error_message.empty() ? "Login failed" : error_message, true); } + std::fprintf(stderr, "bootstrap: skeets_run_bootstrap: createSession succeeded, saving session\n"); created_session.appview_url = login.appview_url.empty() ? kDefaultAppView : login.appview_url; save_session(created_session); @@ -216,6 +247,7 @@ skeets_bootstrap_result_t skeets_run_bootstrap() { result.detail = "Session tokens were saved under the rewrite app root."; result.authenticated = true; result.consumed_login_file = true; + std::fprintf(stderr, "bootstrap: skeets_run_bootstrap: returning success\n"); return result; } diff --git a/src/util/config.cpp b/src/util/config.cpp index b48e439..dfc2427 100644 --- a/src/util/config.cpp +++ b/src/util/config.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #define CONFIG_MAX_ENTRIES 128 @@ -71,6 +72,23 @@ int config_save(config_t *cfg) { fprintf(f, "# sKeets config\n"); for (int i = 0; i < cfg->count; i++) fprintf(f, "%s=%s\n", cfg->entries[i].key, cfg->entries[i].value); + + // Flush to kernel buffers + if (fflush(f) != 0) { + std::fprintf(stderr, "config_save: fflush failed for '%s': %s (errno=%d)\n", + cfg->path, std::strerror(errno), errno); + fclose(f); + return -1; + } + + // Sync to persistent storage (critical on embedded devices) + int fd = fileno(f); + if (fd >= 0 && fsync(fd) != 0) { + std::fprintf(stderr, "config_save: fsync failed for '%s': %s (errno=%d)\n", + cfg->path, std::strerror(errno), errno); + // Don't fail - data may still reach disk on fclose + } + if (fclose(f) != 0) { std::fprintf(stderr, "config_save: fclose failed for '%s': %s (errno=%d)\n", cfg->path, std::strerror(errno), errno); From 47493c4c2abc80716e4b3cb729c2a54ee35a1dc1 Mon Sep 17 00:00:00 2001 From: Marius Scheffel Date: Sat, 14 Mar 2026 11:35:05 +0100 Subject: [PATCH 4/5] Add logging to config_open to trace config file reading --- src/util/config.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/util/config.cpp b/src/util/config.cpp index dfc2427..5f3aea6 100644 --- a/src/util/config.cpp +++ b/src/util/config.cpp @@ -30,12 +30,19 @@ static config_entry_t *find_entry(config_t *cfg, const char *key) { config_t *config_open(const char *path) { config_t *cfg = (config_t *)calloc(1, sizeof(config_t)); - if (!cfg) return NULL; + if (!cfg) { + std::fprintf(stderr, "config_open: calloc failed for '%s'\n", path ? path : "(null)"); + return NULL; + } str_safe_copy(cfg->path, path, sizeof(cfg->path)); FILE *f = fopen(path, "r"); - if (!f) return cfg; /* new config, no file yet */ + if (!f) { + std::fprintf(stderr, "config_open: file '%s' not found (errno=%d), returning empty config\n", path, errno); + return cfg; /* new config, no file yet */ + } + std::fprintf(stderr, "config_open: reading '%s'\n", path); char line[CONFIG_MAX_KEY + CONFIG_MAX_VALUE + 4]; while (fgets(line, sizeof(line), f)) { str_trim(line); @@ -51,6 +58,7 @@ config_t *config_open(const char *path) { config_set_str(cfg, k, v); } fclose(f); + std::fprintf(stderr, "config_open: loaded %d entries from '%s'\n", cfg->count, path); return cfg; } From 522d6d5439de9c1d97c86de7b06317ce310c083b Mon Sep 17 00:00:00 2001 From: Marius Scheffel Date: Sat, 14 Mar 2026 11:46:46 +0100 Subject: [PATCH 5/5] Guard against empty session overwriting valid config.ini MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause found: config.ini was being overwritten with empty session values, causing the app to lose authentication on restart. The log reveals the sequence: 1. Login succeeds, config.ini saved with valid session (6 entries) 2. clear_saved_session() runs (user action or accidental touch), overwrites all 6 session keys with empty strings 3. On next boot, config.ini loads 6 entries — all empty 4. No saved session found, no login.txt → app shows 'waiting for login' Fixes: - persist_session(): Skip saving if the session is entirely empty to prevent overwriting a valid config with blank values - save_session() in bootstrap: Same empty-session guard - clear_saved_session(): Add logging so the call is visible in logs - persist_session(): Log session details (handle, did, token sizes) to make it obvious when an empty session would have been written --- src/kobo/bootstrap.cpp | 10 +++++++++- src/kobo/main.cpp | 11 ++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/kobo/bootstrap.cpp b/src/kobo/bootstrap.cpp index 419de8d..bd32820 100644 --- a/src/kobo/bootstrap.cpp +++ b/src/kobo/bootstrap.cpp @@ -106,7 +106,15 @@ bool has_saved_session(const Bsky::Session& session) { void save_session(const Bsky::Session& session) { const char* config_path = skeets_config_path(); - std::fprintf(stderr, "bootstrap: save_session: config_path='%s'\n", config_path); + + if (session.handle.empty() && session.access_jwt.empty() && session.did.empty()) { + std::fprintf(stderr, "bootstrap: save_session: SKIPPING — session is empty (would overwrite valid config)\n"); + return; + } + + std::fprintf(stderr, "bootstrap: save_session: saving handle='%s' did='%s' pds='%s' access_len=%zu refresh_len=%zu\n", + session.handle.c_str(), session.did.c_str(), session.pds_url.c_str(), + session.access_jwt.size(), session.refresh_jwt.size()); config_t* config = config_open(config_path); if (!config) { diff --git a/src/kobo/main.cpp b/src/kobo/main.cpp index 18b1aa6..d4019f2 100644 --- a/src/kobo/main.cpp +++ b/src/kobo/main.cpp @@ -1540,6 +1540,7 @@ void save_settings(const skeets_app_t& app) { } void clear_saved_session(skeets_app_t& app) { + std::fprintf(stderr, "clear_saved_session: CLEARING session from config\n"); config_t* config = config_open(skeets_config_path()); if (config) { config_set_str(config, "handle", ""); @@ -1570,7 +1571,15 @@ void clear_saved_session(skeets_app_t& app) { void persist_session(skeets_app_t& app, const Bsky::Session& session) { const char* config_path = skeets_config_path(); - std::fprintf(stderr, "persist_session: config_path='%s'\n", config_path); + + if (session.handle.empty() && session.access_jwt.empty() && session.did.empty()) { + std::fprintf(stderr, "persist_session: SKIPPING save — session is empty (would overwrite valid config)\n"); + return; + } + + std::fprintf(stderr, "persist_session: saving handle='%s' did='%s' pds='%s' access_len=%zu refresh_len=%zu\n", + session.handle.c_str(), session.did.c_str(), session.pds_url.c_str(), + session.access_jwt.size(), session.refresh_jwt.size()); config_t* config = config_open(config_path); if (!config) {