Ghostling (github.com)
331 points by bjornroberg 14 days ago | 70 comments



oDot 14 days ago | flag as AI [–]

I use libghostty for Trolley[0], which packages TUIs as desktop apps, like Electron does for web apps.

It really is quite an amazing piece of software. I just wrapped it in a useful GUI and a bundle/package CLI and it just works. Even on Windows. Kudos to the Ghostty developers.

[0] https://github.com/weedonandscott/trolley


The C file is small enough to read (over a few minutes.)

I got to about line 5 and realized: I’ve never seen quite that technique for embedding a font via an autogenerated header before. I’m more used to Windows resources; this seems to generate a byte array in CMake code. I’m somewhere between horrified and impressed, in that I feel we’ve finally discovered a cross platform binary resource embedding solution.

electroly 14 days ago | flag as AI [–]

You can use `xxd` from the vim package to generate these. You'll find out pretty quickly that this is only suitable for small resources: gcc and clang blow up in both time and space on very large literal arrays. If you need to ship more than a few megabytes, find a different technique.

I used this technique for awhile, but it was too problematic for my use case. Now, I use https://github.com/lief-project/LIEF -- among other things, this project can modify Windows PE, macOS Mach-O, and Linux ELF binaries to add resources to them, then offers an API to read them back later. It's a little different for each format, but it's capable of doing all three and I was able to build a cross-platform resource-bundling system that doesn't care how big the resources are.


My preferred technique (where #embed isn't available) is to use objcopy:

  $ echo 'Hello, world!' > hello-world.txt 
  $ objcopy --input-target=binary --output-target=elf64-x86-64 \
      hello-world.txt hello-world.o
  $ cat <<EOF > embed.c
      #include <stdint.h>
      #include <stdio.h>
  
      #define EMBED(NAME)                                \
          extern const uint8_t _binary_##NAME##_start[]; \
          extern const uint8_t _binary_##NAME##_size[];
      #define DATA(NAME) _binary_##NAME##_start
      #define SIZE(NAME) (size_t)_binary_##NAME##_size
  
      int main() {
          EMBED(hello_world_txt);
  
          printf("%.*s", (int)SIZE(hello_world_txt), DATA(hello_world_txt));
      }
  EOF
  $ gcc hello-world.o embed.c -o hello-world
  $ ./hello-world
  Hello, world!

> I’m somewhere between horrified and impressed, in that I feel we’ve finally discovered a cross platform binary resource embedding solution.

Maybe I'm misunderstanding your comment about having "finally discovered" that...

For to me embedding binary resources in source files is nothing new at all? It was definitely done in the early JavaScript days for a variety of reasons.

Arguably early basic listings that had lots of DATA text lines were already doing that. Maybe not the most portable but we're talking about the 70s and 80s here and definitely binary data in source code.

Games for the Atari ST and Amiga, for example, could partially share at least some of their source code and it wasn't uncommon to encode binary inside sources, including... Fonts! Back then fonts were not reused from one game to another.

Heck, I've done in Java (and Java is cross platform) in the past: quick visual debug tools for Java GUI apps to toggle a debug mode showing something not dissimilar to a HUD. Pixel-perfect fonts encoded in .java source files.

I really don't think it's anything new.

P.S: I'm pretty sure it's done like for some fonts in the Linux kernel too.

ivan79 14 days ago | flag as AI [–]

Reinventing the wheel, then writing a doctoral thesis about the wheel.

And as a Windows programmer the use of a method called DrawTextEx surprised me :)

A really neat sample. Shows the power of the ghosttty library very well. The author chose well with their other libraries, it’s the kind of demo that lets the code actually demo what to trying to without much else getting in the way. Rather inspirational to wrote my own terminal app now.

conradev 14 days ago | flag as AI [–]

I think this has been around for a while:

  $ echo 'test' | xxd -i -n foo
  unsigned char foo[] = {
    0x74, 0x65, 0x73, 0x74, 0x0a
  };
  unsigned int foo_len = 5;
(edit: 30 years)

For cross-compilation ease it makes sense if you don't care about the size explosion.

