#include "common/library.h" #include "common/io.h" #include "common/path.h" #include "common/processing.h" #include "common/properties.h" #include "common/windows/unicode.h" #include "common/windows/registry.h" #include "common/strutil.h" #include "detection/terminalshell/terminalshell.h" #include "terminalfont.h" #include #include #include static const char* detectWTProfile(yyjson_val* profile, FFstrbuf* name, double* size) { yyjson_val* font = yyjson_obj_get(profile, "font"); if (!font) { return "yyjson_obj_get(profile, \"font\"); failed"; } if (!yyjson_is_obj(font)) { return "yyjson_is_obj(font) returns false"; } if (name->length == 0) { ffStrbufAppendJsonVal(name, yyjson_obj_get(font, "face")); } if (*size < 0) { yyjson_val* psize = yyjson_obj_get(font, "size"); if (yyjson_is_num(psize)) { *size = unsafe_yyjson_get_num(psize); } } return NULL; } static inline void wrapYyjsonFree(yyjson_doc** doc) { assert(doc); if (*doc) { yyjson_doc_free(*doc); } } static const char* detectFromWTImpl(FFstrbuf* content, FFstrbuf* name, double* size) { yyjson_doc* FF_A_CLEANUP(wrapYyjsonFree) doc = yyjson_read_opts(content->chars, content->length, YYJSON_READ_ALLOW_COMMENTS | YYJSON_READ_ALLOW_TRAILING_COMMAS, NULL, NULL); if (!doc) { return "Failed to parse WT JSON config file"; } yyjson_val* const root = yyjson_doc_get_root(doc); assert(root); yyjson_val* profiles = yyjson_obj_get(root, "profiles"); if (!profiles) { return "yyjson_obj_get(root, \"profiles\") failed"; } FF_STRBUF_AUTO_DESTROY wtProfileId = ffStrbufCreateS(getenv("WT_PROFILE_ID")); ffStrbufTrim(&wtProfileId, '\''); if (wtProfileId.length > 0) { yyjson_val* list = yyjson_obj_get(profiles, "list"); if (yyjson_is_arr(list)) { yyjson_val* profile; size_t idx, max; yyjson_arr_foreach (list, idx, max, profile) { yyjson_val* guid = yyjson_obj_get(profile, "guid"); if (ffStrbufEqualS(&wtProfileId, yyjson_get_str(guid))) { detectWTProfile(profile, name, size); break; } } } } yyjson_val* defaults = yyjson_obj_get(profiles, "defaults"); if (defaults) { detectWTProfile(defaults, name, size); } if (name->length == 0) { ffStrbufSetS(name, "Cascadia Mono"); } if (*size < 0) { *size = 12; } return NULL; } static void detectFromWindowsTerminal(const FFstrbuf* terminalExe, FFTerminalFontResult* terminalFont) { // https://learn.microsoft.com/en-us/windows/terminal/install#settings-json-file FF_STRBUF_AUTO_DESTROY json = ffStrbufCreate(); const char* error = NULL; if (terminalExe && ffIsAbsolutePath(terminalExe->chars)) { FF_STRBUF_AUTO_DESTROY jsonPath = ffStrbufCreateA(MAX_PATH); ffStrbufAppendNS(&jsonPath, ffStrbufLastIndexC(terminalExe, '\\') + 1, terminalExe->chars); ffStrbufAppendS(&jsonPath, ".portable"); if (ffPathExists(jsonPath.chars, FF_PATHTYPE_ANY)) { ffStrbufSubstrBefore(&jsonPath, jsonPath.length - strlen(".portable")); ffStrbufAppendS(&jsonPath, "settings\\settings.json"); if (!ffAppendFileBuffer(jsonPath.chars, &json)) { error = "Error reading Windows Terminal portable settings JSON file"; } } else { PWSTR localAppDataW = NULL; if (SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_LocalAppData, KF_FLAG_DEFAULT, NULL, &localAppDataW))) { ffStrbufSetWS(&jsonPath, localAppDataW); CoTaskMemFree(localAppDataW); if (ffStrbufContainIgnCaseS(terminalExe, "_8wekyb3d8bbwe\\")) { // Microsoft Store version if (ffStrbufContainIgnCaseS(terminalExe, ".WindowsTerminalPreview_")) { // Preview version ffStrbufAppendS(&jsonPath, "\\Packages\\Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe\\LocalState\\settings.json"); if (!ffAppendFileBuffer(jsonPath.chars, &json)) { error = "Error reading Windows Terminal Preview settings JSON file"; } } else { // Stable version ffStrbufAppendS(&jsonPath, "\\Packages\\Microsoft.WindowsTerminal_8wekyb3d8bbwe\\LocalState\\settings.json"); if (!ffAppendFileBuffer(jsonPath.chars, &json)) { error = "Error reading Windows Terminal settings JSON file"; } } } else { ffStrbufAppendS(&jsonPath, "\\Microsoft\\Windows Terminal\\settings.json"); if (!ffAppendFileBuffer(jsonPath.chars, &json)) { error = "Error reading Windows Terminal settings JSON file"; } } } } } if (!error && json.length == 0) { error = ffProcessAppendStdOut(&json, (char* const[]) { "cmd.exe", "/c", // print the file content directly, so we don't need to handle the difference of Windows and POSIX path "if exist %LOCALAPPDATA%\\Packages\\Microsoft.WindowsTerminal_8wekyb3d8bbwe\\LocalState\\settings.json " "( type %LOCALAPPDATA%\\Packages\\Microsoft.WindowsTerminal_8wekyb3d8bbwe\\LocalState\\settings.json ) " "else if exist %LOCALAPPDATA%\\Packages\\Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe\\LocalState\\settings.json " "( type %LOCALAPPDATA%\\Packages\\Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe\\LocalState\\settings.json ) " "else if exist \"%LOCALAPPDATA%\\Microsoft\\Windows Terminal\\settings.json\" " "( type %LOCALAPPDATA%\\Microsoft\\Windows Terminal\\settings.json ) " "else ( call )", NULL }); } if (error) { ffStrbufAppendS(&terminalFont->error, error); return; } ffStrbufTrimRight(&json, '\n'); if (json.length == 0) { ffStrbufAppendS(&terminalFont->error, "Cannot find file \"settings.json\""); return; } FF_STRBUF_AUTO_DESTROY name = ffStrbufCreate(); double size = -1; error = detectFromWTImpl(&json, &name, &size); if (error) { ffStrbufAppendS(&terminalFont->error, error); } else { char sizeStr[16]; snprintf(sizeStr, ARRAY_SIZE(sizeStr), "%g", size); ffFontInitValues(&terminalFont->font, name.chars, sizeStr); } } static void detectMintty(FFTerminalFontResult* terminalFont) { FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate(); if (!ffParsePropFileConfigValues("mintty/config", 2, (FFpropquery[]) { { "Font=", &fontName }, { "FontHeight=", &fontSize } })) { ffParsePropFileConfigValues(".minttyrc", 2, (FFpropquery[]) { { "Font=", &fontName }, { "FontHeight=", &fontSize } }); } if (fontName.length == 0) { ffStrbufAppendS(&fontName, "Lucida Console"); } if (fontSize.length == 0) { ffStrbufAppendC(&fontSize, '9'); } ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars); } static void detectConhost(FFTerminalFontResult* terminalFont) { CONSOLE_FONT_INFOEX cfi = { .cbSize = sizeof(cfi) }; if (!GetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &cfi)) { ffStrbufAppendS(&terminalFont->error, "GetCurrentConsoleFontEx() failed"); return; } FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreateWS(cfi.FaceName); char fontSize[16]; _ultoa((unsigned long) (cfi.dwFontSize.Y), fontSize, 10); ffFontInitValues(&terminalFont->font, fontName.chars, fontSize); } static void detectConEmu(FFTerminalFontResult* terminalFont) { // https://conemu.github.io/en/ConEmuXml.html#search-sequence FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate(); const char* paths[] = { "ConEmuDir", "ConEmuBaseDir", "APPDATA" }; for (uint32_t i = 0; i < ARRAY_SIZE(paths); ++i) { ffStrbufSetS(&path, getenv(paths[i])); if (path.length > 0) { ffStrbufAppendS(&path, "/ConEmu.xml"); if (ffParsePropFileValues(path.chars, 2, (FFpropquery[]) { { "error, "Failed to parse ConEmu.xml"); return; } if (fontName.length > 0) { ffStrbufSubstrBeforeLastC(&fontName, '"'); } else { ffStrbufAppendS(&fontName, "Consola"); } if (fontSize.length > 0) { ffStrbufSubstrBeforeLastC(&fontSize, '"'); } else { ffStrbufAppendS(&fontSize, "14"); } ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars); } static void detectWarp(FFTerminalFontResult* terminalFont) { FF_AUTO_CLOSE_FD HANDLE key = NULL; if (!ffRegOpenKeyForRead(HKEY_CURRENT_USER, L"Software\\Warp.dev\\Warp", &key, &terminalFont->error)) { return; } FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate(); if (ffRegReadValues(key, 2, (FFRegValueArg[]) { FF_ARG(fontName, L"FontName"), FF_ARG(fontSize, L"FontSize") }, &terminalFont->error)) { ffStrbufTrim(&fontName, '"'); ffStrbufAppendS(&fontSize, "px"); } else { ffStrbufSetS(&fontName, "Hack"); ffStrbufSetS(&fontSize, "13.0px"); } ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars); FFstrbuf* fontWeight = FF_LIST_ADD(FFstrbuf, terminalFont->font.styles); ffStrbufInit(fontWeight); if (ffRegReadStrbuf(key, L"FontWeight", fontWeight, NULL)) { ffStrbufTrim(fontWeight, '"'); } else { ffStrbufSetStatic(fontWeight, "Normal"); } } bool ffDetectTerminalFontPlatform(const FFTerminalResult* terminal, FFTerminalFontResult* terminalFont) { if (ffStrbufIgnCaseEqualS(&terminal->processName, "Windows Terminal") || ffStrbufIgnCaseEqualS(&terminal->processName, "WindowsTerminal.exe")) { detectFromWindowsTerminal(&terminal->exe, terminalFont); } else if (ffStrbufIgnCaseEqualS(&terminal->processName, "mintty")) { detectMintty(terminalFont); } else if (ffStrbufIgnCaseEqualS(&terminal->processName, "conhost.exe")) { detectConhost(terminalFont); } else if (ffStrbufStartsWithIgnCaseS(&terminal->processName, "ConEmu")) { detectConEmu(terminalFont); } else if (ffStrbufStartsWithIgnCaseS(&terminal->processName, "warp")) { detectWarp(terminalFont); } else { return false; } return true; }