gtk(x11): set WINDOWID env var for subprocesses

`WINDOWID` is the conventional environment variable for scripts that
want to know the X11 window ID of the terminal, so that it may call
tools like `xprop` or `xdotool`. We already know the window ID for
window protocol handling, so we might as well throw this in for
convenience.
This commit is contained in:
Leah Amelia Chen
2025-02-09 19:52:09 +01:00
committed by Mitchell Hashimoto
parent f8b547f92e
commit 56ea6c406c
11 changed files with 92 additions and 37 deletions

View File

@ -519,9 +519,17 @@ pub fn init(
// This separate block ({}) is important because our errdefers must
// be scoped here to be valid.
{
var env_ = rt_surface.defaultTermioEnv() catch |err| env: {
// If an error occurs, we don't want to block surface startup.
log.warn("error getting env map for surface err={}", .{err});
break :env null;
};
errdefer if (env_) |*env| env.deinit();
// Initialize our IO backend
var io_exec = try termio.Exec.init(alloc, .{
.command = command,
.env = env_,
.shell_integration = config.@"shell-integration",
.shell_integration_features = config.@"shell-integration-features",
.working_directory = config.@"working-directory",

View File

@ -12,6 +12,7 @@ const objc = @import("objc");
const apprt = @import("../apprt.zig");
const font = @import("../font/main.zig");
const input = @import("../input.zig");
const internal_os = @import("../os/main.zig");
const renderer = @import("../renderer.zig");
const terminal = @import("../terminal/main.zig");
const CoreApp = @import("../App.zig");
@ -1026,6 +1027,30 @@ pub const Surface = struct {
};
}
pub fn defaultTermioEnv(self: *const Surface) !?std.process.EnvMap {
const alloc = self.app.core_app.alloc;
var env = try internal_os.getEnvMap(alloc);
errdefer env.deinit();
if (comptime builtin.target.isDarwin()) {
if (env.get("__XCODE_BUILT_PRODUCTS_DIR_PATHS") != null) {
env.remove("__XCODE_BUILT_PRODUCTS_DIR_PATHS");
env.remove("__XPC_DYLD_LIBRARY_PATH");
env.remove("DYLD_FRAMEWORK_PATH");
env.remove("DYLD_INSERT_LIBRARIES");
env.remove("DYLD_LIBRARY_PATH");
env.remove("LD_LIBRARY_PATH");
env.remove("SECURITYSESSIONID");
env.remove("XPC_SERVICE_NAME");
}
// Remove this so that running `ghostty` within Ghostty works.
env.remove("GHOSTTY_MAC_APP");
}
return env;
}
/// The cursor position from the host directly is in screen coordinates but
/// all our interface works in pixels.
fn cursorPosToPixels(self: *const Surface, pos: apprt.CursorPos) !apprt.CursorPos {

View File

@ -874,6 +874,11 @@ pub const Surface = struct {
};
}
pub fn defaultTermioEnv(self: *Surface) !?std.process.EnvMap {
_ = self;
return null;
}
fn sizeCallback(window: glfw.Window, width: i32, height: i32) void {
_ = width;
_ = height;

View File

@ -2252,6 +2252,25 @@ fn doPaste(self: *Surface, data: [:0]const u8) void {
};
}
pub fn defaultTermioEnv(self: *Surface) !?std.process.EnvMap {
const alloc = self.app.core_app.alloc;
var env = try internal_os.getEnvMap(alloc);
errdefer env.deinit();
// Don't leak these GTK environment variables to child processes.
env.remove("GDK_DEBUG");
env.remove("GDK_DISABLE");
env.remove("GSK_RENDERER");
if (self.container.window()) |window| {
// On some window protocols we might want to add specific
// environment variables to subprocesses, such as WINDOWID on X11.
try window.winproto.addSubprocessEnv(&env);
}
return env;
}
/// Check a GValue to see what's type its wrapping. This is equivalent to GTK's
/// `G_VALUE_HOLDS` macro but Zig's C translator does not like it.
fn g_value_holds(value_: ?*c.GValue, g_type: c.GType) bool {

View File

@ -131,4 +131,10 @@ pub const Window = union(Protocol) {
inline else => |v| v.clientSideDecorationEnabled(),
};
}
pub fn addSubprocessEnv(self: *Window, env: *std.process.EnvMap) !void {
switch (self.*) {
inline else => |*v| try v.addSubprocessEnv(env),
}
}
};

View File

@ -61,4 +61,6 @@ pub const Window = struct {
_ = self;
return true;
}
pub fn addSubprocessEnv(_: *Window, _: *std.process.EnvMap) !void {}
};

View File

@ -262,6 +262,11 @@ pub const Window = struct {
};
}
pub fn addSubprocessEnv(self: *Window, env: *std.process.EnvMap) !void {
_ = self;
_ = env;
}
/// Update the blur state of the window.
fn syncBlur(self: *Window) !void {
const manager = self.app_context.kde_blur_manager orelse return;

View File

@ -314,6 +314,13 @@ pub const Window = struct {
);
}
pub fn addSubprocessEnv(self: *Window, env: *std.process.EnvMap) !void {
var buf: [64]u8 = undefined;
const window_id = try std.fmt.bufPrint(&buf, "{}", .{self.window});
try env.put("WINDOWID", window_id);
}
fn getWindowProperty(
self: *Window,
comptime T: type,

View File

@ -2,9 +2,18 @@ const std = @import("std");
const builtin = @import("builtin");
const Allocator = std.mem.Allocator;
const posix = std.posix;
const isFlatpak = @import("flatpak.zig").isFlatpak;
pub const Error = Allocator.Error;
/// Get the environment map.
pub fn getEnvMap(alloc: Allocator) !std.process.EnvMap {
return if (isFlatpak())
std.process.EnvMap.init(alloc)
else
try std.process.getEnvMap(alloc);
}
/// Append a value to an environment variable such as PATH.
/// The returned value is always allocated so it must be freed.
pub fn appendEnv(

View File

@ -26,6 +26,7 @@ pub const shell = @import("shell.zig");
// Functions and types
pub const CFReleaseThread = @import("cf_release_thread.zig");
pub const TempDir = @import("TempDir.zig");
pub const getEnvMap = env.getEnvMap;
pub const appendEnv = env.appendEnv;
pub const appendEnvAlways = env.appendEnvAlways;
pub const prependEnv = env.prependEnv;

View File

@ -682,6 +682,7 @@ pub const ThreadData = struct {
pub const Config = struct {
command: ?[]const u8 = null,
env: ?EnvMap = null,
shell_integration: configpkg.Config.ShellIntegration = .detect,
shell_integration_features: configpkg.Config.ShellIntegrationFeatures = .{},
working_directory: ?[]const u8 = null,
@ -721,18 +722,9 @@ const Subprocess = struct {
errdefer arena.deinit();
const alloc = arena.allocator();
// Set our env vars. For Flatpak builds running in Flatpak we don't
// inherit our environment because the login shell on the host side
// will get it.
var env = env: {
if (comptime build_config.flatpak) {
if (internal_os.isFlatpak()) {
break :env std.process.EnvMap.init(alloc);
}
}
break :env try std.process.getEnvMap(alloc);
};
// Get our env. If a default env isn't provided by the caller
// then we get it ourselves.
var env = cfg.env orelse try internal_os.getEnvMap(alloc);
errdefer env.deinit();
// If we have a resources dir then set our env var
@ -847,35 +839,11 @@ const Subprocess = struct {
try env.put("TERM_PROGRAM", "ghostty");
try env.put("TERM_PROGRAM_VERSION", build_config.version_string);
// When embedding in macOS and running via XCode, XCode injects
// a bunch of things that break our shell process. We remove those.
if (comptime builtin.target.isDarwin() and build_config.artifact == .lib) {
if (env.get("__XCODE_BUILT_PRODUCTS_DIR_PATHS") != null) {
env.remove("__XCODE_BUILT_PRODUCTS_DIR_PATHS");
env.remove("__XPC_DYLD_LIBRARY_PATH");
env.remove("DYLD_FRAMEWORK_PATH");
env.remove("DYLD_INSERT_LIBRARIES");
env.remove("DYLD_LIBRARY_PATH");
env.remove("LD_LIBRARY_PATH");
env.remove("SECURITYSESSIONID");
env.remove("XPC_SERVICE_NAME");
}
// Remove this so that running `ghostty` within Ghostty works.
env.remove("GHOSTTY_MAC_APP");
}
// VTE_VERSION is set by gnome-terminal and other VTE-based terminals.
// We don't want our child processes to think we're running under VTE.
// This is not apprt-specific, so we do it here.
env.remove("VTE_VERSION");
// Don't leak these GTK environment variables to child processes.
if (comptime build_config.app_runtime == .gtk) {
env.remove("GDK_DEBUG");
env.remove("GDK_DISABLE");
env.remove("GSK_RENDERER");
}
// Setup our shell integration, if we can.
const integrated_shell: ?shell_integration.Shell, const shell_command: []const u8 = shell: {
const default_shell_command = cfg.command orelse switch (builtin.os.tag) {