Allow setting terminal env vars via PTY options

Closes #7778.
This commit is contained in:
Kirill Bulatov 2024-03-18 03:15:39 +02:00 committed by GitHub
parent 14b53f18db
commit fe88aaa085
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 90 additions and 7 deletions

View File

@ -1,4 +1,5 @@
use std::cmp::max;
use std::collections::HashMap;
use std::ops::{Deref, DerefMut};
use std::path::PathBuf;
use std::rc::Rc;
@ -195,6 +196,7 @@ impl From<TerminalOptions> for PtyOptions {
working_directory: options.working_directory.take(),
shell: options.command().map(Into::into),
hold: options.hold,
env: HashMap::new(),
}
}
}

View File

@ -167,7 +167,12 @@ impl UiConfig {
/// Derive [`PtyOptions`] from the config.
pub fn pty_config(&self) -> PtyOptions {
let shell = self.shell.clone().map(Into::into);
PtyOptions { shell, working_directory: self.working_directory.clone(), hold: false }
PtyOptions {
shell,
working_directory: self.working_directory.clone(),
hold: false,
env: HashMap::new(),
}
}
/// Generate key bindings for all keyboard hints.

View File

@ -1,5 +1,6 @@
//! TTY related functionality.
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use std::{env, io};
@ -29,6 +30,9 @@ pub struct Options {
/// Remain open after child process exits.
pub hold: bool,
/// Extra environment variables.
pub env: HashMap<String, String>,
}
/// Shell options.

View File

@ -217,9 +217,11 @@ pub fn new(config: &Options, window_size: WindowSize, window_id: u64) -> Result<
builder.env("ALACRITTY_WINDOW_ID", &window_id);
builder.env("USER", user.user);
builder.env("HOME", user.home);
// Set Window ID for clients relying on X11 hacks.
builder.env("WINDOWID", window_id);
for (key, value) in &config.env {
builder.env(key, value);
}
unsafe {
builder.pre_exec(move || {

View File

@ -1,5 +1,8 @@
use log::info;
use log::{info, warn};
use std::collections::{HashMap, HashSet};
use std::ffi::OsStr;
use std::io::Error;
use std::os::windows::ffi::OsStrExt;
use std::os::windows::io::IntoRawHandle;
use std::{mem, ptr};
@ -13,8 +16,8 @@ use windows_sys::{s, w};
use windows_sys::Win32::System::Threading::{
CreateProcessW, InitializeProcThreadAttributeList, UpdateProcThreadAttribute,
EXTENDED_STARTUPINFO_PRESENT, PROCESS_INFORMATION, PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
STARTF_USESTDHANDLES, STARTUPINFOEXW, STARTUPINFOW,
CREATE_UNICODE_ENVIRONMENT, EXTENDED_STARTUPINFO_PRESENT, PROCESS_INFORMATION,
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, STARTF_USESTDHANDLES, STARTUPINFOEXW, STARTUPINFOW,
};
use crate::event::{OnResize, WindowSize};
@ -198,8 +201,18 @@ pub fn new(config: &Options, window_size: WindowSize) -> Option<Pty> {
}
}
// Prepare child process creation arguments.
let cmdline = win32_string(&cmdline(config));
let cwd = config.working_directory.as_ref().map(win32_string);
let mut creation_flags = EXTENDED_STARTUPINFO_PRESENT;
let custom_env_block = convert_custom_env(&config.env);
let custom_env_block_pointer = match &custom_env_block {
Some(custom_env_block) => {
creation_flags |= CREATE_UNICODE_ENVIRONMENT;
custom_env_block.as_ptr() as *mut std::ffi::c_void
},
None => ptr::null_mut(),
};
let mut proc_info: PROCESS_INFORMATION = unsafe { mem::zeroed() };
unsafe {
@ -209,8 +222,8 @@ pub fn new(config: &Options, window_size: WindowSize) -> Option<Pty> {
ptr::null_mut(),
ptr::null_mut(),
false as i32,
EXTENDED_STARTUPINFO_PRESENT,
ptr::null_mut(),
creation_flags,
custom_env_block_pointer,
cwd.as_ref().map_or_else(ptr::null, |s| s.as_ptr()),
&mut startup_info_ex.StartupInfo as *mut STARTUPINFOW,
&mut proc_info as *mut PROCESS_INFORMATION,
@ -230,6 +243,63 @@ pub fn new(config: &Options, window_size: WindowSize) -> Option<Pty> {
Some(Pty::new(conpty, conout, conin, child_watcher))
}
// Windows environment variables are case-insensitive, and the caller is responsible for
// deduplicating environment variables, so do that here while converting.
//
// https://learn.microsoft.com/en-us/previous-versions/troubleshoot/windows/win32/createprocess-cannot-eliminate-duplicate-variables#environment-variables
fn convert_custom_env(custom_env: &HashMap<String, String>) -> Option<Vec<u16>> {
// Windows inherits parent's env when no `lpEnvironment` parameter is specified.
if custom_env.is_empty() {
return None;
}
let mut converted_block = Vec::new();
let mut all_env_keys = HashSet::new();
for (custom_key, custom_value) in custom_env {
let custom_key_os = OsStr::new(custom_key);
if all_env_keys.insert(custom_key_os.to_ascii_uppercase()) {
add_windows_env_key_value_to_block(
&mut converted_block,
custom_key_os,
OsStr::new(&custom_value),
);
} else {
warn!(
"Omitting environment variable pair with duplicate key: \
'{custom_key}={custom_value}'"
);
}
}
// Pull the current process environment after, to avoid overwriting the user provided one.
for (inherited_key, inherited_value) in std::env::vars_os() {
if all_env_keys.insert(inherited_key.to_ascii_uppercase()) {
add_windows_env_key_value_to_block(
&mut converted_block,
&inherited_key,
&inherited_value,
);
}
}
converted_block.push(0);
Some(converted_block)
}
// According to the `lpEnvironment` parameter description:
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessa#parameters
//
// > An environment block consists of a null-terminated block of null-terminated strings. Each
// string is in the following form:
// >
// > name=value\0
fn add_windows_env_key_value_to_block(block: &mut Vec<u16>, key: &OsStr, value: &OsStr) {
block.extend(key.encode_wide());
block.push('=' as u16);
block.extend(value.encode_wide());
block.push(0);
}
// Panic with the last os error as message.
fn panic_shell_spawn() {
panic!("Unable to spawn shell: {}", Error::last_os_error());