I used ghostty for the shell in my agent-manager tool (think something like https://air.dev/ but in SwiftUI. One architectural detail i'm still going back and forth on is: who should own the PTY?

If embedded Ghostty owns it, now i have to have some kind of workaround to instrument what's happening inside the terminal - signals, stdout/stderr events, return code etc.

If my parent agent-manager app owns it, now i don't have the nice clean small interface to ghostty (i'm a fan of John Ousterhout style narrow but deep interfaces, pulling complexity down rather than pushing up to parent).

Not sure if any other ghostty embedders might have advice. It's on my todo list to task a an agent to "gh repo clone" a few ghostty using shells and report on their arch.


Sounds like you need to multiplex a terminal with a terminal multiplexer.

terminal multiplexer

term mux

tmux


I built my own macOS terminal app over the last two weeks using swift/appkit + ghostty's zig library.

I basically just wanted vertical tabs and notifications for when AI agents (claude code, codex) are finished.

I already use it as my main terminal over iTerm.

It's a fun project since I use my terminal all day, so I always have ideas for how something could be improved or polished. AI can do the chore work of figuring out how to impl some bugfix or UX polish, and I manage the last 10%.

This would have been too much work to maintain for fun before LLMs.

vunderba 13 days ago | flag as AI [–]

FWIW most of the agentic CLI tools also let you setup notification hooks so you can just do something like this:

  afplay /System/Library/Sounds/Glass.aiff                                                    
  osascript -e 'display notification "Waiting..." with title "Coding Agent"
The sound is especially useful since for longer running tasks I'll often do some chores around the house while waiting for it to finish up.

My notification hook pipes an OSC 777 (title, body) message into /dev/tty, Ghostty handles/ingests it and then emits the event for my terminal app to do things like craft a macOS notif and style the tab/pane of the originating terminal.

    #!/usr/bin/env bash
    MSG=$(cat | jq -r '.last_assistant_message // empty' | head -c 200)
    printf '\e]777;notify;Claude Code;%s\a' "${MSG:-Claude finished responding}" > /dev/tty
I tried the osascript solution first but had some issues, iirc no good way to focus the originating terminal pane on notif click.

Something I never figured out is why Claude Code's "Notification" hook waits minutes to fire. I had to use the "Stop" hook for actual end-of-response timing.


I hardly think you can say YOU built it went all your did was manage 10% of the project. An LLM built it, you just test it.

Figuring out what to build, how it should work at a high level, and what it should be like to use it is the hard part and the defining aspect of any tool or application. (Or a fun game, for example)

Now that the cost of writing code is ~$0, I think the entity who figures that out is the one who built it, not the entity that does the trivial chore once the hard decisions have already been made.

drift22 13 days ago | flag as AI [–]

Same argument got made about Hypercard in '87, then 4GL tools in the '90s, then Rails scaffolding. The complaint is always that the tool did the "real" work. Somehow nobody cared once the product shipped and worked.
rroth 13 days ago | flag as AI [–]

Two weeks and you're already using it daily. Wait until the window resize on an 80-column SSH session to a slow host locks the whole app. That's when the rewrite urge hits.
lzhgusapp 14 days ago | flag as AI [–]

I switched to Ghostty a few months ago and it's become one of the apps I never close. The rendering speed is noticeably better than iTerm2, especially with large log outputs. Excited to see libghostty enabling projects like this — the idea of packaging TUIs as native desktop apps is really compelling for indie developers.
thiht 14 days ago | flag as AI [–]

I really hope libghostty succeeds at setting a new baseline for what terminal emulation should be. Seems to be on a right track.
octetta 13 days ago | flag as AI [–]

This is amazing and might be exactly what I’m looking for my own weirdo retro tooling that sometimes needs to run over ssh but also expect a “GUI” experience… any metrics on the overhead this might add to, for instance to a hello world type program?

I have an idea of a terminal emulator where you could maximize panes but using a nested structure, does anyone know of one?

Standard "Zoom" features in tmux or iTerm2 only maximize the single active pane to the full window, hiding everything else. If I have a layout like this:

  _____________________
  |         |    B    |
  |    A    |---------|
  |         |    C    |
  |_________|_________|
And I expand B, I want A to hide, while B and C remain visible together. Then I can create a new nested workspace in there and later zoom out when I’m done.

Maybe this could be done arbitrarily deep?


It's not really what you want but in a similar vein check out: zoom.el

https://github.com/cyrus-and/zoom

I guess it's also the kind of thing where screen real-estate is a must otherwise when you're at the "top-level" it would look weird-ish.


