summaryrefslogtreecommitdiffstats
path: root/src/detection/terminalfont
diff options
context:
space:
mode:
authorsumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
committersumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
commit76424950e373d3b04ac3dd13019151bfba3e8423 (patch)
tree4c3cfdbda039e592b9186be3e28f8d7cfd439e8a /src/detection/terminalfont
Add the files
Diffstat (limited to 'src/detection/terminalfont')
-rw-r--r--src/detection/terminalfont/terminalfont.c407
-rw-r--r--src/detection/terminalfont/terminalfont.h13
-rw-r--r--src/detection/terminalfont/terminalfont_android.c71
-rw-r--r--src/detection/terminalfont/terminalfont_apple.m106
-rw-r--r--src/detection/terminalfont/terminalfont_linux.c545
-rw-r--r--src/detection/terminalfont/terminalfont_windows.c287
6 files changed, 1429 insertions, 0 deletions
diff --git a/src/detection/terminalfont/terminalfont.c b/src/detection/terminalfont/terminalfont.c
new file mode 100644
index 0000000..7ef7120
--- /dev/null
+++ b/src/detection/terminalfont/terminalfont.c
@@ -0,0 +1,407 @@
+#include "terminalfont.h"
+#include "common/io.h"
+#include "common/properties.h"
+#include "common/processing.h"
+#include "common/debug.h"
+#include "common/strutil.h"
+#include "detection/terminalshell/terminalshell.h"
+
+static void detectAlacritty(FFTerminalFontResult* terminalFont) {
+ // Maybe using a toml parser to read the config file is better?
+ // https://github.com/cktan/tomlc17
+
+ // Doc: https://alacritty.org/config-alacritty.html#s26
+ FF_STRBUF_AUTO_DESTROY fontNormal = ffStrbufCreate();
+ FF_STRBUF_AUTO_DESTROY fontFamily = ffStrbufCreate();
+ FF_STRBUF_AUTO_DESTROY fontStyle = ffStrbufCreate();
+ FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate();
+
+ do {
+ FFpropquery fontQueryToml[] = {
+ { "normal =", &fontNormal },
+ { "size =", &fontSize },
+ };
+
+ // alacritty parses config files in this order
+ if (ffParsePropFileConfigValues("alacritty/alacritty.toml", 2, fontQueryToml)) {
+ break;
+ }
+ if (ffParsePropFileConfigValues("alacritty.toml", 2, fontQueryToml)) {
+ break;
+ }
+ if (ffParsePropFileConfigValues(".alacritty.toml", 2, fontQueryToml)) {
+ break;
+ }
+ } while (false);
+
+ if (fontNormal.length > 0) {
+ // { family = "Fira Code", style = "Medium" }
+ ffStrbufTrimSpace(&fontNormal);
+ ffStrbufTrimRight(&fontNormal, '}');
+ ffStrbufTrimLeft(&fontNormal, '{');
+ ffStrbufTrimSpace(&fontNormal);
+
+ // family = "Fira Code", style = "Medium"
+ ffStrbufReplaceAllC(&fontNormal, ',', '\n'); // Assume no commas in font names
+ ffParsePropLines(fontNormal.chars, "family =", &fontFamily);
+ ffParsePropLines(fontNormal.chars, "style =", &fontStyle);
+ }
+
+ if (fontFamily.length == 0) {
+#if __APPLE__
+ ffStrbufSetStatic(&fontFamily, "Menlo");
+#elif _WIN32
+ ffStrbufSetStatic(&fontFamily, "Consolas");
+#else
+ ffStrbufSetStatic(&fontFamily, "monospace");
+#endif
+ }
+ if (fontStyle.length == 0) {
+ ffStrbufSetStatic(&fontStyle, "Regular");
+ }
+
+ if (fontSize.length == 0) {
+ ffStrbufSetStatic(&fontSize, "11.25");
+ }
+
+ ffFontInitMoveValues(&terminalFont->font, &fontFamily, &fontSize, &fontStyle);
+}
+
+static bool parseGhosttyConfig(FFstrbuf* path, FFstrbuf* fontName, FFstrbuf* fontNameFallback, FFstrbuf* fontSize) {
+ FF_DEBUG("parsing config: %s", path->chars);
+
+ FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
+ FF_STRBUF_AUTO_DESTROY temp = ffStrbufCreate();
+ if (!ffAppendFileBuffer(path->chars, &buffer)) {
+ FF_DEBUG("cannot read config: %s", path->chars);
+ return false;
+ }
+
+ char* line = NULL;
+ size_t len = 0;
+ while (ffStrbufGetline(&line, &len, &buffer)) {
+ if (ffParsePropLine(line, "font-family =", &temp)) {
+ FF_DEBUG("found font-family='%s' in %s", temp.chars, path->chars);
+ if (fontName->length > 0) {
+ ffStrbufDestroy(fontNameFallback);
+ ffStrbufInitMove(fontNameFallback, fontName);
+ }
+ ffStrbufDestroy(fontName);
+ ffStrbufInitMove(fontName, &temp);
+ } else if (ffParsePropLine(line, "font-size =", fontSize)) {
+ FF_DEBUG("found font-size='%s' in %s", temp.chars, path->chars);
+ // Latter overrides former
+ ffStrbufDestroy(fontSize);
+ ffStrbufInitMove(fontSize, &temp);
+ }
+ }
+ return true;
+}
+
+static void detectGhostty(const FFstrbuf* exe, FFTerminalFontResult* terminalFont, const char* configPathMac, const char* configPathUnix) {
+ FF_DEBUG("detectGhostty: start");
+ FF_STRBUF_AUTO_DESTROY configPath = ffStrbufCreate();
+ FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();
+ FF_STRBUF_AUTO_DESTROY fontNameFallback = ffStrbufCreate();
+ FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate();
+
+ if (configPathMac && configPathUnix) {
+#if __APPLE__
+ ffStrbufSet(&configPath, &instance.state.platform.homeDir);
+ ffStrbufAppendS(&configPath, "Library/Application Support/");
+ ffStrbufAppendS(&configPath, configPathMac); // com.mitchellh.ghostty/config
+ parseGhosttyConfig(&configPath, &fontName, &fontNameFallback, &fontSize);
+#endif
+
+ if (instance.state.platform.configDirs.length > 0) {
+ ffStrbufSet(&configPath, FF_LIST_FIRST(FFstrbuf, instance.state.platform.configDirs));
+ ffStrbufAppendS(&configPath, configPathUnix); // ghostty/config
+ parseGhosttyConfig(&configPath, &fontName, &fontNameFallback, &fontSize);
+ }
+ } else {
+ // Try ghostty +show-config first
+ FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
+ const char* error = ffProcessAppendStdOut(&buffer, (char* const[]){
+ exe->chars,
+ "+show-config",
+ NULL,
+ });
+ if (error == NULL) {
+ char* line = NULL;
+ size_t len = 0;
+ while (ffStrbufGetline(&line, &len, &buffer)) {
+ if (ffStrStartsWith(line, "font-family = ")) {
+ FF_DEBUG("found %s", line);
+ if (fontName.length > 0) {
+ ffStrbufDestroy(&fontNameFallback);
+ ffStrbufInitMove(&fontNameFallback, &fontName);
+ }
+ ffStrbufSetNS(&fontName, (uint32_t) (len - strlen("font-family = ")), line + strlen("font-family = "));
+ } else if (ffStrStartsWith(line, "font-size = ")) {
+ FF_DEBUG("found %s", line);
+ // `ghostty +show-config` reports only one font size even if the config has multiple font sizes
+ ffStrbufSetNS(&fontSize, (uint32_t) (len - strlen("font-size = ")), line + strlen("font-size = "));
+ }
+ }
+ } else {
+ FF_DEBUG("`ghostty +show-config` failed: %s", error);
+ }
+ }
+
+ if (fontName.length == 0) {
+ ffStrbufAppendS(&fontName, "JetBrainsMono Nerd Font");
+ FF_DEBUG("using default family='%s'", fontName.chars);
+ }
+
+ if (fontSize.length == 0) {
+ ffStrbufAppendS(&fontSize,
+#if __APPLE__
+ "13"
+#else
+ "12"
+#endif
+ );
+ FF_DEBUG("using default size='%s'", fontSize.chars);
+ }
+
+ ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars);
+ if (fontNameFallback.length > 0) {
+ FF_DEBUG("applying fallback family='%s'", fontNameFallback.chars);
+ ffFontInitValues(&terminalFont->fallback, fontNameFallback.chars, NULL);
+ }
+ FF_DEBUG("result family='%s' size='%s'%s", fontName.chars, fontSize.chars, fontNameFallback.length ? " (with fallback)" : "");
+ FF_DEBUG("detectGhostty: end");
+}
+
+FF_A_UNUSED static void detectTTY(FFTerminalFontResult* terminalFont) {
+ FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();
+
+ ffParsePropFile(FASTFETCH_TARGET_DIR_ETC "/vconsole.conf", "Font =", &fontName);
+
+ if (fontName.length == 0) {
+ ffStrbufAppendS(&fontName, "VGA default kernel font ");
+ ffProcessAppendStdOut(&fontName, (char* const[]){ "showconsolefont", "--info", NULL });
+
+ ffStrbufTrimRight(&fontName, ' ');
+ }
+
+ if (fontName.length > 0) {
+ ffFontInitCopy(&terminalFont->font, fontName.chars);
+ } else {
+ ffStrbufAppendS(&terminalFont->error, "Couldn't find Font in " FASTFETCH_TARGET_DIR_ETC "/vconsole.conf");
+ }
+}
+
+FF_A_UNUSED static bool detectKitty(const FFstrbuf* exe, FFTerminalFontResult* result) {
+ FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();
+ FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate();
+
+ char fontHex[512] = "", sizeHex[512] = "";
+ // https://github.com/fastfetch-cli/fastfetch/discussions/1030#discussioncomment-9845233
+ if (ffGetTerminalResponse(
+ "\eP+q6b697474792d71756572792d666f6e745f66616d696c79;6b697474792d71756572792d666f6e745f73697a65\e\\", // kitty-query-font_family;kitty-query-font_size
+ 2,
+ "\eP1+r%*[^=]=%511[^\e]\e\\\eP1+r%*[^=]=%511[^\e]\e\\",
+ fontHex,
+ sizeHex) == NULL &&
+ *fontHex && *sizeHex) {
+ // decode hex string
+ for (const char* p = fontHex; p[0] && p[1]; p += 2) {
+ unsigned value;
+ if (sscanf(p, "%2x", &value) == 1) {
+ ffStrbufAppendC(&fontName, (char) value);
+ }
+ }
+ for (const char* p = sizeHex; p[0] && p[1]; p += 2) {
+ unsigned value;
+ if (sscanf(p, "%2x", &value) == 1) {
+ ffStrbufAppendC(&fontSize, (char) value);
+ }
+ }
+ } else {
+ FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();
+ if (!ffProcessAppendStdOut(&buf, (char* const[]){
+ exe->chars,
+ "+kitten",
+ "query-terminal",
+ NULL,
+ })) {
+ ffParsePropLines(buf.chars, "font_family: ", &fontName);
+ ffParsePropLines(buf.chars, "font_size: ", &fontSize);
+ } else {
+ FFpropquery fontQuery[] = {
+ { "font_family ", &fontName },
+ { "font_size ", &fontSize },
+ };
+
+ ffParsePropFileConfigValues("kitty/kitty.conf", 2, fontQuery);
+
+ if (fontName.length == 0) {
+ ffStrbufSetS(&fontName, "monospace");
+ }
+ if (fontSize.length == 0) {
+ ffStrbufSetS(&fontSize, "11.0");
+ }
+ }
+ }
+
+ ffFontInitValues(&result->font, fontName.chars, fontSize.chars);
+
+ return true;
+}
+
+static bool detectWezterm(const FFstrbuf* exe, FFTerminalFontResult* result) {
+ FF_STRBUF_AUTO_DESTROY cli = ffStrbufCreateCopy(exe);
+ ffStrbufSubstrBeforeLastC(&cli, '-');
+
+#ifdef _WIN32
+ ffStrbufAppendS(&cli, ".exe");
+#endif
+
+ FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();
+
+ ffStrbufSetS(&result->error, ffProcessAppendStdOut(&fontName, (char* const[]){ cli.chars, "ls-fonts", "--text", "a", NULL }));
+ if (result->error.length) {
+ return false;
+ }
+
+ // LeftToRight
+ // 0 a \u{61} x_adv=7 cells=1 glyph=a,180 wezterm.font("JetBrains Mono", {weight="Regular", stretch="Normal", style="Normal"})
+ // <built-in>, BuiltIn
+ ffStrbufSubstrAfterFirstC(&fontName, '"');
+ ffStrbufSubstrBeforeFirstC(&fontName, '"');
+
+ if (!fontName.length) {
+ return false;
+ }
+
+ ffFontInitCopy(&result->font, fontName.chars);
+ return true;
+}
+
+static bool detectTabby(FFTerminalFontResult* result) {
+ FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();
+ FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate();
+
+ FFpropquery fontQuery[] = {
+ { "font: ", &fontName },
+ { "fontSize: ", &fontSize },
+ };
+
+ if (!ffParsePropFileConfigValues("tabby/config.yaml", 2, fontQuery)) {
+ return false;
+ }
+
+ if (fontName.length == 0) {
+ ffStrbufSetS(&fontName, "monospace");
+ }
+ if (fontSize.length == 0) {
+ ffStrbufSetS(&fontSize, "14");
+ }
+
+ ffFontInitValues(&result->font, fontName.chars, fontSize.chars);
+
+ return true;
+}
+
+static bool detectContour(const FFstrbuf* exe, FFTerminalFontResult* result) {
+ FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();
+ if (ffProcessAppendStdOut(&buf, (char* const[]){ exe->chars, "font-locator", NULL })) {
+ ffStrbufAppendS(&result->error, "`contour font-locator` failed");
+ return false;
+ }
+
+ //[error] Missing key .logging.enabled. Using default: false.
+ //[error] ...
+ // Matching fonts using : Fontconfig
+ // Font description : (family=Sarasa Term SC Nerd weight=Regular slant=Roman spacing=Monospace, strict_spacing=yes)
+ // Number of fonts found : 49
+ // path /usr/share/fonts/google-noto/NotoSansMono-Regular.ttf Regular Roman
+ // path ...
+
+ uint32_t index = ffStrbufFirstIndexS(&buf, "Font description : (family=");
+ if (index >= buf.length) {
+ return false;
+ }
+ index += (uint32_t) strlen("Font description : (family=");
+ ffStrbufSubstrBefore(&buf, ffStrbufNextIndexS(&buf, index, " weight="));
+ ffFontInitCopy(&result->font, buf.chars + index);
+ return true;
+}
+
+static bool detectRio(FFTerminalFontResult* terminalFont) {
+ FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();
+ FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate();
+
+ FFpropquery fontQueryToml[] = {
+ { "family =", &fontName },
+ { "size =", &fontSize },
+ };
+
+ ffParsePropFileConfigValues("rio/config.toml", 2, fontQueryToml);
+
+ if (fontName.length == 0) {
+ ffStrbufAppendS(&fontName, "Cascadia Code");
+ }
+
+ if (fontSize.length == 0) {
+ ffStrbufAppendS(&fontSize, "18");
+ }
+
+ ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars);
+
+ return true;
+}
+
+bool ffDetectTerminalFontPlatform(const FFTerminalResult* terminal, FFTerminalFontResult* terminalFont);
+
+static bool detectTerminalFontCommon(const FFTerminalResult* terminal, FFTerminalFontResult* terminalFont) {
+ if (ffStrbufStartsWithIgnCaseS(&terminal->processName, "alacritty")) {
+ detectAlacritty(terminalFont);
+ } else if (ffStrbufStartsWithIgnCaseS(&terminal->processName, "wezterm-gui")) {
+ detectWezterm(&terminal->exe, terminalFont);
+ } else if (ffStrbufStartsWithIgnCaseS(&terminal->processName, "tabby")) {
+ detectTabby(terminalFont);
+ } else if (ffStrbufStartsWithIgnCaseS(&terminal->processName, "contour")) {
+ detectContour(&terminal->exe, terminalFont);
+ } else if (ffStrbufStartsWithIgnCaseS(&terminal->processName, "ghostty")) {
+ detectGhostty(&terminal->exe, terminalFont, NULL, NULL);
+ } else if (ffStrbufStartsWithIgnCaseS(&terminal->processName, "Muxy")) {
+ detectGhostty(&terminal->exe, terminalFont, "Muxy/ghostty.conf", "muxy/ghostty.conf");
+ } else if (ffStrbufStartsWithIgnCaseS(&terminal->processName, "rio")) {
+ detectRio(terminalFont);
+ }
+#ifndef _WIN32
+ else if (ffStrbufStartsWithIgnCaseS(&terminal->exe, "/dev/pts/")) {
+ ffStrbufAppendS(&terminalFont->error, "Terminal font detection is not supported on PTS");
+ } else if (ffStrbufIgnCaseEqualS(&terminal->processName, "kitty")) {
+ detectKitty(&terminal->exe, terminalFont);
+ } else if (ffStrbufStartsWithIgnCaseS(&terminal->exe, "/dev/tty")) {
+ detectTTY(terminalFont);
+ }
+#endif
+
+ else {
+ return false;
+ }
+
+ return true;
+}
+
+bool ffDetectTerminalFont(FFTerminalFontResult* result) {
+ const FFTerminalResult* terminal = ffDetectTerminal();
+
+ if (terminal->processName.length == 0) {
+ ffStrbufAppendS(&result->error, "Terminal font needs successful terminal detection");
+ }
+
+ else if (!detectTerminalFontCommon(terminal, result)) {
+ ffDetectTerminalFontPlatform(terminal, result);
+ }
+
+ if (result->error.length == 0 && result->font.pretty.length == 0) {
+ ffStrbufAppendF(&result->error, "Unknown terminal: %s", terminal->processName.chars);
+ }
+
+ return result->error.length == 0;
+}
diff --git a/src/detection/terminalfont/terminalfont.h b/src/detection/terminalfont/terminalfont.h
new file mode 100644
index 0000000..bdc416c
--- /dev/null
+++ b/src/detection/terminalfont/terminalfont.h
@@ -0,0 +1,13 @@
+#pragma once
+
+#include "fastfetch.h"
+#include "common/font.h"
+#include "modules/font/option.h"
+
+typedef struct FFTerminalFontResult {
+ FFstrbuf error;
+ FFfont font;
+ FFfont fallback;
+} FFTerminalFontResult;
+
+bool ffDetectTerminalFont(FFTerminalFontResult* result);
diff --git a/src/detection/terminalfont/terminalfont_android.c b/src/detection/terminalfont/terminalfont_android.c
new file mode 100644
index 0000000..a721078
--- /dev/null
+++ b/src/detection/terminalfont/terminalfont_android.c
@@ -0,0 +1,71 @@
+#include "fastfetch.h"
+#include "terminalfont.h"
+#include "detection/terminalshell/terminalshell.h"
+#include "common/io.h"
+
+#ifdef FF_HAVE_FREETYPE
+ #include "common/library.h"
+ #include <ft2build.h>
+ #include FT_FREETYPE_H
+#endif
+
+#define FF_TERMUX_FONT_PATH FASTFETCH_TARGET_DIR_HOME "/.termux/font.ttf"
+
+const char* detectTermux(FFTerminalFontResult* terminalFont) {
+ if (!ffPathExists(FF_TERMUX_FONT_PATH, FF_PATHTYPE_FILE)) {
+ ffFontInitCopy(&terminalFont->font, "monospace");
+ return NULL;
+ }
+
+#ifdef FF_HAVE_FREETYPE
+
+ FF_LIBRARY_LOAD_MESSAGE(freetype, "libfreetype" FF_LIBRARY_EXTENSION, 2)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(freetype, FT_Init_FreeType);
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(freetype, FT_New_Face);
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(freetype, FT_Done_Face);
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(freetype, FT_Done_FreeType);
+
+ FT_Library library = NULL;
+ FT_Face face = NULL;
+ const char* error = NULL;
+
+ if (ffFT_Init_FreeType(&library)) {
+ error = "FT_Init_FreeType() failed";
+ goto exit;
+ }
+
+ if (ffFT_New_Face(library, FF_TERMUX_FONT_PATH, 0, &face)) {
+ error = "FT_NEW_Face(" FF_TERMUX_FONT_PATH ") failed";
+ goto exit;
+ }
+
+ ffFontInitCopy(&terminalFont->font, face->family_name);
+
+exit:
+ if (face) {
+ ffFT_Done_Face(face);
+ }
+ if (library) {
+ ffFT_Done_FreeType(library);
+ }
+
+ return error;
+
+#else
+
+ FF_UNUSED(terminalFont);
+ return "Fastfetch was built without freetype2 support";
+
+#endif
+}
+
+bool ffDetectTerminalFontPlatform(const FFTerminalResult* terminal, FFTerminalFontResult* terminalFont) {
+ if (ffStrbufEqualS(&terminal->processName, "com.termux")) {
+ ffStrbufSetS(&terminalFont->error, detectTermux(terminalFont));
+ } else {
+ bool ffDetectTerminalFontPlatformLinux(const FFTerminalResult* terminal, FFTerminalFontResult* terminalFont);
+ return ffDetectTerminalFontPlatformLinux(terminal, terminalFont);
+ }
+
+ return true;
+}
diff --git a/src/detection/terminalfont/terminalfont_apple.m b/src/detection/terminalfont/terminalfont_apple.m
new file mode 100644
index 0000000..126235f
--- /dev/null
+++ b/src/detection/terminalfont/terminalfont_apple.m
@@ -0,0 +1,106 @@
+#include "terminalfont.h"
+#include "common/font.h"
+#include "detection/terminalshell/terminalshell.h"
+#include "common/apple/osascript.h"
+
+#include <stdlib.h>
+#include <string.h>
+#import <Foundation/Foundation.h>
+
+static void detectIterm2(FFTerminalFontResult* terminalFont)
+{
+ const char* profile = getenv("ITERM_PROFILE");
+ if (profile == NULL)
+ {
+ ffStrbufAppendS(&terminalFont->error, "environment variable ITERM_PROFILE not set");
+ return;
+ }
+
+ NSError* error;
+ NSString* fileName = [NSString stringWithFormat:@"file://%s/Library/Preferences/com.googlecode.iterm2.plist", instance.state.platform.homeDir.chars];
+ NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL URLWithString:fileName]
+ error:&error];
+ if(error)
+ {
+ ffStrbufAppendS(&terminalFont->error, error.localizedDescription.UTF8String);
+ return;
+ }
+
+ for(NSDictionary* bookmark in dict[@"New Bookmarks"])
+ {
+ if(![bookmark[@"Name"] isEqualToString:@(profile)])
+ continue;
+
+ NSString* normalFont = bookmark[@"Normal Font"];
+ if(!normalFont)
+ {
+ ffStrbufAppendF(&terminalFont->error, "`Normal Font` key in profile `%s` doesn't exist", profile);
+ return;
+ }
+ ffFontInitWithSpace(&terminalFont->font, normalFont.UTF8String);
+
+ NSNumber* useNonAsciiFont = bookmark[@"Use Non-ASCII Font"];
+ if(useNonAsciiFont.boolValue)
+ {
+ NSString* nonAsciiFont = bookmark[@"Non Ascii Font"];
+ if (nonAsciiFont)
+ ffFontInitWithSpace(&terminalFont->fallback, nonAsciiFont.UTF8String);
+ }
+ return;
+ }
+
+ ffStrbufAppendF(&terminalFont->error, "find profile `%s` bookmark failed", profile);
+}
+
+static void detectAppleTerminal(FFTerminalFontResult* terminalFont)
+{
+ FF_STRBUF_AUTO_DESTROY font = ffStrbufCreate();
+ ffOsascript("tell application \"Terminal\" to font name of window frontmost & \" \" & font size of window frontmost", &font);
+
+ if(font.length == 0)
+ {
+ ffStrbufAppendS(&terminalFont->error, "executing osascript failed");
+ return;
+ }
+
+ ffFontInitWithSpace(&terminalFont->font, font.chars);
+}
+
+static void detectWarpTerminal(FFTerminalFontResult* terminalFont)
+{
+ NSError* error;
+ NSString* fileName = [NSString stringWithFormat:@"file://%s/Library/Preferences/dev.warp.Warp-Stable.plist", instance.state.platform.homeDir.chars];
+ NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL URLWithString:fileName]
+ error:&error];
+ if(error)
+ {
+ ffStrbufAppendS(&terminalFont->error, error.localizedDescription.UTF8String);
+ return;
+ }
+
+ NSString* fontName = dict[@"FontName"];
+ if(!fontName)
+ fontName = @"Hack";
+ else
+ fontName = [fontName stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"\""]];
+
+ NSString* fontSize = dict[@"FontSize"];
+ if(!fontSize)
+ fontSize = @"13";
+
+ ffFontInitValues(&terminalFont->font, fontName.UTF8String, fontSize.UTF8String);
+}
+
+bool ffDetectTerminalFontPlatform(const FFTerminalResult* terminal, FFTerminalFontResult* terminalFont)
+{
+ if(ffStrbufIgnCaseEqualS(&terminal->processName, "iterm.app") ||
+ ffStrbufStartsWithIgnCaseS(&terminal->processName, "iTermServer-"))
+ detectIterm2(terminalFont);
+ else if(ffStrbufIgnCaseEqualS(&terminal->processName, "Apple_Terminal"))
+ detectAppleTerminal(terminalFont);
+ else if(ffStrbufIgnCaseEqualS(&terminal->processName, "WarpTerminal"))
+ detectWarpTerminal(terminalFont);
+ else
+ return false;
+ return true;
+}
diff --git a/src/detection/terminalfont/terminalfont_linux.c b/src/detection/terminalfont/terminalfont_linux.c
new file mode 100644
index 0000000..bfc8959
--- /dev/null
+++ b/src/detection/terminalfont/terminalfont_linux.c
@@ -0,0 +1,545 @@
+#include "common/font.h"
+#include "terminalfont.h"
+#include "common/settings.h"
+#include "common/properties.h"
+#include "common/parsing.h"
+#include "common/io.h"
+#include "common/processing.h"
+#include "common/mallocHelper.h"
+#include "common/strutil.h"
+#include "common/binary.h"
+#include "detection/terminalshell/terminalshell.h"
+#include "detection/displayserver/displayserver.h"
+
+static const char* getSystemMonospaceFont(void) {
+ const FFDisplayServerResult* wmde = ffConnectDisplayServer();
+
+ if (ffStrbufIgnCaseEqualS(&wmde->dePrettyName, "Cinnamon")) {
+ const char* systemMonospaceFont = ffSettingsGetGnome("/org/cinnamon/desktop/interface/monospace-font-name", "org.cinnamon.desktop.interface", NULL, "monospace-font-name", FF_VARIANT_TYPE_STRING).strValue;
+ if (ffStrSet(systemMonospaceFont)) {
+ return systemMonospaceFont;
+ }
+ } else if (ffStrbufIgnCaseEqualS(&wmde->dePrettyName, "Mate")) {
+ const char* systemMonospaceFont = ffSettingsGetGnome("/org/mate/interface/monospace-font-name", "org.mate.interface", NULL, "monospace-font-name", FF_VARIANT_TYPE_STRING).strValue;
+ if (ffStrSet(systemMonospaceFont)) {
+ return systemMonospaceFont;
+ }
+ }
+
+ return ffSettingsGetGnome("/org/gnome/desktop/interface/monospace-font-name", "org.gnome.desktop.interface", NULL, "monospace-font-name", FF_VARIANT_TYPE_STRING).strValue;
+}
+
+static void detectKgx(FFTerminalFontResult* terminalFont) {
+ // kgx (gnome console) doesn't support profiles
+ if (!ffSettingsGetGnome("/org/gnome/Console/use-system-font", "org.gnome.Console", NULL, "use-system-font", FF_VARIANT_TYPE_BOOL).boolValue) {
+ FF_AUTO_FREE const char* fontName = ffSettingsGetGnome("/org/gnome/Console/custom-font", "org.gnome.Console", NULL, "custom-font", FF_VARIANT_TYPE_STRING).strValue;
+ if (ffStrSet(fontName)) {
+ ffFontInitPango(&terminalFont->font, fontName);
+ } else {
+ ffStrbufAppendF(&terminalFont->error, "Couldn't get terminal font from GSettings (org.gnome.Console::custom-font)");
+ }
+ } else {
+ FF_AUTO_FREE const char* fontName = getSystemMonospaceFont();
+ if (ffStrSet(fontName)) {
+ ffFontInitPango(&terminalFont->font, fontName);
+ } else {
+ ffStrbufAppendS(&terminalFont->error, "Couldn't get system monospace font name from GSettings / DConf");
+ }
+ }
+}
+
+static void detectPtyxis(FFTerminalFontResult* terminalFont) {
+ if (!ffSettingsGetGnome("/org/gnome/Ptyxis/use-system-font", "org.gnome.Ptyxis", NULL, "use-system-font", FF_VARIANT_TYPE_BOOL).boolValue) {
+ FF_AUTO_FREE const char* fontName = ffSettingsGetGnome("/org/gnome/Ptyxis/font-name", "org.gnome.Ptyxis", NULL, "font-name", FF_VARIANT_TYPE_STRING).strValue;
+ if (ffStrSet(fontName)) {
+ ffFontInitPango(&terminalFont->font, fontName);
+ } else {
+ ffStrbufAppendF(&terminalFont->error, "Couldn't get terminal font from GSettings (org.gnome.Ptyxis::font-name)");
+ }
+ } else {
+ FF_AUTO_FREE const char* fontName = getSystemMonospaceFont();
+ if (ffStrSet(fontName)) {
+ ffFontInitPango(&terminalFont->font, fontName);
+ } else {
+ ffStrbufAppendS(&terminalFont->error, "Couldn't get system monospace font name from GSettings / DConf");
+ }
+ }
+}
+
+static void detectFromGSettings(const char* profilePath, const char* profileList, const char* profile, const char* defaultProfileKey, FFTerminalFontResult* terminalFont) {
+ FF_AUTO_FREE const char* defaultProfile = ffSettingsGetGSettings(profileList, NULL, defaultProfileKey, FF_VARIANT_TYPE_STRING).strValue;
+ if (!ffStrSet(defaultProfile)) {
+ ffStrbufAppendF(&terminalFont->error, "Could not get default profile from gsettings: %s", profileList);
+ return;
+ }
+
+ FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateA(128);
+ ffStrbufAppendS(&path, profilePath);
+ ffStrbufAppendS(&path, defaultProfile);
+ ffStrbufAppendC(&path, '/');
+
+ if (!ffSettingsGetGSettings(profile, path.chars, "use-system-font", FF_VARIANT_TYPE_BOOL).boolValue) {
+ FF_AUTO_FREE const char* fontName = ffSettingsGetGSettings(profile, path.chars, "font", FF_VARIANT_TYPE_STRING).strValue;
+ if (ffStrSet(fontName)) {
+ ffFontInitPango(&terminalFont->font, fontName);
+ } else {
+ ffStrbufAppendF(&terminalFont->error, "Couldn't get terminal font from GSettings (%s::%s::font)", profile, path.chars);
+ }
+ } else {
+ FF_AUTO_FREE const char* fontName = getSystemMonospaceFont();
+ if (ffStrSet(fontName)) {
+ ffFontInitPango(&terminalFont->font, fontName);
+ } else {
+ ffStrbufAppendS(&terminalFont->error, "Couldn't get system monospace font name from GSettings / DConf");
+ }
+ }
+}
+
+static void detectFromConfigFile(const char* configFile, const char* start, FFTerminalFontResult* terminalFont) {
+ FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();
+ ffParsePropFileConfig(configFile, start, &fontName);
+
+ if (fontName.length == 0) {
+ ffStrbufAppendF(&terminalFont->error, "Couldn't find %s in .config/%s", start, configFile);
+ } else {
+ ffFontInitPango(&terminalFont->font, fontName.chars);
+ }
+}
+
+static void detectKonsole(FFTerminalFontResult* terminalFont, const char* rcFile) {
+ FF_STRBUF_AUTO_DESTROY profile = ffStrbufCreate();
+ if (!ffParsePropFileConfig(rcFile, "DefaultProfile =", &profile)) {
+ ffStrbufAppendF(&terminalFont->error, "Configuration \".config/%s\" doesn't exist", rcFile);
+ return;
+ }
+
+ if (profile.length == 0) {
+ ffStrbufAppendS(&terminalFont->error, "Built-in profile is used");
+ return;
+ }
+
+ FF_STRBUF_AUTO_DESTROY profilePath = ffStrbufCreateA(32);
+ ffStrbufAppendS(&profilePath, "konsole/");
+ ffStrbufAppend(&profilePath, &profile);
+
+ FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();
+ ffParsePropFileData(profilePath.chars, "Font =", &fontName);
+
+ if (fontName.length == 0) {
+ ffStrbufAppendF(&terminalFont->error, "Couldn't find \"Font=%%[^\\n]\" in \"%s\"", profilePath.chars);
+ } else {
+ ffFontInitQt(&terminalFont->font, fontName.chars);
+ }
+}
+
+static void detectXFCETerminal(FFTerminalFontResult* terminalFont) {
+ FF_STRBUF_AUTO_DESTROY useSysFont = ffStrbufCreate();
+ FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();
+
+ const char* path = "xfce4/xfconf/xfce-perchannel-xml/xfce4-terminal.xml";
+ bool configFound = ffParsePropFileConfigValues(path, 2, (FFpropquery[]) { { "<property name=\"font-use-system\" type=\"bool\" value=\"", &useSysFont }, { "<property name=\"font-name\" type=\"string\" value=\"", &fontName } });
+
+ if (configFound) {
+ ffStrbufSubstrBeforeLastC(&useSysFont, '"');
+ ffStrbufSubstrBeforeLastC(&fontName, '"');
+ } else {
+ path = "xfce4/terminal/terminalrc";
+ configFound = ffParsePropFileConfigValues(path, 2, (FFpropquery[]) { { "FontUseSystem = ", &useSysFont }, { "FontName = ", &fontName } });
+ }
+
+ if (configFound && (useSysFont.length == 0 || ffStrbufIgnCaseEqualS(&useSysFont, "false"))) {
+ if (fontName.length == 0) {
+ ffStrbufAppendF(&terminalFont->error, "Couldn't find FontName in %s", path);
+ } else {
+ ffFontInitPango(&terminalFont->font, fontName.chars);
+ }
+ } else {
+ const char* systemFontName = ffSettingsGetXFConf("xsettings", "/Gtk/MonospaceFontName", FF_VARIANT_TYPE_STRING).strValue;
+ if (ffStrSet(systemFontName)) {
+ ffFontInitPango(&terminalFont->font, systemFontName);
+ } else {
+ ffStrbufAppendS(&terminalFont->error, "Couldn't find xsettings::/Gtk/MonospaceFontName in XFConf");
+ }
+ }
+}
+
+static void detectDeepinTerminal(FFTerminalFontResult* terminalFont) {
+ FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();
+ FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate();
+
+ FF_STRBUF_AUTO_DESTROY profile = ffStrbufCreateA(64);
+ ffSearchUserConfigFile(&instance.state.platform.configDirs, "deepin/deepin-terminal/config.conf", &profile);
+ FILE* file = fopen(profile.chars, "r");
+
+ if (file) {
+ char* line = NULL;
+ size_t len = 0;
+
+ for (int count = 0; getline(&line, &len, file) != -1 && count < 2;) {
+ if (ffStrEquals(line, "[basic.interface.font]\n")) {
+ if (getline(&line, &len, file) != -1) {
+ ffParsePropLine(line, "value=", &fontName);
+ }
+ ++count;
+ } else if (ffStrEquals(line, "[basic.interface.font_size]\n")) {
+ if (getline(&line, &len, file) != -1) {
+ ffParsePropLine(line, "value=", &fontSize);
+ }
+ ++count;
+ }
+ }
+
+ free(line);
+ fclose(file);
+ }
+
+ if (fontName.length == 0) {
+ ffStrbufAppendS(&fontName, "Noto Sans Mono");
+ }
+ if (fontSize.length == 0) {
+ ffStrbufAppendS(&fontSize, "11");
+ }
+
+ ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars);
+}
+
+static void detectFootTerminal(FFTerminalFontResult* terminalFont) {
+ FF_STRBUF_AUTO_DESTROY font = ffStrbufCreate();
+
+ if (!ffParsePropFileConfig("foot/foot.ini", "font=", &font) || !ffStrSet(font.chars)) {
+ ffFontInitValues(&terminalFont->font, "monospace", "8");
+ return;
+ }
+
+ // Sarasa Term SC Nerd:size=8
+ uint32_t colon = ffStrbufFirstIndexC(&font, ':');
+ if (colon == font.length) {
+ ffFontInitValues(&terminalFont->font, font.chars, "8");
+ return;
+ }
+ uint32_t equal = ffStrbufNextIndexS(&font, colon, "size=");
+ font.chars[colon] = '\0';
+ if (equal == font.length) {
+ ffFontInitValues(&terminalFont->font, font.chars, "8");
+ return;
+ }
+ uint32_t size = equal + (uint32_t) strlen("size=");
+ uint32_t comma = ffStrbufNextIndexC(&font, size, ',');
+ if (comma < font.length) {
+ font.chars[comma] = '\0';
+ }
+ ffFontInitValues(&terminalFont->font, font.chars, &font.chars[size]);
+ if (comma < font.length) {
+ ffFontInitValues(&terminalFont->fallback, &font.chars[comma + 1], NULL);
+ }
+}
+
+static void detectQTerminal(FFTerminalFontResult* terminalFont) {
+ FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();
+ FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate();
+
+ ffParsePropFileConfigValues("qterminal.org/qterminal.ini", 2, (FFpropquery[]) {
+ { "fontFamily=", &fontName },
+ { "fontSize=", &fontSize },
+ });
+
+ if (fontName.length == 0) {
+ ffStrbufAppendS(&fontName, "monospace");
+ }
+ if (fontSize.length == 0) {
+ ffStrbufAppendS(&fontSize, "12");
+ }
+ ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars);
+}
+
+static void detectXterm(FFTerminalFontResult* terminalFont) {
+ FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();
+ FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate();
+
+ ffParsePropFileHomeValues(".Xresources", 2, (FFpropquery[]) {
+ { "xterm*faceName:", &fontName },
+ { "xterm*faceSize:", &fontSize },
+ });
+
+ if (fontName.length == 0) {
+ ffParsePropFileHomeValues(".Xresources", 2, (FFpropquery[]) {
+ { "xterm.vt100.faceName:", &fontName },
+ { "xterm.vt100.faceSize:", &fontSize },
+ });
+ }
+
+ if (fontName.length == 0) {
+ ffStrbufAppendS(&fontName, "fixed");
+ }
+ if (fontSize.length == 0) {
+ ffStrbufAppendS(&fontSize, "8.0");
+ }
+ ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars);
+}
+
+static bool extractStTermFont(const char* str, FF_A_UNUSED uint32_t len, void* userdata) {
+ if (!ffStrContains(str, "size=")) {
+ return true;
+ }
+ ffStrbufSetNS((FFstrbuf*) userdata, len, str);
+ return false;
+}
+
+static void detectSt(FFTerminalFontResult* terminalFont, const FFTerminalResult* terminal) {
+ FF_STRBUF_AUTO_DESTROY size = ffStrbufCreateF("/proc/%u/cmdline", terminal->pid);
+ FF_STRBUF_AUTO_DESTROY font = ffStrbufCreate();
+ if (!ffAppendFileBuffer(size.chars, &font)) {
+ ffStrbufAppendF(&terminalFont->error, "Failed to open %s", size.chars);
+ return;
+ }
+
+ const char* p = memmem(font.chars, font.length, "\0-f", sizeof("\0-f")); // find parameter of `-f`
+ if (p) {
+ // st was executed with `-f` parameter
+ ffStrbufSubstrAfter(&font, (uint32_t) (p + (sizeof("\0-f") - 1) - font.chars));
+ ffStrbufRecalculateLength(&font);
+ } else {
+ ffStrbufClear(&font);
+
+ const char* error = ffBinaryExtractStrings(terminal->exePath.chars, extractStTermFont, &font, (uint32_t) strlen("size=0"));
+ if (error) {
+ ffStrbufAppendS(&terminalFont->error, error);
+ return;
+ }
+ if (font.length == 0) {
+ ffStrbufAppendS(&terminalFont->error, "No font config found in st binary");
+ return;
+ }
+ }
+
+ // JetBrainsMono Nerd Font Mono:pixelsize=12:antialias=true:autohint=true
+
+ uint32_t index = ffStrbufFirstIndexC(&font, ':');
+ if (index != font.length) {
+ uint32_t sIndex = ffStrbufNextIndexS(&font, index + 1, "size=");
+ if (sIndex != font.length) {
+ sIndex += (uint32_t) strlen("size=");
+ uint32_t sIndexEnd = ffStrbufNextIndexC(&font, sIndex, ':');
+ ffStrbufSetNS(&size, sIndexEnd - sIndex, font.chars + sIndex);
+ }
+ ffStrbufSubstrBefore(&font, index);
+ } else {
+ ffStrbufClear(&size);
+ }
+ ffFontInitValues(&terminalFont->font, font.chars, size.chars);
+}
+
+static void detectWarp(FFTerminalFontResult* terminalFont) {
+ FF_STRBUF_AUTO_DESTROY baseDir = ffStrbufCreateA(64);
+
+ FF_LIST_FOR_EACH (FFstrbuf, dirPrefix, instance.state.platform.configDirs) {
+ // We need to copy the dir each time, because it used by multiple threads, so we can't directly write to it.
+ ffStrbufSet(&baseDir, dirPrefix);
+ ffStrbufAppendS(&baseDir, "warp-terminal/user_preferences.json");
+
+ yyjson_doc* doc = yyjson_read_file(baseDir.chars, YYJSON_READ_INSITU | YYJSON_READ_ALLOW_TRAILING_COMMAS | YYJSON_READ_ALLOW_COMMENTS, NULL, NULL);
+ if (!doc) {
+ continue;
+ }
+
+ yyjson_val* prefs = yyjson_obj_get(yyjson_doc_get_root(doc), "prefs");
+ if (yyjson_is_obj(prefs)) {
+ const char* fontName = yyjson_get_str(yyjson_obj_get(prefs, "FontName"));
+ if (!fontName) {
+ fontName = "Hack";
+ }
+ const char* fontSize = yyjson_get_str(yyjson_obj_get(prefs, "FontSize"));
+ if (!fontSize) {
+ fontSize = "13";
+ }
+
+ ffFontInitValues(&terminalFont->font, fontName, fontSize);
+ }
+ yyjson_doc_free(doc);
+ return;
+ }
+}
+
+static void detectTerminator(FFTerminalFontResult* result) {
+ FF_STRBUF_AUTO_DESTROY useSystemFont = ffStrbufCreate();
+ FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();
+
+ if (!ffParsePropFileConfigValues("terminator/config", 2, (FFpropquery[]) {
+ { "use_system_font =", &useSystemFont },
+ { "font =", &fontName },
+ }) ||
+ ffStrbufIgnCaseEqualS(&useSystemFont, "True")) {
+ FF_AUTO_FREE const char* fontName = getSystemMonospaceFont();
+ if (ffStrSet(fontName)) {
+ ffFontInitPango(&result->font, fontName);
+ } else {
+ ffStrbufAppendS(&result->error, "Couldn't get system monospace font name from GSettings / DConf");
+ }
+ return;
+ }
+
+ if (fontName.length == 0) {
+ ffFontInitValues(&result->font, "Mono", "10");
+ } else {
+ ffFontInitPango(&result->font, fontName.chars);
+ }
+}
+
+static void detectWestonTerminal(FFTerminalFontResult* terminalFont) {
+ FF_STRBUF_AUTO_DESTROY font = ffStrbufCreate();
+ FF_STRBUF_AUTO_DESTROY size = ffStrbufCreate();
+ ffParsePropFileConfigValues("weston.ini", 2, (FFpropquery[]) {
+ { "font=", &font },
+ { "font-size=", &size },
+ });
+ if (!font.length) {
+ ffStrbufSetStatic(&font, "DejaVu Sans Mono");
+ }
+ if (!size.length) {
+ ffStrbufSetStatic(&size, "14");
+ }
+ ffFontInitValues(&terminalFont->font, font.chars, size.chars);
+}
+
+static void detectUrxvt(FFTerminalFontResult* terminalFont) {
+ FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
+
+ if (!(ffParsePropFileHomeValues(".Xresources", 1, (FFpropquery[]) {
+ { "URxvt.font:", &buffer },
+ }) ||
+ ffParsePropFileHomeValues(".Xdefaults", 1, (FFpropquery[]) {
+ { "URxvt.font:", &buffer },
+ }))) {
+ ffStrbufAppendS(&terminalFont->error, "Could not find URxvt.font in .Xresources or .Xdefaults");
+ return;
+ }
+
+ uint32_t index = 0;
+
+ char* line = NULL;
+ size_t len = 0;
+ while (ffStrbufGetdelim(&line, &len, ',', &buffer)) {
+ FFfont* font = index == 0 ? &terminalFont->font : &terminalFont->fallback;
+ if (line[0] == '-') {
+ ffFontInitXlfd(font, line);
+ } else if (ffStrStartsWith(line, "xft:")) {
+ ffFontInitXft(font, line + 4);
+ } else {
+ ffStrbufAppendF(&terminalFont->error, "Unknown URxvt font format: %s", line);
+ continue;
+ }
+ index++;
+ if (index > 1) {
+ break;
+ }
+ }
+}
+
+static bool detectCosmicTerm(FFTerminalFontResult* terminalFont) {
+ FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate();
+ FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate();
+
+ FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateCopy(&instance.state.platform.homeDir);
+ ffStrbufAppendS(&path, ".config/cosmic/com.system76.CosmicTerm/v1/");
+ uint32_t baseLen = path.length;
+
+ ffStrbufAppendS(&path, "font_name");
+ ffReadFileBuffer(path.chars, &fontName);
+ ffStrbufTrim(&path, '"');
+ ffStrbufSubstrBefore(&path, baseLen);
+ if (fontName.length == 0) {
+ ffStrbufSetStatic(&fontName, "Noto Sans Mono");
+ }
+
+ ffStrbufAppendS(&path, "font_size");
+ if (ffReadFileBuffer(path.chars, &fontSize)) {
+ ffStrbufAppendS(&fontSize, "px");
+ }
+ ffStrbufSubstrBefore(&path, baseLen);
+ if (fontSize.length == 0) {
+ ffStrbufSetStatic(&fontSize, "14px");
+ }
+
+ ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars);
+
+ return true;
+}
+
+#ifdef __HAIKU__
+static void detectHaikuTerminal(FFTerminalFontResult* terminalFont) {
+ FF_STRBUF_AUTO_DESTROY font = ffStrbufCreate();
+ FF_STRBUF_AUTO_DESTROY size = ffStrbufCreate();
+ ffParsePropFileConfigValues("Terminal/Default", 2, (FFpropquery[]) {
+ { "\"Half Font Family\" , ", &font },
+ { "\"Half Font Size\" , ", &size },
+ });
+ if (!font.length) {
+ ffStrbufSetStatic(&font, "Noto Sans Mono");
+ }
+ if (!size.length) {
+ ffStrbufSetStatic(&size, "12");
+ }
+ ffFontInitValues(&terminalFont->font, font.chars, size.chars);
+}
+#endif
+
+bool
+#ifdef __ANDROID__
+ffDetectTerminalFontPlatformLinux
+#else
+ffDetectTerminalFontPlatform
+#endif
+ (const FFTerminalResult* terminal, FFTerminalFontResult* terminalFont) {
+ if (ffStrbufIgnCaseEqualS(&terminal->processName, "konsole")) {
+ detectKonsole(terminalFont, "konsolerc");
+ } else if (ffStrbufIgnCaseEqualS(&terminal->processName, "yakuake")) {
+ detectKonsole(terminalFont, "yakuakerc");
+ } else if (ffStrbufIgnCaseEqualS(&terminal->processName, "xfce4-terminal")) {
+ detectXFCETerminal(terminalFont);
+ } else if (ffStrbufIgnCaseEqualS(&terminal->processName, "lxterminal")) {
+ detectFromConfigFile("lxterminal/lxterminal.conf", "fontname =", terminalFont);
+ } else if (ffStrbufIgnCaseEqualS(&terminal->processName, "tilix")) {
+ detectFromGSettings("/com/gexperts/Tilix/profiles/", "com.gexperts.Tilix.ProfilesList", "com.gexperts.Tilix.Profile", "default", terminalFont);
+ } else if (ffStrbufStartsWithIgnCaseS(&terminal->processName, "gnome-terminal")) {
+ detectFromGSettings("/org/gnome/terminal/legacy/profiles:/:", "org.gnome.Terminal.ProfilesList", "org.gnome.Terminal.Legacy.Profile", "default", terminalFont);
+ } else if (ffStrbufStartsWithIgnCaseS(&terminal->processName, "ptyxis-agent")) {
+ detectPtyxis(terminalFont);
+ } else if (ffStrbufIgnCaseEqualS(&terminal->processName, "kgx")) {
+ detectKgx(terminalFont);
+ } else if (ffStrbufIgnCaseEqualS(&terminal->processName, "mate-terminal")) {
+ detectFromGSettings("/org/mate/terminal/profiles/", "org.mate.terminal.global", "org.mate.terminal.profile", "default-profile", terminalFont);
+ } else if (ffStrbufIgnCaseEqualS(&terminal->processName, "deepin-terminal")) {
+ detectDeepinTerminal(terminalFont);
+ } else if (ffStrbufIgnCaseEqualS(&terminal->processName, "foot")) {
+ detectFootTerminal(terminalFont);
+ } else if (ffStrbufIgnCaseEqualS(&terminal->processName, "qterminal")) {
+ detectQTerminal(terminalFont);
+ } else if (ffStrbufIgnCaseEqualS(&terminal->processName, "xterm")) {
+ detectXterm(terminalFont);
+ } else if (ffStrbufIgnCaseEqualS(&terminal->processName, "st")) {
+ detectSt(terminalFont, terminal);
+ } else if (ffStrbufIgnCaseEqualS(&terminal->processName, "warp")) {
+ detectWarp(terminalFont);
+ } else if (ffStrbufIgnCaseEqualS(&terminal->processName, "weston-terminal")) {
+ detectWestonTerminal(terminalFont);
+ } else if (ffStrbufStartsWithIgnCaseS(&terminal->processName, "terminator")) {
+ detectTerminator(terminalFont);
+ } else if (ffStrbufStartsWithIgnCaseS(&terminal->processName, "sakura")) {
+ detectFromConfigFile("sakura/sakura.conf", "font=", terminalFont);
+ } else if (ffStrbufStartsWithIgnCaseS(&terminal->processName, "cosmic-term")) {
+ detectCosmicTerm(terminalFont);
+ }
+#ifdef __HAIKU__
+ else if (ffStrbufStartsWithIgnCaseS(&terminal->processName, "Terminal")) {
+ detectHaikuTerminal(terminalFont);
+ }
+#endif
+ else if (ffStrbufStartsWithIgnCaseS(&terminal->processName, "termite")) {
+ detectFromConfigFile("termite/config", "font =", terminalFont);
+ } else if (ffStrbufIgnCaseEqualS(&terminal->processName, "rxvt") || ffStrbufIgnCaseEqualS(&terminal->processName, "urxvt") || ffStrbufIgnCaseEqualS(&terminal->processName, "urxvtd")) {
+ detectUrxvt(terminalFont);
+ } else {
+ return false;
+ }
+ return true;
+}
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;
+}