diff --git a/example/c-vt-build-info/README.md b/example/c-vt-build-info/README.md new file mode 100644 index 000000000..08fc1cb3c --- /dev/null +++ b/example/c-vt-build-info/README.md @@ -0,0 +1,17 @@ +# Example: `ghostty-vt` Build Info + +This contains a simple example of how to use the `ghostty-vt` build info +API to query compile-time build configuration. + +This uses a `build.zig` and `Zig` to build the C program so that we +can reuse a lot of our build logic and depend directly on our source +tree, but Ghostty emits a standard C library that can be used with any +C tooling. + +## Usage + +Run the program: + +```shell-session +zig build run +``` diff --git a/example/c-vt-build-info/build.zig b/example/c-vt-build-info/build.zig new file mode 100644 index 000000000..2cd3d307a --- /dev/null +++ b/example/c-vt-build-info/build.zig @@ -0,0 +1,42 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const run_step = b.step("run", "Run the app"); + + const exe_mod = b.createModule(.{ + .target = target, + .optimize = optimize, + }); + exe_mod.addCSourceFiles(.{ + .root = b.path("src"), + .files = &.{"main.c"}, + }); + + // You'll want to use a lazy dependency here so that ghostty is only + // downloaded if you actually need it. + if (b.lazyDependency("ghostty", .{ + // Setting simd to false will force a pure static build that + // doesn't even require libc, but it has a significant performance + // penalty. If your embedding app requires libc anyway, you should + // always keep simd enabled. + // .simd = false, + })) |dep| { + exe_mod.linkLibrary(dep.artifact("ghostty-vt")); + } + + // Exe + const exe = b.addExecutable(.{ + .name = "c_vt_build_info", + .root_module = exe_mod, + }); + b.installArtifact(exe); + + // Run + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| run_cmd.addArgs(args); + run_step.dependOn(&run_cmd.step); +} diff --git a/example/c-vt-build-info/build.zig.zon b/example/c-vt-build-info/build.zig.zon new file mode 100644 index 000000000..14966615a --- /dev/null +++ b/example/c-vt-build-info/build.zig.zon @@ -0,0 +1,24 @@ +.{ + .name = .c_vt_build_info, + .version = "0.0.0", + .fingerprint = 0xc6b57ed4f83fb16, + .minimum_zig_version = "0.15.1", + .dependencies = .{ + // Ghostty dependency. In reality, you'd probably use a URL-based + // dependency like the one showed (and commented out) below this one. + // We use a path dependency here for simplicity and to ensure our + // examples always test against the source they're bundled with. + .ghostty = .{ .path = "../../" }, + + // Example of what a URL-based dependency looks like: + // .ghostty = .{ + // .url = "https://github.com/ghostty-org/ghostty/archive/COMMIT.tar.gz", + // .hash = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO36s", + // }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/example/c-vt-build-info/src/main.c b/example/c-vt-build-info/src/main.c new file mode 100644 index 000000000..11de240c6 --- /dev/null +++ b/example/c-vt-build-info/src/main.c @@ -0,0 +1,23 @@ +#include +#include + +//! [build-info-query] +void query_build_info() { + bool simd = false; + bool kitty_graphics = false; + bool tmux_control_mode = false; + + ghostty_build_info(GHOSTTY_BUILD_INFO_SIMD, &simd); + ghostty_build_info(GHOSTTY_BUILD_INFO_KITTY_GRAPHICS, &kitty_graphics); + ghostty_build_info(GHOSTTY_BUILD_INFO_TMUX_CONTROL_MODE, &tmux_control_mode); + + printf("SIMD: %s\n", simd ? "enabled" : "disabled"); + printf("Kitty graphics: %s\n", kitty_graphics ? "enabled" : "disabled"); + printf("Tmux control mode: %s\n", tmux_control_mode ? "enabled" : "disabled"); +} +//! [build-info-query] + +int main() { + query_build_info(); + return 0; +} diff --git a/include/ghostty/vt.h b/include/ghostty/vt.h index 5059a0bef..55ceb430d 100644 --- a/include/ghostty/vt.h +++ b/include/ghostty/vt.h @@ -34,6 +34,7 @@ * - @ref osc "OSC Parser" - Parse OSC (Operating System Command) sequences * - @ref sgr "SGR Parser" - Parse SGR (Select Graphic Rendition) sequences * - @ref paste "Paste Utilities" - Validate paste data safety + * - @ref build_info "Build Info" - Query compile-time build configuration * - @ref allocator "Memory Management" - Memory management and custom allocators * - @ref wasm "WebAssembly Utilities" - WebAssembly convenience functions * @@ -45,6 +46,7 @@ * @section examples_sec Examples * * Complete working examples: + * - @ref c-vt-build-info/src/main.c - Build info query example * - @ref c-vt/src/main.c - OSC parser example * - @ref c-vt-encode-key/src/main.c - Key encoding example * - @ref c-vt-encode-mouse/src/main.c - Mouse encoding example @@ -55,6 +57,11 @@ * */ +/** @example c-vt-build-info/src/main.c + * This example demonstrates how to query compile-time build configuration + * such as SIMD support, Kitty graphics, and tmux control mode availability. + */ + /** @example c-vt/src/main.c * This example demonstrates how to use the OSC parser to parse an OSC sequence, * extract command information, and retrieve command-specific data like window titles. @@ -100,6 +107,7 @@ extern "C" { #include #include +#include #include #include #include diff --git a/include/ghostty/vt/build_info.h b/include/ghostty/vt/build_info.h new file mode 100644 index 000000000..3737de875 --- /dev/null +++ b/include/ghostty/vt/build_info.h @@ -0,0 +1,86 @@ +/** + * @file build_info.h + * + * Build info - query compile-time build configuration of libghostty-vt. + */ + +#ifndef GHOSTTY_VT_BUILD_INFO_H +#define GHOSTTY_VT_BUILD_INFO_H + +/** @defgroup build_info Build Info + * + * Query compile-time build configuration of libghostty-vt. + * + * These values reflect the options the library was built with and are + * constant for the lifetime of the process. + * + * ## Basic Usage + * + * Use ghostty_build_info() to query individual build options: + * + * @snippet c-vt-build-info/src/main.c build-info-query + * + * @{ + */ + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Build info data types that can be queried. + * + * Each variant documents the expected output pointer type. + */ +typedef enum { + /** Invalid data type. Never results in any data extraction. */ + GHOSTTY_BUILD_INFO_INVALID = 0, + + /** + * Whether SIMD-accelerated code paths are enabled. + * + * Output type: bool * + */ + GHOSTTY_BUILD_INFO_SIMD = 1, + + /** + * Whether Kitty graphics protocol support is available. + * + * Output type: bool * + */ + GHOSTTY_BUILD_INFO_KITTY_GRAPHICS = 2, + + /** + * Whether tmux control mode support is available. + * + * Output type: bool * + */ + GHOSTTY_BUILD_INFO_TMUX_CONTROL_MODE = 3, +} GhosttyBuildInfo; + +/** + * Query a compile-time build configuration value. + * + * The caller must pass a pointer to the correct output type for the + * requested data (see GhosttyBuildInfo variants for types). + * + * @param data The build info field to query + * @param out Pointer to store the result (type depends on data parameter) + * @return GHOSTTY_SUCCESS on success, GHOSTTY_INVALID_VALUE if the + * data type is invalid + * + * @ingroup build_info + */ +GhosttyResult ghostty_build_info(GhosttyBuildInfo data, void *out); + +#ifdef __cplusplus +} +#endif + +/** @} */ + +#endif /* GHOSTTY_VT_BUILD_INFO_H */ diff --git a/src/lib_vt.zig b/src/lib_vt.zig index 6b6d0ad77..7a75bb92a 100644 --- a/src/lib_vt.zig +++ b/src/lib_vt.zig @@ -217,6 +217,7 @@ comptime { @export(&c.grid_ref_row, .{ .name = "ghostty_grid_ref_row" }); @export(&c.grid_ref_graphemes, .{ .name = "ghostty_grid_ref_graphemes" }); @export(&c.grid_ref_style, .{ .name = "ghostty_grid_ref_style" }); + @export(&c.build_info, .{ .name = "ghostty_build_info" }); // On Wasm we need to export our allocator convenience functions. if (builtin.target.cpu.arch.isWasm()) { diff --git a/src/terminal/c/build_info.zig b/src/terminal/c/build_info.zig new file mode 100644 index 000000000..ed67e371f --- /dev/null +++ b/src/terminal/c/build_info.zig @@ -0,0 +1,79 @@ +const std = @import("std"); +const build_options = @import("terminal_options"); +const Result = @import("result.zig").Result; + +const log = std.log.scoped(.build_info_c); + +/// C: GhosttyBuildInfo +pub const BuildInfo = enum(c_int) { + invalid = 0, + simd = 1, + kitty_graphics = 2, + tmux_control_mode = 3, + + /// Output type expected for querying the data of the given kind. + pub fn OutType(comptime self: BuildInfo) type { + return switch (self) { + .invalid => void, + .simd, .kitty_graphics, .tmux_control_mode => bool, + }; + } +}; + +pub fn get( + data: BuildInfo, + out: ?*anyopaque, +) callconv(.c) Result { + if (comptime std.debug.runtime_safety) { + _ = std.meta.intToEnum(BuildInfo, @intFromEnum(data)) catch { + log.warn("build_info invalid data value={d}", .{@intFromEnum(data)}); + return .invalid_value; + }; + } + + return switch (data) { + inline else => |comptime_data| getTyped( + comptime_data, + @ptrCast(@alignCast(out)), + ), + }; +} + +fn getTyped( + comptime data: BuildInfo, + out: *data.OutType(), +) Result { + switch (data) { + .invalid => return .invalid_value, + .simd => out.* = build_options.simd, + .kitty_graphics => out.* = build_options.kitty_graphics, + .tmux_control_mode => out.* = build_options.tmux_control_mode, + } + + return .success; +} + +test "get simd" { + const testing = std.testing; + var value: bool = undefined; + try testing.expectEqual(Result.success, get(.simd, @ptrCast(&value))); + try testing.expectEqual(build_options.simd, value); +} + +test "get kitty_graphics" { + const testing = std.testing; + var value: bool = undefined; + try testing.expectEqual(Result.success, get(.kitty_graphics, @ptrCast(&value))); + try testing.expectEqual(build_options.kitty_graphics, value); +} + +test "get tmux_control_mode" { + const testing = std.testing; + var value: bool = undefined; + try testing.expectEqual(Result.success, get(.tmux_control_mode, @ptrCast(&value))); + try testing.expectEqual(build_options.tmux_control_mode, value); +} + +test "get invalid" { + try std.testing.expectEqual(Result.invalid_value, get(.invalid, null)); +} diff --git a/src/terminal/c/main.zig b/src/terminal/c/main.zig index d58a81378..11e14f8c7 100644 --- a/src/terminal/c/main.zig +++ b/src/terminal/c/main.zig @@ -1,3 +1,4 @@ +const buildpkg = @import("build_info.zig"); pub const cell = @import("cell.zig"); pub const color = @import("color.zig"); pub const focus = @import("focus.zig"); @@ -17,6 +18,8 @@ pub const style = @import("style.zig"); pub const terminal = @import("terminal.zig"); // The full C API, unexported. +pub const build_info = buildpkg.get; + pub const osc_new = osc.new; pub const osc_free = osc.free; pub const osc_reset = osc.reset; @@ -136,6 +139,7 @@ pub const grid_ref_graphemes = grid_ref.grid_ref_graphemes; pub const grid_ref_style = grid_ref.grid_ref_style; test { + _ = buildpkg; _ = cell; _ = color; _ = grid_ref;