Changes
74 changed files (+117/-463)
-
-
@@ -22,6 +22,14 @@ Third-party [Roon](https://roon.app/) client application using GTK4 and GNOME widgets. ## Runtime Dependencies - gtk4 - libadwaita - libjpeg - librsvg - libgee ## License This project is licensed under [Apache-2.0 License](https://www.apache.org/licenses/LICENSE-2.0).
-
@@ -40,7 +48,26 @@ ```nix develop ``` To develop without [Nix](https://nixos.org/) [Flakes](https://wiki.nixos.org/wiki/Flakes), install required dependencies listed at `outputs > devShell > packages` section in `flake.nix`. To develop without [Nix](https://nixos.org/) [Flakes](https://wiki.nixos.org/wiki/Flakes), install these softwares manually: - Zig compiler v0.14 - Vala compiler v0.56 - pkg-config - Runtime dependencies ### Running application ``` zig build run ``` ### Enable debug logging Add `Plac` to `G_MESSAGES_DEBUG` environment variable: ``` G_MESSAGES_DEBUG=Plac zig build run ``` ### Code Formatting
-
-
core/README.md (deleted)
-
@@ -1,145 +0,0 @@<!-- 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 --> # core Clients use this module and renders state struct managed by this. Every function is blocking, so client is responsible for making them asynchronous. ## Code Styles This section describes module internal rules for writing code. ### Struct creation function A function that creates `struct Foo`, - should be named `init` - should return a value (`Foo`), rather than a pointer of `Foo` (`*Foo`) - can return an error union This is what many Zig projects, including `std`, uses. Let the caller decide where to store. ```zig const std = @import("std"); pub const Foo = struct { bar: i32, pub fn init() Foo { return .{ bar = 0 }; } } ``` ### Struct creation and allocation function A function that allocates and initializes `struct Foo`, - should be named `make` - should return a pointer (`*Foo`) - should NOT take an allocator - can return an error union ```zig const std = @import("std"); pub const Foo = extern struct { const allocator = std.heap.c_allocator; bar: i32, pub fn make() callconv(.C) std.mem.Allocator.Error!*Foo { const dst = try allocator.create(Foo); dst.* = .{ .bar = 0, }; return dst; } } comptime { @export(&Foo.make, .{ .name = "plac_foo_make" }); } ``` ### Exported struct internal Exported structs' initial field must be a pointer to an internal struct: ```c // plac.h typedef struct { void *__pri; uint32_t bar; } plac_foo; ``` ```zig // foo.zig pub const Foo = extern struct { internal: *Internal, bar: u32, const Internal = struct { baz: u32, }; }; ``` Non-ABI stable fields (e.g. slices, reference counter) and internal fields should go inside `internal` field. ### Function parameters Zig team has a plan to drop automatic pass-by-reference conversion for function parameters. To minimize the cost of future migration, do not annotate a parameter with value type if pass-by-value is not desirable. For this reason, struct methods should use pointer type for `self` parameter. ```zig pub const Foo = struct { // OK pub fn bar(self: *Foo): void {} // OK pub fn bar(self: *const Foo): void {} // NG pub fn bar(self: Foo): void {} } ``` ### Exported functions When an exported function takes a pointer, it should be an optional pointer rather than regular pointer, to guard against accidentally passing NULL pointer. ```zig pub const Foo = extern struct { pub fn bar(self: *const Foo) void { // ... } } export fn do_something(foo_ptr: ?*const Foo) void { const foo = foo_ptr orelse return; foo.bar(); } ``` If the function returns result code, define a code for receiving a NULL pointer. If the function returns a pointer, return a NULL pointer and document about it in header file.
-
-
core/build.zig (deleted)
-
@@ -1,136 +0,0 @@// 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"); const RoonExtension = struct { id: []const u8, name: []const u8, version: []const u8, }; const ModuleOptions = struct { freelog: bool, extension: RoonExtension, }; pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); const opts = ModuleOptions{ .freelog = b.option(bool, "freelog", "Enable logging memory releases") orelse false, .extension = .{ .id = b.option([]const u8, "extension-id", "Roon extension ID") orelse "jp.pocka.plac", .name = b.option([]const u8, "extension-name", "Roon extension name") orelse "Plac", .version = b.option([]const u8, "extension-version", "Roon extension version") orelse "0.0.0-dev", }, }; // Zig module { const mod = b.addModule("core", .{ .root_source_file = b.path("src/main.zig"), }); setupModule(b, mod, opts); } // Test { const t = b.addTest(.{ .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); const run = b.addRunArtifact(t); const step = b.step("test", "Run unit tests"); step.dependOn(&run.step); } // 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, }), }); setupModule(b, lib.root_module, opts); 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); } } fn setupModule(b: *std.Build, mod: *std.Build.Module, opts: ModuleOptions) void { 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"); }; mod.addImport("sood", sood); mod.addImport("moo", moo); mod.addImport("websocket", websocket); const options = b.addOptions(); options.addOption(bool, "freelog", opts.freelog); options.addOption(RoonExtension, "extension", opts.extension); mod.addOptions("config", options); }
-
-
-
@@ -14,9 +14,9 @@ // limitations under the License.// // SPDX-License-Identifier: Apache-2.0 .{ .name = .plac_core, .name = .plac_gtk, .version = "0.0.0", .fingerprint = 0xc2c55a635cc4644d, .fingerprint = 0xd79b7f79b5956281, .minimum_zig_version = "0.14.0", .dependencies = .{ .sood = .{
-
-
-
-
core/nix/package.nix (deleted)
-
@@ -1,33 +0,0 @@# 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 { stdenvNoCC }: stdenvNoCC.mkDerivation { pname = "plac"; version = "0.0.0"; src = ../.; phases = [ "unpackPhase" "installPhase" ]; installPhase = '' mkdir -p $out cp $src/build.zig $out cp $src/build.zig.zon $out cp -r $src/src $out ''; }
-
-
-
-
-
-
-
-
-
-
@@ -14,11 +14,13 @@ // 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 main = @import("./main.zig"); pub const browse = @import("./browse.zig"); pub const connection = @import("./connection.zig"); pub const discovery = @import("./discovery.zig"); pub const image = @import("./image.zig"); pub const transport = @import("./transport.zig"); const glib = @cImport({ @cInclude("glib.h");
-
@@ -55,5 +57,16 @@ glib.g_log("Plac", g_level, message.ptr);} comptime { main.export_capi(); browse.export_capi(); connection.export_capi(); discovery.export_capi(); image.export_capi(); transport.export_capi(); } test { _ = @import("./browse.zig"); _ = @import("./connection.zig"); _ = @import("./image.zig"); _ = @import("./services/ImageService.zig"); }
-
-
core/src/main.zig (deleted)
-
@@ -1,36 +0,0 @@// 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 pub const browse = @import("./browse.zig"); pub const connection = @import("./connection.zig"); pub const discovery = @import("./discovery.zig"); pub const image = @import("./image.zig"); pub const transport = @import("./transport.zig"); pub fn export_capi() void { browse.export_capi(); connection.export_capi(); discovery.export_capi(); image.export_capi(); transport.export_capi(); } test { _ = @import("./browse.zig"); _ = @import("./connection.zig"); _ = @import("./image.zig"); _ = @import("./services/ImageService.zig"); }
-
-
-
-
-
-
-
-
-
-
-
-
@@ -41,13 +41,12 @@ lib = pkgs.lib;in rec { packages = { gtk-adwaita = pkgs.callPackage ./gtk-adwaita/nix/package.nix { }; default = pkgs.callPackage ./nix/package.nix { }; update-deps = pkgs.writeShellApplication { name = "update-deps"; text = '' cd core ${pkgs.zon2nix}/bin/zon2nix > nix/deps.nix ''; };
-
-
gtk-adwaita/README.md (deleted)
-
@@ -1,51 +0,0 @@<!-- 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 --> # Plac for GTK4 Plac's GTK4 application, adhering to Gnome Human Interface Guidline. ## Runtime Dependencies - gtk4 - libadwaita - librsvg - libgee ## Development Guide ### System Requirements If you installed Nix on your system and enabled Flake, run `nix develop` and every required dependencies will be installed. - Zig v0.14 - Vala 0.56 ### Running application ``` zig build run ``` ### Enable debug logging Add `Plac` to `G_MESSAGES_DEBUG` environment variable: ``` G_MESSAGES_DEBUG=Plac zig build run ```
-
-
-
@@ -26,6 +26,12 @@ const BuildError = error{NonValaSourceInSourceListError, }; const RoonExtension = struct { id: []const u8, name: []const u8, version: []const u8, }; const app_name = "jp.pocka.plac.gtk-adwaita"; const glib_schemas_dir = "share/glib-2.0/schemas"; const glib_compiled_schema_name = "gschemas.compiled";
-
@@ -35,18 +41,42 @@ const target = b.standardTargetOptions(.{});const optimize = b.standardOptimizeOption(.{}); const freelog = b.option(bool, "freelog", "Enable logging memory releases") orelse false; const extension = RoonExtension{ .id = b.option([]const u8, "extension-id", "Roon extension ID") orelse app_name, .name = b.option([]const u8, "extension-name", "Roon extension name") orelse "Plac for GTK", .version = b.option([]const u8, "extension-version", "Roon extension version") orelse "0.0.0-dev", }; const compile_gschema = b.option(bool, "compile-gschema", "Compile gschema XML file for local run") orelse false; const core = core: { const dep = b.dependency("core", .{ const zig_lib = zig_lib: { const mod = b.createModule(.{ .root_source_file = b.path("src/core/main.zig"), .link_libc = true, .target = target, .optimize = optimize, .freelog = freelog, .@"extension-id" = @as([]const u8, "jp.pocka.plac.gtk-adwaita"), .@"extension-name" = @as([]const u8, "Plac GTK"), }); mod.addImport("sood", b.dependency("sood", .{}).module("sood")); mod.addImport("moo", b.dependency("libmoo", .{}).module("moo")); mod.addImport("websocket", b.dependency("websocket", .{}).module("websocket")); const options = b.addOptions(); options.addOption(bool, "freelog", freelog); options.addOption(RoonExtension, "extension", extension); mod.addOptions("config", options); const obj = b.addObject(.{ .name = "plac", .root_module = mod, }); break :core dep.artifact("plac_glib"); obj.linkSystemLibrary2("glib-2.0", .{ .preferred_link_mode = .dynamic }); obj.installHeader(b.path("src/core/plac.h"), "plac.h"); obj.installHeader(b.path("src/core/plac.vapi"), "plac.vapi"); break :zig_lib obj; }; // Vala source codes to compile.
-
@@ -101,7 +131,7 @@ valac.addArg("--gresources");valac.addFileArg(b.path("data/gresource.xml")); valac.addArg("--vapidir"); valac.addDirectoryArg(core.getEmittedIncludeTree()); valac.addDirectoryArg(b.path("src/core")); // Tell Vala what system libraries to use. Perhaps type checking things? for (system_libraries) |lib| {
-
@@ -176,20 +206,21 @@ // An executable.const exe = exe: { const exe = b.addExecutable(.{ .name = app_name, .target = target, .optimize = optimize, .root_module = b.createModule(.{ // This is a standard C application, so libc is required. .link_libc = true, .target = target, .optimize = optimize, }), }); // This is a standard C application, so libc is required. exe.linkLibC(); // Vala does not bundle system libraries (of course): we have to tell the // linker. for (system_libraries) |lib| { exe.linkSystemLibrary(lib); } exe.linkLibrary(core); exe.root_module.addObject(zig_lib); exe.addCSourceFile(.{ .file = gresouce_c });
-
@@ -277,6 +308,18 @@ const run = b.addRunArtifact(exe);run.step.dependOn(&install_compiled_gschema.step); run.setEnvironmentVariable("GSETTINGS_SCHEMA_DIR", b.pathJoin(&.{ b.install_path, glib_schemas_dir })); const step = b.step("run", "Build and run Plac GTK-Adwaita app"); step.dependOn(&run.step); } // `zig build test` { const t = b.addTest(.{ .root_module = zig_lib.root_module, }); const run = b.addRunArtifact(t); const step = b.step("test", "Run unit tests"); step.dependOn(&run.step); } }
-
-
gtk-adwaita/build.zig.zon (deleted)
-
@@ -1,31 +0,0 @@// 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_gtk, .version = "0.0.0", .fingerprint = 0xd79b7f79b9f2bab4, .minimum_zig_version = "0.14.0", .dependencies = .{ .core = .{ .path = "../core", }, }, .paths = .{ "build.zig", "build.zig.zon", "src/", }, }
-
-
-
-
-
gtk-adwaita/data/icons/scalable/actions/audio-volume-high-symbolic.svg > data/icons/scalable/actions/audio-volume-high-symbolic.svg
-
gtk-adwaita/data/icons/scalable/actions/audio-volume-low-symbolic.svg > data/icons/scalable/actions/audio-volume-low-symbolic.svg
-
gtk-adwaita/data/icons/scalable/actions/go-next-symbolic.svg > data/icons/scalable/actions/go-next-symbolic.svg
-
gtk-adwaita/data/icons/scalable/actions/go-previous-symbolic.svg > data/icons/scalable/actions/go-previous-symbolic.svg
-
gtk-adwaita/data/icons/scalable/actions/image-missing-symbolic.svg > data/icons/scalable/actions/image-missing-symbolic.svg
-
gtk-adwaita/data/icons/scalable/actions/item-missing-symbolic.svg > data/icons/scalable/actions/item-missing-symbolic.svg
-
gtk-adwaita/data/icons/scalable/actions/library-music-symbolic.svg > data/icons/scalable/actions/library-music-symbolic.svg
-
gtk-adwaita/data/icons/scalable/actions/pause-large-symbolic.svg > data/icons/scalable/actions/pause-large-symbolic.svg
-
gtk-adwaita/data/icons/scalable/actions/play-large-symbolic.svg > data/icons/scalable/actions/play-large-symbolic.svg
-
gtk-adwaita/data/icons/scalable/actions/skip-backward-large-symbolic.svg > data/icons/scalable/actions/skip-backward-large-symbolic.svg
-
gtk-adwaita/data/icons/scalable/actions/skip-backwards-10-symbolic.svg > data/icons/scalable/actions/skip-backwards-10-symbolic.svg
-
gtk-adwaita/data/icons/scalable/actions/skip-forward-10-symbolic.svg > data/icons/scalable/actions/skip-forward-10-symbolic.svg
-
gtk-adwaita/data/icons/scalable/actions/skip-forward-large-symbolic.svg > data/icons/scalable/actions/skip-forward-large-symbolic.svg
-
gtk-adwaita/data/icons/scalable/actions/sound-symbolic.svg > data/icons/scalable/actions/sound-symbolic.svg
-
gtk-adwaita/data/icons/scalable/actions/view-more-symbolic.svg > data/icons/scalable/actions/view-more-symbolic.svg
-
-
-
-
-
-
-
-
-
-
gtk-adwaita/data/ui/server-list-network-error-dialog.ui > data/ui/server-list-network-error-dialog.ui
-
gtk-adwaita/data/ui/server-list-unexpected-error-dialog.ui > data/ui/server-list-unexpected-error-dialog.ui
-
-
-
-
@@ -20,6 +20,7 @@ callPackage,zig, gtk4, pkg-config, lib, libjpeg, libadwaita, librsvg,
-
@@ -31,7 +32,17 @@ }:stdenvNoCC.mkDerivation { pname = "plac-gtk-adwaita"; version = "0.0.0"; src = ../.; src = with lib.fileset; toSource { root = ../.; fileset = unions [ ../src ../data ../build.zig ../build.zig.zon ]; }; meta = { mainProgram = "jp.pocka.plac.gtk-adwaita";
-
@@ -93,14 +104,7 @@ ];zigBuildFlags = [ "--system" (callPackage ../../core/nix/deps.nix { }) (callPackage ./deps.nix { }) "-Dcompile-gschema" ]; # zon2nix does not support path dependency, and Zig seems not to have a way to set multiple # dependencies directory nor specify directory name via flags. postPatch = '' substituteInPlace build.zig.zon \ --replace "../core" $(realpath --relative-to=. ${callPackage ../../core/nix/package.nix { }}) ''; }
-
-
-
-
-
-
-
-
-
-
-
-
-
-