vtabs: terminal with vertical tabs on libghostty-vt + GTK4
Uses libghostty-vt (the API Ghostty documents for external embedders) for the terminal core. Ghostty's other C API, ghostty.h, exposes a full terminal surface but only supports macOS and iOS platform tags, so it cannot be embedded on Linux. We supply the layers libghostty-vt deliberately leaves out: PTY and process management, a Cairo/Pango cell renderer, and a GTK4/libadwaita UI with a Zen-style vertical tab sidebar. Nix pins the whole toolchain (Zig 0.16 via zig-overlay, GTK 4.22, libadwaita) so no host setup is needed.
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
//! vtabs: a terminal with vertical tabs, built on libghostty-vt.
|
||||
//!
|
||||
//! libghostty-vt supplies the terminal emulator core (escape sequence
|
||||
//! parsing, screen and scrollback state, key/mouse encoding). Everything
|
||||
//! else — process management, rendering, and the GTK4 UI — lives here.
|
||||
|
||||
const std = @import("std");
|
||||
const adw = @import("adw");
|
||||
const gdk = @import("gdk");
|
||||
const gio = @import("gio");
|
||||
const gtk = @import("gtk");
|
||||
|
||||
const Window = @import("Window.zig");
|
||||
|
||||
/// libghostty-vt logs unimplemented sequences at debug level, which is very
|
||||
/// chatty against a real shell. Keep the app's own warnings and errors.
|
||||
pub const std_options: std.Options = .{
|
||||
.log_level = .info,
|
||||
};
|
||||
|
||||
const css = @embedFile("style.css");
|
||||
|
||||
var gpa: std.heap.DebugAllocator(.{}) = .init;
|
||||
|
||||
pub fn main() u8 {
|
||||
defer _ = gpa.deinit();
|
||||
|
||||
// Non-unique so every launch is its own process. The default GApplication
|
||||
// behavior hands off to an already-running instance over D-Bus, which for
|
||||
// a terminal means a second launch silently does nothing visible here and
|
||||
// opens a window in whatever session owns the first one.
|
||||
const app = adw.Application.new("dev.greyson.vtabs", .{ .non_unique = true });
|
||||
defer app.unref();
|
||||
|
||||
_ = gio.Application.signals.activate.connect(app, ?*anyopaque, &onActivate, null, .{});
|
||||
|
||||
const status = gio.Application.run(app.as(gio.Application), 0, null);
|
||||
return @intCast(status);
|
||||
}
|
||||
|
||||
fn onActivate(app: *adw.Application, _: ?*anyopaque) callconv(.c) void {
|
||||
loadCss();
|
||||
|
||||
const window = Window.create(gpa.allocator(), app) catch |err| {
|
||||
std.log.err("failed to create window: {s}", .{@errorName(err)});
|
||||
return;
|
||||
};
|
||||
window.present();
|
||||
}
|
||||
|
||||
fn loadCss() void {
|
||||
const display = gdk.Display.getDefault() orelse return;
|
||||
const provider = gtk.CssProvider.new();
|
||||
defer provider.unref();
|
||||
|
||||
provider.loadFromString(css);
|
||||
gtk.StyleContext.addProviderForDisplay(
|
||||
display,
|
||||
provider.as(gtk.StyleProvider),
|
||||
gtk.STYLE_PROVIDER_PRIORITY_APPLICATION,
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user