Skip to content

Add HUD notification system for phone calls, SMS, and image display#7

Merged
KerseyFabrications merged 2 commits into
mainfrom
UNIFIED_IMAGE_STORE_DESIGN
Apr 17, 2026
Merged

Add HUD notification system for phone calls, SMS, and image display#7
KerseyFabrications merged 2 commits into
mainfrom
UNIFIED_IMAGE_STORE_DESIGN

Conversation

@KerseyFabrications

Copy link
Copy Markdown
Contributor

Config-driven notification popups with fade in/out, auto-dismiss, and contact photo display from base64 MQTT payloads.

  • Notification state machine (HIDDEN/SHOWING/VISIBLE/COMPACT/HIDING) with two independent slots (phone + image)
  • 6 dynamic text sources: CALLER_NAME, CALLER_NUMBER, CALL_STATUS, SMS_PREVIEW, NOTIFICATION_TITLE, NOTIFICATION_SOURCE
  • notification_photo special element with lazy-loaded placeholder
  • notification_group integer enum resolved at parse time (O(1) render)
  • Mutex-protected phone state (MQTT writer, render thread reader)
  • Base64 photo decode via OpenSSL BIO, texture on render thread
  • Ring event resets incoming_call TTL (keeps notification alive)
  • 9 config.json elements (phone bg, photo, 4 labels, image bg, 2 labels)
  • Notification panel PNG assets (angular beveled, Iron Man aesthetic)
  • Added new original SVG directory.

Config-driven notification popups with fade in/out, auto-dismiss, and
contact photo display from base64 MQTT payloads.

- Notification state machine (HIDDEN/SHOWING/VISIBLE/COMPACT/HIDING)
  with two independent slots (phone + image)
- 6 dynamic text sources: CALLER_NAME, CALLER_NUMBER, CALL_STATUS,
  SMS_PREVIEW, NOTIFICATION_TITLE, NOTIFICATION_SOURCE
- notification_photo special element with lazy-loaded placeholder
- notification_group integer enum resolved at parse time (O(1) render)
- Mutex-protected phone state (MQTT writer, render thread reader)
- Base64 photo decode via OpenSSL BIO, texture on render thread
- Ring event resets incoming_call TTL (keeps notification alive)
- 9 config.json elements (phone bg, photo, 4 labels, image bg, 2 labels)
- Notification panel PNG assets (angular beveled, Iron Man aesthetic)
- Added new original SVG directory.
@qodo-code-review

Copy link
Copy Markdown

Review Summary by Qodo

Add HUD notification system for phone calls, SMS, and image display

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Notification system for phone calls, SMS, and image display with state machine
• Config-driven elements linked to notification groups via notification_group field
• Base64 photo decoding from MQTT payloads with lazy-loaded placeholder textures
• Mutex-protected phone state for thread-safe MQTT writer and render thread reader
• Six dynamic text sources for caller info, call status, SMS preview, and image metadata
• Auto-dismiss with TTL, fade in/out animations, and compact mode for active calls
Diagram
flowchart LR
  MQTT["MQTT Events<br/>phone/image"] -->|notification_handle_phone_event| PhoneSlot["Phone Slot<br/>State Machine"]
  MQTT -->|notification_handle_image_request| ImageSlot["Image Slot<br/>State Machine"]
  PhoneSlot -->|notification_update| StateTransition["State Transitions<br/>SHOWING→VISIBLE→COMPACT→HIDING"]
  ImageSlot -->|notification_update| StateTransition
  StateTransition -->|notification_get_alpha| Renderer["Element Renderer<br/>apply_notification_alpha"]
  Renderer -->|notification_get_text| TextElements["Text Elements<br/>CALLER_NAME, CALL_STATUS, etc."]
  Renderer -->|notification_get_photo_texture| PhotoElement["Photo Element<br/>notification_photo special type"]
  ConfigParser["Config Parser<br/>notification_group field"] -->|notif_group_resolve| ElementLink["Element→Notification Group<br/>O(1) dispatch"]
  ElementLink -->|notification_is_active| RenderSkip["Skip rendering<br/>when group inactive"]
Loading

Grey Divider

File Changes

1. include/config/config_parser.h ✨ Enhancement +10/-0

Add notification text source enum values

include/config/config_parser.h


2. include/ui/notification.h ✨ Enhancement +216/-0

New notification system header with state machine

include/ui/notification.h


3. src/comm/command_processing.c ✨ Enhancement +11/-0

Route phone and image MQTT events to notification handlers

src/comm/command_processing.c


