From 76424950e373d3b04ac3dd13019151bfba3e8423 Mon Sep 17 00:00:00 2001 From: sumuel Date: Mon, 17 Aug 2026 20:44:55 +0000 Subject: Add the files --- src/detection/terminalfont/terminalfont_windows.c | 287 ++++++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 src/detection/terminalfont/terminalfont_windows.c (limited to 'src/detection/terminalfont/terminalfont_windows.c') diff --git a/src/detection/terminalfont/terminalfont_windows.c b/src/detection/terminalfont/terminalfont_windows.c new file mode 100644 index 0000000..87566af --- /dev/null +++ b/src/detection/terminalfont/terminalfont_windows.c @@ -0,0 +1,287 @@ +#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; +} -- cgit v1.2.3