mirror of
https://github.com/Eugeny/tabby.git
synced 2026-08-24 02:34:19 -05:00
[FEATURE] refresh Windows environment variables from registry for new sessions (#11321)
Co-authored-by: Eugene <inbox@null.page>
This commit is contained in:
@@ -8,6 +8,7 @@ export class TerminalConfigProvider extends ConfigProvider {
|
||||
useConPTY: true,
|
||||
environment: {},
|
||||
setComSpec: false,
|
||||
windowsRefreshEnvironment: true,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
let wnr: any = null
|
||||
|
||||
try {
|
||||
wnr = require('windows-native-registry') // eslint-disable-line @typescript-eslint/no-var-requires
|
||||
} catch { }
|
||||
|
||||
let cachedEnvironment: Record<string, string>|null = null
|
||||
|
||||
/** Strips `undefined` values from a process-style environment object. */
|
||||
function normalizeEnv (env: Record<string, string|undefined>): Record<string, string> {
|
||||
const result: Record<string, string> = {}
|
||||
for (const [key, value] of Object.entries(env)) {
|
||||
if (value !== undefined) {
|
||||
result[key] = value
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a variable name to its actual key in `env`. Lookups are
|
||||
* case-insensitive on Windows, where environment variable names are.
|
||||
*/
|
||||
function findKey (env: Record<string, string>, name: string): string|undefined {
|
||||
if (name in env) {
|
||||
return name
|
||||
}
|
||||
if (process.platform !== 'win32') {
|
||||
return undefined
|
||||
}
|
||||
const lower = name.toLowerCase()
|
||||
return Object.keys(env).find(k => k.toLowerCase() === lower)
|
||||
}
|
||||
|
||||
/** Iteratively expands `%VAR%` references within an environment block in place. */
|
||||
function expandWindowsEnv (env: Record<string, string>): void {
|
||||
const pattern = /%([^%]+)%/g
|
||||
let changed = true
|
||||
let iterations = 0
|
||||
while (changed && iterations < 10) {
|
||||
changed = false
|
||||
iterations++
|
||||
for (const [key, value] of Object.entries(env)) {
|
||||
const expanded = value.replace(pattern, (match, varName) => {
|
||||
const found = findKey(env, varName)
|
||||
return found !== undefined ? env[found] : match
|
||||
})
|
||||
if (expanded !== env[key]) {
|
||||
env[key] = expanded
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function readRegistryEnv (hive: any, path: string): Record<string, string> {
|
||||
const env: Record<string, string> = {}
|
||||
if (!wnr) {
|
||||
return env
|
||||
}
|
||||
try {
|
||||
const key = wnr.getRegistryKey(hive, path)
|
||||
if (!key) {
|
||||
return env
|
||||
}
|
||||
for (const [name, entry] of Object.entries(key)) {
|
||||
if (!name || typeof name !== 'string') {
|
||||
continue
|
||||
}
|
||||
const e = entry as any
|
||||
if (e && typeof e.value === 'string') {
|
||||
env[name] = e.value
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore registry read errors
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
function isPathLikeVar (name: string): boolean {
|
||||
const lower = name.toLowerCase()
|
||||
return lower === 'path' || lower === 'pathext'
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges a registry environment source into `target`. Most variables override,
|
||||
* but `Path`/`PATHEXT` are appended to the existing value (resolved
|
||||
* case-insensitively so e.g. system `Path` and user `PATH` don't split).
|
||||
*/
|
||||
function mergeRegistryEnv (target: Record<string, string>, source: Record<string, string>): void {
|
||||
for (const [key, value] of Object.entries(source)) {
|
||||
const existingKey = isPathLikeVar(key) ? findKey(target, key) : undefined
|
||||
if (existingKey !== undefined) {
|
||||
target[existingKey] = target[existingKey] && value
|
||||
? target[existingKey] + ';' + value
|
||||
: target[existingKey] || value
|
||||
} else {
|
||||
target[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuilds the environment block from Windows registry sources,
|
||||
* mimicking the behavior of Windows Terminal's environment refresh.
|
||||
*/
|
||||
function buildWindowsEnvironment (): Record<string, string> {
|
||||
const merged: Record<string, string> = {}
|
||||
|
||||
// System env vars as base, then user and volatile overrides (Path/PATHEXT appended)
|
||||
mergeRegistryEnv(merged, readRegistryEnv(wnr.HK.LM, 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment'))
|
||||
mergeRegistryEnv(merged, readRegistryEnv(wnr.HK.CU, 'Environment'))
|
||||
mergeRegistryEnv(merged, readRegistryEnv(wnr.HK.CU, 'Volatile Environment'))
|
||||
|
||||
// Preserve process-specific vars (e.g. Electron, Node, Angular)
|
||||
// that aren't defined in the registry
|
||||
for (const [key, value] of Object.entries(process.env)) {
|
||||
if (value !== undefined && findKey(merged, key) === undefined) {
|
||||
merged[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
expandWindowsEnv(merged)
|
||||
return merged
|
||||
}
|
||||
|
||||
export function getEnvironment (refreshFromRegistry = false): Record<string, string> {
|
||||
if (process.platform === 'win32' && refreshFromRegistry) {
|
||||
cachedEnvironment = buildWindowsEnvironment()
|
||||
} else {
|
||||
cachedEnvironment ??= normalizeEnv(process.env)
|
||||
}
|
||||
return cachedEnvironment
|
||||
}
|
||||
|
||||
/** Expands `%VAR%` (Windows) / `$VAR` (POSIX) references using the cached environment. */
|
||||
export function substituteEnv (env: Record<string, string>): Record<string, string> {
|
||||
const base = getEnvironment()
|
||||
env = { ...env }
|
||||
const pattern = process.platform === 'win32' ? /%(\w+)%/g : /\$(\w+)\b/g
|
||||
for (const [key, value] of Object.entries(env)) {
|
||||
env[key] = value.toString().replace(pattern, (substring, p1) => {
|
||||
const found = findKey(base, p1)
|
||||
return found !== undefined ? base[found] : ''
|
||||
})
|
||||
}
|
||||
return env
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { Injector } from '@angular/core'
|
||||
import { HostAppService, ConfigService, WIN_BUILD_CONPTY_SUPPORTED, isWindowsBuild, Platform, BootstrapData, BOOTSTRAP_DATA, LogService } from 'tabby-core'
|
||||
import { BaseSession } from 'tabby-terminal'
|
||||
import { SessionOptions, ChildProcess, PTYInterface, PTYProxy } from './api'
|
||||
import { getEnvironment, substituteEnv } from './environment'
|
||||
|
||||
const windowsDirectoryRegex = /([a-zA-Z]:[^\:\[\]\?\"\<\>\|]+)/mi
|
||||
|
||||
@@ -21,21 +22,6 @@ function mergeEnv (...envs) {
|
||||
return result
|
||||
}
|
||||
|
||||
function substituteEnv (env: Record<string, string>) {
|
||||
env = { ...env }
|
||||
const pattern = process.platform === 'win32' ? /%(\w+)%/g : /\$(\w+)\b/g
|
||||
for (const [key, value] of Object.entries(env)) {
|
||||
env[key] = value.toString().replace(pattern, function (substring, p1) {
|
||||
if (process.platform === 'win32') {
|
||||
return Object.entries(process.env).find(x => x[0].toLowerCase() === p1.toLowerCase())?.[1] ?? ''
|
||||
} else {
|
||||
return process.env[p1] ?? ''
|
||||
}
|
||||
})
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
/** @hidden */
|
||||
export class Session extends BaseSession {
|
||||
private pty: PTYProxy|null = null
|
||||
@@ -67,8 +53,12 @@ export class Session extends BaseSession {
|
||||
}
|
||||
|
||||
if (!pty) {
|
||||
const baseEnv = getEnvironment(
|
||||
this.hostApp.platform === Platform.Windows && this.config.store.terminal.windowsRefreshEnvironment,
|
||||
)
|
||||
|
||||
let env = mergeEnv(
|
||||
process.env,
|
||||
baseEnv,
|
||||
{
|
||||
COLORTERM: 'truecolor',
|
||||
TERM: 'xterm-256color',
|
||||
|
||||
@@ -245,3 +245,12 @@ div.mt-4(*ngIf='hostApp.platform === Platform.Windows')
|
||||
[(ngModel)]='config.store.terminal.setComSpec',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title(translate) Refresh environment variables from registry
|
||||
.description(translate) Reload PATH and other variables from the Windows registry for each new session
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.windowsRefreshEnvironment',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user