Something went wrong. Try again.
A GPU terminal emulator for Wayland and Windows on libghostty-vt, drawing with Vulkan, running a program on a pty or a serial port.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121// SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie <jeff@ocjtech.us>// SPDX-License-Identifier: MIT
//! Just enough of a desktop entry file to launch what it describes in a//! terminal: its name, for the window's title, and its `Path`, the//! directory it runs in.//!//! The format is the Desktop Entry Specification's: `[Group]` headers,//! `Key=Value` lines, comments starting with `#`, and the `[Desktop Entry]`//! group first. Localized keys (`Name[fr]`) are skipped: the unlocalized//! one is what every entry has.
const std = @import("std");const Allocator = std.mem.Allocator;
pub const Entry = struct { name: ?[]const u8 = null, path: ?[]const u8 = null,};
pub const ParseError = error{ /// Not a desktop entry: no `[Desktop Entry]` group, or one that is not /// first, or a line that is neither a group, a key nor a comment. NotDesktopEntry,} || Allocator.Error;
/// The entry's fields, allocated from `arena`.pub fn parse(arena: Allocator, text: []const u8) ParseError!Entry { var entry: Entry = .{}; var group: ?[]const u8 = null; var lines = std.mem.splitScalar(u8, text, '\n'); while (lines.next()) |raw| { const line = std.mem.trimEnd(u8, raw, "\r"); if (line.len == 0 or line[0] == '#') continue; if (line[0] == '[') { if (line[line.len - 1] != ']') return error.NotDesktopEntry; const name = line[1 .. line.len - 1]; // The first group must be this one. if (group == null and !std.mem.eql(u8, name, "Desktop Entry")) return error.NotDesktopEntry; group = name; continue; } const g = group orelse return error.NotDesktopEntry; const eq = std.mem.findScalar(u8, line, '=') orelse return error.NotDesktopEntry; if (!std.mem.eql(u8, g, "Desktop Entry")) continue; const key = std.mem.trim(u8, line[0..eq], " "); const value = std.mem.trim(u8, line[eq + 1 ..], " "); if (std.mem.eql(u8, key, "Name")) { entry.name = try unescape(arena, value); } else if (std.mem.eql(u8, key, "Path")) { entry.path = try unescape(arena, value); } } if (group == null) return error.NotDesktopEntry; return entry;}
/// A `string` value's escapes: `\s`, `\n`, `\t`, `\r` and `\\`.fn unescape(arena: Allocator, value: []const u8) Allocator.Error![]const u8 { if (std.mem.findScalar(u8, value, '\\') == null) return arena.dupe(u8, value); var out: std.ArrayList(u8) = try .initCapacity(arena, value.len); var i: usize = 0; while (i < value.len) : (i += 1) { if (value[i] != '\\' or i + 1 == value.len) { out.appendAssumeCapacity(value[i]); continue; } i += 1; out.appendAssumeCapacity(switch (value[i]) { 's' => ' ', 'n' => '\n', 't' => '\t', 'r' => '\r', else => |c| c, }); } return out.items;}
const testing = std.testing;
test "the name and the directory, from the spec's own example" { var arena: std.heap.ArenaAllocator = .init(testing.allocator); defer arena.deinit(); const e = try parse(arena.allocator(), \\[Desktop Entry] \\Version=1.0 \\Type=Application \\Terminal=true \\Name=Editor \\Name[sv]=Redigerare \\Comment=A terminal text editor \\Exec=xdg-editor %f \\Path=/opt/xdg/editor \\ \\[Desktop Action new] \\Name=New \\ ); try testing.expectEqualStrings("Editor", e.name.?); try testing.expectEqualStrings("/opt/xdg/editor", e.path.?);}
test "comments first, escapes, and CRLF" { var arena: std.heap.ArenaAllocator = .init(testing.allocator); defer arena.deinit(); const e = try parse(arena.allocator(), "# made by hand\r\n[Desktop Entry]\r\nName=Two\\sWords\r\n"); try testing.expectEqualStrings("Two Words", e.name.?); try testing.expect(e.path == null);}
test "what is not a desktop entry" { var arena: std.heap.ArenaAllocator = .init(testing.allocator); defer arena.deinit(); const a = arena.allocator(); try testing.expectError(error.NotDesktopEntry, parse(a, "")); try testing.expectError(error.NotDesktopEntry, parse(a, "Name=x\n")); try testing.expectError(error.NotDesktopEntry, parse(a, "[Other]\nName=x\n[Desktop Entry]\n")); try testing.expectError(error.NotDesktopEntry, parse(a, "\x7fELF\x02\x01"));}