Repository navigation
Conversation
jserv
commented
Oct 19, 2026
jserv
commented
Oct 19, 2026
jserv
commented
Oct 19, 2026
jserv
force-pushed
the
fix
branch
2 times, most recently
from
October 19, 2026 21:25
063160d to
ceb5489
Compare
jserv
force-pushed
the
fix
branch
2 times, most recently
from
October 19, 2026 22:42
d2e228a to
af50b25
Compare
On macOS PNG colors came out wrong because an Apple-only pass swizzled pixels twice; libpng now writes native ARGB, and one row-size check replaces depth checks that read the IHDR from before the transforms, so 16-bit and sub-byte PNGs load. JPEG keeps its pixmap valid across longjmp() and fixes its A8 path. TinyVG checks color indices, allocation sizes and varuint widths, which crafted files overran. GIF frames decode into the pixmap the loader returns instead of one pixmap per frame, cutting nyancat.gif from about 880 KB to 88 KB, with real transparency and browser rules for disposal, loop counts and short delays. Interlaced frames two to four rows tall, clear codes at a code-size bump and frames past the canvas no longer corrupt the picture. An animation damages its pixmap when it draws a frame.
A shadow was a blurred copy of the pixmap's bounding box, so the empty corner beside a title tab got one and see-through windows showed their own. Up to two rectangles now cast it: a window's client while it is opaque, and the desktop title tab. The casting pixmap's alpha masks the shadow, so it shows only outside the window. The desktop lights windows from above, inactive ones keep a lighter shadow, and the geometry options depend on the desktop policy. Scan-out stays procedural; per shadow pixel the old geometry costs as before. The animation demo becomes see-through.
comic.tvg drew wedges of black, and tiger.tvg and flowchart.tvg stray discs, because their nested outlines cut holes by the even-odd rule while twin always filled nonzero. A path now carries a fill rule, set with twin_path_set_fill_rule() as part of its saved state, and the TinyVG loader selects even-odd. Strokes and every other path keep the nonzero default.
The window manager choice decides which other options apply, yet it sat in a submenu under Features. Only the menu order changes: defconfig and every CI configuration generate the same config.h.
The demo rendered every drawing at 400x400 and drew it at the corner, so under the mobile policy, which gives the window the 240x258 work area, only a corner of each picture showed; its widget also claimed three quarters of the screen height, which stretched the button and broke the layout on tall desktops. The image now takes the room the button leaves, renders to fit it when that size changes, and is centered; the button keeps its label's height. twin_widget_set_stretch() lets a widget keep its preferred size in a box, and twin_tvg_to_pixmap() returns a pixmap of the drawing's aspect within the size asked, so no margin is stored beside it.
The demo shows overlapping windows: under the mobile policy each one covers the last, and the phone listed a launcher icon that opened six stacked screens. It now depends on the desktop window manager, and its comments no longer describe the other policies.
Two calculators did the same job: the desktop one stacked integers in ellipse buttons whose rectangular bevels left wedges between rows, and the phone kept its own decimal engine and keypad. apps/calc.c is now the only one. Both draw it into a twin window, so the keypad, the arithmetic, painting and input are shared; a front end supplies only the window and a palette. The desktop gets flat round keys colored by role, a fitted display and keyboard input; the phone keeps its theme. The keypad gains an erase key, also on Backspace and Delete, which drops the last character typed. The phone lists the shared app once: its launcher row names the desktop entry it replaces.
jserv
force-pushed
the
fix
branch
2 times, most recently
from
October 20, 2026 06:35
c213654 to
044dc49
Compare
The phone built every app it has. A "Phone apps" menu now selects them one by one; call log needs the dialer, and contacts needs the dialer and messages, which it opens. Settings stays, as it owns the theme and wallpaper. The tests follow the selection: each suite has an empty stand-in when its app is not built, the calculator is tapped wherever its icon lands, and the golden file records the launcher list, so frames recorded for one selection are not compared against another.
Snake, Pong and Tetris were phone apps only, although nothing in them needs the phone shell. They move to apps/ as their own demos, each a kit view in a window of its own on the desktop or under the plain policies, and still the themed launcher apps on the phone. Without the shell every app starts at boot, so these windows open paused until tapped rather than playing unasked. The UI kit no longer reaches into the shell: views open on a screen the shell attaches or on one given to ui_view_new_at(), the theme table lives in the kit, and a covered view is judged by the window manager's policy. A view wider than the phone layout, as a maximizing policy makes it, shows the theme background beside it. Each game owns its ops table, opener and launcher icon, so games.c no longer knows them.
Paths of more than 48 edges are ordered with qsort, compared by top row alone. qsort need not be stable, and the order of edges starting on one row decides the coverage of coincident spans, so glibc and the BSD libc of macOS rasterized a few glyphs and icons differently. Ties now keep their place in the edge array, the order the insertion sort for short paths already gives, so a frame is the same on every host.
The recorded frames carried a build fingerprint that no longer matched any configuration, so every cellphone comparison was skipped. Record them again for the default mobile build, with the shared calculator and the launcher list the phone now shows. With the edge sort fixed they hold on Linux and macOS alike.
CI compares two fresh demo-headless runs captured about 3 s in and allowed 100 differing pixels, yet the Animation demo's GIF showed through a gap between windows and its frame depended on when each run started, so the check failed about one run in three. MADO_FROZEN_TIME=1 now stops twin's clock at startup in the headless backend: no timeout comes due, so every animation keeps its first frame, while the control tool is still served by the poll loop. The Clock reads wall time on its own and shows a fixed time under the same variable. The CI step sets it and requires identical frames.
"Build demo applications" gated every application behind one switch, and the menu still called them demos although they are the applications Mado ships. The menu is now "Applications": each one is chosen on its own, and APPS, true when any is picked, builds the application library, the launcher and its tests. The phone shell is a menuconfig holding its own options and apps, the games have their own group, and every help text says what the application does. The DEMO_* symbols become APP_*. A configuration written before this change no longer sets them, so its applications fall back to the defaults until they are picked again.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two commits.
Fix image loader corruption and decode GIF frames on demand
pixis volatile acrosssetjmp(), the A8 path copies pixels instead of row pointers, and a dead branch is removed.nyancat.gifdrops from about 880 KB to 64 KB. Transparency is real alpha (it used to be a fake opaque checkerboard), disposal follows browser behavior, interlaced frames 2 to 4 rows tall are fixed, an LZW clear at a code-size bump no longer desyncs, off-canvas frames are clipped, and GIF87a is accepted.Cast window shadows from the frame and mask them by window alpha
Validation
make checkpasses: core 337, wm 74, lifetime 21, demos 34. Commit 1 was also built and tested on its own.Note
tests/golden/cellphone.txtneedsmake check-updateon the reference platform. Its stored fingerprint no longer matches, so cellphone golden comparisons are currently skipped. On this macOS host, 11 of its frames already differed ataa36aec.Summary by cubic
Fixes corrupted rendering in the PNG, JPEG, and TinyVG loaders, decodes GIF frames on demand, and reworks window shadows to cast from the window's shape and mask by its alpha. The window manager and every application are now selected individually in Kconfig under top-level menus.
Image loaders
setjmp(), and the A8 path copies pixels instead of row pointers.twin_path_set_fill_rule()), so nested outlines incomic.tvg,tiger.tvg, andflowchart.tvgno longer paint black wedges or stray discs.nyancat.gifdrops from about 880 KB to 88 KB. Transparency is real alpha, disposal follows browser behavior, interlaced and off-canvas frames are fixed, and GIF87a is accepted.twin_widget_set_stretch()keeps the button at its preferred height.Windows and demos
DEMO_*becameAPP_*), so configs written before the rename fall back to defaults until their apps are chosen again. Golden frames record the launcher list so comparisons are skipped for other selections, and deterministic edge sorting makes the refreshed frames match on Linux and macOS alike.MADO_FROZEN_TIME=1, which stops twin's clock so every animation keeps its first frame, and now requires identical renders.Written for commit 875a4a6. Summary will update on new commits.