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 // This separate block ({}) is important because our errdefers must
// be scoped here to be valid. // 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 // Initialize our IO backend
var io_exec = try termio.Exec.init(alloc, .{ var io_exec = try termio.Exec.init(alloc, .{
.command = command, .command = command,
.env = env_,
.shell_integration = config.@"shell-integration", .shell_integration = config.@"shell-integration",
.shell_integration_features = config.@"shell-integration-features", .shell_integration_features = config.@"shell-integration-features",
.working_directory = config.@"working-directory", .working_directory = config.@"working-directory",

View File

@ -12,6 +12,7 @@ const objc = @import("objc");
const apprt = @import("../apprt.zig"); const apprt = @import("../apprt.zig");
const font = @import("../font/main.zig"); const font = @import("../font/main.zig");
const input = @import("../input.zig"); const input = @import("../input.zig");
const internal_os = @import("../os/main.zig");
const renderer = @import("../renderer.zig"); const renderer = @import("../renderer.zig");
const terminal = @import("../terminal/main.zig"); const terminal = @import("../terminal/main.zig");
const CoreApp = @import("../App.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 /// The cursor position from the host directly is in screen coordinates but
/// all our interface works in pixels. /// all our interface works in pixels.
fn cursorPosToPixels(self: *const Surface, pos: apprt.CursorPos) !apprt.CursorPos { 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 { fn sizeCallback(window: glfw.Window, width: i32, height: i32) void {
_ = width; _ = width;
_ = height; _ = 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 /// 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. /// `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 { 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(), 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; _ = self;
return true; 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. /// Update the blur state of the window.
fn syncBlur(self: *Window) !void { fn syncBlur(self: *Window) !void {
const manager = self.app_context.kde_blur_manager orelse return; 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( fn getWindowProperty(
self: *Window, self: *Window,
comptime T: type, comptime T: type,

View File

@ -2,9 +2,18 @@ const std = @import("std");
const builtin = @import("builtin"); const builtin = @import("builtin");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const posix = std.posix; const posix = std.posix;
const isFlatpak = @import("flatpak.zig").isFlatpak;
pub const Error = Allocator.Error; 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. /// Append a value to an environment variable such as PATH.
/// The returned value is always allocated so it must be freed. /// The returned value is always allocated so it must be freed.
pub fn appendEnv( pub fn appendEnv(

View File

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

View File

@ -682,6 +682,7 @@ pub const ThreadData = struct {
pub const Config = struct { pub const Config = struct {
command: ?[]const u8 = null, command: ?[]const u8 = null,
env: ?EnvMap = null,
shell_integration: configpkg.Config.ShellIntegration = .detect, shell_integration: configpkg.Config.ShellIntegration = .detect,
shell_integration_features: configpkg.Config.ShellIntegrationFeatures = .{}, shell_integration_features: configpkg.Config.ShellIntegrationFeatures = .{},
working_directory: ?[]const u8 = null, working_directory: ?[]const u8 = null,
@ -721,18 +722,9 @@ const Subprocess = struct {
errdefer arena.deinit(); errdefer arena.deinit();
const alloc = arena.allocator(); const alloc = arena.allocator();
// Set our env vars. For Flatpak builds running in Flatpak we don't // Get our env. If a default env isn't provided by the caller
// inherit our environment because the login shell on the host side // then we get it ourselves.
// will get it. var env = cfg.env orelse try internal_os.getEnvMap(alloc);
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);
};
errdefer env.deinit(); errdefer env.deinit();
// If we have a resources dir then set our env var // 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", "ghostty");
try env.put("TERM_PROGRAM_VERSION", build_config.version_string); 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. // 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. // 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"); 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. // Setup our shell integration, if we can.
const integrated_shell: ?shell_integration.Shell, const shell_command: []const u8 = shell: { const integrated_shell: ?shell_integration.Shell, const shell_command: []const u8 = shell: {
const default_shell_command = cfg.command orelse switch (builtin.os.tag) { const default_shell_command = cfg.command orelse switch (builtin.os.tag) {