@@ -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' ;
@@ -108,38 +108,6 @@ const getLinuxTerminalCommand = async (): Promise<SpawnArgs | null> => {
108108 return null ;
109109} ;
110110
111- const getXTerminalCommand = async ( command = 'x-terminal-emulator' ) : Promise < SpawnArgs > => {
112- // x-terminal-emulator is a wrapper/symlink to the terminal of choice.
113- // Unfortunately, we need to pass specific args that aren't supported by all terminals (to ensure
114- // terminals run in the foreground), and the Debian XTE wrapper at least doesn't pass through
115- // any of the args we want to use. To fix this, we parse --help to try and detect the underlying
116- // terminal, and run it directly with the args we need.
117- try {
118- // Run the command with -h to get some output we can use to infer the terminal itself.
119- // --version would be nice, but the debian wrapper ignores it. --help isn't supported by xterm.
120- const { stdout } = await spawnToResult ( command , [ '-h' ] ) ;
121- const helpOutput = stdout . toLowerCase ( ) . replace ( / [ ^ \w \d ] + / g, ' ' ) ;
122-
123- if ( helpOutput . includes ( 'gnome terminal' ) && await commandExists ( 'gnome-terminal' ) ) {
124- return getGnomeTerminalCommand ( ) ;
125- } else if ( helpOutput . includes ( 'xfce4 terminal' ) && await commandExists ( 'xfce4-terminal' ) ) {
126- return getXfceTerminalCommand ( ) ;
127- } else if ( helpOutput . includes ( 'konsole' ) && await commandExists ( 'konsole' ) ) {
128- return getKonsoleTerminalCommand ( ) ;
129- }
130- } catch ( e ) {
131- if ( isErrorLike ( e ) && e . message ?. includes ( 'rxvt' ) ) {
132- // Bizarrely, rxvt -h prints help but always returns a non-zero exit code.
133- // Doesn't need any special arguments anyway though, so just ignore it
134- } else {
135- logError ( e ) ;
136- }
137- }
138-
139- // If there's an error, or we just don't recognize the console, give up & run it directly
140- return { command } ;
141- } ;
142-
143111const getKgxCommand = async ( command = 'kgx' ) : Promise < SpawnArgs > => {
144112 return { command, envVars : { DBUS_SESSION_BUS_ADDRESS : '' } } ;
145113} ;
@@ -194,6 +162,59 @@ const getXfceTerminalCommand = async (command = 'xfce4-terminal'): Promise<Spawn
194162 return { command, args : extraArgs } ;
195163} ;
196164
165+ const X_TERMINAL_MATCHERS = [
166+ { helpString : 'gnome terminal' , command : 'gnome-terminal' , commandBuilder : getGnomeTerminalCommand } ,
167+ { helpString : 'xfce4 terminal' , command : 'xfce4-terminal' , commandBuilder : getXfceTerminalCommand } ,
168+ { helpString : 'konsole' , command : 'konsole' , commandBuilder : getKonsoleTerminalCommand } ,
169+ { helpString : 'kgx' , command : 'kgx' , commandBuilder : getKgxCommand }
170+ ] as const ;
171+
172+ const getXTerminalCommand = async ( command = 'x-terminal-emulator' ) : Promise < SpawnArgs > => {
173+ // x-terminal-emulator is a wrapper/symlink to the terminal of choice.
174+ // Unfortunately, we need to pass specific args that aren't supported by all terminals (to ensure
175+ // terminals run in the foreground), and the Debian XTE wrapper at least doesn't pass through
176+ // any of the args we want to use. To fix this, we parse --help to try and detect the underlying
177+ // terminal, and run it directly with the args we need.
178+ try {
179+ // In parallel, get the command's help output, and its real resolved path on disk:
180+ const [ helpOutput , commandPath ] = await Promise . all ( [
181+ // Run the command with -h to get some output we can use to infer the terminal itself.
182+ // --version would be nice, but the debian wrapper ignores it. --help isn't supported by xterm.
183+ spawnToResult ( command , [ '-h' ] )
184+ . then ( ( { stdout } ) => stdout . toLowerCase ( ) . replace ( / [ ^ \w \d ] + / g, ' ' ) ) ,
185+ // We find the command in PATH, and then follow any symbolic links, and see what the real
186+ // underlying path is:
187+ resolveCommandPath ( command )
188+ . then ( ( path ) => { if ( path ) return getRealPath ( path ) } )
189+ ] ) ;
190+
191+ for ( let terminalPattern of X_TERMINAL_MATCHERS ) {
192+ // We match the help output or command path for known strings:
193+ if (
194+ ! helpOutput . includes ( terminalPattern . helpString ) &&
195+ ! commandPath ?. includes ( command )
196+ ) continue ;
197+
198+ // If found, we use the terminal _if_ it exists globally with our expected command.
199+ // We can't use 'command' directly, because some terms (gnome-terminal, Debian generally)
200+ // use weird wrappers that don't behave properly.
201+ if ( await commandExists ( terminalPattern . command ) ) {
202+ return terminalPattern . commandBuilder ( ) ;
203+ }
204+ }
205+ } catch ( e ) {
206+ if ( isErrorLike ( e ) && e . message ?. includes ( 'rxvt' ) ) {
207+ // Bizarrely, rxvt -h prints help but always returns a non-zero exit code.
208+ // Doesn't need any special arguments anyway though, so just ignore it
209+ } else {
210+ logError ( e ) ;
211+ }
212+ }
213+
214+ // If there's an error, or we just don't recognize the console, give up & run it directly:
215+ return { command } ;
216+ } ;
217+
197218const terminals : _ . Dictionary < ChildProcess [ ] | undefined > = { }
198219
199220export class FreshTerminalInterceptor implements Interceptor {
0 commit comments