Files
fastfetch/src/detection/terminalshell/terminalshell_linux.c
T
2024-07-01 23:56:19 +08:00

420 lines
15 KiB
C

#include "terminalshell.h"
#include "common/io/io.h"
#include "common/parsing.h"
#include "common/processing.h"
#include "common/thread.h"
#include "util/stringUtils.h"
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
static void setExeName(FFstrbuf* exe, const char** exeName)
{
assert(exe->length > 0);
uint32_t lastSlashIndex = ffStrbufLastIndexC(exe, '/');
if(lastSlashIndex < exe->length)
*exeName = exe->chars + lastSlashIndex + 1;
}
static pid_t getShellInfo(FFShellResult* result, pid_t pid)
{
pid_t ppid = 0;
int32_t tty = -1;
const char* userShellName = NULL;
{
uint32_t index = ffStrbufLastIndexC(&instance.state.platform.userShell, '/');
if (index == instance.state.platform.userShell.length)
userShellName = instance.state.platform.userShell.chars;
else
userShellName = instance.state.platform.userShell.chars + index + 1;
}
while (ffProcessGetBasicInfoLinux(pid, &result->processName, &ppid, &tty) == NULL)
{
if (!ffStrbufEqualS(&result->processName, userShellName))
{
//Common programs that are between terminal and own process, but are not the shell
if(
// tty < 0 || //A shell should connect to a tty
ffStrbufEqualS(&result->processName, "sh") || //This prevents us from detecting things like pipes and redirects, i hope nobody uses plain `sh` as shell
ffStrbufEqualS(&result->processName, "sudo") ||
ffStrbufEqualS(&result->processName, "su") ||
ffStrbufEqualS(&result->processName, "strace") ||
ffStrbufEqualS(&result->processName, "sshd") ||
ffStrbufEqualS(&result->processName, "gdb") ||
ffStrbufEqualS(&result->processName, "lldb") ||
ffStrbufEqualS(&result->processName, "lldb-mi") ||
ffStrbufEqualS(&result->processName, "login") ||
ffStrbufEqualS(&result->processName, "ltrace") ||
ffStrbufEqualS(&result->processName, "perf") ||
ffStrbufEqualS(&result->processName, "guake-wrapped") ||
ffStrbufEqualS(&result->processName, "time") ||
ffStrbufEqualS(&result->processName, "hyfetch") || //when hyfetch uses fastfetch as backend
ffStrbufEqualS(&result->processName, "clifm") || //https://github.com/leo-arch/clifm/issues/289
ffStrbufEqualS(&result->processName, "valgrind") ||
ffStrbufEqualS(&result->processName, "fastfetch") || //994
ffStrbufEqualS(&result->processName, "flashfetch") ||
ffStrbufContainS(&result->processName, "debug") ||
ffStrbufContainS(&result->processName, "not-found") ||
ffStrbufEndsWithS(&result->processName, ".sh")
)
{
pid = ppid;
ffStrbufClear(&result->processName);
continue;
}
}
result->pid = (uint32_t) pid;
result->ppid = (uint32_t) ppid;
result->tty = tty;
ffProcessGetInfoLinux(pid, &result->processName, &result->exe, &result->exeName, &result->exePath);
break;
}
return ppid;
}
static pid_t getTerminalInfo(FFTerminalResult* result, pid_t pid)
{
pid_t ppid = 0;
while (ffProcessGetBasicInfoLinux(pid, &result->processName, &ppid, NULL) == NULL)
{
//Known shells
if (
ffStrbufEqualS(&result->processName, "sh") ||
ffStrbufEqualS(&result->processName, "ash") ||
ffStrbufEqualS(&result->processName, "bash") ||
ffStrbufEqualS(&result->processName, "zsh") ||
ffStrbufEqualS(&result->processName, "ksh") ||
ffStrbufEqualS(&result->processName, "mksh") ||
ffStrbufEqualS(&result->processName, "oksh") ||
ffStrbufEqualS(&result->processName, "csh") ||
ffStrbufEqualS(&result->processName, "tcsh") ||
ffStrbufEqualS(&result->processName, "fish") ||
ffStrbufEqualS(&result->processName, "dash") ||
ffStrbufEqualS(&result->processName, "pwsh") ||
ffStrbufEqualS(&result->processName, "nu") ||
ffStrbufEqualS(&result->processName, "git-shell") ||
ffStrbufEqualS(&result->processName, "elvish") ||
ffStrbufEqualS(&result->processName, "oil.ovm") ||
ffStrbufEqualS(&result->processName, "xonsh") || // works in Linux but not in macOS because kernel returns `Python` in this case
ffStrbufEqualS(&result->processName, "login") ||
ffStrbufEqualS(&result->processName, "sshd") ||
ffStrbufEqualS(&result->processName, "clifm") || // https://github.com/leo-arch/clifm/issues/289
ffStrbufEqualS(&result->processName, "chezmoi") || // #762
#ifdef __linux__
ffStrbufStartsWithS(&result->processName, "flatpak-") || // #707
#endif
ffStrbufEndsWithS(&result->processName, ".sh")
)
{
pid = ppid;
ffStrbufClear(&result->processName);
continue;
}
#ifdef __APPLE__
// https://github.com/fastfetch-cli/fastfetch/discussions/501
const char* pLeft = strstr(result->processName.chars, " (");
if (pLeft)
{
pLeft += 2;
const char* pRight = strstr(pLeft, "term)");
if (pRight && pRight[5] == '\0')
{
for (; pLeft < pRight; ++pLeft)
if (*pLeft < 'a' || *pLeft > 'z')
break;
if (pLeft == pRight && ffProcessGetBasicInfoLinux(ppid, &result->processName, &ppid, NULL) != NULL)
return 0;
}
}
#endif
result->pid = (uint32_t) pid;
result->ppid = (uint32_t) ppid;
ffProcessGetInfoLinux(pid, &result->processName, &result->exe, &result->exeName, &result->exePath);
break;
}
return ppid;
}
static bool getTerminalInfoByPidEnv(FFTerminalResult* result, const char* pidEnv)
{
const char* envStr = getenv(pidEnv);
if (envStr == NULL)
return false;
pid_t pid = (pid_t) strtol(envStr, NULL, 10);
result->pid = (uint32_t) pid;
if (ffProcessGetBasicInfoLinux(pid, &result->processName, (pid_t*) &result->ppid, NULL) == NULL)
{
ffProcessGetInfoLinux(pid, &result->processName, &result->exe, &result->exeName, &result->exePath);
return true;
}
return false;
}
static void getTerminalFromEnv(FFTerminalResult* result)
{
if(
result->processName.length > 0 &&
!ffStrbufStartsWithS(&result->processName, "login") &&
!ffStrbufEqualS(&result->processName, "(login)") &&
#ifdef __APPLE__
!ffStrbufEqualS(&result->processName, "launchd") &&
!ffStrbufEqualS(&result->processName, "stable") && //for WarpTerminal
#else
!ffStrbufEqualS(&result->processName, "systemd") &&
!ffStrbufEqualS(&result->processName, "init") &&
!ffStrbufEqualS(&result->processName, "(init)") &&
!ffStrbufEqualS(&result->processName, "SessionLeader") && // #750
#endif
!ffStrbufEqualS(&result->processName, "0")
) return;
const char* term = NULL;
//SSH
if(
getenv("SSH_TTY") != NULL
)
term = getenv("SSH_TTY");
else if(
getenv("KITTY_PID") != NULL ||
getenv("KITTY_INSTALLATION_DIR") != NULL
)
{
if (getTerminalInfoByPidEnv(result, "KITTY_PID"))
return;
term = "kitty";
}
#ifdef __linux__ // WSL
//Windows Terminal
else if(
getenv("WT_SESSION") != NULL ||
getenv("WT_PROFILE_ID") != NULL
) term = "Windows Terminal";
//ConEmu
else if(
getenv("ConEmuPID") != NULL
) term = "ConEmu";
#endif
//Alacritty
else if(
getenv("ALACRITTY_SOCKET") != NULL ||
getenv("ALACRITTY_LOG") != NULL ||
getenv("ALACRITTY_WINDOW_ID") != NULL
) term = "Alacritty";
#ifdef __ANDROID__
//Termux
else if(
getenv("TERMUX_VERSION") != NULL ||
getenv("TERMUX_MAIN_PACKAGE_FORMAT") != NULL
)
{
if (getTerminalInfoByPidEnv(result, "TERMUX_APP__PID"))
return;
term = "com.termux";
}
#endif
#if defined(__linux__) || defined(__FreeBSD__)
//Konsole
else if(
getenv("KONSOLE_VERSION") != NULL
) term = "konsole";
else if(
getenv("GNOME_TERMINAL_SCREEN") != NULL ||
getenv("GNOME_TERMINAL_SERVICE") != NULL
) term = "gnome-terminal";
#endif
//MacOS, mintty
else if(getenv("TERM_PROGRAM") != NULL)
term = getenv("TERM_PROGRAM");
else if(getenv("LC_TERMINAL") != NULL)
term = getenv("LC_TERMINAL");
//Normal Terminal
else
{
term = getenv("TERM");
//TTY
if(!ffStrSet(term) || ffStrEquals(term, "linux"))
term = ttyname(STDIN_FILENO);
}
if(ffStrSet(term))
{
ffStrbufSetS(&result->processName, term);
ffStrbufSetS(&result->exe, term);
setExeName(&result->exe, &result->exeName);
}
}
static void getUserShellFromEnv(FFShellResult* result)
{
//If shell detection via processes failed
if(result->processName.length == 0 && instance.state.platform.userShell.length > 0)
{
ffStrbufSet(&result->exe, &instance.state.platform.userShell);
setExeName(&result->exe, &result->exeName);
ffStrbufAppendS(&result->processName, result->exeName);
}
}
bool fftsGetShellVersion(FFstrbuf* exe, const char* exeName, FFstrbuf* version);
bool fftsGetTerminalVersion(FFstrbuf* processName, FFstrbuf* exe, FFstrbuf* version);
static void setShellInfoDetails(FFShellResult* result)
{
ffStrbufClear(&result->version);
fftsGetShellVersion(&result->exe, result->exeName, &result->version);
if(ffStrbufEqualS(&result->processName, "pwsh"))
ffStrbufInitStatic(&result->prettyName, "PowerShell");
else if(ffStrbufEqualS(&result->processName, "nu"))
ffStrbufInitStatic(&result->prettyName, "nushell");
else if(ffStrbufEqualS(&result->processName, "oil.ovm"))
ffStrbufInitStatic(&result->prettyName, "Oils");
else
{
// https://github.com/fastfetch-cli/fastfetch/discussions/280#discussioncomment-3831734
ffStrbufInitS(&result->prettyName, result->exeName);
}
}
static void setTerminalInfoDetails(FFTerminalResult* result)
{
if(ffStrbufStartsWithC(&result->processName, '.') && ffStrbufEndsWithS(&result->processName, "-wrapped"))
{
// For NixOS. Ref: #510 and https://github.com/NixOS/nixpkgs/pull/249428
// We use processName when detecting version and font, overriding it for simplification
ffStrbufSubstrBefore(&result->processName, result->processName.length - (uint32_t) strlen("-wrapped"));
ffStrbufSubstrAfter(&result->processName, 0);
}
if(ffStrbufEqualS(&result->processName, "wezterm-gui"))
ffStrbufInitStatic(&result->prettyName, "WezTerm");
else if(ffStrbufStartsWithS(&result->processName, "tmux:"))
ffStrbufInitStatic(&result->prettyName, "tmux");
else if(ffStrbufStartsWithS(&result->processName, "screen-"))
ffStrbufInitStatic(&result->prettyName, "screen");
#if defined(__ANDROID__)
else if(ffStrbufEqualS(&result->processName, "com.termux"))
ffStrbufInitStatic(&result->prettyName, "Termux");
#elif defined(__linux__) || defined(__FreeBSD__)
else if(ffStrbufStartsWithS(&result->processName, "gnome-terminal"))
ffStrbufInitStatic(&result->prettyName, "GNOME Terminal");
else if(ffStrbufStartsWithS(&result->processName, "kgx"))
ffStrbufInitStatic(&result->prettyName, "GNOME Console");
else if(ffStrbufEqualS(&result->processName, "urxvt") ||
ffStrbufEqualS(&result->processName, "urxvtd") ||
ffStrbufEqualS(&result->processName, "rxvt")
)
ffStrbufInitStatic(&result->prettyName, "rxvt-unicode");
else if(ffStrbufStartsWithS(&result->processName, "ptyxis-agent"))
ffStrbufInitStatic(&result->prettyName, "Ptyxis");
#elif defined(__APPLE__)
else if(ffStrbufEqualS(&result->processName, "iTerm.app") || ffStrbufStartsWithS(&result->processName, "iTermServer-"))
ffStrbufInitStatic(&result->prettyName, "iTerm");
else if(ffStrbufEndsWithS(&result->exePath, "Terminal.app/Contents/MacOS/Terminal"))
{
ffStrbufSetStatic(&result->processName, "Apple_Terminal"); // for terminal font detection
ffStrbufInitStatic(&result->prettyName, "Apple Terminal");
}
else if(ffStrbufEqualS(&result->processName, "Apple_Terminal"))
ffStrbufInitStatic(&result->prettyName, "Apple Terminal");
else if(ffStrbufEqualS(&result->processName, "WarpTerminal"))
ffStrbufInitStatic(&result->prettyName, "Warp");
#endif
else if(strncmp(result->exeName, result->processName.chars, result->processName.length) == 0) // if exeName starts with processName, print it. Otherwise print processName
ffStrbufInitS(&result->prettyName, result->exeName);
else
ffStrbufInitCopy(&result->prettyName, &result->processName);
fftsGetTerminalVersion(&result->processName, &result->exe, &result->version);
}
#if defined(MAXPATH)
#define FF_EXE_PATH_LEN MAXPATH
#elif defined(PATH_MAX)
#define FF_EXE_PATH_LEN PATH_MAX
#else
#define FF_EXE_PATH_LEN 260
#endif
const FFShellResult* ffDetectShell()
{
static FFShellResult result;
static bool init = false;
if(init)
return &result;
init = true;
ffStrbufInit(&result.processName);
ffStrbufInitA(&result.exe, FF_EXE_PATH_LEN);
result.exeName = result.exe.chars;
ffStrbufInit(&result.exePath);
ffStrbufInit(&result.version);
result.pid = 0;
result.ppid = 0;
result.tty = -1;
pid_t ppid = getppid();
ppid = getShellInfo(&result, ppid);
getUserShellFromEnv(&result);
setShellInfoDetails(&result);
return &result;
}
const FFTerminalResult* ffDetectTerminal()
{
static FFTerminalResult result;
static bool init = false;
if(init)
return &result;
init = true;
ffStrbufInit(&result.processName);
ffStrbufInitA(&result.exe, FF_EXE_PATH_LEN);
result.exeName = result.exe.chars;
ffStrbufInit(&result.exePath);
ffStrbufInit(&result.version);
ffStrbufInitS(&result.tty, ttyname(STDOUT_FILENO));
result.pid = 0;
result.ppid = 0;
pid_t ppid = (pid_t) ffDetectShell()->ppid;
if (ppid)
ppid = getTerminalInfo(&result, ppid);
getTerminalFromEnv(&result);
setTerminalInfoDetails(&result);
return &result;
}