mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-11-10 08:50:10 +01:00
378 lines
15 KiB
C
378 lines
15 KiB
C
#include "terminalshell.h"
|
|
#include "common/io.h"
|
|
#include "common/processing.h"
|
|
#include "common/thread.h"
|
|
#include "common/mallocHelper.h"
|
|
#include "common/windows/registry.h"
|
|
#include "common/windows/unicode.h"
|
|
#include "common/windows/version.h"
|
|
#include "common/windows/nt.h"
|
|
#include "common/stringUtils.h"
|
|
|
|
#include <stdalign.h>
|
|
#include <windows.h>
|
|
#include <wchar.h>
|
|
#include <tlhelp32.h>
|
|
#include <ntstatus.h>
|
|
#include <winternl.h>
|
|
#include <shlobj.h>
|
|
|
|
bool fftsGetShellVersion(FFstrbuf* exe, const char* exeName, FFstrbuf* version);
|
|
|
|
static uint32_t getShellInfo(FFShellResult* result, uint32_t pid) {
|
|
uint32_t ppid = 0;
|
|
bool gui = false;
|
|
|
|
while (pid != 0 && ffProcessGetInfoWindows(pid, &ppid, &result->processName, &result->exe, &result->exeName, &result->exePath, &gui)) {
|
|
ffStrbufSet(&result->prettyName, &result->processName);
|
|
if (ffStrbufEndsWithIgnCaseS(&result->prettyName, ".exe")) {
|
|
ffStrbufSubstrBefore(&result->prettyName, result->prettyName.length - 4);
|
|
}
|
|
|
|
// Common programs that are between terminal and own process, but are not the shell
|
|
if (
|
|
!gui && (ffStrbufIgnCaseEqualS(&result->prettyName, "sudo") || ffStrbufIgnCaseEqualS(&result->prettyName, "su") || ffStrbufIgnCaseEqualS(&result->prettyName, "gdb") || ffStrbufIgnCaseEqualS(&result->prettyName, "lldb") || ffStrbufIgnCaseEqualS(&result->prettyName, "lldb-dap") || ffStrbufIgnCaseEqualS(&result->prettyName, "python") || // python on windows generates shim executables
|
|
ffStrbufIgnCaseEqualS(&result->prettyName, "fastfetch") || // scoop warps the real binaries with a "shim" exe
|
|
ffStrbufIgnCaseEqualS(&result->prettyName, "flashfetch") || ffStrbufContainIgnCaseS(&result->prettyName, "debug") || ffStrbufContainIgnCaseS(&result->prettyName, "time") || ffStrbufStartsWithIgnCaseS(&result->prettyName, "ConEmuC") // https://github.com/fastfetch-cli/fastfetch/issues/488#issuecomment-1619982014
|
|
)) {
|
|
ffStrbufClear(&result->processName);
|
|
ffStrbufClear(&result->prettyName);
|
|
ffStrbufClear(&result->exe);
|
|
result->exeName = NULL;
|
|
pid = ppid;
|
|
continue;
|
|
}
|
|
|
|
result->pid = pid;
|
|
|
|
if (gui) {
|
|
// Started without shell
|
|
// In this case, terminal process will be created by fastfetch itself.
|
|
ppid = 0;
|
|
if (ffStrbufIgnCaseEqualS(&result->prettyName, "explorer")) {
|
|
ffStrbufSetS(&result->prettyName, "Windows Explorer");
|
|
}
|
|
} else {
|
|
result->ppid = ppid;
|
|
}
|
|
|
|
break;
|
|
}
|
|
return ppid;
|
|
}
|
|
|
|
static void setShellInfoDetails(FFShellResult* result) {
|
|
if (ffStrbufIgnCaseEqualS(&result->prettyName, "pwsh")) {
|
|
ffStrbufSetS(&result->prettyName, "PowerShell");
|
|
} else if (ffStrbufIgnCaseEqualS(&result->prettyName, "powershell")) {
|
|
ffStrbufSetS(&result->prettyName, "Windows PowerShell");
|
|
} else if (ffStrbufIgnCaseEqualS(&result->prettyName, "powershell_ise")) {
|
|
ffStrbufSetS(&result->prettyName, "Windows PowerShell ISE");
|
|
} else if (ffStrbufIgnCaseEqualS(&result->prettyName, "cmd")) {
|
|
ffStrbufSetS(&result->prettyName, "CMD");
|
|
|
|
if (instance.config.general.detectVersion) {
|
|
FF_AUTO_CLOSE_FD HANDLE snapshot = NULL;
|
|
while (!(snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, result->pid)) && GetLastError() == ERROR_BAD_LENGTH) {}
|
|
|
|
if (snapshot) {
|
|
MODULEENTRY32W module;
|
|
module.dwSize = sizeof(module);
|
|
for (BOOL success = Module32FirstW(snapshot, &module); success; success = Module32NextW(snapshot, &module)) {
|
|
if (wcsncmp(module.szModule, L"clink_dll_", strlen("clink_dll_")) == 0) {
|
|
FF_STRBUF_AUTO_DESTROY clinkVersion = ffStrbufCreate();
|
|
if (ffGetFileVersion(module.szExePath, NULL, &clinkVersion)) {
|
|
ffStrbufAppendF(&result->prettyName, " (with Clink %s)", clinkVersion.chars);
|
|
} else {
|
|
ffStrbufAppendS(&result->prettyName, " (with Clink)");
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if (ffStrbufIgnCaseEqualS(&result->prettyName, "nu")) {
|
|
ffStrbufSetS(&result->prettyName, "nushell");
|
|
} else if (ffStrbufIgnCaseEqualS(&result->prettyName, "explorer")) {
|
|
ffStrbufSetS(&result->prettyName, "Windows Explorer");
|
|
}
|
|
}
|
|
|
|
static bool getTerminalFromEnv(FFTerminalResult* result) {
|
|
if (
|
|
result->processName.length > 0 &&
|
|
ffStrbufIgnCaseCompS(&result->processName, "explorer") != 0) {
|
|
return false;
|
|
}
|
|
|
|
const char* term = getenv("ConEmuPID");
|
|
|
|
if (term) {
|
|
// ConEmu
|
|
uint32_t pid = (uint32_t) strtoul(term, NULL, 10);
|
|
result->pid = pid;
|
|
if (ffProcessGetInfoWindows(pid, NULL, &result->processName, &result->exe, &result->exeName, &result->exePath, NULL)) {
|
|
ffStrbufSet(&result->prettyName, &result->processName);
|
|
if (ffStrbufEndsWithIgnCaseS(&result->prettyName, ".exe")) {
|
|
ffStrbufSubstrBefore(&result->prettyName, result->prettyName.length - 4);
|
|
}
|
|
return true;
|
|
} else {
|
|
term = "ConEmu";
|
|
}
|
|
}
|
|
|
|
// SSH
|
|
if (getenv("SSH_TTY") != NULL) {
|
|
term = getenv("SSH_TTY");
|
|
}
|
|
|
|
// Windows Terminal
|
|
if (!term && (getenv("WT_SESSION") != NULL || getenv("WT_PROFILE_ID") != NULL)) {
|
|
term = "WindowsTerminal";
|
|
}
|
|
|
|
// Alacritty
|
|
if (!term && (getenv("ALACRITTY_SOCKET") != NULL || getenv("ALACRITTY_LOG") != NULL || getenv("ALACRITTY_WINDOW_ID") != NULL)) {
|
|
term = "Alacritty";
|
|
}
|
|
|
|
if (!term) {
|
|
term = getenv("TERM_PROGRAM");
|
|
}
|
|
|
|
// Normal Terminal
|
|
if (!term) {
|
|
term = getenv("TERM");
|
|
}
|
|
|
|
if (term) {
|
|
ffStrbufSetS(&result->processName, term);
|
|
ffStrbufSetS(&result->prettyName, term);
|
|
ffStrbufSetS(&result->exe, term);
|
|
result->exeName = "";
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool detectDefaultTerminal(FFTerminalResult* result) {
|
|
wchar_t regPath[128] = L"SOFTWARE\\Classes\\PackagedCom\\ClassIndex\\";
|
|
wchar_t* uuid = regPath + strlen("SOFTWARE\\Classes\\PackagedCom\\ClassIndex\\");
|
|
FF_AUTO_CLOSE_FD HANDLE hkcu = NULL;
|
|
if (ffRegOpenKeyForRead(HKEY_CURRENT_USER, L"Console\\%%Startup", &hkcu, NULL) &&
|
|
ffRegReadData(hkcu, L"DelegationTerminal", &(FFArgBuffer) {
|
|
.data = uuid,
|
|
.length = (uint32_t) (sizeof(regPath) - (size_t) (uuid - regPath) * sizeof(wchar_t)),
|
|
},
|
|
NULL)) {
|
|
if (wcscmp(uuid, L"{00000000-0000-0000-0000-000000000000}") == 0 || // Let Windows decide
|
|
wcscmp(uuid, L"{B23D10C0-E52E-411E-9D5B-C09FDF709C7D}") == 0) // Conhost
|
|
{
|
|
goto conhost;
|
|
}
|
|
|
|
FF_AUTO_CLOSE_FD HANDLE hklm = NULL;
|
|
if (ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, regPath, &hklm, NULL)) {
|
|
FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate();
|
|
if (ffRegGetSubKey(hklm, 0, &path, NULL)) {
|
|
if (ffStrbufStartsWithS(&path, "Microsoft.WindowsTerminal")) {
|
|
ffStrbufSetS(&result->processName, "WindowsTerminal.exe");
|
|
ffStrbufSetS(&result->prettyName, "WindowsTerminal");
|
|
|
|
PWSTR programFiles = NULL;
|
|
if (SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_ProgramFiles, KF_FLAG_DEFAULT, NULL, &programFiles))) {
|
|
ffStrbufSetWS(&result->exe, programFiles);
|
|
CoTaskMemFree(programFiles);
|
|
programFiles = NULL;
|
|
|
|
ffStrbufAppendS(&result->exe, "\\WindowsApps\\");
|
|
ffStrbufAppend(&result->exe, &path);
|
|
ffStrbufAppendS(&result->exe, "\\WindowsTerminal.exe");
|
|
|
|
if (ffPathExists(result->exe.chars, FF_PATHTYPE_FILE)) {
|
|
result->exeName = result->exe.chars + ffStrbufLastIndexC(&result->exe, '\\') + 1;
|
|
ffStrbufSet(&result->exePath, &result->exe);
|
|
} else {
|
|
ffStrbufDestroy(&result->exe);
|
|
ffStrbufInitMove(&result->exe, &path);
|
|
result->exeName = "";
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
conhost:;
|
|
ULONG_PTR conhostPid = 0;
|
|
ULONG size;
|
|
if (NT_SUCCESS(NtQueryInformationProcess(NtCurrentProcess(), ProcessConsoleHostProcess, &conhostPid, sizeof(conhostPid), &size)) && conhostPid != 0) {
|
|
// For Windows Terminal, it reports the PID of OpenConsole
|
|
if (ffProcessGetInfoWindows((uint32_t) conhostPid, NULL, &result->processName, &result->exe, &result->exeName, &result->exePath, NULL)) {
|
|
ffStrbufSet(&result->prettyName, &result->processName);
|
|
if (ffStrbufEndsWithIgnCaseS(&result->prettyName, ".exe")) {
|
|
ffStrbufSubstrBefore(&result->prettyName, result->prettyName.length - 4);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
ffStrbufClear(&result->exe);
|
|
return false;
|
|
}
|
|
|
|
static uint32_t getTerminalInfo(FFTerminalResult* result, uint32_t pid) {
|
|
if (getenv("MSYSTEM")) {
|
|
// Don't try to detect terminals in MSYS shell
|
|
// It won't work because MSYS doesn't follow process tree of native Windows programs
|
|
return 0;
|
|
}
|
|
|
|
uint32_t ppid = 0;
|
|
bool gui;
|
|
|
|
while (pid != 0 && ffProcessGetInfoWindows(pid, &ppid, &result->processName, &result->exe, &result->exeName, &result->exePath, &gui)) {
|
|
if (!gui) {
|
|
// We are in nested shell
|
|
ffStrbufClear(&result->processName);
|
|
ffStrbufClear(&result->prettyName);
|
|
ffStrbufClear(&result->exe);
|
|
ffStrbufClear(&result->exePath);
|
|
result->exeName = "";
|
|
pid = ppid;
|
|
continue;
|
|
}
|
|
|
|
ffStrbufSet(&result->prettyName, &result->processName);
|
|
if (ffStrbufEndsWithIgnCaseS(&result->prettyName, ".exe")) {
|
|
ffStrbufSubstrBefore(&result->prettyName, result->prettyName.length - 4);
|
|
}
|
|
|
|
if (ffStrbufIgnCaseEqualS(&result->prettyName, "sihost") ||
|
|
ffStrbufIgnCaseEqualS(&result->prettyName, "explorer") ||
|
|
ffStrbufIgnCaseEqualS(&result->prettyName, "wininit")) {
|
|
// A CUI program created by Windows Explorer will spawn a conhost as its child.
|
|
// However the conhost process is just a placeholder;
|
|
// The true terminal can be Windows Terminal or others.
|
|
ffStrbufClear(&result->processName);
|
|
ffStrbufClear(&result->prettyName);
|
|
ffStrbufClear(&result->exe);
|
|
ffStrbufClear(&result->exePath);
|
|
result->exeName = "";
|
|
return 0;
|
|
} else {
|
|
result->pid = pid;
|
|
result->ppid = ppid;
|
|
}
|
|
|
|
break;
|
|
}
|
|
return ppid;
|
|
}
|
|
|
|
static void setTerminalInfoDetails(FFTerminalResult* result) {
|
|
if (ffStrbufIgnCaseEqualS(&result->prettyName, "WindowsTerminal")) {
|
|
ffStrbufSetStatic(&result->prettyName, ffStrbufContainIgnCaseS(&result->exe, ".WindowsTerminalPreview_") ? "Windows Terminal Preview" : "Windows Terminal");
|
|
} else if (ffStrbufIgnCaseEqualS(&result->prettyName, "conhost")) {
|
|
ffStrbufSetStatic(&result->prettyName, "Windows Console");
|
|
} else if (ffStrbufIgnCaseEqualS(&result->prettyName, "Code")) {
|
|
ffStrbufSetStatic(&result->prettyName, "Visual Studio Code");
|
|
} else if (ffStrbufIgnCaseEqualS(&result->prettyName, "explorer")) {
|
|
ffStrbufSetStatic(&result->prettyName, "Windows Explorer");
|
|
} else if (ffStrbufEqualS(&result->prettyName, "wezterm-gui")) {
|
|
ffStrbufSetStatic(&result->prettyName, "WezTerm");
|
|
} else if (ffStrbufIgnCaseEqualS(&result->prettyName, "sshd") || ffStrbufStartsWithIgnCaseS(&result->prettyName, "sshd-")) {
|
|
const char* tty = getenv("SSH_TTY");
|
|
if (tty) {
|
|
ffStrbufSetS(&result->prettyName, tty);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool fftsGetTerminalVersion(FFstrbuf* processName, FFstrbuf* exe, FFstrbuf* version);
|
|
|
|
const FFShellResult* ffDetectShell(void) {
|
|
static FFShellResult result;
|
|
static bool init = false;
|
|
if (init) {
|
|
return &result;
|
|
}
|
|
init = true;
|
|
|
|
ffStrbufInit(&result.processName);
|
|
ffStrbufInitA(&result.exe, MAX_PATH);
|
|
result.exeName = "";
|
|
ffStrbufInit(&result.exePath);
|
|
ffStrbufInit(&result.prettyName);
|
|
ffStrbufInit(&result.version);
|
|
result.pid = 0;
|
|
result.ppid = 0;
|
|
result.tty = -1;
|
|
|
|
uint32_t ppid;
|
|
if (!ffProcessGetInfoWindows(0, &ppid, NULL, NULL, NULL, NULL, NULL)) {
|
|
return &result;
|
|
}
|
|
|
|
const char* ignoreParent = getenv("FFTS_IGNORE_PARENT");
|
|
if (ignoreParent && ffStrEquals(ignoreParent, "1")) {
|
|
ffProcessGetInfoWindows(ppid, &ppid, NULL, NULL, NULL, NULL, NULL);
|
|
}
|
|
|
|
ppid = getShellInfo(&result, ppid);
|
|
|
|
if (result.processName.length > 0) {
|
|
setShellInfoDetails(&result);
|
|
char tmp[MAX_PATH];
|
|
strcpy(tmp, result.exeName);
|
|
char* ext = strrchr(tmp, '.');
|
|
if (ext) {
|
|
*ext = '\0';
|
|
}
|
|
fftsGetShellVersion(result.exePath.length > 0 ? &result.exePath : &result.exe, tmp, &result.version);
|
|
}
|
|
|
|
return &result;
|
|
}
|
|
|
|
const FFTerminalResult* ffDetectTerminal(void) {
|
|
static FFTerminalResult result;
|
|
static bool init = false;
|
|
if (init) {
|
|
return &result;
|
|
}
|
|
init = true;
|
|
|
|
ffStrbufInit(&result.processName);
|
|
ffStrbufInitA(&result.exe, MAX_PATH);
|
|
result.exeName = "";
|
|
ffStrbufInit(&result.exePath);
|
|
ffStrbufInit(&result.prettyName);
|
|
ffStrbufInit(&result.version);
|
|
ffStrbufInit(&result.tty);
|
|
result.pid = 0;
|
|
result.ppid = 0;
|
|
|
|
uint32_t ppid = ffDetectShell()->ppid;
|
|
if (ppid) {
|
|
getTerminalInfo(&result, ppid);
|
|
}
|
|
|
|
if (result.processName.length == 0) {
|
|
getTerminalFromEnv(&result);
|
|
}
|
|
if (result.processName.length == 0) {
|
|
detectDefaultTerminal(&result);
|
|
}
|
|
|
|
if (result.processName.length > 0) {
|
|
setTerminalInfoDetails(&result);
|
|
fftsGetTerminalVersion(&result.processName, result.exePath.length > 0 ? &result.exePath : &result.exe, &result.version);
|
|
}
|
|
|
|
return &result;
|
|
}
|