diff --git a/src/build/GhosttyDist.zig b/src/build/GhosttyDist.zig index d889f2350..f8c221350 100644 --- a/src/build/GhosttyDist.zig +++ b/src/build/GhosttyDist.zig @@ -3,6 +3,7 @@ const GhosttyDist = @This(); const std = @import("std"); const Config = @import("Config.zig"); const SharedDeps = @import("SharedDeps.zig"); +const GhosttyFrameData = @import("GhosttyFrameData.zig"); /// The final source tarball. archive: std.Build.LazyPath, @@ -25,6 +26,10 @@ pub fn init(b: *std.Build, cfg: *const Config) !GhosttyDist { try resources.append(alloc, gtk.resources_c); try resources.append(alloc, gtk.resources_h); } + { + const framedata = GhosttyFrameData.distResources(b); + try resources.append(alloc, framedata.framedata); + } // git archive to create the final tarball. "git archive" is the // easiest way I can find to create a tarball that ignores stuff diff --git a/src/build/GhosttyFrameData.zig b/src/build/GhosttyFrameData.zig index 1644388bc..52c84a66c 100644 --- a/src/build/GhosttyFrameData.zig +++ b/src/build/GhosttyFrameData.zig @@ -5,35 +5,25 @@ const GhosttyFrameData = @This(); const std = @import("std"); const Config = @import("Config.zig"); const SharedDeps = @import("SharedDeps.zig"); - -/// The exe. -exe: *std.Build.Step.Compile, +const DistResource = @import("GhosttyDist.zig").Resource; /// The output path for the compressed framedata zig file output: std.Build.LazyPath, pub fn init(b: *std.Build) !GhosttyFrameData { - const exe = b.addExecutable(.{ - .name = "framegen", - .root_module = b.createModule(.{ - .root_source_file = b.path("src/build/framegen/main.zig"), - .target = b.graph.host, - .strip = false, - .omit_frame_pointer = false, - .unwind_tables = .sync, - }), - }); + const dist = distResources(b); - const run = b.addRunArtifact(exe); - // Both the compressed framedata and the Zig source file - // have to be put in the same directory, since the compressed file - // has to be within the source file's include path. - const dir = run.addOutputDirectoryArg("framedata"); + // Generate the Zig source file that embeds the compressed data + const wf = b.addWriteFiles(); + _ = wf.addCopyFile(dist.framedata.path(b), "framedata.compressed"); + const zig_file = wf.add("framedata.zig", + \\//! This file is auto-generated. Do not edit. + \\ + \\pub const compressed = @embedFile("framedata.compressed"); + \\ + ); - return .{ - .exe = exe, - .output = dir.path(b, "framedata.zig"), - }; + return .{ .output = zig_file }; } /// Add the "framedata" import. @@ -43,3 +33,43 @@ pub fn addImport(self: *const GhosttyFrameData, step: *std.Build.Step.Compile) v .root_source_file = self.output, }); } + +/// Creates the framedata resources that can be prebuilt for our dist build. +pub fn distResources(b: *std.Build) struct { + framedata: DistResource, +} { + const exe = b.addExecutable(.{ + .name = "framegen", + .target = b.graph.host, + }); + exe.addCSourceFile(.{ + .file = b.path("src/build/framegen/main.c"), + .flags = &.{}, + }); + exe.linkLibC(); + + if (b.systemIntegrationOption("zlib", .{})) { + exe.linkSystemLibrary2("zlib", .{ + .preferred_link_mode = .dynamic, + .search_strategy = .mode_first, + }); + } else { + if (b.lazyDependency("zlib", .{ + .target = b.graph.host, + .optimize = .ReleaseFast, + })) |zlib_dep| { + exe.linkLibrary(zlib_dep.artifact("z")); + } + } + + const run = b.addRunArtifact(exe); + run.addDirectoryArg(b.path("src/build/framegen/frames")); + const compressed_file = run.addOutputFileArg("framedata.compressed"); + + return .{ + .framedata = .{ + .dist = "src/build/framegen/framedata.compressed", + .generated = compressed_file, + }, + }; +} diff --git a/src/build/framegen/main.c b/src/build/framegen/main.c new file mode 100644 index 000000000..647768006 --- /dev/null +++ b/src/build/framegen/main.c @@ -0,0 +1,145 @@ +#include +#include +#include +#include +#include +#include +#include + +#define SEPARATOR '\x01' +#define CHUNK_SIZE 16384 + +static int filter_frames(const struct dirent *entry) { + const char *name = entry->d_name; + size_t len = strlen(name); + return len > 4 && strcmp(name + len - 4, ".txt") == 0; +} + +static int compare_frames(const struct dirent **a, const struct dirent **b) { + return strcmp((*a)->d_name, (*b)->d_name); +} + +static char *read_file(const char *path, size_t *out_size) { + FILE *f = fopen(path, "rb"); + if (!f) { + fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno)); + return NULL; + } + + fseek(f, 0, SEEK_END); + long size = ftell(f); + fseek(f, 0, SEEK_SET); + + char *buf = malloc(size); + if (!buf) { + return NULL; + } + + if (fread(buf, 1, size, f) != (size_t)size) { + fprintf(stderr, "Failed to read %s\n", path); + return NULL; + } + + fclose(f); + *out_size = size; + return buf; +} + +int main(int argc, char **argv) { + if (argc != 3) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + const char *frames_dir = argv[1]; + const char *output_file = argv[2]; + + struct dirent **namelist; + int n = scandir(frames_dir, &namelist, filter_frames, compare_frames); + if (n < 0) { + fprintf(stderr, "Failed to scan directory %s: %s\n", frames_dir, strerror(errno)); + return 1; + } + + if (n == 0) { + fprintf(stderr, "No frame files found in %s\n", frames_dir); + return 1; + } + + size_t total_size = 0; + char **frame_contents = calloc(n, sizeof(char*)); + size_t *frame_sizes = calloc(n, sizeof(size_t)); + + for (int i = 0; i < n; i++) { + char path[4096]; + snprintf(path, sizeof(path), "%s/%s", frames_dir, namelist[i]->d_name); + + frame_contents[i] = read_file(path, &frame_sizes[i]); + if (!frame_contents[i]) { + return 1; + } + + total_size += frame_sizes[i]; + if (i < n - 1) total_size++; + } + + char *joined = malloc(total_size); + if (!joined) { + fprintf(stderr, "Failed to allocate joined buffer\n"); + return 1; + } + + size_t offset = 0; + for (int i = 0; i < n; i++) { + memcpy(joined + offset, frame_contents[i], frame_sizes[i]); + offset += frame_sizes[i]; + if (i < n - 1) { + joined[offset++] = SEPARATOR; + } + } + + uLongf compressed_size = compressBound(total_size); + unsigned char *compressed = malloc(compressed_size); + if (!compressed) { + fprintf(stderr, "Failed to allocate compression buffer\n"); + return 1; + } + + z_stream stream = {0}; + stream.next_in = (unsigned char*)joined; + stream.avail_in = total_size; + stream.next_out = compressed; + stream.avail_out = compressed_size; + + // Use -MAX_WBITS for raw DEFLATE (no zlib wrapper) + int ret = deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY); + if (ret != Z_OK) { + fprintf(stderr, "deflateInit2 failed: %d\n", ret); + return 1; + } + + ret = deflate(&stream, Z_FINISH); + if (ret != Z_STREAM_END) { + fprintf(stderr, "deflate failed: %d\n", ret); + deflateEnd(&stream); + return 1; + } + + compressed_size = stream.total_out; + deflateEnd(&stream); + + FILE *out = fopen(output_file, "wb"); + if (!out) { + fprintf(stderr, "Failed to create %s: %s\n", output_file, strerror(errno)); + return 1; + } + + if (fwrite(compressed, 1, compressed_size, out) != compressed_size) { + fprintf(stderr, "Failed to write compressed data\n"); + return 1; + } + + fclose(out); + + return 0; +} diff --git a/src/build/framegen/main.zig b/src/build/framegen/main.zig deleted file mode 100644 index f4a7d9443..000000000 --- a/src/build/framegen/main.zig +++ /dev/null @@ -1,273 +0,0 @@ -const std = @import("std"); -const fs = std.fs; - -/// Generates a compressed file of all the ghostty frames -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - - var arg_iter = try std.process.argsWithAllocator(gpa.allocator()); - // Skip the exe name - _ = arg_iter.skip(); - - const out_dir_path = arg_iter.next() orelse return error.MissingOutputPath; - const compressed_out = "framedata.compressed"; - const zig_out = "framedata.zig"; - - const out_dir = try fs.cwd().openDir(out_dir_path, .{}); - const compressed_file = try out_dir.createFile(compressed_out, .{}); - - // Join the frames with a null byte. We'll split on this later - const all_frames = try std.mem.join(gpa.allocator(), "\x01", &frames); - var fbs = std.io.fixedBufferStream(all_frames); - - const reader = fbs.reader(); - try std.compress.flate.compress(reader, compressed_file.writer(), .{}); - - const compressed_path = try std.fs.path.join(gpa.allocator(), &.{ out_dir_path, compressed_out }); - - const zig_file = try out_dir.createFile(zig_out, .{}); - - try zig_file.writer().print( - \\//! This file is auto-generated. Do not edit. - \\ - \\pub const compressed = @embedFile("{s}"); - , .{compressed_path}); -} - -const frames = [_][]const u8{ - @embedFile("frames/frame_001.txt"), - @embedFile("frames/frame_002.txt"), - @embedFile("frames/frame_003.txt"), - @embedFile("frames/frame_004.txt"), - @embedFile("frames/frame_005.txt"), - @embedFile("frames/frame_006.txt"), - @embedFile("frames/frame_007.txt"), - @embedFile("frames/frame_008.txt"), - @embedFile("frames/frame_009.txt"), - @embedFile("frames/frame_010.txt"), - @embedFile("frames/frame_011.txt"), - @embedFile("frames/frame_012.txt"), - @embedFile("frames/frame_013.txt"), - @embedFile("frames/frame_014.txt"), - @embedFile("frames/frame_015.txt"), - @embedFile("frames/frame_016.txt"), - @embedFile("frames/frame_017.txt"), - @embedFile("frames/frame_018.txt"), - @embedFile("frames/frame_019.txt"), - @embedFile("frames/frame_020.txt"), - @embedFile("frames/frame_021.txt"), - @embedFile("frames/frame_022.txt"), - @embedFile("frames/frame_023.txt"), - @embedFile("frames/frame_024.txt"), - @embedFile("frames/frame_025.txt"), - @embedFile("frames/frame_026.txt"), - @embedFile("frames/frame_027.txt"), - @embedFile("frames/frame_028.txt"), - @embedFile("frames/frame_029.txt"), - @embedFile("frames/frame_030.txt"), - @embedFile("frames/frame_031.txt"), - @embedFile("frames/frame_032.txt"), - @embedFile("frames/frame_033.txt"), - @embedFile("frames/frame_034.txt"), - @embedFile("frames/frame_035.txt"), - @embedFile("frames/frame_036.txt"), - @embedFile("frames/frame_037.txt"), - @embedFile("frames/frame_038.txt"), - @embedFile("frames/frame_039.txt"), - @embedFile("frames/frame_040.txt"), - @embedFile("frames/frame_041.txt"), - @embedFile("frames/frame_042.txt"), - @embedFile("frames/frame_043.txt"), - @embedFile("frames/frame_044.txt"), - @embedFile("frames/frame_045.txt"), - @embedFile("frames/frame_046.txt"), - @embedFile("frames/frame_047.txt"), - @embedFile("frames/frame_048.txt"), - @embedFile("frames/frame_049.txt"), - @embedFile("frames/frame_050.txt"), - @embedFile("frames/frame_051.txt"), - @embedFile("frames/frame_052.txt"), - @embedFile("frames/frame_053.txt"), - @embedFile("frames/frame_054.txt"), - @embedFile("frames/frame_055.txt"), - @embedFile("frames/frame_056.txt"), - @embedFile("frames/frame_057.txt"), - @embedFile("frames/frame_058.txt"), - @embedFile("frames/frame_059.txt"), - @embedFile("frames/frame_060.txt"), - @embedFile("frames/frame_061.txt"), - @embedFile("frames/frame_062.txt"), - @embedFile("frames/frame_063.txt"), - @embedFile("frames/frame_064.txt"), - @embedFile("frames/frame_065.txt"), - @embedFile("frames/frame_066.txt"), - @embedFile("frames/frame_067.txt"), - @embedFile("frames/frame_068.txt"), - @embedFile("frames/frame_069.txt"), - @embedFile("frames/frame_070.txt"), - @embedFile("frames/frame_071.txt"), - @embedFile("frames/frame_072.txt"), - @embedFile("frames/frame_073.txt"), - @embedFile("frames/frame_074.txt"), - @embedFile("frames/frame_075.txt"), - @embedFile("frames/frame_076.txt"), - @embedFile("frames/frame_077.txt"), - @embedFile("frames/frame_078.txt"), - @embedFile("frames/frame_079.txt"), - @embedFile("frames/frame_080.txt"), - @embedFile("frames/frame_081.txt"), - @embedFile("frames/frame_082.txt"), - @embedFile("frames/frame_083.txt"), - @embedFile("frames/frame_084.txt"), - @embedFile("frames/frame_085.txt"), - @embedFile("frames/frame_086.txt"), - @embedFile("frames/frame_087.txt"), - @embedFile("frames/frame_088.txt"), - @embedFile("frames/frame_089.txt"), - @embedFile("frames/frame_090.txt"), - @embedFile("frames/frame_091.txt"), - @embedFile("frames/frame_092.txt"), - @embedFile("frames/frame_093.txt"), - @embedFile("frames/frame_094.txt"), - @embedFile("frames/frame_095.txt"), - @embedFile("frames/frame_096.txt"), - @embedFile("frames/frame_097.txt"), - @embedFile("frames/frame_098.txt"), - @embedFile("frames/frame_099.txt"), - @embedFile("frames/frame_100.txt"), - @embedFile("frames/frame_101.txt"), - @embedFile("frames/frame_102.txt"), - @embedFile("frames/frame_103.txt"), - @embedFile("frames/frame_104.txt"), - @embedFile("frames/frame_105.txt"), - @embedFile("frames/frame_106.txt"), - @embedFile("frames/frame_107.txt"), - @embedFile("frames/frame_108.txt"), - @embedFile("frames/frame_109.txt"), - @embedFile("frames/frame_110.txt"), - @embedFile("frames/frame_111.txt"), - @embedFile("frames/frame_112.txt"), - @embedFile("frames/frame_113.txt"), - @embedFile("frames/frame_114.txt"), - @embedFile("frames/frame_115.txt"), - @embedFile("frames/frame_116.txt"), - @embedFile("frames/frame_117.txt"), - @embedFile("frames/frame_118.txt"), - @embedFile("frames/frame_119.txt"), - @embedFile("frames/frame_120.txt"), - @embedFile("frames/frame_121.txt"), - @embedFile("frames/frame_122.txt"), - @embedFile("frames/frame_123.txt"), - @embedFile("frames/frame_124.txt"), - @embedFile("frames/frame_125.txt"), - @embedFile("frames/frame_126.txt"), - @embedFile("frames/frame_127.txt"), - @embedFile("frames/frame_128.txt"), - @embedFile("frames/frame_129.txt"), - @embedFile("frames/frame_130.txt"), - @embedFile("frames/frame_131.txt"), - @embedFile("frames/frame_132.txt"), - @embedFile("frames/frame_133.txt"), - @embedFile("frames/frame_134.txt"), - @embedFile("frames/frame_135.txt"), - @embedFile("frames/frame_136.txt"), - @embedFile("frames/frame_137.txt"), - @embedFile("frames/frame_138.txt"), - @embedFile("frames/frame_139.txt"), - @embedFile("frames/frame_140.txt"), - @embedFile("frames/frame_141.txt"), - @embedFile("frames/frame_142.txt"), - @embedFile("frames/frame_143.txt"), - @embedFile("frames/frame_144.txt"), - @embedFile("frames/frame_145.txt"), - @embedFile("frames/frame_146.txt"), - @embedFile("frames/frame_147.txt"), - @embedFile("frames/frame_148.txt"), - @embedFile("frames/frame_149.txt"), - @embedFile("frames/frame_150.txt"), - @embedFile("frames/frame_151.txt"), - @embedFile("frames/frame_152.txt"), - @embedFile("frames/frame_153.txt"), - @embedFile("frames/frame_154.txt"), - @embedFile("frames/frame_155.txt"), - @embedFile("frames/frame_156.txt"), - @embedFile("frames/frame_157.txt"), - @embedFile("frames/frame_158.txt"), - @embedFile("frames/frame_159.txt"), - @embedFile("frames/frame_160.txt"), - @embedFile("frames/frame_161.txt"), - @embedFile("frames/frame_162.txt"), - @embedFile("frames/frame_163.txt"), - @embedFile("frames/frame_164.txt"), - @embedFile("frames/frame_165.txt"), - @embedFile("frames/frame_166.txt"), - @embedFile("frames/frame_167.txt"), - @embedFile("frames/frame_168.txt"), - @embedFile("frames/frame_169.txt"), - @embedFile("frames/frame_170.txt"), - @embedFile("frames/frame_171.txt"), - @embedFile("frames/frame_172.txt"), - @embedFile("frames/frame_173.txt"), - @embedFile("frames/frame_174.txt"), - @embedFile("frames/frame_175.txt"), - @embedFile("frames/frame_176.txt"), - @embedFile("frames/frame_177.txt"), - @embedFile("frames/frame_178.txt"), - @embedFile("frames/frame_179.txt"), - @embedFile("frames/frame_180.txt"), - @embedFile("frames/frame_181.txt"), - @embedFile("frames/frame_182.txt"), - @embedFile("frames/frame_183.txt"), - @embedFile("frames/frame_184.txt"), - @embedFile("frames/frame_185.txt"), - @embedFile("frames/frame_186.txt"), - @embedFile("frames/frame_187.txt"), - @embedFile("frames/frame_188.txt"), - @embedFile("frames/frame_189.txt"), - @embedFile("frames/frame_190.txt"), - @embedFile("frames/frame_191.txt"), - @embedFile("frames/frame_192.txt"), - @embedFile("frames/frame_193.txt"), - @embedFile("frames/frame_194.txt"), - @embedFile("frames/frame_195.txt"), - @embedFile("frames/frame_196.txt"), - @embedFile("frames/frame_197.txt"), - @embedFile("frames/frame_198.txt"), - @embedFile("frames/frame_199.txt"), - @embedFile("frames/frame_200.txt"), - @embedFile("frames/frame_201.txt"), - @embedFile("frames/frame_202.txt"), - @embedFile("frames/frame_203.txt"), - @embedFile("frames/frame_204.txt"), - @embedFile("frames/frame_205.txt"), - @embedFile("frames/frame_206.txt"), - @embedFile("frames/frame_207.txt"), - @embedFile("frames/frame_208.txt"), - @embedFile("frames/frame_209.txt"), - @embedFile("frames/frame_210.txt"), - @embedFile("frames/frame_211.txt"), - @embedFile("frames/frame_212.txt"), - @embedFile("frames/frame_213.txt"), - @embedFile("frames/frame_214.txt"), - @embedFile("frames/frame_215.txt"), - @embedFile("frames/frame_216.txt"), - @embedFile("frames/frame_217.txt"), - @embedFile("frames/frame_218.txt"), - @embedFile("frames/frame_219.txt"), - @embedFile("frames/frame_220.txt"), - @embedFile("frames/frame_221.txt"), - @embedFile("frames/frame_222.txt"), - @embedFile("frames/frame_223.txt"), - @embedFile("frames/frame_224.txt"), - @embedFile("frames/frame_225.txt"), - @embedFile("frames/frame_226.txt"), - @embedFile("frames/frame_227.txt"), - @embedFile("frames/frame_228.txt"), - @embedFile("frames/frame_229.txt"), - @embedFile("frames/frame_230.txt"), - @embedFile("frames/frame_231.txt"), - @embedFile("frames/frame_232.txt"), - @embedFile("frames/frame_233.txt"), - @embedFile("frames/frame_234.txt"), - @embedFile("frames/frame_235.txt"), -};