95 lines
3.9 KiB
Zig
95 lines
3.9 KiB
Zig
//! Playpen: a terminal with vertical tabs, web panes and saved layouts, 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 builtin = @import("builtin");
|
|
const adw = @import("adw");
|
|
const gio = @import("gio");
|
|
|
|
const Settings = @import("Settings.zig");
|
|
const Window = @import("Window.zig");
|
|
const appearance = @import("appearance.zig");
|
|
const icons = @import("icons.zig");
|
|
const review = @import("review.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,
|
|
};
|
|
|
|
/// The process allocator, which everything in the app shares: the GTK main
|
|
/// loop, every terminal's parser, and every review connection thread.
|
|
///
|
|
/// `DebugAllocator` only in a debug build, and this is not a matter of taste.
|
|
/// It is thread-safe by way of one mutex around every allocation and every
|
|
/// free, and its backing allocator is the page allocator — so a large
|
|
/// allocation is an `mmap` and its release an `munmap`, both taken under that
|
|
/// single process-wide lock. A review pane's poll allocates a whole `git diff`
|
|
/// and drops it a moment later, several times a second across a handful of open
|
|
/// reviews, and each one of those was stalling the main loop and every other
|
|
/// request behind the same lock. `c_allocator` has a per-thread cache and no
|
|
/// global lock; libc is already linked for GTK.
|
|
var debug_gpa: std.heap.DebugAllocator(.{}) = .init;
|
|
|
|
fn gpa() std.mem.Allocator {
|
|
return if (builtin.mode == .Debug) debug_gpa.allocator() else std.heap.c_allocator;
|
|
}
|
|
|
|
pub fn main() u8 {
|
|
defer if (builtin.mode == .Debug) {
|
|
_ = debug_gpa.deinit();
|
|
};
|
|
|
|
// Before the allocator's own teardown, since the settings arena comes out
|
|
// of it. A no-op if the app never got as far as activating.
|
|
defer Settings.deinit();
|
|
|
|
// Also before the allocator, and after the window: the review server owns
|
|
// threads, and stopping it waits for the ones still inside a request rather
|
|
// than freeing the memory they are reading. A no-op if it never started.
|
|
defer review.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.playpen", .{ .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 {
|
|
// The file both of the next two read from: the scheme, and the tabs the
|
|
// window opens itself with.
|
|
Settings.init(gpa());
|
|
|
|
// Before the window, so that the first frame is drawn in the scheme the
|
|
// user chose rather than repainted into it a moment later.
|
|
appearance.init();
|
|
|
|
// Same reasoning: the bundled icons are registered before anything asks
|
|
// for one by name.
|
|
icons.init();
|
|
|
|
// Before the window, because every tab is announced to the server as it is
|
|
// created and every terminal is handed the endpoint of the tab it opens in.
|
|
// Started unconditionally rather than on the first review pane, so an agent
|
|
// running in a tab has a `PLAYPEN_REVIEW_URL` from the moment it starts.
|
|
review.init(gpa());
|
|
|
|
const window = Window.create(gpa(), app) catch |err| {
|
|
std.log.err("failed to create window: {s}", .{@errorName(err)});
|
|
return;
|
|
};
|
|
window.present();
|
|
}
|