Loading...
// SPDX-License-Identifier: GPL-2.0+
/*
 * Dump functions for expo objects
 *
 * Copyright 2025 Canonical Ltd
 * Written by Simon Glass <simon.glass@canonical.com>
 */

#define LOG_CATEGORY	LOGC_EXPO

#include <dm.h>
#include <expo.h>
#include <mapmem.h>
#include <membuf.h>
#include <os.h>
#include <video.h>
#include <video_console.h>
#include "scene_internal.h"

/**
 * typedef dump_out_func - Function type for dump output
 *
 * @priv: Private data for the output function
 * @buf: Buffer containing data to output
 * @len: Length of data in buffer
 */
typedef void (*dump_out_func)(void *priv, const char *buf, int len);

/**
 * struct dump_ctx - Context for dumping expo structures
 *
 * @out: Output function to call for each piece of output
 * @priv: Private data to pass to output function
 * @scn: Current scene being dumped (or NULL if not in a scene)
 * @indent: Current indentation level (number of spaces)
 */
struct dump_ctx {
	dump_out_func out;
	void *priv;
	struct scene *scn;
	int indent;
};

/**
 * dump_out_membuf() - Output function that writes to a membuf
 *
 * @priv: Pointer to struct membuf
 * @buf: Buffer containing data to output
 * @len: Length of data in buffer
 */
static void dump_out_membuf(void *priv, const char *buf, int len)
{
	struct membuf *mb = priv;

	membuf_put(mb, buf, len);
}

/**
 * outf() - Output a formatted string with indentation
 *
 * @ctx: Dump context containing output function, scene, and indent level
 * @fmt: Format string
 * @...: Arguments for format string
 */
static void outf(struct dump_ctx *ctx, const char *fmt, ...)
{
	char buf[256];
	char indent_buf[64];
	va_list args;
	int len;

	len = snprintf(indent_buf, sizeof(indent_buf), "%*s", ctx->indent, "");
	ctx->out(ctx->priv, indent_buf, len);

	va_start(args, fmt);
	len = vsnprintf(buf, sizeof(buf), fmt, args);
	ctx->out(ctx->priv, buf, len);
	va_end(args);
}

static const char *obj_name(struct dump_ctx *ctx, uint id)
{
	struct scene_obj *obj;

	if (!id)
		return "(none)";

	obj = scene_obj_find(ctx->scn, id, SCENEOBJT_NONE);
	if (!obj)
		return "(not found)";

	return obj->name;
}

static void dump_menu(struct dump_ctx *ctx, struct scene_obj_menu *menu)
{
	struct scene_obj *obj = &menu->obj;
	struct scene_menitem *item;

	outf(ctx, "Menu: pointer_id %x title_id %x manual %d\n",
	     menu->pointer_id, menu->title_id,
	     !!(obj->flags & SCENEOF_MANUAL));

	ctx->indent += 2;
	list_for_each_entry(item, &menu->item_head, sibling) {
		outf(ctx, "Item %x: name '%s' key_id %x label_id %x desc_id %x preview_id %x\n",
		     item->id, item->name, item->key_id, item->label_id,
		     item->desc_id, item->preview_id);
	}
	ctx->indent -= 2;
}

static void dump_text(struct dump_ctx *ctx, struct scene_obj_txt *txt)
{
	const char *str = expo_get_str(ctx->scn->expo, txt->gen.str_id);
	const struct vidconsole_mline *mline;

	outf(ctx, "Text: str_id %x font_name '%s' font_size %x\n",
	     txt->gen.str_id,
	     txt->gen.font_name ? txt->gen.font_name : "(default)",
	     txt->gen.font_size);
	ctx->indent += 2;
	outf(ctx, "str '%s'\n", str ? str : "(null)");
	alist_for_each(mline, &txt->gen.lines) {
		outf(ctx, "mline: start %x len %x bbox (%x,%x)-(%x,%x)\n",
		     mline->start, mline->len,
		     mline->bbox.x0, mline->bbox.y0,
		     mline->bbox.x1, mline->bbox.y1);
	}
	ctx->indent -= 2;
}

