<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Wwumin</title>
    <description>The latest articles on DEV Community by Wwumin (@wwumin).</description>
    <link>https://dev.to/wwumin</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4206828%2F6169a231-6ae7-4aff-8518-5b328ef0b7e4.png</url>
      <title>DEV Community: Wwumin</title>
      <link>https://dev.to/wwumin</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wwumin"/>
    <language>en</language>
    <item>
      <title>Testing an embedded GUI without the board: 46 scripted taps and one make</title>
      <dc:creator>Wwumin</dc:creator>
      <pubDate>Sun, 25 Oct 2026 15:43:30 +0000</pubDate>
      <link>https://dev.to/wwumin/testing-an-embedded-gui-without-the-board-46-scripted-taps-and-one-make-343o</link>
      <guid>https://dev.to/wwumin/testing-an-embedded-gui-without-the-board-46-scripted-taps-and-one-make-343o</guid>
      <description>&lt;p&gt;One PC, one &lt;code&gt;make -C test run&lt;/code&gt;, 46 scripted touches: the real state machine of a&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;1024×600 DRM-direct photo frame, driven from beginning to end. Not an emulator, not&lt;br&gt;
screenshot diffing — the same C sources that run on the board, with only the display&lt;br&gt;
and the touch panel replaced by stubs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The loop everyone knows
&lt;/h2&gt;

&lt;p&gt;If you build embedded GUIs you know the cycle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;change one line of layout code;&lt;/li&gt;
&lt;li&gt;cross-compile;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scp&lt;/code&gt; it to the board (or a USB stick, or a SD card);&lt;/li&gt;
&lt;li&gt;reboot, watch the screen, poke it with a finger;&lt;/li&gt;
&lt;li&gt;discover a button is 3 pixels off. Back to step 1.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Five minutes per iteration — and &lt;strong&gt;nobody else can verify it for you&lt;/strong&gt;. Code review&lt;br&gt;
cannot tell whether a tap really lands on a button; only human eyes can. And the moment&lt;br&gt;
you try to put "regression" into CI, you find the whole program is welded to &lt;code&gt;drm&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;linux/input.h&lt;/code&gt; and a vendor &lt;code&gt;.so&lt;/code&gt;; it will not even compile without the board.&lt;/p&gt;

&lt;p&gt;That is how this project started: a photo album for a Rockchip RK1808 board, DRM-direct&lt;br&gt;
1024×600, password lock, slideshow, six mini games, plain C11, no Qt / X11 / Wayland /&lt;br&gt;
libpng / libjpeg. I split "verify on the board" into three layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;logic&lt;/strong&gt; — pure functions and state machines, millisecond unit tests on the PC (41 &lt;code&gt;CHECK&lt;/code&gt;s);&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;whole app&lt;/strong&gt; — display and touch replaced by stubs, so the &lt;em&gt;real&lt;/em&gt; state machine runs
through 46 scripted taps on the PC;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;art&lt;/strong&gt; — every bitmap is regenerated by Python scripts, reproducible at any time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result: &lt;code&gt;make -C test run&lt;/code&gt; finishes in 31 seconds on Windows (exit 0, last line&lt;br&gt;
&lt;code&gt;[main] 清理退出&lt;/code&gt;), and CI on &lt;code&gt;ubuntu-latest&lt;/code&gt; runs the whole chain &lt;strong&gt;with no board and no&lt;br&gt;
display&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  The idea: replace implementations, not the code under test
&lt;/h2&gt;

&lt;p&gt;Most "embedded unit test" attempts fail because they mock away the thing they are meant&lt;br&gt;
to test. I did the opposite: &lt;strong&gt;don't touch the application, replace the three real-world&lt;br&gt;
entry points&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. DRM → a &lt;code&gt;malloc&lt;/code&gt;'d framebuffer
&lt;/h3&gt;

&lt;p&gt;The real &lt;code&gt;DRMshowUp()&lt;/code&gt; is &lt;code&gt;drmModePageFlip()&lt;/code&gt; plus a &lt;code&gt;poll()&lt;/code&gt; for vblank. The stub just&lt;br&gt;
writes the BGRA buffer out as a 24-bit BMP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cm"&gt;/* test/stub/host/drm_stub.c */&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;uint8_t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;g_buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="cm"&gt;/* plain memory as "video memory" */&lt;/span&gt;
&lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;g_frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;DRMshowUp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;drmHandle&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;drm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/* every 20th frame lands in out/frame_XXXX.bmp for eyeballing */&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="n"&gt;g_frame&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;write_bmp24&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g_buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pitch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The drawing code only requires "a writable framebuffer + a page flip", so the stub fools&lt;br&gt;
all of it. Bonus: &lt;strong&gt;the exported frames are the real render&lt;/strong&gt;, so a layout regression is&lt;br&gt;
one glance away.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Touch → a script file
&lt;/h3&gt;

