From c82d3dcccd0c274a44ea400af49ee7fbccff9fd8 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 4 Nov 2025 13:53:34 -0800 Subject: [PATCH 1/8] Removes outdated comment --- build.zig | 131 +++++++++++++++++++++++++----------------------------- 1 file changed, 61 insertions(+), 70 deletions(-) diff --git a/build.zig b/build.zig index 4204311..2fe2fb3 100644 --- a/build.zig +++ b/build.zig @@ -107,76 +107,67 @@ pub fn build(b: *std.Build) void { }); // Build libpipewire - { - // Build the library - const libpipewire = b.addLibrary(.{ - .name = "pipewire-0.3", - // Pipewire needs to be build as a dynamic library for its symbols to be available to the - // dynamic libraries it dlopens. Alternatively, you can link it statically, but you'll need - // to set rdynamic on the executable to make these symbols available. - .linkage = .static, - .root_module = b.createModule(.{ - .target = target, - .optimize = optimize, - }), - }); - libpipewire.addCSourceFiles(.{ - .root = upstream.path("src/pipewire"), - .files = &.{ - "buffers.c", - "conf.c", - "context.c", - "control.c", - "core.c", - "data-loop.c", - "filter.c", - "global.c", - "impl-client.c", - "impl-core.c", - "impl-device.c", - "impl-factory.c", - "impl-link.c", - "impl-metadata.c", - "impl-module.c", - "impl-node.c", - "impl-port.c", - "introspect.c", - "log.c", - "loop.c", - "main-loop.c", - "mem.c", - "pipewire.c", - "properties.c", - "protocol.c", - "proxy.c", - "resource.c", - "settings.c", - "stream.c", - "thread-loop.c", - "thread.c", - "timer-queue.c", - "utils.c", - "work-queue.c", - }, - .flags = flags, - }); - libpipewire.linkLibC(); - - // Add include paths - libpipewire.addIncludePath(b.dependency("valgrind_h", .{}).path("")); - libpipewire.addIncludePath(upstream.path("spa/include")); - libpipewire.addIncludePath(upstream.path("src")); - libpipewire.addConfigHeader(version_h); - libpipewire.addConfigHeader(config_h); - - // Install public headers - libpipewire.installHeadersDirectory(upstream.path("src/pipewire"), "pipewire", .{}); - libpipewire.installHeadersDirectory(upstream.path("spa/include/spa"), "spa", .{}); - libpipewire.installConfigHeader(version_h); - - // Install the library - b.installArtifact(libpipewire); - } + const libpipewire = b.addLibrary(.{ + .name = "pipewire-0.3", + .linkage = .static, + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + }), + }); + libpipewire.addCSourceFiles(.{ + .root = upstream.path("src/pipewire"), + .files = &.{ + "buffers.c", + "conf.c", + "context.c", + "control.c", + "core.c", + "data-loop.c", + "filter.c", + "global.c", + "impl-client.c", + "impl-core.c", + "impl-device.c", + "impl-factory.c", + "impl-link.c", + "impl-metadata.c", + "impl-module.c", + "impl-node.c", + "impl-port.c", + "introspect.c", + "log.c", + "loop.c", + "main-loop.c", + "mem.c", + "pipewire.c", + "properties.c", + "protocol.c", + "proxy.c", + "resource.c", + "settings.c", + "stream.c", + "thread-loop.c", + "thread.c", + "timer-queue.c", + "utils.c", + "work-queue.c", + }, + .flags = flags, + }); + libpipewire.linkLibC(); + + libpipewire.addIncludePath(b.dependency("valgrind_h", .{}).path("")); + libpipewire.addIncludePath(upstream.path("spa/include")); + libpipewire.addIncludePath(upstream.path("src")); + libpipewire.addConfigHeader(version_h); + libpipewire.addConfigHeader(config_h); + + libpipewire.installHeadersDirectory(upstream.path("src/pipewire"), "pipewire", .{}); + libpipewire.installHeadersDirectory(upstream.path("spa/include/spa"), "spa", .{}); + libpipewire.installConfigHeader(version_h); + + b.installArtifact(libpipewire); // Build the plugins and modules { From b39754bfbc3fa32712699d9c95d3463c79e1a2fa Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 4 Nov 2025 17:02:20 -0800 Subject: [PATCH 2/8] Temporarily links everything dynamically This is "more static" than dlopen, and will let us start to implement our own dlsym, at which point we can switch everything to static. --- build.zig | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/build.zig b/build.zig index 2fe2fb3..c74a6c0 100644 --- a/build.zig +++ b/build.zig @@ -109,7 +109,7 @@ pub fn build(b: *std.Build) void { // Build libpipewire const libpipewire = b.addLibrary(.{ .name = "pipewire-0.3", - .linkage = .static, + .linkage = .dynamic, .root_module = b.createModule(.{ .target = target, .optimize = optimize, @@ -169,6 +169,9 @@ pub fn build(b: *std.Build) void { b.installArtifact(libpipewire); + _ = install_dir.addCopyFile(libpipewire.getEmittedBin(), "libpipewire-0.3.so"); + _ = install_dir.addCopyFile(libpipewire.getEmittedBin(), "libpipewire-0.3.so.0"); + // Build the plugins and modules { const pm_ctx: PluginAndModuleCtx = .{ @@ -178,6 +181,7 @@ pub fn build(b: *std.Build) void { .version = version_h, .config = config_h, .install_dir = install_dir, + .libpipewire = libpipewire, }; // Build and install the plugins @@ -319,9 +323,6 @@ pub fn linkAndInstall( // Statically link libpipewire exe.linkLibrary(dep.artifact("pipewire-0.3")); - // Necessary for SPA to load symbols from the statically linked libpipewire - exe.rdynamic = true; - // Note that the cache rpath will still be present: https://github.com/ziglang/zig/issues/24349 exe.root_module.addRPathSpecial("$ORIGIN/pipewire-0.3"); @@ -350,6 +351,7 @@ pub const PluginAndModuleCtx = struct { target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, install_dir: *std.Build.Step.WriteFile, + libpipewire: *std.Build.Step.Compile, }; pub const PipewireModule = struct { @@ -385,6 +387,8 @@ pub const PipewireModule = struct { b.fmt("libpipewire-module-{s}.so", .{self.name}), })); + ctx.libpipewire.linkLibrary(lib); + return lib; } }; @@ -425,6 +429,8 @@ pub const PipewirePlugin = struct { b.fmt("libspa-{s}.so", .{self.name}), })); + ctx.libpipewire.linkLibrary(lib); + return lib; } }; From 0b225c94af3d8dcb3e34512bab2d127e10d2b3b8 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Tue, 4 Nov 2025 17:50:14 -0800 Subject: [PATCH 3/8] Stubs out dlopen/dlclose/dlsym/dlerror Need to actually implement dlsym for this to work. This will also break SDL, so we may need to switch to an example that doesn't require it or switch to a statically linked window alternative, or just modify the example temporarily to not need graphics. --- build.zig | 36 +++++++++++++++--------------------- src/dl.zig | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 21 deletions(-) create mode 100644 src/dl.zig diff --git a/build.zig b/build.zig index c74a6c0..1a33044 100644 --- a/build.zig +++ b/build.zig @@ -26,6 +26,17 @@ pub fn build(b: *std.Build) void { .install_subdir = "pipewire-0.3", }); + // Compile our dl stub + const dl = b.addLibrary(.{ + .name = "dl", + .linkage = .static, + .root_module = b.createModule(.{ + .root_source_file = b.path("src/dl.zig"), + .target = target, + .optimize = optimize, + }), + }); + // Build and install the configuration { const generate_conf = b.addExecutable(.{ @@ -109,12 +120,13 @@ pub fn build(b: *std.Build) void { // Build libpipewire const libpipewire = b.addLibrary(.{ .name = "pipewire-0.3", - .linkage = .dynamic, + .linkage = .static, .root_module = b.createModule(.{ .target = target, .optimize = optimize, }), }); + libpipewire.linkLibrary(dl); libpipewire.addCSourceFiles(.{ .root = upstream.path("src/pipewire"), .files = &.{ @@ -169,9 +181,6 @@ pub fn build(b: *std.Build) void { b.installArtifact(libpipewire); - _ = install_dir.addCopyFile(libpipewire.getEmittedBin(), "libpipewire-0.3.so"); - _ = install_dir.addCopyFile(libpipewire.getEmittedBin(), "libpipewire-0.3.so.0"); - // Build the plugins and modules { const pm_ctx: PluginAndModuleCtx = .{ @@ -365,7 +374,7 @@ pub const PipewireModule = struct { ) *std.Build.Step.Compile { const lib = b.addLibrary(.{ .name = b.fmt("pipewire-module-{s}", .{self.name}), - .linkage = .dynamic, + .linkage = .static, .root_module = b.createModule(.{ .target = ctx.target, .optimize = ctx.optimize, @@ -382,13 +391,6 @@ pub const PipewireModule = struct { lib.addConfigHeader(ctx.config); lib.linkLibC(); - _ = ctx.install_dir.addCopyFile(lib.getEmittedBin(), b.pathJoin(&.{ - "modules", - b.fmt("libpipewire-module-{s}.so", .{self.name}), - })); - - ctx.libpipewire.linkLibrary(lib); - return lib; } }; @@ -404,7 +406,7 @@ pub const PipewirePlugin = struct { ) *std.Build.Step.Compile { const lib = b.addLibrary(.{ .name = b.fmt("spa-{s}", .{self.name}), - .linkage = .dynamic, + .linkage = .static, .root_module = b.createModule(.{ .target = ctx.target, .optimize = ctx.optimize, @@ -423,14 +425,6 @@ pub const PipewirePlugin = struct { lib.addConfigHeader(ctx.config); lib.linkLibC(); - _ = ctx.install_dir.addCopyFile(lib.getEmittedBin(), b.pathJoin(&.{ - "plugins", - self.name, - b.fmt("libspa-{s}.so", .{self.name}), - })); - - ctx.libpipewire.linkLibrary(lib); - return lib; } }; diff --git a/src/dl.zig b/src/dl.zig new file mode 100644 index 0000000..86f6d68 --- /dev/null +++ b/src/dl.zig @@ -0,0 +1,37 @@ +const std = @import("std"); + +const log = std.log.scoped(.dl); // XXX: use this +const gpa = std.heap.smp_allocator; +const assert = std.debug.assert; + +export fn dlopen(path: ?[*:0]const c_char, flags: c_int) callconv(.c) ?*anyopaque { + comptime assert(@sizeOf(c_char) == @sizeOf(u8)); + const path_u8: ?[*:0]const u8 = @ptrCast(path); + log.info("dlopen({?s}, {})", .{ path_u8, flags }); + + const span = std.mem.span(path_u8 orelse return null); + const handle = gpa.dupeZ(u8, span) catch return null; + return handle.ptr; +} + +export fn dlclose(handle: ?*anyopaque) callconv(.c) c_int { + comptime assert(@sizeOf(c_char) == @sizeOf(u8)); + const path: [*:0]const u8 = @ptrCast(handle.?); + log.info("dlclose({s})", .{path}); + + gpa.free(std.mem.span(path)); + return 0; +} + +export fn dlsym(noalias handle: *anyopaque, noalias symbol_c: ?[*:0]c_char) ?*anyopaque { + const path: ?[*:0]u8 = @ptrCast(handle); + const symbol: ?[*:0]u8 = @ptrCast(symbol_c); + log.info("dlsym({?s}, {?s})", .{ path, symbol }); + + log.err("dlsym unimplemented!", .{}); + return null; +} + +export fn dlerror() ?[*:0]const u8 { + return null; +} From aa444efa4570a17ef0afd578584d62597d9c54ef Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Wed, 5 Nov 2025 14:47:28 -0800 Subject: [PATCH 4/8] Progress on dlfcn stub, need to try wirign up to actual symbols now --- build.zig | 8 ++-- src/dl.zig | 37 ----------------- src/dlfcn.zig | 110 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 41 deletions(-) delete mode 100644 src/dl.zig create mode 100644 src/dlfcn.zig diff --git a/build.zig b/build.zig index 1a33044..b9e0d2f 100644 --- a/build.zig +++ b/build.zig @@ -27,11 +27,11 @@ pub fn build(b: *std.Build) void { }); // Compile our dl stub - const dl = b.addLibrary(.{ - .name = "dl", + const dlfcn = b.addLibrary(.{ + .name = "dlfcn", .linkage = .static, .root_module = b.createModule(.{ - .root_source_file = b.path("src/dl.zig"), + .root_source_file = b.path("src/dlfcn.zig"), .target = target, .optimize = optimize, }), @@ -126,7 +126,7 @@ pub fn build(b: *std.Build) void { .optimize = optimize, }), }); - libpipewire.linkLibrary(dl); + libpipewire.linkLibrary(dlfcn); libpipewire.addCSourceFiles(.{ .root = upstream.path("src/pipewire"), .files = &.{ diff --git a/src/dl.zig b/src/dl.zig deleted file mode 100644 index 86f6d68..0000000 --- a/src/dl.zig +++ /dev/null @@ -1,37 +0,0 @@ -const std = @import("std"); - -const log = std.log.scoped(.dl); // XXX: use this -const gpa = std.heap.smp_allocator; -const assert = std.debug.assert; - -export fn dlopen(path: ?[*:0]const c_char, flags: c_int) callconv(.c) ?*anyopaque { - comptime assert(@sizeOf(c_char) == @sizeOf(u8)); - const path_u8: ?[*:0]const u8 = @ptrCast(path); - log.info("dlopen({?s}, {})", .{ path_u8, flags }); - - const span = std.mem.span(path_u8 orelse return null); - const handle = gpa.dupeZ(u8, span) catch return null; - return handle.ptr; -} - -export fn dlclose(handle: ?*anyopaque) callconv(.c) c_int { - comptime assert(@sizeOf(c_char) == @sizeOf(u8)); - const path: [*:0]const u8 = @ptrCast(handle.?); - log.info("dlclose({s})", .{path}); - - gpa.free(std.mem.span(path)); - return 0; -} - -export fn dlsym(noalias handle: *anyopaque, noalias symbol_c: ?[*:0]c_char) ?*anyopaque { - const path: ?[*:0]u8 = @ptrCast(handle); - const symbol: ?[*:0]u8 = @ptrCast(symbol_c); - log.info("dlsym({?s}, {?s})", .{ path, symbol }); - - log.err("dlsym unimplemented!", .{}); - return null; -} - -export fn dlerror() ?[*:0]const u8 { - return null; -} diff --git a/src/dlfcn.zig b/src/dlfcn.zig new file mode 100644 index 0000000..b8f0a70 --- /dev/null +++ b/src/dlfcn.zig @@ -0,0 +1,110 @@ +const std = @import("std"); + +const log = std.log.scoped(.dl); +const assert = std.debug.assert; + +pub const Lib = struct { + name: []const u8, + symbols: std.StaticStringMap(*anyopaque), + + pub fn format(self: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void { + try writer.print("@\"{f}\"", .{std.zig.fmtString(self.name)}); + } +}; + +const main_program = "@SELF"; +const libs: std.StaticStringMap(Lib) = .initComptime(.{ + .{ + main_program, + Lib{ + .name = main_program, + .symbols = .initComptime(.{}), + }, + }, + .{ + "pipewire-0.3/plugins/support/libspa-support.so", + Lib{ + .name = "spa-support", + .symbols = .initComptime(.{ + // Implement! + // .{ "spa_handle_factory_enum", null }, + }), + }, + }, +}); + +export fn dlopen(path: ?[*:0]const c_char, flags: Flags) callconv(.c) ?*anyopaque { + comptime assert(@sizeOf(c_char) == @sizeOf(u8)); + const path_u8: [*:0]const u8 = if (path) |p| @ptrCast(p) else main_program; + const span = std.mem.span(path_u8); + const lib = if (libs.getIndex(span)) |index| &libs.kvs.values[index] else null; + log.info("dlopen(\"{f}\", {f}) -> {?f}", .{ std.zig.fmtString(span), flags, lib }); + return @ptrCast(@constCast(lib)); +} + +export fn dlclose(handle: ?*anyopaque) callconv(.c) c_int { + const lib: *const Lib = @ptrCast(@alignCast(handle.?)); + log.info("dlclose({f})", .{lib}); + return 0; +} + +export fn dlsym(noalias handle: ?*anyopaque, noalias name_c: ?[*:0]c_char) ?*anyopaque { + const lib: *const Lib = @ptrCast(@alignCast(handle.?)); + const name = std.mem.span(@as([*:0]u8, @ptrCast(name_c.?))); + const symbol = lib.symbols.get(name) orelse null; + log.info("dlsym({f}, \"{f}\") -> 0x{x}", .{ + lib, + std.zig.fmtString(name), + @intFromPtr(symbol), + }); + return symbol; +} + +export fn dlerror() ?[*:0]const u8 { + return null; +} + +export fn dlinfo(noalias handle: ?*anyopaque, request: c_int, noalias info: ?*anyopaque) c_int { + const lib: *const Lib = @ptrCast(@alignCast(handle.?)); // XXX: null allowed? + log.info("dlinfo({f}, {}, {x})", .{ lib, request, @intFromPtr(info) }); + @panic("unimplemented"); +} + +const Flags = packed struct(c_int) { + lazy: bool, + now: bool, + noload: bool, + _pad0: u5 = 0, + global: bool, + _pad1: u3 = 0, + nodelete: bool, + _pad2: std.meta.Int(.unsigned, @bitSizeOf(c_int) - 13) = 0, + + pub fn format(self: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void { + try writer.writeAll(".{"); + var first = true; + inline for (@typeInfo(@This()).@"struct".fields) |field| { + const val = @field(self, field.name); + switch (@typeInfo(field.type)) { + .bool => if (val) { + if (!first) { + try writer.writeAll(","); + } + first = false; + try writer.writeAll(" "); + try writer.print(".{s} = true", .{field.name}); + }, + .int => if (val != 0) { + if (!first) { + try writer.writeAll(", "); + first = false; + } + try writer.print(".{s} = {x}", .{ field.name, val }); + }, + else => comptime unreachable, + } + } + if (!first) try writer.writeAll(" "); + try writer.writeAll("}"); + } +}; From 751a5e62ea457ce933698fa08a4b6ba623c74b01 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Wed, 5 Nov 2025 15:50:07 -0800 Subject: [PATCH 5/8] Adds actual symbols to table --- build.zig | 8 +++++ src/dlfcn.zig | 84 +++++++++++++++++++++++++++++++-------------------- 2 files changed, 59 insertions(+), 33 deletions(-) diff --git a/build.zig b/build.zig index b9e0d2f..ed80181 100644 --- a/build.zig +++ b/build.zig @@ -191,6 +191,7 @@ pub fn build(b: *std.Build) void { .config = config_h, .install_dir = install_dir, .libpipewire = libpipewire, + .dlfcn = dlfcn, }; // Build and install the plugins @@ -361,6 +362,7 @@ pub const PluginAndModuleCtx = struct { optimize: std.builtin.OptimizeMode, install_dir: *std.Build.Step.WriteFile, libpipewire: *std.Build.Step.Compile, + dlfcn: *std.Build.Step.Compile, }; pub const PipewireModule = struct { @@ -391,6 +393,9 @@ pub const PipewireModule = struct { lib.addConfigHeader(ctx.config); lib.linkLibC(); + ctx.dlfcn.linkLibrary(lib); + ctx.dlfcn.addIncludePath(ctx.upstream.path("spa/include")); + return lib; } }; @@ -425,6 +430,9 @@ pub const PipewirePlugin = struct { lib.addConfigHeader(ctx.config); lib.linkLibC(); + ctx.dlfcn.linkLibrary(lib); + ctx.dlfcn.addIncludePath(ctx.upstream.path("spa/include")); + return lib; } }; diff --git a/src/dlfcn.zig b/src/dlfcn.zig index b8f0a70..f8c947c 100644 --- a/src/dlfcn.zig +++ b/src/dlfcn.zig @@ -3,21 +3,20 @@ const std = @import("std"); const log = std.log.scoped(.dl); const assert = std.debug.assert; -pub const Lib = struct { - name: []const u8, - symbols: std.StaticStringMap(*anyopaque), +const support = struct { + pub const c = @cImport({ + @cInclude("spa/support/plugin.h"); + @cInclude("spa/support/log.h"); + }); - pub fn format(self: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void { - try writer.print("@\"{f}\"", .{std.zig.fmtString(self.name)}); - } + extern const spa_log_topic_enum: c.spa_log_topic_enum; }; -const main_program = "@SELF"; const libs: std.StaticStringMap(Lib) = .initComptime(.{ .{ - main_program, + Lib.main_program_name, Lib{ - .name = main_program, + .name = Lib.main_program_name, .symbols = .initComptime(.{}), }, }, @@ -26,19 +25,17 @@ const libs: std.StaticStringMap(Lib) = .initComptime(.{ Lib{ .name = "spa-support", .symbols = .initComptime(.{ - // Implement! - // .{ "spa_handle_factory_enum", null }, + .{ "spa_handle_factory_enum", Lib.sym(&support.c.spa_handle_factory_enum) }, + .{ "spa_log_topic_enum", Lib.sym(&support.spa_log_topic_enum) }, }), }, }, }); -export fn dlopen(path: ?[*:0]const c_char, flags: Flags) callconv(.c) ?*anyopaque { - comptime assert(@sizeOf(c_char) == @sizeOf(u8)); - const path_u8: [*:0]const u8 = if (path) |p| @ptrCast(p) else main_program; - const span = std.mem.span(path_u8); +export fn dlopen(path: ?[*:0]const u8, mode: std.c.RTLD) callconv(.c) ?*anyopaque { + const span = if (path) |p| std.mem.span(p) else Lib.main_program_name; const lib = if (libs.getIndex(span)) |index| &libs.kvs.values[index] else null; - log.info("dlopen(\"{f}\", {f}) -> {?f}", .{ std.zig.fmtString(span), flags, lib }); + log.info("dlopen(\"{f}\", {f}) -> {?f}", .{ std.zig.fmtString(span), FmtMode.init(mode), lib }); return @ptrCast(@constCast(lib)); } @@ -48,20 +45,29 @@ export fn dlclose(handle: ?*anyopaque) callconv(.c) c_int { return 0; } -export fn dlsym(noalias handle: ?*anyopaque, noalias name_c: ?[*:0]c_char) ?*anyopaque { +export fn dlsym(noalias handle: ?*anyopaque, noalias name: [*:0]u8) ?*anyopaque { const lib: *const Lib = @ptrCast(@alignCast(handle.?)); - const name = std.mem.span(@as([*:0]u8, @ptrCast(name_c.?))); - const symbol = lib.symbols.get(name) orelse null; - log.info("dlsym({f}, \"{f}\") -> 0x{x}", .{ + const span = std.mem.span(name); + var msg: ?[:0]const u8 = null; + const symbol = lib.symbols.get(span) orelse b: { + msg = "symbol not found"; + break :b null; + }; + log.info("dlsym({f}, \"{f}\") -> 0x{x} ({s})", .{ lib, - std.zig.fmtString(name), + std.zig.fmtString(span), @intFromPtr(symbol), + if (msg) |m| m else "success", }); + if (msg) |m| err = m; return symbol; } +var err: ?[*:0]const u8 = null; export fn dlerror() ?[*:0]const u8 { - return null; + const result = err; + err = null; + return result; } export fn dlinfo(noalias handle: ?*anyopaque, request: c_int, noalias info: ?*anyopaque) c_int { @@ -70,21 +76,33 @@ export fn dlinfo(noalias handle: ?*anyopaque, request: c_int, noalias info: ?*an @panic("unimplemented"); } -const Flags = packed struct(c_int) { - lazy: bool, - now: bool, - noload: bool, - _pad0: u5 = 0, - global: bool, - _pad1: u3 = 0, - nodelete: bool, - _pad2: std.meta.Int(.unsigned, @bitSizeOf(c_int) - 13) = 0, +pub const Lib = struct { + const main_program_name = "@SELF"; + + name: []const u8, + symbols: std.StaticStringMap(*anyopaque), pub fn format(self: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void { + try writer.print("@\"{f}\"", .{std.zig.fmtString(self.name)}); + } + + pub fn sym(val: anytype) *anyopaque { + return @ptrCast(@constCast(val)); + } +}; + +pub const FmtMode = struct { + val: std.c.RTLD, + + pub fn init(val: std.c.RTLD) @This() { + return .{ .val = val }; + } + + pub fn format(self: anytype, writer: *std.Io.Writer) std.Io.Writer.Error!void { try writer.writeAll(".{"); var first = true; - inline for (@typeInfo(@This()).@"struct".fields) |field| { - const val = @field(self, field.name); + inline for (@typeInfo(@TypeOf(self.val)).@"struct".fields) |field| { + const val = @field(self.val, field.name); switch (@typeInfo(field.type)) { .bool => if (val) { if (!first) { From 004267ede55b0222fb335574873aed6df086f1d1 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Wed, 5 Nov 2025 15:50:16 -0800 Subject: [PATCH 6/8] Temporarily disables SDL in example so we can test without dynamic linking --- src/screen-play.c | 100 +++++++++++++++++++++++----------------------- src/sdl.h | 38 +++++++++--------- 2 files changed, 70 insertions(+), 68 deletions(-) diff --git a/src/screen-play.c b/src/screen-play.c index f13af81..3d1dcfe 100644 --- a/src/screen-play.c +++ b/src/screen-play.c @@ -61,14 +61,14 @@ struct data { static void handle_events(struct data *data) { - SDL_Event event; - while (SDL_PollEvent(&event)) { - switch (event.type) { - case SDL_EVENT_QUIT: - pw_main_loop_quit(data->loop); - break; - } - } + // SDL_Event event; + // while (SDL_PollEvent(&event)) { + // switch (event.type) { + // case SDL_EVENT_QUIT: + // pw_main_loop_quit(data->loop); + // break; + // } + // } } /* our data processing function is in general: @@ -147,19 +147,19 @@ on_process(void *_data) data->cursor_rect.w = mb->size.width; data->cursor_rect.h = mb->size.height; - if (data->cursor == NULL) { - data->cursor = SDL_CreateTexture(data->renderer, - id_to_sdl_format(mb->format), - SDL_TEXTUREACCESS_STREAMING, - mb->size.width, mb->size.height); - SDL_SetTextureBlendMode(data->cursor, SDL_BLENDMODE_BLEND); - } + // if (data->cursor == NULL) { + // data->cursor = SDL_CreateTexture(data->renderer, + // id_to_sdl_format(mb->format), + // SDL_TEXTUREACCESS_STREAMING, + // mb->size.width, mb->size.height); + // SDL_SetTextureBlendMode(data->cursor, SDL_BLENDMODE_BLEND); + // } - if (!SDL_LockTexture(data->cursor, NULL, &cdata, &cstride)) { - fprintf(stderr, "Couldn't lock cursor texture: %s\n", SDL_GetError()); - goto done; - } + // if (!SDL_LockTexture(data->cursor, NULL, &cdata, &cstride)) { + // fprintf(stderr, "Couldn't lock cursor texture: %s\n", SDL_GetError()); + // goto done; + // } /* copy the cursor bitmap into the texture */ src = SPA_PTROFF(mb, mb->offset, uint8_t); @@ -171,7 +171,7 @@ on_process(void *_data) dst += cstride; src += mb->stride; } - SDL_UnlockTexture(data->cursor); + // SDL_UnlockTexture(data->cursor); render_cursor = true; } @@ -181,22 +181,22 @@ on_process(void *_data) void *datas[4]; sstride = data->stride; if (buf->n_datas == 1) { - SDL_UpdateTexture(data->texture, NULL, - sdata, sstride); + // SDL_UpdateTexture(data->texture, NULL, + // sdata, sstride); } else { datas[0] = sdata; datas[1] = buf->datas[1].data; datas[2] = buf->datas[2].data; - SDL_UpdateYUVTexture(data->texture, NULL, - datas[0], sstride, - datas[1], sstride / 2, - datas[2], sstride / 2); + // SDL_UpdateYUVTexture(data->texture, NULL, + // datas[0], sstride, + // datas[1], sstride / 2, + // datas[2], sstride / 2); } } else { - if (!SDL_LockTexture(data->texture, NULL, &ddata, &dstride)) { - fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); - } + // if (!SDL_LockTexture(data->texture, NULL, &ddata, &dstride)) { + // fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); + // } sstride = buf->datas[0].chunk->stride; if (sstride == 0) @@ -225,16 +225,16 @@ on_process(void *_data) dst += dstride; } } - SDL_UnlockTexture(data->texture); + // SDL_UnlockTexture(data->texture); } - SDL_RenderClear(data->renderer); - /* now render the video and then the cursor if any */ - SDL_RenderTexture(data->renderer, data->texture, &data->rect, NULL); - if (render_cursor) { - SDL_RenderTexture(data->renderer, data->cursor, NULL, &data->cursor_rect); - } - SDL_RenderPresent(data->renderer); + // SDL_RenderClear(data->renderer); + // /* now render the video and then the cursor if any */ + // SDL_RenderTexture(data->renderer, data->texture, &data->rect, NULL); + // if (render_cursor) { + // SDL_RenderTexture(data->renderer, data->cursor, NULL, &data->cursor_rect); + // } + // SDL_RenderPresent(data->renderer); done: pw_stream_queue_buffer(stream, b); @@ -498,15 +498,15 @@ int main(int argc, char *argv[]) &stream_events, &data); - if (!SDL_Init(SDL_INIT_VIDEO)) { - fprintf(stderr, "can't initialize SDL: %s\n", SDL_GetError()); - return -1; - } + // if (!SDL_Init(SDL_INIT_VIDEO)) { + // fprintf(stderr, "can't initialize SDL: %s\n", SDL_GetError()); + // return -1; + // } - if (!SDL_CreateWindowAndRenderer("Demo", WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) { - fprintf(stderr, "can't create window: %s\n", SDL_GetError()); - return -1; - } + // if (!SDL_CreateWindowAndRenderer("Demo", WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) { + // fprintf(stderr, "can't create window: %s\n", SDL_GetError()); + // return -1; + // } /* build the extra parameters to connect with. To connect, we can provide * a list of supported formats. We use a builder that writes the param @@ -543,11 +543,11 @@ int main(int argc, char *argv[]) pw_stream_destroy(data.stream); pw_main_loop_destroy(data.loop); - SDL_DestroyTexture(data.texture); - if (data.cursor) - SDL_DestroyTexture(data.cursor); - SDL_DestroyRenderer(data.renderer); - SDL_DestroyWindow(data.window); + // SDL_DestroyTexture(data.texture); + // if (data.cursor) + // SDL_DestroyTexture(data.cursor); + // SDL_DestroyRenderer(data.renderer); + // SDL_DestroyWindow(data.window); pw_deinit(); return 0; diff --git a/src/sdl.h b/src/sdl.h index 002d134..b066476 100644 --- a/src/sdl.h +++ b/src/sdl.h @@ -148,21 +148,22 @@ static inline struct spa_pod *sdl_build_formats(SDL_Renderer * renderer, struct SDL_PropertiesID props = SDL_GetRendererProperties(renderer); - const SDL_PixelFormat *texture_formats = SDL_GetPointerProperty( - props, - SDL_PROP_RENDERER_TEXTURE_FORMATS_POINTER, - NULL - ); + // const SDL_PixelFormat *texture_formats = nullptr; + // SDL_GetPointerProperty( + // props, + // SDL_PROP_RENDERER_TEXTURE_FORMATS_POINTER, + // NULL + // ); /* first the formats supported by the textures */ - for (i = 0, c = 0; texture_formats[i] != SDL_PIXELFORMAT_UNKNOWN; i++) { - uint32_t id = sdl_format_to_id(texture_formats[i]); - if (id == 0) - continue; - if (c++ == 0) - spa_pod_builder_id(b, SPA_VIDEO_FORMAT_UNKNOWN); - spa_pod_builder_id(b, id); - } + // for (i = 0, c = 0; texture_formats[i] != SDL_PIXELFORMAT_UNKNOWN; i++) { + // uint32_t id = sdl_format_to_id(texture_formats[i]); + // if (id == 0) + // continue; + // if (c++ == 0) + // spa_pod_builder_id(b, SPA_VIDEO_FORMAT_UNKNOWN); + // spa_pod_builder_id(b, id); + // } /* then all the other ones SDL can convert from/to */ SPA_FOR_EACH_ELEMENT_VAR(sdl_video_formats, f) { uint32_t id = f->id; @@ -172,11 +173,12 @@ static inline struct spa_pod *sdl_build_formats(SDL_Renderer * renderer, struct spa_pod_builder_id(b, SPA_VIDEO_FORMAT_RGBA_F32); spa_pod_builder_pop(b, &f[1]); /* add size and framerate ranges */ - uint64_t max_texture_size = SDL_GetNumberProperty( - props, - SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, - 0 - ); + // uint64_t max_texture_size = SDL_GetNumberProperty( + // props, + // SDL_PROP_RENDERER_MAX_TEXTURE_SIZE_NUMBER, + // 0 + // ); + uint64_t max_texture_size = 4096; spa_pod_builder_add(b, SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( &SPA_RECTANGLE(WIDTH, HEIGHT), From 93d6718241974d77a264d995f8506608b760fe27 Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Fri, 7 Nov 2025 16:17:55 -0800 Subject: [PATCH 7/8] Adds all modules and plugins for screen play example, namespaces symbols to avoid collisions I believe this is now working with full static linking, but to be sure we'll need to find a way to display the image with static linking, write it to a file, or use a different example that doesn't require video. --- build.zig | 40 ++++++++++++++-- src/dlfcn.zig | 129 ++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 157 insertions(+), 12 deletions(-) diff --git a/build.zig b/build.zig index ed80181..e5ec658 100644 --- a/build.zig +++ b/build.zig @@ -333,9 +333,6 @@ pub fn linkAndInstall( // Statically link libpipewire exe.linkLibrary(dep.artifact("pipewire-0.3")); - // Note that the cache rpath will still be present: https://github.com/ziglang/zig/issues/24349 - exe.root_module.addRPathSpecial("$ORIGIN/pipewire-0.3"); - // Install Pipewire's dependencies b.installDirectory(.{ .install_dir = .bin, @@ -393,6 +390,9 @@ pub const PipewireModule = struct { lib.addConfigHeader(ctx.config); lib.linkLibC(); + namespace(lib, "pipewire__module_init"); + namespace(lib, "mod_topic"); + ctx.dlfcn.linkLibrary(lib); ctx.dlfcn.addIncludePath(ctx.upstream.path("spa/include")); @@ -430,9 +430,43 @@ pub const PipewirePlugin = struct { lib.addConfigHeader(ctx.config); lib.linkLibC(); + namespace(lib, "spa_handle_factory_enum"); + namespace(lib, "spa_log_topic_enum"); + ctx.dlfcn.linkLibrary(lib); ctx.dlfcn.addIncludePath(ctx.upstream.path("spa/include")); return lib; } }; + +pub fn namespace(library: *std.Build.Step.Compile, symbol: []const u8) void { + const b = library.root_module.owner; + library.root_module.addCMacro( + symbol, + b.fmt("{f}", .{Namespaced.init(library.name, symbol)}), + ); +} + +pub const Namespaced = struct { + prefix: []const u8, + symbol: []const u8, + + pub fn init(prefix: []const u8, symbol: []const u8) Namespaced { + return .{ + .prefix = prefix, + .symbol = symbol, + }; + } + + pub fn format(self: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void { + for (self.prefix) |c| { + switch (c) { + '-' => try writer.writeByte('_'), + else => try writer.writeByte(c), + } + } + try writer.writeAll("__"); + try writer.writeAll(self.symbol); + } +}; diff --git a/src/dlfcn.zig b/src/dlfcn.zig index f8c947c..4027af4 100644 --- a/src/dlfcn.zig +++ b/src/dlfcn.zig @@ -3,13 +3,30 @@ const std = @import("std"); const log = std.log.scoped(.dl); const assert = std.debug.assert; -const support = struct { - pub const c = @cImport({ - @cInclude("spa/support/plugin.h"); - @cInclude("spa/support/log.h"); - }); +pub const c = @cImport({ + @cInclude("spa/support/plugin.h"); + @cInclude("spa/support/log.h"); +}); + +const plugins = struct { + const SpaHandleFactoryEnum = @TypeOf(c.spa_handle_factory_enum); + + pub extern const spa_support__spa_handle_factory_enum: SpaHandleFactoryEnum; + pub extern const spa_videoconvert__spa_handle_factory_enum: SpaHandleFactoryEnum; - extern const spa_log_topic_enum: c.spa_log_topic_enum; + pub extern const spa_support__spa_log_topic_enum: c.spa_log_topic_enum; + pub extern const spa_videoconvert__spa_log_topic_enum: c.spa_log_topic_enum; +}; + +const modules = struct { + const PipewireModuleInit = fn (_: *anyopaque, _: *anyopaque) callconv(.c) void; + + pub extern const pipewire_module_protocol_native__pipewire__module_init: PipewireModuleInit; + pub extern const pipewire_module_client_node__pipewire__module_init: PipewireModuleInit; + pub extern const pipewire_module_client_device__pipewire__module_init: PipewireModuleInit; + pub extern const pipewire_module_adapter__pipewire__module_init: PipewireModuleInit; + pub extern const pipewire_module_metadata__pipewire__module_init: PipewireModuleInit; + pub extern const pipewire_module_session_manager__pipewire__module_init: PipewireModuleInit; }; const libs: std.StaticStringMap(Lib) = .initComptime(.{ @@ -25,8 +42,102 @@ const libs: std.StaticStringMap(Lib) = .initComptime(.{ Lib{ .name = "spa-support", .symbols = .initComptime(.{ - .{ "spa_handle_factory_enum", Lib.sym(&support.c.spa_handle_factory_enum) }, - .{ "spa_log_topic_enum", Lib.sym(&support.spa_log_topic_enum) }, + .{ + "spa_handle_factory_enum", + Lib.sym(&plugins.spa_support__spa_handle_factory_enum), + }, + .{ + "spa_log_topic_enum", + Lib.sym(&plugins.spa_support__spa_log_topic_enum), + }, + }), + }, + }, + .{ + "pipewire-0.3/plugins/videoconvert/libspa-videoconvert.so", + Lib{ + .name = "libspa-videoconvert", + .symbols = .initComptime(.{ + .{ + "spa_handle_factory_enum", + Lib.sym(&plugins.spa_videoconvert__spa_handle_factory_enum), + }, + .{ + "spa_log_topic_enum", + Lib.sym(&plugins.spa_videoconvert__spa_log_topic_enum), + }, + }), + }, + }, + .{ + "pipewire-0.3/modules/libpipewire-module-protocol-native.so", + Lib{ + .name = "libpipewire-module-protocol-native", + .symbols = .initComptime(.{ + .{ + "pipewire__module_init", + Lib.sym(&modules.pipewire_module_protocol_native__pipewire__module_init), + }, + }), + }, + }, + .{ + "pipewire-0.3/modules/libpipewire-module-client-node.so", + Lib{ + .name = "libpipewire-module-client-node", + .symbols = .initComptime(.{ + .{ + "pipewire__module_init", + Lib.sym(&modules.pipewire_module_client_node__pipewire__module_init), + }, + }), + }, + }, + .{ + "pipewire-0.3/modules/libpipewire-module-client-device.so", + Lib{ + .name = "libpipewire-module-client-device", + .symbols = .initComptime(.{ + .{ + "pipewire__module_init", + Lib.sym(&modules.pipewire_module_client_device__pipewire__module_init), + }, + }), + }, + }, + .{ + "pipewire-0.3/modules/libpipewire-module-adapter.so", + Lib{ + .name = "libpipewire-module-adapter", + .symbols = .initComptime(.{ + .{ + "pipewire__module_init", + Lib.sym(&modules.pipewire_module_adapter__pipewire__module_init), + }, + }), + }, + }, + .{ + "pipewire-0.3/modules/libpipewire-module-metadata.so", + Lib{ + .name = "libpipewire-module-metadata", + .symbols = .initComptime(.{ + .{ + "pipewire__module_init", + Lib.sym(&modules.pipewire_module_metadata__pipewire__module_init), + }, + }), + }, + }, + .{ + "pipewire-0.3/modules/libpipewire-module-session-manager.so", + Lib{ + .name = "libpipewire-module-session-manager", + .symbols = .initComptime(.{ + .{ + "pipewire__module_init", + Lib.sym(&modules.pipewire_module_session_manager__pipewire__module_init), + }, }), }, }, @@ -71,7 +182,7 @@ export fn dlerror() ?[*:0]const u8 { } export fn dlinfo(noalias handle: ?*anyopaque, request: c_int, noalias info: ?*anyopaque) c_int { - const lib: *const Lib = @ptrCast(@alignCast(handle.?)); // XXX: null allowed? + const lib: *const Lib = @ptrCast(@alignCast(handle.?)); log.info("dlinfo({f}, {}, {x})", .{ lib, request, @intFromPtr(info) }); @panic("unimplemented"); } From b1a88b689223c77590314d1e8d4b06217945f81c Mon Sep 17 00:00:00 2001 From: Mason Remaley Date: Fri, 7 Nov 2025 17:27:45 -0800 Subject: [PATCH 8/8] Adds stubs for when RTLD_NEXT is passed in --- src/dlfcn.zig | 91 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 88 insertions(+), 3 deletions(-) diff --git a/src/dlfcn.zig b/src/dlfcn.zig index 4027af4..f4ea479 100644 --- a/src/dlfcn.zig +++ b/src/dlfcn.zig @@ -6,6 +6,7 @@ const assert = std.debug.assert; pub const c = @cImport({ @cInclude("spa/support/plugin.h"); @cInclude("spa/support/log.h"); + @cInclude("dlfcn.h"); }); const plugins = struct { @@ -29,6 +30,51 @@ const modules = struct { pub extern const pipewire_module_session_manager__pipewire__module_init: PipewireModuleInit; }; +const fops = struct { + pub fn OPENAT64( + dirfd: c_int, + path: [*:0]const u8, + oflag: c_int, + mode: std.c.mode_t, + ) callconv(.c) c_int { + return @intCast(std.os.linux.openat(dirfd, path, @bitCast(oflag), mode)); + } + + pub fn dup(oldfd: c_int) callconv(.c) c_int { + return @intCast(std.os.linux.dup(oldfd)); + } + + pub fn close(fd: c_int) callconv(.c) c_int { + return @intCast(std.os.linux.close(fd)); + } + + pub fn ioctl(fd: c_int, request: c_ulong, arg: *anyopaque) callconv(.c) c_int { + return @intCast(std.os.linux.ioctl(fd, @intCast(request), @intFromPtr(arg))); + } + + pub fn mmap64( + addr: ?[*]u8, + length: usize, + prot: c_int, + flags: c_int, + fd: c_int, + offset: i64, + ) callconv(.c) *anyopaque { + return @ptrFromInt(std.os.linux.mmap( + addr, + length, + @intCast(prot), + @bitCast(flags), + fd, + offset, + )); + } + + pub fn munmap(addr: [*]const u8, length: usize) callconv(.c) c_int { + return @intCast(std.os.linux.munmap(addr, length)); + } +}; + const libs: std.StaticStringMap(Lib) = .initComptime(.{ .{ Lib.main_program_name, @@ -37,6 +83,38 @@ const libs: std.StaticStringMap(Lib) = .initComptime(.{ .symbols = .initComptime(.{}), }, }, + .{ + Lib.rtld_next_name, + Lib{ + .name = Lib.rtld_next_name, + .symbols = .initComptime(.{ + .{ + "OPENAT64", + Lib.sym(&fops.OPENAT64), + }, + .{ + "dup", + Lib.sym(&fops.dup), + }, + .{ + "close", + Lib.sym(&fops.close), + }, + .{ + "ioctl", + Lib.sym(&fops.ioctl), + }, + .{ + "mmap64", + Lib.sym(&fops.mmap64), + }, + .{ + "munmap", + Lib.sym(&fops.munmap), + }, + }), + }, + }, .{ "pipewire-0.3/plugins/support/libspa-support.so", Lib{ @@ -160,9 +238,15 @@ export fn dlsym(noalias handle: ?*anyopaque, noalias name: [*:0]u8) ?*anyopaque const lib: *const Lib = @ptrCast(@alignCast(handle.?)); const span = std.mem.span(name); var msg: ?[:0]const u8 = null; - const symbol = lib.symbols.get(span) orelse b: { - msg = "symbol not found"; - break :b null; + const symbol = b: { + if (handle == c.RTLD_NEXT) { + break :b lib.symbols.get(Lib.rtld_next_name).?; + } + const symbol = lib.symbols.get(span) orelse { + msg = "symbol not found"; + break :b null; + }; + break :b symbol; }; log.info("dlsym({f}, \"{f}\") -> 0x{x} ({s})", .{ lib, @@ -189,6 +273,7 @@ export fn dlinfo(noalias handle: ?*anyopaque, request: c_int, noalias info: ?*an pub const Lib = struct { const main_program_name = "@SELF"; + const rtld_next_name = "@RTLD_NEXT"; name: []const u8, symbols: std.StaticStringMap(*anyopaque),