UNPKG

9.52 kBJavaScriptView Raw
1/*
2 Terminal Kit
3
4 Copyright (c) 2009 - 2020 Cédric Ronvel
5
6 The MIT License (MIT)
7
8 Permission is hereby granted, free of charge, to any person obtaining a copy
9 of this software and associated documentation files (the "Software"), to deal
10 in the Software without restriction, including without limitation the rights
11 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 copies of the Software, and to permit persons to whom the Software is
13 furnished to do so, subject to the following conditions:
14
15 The above copyright notice and this permission notice shall be included in all
16 copies or substantial portions of the Software.
17
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 SOFTWARE.
25*/
26
27"use strict" ;
28
29
30
31const Promise = require( 'seventh' ) ;
32const exec = require( 'child_process' ).exec ;
33
34const termkit = require( './termkit.js' ) ;
35
36
37
38// Try to guess the terminal without any async system call, using TERM and COLORTERM
39// 'unpipe' is used when we will get a TTY even if we haven't one ATM
40exports.guessTerminal = function( unpipe ) {
41 var envVar , version ;
42
43 var isSSH = !! process.env.SSH_CONNECTION ;
44 var isTTY = !! process.stdout.isTTY ;
45
46 if ( ! isTTY && ! unpipe ) {
47 return {
48 isTTY: isTTY ,
49 isSSH: isSSH ,
50 appId: 'none' ,
51 safe: true ,
52 generic: 'none'
53 } ;
54 }
55
56 var t256color = ( process.env.TERM && process.env.TERM.match( /256/ ) ) ||
57 ( process.env.COLORTERM && process.env.COLORTERM.match( /256/ ) ) ;
58 var tTrueColor = process.env.COLORTERM && process.env.COLORTERM.match( /^(truecolor|24bits?)$/ ) ;
59
60 var appId = process.env.COLORTERM && ! tTrueColor ? process.env.COLORTERM : process.env.TERM ;
61
62 // safe is true if we are sure about our guess
63 var safe = !! process.env.TERM && process.env.TERM !== 'xterm' && process.env.TERM !== 'xterm-256color' ;
64
65 var generic = appId ;
66
67 switch ( appId ) {
68 case 'xterm' :
69 case 'xterm-256color' :
70 if ( safe ) { break ; }
71
72 if ( tTrueColor ) {
73 appId = generic = 'xterm-truecolor' ;
74 }
75
76 // Many terminal advertise them as xterm, we will try to guess some of them here,
77 // using environment variable
78 if ( process.env.VTE_VERSION ) {
79 version = parseInt( process.env.VTE_VERSION , 10 ) ;
80
81 if ( version >= 3803 ) {
82 appId = t256color || tTrueColor ? 'gnome-256color' : 'gnome' ;
83 safe = true ;
84 break ;
85 }
86 }
87
88 for ( envVar in process.env ) {
89 if ( envVar.match( /KONSOLE/ ) ) {
90 appId = t256color || tTrueColor ? 'konsole-256color' : 'konsole' ;
91 safe = true ;
92 break ;
93 }
94 }
95
96 break ;
97
98 case 'linux' :
99 case 'aterm' :
100 case 'kuake' :
101 case 'tilda' :
102 case 'terminology' :
103 case 'wterm' :
104 case 'mrxvt' :
105 break ;
106 case 'gnome' :
107 case 'gnome-256color' :
108 case 'gnome-terminal' :
109 case 'gnome-terminal-256color' :
110 case 'terminator' : // it uses gnome terminal lib
111 case 'guake' : // same here
112 appId = t256color || tTrueColor ? 'gnome-256color' : 'gnome' ;
113 break ;
114 case 'konsole' :
115 appId = t256color || tTrueColor ? 'konsole-256color' : 'konsole' ;
116 break ;
117 case 'rxvt' :
118 case 'rxvt-xpm' :
119 case 'rxvt-unicode-256color' :
120 case 'urxvt256c' :
121 case 'urxvt256c-ml' :
122 case 'rxvt-unicode' :
123 case 'urxvt' :
124 case 'urxvt-ml' :
125 if ( process.env.TERM === 'rxvt' ) { appId = 'rxvt-256color' ; }
126 else { appId = t256color || tTrueColor ? 'rxvt-256color' : 'rxvt' ; }
127 break ;
128 case 'xfce' :
129 case 'xfce-terminal' :
130 case 'xfce4-terminal' :
131 appId = 'xfce' ;
132 break ;
133 case 'eterm' :
134 case 'Eterm' :
135 appId = t256color || tTrueColor ? 'eterm-256color' : 'eterm' ;
136 break ;
137 case 'atomic-terminal' :
138 appId = 'atomic-terminal' ;
139 break ;
140 default :
141 if ( ! appId ) { generic = 'unknown' ; }
142 else { generic = appId = generic.toLowerCase() ; }
143 break ;
144 }
145
146 return {
147 isTTY , isSSH , appId , safe , generic: safe ? appId : generic
148 } ;
149} ;
150
151
152
153function getParentProcess( pid ) {
154 var parentPid , appName ;
155
156 return new Promise( ( resolve , reject ) => {
157 exec( 'ps -h -o ppid -p ' + pid , ( error , stdout ) => {
158 if ( error ) { reject( error ) ; return ; }
159
160 parentPid = parseInt( stdout , 10 ) ;
161
162 exec( 'ps -h -o comm -p ' + parentPid , ( error_ , stdout_ ) => {
163 if ( error_ ) { reject( error_ ) ; return ; }
164
165 appName = stdout_.trim() ;
166 resolve( { pid: parentPid , appName } ) ;
167 } ) ;
168 } ) ;
169 } ) ;
170}
171
172
173
174// Work localy, do not work over SSH
175exports.getParentTerminalInfo = async function( callback ) {
176 var loopAgain , error , appName , appId , pid = process.pid ;
177
178 if ( process.env.SSH_CONNECTION ) {
179 error = new Error( 'SSH connection detected, .getParentTerminalInfo() is useless in this context.' ) ;
180 if ( callback ) { callback( error ) ; return ; }
181 throw error ;
182 }
183
184 var t256color = ( process.env.TERM && process.env.TERM.match( /256/ ) ) ||
185 ( process.env.COLORTERM && process.env.COLORTERM.match( /256/ ) ) ;
186 var tTrueColor = process.env.COLORTERM && process.env.COLORTERM.match( /^(truecolor|24bits?)$/ ) ;
187
188 try {
189 loopAgain = true ;
190
191 while ( loopAgain ) {
192 ( { appName , pid } = await getParentProcess( pid ) ) ;
193
194 //console.log( 'found:' , appName , pid ) ;
195
196 // Do NOT skip the first, there are case where the terminal may run directly node.js without any shell in between
197 //if ( ++ loop <= 1 ) { asyncCallback( undefined , true ) ; return ; }
198
199 loopAgain = false ;
200
201 switch ( appName ) {
202 case 'linux' :
203 case 'xterm' :
204 case 'konsole' :
205 case 'gnome-terminal' :
206 case 'aterm' :
207 case 'guake' :
208 case 'kuake' :
209 case 'tilda' :
210 case 'terminology' :
211 case 'wterm' :
212 case 'mrxvt' :
213 appId = t256color || tTrueColor ? appName + '-256color' : appName ;
214 break ;
215 case 'atomic-terminal' :
216 appId = appName ;
217 break ;
218 case 'login' :
219 appName = 'linux' ;
220 appId = appName ;
221 break ;
222 // Use terminator as gnome-terminal, since it uses the gnome-terminal renderer
223 case 'terminator' :
224 appId = t256color || tTrueColor ? 'gnome-256color' : 'gnome' ;
225 break ;
226 // Use rxvt as xterm-256color
227 case 'rxvt' :
228 case 'urxvt256c' :
229 case 'urxvt256c-ml' :
230 appId = 'rxvt-256color' ;
231 break ;
232 // Use rxvt as xterm
233 case 'urxvt' :
234 case 'urxvt-ml' :
235 appId = 'rxvt' ;
236 break ;
237 // xfce4-terminal
238 case 'xfce4-terminal' :
239 appId = 'xfce' ;
240 break ;
241 case 'gnome-terminal-' :
242 appName = 'gnome-terminal' ;
243 appId = t256color || tTrueColor ? 'gnome-256color' : 'gnome' ;
244 break ;
245 case 'Eterm' :
246 case 'eterm' :
247 appName = 'Eterm' ;
248 appId = t256color || tTrueColor ? 'eterm-256color' : 'eterm' ;
249 break ;
250 default :
251 if ( appName.match( /gnome-terminal/ ) ) {
252 appName = 'gnome-terminal' ;
253 appId = t256color || tTrueColor ? 'gnome-256color' : 'gnome' ;
254 break ;
255 }
256
257 if ( ! pid || pid === 1 ) { throw new Error( 'Terminal not found' ) ; }
258 loopAgain = true ;
259 }
260 }
261 }
262 catch ( error_ ) {
263 if ( callback ) { callback( error_ ) ; return ; }
264 throw error_ ;
265 }
266
267 var result = {
268 appId: appId ,
269 appName: appName ,
270 pid: pid ,
271 safe: true
272 } ;
273
274 if ( callback ) { callback( undefined , result ) ; return ; }
275
276 return result ;
277} ;
278
279
280
281// Work locally, do not work over SSH
282exports.getDetectedTerminal = async function( callback ) {
283 var terminal , info ,
284 guessed = termkit.guessTerminal() ;
285
286 if ( guessed.safe || guessed.isSSH ) {
287 // If we have a good guess, use it now
288 terminal = termkit.createTerminal( {
289 stdin: process.stdin ,
290 stdout: process.stdout ,
291 stderr: process.stderr ,
292 generic: ( process.env.TERM && process.env.TERM.toLowerCase() ) || 'unknown' ,
293 appId: guessed.safe ? guessed.appId : undefined ,
294 // appName: guessed.safe ? guessed.appName : undefined ,
295 isTTY: guessed.isTTY ,
296 isSSH: guessed.isSSH ,
297 processSigwinch: true ,
298 preferProcessSigwinch: !! termkit.globalConfig.preferProcessSigwinch
299 } ) ;
300
301 if ( callback ) { callback( undefined , terminal ) ; }
302 return terminal ;
303 }
304
305 try {
306 info = await termkit.getParentTerminalInfo() ;
307
308 terminal = termkit.createTerminal( {
309 stdin: process.stdin ,
310 stdout: process.stdout ,
311 stderr: process.stderr ,
312 generic: ( process.env.TERM && process.env.TERM.toLowerCase() ) || 'unknown' ,
313 appId: info.appId ,
314 appName: info.appName ,
315 isTTY: guessed.isTTY ,
316 isSSH: guessed.isSSH ,
317 pid: info.pid ,
318 processSigwinch: true ,
319 preferProcessSigwinch: !! termkit.globalConfig.preferProcessSigwinch
320 } ) ;
321 }
322 catch ( error ) {
323 // Do not issue error
324 terminal = termkit.createTerminal( {
325 stdin: process.stdin ,
326 stdout: process.stdout ,
327 stderr: process.stderr ,
328 generic: ( process.env.TERM && process.env.TERM.toLowerCase() ) || 'unknown' ,
329 appId: guessed.safe ? guessed.appId : undefined ,
330 // appName: guessed.safe ? guessed.appName : undefined ,
331 isTTY: guessed.isTTY ,
332 isSSH: guessed.isSSH ,
333 processSigwinch: true ,
334 preferProcessSigwinch: !! termkit.globalConfig.preferProcessSigwinch
335 } ) ;
336 }
337
338 if ( callback ) { callback( undefined , terminal ) ; }
339 return terminal ;
340} ;
341