👾 Tentaclar Aliens' Epic Extraterrestrial Jungle Dance Party 👾
03 Jun 2026

Scritch your catgirl(1)

catgirl(1), showcasing the overflow indicator ($)

Continuing from where we left off in the catgirl devlog, this post covers further fixes and QoL enhancements applied since then.

Reminder: All patches and enhancements mentioned in this post are available on Codeberg and my OBS respository (for deb packages)

Dispelling some (n)curses

catgirl(1) uses ncurses as its toolkit of choice for rendering and implementing its TUI.

Even though it isn't flashy or exciting by today's standards, ncurses (and other curses ports) remains in use due to its portability across operating systems - even Win32/WNT via pdcurses - and its support for a wide variety of terminal emulators via terminfo(5).

As one might expect from a TUI API originally designed in 1978 (!), the (n)curses C interface is quite baroque, requiring one to write a fair amount of boilerplate code to get anything on the terminal: ncurses "windows" have to be invalidated & refreshed by hand, and resizing or reflowing an ncurses TUI must be handled explicitly. Dynamically-sized TUIs in ncurses are built by computing window dimensions relative to extern C variables COLS and LINES, which ncurses updates on SIGWINCH to match the current terminal size.

Naturally, this results in a lot of housekeeping code just to draw the UI, and more code usually means more places for things to go wrong.

Reflowing

Reflowing (or wrapping) in typography is the process of moving words between lines - usually from the line above to the line below - as a result of a typographical change - e.g. adjusting the font size, or, more commonly in text-based TUI applications, changing the dimensions of the output surface.

It turns out that terminal emulators are relatively unconstrained when it comes resizing. KDE's Konsole, for instance, will happily allow the user to shrink a terminal viewport to a miniscule 1x1 or even a pixel-thin 1x0.

Does it make sense to reflow on a 1x0 viewport? Not really, as nothing will be visible either way. However, it does makes sense to make sure we do not break if this does happen (in my case - by accident, while attempting to resize the terminal horizontally in order to wedge it next to an emacs(1) frame).

Resizing

The tiny-terminal edge case also needs to be handled when computing new window extents after a SIGWINCH. ncurses' wresize() does not appreciate being fed values < 1 for its line or column parameters, so some clamping was necessary here.

Overflow Indicators ($)

As per RFC 1459, IRC typically has a line length limit of 512 octets. While many modern servers and clients support longer lines (often advertised via the LINELEN ISUPPORT token), catgirl(1) remains faithful to the original 512-octet limit. It ensures long lines are split into multiple messages at this boundary, visually indicating the split in the input prompt.

The input prompt window (or "pad" in ncurses jargon) has a fixed backing buffer. catgirl(1) allocates its size to 1024, allowing us to edit roughly two messages' worth of text at once at the prompt.

One could work around this by managing the backing buffer manually, but catgirl(1)'s input prompt performs IRC formatting code interpolation. This requires iterating over the entire string before rendering; at a certain point, interpolating massive strings becomes a performance drag. A fixed column size is a fair compromise.

But how do we signal that the input has overflowed the pad's backing buffer? Borrowing a convention from several traditional UNIX programs, I've added a $ indicator. If the EOL is currently off-screen, a $ is rendered at the bottom-right corner of the input prompt.

The logic for this was slightly more involved than expected. We have to track the message's content length against the backing buffer's limit and render the $ in its own 1x1 ncurses window at the viewport's edge.

Securely Passing Secrets

catgirl(1) has excellent support for certificate authentication via SASL or CertFP - you simply pass a filename containing your key.

But what if you want to use a secret manager, or something like systemd-creds(1), to avoid storing secrets in plaintext? (Note: Thanks to CM on RektIRC for suggesting this).

Since catgirl(1) accepts any filename, it can handle pseudo-files like /dev/fd/.... I've been using a wrapper script to securely decrypt and pass certificates for months:

#!/bin/sh
set -e
SECRET=$(mktemp --suffix=.catgirl.cred)
if exec 3<>"$SECRET"; then
    unlink -- "$SECRET"
else
    unlink -- "$SECRET"
    exit 1
fi
systemd-creds --user decrypt "$HOME/.config/catgirl/irc.pem.cred" >&3
exec "${CATGIRL:-catgirl}" -c /dev/fd/3 "$@"

The above sh(1) script:

  1. Opens a temporary file.
  2. Associates it with file descriptor 3.
  3. Unlinks (deletes) the file immediately (it stays on disk only as long as the FD is open).
  4. Decrypts the secret (the certificate) into that FD.
  5. Passes the path via /dev/fd/ to the client.

This ensures the plaintext secret never sits on the disk. However, there's a catch: an open(2) on /dev/fd/n is often equivalent to a dup(2), meaning the file object remains open and accessible via /proc/<pid>/fd/3.

While some programs use special notations like fd:<n>, I've opted for a simpler fix, involving detecting pseudo-fd filenames and closing the corresponding FD after use. It doesn't cover every edge case (like symlinks), but it handles the standard workflow decently.

Incidentially, I think this is a good example of "progressive enhancement" in a systems programming context: Making the client accommodate the quirks of the Linux/BSD pseudo-fd filesystem.

Wrapping up and more IRCv3 Stuff

While working on the above patches, I've spent some time prototyping an implementation of IRCv3 replies. Some clients, like Halloy, opt for a traditional, threaded reply display. Due to the complexity of implementing threaded views in an ncurses TUI, and due to some of my own opinions on their compatibility with the "IRC climate" of ephemeral backlogs and fast-paced "FIFO" communication, my implementation is based on color-coding/marking related messages in reply chains:

This could be how replies eventually look like, possibly…?

Few networks support the +reply tag yet, so this will be a slow-burn refinement until the extension sees wider adoption.

In the meantime, working on this has made me a bit more ambivalent about certain IRCv3 features. I'll go over these opinions in a future post.

Tags: c programming irc
Other posts
Creative Commons License
tilde.club/–alcor by Alcor's Tentaclar Aliens' Epic Extraterrestrial Jungle Dance Party is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.