From 56ea6c406c155a48947274cd871ae274e654c957 Mon Sep 17 00:00:00 2001 From: Leah Amelia Chen Date: Sun, 9 Feb 2025 19:52:09 +0100 Subject: [PATCH] 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. --- src/Surface.zig | 8 ++++++ src/apprt/embedded.zig | 25 ++++++++++++++++++ src/apprt/glfw.zig | 5 ++++ src/apprt/gtk/Surface.zig | 19 ++++++++++++++ src/apprt/gtk/winproto.zig | 6 +++++ src/apprt/gtk/winproto/noop.zig | 2 ++ src/apprt/gtk/winproto/wayland.zig | 5 ++++ src/apprt/gtk/winproto/x11.zig | 7 +++++ src/os/env.zig | 9 +++++++ src/os/main.zig | 1 + src/termio/Exec.zig | 42 ++++-------------------------- 11 files changed, 92 insertions(+), 37 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index d9a985aa7..e7e8e20af 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -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", diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 358e9f291..864e00205 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -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 { diff --git a/src/apprt/glfw.zig b/src/apprt/glfw.zig index 686a70ddb..729decc0f 100644 --- a/src/apprt/glfw.zig +++ b/src/apprt/glfw.zig @@ -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; diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index b34ca9aa3..b9f8949fb 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -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 { diff --git a/src/apprt/gtk/winproto.zig b/src/apprt/gtk/winproto.zig index e6020f49e..c752ee692 100644 --- a/src/apprt/gtk/winproto.zig +++ b/src/apprt/gtk/winproto.zig @@ -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), + } + } }; diff --git a/src/apprt/gtk/winproto/noop.zig b/src/apprt/gtk/winproto/noop.zig index 38703aecb..cb1c0e9eb 100644 --- a/src/apprt/gtk/winproto/noop.zig +++ b/src/apprt/gtk/winproto/noop.zig @@ -61,4 +61,6 @@ pub const Window = struct { _ = self; return true; } + + pub fn addSubprocessEnv(_: *Window, _: *std.process.EnvMap) !void {} }; diff --git a/src/apprt/gtk/winproto/wayland.zig b/src/apprt/gtk/winproto/wayland.zig index 3e239eb29..f2ef17d73 100644 --- a/src/apprt/gtk/winproto/wayland.zig +++ b/src/apprt/gtk/winproto/wayland.zig @@ -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; diff --git a/src/apprt/gtk/winproto/x11.zig b/src/apprt/gtk/winproto/x11.zig index c58df6dea..6b60b0edf 100644 --- a/src/apprt/gtk/winproto/x11.zig +++ b/src/apprt/gtk/winproto/x11.zig @@ -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, diff --git a/src/os/env.zig b/src/os/env.zig index fe2be20de..1916053b3 100644 --- a/src/os/env.zig +++ b/src/os/env.zig @@ -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( diff --git a/src/os/main.zig b/src/os/main.zig index df6f894f5..cb9355931 100644 --- a/src/os/main.zig +++ b/src/os/main.zig @@ -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; diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 4428b16e1..d9730b970 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -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) {