Merge pull request #1324 from mitchellh/libghostty-linux

libghostty: support build on Linux
This commit is contained in:
Mitchell Hashimoto
2024-01-18 09:16:44 -08:00
committed by GitHub
7 changed files with 184 additions and 38 deletions

View File

@ -40,6 +40,25 @@ jobs:
- name: Test Build
run: nix develop -c zig build -Dstatic=true -Dapp-runtime=glfw -Dtarget=${{ matrix.target }}
build-linux-libghostty:
runs-on: ubuntu-latest
needs: test
steps:
- name: Checkout code
uses: actions/checkout@v4
# Install Nix and use that to run our tests so our environment matches exactly.
- uses: cachix/install-nix-action@v24
with:
nix_path: nixpkgs=channel:nixos-unstable
- uses: cachix/cachix-action@v14
with:
name: ghostty
authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}"
- name: Build Libghostty
run: nix develop -c zig build -Dapp-runtime=none
build-nix:
runs-on: ubuntu-latest
needs: test

View File

@ -405,7 +405,7 @@ pub fn build(b: *std.Build) !void {
}
// App (Linux)
if (target.result.os.tag == .linux) {
if (target.result.os.tag == .linux and config.app_runtime != .none) {
// https://developer.gnome.org/documentation/guidelines/maintainer/integrating.html
// Desktop file so that we have an icon and other metadata
@ -428,6 +428,52 @@ pub fn build(b: *std.Build) !void {
b.installFile("images/icons/icon_256x256@2x@2x.png", "share/icons/hicolor/256x256@2/apps/com.mitchellh.ghostty.png");
}
// libghostty (non-Darwin)
if (!builtin.target.isDarwin() and config.app_runtime == .none) {
// Shared
{
const lib = b.addSharedLibrary(.{
.name = "ghostty",
.root_source_file = .{ .path = "src/main_c.zig" },
.optimize = optimize,
.target = target,
});
lib.root_module.addOptions("build_options", exe_options);
_ = try addDeps(b, lib, config);
const lib_install = b.addInstallLibFile(
lib.getEmittedBin(),
"libghostty.so",
);
b.getInstallStep().dependOn(&lib_install.step);
}
// Static
{
const lib = b.addStaticLibrary(.{
.name = "ghostty",
.root_source_file = .{ .path = "src/main_c.zig" },
.optimize = optimize,
.target = target,
});
lib.root_module.addOptions("build_options", exe_options);
_ = try addDeps(b, lib, config);
const lib_install = b.addInstallLibFile(
lib.getEmittedBin(),
"libghostty.a",
);
b.getInstallStep().dependOn(&lib_install.step);
}
// Copy our ghostty.h to include.
const header_install = b.addInstallHeaderFile(
"include/ghostty.h",
"ghostty.h",
);
b.getInstallStep().dependOn(&header_install.step);
}
// On Mac we can build the embedding library. This only handles the macOS lib.
if (builtin.target.isDarwin() and target.result.os.tag == .macos) {
// Create the universal macOS lib.

View File

@ -30,6 +30,11 @@ typedef void *ghostty_surface_t;
typedef void *ghostty_inspector_t;
// Enums are up top so we can reference them later.
typedef enum {
GHOSTTY_PLATFORM_INVALID,
GHOSTTY_PLATFORM_MACOS,
} ghostty_platform_e;
typedef enum {
GHOSTTY_CLIPBOARD_STANDARD,
GHOSTTY_CLIPBOARD_SELECTION,
@ -350,8 +355,17 @@ typedef struct {
} ghostty_error_s;
typedef struct {
void *userdata;
void *nsview;
} ghostty_platform_macos_s;
typedef union {
ghostty_platform_macos_s macos;
} ghostty_platform_u;
typedef struct {
ghostty_platform_e platform_tag;
ghostty_platform_u platform;
void *userdata;
double scale_factor;
uint16_t font_size;
const char *working_directory;
@ -472,9 +486,6 @@ uintptr_t ghostty_surface_pwd(ghostty_surface_t, char *, uintptr_t);
ghostty_inspector_t ghostty_surface_inspector(ghostty_surface_t);
void ghostty_inspector_free(ghostty_surface_t);
bool ghostty_inspector_metal_init(ghostty_inspector_t, void *);
void ghostty_inspector_metal_render(ghostty_inspector_t, void *, void *);
bool ghostty_inspector_metal_shutdown(ghostty_inspector_t);
void ghostty_inspector_set_focus(ghostty_inspector_t, bool);
void ghostty_inspector_set_content_scale(ghostty_inspector_t, double, double);
void ghostty_inspector_set_size(ghostty_inspector_t, uint32_t, uint32_t);
@ -484,6 +495,12 @@ void ghostty_inspector_mouse_scroll(ghostty_inspector_t, double, double, ghostty
void ghostty_inspector_key(ghostty_inspector_t, ghostty_input_action_e, ghostty_input_key_e, ghostty_input_mods_e);
void ghostty_inspector_text(ghostty_inspector_t, const char *);
#ifdef __APPLE__
bool ghostty_inspector_metal_init(ghostty_inspector_t, void *);
void ghostty_inspector_metal_render(ghostty_inspector_t, void *, void *);
bool ghostty_inspector_metal_shutdown(ghostty_inspector_t);
#endif
// APIs I'd like to get rid of eventually but are still needed for now.
// Don't use these unless you know what you're doing.
void ghostty_set_window_background_blur(ghostty_surface_t, void *);

View File

@ -277,8 +277,11 @@ extension Ghostty {
/// in the returned struct is only valid as long as this struct is retained.
func ghosttyConfig(view: SurfaceView) -> ghostty_surface_config_s {
var config = ghostty_surface_config_new()
config.platform_tag = GHOSTTY_PLATFORM_MACOS
config.platform = ghostty_platform_u(macos: ghostty_platform_macos_s(
nsview: Unmanaged.passUnretained(view).toOpaque()
))
config.userdata = Unmanaged.passUnretained(view).toOpaque()
config.nsview = Unmanaged.passUnretained(view).toOpaque()
config.scale_factor = NSScreen.main!.backingScaleFactor
if let fontSize = fontSize { config.font_size = fontSize }

View File

@ -231,9 +231,49 @@ pub const App = struct {
}
};
/// Platform-specific configuration for libghostty.
pub const Platform = union(PlatformTag) {
macos: MacOS,
// If our build target for libghostty is not darwin then we do
// not include macos support at all.
pub const MacOS = if (builtin.target.isDarwin()) struct {
/// The view to render the surface on.
nsview: objc.Object,
} else void;
// The C ABI compatible version of this union. The tag is expected
// to be stored elsewhere.
pub const C = extern union {
macos: extern struct {
nsview: ?*anyopaque,
},
};
/// Initialize a Platform a tag and configuration from the C ABI.
pub fn init(tag_int: c_int, c_platform: C) !Platform {
const tag = try std.meta.intToEnum(PlatformTag, tag_int);
return switch (tag) {
.macos => if (MacOS != void) macos: {
const config = c_platform.macos;
const nsview = objc.Object.fromId(config.nsview orelse
break :macos error.NSViewMustBeSet);
break :macos .{ .macos = .{ .nsview = nsview } };
} else error.UnsupportedPlatform,
};
}
};
pub const PlatformTag = enum(c_int) {
// "0" is reserved for invalid so we can detect unset values
// from the C API.
macos = 1,
};
pub const Surface = struct {
app: *App,
nsview: objc.Object,
platform: Platform,
core_surface: CoreSurface,
content_scale: apprt.ContentScale,
size: apprt.SurfaceSize,
@ -243,12 +283,14 @@ pub const Surface = struct {
inspector: ?*Inspector = null,
pub const Options = extern struct {
/// The platform that this surface is being initialized for and
/// the associated platform-specific configuration.
platform_tag: c_int = 0,
platform: Platform.C = undefined,
/// Userdata passed to some of the callbacks.
userdata: ?*anyopaque = null,
/// The pointer to the backing NSView for the surface.
nsview: ?*anyopaque = null,
/// The scale factor of the screen.
scale_factor: f64 = 1,
@ -279,13 +321,10 @@ pub const Surface = struct {
};
pub fn init(self: *Surface, app: *App, opts: Options) !void {
const nsview = objc.Object.fromId(opts.nsview orelse
return error.NSViewMustBeSet);
self.* = .{
.app = app,
.platform = try Platform.init(opts.platform_tag, opts.platform),
.core_surface = undefined,
.nsview = nsview,
.content_scale = .{
.x = @floatCast(opts.scale_factor),
.y = @floatCast(opts.scale_factor),
@ -1584,30 +1623,33 @@ pub const CAPI = struct {
ptr.freeInspector();
}
export fn ghostty_inspector_metal_init(ptr: *Inspector, device: objc.c.id) bool {
return ptr.initMetal(objc.Object.fromId(device));
}
export fn ghostty_inspector_metal_render(
ptr: *Inspector,
command_buffer: objc.c.id,
descriptor: objc.c.id,
) void {
return ptr.renderMetal(
objc.Object.fromId(command_buffer),
objc.Object.fromId(descriptor),
) catch |err| {
log.err("error rendering inspector err={}", .{err});
return;
};
}
export fn ghostty_inspector_metal_shutdown(ptr: *Inspector) void {
if (ptr.backend) |v| {
v.deinit();
ptr.backend = null;
// Inspector Metal APIs are only available on Apple systems
usingnamespace if (builtin.target.isDarwin()) struct {
export fn ghostty_inspector_metal_init(ptr: *Inspector, device: objc.c.id) bool {
return ptr.initMetal(objc.Object.fromId(device));
}
}
export fn ghostty_inspector_metal_render(
ptr: *Inspector,
command_buffer: objc.c.id,
descriptor: objc.c.id,
) void {
return ptr.renderMetal(
objc.Object.fromId(command_buffer),
objc.Object.fromId(descriptor),
) catch |err| {
log.err("error rendering inspector err={}", .{err});
return;
};
}
export fn ghostty_inspector_metal_shutdown(ptr: *Inspector) void {
if (ptr.backend) |v| {
v.deinit();
ptr.backend = null;
}
}
} else struct {};
export fn ghostty_inspector_set_size(ptr: *Inspector, w: u32, h: u32) void {
ptr.updateSize(w, h);

View File

@ -466,7 +466,9 @@ pub fn finalizeSurfaceInit(self: *const Metal, surface: *apprt.Surface) !void {
},
apprt.embedded => .{
.view = surface.nsview,
.view = switch (surface.platform) {
.macos => |v| v.nsview,
},
.scaleFactor = @floatCast(surface.content_scale.x),
},

View File

@ -422,6 +422,12 @@ pub fn surfaceInit(surface: *apprt.Surface) !void {
},
apprt.glfw => try self.threadEnter(surface),
apprt.embedded => {
// TODO(mitchellh): this does nothing today to allow libghostty
// to compile for OpenGL targets but libghostty is strictly
// broken for rendering on this platforms.
},
}
// These are very noisy so this is commented, but easy to uncomment
@ -529,6 +535,12 @@ pub fn threadEnter(self: *const OpenGL, surface: *apprt.Surface) !void {
gl.glad.versionMinor(@intCast(version)),
});
},
apprt.embedded => {
// TODO(mitchellh): this does nothing today to allow libghostty
// to compile for OpenGL targets but libghostty is strictly
// broken for rendering on this platforms.
},
}
}
@ -548,6 +560,10 @@ pub fn threadExit(self: *const OpenGL) void {
gl.glad.unload();
glfw.makeContextCurrent(null);
},
apprt.embedded => {
// TODO: see threadEnter
},
}
}
@ -1815,6 +1831,7 @@ pub fn drawFrame(self: *OpenGL, surface: *apprt.Surface) !void {
switch (apprt.runtime) {
apprt.glfw => surface.window.swapBuffers(),
apprt.gtk => {},
apprt.embedded => {},
else => @compileError("unsupported runtime"),
}
}