static void dump_box(struct dump_ctx *ctx, struct scene_obj_box *box)
{
	outf(ctx, "Box: fill %d width %x\n", box->fill, box->width);
}

static void dump_image(struct dump_ctx *ctx, struct scene_obj_img *img)
{
	outf(ctx, "Image: data %lx\n", (ulong)map_to_sysmem(img->data));
}

static void dump_textline(struct dump_ctx *ctx,
			  struct scene_obj_textline *tline)
{
	outf(ctx, "Textline: label_id %x edit_id %x\n",
	     tline->tin.label_id, tline->tin.edit_id);
	ctx->indent += 2;
	outf(ctx, "line_chars %x pos %x\n", tline->tin.line_chars, tline->pos);
	ctx->indent -= 2;
}

static void dump_textedit(struct dump_ctx *ctx,
			  struct scene_obj_txtedit *tedit)
{
	outf(ctx, "Textedit: label_id %x edit_id %x\n",
	     tedit->tin.label_id, tedit->tin.edit_id);
}

static void obj_dump_(struct dump_ctx *ctx, struct scene_obj *obj)
{
	char flags_buf[256];
	bool first = true;
	int bit;
	int pos = 0;

	outf(ctx, "Object %x (%s): type %s\n", obj->id, obj->name,
	     scene_obj_type_name(obj->type));
	ctx->indent += 2;

	/* Build flags string */
	for (bit = 0; bit < 16; bit++) {
		uint flag = BIT(bit);

		if (obj->flags & flag) {
			pos += snprintf(flags_buf + pos, sizeof(flags_buf) - pos,
					"%s%s", first ? "" : ", ",
					scene_flag_name(flag));
			first = false;
		}
	}
	outf(ctx, "flags %s\n", pos > 0 ? flags_buf : "");
	outf(ctx, "bbox: (%x,%x)-(%x,%x)\n",
	     obj->bbox.x0, obj->bbox.y0, obj->bbox.x1, obj->bbox.y1);
	outf(ctx, "dims: %xx%x\n", obj->dims.x, obj->dims.y);

	switch (obj->type) {
	case SCENEOBJT_NONE:
		break;
	case SCENEOBJT_IMAGE:
		dump_image(ctx, (struct scene_obj_img *)obj);
		break;
	case SCENEOBJT_TEXT:
		dump_text(ctx, (struct scene_obj_txt *)obj);
		break;
	case SCENEOBJT_BOX:
		dump_box(ctx, (struct scene_obj_box *)obj);
		break;
	case SCENEOBJT_MENU:
		dump_menu(ctx, (struct scene_obj_menu *)obj);
		break;
	case SCENEOBJT_TEXTLINE:
		dump_textline(ctx, (struct scene_obj_textline *)obj);
		break;
	case SCENEOBJT_TEXTEDIT:
		dump_textedit(ctx, (struct scene_obj_txtedit *)obj);
		break;
	}
	ctx->indent -= 2;
}

static void scene_dump_(struct dump_ctx *ctx)
{
	struct scene_obj *obj;

	outf(ctx, "Scene %x: name '%s'\n", ctx->scn->id, ctx->scn->name);
	ctx->indent += 2;
	outf(ctx, "title_id %x (%s)\n",
	     ctx->scn->title_id, obj_name(ctx, ctx->scn->title_id));
	outf(ctx, "highlight_id %x (%s)\n",
	     ctx->scn->highlight_id, obj_name(ctx, ctx->scn->highlight_id));

	list_for_each_entry(obj, &ctx->scn->obj_head, sibling) {
		/* Skip hidden objects */
		if (obj->flags & SCENEOF_HIDE)
			continue;
		obj_dump_(ctx, obj);
	}
	ctx->indent -= 2;
}

void scene_dump(struct membuf *mb, struct scene *scn, int indent)
{
	struct dump_ctx ctx;

	ctx.out = dump_out_membuf;
	ctx.priv = mb;
	ctx.scn = scn;
	ctx.indent = indent;

	scene_dump_(&ctx);
}

static void expo_dump_(struct dump_ctx *ctx, struct expo *exp)
{
	struct scene *scn;
	struct expo_theme *theme = &exp->theme;

	outf(ctx, "Expo: name '%s'\n", exp->name);
	ctx->indent = 2;
	outf(ctx, "display %s\n",
	     exp->display ? exp->display->name : "(null)");
	outf(ctx, "cons %s\n", exp->cons ? exp->cons->name : "(none)");
	outf(ctx, "mouse %s\n", exp->mouse ? exp->mouse->name : "(none)");
	outf(ctx, "scene_id %x\n", exp->scene_id);
	outf(ctx, "next_id %x\n", exp->next_id);
	outf(ctx, "req_width %x\n", exp->req_width);
	outf(ctx, "req_height %x\n", exp->req_height);
	outf(ctx, "text_mode %d\n", exp->text_mode);
	outf(ctx, "popup %d\n", exp->popup);
	outf(ctx, "show_highlight %d\n", exp->show_highlight);
	outf(ctx, "mouse_enabled %d\n", exp->mouse_enabled);
	outf(ctx, "mouse_ptr %p\n", exp->mouse_ptr);
	outf(ctx, "mouse_size %xx%x\n", exp->mouse_size.w,
	     exp->mouse_size.h);
	outf(ctx, "mouse_pos (%x,%x)\n", exp->mouse_pos.x,
	     exp->mouse_pos.y);
	outf(ctx, "damage (%x,%x)-(%x,%x)\n", exp->damage.x0, exp->damage.y0,
	     exp->damage.x1, exp->damage.y1);
	outf(ctx, "done %d\n", exp->done);
	outf(ctx, "save %d\n", exp->save);
	outf(ctx, "last_key_ms %ld\n", exp->last_key_ms);

	if (exp->display) {
		struct video_priv *vid_priv = dev_get_uclass_priv(exp->display);

		outf(ctx, "video: %xx%x white_on_black %d\n",
		     vid_priv->xsize, vid_priv->ysize,
		     vid_priv->white_on_black);
	}

	outf(ctx, "Theme:\n");
	ctx->indent = 4;
	outf(ctx, "font_size %x\n", theme->font_size);
	outf(ctx, "white_on_black %d\n", theme->white_on_black);
	outf(ctx, "menu_inset %x\n", theme->menu_inset);
	outf(ctx, "menuitem_gap_y %x\n", theme->menuitem_gap_y);

	ctx->indent = 0;
	outf(ctx, "\nScenes:\n");
	ctx->indent = 2;
	list_for_each_entry(scn, &exp->scene_head, sibling) {
		ctx->scn = scn;
		scene_dump_(ctx);
		outf(ctx, "\n");
	}
}

void expo_dump(struct expo *exp, struct membuf *mb)
{
	struct dump_ctx ctx;

	ctx.out = dump_out_membuf;
	ctx.priv = mb;
	ctx.scn = NULL;
	ctx.indent = 0;

	expo_dump_(&ctx, exp);
}

/**
 * dump_out_file() - Output function that writes to a file
 *
 * @priv: Pointer to file descriptor (int *)
 * @buf: Buffer containing data to output
 * @len: Length of data in buffer
 */
static void dump_out_file(void *priv, const char *buf, int len)
{
	int *fdp = priv;

	os_write(*fdp, buf, len);
}

int expo_dump_file(struct expo *exp, const char *fname)
{
	struct dump_ctx ctx;
	int fd;

	fd = os_open(fname, OS_O_WRONLY | OS_O_CREAT | OS_O_TRUNC);
	if (fd < 0)
		return fd;

	ctx.out = dump_out_file;
	ctx.priv = &fd;
	ctx.scn = NULL;
	ctx.indent = 0;

	expo_dump_(&ctx, exp);

	os_close(fd);

	return 0;
}