&lt;p&gt;Real input is &lt;code&gt;EV_ABS&lt;/code&gt; events from &lt;code&gt;/dev/input/eventN&lt;/code&gt;, with press and release arriving&lt;br&gt;
as separate &lt;code&gt;SYN&lt;/code&gt; packets. The stub turns each line of &lt;code&gt;clicks.txt&lt;/code&gt; into "press, hold for&lt;br&gt;
2 polls, release":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# x   y   hold ms after press
378 303 300     ← tap "Album" on the main menu
518 303 0
658 303 0
378 387 0
176 227 500
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is &lt;strong&gt;46 actions&lt;/strong&gt;: enter the password, browse the album, go back, open the game menu,&lt;br&gt;
play a few moves of 2048 / Tetris / Snake / Tic-Tac-Toe / Minesweeper / Xiangqi, then&lt;br&gt;
exit. When the script ends the stub quits the process (&lt;code&gt;g_script_done_quit = 1&lt;/code&gt;), which is&lt;br&gt;
what makes this a smoke test rather than an infinite loop.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Headers → a shadow include directory
&lt;/h3&gt;

&lt;p&gt;The real code does &lt;code&gt;#include "DRMwrap.h"&lt;/code&gt; for the vendor SDK. The test build puts &lt;code&gt;-Istub&lt;/code&gt;&lt;br&gt;
first, where a shadow header with the same name lives, plus a minimal &lt;code&gt;linux/input.h&lt;/code&gt; that&lt;br&gt;
only defines &lt;code&gt;struct input_event&lt;/code&gt;. &lt;strong&gt;No cross toolchain needed:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nv"&gt;CFLAGS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nt"&gt;-std&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;c11 &lt;span class="nt"&gt;-Wall&lt;/span&gt; &lt;span class="nt"&gt;-Wextra&lt;/span&gt; &lt;span class="nt"&gt;-O1&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
         &lt;span class="nt"&gt;-I&lt;/span&gt;../album &lt;span class="nt"&gt;-Istub&lt;/span&gt;/host &lt;span class="nt"&gt;-Istub&lt;/span&gt; &lt;span class="nt"&gt;-I&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No environment variables either: &lt;code&gt;display.c&lt;/code&gt; picks the stub path via &lt;code&gt;#if DRM_STUB_BUILD&lt;/code&gt;,&lt;br&gt;
and the touch stub takes care of shutting down.&lt;/p&gt;
&lt;h2&gt;
  
  
  Three test layers, two orders of magnitude apart
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight make"&gt;&lt;code&gt;&lt;span class="nl"&gt;make -C test unit    # seconds&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;2048 rules + touch-device detection&lt;/span&gt;
&lt;span class="nl"&gt;make -C test run     # 31 s&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;the whole state machine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nf"&gt; 46 taps&lt;/span&gt;
&lt;span class="err"&gt;make&lt;/span&gt; &lt;span class="err"&gt;-C&lt;/span&gt; &lt;span class="err"&gt;test&lt;/span&gt; &lt;span class="err"&gt;check&lt;/span&gt;   &lt;span class="c"&gt;# unit first, then the full app
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;unit_2048&lt;/code&gt; — merge / move / spawn rules of 2048, pure functions, 40+ &lt;code&gt;CHECK&lt;/code&gt; assertions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;unit_detect&lt;/code&gt; — touch device detection: pick the touchscreen out of &lt;code&gt;/proc/bus/input/devices&lt;/code&gt;
text. Drivers are named whatever the vendor felt like, so it is detected at runtime, not
hard-coded.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;album_test&lt;/code&gt; — full smoke test: &lt;code&gt;album_test ../bmp /dev/null&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The value of the split is &lt;strong&gt;localisation&lt;/strong&gt;: a unit failure is a logic bug, a full-app&lt;br&gt;
failure is a flow or layout bug. Both CI failures below were pinned to a single step&lt;br&gt;
immediately.&lt;/p&gt;
&lt;h2&gt;
  
  
  Art assets must be reproducible too
