diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 79db39cae..1294fb801 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 diff --git a/build.zig b/build.zig index 0a923b208..0baf6a245 100644 --- a/build.zig +++ b/build.zig @@ -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. diff --git a/include/ghostty.h b/include/ghostty.h index ff4056778..9035d9c0a 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -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 *); diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index 22c3c3e7d..2d320eab1 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -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 } diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index ed27fe8b8..44cc044ce 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -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); diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index c881e7be0..7a689f6be 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -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), }, diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index b4ebfb476..8b74167a3 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -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"), } }