Skip to content

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
InterCentury merged 22 commits into
mainfrom
Linux-Dev
Oct 5, 2026

Conversation

@InterCentury

Copy link
Copy Markdown
Member

Three independent feature/fix lines plus assorted small fixes:

  1. Linux distro-aware ASCII art database — replaces the single hardcoded placeholder logo with a per-distro ASCII art system, detected from /etc/os-release.

  2. 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.

  3. Linux config dir rename — ~/.config/binaryfetch/ to ~/.config/BinaryFetch/ to match Windows' PascalCase convention.

  4. 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

  • Added #include "core/linux_ascii_database.h", .
  • Added readOSReleaseValue(key) — parses a single KEY=VALUE line from /etc/os-release, strips quotes, lowercases.
  • Added splitIdLikeChain(raw) — tokenizes ID_LIKE= into an ordered fallback chain.
  • Added resolveDistroArt() — direct ID= lookup (key or alias), then walk ID_LIKE= chain (same lookup), then 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 or broken database (missing "linux" key) now fails loudly via stderr warning plus false return, rather than silently rendering a fixed placeholder — deliberate for testability.

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.

  1. feat(windows): animated GIF support via Sixel (new Gif mode)

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

  • 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

  • Added #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() and drawGifFrame0IfDue() mirror printImageAndPad() and 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) and ... && 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 plus std::stod, since ConfigManager has no getNestedDouble()), and max_frames.

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)

  • 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.

  1. fix(gif): correct cell-size detection, add opt-in viewer mode for tall 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 and cell_height_px and apply them with setCellWidthPx and 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.

Docs

Add docs/GIF_Animation_Position_Issue.md describing the root cause and the available options.

  1. fix(linux): rename config dir binaryfetch to BinaryFetch to match Windows

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:

  • XDG_CONFIG_HOME branch: "/binaryfetch" changed to "/BinaryFetch".
  • HOME branch: "/.config/binaryfetch" changed to "/.config/BinaryFetch".

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.

  1. Assorted fixes
  • fix(linux): reorder compact processor display settings for improved clarity.
  • fix(linux): update empty bar representation colors for consistency.
  • fix(image): use 6x7x6 palette with Bayer dithering to remove colour banding.
  • fix(windows): update default buggy ASCII art of Windows build at src/BinaryFetch/cpp/windows/core/AsciiArt.cpp.
  • fix(video): update src/BinaryFetch/cpp/windows/core/Video.cpp.

Testing

  • Linux: verified per-distro resolution against /etc/os-release across multiple distros; alias and ID_LIKE chain fallback confirmed; missing-"linux"-key failure path confirmed to warn and return false.
  • Windows: GIF load and playback verified for multi-frame GIFs; cell-size detection verified via XTWINOPS and Win32 fallback; viewer mode verified for tall outputs.
  • Config dir rename: verified with case-sensitive rm -rf on Linux.

Migration notes

  • Linux only, breaking: kDefaultAsciiArt is gone. Consumers needing a non-empty embedded fallback must use the database's "linux" entry.
  • Existing BinaryArt.txt at the old lowercase path will not be picked up after the rename — one-time manual move may be needed for users who customized it.
  • No Windows build changes for the Linux database work; no Linux build changes for the GIF work.

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
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
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
@InterCentury
InterCentury merged commit 0755438 into main Oct 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant