mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
macos: config API
This commit is contained in:
12
build.zig
12
build.zig
@ -134,18 +134,6 @@ pub fn build(b: *std.build.Builder) !void {
|
||||
b.installFile("dist/macos/Ghostty.icns", "Ghostty.app/Contents/Resources/Ghostty.icns");
|
||||
}
|
||||
|
||||
// c lib
|
||||
{
|
||||
const static_lib = b.addStaticLibrary("ghostty", "src/main_c.zig");
|
||||
static_lib.setBuildMode(mode);
|
||||
static_lib.setTarget(target);
|
||||
static_lib.install();
|
||||
static_lib.linkLibC();
|
||||
static_lib.addOptions("build_options", exe_options);
|
||||
_ = try addDeps(b, static_lib, true);
|
||||
b.default_step.dependOn(&static_lib.step);
|
||||
}
|
||||
|
||||
// On Mac we can build the app.
|
||||
const macapp = b.step("macapp", "Build macOS app using XCode.");
|
||||
if (builtin.target.isDarwin()) {
|
||||
|
@ -1,3 +1,10 @@
|
||||
// Ghostty embedding API. The documentation for the embedding API is
|
||||
// only within the Zig source files that define the implementations. This
|
||||
// isn't meant to be a general purpose embedding API (yet) so there hasn't
|
||||
// been documentation or example work beyond that.
|
||||
//
|
||||
// The only consumer of this API is the macOS app, but the API is built to
|
||||
// be more general purpose.
|
||||
#ifndef GHOSTTY_H
|
||||
#define GHOSTTY_H
|
||||
|
||||
@ -7,9 +14,14 @@ extern "C" {
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define GHOSTTY_SUCCESS 0
|
||||
typedef void *ghostty_t;
|
||||
typedef void *ghostty_config_t;
|
||||
|
||||
int ghostty_init(void);
|
||||
ghostty_t ghostty_init(void);
|
||||
ghostty_config_t ghostty_config_new(ghostty_t);
|
||||
void ghostty_config_free(ghostty_t, ghostty_config_t);
|
||||
void ghostty_config_load_string(ghostty_t, ghostty_config_t, const char *, uintptr_t);
|
||||
void ghostty_config_finalize(ghostty_config_t);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
@ -1,15 +1,68 @@
|
||||
import OSLog
|
||||
import SwiftUI
|
||||
import GhosttyKit
|
||||
|
||||
@main
|
||||
struct GhosttyApp: App {
|
||||
init() {
|
||||
assert(ghostty_init() == GHOSTTY_SUCCESS, "ghostty failed to initialize");
|
||||
}
|
||||
static let logger = Logger(
|
||||
subsystem: Bundle.main.bundleIdentifier!,
|
||||
category: String(describing: GhosttyApp.self)
|
||||
)
|
||||
|
||||
/// The ghostty global state. Only one per process.
|
||||
@StateObject private var ghostty = GhosttyState()
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
switch ghostty.readiness {
|
||||
case .error:
|
||||
Text("Error")
|
||||
case .ready:
|
||||
Text("Hello!").font(.largeTitle)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GhosttyState: ObservableObject {
|
||||
enum Readiness {
|
||||
case error, ready
|
||||
}
|
||||
|
||||
/// The readiness value of the state.
|
||||
var readiness: Readiness { ghostty != nil ? .ready : .error }
|
||||
|
||||
/// The ghostty global state.
|
||||
var ghostty: ghostty_t? = nil
|
||||
|
||||
/// The ghostty global configuration.
|
||||
var config: ghostty_config_t? = nil
|
||||
|
||||
init() {
|
||||
// Initialize ghostty global state. This happens once per process.
|
||||
guard let g = ghostty_init() else {
|
||||
GhosttyApp.logger.critical("ghostty_init failed")
|
||||
return
|
||||
}
|
||||
|
||||
// Initialize the global configuration.
|
||||
guard let cfg = ghostty_config_new(g) else {
|
||||
GhosttyApp.logger.critical("ghostty_config_new failed")
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: we'd probably do some config loading here... for now we'd
|
||||
// have to do this synchronously. When we support config updating we can do
|
||||
// this async and update later.
|
||||
|
||||
// Finalize will make our defaults available.
|
||||
ghostty_config_finalize(cfg)
|
||||
|
||||
ghostty = g;
|
||||
config = cfg;
|
||||
}
|
||||
|
||||
deinit {
|
||||
ghostty_config_free(ghostty, config)
|
||||
}
|
||||
}
|
||||
|
10
src/App.zig
10
src/App.zig
@ -297,13 +297,5 @@ pub const Wasm = if (!builtin.target.isWasm()) struct {} else struct {
|
||||
};
|
||||
|
||||
pub const CAPI = struct {
|
||||
const ProcessState = @import("main.zig").ProcessState;
|
||||
var state: ?ProcessState = null;
|
||||
|
||||
export fn ghostty_init() c_int {
|
||||
assert(state == null);
|
||||
state = undefined;
|
||||
ProcessState.init(&state.?);
|
||||
return 0;
|
||||
}
|
||||
const Ghostty = @import("main_c.zig").Ghostty;
|
||||
};
|
||||
|
@ -583,6 +583,59 @@ pub const Wasm = if (!builtin.target.isWasm()) struct {} else struct {
|
||||
}
|
||||
};
|
||||
|
||||
// Wasm API.
|
||||
pub const CAPI = struct {
|
||||
const Ghostty = @import("main_c.zig").Ghostty;
|
||||
const cli_args = @import("cli_args.zig");
|
||||
|
||||
/// Create a new configuration filled with the initial default values.
|
||||
export fn ghostty_config_new(g: *Ghostty) ?*Config {
|
||||
const result = g.alloc.create(Config) catch |err| {
|
||||
log.err("error allocating config err={}", .{err});
|
||||
return null;
|
||||
};
|
||||
|
||||
result.* = Config.default(g.alloc) catch |err| {
|
||||
log.err("error creating config err={}", .{err});
|
||||
return null;
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export fn ghostty_config_free(g: *Ghostty, ptr: ?*Config) void {
|
||||
if (ptr) |v| {
|
||||
v.deinit();
|
||||
g.alloc.destroy(v);
|
||||
}
|
||||
}
|
||||
|
||||
/// Load the configuration from a string in the same format as
|
||||
/// the file-based syntax for the desktop version of the terminal.
|
||||
export fn ghostty_config_load_string(
|
||||
g: *Ghostty,
|
||||
self: *Config,
|
||||
str: [*]const u8,
|
||||
len: usize,
|
||||
) void {
|
||||
config_load_string_(g, self, str[0..len]) catch |err| {
|
||||
log.err("error loading config err={}", .{err});
|
||||
};
|
||||
}
|
||||
|
||||
fn config_load_string_(g: *Ghostty, self: *Config, str: []const u8) !void {
|
||||
var fbs = std.io.fixedBufferStream(str);
|
||||
var iter = cli_args.lineIterator(fbs.reader());
|
||||
try cli_args.parse(Config, g.alloc, self, &iter);
|
||||
}
|
||||
|
||||
export fn ghostty_config_finalize(self: *Config) void {
|
||||
self.finalize() catch |err| {
|
||||
log.err("error finalizing config err={}", .{err});
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
test {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
|
89
src/main.zig
89
src/main.zig
@ -2,100 +2,17 @@ const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const options = @import("build_options");
|
||||
const glfw = @import("glfw");
|
||||
const fontconfig = @import("fontconfig");
|
||||
const freetype = @import("freetype");
|
||||
const harfbuzz = @import("harfbuzz");
|
||||
const macos = @import("macos");
|
||||
const tracy = @import("tracy");
|
||||
const xev = @import("xev");
|
||||
const renderer = @import("renderer.zig");
|
||||
const xdg = @import("xdg.zig");
|
||||
const internal_os = @import("os/main.zig");
|
||||
|
||||
const App = @import("App.zig");
|
||||
const cli_args = @import("cli_args.zig");
|
||||
const Config = @import("config.zig").Config;
|
||||
|
||||
/// ProcessState represents the global process state. There should only
|
||||
/// be one of these at any given moment. This is extracted into a dedicated
|
||||
/// struct because it is reused by main and the static C lib.
|
||||
///
|
||||
/// ProcessState.init should be one of the first things ever called
|
||||
/// when using Ghostty. Ghostty calls this for you so this is more of a note
|
||||
/// for maintainers.
|
||||
pub const ProcessState = struct {
|
||||
const GPA = std.heap.GeneralPurposeAllocator(.{});
|
||||
|
||||
gpa: ?GPA,
|
||||
alloc: std.mem.Allocator,
|
||||
|
||||
pub fn init(self: *ProcessState) void {
|
||||
// Output some debug information right away
|
||||
std.log.info("dependency harfbuzz={s}", .{harfbuzz.versionString()});
|
||||
if (options.fontconfig) {
|
||||
std.log.info("dependency fontconfig={d}", .{fontconfig.version()});
|
||||
}
|
||||
std.log.info("renderer={}", .{renderer.Renderer});
|
||||
std.log.info("libxev backend={}", .{xev.backend});
|
||||
|
||||
// First things first, we fix our file descriptors
|
||||
internal_os.fixMaxFiles();
|
||||
|
||||
// We need to make sure the process locale is set properly. Locale
|
||||
// affects a lot of behaviors in a shell.
|
||||
internal_os.ensureLocale();
|
||||
|
||||
// Initialize ourself to nothing so we don't have any extra state.
|
||||
self.* = .{
|
||||
.gpa = null,
|
||||
.alloc = undefined,
|
||||
};
|
||||
errdefer self.deinit();
|
||||
|
||||
self.gpa = gpa: {
|
||||
// Use the libc allocator if it is available beacuse it is WAY
|
||||
// faster than GPA. We only do this in release modes so that we
|
||||
// can get easy memory leak detection in debug modes.
|
||||
if (builtin.link_libc) {
|
||||
if (switch (builtin.mode) {
|
||||
.ReleaseSafe, .ReleaseFast => true,
|
||||
|
||||
// We also use it if we can detect we're running under
|
||||
// Valgrind since Valgrind only instruments the C allocator
|
||||
else => std.valgrind.runningOnValgrind() > 0,
|
||||
}) break :gpa null;
|
||||
}
|
||||
|
||||
break :gpa GPA{};
|
||||
};
|
||||
|
||||
self.alloc = alloc: {
|
||||
const base = if (self.gpa) |*value|
|
||||
value.allocator()
|
||||
else if (builtin.link_libc)
|
||||
std.heap.c_allocator
|
||||
else
|
||||
unreachable;
|
||||
|
||||
// If we're tracing, wrap the allocator
|
||||
if (!tracy.enabled) break :alloc base;
|
||||
var tracy_alloc = tracy.allocator(base, null);
|
||||
break :alloc tracy_alloc.allocator();
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *ProcessState) void {
|
||||
if (self.gpa) |*value| {
|
||||
// We want to ensure that we deinit the GPA because this is
|
||||
// the point at which it will output if there were safety violations.
|
||||
_ = value.deinit();
|
||||
}
|
||||
}
|
||||
};
|
||||
const Ghostty = @import("main_c.zig").Ghostty;
|
||||
|
||||
pub fn main() !void {
|
||||
var state: ProcessState = undefined;
|
||||
ProcessState.init(&state);
|
||||
var state: Ghostty = undefined;
|
||||
Ghostty.init(&state);
|
||||
defer state.deinit();
|
||||
const alloc = state.alloc;
|
||||
|
||||
|
103
src/main_c.zig
103
src/main_c.zig
@ -2,10 +2,109 @@
|
||||
// within other applications. Depending on the build settings some APIs
|
||||
// may not be available (i.e. embedding into macOS exposes various Metal
|
||||
// support).
|
||||
//
|
||||
// This currently isn't supported as a general purpose embedding API.
|
||||
// This is currently used only to embed ghostty within a macOS app. However,
|
||||
// it could be expanded to be general purpose in the future.
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
const builtin = @import("builtin");
|
||||
const options = @import("build_options");
|
||||
const fontconfig = @import("fontconfig");
|
||||
const harfbuzz = @import("harfbuzz");
|
||||
const renderer = @import("renderer.zig");
|
||||
const tracy = @import("tracy");
|
||||
const xev = @import("xev");
|
||||
const internal_os = @import("os/main.zig");
|
||||
const main = @import("main.zig");
|
||||
|
||||
pub usingnamespace @import("App.zig").CAPI;
|
||||
|
||||
/// Global options so we can log. This is identical to main.
|
||||
pub const std_options = main.std_options;
|
||||
|
||||
pub usingnamespace @import("App.zig").CAPI;
|
||||
pub usingnamespace @import("config.zig").CAPI;
|
||||
|
||||
/// Initialize ghostty global state. It is possible to have more than
|
||||
/// one global state but it has zero practical benefit.
|
||||
export fn ghostty_init() ?*Ghostty {
|
||||
assert(builtin.link_libc);
|
||||
const alloc = std.heap.c_allocator;
|
||||
const g = alloc.create(Ghostty) catch return null;
|
||||
Ghostty.init(g);
|
||||
return g;
|
||||
}
|
||||
|
||||
/// This represents the global process state. There should only
|
||||
/// be one of these at any given moment. This is extracted into a dedicated
|
||||
/// struct because it is reused by main and the static C lib.
|
||||
///
|
||||
/// init should be one of the first things ever called when using Ghostty.
|
||||
pub const Ghostty = struct {
|
||||
const GPA = std.heap.GeneralPurposeAllocator(.{});
|
||||
|
||||
gpa: ?GPA,
|
||||
alloc: std.mem.Allocator,
|
||||
|
||||
pub fn init(self: *Ghostty) void {
|
||||
// Output some debug information right away
|
||||
std.log.info("dependency harfbuzz={s}", .{harfbuzz.versionString()});
|
||||
if (options.fontconfig) {
|
||||
std.log.info("dependency fontconfig={d}", .{fontconfig.version()});
|
||||
}
|
||||
std.log.info("renderer={}", .{renderer.Renderer});
|
||||
std.log.info("libxev backend={}", .{xev.backend});
|
||||
|
||||
// First things first, we fix our file descriptors
|
||||
internal_os.fixMaxFiles();
|
||||
|
||||
// We need to make sure the process locale is set properly. Locale
|
||||
// affects a lot of behaviors in a shell.
|
||||
internal_os.ensureLocale();
|
||||
|
||||
// Initialize ourself to nothing so we don't have any extra state.
|
||||
self.* = .{
|
||||
.gpa = null,
|
||||
.alloc = undefined,
|
||||
};
|
||||
errdefer self.deinit();
|
||||
|
||||
self.gpa = gpa: {
|
||||
// Use the libc allocator if it is available beacuse it is WAY
|
||||
// faster than GPA. We only do this in release modes so that we
|
||||
// can get easy memory leak detection in debug modes.
|
||||
if (builtin.link_libc) {
|
||||
if (switch (builtin.mode) {
|
||||
.ReleaseSafe, .ReleaseFast => true,
|
||||
|
||||
// We also use it if we can detect we're running under
|
||||
// Valgrind since Valgrind only instruments the C allocator
|
||||
else => std.valgrind.runningOnValgrind() > 0,
|
||||
}) break :gpa null;
|
||||
}
|
||||
|
||||
break :gpa GPA{};
|
||||
};
|
||||
|
||||
self.alloc = alloc: {
|
||||
const base = if (self.gpa) |*value|
|
||||
value.allocator()
|
||||
else if (builtin.link_libc)
|
||||
std.heap.c_allocator
|
||||
else
|
||||
unreachable;
|
||||
|
||||
// If we're tracing, wrap the allocator
|
||||
if (!tracy.enabled) break :alloc base;
|
||||
var tracy_alloc = tracy.allocator(base, null);
|
||||
break :alloc tracy_alloc.allocator();
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Ghostty) void {
|
||||
if (self.gpa) |*value| {
|
||||
// We want to ensure that we deinit the GPA because this is
|
||||
// the point at which it will output if there were safety violations.
|
||||
_ = value.deinit();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
Reference in New Issue
Block a user