@@ -8,7 +8,7 @@ import { Interceptor } from '..';
88import { HtkConfig } from '../../config' ;
99import { logError , addBreadcrumb } from '../../error-tracking' ;
1010import { isErrorLike } from '../../util/error' ;
11- import { canAccess , commandExists } from '../../util/fs' ;
11+ import { canAccess , commandExists , getRealPath , resolveCommandPath } from '../../util/fs' ;
1212import { spawnToResult } from '../../util/process-management' ;
1313
1414import { getTerminalEnvVars } from './terminal-env-overrides' ;
@@ -21,6 +21,9 @@ interface SpawnArgs {
2121 args ?: string [ ] ;
2222 options ?: SpawnOptions ;
2323 skipStartupScripts ?: true ;
24+ envVars ?: {
25+ [ key : string ] : string ;
26+ } ;
2427}
2528
2629const getTerminalCommand = _ . memoize ( async ( ) : Promise < SpawnArgs | null > => {
@@ -32,7 +35,10 @@ const getTerminalCommand = _.memoize(async (): Promise<SpawnArgs | null> => {
3235 else result = Promise . resolve ( null ) ;
3336
3437 result . then ( ( terminal ) => {
35- if ( terminal ) addBreadcrumb ( 'Found terminal' , { data : { terminal } } ) ;
38+ if ( terminal ) {
39+ console . log ( `Detected terminal command: ${ terminal . command } ` ) ;
40+ addBreadcrumb ( 'Found terminal' , { data : { terminal } } ) ;
41+ }
3642 else logError ( 'No terminal could be detected' ) ;
3743 } ) ;
3844
@@ -80,6 +86,7 @@ const getLinuxTerminalCommand = async (): Promise<SpawnArgs | null> => {
8086 const defaultTerminal = gSettingsTerminalKey && gSettingsTerminalKey . getValue ( ) ;
8187 if ( defaultTerminal && await commandExists ( defaultTerminal ) ) {
8288 if ( defaultTerminal . includes ( 'gnome-terminal' ) ) return getGnomeTerminalCommand ( defaultTerminal ) ;
89+ if ( defaultTerminal . includes ( 'kgx' ) ) return getKgxCommand ( defaultTerminal ) ;
8390 if ( defaultTerminal . includes ( 'konsole' ) ) return getKonsoleTerminalCommand ( defaultTerminal ) ;
8491 if ( defaultTerminal . includes ( 'xfce4-terminal' ) ) return getXfceTerminalCommand ( defaultTerminal ) ;
8592 if ( defaultTerminal . includes ( 'x-terminal-emulator' ) ) return getXTerminalCommand ( defaultTerminal ) ;
@@ -91,6 +98,7 @@ const getLinuxTerminalCommand = async (): Promise<SpawnArgs | null> => {
9198 // If a specific term like this is installed, it's probably the preferred one
9299 if ( await commandExists ( 'konsole' ) ) return getKonsoleTerminalCommand ( ) ;
93100 if ( await commandExists ( 'xfce4-terminal' ) ) return getXfceTerminalCommand ( ) ;
101+ if ( await commandExists ( 'kgx' ) ) return getKgxCommand ( ) ;
94102 if ( await commandExists ( 'kitty' ) ) return { command : 'kitty' } ;
95103 if ( await commandExists ( 'urxvt' ) ) return { command : 'urxvt' } ;
96104 if ( await commandExists ( 'rxvt' ) ) return { command : 'rxvt' } ;
@@ -103,36 +111,8 @@ const getLinuxTerminalCommand = async (): Promise<SpawnArgs | null> => {
103111 return null ;
104112} ;
105113
106- const getXTerminalCommand = async ( command = 'x-terminal-emulator' ) : Promise < SpawnArgs > => {
107- // x-terminal-emulator is a wrapper/symlink to the terminal of choice.
108- // Unfortunately, we need to pass specific args that aren't supported by all terminals (to ensure
109- // terminals run in the foreground), and the Debian XTE wrapper at least doesn't pass through
110- // any of the args we want to use. To fix this, we parse --help to try and detect the underlying
111- // terminal, and run it directly with the args we need.
112- try {
113- // Run the command with -h to get some output we can use to infer the terminal itself.
114- // --version would be nice, but the debian wrapper ignores it. --help isn't supported by xterm.
115- const { stdout } = await spawnToResult ( command , [ '-h' ] ) ;
116- const helpOutput = stdout . toLowerCase ( ) . replace ( / [ ^ \w \d ] + / g, ' ' ) ;
117-
118- if ( helpOutput . includes ( 'gnome terminal' ) && await commandExists ( 'gnome-terminal' ) ) {
119- return getGnomeTerminalCommand ( ) ;
120- } else if ( helpOutput . includes ( 'xfce4 terminal' ) && await commandExists ( 'xfce4-terminal' ) ) {
121- return getXfceTerminalCommand ( ) ;
122- } else if ( helpOutput . includes ( 'konsole' ) && await commandExists ( 'konsole' ) ) {
123- return getKonsoleTerminalCommand ( ) ;
124- }
125- } catch ( e ) {
126- if ( isErrorLike ( e ) && e . message ?. includes ( 'rxvt' ) ) {
127- // Bizarrely, rxvt -h prints help but always returns a non-zero exit code.
128- // Doesn't need any special arguments anyway though, so just ignore it
129- } else {
130- logError ( e ) ;
131- }
132- }
133-
134- // If there's an error, or we just don't recognize the console, give up & run it directly
135- return { command } ;
114+ const getKgxCommand = async ( command = 'kgx' ) : Promise < SpawnArgs > => {
115+ return { command, envVars : { DBUS_SESSION_BUS_ADDRESS : '' } } ;
136116} ;
137117
138118const getKonsoleTerminalCommand = async ( command = 'konsole' ) : Promise < SpawnArgs > => {
@@ -185,6 +165,59 @@ const getXfceTerminalCommand = async (command = 'xfce4-terminal'): Promise<Spawn
185165 return { command, args : extraArgs } ;
186166} ;
187167
168+ const X_TERMINAL_MATCHERS = [
169+ { helpString : 'gnome terminal' , command : 'gnome-terminal' , commandBuilder : getGnomeTerminalCommand } ,
170+ { helpString : 'xfce4 terminal' , command : 'xfce4-terminal' , commandBuilder : getXfceTerminalCommand } ,
171+ { helpString : 'konsole' , command : 'konsole' , commandBuilder : getKonsoleTerminalCommand } ,
172+ { helpString : 'kgx' , command : 'kgx' , commandBuilder : getKgxCommand }
173+ ] as const ;
174+
175+ const getXTerminalCommand = async ( command = 'x-terminal-emulator' ) : Promise < SpawnArgs > => {
176+ // x-terminal-emulator is a wrapper/symlink to the terminal of choice.
177+ // Unfortunately, we need to pass specific args that aren't supported by all terminals (to ensure
178+ // terminals run in the foreground), and the Debian XTE wrapper at least doesn't pass through
179+ // any of the args we want to use. To fix this, we parse --help to try and detect the underlying
180+ // terminal, and run it directly with the args we need.
181+ try {
182+ // In parallel, get the command's help output, and its real resolved path on disk:
183+ const [ helpOutput , commandPath ] = await Promise . all ( [
184+ // Run the command with -h to get some output we can use to infer the terminal itself.
185+ // --version would be nice, but the debian wrapper ignores it. --help isn't supported by xterm.
186+ spawnToResult ( command , [ '-h' ] )
187+ . then ( ( { stdout } ) => stdout . toLowerCase ( ) . replace ( / [ ^ \w \d ] + / g, ' ' ) ) ,
188+ // We find the command in PATH, and then follow any symbolic links, and see what the real
189+ // underlying path is:
190+ resolveCommandPath ( command )
191+ . then ( ( path ) => { if ( path ) return getRealPath ( path ) } )
192+ ] ) ;
193+
194+ for ( let terminalPattern of X_TERMINAL_MATCHERS ) {
195+ // We match the help output or command path for known strings:
196+ if (
197+ ! helpOutput . includes ( terminalPattern . helpString ) &&
198+ ! commandPath ?. includes ( command )
199+ ) continue ;
200+
201+ // If found, we use the terminal _if_ it exists globally with our expected command.
202+ // We can't use 'command' directly, because some terms (gnome-terminal, Debian generally)
203+ // use weird wrappers that don't behave properly.
204+ if ( await commandExists ( terminalPattern . command ) ) {
205+ return terminalPattern . commandBuilder ( ) ;
206+ }
207+ }
208+ } catch ( e ) {
209+ if ( isErrorLike ( e ) && e . message ?. includes ( 'rxvt' ) ) {
210+ // Bizarrely, rxvt -h prints help but always returns a non-zero exit code.
211+ // Doesn't need any special arguments anyway though, so just ignore it
212+ } else {
213+ logError ( e ) ;
214+ }
215+ }
216+
217+ // If there's an error, or we just don't recognize the console, give up & run it directly:
218+ return { command } ;
219+ } ;
220+
188221const terminals : _ . Dictionary < ChildProcess [ ] | undefined > = { }
189222
190223export class FreshTerminalInterceptor implements Interceptor {
@@ -206,7 +239,7 @@ export class FreshTerminalInterceptor implements Interceptor {
206239 const terminalSpawnArgs = await getTerminalCommand ( ) ;
207240 if ( ! terminalSpawnArgs ) throw new Error ( 'Could not find a suitable terminal' ) ;
208241
209- const { command, args, options, skipStartupScripts } = terminalSpawnArgs ;
242+ const { command, args, options, skipStartupScripts, envVars } = terminalSpawnArgs ;
210243
211244 // Our PATH override below may not work, e.g. because OSX's path_helper always prepends
212245 // the real paths over the top, and git-bash ignore env var paths overrides. To fix this,
@@ -229,6 +262,7 @@ export class FreshTerminalInterceptor implements Interceptor {
229262 env : {
230263 ...currentEnv ,
231264 ...getTerminalEnvVars ( proxyPort , this . config . https , currentEnv ) ,
265+ ...envVars ,
232266 } ,
233267 cwd : currentEnv . HOME || currentEnv . USERPROFILE
234268 } )
@@ -277,4 +311,4 @@ export class FreshTerminalInterceptor implements Interceptor {
277311
278312 await resetShellStartupScripts ( ) ;
279313 }
280- }
314+ }
0 commit comments