Display device controls; device IDs in enum-devices; fix non-OK parse

Display controls (enum/get/set):
- Add PROTO_DISPLAY_CTRL_SCALE/ANCHOR/NO_SIGNAL_FPS constants to protocol.h
- handle_enum_controls: if device index maps to an active display slot,
  return the three display controls (scale, anchor, no_signal_fps)
- handle_get_control: read display control values from slot under mutex
- handle_set_control: write display control values to slot under mutex;
  scale/anchor are applied to the viewer by display_loop_tick each tick

Device IDs in enum-devices output:
- Proto_Display_Device_Info gains device_id field (wire format +2 bytes)
- handle_enum_devices computes device_id = total_v4l2 + display_index
- on_video_node/on_standalone callbacks take int* userdata to print [idx]
- on_display prints [device_id] from the wire field

Bug fix — protocol error on invalid device index:
- proto_read_enum_controls_response: early-return APP_OK after reading
  status if status != OK; error responses have no count/data fields, so
  the CUR_CHECK on count was failing with "payload too short"

Helpers added to main.c:
- count_v4l2_devices(): sum of media vnodes + standalone
- find_display_by_device_idx(): maps flat index to Display_Slot

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-29 22:02:42 +00:00
parent 1066f793e2
commit 8c4cd69443
4 changed files with 155 additions and 11 deletions

View File

