Changes
21 changed files (+371/-146)
-
core/build.zig (new)
-
@@ -0,0 +1,167 @@// Copyright 2025 Shota FUJI // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 //! This file defines how `zig build` command behaves. //! Run `zig build --help` for available subcommands and options. //! //! Learn more at //! https://ziglang.org/learn/build-system/ const std = @import("std"); pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); const sood = sood: { const dep = b.dependency("sood", .{}); break :sood dep.module("sood"); }; const moo = moo: { const dep = b.dependency("libmoo", .{}); break :moo dep.module("moo"); }; const websocket = websocket: { const dep = b.dependency("websocket", .{}); break :websocket dep.module("websocket"); }; // Static library for GLib { const linkage = b.option( std.builtin.LinkMode, "linkage", "Link mode of the generated library file", ) orelse .static; const lib = b.addLibrary(.{ .name = "plac_glib", .linkage = linkage, .root_module = b.createModule(.{ .root_source_file = b.path("src/main.glib.zig"), .target = target, .optimize = optimize, }), }); lib.root_module.addImport("sood", sood); lib.root_module.addImport("moo", moo); lib.root_module.addImport("websocket", websocket); lib.linkLibC(); lib.linkSystemLibrary2("glib-2.0", .{ .preferred_link_mode = .dynamic }); lib.installHeader(b.path("src/plac.h"), "plac.h"); lib.installHeader(b.path("src/plac.vapi"), "plac.vapi"); b.installArtifact(lib); const artifact = b.addInstallArtifact(lib, .{}); const step = b.step("glib", "Build library for GLib application"); step.dependOn(&artifact.step); b.default_step.dependOn(step); } // XCFramework // https://mitchellh.com/writing/zig-and-swiftui if (target.result.os.tag.isDarwin()) { const arm = b.addLibrary(.{ .name = "plac_apple_arm", .linkage = .static, .root_module = b.createModule(.{ .root_source_file = b.path("src/main.apple.zig"), .target = b.resolveTargetQuery(.{ .os_tag = .macos, .cpu_arch = .aarch64, }), .optimize = optimize, }), }); // While I could have dedicated unit just for copying, this is simpler and easier. arm.installHeader(b.path("src/plac.h"), "plac.h"); arm.installHeader(b.path("src/plac.modulemap"), "module.modulemap"); const intel = b.addLibrary(.{ .name = "plac_apple_intel", .linkage = .static, .root_module = b.createModule(.{ .root_source_file = b.path("src/main.apple.zig"), .target = b.resolveTargetQuery(.{ .os_tag = .macos, .cpu_arch = .x86_64, }), .optimize = optimize, }), }); inline for (.{ arm, intel }) |compile| { compile.linkLibC(); // Without bundling those, XCode is unable to resolve symbols. compile.bundle_compiler_rt = true; compile.bundle_ubsan_rt = true; compile.root_module.addImport("sood", sood); compile.root_module.addImport("moo", moo); compile.root_module.addImport("websocket", websocket); } const universal_lib = universal: { const lipo = b.addSystemCommand(&.{"lipo"}); lipo.addArg("-create"); lipo.addArg("-output"); const output = lipo.addOutputFileArg("libplac.a"); lipo.addFileArg(arm.getEmittedBin()); lipo.addFileArg(intel.getEmittedBin()); break :universal output; }; const xcframework = xcframework: { const xcodebuild = b.addSystemCommand(&.{"xcodebuild"}); xcodebuild.addArg("-create-xcframework"); xcodebuild.addArg("-library"); xcodebuild.addFileArg(universal_lib); xcodebuild.addArg("-headers"); xcodebuild.addDirectoryArg(arm.getEmittedIncludeTree()); xcodebuild.addArg("-output"); break :xcframework xcodebuild.addOutputDirectoryArg("PlacCore.xcframework"); }; const step = b.step("apple", "Build xcframework file for Apple platforms"); step.dependOn(&b.addInstallDirectory(.{ .source_dir = xcframework, .install_dir = .{ .custom = "xcframeworks" }, .install_subdir = "PlacCore.xcframework", }).step); b.default_step.dependOn(step); } }
-
-
core/build.zig.zon (new)
-
@@ -0,0 +1,40 @@// Copyright 2025 Shota FUJI // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 .{ .name = .plac_core, .version = "0.0.0", .fingerprint = 0xc2c55a635cc4644d, .minimum_zig_version = "0.14.0", .dependencies = .{ .sood = .{ .url = "https://git.pocka.jp/libsood.git/archive/8080245c2696cc6404b0628e5c3eb363cb80014f.tar.gz", .hash = "sood-0.0.0-A_jj-7ITAQAPlaaR2AHFXwPvBWzNBCCPTT--OCHnRQ_i", }, .libmoo = .{ .url = "https://git.pocka.jp/libmoo.git/archive/281d63245052c303c8851c4bfcbb428d78f9b52f.tar.gz", .hash = "libmoo-0.0.0-HVqw0sQVAQBtlGvtbF7pEkBkw5FKNr6zBYbGlHBienyE", }, .websocket = .{ .url = "https://github.com/karlseguin/websocket.zig/archive/c1c53b062eab871b95b70409daadfd6ac3d6df61.tar.gz", .hash = "websocket-0.1.0-ZPISdYBIAwB1yO6AFDHRHLaZSmpdh4Bz4dCmaQUqNNWh", }, }, .paths = .{ "build.zig", "build.zig.zon", "src/", }, }
-
-
core/src/main.apple.zig (new)
-
@@ -0,0 +1,23 @@// Copyright 2025 Shota FUJI // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 //! This file is an entrypoint of library for Apple platforms. const main = @import("./main.zig"); comptime { main.export_capi(); }
-
-
core/src/main.zig (new)
-
@@ -0,0 +1,25 @@// Copyright 2025 Shota FUJI // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 const connection = @import("./connection.zig"); const discovery = @import("./discovery.zig"); const transport = @import("./transport.zig"); pub fn export_capi() void { connection.export_capi(); discovery.export_capi(); transport.export_capi(); }
-
-
core/src/plac.modulemap (new)
-
@@ -0,0 +1,19 @@// Copyright 2025 Shota FUJI // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 module PlacKit { umbrella header "plac.h" export * }
-
-
-
@@ -30,45 +30,13 @@ pub fn build(b: *std.Build) !void {const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); const sood = sood: { const dep = b.dependency("sood", .{}); break :sood dep.module("sood"); }; const moo = moo: { const dep = b.dependency("libmoo", .{}); break :moo dep.module("moo"); }; const websocket = websocket: { const dep = b.dependency("websocket", .{}); break :websocket dep.module("websocket"); }; const core = core: { const lib = b.addLibrary(.{ .name = "plac_core", .linkage = .static, .root_module = b.createModule(.{ .root_source_file = b.path("src/core.zig"), .target = target, .optimize = optimize, }), const dep = b.dependency("core", .{ .target = target, .optimize = optimize, }); lib.root_module.addImport("sood", sood); lib.root_module.addImport("moo", moo); lib.root_module.addImport("websocket", websocket); lib.linkLibC(); lib.linkSystemLibrary2("glib-2.0", .{ .preferred_link_mode = .dynamic }); lib.installHeader(b.path("src/core.h"), "plac_core.h"); lib.installHeader(b.path("src/core.vapi"), "plac_core.vapi"); break :core lib; break :core dep.artifact("plac_glib"); }; // Vala source codes to compile.
-
@@ -123,7 +91,7 @@ valac.addArgs(&.{ "--pkg", lib });} valac.addArgs(&.{ "--pkg", "posix" }); valac.addArgs(&.{ "--pkg", "plac_core" }); valac.addArgs(&.{ "--pkg", "plac" }); // Tell Vala to emit C source files under the output directory. // The directory usually is Zig cache directory (like ".zig-cache/o/xxx").
-
-
-
@@ -19,17 +19,8 @@ .version = "0.0.0",.fingerprint = 0xd79b7f79b9f2bab4, .minimum_zig_version = "0.14.0", .dependencies = .{ .sood = .{ .url = "https://git.pocka.jp/libsood.git/archive/8080245c2696cc6404b0628e5c3eb363cb80014f.tar.gz", .hash = "sood-0.0.0-A_jj-7ITAQAPlaaR2AHFXwPvBWzNBCCPTT--OCHnRQ_i", }, .libmoo = .{ .url = "https://git.pocka.jp/libmoo.git/archive/281d63245052c303c8851c4bfcbb428d78f9b52f.tar.gz", .hash = "libmoo-0.0.0-HVqw0sQVAQBtlGvtbF7pEkBkw5FKNr6zBYbGlHBienyE", }, .websocket = .{ .url = "https://github.com/karlseguin/websocket.zig/archive/c1c53b062eab871b95b70409daadfd6ac3d6df61.tar.gz", .hash = "websocket-0.1.0-ZPISdYBIAwB1yO6AFDHRHLaZSmpdh4Bz4dCmaQUqNNWh", .core = .{ .path = "../core", }, }, .paths = .{
-
-
-
@@ -15,13 +15,28 @@ //// SPDX-License-Identifier: Apache-2.0 namespace Plac { public class Connection : GLib.Object { private ConnectionRaw conn; namespace Discovery { public async Plac.Discovery.ScanResult? scan_async() { GLib.SourceFunc callback = scan_async.callback; Plac.Discovery.ScanResult? result = null; new GLib.Thread<void>("server-scanner", () => { result = Plac.Discovery.scan(); GLib.Idle.add((owned) callback); }); yield; return (owned) result; } } public class AsyncConnection : GLib.Object { private Connection conn; private GLib.Thread<void>? thread; private bool is_closed; public Connection(Discovery.Server server) { this.conn = new ConnectionRaw(server); public AsyncConnection(Discovery.Server server) { this.conn = new Connection(server); this.thread = null; this.is_closed = false; }
-
-
-
@@ -47,7 +47,7 @@this.render(); } public void listen(Plac.Connection conn) { public void listen(Plac.AsyncConnection conn) { conn.zones_changed.connect((event) => { foreach (string id in event.removed) { GLib.log("Plac", LEVEL_DEBUG, "Zone id=%s removed", id);
-
-
-
@@ -27,7 +27,7 @@ [GtkChild]private unowned PlaybackToolbar playback_toolbar; private Plac.Discovery.Server server; private Plac.Connection conn; private Plac.AsyncConnection conn; public MainWindow(Gtk.Application app, Plac.Discovery.Server server) { (typeof (ServerConnecting)).ensure();
-
@@ -35,7 +35,7 @@ (typeof (PlaybackToolbar)).ensure();Object(application: app); this.server = server; this.conn = new Plac.Connection(server); this.conn = new Plac.AsyncConnection(server); } public void start() {
-
-
-
@@ -17,25 +17,27 @@ * SPDX-License-Identifier: Apache-2.0* * === * * C99 header file for helper C API for Plac GTK-Adwaita app. * C99 header file for helper C API for Plac core library. * This file is not checked against a generated library file: carefully write and review * definitions and implementations. */ #ifndef PLAC_GTK_ADWAITA_CORE_H #define PLAC_GTK_ADWAITA_CORE_H #ifndef PLAC_CORE_H #define PLAC_CORE_H #include <stddef.h> #include <stdint.h> // discovery.Server typedef struct { void *__pri; } plac_discovery_server; typedef struct { void *__pri; char *id; char *name; char *version; uint16_t http_port; } plac_discovery_server; plac_discovery_server *plac_discovery_server_retain(plac_discovery_server*); void plac_discovery_server_release(plac_discovery_server*); char *plac_discovery_server_id(plac_discovery_server*); char *plac_discovery_server_name(plac_discovery_server*); char *plac_discovery_server_version(plac_discovery_server*); uint16_t *plac_discovery_server_http_port(plac_discovery_server*); // discovery.ScanResult.Code typedef enum {
-
-
-
@@ -14,7 +14,7 @@ // limitations under the License.// // SPDX-License-Identifier: Apache-2.0 [CCode (cheader_filename = "plac_core.h")] [CCode (cheader_filename = "plac.h")] namespace Plac { namespace Discovery { [CCode (
-
@@ -30,18 +30,10 @@[CCode (cname = "plac_discovery_server_release")] public void unref (); public string id { [CCode (cname = "plac_discovery_server_id")] get; } public string name { [CCode (cname = "plac_discovery_server_name")] get; } public string version { [CCode (cname = "plac_discovery_server_version")] get; } public uint16 http_port { [CCode (cname = "plac_discovery_server_http_port")] get; } public string id; public string name; public string version; public uint16 http_port; } [CCode (
-
@@ -80,22 +72,8 @@ public Server[] servers;public ScanResultCode code; } [CCode (cname = "plac_discovery_scan")] public ScanResult? scan(); public async ScanResult? scan_async() { GLib.SourceFunc callback = scan_async.callback; ScanResult? result = null; new GLib.Thread<void>("server-scanner", () => { result = Plac.Discovery.scan(); GLib.Idle.add((owned) callback); }); yield; return (owned) result; } } namespace Transport {
-
@@ -217,7 +195,7 @@ ref_function = "plac_connection_event_retain",unref_function = "plac_connection_event_release" )] [Compact] private class ConnectionEvent { public class ConnectionEvent { [CCode (cname = "plac_connection_event_retain")] public void @ref ();
-
@@ -253,9 +231,9 @@ ref_function = "plac_connection_retain",unref_function = "plac_connection_release" )] [Compact] private class ConnectionRaw { private class Connection { [CCode (cname = "plac_connection_make")] public ConnectionRaw(Discovery.Server server); public Connection(Discovery.Server server); [CCode (cname = "plac_connection_retain")] public void @ref ();
-
-
-
@@ -14,11 +14,11 @@ // limitations under the License.// // SPDX-License-Identifier: Apache-2.0 //! This file is an entrypoint for GLib integrated library, for GTK applications. const std = @import("std"); const connection = @import("./core/connection.zig"); const discovery = @import("./core/discovery.zig"); const transport = @import("./core/transport.zig"); const main = @import("./main.zig"); const glib = @cImport({ @cInclude("glib.h");
-
@@ -55,18 +55,5 @@ glib.g_log("Plac", g_level, message.ptr);} comptime { @export(&discovery.Server.retain, .{ .name = "plac_discovery_server_retain" }); @export(&discovery.Server.release, .{ .name = "plac_discovery_server_release" }); @export(&discovery.Server.getId, .{ .name = "plac_discovery_server_id" }); @export(&discovery.Server.getName, .{ .name = "plac_discovery_server_name" }); @export(&discovery.Server.getVersion, .{ .name = "plac_discovery_server_version" }); @export(&discovery.Server.getHttpPort, .{ .name = "plac_discovery_server_http_port" }); @export(&discovery.ScanResult.retain, .{ .name = "plac_discovery_scan_result_retain" }); @export(&discovery.ScanResult.release, .{ .name = "plac_discovery_scan_result_release" }); @export(&discovery.scan, .{ .name = "plac_discovery_scan" }); connection.export_capi(); transport.export_capi(); main.export_capi(); }
-
-
-
-
@@ -27,7 +27,14 @@ const udp_send_tries = 4;const udp_receive_window_ms = 1300; pub const Server = extern struct { const cname = "plac_discovery_server"; const allocator = std.heap.c_allocator; internal: *Internal, id: [*:0]const u8, name: [*:0]const u8, version: [*:0]const u8, http_port: u16, pub const Internal = struct { id: [:0]const u8,
-
@@ -39,8 +46,6 @@ ref_count: i64 = 1,}; pub fn make(resp: *const sood.discovery.Response, addr: *const std.net.Address) !*Server { const allocator = std.heap.c_allocator; const self = try allocator.create(Server); errdefer allocator.destroy(self);
-
@@ -68,25 +73,34 @@ };self.* = .{ .internal = internal, .id = id.ptr, .name = name.ptr, .version = version.ptr, .http_port = resp.http_port, }; return self; } pub fn retain(ptr: ?*Server) callconv(.C) *Server { var self = ptr orelse @panic("Received null pointer on Server.retain"); var self = ptr orelse @panic(std.fmt.comptimePrint( "Received null pointer on {s}_{s}", .{ cname, @src().fn_name }, )); self.internal.ref_count += 1; return self; } pub fn release(ptr: ?*Server) callconv(.C) void { var self = ptr orelse @panic("Received null pointer on Server.release"); var self = ptr orelse @panic(std.fmt.comptimePrint( "Received null pointer on {s}_{s}", .{ cname, @src().fn_name }, )); self.internal.ref_count -= 1; if (self.internal.ref_count == 0) { std.log.debug("Releasing {*}...", .{self}); const allocator = std.heap.c_allocator; allocator.free(self.internal.id); allocator.free(self.internal.name);
-
@@ -98,32 +112,16 @@ std.log.warn("Over deref detected {*}, count={d}", .{ self, self.internal.ref_count });} } pub fn getId(ptr: ?*const Server) callconv(.C) [*:0]const u8 { const self = ptr orelse @panic("Received null pointer on Server.id"); return self.internal.id.ptr; } pub fn getName(ptr: ?*const Server) callconv(.C) [*:0]const u8 { const self = ptr orelse @panic("Received null pointer on Server.name"); return self.internal.name.ptr; } pub fn getVersion(ptr: ?*const Server) callconv(.C) [*:0]const u8 { const self = ptr orelse @panic("Received null pointer on Server.version"); return self.internal.version.ptr; } pub fn getHttpPort(ptr: ?*const Server) callconv(.C) u16 { const self = ptr orelse @panic("Received null pointer on Server.httpPort"); return self.internal.address.getPort(); pub fn export_capi() void { @export(&retain, .{ .name = std.fmt.comptimePrint("{s}_retain", .{cname}) }); @export(&release, .{ .name = std.fmt.comptimePrint("{s}_release", .{cname}) }); } }; pub const ScanResult = extern struct { const cname = "plac_discovery_scan_result"; const allocator = std.heap.c_allocator; internal: *Internal, servers_ptr: [*]*Server,
-
@@ -146,8 +144,6 @@ ref_count: i64 = 1,}; pub fn make() !*ScanResult { const allocator = std.heap.c_allocator; const self = try allocator.create(ScanResult); errdefer allocator.destroy(self);
-
@@ -168,14 +164,12 @@ return self;} pub fn setServers(self: *ScanResult, input: *const std.StringHashMap(*Server)) !void { const allocator = std.heap.c_allocator; const servers = try allocator.alloc(*Server, input.count()); var i: usize = 0; var iter = input.valueIterator(); while (iter.next()) |server| { std.log.debug("Found server ({s})", .{server.*.getName()}); std.log.debug("Found server ({s})", .{server.*.name}); servers[i] = server.*; i += 1; }
-
@@ -186,20 +180,24 @@ self.servers_len = servers.len;} pub fn retain(ptr: ?*ScanResult) callconv(.C) *ScanResult { var self = ptr orelse @panic("Received null pointer on ScanResult.retain"); var self = ptr orelse @panic(std.fmt.comptimePrint( "Received null pointer on {s}_{s}", .{ cname, @src().fn_name }, )); self.internal.ref_count += 1; return self; } pub fn release(ptr: ?*ScanResult) callconv(.C) void { var self = ptr orelse @panic("Received null pointer on ScanResult.release"); var self = ptr orelse @panic(std.fmt.comptimePrint( "Received null pointer on {s}_{s}", .{ cname, @src().fn_name }, )); self.internal.ref_count -= 1; if (self.internal.ref_count == 0) { std.log.debug("Releasing {*}...", .{self}); const allocator = std.heap.c_allocator; for (self.internal.servers) |server| { server.release(); }
-
@@ -209,6 +207,11 @@ allocator.destroy(self);} else if (self.internal.ref_count < 0) { std.log.warn("Over deref detected {*}, count={d}", .{ self, self.internal.ref_count }); } } pub fn export_capi() void { @export(&retain, .{ .name = std.fmt.comptimePrint("{s}_retain", .{cname}) }); @export(&release, .{ .name = std.fmt.comptimePrint("{s}_release", .{cname}) }); } };
-
@@ -385,3 +388,10 @@ else => ScanError.SocketError,}; }; } pub fn export_capi() void { Server.export_capi(); ScanResult.export_capi(); @export(&scan, .{ .name = "plac_discovery_scan" }); }
-
-
-
-
-
-
-