Skip to content

Commit debcffb

Browse files
authored
libghostty: make headers C++ compatible (#11950)
The headers were not C++ compatible and would fail compiling before (see #11878). The only reason is because our typedefs would conflict since we named them identically. This also adds a `c-vt-stream` example and a `cpp-vt-stream` example, the latter primarily to verify we can build in C++ mode.
2 parents 0f6e733 + 1fcd80d commit debcffb

File tree

17 files changed

+309
-12
lines changed

17 files changed

+309
-12
lines changed

example/c-vt-stream/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Example: VT Stream Processing in C
2+
3+
This contains a simple example of how to use `ghostty_terminal_vt_write`
4+
to parse and process VT sequences in C. This is the C equivalent of
5+
the `zig-vt-stream` example, ideal for read-only terminal applications
6+
such as replay tooling, CI log viewers, and PaaS builder output.
7+
8+
This uses a `build.zig` and `Zig` to build the C program so that we
9+
can reuse a lot of our build logic and depend directly on our source
10+
tree, but Ghostty emits a standard C library that can be used with any
11+
C tooling.
12+
13+
## Usage
14+
15+
Run the program:
16+
17+
```shell-session
18+
zig build run
19+
```

example/c-vt-stream/build.zig

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
const std = @import("std");
2+
3+
pub fn build(b: *std.Build) void {
4+
const target = b.standardTargetOptions(.{});
5+
const optimize = b.standardOptimizeOption(.{});
6+
7+
const run_step = b.step("run", "Run the app");
8+
9+
const exe_mod = b.createModule(.{
10+
.target = target,
11+
.optimize = optimize,
12+
});
13+
exe_mod.addCSourceFiles(.{
14+
.root = b.path("src"),
15+
.files = &.{"main.c"},
16+
});
17+
18+
// You'll want to use a lazy dependency here so that ghostty is only
19+
// downloaded if you actually need it.
20+
if (b.lazyDependency("ghostty", .{
21+
// Setting simd to false will force a pure static build that
22+
// doesn't even require libc, but it has a significant performance
23+
// penalty. If your embedding app requires libc anyway, you should
24+
// always keep simd enabled.
25+
// .simd = false,
26+
})) |dep| {
27+
exe_mod.linkLibrary(dep.artifact("ghostty-vt"));
28+
}
29+
30+
// Exe
31+
const exe = b.addExecutable(.{
32+
.name = "c_vt_stream",
33+
.root_module = exe_mod,
34+
});
35+
b.installArtifact(exe);
36+
37+
// Run
38+
const run_cmd = b.addRunArtifact(exe);
39+
run_cmd.step.dependOn(b.getInstallStep());
40+
if (b.args) |args| run_cmd.addArgs(args);
41+
run_step.dependOn(&run_cmd.step);
42+
}

example/c-vt-stream/build.zig.zon

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
.{
2+
.name = .c_vt_stream,
3+
.version = "0.0.0",
4+
.fingerprint = 0xd5bb3fc45e3f4dfc,
5+
.minimum_zig_version = "0.15.1",
6+
.dependencies = .{
7+
// Ghostty dependency. In reality, you'd probably use a URL-based
8+
// dependency like the one showed (and commented out) below this one.
9+
// We use a path dependency here for simplicity and to ensure our
10+
// examples always test against the source they're bundled with.
11+
.ghostty = .{ .path = "../../" },
12+
13+
// Example of what a URL-based dependency looks like:
14+
// .ghostty = .{
15+
// .url = "https://github.com/ghostty-org/ghostty/archive/COMMIT.tar.gz",
16+
// .hash = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO36s",
17+
// },
18+
},
19+
.paths = .{
20+
"build.zig",
21+
"build.zig.zon",
22+
"src",
23+
},
24+
}

example/c-vt-stream/src/main.c

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#include <assert.h>
2+
#include <stdio.h>
3+
#include <string.h>
4+
#include <ghostty/vt.h>
5+
6+
int main(void) {
7+
//! [vt-stream-init]
8+
// Create a terminal
9+
GhosttyTerminal terminal;
10+
GhosttyTerminalOptions opts = {
11+
.cols = 80,
12+
.rows = 24,
13+
.max_scrollback = 0,
14+
};
15+
GhosttyResult result = ghostty_terminal_new(NULL, &terminal, opts);
16+
assert(result == GHOSTTY_SUCCESS);
17+
//! [vt-stream-init]
18+
19+
//! [vt-stream-write]
20+
// Feed VT data into the terminal
21+
const char *text = "Hello, World!\r\n";
22+
ghostty_terminal_vt_write(terminal, (const uint8_t *)text, strlen(text));
23+
24+
// ANSI color codes: ESC[1;32m = bold green, ESC[0m = reset
25+
text = "\x1b[1;32mGreen Text\x1b[0m\r\n";
26+
ghostty_terminal_vt_write(terminal, (const uint8_t *)text, strlen(text));
27+
28+
// Cursor positioning: ESC[1;1H = move to row 1, column 1
29+
text = "\x1b[1;1HTop-left corner\r\n";
30+
ghostty_terminal_vt_write(terminal, (const uint8_t *)text, strlen(text));
31+
32+
// Cursor movement: ESC[5B = move down 5 lines
33+
text = "\x1b[5B";
34+
ghostty_terminal_vt_write(terminal, (const uint8_t *)text, strlen(text));
35+
text = "Moved down!\r\n";
36+
ghostty_terminal_vt_write(terminal, (const uint8_t *)text, strlen(text));
37+
38+
// Erase line: ESC[2K = clear entire line
39+
text = "\x1b[2K";
40+
ghostty_terminal_vt_write(terminal, (const uint8_t *)text, strlen(text));
41+
text = "New content\r\n";
42+
ghostty_terminal_vt_write(terminal, (const uint8_t *)text, strlen(text));
43+
44+
// Multiple lines
45+
text = "Line A\r\nLine B\r\nLine C\r\n";
46+
ghostty_terminal_vt_write(terminal, (const uint8_t *)text, strlen(text));
47+
//! [vt-stream-write]
48+
49+
//! [vt-stream-read]
50+
// Get the final terminal state as a plain string using the formatter
51+
GhosttyFormatterTerminalOptions fmt_opts =
52+
GHOSTTY_INIT_SIZED(GhosttyFormatterTerminalOptions);
53+
fmt_opts.emit = GHOSTTY_FORMATTER_FORMAT_PLAIN;
54+
fmt_opts.trim = true;
55+
56+
GhosttyFormatter formatter;
57+
result = ghostty_formatter_terminal_new(NULL, &formatter, terminal, fmt_opts);
58+
assert(result == GHOSTTY_SUCCESS);
59+
60+
uint8_t *buf = NULL;
61+
size_t len = 0;
62+
result = ghostty_formatter_format_alloc(formatter, NULL, &buf, &len);
63+
assert(result == GHOSTTY_SUCCESS);
64+
65+
fwrite(buf, 1, len, stdout);
66+
printf("\n");
67+
68+
ghostty_free(NULL, buf, len);
69+
ghostty_formatter_free(formatter);
70+
//! [vt-stream-read]
71+
72+
ghostty_terminal_free(terminal);
73+
return 0;
74+
}

example/cpp-vt-stream/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Example: VT Stream Processing in C++
2+
3+
This contains a simple example of how to use `ghostty_terminal_vt_write`
4+
to parse and process VT sequences in C++. This is a simplified C++ port
5+
of the `c-vt-stream` example that verifies libghostty compiles in C++
6+
mode.
7+
8+
> [!IMPORTANT]
9+
>
10+
> **`libghostty` is a C library.** This example is only here so our CI
11+
> verifies that the library can be built in used from C++ files.
12+
13+
## Usage
14+
15+
Run the program:
16+
17+
```shell-session
18+
zig build run
19+
```

example/cpp-vt-stream/build.zig

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
const std = @import("std");
2+
3+
pub fn build(b: *std.Build) void {
4+
const target = b.standardTargetOptions(.{});
5+
const optimize = b.standardOptimizeOption(.{});
6+
7+
const run_step = b.step("run", "Run the app");
8+
9+
const exe_mod = b.createModule(.{
10+
.target = target,
11+
.optimize = optimize,
12+
});
13+
exe_mod.addCSourceFiles(.{
14+
.root = b.path("src"),
15+
.files = &.{"main.cpp"},
16+
});
17+
18+
// You'll want to use a lazy dependency here so that ghostty is only
19+
// downloaded if you actually need it.
20+
if (b.lazyDependency("ghostty", .{
21+
// Setting simd to false will force a pure static build that
22+
// doesn't even require libc, but it has a significant performance
23+
// penalty. If your embedding app requires libc anyway, you should
24+
// always keep simd enabled.
25+
// .simd = false,
26+
})) |dep| {
27+
exe_mod.linkLibrary(dep.artifact("ghostty-vt"));
28+
}
29+
30+
// Exe
31+
const exe = b.addExecutable(.{
32+
.name = "cpp_vt_stream",
33+
.root_module = exe_mod,
34+
});
35+
b.installArtifact(exe);
36+
37+
// Run
38+
const run_cmd = b.addRunArtifact(exe);
39+
run_cmd.step.dependOn(b.getInstallStep());
40+
if (b.args) |args| run_cmd.addArgs(args);
41+
run_step.dependOn(&run_cmd.step);
42+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
.{
2+
.name = .cpp_vt_stream,
3+
.version = "0.0.0",
4+
.fingerprint = 0x112f5d044ef8c2ac,
5+
.minimum_zig_version = "0.15.1",
6+
.dependencies = .{
7+
// Ghostty dependency. In reality, you'd probably use a URL-based
8+
// dependency like the one showed (and commented out) below this one.
9+
// We use a path dependency here for simplicity and to ensure our
10+
// examples always test against the source they're bundled with.
11+
.ghostty = .{ .path = "../../" },
12+
13+
// Example of what a URL-based dependency looks like:
14+
// .ghostty = .{
15+
// .url = "https://github.com/ghostty-org/ghostty/archive/COMMIT.tar.gz",
16+
// .hash = "N-V-__8AAMVLTABmYkLqhZPLXnMl-KyN38R8UVYqGrxqO36s",
17+
// },
18+
},
19+
.paths = .{
20+
"build.zig",
21+
"build.zig.zon",
22+
"src",
23+
},
24+
}

example/cpp-vt-stream/src/main.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#include <cassert>
2+
#include <cstdio>
3+
#include <cstring>
4+
#include <ghostty/vt.h>
5+
6+
int main() {
7+
// Create a terminal
8+
GhosttyTerminal terminal;
9+
GhosttyTerminalOptions opts = {
10+
.cols = 80,
11+
.rows = 24,
12+
.max_scrollback = 0,
13+
};
14+
GhosttyResult result = ghostty_terminal_new(nullptr, &terminal, opts);
15+
assert(result == GHOSTTY_SUCCESS);
16+
17+
// Feed VT data into the terminal
18+
const char *text = "Hello from C++!\r\n";
19+
ghostty_terminal_vt_write(terminal, reinterpret_cast<const uint8_t *>(text), std::strlen(text));
20+
21+
text = "\x1b[1;32mGreen Text\x1b[0m\r\n";
22+
ghostty_terminal_vt_write(terminal, reinterpret_cast<const uint8_t *>(text), std::strlen(text));
23+
24+
text = "\x1b[1;1HTop-left corner\r\n";
25+
ghostty_terminal_vt_write(terminal, reinterpret_cast<const uint8_t *>(text), std::strlen(text));
26+
27+
// Get the final terminal state as a plain string
28+
GhosttyFormatterTerminalOptions fmt_opts =
29+
GHOSTTY_INIT_SIZED(GhosttyFormatterTerminalOptions);
30+
fmt_opts.emit = GHOSTTY_FORMATTER_FORMAT_PLAIN;
31+
fmt_opts.trim = true;
32+
33+
GhosttyFormatter formatter;
34+
result = ghostty_formatter_terminal_new(nullptr, &formatter, terminal, fmt_opts);
35+
assert(result == GHOSTTY_SUCCESS);
36+
37+
uint8_t *buf = nullptr;
38+
size_t len = 0;
39+
result = ghostty_formatter_format_alloc(formatter, nullptr, &buf, &len);
40+
assert(result == GHOSTTY_SUCCESS);
41+
42+
std::fwrite(buf, 1, len, stdout);
43+
std::printf("\n");
44+
45+
ghostty_free(nullptr, buf, len);
46+
ghostty_formatter_free(formatter);
47+
ghostty_terminal_free(terminal);
48+
return 0;
49+
}

include/ghostty/vt/formatter.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ typedef struct {
111111
*
112112
* @ingroup formatter
113113
*/
114-
typedef struct GhosttyFormatter* GhosttyFormatter;
114+
typedef struct GhosttyFormatterImpl* GhosttyFormatter;
115115

116116
/**
117117
* Options for creating a terminal formatter.

include/ghostty/vt/key/encoder.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
*
2323
* @ingroup key
2424
*/
25-
typedef struct GhosttyKeyEncoder *GhosttyKeyEncoder;
25+
typedef struct GhosttyKeyEncoderImpl *GhosttyKeyEncoder;
2626

2727
/**
2828
* Kitty keyboard protocol flags.

0 commit comments

Comments
 (0)