#include "terminalshell.h" #include "common/processing.h" #include "common/thread.h" #include "util/mallocHelper.h" #include #include #include #include #include static bool getProductVersion(const wchar_t* filePath, FFstrbuf* version) { DWORD handle; DWORD size = GetFileVersionInfoSizeW(filePath, &handle); if(size > 0) { FF_AUTO_FREE void* versionData = malloc(size); if(GetFileVersionInfoW(filePath, handle, size, versionData)) { VS_FIXEDFILEINFO* verInfo; UINT len; if(VerQueryValueW(versionData, L"\\", (void**)&verInfo, &len) && len && verInfo->dwSignature == 0xFEEF04BD) { ffStrbufAppendF(version, "%u.%u.%u.%u", (unsigned)(( verInfo->dwProductVersionMS >> 16 ) & 0xffff), (unsigned)(( verInfo->dwProductVersionMS >> 0 ) & 0xffff), (unsigned)(( verInfo->dwProductVersionLS >> 16 ) & 0xffff), (unsigned)(( verInfo->dwProductVersionLS >> 0 ) & 0xffff) ); return true; } } } return false; } static bool getProcessInfo(uint32_t pid, uint32_t* ppid, FFstrbuf* pname, FFstrbuf* exe, const char** exeName) { HANDLE hProcess = pid == 0 ? GetCurrentProcess() : OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, TRUE, pid); if(ppid) { PROCESS_BASIC_INFORMATION info; ULONG size; if(NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessBasicInformation, &info, sizeof(info), &size))) { assert(size == sizeof(info)); *ppid = (uint32_t)info.InheritedFromUniqueProcessId; } else { CloseHandle(hProcess); return false; } } if(exe) { DWORD bufSize = exe->allocated; if(QueryFullProcessImageNameA(hProcess, 0, exe->chars, &bufSize)) exe->length = bufSize; else { CloseHandle(hProcess); return false; } } if(pname && exeName) { *exeName = exe->chars + ffStrbufLastIndexC(exe, '\\') + 1; ffStrbufSetS(pname, *exeName); } CloseHandle(hProcess); return true; } static bool getTerminalInfoByEnumeratingChildProcesses(FFTerminalShellResult* result, uint32_t ppid) { ULONG size = 0; if(NtQuerySystemInformation(SystemProcessInformation, NULL, 0, &size) != STATUS_INFO_LENGTH_MISMATCH) return false; size += sizeof(SystemProcessInformation) * 5; //What if new processes are created during two syscalls? SYSTEM_PROCESS_INFORMATION* FF_AUTO_FREE pstart = (SYSTEM_PROCESS_INFORMATION*)malloc(size); if(!pstart) return false; if(!NT_SUCCESS(NtQuerySystemInformation(SystemProcessInformation, pstart, size, NULL))) return false; uint32_t currentProcessId = (uint32_t) GetCurrentProcessId(); for (SYSTEM_PROCESS_INFORMATION* ptr = pstart; ptr->NextEntryOffset; ptr = (SYSTEM_PROCESS_INFORMATION*)((uint8_t*)ptr + ptr->NextEntryOffset)) { if ((uint32_t)(uintptr_t) ptr->InheritedFromUniqueProcessId != ppid) continue; uint32_t pid = (uint32_t)(uintptr_t) ptr->UniqueProcessId; if (pid == currentProcessId) continue; if(!getProcessInfo(pid, NULL, &result->terminalProcessName, &result->terminalExe, &result->terminalExeName)) return false; result->terminalPid = pid; ffStrbufSet(&result->terminalPrettyName, &result->terminalProcessName); if(ffStrbufEndsWithIgnCaseS(&result->terminalPrettyName, ".exe")) ffStrbufSubstrBefore(&result->terminalPrettyName, result->terminalPrettyName.length - 4); return true; } return false; } bool fftsGetShellVersion(FFstrbuf* exe, const char* exeName, FFstrbuf* version); static uint32_t getShellInfo(FFTerminalShellResult* result, uint32_t pid) { uint32_t ppid; if(pid == 0 || !getProcessInfo(pid, &ppid, &result->shellProcessName, &result->shellExe, &result->shellExeName)) return 0; ffStrbufSet(&result->shellPrettyName, &result->shellProcessName); if(ffStrbufEndsWithIgnCaseS(&result->shellPrettyName, ".exe")) ffStrbufSubstrBefore(&result->shellPrettyName, result->shellPrettyName.length - 4); //Common programs that are between terminal and own process, but are not the shell if( ffStrbufIgnCaseEqualS(&result->shellPrettyName, "sudo") || ffStrbufIgnCaseEqualS(&result->shellPrettyName, "su") || ffStrbufIgnCaseEqualS(&result->shellPrettyName, "doas") || ffStrbufIgnCaseEqualS(&result->shellPrettyName, "strace") || ffStrbufIgnCaseEqualS(&result->shellPrettyName, "sshd") || ffStrbufIgnCaseEqualS(&result->shellPrettyName, "gdb") || ffStrbufIgnCaseEqualS(&result->shellPrettyName, "lldb") || ffStrbufIgnCaseEqualS(&result->shellPrettyName, "guake-wrapped") || ffStrbufIgnCaseEqualS(&result->shellPrettyName, "fastfetch") || //scoop warps the real binaries with a "shim" exe ffStrbufIgnCaseEqualS(&result->shellPrettyName, "flashfetch") || ffStrbufContainIgnCaseS(&result->shellPrettyName, "debug") || ffStrbufStartsWithIgnCaseS(&result->shellPrettyName, "ConEmuC") // https://github.com/fastfetch-cli/fastfetch/issues/488#issuecomment-1619982014 ) { ffStrbufClear(&result->shellProcessName); ffStrbufClear(&result->shellPrettyName); ffStrbufClear(&result->shellExe); result->shellExeName = NULL; return getShellInfo(result, ppid); } ffStrbufClear(&result->shellVersion); fftsGetShellVersion(&result->shellExe, result->shellPrettyName.chars, &result->shellVersion); result->shellPid = pid; if(ffStrbufIgnCaseEqualS(&result->shellPrettyName, "pwsh")) ffStrbufSetS(&result->shellPrettyName, "PowerShell"); else if(ffStrbufIgnCaseEqualS(&result->shellPrettyName, "powershell")) ffStrbufSetS(&result->shellPrettyName, "Windows PowerShell"); else if(ffStrbufIgnCaseEqualS(&result->shellPrettyName, "powershell_ise")) ffStrbufSetS(&result->shellPrettyName, "Windows PowerShell ISE"); else if(ffStrbufIgnCaseEqualS(&result->shellPrettyName, "cmd")) { ffStrbufClear(&result->shellPrettyName); HANDLE snapshot; while(!(snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 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) { ffStrbufAppendS(&result->shellPrettyName, "CMD (with Clink "); getProductVersion(module.szExePath, &result->shellPrettyName); ffStrbufAppendC(&result->shellPrettyName, ')'); break; } } CloseHandle(snapshot); } if(result->shellPrettyName.length == 0) ffStrbufAppendS(&result->shellPrettyName, "Command Prompt"); } else if(ffStrbufIgnCaseEqualS(&result->shellPrettyName, "nu")) ffStrbufSetS(&result->shellPrettyName, "nushell"); else if(ffStrbufIgnCaseEqualS(&result->shellPrettyName, "explorer")) { ffStrbufSetS(&result->shellPrettyName, "Windows Explorer"); // Started without shell // In this case, terminal process will be created by fastfetch itself. return 0; } return ppid; } static uint32_t getTerminalInfo(FFTerminalShellResult* result, uint32_t pid) { uint32_t ppid; if(pid == 0 || !getProcessInfo(pid, &ppid, &result->terminalProcessName, &result->terminalExe, &result->terminalExeName)) return 0; ffStrbufSet(&result->terminalPrettyName, &result->terminalProcessName); if(ffStrbufEndsWithIgnCaseS(&result->terminalPrettyName, ".exe")) ffStrbufSubstrBefore(&result->terminalPrettyName, result->terminalPrettyName.length - 4); if( ffStrbufIgnCaseEqualS(&result->terminalPrettyName, "pwsh") || ffStrbufIgnCaseEqualS(&result->terminalPrettyName, "cmd") || ffStrbufIgnCaseEqualS(&result->terminalPrettyName, "bash") || ffStrbufIgnCaseEqualS(&result->terminalPrettyName, "zsh") || ffStrbufIgnCaseEqualS(&result->terminalPrettyName, "fish") || ffStrbufIgnCaseEqualS(&result->terminalPrettyName, "nu") || ffStrbufIgnCaseEqualS(&result->terminalPrettyName, "powershell") || ffStrbufIgnCaseEqualS(&result->terminalPrettyName, "powershell_ise") ) { //We are nested shell ffStrbufClear(&result->terminalProcessName); ffStrbufClear(&result->terminalPrettyName); ffStrbufClear(&result->terminalExe); result->terminalExeName = ""; return getTerminalInfo(result, ppid); } if(ffStrbufIgnCaseEqualS(&result->terminalPrettyName, "sihost") || ffStrbufIgnCaseEqualS(&result->terminalPrettyName, "explorer") ) { ffStrbufClear(&result->terminalProcessName); ffStrbufClear(&result->terminalPrettyName); ffStrbufClear(&result->terminalExe); result->terminalExeName = ""; // Maybe terminal process is created by shell if(!getTerminalInfoByEnumeratingChildProcesses(result, result->shellPid)) return 0; } else result->terminalPid = pid; if(ffStrbufIgnCaseEqualS(&result->terminalPrettyName, "WindowsTerminal")) ffStrbufSetS(&result->terminalPrettyName, ffStrbufContainIgnCaseS(&result->terminalExe, ".WindowsTerminalPreview_") ? "Windows Terminal Preview" : "Windows Terminal" ); else if(ffStrbufIgnCaseEqualS(&result->terminalPrettyName, "conhost")) ffStrbufSetS(&result->terminalPrettyName, "Console Window Host"); else if(ffStrbufIgnCaseEqualS(&result->terminalPrettyName, "Code")) ffStrbufSetS(&result->terminalPrettyName, "Visual Studio Code"); else if(ffStrbufIgnCaseEqualS(&result->terminalPrettyName, "explorer")) ffStrbufSetS(&result->terminalPrettyName, "Windows Explorer"); else if(ffStrbufStartsWithIgnCaseS(&result->terminalPrettyName, "ConEmuC")) ffStrbufSetS(&result->terminalPrettyName, "ConEmu"); else if(ffStrbufEqualS(&result->terminalPrettyName, "wezterm-gui")) ffStrbufInitS(&result->terminalPrettyName, "WezTerm"); return ppid; } static void getTerminalFromEnv(FFTerminalShellResult* result) { if( result->terminalProcessName.length > 0 && ffStrbufIgnCaseCompS(&result->terminalProcessName, "explorer") != 0 ) return; const char* term = NULL; //SSH if(getenv("SSH_CONNECTION") != NULL) term = getenv("SSH_TTY"); //Windows Terminal if(!term && ( getenv("WT_SESSION") != NULL || getenv("WT_PROFILE_ID") != NULL )) term = "Windows Terminal"; //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->terminalProcessName, term); ffStrbufSetS(&result->terminalPrettyName, term); ffStrbufSetS(&result->terminalExe, term); result->terminalExeName = ""; } } bool fftsGetTerminalVersion(FFstrbuf* processName, FFstrbuf* exe, FFstrbuf* version); const FFTerminalShellResult* ffDetectTerminalShell(void) { static FFThreadMutex mutex = FF_THREAD_MUTEX_INITIALIZER; static FFTerminalShellResult result; static bool init = false; ffThreadMutexLock(&mutex); if(init) { ffThreadMutexUnlock(&mutex); return &result; } init = true; ffStrbufInit(&result.shellProcessName); ffStrbufInitA(&result.shellExe, 128); result.shellExeName = ""; ffStrbufInit(&result.shellPrettyName); ffStrbufInit(&result.shellVersion); result.shellPid = 0; ffStrbufInit(&result.terminalProcessName); ffStrbufInitA(&result.terminalExe, 128); result.terminalExeName = ""; ffStrbufInit(&result.terminalPrettyName); result.terminalPid = 0; ffStrbufInit(&result.userShellExe); result.userShellExeName = ""; ffStrbufInit(&result.userShellVersion); uint32_t ppid; if(!getProcessInfo(0, &ppid, NULL, NULL, NULL)) goto exit; ppid = getShellInfo(&result, ppid); if(ppid) getTerminalInfo(&result, ppid); if(result.terminalProcessName.length == 0) getTerminalFromEnv(&result); ffStrbufInit(&result.terminalVersion); fftsGetTerminalVersion(&result.terminalProcessName, &result.terminalExe, &result.terminalVersion); exit: ffThreadMutexUnlock(&mutex); return &result; }