Zig, Gtk+ and libvte concept


This is in a very rough proof of concept stage right now. I’m learning how to translate from C to Zig and call Gtk functions, work with Gtk pointer types in Zig, etc as I go. And I haven’t had a lot of time yet to work on it. Right now this is just a barebones terminal, but it is planned to have tabs and the ability to do split panes, while staying lightweight. I don’t plan on having a menubar or extensive configuration beyond things like setting colors and font, because there’s plenty of options like Gnome terminal or XFCE terminal that have all the bells and whistles.

I originally started something similar in Rust, but have had frequent roadblocks due to incomplete bindings. I actually had to fork a couple of the crates in order to use them with newer Gtk crates. I thought I’d try this in Zig as theoretically I won’t need bindings. In practice, at the very least the g_signal_connect function must be re-implemented because it is defined with an ugly macro, but other than that it seems I’m just making very frequent use of @ptrCast, @intToEnum and now @alignCast in order to pass pointers into the callbacks.

The basic roadmap, from my last commit:
Date: Thu May 20 09:46:12 2021 -0400

Basic working terminal with tab creation in a separate function

* Add widgets to structs and pass structs of widgets around to callbacks
* Close terminal by removing page from notebook instead of destroying widget
    (depends on above)
* Add tab creation button as notebook action widget
* Implement keypress handling
* Implement command line options for:
  * Run custom command
  * Specify working directory
  * Specify terminal size
  * Setting titles for tab and window
* Implement some basic configuration in a configuration file, possibly with gui
  * Specify colors
  * Specify fonts
  * Specify shell or fallback to user shell?
  * Keep this part simple
* Implement split panes as simply as possible
  * Not highest priority but must be planned for. Need a way to keep track of
    open terminals dynamically, and tabs will have to refer to a parent container
    and not the actual vte widget.

Spent just a little time today to push this along. I managed to figure out how to pass arbitrary data into a GCallback from Zig, which allowed me to start implementing the minimal expected command line options. So as of now, while the interface is still only supporting a single tab, it’s possible to give it a startup command and title. There’s an option for setting the working directory as well but it’s not yet implemented.

Even if this program itself doesn’t float everyone’s boat, I hope that it might at the least encourage someone else to try playing with gtk and Zig, and perhaps the code can give some pointers if you do.

I did some code refactor on this and moved most of the callbacks into separate functions in the new gui.zig file, which was split off of main.zig.

Also rearranged the gui somewhat, and recreated it in Glade in order to more easily generate an application menu and add key shortcuts. I’m not a fan of having a bunch of extra toolbars, buttons, menubars etc on an application like this, so what I did was made the button which pops up the menu be the “action widget” for the tab bar. Which gives it a menu with no menu bar. I think it’s cool, because the only space taken up by the “interface” is the tab bar itself, which I will probably be taking steps to shrink and minimize using some custom css soon.

Note that most of the menu entries don’t yet work. Right now you can add tabs and close them either with Ctrl-d or the tab close button. The rest is dependent on initializing the HashMap which will track all of the open terminals and their tab labels. Once that’s done, it should be trivial to get the split panes and everything else working. After that it’s on to creating a system for storing and retrieving preferences and creating a dialog to do so graphically.

Just committed support for splitting each tab into panes and rotating the orientation of the split between vertical and horizontal. Each time a tab is opened it’s internal widgets are now tracked via a “Tab” struct and appended to a hashmap, allowing to manage what is open dynamically. This is only partially implemented at this time, as I’m not yet properly handling closing terminals and tabs. However, this is already further along than I got when I tried this in Rust.


@jeang3nie is on fire! :fire: Awesome progress man!

1 Like

This evening’s commit includes a skeleton for an eventual preferences dialog. Non-functional as of now, but looks kinda pretty.

The preferences included were pretty shamelessly cribbed from a subset of Xfce4-Terminal’s preferences. Subject to change, but might be close to the final form.

I was considering a few different things for a config file format. I use toml in Rust, because I know it and it’s dead simple to use with serde. I considered zzz as well, as it’s a project by a fellow zig user. But in reality, all I really need is a simple key=value store, so I’ll probably write my own small parser.

1 Like

Looks cool :slight_smile:

If you’re looking for a simple config file format you might be interested in my zig implementation of a NestedText parser, GitHub - LewisGaul/zig-nestedtext: Zig NestedText parser library - a simple human readable data format based on YAML - I’d be happy to address anything that comes up :slight_smile:

Oh, cool! I’ll check it out. I like the idea of using code from the community.

So briefly looking at Nested Text, and your implementation, I do like the format and the code is obviously high quality. My understanding of the state currently is that in order to go from nt on disk to a Zig struct I would need to use your library to convert to json, and then use std.json to parse that into a struct. Is that correct? And then going the other direction would just be the reverse.

I’ll have to familiarize myself with std.json apparently, but it looks like that should give me the ability to do some nice things like structs with some enum fields to represent the data from my comboboxes. So I think it could work.