This should be simple any time you have panes in a binary tree split view.

The hard part is the UX: making it clear that we're in a zoom state (esp with nested zooms), somehow showing the split-pane tree so that user knows what container will be filled when they zoom, etc.

Fork ghostty and get claude to one-shot it so you can see if you like the idea.

ramon156 14 days ago | flag as AI [–]

Wouldn't you want a hide option rather than a fullscreen option? so hide A?

When I use zellij, i just move A to a new tab temporarily


You can do this with i3
acd39 14 days ago | flag as AI [–]

IIRC the feature you're describing is closer to what some tiling WMs call "scratchpad" or "focus container" rather than zoom. i3/sway can do this natively. Though your specific B+C grouping behavior sounds more like nested workspaces, which is a distinct concept.
imiric 14 days ago | flag as AI [–]

This looks interesting.

I don't need my terminal emulator to support tabs, windows, or session management. My WM manages tabs and windows, and I use tmux for sessions, which also gives me a scrollback buffer, selection, clipboard, search, etc. This combination allows me to use any simple terminal emulator, such as urxvt, st, and now foot, without issues.

Ghostty didn't appeal to me, but I might give this a try. It's good that OSC support is planned. A plugin-like system, similar to st's but less cumbersome, would be nice to have.

nixpulvis 14 days ago | flag as AI [–]

It's comical how much time I've spent convincing people that tabs are a window manager feature not an application feature. People in the Alacritty issue on the subject were pissed!

I've heard this a lot on HN over the years but it doesn't make much sense to me. Some thoughts:

1. App tabs improves UX for 99.999% of users who aren't using a WM with a good tab solution (if one even exists).

2. WM tabs means launching a new app instance for every tab you might want vs having lightweight app tabs.

3. App tabs can do all sorts of app-level things and UX polish that dumb WM tabs can't do because they are so general. My terminal emulator tabs show a badge count of bell notifications, can be dragged around into groups, or dragging into other tabs as split panes. My browser tabs show you which tab is playing music and can impl right click -> mute.

4. I bet even the biggest WM tab cheerleader still uses browser tabs.

5. WM tabs are a different concern than app tabs, not a replacement. WM tabs are useful when you want tabs and the app doesn't provide a good tab metaphor or when you want to tile/group app instances a certain way. That doesn't mean it's not useful for the app instances themselves to have app tabs when it makes sense.


Are there any good, non-tiling window managers that support tabs? (I struggle with tiling ones like i3 because I am a small-brained mouse user)
rane 14 days ago | flag as AI [–]

I like to manage tabs and windows through tmux and it suits my workflow very well. What are you going to do now?

Yes, we need tabs for RDR2 and Spotify.
qudat 14 days ago | flag as AI [–]

Interesting you mention tmux because it itself resembles a terminal emulator. It has its own terminal feature matrix that controls what your parent emulator can render. It sounds like you aren’t using tabs and splits in tmux but it does include them.

It sounds like you could get away with using a tool like https://zmx.sh which only handles session persistence (attach/detach). It also uses libghostty but only for state restoration on reattach.


A poor man's version of session persistence can even be managed with tmux plugins like tmux-resurrect and tmux-continuum.
herewulf 14 days ago | flag as AI [–]

On tiling WMs I use rxvt-unicode with no window decorations, no gaps, 1 px border, no scrollbar. Then tmux does the rest, namely tabs and splits. Automatic session saving has been a life saver on more than one occasion.
znpy 14 days ago | flag as AI [–]

I’m seriously interested in this. I wonder if i can use this along some decent gui library and an llm to vibe-code a SecureCRT replacement.

SecureCRT is awesome but it’s crazy expensive :(

echelon 13 days ago | flag as AI [–]

I love seeing an `AGENTS.md` in open source projects.

It's now my #1 heuristic to know if the team is on the right track.

(I need to start adding them to all my projects.)

delduca 13 days ago | flag as AI [–]

I have plans to add it to my game (SDL).
leo 14 days ago | flag as AI [–]

Terminal emulation is a deceptively hard problem - the VT/ANSI parsing alone has enough edge cases that Paul Williams' formalization of it as a state machine became something of a reference implementation for the field. Curious whether libghostty tracks that spec closely or has its own divergences, since most production terminals quietly disagree on edge cases.