Repository navigation
feat(windows): add image, video, and animated GIF capabilities to Windows Terminal — Sixel-rendered Gif mode, per-frame pre-encoding, configurable loop and speed, cell-size overrides, viewer mode for tall layouts, and improved color accuracy - #21
Merged
Merged
Conversation
pull updates from main branch
Replaces the Linux edition's single hardcoded placeholder logo with
a full per-distro ASCII art system, detected automatically from
/etc/os-release. Mirrors the existing three-way config-source split
(Dev / ReleaseSource / Production) added earlier for BinaryFetch_Config.
New files:
- include/core/linux_ascii_database.h
DistroArt { art, aliases } struct.
getDistroArtDatabase() — returns the full flat map.
getDistroArt(distroId) — key match -> alias scan -> "linux"
fallback. Total function, never empty/crashes.
- cpp/Linux/core/linux_ascii_database.cpp
Auto-generated (see tooling below). One flat
std::map<std::string, DistroArt>, alphabetical by key,
keyed on ID= from /etc/os-release. 528 distro entries.
Reuses the existing $N color-tag syntax already parsed by
processColorCodes() in AsciiArt.cpp — no new rendering path.
- Ascii_DataBase_Generator/ascii_art_database_generator.py
Interactive one-shot generator: prompts for a source markdown
file (## N. name headings + ```text fences) and an output
path, parses all entries, matches known aliases from an
internal lookup table (e.g. ubuntu -> ubuntu-desktop,
ubuntu-server), guarantees a "linux" fallback entry exists,
and emits a ready-to-compile linux_ascii_database.cpp.
Auto-detects and rewrites a directory-only output path to
<dir>/linux_ascii_database.cpp instead of failing.
Changed:
- cpp/Linux/core/AsciiArt.cpp
+ #include "core/linux_ascii_database.h", <algorithm>
+ readOSReleaseValue(key) — parses a single KEY=VALUE line
from /etc/os-release, strips quotes, lowercases.
+ splitIdLikeChain(raw) — tokenizes ID_LIKE= into an ordered
fallback chain.
+ resolveDistroArt() — direct ID= lookup (key or alias) ->
walk ID_LIKE= chain (same lookup) -> database's "linux"
entry. Kept in AsciiArt.cpp rather than the database itself
since getDistroArt() alone doesn't walk ID_LIKE.
- Removed kDefaultAsciiArt placeholder logo entirely.
* copyDefaultArt() now writes resolveDistroArt()'s result to
BinaryArt.txt; returns false with a stderr warning if the
database's "linux" fallback is somehow missing, instead of
silently writing a hardcoded placeholder.
* loadArtFromEmbedded() same fail-loud behavior on empty
resolveDistroArt() result.
loadFromFile(), loadArtFromPath(), getUserArtPath(),
ensureDirectoryExists(), and all width/UTF-8/color helpers
unchanged — distro resolution happens strictly upstream of
the existing load/parse pipeline.
- CMakeLists.txt
+ include/core/linux_ascii_database.h added to
BINARYFETCH_HEADERS (shared list; inert on Windows, no
Windows .cpp includes it).
+ cpp/Linux/core/linux_ascii_database.cpp added to the Linux
branch of BINARYFETCH_SOURCES only — never compiled into
the Windows target.
Behavior:
- First run (no ~/.config/binaryfetch/BinaryArt.txt): detects the
running distro via ID=, falls back through ID_LIKE= chain, then
the database's mandatory "linux" entry; writes the resolved art
to BinaryArt.txt (self-heal, same as before).
- Existing BinaryArt.txt is still authoritative and untouched —
distro detection only fires on first-time file creation, exactly
like the prior placeholder-logo self-heal did.
- Disk-write failure path (copyDefaultArt() fails) falls through
to loadArtFromEmbedded(), which now also resolves per-distro
art instead of parsing a hardcoded string.
- An empty/broken database (missing "linux" key) now fails loudly
via stderr warning + false return, rather than silently
rendering a fixed placeholder — deliberate for testability.
Tested: N/A
BREAKING: kDefaultAsciiArt removed from AsciiArt.cpp (Linux only).
Any code path assuming a guaranteed non-empty embedded fallback
must rely on the database's "linux" entry instead.
…dows
Linux builds were writing to ~/.config/binaryfetch/ (lowercase),
while the Windows build uses C:\Users\Public\BinaryFetch\
(PascalCase). Inconsistent naming across platforms made manual
testing error-prone: `rm -rf ~/.config/BinaryFetch` silently did
nothing on a case-sensitive filesystem because the real directory
was ~/.config/binaryfetch, leaving stale self-healed files (e.g.
BinaryArt.txt) in place across supposedly-clean test runs.
Changed getLinuxConfigDir() in both files that define it:
- cpp/Linux/core/AsciiArt.cpp
- cpp/Linux/core/config_management.cpp
"/binaryfetch" -> "/BinaryFetch" (XDG_CONFIG_HOME branch)
"/.config/binaryfetch" -> "/.config/BinaryFetch" (HOME branch)
Resulting paths:
~/.config/BinaryFetch/BinaryArt.txt
~/.config/BinaryFetch/BinaryFetch_Config.jsonc
No other logic changed — self-heal, .jsonc/.json resolution order,
and the embedded-default fallback all behave identically, just
under the renamed
- Add setVerboseLogging() to MemoryInfo to toggle [MemoryInfo] diagnostic output (dmidecode/SMBIOS fallback messages) - Defaults to off, so normal runs no longer print root/permission warnings to stderr - Debug output can still be re-enabled by calling setVerboseLogging(true) before fetching
… (gif in windows terminal)
Adds a third art mode alongside the existing ASCII Art and static
Image modes: animated GIFs, rendered frame-by-frame over Sixel,
looping in place after the info text has printed once.
New files:
- include/core/Gif.h
TerminalGif class — load()/drawFrame()/getFrameCount()/
getFrameDelayMs()/getRowSpan()/getColSpan()/padding and
cell-size overrides. Same public shape as TerminalImage,
but holds every decoded frame pre-encoded as Sixel rather
than a single image.
- cpp/windows/core/Gif.cpp
Decodes all frames in one stbi_load_gif_from_memory() call
(stb_image.h, already linked via Image.cpp's
STB_IMAGE_IMPLEMENTATION). Duplicates Image.cpp's private
Sixel-encoding internals (palette, 32x32x32 color lookup,
band encoder, nearest-neighbor scaler) rather than sharing
them, to avoid touching Image.cpp. Every frame is
Sixel-encoded up front during load() — no per-frame
re-quantization during playback.
Deliberately does NOT crop individual frames to their
opaque bounds (unlike TerminalImage): per-frame cropping
would shift each frame's anchor point and cause visible
drift during animation. All frames share one canvas-sized
rowSpan/colSpan instead.
Changed:
- CMakeLists.txt
+ include/core/Gif.h added to BINARYFETCH_HEADERS
+ cpp/windows/core/Gif.cpp added to the Windows-only
BINARYFETCH_SOURCES block. No Linux-side changes.
- Default_BinaryFetch_Config.jsonc
+ new art.Gif block (enabled, gif_path,
image_size_percentage, padding_up/left/right, loop,
speed_multiplier, max_frames). Defaults to
enabled: false — no existing config's behavior changes.
- main.cpp
+ #include "Gif.h"
+ ArtMode gains a GIF value: { ASCII, IMAGE, GIF, NONE }
+ LivePrinter takes a const TerminalGif& ref; gains
gifFrame0Drawn state and getCursorRow(); finish()/
artHeight()/printAndPad() gain a GIF case parallel to
the existing IMAGE case; new printGifAndPad()/
drawGifFrame0IfDue() mirror printImageAndPad()/
drawImageIfDue() exactly (same DECSC-save /
relative-hop / draw / DECRC-restore cursor pattern).
+ Art-loading prelude: Gif is attempted first
(if (gifEnabled)); existing
if (mode == ArtMode::NONE && imageEnabled) / ...&&
asciiEnabled) chain needed no edits, since both already
gate on mode == ArtMode::NONE.
+ New animation loop at the end of main(), after
lp.finish(): replays pre-encoded frames in place via the
same relative-cursor trick, honoring loop,
speed_multiplier (read via getNestedString + std::stod,
since ConfigManager has no getNestedDouble()), and
max_frames.
Priority / fallback behavior:
Gif (enabled + loads) > Image (enabled + loads) >
Ascii_Art (enabled + loads) > none.
Only the first mode that is both enabled and successfully
loads is used; lower-priority modes are never attempted.
Exactly extends the fallback chain that already existed
between Image and Ascii_Art — that two-rung chain is
unmodified, Gif is a new first rung ahead of it.
Explicitly untouched (verified):
include/core/Image.h, cpp/windows/core/Image.cpp,
include/core/AsciiArt.h, cpp/windows/core/AsciiArt.cpp,
include/core/config_management.h,
cpp/windows/core/config_management.cpp, and every registered
info-section lambda in main.cpp (header_settings,
compact_date_and_time, compact_operating_system,
detailed_display_monitor, detailed_audio_and_power, etc.).
Known limitations (not implemented, flagged for follow-up):
- No per-frame opaque-bounds cropping (see rationale above).
- loop: false plays through once and exits — does not freeze
on the last frame and wait for Ctrl+C.
- No SIGINT handler for explicit terminal-state cleanup beyond
each frame's own DECRC restore.
- Windows-only, matching TerminalImage's existing scope.
Scope: Windows only. No Linux build changes.
…l output Fix GIF reserving too many rows and landing above the info column: - Gif.cpp: query cell size via XTWINOPS (CSI 16 t) first, same as Image.cpp, falling back to the Win32 probe - main.cpp: read art.Gif.cell_width_px / cell_height_px and apply them with setCellWidthPx / setCellHeightPx Add art.Gif.viewer_mode (default false) for layouts taller than the window: - The animation loop hops up from the bottom of the output, and the terminal clamps CSI n A at the top of the visible window, so frames were drawn beside the middle of the info instead of the header - runGifViewer(): alternate screen, GIF pinned top-left and always animating, captured info text scrollable beside it (arrows, PgUp/PgDn, Home/End, mouse wheel; q/Esc/Ctrl+C to quit) - Info output is captured via cout.rdbuf; LivePrinter gets ArtMode::NONE in viewer mode - Normal print-and-exit behavior is unchanged when viewer_mode is false Add docs/GIF_Animation_Position_Issue.md describing the root cause and the available options.
…lt_BinaryFetch_Config.jsonc & src\BinaryFetch\resources\Dev_jsonc\Dev_BinaryFetch_Config.jsonc
Two intermittent glitches showed up in video playback, depending on
which decoder path Media Foundation picked.
1. Diagonal smear / sheared picture
Rows were read using the stride captured once at open time. Decoders
can pad rows or change frame size / stride mid-stream, so rows were
read at the wrong width and the error accumulated down the frame.
- Read the real row pitch from IMF2DBuffer::Lock2D each frame, with
a fallback to the media type stride for plain buffers.
- Handle MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED by re-reading the
frame size and stride and rebuilding the column map.
2. Green line on the right/bottom edge
Decoders pad frames to block sizes, and the padding can contain
uninitialized (zeroed YUV = green) data.
- Add computeCrop(): use MF_MT_MINIMUM_DISPLAY_APERTURE when
available, plus a small EDGE_TRIM margin (2px) as a safety net.
- Column and row mapping now sample from the cropped region.
File: Video.cpp
Video now eases in from black over the first moments of playback instead of appearing abruptly. - Scale each decoded frame's RGB by a smoothstep brightness factor for the first N frames, then switch to full brightness (cost is a single multiply + shift per channel, and only during the fade). - Fade length is set in milliseconds and converted to frames using the effective fps, so it lasts the same time at any frame rate. - Frame counter is reset in load() but not in restart(), so looping does not fade again. It happens once per BinaryFetch run. - Only frames actually shown are counted; frames dropped by the "running behind" check do not advance the fade. - Add TerminalVideo::setFadeInMs(int ms): clamped to 0..5000, 0 turns the fade off, default is 800 ms. Call before load(). Files: Video.cpp, Video.h
…lt_BinaryFetch_Config.jsonc before production to v1.8
Old config files in C:\Users\Public\BinaryFetch could conflict with the new version, so the installer now removes them first. - Add RemoveOldDataFolder(): if C:\Users\Public\BinaryFetch exists (case-insensitive), delete it recursively with DelTree. - Run it at ssInstall, just before files are copied, so cancelling the wizard earlier leaves existing data untouched. - Works in /SILENT and /VERYSILENT installs with no prompt. A failed delete (e.g. file in use) is logged and does not abort the install. - BinaryFetch recreates its default config and ASCII art on first run. - Bump version to 1.8 (header, AppVersion, OutputBaseFilename, and the success message). AppId, PATH handling and uninstaller unchanged. Note: user-customised config and BinaryArt.txt are lost on install. File: BinaryFetch-v1.8.iss
…\windows\core\AsciiArt.cpp
…ows\core\AsciiArt.cpp
The viewers waited on the console input handle and then called ReadFile(). The handle is signalled by any input event (resize, focus, mouse), so ReadFile() could block until the user clicked the terminal. Read the input queue with ReadConsoleInputW instead, never blocking and ignoring non-character events. Quit keys and scrolling are unchanged. File: main.cpp
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.
Three independent feature/fix lines plus assorted small fixes:
Linux distro-aware ASCII art database — replaces the single hardcoded placeholder logo with a per-distro ASCII art system, detected from /etc/os-release.
Windows animated GIF support via Sixel — adds a third art mode (ASCII / Image / GIF) with frame-by-frame Sixel playback, plus a follow-up fix for cell-size detection and an opt-in viewer mode.
Linux config dir rename — ~/.config/binaryfetch/ to ~/.config/BinaryFetch/ to match Windows' PascalCase convention.
feat(linux): distro-aware ASCII art database with generator tooling
Replaces the Linux edition's single hardcoded placeholder logo with a full per-distro ASCII art system, auto-detected from /etc/os-release. Mirrors the existing three-way config-source split (Dev / ReleaseSource / Production).
New files
include/core/linux_ascii_database.h — DistroArt { art, aliases } struct; getDistroArtDatabase() returns the full flat map; getDistroArt(distroId) does key match, then alias scan, then "linux" fallback. Total function, never empty or crashes.
cpp/Linux/core/linux_ascii_database.cpp — auto-generated (see tooling below). One flat std::map<std::string, DistroArt>, alphabetical by key, keyed on ID= from /etc/os-release. 528 distro entries. Reuses the existing $N color-tag syntax already parsed by processColorCodes() in AsciiArt.cpp — no new rendering path.
Ascii_DataBase_Generator/ascii_art_database_generator.py — interactive one-shot generator. Prompts for a source markdown file (## N. name headings plus text fences) and an output path, parses all entries, matches known aliases from an internal lookup table (e.g. ubuntu to ubuntu-desktop, ubuntu-server), guarantees a "linux" fallback entry exists, and emits a ready-to-compile linux_ascii_database.cpp. Auto-detects and rewrites a directory-only output path to
/linux_ascii_database.cpp instead of failing.Changed
cpp/Linux/core/AsciiArt.cpp
CMakeLists.txt
Behavior
Breaking
kDefaultAsciiArt removed from AsciiArt.cpp (Linux only). Any code path assuming a guaranteed non-empty embedded fallback must rely on the database's "linux" entry instead.
Adds a third art mode alongside the existing ASCII Art and static Image modes: animated GIFs, rendered frame-by-frame over Sixel, looping in place after the info text has printed once.
New files
include/core/Gif.h — TerminalGif class with load(), drawFrame(), getFrameCount(), getFrameDelayMs(), getRowSpan(), getColSpan(), plus padding and cell-size overrides. Same public shape as TerminalImage, but holds every decoded frame pre-encoded as Sixel rather than a single image.
cpp/windows/core/Gif.cpp — decodes all frames in one stbi_load_gif_from_memory() call (stb_image.h, already linked via Image.cpp's STB_IMAGE_IMPLEMENTATION). Duplicates Image.cpp's private Sixel-encoding internals (palette, 32x32x32 color lookup, band encoder, nearest-neighbor scaler) rather than sharing them, to avoid touching Image.cpp. Every frame is Sixel-encoded up front during load() — no per-frame re-quantization during playback.
Deliberately does not crop individual frames to their opaque bounds (unlike TerminalImage): per-frame cropping would shift each frame's anchor point and cause visible drift during animation. All frames share one canvas-sized rowSpan and colSpan instead.
Changed
CMakeLists.txt
Default_BinaryFetch_Config.jsonc
main.cpp
Priority and fallback behavior
Gif (enabled plus loads) > Image (enabled plus loads) > Ascii_Art (enabled plus loads) > none.
Only the first mode that is both enabled and successfully loads is used; lower-priority modes are never attempted. Exactly extends the fallback chain that already existed between Image and Ascii_Art — that two-rung chain is unmodified, Gif is a new first rung ahead of it.
Explicitly untouched (verified)
include/core/Image.h, cpp/windows/core/Image.cpp, include/core/AsciiArt.h, cpp/windows/core/AsciiArt.cpp, include/core/config_management.h, cpp/windows/core/config_management.cpp, and every registered info-section lambda in main.cpp (header_settings, compact_date_and_time, compact_operating_system, detailed_display_monitor, detailed_audio_and_power, etc.).
Known limitations (not implemented, flagged for follow-up)
Scope
Windows only. No Linux build changes.
Fix GIF reserving too many rows and landing above the info column
Add art.Gif.viewer_mode (default false) for layouts taller than the window
Docs
Add docs/GIF_Animation_Position_Issue.md describing the root cause and the available options.
Linux builds were writing to ~/.config/binaryfetch/ (lowercase), while the Windows build uses C:\Users\Public\BinaryFetch\ (PascalCase). Inconsistent naming across platforms made manual testing error-prone: rm -rf ~/.config/BinaryFetch silently did nothing on a case-sensitive filesystem because the real directory was ~/.config/binaryfetch, leaving stale self-healed files (e.g. BinaryArt.txt) in place across supposedly-clean test runs.
Changed getLinuxConfigDir() in both files that define it
cpp/Linux/core/AsciiArt.cpp and cpp/Linux/core/config_management.cpp:
Resulting paths
~/.config/BinaryFetch/BinaryArt.txt
~/.config/BinaryFetch/BinaryFetch_Config.jsonc
No other logic changed — self-heal, .jsonc/.json resolution order, and the embedded-default fallback all behave identically, just under the renamed path.
Testing
Migration notes