@@ -510,6 +510,14 @@ static void display_loop_tick(struct Node *node)
if (d->current_state != DISP_OPEN || !d->viewer) { continue; }
/* Sync scale/anchor — may be updated live via SET_CONTROL */
pthread_mutex_lock(&d->mutex);
Xorg_Scale cur_scale = d->scale;
Xorg_Anchor cur_anchor = d->anchor;
pthread_mutex_unlock(&d->mutex);
xorg_viewer_set_scale(d->viewer, cur_scale);
xorg_viewer_set_anchor(d->viewer, cur_anchor);
/* Deliver pending frame (no lock held during decode/upload) */
pthread_mutex_lock(&d->mutex);
uint8_t *fdata = NULL;
@@ -787,6 +795,43 @@ static const char *resolve_device_path(struct Node *node, int idx)
return NULL;
}
/* Count all V4L2 device indices (media vnodes + standalone). */
static int count_v4l2_devices(struct Node *node)
{
int n = 0;
for (int i = 0; i < node->devices.media_count; i++) {
n += node->devices.media[i].vnode_count;
}
for (int i = 0; i < node->devices.vnode_count; i++) {
if (!node->devices.vnodes[i].claimed) { n++; }
}
return n;
}
/*
* If idx falls in the display device range (>= count_v4l2_devices), return
* the corresponding active Display_Slot, or NULL if out of range.
* The slot may be read without holding its mutex for the non-mutex fields;
* callers should lock the mutex for fields that require it.
*/
static struct Display_Slot *find_display_by_device_idx(struct Node *node, int idx)
{
int base = count_v4l2_devices(node);
if (idx < base) { return NULL; }
int disp_idx = idx - base;
int found = 0;
for (int i = 0; i < MAX_DISPLAYS; i++) {
struct Display_Slot *d = &node->displays[i];
pthread_mutex_lock(&d->mutex);
int active = d->allocated && d->wanted_state == DISP_OPEN;
pthread_mutex_unlock(&d->mutex);
if (!active) { continue; }
if (found == disp_idx) { return d; }
found++;
}
return NULL;
}
/* -------------------------------------------------------------------------
* Request handlers
* ------------------------------------------------------------------------- */
@@ -829,6 +874,8 @@ static void handle_enum_devices(struct Node *node,
standalone_count++;
}
int total_v4l2 = vnode_offset + standalone_count;
struct Proto_Display_Device_Info disp_infos[MAX_DISPLAYS];
int disp_count = 0;
for (int i = 0; i < MAX_DISPLAYS; i++) {
@@ -836,6 +883,7 @@ static void handle_enum_devices(struct Node *node,
pthread_mutex_lock(&d->mutex);
int snap = d->allocated && d->wanted_state == DISP_OPEN;
struct Proto_Display_Device_Info info = {
.device_id = (uint16_t)(total_v4l2 + disp_count),
.stream_id = d->stream_id,
.win_x = (int16_t)d->win_x,
.win_y = (int16_t)d->win_y,
@@ -869,7 +917,33 @@ static void handle_enum_controls(struct Node *node,
}
const char *path = resolve_device_path(node, (int)req.device_index);
if (!path) {
proto_write_control_response(conn, req.request_id, PROTO_STATUS_NOT_FOUND, NULL, 0);
struct Display_Slot *disp = find_display_by_device_idx(node, (int)req.device_index);
if (!disp) {
proto_write_control_response(conn, req.request_id, PROTO_STATUS_NOT_FOUND, NULL, 0);
return;
}
pthread_mutex_lock(&disp->mutex);
int scale = (int)disp->scale;
int anchor = (int)disp->anchor;
int no_signal_fps = disp->no_signal_fps > 0 ? disp->no_signal_fps : 15;
pthread_mutex_unlock(&disp->mutex);
struct Proto_Control_Info ctrls[] = {
{ .id = PROTO_DISPLAY_CTRL_SCALE,
.type = 1, .name = "Scale",
.min = 0, .max = 3, .step = 1, .default_val = 1,
.current_val = scale },
{ .id = PROTO_DISPLAY_CTRL_ANCHOR,
.type = 1, .name = "Anchor",
.min = 0, .max = 1, .step = 1, .default_val = 0,
.current_val = anchor },
{ .id = PROTO_DISPLAY_CTRL_NO_SIGNAL_FPS,
.type = 1, .name = "No-signal FPS",
.min = 1, .max = 60, .step = 1, .default_val = 15,
.current_val = no_signal_fps },
};
e = proto_write_enum_controls_response(conn,
req.request_id, PROTO_STATUS_OK, ctrls, 3);
if (!APP_IS_OK(e)) { app_error_print(&e); }
return;
}
struct V4l2_Ctrl_Handle *handle;
@@ -898,7 +972,27 @@ static void handle_get_control(struct Node *node,
}
const char *path = resolve_device_path(node, (int)req.device_index);
if (!path) {
proto_write_control_response(conn, req.request_id, PROTO_STATUS_NOT_FOUND, NULL, 0);
struct Display_Slot *disp = find_display_by_device_idx(node, (int)req.device_index);
if (!disp) {
proto_write_control_response(conn, req.request_id, PROTO_STATUS_NOT_FOUND, NULL, 0);
return;
}
pthread_mutex_lock(&disp->mutex);
int32_t value = 0;
int found = 1;
switch (req.control_id) {
case PROTO_DISPLAY_CTRL_SCALE: value = (int32_t)disp->scale; break;
case PROTO_DISPLAY_CTRL_ANCHOR: value = (int32_t)disp->anchor; break;
case PROTO_DISPLAY_CTRL_NO_SIGNAL_FPS: value = disp->no_signal_fps > 0 ? disp->no_signal_fps : 15; break;
default: found = 0; break;
}
pthread_mutex_unlock(&disp->mutex);
if (!found) {
proto_write_control_response(conn, req.request_id, PROTO_STATUS_NOT_FOUND, NULL, 0);
return;
}
e = proto_write_get_control_response(conn, req.request_id, PROTO_STATUS_OK, value);
if (!APP_IS_OK(e)) { app_error_print(&e); }
return;
}
struct V4l2_Ctrl_Handle *handle;
@@ -930,7 +1024,34 @@ static void handle_set_control(struct Node *node,
}
const char *path = resolve_device_path(node, (int)req.device_index);
if (!path) {
proto_write_control_response(conn, req.request_id, PROTO_STATUS_NOT_FOUND, NULL, 0);
struct Display_Slot *disp = find_display_by_device_idx(node, (int)req.device_index);
if (!disp) {
proto_write_control_response(conn, req.request_id, PROTO_STATUS_NOT_FOUND, NULL, 0);
return;
}
pthread_mutex_lock(&disp->mutex);
int found = 1;
switch (req.control_id) {
case PROTO_DISPLAY_CTRL_SCALE:
if (req.value >= 0 && req.value <= 3) {
disp->scale = (Xorg_Scale)req.value;
}
break;
case PROTO_DISPLAY_CTRL_ANCHOR:
if (req.value >= 0 && req.value <= 1) {
disp->anchor = (Xorg_Anchor)req.value;
}
break;
case PROTO_DISPLAY_CTRL_NO_SIGNAL_FPS:
if (req.value >= 1 && req.value <= 60) {
disp->no_signal_fps = (int)req.value;
}
break;
default: found = 0; break;
}
pthread_mutex_unlock(&disp->mutex);
uint16_t status = found ? PROTO_STATUS_OK : PROTO_STATUS_NOT_FOUND;
proto_write_control_response(conn, req.request_id, status, NULL, 0);
return;
}
struct V4l2_Ctrl_Handle *handle;