//! DBus helper for IPC const Self = @This(); const std = @import("std"); const Allocator = std.mem.Allocator; const gio = @import("gio"); const glib = @import("glib"); const apprt = @import("../../../apprt.zig"); const ApprtApp = @import("../App.zig"); /// The target for this IPC. target: apprt.ipc.Target, /// Connection to the DBus session bus. dbus: *gio.DBusConnection, /// The bus name of the Ghostty instance that we are calling. bus_name: [:0]const u8, /// The object path of the Ghostty instance that we are calling. object_path: [:0]const u8, /// Used to build the DBus payload. payload_builder: *glib.VariantBuilder, /// Used to build the parameters for the IPC. parameters_builder: *glib.VariantBuilder, /// Initialize the helper. pub fn init(alloc: Allocator, target: apprt.ipc.Target, action: [:0]const u8) (Allocator.Error || std.Io.Writer.Error || apprt.ipc.Errors)!Self { var buf: [256]u8 = undefined; var stderr_writer = std.fs.File.stderr().writer(&buf); const stderr = &stderr_writer.interface; // Get the appropriate bus name and object path for contacting the // Ghostty instance we're interested in. const bus_name: [:0]const u8, const object_path: [:0]const u8 = switch (target) { .class => |class| result: { // Force the usage of the class specified on the CLI to determine the // bus name and object path. const object_path = try std.fmt.allocPrintSentinel(alloc, "/{s}", .{class}, 0); std.mem.replaceScalar(u8, object_path, '.', '/'); std.mem.replaceScalar(u8, object_path, '-', '_'); break :result .{ class, object_path }; }, .detect => .{ ApprtApp.application_id, ApprtApp.object_path }, }; errdefer { switch (target) { .class => alloc.free(object_path), .detect => {}, } } if (gio.Application.idIsValid(bus_name.ptr) == 0) { try stderr.print("D-Bus bus name is not valid: {s}\n", .{bus_name}); try stderr.flush(); return error.IPCFailed; } if (glib.Variant.isObjectPath(object_path.ptr) == 0) { try stderr.print("D-Bus object path is not valid: {s}\n", .{object_path}); try stderr.flush(); return error.IPCFailed; } // Get a connection to the DBus session bus. const dbus = dbus: { var err_: ?*glib.Error = null; defer if (err_) |err| err.free(); const dbus_ = gio.busGetSync(.session, null, &err_); if (err_) |err| { try stderr.print( "Unable to establish connection to D-Bus session bus: {s}\n", .{err.f_message orelse "(unknown)"}, ); try stderr.flush(); return error.IPCFailed; } break :dbus dbus_ orelse { try stderr.print("gio.busGetSync returned null\n", .{}); try stderr.flush(); return error.IPCFailed; }; }; // Set up the payload builder. const payload_variant_type = glib.VariantType.new("(sava{sv})"); defer glib.free(payload_variant_type); const payload_builder = glib.VariantBuilder.new(payload_variant_type); // Add the action name to the payload. { const s_variant_type = glib.VariantType.new("s"); defer s_variant_type.free(); const bytes = glib.Bytes.new(action.ptr, action.len + 1); defer bytes.unref(); const value = glib.Variant.newFromBytes(s_variant_type, bytes, @intFromBool(true)); payload_builder.addValue(value); } // Set up the parameter builder. const parameters_variant_type = glib.VariantType.new("av"); defer parameters_variant_type.free(); const parameters_builder = glib.VariantBuilder.new(parameters_variant_type); return .{ .target = target, .dbus = dbus, .bus_name = bus_name, .object_path = object_path, .payload_builder = payload_builder, .parameters_builder = parameters_builder, }; } /// Add a parameter to the IPC call. pub fn addParameter(self: *Self, variant: *glib.Variant) void { self.parameters_builder.add("v", variant); } /// Send the IPC to the remote Ghostty. Once it completes, nothing further /// should be done with this object other than call `deinit`. pub fn send(self: *Self) (std.Io.Writer.Error || apprt.ipc.Errors)!void { var buf: [256]u8 = undefined; var stderr_writer = std.fs.File.stderr().writer(&buf); const stderr = &stderr_writer.interface; // finish building the parameters const parameters = self.parameters_builder.end(); // Add the parameters to the payload. self.payload_builder.addValue(parameters); // Add the platform data to the payload. { const platform_data_variant_type = glib.VariantType.new("a{sv}"); defer platform_data_variant_type.free(); self.payload_builder.open(platform_data_variant_type); defer self.payload_builder.close(); // We have no platform data. } const payload = self.payload_builder.end(); { var err_: ?*glib.Error = null; defer if (err_) |err| err.free(); const result_ = self.dbus.callSync( self.bus_name, self.object_path, "org.gtk.Actions", "Activate", payload, null, // We don't care about the return type, we don't do anything with it. .{}, // no flags -1, // default timeout null, // not cancellable &err_, ); defer if (result_) |result| result.unref(); if (err_) |err| { try stderr.print( "D-Bus method call returned an error err={s}\n", .{err.f_message orelse "(unknown)"}, ); try stderr.flush(); return error.IPCFailed; } } } /// Free/unref any data held by this instance. pub fn deinit(self: *Self, alloc: Allocator) void { switch (self.target) { .class => alloc.free(self.object_path), .detect => {}, } self.parameters_builder.unref(); self.payload_builder.unref(); self.dbus.unref(); }