/* * main.cpp * MontaukOS Weather app - standalone Window Server process * Fetches current weather from wttr.in via HTTPS (BearSSL) * Displays temperature, description, feels like, and location * Copyright (c) 2026 Daniel Hammer */ #include #include #include #include #include #include #include #include #include extern "C" { #include #include #include } using namespace gui; using namespace gui::colors; // ============================================================================ // Constants // ============================================================================ static constexpr int INIT_W = 380; static constexpr int INIT_H = 280; static constexpr int HEADER_H = 160; static constexpr int FOOTER_H = 50; static constexpr int ICON_SIZE = 80; static constexpr int ICON_X = 28; static constexpr int ICON_Y = 40; static constexpr int INFO_X = ICON_X + ICON_SIZE + 20; // 128 static constexpr int TEMP_Y = 40; static int TEMP_SIZE = 40; static constexpr int DESC_Y = 92; static int DESC_SIZE = 17; static constexpr int FEELS_Y = 116; static int LABEL_SIZE = 15; static constexpr int RESP_MAX = 65536; static const char WTTR_HOST[] = "wttr.in"; // ============================================================================ // App state // ============================================================================ enum class AppPhase { IDLE, LOADING, DONE, ERR }; static AppPhase g_phase = AppPhase::IDLE; static char g_temp[32] = {}; static char g_desc[128] = {}; static char g_feels[48] = {}; static char g_location[128] = {}; static char g_status[256] = {}; static int g_win_w = INIT_W; static int g_win_h = INIT_H; static char* g_resp_buf = nullptr; // Fonts static TrueTypeFont* g_font = nullptr; // Roboto Medium static TrueTypeFont* g_font_bold = nullptr; // Roboto Bold // Weather icon (loaded per-fetch based on weather code) static SvgIcon g_icon = {nullptr, 0, 0}; static char g_icon_name[64] = {}; // TLS state (lazy-init on first fetch) static bool g_tls_ready = false; static uint32_t g_server_ip = 0; static tls::TrustAnchors g_tas = {nullptr, 0, 0}; // ============================================================================ // HTTP parsing // ============================================================================ static int find_header_end(const char* buf, int len) { for (int i = 0; i + 3 < len; i++) if (buf[i]=='\r' && buf[i+1]=='\n' && buf[i+2]=='\r' && buf[i+3]=='\n') return i + 4; return -1; } static int parse_status_code(const char* buf, int len) { int i = 0; while (i < len && buf[i] != ' ') i++; if (i >= len || i + 3 >= len) return -1; i++; if (buf[i] < '0' || buf[i] > '9') return -1; return (buf[i]-'0')*100 + (buf[i+1]-'0')*10 + (buf[i+2]-'0'); } // ============================================================================ // JSON parsing // ============================================================================ // Extract value of a simple JSON string field: "key":"value" static int extract_json_string(const char* buf, int len, const char* key, char* out, int maxOut) { int klen = (int)strlen(key); for (int i = 0; i < len - klen - 3; i++) { if (buf[i] != '"') continue; if (memcmp(buf + i + 1, key, klen) != 0) continue; if (buf[i + 1 + klen] != '"') continue; if (buf[i + 2 + klen] != ':') continue; int p = i + 3 + klen; while (p < len && (buf[p]==' ' || buf[p]=='\t')) p++; if (p >= len || buf[p] != '"') continue; p++; int j = 0; while (p < len && j < maxOut - 1 && buf[p] != '"') { if (buf[p] == '\\' && p + 1 < len) { p++; out[j++] = buf[p]; } else out[j++] = buf[p]; p++; } out[j] = '\0'; return j; } out[0] = '\0'; return 0; } // Find the first occurrence of needle in buf[0..len-1]. Returns offset or -1. static int find_substr(const char* buf, int len, const char* needle) { int nlen = (int)strlen(needle); if (nlen > len) return -1; for (int i = 0; i <= len - nlen; i++) { if (memcmp(buf + i, needle, nlen) == 0) return i; } return -1; } // Extract the "value" string within a named JSON array-of-objects field. // e.g., "weatherDesc":[{"value":"Partly cloudy"}] → "Partly cloudy" // Works by finding section_key first, then the first "value" after it. static int extract_array_value(const char* buf, int len, const char* section_key, char* out, int maxOut) { int pos = find_substr(buf, len, section_key); if (pos < 0) { out[0] = '\0'; return 0; } return extract_json_string(buf + pos, len - pos, "value", out, maxOut); } // Parse a signed integer from a decimal string. static int parse_int(const char* s) { int sign = 1, val = 0, i = 0; if (s[0] == '-') { sign = -1; i = 1; } while (s[i] >= '0' && s[i] <= '9') val = val * 10 + (s[i++] - '0'); return sign * val; } // ============================================================================ // Weather code → icon filename // Maps wttr.in WMO-style weather codes to Flat-Remix panel icon names. // ============================================================================ static const char* weather_code_to_icon(int code) { switch (code) { case 113: return "weather-clear.svg"; case 116: return "weather-few-clouds.svg"; case 119: return "weather-clouds.svg"; case 122: return "weather-overcast.svg"; case 143: return "weather-mist.svg"; case 248: case 260: return "weather-fog.svg"; case 176: case 263: case 266: case 353: return "weather-showers-scattered.svg"; case 293: case 296: case 299: case 302: case 305: case 308: case 356: case 359: return "weather-showers.svg"; case 179: case 362: case 365: case 368: return "weather-snow-scattered.svg"; case 323: case 326: case 329: case 332: case 335: case 338: case 371: case 374: return "weather-snow.svg"; case 227: case 230: return "weather-snow.svg"; case 182: case 311: case 314: case 317: case 320: return "weather-snow-rain.svg"; case 185: case 281: case 284: return "weather-freezing-rain.svg"; case 350: case 377: return "weather-hail.svg"; case 200: case 386: case 389: case 392: case 395: return "weather-storm.svg"; default: return "weather-none-available.svg"; } } // Load the weather icon for the given icon filename (caches the last result). static void load_weather_icon(const char* icon_name) { if (strcmp(g_icon_name, icon_name) == 0 && g_icon.pixels) return; if (g_icon.pixels) svg_free(g_icon); static char path[80]; snprintf(path, sizeof(path), "0:/icons/%s", icon_name); // ColorScheme-Text paths (main shape) use this fill_color; accent classes // (ColorScheme-Highlight=blue, ColorScheme-NeutralText=yellow, etc.) use their // CSS-defined colors as parsed from the SVG's