&lt;/h2&gt;

&lt;p&gt;There is not one binary asset that needs licensing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;24 C files, 5,578 lines;&lt;/li&gt;
&lt;li&gt;11 Python scripts, 2,379 lines (backgrounds, buttons, chess pieces, tetrominoes, snake,
landscapes, bitmap font, BMP → C array);&lt;/li&gt;
&lt;li&gt;158 BMPs generated on demand by &lt;code&gt;make assets&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;whole repo: 77 files, ~2.5 MB.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One detail: button sprites need real transparency (&lt;code&gt;A=0&lt;/code&gt;) in the rounded corners, and&lt;br&gt;
24-bit BMP has no alpha channel at all. So the generator writes hand-made &lt;strong&gt;32-bit BMP&lt;br&gt;
headers&lt;/strong&gt; and stores every pixel as BGRA. Where you would normally reach for a library,&lt;br&gt;
hand-writing the format was the more controllable option.&lt;/p&gt;
&lt;h2&gt;
  
  
  Pushing it into CI — and getting slapped by Linux twice
&lt;/h2&gt;

&lt;p&gt;The workflow is deliberately boring (&lt;code&gt;.github/workflows/ci.yml&lt;/code&gt;, ubuntu-latest):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;gcc --version&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;install a CJK font (the generator renders Chinese labels)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;make -C test unit&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pip install pillow &amp;amp;&amp;amp; make assets&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;count the generated BMPs, fail under 150&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;make -C test run&lt;/code&gt; — 46 taps, real state machine&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;First run: the unit tests failed.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unit_detect: [FAIL] 位掩码数组长度 1 够装下 ABS 位
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My machine (MinGW) has a 32-bit &lt;code&gt;unsigned long&lt;/code&gt;, so I asserted &lt;code&gt;N_ABS &amp;gt;= 2&lt;/code&gt; for a 64-bit&lt;br&gt;
bitmask (&lt;code&gt;ABS_MAX = 0x3f&lt;/code&gt;). On 64-bit Linux, one &lt;code&gt;unsigned long&lt;/code&gt; already holds 64 bits —&lt;br&gt;
&lt;strong&gt;one is enough&lt;/strong&gt;. The fix is not to change the number but to assert on &lt;em&gt;capacity&lt;/em&gt; instead&lt;br&gt;
of word width:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;CHECK&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;N_ABS&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;ABS_MAX&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s"&gt;"bitmask capacity %d bit holds ABS_MAX+1 = %d bit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Second run: asset generation failed.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OSError: cannot open resource
  File "tools/gen_assets.py", line 93, ... F(FONT_BOLD, 46) ...
  File "tools/gen_assets.py", line 29, FONT_BOLD = r"C:\Windows\Fonts\msyhbd.ttc"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes — hard-coded &lt;code&gt;C:\Windows\Fonts&lt;/code&gt;. The fix was &lt;code&gt;tools/fontkit.py&lt;/code&gt;: resolve a CJK font per&lt;br&gt;
platform (Microsoft YaHei / SimHei / DengXian on Windows → Noto Sans CJK on Linux →&lt;br&gt;
DejaVu), allow the &lt;code&gt;ALBUM_FONT&lt;/code&gt; env var to override, and fall back to Pillow's built-in&lt;br&gt;
bitmap font — &lt;strong&gt;degrade instead of crash&lt;/strong&gt;. CI gained one line:&lt;br&gt;
&lt;code&gt;sudo apt-get install -y fonts-noto-cjk&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Third run: green.&lt;/strong&gt; Every push to &lt;code&gt;main&lt;/code&gt; now runs the chain, and the badge sits on top&lt;br&gt;
of the README.&lt;/p&gt;

