summaryrefslogtreecommitdiffstats
path: root/src/detection/terminalfont/terminalfont_windows.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/detection/terminalfont/terminalfont_windows.c')
-rw-r--r--src/detection/terminalfont/terminalfont_windows.c287
1 files changed, 287 insertions, 0 deletions
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 <shlobj.h>
+#include <windows.h>
+#include <stdlib.h>
+
+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[]) { { "<value name=\"FontName\" type=\"string\" data=\"", &fontName }, { "<value name=\"FontSize\" type=\"ulong\" data=\"", &fontSize } })) {
+ break;
+ }
+ }
+ }
+
+ if (fontName.length == 0 && fontSize.length == 0) {
+ ffStrbufAppendS(&terminalFont->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;
+}