Add no-signal animation to display windows

When a viewer window has no incoming stream, renders animated analog-TV
noise (hash-based, scanlines, phosphor tint) at configurable fps (default
15) with a centred "NO SIGNAL" text overlay.

- xorg: FRAG_NOSIGNAL_SRC shader + xorg_viewer_render_no_signal(v, time, noise_res)
- main: Display_Slot gains no_signal_fps + last_no_signal_t; display_loop_tick
  drives no-signal render on idle slots via clock_gettime rate limiting
- protocol: START_DISPLAY extended by 2 bytes — no_signal_fps (0=default 15)
  + reserved; reader is backward-compatible (defaults 0 if length < 18)
- controller_cli: no_signal_fps optional arg on start-display
- docs: protocol.md updated with new field

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-29 19:20:53 +00:00
parent 7808d832be
commit 54d48c9c8e
7 changed files with 158 additions and 31 deletions

View File

@@ -7,6 +7,7 @@
#include <pthread.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <time.h>
#include <sys/sysmacros.h>
#include <linux/videodev2.h>
@@ -183,6 +184,10 @@ struct Display_Slot {
uint32_t frame_len;
int frame_ready;
/* No-signal animation */
int no_signal_fps; /* 0 → default 15 */
double last_no_signal_t;
/* Viewer — created and used only on the main thread */
Xorg_Viewer *viewer;
};
@@ -521,6 +526,16 @@ static void display_loop_tick(struct Node *node)
xorg_viewer_push_mjpeg(d->viewer, vf.data, vf.data_len);
}
free(fdata);
} else {
/* No live frame — render no-signal animation at configured fps */
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
double now = (double)ts.tv_sec + (double)ts.tv_nsec * 1e-9;
double interval = 1.0 / (double)(d->no_signal_fps > 0 ? d->no_signal_fps : 15);
if (now - d->last_no_signal_t >= interval) {
xorg_viewer_render_no_signal(d->viewer, (float)now, 80.0f);
d->last_no_signal_t = now;
}
}
/* Poll GLFW events; if user closes the window, treat as STOP_DISPLAY */
@@ -1083,9 +1098,10 @@ static void handle_start_display(struct Node *node,
d->win_y = (int)req.win_y;
d->win_w = req.win_w > 0 ? (int)req.win_w : 1280;
d->win_h = req.win_h > 0 ? (int)req.win_h : 720;
d->scale = proto_scale_to_xorg(req.scale);
d->anchor = proto_anchor_to_xorg(req.anchor);
d->wanted_state = DISP_OPEN; /* reconciled by display_loop_tick */
d->scale = proto_scale_to_xorg(req.scale);
d->anchor = proto_anchor_to_xorg(req.anchor);
d->no_signal_fps = req.no_signal_fps > 0 ? (int)req.no_signal_fps : 15;
d->wanted_state = DISP_OPEN; /* reconciled by display_loop_tick */
pthread_mutex_unlock(&d->mutex);
proto_write_control_response(conn, req.request_id, PROTO_STATUS_OK, NULL, 0);