&lt;p&gt;Those two failures are the most valuable part of the story. I thought "works on my&lt;br&gt;
machine" meant it works; in reality &lt;strong&gt;my machine was covering for two platform&lt;br&gt;
assumptions&lt;/strong&gt; — the width of &lt;code&gt;long&lt;/code&gt;, and the location of a font. That is exactly why you&lt;br&gt;
run a hardware-free test suite in CI: it is a clean box with none of your environment,&lt;br&gt;
there specifically to slap you.&lt;/p&gt;
&lt;h2&gt;
  
  
  What it buys you
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;before&lt;/th&gt;
&lt;th&gt;after&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;see a layout change&lt;/td&gt;
&lt;td&gt;cross-compile + flash + eyeball, ~5 min&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;make -C test run&lt;/code&gt;, 31 s + exported frames&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;logic regression&lt;/td&gt;
&lt;td&gt;tap through it by hand&lt;/td&gt;
&lt;td&gt;1-second unit run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;can a stranger verify it?&lt;/td&gt;
&lt;td&gt;no (needs the board)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;git clone &amp;amp;&amp;amp; make -C test unit&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;regression on every push&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;46 taps automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;And none of it is specific to this board. DRM, evdev and the framebuffer look the same on&lt;br&gt;
RK1808 / RK3568 / Allwinner / Raspberry Pi. The only things you actually need to replace&lt;br&gt;
are display, input, and vendor headers.&lt;/p&gt;
&lt;h2&gt;
  
  
  Five things the stubs do &lt;em&gt;not&lt;/em&gt; cover
&lt;/h2&gt;

&lt;p&gt;To be honest: this guarantees that &lt;strong&gt;the state machine is reachable and the layout does&lt;br&gt;
not regress&lt;/strong&gt; — not that the result is pixel-identical to the board. Five real-world&lt;br&gt;
differences stay outside the stubs, all documented in the README:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;page-flip timing&lt;/strong&gt; — the real path waits for vblank, and single-buffered transitions
can tear on fast refreshes; the stub has no notion of time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;touch coordinate mapping&lt;/strong&gt; — the stub assumes 1024×600 logical coordinates; the real
driver may rotate, scale, or use a different range, which is why &lt;code&gt;touch.c&lt;/code&gt; normalises.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;vendor init path&lt;/strong&gt; — &lt;code&gt;DRMinit&lt;/code&gt; / &lt;code&gt;DRMcreateFB&lt;/code&gt; from &lt;code&gt;DRMwrap&lt;/code&gt; only really execute on
the board; the stub provides same-signature empty implementations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pixel format&lt;/strong&gt; — the board is 32 bpp XRGB8888, the stub writes BGRA BMP; channel
order matches, alpha handling does not (board ignores it, stub keeps it).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;timing-sensitive input&lt;/strong&gt; — long press, double tap and fast repeated taps are modelled
with &lt;code&gt;g_hold_ticks&lt;/code&gt;; the real event rate belongs to the driver, so these behaviours are
"reachable", not "feel identical".&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The mitigation is plain: &lt;strong&gt;look at the exported frames before flashing, then spot-check&lt;br&gt;
three screens on the board&lt;/strong&gt; (main menu, album, game menu). The stub is responsible for&lt;br&gt;
"no regressions"; the board is responsible for "actually looks right".&lt;/p&gt;
&lt;h2&gt;
  
  
  Repo
&lt;/h2&gt;

&lt;p&gt;Full sources, the 46-tap script and the CI config (MIT):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/Wwumin/rk1808-photo-album" rel="noopener noreferrer"&gt;https://github.com/Wwumin/rk1808-photo-album&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Wwumin/rk1808-photo-album
&lt;span class="nb"&gt;cd &lt;/span&gt;rk1808-photo-album
make &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="nb"&gt;test &lt;/span&gt;unit     &lt;span class="c"&gt;# logic regression, ~1 s&lt;/span&gt;
make assets           &lt;span class="c"&gt;# regenerate 158 bitmaps (needs Pillow)&lt;/span&gt;
make &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="nb"&gt;test &lt;/span&gt;run      &lt;span class="c"&gt;# 46-tap smoke test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No board is needed for the PC path; only the on-board path needs the vendor&lt;br&gt;
&lt;code&gt;libDRMwrap.so&lt;/code&gt; (see README → &lt;em&gt;Building for the board&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;If you also build embedded GUIs while the board is somewhere else, I would love to hear&lt;br&gt;
you — especially on the question of &lt;strong&gt;how real a stub should be&lt;/strong&gt;: too fake and you fool&lt;br&gt;
yourself, too real and you are rewriting the driver.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Project: RK1808 1024×600 DRM-direct photo frame, six mini games, C11. 77 files, 2.5 MB.&lt;br&gt;
CI green on run #40169371435.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>c</category>
      <category>testing</category>
      <category>githubactions</category>
      <category>embedded</category>
    </item>
  </channel>
</rss>
