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

@@ -137,14 +137,17 @@ static void on_video_node(
uint8_t pflags, uint8_t is_capture,
void *ud)
{
(void)eflags; (void)pflags; (void)ud;
(void)eflags; (void)pflags;
int *idx = ud;
char caps[128];
caps_str(dcaps, caps, sizeof(caps));
printf(" video %.*s entity=%.*s type=0x%08x caps=[%s]%s\n",
printf(" [%d] video %.*s entity=%.*s type=0x%08x caps=[%s]%s\n",
*idx,
(int)path_len, path,
(int)ename_len, ename,
etype, caps,
is_capture ? " [capture]" : "");
(*idx)++;
}
static void on_standalone(
@@ -152,10 +155,12 @@ static void on_standalone(
const char *name, uint8_t name_len,
void *ud)
{
(void)ud;
printf(" standalone %.*s card=%.*s\n",
int *idx = ud;
printf(" [%d] standalone %.*s card=%.*s\n",
*idx,
(int)path_len, path,
(int)name_len, name);
(*idx)++;
}
static const char *scale_name(uint8_t s)
@@ -170,6 +175,7 @@ static const char *scale_name(uint8_t s)
}
static void on_display(
uint16_t device_id,
uint16_t stream_id,
int16_t win_x, int16_t win_y,
uint16_t win_w, uint16_t win_h,
@@ -177,8 +183,8 @@ static void on_display(
void *ud)
{
(void)ud;
printf(" display stream=%u pos=%d,%d size=%ux%u scale=%s anchor=%s\n",
stream_id, win_x, win_y, win_w, win_h,
printf(" [%u] display stream=%u pos=%d,%d size=%ux%u scale=%s anchor=%s\n",
device_id, stream_id, win_x, win_y, win_w, win_h,
scale_name(scale),
anchor == 0 ? "center" : "topleft");
}
@@ -231,9 +237,10 @@ static void on_frame(struct Transport_Conn *conn,
switch (cs->pending_cmd) {
case PROTO_CMD_ENUM_DEVICES: {
struct Proto_Response_Header hdr;
int dev_idx = 0;
struct App_Error e = proto_read_enum_devices_response(
frame->payload, frame->payload_length, &hdr,
on_media_device, on_video_node, on_standalone, on_display, NULL);
on_media_device, on_video_node, on_standalone, on_display, &dev_idx);
if (!APP_IS_OK(e)) { app_error_print(&e); }
else if (hdr.status != PROTO_STATUS_OK) {
fprintf(stderr, "ENUM_DEVICES: status=%u\n", hdr.status);

View File

@@ -149,9 +149,11 @@ struct Proto_Standalone_Device_Info {
/*
* An active display window (video sink role).
* device_id is the flat device index (follows all V4L2 devices).
* stream_id is the stream being displayed; win_* are current geometry.
*/
struct Proto_Display_Device_Info {
uint16_t device_id;
uint16_t stream_id;
int16_t win_x, win_y;
uint16_t win_w, win_h;
@@ -159,6 +161,15 @@ struct Proto_Display_Device_Info {
uint8_t anchor;
};
/* -------------------------------------------------------------------------
* Display device pseudo-control IDs — used in ENUM_CONTROLS / GET_CONTROL /
* SET_CONTROL for display device indices returned by ENUM_DEVICES.
* ------------------------------------------------------------------------- */
#define PROTO_DISPLAY_CTRL_SCALE 0x00D00001u /* int 0-3: stretch/fit/fill/1:1 */
#define PROTO_DISPLAY_CTRL_ANCHOR 0x00D00002u /* int 0-1: center/topleft */
#define PROTO_DISPLAY_CTRL_NO_SIGNAL_FPS 0x00D00003u /* int 1-60: no-signal animation fps */
struct Proto_Monitor_Info {
int32_t x, y;
uint32_t width, height;
@@ -495,6 +506,7 @@ struct App_Error proto_read_enum_devices_response(
const char *name, uint8_t name_len,
void *userdata),
void (*on_display)(
uint16_t device_id,
uint16_t stream_id,
int16_t win_x, int16_t win_y,
uint16_t win_w, uint16_t win_h,

View File

@@ -414,6 +414,7 @@ struct App_Error proto_write_enum_devices_response(struct Transport_Conn *conn,
e = wbuf_u16(&b, display_count); if (!APP_IS_OK(e)) { goto fail; }
for (uint16_t i = 0; i < display_count; i++) {
const struct Proto_Display_Device_Info *d = &displays[i];
e = wbuf_u16(&b, d->device_id); if (!APP_IS_OK(e)) { goto fail; }
e = wbuf_u16(&b, d->stream_id); if (!APP_IS_OK(e)) { goto fail; }
e = wbuf_i16(&b, d->win_x); if (!APP_IS_OK(e)) { goto fail; }
e = wbuf_i16(&b, d->win_y); if (!APP_IS_OK(e)) { goto fail; }
@@ -791,6 +792,7 @@ struct App_Error proto_read_enum_devices_response(
uint16_t display_count = cur_u16(&c);
CUR_CHECK(c);
for (uint16_t i = 0; i < display_count; i++) {
uint16_t device_id = cur_u16(&c);
uint16_t stream_id = cur_u16(&c);
int16_t win_x = (int16_t)cur_u16(&c);
int16_t win_y = (int16_t)cur_u16(&c);
@@ -800,7 +802,7 @@ struct App_Error proto_read_enum_devices_response(
uint8_t anchor = cur_u8(&c);
CUR_CHECK(c);
if (on_display) {
on_display(stream_id, win_x, win_y,
on_display(device_id, stream_id, win_x, win_y,
win_w, win_h, scale, anchor, userdata);
}
}
@@ -830,6 +832,8 @@ struct App_Error proto_read_enum_controls_response(
header_out->request_id = cur_u16(&c);
header_out->status = cur_u16(&c);
CUR_CHECK(c);
if (header_out->status != PROTO_STATUS_OK) { return APP_OK; }
uint16_t count = cur_u16(&c);
CUR_CHECK(c);

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;