gtk/portal: General improvements

- Token is formatted without allocation
- Reusable function for formatToken
- Tests in portal.zig are actuall included now
This commit is contained in:
Timon de Groot
2026-03-22 21:09:38 +01:00
parent 855a6b01fc
commit 37d297c03c
2 changed files with 54 additions and 23 deletions

View File

@@ -13,4 +13,5 @@ test {
@import("std").testing.refAllDecls(@This());
_ = @import("gtk/ext.zig");
_ = @import("gtk/key.zig");
_ = @import("gtk/portal.zig");
}

View File

@@ -5,39 +5,39 @@ const gio = @import("gio");
const Allocator = std.mem.Allocator;
pub const OpenURI = @import("portal/OpenURI.zig");
pub const token_hex_len = @sizeOf(usize) * 2;
pub const TokenBuffer = [token_hex_len + 1]u8;
const token_format = std.fmt.comptimePrint("{{x:0>{}}}", .{token_hex_len});
/// Generate a token suitable for use in requests to the XDG Desktop Portal
pub fn generateToken() usize {
return std.crypto.random.int(usize);
}
/// Format a request token consistently for use in portal object paths and payloads.
pub fn formatToken(buf: *TokenBuffer, token: usize) [:0]const u8 {
return std.fmt.bufPrintZ(buf, token_format, .{token}) catch unreachable;
}
/// Get the XDG portal request path for the current Ghostty instance.
///
/// If this sounds like nonsense, see `request` for an explanation as to
/// why we need to do this.
pub fn getRequestPath(alloc: Allocator, dbus: *gio.DBusConnection, token: usize) (Allocator.Error || std.fmt.BufPrintError || error{NoDBusUniqueName})![:0]const u8 {
// See https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Request.html
// for the syntax XDG portals expect.
/// See https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Request.html
/// for the protocol of the Request interface.
pub fn getRequestPath(alloc: Allocator, dbus: *gio.DBusConnection, token: usize) (Allocator.Error || error{NoDBusUniqueName})![:0]const u8 {
// Get the unique name from D-Bus and strip the leading `:`
const unique_name = try alloc.dupe(u8, std.mem.span(
const unique_name = std.mem.span(
dbus.getUniqueName() orelse {
return error.NoDBusUniqueName;
},
)[1..]);
defer alloc.free(unique_name);
)[1..];
return buildRequestPath(alloc, unique_name, token);
}
/// Build the XDG portal request path for given unique name and token.
fn buildRequestPath(alloc: Allocator, unique_name: []u8, token: usize) (Allocator.Error || std.fmt.BufPrintError)![:0]const u8 {
var token_buf: [16]u8 = undefined;
const token_string = try std.fmt.bufPrint(&token_buf, "{x:0>16}", .{token});
// Sanitize the unique name by replacing every `.` with `_`. In effect, this
// will turn a unique name like `1.192` into `1_192`.
std.mem.replaceScalar(u8, unique_name, '.', '_');
fn buildRequestPath(alloc: Allocator, unique_name: []const u8, token: usize) Allocator.Error![:0]const u8 {
var token_buf: TokenBuffer = undefined;
const token_string = formatToken(&token_buf, token);
const object_path = try std.mem.joinZ(
alloc,
@@ -49,27 +49,57 @@ fn buildRequestPath(alloc: Allocator, unique_name: []u8, token: usize) (Allocato
},
);
// Sanitize the unique name by replacing every `.` with `_`. In effect, this
// will turn a unique name like `1.192` into `1_192`.
// This sounds arbitrary, but it's part of the Request protocol.
_ = std.mem.replaceScalar(u8, object_path, '.', '_');
return object_path;
}
/// Try and parse the token out of a request path.
pub fn parseRequestPath(request_path: []const u8) ?usize {
pub fn parseRequestPathToken(request_path: []const u8) ?usize {
const index = std.mem.lastIndexOfScalar(u8, request_path, '/') orelse return null;
const token = request_path[index + 1 ..];
return std.fmt.parseUnsigned(usize, token, 16) catch return null;
}
test "formatToken pads to fixed width" {
const testing = std.testing;
var token_buf: TokenBuffer = undefined;
const token = formatToken(&token_buf, 0x42);
try testing.expectEqual(@as(usize, token_hex_len), token.len);
try testing.expectEqualStrings("0000000000000042", token);
}
test "buildRequestPath" {
const testing = std.testing;
const path = try buildRequestPath(testing.allocator, "1_42", 0x75af01a79c6fea34);
try testing.expectEqualStrings("/org/freedesktop/portal/desktop/request/1_42/75af01a79c6fea34", path);
const path = try buildRequestPath(testing.allocator, "1.42", 0x75af01a79c6fea34);
try testing.expectEqualStrings(
"/org/freedesktop/portal/desktop/request/1_42/75af01a79c6fea34",
path,
);
testing.allocator.free(path);
}
test "parseRequestPath" {
test "buildRequestPath pads token" {
const testing = std.testing;
const path = try buildRequestPath(testing.allocator, "1.42", 0x42);
try testing.expectEqualStrings(
"/org/freedesktop/portal/desktop/request/1_42/0000000000000042",
path,
);
testing.allocator.free(path);
}
test "parseRequestPathToken" {
const testing = std.testing;
try testing.expectEqual(0x75af01a79c6fea34, parseRequestPath("/org/freedesktop/portal/desktop/request/1_42/75af01a79c6fea34").?);
try testing.expectEqual(null, parseRequestPath("/org/freedesktop/portal/desktop/request/1_42/75af01a79c6fGa34"));
try testing.expectEqual(null, parseRequestPath("75af01a79c6fea34"));
try testing.expectEqual(0x75af01a79c6fea34, parseRequestPathToken("/org/freedesktop/portal/desktop/request/1_42/75af01a79c6fea34").?);
try testing.expectEqual(null, parseRequestPathToken("/org/freedesktop/portal/desktop/request/1_42/75af01a79c6fGa34"));
try testing.expectEqual(null, parseRequestPathToken("75af01a79c6fea34"));
}