2021-06-14 23:22:14 +02:00
#include "fastfetch.h"
2022-07-25 11:48:48 +02:00
#include "common/io.h"
2022-07-25 13:59:19 +02:00
#include "common/parsing.h"
2022-07-25 14:45:41 +02:00
#include "common/processing.h"
2022-10-12 14:13:20 +08:00
#include "common/thread.h"
2022-10-08 16:04:55 +08:00
#include "terminalshell.h"
2021-06-14 23:22:14 +02:00
#include <ctype.h>
#include <string.h>
2022-06-18 14:25:31 +02:00
#include <stdlib.h>
2021-06-14 23:22:14 +02:00
#include <unistd.h>
2022-10-11 14:59:09 +08:00
#ifdef __APPLE__
#include <libproc.h>
2022-10-26 19:49:25 +08:00
#elif defined(__FreeBSD__)
#include <sys/types.h>
#include <sys/user.h>
#include <sys/sysctl.h>
2022-10-11 14:59:09 +08:00
#endif
2021-06-14 23:22:14 +02:00
static void setExeName ( FFstrbuf * exe , const char ** exeName )
{
2022-10-08 22:16:45 +08:00
assert ( exe -> length > 0 );
2021-06-14 23:22:14 +02:00
uint32_t lastSlashIndex = ffStrbufLastIndexC ( exe , '/' );
if ( lastSlashIndex < exe -> length )
* exeName = exe -> chars + lastSlashIndex + 1 ;
}
2022-10-11 14:59:09 +08:00
static void getProcessInformation ( pid_t pid , FFstrbuf * processName , FFstrbuf * exe , const char ** exeName )
2021-06-14 23:22:14 +02:00
{
2022-10-08 22:16:45 +08:00
assert ( processName -> length > 0 );
2022-10-11 14:59:09 +08:00
ffStrbufClear ( exe );
2022-10-08 22:16:45 +08:00
2022-10-14 16:39:48 +08:00
#ifdef __linux__
2021-06-14 23:22:14 +02:00
2022-10-11 14:59:09 +08:00
char cmdlineFilePath [ 64 ];
snprintf ( cmdlineFilePath , sizeof ( cmdlineFilePath ), "/proc/%d/cmdline" , ( int ) pid );
if ( ffAppendFileBuffer ( cmdlineFilePath , exe ))
2022-10-08 22:16:45 +08:00
{
ffStrbufSubstrBeforeFirstC ( exe , '\0' ); //Trim the arguments
ffStrbufTrimLeft ( exe , '-' ); //Happens in TTY
}
2021-06-14 23:22:14 +02:00
2022-10-11 14:59:09 +08:00
#elif defined(__APPLE__)
int length = proc_pidpath (( int ) pid , exe -> chars , exe -> allocated );
if ( length > 0 )
exe -> length = ( uint32_t ) length ;
#else
2022-10-26 19:49:25 +08:00
size_t size = exe -> allocated ;
if ( ! sysctl (
( int []){ CTL_KERN , KERN_PROC , KERN_PROC_PATHNAME , pid }, 4 ,
exe -> chars , & size ,
NULL , 0
))
exe -> length = ( uint32_t ) size ;
2022-10-11 14:59:09 +08:00
#endif
2021-06-14 23:22:14 +02:00
if ( exe -> length == 0 )
ffStrbufSet ( exe , processName );
setExeName ( exe , exeName );
}
2022-10-11 14:59:09 +08:00
static const char * getProcessNameAndPpid ( pid_t pid , char * name , pid_t * ppid )
2021-06-14 23:22:14 +02:00
{
2022-10-11 14:59:09 +08:00
const char * error = NULL ;
2021-06-14 23:22:14 +02:00
2022-10-14 16:39:48 +08:00
#ifdef __linux__
2021-06-14 23:22:14 +02:00
2022-10-11 14:59:09 +08:00
char statFilePath [ 64 ];
snprintf ( statFilePath , sizeof ( statFilePath ), "/proc/%d/stat" , ( int ) pid );
FILE * stat = fopen ( statFilePath , "r" );
2021-06-14 23:22:14 +02:00
if ( stat == NULL )
2022-10-11 14:59:09 +08:00
return "fopen(statFilePath, \" r \" ) failed" ;
* ppid = 0 ;
if (
fscanf ( stat , "%*s (%255[^)]) %*c %d" , name , ppid ) != 2 || //stat (comm) state ppid
! ffStrSet ( name ) ||
* ppid == 0
)
error = "fscanf(stat) failed" ;
fclose ( stat );
2021-06-14 23:22:14 +02:00
2022-10-11 14:59:09 +08:00
#elif defined(__APPLE__)
struct proc_bsdshortinfo proc ;
if ( proc_pidinfo ( pid , PROC_PIDT_SHORTBSDINFO , 0 , & proc , PROC_PIDT_SHORTBSDINFO_SIZE ) <= 0 )
error = "proc_pidinfo(pid) failed" ;
else
{
* ppid = ( pid_t ) proc . pbsi_ppid ;
strncpy ( name , proc . pbsi_comm , 16 ); //trancated to 16 chars
}
#else
2022-10-26 19:49:25 +08:00
struct kinfo_proc proc ;
size_t size = sizeof ( proc );
if ( sysctl (
( int []){ CTL_KERN , KERN_PROC , KERN_PROC_PID , pid }, 4 ,
& proc , & size ,
NULL , 0
))
error = "sysctl(KERN_PROC_PID) failed" ;
else
{
* ppid = ( pid_t ) proc . ki_ppid ;
strncpy ( name , proc . ki_comm , COMMLEN );
}
2022-10-11 14:59:09 +08:00
#endif
return error ;
}
static void getTerminalShell ( FFTerminalShellResult * result , pid_t pid )
{
2021-06-14 23:22:14 +02:00
char name [ 256 ];
name [ 0 ] = '\0' ;
2022-10-11 14:59:09 +08:00
pid_t ppid = 0 ;
2021-06-14 23:22:14 +02:00
2022-10-11 14:59:09 +08:00
if ( getProcessNameAndPpid ( pid , name , & ppid ))
2021-06-15 09:21:38 +02:00
return ;
2021-06-14 23:22:14 +02:00
//Common programs that are between terminal and own process, but are not the shell
if (
2022-12-12 13:55:19 +01:00
strcasecmp ( name , "sh" ) == 0 || //This prevents us from detecting things like pipes and redirects, i hope nobody uses plain `sh` as shell
2022-07-08 11:24:11 +02:00
strcasecmp ( name , "sudo" ) == 0 ||
strcasecmp ( name , "su" ) == 0 ||
strcasecmp ( name , "doas" ) == 0 ||
strcasecmp ( name , "strace" ) == 0 ||
strcasecmp ( name , "sshd" ) == 0 ||
strcasecmp ( name , "gdb" ) == 0 ||
2022-10-11 14:59:09 +08:00
strcasecmp ( name , "lldb" ) == 0 ||
strcasecmp ( name , "guake-wrapped" ) == 0 ||
strcasestr ( name , "debug" ) != NULL
2021-06-14 23:22:14 +02:00
) {
getTerminalShell ( result , ppid );
return ;
}
//Known shells
if (
strcasecmp ( name , "bash" ) == 0 ||
strcasecmp ( name , "zsh" ) == 0 ||
strcasecmp ( name , "ksh" ) == 0 ||
2022-10-27 16:30:49 +08:00
strcasecmp ( name , "csh" ) == 0 ||
strcasecmp ( name , "tcsh" ) == 0 ||
2021-06-14 23:22:14 +02:00
strcasecmp ( name , "fish" ) == 0 ||
strcasecmp ( name , "dash" ) == 0 ||
2022-10-08 16:04:55 +08:00
strcasecmp ( name , "pwsh" ) == 0 ||
2022-10-14 16:28:03 +08:00
strcasecmp ( name , "nu" ) == 0 ||
2021-06-14 23:22:14 +02:00
strcasecmp ( name , "git-shell" ) == 0
) {
2022-10-14 18:31:17 +08:00
if ( result -> shellProcessName . length == 0 )
{
ffStrbufSetS ( & result -> shellProcessName , name );
getProcessInformation ( pid , & result -> shellProcessName , & result -> shellExe , & result -> shellExeName );
}
2021-06-14 23:22:14 +02:00
getTerminalShell ( result , ppid );
return ;
}
2022-10-08 22:16:45 +08:00
ffStrbufSetS ( & result -> terminalProcessName , name );
2021-06-14 23:22:14 +02:00
getProcessInformation ( pid , & result -> terminalProcessName , & result -> terminalExe , & result -> terminalExeName );
}
static void getTerminalFromEnv ( FFTerminalShellResult * result )
{
if (
2021-06-15 09:21:38 +02:00
result -> terminalProcessName . length > 0 &&
! ffStrbufStartsWithIgnCaseS ( & result -> terminalProcessName , "login" ) &&
2022-11-17 18:14:14 +08:00
! ffStrbufIgnCaseEqualS ( & result -> terminalProcessName , "(login)" ) &&
#ifdef __APPLE__
! ffStrbufIgnCaseEqualS ( & result -> terminalProcessName , "launchd" ) &&
! ffStrbufIgnCaseEqualS ( & result -> terminalProcessName , "stable" ) && //for WarpTerminal
#else
! ffStrbufIgnCaseEqualS ( & result -> terminalProcessName , "systemd" ) &&
! ffStrbufIgnCaseEqualS ( & result -> terminalProcessName , "init" ) &&
! ffStrbufIgnCaseEqualS ( & result -> terminalProcessName , "(init)" ) &&
#endif
2021-06-15 09:21:38 +02:00
ffStrbufIgnCaseCompS ( & result -> terminalProcessName , "0" ) != 0
2021-06-14 23:22:14 +02:00
) return ;
2022-10-14 18:38:18 +08:00
const char * term = NULL ;
2021-10-17 01:05:45 +08:00
//SSH
if ( getenv ( "SSH_CONNECTION" ) != NULL )
term = getenv ( "SSH_TTY" );
2022-11-17 18:14:14 +08:00
#ifdef __linux__
2021-10-17 01:05:45 +08:00
//Windows Terminal
2022-06-18 14:51:10 +02:00
if ( ! ffStrSet ( term ) && (
getenv ( "WT_SESSION" ) != NULL ||
getenv ( "WT_PROFILE_ID" ) != NULL
)) term = "Windows Terminal" ;
2022-11-17 18:14:14 +08:00
#endif
2021-10-17 01:05:45 +08:00
2022-09-21 15:15:36 +08:00
//Alacritty
if ( ! ffStrSet ( term ) && (
getenv ( "ALACRITTY_SOCKET" ) != NULL ||
getenv ( "ALACRITTY_LOG" ) != NULL ||
getenv ( "ALACRITTY_WINDOW_ID" ) != NULL
)) term = "Alacritty" ;
2022-10-14 18:51:04 +08:00
#ifdef __ANDROID__
2022-06-18 00:45:28 +02:00
//Termux
if ( ! ffStrSet ( term ) && (
getenv ( "TERMUX_VERSION" ) != NULL ||
getenv ( "TERMUX_MAIN_PACKAGE_FORMAT" ) != NULL ||
getenv ( "TMUX_TMPDIR" ) != NULL
)) term = "Termux" ;
2022-10-14 18:51:04 +08:00
#endif
2022-06-18 00:45:28 +02:00
2022-10-14 18:51:04 +08:00
#ifdef __linux__
2022-09-20 11:16:08 +02:00
//Konsole
if ( ! ffStrSet ( term ) && (
getenv ( "KONSOLE_VERSION" ) != NULL
)) term = "konsole" ;
2022-10-14 18:51:04 +08:00
#endif
2022-09-20 11:16:08 +02:00
2022-10-02 17:52:09 +08:00
//MacOS, mintty
2022-09-04 09:02:11 -07:00
if ( ! ffStrSet ( term ))
term = getenv ( "TERM_PROGRAM" );
2022-10-14 18:51:04 +08:00
#ifdef __linux__
2022-10-02 17:52:09 +08:00
if ( ! ffStrSet ( term ))
{
2022-10-14 18:51:04 +08:00
//We are in WSL but not in Windows Terminal
2022-11-17 22:35:40 +08:00
if ( getenv ( "WSL_DISTRO" ) != NULL || getenv ( "WSL_INTEROP" ) != NULL )
term = "conhost" ;
2022-10-02 17:52:09 +08:00
}
2022-10-14 18:51:04 +08:00
#endif
2022-09-24 00:57:32 +08:00
2021-10-17 01:05:45 +08:00
//Normal Terminal
2022-01-07 18:00:38 +01:00
if ( ! ffStrSet ( term ))
2021-10-17 01:05:45 +08:00
term = getenv ( "TERM" );
2021-06-14 23:22:14 +02:00
//TTY
2022-01-07 18:00:38 +01:00
if ( ! ffStrSet ( term ) || strcasecmp ( term , "linux" ) == 0 )
2021-06-14 23:22:14 +02:00
term = ttyname ( STDIN_FILENO );
2022-10-08 22:16:45 +08:00
if ( ffStrSet ( term ))
{
ffStrbufSetS ( & result -> terminalProcessName , term );
ffStrbufSetS ( & result -> terminalExe , term );
setExeName ( & result -> terminalExe , & result -> terminalExeName );
}
2021-06-14 23:22:14 +02:00
}
2022-10-27 16:30:49 +08:00
static void getUserShellFromEnv ( const FFinstance * instance , FFTerminalShellResult * result )
2021-06-14 23:22:14 +02:00
{
2022-10-27 16:30:49 +08:00
if ( instance -> state . passwd -> pw_shell [ 0 ] != '\0' )
ffStrbufAppendS ( & result -> userShellExe , instance -> state . passwd -> pw_shell );
else
ffStrbufAppendS ( & result -> userShellExe , getenv ( "SHELL" ));
2022-10-08 22:16:45 +08:00
if ( result -> userShellExe . length == 0 )
return ;
2022-10-27 16:30:49 +08:00
2021-06-14 23:22:14 +02:00
setExeName ( & result -> userShellExe , & result -> userShellExeName );
//If shell detection via processes failed
if ( result -> shellProcessName . length == 0 && result -> userShellExe . length > 0 )
{
ffStrbufAppendS ( & result -> shellProcessName , result -> userShellExeName );
ffStrbufSet ( & result -> shellExe , & result -> userShellExe );
setExeName ( & result -> shellExe , & result -> shellExeName );
}
}
static void getShellVersionGeneric ( FFstrbuf * exe , const char * exeName , FFstrbuf * version )
{
FFstrbuf command ;
ffStrbufInit ( & command );
ffStrbufAppendS ( & command , "printf \" %s \" \" $" );
ffStrbufAppendTransformS ( & command , exeName , toupper );
ffStrbufAppendS ( & command , "_VERSION \" " );
ffProcessAppendStdOut ( version , ( char * const []) {
"env" ,
"-i" ,
exe -> chars ,
"-c" ,
command . chars ,
NULL
});
ffStrbufSubstrBeforeFirstC ( version , '(' );
ffStrbufRemoveStrings ( version , 2 , "-release" , "release" );
ffStrbufDestroy ( & command );
}
2022-10-14 16:28:03 +08:00
bool fftsGetShellVersion ( FFstrbuf * exe , const char * exeName , FFstrbuf * version );
2021-06-14 23:22:14 +02:00
static void getShellVersion ( FFstrbuf * exe , const char * exeName , FFstrbuf * version )
{
2022-10-08 22:16:45 +08:00
ffStrbufClear ( version );
2022-10-14 16:28:03 +08:00
if ( ! fftsGetShellVersion ( exe , exeName , version ))
2021-06-14 23:22:14 +02:00
getShellVersionGeneric ( exe , exeName , version );
}
2023-01-10 19:24:59 +08:00
bool fftsGetTerminalVersion ( FFstrbuf * processName , FFstrbuf * exe , FFstrbuf * version );
2022-10-14 16:28:03 +08:00
const FFTerminalShellResult * ffDetectTerminalShell ( const FFinstance * instance )
2021-06-14 23:22:14 +02:00
{
2021-10-19 19:34:55 +02:00
FF_UNUSED ( instance );
2021-06-14 23:22:14 +02:00
2022-10-12 14:13:20 +08:00
static FFThreadMutex mutex = FF_THREAD_MUTEX_INITIALIZER ;
2021-06-14 23:22:14 +02:00
static FFTerminalShellResult result ;
static bool init = false ;
2022-10-12 14:13:20 +08:00
ffThreadMutexLock ( & mutex );
2021-06-14 23:22:14 +02:00
if ( init )
{
2022-10-12 14:13:20 +08:00
ffThreadMutexUnlock ( & mutex );
2021-06-14 23:22:14 +02:00
return & result ;
}
init = true ;
2022-10-27 16:29:52 +08:00
#ifdef __APPLE__
const uint32_t exePathLen = PROC_PIDPATHINFO_MAXSIZE ;
#elif defined(MAXPATH)
const uint32_t exePathLen = MAXPATH ;
#else
const uint32_t exePathLen = 260 ;
#endif
2021-06-14 23:22:14 +02:00
ffStrbufInit ( & result . shellProcessName );
2022-10-27 16:29:52 +08:00
ffStrbufInitA ( & result . shellExe , exePathLen );
2021-06-14 23:22:14 +02:00
result . shellExeName = result . shellExe . chars ;
ffStrbufInit ( & result . shellVersion );
ffStrbufInit ( & result . terminalProcessName );
2022-10-27 16:29:52 +08:00
ffStrbufInitA ( & result . terminalExe , exePathLen );
2021-06-14 23:22:14 +02:00
result . terminalExeName = result . terminalExe . chars ;
ffStrbufInit ( & result . userShellExe );
result . userShellExeName = result . userShellExe . chars ;
ffStrbufInit ( & result . userShellVersion );
2022-10-11 14:59:09 +08:00
getTerminalShell ( & result , getppid ());
2021-06-14 23:22:14 +02:00
getTerminalFromEnv ( & result );
2022-10-27 16:30:49 +08:00
getUserShellFromEnv ( instance , & result );
2021-06-14 23:22:14 +02:00
getShellVersion ( & result . shellExe , result . shellExeName , & result . shellVersion );
if ( strcasecmp ( result . shellExeName , result . userShellExeName ) != 0 )
getShellVersion ( & result . userShellExe , result . userShellExeName , & result . userShellVersion );
else
ffStrbufSet ( & result . userShellVersion , & result . shellVersion );
2022-10-14 19:02:51 +08:00
if ( ffStrbufEqualS ( & result . shellProcessName , "pwsh" ))
ffStrbufInitS ( & result . shellPrettyName , "PowerShell" );
else if ( ffStrbufEqualS ( & result . shellProcessName , "nu" ))
ffStrbufInitS ( & result . shellPrettyName , "nushell" );
else
{
// https://github.com/LinusDierheimer/fastfetch/discussions/280#discussioncomment-3831734
ffStrbufInitS ( & result . shellPrettyName , result . shellExeName );
}
2022-10-09 16:51:53 +08:00
2022-10-14 19:02:51 +08:00
if ( ffStrbufEqualS ( & result . terminalProcessName , "iTerm.app" ))
ffStrbufInitS ( & result . terminalPrettyName , "iTerm" );
else if ( ffStrbufEqualS ( & result . terminalProcessName , "Apple_Terminal" ))
ffStrbufInitS ( & result . terminalPrettyName , "Apple Terminal" );
2022-11-17 18:14:14 +08:00
else if ( ffStrbufEqualS ( & result . terminalProcessName , "WarpTerminal" ))
ffStrbufInitS ( & result . terminalPrettyName , "Warp" );
2022-10-14 19:02:51 +08:00
else if ( strncmp ( result . terminalExeName , result . terminalProcessName . chars , result . terminalProcessName . length ) == 0 ) // if exeName starts with processName, print it. Otherwise print processName
2022-10-09 16:51:53 +08:00
ffStrbufInitS ( & result . terminalPrettyName , result . terminalExeName );
else
ffStrbufInitCopy ( & result . terminalPrettyName , & result . terminalProcessName );
2023-01-10 19:24:59 +08:00
ffStrbufInit ( & result . terminalVersion );
fftsGetTerminalVersion ( & result . terminalProcessName , & result . terminalExe , & result . terminalVersion );
2022-10-12 14:13:20 +08:00
ffThreadMutexUnlock ( & mutex );
2021-06-14 23:22:14 +02:00
return & result ;
}