Thanks for the comment on the code!

I’m assuming you’re referring to parsing into a typed struct (interpreting fields as more types than just strings), which is a mode of parsing std.json has that my library does not. I have an issue tracking the addition of this, and if it’s something that would be useful to you I’d be happy to work on it (or welcome a PR). I probably won’t get a chance for a week or so though.

As you say, you should be able to work around this limitation by going via json (my implementation is based on the json implementation so familiarising yourself with both shouldn’t be too bad hopefully).

Yes, that. I’m fleshing out the fields a little bit already for what my needs would be. I did take some time to look at std.json last night, and I want to see what I can do to help rather than just asking you to expand your code. But I’m leaving on a trip myself in a few days, so it probably wouldn’t be until after the 5th that I would have time anyway.

Last git commit before I take five days off to deliver my daughter to college in the northeast. I’ve implemented the data structurers which will hold all of the preferences that are tweakable in the preferences dialog. Right now the only ones which get updated (and only in the sense that the struct gets updated, nothing gets actually updated in the interface or saved to disk yet) are the color settings. The rest should be straightforward. Many thanks to everyone who helped me through figuring out how to get all of the colors in an inline for loop over the course of the day.

@LewisGaul I’m on the master branch of Zig and your nested text library doesn’t compile. I cloned it and created a branch, fixed a couple things but there’s more to do. I’ll hit you up sometime after I get back home, hopefully with a PR. Zig-clap maintains a sesparate branch, zig-master, which tracks against Zig’s master branch. Maybe Nested Text could do the same?

Yeah would be good to have a zig-nestedtext branch supporting zig master branch. I like primarily supporting the latest zig release, but there are some improvements to make use of from zig master (e.g. ordered JSON objects) and there’s clearly users this could be useful for.

For what it’s worth, I was able to compile with zig master when I was working on it (aside from the clap dependency), so hopefully not too hard to get working.

Anyway, nice to have someone looking into it, just get in touch if you want to work anything out together (e.g. email me).

1 Like

Back from my trip, didn’t do much today but I implemented some keyboard shortcuts to switch between tabs quickly. Pressing Alt + 1-9 takes you to that number tab. Still need to do a next/previous tab switcher, and some keyboard switching between panes.

I’ve got the config options into a struct which has been slimmed down quite a bit by using tagged unions extensively. This struct will probably live in memory for the life of the program so it was important to me to do some optimization here. At some point I’ll have to do the work to get this data to and from disk, and actually set all of the configurable options for every open terminal.

Lots to do, but I think it’s close to what I would consider a useful program now.

I added to the keyboard shortcuts today so you can now do either Alt/LeftArrow or Ctrl/PageUp to go to the previous tab, and Alt/RightArrow or Ctrl/PageDown for the next tab. Finally got around to filling out the README file a bit more, and it reflects that Zterm now builds with Zig version 0.8.0 (Whooot!).


There were a few commits today and I’m happy with the progress. While settings are still not saved to disk, they are persistant in memory for the lifetime of the program and most of them are applied more or less correctly.

Settings which are working:

  • Cursor
  • Scrollback
  • Colors
  • Background - solid color and transparent (with limitations)
  • Window title

Settings which still need implemented:

  • Custom command (defaults to user’s shell or /bin/sh if unable to determine)
  • Image as background

Additionally, I’ve tweaked the default colors, scrollback lines and a few layout and spacing issues in the preferences window to make things more usable out of the box. I couldn’t resist Zigifying things a little bit, so the preview text that appears when you pop up the font selector now reads “All your commands are belong to us.”

Wrapping things up, I added a Makefile to handle installation, an icon and a desktop file to give it a menu entry.


Disclaimer: I haven’t checked the sources. Why is the Makefile necessary? Can’t build.zig cover it?

The Makefile is only there to cover installing the files into the filesystem, it uses build.zig to build the program.

I thought about adding installation steps to build.zig, but it seems bad form to me to encourage, or even tempt, people to run the compiler as root and give it filesystem access.

I mean, typically a zig build --prefix $SOMEWHERE should be enough – if the prefix is /usr then the user gets a system-wide installation, if the prefix is $HOME/.local then they get a user-local installation, etc…

At least that’s what I’d try to do, so that the user only needs Zig in order to install this software. But that’s such a minor thing, and make such a widely available tool, that you can safely ignore me :slight_smile:

1 Like

Well yeah, I could do that. And like I said, that sort of encourages our at least tempts people to run the compiler as root, which is probably not great. If the installation was always going to be confined to the user’s home directory I would be fine with installing the files via build.zig, but I’m not convinced that people would do that.

And if the exe was the only file in question, I’d expect that people would do a simple “sudo cp zig-out/bin/exe” after building it. I only added the Makefile after adding two extra files that are expected to be installed into the filesystem.

Is kind of a philosophical point really. And it’s one that probably should be addressed by the community and a “best practice” adopted. I’ve caught a bit of that discussion happening actually, but I don’t think anything is really resolved about it.

1 Like