mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-09-13 10:52:08 +02:00
75d2c668d0
Moves module option structs out of global config and removes dynamic allocation, using static buffers and size assertions for safer, simpler management. Refactors module initialization and destruction to work with isolated option structs, eliminating the need for the previous modules container and related code. Unifies module option header usage and updates detection logic to leverage statically-sized option buffers, improving encapsulation and maintainability. Simplifies module function signatures and reduces coupling between modules and config. Enhances future extensibility and reliability by enforcing max size constraints and removing unnecessary indirections.
452 lines
16 KiB
C
452 lines
16 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, "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") ||
|
|
ffStrbufContainS(&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") ||
|
|
ffStrbufEqualS(&result->processName, "proot") ||
|
|
ffStrbufEqualS(&result->processName, "script") ||
|
|
ffStrbufContainS(&result->processName, "debug") ||
|
|
ffStrbufContainS(&result->processName, "command-not-") ||
|
|
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, "sudo") ||
|
|
ffStrbufEqualS(&result->processName, "su") ||
|
|
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, "clifm") || // https://github.com/leo-arch/clifm/issues/289
|
|
ffStrbufEqualS(&result->processName, "chezmoi") || // #762
|
|
ffStrbufEqualS(&result->processName, "proot") ||
|
|
ffStrbufEqualS(&result->processName, "script") ||
|
|
ffStrbufEqualS(&result->processName, "init") ||
|
|
ffStrbufEqualS(&result->processName, "systemd") ||
|
|
#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)
|
|
{
|
|
if (!ffStrbufStartsWithS(&result->processName, "login") &&
|
|
!ffStrbufEqualS(&result->processName, "(login)") &&
|
|
|
|
#ifdef __APPLE__
|
|
!ffStrbufEqualS(&result->processName, "launchd") &&
|
|
#else
|
|
!ffStrbufEqualS(&result->processName, "systemd") &&
|
|
!ffStrbufEqualS(&result->processName, "init") &&
|
|
!ffStrbufEqualS(&result->processName, "(init)") &&
|
|
!ffStrbufEqualS(&result->processName, "SessionLeader") && // #750
|
|
#endif
|
|
|
|
!ffStrbufEqualS(&result->processName, "0")
|
|
) return;
|
|
|
|
ffStrbufClear(&result->processName);
|
|
ffStrbufClear(&result->exe);
|
|
result->exeName = result->exe.chars;
|
|
ffStrbufClear(&result->exePath);
|
|
result->pid = result->ppid = 0;
|
|
}
|
|
|
|
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__) || defined(__NetBSD__)
|
|
//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* exePath, FFstrbuf* version);
|
|
|
|
bool fftsGetTerminalVersion(FFstrbuf* processName, FFstrbuf* exe, FFstrbuf* version);
|
|
|
|
static void setShellInfoDetails(FFShellResult* result)
|
|
{
|
|
ffStrbufClear(&result->version);
|
|
fftsGetShellVersion(&result->exe, result->exeName, &result->exePath, &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, '.') && ffStrbufContainS(&result->processName, "-wrap"))
|
|
{
|
|
// For NixOS. Ref: #510 and https://github.com/NixOS/nixpkgs/pull/249428
|
|
// We use processName when detecting version and font, overriding it for simplification
|
|
ffStrbufSubstrBeforeLastC(&result->processName, '-');
|
|
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");
|
|
else if(ffStrbufEqualS(&result->processName, "sshd") || ffStrbufStartsWithS(&result->processName, "sshd-"))
|
|
ffStrbufInitCopy(&result->prettyName, &result->tty);
|
|
|
|
#if defined(__ANDROID__)
|
|
|
|
else if(ffStrbufEqualS(&result->processName, "com.termux"))
|
|
ffStrbufInitStatic(&result->prettyName, "Termux");
|
|
|
|
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__)
|
|
|
|
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"); // $TERM_PROGRAM, for terminal font detection
|
|
ffStrbufInitStatic(&result->prettyName, "Apple Terminal");
|
|
}
|
|
else if(ffStrbufEqualS(&result->processName, "Apple_Terminal"))
|
|
ffStrbufInitStatic(&result->prettyName, "Apple Terminal");
|
|
else if(ffStrbufEndsWithS(&result->exePath, "Warp.app/Contents/MacOS/stable"))
|
|
{
|
|
ffStrbufSetStatic(&result->processName, "WarpTerminal"); // $TERM_PROGRAM, for terminal font detection
|
|
ffStrbufInitStatic(&result->prettyName, "Warp");
|
|
}
|
|
else if(ffStrbufEqualS(&result->processName, "WarpTerminal"))
|
|
ffStrbufInitStatic(&result->prettyName, "Warp");
|
|
|
|
#elif defined(__HAIKU__)
|
|
|
|
else if(ffStrbufEqualS(&result->processName, "Terminal"))
|
|
ffStrbufInitStatic(&result->prettyName, "Haiku Terminal");
|
|
|
|
#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();
|
|
|
|
const char* ignoreParent = getenv("FFTS_IGNORE_PARENT");
|
|
if (ignoreParent && ffStrEquals(ignoreParent, "1"))
|
|
{
|
|
FF_STRBUF_AUTO_DESTROY _ = ffStrbufCreate();
|
|
ffProcessGetBasicInfoLinux(ppid, &_, &ppid, NULL);
|
|
}
|
|
|
|
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;
|
|
}
|