From 1c48299038c54c4314aae29b6f74e85f8e0c6132 Mon Sep 17 00:00:00 2001 From: lahiru Date: Tue, 7 Jul 2026 14:15:11 +0530 Subject: [PATCH 1/2] feat(examples): add Matrix digital-rain SDL2 app Port of the Phoebe LVGL app_matrix to a standalone 320x170 SDL2 program. Falling green glyph columns with bright heads and fading trails on a dark green base. No network or sensors. Renders in the desktop emulator (X11) and on-device via the Wayland/KMSDRM launcher wrapper. Keys: ESC / Q to quit. --- examples/SDL2_Matrix/.gitignore | 1 + examples/SDL2_Matrix/Makefile | 18 +++ examples/SDL2_Matrix/main.c | 156 ++++++++++++++++++++++ examples/SDL2_Matrix/packaging/build.sh | 3 + examples/SDL2_Matrix/packaging/ci-deps.sh | 3 + examples/SDL2_Matrix/packaging/meta.env | 9 ++ examples/SDL2_Matrix/packaging/stage.sh | 53 ++++++++ 7 files changed, 243 insertions(+) create mode 100644 examples/SDL2_Matrix/.gitignore create mode 100644 examples/SDL2_Matrix/Makefile create mode 100644 examples/SDL2_Matrix/main.c create mode 100755 examples/SDL2_Matrix/packaging/build.sh create mode 100755 examples/SDL2_Matrix/packaging/ci-deps.sh create mode 100644 examples/SDL2_Matrix/packaging/meta.env create mode 100755 examples/SDL2_Matrix/packaging/stage.sh diff --git a/examples/SDL2_Matrix/.gitignore b/examples/SDL2_Matrix/.gitignore new file mode 100644 index 0000000..f5f6bec --- /dev/null +++ b/examples/SDL2_Matrix/.gitignore @@ -0,0 +1 @@ +matrix diff --git a/examples/SDL2_Matrix/Makefile b/examples/SDL2_Matrix/Makefile new file mode 100644 index 0000000..6704df1 --- /dev/null +++ b/examples/SDL2_Matrix/Makefile @@ -0,0 +1,18 @@ +CC ?= gcc +CFLAGS ?= -std=c11 -Wall -Wextra -O2 +PKGS := sdl2 SDL2_ttf +PKG_CFLAGS := $(shell pkg-config --cflags $(PKGS)) +PKG_LIBS := $(shell pkg-config --libs $(PKGS)) + +TARGET := matrix +SRC := main.c + +all: $(TARGET) + +$(TARGET): $(SRC) + $(CC) $(CFLAGS) $(PKG_CFLAGS) $(SRC) -o $(TARGET) $(PKG_LIBS) -lm + +clean: + rm -f $(TARGET) + +.PHONY: all clean diff --git a/examples/SDL2_Matrix/main.c b/examples/SDL2_Matrix/main.c new file mode 100644 index 0000000..4aeb8bd --- /dev/null +++ b/examples/SDL2_Matrix/main.c @@ -0,0 +1,156 @@ +/* + * Matrix digital rain for CardputerZero (320x170, SDL2). + * + * Ported from the Phoebe LVGL app (app_matrix). Falling columns of green + * glyphs on a dark base -- pure eye-candy, no network or sensors. Renders in + * the desktop emulator window and on-device via the KMSDRM/Wayland backend. + * + * Keys: ESC / Q quit. + */ +#include +#include +#include +#include +#include + +#define SCREEN_W 320 +#define SCREEN_H 170 +#define TICK_MS 16 + +#define FONT_PATH_1 "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf" +#define FONT_PATH_2 "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" + +#define GLYPH_W 10 /* column pitch in px */ +#define GLYPH_H 14 /* row pitch in px */ +#define COLS (SCREEN_W / GLYPH_W) +#define ROWS (SCREEN_H / GLYPH_H + 2) + +static const char CHARSET[] = + "0123456789ABCDEFGHJKLMNPQRSTUVWXYZ#$%&*+=<>?"; +static const int CHARSET_N = (int)sizeof(CHARSET) - 1; + +/* xorshift32 -- same generator the original app used. */ +static uint32_t rng_state = 0x1234abcdu; +static uint32_t xrand(void) { + uint32_t x = rng_state; + x ^= x << 13; x ^= x >> 17; x ^= x << 5; + rng_state = x; + return x; +} + +typedef struct { + float head; /* y of the leading glyph, in rows (may be negative) */ + float speed; /* rows per tick */ + int len; /* trail length in rows */ + char glyph[ROWS + 4]; +} Column; + +static Column cols[COLS]; + +static void column_reset(Column *c, int seed_above) { + c->head = seed_above ? -(float)(xrand() % (ROWS * 2)) : 0.0f; + c->speed = 0.15f + (xrand() % 100) / 300.0f; /* 0.15 .. ~0.48 rows/tick */ + c->len = 5 + (int)(xrand() % (ROWS - 4)); + for (int i = 0; i < ROWS + 4; i++) + c->glyph[i] = CHARSET[xrand() % CHARSET_N]; +} + +static void draw_glyph(SDL_Renderer *ren, TTF_Font *font, char ch, + int px, int py, SDL_Color c) { + if (!font) return; + char s[2] = { ch, 0 }; + SDL_Surface *surf = TTF_RenderUTF8_Blended(font, s, c); + if (!surf) return; + SDL_Texture *tex = SDL_CreateTextureFromSurface(ren, surf); + SDL_Rect dst = { px, py, surf->w, surf->h }; + SDL_FreeSurface(surf); + if (!tex) return; + SDL_RenderCopy(ren, tex, NULL, &dst); + SDL_DestroyTexture(tex); +} + +int main(int argc, char **argv) { + (void)argc; (void)argv; + + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) { + fprintf(stderr, "SDL_Init: %s\n", SDL_GetError()); + return 1; + } + if (TTF_Init() != 0) { + fprintf(stderr, "TTF_Init: %s\n", TTF_GetError()); + SDL_Quit(); + return 1; + } + + SDL_Window *win = SDL_CreateWindow("Matrix", + SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, + SCREEN_W, SCREEN_H, SDL_WINDOW_BORDERLESS); + if (!win) { fprintf(stderr, "SDL_CreateWindow: %s\n", SDL_GetError()); + TTF_Quit(); SDL_Quit(); return 1; } + + SDL_Renderer *ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_SOFTWARE); + if (!ren) { fprintf(stderr, "SDL_CreateRenderer: %s\n", SDL_GetError()); + SDL_DestroyWindow(win); TTF_Quit(); SDL_Quit(); return 1; } + + TTF_Font *font = TTF_OpenFont(FONT_PATH_1, 12); + if (!font) font = TTF_OpenFont(FONT_PATH_2, 12); + if (!font) fprintf(stderr, "TTF_OpenFont: %s\n", TTF_GetError()); + + for (int i = 0; i < COLS; i++) column_reset(&cols[i], 1); + + int running = 1; + while (running) { + Uint32 now = SDL_GetTicks(); + SDL_Event ev; + while (SDL_PollEvent(&ev)) { + if (ev.type == SDL_QUIT) running = 0; + else if (ev.type == SDL_KEYDOWN) { + SDL_Keycode k = ev.key.keysym.sym; + if (k == SDLK_ESCAPE || k == SDLK_q) running = 0; + } + } + + /* advance + draw */ + SDL_SetRenderDrawColor(ren, 0x00, 0x10, 0x05, 255); /* dark green base */ + SDL_RenderClear(ren); + + for (int i = 0; i < COLS; i++) { + Column *c = &cols[i]; + c->head += c->speed; + /* occasionally mutate a glyph so columns shimmer */ + if ((xrand() & 0x1f) == 0) + c->glyph[xrand() % (ROWS + 4)] = CHARSET[xrand() % CHARSET_N]; + + int px = i * GLYPH_W + 1; + int head_row = (int)c->head; + for (int t = 0; t < c->len; t++) { + int row = head_row - t; + if (row < 0 || row >= ROWS) continue; + SDL_Color col; + if (t == 0) { + col = (SDL_Color){ 0xCC, 0xFF, 0xCC, 255 }; /* bright head */ + } else { + int fade = 255 - (t * 220 / c->len); + if (fade < 30) fade = 30; + col = (SDL_Color){ 0x22, (Uint8)(0x88 + fade / 4), + 0x44, (Uint8)fade }; + } + char ch = c->glyph[(row + i) % (ROWS + 4)]; + draw_glyph(ren, font, ch, px, row * GLYPH_H, col); + } + if (head_row - c->len > ROWS) column_reset(c, 0); + } + + SDL_RenderPresent(ren); + + Uint32 elapsed = SDL_GetTicks() - now; + if (elapsed < TICK_MS) SDL_Delay(TICK_MS - elapsed); + } + + if (font) TTF_CloseFont(font); + SDL_DestroyRenderer(ren); + SDL_DestroyWindow(win); + TTF_Quit(); + SDL_Quit(); + return 0; +} diff --git a/examples/SDL2_Matrix/packaging/build.sh b/examples/SDL2_Matrix/packaging/build.sh new file mode 100755 index 0000000..68fbf72 --- /dev/null +++ b/examples/SDL2_Matrix/packaging/build.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +set -euo pipefail +make diff --git a/examples/SDL2_Matrix/packaging/ci-deps.sh b/examples/SDL2_Matrix/packaging/ci-deps.sh new file mode 100755 index 0000000..b0c7dfe --- /dev/null +++ b/examples/SDL2_Matrix/packaging/ci-deps.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +set -euo pipefail +apt-get install -y libsdl2-dev libsdl2-ttf-dev diff --git a/examples/SDL2_Matrix/packaging/meta.env b/examples/SDL2_Matrix/packaging/meta.env new file mode 100644 index 0000000..9f02c06 --- /dev/null +++ b/examples/SDL2_Matrix/packaging/meta.env @@ -0,0 +1,9 @@ +PKG_NAME=sdl2-matrix +PKG_VERSION=0.1 +PKG_REVISION=m5stack1 +PKG_DESC="Matrix digital-rain screensaver for CardputerZero (SDL2)" +PKG_DEPENDS="libsdl2-2.0-0, libsdl2-ttf-2.0-0, libwayland-client0, fonts-dejavu-core" +APP_NAME="Matrix" +APP_EXEC=/usr/share/APPLaunch/bin/sdl2-matrix +APP_TERMINAL=false +APP_ICON=share/images/sdl2-matrix.png diff --git a/examples/SDL2_Matrix/packaging/stage.sh b/examples/SDL2_Matrix/packaging/stage.sh new file mode 100755 index 0000000..2501a27 --- /dev/null +++ b/examples/SDL2_Matrix/packaging/stage.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +set -euo pipefail + +install -D -m 0755 matrix "$STAGE$APP_INSTALL_DIR/matrix" + +# Wayland/KMSDRM launcher wrapper: CM0 runs a labwc Wayland compositor that +# owns /dev/dri/card0, so probe for a live Wayland socket first, then fall +# back kmsdrm -> offscreen. See SDL2_HelloWorld for the rationale. +cat >"$STAGE$INSTALL_PREFIX/bin/$PKG_NAME" <"\$LOG" 2>/dev/null || LOG=/dev/null + +if [ -z "\${XDG_RUNTIME_DIR:-}" ]; then + _uid=\$(id -u 2>/dev/null || echo 1000) + if [ -d "/run/user/\$_uid" ]; then + XDG_RUNTIME_DIR="/run/user/\$_uid" + elif [ -d "/run/user/1000" ]; then + XDG_RUNTIME_DIR="/run/user/1000" + fi + [ -n "\$XDG_RUNTIME_DIR" ] && export XDG_RUNTIME_DIR +fi + +_wl_ok=0 +if [ -n "\${WAYLAND_DISPLAY:-}" ] && [ -n "\${XDG_RUNTIME_DIR:-}" ] && \\ + [ -S "\$XDG_RUNTIME_DIR/\$WAYLAND_DISPLAY" ]; then + _wl_ok=1 +elif [ -n "\${XDG_RUNTIME_DIR:-}" ]; then + for _c in wayland-0 wayland-1; do + if [ -S "\$XDG_RUNTIME_DIR/\$_c" ]; then + WAYLAND_DISPLAY=\$_c + export WAYLAND_DISPLAY + _wl_ok=1 + break + fi + done +fi + +if [ -z "\${SDL_VIDEODRIVER:-}" ]; then + if [ "\$_wl_ok" = 1 ]; then + SDL_VIDEODRIVER=wayland + elif [ -e /dev/dri/card0 ]; then + SDL_VIDEODRIVER=kmsdrm + else + SDL_VIDEODRIVER=offscreen + fi + export SDL_VIDEODRIVER +fi + +echo "[$PKG_NAME] driver=\$SDL_VIDEODRIVER WAYLAND_DISPLAY=\${WAYLAND_DISPLAY:-} XDG_RUNTIME_DIR=\${XDG_RUNTIME_DIR:-} uid=\$(id -u)" >>"\$LOG" 2>&1 +exec $APP_INSTALL_DIR/matrix "\$@" >>"\$LOG" 2>&1 +EOF +chmod 0755 "$STAGE$INSTALL_PREFIX/bin/$PKG_NAME" From 3632eda4fb9c6d0108d21d6f9181968a8af37eb0 Mon Sep 17 00:00:00 2001 From: lahiru Date: Tue, 7 Jul 2026 15:40:35 +0530 Subject: [PATCH 2/2] feat(examples): add app icon (48x48) for SDL2_Matrix --- examples/SDL2_Matrix/packaging/icon.png | Bin 0 -> 1045 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/SDL2_Matrix/packaging/icon.png diff --git a/examples/SDL2_Matrix/packaging/icon.png b/examples/SDL2_Matrix/packaging/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b9514c85e478be500d0266ffb00a31709e3d403e GIT binary patch literal 1045 zcmV+w1nT>VP)aXaB0ea%R}uUJMQ|@V@Ik~E zLD-8rbPqvsT3r~p7b#q;EgMB>Hk&rvlD0Q(UDM>IjSuO)P12ilYjW2ltY5fr@A>8Y ze&6r;o!>bp+$$l%ZdX=l-!3FHsg%jwP%FfXVIrikp@?`^O?Ta8N?U^Dbw>&s@`P*% z$Rn>4-_)^6Fy*r@Vv~TZn(nF<;>9T7wl2T{)CGCSWx$x$>zo}`Wn=@SdKvwE%OcXr_^qaepxtgc}1fs1_u7I)w z+KrtWFo7sLwr6gq2L5kAyJ6Ho!BMq_QGDp!jv5fz?5zPk3spE)i$ohR|ZJx#xRL1{i!agd$Ysa4wqmo2E8g3Cy-^kr&@H5~k6=3}NUkH48*zgCe0Smy`__M=<>Dx)XPj}!yJHYCq z#?_ZXXtOIw4v7Qj4&yoKCHD0skx#}={aa2U-wFT5{mEYAJ~_G6qsYN4iX8MPa*)v{ zelTnkulF9F4yEDKuz$h@-{a!x+YAA|&X$I#m7v1kanu~R+7tcX2sp&h+ ze15GYKcN6^ZiPKZJ85Wf<8Hr?mccf(*%d3zR!9MV05o>g~R{fz(} zryqta{PW=`Yb&ee35Z{~fy3>j^@aWTj`turB;prtR4SkmcpPBi$}OWYqo$bpcnpA! zHv>2toy5Njv!u)loe2djjl@Y#F5)}xr}fx={{3@~<%vWka+sMS;@^f* zL%D(SpBs>)8hdMCsD@H7Aqyx4>9grDaAy`*n0GyPd^$y7s8 z)L-?bwZEh<)Az)6fr8hpb&L%Gf)6Tc4yK