mirror of https://github.com/nirenjan/libx52.git
feat: Add scroll functionality to libx52util
This change adds the ability to scroll a text blob to fit within the 16 character MFD limit. The daemon can use this in the future to implement scrolling similar to how the Windows SST software did it.lipc-refactor
parent
4422ee89c0
commit
130a1f67de
|
|
@ -60,6 +60,119 @@ extern "C" {
|
||||||
LIBX52UTIL_API int libx52util_convert_utf8_string(const uint8_t *input,
|
LIBX52UTIL_API int libx52util_convert_utf8_string(const uint8_t *input,
|
||||||
uint8_t *output, size_t *len);
|
uint8_t *output, size_t *len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name MFD line scrolling
|
||||||
|
*
|
||||||
|
* Step a 16-cell window over a virtual tape of X52 MFD display bytes (same
|
||||||
|
* encoding as libx52_set_text / libx52util_convert_utf8_string output). Cell
|
||||||
|
* count follows convert semantics (multi-byte glyphs use multiple cells).
|
||||||
|
*
|
||||||
|
* **Default wrap:** unless #LIBX52UTIL_SCROLL_SINGLE_PASS is set, each
|
||||||
|
* libx52util_scroll_next() advances the window modulo the tape period so the
|
||||||
|
* marquee loops until libx52util_scroll_reset() or libx52util_scroll_free().
|
||||||
|
*
|
||||||
|
* These routines are not thread-safe; callers must serialize access to a given
|
||||||
|
* libx52util_scroll_state.
|
||||||
|
*
|
||||||
|
* @{
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Opaque scroll state allocated by libx52util_scroll_new(). */
|
||||||
|
typedef struct libx52util_scroll libx52util_scroll_state;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Bitwise flags for libx52util_scroll_new().
|
||||||
|
*
|
||||||
|
* Combine enumerator values with `|`. Each enumerator is a distinct power of two.
|
||||||
|
*/
|
||||||
|
typedef enum libx52util_scroll_flags {
|
||||||
|
LIBX52UTIL_SCROLL_NONE = 0,
|
||||||
|
/** Virtual prefix of 16 ASCII spaces before converted text (scroll-in). */
|
||||||
|
LIBX52UTIL_SCROLL_IN = 1u << 0,
|
||||||
|
/** Virtual suffix of 16 ASCII spaces after converted text (scroll-out). */
|
||||||
|
LIBX52UTIL_SCROLL_OUT = 1u << 1,
|
||||||
|
/**
|
||||||
|
* Horizontal direction opposite the default ticker (default advances the window
|
||||||
|
* so content appears to move left; with this flag, content moves right).
|
||||||
|
*/
|
||||||
|
LIBX52UTIL_SCROLL_LTR = 1u << 2,
|
||||||
|
/**
|
||||||
|
* Disable looping: after one full cycle over distinct window positions, the
|
||||||
|
* window clamps at the end; further libx52util_scroll_next() returns -EAGAIN
|
||||||
|
* until libx52util_scroll_reset() or libx52util_scroll_new(). Without this flag,
|
||||||
|
* the window wraps continuously. Implies LIBX52UTIL_SCROLL_OUT.
|
||||||
|
*/
|
||||||
|
LIBX52UTIL_SCROLL_SINGLE_PASS = 1u << 3,
|
||||||
|
} libx52util_scroll_flags;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Allocate scroll state from a UTF-8 string.
|
||||||
|
*
|
||||||
|
* Converts @p utf8_string with libx52util_convert_utf8_string() using an
|
||||||
|
* internal 256-byte output buffer. Converted length is in display cells, same
|
||||||
|
* as libx52util_convert_utf8_string()'s output length.
|
||||||
|
*
|
||||||
|
* @param[out] state Receives the new state; must not be NULL.
|
||||||
|
* @param[in] utf8_string NUL-terminated UTF-8 input; must not be NULL.
|
||||||
|
* @param[in] flags Bitwise OR of #libx52util_scroll_flags values.
|
||||||
|
*
|
||||||
|
* @returns 0 if the full string fit the internal buffer; -E2BIG if conversion
|
||||||
|
* stopped early but @p *state is still a valid object for scroll_next/reset/free
|
||||||
|
* (truncated prefix only). On -EINVAL or -ENOMEM, @p *state is left unchanged.
|
||||||
|
* -EINVAL: NULL @p state, NULL @p utf8_string, or invalid argument use.
|
||||||
|
* -ENOMEM: allocation failed.
|
||||||
|
*
|
||||||
|
* If the converted text length is at most 16 cells, or the full virtual tape
|
||||||
|
* (optional 16-space prefix + text + optional 16-space suffix) is at most 16
|
||||||
|
* cells long, there is no window motion: libx52util_scroll_next() returns 0
|
||||||
|
* once with a padded line, then -EAGAIN without advancing.
|
||||||
|
*/
|
||||||
|
LIBX52UTIL_API int libx52util_scroll_new(libx52util_scroll_state **state,
|
||||||
|
const uint8_t *utf8_string,
|
||||||
|
libx52util_scroll_flags flags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Free scroll state.
|
||||||
|
*
|
||||||
|
* If @p state is non-NULL and @p *state is non-NULL, frees @p *state and sets
|
||||||
|
* @p *state to NULL. If @p state is NULL, no operation is performed.
|
||||||
|
*/
|
||||||
|
LIBX52UTIL_API void libx52util_scroll_free(libx52util_scroll_state **state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Rewind the scroll window to the initial position.
|
||||||
|
*
|
||||||
|
* @param[in] state Scroll state from libx52util_scroll_new().
|
||||||
|
*
|
||||||
|
* @returns 0 on success, -EINVAL if @p state is NULL.
|
||||||
|
*/
|
||||||
|
LIBX52UTIL_API int libx52util_scroll_reset(libx52util_scroll_state *state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Emit the next 16-byte MFD line for the current window.
|
||||||
|
*
|
||||||
|
* Writes up to 16 display bytes into @p display (ASCII space padding where the
|
||||||
|
* window extends past the virtual tape). Returns 0 when the new frame differs
|
||||||
|
* from the last frame returned with 0; updates the last-emitted snapshot and
|
||||||
|
* advances the internal window for the following call. Returns -EAGAIN when the
|
||||||
|
* candidate frame equals that last snapshot: @p display is not modified, the
|
||||||
|
* snapshot is unchanged, but the window index still advances (wrap or
|
||||||
|
* single-pass clamp) so uniform runs eventually change at boundaries.
|
||||||
|
*
|
||||||
|
* When there is no scrolling (see libx52util_scroll_new): the first call
|
||||||
|
* returns 0; later calls return -EAGAIN without advancing the window.
|
||||||
|
*
|
||||||
|
* @param[in] state Scroll state; must not be NULL.
|
||||||
|
* @param[out] display 16-byte output buffer; must not be NULL.
|
||||||
|
*
|
||||||
|
* @returns 0 on a new visible frame, -EAGAIN when unchanged or static repeat,
|
||||||
|
* -EINVAL if @p state or @p display is NULL.
|
||||||
|
*/
|
||||||
|
LIBX52UTIL_API int libx52util_scroll_next(libx52util_scroll_state *state,
|
||||||
|
uint8_t display[16]);
|
||||||
|
|
||||||
|
/** @} */
|
||||||
|
|
||||||
/** @} */
|
/** @} */
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ util_char_map = custom_target('util-char-map',
|
||||||
install_dir: [false, get_option('datadir') / 'x52d'])
|
install_dir: [false, get_option('datadir') / 'x52d'])
|
||||||
|
|
||||||
lib_libx52util = library('x52util', util_char_map, 'char_map_lookup.c',
|
lib_libx52util = library('x52util', util_char_map, 'char_map_lookup.c',
|
||||||
|
'scroll.c',
|
||||||
install: true,
|
install: true,
|
||||||
version: libx52util_version,
|
version: libx52util_version,
|
||||||
c_args: sym_hidden_cargs,
|
c_args: sym_hidden_cargs,
|
||||||
|
|
@ -36,6 +37,18 @@ test('libx52util-bmp-test', libx52util_bmp_test,
|
||||||
protocol: 'tap',
|
protocol: 'tap',
|
||||||
args: [util_char_map[1]])
|
args: [util_char_map[1]])
|
||||||
|
|
||||||
|
libx52util_scroll_test = executable(
|
||||||
|
'libx52util-scroll-test',
|
||||||
|
'scroll_test.c',
|
||||||
|
build_by_default: false,
|
||||||
|
include_directories: [includes, lib_libx52util.private_dir_include()],
|
||||||
|
link_with: [lib_libx52util],
|
||||||
|
dependencies: [dep_cmocka],
|
||||||
|
)
|
||||||
|
|
||||||
|
test('libx52util-scroll-test', libx52util_scroll_test,
|
||||||
|
protocol: 'tap')
|
||||||
|
|
||||||
benchmark('libx52util-bmp-bench', libx52util_bmp_test,
|
benchmark('libx52util-bmp-bench', libx52util_bmp_test,
|
||||||
protocol: 'tap',
|
protocol: 'tap',
|
||||||
args: [util_char_map[1]])
|
args: [util_char_map[1]])
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
/*
|
||||||
|
* Saitek X52 Pro MFD line scrolling (libx52util)
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <libx52/libx52util.h>
|
||||||
|
|
||||||
|
#define SCROLL_OUT_BUF 256u
|
||||||
|
#define SCROLL_WINDOW 16u
|
||||||
|
#define SCROLL_PAD 16u
|
||||||
|
|
||||||
|
struct libx52util_scroll {
|
||||||
|
uint8_t text[SCROLL_OUT_BUF];
|
||||||
|
size_t text_len;
|
||||||
|
uint32_t flags;
|
||||||
|
size_t prefix;
|
||||||
|
size_t suffix;
|
||||||
|
size_t tape_len;
|
||||||
|
size_t max_pos;
|
||||||
|
size_t pos;
|
||||||
|
bool has_last;
|
||||||
|
uint8_t last[SCROLL_WINDOW];
|
||||||
|
bool static_mode;
|
||||||
|
bool static_emitted;
|
||||||
|
bool single_pass;
|
||||||
|
bool single_pass_done;
|
||||||
|
bool ltr;
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint8_t tape_byte(const struct libx52util_scroll *st, size_t index)
|
||||||
|
{
|
||||||
|
if (index < st->prefix) {
|
||||||
|
return (uint8_t)' ';
|
||||||
|
}
|
||||||
|
index -= st->prefix;
|
||||||
|
if (index < st->text_len) {
|
||||||
|
return st->text[index];
|
||||||
|
}
|
||||||
|
index -= st->text_len;
|
||||||
|
if (index < st->suffix) {
|
||||||
|
return (uint8_t)' ';
|
||||||
|
}
|
||||||
|
return (uint8_t)' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fill_frame(const struct libx52util_scroll *st, size_t tape_pos,
|
||||||
|
uint8_t display[SCROLL_WINDOW])
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < SCROLL_WINDOW; i++) {
|
||||||
|
size_t idx = tape_pos + i;
|
||||||
|
if (idx < st->tape_len) {
|
||||||
|
display[i] = tape_byte(st, idx);
|
||||||
|
} else {
|
||||||
|
display[i] = (uint8_t)' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void advance_pos(struct libx52util_scroll *st)
|
||||||
|
{
|
||||||
|
if (st->static_mode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (st->single_pass_done) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!st->ltr) {
|
||||||
|
if (st->pos < st->max_pos) {
|
||||||
|
st->pos++;
|
||||||
|
} else if (!st->single_pass) {
|
||||||
|
st->pos = 0;
|
||||||
|
} else {
|
||||||
|
st->single_pass_done = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (st->pos > 0) {
|
||||||
|
st->pos--;
|
||||||
|
} else if (!st->single_pass) {
|
||||||
|
st->pos = st->max_pos;
|
||||||
|
} else {
|
||||||
|
st->single_pass_done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int libx52util_scroll_new(libx52util_scroll_state **state,
|
||||||
|
const uint8_t *utf8_string, libx52util_scroll_flags flags)
|
||||||
|
{
|
||||||
|
struct libx52util_scroll *st;
|
||||||
|
size_t out_len;
|
||||||
|
int conv_rc;
|
||||||
|
|
||||||
|
if (!state || !utf8_string) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
st = calloc(1, sizeof(*st));
|
||||||
|
if (!st) {
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_len = SCROLL_OUT_BUF;
|
||||||
|
conv_rc = libx52util_convert_utf8_string(utf8_string, st->text, &out_len);
|
||||||
|
if (conv_rc == -EINVAL) {
|
||||||
|
free(st);
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
st->text_len = out_len;
|
||||||
|
st->flags = (uint32_t)flags;
|
||||||
|
|
||||||
|
{
|
||||||
|
uint32_t eff = st->flags;
|
||||||
|
if (eff & (uint32_t)LIBX52UTIL_SCROLL_SINGLE_PASS) {
|
||||||
|
eff |= (uint32_t)LIBX52UTIL_SCROLL_OUT;
|
||||||
|
}
|
||||||
|
st->prefix = (eff & (uint32_t)LIBX52UTIL_SCROLL_IN) ? SCROLL_PAD : 0;
|
||||||
|
st->suffix = (eff & (uint32_t)LIBX52UTIL_SCROLL_OUT) ? SCROLL_PAD : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
st->tape_len = st->prefix + st->text_len + st->suffix;
|
||||||
|
st->ltr = (st->flags & (uint32_t)LIBX52UTIL_SCROLL_LTR) != 0;
|
||||||
|
st->single_pass = (st->flags & (uint32_t)LIBX52UTIL_SCROLL_SINGLE_PASS) != 0;
|
||||||
|
|
||||||
|
st->static_mode =
|
||||||
|
(st->text_len <= SCROLL_WINDOW) || (st->tape_len <= SCROLL_WINDOW);
|
||||||
|
|
||||||
|
if (st->tape_len > SCROLL_WINDOW) {
|
||||||
|
st->max_pos = st->tape_len - SCROLL_WINDOW;
|
||||||
|
} else {
|
||||||
|
st->max_pos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
st->pos = st->ltr ? st->max_pos : 0;
|
||||||
|
st->has_last = false;
|
||||||
|
st->static_emitted = false;
|
||||||
|
st->single_pass_done = false;
|
||||||
|
|
||||||
|
*state = st;
|
||||||
|
|
||||||
|
if (conv_rc == -E2BIG) {
|
||||||
|
return -E2BIG;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void libx52util_scroll_free(libx52util_scroll_state **state)
|
||||||
|
{
|
||||||
|
if (!state || !*state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
free(*state);
|
||||||
|
*state = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int libx52util_scroll_reset(libx52util_scroll_state *state)
|
||||||
|
{
|
||||||
|
if (!state) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->pos = state->ltr ? state->max_pos : 0;
|
||||||
|
state->has_last = false;
|
||||||
|
state->static_emitted = false;
|
||||||
|
state->single_pass_done = false;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int libx52util_scroll_next(libx52util_scroll_state *state, uint8_t display[SCROLL_WINDOW])
|
||||||
|
{
|
||||||
|
uint8_t candidate[SCROLL_WINDOW];
|
||||||
|
|
||||||
|
if (!state || !display) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state->static_mode && state->static_emitted) {
|
||||||
|
return -EAGAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
fill_frame(state, state->pos, candidate);
|
||||||
|
|
||||||
|
if (state->has_last &&
|
||||||
|
memcmp(candidate, state->last, SCROLL_WINDOW) == 0) {
|
||||||
|
advance_pos(state);
|
||||||
|
return -EAGAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(display, candidate, SCROLL_WINDOW);
|
||||||
|
memcpy(state->last, candidate, SCROLL_WINDOW);
|
||||||
|
state->has_last = true;
|
||||||
|
|
||||||
|
if (state->static_mode) {
|
||||||
|
state->static_emitted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
advance_pos(state);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,332 @@
|
||||||
|
/*
|
||||||
|
* libx52util MFD scroll tests (cmocka, TAP output)
|
||||||
|
*
|
||||||
|
* Copyright (C) 2026 Nirenjan Krishnan (nirenjan@nirenjan.org)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only WITH Classpath-exception-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <setjmp.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <cmocka.h>
|
||||||
|
#include <libx52/libx52util.h>
|
||||||
|
|
||||||
|
#define MFD_LINE 16u
|
||||||
|
|
||||||
|
static void utf8_to_mfd(const char *utf8, uint8_t *out, size_t *out_len)
|
||||||
|
{
|
||||||
|
*out_len = 256;
|
||||||
|
(void)libx52util_convert_utf8_string((const uint8_t *)utf8, out, out_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool frame_all_spaces(const uint8_t *d)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < MFD_LINE; i++) {
|
||||||
|
if (d[i] != (uint8_t)' ') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_scroll_new_null_state(void **state)
|
||||||
|
{
|
||||||
|
(void)state;
|
||||||
|
assert_int_equal(-EINVAL,
|
||||||
|
libx52util_scroll_new(NULL, (const uint8_t *)"x",
|
||||||
|
LIBX52UTIL_SCROLL_NONE));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_scroll_new_null_utf8(void **state)
|
||||||
|
{
|
||||||
|
(void)state;
|
||||||
|
libx52util_scroll_state *st = NULL;
|
||||||
|
assert_int_equal(-EINVAL,
|
||||||
|
libx52util_scroll_new(&st, NULL, LIBX52UTIL_SCROLL_NONE));
|
||||||
|
assert_null(st);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_scroll_next_null_state(void **state)
|
||||||
|
{
|
||||||
|
(void)state;
|
||||||
|
uint8_t disp[MFD_LINE];
|
||||||
|
|
||||||
|
memset(disp, 0xAA, sizeof(disp));
|
||||||
|
assert_int_equal(-EINVAL, libx52util_scroll_next(NULL, disp));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_scroll_next_null_display(void **state)
|
||||||
|
{
|
||||||
|
(void)state;
|
||||||
|
libx52util_scroll_state *st = NULL;
|
||||||
|
|
||||||
|
assert_int_equal(0, libx52util_scroll_new(&st, (const uint8_t *)"z",
|
||||||
|
LIBX52UTIL_SCROLL_NONE));
|
||||||
|
assert_non_null(st);
|
||||||
|
assert_int_equal(-EINVAL, libx52util_scroll_next(st, NULL));
|
||||||
|
libx52util_scroll_free(&st);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_scroll_reset_null(void **state)
|
||||||
|
{
|
||||||
|
(void)state;
|
||||||
|
assert_int_equal(-EINVAL, libx52util_scroll_reset(NULL));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_short_string_first_then_eagain(void **state)
|
||||||
|
{
|
||||||
|
(void)state;
|
||||||
|
libx52util_scroll_state *st = NULL;
|
||||||
|
uint8_t disp[MFD_LINE];
|
||||||
|
|
||||||
|
assert_int_equal(0, libx52util_scroll_new(&st, (const uint8_t *)"AB",
|
||||||
|
LIBX52UTIL_SCROLL_NONE));
|
||||||
|
assert_non_null(st);
|
||||||
|
assert_int_equal(0, libx52util_scroll_next(st, disp));
|
||||||
|
assert_int_equal(-EAGAIN, libx52util_scroll_next(st, disp));
|
||||||
|
libx52util_scroll_free(&st);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_long_string_wrap(void **state)
|
||||||
|
{
|
||||||
|
(void)state;
|
||||||
|
libx52util_scroll_state *st = NULL;
|
||||||
|
uint8_t first[MFD_LINE];
|
||||||
|
uint8_t disp[MFD_LINE];
|
||||||
|
uint8_t disp2[MFD_LINE];
|
||||||
|
|
||||||
|
assert_int_equal(
|
||||||
|
0, libx52util_scroll_new(&st, (const uint8_t *)"01234567890123456",
|
||||||
|
LIBX52UTIL_SCROLL_NONE));
|
||||||
|
assert_non_null(st);
|
||||||
|
assert_int_equal(0, libx52util_scroll_next(st, first));
|
||||||
|
assert_int_equal(0, libx52util_scroll_next(st, disp));
|
||||||
|
assert_int_equal(0, libx52util_scroll_next(st, disp2));
|
||||||
|
assert_memory_equal(first, disp2, MFD_LINE);
|
||||||
|
libx52util_scroll_free(&st);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_identical_glyph_run(void **state)
|
||||||
|
{
|
||||||
|
(void)state;
|
||||||
|
char buf[32];
|
||||||
|
libx52util_scroll_state *st = NULL;
|
||||||
|
uint8_t disp[MFD_LINE];
|
||||||
|
uint8_t disp2[MFD_LINE];
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
memset(buf, 'A', sizeof(buf));
|
||||||
|
buf[17] = '\0';
|
||||||
|
assert_int_equal(0, libx52util_scroll_new(&st, (const uint8_t *)buf,
|
||||||
|
LIBX52UTIL_SCROLL_NONE));
|
||||||
|
assert_non_null(st);
|
||||||
|
assert_int_equal(0, libx52util_scroll_next(st, disp));
|
||||||
|
memcpy(disp2, disp, sizeof(disp));
|
||||||
|
rc = libx52util_scroll_next(st, disp);
|
||||||
|
assert_int_equal(-EAGAIN, rc);
|
||||||
|
assert_memory_equal(disp, disp2, MFD_LINE);
|
||||||
|
libx52util_scroll_free(&st);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_scroll_in_first_all_spaces(void **state)
|
||||||
|
{
|
||||||
|
(void)state;
|
||||||
|
libx52util_scroll_state *st = NULL;
|
||||||
|
uint8_t disp[MFD_LINE];
|
||||||
|
|
||||||
|
assert_int_equal(
|
||||||
|
0,
|
||||||
|
libx52util_scroll_new(&st, (const uint8_t *)"01234567890123456789",
|
||||||
|
LIBX52UTIL_SCROLL_IN));
|
||||||
|
assert_non_null(st);
|
||||||
|
assert_int_equal(0, libx52util_scroll_next(st, disp));
|
||||||
|
assert_true(frame_all_spaces(disp));
|
||||||
|
libx52util_scroll_free(&st);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_scroll_out_eventually_all_spaces(void **state)
|
||||||
|
{
|
||||||
|
(void)state;
|
||||||
|
libx52util_scroll_state *st = NULL;
|
||||||
|
uint8_t disp[MFD_LINE];
|
||||||
|
bool saw_all_spaces = false;
|
||||||
|
int rc;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
assert_int_equal(
|
||||||
|
0, libx52util_scroll_new(&st, (const uint8_t *)"01234567890123456",
|
||||||
|
LIBX52UTIL_SCROLL_OUT));
|
||||||
|
assert_non_null(st);
|
||||||
|
for (i = 0; i < 64 && !saw_all_spaces; i++) {
|
||||||
|
rc = libx52util_scroll_next(st, disp);
|
||||||
|
if (rc == 0 && frame_all_spaces(disp)) {
|
||||||
|
saw_all_spaces = true;
|
||||||
|
} else if (rc == -EAGAIN) {
|
||||||
|
continue;
|
||||||
|
} else if (rc != 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_true(saw_all_spaces);
|
||||||
|
libx52util_scroll_free(&st);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_reset_rewinds(void **state)
|
||||||
|
{
|
||||||
|
(void)state;
|
||||||
|
libx52util_scroll_state *st = NULL;
|
||||||
|
uint8_t first[MFD_LINE];
|
||||||
|
uint8_t disp[MFD_LINE];
|
||||||
|
|
||||||
|
assert_int_equal(
|
||||||
|
0, libx52util_scroll_new(&st, (const uint8_t *)"01234567890123456",
|
||||||
|
LIBX52UTIL_SCROLL_NONE));
|
||||||
|
assert_non_null(st);
|
||||||
|
assert_int_equal(0, libx52util_scroll_next(st, first));
|
||||||
|
(void)libx52util_scroll_next(st, disp);
|
||||||
|
(void)libx52util_scroll_next(st, disp);
|
||||||
|
assert_int_equal(0, libx52util_scroll_reset(st));
|
||||||
|
assert_int_equal(0, libx52util_scroll_next(st, disp));
|
||||||
|
assert_memory_equal(first, disp, MFD_LINE);
|
||||||
|
libx52util_scroll_free(&st);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_single_pass_clamp_then_reset(void **state)
|
||||||
|
{
|
||||||
|
(void)state;
|
||||||
|
libx52util_scroll_state *st = NULL;
|
||||||
|
uint8_t disp[MFD_LINE];
|
||||||
|
int rc;
|
||||||
|
int zeros = 0;
|
||||||
|
int steps = 0;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
assert_int_equal(
|
||||||
|
0, libx52util_scroll_new(&st, (const uint8_t *)"01234567890123456",
|
||||||
|
LIBX52UTIL_SCROLL_SINGLE_PASS));
|
||||||
|
assert_non_null(st);
|
||||||
|
while (steps < 4096) {
|
||||||
|
rc = libx52util_scroll_next(st, disp);
|
||||||
|
steps++;
|
||||||
|
if (rc == 0) {
|
||||||
|
zeros++;
|
||||||
|
}
|
||||||
|
if (zeros > 2 && rc == -EAGAIN) {
|
||||||
|
int eagain_streak = 0;
|
||||||
|
for (i = 0; i < 8; i++) {
|
||||||
|
uint8_t tmp[MFD_LINE];
|
||||||
|
|
||||||
|
memcpy(tmp, disp, sizeof(tmp));
|
||||||
|
rc = libx52util_scroll_next(st, disp);
|
||||||
|
steps++;
|
||||||
|
if (rc == -EAGAIN && memcmp(disp, tmp, MFD_LINE) == 0) {
|
||||||
|
eagain_streak++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (eagain_streak >= 8) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_int_equal(0, libx52util_scroll_reset(st));
|
||||||
|
assert_true(zeros > 0);
|
||||||
|
assert_int_equal(0, libx52util_scroll_next(st, disp));
|
||||||
|
libx52util_scroll_free(&st);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_e2big_truncated_prefix(void **state)
|
||||||
|
{
|
||||||
|
(void)state;
|
||||||
|
char big[512];
|
||||||
|
libx52util_scroll_state *st = NULL;
|
||||||
|
uint8_t disp[MFD_LINE];
|
||||||
|
uint8_t mfd[256];
|
||||||
|
size_t mfd_len;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
memset(big, 'B', sizeof(big) - 1);
|
||||||
|
big[sizeof(big) - 1] = '\0';
|
||||||
|
rc = libx52util_scroll_new(&st, (const uint8_t *)big, LIBX52UTIL_SCROLL_NONE);
|
||||||
|
assert_int_equal(-E2BIG, rc);
|
||||||
|
assert_non_null(st);
|
||||||
|
assert_int_equal(0, libx52util_scroll_next(st, disp));
|
||||||
|
mfd_len = sizeof(mfd);
|
||||||
|
utf8_to_mfd(big, mfd, &mfd_len);
|
||||||
|
assert_true((size_t)mfd_len >= MFD_LINE);
|
||||||
|
assert_memory_equal(mfd, disp, MFD_LINE);
|
||||||
|
libx52util_scroll_free(&st);
|
||||||
|
assert_null(st);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_scroll_free_nulls_state(void **state)
|
||||||
|
{
|
||||||
|
(void)state;
|
||||||
|
libx52util_scroll_state *st = NULL;
|
||||||
|
|
||||||
|
assert_int_equal(0, libx52util_scroll_new(&st, (const uint8_t *)"q",
|
||||||
|
LIBX52UTIL_SCROLL_NONE));
|
||||||
|
assert_non_null(st);
|
||||||
|
libx52util_scroll_free(&st);
|
||||||
|
assert_null(st);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_ltr_first_frame_trailing(void **state)
|
||||||
|
{
|
||||||
|
(void)state;
|
||||||
|
libx52util_scroll_state *st = NULL;
|
||||||
|
uint8_t disp[MFD_LINE];
|
||||||
|
uint8_t mfd[256];
|
||||||
|
size_t mfd_len;
|
||||||
|
|
||||||
|
assert_int_equal(
|
||||||
|
0, libx52util_scroll_new(&st, (const uint8_t *)"01234567890123456",
|
||||||
|
LIBX52UTIL_SCROLL_LTR));
|
||||||
|
assert_non_null(st);
|
||||||
|
mfd_len = sizeof(mfd);
|
||||||
|
utf8_to_mfd("01234567890123456", mfd, &mfd_len);
|
||||||
|
assert_true(mfd_len >= 17);
|
||||||
|
assert_int_equal(0, libx52util_scroll_next(st, disp));
|
||||||
|
assert_memory_equal(mfd + (mfd_len - MFD_LINE), disp, MFD_LINE);
|
||||||
|
libx52util_scroll_free(&st);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_scroll_free_defensive_null(void **state)
|
||||||
|
{
|
||||||
|
(void)state;
|
||||||
|
libx52util_scroll_state *st = NULL;
|
||||||
|
|
||||||
|
libx52util_scroll_free(NULL);
|
||||||
|
libx52util_scroll_free(&st);
|
||||||
|
assert_null(st);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
const struct CMUnitTest tests[] = {
|
||||||
|
cmocka_unit_test(test_scroll_new_null_state),
|
||||||
|
cmocka_unit_test(test_scroll_new_null_utf8),
|
||||||
|
cmocka_unit_test(test_scroll_next_null_state),
|
||||||
|
cmocka_unit_test(test_scroll_next_null_display),
|
||||||
|
cmocka_unit_test(test_scroll_reset_null),
|
||||||
|
cmocka_unit_test(test_short_string_first_then_eagain),
|
||||||
|
cmocka_unit_test(test_long_string_wrap),
|
||||||
|
cmocka_unit_test(test_identical_glyph_run),
|
||||||
|
cmocka_unit_test(test_scroll_in_first_all_spaces),
|
||||||
|
cmocka_unit_test(test_scroll_out_eventually_all_spaces),
|
||||||
|
cmocka_unit_test(test_reset_rewinds),
|
||||||
|
cmocka_unit_test(test_single_pass_clamp_then_reset),
|
||||||
|
cmocka_unit_test(test_e2big_truncated_prefix),
|
||||||
|
cmocka_unit_test(test_scroll_free_nulls_state),
|
||||||
|
cmocka_unit_test(test_ltr_first_frame_trailing),
|
||||||
|
cmocka_unit_test(test_scroll_free_defensive_null),
|
||||||
|
};
|
||||||
|
|
||||||
|
cmocka_set_message_output(CM_OUTPUT_TAP);
|
||||||
|
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue