@@ -8,7 +8,7 @@ import { Interceptor } from '..';
8
8
import { HtkConfig } from '../../config' ;
9
9
import { logError , addBreadcrumb } from '../../error-tracking' ;
10
10
import { isErrorLike } from '../../util/error' ;
11
- import { canAccess , commandExists } from '../../util/fs' ;
11
+ import { canAccess , commandExists , getRealPath , resolveCommandPath } from '../../util/fs' ;
12
12
import { spawnToResult } from '../../util/process-management' ;
13
13
14
14
import { getTerminalEnvVars } from './terminal-env-overrides' ;
@@ -21,6 +21,9 @@ interface SpawnArgs {
21
21
args ?: string [ ] ;
22
22
options ?: SpawnOptions ;
23
23
skipStartupScripts ?: true ;
24
+ envVars ?: {
25
+ [ key : string ] : string ;
26
+ } ;
24
27
}
25
28
26
29
const getTerminalCommand = _ . memoize ( async ( ) : Promise < SpawnArgs | null > => {
@@ -32,7 +35,10 @@ const getTerminalCommand = _.memoize(async (): Promise<SpawnArgs | null> => {
32
35
else result = Promise . resolve ( null ) ;
33
36
34
37
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
+ }
36
42
else logError ( 'No terminal could be detected' ) ;
37
43
} ) ;
38
44
@@ -80,6 +86,7 @@ const getLinuxTerminalCommand = async (): Promise<SpawnArgs | null> => {
80
86
const defaultTerminal = gSettingsTerminalKey && gSettingsTerminalKey . getValue ( ) ;
81
87
if ( defaultTerminal && await commandExists ( defaultTerminal ) ) {
82
88
if ( defaultTerminal . includes ( 'gnome-terminal' ) ) return getGnomeTerminalCommand ( defaultTerminal ) ;
89
+ if ( defaultTerminal . includes ( 'kgx' ) ) return getKgxCommand ( defaultTerminal ) ;
83
90
if ( defaultTerminal . includes ( 'konsole' ) ) return getKonsoleTerminalCommand ( defaultTerminal ) ;
84
91
if ( defaultTerminal . includes ( 'xfce4-terminal' ) ) return getXfceTerminalCommand ( defaultTerminal ) ;
85
92
if ( defaultTerminal . includes ( 'x-terminal-emulator' ) ) return getXTerminalCommand ( defaultTerminal ) ;
@@ -91,6 +98,7 @@ const getLinuxTerminalCommand = async (): Promise<SpawnArgs | null> => {
91
98
// If a specific term like this is installed, it's probably the preferred one
92
99
if ( await commandExists ( 'konsole' ) ) return getKonsoleTerminalCommand ( ) ;
93
100
if ( await commandExists ( 'xfce4-terminal' ) ) return getXfceTerminalCommand ( ) ;
101
+ if ( await commandExists ( 'kgx' ) ) return getKgxCommand ( ) ;
94
102
if ( await commandExists ( 'kitty' ) ) return { command : 'kitty' } ;
95
103
if ( await commandExists ( 'urxvt' ) ) return { command : 'urxvt' } ;
96
104
if ( await commandExists ( 'rxvt' ) ) return { command : 'rxvt' } ;
@@ -103,36 +111,8 @@ const getLinuxTerminalCommand = async (): Promise<SpawnArgs | null> => {
103
111
return null ;
104
112
} ;
105
113
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 : '' } } ;
136
116
} ;
137
117
138
118
const getKonsoleTerminalCommand = async ( command = 'konsole' ) : Promise < SpawnArgs > => {
@@ -185,6 +165,59 @@ const getXfceTerminalCommand = async (command = 'xfce4-terminal'): Promise<Spawn
185
165
return { command, args : extraArgs } ;
186
166
} ;
187
167
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
+
188
221
const terminals : _ . Dictionary < ChildProcess [ ] | undefined > = { }
189
222
190
223
export class FreshTerminalInterceptor implements Interceptor {
@@ -206,7 +239,7 @@ export class FreshTerminalInterceptor implements Interceptor {
206
239
const terminalSpawnArgs = await getTerminalCommand ( ) ;
207
240
if ( ! terminalSpawnArgs ) throw new Error ( 'Could not find a suitable terminal' ) ;
208
241
209
- const { command, args, options, skipStartupScripts } = terminalSpawnArgs ;
242
+ const { command, args, options, skipStartupScripts, envVars } = terminalSpawnArgs ;
210
243
211
244
// Our PATH override below may not work, e.g. because OSX's path_helper always prepends
212
245
// 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 {
229
262
env : {
230
263
...currentEnv ,
231
264
...getTerminalEnvVars ( proxyPort , this . config . https , currentEnv ) ,
265
+ ...envVars ,
232
266
} ,
233
267
cwd : currentEnv . HOME || currentEnv . USERPROFILE
234
268
} )
@@ -277,4 +311,4 @@ export class FreshTerminalInterceptor implements Interceptor {
277
311
278
312
await resetShellStartupScripts ( ) ;
279
313
}
280
- }
314
+ }
0 commit comments