Merge pull request #1830 from ghostty-org/xdg-title

xdg-terminal-exec invocations set title based on command
This commit is contained in:
Mitchell Hashimoto
2024-06-06 14:53:31 -07:00
committed by GitHub
3 changed files with 98 additions and 24 deletions

View File

@ -513,7 +513,27 @@ pub fn init(
};
}
if (config.title) |title| try rt_surface.setTitle(title);
if (config.title) |title| {
try rt_surface.setTitle(title);
} else if ((comptime builtin.os.tag == .linux) and
config.@"_xdg-terminal-exec")
xdg: {
// For xdg-terminal-exec execution we special-case and set the window
// title to the command being executed. This allows window managers
// to set custom styling based on the command being executed.
const command = config.command orelse break :xdg;
if (command.len > 0) {
const title = alloc.dupeZ(u8, command) catch |err| {
log.warn(
"error copying command for title, title will not be set err={}",
.{err},
);
break :xdg;
};
defer alloc.free(title);
try rt_surface.setTitle(title);
}
}
}
pub fn deinit(self: *Surface) void {

View File

@ -1108,6 +1108,9 @@ _errors: ErrorList = .{},
/// as loadTheme which has more details on why.
_replay_steps: std.ArrayListUnmanaged(Replay.Step) = .{},
/// Set to true if Ghostty was executed as xdg-terminal-exec on Linux.
@"_xdg-terminal-exec": bool = false,
pub fn deinit(self: *Config) void {
if (self._arena) |arena| arena.deinit();
self.* = undefined;
@ -1654,13 +1657,16 @@ pub fn loadCliArgs(self: *Config, alloc_gpa: Allocator) !void {
// On Linux, we have a special case where if the executing
// program is "xdg-terminal-exec" then we treat all CLI
// args as if they are a command to execute.
if (comptime builtin.os.tag == .linux) xdg: {
if (!std.mem.eql(
u8,
std.fs.path.basename(std.mem.sliceTo(std.os.argv[0], 0)),
"xdg-terminal-exec",
)) break :xdg;
//
// In this mode, we also behave slightly differently:
//
// - The initial window title is set to the full command. This
// can be used with window managers to modify positioning,
// styling, etc. based on the command.
//
// See: https://github.com/Vladimir-csp/xdg-terminal-exec
if (comptime builtin.os.tag == .linux) {
if (internal_os.xdg.parseTerminalExec(std.os.argv)) |args| {
const arena_alloc = self._arena.?.allocator();
// First, we add an artificial "-e" so that if we
@ -1672,16 +1678,22 @@ pub fn loadCliArgs(self: *Config, alloc_gpa: Allocator) !void {
// a command to execute.
var command = std.ArrayList(u8).init(arena_alloc);
errdefer command.deinit();
for (std.os.argv[1..]) |arg_raw| {
for (args) |arg_raw| {
const arg = std.mem.sliceTo(arg_raw, 0);
try self._replay_steps.append(arena_alloc, .{ .arg = try arena_alloc.dupe(u8, arg) });
try self._replay_steps.append(
arena_alloc,
.{ .arg = try arena_alloc.dupe(u8, arg) },
);
try command.appendSlice(arg);
try command.append(' ');
}
self.@"_xdg-terminal-exec" = true;
self.command = command.items[0 .. command.items.len - 1];
return;
}
}
// Parse the config from the CLI args
var iter = try std.process.argsWithAllocator(alloc_gpa);

View File

@ -78,6 +78,23 @@ pub fn config(alloc: Allocator, opts: Options) ![]u8 {
return error.NoHomeDir;
}
/// Parses the xdg-terminal-exec specification. This expects argv[0] to
/// be "xdg-terminal-exec".
pub fn parseTerminalExec(argv: []const [*:0]const u8) ?[]const [*:0]const u8 {
if (!std.mem.eql(
u8,
std.fs.path.basename(std.mem.sliceTo(argv[0], 0)),
"xdg-terminal-exec",
)) return null;
// We expect at least one argument
if (argv.len < 2) return &.{};
// If the first argument is "-e" we skip it.
const start: usize = if (std.mem.eql(u8, std.mem.sliceTo(argv[1], 0), "-e")) 2 else 1;
return argv[start..];
}
test {
const testing = std.testing;
const alloc = testing.allocator;
@ -88,3 +105,28 @@ test {
try testing.expect(value.len > 0);
}
}
test parseTerminalExec {
const testing = std.testing;
{
const actual = parseTerminalExec(&.{ "a", "b", "c" });
try testing.expect(actual == null);
}
{
const actual = parseTerminalExec(&.{"xdg-terminal-exec"}).?;
try testing.expectEqualSlices([*:0]const u8, actual, &.{});
}
{
const actual = parseTerminalExec(&.{ "xdg-terminal-exec", "a", "b", "c" }).?;
try testing.expectEqualSlices([*:0]const u8, actual, &.{ "a", "b", "c" });
}
{
const actual = parseTerminalExec(&.{ "xdg-terminal-exec", "-e", "a", "b", "c" }).?;
try testing.expectEqualSlices([*:0]const u8, actual, &.{ "a", "b", "c" });
}
{
const actual = parseTerminalExec(&.{ "xdg-terminal-exec", "a", "-e", "b", "c" }).?;
try testing.expectEqualSlices([*:0]const u8, actual, &.{ "a", "-e", "b", "c" });
}
}