View more (6)
4. src/config/config_parser.c ✨ Enhancement +16/-0

Parse notification_group field and resolve text sources

src/config/config_parser.c


5. src/core/mirage.c ✨ Enhancement +5/-0

Initialize and shutdown notification system

src/core/mirage.c


6. src/rendering/element_renderer.c ✨ Enhancement +74/-0

Apply notification alpha to elements and render photo

src/rendering/element_renderer.c


7. src/ui/notification.c ✨ Enhancement +568/-0

Notification state machine and MQTT event handling

src/ui/notification.c


8. CMakeLists.txt ⚙️ Configuration changes +1/-0

Add notification.c to build sources

CMakeLists.txt


9. config.json ⚙️ Configuration changes +105/-0

Add phone and image notification UI elements

config.json


Grey Divider

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented Apr 17, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (2) 📎 Requirement gaps (0)

Grey Divider


Action required

1. if (b64) missing braces📘 Rule violation ✧ Quality
Description
New code uses single-statement if bodies without braces, which violates the required always-braced
control-structure style. This increases the risk of future edits introducing subtle logic bugs.
Code

src/ui/notification.c[R89-92]

+      if (b64)
+         BIO_free(b64);
+      if (mem)
+         BIO_free(mem);
Evidence
The checklist requires braces for all control-structure bodies, even when the body is a single
statement. In base64_decode, the if (b64) and if (mem) statements have unbraced
single-statement bodies.

Rule 278876: Always use braces for single-statement control structure bodies
src/ui/notification.c[89-92]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new code uses single-statement `if` bodies without braces (e.g., `if (b64) BIO_free(b64);`). The project rule requires braces for all control-structure bodies.
## Issue Context
This occurs in the error-handling path of base64 decoding.
## Fix Focus Areas
- src/ui/notification.c[89-92]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. s_image accessed without mutex📘 Rule violation ≡ Correctness
Description
The MQTT-thread handler writes s_image fields without using s_image.image_mutex, while the
render thread reads/updates the same shared state elsewhere. This creates a cross-thread data race
and can lead to inconsistent notification state or crashes.
Code

src/ui/notification.c[R328-362]

