Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ zig-pkg
/*.bmp
/*.mp4
/*.mov
/*.png
/*.jpg
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
</p>
<h1 align="center">Spacecap</h1>

A hardware accelerated screen recording tool for Linux. _Still in development
(see features/roadmap below)_.
A hardware accelerated screen recording tool for Linux. _Still in early
development (see features/roadmap below)_.

- Written in [Zig](https://ziglang.org/) (0.16.0).
- Video encoding with Vulkan Video ([vulkan-zig](https://github.com/Snektron/vulkan-zig)).
Expand Down
92 changes: 35 additions & 57 deletions build.zig
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
const std = @import("std");
const ffmpeg_build_util = @import("build/ffmpeg_build.zig");
const version = @import("build/version.zig");
const build_linux_app_image = @import("./build/app_image.zig").build_linux_app_image;

const EXE_NAME = "spacecap";
const PackageVersion = version.PackageVersion;

// TODO: There are still some issues with the Zig backend. Use LLMV for now...
const USE_LLVM = true;

fn compile_shader(
allocator: std.mem.Allocator,
b: *std.Build,
Expand Down Expand Up @@ -76,12 +80,6 @@ fn add_shared_dependencies(
.optimize = optimize,
}).module("clap");
exe.root_module.addImport("clap", clap);

const ffmpeg_build = switch (target.result.os.tag) {
.windows => ffmpeg_build_util.build_windows(b),
else => ffmpeg_build_util.build_linux(b),
};
ffmpeg_build_util.link_libs(exe, ffmpeg_build);
}

fn add_linux_dependencies(
Expand Down Expand Up @@ -112,8 +110,32 @@ fn add_linux_dependencies(
// Vulkan is linked directly, because it is required that the
// system has the libs installed.
exe.root_module.linkSystemLibrary("vulkan", .{});

ffmpeg_build_util.build_linux(b, exe);
}

fn add_windows_dependencies(
allocator: std.mem.Allocator,
b: *std.Build,
exe: *std.Build.Step.Compile,
_: std.Build.ResolvedTarget,
_: std.builtin.OptimizeMode,
) !void {
_ = allocator;
ffmpeg_build_util.build_windows(b, exe);
const vulkan_sdk_path_windows = b.graph.environ_map.get("VULKAN_SDK_PATH_WINDOWS").?;
exe.root_module.addLibraryPath(.{ .cwd_relative = vulkan_sdk_path_windows });

exe.root_module.linkSystemLibrary("vulkan-1", .{});

// All windows machines should be able to link to this by default
exe.root_module.linkSystemLibrary("gdi32", .{});
}

/// NOTE: This is not used anymore. We are statically linking everything we can
/// and it is not necessary. Keeping it around in case we need it for Windows
/// things.
///
/// Install a dynamic library in the <target>/lib directory
/// e.g. zig-out/linux/lib/SDL3.so
///
Expand Down Expand Up @@ -183,29 +205,11 @@ fn build_windows(
.name = EXE_NAME,
.root_module = module,
.version = package_version.semantic_version,
.use_llvm = true,
.use_llvm = USE_LLVM,
});
// TODO: seems like rpath is not working
exe.root_module.addRPath(b.path("./lib"));

try add_shared_dependencies(allocator, b, exe, target, optimize);

const vulkan_sdk_path_windows = b.graph.environ_map.get("VULKAN_SDK_PATH_WINDOWS").?;
exe.root_module.addLibraryPath(.{ .cwd_relative = vulkan_sdk_path_windows });

try install_and_link_system_library(.{
.allocator = allocator,
.b = b,
.exe = exe,
.source_dir = vulkan_sdk_path_windows,
.lib_name = "vulkan-1",
.target = .windows,
});

// All windows machines should be able to link to this by default
exe.root_module.linkSystemLibrary("gdi32", .{});
// Required for ffmpeg.
exe.root_module.linkSystemLibrary("bcrypt", .{});
try add_windows_dependencies(allocator, b, exe, target, optimize);

const install_step = b.addInstallArtifact(exe, .{
.dest_dir = .{ .override = .{ .custom = "windows" } },
Expand Down Expand Up @@ -234,9 +238,7 @@ fn build_linux(
.name = EXE_NAME,
.root_module = module,
.version = package_version.semantic_version,
// TODO: There are currently some pointer alignment issues
// with pipewire using the Zig backend. Just stick to LLVM for now...
.use_llvm = true,
.use_llvm = USE_LLVM,
});

if (!nix) {
Expand Down Expand Up @@ -270,38 +272,15 @@ fn build_linux(
return &install_step.step;
}

fn build_linux_app_image(
b: *std.Build,
allocator: std.mem.Allocator,
linux_install_step: *std.Build.Step,
) *std.Build.Step {
const appimage_step = b.step("appimage", "Build Linux AppImage");

const buffer = b.build_root.handle.readFileAlloc(
b.graph.io,
"./build/build_app_image.sh",
allocator,
.limited(1024 * 1024),
) catch unreachable;
defer allocator.free(buffer);

const cmd = b.addSystemCommand(&.{ "bash", "-lc", buffer });

cmd.step.dependOn(linux_install_step);
appimage_step.dependOn(&cmd.step);

return appimage_step;
}

fn build_unit_tests_default(
fn build_unit_tests(
allocator: std.mem.Allocator,
b: *std.Build,
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
options: *std.Build.Step.Options,
) !void {
const unit_test_files = [_][]const u8{
"./src/test.zig",
"./src/main.zig",
};

const test_step = b.step("test", "Run unit tests");
Expand All @@ -317,8 +296,7 @@ fn build_unit_tests_default(
const exe = b.addTest(.{
.root_module = module,
.test_runner = .{ .path = b.path("./src/test_runner.zig"), .mode = .simple },
// Keep test linking behavior aligned with Linux executable builds.
.use_llvm = true,
.use_llvm = USE_LLVM,
});

try add_shared_dependencies(allocator, b, exe, target, optimize);
Expand Down Expand Up @@ -407,7 +385,7 @@ pub fn build(b: *std.Build) !void {
b.getInstallStep().dependOn(appimage_step);
}

try build_unit_tests_default(
try build_unit_tests(
allocator,
b,
linux_target,
Expand Down
26 changes: 26 additions & 0 deletions build/app_image.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const std = @import("std");

pub fn build_linux_app_image(
b: *std.Build,
allocator: std.mem.Allocator,
linux_install_step: *std.Build.Step,
) *std.Build.Step {
const appimage_step = b.step("appimage", "Build Linux AppImage");

const buffer = b.build_root.handle.readFileAlloc(
b.graph.io,
"./build/build_app_image.sh",
allocator,
.limited(1024 * 1024),
) catch |err| {
@panic(@errorName(err));
};
defer allocator.free(buffer);

const cmd = b.addSystemCommand(&.{ "bash", "-lc", buffer });

cmd.step.dependOn(linux_install_step);
appimage_step.dependOn(&cmd.step);

return appimage_step;
}
5 changes: 4 additions & 1 deletion build/ffmpeg_build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,10 @@ common_configure_flags=(
target_configure_flags=()
case "$target" in
linux)
target_configure_flags=()
target_configure_flags=(
--enable-encoder=png
--enable-zlib
)
;;
windows)
target_configure_flags=(
Expand Down
70 changes: 39 additions & 31 deletions build/ffmpeg_build.zig
Original file line number Diff line number Diff line change
@@ -1,19 +1,42 @@
const std = @import("std");

pub const FfmpegBuild = struct {
step: *std.Build.Step,
/// Directory will contain the generated ffmpeg headers.
include_dir: std.Build.LazyPath,
/// Directory will contain the static libraries.
lib_dir: std.Build.LazyPath,
};
pub fn build_linux(
b: *std.Build,
exe: *std.Build.Step.Compile,
) void {
const ffmpeg_build = build_for_target(
b,
exe,
"linux",
"ffmpeg-build",
"ffmpeg-install",
);
link_libs(exe, ffmpeg_build.include_dir, ffmpeg_build.lib_dir);
exe.root_module.linkSystemLibrary("zlib", .{});
}

pub fn build_windows(
b: *std.Build,
exe: *std.Build.Step.Compile,
) void {
const ffmpeg_build = build_for_target(
b,
exe,
"windows",
"ffmpeg-build-windows",
"ffmpeg-install-windows",
);
link_libs(exe, ffmpeg_build.include_dir, ffmpeg_build.lib_dir);
exe.root_module.linkSystemLibrary("bcrypt", .{});
}

fn build_for_target(
b: *std.Build,
exe: *std.Build.Step.Compile,
target: []const u8,
build_dir_name: []const u8,
install_dir_name: []const u8,
) FfmpegBuild {
) struct { include_dir: std.Build.LazyPath, lib_dir: std.Build.LazyPath } {
const ffmpeg = b.dependency("ffmpeg", .{});
const build_ffmpeg_step = b.addSystemCommand(&.{"bash"});
build_ffmpeg_step.addFileArg(b.path("build/ffmpeg_build.sh"));
Expand All @@ -23,36 +46,21 @@ fn build_for_target(
build_ffmpeg_step.addArg(ffmpeg.path("").getPath(b));
build_ffmpeg_step.expectExitCode(0);
build_ffmpeg_step.setName(build_dir_name);
exe.step.dependOn(&build_ffmpeg_step.step);

return .{
.step = &build_ffmpeg_step.step,
.include_dir = ffmpeg_install_prefix.path(b, "include"),
.lib_dir = ffmpeg_install_prefix.path(b, "lib"),
};
}

pub fn build_linux(b: *std.Build) FfmpegBuild {
return build_for_target(
b,
"linux",
"ffmpeg-build",
"ffmpeg-install",
);
}

pub fn build_windows(b: *std.Build) FfmpegBuild {
return build_for_target(
b,
"windows",
"ffmpeg-build-windows",
"ffmpeg-install-windows",
);
}

pub fn link_libs(exe: *std.Build.Step.Compile, ffmpeg_build: FfmpegBuild) void {
exe.step.dependOn(ffmpeg_build.step);
exe.root_module.addIncludePath(ffmpeg_build.include_dir);
exe.root_module.addLibraryPath(ffmpeg_build.lib_dir);
fn link_libs(
exe: *std.Build.Step.Compile,
include_dir: std.Build.LazyPath,
lib_dir: std.Build.LazyPath,
) void {
exe.root_module.addIncludePath(include_dir);
exe.root_module.addLibraryPath(lib_dir);
exe.root_module.linkSystemLibrary("avformat", .{ .preferred_link_mode = .static });
exe.root_module.linkSystemLibrary("avcodec", .{ .preferred_link_mode = .static });
exe.root_module.linkSystemLibrary("avdevice", .{ .preferred_link_mode = .static });
Expand Down
1 change: 1 addition & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [
pkgs.vulkan-loader
pkgs.glib
pkgs.zlib

# Required for linux tray icon.
pkgs.gtk3
Expand Down
3 changes: 2 additions & 1 deletion src/args.zig
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const shared_params = (

const linux_params = (
\\-s, --send <command> Send a command to a running Spacecap instance.
\\ Commands: save-replay, start-replay-buffer, stop-replay-buffer, toggle-replay-buffer, start-recording, stop-recording, toggle-recording
\\ Commands: save-replay, screenshot, start-replay-buffer, stop-replay-buffer, toggle-replay-buffer, start-recording, stop-recording, toggle-recording
\\
);

Expand All @@ -28,6 +28,7 @@ const windows_params_parsed = clap.parseParamsComptime(shared_params ++ windows_

pub const SendCommand = enum {
@"save-replay",
screenshot,
@"start-replay-buffer",
@"stop-replay-buffer",
@"toggle-replay-buffer",
Expand Down
Loading
Loading