apprt: start embedded implement, make App API available to C

This commit is contained in:
Mitchell Hashimoto
2023-02-16 08:52:40 -08:00
parent ba8f142770
commit eed6979868
9 changed files with 131 additions and 11 deletions

View File

@ -16,14 +16,35 @@ extern "C" {
#define GHOSTTY_SUCCESS 0
//-------------------------------------------------------------------
// Types
// Fully defined types. This MUST be kept in sync with equivalent Zig
// structs. To find the Zig struct, grep for this type name. The documentation
// for all of these types is available in the Zig source.
typedef void (*ghostty_runtime_wakeup_cb)(void *);
typedef struct {
void *userdata;
ghostty_runtime_wakeup_cb wakeup_cb;
} ghostty_runtime_config_s;
// Opaque types
typedef void *ghostty_app_t;
typedef void *ghostty_config_t;
//-------------------------------------------------------------------
// Published API
int ghostty_init(void);
ghostty_config_t ghostty_config_new();
void ghostty_config_free(ghostty_config_t);
void ghostty_config_load_string(ghostty_config_t, const char *, uintptr_t);
void ghostty_config_finalize(ghostty_config_t);
ghostty_app_t ghostty_app_new(ghostty_runtime_config_s *, ghostty_config_t);
void ghostty_app_free(ghostty_app_t);
#ifdef __cplusplus
}
#endif

View File

@ -65,9 +65,13 @@ pub const Darwin = struct {
/// Initialize the main app instance. This creates the main window, sets
/// up the renderer state, compiles the shaders, etc. This is the primary
/// "startup" logic.
pub fn create(alloc: Allocator, config: *const Config) !*App {
pub fn create(
alloc: Allocator,
rt_opts: apprt.runtime.App.Options,
config: *const Config,
) !*App {
// Initialize app runtime
var app_backend = try apprt.runtime.App.init();
var app_backend = try apprt.runtime.App.init(rt_opts);
errdefer app_backend.terminate();
// The mailbox for messaging this thread
@ -303,3 +307,35 @@ pub const Wasm = if (!builtin.target.isWasm()) struct {} else struct {
// }
// }
};
// C API
pub const CAPI = struct {
const global = &@import("main.zig").state;
/// Create a new app.
export fn ghostty_app_new(
opts: *const apprt.runtime.App.Options,
config: *const Config,
) ?*App {
return app_new_(opts, config) catch |err| {
log.err("error initializing app err={}", .{err});
return null;
};
}
fn app_new_(
opts: *const apprt.runtime.App.Options,
config: *const Config,
) !*App {
const app = try App.create(global.alloc, opts.*, config);
errdefer app.destroy();
return app;
}
export fn ghostty_app_free(ptr: ?*App) void {
if (ptr) |v| {
v.destroy();
v.alloc.destroy(v);
}
}
};

View File

@ -4,6 +4,7 @@ const DevMode = @This();
const std = @import("std");
const builtin = @import("builtin");
const build_config = @import("build_config.zig");
const imgui = @import("imgui");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
@ -15,7 +16,8 @@ const Config = @import("config.zig").Config;
/// If this is false, the rest of the terminal will be compiled without
/// dev mode support at all.
pub const enabled = !builtin.target.isWasm();
/// TODO: remove this and use build_config everywhere
pub const enabled = build_config.devmode_enabled;
/// The global DevMode instance that can be used app-wide. Assume all functions
/// are NOT thread-safe unless otherwise noted.

View File

@ -9,20 +9,22 @@
//! logic as possible, and to only reach out to platform-specific implementation
//! code when absolutely necessary.
const builtin = @import("builtin");
const build_config = @import("build_config.zig");
pub usingnamespace @import("apprt/structs.zig");
pub const glfw = @import("apprt/glfw.zig");
pub const browser = @import("apprt/browser.zig");
pub const embedded = @import("apprt/embedded.zig");
pub const Window = @import("apprt/Window.zig");
/// The implementation to use for the app runtime. This is comptime chosen
/// so that every build has exactly one application runtime implementation.
/// Note: it is very rare to use Runtime directly; most usage will use
/// Window or something.
pub const runtime = if (builtin.target.isWasm())
browser
else switch (builtin.os.tag) {
else => glfw,
pub const runtime = switch (build_config.artifact) {
.exe => glfw,
.lib => embedded,
.wasm_module => browser,
};
test {

43
src/apprt/embedded.zig Normal file
View File

@ -0,0 +1,43 @@
//! Application runtime for the embedded version of Ghostty. The embedded
//! version is when Ghostty is embedded within a parent host application,
//! rather than owning the application lifecycle itself. This is used for
//! example for the macOS build of Ghostty so that we can use a native
//! Swift+XCode-based application.
pub const App = struct {
/// Because we only expect the embedding API to be used in embedded
/// environments, the options are extern so that we can expose it
/// directly to a C callconv and not pay for any translation costs.
///
/// C type: ghostty_runtime_config_s
pub const Options = extern struct {
/// Userdata that is passed to all the callbacks.
userdata: ?*anyopaque = null,
/// Callback called to wakeup the event loop. This should trigger
/// a full tick of the app loop.
wakeup: *const fn (?*anyopaque) callconv(.C) void,
};
pub fn init(_: Options) !App {
return .{};
}
pub fn terminate(self: App) void {
_ = self;
}
pub fn wakeup(self: App) !void {
_ = self;
}
pub fn wait(self: App) !void {
_ = self;
}
};
pub const Window = struct {
pub fn deinit(self: *Window) void {
_ = self;
}
};

View File

@ -26,7 +26,9 @@ const glfwNative = glfw.Native(.{
const log = std.log.scoped(.glfw);
pub const App = struct {
pub fn init() !App {
pub const Options = struct {};
pub fn init(_: Options) !App {
if (!glfw.init(.{})) return error.GlfwInitFailed;
return .{};
}

View File

@ -10,6 +10,10 @@ const assert = std.debug.assert;
/// building a standalone exe, an embedded lib, etc.
pub const artifact = Artifact.detect();
/// Whether our devmode UI is enabled or not. This requires imgui to be
/// compiled.
pub const devmode_enabled = artifact == .exe;
pub const Artifact = enum {
/// Standalone executable
exe,
@ -29,8 +33,11 @@ pub const Artifact = enum {
return switch (builtin.output_mode) {
.Exe => .exe,
.Obj => .lib,
else => unreachable,
.Lib => .lib,
else => {
@compileLog(builtin.output_mode);
@compileError("unsupported artifact output mode");
},
};
}
};

View File

@ -92,7 +92,7 @@ pub fn main() !void {
glfw.setErrorCallback(glfwErrorCallback);
// Run our app with a single initial window to start.
var app = try App.create(alloc, &config);
var app = try App.create(alloc, .{}, &config);
defer app.destroy();
try app.run();
}

View File

@ -11,10 +11,17 @@ const assert = std.debug.assert;
const builtin = @import("builtin");
const main = @import("main.zig");
// Some comptime assertions that our C API depends on.
comptime {
const apprt = @import("apprt.zig");
assert(apprt.runtime == apprt.embedded);
}
/// Global options so we can log. This is identical to main.
pub const std_options = main.std_options;
pub usingnamespace @import("config.zig").CAPI;
pub usingnamespace @import("App.zig").CAPI;
/// Initialize ghostty global state. It is possible to have more than
/// one global state but it has zero practical benefit.