+void notification_handle_image_request(struct json_object *root) {
+   if (!root) {
+      return;
+   }
+
+   struct json_object *j_tmp;
+
+   const char *title = "";
+   if (json_object_object_get_ex(root, "title", &j_tmp)) {
+      title = json_object_get_string(j_tmp);
+   }
+   snprintf(s_image.title, sizeof(s_image.title), "%s", title ? title : "");
+
+   const char *source = "";
+   if (json_object_object_get_ex(root, "source", &j_tmp)) {
+      source = json_object_get_string(j_tmp);
+   }
+   snprintf(s_image.source, sizeof(s_image.source), "%s", source ? source : "");
+
+   const char *url = "";
+   if (json_object_object_get_ex(root, "image_url", &j_tmp)) {
+      url = json_object_get_string(j_tmp);
+   }
+   snprintf(s_image.image_url, sizeof(s_image.image_url), "%s", url ? url : "");
+
+   s_image.start_time_ms = now_ms();
+   s_image.ttl_ms = NOTIF_IMAGE_TTL_MS;
+
+   struct json_object *j_ttl;
+   if (json_object_object_get_ex(root, "ttl", &j_ttl)) {
+      s_image.ttl_ms = (uint32_t)(json_object_get_int(j_ttl) * 1000);
+   }
+
+   image_set_state(NOTIF_STATE_SHOWING, NOTIF_FADE_IN_IMAGE_MS);
+
Evidence
The checklist requires shared cross-thread flags/counters to be atomic (or otherwise synchronized).
notification_handle_image_request() is documented as MQTT-thread code but updates shared s_image
state without locking or atomics, while render-thread code uses that state (e.g., via
notification_get_text() and notification_update()).

Rule 290958: Declare shared cross-thread flags as _Atomic types
src/ui/notification.c[328-362]
src/ui/notification.c[521-526]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`notification_handle_image_request()` (MQTT callback thread) writes shared `s_image` fields without acquiring `s_image.image_mutex` and without using atomics, while render-thread paths read/act on this state.
## Issue Context
`notif_image_t` already includes `pthread_mutex_t image_mutex`, suggesting the intended synchronization mechanism.
## Fix Focus Areas
- src/ui/notification.c[328-362]
- src/ui/notification.c[435-468]
- src/ui/notification.c[474-492]
- src/ui/notification.c[521-526]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. mirage.c over 1500 lines 📘 Rule violation ✧ Quality
Description
A .c file modified by this PR is already well over 1,500 lines (line numbers in the diff exceed
2,400). This violates the C file size limit requirement.
Code

src/core/mirage.c[R1797-1799]

+   /* Initialize notification system (phone/SMS/image HUD popups) */
+   notification_init();
+
Evidence
The checklist mandates that no changed .c file exceeds 1,500 lines. The diff shows edits occurring
at line ~1797 and again at ~2461 in src/core/mirage.c, proving the file exceeds the limit.

Rule 278884: Limit C source file length to 1,500 lines
src/core/mirage.c[1797-1799]
src/core/mirage.c[2461-2461]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`src/core/mirage.c` exceeds the 1,500-line limit but is modified in this PR.
## Issue Context
The diff shows changes at ~1797 and ~2461, indicating the file is already >1,500 lines.
## Fix Focus Areas
- src/core/mirage.c[1797-1799]
- src/core/mirage.c[2461-2461]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (3)
4. element_renderer.c over 1500 📘 Rule violation ✧ Quality
Description
A .c file modified by this PR is already well over 1,500 lines (diff line numbers exceed 2,100).
This violates the C file size limit requirement.
Code

src/rendering/element_renderer.c[R2119-2121]

+   /* Update notification timers and state transitions */
+   notification_update();
+
Evidence
The checklist mandates that no changed .c file exceeds 1,500 lines. The diff shows modifications
around line ~2119 in src/rendering/element_renderer.c, proving the file exceeds the limit.

Rule 278884: Limit C source file length to 1,500 lines
src/rendering/element_renderer.c[2119-2121]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`src/rendering/element_renderer.c` exceeds the 1,500-line limit but is modified in this PR.
## Issue Context
The diff shows changes around line ~2119, indicating the file is already >1,500 lines.
## Fix Focus Areas
- src/rendering/element_renderer.c[2119-2121]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Wrong device routing variable🐞 Bug ≡ Correctness
Description
parse_json_command() compares tmpstr to "phone"/"image" after tmpstr has already been reused
for non-device fields (e.g., "format"), so notifications may be skipped or accidentally triggered
based on unrelated JSON values. This makes notification handling unreliable and topic-payload
dependent in unintended ways.
Code

src/comm/command_processing.c[R913-921]

+   /* Phone notification events (from DAWN phone_service via HUD topic) */
+   if (tmpstr != NULL && strcmp(tmpstr, "phone") == 0) {
+      notification_handle_phone_event(parsed_json);
+   }
+
+   /* Image display requests (from DAWN image_search_tool via HUD topic) */
+   if (tmpstr != NULL && strcmp(tmpstr, "image") == 0) {
+      notification_handle_image_request(parsed_json);
+   }
Evidence
The function first stores the JSON "device" string in tmpstr, but later overwrites tmpstr while
parsing other keys (example: "format" for Motion). The newly-added phone/image checks at the end of
the function use tmpstr again, but it is no longer guaranteed to contain the original device
value.

src/comm/command_processing.c[456-474]
src/comm/command_processing.c[913-921]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`tmpstr` is used for multiple meanings inside `parse_json_command()` (device, format, other strings). The phone/image handler dispatch uses `tmpstr` at the end, but `tmpstr` may not still be the device value.
### Issue Context
Notifications should dispatch based on the **original** `device` field, not whatever `tmpstr` was last assigned to during parsing.
### Fix Focus Areas
- src/comm/command_processing.c[456-474]
- src/comm/command_processing.c[913-921]
### Implementation guidance
- Introduce a dedicated variable, e.g. `const char *device_str = NULL;`, assign it once from the `device` key, and never reuse it.
- Run the phone/image dispatch checks against `device_str` (or dispatch immediately after parsing `device` and return early).
- Avoid reusing `tmpstr` for other fields once `device_str` is captured.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Unbounded base64 decode🐞 Bug ⛨ Security
Description
base64_decode() allocates based on strlen(input) with no size cap and casts size_t lengths to
int for OpenSSL/SDL APIs without bounds checks, enabling memory exhaustion or truncation from a
large MQTT payload. It also performs a single BIO_read(), which can return partial data, causing
sporadic image decode failures.
Code

src/ui/notification.c[R68-109]

+static unsigned char *base64_decode(const char *input, size_t *out_size) {
+   if (!input || !out_size) {
+      return NULL;
+   }
+
+   size_t input_len = strlen(input);
+   if (input_len == 0) {
+      return NULL;
+   }
+
+   /* Output is at most 3/4 of input length */
+   size_t max_out = (input_len * 3) / 4 + 4;
+   unsigned char *output = malloc(max_out);
+   if (!output) {
+      return NULL;
+   }
+
+   BIO *b64 = BIO_new(BIO_f_base64());
+   BIO *mem = BIO_new_mem_buf(input, (int)input_len);
+   if (!b64 || !mem) {
+      free(output);
+      if (b64)
+         BIO_free(b64);
+      if (mem)
+         BIO_free(mem);
+      return NULL;
+   }
+
+   BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
+   BIO_push(b64, mem);
+
+   int decoded_len = BIO_read(b64, output, (int)max_out);
+   BIO_free_all(b64);
+
+   if (decoded_len <= 0) {
+      free(output);
+      return NULL;
+   }
+
+   *out_size = (size_t)decoded_len;
+   return output;
+}
Evidence
The photo data comes from JSON (photo.data) handled on the MQTT thread and is passed directly to
base64_decode(). The decoder computes max_out from the input length and allocates it, then
passes (int)input_len and (int)max_out into OpenSSL; later, the decoded size is also cast to
int for SDL_RWFromMem(). None of these conversions are guarded, and the single BIO_read() can
truncate large decodes.

src/ui/notification.c[68-108]
src/ui/notification.c[226-241]
src/ui/notification.c[546-554]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Base64 decode is unbounded and uses unsafe size casts, allowing OOM/DoS and truncation. Decode may also be incomplete due to a single `BIO_read()`.
### Issue Context
The base64 input is sourced from MQTT JSON payloads (`photo.data`).
### Fix Focus Areas
- src/ui/notification.c[68-108]
- src/ui/notification.c[226-241]
- src/ui/notification.c[546-554]
### Implementation guidance
- Enforce a maximum accepted base64 input length and/or maximum decoded byte size (e.g., 256KB or whatever is appropriate for contact photos).
- Validate `input_len <= INT_MAX` and `max_out <= INT_MAX` before casting; reject otherwise.
- Read in a loop until EOF (accumulate decoded bytes) or switch to a decoder API that provides deterministic output sizing (ensuring full decode).
- When creating RWops, validate `photo_data_size <= INT_MAX` before calling `SDL_RWFromMem()`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

7. Negative TTL wraps 🐞 Bug ≡ Correctness
Description
TTL overrides from JSON are multiplied by 1000 and cast to uint32_t without validation, so a
negative ttl becomes a huge positive TTL. This can keep notifications visible for extremely long
periods instead of expiring normally.
Code

src/ui/notification.c[R317-320]

+      struct json_object *j_ttl;
+      if (json_object_object_get_ex(root, "ttl", &j_ttl)) {
+         s_phone.ttl_ms = (uint32_t)(json_object_get_int(j_ttl) * 1000);
+      }
Evidence
Both the phone (SMS) and image handlers accept a JSON ttl and assign ttl_ms via
(uint32_t)(json_object_get_int(...) * 1000). If the JSON int is negative, the signed-to-unsigned
conversion wraps to a very large TTL in milliseconds.

src/ui/notification.c[301-323]
src/ui/notification.c[356-362]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Negative or excessively large TTL values can wrap or create unintended behavior because they’re cast directly to `uint32_t` after multiplication.
### Issue Context
TTL is user-controlled via MQTT JSON.
### Fix Focus Areas
- src/ui/notification.c[317-320]
- src/ui/notification.c[356-359]
### Implementation guidance
- Read TTL into a signed temporary (e.g., `int ttl_sec = json_object_get_int(...)`).
- If `ttl_sec < 0`, either ignore it (keep default) or clamp to 0.
- Clamp to a reasonable maximum (e.g., 0..300 seconds) before converting to milliseconds.
- Consider guarding the multiplication against overflow (use `int64_t` for the intermediate).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment thread src/ui/notification.c Outdated
Comment thread src/ui/notification.c
Comment thread src/core/mirage.c
Comment thread src/rendering/element_renderer.c
Comment thread src/comm/command_processing.c
Comment thread src/ui/notification.c
- Save device_str before tmpstr reuse to prevent wrong notification
  routing when device field is overwritten by format/other parsing
- Add image_mutex protection to s_image state machine update and
  text source reads (matches s_phone mutex pattern)
- Cap base64 decode at 512KB input to prevent OOM from rogue payloads
- Add braces to single-statement if bodies in BIO error path

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@KerseyFabrications KerseyFabrications merged commit 49c2bb9 into main Apr 17, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant