From 76424950e373d3b04ac3dd13019151bfba3e8423 Mon Sep 17 00:00:00 2001 From: sumuel Date: Mon, 17 Aug 2026 20:44:55 +0000 Subject: Add the files --- .../terminalshell/terminalshell_windows.c | 379 +++++++++++++++++++++ 1 file changed, 379 insertions(+) create mode 100644 src/detection/terminalshell/terminalshell_windows.c (limited to 'src/detection/terminalshell/terminalshell_windows.c') diff --git a/src/detection/terminalshell/terminalshell_windows.c b/src/detection/terminalshell/terminalshell_windows.c new file mode 100644 index 0000000..d037663 --- /dev/null +++ b/src/detection/terminalshell/terminalshell_windows.c @@ -0,0 +1,379 @@ +#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/strutil.h" + +#include +#include +#include +#include +#include +#include +#include + +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"); + } else if (ffStrbufIgnCaseEqualS(&result->prettyName, "busybox")) { + ffStrbufInitStatic(&result->prettyName, "ash"); + } +} + +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); + } + } +} + +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'; + } + if (instance.config.general.detectVersion) { + 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); + if (instance.config.general.detectVersion) { + fftsGetTerminalVersion(&result.processName, result.exePath.length > 0 ? &result.exePath : &result.exe, &result.version); + } + } + + return &result; +} -- cgit v1.2.3