From e0c31be9d5e8bd227307d50050850c51d80b93a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Sat, 11 Jul 2026 15:23:24 +0800 Subject: [PATCH 01/67] Battery: changes battery status to array type in custom format --- src/modules/battery/battery.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/modules/battery/battery.c b/src/modules/battery/battery.c index 61cef84bb4..d1bff71e23 100644 --- a/src/modules/battery/battery.c +++ b/src/modules/battery/battery.c @@ -121,36 +121,35 @@ static void printBattery(FFBatteryOptions* options, FFBatteryResult* result, uin ffDurationAppendNum((uint32_t) result->timeRemaining, &timeStr); } - FF_STRBUF_AUTO_DESTROY statusStr = ffStrbufCreate(); + FF_LIST_AUTO_DESTROY status = ffListCreate(); if (result->status & FF_BATTERY_STATUS_AC_CONNECTED) { - ffStrbufAppendS(&statusStr, "AC Connected, "); + ffStrbufInitStatic(FF_LIST_ADD(FFstrbuf, status), "AC Connected"); } if (result->status & FF_BATTERY_STATUS_USB_CONNECTED) { - ffStrbufAppendS(&statusStr, "USB Connected, "); + ffStrbufInitStatic(FF_LIST_ADD(FFstrbuf, status), "USB Connected"); } if (result->status & FF_BATTERY_STATUS_WIRELESS_CONNECTED) { - ffStrbufAppendS(&statusStr, "Wireless Connected, "); + ffStrbufInitStatic(FF_LIST_ADD(FFstrbuf, status), "Wireless Connected"); } if (result->status & FF_BATTERY_STATUS_CHARGING) { - ffStrbufAppendS(&statusStr, "Charging, "); + ffStrbufInitStatic(FF_LIST_ADD(FFstrbuf, status), "Charging"); } if (result->status & FF_BATTERY_STATUS_DISCHARGING) { - ffStrbufAppendS(&statusStr, "Discharging, "); + ffStrbufInitStatic(FF_LIST_ADD(FFstrbuf, status), "Discharging"); } if (result->status & FF_BATTERY_STATUS_CRITICAL) { - ffStrbufAppendS(&statusStr, "Critical, "); + ffStrbufInitStatic(FF_LIST_ADD(FFstrbuf, status), "Critical"); } if (result->status & FF_BATTERY_STATUS_UNKNOWN) { - ffStrbufAppendS(&statusStr, "Unknown, "); + ffStrbufInitStatic(FF_LIST_ADD(FFstrbuf, status), "Unknown"); } - ffStrbufSubstrBefore(&statusStr, statusStr.length - 2); // Remove last ", " FF_PRINT_FORMAT_CHECKED(key.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) { FF_ARG(result->manufacturer, "manufacturer"), FF_ARG(result->modelName, "model-name"), FF_ARG(result->technology, "technology"), FF_ARG(capacityNum, "capacity"), - FF_ARG(statusStr, "status"), + FF_ARG(status, "status"), FF_ARG(tempStr, "temperature"), FF_ARG(result->cycleCount, "cycle-count"), FF_ARG(result->serial, "serial"), From 317c0cfb86f6d8a56ac213b68f1baae36000af6a Mon Sep 17 00:00:00 2001 From: clice lee Date: Sat, 11 Jul 2026 23:57:42 -0400 Subject: [PATCH 02/67] TerminalFont (Ghostty): parse config files statically instead of exec'ing `ghostty +show-config` (#2122) --- src/detection/terminalfont/terminalfont.c | 119 +++++++++++++--------- 1 file changed, 73 insertions(+), 46 deletions(-) diff --git a/src/detection/terminalfont/terminalfont.c b/src/detection/terminalfont/terminalfont.c index e3b43a52a3..a5a9c801ff 100644 --- a/src/detection/terminalfont/terminalfont.c +++ b/src/detection/terminalfont/terminalfont.c @@ -3,7 +3,6 @@ #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) { @@ -67,14 +66,17 @@ static void detectAlacritty(FFTerminalFontResult* terminalFont) { ffFontInitMoveValues(&terminalFont->font, &fontFamily, &fontSize, &fontStyle); } -static bool parseGhosttyConfig(FFstrbuf* path, FFstrbuf* fontName, FFstrbuf* fontNameFallback, FFstrbuf* fontSize) { +// Maximum number of `config-file` directives to follow, guarding against runaway includes +#define FF_GHOSTTY_MAX_CONFIG_FILES 16 + +static void parseGhosttyConfig(const FFstrbuf* path, FFstrbuf* fontName, FFstrbuf* fontNameFallback, FFstrbuf* fontSize, FFlist* configFiles /* list of FFstrbuf */) { 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; + return; } char* line = nullptr; @@ -82,70 +84,95 @@ static bool parseGhosttyConfig(FFstrbuf* path, FFstrbuf* fontName, FFstrbuf* fon while (ffStrbufGetline(&line, &len, &buffer)) { if (ffParsePropLine(line, "font-family =", &temp)) { FF_DEBUG("found font-family='%s' in %s", temp.chars, path->chars); + // Latter overrides former; former becomes the fallback font if (fontName->length > 0) { ffStrbufDestroy(fontNameFallback); ffStrbufInitMove(fontNameFallback, fontName); } ffStrbufDestroy(fontName); ffStrbufInitMove(fontName, &temp); - } else if (ffParsePropLine(line, "font-size =", fontSize)) { + } else if (ffParsePropLine(line, "font-size =", &temp)) { FF_DEBUG("found font-size='%s' in %s", temp.chars, path->chars); // Latter overrides former ffStrbufDestroy(fontSize); ffStrbufInitMove(fontSize, &temp); + } else if (ffParsePropLine(line, "config-file =", &temp)) { + // Doc: https://ghostty.org/docs/config/reference#config-file + // A leading `?` suppresses errors if the file doesn't exist; missing files are skipped here either way + ffStrbufTrimLeft(&temp, '?'); + ffStrbufTrimLeft(&temp, '"'); + ffStrbufTrimRight(&temp, '"'); + if (temp.length == 0) { + continue; + } + + if (!ffStrbufStartsWithC(&temp, '/')) { + // Relative paths are relative to the file containing the `config-file` directive + FF_STRBUF_AUTO_DESTROY absolutePath = ffStrbufCreateCopy(path); + ffStrbufSubstrBeforeLastC(&absolutePath, '/'); + ffStrbufAppendC(&absolutePath, '/'); + ffStrbufAppend(&absolutePath, &temp); + ffStrbufDestroy(&temp); + ffStrbufInitMove(&temp, &absolutePath); + } + + // Each unique file is only loaded once, which also prevents include cycles + bool loaded = ffStrbufEqual(&temp, path); + if (!loaded) { + FF_LIST_FOR_EACH(FFstrbuf, it, *configFiles) { + if (ffStrbufEqual(it, &temp)) { + loaded = true; + break; + } + } + } + + if (loaded) { + FF_DEBUG("config-file '%s' was already loaded, skipping to avoid cycles", temp.chars); + } else if (configFiles->length >= FF_GHOSTTY_MAX_CONFIG_FILES) { + FF_DEBUG("too many config-file directives, ignoring '%s'", temp.chars); + } else { + FF_DEBUG("found config-file='%s' in %s", temp.chars, path->chars); + ffStrbufInitMove(FF_LIST_ADD(FFstrbuf, *configFiles), &temp); + } + ffStrbufClear(&temp); } } - return true; } -static void detectGhostty(const FFstrbuf* exe, FFTerminalFontResult* terminalFont, const char* configPathMac, const char* configPathUnix) { +static void detectGhostty(FFTerminalFontResult* terminalFont, [[maybe_unused]] 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(); + FF_LIST_AUTO_DESTROY configFiles = ffListCreate(); // list of FFstrbuf + + // Ghostty loads the XDG config first, then (on macOS) the Application Support config, + // so values in the latter override the former + 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, &configFiles); + } - 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); + ffStrbufSet(&configPath, &instance.state.platform.homeDir); + ffStrbufAppendS(&configPath, "Library/Application Support/"); + ffStrbufAppendS(&configPath, configPathMac); // com.mitchellh.ghostty/config + parseGhosttyConfig(&configPath, &fontName, &fontNameFallback, &fontSize, &configFiles); #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", - nullptr, - }); - if (error == nullptr) { - char* line = nullptr; - 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); - } + // Files referenced by `config-file` don't take effect until the whole configuration is loaded, + // so they are parsed after all root config files, in the order they were found + for (uint32_t i = 0; i < configFiles.length; ++i) { + // Copy the path as parseGhosttyConfig may grow the list, invalidating pointers into it + ffStrbufSet(&configPath, FF_LIST_GET(FFstrbuf, configFiles, i)); + parseGhosttyConfig(&configPath, &fontName, &fontNameFallback, &fontSize, &configFiles); + } + + FF_LIST_FOR_EACH(FFstrbuf, it, configFiles) { + ffStrbufDestroy(it); } if (fontName.length == 0) { @@ -365,9 +392,9 @@ static bool detectTerminalFontCommon(const FFTerminalResult* terminal, FFTermina } else if (ffStrbufStartsWithIgnCaseS(&terminal->processName, "contour")) { detectContour(&terminal->exe, terminalFont); } else if (ffStrbufStartsWithIgnCaseS(&terminal->processName, "ghostty")) { - detectGhostty(&terminal->exe, terminalFont, nullptr, nullptr); + detectGhostty(terminalFont, "com.mitchellh.ghostty/config", "ghostty/config"); } else if (ffStrbufStartsWithIgnCaseS(&terminal->processName, "Muxy")) { - detectGhostty(&terminal->exe, terminalFont, "Muxy/ghostty.conf", "muxy/ghostty.conf"); + detectGhostty(terminalFont, "Muxy/ghostty.conf", "muxy/ghostty.conf"); } else if (ffStrbufStartsWithIgnCaseS(&terminal->processName, "rio")) { detectRio(terminalFont); } From 773e3ea497a38b3cfd8d92d5abcfa320368f37e1 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Mon, 13 Jul 2026 10:03:15 +0800 Subject: [PATCH 03/67] TerminalFont: small code cleanups --- src/detection/terminalfont/terminalfont.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/detection/terminalfont/terminalfont.c b/src/detection/terminalfont/terminalfont.c index a5a9c801ff..db0f61b4dc 100644 --- a/src/detection/terminalfont/terminalfont.c +++ b/src/detection/terminalfont/terminalfont.c @@ -66,10 +66,10 @@ static void detectAlacritty(FFTerminalFontResult* terminalFont) { ffFontInitMoveValues(&terminalFont->font, &fontFamily, &fontSize, &fontStyle); } -// Maximum number of `config-file` directives to follow, guarding against runaway includes -#define FF_GHOSTTY_MAX_CONFIG_FILES 16 - static void parseGhosttyConfig(const FFstrbuf* path, FFstrbuf* fontName, FFstrbuf* fontNameFallback, FFstrbuf* fontSize, FFlist* configFiles /* list of FFstrbuf */) { + // Maximum number of `config-file` directives to follow, guarding against runaway includes + enum { FF_GHOSTTY_MAX_CONFIG_FILES = 16 }; + FF_DEBUG("parsing config: %s", path->chars); FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); @@ -100,8 +100,7 @@ static void parseGhosttyConfig(const FFstrbuf* path, FFstrbuf* fontName, FFstrbu // Doc: https://ghostty.org/docs/config/reference#config-file // A leading `?` suppresses errors if the file doesn't exist; missing files are skipped here either way ffStrbufTrimLeft(&temp, '?'); - ffStrbufTrimLeft(&temp, '"'); - ffStrbufTrimRight(&temp, '"'); + ffStrbufTrim(&temp, '"'); if (temp.length == 0) { continue; } From eeae6019242350752575d78f007be491f934b747 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Mon, 13 Jul 2026 09:19:56 +0800 Subject: [PATCH 04/67] Global: silences compiler warnings --- src/detection/opengl/opengl_shared.c | 2 +- src/logo/image/im6.c | 3 +++ src/logo/image/im7.c | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/detection/opengl/opengl_shared.c b/src/detection/opengl/opengl_shared.c index 2c6cf74f3f..b3fc2309bc 100644 --- a/src/detection/opengl/opengl_shared.c +++ b/src/detection/opengl/opengl_shared.c @@ -184,7 +184,7 @@ const char* ffOpenGLDetectByEGL(FFOpenGLResult* result) { } if (ffeglGetPlatformDisplay) { FF_DEBUG("Trying eglGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA)"); - display = ffeglGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA, EGL_DEFAULT_DISPLAY, nullptr); + display = ffeglGetPlatformDisplay(EGL_PLATFORM_SURFACELESS_MESA, nullptr /*EGL_DEFAULT_DISPLAY*/, nullptr); FF_DEBUG("eglGetPlatformDisplay() %s", display == EGL_NO_DISPLAY ? "failed" : "succeeded"); } else { FF_DEBUG("eglGetPlatformDisplay is unavailable, falling back to eglGetDisplay"); diff --git a/src/logo/image/im6.c b/src/logo/image/im6.c index 9fb4338c52..9450123550 100644 --- a/src/logo/image/im6.c +++ b/src/logo/image/im6.c @@ -3,7 +3,10 @@ #include "image.h" #include "common/library.h" + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wimplicit-int-float-conversion" #include + #pragma GCC diagnostic pop static FF_LIBRARY_SYMBOL(ResizeImage) diff --git a/src/logo/image/im7.c b/src/logo/image/im7.c index 967e79ea09..87b60c45f3 100644 --- a/src/logo/image/im7.c +++ b/src/logo/image/im7.c @@ -3,7 +3,10 @@ #include "image.h" #include "common/library.h" + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wimplicit-int-float-conversion" #include + #pragma GCC diagnostic pop static FF_LIBRARY_SYMBOL(ResizeImage) From 98c8c5b3449457efd163089a3392c91f58496381 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Mon, 13 Jul 2026 10:18:01 +0800 Subject: [PATCH 05/67] CMake (Linux): detects MUSL automatically --- CMakeLists.txt | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 56ef392e0b..59e46817af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -182,6 +182,26 @@ endif() set(WARNING_FLAGS "-Wall -Wextra -Wconversion -Werror=uninitialized -Werror=return-type -Werror=vla") +if(LINUX) + if(NOT IS_MUSL) # `NOT DEFINED IS_MUSL` doesn't work + execute_process( + COMMAND ${CMAKE_C_COMPILER} -print-file-name=libc.so + OUTPUT_VARIABLE LIBC_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + + if(LIBC_PATH MATCHES "-musl") + set(IS_MUSL ON CACHE BOOL "Build with musl libc" FORCE) + endif() + endif() + + if(IS_MUSL) + message(STATUS "MUSL libc detected") + # Silence ioctl related warnings + set(WARNING_FLAGS "${WARNING_FLAGS} -Wno-sign-conversion") + endif() +endif() + set(CMAKE_C_STANDARD 23) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS} -Werror=incompatible-pointer-types -Werror=implicit-function-declaration -Werror=int-conversion") From 0b3ef5d35c27724d033188eb6156e5eb91c4f821 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Mon, 13 Jul 2026 10:35:03 +0800 Subject: [PATCH 06/67] CMake: adds comments --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 59e46817af..a4c454dfa8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -202,6 +202,9 @@ if(LINUX) endif() endif() +# Minimal compiler version supported: GCC 13 (2023-04-26), Clang 16 (2023-03-17) +# Most C23 features are allowed with noticeable exceptions of `constexpr` and `#embed` +# See https://cppreference.com/c/compiler_support/23 for detail set(CMAKE_C_STANDARD 23) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS} -Werror=incompatible-pointer-types -Werror=implicit-function-declaration -Werror=int-conversion") From 8ca6bb084db9d1a8c3e34f66df00802b161f1b18 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 14 Jul 2026 10:50:36 +0800 Subject: [PATCH 07/67] Common (Library): adds FF_LIBRARY_LOAD_SYMBOL_VAR_LAZY --- src/common/library.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/common/library.h b/src/common/library.h index 872bfc9fa6..06003cc938 100644 --- a/src/common/library.h +++ b/src/common/library.h @@ -57,6 +57,9 @@ static inline void ffLibraryUnload(void** handle) { #define FF_LIBRARY_LOAD_SYMBOL_VAR(library, varName, symbolName, returnValue) \ FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, (varName).ff##symbolName, symbolName, returnValue); + #define FF_LIBRARY_LOAD_SYMBOL_VAR_LAZY(library, varName, symbolName) \ + (varName).ff##symbolName = (typeof(&symbolName)) dlsym(library, #symbolName); + #define FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(library, varName, symbolName) \ FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, (varName).ff##symbolName, symbolName, "dlsym " #symbolName " failed"); @@ -94,6 +97,9 @@ void* ffLibraryLoadMulti(const char* path, int maxVersion, ...); #define FF_LIBRARY_LOAD_SYMBOL_VAR(library, varName, symbolName, returnValue) \ FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, (varName).ff##symbolName, symbolName, returnValue); + #define FF_LIBRARY_LOAD_SYMBOL_VAR_LAZY(library, varName, symbolName) \ + (varName).ff##symbolName = (typeof(&symbolName)) &symbolName; + #define FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(library, varName, symbolName) \ FF_LIBRARY_LOAD_SYMBOL_ADDRESS(library, (varName).ff##symbolName, symbolName, "dlsym " #symbolName " failed"); From 29d626f41d25de8e917aff587e5d660313f47283 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 14 Jul 2026 10:56:44 +0800 Subject: [PATCH 08/67] DisplayServer (Linux): wait for delayed Wayland output events Ref: #2451 --- .../linux/wayland/global-output.c | 14 ++++++- .../displayserver/linux/wayland/kde-output.c | 14 ++++++- .../displayserver/linux/wayland/wayland.c | 39 +++++++++++++++++++ .../displayserver/linux/wayland/wayland.h | 9 +++++ 4 files changed, 74 insertions(+), 2 deletions(-) diff --git a/src/detection/displayserver/linux/wayland/global-output.c b/src/detection/displayserver/linux/wayland/global-output.c index e3c8018536..573f23f903 100644 --- a/src/detection/displayserver/linux/wayland/global-output.c +++ b/src/detection/displayserver/linux/wayland/global-output.c @@ -25,6 +25,11 @@ static void waylandOutputScaleListener(void* data, [[maybe_unused]] struct wl_ou display->dpi = 96 * (uint32_t) scale; } +static void waylandOutputDoneListener(void* data, [[maybe_unused]] struct wl_output* output) { + WaylandDisplay* display = data; + display->done = true; +} + static void waylandOutputGeometryListener(void* data, [[maybe_unused]] struct wl_output* output, [[maybe_unused]] int32_t x, @@ -54,7 +59,7 @@ static void handleXdgLogicalSize(void* data, [[maybe_unused]] struct zxdg_output static void* outputListener[] = { waylandOutputGeometryListener, // geometry waylandOutputModeListener, // mode - stubListener, // done + waylandOutputDoneListener, // done waylandOutputScaleListener, // scale ffWaylandOutputNameListener, // name ffWaylandOutputDescriptionListener, // description @@ -122,6 +127,13 @@ const char* ffWaylandHandleGlobalOutput(WaylandData* wldata, struct wl_registry* wldata->ffwl_proxy_destroy(output); return "Failed to roundtrip wl_output"; } + if (bindVersion >= WL_OUTPUT_DONE_SINCE_VERSION && !display.done) { + const char* error = ffWaylandWaitForDone(&display); + if (error) { + wldata->ffwl_proxy_destroy(output); + return error; + } + } if (wldata->zxdgOutputManager) { uint32_t bindVersion = min(version, ZXDG_OUTPUT_V1_DESCRIPTION_SINCE_VERSION); diff --git a/src/detection/displayserver/linux/wayland/kde-output.c b/src/detection/displayserver/linux/wayland/kde-output.c index d26cfbc688..0464ab4370 100644 --- a/src/detection/displayserver/linux/wayland/kde-output.c +++ b/src/detection/displayserver/linux/wayland/kde-output.c @@ -147,11 +147,16 @@ static void waylandKdePriorityListener(void* data, [[maybe_unused]] struct kde_o display->primary = priority == 1; } +static void waylandKdeDoneListener(void* data, [[maybe_unused]] struct kde_output_device_v2* kde_output_device_v2) { + WaylandDisplay* display = data; + display->done = true; +} + static struct kde_output_device_v2_listener outputListener = { .geometry = waylandKdeGeometryListener, .current_mode = waylandKdeCurrentModeListener, .mode = waylandKdeModeListener, - .done = (void*) stubListener, + .done = waylandKdeDoneListener, .scale = waylandKdeScaleListener, .edid = waylandKdeEdidListener, .enabled = waylandKdeEnabledListener, @@ -211,6 +216,13 @@ static const char* waylandKdeHandleOutput(WaylandData* wldata, struct wl_proxy* wldata->ffwl_proxy_destroy(output); return "Failed to roundtrip kde_output_device_v2"; } + if (!display.done) { + const char* error = ffWaylandWaitForDone(&display); + if (error) { + wldata->ffwl_proxy_destroy(output); + return error; + } + } // Destroy any mode proxies that were created during the listeners. // wl proxies created for modes are not automatically freed by destroying // the parent output proxy, so destroy them explicitly to avoid leaks. diff --git a/src/detection/displayserver/linux/wayland/wayland.c b/src/detection/displayserver/linux/wayland/wayland.c index 7e37aee8dd..6b9c41b697 100644 --- a/src/detection/displayserver/linux/wayland/wayland.c +++ b/src/detection/displayserver/linux/wayland/wayland.c @@ -217,6 +217,39 @@ uint32_t ffWaylandHandleRotation(WaylandDisplay* display) { return rotation; } +const char* ffWaylandWaitForDone(WaylandDisplay* display) { + int32_t timeout = instance.config.general.processingTimeout; + struct timespec ts; + if (timeout == 0) { + return "timeout is disabled"; + } else if (timeout > 0) { + if (!display->parent->ffwl_display_dispatch_timeout) { + return "timeout is enabled, but the libwayland-client version is too old to support it"; + } + ts.tv_sec = timeout / 1000; + ts.tv_nsec = (timeout % 1000) * 1000000; + } + + WaylandData* wldata = display->parent; + + // Some compositors emit the final output state asynchronously after the roundtrip callback (#2074) + while (!display->done) { + if (timeout > 0) { + if (wldata->ffwl_display_dispatch_timeout(wldata->display, &ts) < 0) { + return "wl_display_dispatch_timeout() failed"; + } + } else if (timeout < 0) { + if (wldata->ffwl_display_dispatch(wldata->display) < 0) { + return "wl_display_dispatch() failed"; + } + } else { + return "Timeout exceeded, but the compositor did not send a done event"; + } + } + + return nullptr; +} + const char* ffdsConnectWayland(FFDisplayServerResult* result) { if (getenv("XDG_RUNTIME_DIR") == nullptr) { return "Wayland requires $XDG_RUNTIME_DIR being set"; @@ -236,6 +269,12 @@ const char* ffdsConnectWayland(FFDisplayServerResult* result) { FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(wayland, data, wl_proxy_destroy) FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(wayland, data, wl_display_roundtrip) + if (instance.config.general.processingTimeout < 0) { + FF_LIBRARY_LOAD_SYMBOL_VAR_LAZY(wayland, data, wl_display_dispatch) + } else if (instance.config.general.processingTimeout > 0) { + FF_LIBRARY_LOAD_SYMBOL_VAR_LAZY(wayland, data, wl_display_dispatch_timeout) + } + data.display = ffwl_display_connect(nullptr); if (data.display == nullptr) { return "wl_display_connect returned nullptr"; diff --git a/src/detection/displayserver/linux/wayland/wayland.h b/src/detection/displayserver/linux/wayland/wayland.h index a661936cf0..257498c0f0 100644 --- a/src/detection/displayserver/linux/wayland/wayland.h +++ b/src/detection/displayserver/linux/wayland/wayland.h @@ -20,12 +20,19 @@ typedef enum WaylandProtocolType: uint8_t { FF_WAYLAND_PROTOCOL_TYPE_KDE_REGISTRY, } WaylandProtocolType; +// In case users have an ancient version of libwayland-client +int wl_display_dispatch_timeout(struct wl_display *display, const struct timespec *timeout); + typedef struct WaylandData { FFDisplayServerResult* result; FF_LIBRARY_SYMBOL(wl_proxy_marshal_constructor_versioned) FF_LIBRARY_SYMBOL(wl_proxy_add_listener) FF_LIBRARY_SYMBOL(wl_proxy_destroy) FF_LIBRARY_SYMBOL(wl_display_roundtrip) + union { + FF_LIBRARY_SYMBOL(wl_display_dispatch) + FF_LIBRARY_SYMBOL(wl_display_dispatch_timeout) + }; struct wl_display* display; WaylandProtocolType protocolType; struct wl_proxy* zxdgOutputManager; @@ -58,6 +65,7 @@ typedef struct WaylandDisplay { FFstrbuf serial; uint8_t bitDepth; bool primary; + bool done; } WaylandDisplay; inline static void stubListener(void* data, ...) { @@ -79,6 +87,7 @@ void ffWaylandOutputNameListener(void* data, [[maybe_unused]] void* output, cons void ffWaylandOutputDescriptionListener(void* data, [[maybe_unused]] void* output, const char* description); // Modifies content of display. Don't call this function when calling ffdsAppendDisplay uint32_t ffWaylandHandleRotation(WaylandDisplay* display); +const char* ffWaylandWaitForDone(WaylandDisplay* display); const char* ffWaylandHandleGlobalOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version); const char* ffWaylandHandleKdeOutputRegistry(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version); From 1c352bb4dec120bdd4ec41ac8cc65dea87049608 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 14 Jul 2026 11:03:00 +0800 Subject: [PATCH 09/67] DisplayServer (Linux): removes an old hack --- .../linux/wayland/global-output.c | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/detection/displayserver/linux/wayland/global-output.c b/src/detection/displayserver/linux/wayland/global-output.c index 573f23f903..d92d055d72 100644 --- a/src/detection/displayserver/linux/wayland/global-output.c +++ b/src/detection/displayserver/linux/wayland/global-output.c @@ -54,19 +54,14 @@ static void handleXdgLogicalSize(void* data, [[maybe_unused]] struct zxdg_output } } -// Dirty hack for #477 -// The order of these callbacks MUST follow `struct wl_output_listener` -static void* outputListener[] = { - waylandOutputGeometryListener, // geometry - waylandOutputModeListener, // mode - waylandOutputDoneListener, // done - waylandOutputScaleListener, // scale - ffWaylandOutputNameListener, // name - ffWaylandOutputDescriptionListener, // description +static struct wl_output_listener outputListener = { + .geometry = waylandOutputGeometryListener, + .mode = waylandOutputModeListener, + .done = waylandOutputDoneListener, + .scale = waylandOutputScaleListener, + .name = (void*) ffWaylandOutputNameListener, + .description = (void*) ffWaylandOutputDescriptionListener, }; -static_assert( - sizeof(outputListener) >= sizeof(struct wl_output_listener), - "sizeof(outputListener) is too small. Please report it to fastfetch github issue"); static struct zxdg_output_v1_listener zxdgOutputListener = { .logical_position = (void*) stubListener, From a0fd59d869465b63a981ff377a2ded5bb9b9dbd8 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 14 Jul 2026 11:05:17 +0800 Subject: [PATCH 10/67] DisplayServer (Linux): don't try to inline functions with variable arguments --- .../linux/wayland/global-output.c | 24 +++---- .../displayserver/linux/wayland/kde-output.c | 64 +++++++++---------- .../displayserver/linux/wayland/wayland.c | 6 +- .../displayserver/linux/wayland/wayland.h | 4 +- 4 files changed, 50 insertions(+), 48 deletions(-) diff --git a/src/detection/displayserver/linux/wayland/global-output.c b/src/detection/displayserver/linux/wayland/global-output.c index d92d055d72..26553a2dd0 100644 --- a/src/detection/displayserver/linux/wayland/global-output.c +++ b/src/detection/displayserver/linux/wayland/global-output.c @@ -64,9 +64,9 @@ static struct wl_output_listener outputListener = { }; static struct zxdg_output_v1_listener zxdgOutputListener = { - .logical_position = (void*) stubListener, + .logical_position = (void*) ffWaylandStubListener, .logical_size = handleXdgLogicalSize, - .done = (void*) stubListener, + .done = (void*) ffWaylandStubListener, .name = (void*) ffWaylandOutputNameListener, .description = (void*) ffWaylandOutputDescriptionListener, }; @@ -84,17 +84,17 @@ static void handleWpTfNamed(void *data, [[maybe_unused]] struct wp_image_descrip } static const struct wp_image_description_info_v1_listener wpImageDescInfoListener = { - .done = (void*) stubListener, - .icc_file = (void*) stubListener, - .primaries = (void*) stubListener, - .primaries_named = (void*) stubListener, - .tf_power = (void*) stubListener, + .done = (void*) ffWaylandStubListener, + .icc_file = (void*) ffWaylandStubListener, + .primaries = (void*) ffWaylandStubListener, + .primaries_named = (void*) ffWaylandStubListener, + .tf_power = (void*) ffWaylandStubListener, .tf_named = (void*) handleWpTfNamed, - .luminances = (void*) stubListener, - .target_primaries = (void*) stubListener, - .target_luminance = (void*) stubListener, - .target_max_cll = (void*) stubListener, - .target_max_fall = (void*) stubListener, + .luminances = (void*) ffWaylandStubListener, + .target_primaries = (void*) ffWaylandStubListener, + .target_luminance = (void*) ffWaylandStubListener, + .target_max_cll = (void*) ffWaylandStubListener, + .target_max_fall = (void*) ffWaylandStubListener, }; const char* ffWaylandHandleGlobalOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) { diff --git a/src/detection/displayserver/linux/wayland/kde-output.c b/src/detection/displayserver/linux/wayland/kde-output.c index 0464ab4370..602827d302 100644 --- a/src/detection/displayserver/linux/wayland/kde-output.c +++ b/src/detection/displayserver/linux/wayland/kde-output.c @@ -33,8 +33,8 @@ static const struct kde_output_device_mode_v2_listener modeListener = { .size = waylandKdeModeSizeListener, .refresh = waylandKdeModeRefreshListener, .preferred = waylandKdeModePreferredListener, - .removed = (void*) stubListener, - .flags = (void*) stubListener, + .removed = (void*) ffWaylandStubListener, + .flags = (void*) ffWaylandStubListener, }; static void waylandKdeModeListener(void* data, [[maybe_unused]] struct kde_output_device_v2* _, struct kde_output_device_mode_v2* mode) { @@ -160,39 +160,39 @@ static struct kde_output_device_v2_listener outputListener = { .scale = waylandKdeScaleListener, .edid = waylandKdeEdidListener, .enabled = waylandKdeEnabledListener, - .uuid = (void*) stubListener, - .serial_number = (void*) stubListener, - .eisa_id = (void*) stubListener, - .capabilities = (void*) stubListener, - .overscan = (void*) stubListener, - .vrr_policy = (void*) stubListener, - .rgb_range = (void*) stubListener, + .uuid = (void*) ffWaylandStubListener, + .serial_number = (void*) ffWaylandStubListener, + .eisa_id = (void*) ffWaylandStubListener, + .capabilities = (void*) ffWaylandStubListener, + .overscan = (void*) ffWaylandStubListener, + .vrr_policy = (void*) ffWaylandStubListener, + .rgb_range = (void*) ffWaylandStubListener, .name = waylandKdeNameListener, .high_dynamic_range = waylandKdeHdrListener, - .sdr_brightness = (void*) stubListener, - .wide_color_gamut = (void*) stubListener, - .auto_rotate_policy = (void*) stubListener, - .icc_profile_path = (void*) stubListener, - .brightness_metadata = (void*) stubListener, - .brightness_overrides = (void*) stubListener, - .sdr_gamut_wideness = (void*) stubListener, - .color_profile_source = (void*) stubListener, - .brightness = (void*) stubListener, - .color_power_tradeoff = (void*) stubListener, - .dimming = (void*) stubListener, - .replication_source = (void*) stubListener, - .ddc_ci_allowed = (void*) stubListener, + .sdr_brightness = (void*) ffWaylandStubListener, + .wide_color_gamut = (void*) ffWaylandStubListener, + .auto_rotate_policy = (void*) ffWaylandStubListener, + .icc_profile_path = (void*) ffWaylandStubListener, + .brightness_metadata = (void*) ffWaylandStubListener, + .brightness_overrides = (void*) ffWaylandStubListener, + .sdr_gamut_wideness = (void*) ffWaylandStubListener, + .color_profile_source = (void*) ffWaylandStubListener, + .brightness = (void*) ffWaylandStubListener, + .color_power_tradeoff = (void*) ffWaylandStubListener, + .dimming = (void*) ffWaylandStubListener, + .replication_source = (void*) ffWaylandStubListener, + .ddc_ci_allowed = (void*) ffWaylandStubListener, .max_bits_per_color = (void*) waylandKdeMaxBitsPerColorListener, - .max_bits_per_color_range = (void*) stubListener, - .automatic_max_bits_per_color_limit = (void*) stubListener, - .edr_policy = (void*) stubListener, - .sharpness = (void*) stubListener, + .max_bits_per_color_range = (void*) ffWaylandStubListener, + .automatic_max_bits_per_color_limit = (void*) ffWaylandStubListener, + .edr_policy = (void*) ffWaylandStubListener, + .sharpness = (void*) ffWaylandStubListener, .priority = waylandKdePriorityListener, - .auto_brightness = (void*) stubListener, - .removed = (void*) stubListener, - .hdr_icc_profile_path = (void*) stubListener, - .hdr_color_profile_source = (void*) stubListener, - .abm_level = (void*) stubListener, + .auto_brightness = (void*) ffWaylandStubListener, + .removed = (void*) ffWaylandStubListener, + .hdr_icc_profile_path = (void*) ffWaylandStubListener, + .hdr_color_profile_source = (void*) ffWaylandStubListener, + .abm_level = (void*) ffWaylandStubListener, }; static const char* waylandKdeHandleOutput(WaylandData* wldata, struct wl_proxy* output) { @@ -300,7 +300,7 @@ static void waylandKdeOutputListener(void* data, [[maybe_unused]] struct kde_out static struct kde_output_device_registry_v2_listener registryListener = { .output = waylandKdeOutputListener, - .finished = (void*) stubListener, + .finished = (void*) ffWaylandStubListener, }; const char* ffWaylandHandleKdeOutputRegistry(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) { diff --git a/src/detection/displayserver/linux/wayland/wayland.c b/src/detection/displayserver/linux/wayland/wayland.c index 6b9c41b697..c1beb69118 100644 --- a/src/detection/displayserver/linux/wayland/wayland.c +++ b/src/detection/displayserver/linux/wayland/wayland.c @@ -292,7 +292,7 @@ const char* ffdsConnectWayland(FFDisplayServerResult* result) { struct wl_registry_listener registry_listener = { .global = waylandGlobalAddListener, - .global_remove = (void*) stubListener + .global_remove = (void*) ffWaylandStubListener }; data.ffwl_proxy_add_listener(registry, (void (**)(void)) ®istry_listener, &data); @@ -378,6 +378,10 @@ const char* ffdsConnectWayland(FFDisplayServerResult* result) { return nullptr; } +void ffWaylandStubListener(...) { + // no-op +} + #else const char* ffdsConnectWayland([[maybe_unused]] FFDisplayServerResult* result) { diff --git a/src/detection/displayserver/linux/wayland/wayland.h b/src/detection/displayserver/linux/wayland/wayland.h index 257498c0f0..b7d486cce9 100644 --- a/src/detection/displayserver/linux/wayland/wayland.h +++ b/src/detection/displayserver/linux/wayland/wayland.h @@ -68,9 +68,7 @@ typedef struct WaylandDisplay { bool done; } WaylandDisplay; -inline static void stubListener(void* data, ...) { - (void) data; -} +void ffWaylandStubListener(...); inline static uint64_t ffWaylandGenerateIdFromName(const char* name) { uint64_t id = 0; From 7dcc7d40d54e40003c92c8dc3084c0fdc9aff30a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Tue, 14 Jul 2026 14:36:20 +0800 Subject: [PATCH 11/67] InitSystem (Windows): adds support Note: Windows doesn't have a unix-like init system process, but it does have the first userland process, which is `smss.exe` --- CMakeLists.txt | 2 +- src/detection/initsystem/initsystem_windows.c | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 src/detection/initsystem/initsystem_windows.c diff --git a/CMakeLists.txt b/CMakeLists.txt index a4c454dfa8..a38f812a98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1123,7 +1123,7 @@ elseif(WIN32) src/detection/gpu/gpu_windows.cpp src/detection/host/host_windows.c src/detection/icons/icons_windows.c - src/detection/initsystem/initsystem_nosupport.c + src/detection/initsystem/initsystem_windows.c src/detection/keyboard/keyboard_windows.c src/detection/libc/libc_windows.cpp src/detection/lm/lm_nosupport.c diff --git a/src/detection/initsystem/initsystem_windows.c b/src/detection/initsystem/initsystem_windows.c new file mode 100644 index 0000000000..4b4e647728 --- /dev/null +++ b/src/detection/initsystem/initsystem_windows.c @@ -0,0 +1,38 @@ +#include "initsystem.h" +#include "common/windows/unicode.h" +#include "common/windows/nt.h" +#include "common/windows/version.h" + +#include +#include +#include + +const char* ffDetectInitSystem(FFInitSystemResult* result) { + // We only need to find the first user process, so 1024 entries should be enough + SYSTEM_PROCESS_INFORMATION buffer[1024] = {}; + ULONG size = sizeof(buffer); + NTSTATUS status = NtQuerySystemInformation(SystemProcessInformation, buffer, size, &size); + if (status != STATUS_INFO_LENGTH_MISMATCH && !NT_SUCCESS(status)) { + return "NtQuerySystemInformation(SystemProcessInformation) failed"; + } + + for (SYSTEM_PROCESS_INFORMATION* ptr = buffer; ; ptr = (SYSTEM_PROCESS_INFORMATION*) ((uint8_t*) ptr + ptr->NextEntryOffset)) { + assert(ptr >= buffer && (uint8_t*) ptr < (uint8_t*) buffer + size); + uint16_t len = ptr->ImageName.Length / sizeof(*ptr->ImageName.Buffer); + if (ptr->InheritedFromUniqueProcessId == (HANDLE)(uintptr_t) 4 /* System */ && + len > 4 && _wcsnicmp(ptr->ImageName.Buffer + len - 4, L".exe", 4) == 0) { // smss.exe + result->pid = (uint32_t)(uintptr_t) ptr->UniqueProcessId; + // We have no permission to open the process for querying the full information + wchar_t exePath[MAX_PATH]; + _snwprintf(exePath, ARRAY_SIZE(exePath), L"%ls\\system32\\%.*ls", (const wchar_t*) SharedUserData->NtSystemRoot, len, ptr->ImageName.Buffer); + ffGetFileVersion(exePath, NULL, &result->version); + ffStrbufSetWS(&result->exe, exePath); + ffStrbufSetNWS(&result->name, len - 4, ptr->ImageName.Buffer); + return nullptr; + } + // The last process in the list always has a NextEntryOffset of 0, even if the buffer was truncated. + if (!ptr->NextEntryOffset) { + return "Could not find init system process"; + } + } +} From 2b03bacb282bad96a94b12ee33d3af69f1533a2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Tue, 14 Jul 2026 15:07:30 +0800 Subject: [PATCH 12/67] Global: further code cleanups --- src/common/unused.h | 6 +- .../linux/wayland/global-output.c | 24 +++---- .../displayserver/linux/wayland/kde-output.c | 64 +++++++++---------- .../displayserver/linux/wayland/wayland.c | 6 +- .../displayserver/linux/wayland/wayland.h | 2 - 5 files changed, 47 insertions(+), 55 deletions(-) diff --git a/src/common/unused.h b/src/common/unused.h index 98e21a2e6e..9eb9f7dca4 100644 --- a/src/common/unused.h +++ b/src/common/unused.h @@ -1,6 +1,4 @@ #pragma once -static inline void ffUnused(int dummy, ...) { - (void) dummy; -} -#define FF_UNUSED(...) ffUnused(0, __VA_ARGS__); +static inline void ffUnused(...) { /* no-op */ } +#define FF_UNUSED(...) ffUnused(__VA_ARGS__); diff --git a/src/detection/displayserver/linux/wayland/global-output.c b/src/detection/displayserver/linux/wayland/global-output.c index 26553a2dd0..6639a70c28 100644 --- a/src/detection/displayserver/linux/wayland/global-output.c +++ b/src/detection/displayserver/linux/wayland/global-output.c @@ -64,9 +64,9 @@ static struct wl_output_listener outputListener = { }; static struct zxdg_output_v1_listener zxdgOutputListener = { - .logical_position = (void*) ffWaylandStubListener, + .logical_position = (void*) ffUnused, .logical_size = handleXdgLogicalSize, - .done = (void*) ffWaylandStubListener, + .done = (void*) ffUnused, .name = (void*) ffWaylandOutputNameListener, .description = (void*) ffWaylandOutputDescriptionListener, }; @@ -84,17 +84,17 @@ static void handleWpTfNamed(void *data, [[maybe_unused]] struct wp_image_descrip } static const struct wp_image_description_info_v1_listener wpImageDescInfoListener = { - .done = (void*) ffWaylandStubListener, - .icc_file = (void*) ffWaylandStubListener, - .primaries = (void*) ffWaylandStubListener, - .primaries_named = (void*) ffWaylandStubListener, - .tf_power = (void*) ffWaylandStubListener, + .done = (void*) ffUnused, + .icc_file = (void*) ffUnused, + .primaries = (void*) ffUnused, + .primaries_named = (void*) ffUnused, + .tf_power = (void*) ffUnused, .tf_named = (void*) handleWpTfNamed, - .luminances = (void*) ffWaylandStubListener, - .target_primaries = (void*) ffWaylandStubListener, - .target_luminance = (void*) ffWaylandStubListener, - .target_max_cll = (void*) ffWaylandStubListener, - .target_max_fall = (void*) ffWaylandStubListener, + .luminances = (void*) ffUnused, + .target_primaries = (void*) ffUnused, + .target_luminance = (void*) ffUnused, + .target_max_cll = (void*) ffUnused, + .target_max_fall = (void*) ffUnused, }; const char* ffWaylandHandleGlobalOutput(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) { diff --git a/src/detection/displayserver/linux/wayland/kde-output.c b/src/detection/displayserver/linux/wayland/kde-output.c index 602827d302..ab8829cf48 100644 --- a/src/detection/displayserver/linux/wayland/kde-output.c +++ b/src/detection/displayserver/linux/wayland/kde-output.c @@ -33,8 +33,8 @@ static const struct kde_output_device_mode_v2_listener modeListener = { .size = waylandKdeModeSizeListener, .refresh = waylandKdeModeRefreshListener, .preferred = waylandKdeModePreferredListener, - .removed = (void*) ffWaylandStubListener, - .flags = (void*) ffWaylandStubListener, + .removed = (void*) ffUnused, + .flags = (void*) ffUnused, }; static void waylandKdeModeListener(void* data, [[maybe_unused]] struct kde_output_device_v2* _, struct kde_output_device_mode_v2* mode) { @@ -160,39 +160,39 @@ static struct kde_output_device_v2_listener outputListener = { .scale = waylandKdeScaleListener, .edid = waylandKdeEdidListener, .enabled = waylandKdeEnabledListener, - .uuid = (void*) ffWaylandStubListener, - .serial_number = (void*) ffWaylandStubListener, - .eisa_id = (void*) ffWaylandStubListener, - .capabilities = (void*) ffWaylandStubListener, - .overscan = (void*) ffWaylandStubListener, - .vrr_policy = (void*) ffWaylandStubListener, - .rgb_range = (void*) ffWaylandStubListener, + .uuid = (void*) ffUnused, + .serial_number = (void*) ffUnused, + .eisa_id = (void*) ffUnused, + .capabilities = (void*) ffUnused, + .overscan = (void*) ffUnused, + .vrr_policy = (void*) ffUnused, + .rgb_range = (void*) ffUnused, .name = waylandKdeNameListener, .high_dynamic_range = waylandKdeHdrListener, - .sdr_brightness = (void*) ffWaylandStubListener, - .wide_color_gamut = (void*) ffWaylandStubListener, - .auto_rotate_policy = (void*) ffWaylandStubListener, - .icc_profile_path = (void*) ffWaylandStubListener, - .brightness_metadata = (void*) ffWaylandStubListener, - .brightness_overrides = (void*) ffWaylandStubListener, - .sdr_gamut_wideness = (void*) ffWaylandStubListener, - .color_profile_source = (void*) ffWaylandStubListener, - .brightness = (void*) ffWaylandStubListener, - .color_power_tradeoff = (void*) ffWaylandStubListener, - .dimming = (void*) ffWaylandStubListener, - .replication_source = (void*) ffWaylandStubListener, - .ddc_ci_allowed = (void*) ffWaylandStubListener, + .sdr_brightness = (void*) ffUnused, + .wide_color_gamut = (void*) ffUnused, + .auto_rotate_policy = (void*) ffUnused, + .icc_profile_path = (void*) ffUnused, + .brightness_metadata = (void*) ffUnused, + .brightness_overrides = (void*) ffUnused, + .sdr_gamut_wideness = (void*) ffUnused, + .color_profile_source = (void*) ffUnused, + .brightness = (void*) ffUnused, + .color_power_tradeoff = (void*) ffUnused, + .dimming = (void*) ffUnused, + .replication_source = (void*) ffUnused, + .ddc_ci_allowed = (void*) ffUnused, .max_bits_per_color = (void*) waylandKdeMaxBitsPerColorListener, - .max_bits_per_color_range = (void*) ffWaylandStubListener, - .automatic_max_bits_per_color_limit = (void*) ffWaylandStubListener, - .edr_policy = (void*) ffWaylandStubListener, - .sharpness = (void*) ffWaylandStubListener, + .max_bits_per_color_range = (void*) ffUnused, + .automatic_max_bits_per_color_limit = (void*) ffUnused, + .edr_policy = (void*) ffUnused, + .sharpness = (void*) ffUnused, .priority = waylandKdePriorityListener, - .auto_brightness = (void*) ffWaylandStubListener, - .removed = (void*) ffWaylandStubListener, - .hdr_icc_profile_path = (void*) ffWaylandStubListener, - .hdr_color_profile_source = (void*) ffWaylandStubListener, - .abm_level = (void*) ffWaylandStubListener, + .auto_brightness = (void*) ffUnused, + .removed = (void*) ffUnused, + .hdr_icc_profile_path = (void*) ffUnused, + .hdr_color_profile_source = (void*) ffUnused, + .abm_level = (void*) ffUnused, }; static const char* waylandKdeHandleOutput(WaylandData* wldata, struct wl_proxy* output) { @@ -300,7 +300,7 @@ static void waylandKdeOutputListener(void* data, [[maybe_unused]] struct kde_out static struct kde_output_device_registry_v2_listener registryListener = { .output = waylandKdeOutputListener, - .finished = (void*) ffWaylandStubListener, + .finished = (void*) ffUnused, }; const char* ffWaylandHandleKdeOutputRegistry(WaylandData* wldata, struct wl_registry* registry, uint32_t name, uint32_t version) { diff --git a/src/detection/displayserver/linux/wayland/wayland.c b/src/detection/displayserver/linux/wayland/wayland.c index c1beb69118..29c1d46e9b 100644 --- a/src/detection/displayserver/linux/wayland/wayland.c +++ b/src/detection/displayserver/linux/wayland/wayland.c @@ -292,7 +292,7 @@ const char* ffdsConnectWayland(FFDisplayServerResult* result) { struct wl_registry_listener registry_listener = { .global = waylandGlobalAddListener, - .global_remove = (void*) ffWaylandStubListener + .global_remove = (void*) ffUnused }; data.ffwl_proxy_add_listener(registry, (void (**)(void)) ®istry_listener, &data); @@ -378,10 +378,6 @@ const char* ffdsConnectWayland(FFDisplayServerResult* result) { return nullptr; } -void ffWaylandStubListener(...) { - // no-op -} - #else const char* ffdsConnectWayland([[maybe_unused]] FFDisplayServerResult* result) { diff --git a/src/detection/displayserver/linux/wayland/wayland.h b/src/detection/displayserver/linux/wayland/wayland.h index b7d486cce9..e225eb881b 100644 --- a/src/detection/displayserver/linux/wayland/wayland.h +++ b/src/detection/displayserver/linux/wayland/wayland.h @@ -68,8 +68,6 @@ typedef struct WaylandDisplay { bool done; } WaylandDisplay; -void ffWaylandStubListener(...); - inline static uint64_t ffWaylandGenerateIdFromName(const char* name) { uint64_t id = 0; size_t len = strlen(name); From b41cdb0ca6748fb162c061151d7949494b8e94b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Tue, 14 Jul 2026 18:58:00 +0800 Subject: [PATCH 13/67] Chore: siliences more compiler warnings --- src/logo/image/image.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/logo/image/image.c b/src/logo/image/image.c index bc33e67f70..93ea082332 100644 --- a/src/logo/image/image.c +++ b/src/logo/image/image.c @@ -392,12 +392,15 @@ static bool compressBlob(void** blob, size_t* length) { #endif // FF_HAVE_ZLIB + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wimplicit-int-float-conversion" // We use only the defines from here, that are exactly the same in both versions #ifdef FF_HAVE_IMAGEMAGICK7 #include #else #include #endif + #pragma GCC diagnostic pop typedef struct ImageData { FF_LIBRARY_SYMBOL(CopyMagickString) From dd30c82b83667ce45d71fb4d32b5b0c2cb727e19 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Wed, 15 Jul 2026 09:16:03 +0800 Subject: [PATCH 14/67] TerminalFont: supports both syntax for alacritty --- src/detection/terminalfont/terminalfont.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/detection/terminalfont/terminalfont.c b/src/detection/terminalfont/terminalfont.c index db0f61b4dc..b7c2d0a5a4 100644 --- a/src/detection/terminalfont/terminalfont.c +++ b/src/detection/terminalfont/terminalfont.c @@ -18,6 +18,7 @@ static void detectAlacritty(FFTerminalFontResult* terminalFont) { do { FFpropquery fontQueryToml[] = { { "normal =", &fontNormal }, + { "family =", &fontFamily }, { "size =", &fontSize }, }; @@ -34,6 +35,8 @@ static void detectAlacritty(FFTerminalFontResult* terminalFont) { } while (false); if (fontNormal.length > 0) { + ffStrbufClear(&fontFamily); // If both `normal` and `family` are specified, `normal` takes precedence + // { family = "Fira Code", style = "Medium" } ffStrbufTrimSpace(&fontNormal); ffStrbufTrimRight(&fontNormal, '}'); From ac3fc219cfc8f3df6bce811fd15f829ea6c4936d Mon Sep 17 00:00:00 2001 From: Carter Li Date: Wed, 15 Jul 2026 09:40:51 +0800 Subject: [PATCH 15/67] Doc: updates README [ci skip] --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a55f2798dd..d2b0a53d92 100644 --- a/README.md +++ b/README.md @@ -199,7 +199,8 @@ See [#1096](https://github.com/fastfetch-cli/fastfetch/issues/1096). ### Q: Fastfetch shows fewer dpkg packages than neofetch. Is it a bug? -Neofetch incorrectly counts `rc` packages (packages that have been removed but still have configuration files remaining). See bug: https://github.com/dylanaraps/neofetch/issues/2278 +1. Neofetch incorrectly counts `rc` packages for apt (packages that have been removed but still have configuration files remaining). See bug: https://github.com/dylanaraps/neofetch/issues/2278 +2. Neofetch incorrectly counts `gpg-pubkey` as packages for rpm. You may check the results of `dnf list --installed | wc -l` and `rpm -qa | wc -l` to see the difference. ### Q: I use Debian / Ubuntu / Debian-derived distro. My GPU is detected as `XXXX Device XXXX (VGA compatible)`. Is this a bug? From bc8ce391933f43b0002579be0adf84580a634554 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Thu, 16 Jul 2026 10:14:20 +0800 Subject: [PATCH 16/67] Doc: update logo_request template [ci skip] Ref: #2460 --- .github/ISSUE_TEMPLATE/logo_request.yml | 39 ++++++++++++++++++++----- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/logo_request.yml b/.github/ISSUE_TEMPLATE/logo_request.yml index fa0570dab3..d3d605d57e 100644 --- a/.github/ISSUE_TEMPLATE/logo_request.yml +++ b/.github/ISSUE_TEMPLATE/logo_request.yml @@ -6,16 +6,31 @@ body: - type: markdown attributes: value: | - Tip: You can display a logo in fastfetch without adding it to fastfetch's official repo. + > [!WARNING] + > **NB:** If your logo request does not meet the basic requirements outlined in this template, the issue may be closed at any time without any further explanation. + + **Tip:** You can display a logo in fastfetch without adding it to fastfetch's official repo. For highly customized, personal logos, we recommend keeping them locally. - Please refer to https://github.com/fastfetch-cli/fastfetch/wiki/Migrate-Neofetch-Logo-To-Fastfetch + You can test your logo locally using: + `fastfetch -l /path/to/logo.txt --logo-color-1 white --logo-color-2 red` + Please refer to the [Migration Guide](https://github.com/fastfetch-cli/fastfetch/wiki/Migrate-Neofetch-Logo-To-Fastfetch) for details on color placeholders (`$1`, `$2`, etc.). + - type: textarea attributes: label: OS - description: Paste the contents of `/etc/os-release` and `/etc/lsb-release` here. If neither file exists, describe how to identify the distro. + description: Paste the contents of `/etc/os-release` (specifically the `ID` and `PRETTY_NAME` fields). If neither file exists, describe how to identify the distro. placeholder: cat /etc/os-release validations: required: true + + - type: input + attributes: + label: Public repository URL + description: A public repository is required to verify the request. Please provide a link to the repository where the distro is developed. + placeholder: https://github.com/example/distro + validations: + required: true + - type: input attributes: label: Distro Website @@ -23,25 +38,35 @@ body: placeholder: https://example.com validations: required: true + - type: textarea attributes: label: ASCII Art - description: The ASCII logo should not take up too much space (smaller than 50x20 characters, W x H). Please also include the color codes if they are not available in `os-release`. - placeholder: Paste ASCII art here + description: | + The ASCII logo should not take up too much space (smaller than 50x20 characters, W x H). + **Important:** Fastfetch uses `$1`, `$2`, `$3` etc. for color placeholders. Do NOT use hardcoded ANSI escape codes (like `\033[38;2...m`) or Neofetch's `${c1}` syntax. + Take as examples. + placeholder: Paste ASCII art with $1, $2 placeholders here validations: required: true + - type: input attributes: label: Original Image URL description: If the ASCII art is based on an image, please provide a link to the original image file. placeholder: Image URL from the distro website mentioned above + - type: checkboxes attributes: label: Checklist options: + - label: My distro has reached a stable state (out of beta, alpha, RC). + required: true - label: The ASCII art is smaller than 50x20 characters (W x H). required: true - - label: The ASCII art includes color codes, or the color codes are available in `os-release`. + - label: The ASCII art uses `$N` color placeholders (or colors are natively available in `os-release`). + required: true + - label: The ASCII art has no unnecessary trailing spaces at the end of lines. required: true - - label: The ASCII art has no internal padding (spaces at the start and/or end of lines). + - label: I promise that I will send a removal request if the distro is discontinued. required: true From 21fbf14fb822320d10d966ad4de8b1854e737a1e Mon Sep 17 00:00:00 2001 From: Carter Li Date: Thu, 16 Jul 2026 10:44:55 +0800 Subject: [PATCH 17/67] Logo (Builtin): removes Furreto, EmperorOS and Magix Ref: #2460 --- src/logo/ascii/e.inc | 11 ----------- src/logo/ascii/e/emperoros.txt | 13 ------------- src/logo/ascii/f.inc | 13 ------------- src/logo/ascii/f/furreto.txt | 22 ---------------------- src/logo/ascii/m.inc | 13 ------------- src/logo/ascii/m/magix.txt | 19 ------------------- 6 files changed, 91 deletions(-) delete mode 100644 src/logo/ascii/e/emperoros.txt delete mode 100644 src/logo/ascii/f/furreto.txt delete mode 100644 src/logo/ascii/m/magix.txt diff --git a/src/logo/ascii/e.inc b/src/logo/ascii/e.inc index 3e5fd91081..11e3672d8c 100644 --- a/src/logo/ascii/e.inc +++ b/src/logo/ascii/e.inc @@ -65,17 +65,6 @@ static const FFlogo E[] = { }, }, #endif - #ifdef FASTFETCH_DATATEXT_LOGO_EMPEROROS - // EmperorOS - { - .names = { "Emperor" }, - .lines = FASTFETCH_DATATEXT_LOGO_EMPEROROS, - .colors = { - FF_COLOR_FG_YELLOW, - FF_COLOR_FG_DEFAULT, - }, - }, - #endif #ifdef FASTFETCH_DATATEXT_LOGO_ENOS // EN-OS { diff --git a/src/logo/ascii/e/emperoros.txt b/src/logo/ascii/e/emperoros.txt deleted file mode 100644 index 55dcaa97c7..0000000000 --- a/src/logo/ascii/e/emperoros.txt +++ /dev/null @@ -1,13 +0,0 @@ - !! - !!!! - llllll - llllll - IIIIIIIIII - IIIIIIIIIIIIIIIIIIII -;;;;;;;;;;;;;;;;;;;;;;;; - ;;;;;;;;;;;;;;;;;;;; - :;;::;:;:: - :::::: - ,,,,,, - ,,,, - "" \ No newline at end of file diff --git a/src/logo/ascii/f.inc b/src/logo/ascii/f.inc index eaa0ae0366..921d88cb9f 100644 --- a/src/logo/ascii/f.inc +++ b/src/logo/ascii/f.inc @@ -283,19 +283,6 @@ static const FFlogo F[] = { .colorTitle = FF_COLOR_FG_DEFAULT, }, #endif - #ifdef FASTFETCH_DATATEXT_LOGO_FURRETO - // Furreto - { - .names = { "Furreto" }, - .lines = FASTFETCH_DATATEXT_LOGO_FURRETO, - .colors = { - FF_COLOR_FG_WHITE, - FF_COLOR_FG_LIGHT_MAGENTA, - }, - .colorKeys = FF_COLOR_FG_CYAN, - .colorTitle = FF_COLOR_FG_CYAN, - }, - #endif // LAST {}, }; diff --git a/src/logo/ascii/f/furreto.txt b/src/logo/ascii/f/furreto.txt deleted file mode 100644 index 95ee07c989..0000000000 --- a/src/logo/ascii/f/furreto.txt +++ /dev/null @@ -1,22 +0,0 @@ - .$2xOOko $1.$2odd, - oX$1WW$2KOOOO. 'ON$1WW$20kkk. - $1.$2k0XKOOOOOOcOON$1W$2NOOOOO. - xOOOOOOOOOkkOOOOOOOOO; - $1.$2O0OkkocxO000000kcdk0OO0OOkx - k$1W$1M$2Xkkkkloxkkkx; :dxxxxddc... - 'kO0OOOOOkc .cl:..kk0KK0Okc - ;kOOO0000xd. dO00000Oo .xkO$1NMM$2XOOOO -.dddxkOOOkddc.kKN$1WW$2N000000l.ddk0000OOOO. - 'dd:;ddddd;.dK$1MMMW$2K00KKK0O::ddxkO00Oko - .okxkOKK0kkOO00KKOxxlodddddddl - .00OOkkkkkkkkOOO00OOOO0O; .dddl - 'kkkkkxxkkkkkkkOOkxdxkxxddd. - cddddddddxxkkkkkxddddddddddo - 'ddddddodddddddddddddddddddc - $1.$2ddddddodddddddddodddddddc - .odddo. - - $1.$2kOOkkk; - lkK$1WN$2kkkxc - kkxkkkkkkx. - ,,..xxx. \ No newline at end of file diff --git a/src/logo/ascii/m.inc b/src/logo/ascii/m.inc index b0a1456918..8200f61f1e 100644 --- a/src/logo/ascii/m.inc +++ b/src/logo/ascii/m.inc @@ -151,19 +151,6 @@ static const FFlogo M[] = { .colorTitle = FF_COLOR_FG_DEFAULT, }, #endif - #ifdef FASTFETCH_DATATEXT_LOGO_MAGIX - // Magix - { - .names = { "Magix", "MagixOS" }, - .lines = FASTFETCH_DATATEXT_LOGO_MAGIX, - .colors = { - FF_COLOR_FG_LIGHT_MAGENTA, - FF_COLOR_FG_CYAN, - }, - .colorKeys = FF_COLOR_FG_CYAN, - .colorTitle = FF_COLOR_FG_LIGHT_MAGENTA, - }, - #endif #ifdef FASTFETCH_DATATEXT_LOGO_MAGPIEOS // MagpieOS { diff --git a/src/logo/ascii/m/magix.txt b/src/logo/ascii/m/magix.txt deleted file mode 100644 index 3f19aa11d7..0000000000 --- a/src/logo/ascii/m/magix.txt +++ /dev/null @@ -1,19 +0,0 @@ - $2@ - @@--=====@@ - @@--==@@ @@====+@ - @-@@ @==@ - @=@ - @=@$1 @=@ @-==== @=@$2 - @=@$1 @-===@==++@===+@$2 - @=@$1 @--====@@=====+@$2 --=@$1 @--==========++@$2 -==$1 @--==========++@$2 @=@ -@==$1 @--=======@==++@$2 @=+@ - @==$1 @-==========++$2 @=@ - @==$1 @-=======@=%$2 @=@ - @==$1 @@@@@@$2 @=@ - @====@@@ @@===+% - @@=====@@==++++@@ - =#@=@ - @==@++@ - @@@ From d9f5e006b586bd9007112c62308ddab98c3669b4 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Thu, 16 Jul 2026 10:48:12 +0800 Subject: [PATCH 18/67] Logo (Builtin): removes MagpieOS Dead years ago: https://github.com/magpie-linux --- src/logo/ascii/m.inc | 15 --------------- src/logo/ascii/m/magpieos.txt | 20 -------------------- 2 files changed, 35 deletions(-) delete mode 100644 src/logo/ascii/m/magpieos.txt diff --git a/src/logo/ascii/m.inc b/src/logo/ascii/m.inc index 8200f61f1e..68faf3b9db 100644 --- a/src/logo/ascii/m.inc +++ b/src/logo/ascii/m.inc @@ -151,21 +151,6 @@ static const FFlogo M[] = { .colorTitle = FF_COLOR_FG_DEFAULT, }, #endif - #ifdef FASTFETCH_DATATEXT_LOGO_MAGPIEOS - // MagpieOS - { - .names = { "MagpieOS", "Magpie" }, - .lines = FASTFETCH_DATATEXT_LOGO_MAGPIEOS, - .colors = { - FF_COLOR_FG_GREEN, - FF_COLOR_FG_RED, - FF_COLOR_FG_YELLOW, - FF_COLOR_FG_MAGENTA, - }, - .colorKeys = FF_COLOR_FG_GREEN, - .colorTitle = FF_COLOR_FG_RED, - }, - #endif #ifdef FASTFETCH_DATATEXT_LOGO_MANDRIVA // Mandriva { diff --git a/src/logo/ascii/m/magpieos.txt b/src/logo/ascii/m/magpieos.txt deleted file mode 100644 index 018597d58a..0000000000 --- a/src/logo/ascii/m/magpieos.txt +++ /dev/null @@ -1,20 +0,0 @@ - ;00000 :000Ol - .x00kk00: O0kk00k; - l00: :00. o0k :O0k. - .k0k. x$2d$dddd$1k' .d00; - k0k. $2.dddddl $1o00, - o00. $2':cc:. $1d0O -.00l ,00. -l00. d0x -k0O .:k0o -O0k ;dO0000d. -k0O .O0O$2xxxxk$100: -o00. k0O$2dddddd$1occ -'00l x0O$2dddddo$3;..$1 - x00. .x00$2kxxd$3:..$1 - .O0x .:oxxx$4Okl.$1 - .x0d $4,xx,$1 - .:o. $4.xd ckd$1 - .. $4dxl .xx; - :xxolldxd' - ;oxdl. \ No newline at end of file From 445311978ef08e5ed3880b9aaa4023d2d5f442d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Thu, 16 Jul 2026 19:09:39 +0800 Subject: [PATCH 19/67] Logo: silence GCC warnings by unsilencing CLANG warnings --- src/logo/image/im6.c | 3 --- src/logo/image/im7.c | 3 --- src/logo/image/image.c | 3 --- 3 files changed, 9 deletions(-) diff --git a/src/logo/image/im6.c b/src/logo/image/im6.c index 9450123550..9fb4338c52 100644 --- a/src/logo/image/im6.c +++ b/src/logo/image/im6.c @@ -3,10 +3,7 @@ #include "image.h" #include "common/library.h" - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wimplicit-int-float-conversion" #include - #pragma GCC diagnostic pop static FF_LIBRARY_SYMBOL(ResizeImage) diff --git a/src/logo/image/im7.c b/src/logo/image/im7.c index 87b60c45f3..967e79ea09 100644 --- a/src/logo/image/im7.c +++ b/src/logo/image/im7.c @@ -3,10 +3,7 @@ #include "image.h" #include "common/library.h" - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wimplicit-int-float-conversion" #include - #pragma GCC diagnostic pop static FF_LIBRARY_SYMBOL(ResizeImage) diff --git a/src/logo/image/image.c b/src/logo/image/image.c index 93ea082332..bc33e67f70 100644 --- a/src/logo/image/image.c +++ b/src/logo/image/image.c @@ -392,15 +392,12 @@ static bool compressBlob(void** blob, size_t* length) { #endif // FF_HAVE_ZLIB - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wimplicit-int-float-conversion" // We use only the defines from here, that are exactly the same in both versions #ifdef FF_HAVE_IMAGEMAGICK7 #include #else #include #endif - #pragma GCC diagnostic pop typedef struct ImageData { FF_LIBRARY_SYMBOL(CopyMagickString) From 8bf1aca7fc2b94b1e1e5b6a5937e7fbbb89393a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Thu, 16 Jul 2026 19:03:43 +0800 Subject: [PATCH 20/67] GPU (Windows): correctly report virtual GPUs Fixes #2461 --- src/detection/gpu/gpu_windows.c | 521 +++++++++++++++++--------------- 1 file changed, 275 insertions(+), 246 deletions(-) diff --git a/src/detection/gpu/gpu_windows.c b/src/detection/gpu/gpu_windows.c index 9a6b95ad7e..526ff66a5d 100644 --- a/src/detection/gpu/gpu_windows.c +++ b/src/detection/gpu/gpu_windows.c @@ -17,12 +17,16 @@ #include #include #include + #include #include #define GUID_DEVCLASS_DISPLAY_STRING L"{4d36e968-e325-11ce-bfc1-08002be10318}" // Found in -static bool queryPciDeviceInfo(FFGPUResult* gpu, D3DKMT_DEVICE_IDS* outDeviceIds, bool queryPcieGen) { - FF_DEBUG("Query PCI device info: %08llX", gpu->deviceId); +// https://wine-devel.winehq.narkive.com/vKO2Bkgj/patch-1-2-dxgi-tests-add-test-for-enumerating-display-adapters-using-setupapi +DEFINE_DEVPROPKEY(DEVPROPKEY_DISPLAY_ADAPTER_LUID, 0x60b193cb, 0x5276, 0x4d0f, 0x96, 0xfc, 0xf1, 0x73, 0xab, 0xad, 0x3e, 0xc6, 2); + +static bool queryDeviceInfoCM(FFGPUResult* gpu, uint64_t luid, D3DKMT_DEVICE_IDS* outDeviceIds, bool queryPcieGen) { + FF_DEBUG("Query PCI device info: %08llX with LUID: %08llX", gpu->deviceId, luid); static FFlist deviceIdsCache; static bool initialized; @@ -32,7 +36,10 @@ static bool queryPciDeviceInfo(FFGPUResult* gpu, D3DKMT_DEVICE_IDS* outDeviceIds uint32_t maxLinkSpeed; uint32_t maxLinkWidth; D3DKMT_DEVICE_IDS deviceIds; - uint64_t adapterAddress; + uint64_t pciAddr; + uint64_t luid; + FFstrbuf vendor; + FFstrbuf name; } CacheEntry; if (!initialized) { @@ -71,13 +78,36 @@ static bool queryPciDeviceInfo(FFGPUResult* gpu, D3DKMT_DEVICE_IDS* outDeviceIds } } - if (wcsncmp(devId, L"PCI\\", 4) != 0) { - FF_DEBUG("Skipping non-PCI device ID: %ls", devId); - continue; - } - CacheEntry* entry = FF_LIST_ADD(CacheEntry, deviceIdsCache); - *entry = (CacheEntry) {}; + *entry = (CacheEntry){}; + + ULONG bufLen = sizeof(entry->luid); + DEVPROPTYPE type; + CONFIGRET ret = CM_Get_DevNode_PropertyW(devInst, &DEVPROPKEY_DISPLAY_ADAPTER_LUID, &type, (PBYTE) &entry->luid, &bufLen, 0); + if (ret != CR_SUCCESS) { // Not available on Windows 8.1 + FF_DEBUG("Failed to get device LUID: %s", ffDebugConfigRet(ret)); + + uint32_t pciBus = 0; + ULONG pciBufLen = sizeof(pciBus); + if (CM_Get_DevNode_Registry_PropertyW(devInst, CM_DRP_BUSNUMBER, nullptr, &pciBus, &pciBufLen, 0) == CR_SUCCESS) { + uint32_t pciAddr = 0; + pciBufLen = sizeof(pciAddr); + if (CM_Get_DevNode_Registry_PropertyW(devInst, CM_DRP_ADDRESS, nullptr, &pciAddr, &pciBufLen, 0) == CR_SUCCESS) { + entry->pciAddr = ffGPUPciAddr2Id(0, pciBus, (pciAddr >> 16) & 0xFFFF, pciAddr & 0xFFFF); + FF_DEBUG("Cached device IDs for PCI bus %u: vendor=0x%04x device=0x%04x", pciBus, entry->deviceIds.VendorID, entry->deviceIds.DeviceID); + } else { + FF_DEBUG("Failed to get PCI address"); + } + } else { + FF_DEBUG("Failed to get PCI bus number"); + } + + if (entry->pciAddr == 0) { + FF_DEBUG("Skipping device ID: %ls due to missing LUID and PCI address", devId); + deviceIdsCache.length--; + continue; + } + } // L"PCI\\VEN_10DE&DEV_2782&SUBSYS_513417AA&REV_A1\\4&3674a6b9&0&0008" if (swscanf(devId + 4, L"VEN_%x&DEV_%x&SUBSYS_%4x%4x&REV_%x", &entry->deviceIds.VendorID, &entry->deviceIds.DeviceID, &entry->deviceIds.SubSystemID, &entry->deviceIds.SubVendorID, &entry->deviceIds.RevisionID) >= 2) { @@ -87,32 +117,44 @@ static bool queryPciDeviceInfo(FFGPUResult* gpu, D3DKMT_DEVICE_IDS* outDeviceIds // And yeah, DXGKMDT_OPM_BUS_TYPE_PCIEXPRESS (3) exists entry->deviceIds.BusType = 1; } else { - FF_DEBUG("Failed to parse PCI IDs from device ID string"); - deviceIdsCache.length--; // remove the cache entry since it's not valid - continue; + FF_DEBUG("Failed to parse PCI IDs from device ID: %ls", devId); + entry->deviceIds.VendorID = -1u; } - uint32_t pciBus = 0; - ULONG pciBufLen = sizeof(pciBus); - if (CM_Get_DevNode_Registry_PropertyW(devInst, CM_DRP_BUSNUMBER, nullptr, &pciBus, &pciBufLen, 0) == CR_SUCCESS) { - uint32_t pciAddr = 0; - pciBufLen = sizeof(pciAddr); - if (CM_Get_DevNode_Registry_PropertyW(devInst, CM_DRP_ADDRESS, nullptr, &pciAddr, &pciBufLen, 0) == CR_SUCCESS) { - entry->adapterAddress = ffGPUPciAddr2Id(0, pciBus, (pciAddr >> 16) & 0xFFFF, pciAddr & 0xFFFF); - FF_DEBUG("Cached device IDs for PCI bus %u: vendor=0x%04x device=0x%04x", pciBus, entry->deviceIds.VendorID, entry->deviceIds.DeviceID); + wchar_t wstr[256]; + bufLen = sizeof(wstr); + ret = CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_Device_FriendlyName, &type, (PBYTE) wstr, &bufLen, 0); + if (ret == CR_SUCCESS) { + ffStrbufSetNWS(&entry->name, (bufLen - 1) / sizeof(wchar_t), wstr); + FF_DEBUG("Device friendly name: %ls", wstr); + } else { + FF_DEBUG("Failed to get device friendly name: %s", ffDebugConfigRet(ret)); + + bufLen = sizeof(wstr); + ret = CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_Device_DeviceDesc, &type, (PBYTE) wstr, &bufLen, 0); + if (ret == CR_SUCCESS) { + ffStrbufSetNWS(&entry->name, (bufLen - 1) / sizeof(wchar_t), wstr); + FF_DEBUG("Device description: %ls", wstr); } else { - FF_DEBUG("Failed to get PCI address"); + FF_DEBUG("Failed to get device description: %s", ffDebugConfigRet(ret)); } + } + + bufLen = sizeof(wstr); + ret = CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_Device_Manufacturer, &type, (PBYTE) wstr, &bufLen, 0); + if (ret == CR_SUCCESS) { + ffStrbufSetNWS(&entry->vendor, (bufLen - 1) / sizeof(wchar_t), wstr); + FF_DEBUG("Device vendor name: %ls", wstr); } else { - FF_DEBUG("Failed to get PCI bus number"); + FF_DEBUG("Failed to get device vendor name: %s", ffDebugConfigRet(ret)); } - if (queryPcieGen) { + if (queryPcieGen && entry->deviceIds.VendorID != -1u) { DEVPROPTYPE propType; - pciBufLen = sizeof(entry->maxLinkSpeed); + bufLen = sizeof(entry->maxLinkSpeed); // Reports PCIe gen despite the PKEY name - CONFIGRET ret = CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_PciDevice_MaxLinkSpeed, &propType, (PBYTE) &entry->maxLinkSpeed, &pciBufLen, 0); + CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_PciDevice_MaxLinkSpeed, &propType, (PBYTE) &entry->maxLinkSpeed, &bufLen, 0); if (ret == CR_SUCCESS) { FF_DEBUG("PCIe max GEN: %u", entry->maxLinkSpeed); } else { @@ -120,8 +162,8 @@ static bool queryPciDeviceInfo(FFGPUResult* gpu, D3DKMT_DEVICE_IDS* outDeviceIds } if (entry->maxLinkSpeed != FF_GPU_PCIE_SPEED_UNSET) { - pciBufLen = sizeof(entry->maxLinkWidth); - ret = CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_PciDevice_MaxLinkWidth, &propType, (PBYTE) &entry->maxLinkWidth, &pciBufLen, 0); + bufLen = sizeof(entry->maxLinkWidth); + ret = CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_PciDevice_MaxLinkWidth, &propType, (PBYTE) &entry->maxLinkWidth, &bufLen, 0); if (ret == CR_SUCCESS) { FF_DEBUG("PCIe max link width: %u", entry->maxLinkWidth); } else { @@ -129,8 +171,8 @@ static bool queryPciDeviceInfo(FFGPUResult* gpu, D3DKMT_DEVICE_IDS* outDeviceIds } } - pciBufLen = sizeof(entry->currentLinkSpeed); - ret = CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_PciDevice_CurrentLinkSpeed, &propType, (PBYTE) &entry->currentLinkSpeed, &pciBufLen, 0); + bufLen = sizeof(entry->currentLinkSpeed); + ret = CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_PciDevice_CurrentLinkSpeed, &propType, (PBYTE) &entry->currentLinkSpeed, &bufLen, 0); if (ret == CR_SUCCESS) { FF_DEBUG("PCIe GEN: %u", entry->currentLinkSpeed); } else { @@ -138,8 +180,8 @@ static bool queryPciDeviceInfo(FFGPUResult* gpu, D3DKMT_DEVICE_IDS* outDeviceIds } if (entry->currentLinkSpeed != FF_GPU_PCIE_SPEED_UNSET) { - pciBufLen = sizeof(entry->currentLinkWidth); - ret = CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_PciDevice_CurrentLinkWidth, &propType, (PBYTE) &entry->currentLinkWidth, &pciBufLen, 0); + bufLen = sizeof(entry->currentLinkWidth); + ret = CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_PciDevice_CurrentLinkWidth, &propType, (PBYTE) &entry->currentLinkWidth, &bufLen, 0); if (ret == CR_SUCCESS) { FF_DEBUG("PCIe current link width: %u", entry->currentLinkWidth); } else { @@ -151,12 +193,31 @@ static bool queryPciDeviceInfo(FFGPUResult* gpu, D3DKMT_DEVICE_IDS* outDeviceIds } FF_LIST_FOR_EACH (CacheEntry, entry, deviceIdsCache) { - if (gpu->deviceId == entry->adapterAddress) { - FF_DEBUG("Cache hit for adapter address: %08llX", gpu->deviceId); + if (luid == entry->luid || entry->pciAddr == gpu->deviceId) { + FF_DEBUG("Cache hit for adapter LUID: %08llX", luid); if (outDeviceIds->VendorID == -1u) { *outDeviceIds = entry->deviceIds; } + if (gpu->vendor.length == 0) { + if (entry->deviceIds.VendorID != -1u) { + ffStrbufSetStatic(&gpu->vendor, ffGPUGetVendorString(entry->deviceIds.VendorID)); + } + if (gpu->vendor.length == 0 && entry->vendor.length > 0) { + ffStrbufDestroy(&gpu->vendor); + ffStrbufInitMove(&gpu->vendor, &entry->vendor); + } + } else if (gpu->name.length == 0 && entry->deviceIds.VendorID == -1u) { + // Some Indirect display adapters reports fake Device IDs + ffStrbufDestroy(&gpu->vendor); + ffStrbufInitMove(&gpu->vendor, &entry->vendor); + } + + if (gpu->name.length == 0) { + ffStrbufDestroy(&gpu->name); + ffStrbufInitMove(&gpu->name, &entry->name); + } + if (queryPcieGen) { gpu->psMax.gen = (uint16_t) entry->maxLinkSpeed; gpu->psMax.lanes = (uint16_t) entry->maxLinkWidth; @@ -167,37 +228,10 @@ static bool queryPciDeviceInfo(FFGPUResult* gpu, D3DKMT_DEVICE_IDS* outDeviceIds } } - FF_DEBUG("Cache miss for adapter address: %08llX", gpu->deviceId); + FF_DEBUG("Cache miss for adapter LUID: %08llX", luid); return false; } -static bool queryVendorNameViaRegistry(FFstrbuf* vendor, D3DKMT_HANDLE hAdapter) { - // `KMTQAITYPE_QUERY_ADAPTER_UNIQUE_GUID` reports the GUID value used by the adapter's registry key (DirectX and Video) - - GUID guid; - NTSTATUS status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { - .hAdapter = hAdapter, - .Type = KMTQAITYPE_QUERY_ADAPTER_UNIQUE_GUID, - .pPrivateDriverData = &guid, - .PrivateDriverDataSize = sizeof(guid), - }); - if (!NT_SUCCESS(status)) { - FF_DEBUG("Failed to query adapter unique GUID: %s", ffDebugNtStatus(status)); - return false; - } - - wchar_t path[PATH_MAX]; - swprintf(path, ARRAY_SIZE(path), L"SYSTEM\\CurrentControlSet\\Control\\Video\\{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}\\0000", guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]); - - FF_DEBUG("Querying registry: HKEY_LOCAL_MACHINE\\%ls\\ProviderName", path); - FF_AUTO_CLOSE_FD HANDLE key = nullptr; - if (!ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, path, &key, nullptr)) { - return false; - } - - return ffRegReadStrbuf(key, L"ProviderName", vendor, nullptr); -} - #else #include #include @@ -301,6 +335,8 @@ ffGPUDetectWsl2 goto close_adapter; } + uint64_t luid = ((uint64_t) adapter->AdapterLuid.HighPart << 32) | (uint64_t) adapter->AdapterLuid.LowPart; + FFGPUResult* gpu = FF_LIST_ADD(FFGPUResult, *gpus); ffStrbufInit(&gpu->vendor); ffStrbufInit(&gpu->name); @@ -321,7 +357,7 @@ ffGPUDetectWsl2 : FF_GPU_TYPE_UNKNOWN; gpu->pcieSpeed = FF_GPU_PCIE_SPEED_UNSET; - D3DKMT_DRIVERVERSION wddmVersion = KMT_DRIVERVERSION_WDDM_2_0; + D3DKMT_DRIVERVERSION wddmVersion; status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { .hAdapter = adapter->hAdapter, .Type = KMTQAITYPE_DRIVERVERSION, @@ -332,6 +368,7 @@ ffGPUDetectWsl2 ffStrbufSetF(&gpu->platformApi, "WDDM %u.%u", (uint32_t) wddmVersion / 1000, ((uint32_t) wddmVersion % 1000) / 100); FF_DEBUG("Adapter #%u WDDM version: %u", i, (uint32_t) wddmVersion); } else { + wddmVersion = KMT_DRIVERVERSION_WDDM_1_3; // Smallest supported WDDM version, used for fallback ffStrbufSetStatic(&gpu->platformApi, "WDDM"); FF_DEBUG("KMTQAITYPE_DRIVERVERSION query failed for adapter #%u", i); } @@ -352,7 +389,7 @@ ffGPUDetectWsl2 adapterAddress.FunctionNumber); } else { adapterAddress.BusNumber = -1u; - gpu->deviceId = ffGPUGeneral2Id(((uint64_t) adapter->AdapterLuid.HighPart << 32) | (uint64_t) adapter->AdapterLuid.LowPart); + gpu->deviceId = ffGPUGeneral2Id(luid); FF_DEBUG("KMTQAITYPE_ADAPTERADDRESS query failed for adapter #%u, fallback to LUID-based deviceId: %s", i, ffDebugNtStatus(status)); @@ -376,14 +413,6 @@ ffGPUDetectWsl2 FF_DEBUG("KMTQAITYPE_PHYSICALADAPTERDEVICEIDS query failed for adapter #%u: %s", i, ffDebugNtStatus(status)); } - #if _WIN32 && FF_WIN81_COMPAT - if (adapterAddress.BusNumber != -1u && (deviceIds.DeviceIds.VendorID == -1u || options->driverSpecific)) { - if (queryPciDeviceInfo(gpu, &deviceIds.DeviceIds, options->driverSpecific) && gpu->vendor.length == 0) { - ffStrbufSetStatic(&gpu->vendor, ffGPUGetVendorString(deviceIds.DeviceIds.VendorID)); - } - } - #endif - D3DKMT_UMD_DRIVER_VERSION umdDriverVersion; status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { .hAdapter = adapter->hAdapter, @@ -403,63 +432,55 @@ ffGPUDetectWsl2 FF_DEBUG("KMTQAITYPE_UMD_DRIVER_VERSION query failed for adapter #%u: %s", i, ffDebugNtStatus(status)); } - typeof(&ffDetectNvidiaGpuInfo) detectFn; - const char* dllName; - if (options->driverSpecific && getDriverSpecificDetectionFn(gpu->vendor.chars, &detectFn, &dllName)) { - FF_DEBUG("Calling driver-specific detection function for vendor: %s, DLL: %s", gpu->vendor.chars, dllName); - [[maybe_unused]] const char* error = detectFn( - &(FFGpuDriverCondition) { - .type = FF_GPU_DRIVER_CONDITION_TYPE_LUID | - (deviceIds.DeviceIds.VendorID != -1u ? FF_GPU_DRIVER_CONDITION_TYPE_DEVICE_ID : 0) | - (adapterAddress.BusNumber != -1u ? FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID : 0), - .pciDeviceId = { - .deviceId = deviceIds.DeviceIds.DeviceID, - .vendorId = deviceIds.DeviceIds.VendorID, - .subSystemId = deviceIds.DeviceIds.SubSystemID, - .revId = deviceIds.DeviceIds.RevisionID, - }, - .pciBusId = { - .domain = 0, - .bus = adapterAddress.BusNumber, - .device = adapterAddress.DeviceNumber, - .func = adapterAddress.FunctionNumber, - }, - .luid = ((uint64_t) adapter->AdapterLuid.HighPart << 32) | (uint64_t) adapter->AdapterLuid.LowPart, - }, - (FFGpuDriverResult) { - .index = &gpu->index, - .temp = options->temp ? &gpu->temperature : nullptr, - .memory = options->driverSpecific ? &gpu->dedicated : nullptr, - .sharedMemory = options->driverSpecific ? &gpu->shared : nullptr, - .memoryType = options->driverSpecific ? &gpu->memoryType : nullptr, - .coreCount = options->driverSpecific ? (uint32_t*) &gpu->coreCount : nullptr, - .coreUsage = options->driverSpecific ? &gpu->coreUsage : nullptr, - .type = &gpu->type, - .frequency = options->driverSpecific ? &gpu->frequency : nullptr, - .name = &gpu->name, - .psCurr = options->driverSpecific ? &gpu->psCurr : nullptr, - .psMax = options->driverSpecific ? &gpu->psMax : nullptr, - }, - dllName); - FF_DEBUG("Driver-specific detection completed: %s", error ?: "Success"); - } else if (options->driverSpecific) { - FF_DEBUG("No driver-specific detection function found for vendor: %s", gpu->vendor.chars); - } - -#if _WIN32 - // Put this after the driver-specific detection, as `getDriverSpecificDetectionFn` never succeeds - if (gpu->vendor.length == 0 && wddmVersion >= KMT_DRIVERVERSION_WDDM_2_4) { - // For non-PCI devices - FF_DEBUG("Attempting to query vendor name via registry for adapter #%u", i); - queryVendorNameViaRegistry(&gpu->vendor, adapter->hAdapter); + bool isIndirectDisplayDevice = gpu->type == FF_GPU_TYPE_UNKNOWN && adapterType.IndirectDisplayDevice; + if (isIndirectDisplayDevice) { + FF_DEBUG("Adapter #%u is an indirect display device", i); } - #if !FF_WIN81_COMPAT - if (adapterAddress.BusNumber != -1u && options->driverSpecific && gpu->pcieSpeed == FF_GPU_PCIE_SPEED_UNSET) { - queryPciDeviceInfo(gpu, &deviceIds.DeviceIds, options->driverSpecific); - } - #endif -#endif + if (!isIndirectDisplayDevice) { + typeof(&ffDetectNvidiaGpuInfo) detectFn; + const char* dllName; + if (options->driverSpecific && getDriverSpecificDetectionFn(gpu->vendor.chars, &detectFn, &dllName)) { + FF_DEBUG("Calling driver-specific detection function for vendor: %s, DLL: %s", gpu->vendor.chars, dllName); + [[maybe_unused]] const char* error = detectFn( + &(FFGpuDriverCondition) { + .type = FF_GPU_DRIVER_CONDITION_TYPE_LUID | + (deviceIds.DeviceIds.VendorID != -1u ? FF_GPU_DRIVER_CONDITION_TYPE_DEVICE_ID : 0) | + (adapterAddress.BusNumber != -1u ? FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID : 0), + .pciDeviceId = { + .deviceId = deviceIds.DeviceIds.DeviceID, + .vendorId = deviceIds.DeviceIds.VendorID, + .subSystemId = deviceIds.DeviceIds.SubSystemID, + .revId = deviceIds.DeviceIds.RevisionID, + }, + .pciBusId = { + .domain = 0, + .bus = adapterAddress.BusNumber, + .device = adapterAddress.DeviceNumber, + .func = adapterAddress.FunctionNumber, + }, + .luid = luid, + }, + (FFGpuDriverResult) { + .index = &gpu->index, + .temp = options->temp ? &gpu->temperature : nullptr, + .memory = options->driverSpecific ? &gpu->dedicated : nullptr, + .sharedMemory = options->driverSpecific ? &gpu->shared : nullptr, + .memoryType = options->driverSpecific ? &gpu->memoryType : nullptr, + .coreCount = options->driverSpecific ? (uint32_t*) &gpu->coreCount : nullptr, + .coreUsage = options->driverSpecific ? &gpu->coreUsage : nullptr, + .type = &gpu->type, + .frequency = options->driverSpecific ? &gpu->frequency : nullptr, + .name = &gpu->name, + .psCurr = options->driverSpecific ? &gpu->psCurr : nullptr, + .psMax = options->driverSpecific ? &gpu->psMax : nullptr, + }, + dllName); + FF_DEBUG("Driver-specific detection completed: %s", error ?: "Success"); + } else if (options->driverSpecific) { + FF_DEBUG("No driver-specific detection function found for vendor: %s", gpu->vendor.chars); + } + }; if (gpu->name.length == 0) { D3DKMT_ADAPTERREGISTRYINFO registryInfo; @@ -477,148 +498,156 @@ ffGPUDetectWsl2 } } - if (gpu->dedicated.total == FF_GPU_VMEM_SIZE_UNSET && gpu->shared.total == FF_GPU_VMEM_SIZE_UNSET) { - if (wddmVersion >= KMT_DRIVERVERSION_WDDM_3_1 && options->driverSpecific) { - // Supports memory usage query; requires Windows 11 (22H2) or later - D3DKMT_QUERYSTATISTICS queryStatistics = { - .Type = D3DKMT_QUERYSTATISTICS_SEGMENT_GROUP_USAGE, - .AdapterLuid = adapter->AdapterLuid, - .QuerySegmentGroupUsage = { - .PhysicalAdapterIndex = 0, - .SegmentGroup = D3DKMT_MEMORY_SEGMENT_GROUP_LOCAL, - }, - }; - status = D3DKMTQueryStatistics(&queryStatistics); - if (NT_SUCCESS(status)) { - D3DKMT_QUERYSTATISTICS_MEMORY_USAGE* info = &queryStatistics.QueryResult.SegmentGroupUsageInformation; - uint64_t used = info->AllocatedBytes + info->ModifiedBytes + info->StandbyBytes; - uint64_t total = used + info->FreeBytes + info->ZeroBytes; - gpu->dedicated.used = used; - gpu->dedicated.total = total; - FF_DEBUG("Adapter #%u local memory usage: used=%" PRIu64 " total=%" PRIu64, i, used, total); - } else { - FF_DEBUG("D3DKMT_QUERYSTATISTICS_SEGMENT_GROUP_USAGE (LOCAL) failed for adapter #%u: %s", - i, - ffDebugNtStatus(status)); - } - - queryStatistics.QuerySegmentGroupUsage.SegmentGroup = D3DKMT_MEMORY_SEGMENT_GROUP_NON_LOCAL; - status = D3DKMTQueryStatistics(&queryStatistics); - if (NT_SUCCESS(status)) { - D3DKMT_QUERYSTATISTICS_MEMORY_USAGE* info = &queryStatistics.QueryResult.SegmentGroupUsageInformation; - uint64_t used = info->AllocatedBytes + info->ModifiedBytes + info->StandbyBytes; - uint64_t total = used + info->FreeBytes + info->ZeroBytes; - gpu->shared.used = used; - gpu->shared.total = total; - FF_DEBUG("Adapter #%u non-local memory usage: used=%" PRIu64 " total=%" PRIu64, i, used, total); - } else { - FF_DEBUG("D3DKMT_QUERYSTATISTICS_SEGMENT_GROUP_USAGE (NON_LOCAL) failed for adapter #%u: %s", - i, - ffDebugNtStatus(status)); - } - } else { - // Supports basic segment (total) size query - D3DKMT_SEGMENTSIZEINFO segmentSizeInfo = {}; - status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { - .hAdapter = adapter->hAdapter, - .Type = KMTQAITYPE_GETSEGMENTSIZE, - .pPrivateDriverData = &segmentSizeInfo, - .PrivateDriverDataSize = sizeof(segmentSizeInfo), - }); - if (NT_SUCCESS(status)) { - FF_DEBUG("Adapter #%u segment size - DedicatedVideoMemorySize: %" PRIu64 - ", DedicatedSystemMemorySize: %" PRIu64 ", SharedSystemMemorySize: %" PRIu64, - i, - (uint64_t) segmentSizeInfo.DedicatedVideoMemorySize, - (uint64_t) segmentSizeInfo.DedicatedSystemMemorySize, - (uint64_t) segmentSizeInfo.SharedSystemMemorySize); - gpu->dedicated.total = segmentSizeInfo.DedicatedVideoMemorySize; - gpu->shared.total = segmentSizeInfo.DedicatedSystemMemorySize + segmentSizeInfo.SharedSystemMemorySize; - } else { - FF_DEBUG("Failed to query segment size information for adapter #%u: %s", i, ffDebugNtStatus(status)); - } - } + #if _WIN32 + if (gpu->name.length == 0 || gpu->vendor.length == 0 || (options->driverSpecific && gpu->pcieSpeed == FF_GPU_PCIE_SPEED_UNSET)) { + queryDeviceInfoCM(gpu, luid, &deviceIds.DeviceIds, options->driverSpecific); } + #endif - if (wddmVersion >= KMT_DRIVERVERSION_WDDM_2_4) { - if (gpu->frequency == FF_GPU_FREQUENCY_UNSET) { - for (uint32_t nodeIdx = 0;; nodeIdx++) { - D3DKMT_NODEMETADATA nodeMetadata = { - .NodeOrdinalAndAdapterIndex = (0 << 16) | nodeIdx, + if (!isIndirectDisplayDevice) { + if (gpu->dedicated.total == FF_GPU_VMEM_SIZE_UNSET && gpu->shared.total == FF_GPU_VMEM_SIZE_UNSET) { + if (wddmVersion >= KMT_DRIVERVERSION_WDDM_3_1 && options->driverSpecific) { + // Supports memory usage query; requires Windows 11 (22H2) or later + D3DKMT_QUERYSTATISTICS queryStatistics = { + .Type = D3DKMT_QUERYSTATISTICS_SEGMENT_GROUP_USAGE, + .AdapterLuid = adapter->AdapterLuid, + .QuerySegmentGroupUsage = { + .PhysicalAdapterIndex = 0, + .SegmentGroup = D3DKMT_MEMORY_SEGMENT_GROUP_LOCAL, + }, }; + status = D3DKMTQueryStatistics(&queryStatistics); + if (NT_SUCCESS(status)) { + D3DKMT_QUERYSTATISTICS_MEMORY_USAGE* info = &queryStatistics.QueryResult.SegmentGroupUsageInformation; + uint64_t used = info->AllocatedBytes + info->ModifiedBytes + info->StandbyBytes; + uint64_t total = used + info->FreeBytes + info->ZeroBytes; + gpu->dedicated.used = used; + gpu->dedicated.total = total; + FF_DEBUG("Adapter #%u local memory usage: used=%" PRIu64 " total=%" PRIu64, i, used, total); + } else { + FF_DEBUG("D3DKMT_QUERYSTATISTICS_SEGMENT_GROUP_USAGE (LOCAL) failed for adapter #%u: %s", + i, + ffDebugNtStatus(status)); + } + + queryStatistics.QuerySegmentGroupUsage.SegmentGroup = D3DKMT_MEMORY_SEGMENT_GROUP_NON_LOCAL; + status = D3DKMTQueryStatistics(&queryStatistics); + if (NT_SUCCESS(status)) { + D3DKMT_QUERYSTATISTICS_MEMORY_USAGE* info = &queryStatistics.QueryResult.SegmentGroupUsageInformation; + uint64_t used = info->AllocatedBytes + info->ModifiedBytes + info->StandbyBytes; + uint64_t total = used + info->FreeBytes + info->ZeroBytes; + gpu->shared.used = used; + gpu->shared.total = total; + FF_DEBUG("Adapter #%u non-local memory usage: used=%" PRIu64 " total=%" PRIu64, i, used, total); + } else { + FF_DEBUG("D3DKMT_QUERYSTATISTICS_SEGMENT_GROUP_USAGE (NON_LOCAL) failed for adapter #%u: %s", + i, + ffDebugNtStatus(status)); + } + } else { + // Supports basic segment (total) size query + D3DKMT_SEGMENTSIZEINFO segmentSizeInfo = {}; status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { .hAdapter = adapter->hAdapter, - .Type = KMTQAITYPE_NODEMETADATA, - .pPrivateDriverData = &nodeMetadata, - .PrivateDriverDataSize = sizeof(nodeMetadata), + .Type = KMTQAITYPE_GETSEGMENTSIZE, + .pPrivateDriverData = &segmentSizeInfo, + .PrivateDriverDataSize = sizeof(segmentSizeInfo), }); - if (!NT_SUCCESS(status)) { break; } + if (NT_SUCCESS(status)) { + FF_DEBUG("Adapter #%u segment size - DedicatedVideoMemorySize: %" PRIu64 + ", DedicatedSystemMemorySize: %" PRIu64 ", SharedSystemMemorySize: %" PRIu64, + i, + (uint64_t) segmentSizeInfo.DedicatedVideoMemorySize, + (uint64_t) segmentSizeInfo.DedicatedSystemMemorySize, + (uint64_t) segmentSizeInfo.SharedSystemMemorySize); + gpu->dedicated.total = segmentSizeInfo.DedicatedVideoMemorySize; + gpu->shared.total = segmentSizeInfo.DedicatedSystemMemorySize + segmentSizeInfo.SharedSystemMemorySize; + } else { + FF_DEBUG("Failed to query segment size information for adapter #%u: %s", i, ffDebugNtStatus(status)); + } + } + } - if (nodeMetadata.NodeData.EngineType != DXGK_ENGINE_TYPE_3D) { continue; } + if (wddmVersion >= KMT_DRIVERVERSION_WDDM_2_4) { + if (gpu->frequency == FF_GPU_FREQUENCY_UNSET) { + for (uint32_t nodeIdx = 0;; nodeIdx++) { + D3DKMT_NODEMETADATA nodeMetadata = { + .NodeOrdinalAndAdapterIndex = (0 << 16) | nodeIdx, + }; + status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { + .hAdapter = adapter->hAdapter, + .Type = KMTQAITYPE_NODEMETADATA, + .pPrivateDriverData = &nodeMetadata, + .PrivateDriverDataSize = sizeof(nodeMetadata), + }); + if (!NT_SUCCESS(status)) { break; } + + if (nodeMetadata.NodeData.EngineType != DXGK_ENGINE_TYPE_3D) { continue; } + + D3DKMT_NODE_PERFDATA nodePerfData = { + .NodeOrdinal = nodeIdx, + .PhysicalAdapterIndex = 0, + }; + status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { + .hAdapter = adapter->hAdapter, + .Type = KMTQAITYPE_NODEPERFDATA, + .pPrivateDriverData = &nodePerfData, + .PrivateDriverDataSize = sizeof(nodePerfData), + }); + if (NT_SUCCESS(status)) { + if (nodePerfData.MaxFrequency != 0) { + gpu->frequency = (uint32_t) (nodePerfData.MaxFrequency / 1000 / 1000); + FF_DEBUG("Adapter #%u max graphics frequency: %u MHz", i, gpu->frequency); + } else { + FF_DEBUG("Adapter #%u does not report max graphics frequency", i); + } + break; + } else { + FF_DEBUG("Failed to query node performance data for adapter #%u node #%u: %s", + i, + nodeIdx, + ffDebugNtStatus(status)); + } + } + } - D3DKMT_NODE_PERFDATA nodePerfData = { - .NodeOrdinal = nodeIdx, + if (options->temp && gpu->temperature == FF_GPU_TEMP_UNSET) { + D3DKMT_ADAPTER_PERFDATA adapterPerfData = { .PhysicalAdapterIndex = 0, }; status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { .hAdapter = adapter->hAdapter, - .Type = KMTQAITYPE_NODEPERFDATA, - .pPrivateDriverData = &nodePerfData, - .PrivateDriverDataSize = sizeof(nodePerfData), + .Type = KMTQAITYPE_ADAPTERPERFDATA, + .pPrivateDriverData = &adapterPerfData, + .PrivateDriverDataSize = sizeof(adapterPerfData), }); if (NT_SUCCESS(status)) { - if (nodePerfData.MaxFrequency != 0) { - gpu->frequency = (uint32_t) (nodePerfData.MaxFrequency / 1000 / 1000); - FF_DEBUG("Adapter #%u max graphics frequency: %u MHz", i, gpu->frequency); + if (adapterPerfData.Temperature != 0) { + gpu->temperature = adapterPerfData.Temperature / 10.0; + FF_DEBUG("Adapter #%u temperature: %.1f°C", i, gpu->temperature); } else { - FF_DEBUG("Adapter #%u does not report max graphics frequency", i); + FF_DEBUG("Adapter #%u does not report temperature data", i); } - break; } else { - FF_DEBUG("Failed to query node performance data for adapter #%u node #%u: %s", - i, - nodeIdx, - ffDebugNtStatus(status)); + FF_DEBUG("Failed to query temperature for adapter #%u: %s", i, ffDebugNtStatus(status)); } } } - if (options->temp && gpu->temperature == FF_GPU_TEMP_UNSET) { - D3DKMT_ADAPTER_PERFDATA adapterPerfData = { - .PhysicalAdapterIndex = 0, - }; - status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) { - .hAdapter = adapter->hAdapter, - .Type = KMTQAITYPE_ADAPTERPERFDATA, - .pPrivateDriverData = &adapterPerfData, - .PrivateDriverDataSize = sizeof(adapterPerfData), - }); - if (NT_SUCCESS(status)) { - if (adapterPerfData.Temperature != 0) { - gpu->temperature = adapterPerfData.Temperature / 10.0; - FF_DEBUG("Adapter #%u temperature: %.1f°C", i, gpu->temperature); - } else { - FF_DEBUG("Adapter #%u does not report temperature data", i); - } - } else { - FF_DEBUG("Failed to query temperature for adapter #%u: %s", i, ffDebugNtStatus(status)); + if (gpu->type == FF_GPU_TYPE_UNKNOWN) { + if (ffGPUDetectTypeByVendorAndName(gpu)) { + // OK + } + #if _WIN32 + else if (ffIsWindows10OrGreater()) { + const char* ffGPUDetectTypeWithDXCore(LUID adapterLuid, FFGPUResult * gpu); + [[maybe_unused]] const char* error = ffGPUDetectTypeWithDXCore(adapter->AdapterLuid, gpu); + FF_DEBUG("DXCore GPU type detection result: %s", error ?: "Success"); + } + #endif + else { + FF_DEBUG("Unable to determine GPU type by any method for this adapter"); } - } - } - - if (gpu->type == FF_GPU_TYPE_UNKNOWN) { - if (ffGPUDetectTypeByVendorAndName(gpu)) { - // OK - } -#if _WIN32 - else if (ffIsWindows10OrGreater()) { - const char* ffGPUDetectTypeWithDXCore(LUID adapterLuid, FFGPUResult * gpu); - [[maybe_unused]] const char* error = ffGPUDetectTypeWithDXCore(adapter->AdapterLuid, gpu); - FF_DEBUG("DXCore GPU type detection result: %s", error ?: "Success"); - } -#endif - else { - FF_DEBUG("Unable to determine GPU type by any method for this adapter"); } } From f0564fb9a75ccbe371afd68c056fcc5823c6b0fe Mon Sep 17 00:00:00 2001 From: Carter Li Date: Fri, 17 Jul 2026 10:34:56 +0800 Subject: [PATCH 21/67] Camera: adds missing `ffStrbufDestroy` --- src/modules/camera/camera.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/modules/camera/camera.c b/src/modules/camera/camera.c index 08cb107067..2bef6565a1 100644 --- a/src/modules/camera/camera.c +++ b/src/modules/camera/camera.c @@ -53,6 +53,7 @@ bool ffPrintCamera(FFCameraOptions* options) { FF_LIST_FOR_EACH (FFCameraResult, dev, result) { ffStrbufDestroy(&dev->name); + ffStrbufDestroy(&dev->vendor); ffStrbufDestroy(&dev->id); ffStrbufDestroy(&dev->colorspace); } @@ -99,6 +100,7 @@ bool ffGenerateCameraJsonResult([[maybe_unused]] FFCameraOptions* options, yyjso FF_LIST_FOR_EACH (FFCameraResult, dev, result) { ffStrbufDestroy(&dev->name); + ffStrbufDestroy(&dev->vendor); ffStrbufDestroy(&dev->id); ffStrbufDestroy(&dev->colorspace); } From 6c03db8be9a9272e97a9d2fc41397469e7f4767d Mon Sep 17 00:00:00 2001 From: Carter Li Date: Fri, 17 Jul 2026 10:35:30 +0800 Subject: [PATCH 22/67] Codec (macOS): fixes a memory leak --- src/detection/codec/codec_apple.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/detection/codec/codec_apple.c b/src/detection/codec/codec_apple.c index 36ca55a992..b202fad349 100644 --- a/src/detection/codec/codec_apple.c +++ b/src/detection/codec/codec_apple.c @@ -41,7 +41,7 @@ static FFCodecType ffCodecCodecToType(CMVideoCodecType codec) { } static FFCodecType ffCodecDetectEncoders(void) { - CFArrayRef encoderList = nullptr; + FF_CFTYPE_AUTO_RELEASE CFArrayRef encoderList = nullptr; if (VTCopyVideoEncoderList(nullptr, &encoderList) != noErr || !encoderList) { return FF_CODEC_TYPE_NONE; } From 0feec933ea8e90a2c65ab79a72d55e27c2b5660c Mon Sep 17 00:00:00 2001 From: Carter Li Date: Fri, 17 Jul 2026 10:40:48 +0800 Subject: [PATCH 23/67] Camera: removes unused `#include`s --- src/modules/camera/camera.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/modules/camera/camera.c b/src/modules/camera/camera.c index 2bef6565a1..1e601f1966 100644 --- a/src/modules/camera/camera.c +++ b/src/modules/camera/camera.c @@ -1,7 +1,5 @@ #include "common/printing.h" #include "common/jsonconfig.h" -#include "common/strutil.h" -#include "detection/libc/libc.h" #include "detection/camera/camera.h" #include "modules/camera/camera.h" From 62180366fc3b22d620e4ea45aa2f4e7af982bdcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Fri, 17 Jul 2026 23:00:31 +0800 Subject: [PATCH 24/67] Theme (Windows): reports `basic` is DWM is disabled --- src/detection/theme/theme_windows.c | 37 +++++++++++++++++------------ 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/src/detection/theme/theme_windows.c b/src/detection/theme/theme_windows.c index 68372eb0c4..4c82d2c04e 100644 --- a/src/detection/theme/theme_windows.c +++ b/src/detection/theme/theme_windows.c @@ -1,27 +1,34 @@ #include "theme.h" #include "detection/os/os.h" +#include "detection/displayserver/displayserver.h" const char* ffDetectTheme(FFThemeResult* result) { const FFOSResult* os = ffDetectOS(); + const FFDisplayServerResult* displayServer = ffConnectDisplayServer(); + if (ffStrbufEqualS(&displayServer->wmProcessName, "dwm.exe")) { + uint32_t ver = (uint32_t) ffStrbufToUInt(&os->version, 0); - if (ver > 1000) { - // Windows Server - if (ver >= 2016) { - ffStrbufSetStatic(&result->theme1, "Fluent"); - } else if (ver >= 2012) { - ffStrbufSetStatic(&result->theme1, "Metro"); - } else { - ffStrbufSetStatic(&result->theme1, "Aero"); - } - } else { - if (ver >= 10) { - ffStrbufSetStatic(&result->theme1, "Fluent"); - } else if (ver >= 8) { - ffStrbufSetStatic(&result->theme1, "Metro"); + if (ver > 1000) { + // Windows Server + if (ver >= 2016) { + ffStrbufSetStatic(&result->theme1, "Fluent"); + } else if (ver >= 2012) { + ffStrbufSetStatic(&result->theme1, "Metro"); + } else { + ffStrbufSetStatic(&result->theme1, "Aero"); + } } else { - ffStrbufSetStatic(&result->theme1, "Aero"); + if (ver >= 10) { + ffStrbufSetStatic(&result->theme1, "Fluent"); + } else if (ver >= 8) { + ffStrbufSetStatic(&result->theme1, "Metro"); + } else { + ffStrbufSetStatic(&result->theme1, "Aero"); + } } + } else if (ffStrbufEqualS(&displayServer->wmProcessName, "explorer.exe")) { + ffStrbufSetStatic(&result->theme1, "Basic"); } return nullptr; } From 9db4ec82fdd6eba431326b7eed81c6a2c7444f83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Sun, 19 Jul 2026 18:06:00 +0800 Subject: [PATCH 25/67] Version (macOS): siliences a compiler warning --- src/common/apple/version.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/apple/version.m b/src/common/apple/version.m index 133ec5b196..e6d4fae621 100644 --- a/src/common/apple/version.m +++ b/src/common/apple/version.m @@ -16,7 +16,7 @@ bool ffGetAppNameAndVersion(const char* exePath, FFstrbuf* retName, FFstrbuf* re lastSlash -= strlen("MacOS"); char infoPlistPath[PATH_MAX]; - memcpy(infoPlistPath, exePath, lastSlash - exePath); + memcpy(infoPlistPath, exePath, (size_t) (lastSlash - exePath)); memcpy(infoPlistPath + (lastSlash - exePath), "Info.plist", sizeof("Info.plist")); // X.app/Contents/Info.plist NSError* error; NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL fileURLWithPath:@(infoPlistPath)] From 7223803a76a36363c19304d74d005a4158d4c4c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mil=C3=A1n=20Figueredo?= Date: Mon, 20 Jul 2026 09:29:59 +0200 Subject: [PATCH 26/67] Common (Base64): corrects byte order on big-endian hosts (#2470) ffBase64EncodeRaw unconditionally byte-swapped the input word via __builtin_bswap32, which only produces the intended big-endian byte layout on little-endian hosts. On big-endian hosts the word is already in the correct order, so the swap corrupted the encoded output. Guard the swap with __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ so the encoder is correct on both endiannesses. --- src/common/impl/base64.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/common/impl/base64.c b/src/common/impl/base64.c index 98a415ffbd..3f5ddf139e 100644 --- a/src/common/impl/base64.c +++ b/src/common/impl/base64.c @@ -6,7 +6,13 @@ void ffBase64EncodeRaw(uint32_t size, const char* str, uint32_t* out_size, char* char* out = output; const char* ends = str + (size - size % 3); while (str != ends) { - uint32_t n = __builtin_bswap32(*(uint32_t*) str); + uint32_t n = *(uint32_t*) str; + #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + // The 3 input bytes must be laid out big-endian (str[0] in the most + // significant position). On little-endian hosts swap; on big-endian + // hosts the word is already in the right order. + n = __builtin_bswap32(n); + #endif *out++ = chars[(n >> 26) & 63]; *out++ = chars[(n >> 20) & 63]; *out++ = chars[(n >> 14) & 63]; From db46e06f2e56e660cad77a01c0b6faf3e14f9dd8 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Mon, 20 Jul 2026 15:36:57 +0800 Subject: [PATCH 27/67] Common (Base64): unifies the code of endian testing --- src/common/impl/base64.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/impl/base64.c b/src/common/impl/base64.c index 3f5ddf139e..fa987c303c 100644 --- a/src/common/impl/base64.c +++ b/src/common/impl/base64.c @@ -7,7 +7,7 @@ void ffBase64EncodeRaw(uint32_t size, const char* str, uint32_t* out_size, char* const char* ends = str + (size - size % 3); while (str != ends) { uint32_t n = *(uint32_t*) str; - #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + #if !__BIG_ENDIAN__ // The 3 input bytes must be laid out big-endian (str[0] in the most // significant position). On little-endian hosts swap; on big-endian // hosts the word is already in the right order. From 69f3d9ae3399ef4dbc8d4b2bb58f17b0ddf331a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Sun, 19 Jul 2026 20:42:49 +0800 Subject: [PATCH 28/67] CI (FreeBSD): fixes building --- .github/workflows/build-freebsd-amd64.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-freebsd-amd64.yml b/.github/workflows/build-freebsd-amd64.yml index 041bc4ff20..a6af2cba09 100644 --- a/.github/workflows/build-freebsd-amd64.yml +++ b/.github/workflows/build-freebsd-amd64.yml @@ -31,7 +31,7 @@ jobs: - name: Update and install dependencies run: | sudo pkg update - sudo pkg install -y cmake git pkgconf binutils wayland vulkan-headers vulkan-loader libxcb libXrandr libX11 libdrm glib dconf dbus sqlite3-tcl egl opencl ocl-icd v4l_compat chafa lua54 libva libvdpau + sudo pkg install -y cmake git pkgconf binutils wayland vulkan-headers vulkan-loader libxcb libXrandr libX11 libdrm glib dconf dbus sqlite3 egl opencl ocl-icd v4l_compat chafa lua54 libva libvdpau - name: CMake configuration run: cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_BUILD_TYPE=${{ vars.CMAKE_BUILD_TYPE || 'RelWithDebInfo' }} . From 0b72c9b08305461677a2135868bb17a6fb62c0ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Tue, 21 Jul 2026 21:12:16 +0800 Subject: [PATCH 29/67] Networking (Windows): simplifies code --- src/common/impl/networking_windows.c | 52 +++++++++++----------------- 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/src/common/impl/networking_windows.c b/src/common/impl/networking_windows.c index ba4af1dc8b..b939e6a566 100644 --- a/src/common/impl/networking_windows.c +++ b/src/common/impl/networking_windows.c @@ -141,16 +141,14 @@ const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* ho } // Initialize overlapped structure with WSA event for asynchronous I/O - state->overlapped = (OVERLAPPED) { - .hEvent = WSACreateEvent() - }; + state->overlapped = (OVERLAPPED) {}; - if (state->overlapped.hEvent == WSA_INVALID_EVENT) { - FF_DEBUG("WSACreateEvent() failed"); + if (!NT_SUCCESS(NtCreateEvent(&state->overlapped.hEvent, EVENT_ALL_ACCESS, nullptr, NotificationEvent, FALSE))) { + FF_DEBUG("NtCreateEvent() failed"); closesocket(state->sockfd); FreeAddrInfoW(addr); state->sockfd = INVALID_SOCKET; - return "WSACreateEvent() failed"; + return "NtCreateEvent() failed"; } // Build HTTP command @@ -194,7 +192,7 @@ const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* ho if (!result) { if (WSAGetLastError() != WSA_IO_PENDING) { FF_DEBUG("ConnectEx() failed: %s", ffDebugWin32Error((DWORD) WSAGetLastError())); - WSACloseEvent(state->overlapped.hEvent); + NtClose(state->overlapped.hEvent); closesocket(state->sockfd); state->sockfd = INVALID_SOCKET; ffStrbufDestroy(&state->command); @@ -219,37 +217,27 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf return "ffNetworkingSendHttpRequest() failed"; } + DWORD transfer; uint32_t timeout = state->timeout; - if (timeout > 0) { - FF_DEBUG("WSAWaitForMultipleEvents with timeout: %u ms", timeout); - DWORD result = WSAWaitForMultipleEvents(1, &state->overlapped.hEvent, TRUE, timeout, FALSE); - if (result != WSA_WAIT_EVENT_0) { - if (result == WSA_WAIT_TIMEOUT) { - FF_DEBUG("WSAWaitForMultipleEvents timed out"); - } else { - FF_DEBUG("WSAWaitForMultipleEvents failed: %s", ffDebugWin32Error((DWORD) WSAGetLastError())); - } - if (CancelIoEx((HANDLE) state->sockfd, &state->overlapped)) { - WSAWaitForMultipleEvents(1, &state->overlapped.hEvent, TRUE, 10, TRUE); - } - WSACloseEvent(state->overlapped.hEvent); - closesocket(state->sockfd); - ffStrbufDestroy(&state->command); - return "WSAWaitForMultipleEvents() failed or timeout"; + if (!GetOverlappedResultEx((HANDLE) state->sockfd, &state->overlapped, &transfer, timeout > 0 ? timeout : INFINITE, FALSE)) { + DWORD error = GetLastError(); + if (error == WAIT_TIMEOUT) { + FF_DEBUG("GetOverlappedResultEx timed out"); + } else { + FF_DEBUG("GetOverlappedResultEx failed: %s", ffDebugWin32Error(error)); } - } - - DWORD transfer, flags; - if (!WSAGetOverlappedResult(state->sockfd, &state->overlapped, &transfer, TRUE, &flags)) { - FF_DEBUG("WSAGetOverlappedResult failed: %s", ffDebugWin32Error((DWORD) WSAGetLastError())); + IO_STATUS_BLOCK cancelIosb = {}; + if (NT_SUCCESS(NtCancelIoFileEx((HANDLE) state->sockfd, (PIO_STATUS_BLOCK) &state->overlapped, &cancelIosb))) { + NtWaitForSingleObject(state->overlapped.hEvent, TRUE, &(LARGE_INTEGER) { .QuadPart = (int64_t) 10 * -10000 }); + } + NtClose(state->overlapped.hEvent); closesocket(state->sockfd); - WSACloseEvent(state->overlapped.hEvent); ffStrbufDestroy(&state->command); - return "WSAGetOverlappedResult() failed"; + return "GetOverlappedResultEx() failed or timeout"; } - FF_DEBUG("WSAGetOverlappedResult succeeded, %u bytes sent", (unsigned) transfer); + FF_DEBUG("GetOverlappedResultEx succeeded, %u bytes sent", (unsigned) transfer); ffStrbufDestroy(&state->command); - WSACloseEvent(state->overlapped.hEvent); + NtClose(state->overlapped.hEvent); state->overlapped.hEvent = nullptr; if (setsockopt(state->sockfd, SOL_SOCKET, SO_UPDATE_CONNECT_CONTEXT, nullptr, 0) != 0) { From dda39f0c6712788ecf257a987ac5630f878d92ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=99=93=E4=BC=9F?= Date: Tue, 21 Jul 2026 21:14:38 +0800 Subject: [PATCH 30/67] CI (Linux): adds loong64 build workflow (#2469) * CI: add loong64 build workflow Add reusable workflow for building fastfetch on LoongArch 64-bit architecture using QEMU user-mode emulation + Docker container (lcr.loongnix.cn/debian:14). Integrate into CI pipeline and release dependencies. * CI (loong64): pin docker/setup-qemu-action to full commit SHA Pin to 06116385d9baf250c9f4dcb4858b16962ea869c3 (v4.1.0) for immutable action reference as required by Codacy. --- .github/workflows/build-linux-loong64.yml | 57 +++++++++++++++++++++++ .github/workflows/ci.yml | 10 ++++ 2 files changed, 67 insertions(+) create mode 100644 .github/workflows/build-linux-loong64.yml diff --git a/.github/workflows/build-linux-loong64.yml b/.github/workflows/build-linux-loong64.yml new file mode 100644 index 0000000000..25b140d8f4 --- /dev/null +++ b/.github/workflows/build-linux-loong64.yml @@ -0,0 +1,57 @@ +name: Reusable Linux loong64 + +on: + workflow_call: + +env: + CMAKE_BUILD_TYPE: ${{ vars.CMAKE_BUILD_TYPE || 'RelWithDebInfo' }} + +jobs: + build: + runs-on: ubuntu-24.04 + steps: + - name: checkout repository + uses: actions/checkout@v7 + + - name: set up QEMU for loong64 + uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0 + + - name: build in loong64 container + run: | + docker run --rm --platform linux/loong64 \ + -v ${{ github.workspace }}:/workspace \ + -w /workspace \ + lcr.loongnix.cn/debian:14 \ + bash -c ' + set -e + uname -a + cat /etc/os-release || true + + apt-get update + apt-get install -y --no-install-recommends \ + cmake make gcc g++ \ + libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev \ + libdconf-dev libdbus-1-dev libmagickcore-dev \ + libsqlite3-dev librpm-dev \ + libegl-dev libglx-dev ocl-icd-opencl-dev \ + libpulse-dev libdrm-dev \ + libelf-dev libefl-all-dev \ + liblua5.4-dev \ + libvdpau-dev libva-dev \ + rpm + + cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr . + cmake --build . --target package --verbose -j$(nproc) + ./fastfetch --list-features + time ./fastfetch -c presets/ci.jsonc --stat false + time ./fastfetch -c presets/ci.jsonc --format json + time ./flashfetch + ldd fastfetch + ctest --output-on-failure + ' + + - name: upload artifacts + uses: actions/upload-artifact@v7 + with: + name: fastfetch-linux-loong64 + path: ./fastfetch-*.* diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 00d37612df..a309d42ecc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,6 +54,15 @@ jobs: uses: ./.github/workflows/build-linux-armv7l.yml secrets: inherit + linux-loong64: + needs: no-features-test + name: Linux-loong64 + permissions: + security-events: write + contents: read + uses: ./.github/workflows/build-linux-loong64.yml + secrets: inherit + linux-vms: needs: no-features-test name: Linux-${{ matrix.arch }} @@ -188,6 +197,7 @@ jobs: - linux-hosts - linux-i686 - linux-armv7l + - linux-loong64 - linux-vms - musl-amd64 - macos-hosts From bc1d305aaacc6668fda935e1bcbdfd3ddbce6fbd Mon Sep 17 00:00:00 2001 From: Carter Li Date: Wed, 22 Jul 2026 09:29:26 +0800 Subject: [PATCH 31/67] Logo (Builtin): removes hypros Ref: #1178 #2460 --- src/logo/ascii/h.inc | 13 ------------- src/logo/ascii/h/hypros.txt | 17 ----------------- 2 files changed, 30 deletions(-) delete mode 100644 src/logo/ascii/h/hypros.txt diff --git a/src/logo/ascii/h.inc b/src/logo/ascii/h.inc index 9d471eb737..d4efd6cc47 100644 --- a/src/logo/ascii/h.inc +++ b/src/logo/ascii/h.inc @@ -168,19 +168,6 @@ static const FFlogo H[] = { .colorTitle = FF_COLOR_FG_GREEN, }, #endif - #ifdef FASTFETCH_DATATEXT_LOGO_HYPROS - // HyprOS - { - .names = { "hypros" }, - .lines = FASTFETCH_DATATEXT_LOGO_HYPROS, - .colors = { - FF_COLOR_FG_RED, - FF_COLOR_FG_YELLOW, - FF_COLOR_FG_CYAN, - FF_COLOR_FG_BLUE, - }, - }, - #endif #ifdef FASTFETCH_DATATEXT_LOGO_HYPERBOLA // Hyperbola { diff --git a/src/logo/ascii/h/hypros.txt b/src/logo/ascii/h/hypros.txt deleted file mode 100644 index 5b7d4dddf0..0000000000 --- a/src/logo/ascii/h/hypros.txt +++ /dev/null @@ -1,17 +0,0 @@ - ___ - ,adZZEEEE#&$2>=x. - $1,zAP*~'$4_,-$2'~*VM$2N&x. - $1,%&P^`$4<$3,.$4<<$2,-===--.N>x - $1.%M7$4//$3,%^$2,x<3#$13EEbo$2<&>&b - $1&#/$4/$3.<^$4/$2x<>^$4-.`$1`+&WW$2<&N&; -$1/#/$4//$34$4//$2/W/ $4^+.`$1`###$2NM\ -$1##'$4|$3.l$4|$2,&/ $4`.',$1I#I$2HI# -$1#I$4||$3`I$4|$2(#( $3)`'$1)##$2H~^ -$1@\$4|||$3\$4\$2`X\ $3///$1,##%V$3'/ -$4\\\\\\$3Y,$2*@b, $3.-+//$1/&#%#/$3,' -$4`\\\\$2,.$4\$3<$2`*$3^`x<$1,z<#&#x"$3,' - $3`x<<$2`Xx,$3`<_`$1+{##&@P^$4'>$3' - $3`<_<<$2^<\-.$3`*`>$1<^'$4,-' - $3`<_=-$2^\Xx$1XX\.$3+<. - $3`^<_-$2^ Date: Wed, 22 Jul 2026 11:08:17 +0800 Subject: [PATCH 32/67] Common (macOS): adds M5x temp detection support --- src/common/apple/smc_temps.c | 35 +++++++++++++++++++++++++++++++++++ src/common/apple/smc_temps.h | 2 ++ src/detection/cpu/cpu_apple.c | 3 +++ src/detection/gpu/gpu_apple.c | 3 +++ 4 files changed, 43 insertions(+) diff --git a/src/common/apple/smc_temps.c b/src/common/apple/smc_temps.c index 74b7929252..9fb18210f4 100644 --- a/src/common/apple/smc_temps.c +++ b/src/common/apple/smc_temps.c @@ -392,6 +392,27 @@ const char* ffDetectSmcTemps(enum FFTempType type, double* result) { count += detectTemp(conn, "Tp0e", result); // CPU performance core 8 break; + case FF_TEMP_CPU_M5X: + count += detectTemp(conn, "Tp00", result); // CPU super core 1 + count += detectTemp(conn, "Tp04", result); // CPU super core 2 + count += detectTemp(conn, "Tp08", result); // CPU super core 3 + count += detectTemp(conn, "Tp0C", result); // CPU super core 4 + count += detectTemp(conn, "Tp0G", result); // CPU super core 5 + count += detectTemp(conn, "Tp0K", result); // CPU super core 6 + count += detectTemp(conn, "Tp0O", result); // CPU performance core 1 + count += detectTemp(conn, "Tp0R", result); // CPU performance core 2 + count += detectTemp(conn, "Tp0U", result); // CPU performance core 3 + count += detectTemp(conn, "Tp0X", result); // CPU performance core 4 + count += detectTemp(conn, "Tp0a", result); // CPU performance core 5 + count += detectTemp(conn, "Tp0d", result); // CPU performance core 6 + count += detectTemp(conn, "Tp0g", result); // CPU performance core 7 + count += detectTemp(conn, "Tp0j", result); // CPU performance core 8 + count += detectTemp(conn, "Tp0m", result); // CPU performance core 9 + count += detectTemp(conn, "Tp0p", result); // CPU performance core 10 + count += detectTemp(conn, "Tp0u", result); // CPU performance core 11 + count += detectTemp(conn, "Tp0y", result); // CPU performance core 12 + break; + case FF_TEMP_GPU_INTEL: count += detectTemp(conn, "TCGC", result); // GPU Intel Graphics goto gpu_unknown; @@ -442,6 +463,17 @@ const char* ffDetectSmcTemps(enum FFTempType type, double* result) { count += detectTemp(conn, "Tg0k", result); // GPU 8 break; + case FF_TEMP_GPU_M5X: + count += detectTemp(conn, "Tg0U", result); // GPU 1 + count += detectTemp(conn, "Tg0X", result); // GPU 2 + count += detectTemp(conn, "Tg0d", result); // GPU 3 + count += detectTemp(conn, "Tg0g", result); // GPU 4 + count += detectTemp(conn, "Tg0j", result); // GPU 5 + count += detectTemp(conn, "Tg1Y", result); // GPU 6 + count += detectTemp(conn, "Tg1c", result); // GPU 7 + count += detectTemp(conn, "Tg1g", result); // GPU 8 + break; + case FF_TEMP_BATTERY: count += detectTemp(conn, "TB1T", result); // Battery count += detectTemp(conn, "TB2T", result); // Battery @@ -452,6 +484,9 @@ const char* ffDetectSmcTemps(enum FFTempType type, double* result) { count += detectTemp(conn, "Tm06", result); // Memory 2 count += detectTemp(conn, "Tm08", result); // Memory 3 count += detectTemp(conn, "Tm09", result); // Memory 4 + count += detectTemp(conn, "Tm0p", result); // Memory Proximity 1 (M4) + count += detectTemp(conn, "Tm1p", result); // Memory Proximity 2 (M4) + count += detectTemp(conn, "Tm2p", result); // Memory Proximity 3 (M4) break; } diff --git a/src/common/apple/smc_temps.h b/src/common/apple/smc_temps.h index cf0c043055..0f03a912b3 100644 --- a/src/common/apple/smc_temps.h +++ b/src/common/apple/smc_temps.h @@ -14,6 +14,7 @@ enum FFTempType: uint8_t { FF_TEMP_CPU_M2X, FF_TEMP_CPU_M3X, FF_TEMP_CPU_M4X, + FF_TEMP_CPU_M5X, FF_TEMP_GPU_INTEL, FF_TEMP_GPU_AMD, @@ -22,6 +23,7 @@ enum FFTempType: uint8_t { FF_TEMP_GPU_M2X, FF_TEMP_GPU_M3X, FF_TEMP_GPU_M4X, + FF_TEMP_GPU_M5X, FF_TEMP_BATTERY, diff --git a/src/detection/cpu/cpu_apple.c b/src/detection/cpu/cpu_apple.c index 91beb688ef..bce7d55b29 100644 --- a/src/detection/cpu/cpu_apple.c +++ b/src/detection/cpu/cpu_apple.c @@ -25,6 +25,9 @@ static double detectCpuTemp(const FFCPUOptions* options, const FFstrbuf* cpuName case 4: error = ffDetectSmcTemps(FF_TEMP_CPU_M4X, &result); break; + case 5: + error = ffDetectSmcTemps(FF_TEMP_CPU_M5X, &result); + break; default: error = "Unsupported Apple Silicon CPU"; } diff --git a/src/detection/gpu/gpu_apple.c b/src/detection/gpu/gpu_apple.c index 4942252987..f3775bc5a3 100644 --- a/src/detection/gpu/gpu_apple.c +++ b/src/detection/gpu/gpu_apple.c @@ -28,6 +28,9 @@ static double detectGpuTemp(const FFstrbuf* gpuName) { case 4: error = ffDetectSmcTemps(FF_TEMP_GPU_M4X, &result); break; + case 5: + error = ffDetectSmcTemps(FF_TEMP_GPU_M5X, &result); + break; default: error = "Unsupported Apple Silicon GPU"; break; From 111c1bab755f27050aeb75aba17d077f4dffe215 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Thu, 23 Jul 2026 09:55:31 +0800 Subject: [PATCH 33/67] Codec (macOS): fixes detection for VP9 --- src/detection/codec/codec_apple.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/detection/codec/codec_apple.c b/src/detection/codec/codec_apple.c index b202fad349..2cf2a48b2f 100644 --- a/src/detection/codec/codec_apple.c +++ b/src/detection/codec/codec_apple.c @@ -65,16 +65,20 @@ static FFCodecType ffCodecDetectEncoders(void) { static FFCodecType ffCodecDetectDecoders() { FFCodecType types = FF_CODEC_TYPE_NONE; for (uint32_t i = 0; i < ARRAY_SIZE(FF_CODEC_CODECS); ++i) { - if (types & FF_CODEC_CODECS[i].type) { + auto codec = FF_CODEC_CODECS[i]; + if (types & codec.type) { continue; } - bool supported = VTIsHardwareDecodeSupported(FF_CODEC_CODECS[i].codec); + if (__builtin_available(macOS 11.0, *)) { + VTRegisterSupplementalVideoDecoderIfAvailable(codec.codec); + } + bool supported = VTIsHardwareDecodeSupported(codec.codec); if (!supported) { continue; } - types |= FF_CODEC_CODECS[i].type; + types |= codec.type; } return types; From c5a8950ad4d3a460d5789c66e402164ae8cea693 Mon Sep 17 00:00:00 2001 From: h4sht Date: Fri, 24 Jul 2026 02:39:07 +0200 Subject: [PATCH 34/67] Weather: fixes memory leak and replace aggressive exit(1) with proper error handling (#2474) - Add missing ffStrbufDestroy(&options->location) in ffDestroyWeatherOptions to prevent memory leak when location is set via config - Replace exit(1) in ffPrepareWeather with setting status to an error string, allowing the error to be properly propagated through the existing error mechanism Co-authored-by: tru3 --- src/detection/weather/weather.c | 4 ++-- src/modules/weather/weather.c | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/detection/weather/weather.c b/src/detection/weather/weather.c index 8a70a966a3..0f65af8cfd 100644 --- a/src/detection/weather/weather.c +++ b/src/detection/weather/weather.c @@ -7,8 +7,8 @@ static const char* status = FF_UNITIALIZED; void ffPrepareWeather(FFWeatherOptions* options) { if (status != FF_UNITIALIZED) { - fputs("Error: Weather module can only be used once due to internal limitations\n", stderr); - exit(1); + status = "Weather module can only be used once due to internal limitations"; + return; } state.timeout = options->timeout; diff --git a/src/modules/weather/weather.c b/src/modules/weather/weather.c index 6c156929d8..414a18e7e7 100644 --- a/src/modules/weather/weather.c +++ b/src/modules/weather/weather.c @@ -87,6 +87,7 @@ void ffInitWeatherOptions(FFWeatherOptions* options) { void ffDestroyWeatherOptions(FFWeatherOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); + ffStrbufDestroy(&options->location); ffStrbufDestroy(&options->outputFormat); } From 97fa52fef0de4b83a601fee0fc312b8e54fca43a Mon Sep 17 00:00:00 2001 From: h4sht Date: Fri, 24 Jul 2026 02:41:54 +0200 Subject: [PATCH 35/67] FFstrbuf: removes dead code in ffStrbufSubstrBefore (#2475) The redundant `if (index < strbuf->length)` check inside the static string branch is always true since `strbuf->length <= index` is already checked at the top of the function and returns false. Remove this dead code for clarity. Co-authored-by: tru3 --- src/common/impl/FFstrbuf.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/common/impl/FFstrbuf.c b/src/common/impl/FFstrbuf.c index 35747f8fe4..7401a2f22f 100644 --- a/src/common/impl/FFstrbuf.c +++ b/src/common/impl/FFstrbuf.c @@ -410,9 +410,7 @@ bool ffStrbufSubstrBefore(FFstrbuf* strbuf, uint32_t index) { if (strbuf->allocated == 0) { // static string - if (index < strbuf->length) { - ffStrbufInitNS(strbuf, index, strbuf->chars); - } + ffStrbufInitNS(strbuf, index, strbuf->chars); return true; } From f6c25667fead018338822ead2dacc7b3016c4d8b Mon Sep 17 00:00:00 2001 From: h4sht Date: Fri, 24 Jul 2026 02:43:31 +0200 Subject: [PATCH 36/67] Display: adds missing ffStrbufDestroy calls in ffOptionsDestroyDisplay (#2476) 13 FFstrbuf fields were initialized but never destroyed, potentially leaking memory when configured via JSON or command-line: - barBorderLeft, barBorderRight - barBorderLeftElapsed, barBorderRightElapsed - barColorElapsed, barColorTotal, barColorBorder - tempColorGreen, tempColorYellow, tempColorRed - percentColorGreen, percentColorYellow, percentColorRed Co-authored-by: tru3 --- src/options/display.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/options/display.c b/src/options/display.c index a60286c865..a1136e7b1a 100644 --- a/src/options/display.c +++ b/src/options/display.c @@ -897,6 +897,19 @@ void ffOptionsDestroyDisplay(FFOptionsDisplay* options) { ffStrbufDestroy(&options->keyValueSeparator); ffStrbufDestroy(&options->barCharElapsed); ffStrbufDestroy(&options->barCharTotal); + ffStrbufDestroy(&options->barBorderLeft); + ffStrbufDestroy(&options->barBorderRight); + ffStrbufDestroy(&options->barBorderLeftElapsed); + ffStrbufDestroy(&options->barBorderRightElapsed); + ffStrbufDestroy(&options->barColorElapsed); + ffStrbufDestroy(&options->barColorTotal); + ffStrbufDestroy(&options->barColorBorder); + ffStrbufDestroy(&options->tempColorGreen); + ffStrbufDestroy(&options->tempColorYellow); + ffStrbufDestroy(&options->tempColorRed); + ffStrbufDestroy(&options->percentColorGreen); + ffStrbufDestroy(&options->percentColorYellow); + ffStrbufDestroy(&options->percentColorRed); FF_LIST_FOR_EACH (FFstrbuf, item, options->constants) { ffStrbufDestroy(item); } From 18eef29d0a9aa0b41f6b406d9896daf886c89670 Mon Sep 17 00:00:00 2001 From: tru3 Date: Thu, 23 Jul 2026 21:49:57 +0200 Subject: [PATCH 37/67] PublicIP: replaces exit(1) with proper error status - Add missing ffStrbufDestroy(&options->outputColor) in ffDestroySeparatorOptions to prevent memory leak when outputColor is set via JSON config - Replace two exit(1) calls in ffPreparePublicIp with proper error status propagation, matching the pattern used in the weather module --- src/detection/publicip/publicip.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/detection/publicip/publicip.c b/src/detection/publicip/publicip.c index 74c592035f..a1b5ba5a95 100644 --- a/src/detection/publicip/publicip.c +++ b/src/detection/publicip/publicip.c @@ -9,8 +9,8 @@ void ffPreparePublicIp(FFPublicIPOptions* options) { FFNetworkingState* state = &states[options->ipv6]; const char** status = &statuses[options->ipv6]; if (*status != FF_UNINITIALIZED) { - fputs("Error: PublicIp module can only be used once due to internal limitations\n", stderr); - exit(1); + *status = "PublicIp module can only be used once due to internal limitations"; + return; } state->timeout = options->timeout; @@ -25,8 +25,8 @@ void ffPreparePublicIp(FFPublicIPOptions* options) { uint32_t hostStartIndex = ffStrbufFirstIndexS(&host, "://"); if (hostStartIndex < host.length) { if (hostStartIndex != 4 || !ffStrbufStartsWithIgnCaseS(&host, "http")) { - fputs("Error: only http: protocol is supported. Use `Command` module with `curl` if needed\n", stderr); - exit(1); + *status = "Only http: protocol is supported. Use `Command` module with `curl` if needed"; + return; } ffStrbufSubstrAfter(&host, hostStartIndex + (uint32_t) (strlen("://") - 1)); } From ccf39093726bc227f5b3c602a7c17e722e811fda Mon Sep 17 00:00:00 2001 From: tru3 Date: Thu, 23 Jul 2026 21:49:57 +0200 Subject: [PATCH 38/67] Separator: fixes memory leakes - Add missing ffStrbufDestroy(&options->outputColor) in ffDestroySeparatorOptions to prevent memory leak when outputColor is set via JSON config - Replace two exit(1) calls in ffPreparePublicIp with proper error status propagation, matching the pattern used in the weather module --- src/modules/separator/separator.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/separator/separator.c b/src/modules/separator/separator.c index e0179b1e9b..733bd38e10 100644 --- a/src/modules/separator/separator.c +++ b/src/modules/separator/separator.c @@ -117,6 +117,7 @@ void ffInitSeparatorOptions(FFSeparatorOptions* options) { void ffDestroySeparatorOptions(FFSeparatorOptions* options) { ffStrbufDestroy(&options->string); + ffStrbufDestroy(&options->outputColor); } FFModuleBaseInfo ffSeparatorModuleInfo = { From 6490e2707f85f82c53f38bb8b58b27122e5bd2bd Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 28 Jul 2026 10:31:32 +0800 Subject: [PATCH 39/67] Networking: relaxes accepted response format --- src/common/impl/networking_linux.c | 2 +- src/common/impl/networking_windows.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/impl/networking_linux.c b/src/common/impl/networking_linux.c index dbee39a69f..530c9e7af8 100644 --- a/src/common/impl/networking_linux.c +++ b/src/common/impl/networking_linux.c @@ -481,7 +481,7 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf return "No HTTP header end found"; } - if (!ffStrbufStartsWithS(buffer, "HTTP/1.0 200 OK\r\n")) { + if (!ffStrbufStartsWithS(buffer, "HTTP/1.0 200 OK\r\n") && !ffStrbufStartsWithS(buffer, "HTTP/1.1 200 OK\r\n")) { FF_DEBUG("Invalid response: %.40s...", buffer->chars); return "Invalid response"; } diff --git a/src/common/impl/networking_windows.c b/src/common/impl/networking_windows.c index b939e6a566..d9049eb62e 100644 --- a/src/common/impl/networking_windows.c +++ b/src/common/impl/networking_windows.c @@ -334,7 +334,7 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf return "No HTTP header end found"; } - if (!ffStrbufStartsWithS(buffer, "HTTP/1.0 200 OK\r\n")) { + if (!ffStrbufStartsWithS(buffer, "HTTP/1.0 200 OK\r\n") && !ffStrbufStartsWithS(buffer, "HTTP/1.1 200 OK\r\n")) { FF_DEBUG("Invalid response: %.40s...", buffer->chars); return "Invalid response"; } From 4f0b00116e961d517856b0a3e5caa79b55079ff1 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 28 Jul 2026 10:37:36 +0800 Subject: [PATCH 40/67] PublicIP: fixes invalid URL parser --- src/detection/publicip/publicip.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/detection/publicip/publicip.c b/src/detection/publicip/publicip.c index a1b5ba5a95..4ae7116e53 100644 --- a/src/detection/publicip/publicip.c +++ b/src/detection/publicip/publicip.c @@ -34,9 +34,8 @@ void ffPreparePublicIp(FFPublicIPOptions* options) { FF_STRBUF_AUTO_DESTROY path = ffStrbufCreate(); if (pathStartIndex != host.length) { - ffStrbufAppendNS(&path, pathStartIndex, host.chars + (host.length - pathStartIndex)); - host.length = pathStartIndex; - host.chars[pathStartIndex] = '\0'; + ffStrbufAppendNS(&path, host.length - pathStartIndex, host.chars + pathStartIndex); + ffStrbufSubstrBefore(&host, pathStartIndex); } *status = ffNetworkingSendHttpRequest(state, host.chars, path.length == 0 ? "/" : path.chars, nullptr); From 5476e014075681dd5b309f2734ed60c8dd648733 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 28 Jul 2026 10:38:53 +0800 Subject: [PATCH 41/67] Revert "PublicIP: replaces exit(1) with proper error status" This reverts commit 18eef29d0a9aa0b41f6b406d9896daf886c89670. --- src/detection/publicip/publicip.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/detection/publicip/publicip.c b/src/detection/publicip/publicip.c index 4ae7116e53..c0e8afb478 100644 --- a/src/detection/publicip/publicip.c +++ b/src/detection/publicip/publicip.c @@ -9,8 +9,8 @@ void ffPreparePublicIp(FFPublicIPOptions* options) { FFNetworkingState* state = &states[options->ipv6]; const char** status = &statuses[options->ipv6]; if (*status != FF_UNINITIALIZED) { - *status = "PublicIp module can only be used once due to internal limitations"; - return; + fputs("Error: PublicIp module can only be used once due to internal limitations\n", stderr); + exit(1); } state->timeout = options->timeout; @@ -25,8 +25,8 @@ void ffPreparePublicIp(FFPublicIPOptions* options) { uint32_t hostStartIndex = ffStrbufFirstIndexS(&host, "://"); if (hostStartIndex < host.length) { if (hostStartIndex != 4 || !ffStrbufStartsWithIgnCaseS(&host, "http")) { - *status = "Only http: protocol is supported. Use `Command` module with `curl` if needed"; - return; + fputs("Error: only http: protocol is supported. Use `Command` module with `curl` if needed\n", stderr); + exit(1); } ffStrbufSubstrAfter(&host, hostStartIndex + (uint32_t) (strlen("://") - 1)); } From 738ad59f542dc686ce7b5858417e5e1168b04883 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 28 Jul 2026 10:40:33 +0800 Subject: [PATCH 42/67] Revert "Weather: replaces aggressive exit(1) with proper error handling (#2474)" This reverts commit c5a8950ad4d3a460d5789c66e402164ae8cea693. --- src/detection/weather/weather.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/detection/weather/weather.c b/src/detection/weather/weather.c index 0f65af8cfd..8a70a966a3 100644 --- a/src/detection/weather/weather.c +++ b/src/detection/weather/weather.c @@ -7,8 +7,8 @@ static const char* status = FF_UNITIALIZED; void ffPrepareWeather(FFWeatherOptions* options) { if (status != FF_UNITIALIZED) { - status = "Weather module can only be used once due to internal limitations"; - return; + fputs("Error: Weather module can only be used once due to internal limitations\n", stderr); + exit(1); } state.timeout = options->timeout; From 2c736697d26a4ae0f575d853fa68ecd11955f868 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 28 Jul 2026 10:52:04 +0800 Subject: [PATCH 43/67] Watch: adds `-w/--watch` as a seconds-based alias for `--dynamic-interval` Closes #2478 --- doc/help.json | 10 ++++++++++ src/fastfetch.c | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/doc/help.json b/doc/help.json index da6732fbc3..511099b3ee 100644 --- a/doc/help.json +++ b/doc/help.json @@ -83,6 +83,16 @@ "type": "num", "default": 0 } + }, + { + "long": "watch", + "short": "w", + "desc": "Alias of dynamic-interval, but the value is in seconds instead of milliseconds. If no value is provided, it defaults to 1 second", + "arg": { + "type": "num", + "optional": true, + "default": 1 + } } ], "Config": [ diff --git a/src/fastfetch.c b/src/fastfetch.c index cad134e15c..3146cefba7 100644 --- a/src/fastfetch.c +++ b/src/fastfetch.c @@ -670,6 +670,12 @@ static void parseCommand(FFdata* data, char* key, char* value) { } } else if (ffStrEqualsIgnCase(key, "--dynamic-interval")) { instance.state.dynamicInterval = ffOptionParseUInt32(key, value); // seconds to milliseconds + } else if (ffStrEqualsIgnCase(key, "-w") || ffStrEqualsIgnCase(key, "--watch")) { + if (value == nullptr) { + instance.state.dynamicInterval = 1000; // default to 1 second if no value is provided + } else { + instance.state.dynamicInterval = ffOptionParseUInt32(key, value) * 1000; // seconds to milliseconds + } } else { return; } From 476cd673450a21c7f6e9492ca3555269a23fb611 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 28 Jul 2026 13:29:47 +0800 Subject: [PATCH 44/67] Networking: adds Content-Length size limit to prevent excessive memory allocation and potential attacks --- src/common/impl/networking_linux.c | 7 +++++++ src/common/impl/networking_windows.c | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/common/impl/networking_linux.c b/src/common/impl/networking_linux.c index 530c9e7af8..770c0ce774 100644 --- a/src/common/impl/networking_linux.c +++ b/src/common/impl/networking_linux.c @@ -457,6 +457,13 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf if (clHeader) { contentLength = (uint32_t) strtoul(clHeader + 15, nullptr, 10); if (contentLength > 0) { + if (contentLength > 1024 * 1024) { // 1MB limit to prevent excessive memory allocation and potential attacks + FF_DEBUG("Content-Length is too large: %u bytes, aborting", contentLength); + close(state->sockfd); + state->sockfd = -1; + return "Content-Length too large"; + } + FF_DEBUG("Detected Content-Length: %u, pre-allocating buffer", contentLength); // Ensure buffer is large enough, adding header size and some margin ffStrbufEnsureFree(buffer, contentLength + 16); diff --git a/src/common/impl/networking_windows.c b/src/common/impl/networking_windows.c index d9049eb62e..409edff7e5 100644 --- a/src/common/impl/networking_windows.c +++ b/src/common/impl/networking_windows.c @@ -310,6 +310,13 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf if (clHeader) { contentLength = (uint32_t) strtoul(clHeader + 15, nullptr, 10); if (contentLength > 0) { + if (contentLength > 1024 * 1024) { // 1MB limit to prevent excessive memory allocation and potential attacks + FF_DEBUG("Content-Length is too large: %u bytes, aborting", contentLength); + closesocket(state->sockfd); + state->sockfd = INVALID_SOCKET; + return "Content-Length too large"; + } + FF_DEBUG("Detected Content-Length: %u, pre-allocating buffer", contentLength); // Ensure buffer is large enough, adding header size and some margin ffStrbufEnsureFree(buffer, contentLength + 16); From 1c3f0eb7c0ab15380af2f2bcdc6f1a6db05c8b5a Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 28 Jul 2026 14:36:19 +0800 Subject: [PATCH 45/67] FFstrbuf: adds integer overflow checks to EnsureFreeNoCheck and EnsureFixedLengthFree --- src/common/impl/FFstrbuf.c | 40 +++++++++++++++++++++++++++++--------- tests/strbuf.c | 2 +- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/common/impl/FFstrbuf.c b/src/common/impl/FFstrbuf.c index 7401a2f22f..2ed3e9de84 100644 --- a/src/common/impl/FFstrbuf.c +++ b/src/common/impl/FFstrbuf.c @@ -1,6 +1,7 @@ #include "common/FFstrbuf.h" #include "common/mallocHelper.h" #include "common/strutil.h" +#include "common/debug.h" #include #include @@ -64,13 +65,24 @@ FFstrbuf ffStrbufCreateF(const char* format, ...) { } void ffStrbufEnsureFreeNoCheck(FFstrbuf* strbuf, uint32_t free) { - uint32_t allocate = strbuf->length + free; + uint32_t allocate; + if (__builtin_expect(__builtin_uadd_overflow(strbuf->length, free, &allocate), false)) { + FF_DEBUG("Error: Integer overflow when calculating allocation size. Aborting"); + abort(); + } + if (allocate < FASTFETCH_STRBUF_DEFAULT_ALLOC) { // `<` for null terminator allocate = FASTFETCH_STRBUF_DEFAULT_ALLOC; } else { + if (__builtin_expect(allocate > (UINT32_MAX >> 1), false)) { + // User tried to allocate more than 2GB of memory, which exceeds the maximum size supported by FFstrbuf. + // This is likely an error or an attempt to exploit the program. Abort to prevent potential issues. + FF_DEBUG("Error: Attempted to allocate %" PRIu32 " bytes more than 2GB of memory in FFstrbuf. Aborting", allocate); + abort(); + } + // Round up to the next power of 2. // If the value is already a power of 2, it will be rounded up to the next power of 2. - assert(allocate < (UINT32_MAX >> 1)); allocate = 1U << (32 - __builtin_clz(allocate)); } @@ -91,16 +103,17 @@ void ffStrbufEnsureFreeNoCheck(FFstrbuf* strbuf, uint32_t free) { // Ensure that at least `free` bytes are available in the buffer besides the current length // for an empty buffer, free + 1 length memory will be allocated(+1 for the NUL) +// This function ensures a dynamic buffer is allocated even if free == 0 void ffStrbufEnsureFixedLengthFree(FFstrbuf* strbuf, uint32_t free) { - uint32_t oldFree = ffStrbufGetFree(strbuf); - if (oldFree >= free && !(strbuf->allocated == 0 && strbuf->length > 0)) { - return; - } - - uint32_t newCap = strbuf->allocated + (free - oldFree); + uint32_t newCap; if (strbuf->allocated == 0) { - newCap += strbuf->length + 1; + assert(strbuf->length < UINT32_MAX - 1); // We don't use static strings with length >= UINT32_MAX - 1, so this should never happen + if (__builtin_expect(__builtin_uadd_overflow(strbuf->length + 1, free, &newCap), false)) { + FF_DEBUG("Error: Integer overflow when calculating new capacity. Aborting"); + abort(); + } + char* newbuf = malloc(sizeof(*strbuf->chars) * newCap); if (strbuf->length == 0) { *newbuf = '\0'; @@ -109,6 +122,15 @@ void ffStrbufEnsureFixedLengthFree(FFstrbuf* strbuf, uint32_t free) { } strbuf->chars = newbuf; } else { + uint32_t oldFree = ffStrbufGetFree(strbuf); + if (oldFree >= free) { + return; + } + + if (__builtin_expect(__builtin_uadd_overflow(strbuf->allocated, free - oldFree, &newCap), false)) { + FF_DEBUG("Error: Integer overflow when calculating new capacity. Aborting"); + abort(); + } strbuf->chars = realloc(strbuf->chars, sizeof(*strbuf->chars) * newCap); } diff --git a/tests/strbuf.c b/tests/strbuf.c index 6007ff9087..a8e698cafc 100644 --- a/tests/strbuf.c +++ b/tests/strbuf.c @@ -414,7 +414,7 @@ int main(void) { ffStrbufInit(&strbuf); ffStrbufEnsureFixedLengthFree(&strbuf, 0); VERIFY(strbuf.length == 0); - VERIFY(strbuf.allocated == 0); + VERIFY(strbuf.allocated == 1); ffStrbufDestroy(&strbuf); // ffStrbufEnsureFixedLengthFree / empty buffer but oldFree >= newFree From dc464d6efe2b644004515a66f0bdd7d28fa220f7 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 28 Jul 2026 14:51:54 +0800 Subject: [PATCH 46/67] FFstrbuf: prefers `ffStrbufEnsureFree` --- src/common/FFstrbuf.h | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/common/FFstrbuf.h b/src/common/FFstrbuf.h index 7b40bb42d1..e3722ad82d 100644 --- a/src/common/FFstrbuf.h +++ b/src/common/FFstrbuf.h @@ -221,9 +221,7 @@ static inline void ffStrbufClear(FFstrbuf* strbuf) { } static inline void ffStrbufAppendC(FFstrbuf* strbuf, char c) { - if (__builtin_expect(ffStrbufGetFree(strbuf) == 0, false)) { - ffStrbufEnsureFreeNoCheck(strbuf, 1); - } + ffStrbufEnsureFree(strbuf, 1); strbuf->chars[strbuf->length++] = c; strbuf->chars[strbuf->length] = '\0'; } @@ -232,9 +230,7 @@ static inline void ffStrbufAppendNC(FFstrbuf* strbuf, uint32_t num, char c) { if (__builtin_expect(num == 0, false)) { return; } - if (__builtin_expect(ffStrbufGetFree(strbuf) < num, false)) { - ffStrbufEnsureFreeNoCheck(strbuf, num); - } + ffStrbufEnsureFree(strbuf, num); memset(&strbuf->chars[strbuf->length], c, num); strbuf->length += num; @@ -245,9 +241,7 @@ static inline void ffStrbufAppendNS(FFstrbuf* strbuf, uint32_t length, const cha if (__builtin_expect(value == nullptr || length == 0, false)) { return; } - if (__builtin_expect(ffStrbufGetFree(strbuf) < length, false)) { - ffStrbufEnsureFreeNoCheck(strbuf, length); - } + ffStrbufEnsureFree(strbuf, length); memcpy(&strbuf->chars[strbuf->length], value, length); strbuf->length += length; From b653ef730d3183976e4388f6d7c0794c8d7f9d49 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 28 Jul 2026 15:11:46 +0800 Subject: [PATCH 47/67] General: removes `general.preRun` due to security concerns --- doc/json_schema.json | 5 ----- src/options/general.c | 7 +------ 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/doc/json_schema.json b/doc/json_schema.json index e9271b2ff7..4197c241bf 100644 --- a/doc/json_schema.json +++ b/doc/json_schema.json @@ -912,11 +912,6 @@ "description": "Set the timeout (ms) when waiting for child processes, `-1` for no timeout", "default": 5000 }, - "preRun": { - "type": "string", - "description": "Command to run before printing logos", - "default": "" - }, "detectVersion": { "type": "boolean", "description": "Whether to detect and display component versions. Mainly for benchmarking", diff --git a/src/options/general.c b/src/options/general.c index d1ee8e7818..ee7204218b 100644 --- a/src/options/general.c +++ b/src/options/general.c @@ -24,12 +24,7 @@ const char* ffOptionsParseGeneralJsonConfig(FFOptionsGeneral* options, yyjson_va } else if (unsafe_yyjson_equals_str(key, "processingTimeout")) { options->processingTimeout = (int32_t) yyjson_get_int(val); } else if (unsafe_yyjson_equals_str(key, "preRun")) { - if (!yyjson_is_str(val)) { - return "general.preRun must be a string"; - } - if (system(unsafe_yyjson_get_str(val)) < 0) { - return "Failed to execute preRun command"; - } + return "general.preRun is removed due to security concerns."; } else if (unsafe_yyjson_equals_str(key, "detectVersion")) { options->detectVersion = yyjson_get_bool(val); } else if (unsafe_yyjson_equals_str(key, "playerName")) { From a58fc4f77daeeeb84984a298872896e5cf42755a Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 28 Jul 2026 15:21:28 +0800 Subject: [PATCH 48/67] CI: disable Haiku Ref: https://github.com/haikuports/haikuports/issues/14445 --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a309d42ecc..46ac9d4fab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -154,6 +154,7 @@ jobs: secrets: inherit haiku-amd64: + if: false # Disabled because the Haiku build is currently broken needs: no-features-test name: Haiku-amd64 permissions: @@ -207,7 +208,7 @@ jobs: - dragonfly-amd64 - solaris-amd64 - omnios-amd64 - - haiku-amd64 + # - haiku-amd64 - windows-hosts permissions: contents: write From 305ab14e7b0f4f02f16b1bce08a2021259fd97a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Tue, 28 Jul 2026 23:40:57 +0800 Subject: [PATCH 49/67] DisplayServer (Linux): don't assume the monitor has only one preferred mode Fixes #2481 --- .../displayserver/linux/wayland/kde-output.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/detection/displayserver/linux/wayland/kde-output.c b/src/detection/displayserver/linux/wayland/kde-output.c index ab8829cf48..7c49d858cf 100644 --- a/src/detection/displayserver/linux/wayland/kde-output.c +++ b/src/detection/displayserver/linux/wayland/kde-output.c @@ -57,23 +57,22 @@ static void waylandKdeCurrentModeListener(void* data, [[maybe_unused]] struct kd return; } - int set = 0; + bool foundCurrent = false, foundPreferred = false; FF_LIST_FOR_EACH (WaylandKdeMode, m, *(FFlist*) wldata->internal) { - if (m->pMode == mode) { + if (!foundCurrent && m->pMode == mode) { wldata->width = m->width; wldata->height = m->height; wldata->refreshRate = m->refreshRate; - if (++set == 2) { - break; - } + foundCurrent = true; } - if (m->preferred) { + if (!foundPreferred && m->preferred) { wldata->preferredWidth = m->width; wldata->preferredHeight = m->height; wldata->preferredRefreshRate = m->refreshRate; - if (++set == 2) { - break; - } + foundPreferred = true; + } + if (foundCurrent && foundPreferred) { + break; } } } From 00b7c5b56cb751246c77213261a1160837eb6900 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Sat, 25 Jul 2026 18:27:52 +0800 Subject: [PATCH 50/67] Netif (Hurd): fixes a compiler warning --- src/common/impl/netif_gnu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/impl/netif_gnu.c b/src/common/impl/netif_gnu.c index 6593899a65..5e890dda0b 100644 --- a/src/common/impl/netif_gnu.c +++ b/src/common/impl/netif_gnu.c @@ -6,7 +6,7 @@ #include bool ffNetifGetDefaultRouteImplV4(FFNetifDefaultRouteResult* result) { - FILE* FF_AUTO_CLOSE_FILE netRoute = fopen("/proc/route", "r"); + FF_AUTO_CLOSE_FILE FILE* netRoute = fopen("/proc/route", "r"); if (!netRoute) { return false; From 5eaf98ecd2f4547d100b2541f4a6500c9a12a926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Wed, 29 Jul 2026 20:15:54 +0800 Subject: [PATCH 51/67] Common: forces `optionBuf` to be 8-byte aligned --- src/common/impl/commandoption.c | 2 +- src/common/impl/jsonconfig.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/impl/commandoption.c b/src/common/impl/commandoption.c index eacf4eab9b..f0b5f06672 100644 --- a/src/common/impl/commandoption.c +++ b/src/common/impl/commandoption.c @@ -175,7 +175,7 @@ static bool parseStructureCommand( for (FFModuleBaseInfo** modules = ffModuleInfos[toupper(line[0]) - 'A']; *modules; ++modules) { FFModuleBaseInfo* baseInfo = *modules; if (ffStrEqualsIgnCase(line, baseInfo->name)) { - uint8_t optionBuf[FF_OPTION_MAX_SIZE]; + alignas(uint64_t) uint8_t optionBuf[FF_OPTION_MAX_SIZE]; baseInfo->initOptions(optionBuf); if (data->resultDoc != nullptr) { fn(data, baseInfo, optionBuf); diff --git a/src/common/impl/jsonconfig.c b/src/common/impl/jsonconfig.c index 9feee1bf56..5179d5b5d3 100644 --- a/src/common/impl/jsonconfig.c +++ b/src/common/impl/jsonconfig.c @@ -95,7 +95,7 @@ static bool parseModuleJsonObject(const char* type, yyjson_val* jsonVal, yyjson_ for (FFModuleBaseInfo** modules = ffModuleInfos[toupper(type[0]) - 'A']; *modules; ++modules) { FFModuleBaseInfo* baseInfo = *modules; if (ffStrEqualsIgnCase(type, baseInfo->name)) { - uint8_t optionBuf[FF_OPTION_MAX_SIZE]; + alignas(uint64_t) uint8_t optionBuf[FF_OPTION_MAX_SIZE]; baseInfo->initOptions(optionBuf); if (jsonVal) { baseInfo->parseJsonObject(optionBuf, jsonVal); From 3fae9e0274ca0a3ccde9d4eb95f8f6c81d3b7f23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Fri, 31 Jul 2026 23:12:10 +0800 Subject: [PATCH 52/67] Libc (Windows): simplifies code --- src/detection/libc/libc_windows.cpp | 49 +++++++++++------------------ 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/src/detection/libc/libc_windows.cpp b/src/detection/libc/libc_windows.cpp index 20bbeda009..c3d866eb4d 100644 --- a/src/detection/libc/libc_windows.cpp +++ b/src/detection/libc/libc_windows.cpp @@ -7,48 +7,37 @@ extern "C" { #endif template -class version_t { - constexpr static auto buflen() noexcept { - unsigned int len = 2; // "." - if (Major == 0) { +struct version_t { + static constexpr uint32_t digits(uint32_t n) noexcept { + uint32_t len = 1; + for (auto temp = n / 10; temp; temp /= 10) { len++; - } else { - for (auto n = Major; n; len++, n /= 10); - } - - if (Minor == 0) { - len++; - } else { - for (auto n = Minor; n; len++, n /= 10); } return len; } - char buf[buflen()] = {}; + char buf[digits(Major) + digits(Minor) + 2]; - public: constexpr version_t() noexcept { - auto ptr = buf + buflen(); + auto ptr = buf + sizeof(buf); *--ptr = '\0'; - if (Minor == 0) { - *--ptr = '0'; - } else { - for (auto n = Minor; n; n /= 10) { - *--ptr = "0123456789"[n % 10]; + auto append = [&](uint32_t n) noexcept { + if (n == 0) { + *--ptr = '0'; + } else { + for (; n; n /= 10) { + *--ptr = static_cast('0' + n % 10); + } } - } + }; + + append(Minor); *--ptr = '.'; - if (Major == 0) { - *--ptr = '0'; - } else { - for (auto n = Major; n; n /= 10) { - *--ptr = "0123456789"[n % 10]; - } - } + append(Major); } - constexpr operator const char*() const { + constexpr operator const char*() const noexcept { return buf; } }; @@ -63,6 +52,6 @@ extern "C" const char* ffDetectLibc(FFLibcResult* result) { result->name = "msvcrt"; #endif - result->version = version<(__MSVCRT_VERSION__ >> 8), (__MSVCRT_VERSION__ & 8)>; + result->version = version<(__MSVCRT_VERSION__ >> 8), (__MSVCRT_VERSION__ & 0xFF)>; return nullptr; } From 66ce39e3fce3ef504a8bcc98d19a224e70dd496b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Sun, 2 Aug 2026 22:02:54 +0800 Subject: [PATCH 53/67] Shell (Windows): fixes incompatibility with the newest fish version --- src/detection/terminalshell/terminalshell.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/detection/terminalshell/terminalshell.c b/src/detection/terminalshell/terminalshell.c index 97cb9c3ef3..2d531f9712 100644 --- a/src/detection/terminalshell/terminalshell.c +++ b/src/detection/terminalshell/terminalshell.c @@ -90,7 +90,13 @@ static bool getShellVersionFish(FFstrbuf* exe, FFstrbuf* version) { return false; } uint32_t index = ffStrbufFirstIndexC(version, ' '); // skip "fish," - index = ffStrbufNextIndexC(version, index + 1, ' '); // skip "version" + while (!ffCharIsDigit(version->chars[index + 1])) { + index = ffStrbufNextIndexC(version, index + 1, ' '); // skip "version" + if (index == version->length) { + return false; + } + } + ffStrbufSubstrAfter(version, index); ffStrbufSubstrBeforeFirstC(version, ' '); return true; From 241ddb79b0460a9aea80e08eafa4dbfbe92b4007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Mon, 3 Aug 2026 20:00:27 +0800 Subject: [PATCH 54/67] Global: first step to full C++ project --- src/common/mallocHelper.h | 177 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) diff --git a/src/common/mallocHelper.h b/src/common/mallocHelper.h index e0c1829481..e917fe6bf2 100644 --- a/src/common/mallocHelper.h +++ b/src/common/mallocHelper.h @@ -37,3 +37,180 @@ static inline size_t ffMallocUsableSize(const void* ptr) { return 0; // Not supported #endif } + +#if __cplusplus + + #include + #include + + #if defined(_WIN32) + #include + #endif + +[[gnu::always_inline]] +static inline void* ffAlignedAlloc(size_t size, size_t alignment) noexcept { + #if defined(_WIN32) + return _aligned_malloc(size, alignment); + #else + void* ptr = nullptr; + if (posix_memalign(&ptr, alignment, size) == 0) [[likely]] { + return ptr; + } + return nullptr; + #endif +} + +[[gnu::always_inline]] +static inline void ffAlignedFree(void* ptr) noexcept { + #if defined(_WIN32) + _aligned_free(ptr); + #else + ::free(ptr); + #endif +} + +[[gnu::always_inline]] +void* operator new(size_t size) { + if (void* ptr = ::malloc(size)) [[likely]] { + return ptr; + } + std::abort(); +} + +[[gnu::always_inline]] +void operator delete(void* ptr) noexcept { + ::free(ptr); +} + +[[gnu::always_inline]] +void operator delete(void* ptr, size_t size) noexcept { + (void) size; + ::free(ptr); +} + +[[gnu::always_inline]] +void* operator new(size_t size, const std::nothrow_t&) noexcept { + return ::malloc(size); +} + +[[gnu::always_inline]] +void operator delete(void* ptr, const std::nothrow_t&) noexcept { + ::free(ptr); +} + +[[gnu::always_inline]] +void operator delete(void* ptr, size_t size, const std::nothrow_t&) noexcept { + (void) size; + ::free(ptr); +} + +[[gnu::always_inline]] +void* operator new[](size_t size) { + if (void* ptr = ::malloc(size)) [[likely]] { + return ptr; + } + std::abort(); +} + +[[gnu::always_inline]] +void operator delete[](void* ptr) noexcept { + ::free(ptr); +} + +[[gnu::always_inline]] +void operator delete[](void* ptr, size_t size) noexcept { + (void) size; + ::free(ptr); +} + +[[gnu::always_inline]] +void* operator new[](size_t size, const std::nothrow_t&) noexcept { + return ::malloc(size); +} + +[[gnu::always_inline]] +void operator delete[](void* ptr, const std::nothrow_t&) noexcept { + ::free(ptr); +} + +[[gnu::always_inline]] +void operator delete[](void* ptr, size_t size, const std::nothrow_t&) noexcept { + (void) size; + ::free(ptr); +} + + #if __cplusplus >= 201703L // std::align_val_t (over-aligned new/delete, C++17) + +[[gnu::always_inline]] +void* operator new(size_t size, std::align_val_t alignment) { + if (void* ptr = ffAlignedAlloc(size, alignment)) { + return ptr; + } + std::abort(); +} + +[[gnu::always_inline]] +void operator delete(void* ptr, std::align_val_t) noexcept { + ffAlignedFree(ptr); +} + +[[gnu::always_inline]] +void operator delete(void* ptr, size_t size, std::align_val_t) noexcept { + (void) size; + ffAlignedFree(ptr); +} + +[[gnu::always_inline]] +void* operator new(size_t size, std::align_val_t alignment, const std::nothrow_t&) noexcept { + return ffAlignedAlloc(size, static_cast(alignment)); +} + +[[gnu::always_inline]] +void operator delete(void* ptr, std::align_val_t, const std::nothrow_t&) noexcept { + ffAlignedFree(ptr); +} + +[[gnu::always_inline]] +void operator delete(void* ptr, size_t size, std::align_val_t, const std::nothrow_t&) noexcept { + (void) size; + ffAlignedFree(ptr); +} + +[[gnu::always_inline]] +void* operator new[](size_t size, std::align_val_t alignment) { + if (void* ptr = ffAlignedAlloc(size, static_cast(alignment))) { + return ptr; + } + std::abort(); +} + +[[gnu::always_inline]] +void operator delete[](void* ptr, std::align_val_t) noexcept { + ffAlignedFree(ptr); +} + +[[gnu::always_inline]] +void operator delete[](void* ptr, size_t size, std::align_val_t) noexcept { + (void) size; + ffAlignedFree(ptr); +} + +[[gnu::always_inline]] +void* operator new[](size_t size, std::align_val_t alignment, const std::nothrow_t&) noexcept { + return ffAlignedAlloc(size, static_cast(alignment)); +} + +[[gnu::always_inline]] +void operator delete[](void* ptr, std::align_val_t, const std::nothrow_t&) noexcept { + ffAlignedFree(ptr); +} + +[[gnu::always_inline]] +void operator delete[](void* ptr, size_t size, std::align_val_t, const std::nothrow_t&) noexcept { + (void) size; + ffAlignedFree(ptr); +} + + #endif // __cplusplus >= 201703L + +#endif From 1626ced83c241f695e3394e62ff5c91ebb5cd168 Mon Sep 17 00:00:00 2001 From: aldahiiir Date: Fri, 31 Jul 2026 13:24:26 -0600 Subject: [PATCH 55/67] OS (Linux): detects Ubuntu Studio installed with core mode The minimal (core) installation ships the `ubuntustudio-desktop-core` metapackage instead of `ubuntustudio-desktop`, so the existing check missed it and the system was reported as Kubuntu. Fixes #2485 --- src/detection/os/os_linux.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/detection/os/os_linux.c b/src/detection/os/os_linux.c index a7b1ffa3f7..02dcb240ac 100644 --- a/src/detection/os/os_linux.c +++ b/src/detection/os/os_linux.c @@ -92,7 +92,8 @@ static bool parseOsRelease(const char* fileName, FFOSResult* result) { } // xdgConfigDirs contains plasma only - if (ffPathExists("/var/lib/dpkg/info/ubuntustudio-desktop.list", FF_PATHTYPE_FILE)) { + // `ubuntustudio-desktop-core` is installed on both the full and core installations + if (ffPathExists("/var/lib/dpkg/info/ubuntustudio-desktop-core.list", FF_PATHTYPE_FILE)) { ffStrbufSetStatic(&result->name, "Ubuntu Studio"); ffStrbufSetStatic(&result->id, "ubuntu-studio"); ffStrbufSetStatic(&result->idLike, "ubuntu"); From 3196f5efaa2305dcf3a0799b4bebc2f058417fea Mon Sep 17 00:00:00 2001 From: Carter Li Date: Fri, 31 Jul 2026 10:56:13 +0800 Subject: [PATCH 56/67] chore: adds `defaultOrder` to every module's definition --- src/common/option.h | 1 + src/modules/battery/battery.c | 3 ++- src/modules/bios/bios.c | 3 ++- src/modules/bluetooth/bluetooth.c | 3 ++- src/modules/bluetoothradio/bluetoothradio.c | 3 ++- src/modules/board/board.c | 3 ++- src/modules/bootmgr/bootmgr.c | 3 ++- src/modules/break/break.c | 1 + src/modules/brightness/brightness.c | 3 ++- src/modules/btrfs/btrfs.c | 3 ++- src/modules/camera/camera.c | 3 ++- src/modules/chassis/chassis.c | 1 + src/modules/codec/codec.c | 3 ++- src/modules/colors/colors.c | 1 + src/modules/command/command.c | 2 +- src/modules/cpu/cpu.c | 3 ++- src/modules/cpucache/cpucache.c | 3 ++- src/modules/cpuusage/cpuusage.c | 3 ++- src/modules/cursor/cursor.c | 1 + src/modules/datetime/datetime.c | 3 ++- src/modules/de/de.c | 3 ++- src/modules/disk/disk.c | 3 ++- src/modules/diskio/diskio.c | 3 ++- src/modules/display/display.c | 3 ++- src/modules/dns/dns.c | 3 ++- src/modules/editor/editor.c | 3 ++- src/modules/font/font.c | 3 ++- src/modules/gamepad/gamepad.c | 3 ++- src/modules/gpu/gpu.c | 1 + src/modules/host/host.c | 3 ++- src/modules/icons/icons.c | 3 ++- src/modules/initsystem/initsystem.c | 3 ++- src/modules/kernel/kernel.c | 3 ++- src/modules/keyboard/keyboard.c | 3 ++- src/modules/lm/lm.c | 3 ++- src/modules/loadavg/loadavg.c | 3 ++- src/modules/locale/locale.c | 3 ++- src/modules/localip/localip.c | 3 ++- src/modules/media/media.c | 3 ++- src/modules/memory/memory.c | 3 ++- src/modules/monitor/monitor.c | 3 ++- src/modules/mouse/mouse.c | 3 ++- src/modules/netio/netio.c | 3 ++- src/modules/opencl/opencl.c | 3 ++- src/modules/opengl/opengl.c | 3 ++- src/modules/os/os.c | 3 ++- src/modules/packages/packages.c | 3 ++- src/modules/physicaldisk/physicaldisk.c | 3 ++- src/modules/physicalmemory/physicalmemory.c | 3 ++- src/modules/player/player.c | 3 ++- src/modules/poweradapter/poweradapter.c | 3 ++- src/modules/processes/processes.c | 4 +++- src/modules/publicip/publicip.c | 3 ++- src/modules/separator/separator.c | 1 + src/modules/shell/shell.c | 3 ++- src/modules/sound/sound.c | 3 ++- src/modules/swap/swap.c | 3 ++- src/modules/terminal/terminal.c | 3 ++- src/modules/terminalfont/terminalfont.c | 1 + src/modules/terminalsize/terminalsize.c | 1 + src/modules/terminaltheme/terminaltheme.c | 3 ++- src/modules/theme/theme.c | 3 ++- src/modules/title/title.c | 3 ++- src/modules/tpm/tpm.c | 3 ++- src/modules/uptime/uptime.c | 3 ++- src/modules/users/users.c | 3 ++- src/modules/version/version.c | 3 ++- src/modules/vulkan/vulkan.c | 3 ++- src/modules/wallpaper/wallpaper.c | 3 ++- src/modules/weather/weather.c | 3 ++- src/modules/wifi/wifi.c | 3 ++- src/modules/wm/wm.c | 3 ++- src/modules/wmtheme/wmtheme.c | 3 ++- src/modules/zpool/zpool.c | 3 ++- 74 files changed, 139 insertions(+), 65 deletions(-) diff --git a/src/common/option.h b/src/common/option.h index 9f54d3c1a2..e2697042c6 100644 --- a/src/common/option.h +++ b/src/common/option.h @@ -34,6 +34,7 @@ typedef struct FFModuleBaseInfo { bool (*generateJsonResult)(void* options, struct yyjson_mut_doc* doc, struct yyjson_mut_val* module); // true on success void (*generateJsonConfig)(void* options, struct yyjson_mut_doc* doc, struct yyjson_mut_val* obj); FFModuleFormatArgList formatArgs; + const uint8_t defaultOrder; } FFModuleBaseInfo; typedef enum FFModuleKeyType: uint8_t { diff --git a/src/modules/battery/battery.c b/src/modules/battery/battery.c index d1bff71e23..7b8405e71e 100644 --- a/src/modules/battery/battery.c +++ b/src/modules/battery/battery.c @@ -321,5 +321,6 @@ FFModuleBaseInfo ffBatteryModuleInfo = { { "Battery time remaining minutes", "time-minutes" }, { "Battery time remaining seconds", "time-seconds" }, { "Battery time remaining (formatted)", "time-formatted" }, - })) + })), + .defaultOrder = 44, }; diff --git a/src/modules/bios/bios.c b/src/modules/bios/bios.c index 6113b09296..c000080f49 100644 --- a/src/modules/bios/bios.c +++ b/src/modules/bios/bios.c @@ -144,5 +144,6 @@ FFModuleBaseInfo ffBiosModuleInfo = { { "BIOS vendor", "vendor" }, { "BIOS version", "version" }, { "Firmware type", "type" }, - })) + })), + .defaultOrder = 5, }; diff --git a/src/modules/bluetooth/bluetooth.c b/src/modules/bluetooth/bluetooth.c index 6721c336da..42a251886a 100644 --- a/src/modules/bluetooth/bluetooth.c +++ b/src/modules/bluetooth/bluetooth.c @@ -166,5 +166,6 @@ FFModuleBaseInfo ffBluetoothModuleInfo = { { "Battery percentage number", "battery-percentage" }, { "Is connected", "connected" }, { "Battery percentage bar", "battery-percentage-bar" }, - })) + })), + .defaultOrder = 58, }; diff --git a/src/modules/bluetoothradio/bluetoothradio.c b/src/modules/bluetoothradio/bluetoothradio.c index 9efc0a0578..fc113fc832 100644 --- a/src/modules/bluetoothradio/bluetoothradio.c +++ b/src/modules/bluetoothradio/bluetoothradio.c @@ -207,5 +207,6 @@ FFModuleBaseInfo ffBluetoothRadioModuleInfo = { { "Vendor", "vendor" }, { "Discoverable", "discoverable" }, { "Connectable / Pairable", "connectable" }, - })) + })), + .defaultOrder = 59, }; diff --git a/src/modules/board/board.c b/src/modules/board/board.c index bc8f37caed..1a85ea3930 100644 --- a/src/modules/board/board.c +++ b/src/modules/board/board.c @@ -121,5 +121,6 @@ FFModuleBaseInfo ffBoardModuleInfo = { { "Board vendor", "vendor" }, { "Board version", "version" }, { "Board serial number", "serial" }, - })) + })), + .defaultOrder = 7, }; diff --git a/src/modules/bootmgr/bootmgr.c b/src/modules/bootmgr/bootmgr.c index 05cb46713a..494885fd6c 100644 --- a/src/modules/bootmgr/bootmgr.c +++ b/src/modules/bootmgr/bootmgr.c @@ -117,5 +117,6 @@ FFModuleBaseInfo ffBootmgrModuleInfo = { { "Firmware file name", "firmware-name" }, { "Is secure boot enabled", "secure-boot" }, { "Boot order", "order" }, - })) + })), + .defaultOrder = 6, }; diff --git a/src/modules/break/break.c b/src/modules/break/break.c index 7bca2b61ca..011458a283 100644 --- a/src/modules/break/break.c +++ b/src/modules/break/break.c @@ -33,4 +33,5 @@ FFModuleBaseInfo ffBreakModuleInfo = { .destroyOptions = (void*) ffDestroyBreakOptions, .parseJsonObject = (void*) ffParseBreakJsonObject, .printModule = (void*) ffPrintBreak, + .defaultOrder = 71, }; diff --git a/src/modules/brightness/brightness.c b/src/modules/brightness/brightness.c index 1d4971fcb6..479a2ccb9b 100644 --- a/src/modules/brightness/brightness.c +++ b/src/modules/brightness/brightness.c @@ -206,5 +206,6 @@ FFModuleBaseInfo ffBrightnessModuleInfo = { { "Current brightness value", "current" }, { "Screen brightness (percentage bar)", "percentage-bar" }, { "Is built-in screen", "is-builtin" }, - })) + })), + .defaultOrder = 18, }; diff --git a/src/modules/btrfs/btrfs.c b/src/modules/btrfs/btrfs.c index d9661c2e30..21068e85db 100644 --- a/src/modules/btrfs/btrfs.c +++ b/src/modules/btrfs/btrfs.c @@ -220,5 +220,6 @@ FFModuleBaseInfo ffBtrfsModuleInfo = { { "Allocated percentage bar", "allocated-percentage-bar" }, { "Node size", "node-size" }, { "Sector size", "sector-size" }, - })) + })), + .defaultOrder = 42, }; diff --git a/src/modules/camera/camera.c b/src/modules/camera/camera.c index 1e601f1966..02c454bbbf 100644 --- a/src/modules/camera/camera.c +++ b/src/modules/camera/camera.c @@ -130,5 +130,6 @@ FFModuleBaseInfo ffCameraModuleInfo = { { "Identifier", "id" }, { "Width (in px)", "width" }, { "Height (in px)", "height" }, - })) + })), + .defaultOrder = 61, }; diff --git a/src/modules/chassis/chassis.c b/src/modules/chassis/chassis.c index 1cd3089098..9b5b21e7a6 100644 --- a/src/modules/chassis/chassis.c +++ b/src/modules/chassis/chassis.c @@ -124,4 +124,5 @@ FFModuleBaseInfo ffChassisModuleInfo = { { "Chassis version", "version" }, { "Chassis serial number", "serial" }, })), + .defaultOrder = 8, }; diff --git a/src/modules/codec/codec.c b/src/modules/codec/codec.c index 5d3cbac902..a9a465bf84 100644 --- a/src/modules/codec/codec.c +++ b/src/modules/codec/codec.c @@ -264,5 +264,6 @@ FFModuleBaseInfo ffCodecModuleInfo = { { "Decoder / Encoder", "direction" }, { "Compatibility alias of codec types", "types" }, { "Platform API used for detection", "platform-api" }, - })) + })), + .defaultOrder = 37, }; diff --git a/src/modules/colors/colors.c b/src/modules/colors/colors.c index 43cdffb31b..8d225d4bf1 100644 --- a/src/modules/colors/colors.c +++ b/src/modules/colors/colors.c @@ -302,4 +302,5 @@ FFModuleBaseInfo ffColorsModuleInfo = { .parseJsonObject = (void*) ffParseColorsJsonObject, .printModule = (void*) ffPrintColors, .generateJsonConfig = (void*) ffGenerateColorsJsonConfig, + .defaultOrder = 72, }; diff --git a/src/modules/command/command.c b/src/modules/command/command.c index 751b971398..4de947a792 100644 --- a/src/modules/command/command.c +++ b/src/modules/command/command.c @@ -163,5 +163,5 @@ FFModuleBaseInfo ffCommandModuleInfo = { .generateJsonConfig = (void*) ffGenerateCommandJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "Command result", "result" }, - })) + })), }; diff --git a/src/modules/cpu/cpu.c b/src/modules/cpu/cpu.c index 51f84c9f88..2716b2e037 100644 --- a/src/modules/cpu/cpu.c +++ b/src/modules/cpu/cpu.c @@ -276,5 +276,6 @@ FFModuleBaseInfo ffCPUModuleInfo = { { "CPU code name", "code-name" }, { "CPU technology", "technology" }, #endif - })) + })), + .defaultOrder = 33, }; diff --git a/src/modules/cpucache/cpucache.c b/src/modules/cpucache/cpucache.c index 9753e41275..7dd39d1cb6 100644 --- a/src/modules/cpucache/cpucache.c +++ b/src/modules/cpucache/cpucache.c @@ -234,5 +234,6 @@ FFModuleBaseInfo ffCPUCacheModuleInfo = { .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "Separate result", "result" }, { "Sum result", "sum" }, - })) + })), + .defaultOrder = 34, }; diff --git a/src/modules/cpuusage/cpuusage.c b/src/modules/cpuusage/cpuusage.c index 5c01e914ac..ea592a2739 100644 --- a/src/modules/cpuusage/cpuusage.c +++ b/src/modules/cpuusage/cpuusage.c @@ -198,5 +198,6 @@ FFModuleBaseInfo ffCPUUsageModuleInfo = { { "CPU usage (percentage bar, average)", "avg-bar" }, { "CPU usage (percentage bar, maximum)", "max-bar" }, { "CPU usage (percentage bar, minimum)", "min-bar" }, - })) + })), + .defaultOrder = 35, }; diff --git a/src/modules/cursor/cursor.c b/src/modules/cursor/cursor.c index 748f78cc7e..1be3ac6e35 100644 --- a/src/modules/cursor/cursor.c +++ b/src/modules/cursor/cursor.c @@ -112,4 +112,5 @@ FFModuleBaseInfo ffCursorModuleInfo = { { "Cursor theme", "theme" }, { "Cursor size", "size" }, })), + .defaultOrder = 27, }; diff --git a/src/modules/datetime/datetime.c b/src/modules/datetime/datetime.c index 8e47e147c0..14e3b8251f 100644 --- a/src/modules/datetime/datetime.c +++ b/src/modules/datetime/datetime.c @@ -185,5 +185,6 @@ FFModuleBaseInfo ffDateTimeModuleInfo = { { "Locale-dependent timezone name or abbreviation", "timezone-name" }, { "Day in month with leading zero", "day-pretty" }, { "AM or PM", "am-pm" }, - })) + })), + .defaultOrder = 52, }; diff --git a/src/modules/de/de.c b/src/modules/de/de.c index ad714d6f52..8e301fa32e 100644 --- a/src/modules/de/de.c +++ b/src/modules/de/de.c @@ -94,5 +94,6 @@ FFModuleBaseInfo ffDEModuleInfo = { { "DE process name", "process-name" }, { "DE pretty name", "pretty-name" }, { "DE version", "version" }, - })) + })), + .defaultOrder = 21, }; diff --git a/src/modules/disk/disk.c b/src/modules/disk/disk.c index 72192e260e..74b6488558 100644 --- a/src/modules/disk/disk.c +++ b/src/modules/disk/disk.c @@ -486,5 +486,6 @@ FFModuleBaseInfo ffDiskModuleInfo = { { "Years fraction after creation", "years-fraction" }, { "Size free", "size-free" }, { "Size available", "size-available" }, - })) + })), + .defaultOrder = 41, }; diff --git a/src/modules/diskio/diskio.c b/src/modules/diskio/diskio.c index 3ff31fad4f..372910d10a 100644 --- a/src/modules/diskio/diskio.c +++ b/src/modules/diskio/diskio.c @@ -189,5 +189,6 @@ FFModuleBaseInfo ffDiskIOModuleInfo = { { "Size of data written [per second] (in bytes)", "bytes-written" }, { "Number of reads", "read-count" }, { "Number of writes", "write-count" }, - })) + })), + .defaultOrder = 67, }; diff --git a/src/modules/display/display.c b/src/modules/display/display.c index bdcec58eac..eceb9a2d13 100644 --- a/src/modules/display/display.c +++ b/src/modules/display/display.c @@ -445,5 +445,6 @@ FFModuleBaseInfo ffDisplayModuleInfo = { { "Screen preferred height (in pixels)", "preferred-height" }, { "Screen preferred refresh rate (in Hz)", "preferred-refresh-rate" }, { "DPI", "dpi" }, - })) + })), + .defaultOrder = 17, }; diff --git a/src/modules/dns/dns.c b/src/modules/dns/dns.c index 79df55bacb..396c3bd431 100644 --- a/src/modules/dns/dns.c +++ b/src/modules/dns/dns.c @@ -144,5 +144,6 @@ FFModuleBaseInfo ffDNSModuleInfo = { .generateJsonConfig = (void*) ffGenerateDNSJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "DNS result", "result" }, - })) + })), + .defaultOrder = 50, }; diff --git a/src/modules/editor/editor.c b/src/modules/editor/editor.c index 352661ecbb..342b31812c 100644 --- a/src/modules/editor/editor.c +++ b/src/modules/editor/editor.c @@ -117,5 +117,6 @@ FFModuleBaseInfo ffEditorModuleInfo = { { "Exe name of real path", "exe-name" }, { "Full path of real path", "path" }, { "Version", "version" }, - })) + })), + .defaultOrder = 16, }; diff --git a/src/modules/font/font.c b/src/modules/font/font.c index 14cfa8fb18..be940b32d1 100644 --- a/src/modules/font/font.c +++ b/src/modules/font/font.c @@ -109,5 +109,6 @@ FFModuleBaseInfo ffFontModuleInfo = { { "Font 3", "font3" }, { "Font 4", "font4" }, { "Combined fonts for display", "combined" }, - })) + })), + .defaultOrder = 26, }; diff --git a/src/modules/gamepad/gamepad.c b/src/modules/gamepad/gamepad.c index 5404cb8c26..f3c3bdfe40 100644 --- a/src/modules/gamepad/gamepad.c +++ b/src/modules/gamepad/gamepad.c @@ -202,5 +202,6 @@ FFModuleBaseInfo ffGamepadModuleInfo = { { "Serial number", "serial" }, { "Battery percentage num", "battery-percentage" }, { "Battery percentage bar", "battery-percentage-bar" }, - })) + })), + .defaultOrder = 62, }; diff --git a/src/modules/gpu/gpu.c b/src/modules/gpu/gpu.c index 163fca8fb5..19ba8c868b 100644 --- a/src/modules/gpu/gpu.c +++ b/src/modules/gpu/gpu.c @@ -511,4 +511,5 @@ FFModuleBaseInfo ffGPUModuleInfo = { { "PCIe maximum speed in gen and lanes", "pcie-max-speed" }, { "PCIe current speed in gen and lanes", "pcie-curr-speed" }, })), + .defaultOrder = 36, }; diff --git a/src/modules/host/host.c b/src/modules/host/host.c index 680ba8849d..041ab712e6 100644 --- a/src/modules/host/host.c +++ b/src/modules/host/host.c @@ -152,5 +152,6 @@ FFModuleBaseInfo ffHostModuleInfo = { { "Product vendor", "vendor" }, { "Product serial number", "serial" }, { "Product uuid", "uuid" }, - })) + })), + .defaultOrder = 4, }; diff --git a/src/modules/icons/icons.c b/src/modules/icons/icons.c index d52ddc24b3..15dea09287 100644 --- a/src/modules/icons/icons.c +++ b/src/modules/icons/icons.c @@ -105,5 +105,6 @@ FFModuleBaseInfo ffIconsModuleInfo = { .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "Icons part 1", "icons1" }, { "Icons part 2", "icons2" }, - })) + })), + .defaultOrder = 25, }; diff --git a/src/modules/initsystem/initsystem.c b/src/modules/initsystem/initsystem.c index faab272a31..7a02e0309b 100644 --- a/src/modules/initsystem/initsystem.c +++ b/src/modules/initsystem/initsystem.c @@ -116,5 +116,6 @@ FFModuleBaseInfo ffInitSystemModuleInfo = { { "Init system exe path", "exe" }, { "Init system version path", "version" }, { "Init system pid", "pid" }, - })) + })), + .defaultOrder = 10, }; diff --git a/src/modules/kernel/kernel.c b/src/modules/kernel/kernel.c index 0c40d39f6a..9ae5878df4 100644 --- a/src/modules/kernel/kernel.c +++ b/src/modules/kernel/kernel.c @@ -77,5 +77,6 @@ FFModuleBaseInfo ffKernelModuleInfo = { { "Architecture", "arch" }, { "Display version", "display-version" }, { "Page size", "page-size" }, - })) + })), + .defaultOrder = 9, }; diff --git a/src/modules/keyboard/keyboard.c b/src/modules/keyboard/keyboard.c index 5e3f5cbfe2..76e6c36f95 100644 --- a/src/modules/keyboard/keyboard.c +++ b/src/modules/keyboard/keyboard.c @@ -160,5 +160,6 @@ FFModuleBaseInfo ffKeyboardModuleInfo = { .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "Name", "name" }, { "Serial number", "serial" }, - })) + })), + .defaultOrder = 64, }; diff --git a/src/modules/lm/lm.c b/src/modules/lm/lm.c index b8e7c1fc39..ed945bad46 100644 --- a/src/modules/lm/lm.c +++ b/src/modules/lm/lm.c @@ -118,5 +118,6 @@ FFModuleBaseInfo ffLMModuleInfo = { { "LM service", "service" }, { "LM type", "type" }, { "LM version", "version" }, - })) + })), + .defaultOrder = 20, }; diff --git a/src/modules/loadavg/loadavg.c b/src/modules/loadavg/loadavg.c index 61939f5151..a6fb6f800a 100644 --- a/src/modules/loadavg/loadavg.c +++ b/src/modules/loadavg/loadavg.c @@ -160,5 +160,6 @@ FFModuleBaseInfo ffLoadavgModuleInfo = { { "Load average over 1min", "loadavg1" }, { "Load average over 5min", "loadavg2" }, { "Load average over 15min", "loadavg3" }, - })) + })), + .defaultOrder = 12, }; diff --git a/src/modules/locale/locale.c b/src/modules/locale/locale.c index 9c25341aa2..cb9c8e7ec5 100644 --- a/src/modules/locale/locale.c +++ b/src/modules/locale/locale.c @@ -77,5 +77,6 @@ FFModuleBaseInfo ffLocaleModuleInfo = { .generateJsonConfig = (void*) ffGenerateLocaleJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "Locale code", "result" }, - })) + })), + .defaultOrder = 53, }; diff --git a/src/modules/localip/localip.c b/src/modules/localip/localip.c index 952d72ef66..eeb304e283 100644 --- a/src/modules/localip/localip.c +++ b/src/modules/localip/localip.c @@ -439,5 +439,6 @@ FFModuleBaseInfo ffLocalIPModuleInfo = { { "MTU size in bytes", "mtu" }, { "Link speed (formatted)", "speed" }, { "Interface flags", "flags" }, - })) + })), + .defaultOrder = 49, }; diff --git a/src/modules/media/media.c b/src/modules/media/media.c index 884d8e4521..5f926d1156 100644 --- a/src/modules/media/media.c +++ b/src/modules/media/media.c @@ -273,5 +273,6 @@ FFModuleBaseInfo ffMediaModuleInfo = { { "Player name", "player-name" }, { "Player ID", "player-id" }, { "URL", "url" }, - })) + })), + .defaultOrder = 47, }; diff --git a/src/modules/memory/memory.c b/src/modules/memory/memory.c index efdf656437..73a8fc173b 100644 --- a/src/modules/memory/memory.c +++ b/src/modules/memory/memory.c @@ -131,5 +131,6 @@ FFModuleBaseInfo ffMemoryModuleInfo = { { "Total size", "total" }, { "Percentage used (num)", "percentage" }, { "Percentage used (bar)", "percentage-bar" }, - })) + })), + .defaultOrder = 38, }; diff --git a/src/modules/monitor/monitor.c b/src/modules/monitor/monitor.c index 41beeaba2b..6c8bed1708 100644 --- a/src/modules/monitor/monitor.c +++ b/src/modules/monitor/monitor.c @@ -125,5 +125,6 @@ FFModuleBaseInfo ffMonitorModuleInfo = { { "Serial number", "serial" }, { "Maximum refresh rate in Hz", "refresh-rate" }, { "True if the display is HDR compatible", "hdr-compatible" }, - })) + })), + .defaultOrder = 19, }; diff --git a/src/modules/mouse/mouse.c b/src/modules/mouse/mouse.c index 37e78be9ae..82dd6b053e 100644 --- a/src/modules/mouse/mouse.c +++ b/src/modules/mouse/mouse.c @@ -163,5 +163,6 @@ FFModuleBaseInfo ffMouseModuleInfo = { .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "Mouse name", "name" }, { "Mouse serial number", "serial" }, - })) + })), + .defaultOrder = 63, }; diff --git a/src/modules/netio/netio.c b/src/modules/netio/netio.c index 17047fffd9..8607a21af6 100644 --- a/src/modules/netio/netio.c +++ b/src/modules/netio/netio.c @@ -220,5 +220,6 @@ FFModuleBaseInfo ffNetIOModuleInfo = { { "Number of errors sent [per second]", "tx-errors" }, { "Number of packets dropped when receiving [per second]", "rx-drops" }, { "Number of packets dropped when sending [per second]", "tx-drops" }, - })) + })), + .defaultOrder = 66, }; diff --git a/src/modules/opencl/opencl.c b/src/modules/opencl/opencl.c index acfe861cb9..0a80d9d16b 100644 --- a/src/modules/opencl/opencl.c +++ b/src/modules/opencl/opencl.c @@ -132,5 +132,6 @@ FFModuleBaseInfo ffOpenCLModuleInfo = { { "Platform version", "version" }, { "Platform name", "name" }, { "Platform vendor", "vendor" }, - })) + })), + .defaultOrder = 56, }; diff --git a/src/modules/opengl/opengl.c b/src/modules/opengl/opengl.c index 1ecce858ae..29adcf0f0d 100644 --- a/src/modules/opengl/opengl.c +++ b/src/modules/opengl/opengl.c @@ -141,5 +141,6 @@ FFModuleBaseInfo ffOpenGLModuleInfo = { { "OpenGL vendor", "vendor" }, { "OpenGL shading language version", "slv" }, { "OpenGL library used", "library" }, - })) + })), + .defaultOrder = 55, }; diff --git a/src/modules/os/os.c b/src/modules/os/os.c index ea08a567c7..d0dc5600b7 100644 --- a/src/modules/os/os.c +++ b/src/modules/os/os.c @@ -193,5 +193,6 @@ FFModuleBaseInfo ffOSModuleInfo = { { "Version codename of the OS", "codename" }, { "Build ID of the OS", "build-id" }, { "Architecture of the OS", "arch" }, - })) + })), + .defaultOrder = 3, }; diff --git a/src/modules/packages/packages.c b/src/modules/packages/packages.c index 8cb4027bde..1553893f1a 100644 --- a/src/modules/packages/packages.c +++ b/src/modules/packages/packages.c @@ -573,5 +573,6 @@ FFModuleBaseInfo ffPackagesModuleInfo = { { "Total number of all hpkg packages", "hpkg-all" }, { "Total number of all nix packages", "nix-all" }, { "Number of all packages", "all" }, - })) + })), + .defaultOrder = 14, }; diff --git a/src/modules/physicaldisk/physicaldisk.c b/src/modules/physicaldisk/physicaldisk.c index b0b7c58607..8935438840 100644 --- a/src/modules/physicaldisk/physicaldisk.c +++ b/src/modules/physicaldisk/physicaldisk.c @@ -284,5 +284,6 @@ FFModuleBaseInfo ffPhysicalDiskModuleInfo = { { "Device kind (Read-only or Read-write)", "readonly-type" }, { "Product revision", "revision" }, { "Device temperature (formatted)", "temperature" }, - })) + })), + .defaultOrder = 68, }; diff --git a/src/modules/physicalmemory/physicalmemory.c b/src/modules/physicalmemory/physicalmemory.c index d4bee7db53..f044e59071 100644 --- a/src/modules/physicalmemory/physicalmemory.c +++ b/src/modules/physicalmemory/physicalmemory.c @@ -180,5 +180,6 @@ FFModuleBaseInfo ffPhysicalMemoryModuleInfo = { { "Part number", "part-number" }, { "True if ECC enabled", "is-ecc-enabled" }, { "True if a memory module is installed in the slot", "is-installed" }, - })) + })), + .defaultOrder = 39, }; diff --git a/src/modules/player/player.c b/src/modules/player/player.c index 7d2248093e..0d1fd03b8f 100644 --- a/src/modules/player/player.c +++ b/src/modules/player/player.c @@ -122,5 +122,6 @@ FFModuleBaseInfo ffPlayerModuleInfo = { { "Player name", "name" }, { "Player Identifier", "id" }, { "URL name", "url" }, - })) + })), + .defaultOrder = 46, }; diff --git a/src/modules/poweradapter/poweradapter.c b/src/modules/poweradapter/poweradapter.c index 12086c9b2d..be20d2a6d7 100644 --- a/src/modules/poweradapter/poweradapter.c +++ b/src/modules/poweradapter/poweradapter.c @@ -125,5 +125,6 @@ FFModuleBaseInfo ffPowerAdapterModuleInfo = { { "Power adapter model", "model" }, { "Power adapter description", "description" }, { "Power adapter serial number", "serial" }, - })) + })), + .defaultOrder = 45, }; diff --git a/src/modules/processes/processes.c b/src/modules/processes/processes.c index d5542bac49..daf7a23ed8 100644 --- a/src/modules/processes/processes.c +++ b/src/modules/processes/processes.c @@ -72,5 +72,7 @@ FFModuleBaseInfo ffProcessesModuleInfo = { .generateJsonResult = (void*) ffGenerateProcessesJsonResult, .generateJsonConfig = (void*) ffGenerateProcessesJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { - { "Process count", "result" } })) + { "Process count", "result" }, + })), + .defaultOrder = 13, }; diff --git a/src/modules/publicip/publicip.c b/src/modules/publicip/publicip.c index 552173395e..5d52e74a70 100644 --- a/src/modules/publicip/publicip.c +++ b/src/modules/publicip/publicip.c @@ -121,5 +121,6 @@ FFModuleBaseInfo ffPublicIPModuleInfo = { .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "Public IP address", "ip" }, { "Location", "location" }, - })) + })), + .defaultOrder = 48, }; diff --git a/src/modules/separator/separator.c b/src/modules/separator/separator.c index 733bd38e10..bd84d7a40d 100644 --- a/src/modules/separator/separator.c +++ b/src/modules/separator/separator.c @@ -128,4 +128,5 @@ FFModuleBaseInfo ffSeparatorModuleInfo = { .parseJsonObject = (void*) ffParseSeparatorJsonObject, .printModule = (void*) ffPrintSeparator, .generateJsonConfig = (void*) ffGenerateSeparatorJsonConfig, + .defaultOrder = 2, }; diff --git a/src/modules/shell/shell.c b/src/modules/shell/shell.c index d840135dd1..84db1baca0 100644 --- a/src/modules/shell/shell.c +++ b/src/modules/shell/shell.c @@ -106,5 +106,6 @@ FFModuleBaseInfo ffShellModuleInfo = { { "Shell pretty name", "pretty-name" }, { "Shell full exe path", "exe-path" }, { "Shell tty used", "tty" }, - })) + })), + .defaultOrder = 15, }; diff --git a/src/modules/sound/sound.c b/src/modules/sound/sound.c index bf37c1f1bc..6bc73cbe7b 100644 --- a/src/modules/sound/sound.c +++ b/src/modules/sound/sound.c @@ -215,5 +215,6 @@ FFModuleBaseInfo ffSoundModuleInfo = { { "Identifier", "identifier" }, { "Volume (in percentage bar)", "volume-percentage-bar" }, { "Platform API used", "platform-api" }, - })) + })), + .defaultOrder = 60, }; diff --git a/src/modules/swap/swap.c b/src/modules/swap/swap.c index 78568e717c..cf9d107c60 100644 --- a/src/modules/swap/swap.c +++ b/src/modules/swap/swap.c @@ -196,5 +196,6 @@ FFModuleBaseInfo ffSwapModuleInfo = { { "Percentage used (num)", "percentage" }, { "Percentage used (bar)", "percentage-bar" }, { "Name", "name" }, - })) + })), + .defaultOrder = 40, }; diff --git a/src/modules/terminal/terminal.c b/src/modules/terminal/terminal.c index 979f3303ba..5b2e9305fe 100644 --- a/src/modules/terminal/terminal.c +++ b/src/modules/terminal/terminal.c @@ -100,5 +100,6 @@ FFModuleBaseInfo ffTerminalModuleInfo = { { "Terminal version", "version" }, { "Terminal full exe path", "exe-path" }, { "Terminal tty / pts used", "tty" }, - })) + })), + .defaultOrder = 29, }; diff --git a/src/modules/terminalfont/terminalfont.c b/src/modules/terminalfont/terminalfont.c index 6285b28777..87a0837185 100644 --- a/src/modules/terminalfont/terminalfont.c +++ b/src/modules/terminalfont/terminalfont.c @@ -119,4 +119,5 @@ FFModuleBaseInfo ffTerminalFontModuleInfo = { { "Terminal font size", "size" }, { "Terminal font styles", "styles" }, })), + .defaultOrder = 30, }; diff --git a/src/modules/terminalsize/terminalsize.c b/src/modules/terminalsize/terminalsize.c index 81a9761b37..088b97fcef 100644 --- a/src/modules/terminalsize/terminalsize.c +++ b/src/modules/terminalsize/terminalsize.c @@ -90,4 +90,5 @@ FFModuleBaseInfo ffTerminalSizeModuleInfo = { { "Terminal width (in pixels)", "width" }, { "Terminal height (in pixels)", "height" }, })), + .defaultOrder = 31, }; diff --git a/src/modules/terminaltheme/terminaltheme.c b/src/modules/terminaltheme/terminaltheme.c index 6e7967b35b..3d5239b2f6 100644 --- a/src/modules/terminaltheme/terminaltheme.c +++ b/src/modules/terminaltheme/terminaltheme.c @@ -106,5 +106,6 @@ FFModuleBaseInfo ffTerminalThemeModuleInfo = { { "Terminal foreground type (Dark / Light)", "fg-type" }, { "Terminal background color", "bg-color" }, { "Terminal background type (Dark / Light)", "bg-type" }, - })) + })), + .defaultOrder = 32, }; diff --git a/src/modules/theme/theme.c b/src/modules/theme/theme.c index 4c88b68f76..f51a4aac64 100644 --- a/src/modules/theme/theme.c +++ b/src/modules/theme/theme.c @@ -98,5 +98,6 @@ FFModuleBaseInfo ffThemeModuleInfo = { .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "Theme part 1", "theme1" }, { "Theme part 2", "theme2" }, - })) + })), + .defaultOrder = 24, }; diff --git a/src/modules/title/title.c b/src/modules/title/title.c index ca7343a924..ce877caed7 100644 --- a/src/modules/title/title.c +++ b/src/modules/title/title.c @@ -197,5 +197,6 @@ FFModuleBaseInfo ffTitleModuleInfo = { { "UID (*nix) / SID (Windows)", "user-id" }, { "PID of current process", "pid" }, { "CWD with home dir replaced by `~`", "cwd" }, - })) + })), + .defaultOrder = 1, }; diff --git a/src/modules/tpm/tpm.c b/src/modules/tpm/tpm.c index b6dd62dd89..6596d6261c 100644 --- a/src/modules/tpm/tpm.c +++ b/src/modules/tpm/tpm.c @@ -94,5 +94,6 @@ FFModuleBaseInfo ffTPMModuleInfo = { .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "TPM device version", "version" }, { "TPM general description", "description" }, - })) + })), + .defaultOrder = 69, }; diff --git a/src/modules/uptime/uptime.c b/src/modules/uptime/uptime.c index dd0d90d2b9..41ac4526a1 100644 --- a/src/modules/uptime/uptime.c +++ b/src/modules/uptime/uptime.c @@ -102,5 +102,6 @@ FFModuleBaseInfo ffUptimeModuleInfo = { { "Days of year after boot", "days-of-year" }, { "Years fraction after boot", "years-fraction" }, { "Formatted uptime", "formatted" }, - })) + })), + .defaultOrder = 11, }; diff --git a/src/modules/users/users.c b/src/modules/users/users.c index 5575bbeaab..fbf1ffaa8e 100644 --- a/src/modules/users/users.c +++ b/src/modules/users/users.c @@ -202,5 +202,6 @@ FFModuleBaseInfo ffUsersModuleInfo = { { "Years integer after login", "years" }, { "Days of year after login", "days-of-year" }, { "Years fraction after login", "years-fraction" }, - })) + })), + .defaultOrder = 57, }; diff --git a/src/modules/version/version.c b/src/modules/version/version.c index 211eece8df..fa3b59fe7f 100644 --- a/src/modules/version/version.c +++ b/src/modules/version/version.c @@ -113,5 +113,6 @@ FFModuleBaseInfo ffVersionModuleInfo = { { "Date time when compiling", "compile-time" }, { "Compiler used when compiling", "compiler" }, { "Libc used when compiling", "libc" }, - })) + })), + .defaultOrder = 70, }; diff --git a/src/modules/vulkan/vulkan.c b/src/modules/vulkan/vulkan.c index abfd3612de..03123b7ae6 100644 --- a/src/modules/vulkan/vulkan.c +++ b/src/modules/vulkan/vulkan.c @@ -145,5 +145,6 @@ FFModuleBaseInfo ffVulkanModuleInfo = { { "API version", "api-version" }, { "Conformance version", "conformance-version" }, { "Instance version", "instance-version" }, - })) + })), + .defaultOrder = 54, }; diff --git a/src/modules/wallpaper/wallpaper.c b/src/modules/wallpaper/wallpaper.c index 6d11029531..b5fab48f50 100644 --- a/src/modules/wallpaper/wallpaper.c +++ b/src/modules/wallpaper/wallpaper.c @@ -86,5 +86,6 @@ FFModuleBaseInfo ffWallpaperModuleInfo = { .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "File name", "file-name" }, { "Full path", "full-path" }, - })) + })), + .defaultOrder = 28, }; diff --git a/src/modules/weather/weather.c b/src/modules/weather/weather.c index 414a18e7e7..152cb057bd 100644 --- a/src/modules/weather/weather.c +++ b/src/modules/weather/weather.c @@ -102,5 +102,6 @@ FFModuleBaseInfo ffWeatherModuleInfo = { .generateJsonConfig = (void*) ffGenerateWeatherJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "Weather result", "result" }, - })) + })), + .defaultOrder = 65, }; diff --git a/src/modules/wifi/wifi.c b/src/modules/wifi/wifi.c index 0a23cc2d79..0e9df22c98 100644 --- a/src/modules/wifi/wifi.c +++ b/src/modules/wifi/wifi.c @@ -235,5 +235,6 @@ FFModuleBaseInfo ffWifiModuleInfo = { { "Connection signal quality (percentage bar)", "signal-quality-bar" }, { "Connection channel number", "channel" }, { "Connection channel band in GHz", "band" }, - })) + })), + .defaultOrder = 51, }; diff --git a/src/modules/wm/wm.c b/src/modules/wm/wm.c index da492d3dea..5e23826e1c 100644 --- a/src/modules/wm/wm.c +++ b/src/modules/wm/wm.c @@ -134,5 +134,6 @@ FFModuleBaseInfo ffWMModuleInfo = { { "WM protocol name", "protocol-name" }, { "WM plugin name", "plugin-name" }, { "WM version", "version" }, - })) + })), + .defaultOrder = 22, }; diff --git a/src/modules/wmtheme/wmtheme.c b/src/modules/wmtheme/wmtheme.c index a45c9baa3c..f1ab4105f7 100644 --- a/src/modules/wmtheme/wmtheme.c +++ b/src/modules/wmtheme/wmtheme.c @@ -71,5 +71,6 @@ FFModuleBaseInfo ffWMThemeModuleInfo = { .generateJsonConfig = (void*) ffGenerateWMThemeJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "WM theme", "result" }, - })) + })), + .defaultOrder = 23, }; diff --git a/src/modules/zpool/zpool.c b/src/modules/zpool/zpool.c index cddd1c7054..7e5f03a52d 100644 --- a/src/modules/zpool/zpool.c +++ b/src/modules/zpool/zpool.c @@ -214,5 +214,6 @@ FFModuleBaseInfo ffZpoolModuleInfo = { { "Size allocated percentage bar", "allocated-percentage-bar" }, { "Fragmentation percentage bar", "frag-percentage-bar" }, { "Is read-only", "is-readonly" }, - })) + })), + .defaultOrder = 43, }; From 2af56232a4e14d8d02b30f9f848d2b85500faf3d Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 4 Aug 2026 15:08:38 +0800 Subject: [PATCH 57/67] FFlist: adds `FF_LIST_INSERT_AT` and `FF_LIST_REMOVE_AT` --- src/common/FFlist.h | 24 +++++++++++++-- tests/list.c | 73 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/src/common/FFlist.h b/src/common/FFlist.h index cf1377b273..630b0734b2 100644 --- a/src/common/FFlist.h +++ b/src/common/FFlist.h @@ -4,6 +4,7 @@ #include #include #include +#include #define FF_LIST_DEFAULT_ALLOC 16 @@ -110,6 +111,19 @@ static inline void* ffListAdd(FFlist* list, uint32_t elementSize) { return ffListGet(list, elementSize, list->length - 1); } +static inline void ffListRemoveAt(FFlist* list, uint32_t elementSize, uint32_t index) { + assert(list->length > index); + memmove(list->data + (index * elementSize), list->data + ((index + 1) * elementSize), (size_t) (list->length - index - 1) * elementSize); + --list->length; +} + +static inline void ffListInsertAt(FFlist* list, uint32_t elementSize, uint32_t index, const void* element) { + assert(list->length >= index); + ffListAdd(list, elementSize); + memmove(list->data + ((index + 1) * elementSize), list->data + (index * elementSize), (size_t) (list->length - index - 1) * elementSize); + memcpy(list->data + (index * elementSize), element, elementSize); +} + #define FF_LIST_FOR_EACH(itemType, itemVarName, listVar) \ for (itemType* itemVarName = (itemType*) (listVar).data; \ itemVarName - (itemType*) (listVar).data < (intptr_t) (listVar).length; \ @@ -125,6 +139,12 @@ static inline void* ffListAdd(FFlist* list, uint32_t elementSize) { #define FF_LIST_ADD(itemType, listVar) (itemType*) ffListAdd(&(listVar), (uint32_t) sizeof(itemType)) +#define FF_LIST_REMOVE_AT(itemType, listVar, index) \ + ffListRemoveAt(&(listVar), (uint32_t) sizeof(itemType), (index)) + +#define FF_LIST_INSERT_AT(itemType, listVar, index, pElement) \ + ffListInsertAt(&(listVar), (uint32_t) sizeof(itemType), (index), (pElement)) + #define FF_LIST_FIRST(itemType, listVar) FF_LIST_GET(itemType, listVar, 0) #define FF_LIST_LAST(itemType, listVar) \ ({ \ @@ -134,9 +154,9 @@ static inline void* ffListAdd(FFlist* list, uint32_t elementSize) { #define FF_LIST_CONTAINS(listVar, pCompElement, compFunc) \ ({ \ - typedef typeof(*(pCompElement)) compElementType; \ + typedef typeof(*(pCompElement)) compElementType; \ typedef bool compFuncType(const compElementType*, const compElementType*); \ - static_assert(__builtin_types_compatible_p(typeof(compFunc), compFuncType), "Incompatible callback function"); \ + static_assert(__builtin_types_compatible_p(typeof(compFunc), compFuncType), "Incompatible callback function"); \ ffListContains(&(listVar), (uint32_t) sizeof(*(pCompElement)), (pCompElement), (bool (*)(const void*, const void*)) compFunc); \ }) diff --git a/tests/list.c b/tests/list.c index 2184dd79c3..a42d088780 100644 --- a/tests/list.c +++ b/tests/list.c @@ -100,6 +100,79 @@ int main(void) { VERIFY(*FF_LIST_GET(uint32_t, list, 0) == 2); VERIFY(*FF_LIST_GET(uint32_t, list, list.length - 1) == FF_LIST_DEFAULT_ALLOC); + // insertAt + ffListClear(&list); + for (uint32_t i = 1; i <= 5; ++i) { + *FF_LIST_ADD(uint32_t, list) = i; + } + // list = [1,2,3,4,5] + + { + uint32_t v = 0; + FF_LIST_INSERT_AT(uint32_t, list, 0, &v); // insert at head + } + VERIFY(list.length == 6); + VERIFY(*FF_LIST_GET(uint32_t, list, 0) == 0); + VERIFY(*FF_LIST_GET(uint32_t, list, 1) == 1); + VERIFY(*FF_LIST_GET(uint32_t, list, 5) == 5); + // list = [0,1,2,3,4,5] + + { + uint32_t v = 99; + FF_LIST_INSERT_AT(uint32_t, list, 3, &v); // insert in the middle + } + VERIFY(list.length == 7); + VERIFY(*FF_LIST_GET(uint32_t, list, 2) == 2); + VERIFY(*FF_LIST_GET(uint32_t, list, 3) == 99); + VERIFY(*FF_LIST_GET(uint32_t, list, 4) == 3); + VERIFY(*FF_LIST_GET(uint32_t, list, 6) == 5); + // list = [0,1,2,99,3,4,5] + + { + uint32_t v = 6; + FF_LIST_INSERT_AT(uint32_t, list, list.length, &v); // insert at tail + } + VERIFY(list.length == 8); + VERIFY(*FF_LIST_GET(uint32_t, list, 3) == 99); + VERIFY(*FF_LIST_GET(uint32_t, list, 7) == 6); + // list = [0,1,2,99,3,4,5,6] + + ffListClear(&list); + { + uint32_t v = 42; + FF_LIST_INSERT_AT(uint32_t, list, 0, &v); // insert into empty list + } + VERIFY(list.length == 1); + VERIFY(*FF_LIST_GET(uint32_t, list, 0) == 42); + + // removeAt + FF_LIST_REMOVE_AT(uint32_t, list, 0); // remove the only element + VERIFY(list.length == 0); + + for (uint32_t i = 1; i <= 5; ++i) { + *FF_LIST_ADD(uint32_t, list) = i; + } + // list = [1,2,3,4,5] + + FF_LIST_REMOVE_AT(uint32_t, list, 0); // remove head + VERIFY(list.length == 4); + VERIFY(*FF_LIST_GET(uint32_t, list, 0) == 2); + VERIFY(*FF_LIST_GET(uint32_t, list, list.length - 1) == 5); + // list = [2,3,4,5] + + FF_LIST_REMOVE_AT(uint32_t, list, 1); // remove in the middle + VERIFY(list.length == 3); + VERIFY(*FF_LIST_GET(uint32_t, list, 0) == 2); + VERIFY(*FF_LIST_GET(uint32_t, list, 1) == 4); + VERIFY(*FF_LIST_GET(uint32_t, list, 2) == 5); + // list = [2,4,5] + + FF_LIST_REMOVE_AT(uint32_t, list, list.length - 1); // remove tail + VERIFY(list.length == 2); + VERIFY(*FF_LIST_GET(uint32_t, list, 0) == 2); + VERIFY(*FF_LIST_GET(uint32_t, list, 1) == 4); + // list = [2,4] + // Destroy ffListDestroy(&list); From 6d795a35722c1505dfaf834a1d9b5fa06b3cb3cd Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 4 Aug 2026 10:41:26 +0800 Subject: [PATCH 58/67] Fastfetch: adds interactive `--gen-config` mode removes `--gen-config-*` flags (merged into `--gen-config`) --- CMakeLists.txt | 1 + doc/help.json | 22 +- src/common/ffdata.h | 1 + src/common/genconfig.h | 11 + src/common/impl/genconfig.c | 1092 +++++++++++++++++++++++++++++++++++ src/fastfetch.c | 63 +- 6 files changed, 1154 insertions(+), 36 deletions(-) create mode 100644 src/common/genconfig.h create mode 100644 src/common/impl/genconfig.c diff --git a/CMakeLists.txt b/CMakeLists.txt index a38f812a98..ada50309dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -434,6 +434,7 @@ set(LIBFASTFETCH_SRC src/common/impl/font.c src/common/impl/format.c src/common/impl/frequency.c + src/common/impl/genconfig.c src/common/impl/init.c src/common/impl/jsonconfig.c src/common/impl/library.c diff --git a/doc/help.json b/doc/help.json index 511099b3ee..e24d294fc7 100644 --- a/doc/help.json +++ b/doc/help.json @@ -107,26 +107,8 @@ }, { "long": "gen-config", - "desc": "Generate a minimal config file at the specified path", - "remark": "Defaults to \"~/.config/fastfetch/config.jsonc\". Prints the generated config if is \"-\"", - "arg": { - "type": "path", - "optional": true - } - }, - { - "long": "gen-config-full", - "desc": "Generate a full config file with all optional settings at the specified path", - "remark": "Defaults to \"~/.config/fastfetch/config.jsonc\". Prints the generated config if is \"-\"", - "arg": { - "type": "path", - "optional": true - } - }, - { - "long": "gen-config-force", - "desc": "Generate a config file at the specified path, overwriting any existing file", - "remark": "Defaults to \"~/.config/fastfetch/config.jsonc\"", + "desc": "Interactively generate a config file at the specified path", + "remark": "Opens an interactive configuration UI when run in a terminal. Falls back to non-interactive generation when env-var `$NO_COLOR` is set or stdin/stdout is not a TTY. Defaults to \"~/.config/fastfetch/config.jsonc\". Prints the generated config if is \"-\"", "arg": { "type": "path", "optional": true diff --git a/src/common/ffdata.h b/src/common/ffdata.h index 76857a1ce3..fe2d4f66f2 100644 --- a/src/common/ffdata.h +++ b/src/common/ffdata.h @@ -19,4 +19,5 @@ typedef struct FFdata { FFstrbuf genConfigPath; // Path to generate configuration file FFDataResultDocType docType; // Type of result document bool configLoaded; + bool genConfigInteractive; // `--gen-config` entered the interactive CUI } FFdata; diff --git a/src/common/genconfig.h b/src/common/genconfig.h new file mode 100644 index 0000000000..91f136a3d0 --- /dev/null +++ b/src/common/genconfig.h @@ -0,0 +1,11 @@ +#pragma once + +#include "common/ffdata.h" + +// Runs the interactive config generation CUI. +// +// On success (the user saved the config), it sets `data->structure` to the user chosen +// module structure and `instance.config.logo.type` to the user chosen logo type, creates +// the result document and returns true. The caller is expected to call `writeConfigFile`. +// Returns false if the user quits without saving. +bool ffGenConfigInteractive(FFdata* data); diff --git a/src/common/impl/genconfig.c b/src/common/impl/genconfig.c new file mode 100644 index 0000000000..f874790774 --- /dev/null +++ b/src/common/impl/genconfig.c @@ -0,0 +1,1092 @@ +#include "common/genconfig.h" + +#include "fastfetch.h" + +#include "common/io.h" +#include "common/strutil.h" +#include "detection/terminalsize/terminalsize.h" +#include "fastfetch_datatext.h" +#include "modules/modules.h" + +#include +#include +#include + +#ifndef _WIN32 + #include + #include + #include +#else + #include +#endif + +// Layout (rows): +// 0: title +// 1: blank +// 2: logo type row +// 3: output mode row +// 4: blank +// 5: modules title row +// 6: blank +// 7..7+listRows-1: module grid +// 7+listRows: description row +// 8+listRows: help line 1 +// 9+listRows: help line 2 +#define FF_GEN_CONFIG_LIST_TOP 7 +#define FF_GEN_CONFIG_BOTTOM_CHROME 3 + +typedef enum : uint8_t { + FF_GEN_CONFIG_ITEM_STATUS_UNSELECTED = false, + FF_GEN_CONFIG_ITEM_STATUS_SELECTED = true, + FF_GEN_CONFIG_ITEM_STATUS_SPECIAL = 2, +} FFGenConfigItemStatus; + +typedef struct FFGenConfigItem { + FFModuleBaseInfo* baseInfo; + FFGenConfigItemStatus status; +} FFGenConfigItem; + +typedef struct FFGenConfigLayout { + uint16_t listLeft; + uint16_t listRows; + uint32_t colWidth; + uint32_t columns; + uint32_t itemsPerColumn; +} FFGenConfigLayout; + +typedef struct FFGenConfigUI { + FFlist items; // FFGenConfigItem + int32_t cursor; + uint32_t viewOffset; + FFLogoType logoType; + bool fullConfig; + bool confirmingOverwrite; + uint16_t rows; + uint16_t cols; + FFGenConfigLayout layout; +} FFGenConfigUI; + +typedef enum : uint8_t { + FF_GEN_KEY_UP, + FF_GEN_KEY_DOWN, + FF_GEN_KEY_LEFT, + FF_GEN_KEY_RIGHT, + FF_GEN_KEY_PAGE_UP, + FF_GEN_KEY_PAGE_DOWN, + FF_GEN_KEY_HOME, + FF_GEN_KEY_END, + FF_GEN_KEY_ENTER, + FF_GEN_KEY_ESCAPE, + FF_GEN_KEY_CHAR, + FF_GEN_KEY_UNKNOWN, +} FFGenConfigKey; + +#ifndef _WIN32 +static struct termios gOriginalTermios; +static bool gRawModeActive = false; + +static void restoreRawMode(void) { + if (gRawModeActive) { + tcsetattr(STDIN_FILENO, TCSAFLUSH, &gOriginalTermios); + gRawModeActive = false; + } +} + +static void enterRawMode(void) { + if (gRawModeActive) { + return; + } + if (tcgetattr(STDIN_FILENO, &gOriginalTermios) != 0) { + return; + } + struct termios raw = gOriginalTermios; + raw.c_lflag &= (tcflag_t) ~(ICANON | ECHO | ISIG | IEXTEN); + raw.c_iflag &= (tcflag_t) ~(IXON | ICRNL | BRKINT | INPCK | ISTRIP); + raw.c_cflag |= CS8; + raw.c_oflag &= (tcflag_t) ~OPOST; + raw.c_cc[VMIN] = 0; + raw.c_cc[VTIME] = 1; + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) != 0) { + return; + } + gRawModeActive = true; +} +#else +static DWORD gOriginalInputMode; +static DWORD gOriginalOutputMode; +static bool gRawModeActive = false; + +static void restoreRawMode(void) { + if (gRawModeActive) { + SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), gOriginalInputMode); + SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), gOriginalOutputMode); + gRawModeActive = false; + } +} + +static void enterRawMode(void) { + if (gRawModeActive) { + return; + } + HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE); + HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE); + + if (GetConsoleMode(hInput, &gOriginalInputMode)) { + DWORD newMode = gOriginalInputMode; + newMode &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT); + newMode |= ENABLE_VIRTUAL_TERMINAL_INPUT; + SetConsoleMode(hInput, newMode); + } + + if (GetConsoleMode(hOutput, &gOriginalOutputMode)) { + DWORD newMode = gOriginalOutputMode; + newMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN; + SetConsoleMode(hOutput, newMode); + } + + FlushConsoleInputBuffer(hInput); + gRawModeActive = true; +} +#endif + +static void getTerminalSize(uint16_t* rows, uint16_t* cols) { + FFTerminalSizeResult size; + if (ffDetectTerminalSize(&size) && size.rows > 0 && size.columns > 0) { + *rows = size.rows; + *cols = size.columns; + } else { + *rows = 24; + *cols = 80; + } +} + +static int compareModuleInfo(const void* a, const void* b) { + const FFModuleBaseInfo* const* ma = (const FFModuleBaseInfo* const*) a; + const FFModuleBaseInfo* const* mb = (const FFModuleBaseInfo* const*) b; + if ((*ma)->defaultOrder != (*mb)->defaultOrder) { + return (int) (*ma)->defaultOrder - (int) (*mb)->defaultOrder; + } + return strcmp((*ma)->name, (*mb)->name); +} + +static void collectModuleInfos(FFlist* modules) { + ffListInitA(modules, sizeof(FFModuleBaseInfo*), 64); + for (uint32_t i = 0; i <= 'Z' - 'A'; ++i) { + for (FFModuleBaseInfo** it = ffModuleInfos[i]; *it; ++it) { + if ((*it)->defaultOrder == 0) { + continue; + } + *(FFModuleBaseInfo**) ffListAdd(modules, sizeof(FFModuleBaseInfo*)) = *it; + } + } + ffListSort(modules, sizeof(FFModuleBaseInfo*), compareModuleInfo); +} + +static bool isInDefaultStructure(const char* moduleName) { + char* moduleType = nullptr; + size_t moduleLen = 0; + FF_STRBUF_AUTO_DESTROY structure = ffStrbufCreateS(FASTFETCH_DATATEXT_STRUCTURE); + while (ffStrbufGetdelim(&moduleType, &moduleLen, ':', &structure)) { + if (moduleLen == strlen(moduleName) && ffStrEqualsIgnCase(moduleType, moduleName)) { + return true; + } + } + return false; +} + +static void initItems(FFlist* items, const FFlist* modules) { + ffListInitA(items, sizeof(FFGenConfigItem), modules->length + 8); + FF_LIST_FOR_EACH (FFModuleBaseInfo*, info, *modules) { + FFGenConfigItem* item = FF_LIST_ADD(FFGenConfigItem, *items); + item->baseInfo = *info; + if (ffStrEqualsIgnCase((*info)->name, FF_BREAK_MODULE_NAME) || + ffStrEqualsIgnCase((*info)->name, FF_SEPARATOR_MODULE_NAME)) { + item->status = FF_GEN_CONFIG_ITEM_STATUS_SPECIAL; + } else { + item->status = isInDefaultStructure((*info)->name) + ? FF_GEN_CONFIG_ITEM_STATUS_SELECTED + : FF_GEN_CONFIG_ITEM_STATUS_UNSELECTED; + } + } +} + +static void moveItemDown(FFGenConfigUI* ui, uint32_t idx) { + if (idx + 1 >= ui->items.length) { + return; + } + FFGenConfigItem* items = (FFGenConfigItem*) ui->items.data; + FFGenConfigItem tmp = items[idx]; + items[idx] = items[idx + 1]; + items[idx + 1] = tmp; + ui->cursor = (int32_t) (idx + 1); +} + +static void moveItemUp(FFGenConfigUI* ui, uint32_t idx) { + if (idx == 0) { + return; + } + FFGenConfigItem* items = (FFGenConfigItem*) ui->items.data; + FFGenConfigItem tmp = items[idx]; + items[idx] = items[idx - 1]; + items[idx - 1] = tmp; + ui->cursor = (int32_t) (idx - 1); +} + +static void removeAllBreaksSeparators(FFGenConfigUI* ui) { + for (uint32_t i = ui->items.length; i-- > 0;) { + FFGenConfigItem* item = FF_LIST_GET(FFGenConfigItem, ui->items, i); + if (item->status == FF_GEN_CONFIG_ITEM_STATUS_SPECIAL) { + FF_LIST_REMOVE_AT(FFGenConfigItem, ui->items, i); + } + } + if ((uint32_t) ui->cursor >= ui->items.length && ui->items.length > 0) { + ui->cursor = (int32_t) ui->items.length - 1; + } +} + +static void computeLayout(FFGenConfigUI* ui) { + FFGenConfigLayout* layout = &ui->layout; + if (ui->rows < 10) { + ui->rows = 10; + } + int32_t listRows = (int32_t) ui->rows - FF_GEN_CONFIG_LIST_TOP - FF_GEN_CONFIG_BOTTOM_CHROME; + layout->listRows = listRows < 0 ? 0 : (uint16_t) listRows; + layout->listLeft = 2; + + uint32_t maxContentWidth = 0; + FF_LIST_FOR_EACH (FFGenConfigItem, item, ui->items) { + uint32_t w = (uint32_t) strlen(item->baseInfo->name) + 4; // "[x] " / "[─] " prefix + if (w > maxContentWidth) { + maxContentWidth = w; + } + } + layout->colWidth = maxContentWidth + 2; + if (layout->colWidth < 1) { + layout->colWidth = 1; + } + + uint32_t gridWidth = ui->cols > 4 ? (uint32_t) ui->cols - 4 : 1; + uint32_t maxColumns = gridWidth / layout->colWidth; + if (maxColumns < 1) { + maxColumns = 1; + } + + uint32_t length = ui->items.length; + if (length == 0 || layout->listRows == 0) { + layout->columns = 1; + layout->itemsPerColumn = length > 0 ? length : 1; + return; + } + uint32_t neededColumns = (length + layout->listRows - 1) / layout->listRows; + if (neededColumns < 1) { + neededColumns = 1; + } + layout->columns = neededColumns < maxColumns ? neededColumns : maxColumns; + layout->itemsPerColumn = (length + layout->columns - 1) / layout->columns; +} + +static void recomputeView(FFGenConfigUI* ui) { + const FFGenConfigLayout* layout = &ui->layout; + uint32_t length = ui->items.length; + if (length == 0) { + ui->cursor = 0; + ui->viewOffset = 0; + return; + } + if (ui->cursor < 0) { + ui->cursor = 0; + } + if ((uint32_t) ui->cursor >= length) { + ui->cursor = (int32_t) length - 1; + } + + uint32_t rowsPerColumn = layout->itemsPerColumn; + if (rowsPerColumn <= layout->listRows) { + ui->viewOffset = 0; + return; + } + + uint32_t cursorRow = (uint32_t) ui->cursor % rowsPerColumn; + uint32_t pageTop = ui->viewOffset; + if (cursorRow < pageTop) { + pageTop = cursorRow; + } else if (cursorRow >= pageTop + layout->listRows) { + pageTop = cursorRow - layout->listRows + 1; + } + uint32_t maxTop = rowsPerColumn - layout->listRows; + if (pageTop > maxTop) { + pageTop = maxTop; + } + ui->viewOffset = pageTop; +} + +static void toggleItem(FFGenConfigUI* ui, uint32_t idx) { + FFGenConfigItem* item = FF_LIST_GET(FFGenConfigItem, ui->items, idx); + if (item->status == FF_GEN_CONFIG_ITEM_STATUS_SPECIAL) { + return; + } + item->status = !item->status; +} + +static void selectAllModules(FFGenConfigUI* ui) { + FF_LIST_FOR_EACH (FFGenConfigItem, item, ui->items) { + if (item->status != FF_GEN_CONFIG_ITEM_STATUS_SPECIAL) { + item->status = FF_GEN_CONFIG_ITEM_STATUS_SELECTED; + } + } +} + +static void invertAllModules(FFGenConfigUI* ui) { + FF_LIST_FOR_EACH (FFGenConfigItem, item, ui->items) { + if (item->status != FF_GEN_CONFIG_ITEM_STATUS_SPECIAL) { + item->status = !item->status; + } + } +} + +static void addBreakBelow(FFGenConfigUI* ui, uint32_t idx) { + FFGenConfigItem br = { .baseInfo = &ffBreakModuleInfo, .status = FF_GEN_CONFIG_ITEM_STATUS_SPECIAL }; + FF_LIST_INSERT_AT(FFGenConfigItem, ui->items, idx + 1, &br); +} + +static void addSeparatorBelow(FFGenConfigUI* ui, uint32_t idx) { + FFGenConfigItem sep = { .baseInfo = &ffSeparatorModuleInfo, .status = FF_GEN_CONFIG_ITEM_STATUS_SPECIAL }; + FF_LIST_INSERT_AT(FFGenConfigItem, ui->items, idx + 1, &sep); +} + +static void cycleLogoType(FFGenConfigUI* ui, int delta) { + switch (ui->logoType) { + case FF_LOGO_TYPE_AUTO: + ui->logoType = delta > 0 ? FF_LOGO_TYPE_SMALL : FF_LOGO_TYPE_NONE; + break; + case FF_LOGO_TYPE_SMALL: + ui->logoType = delta > 0 ? FF_LOGO_TYPE_NONE : FF_LOGO_TYPE_AUTO; + break; + default: + ui->logoType = delta > 0 ? FF_LOGO_TYPE_AUTO : FF_LOGO_TYPE_SMALL; + break; + } +} + +static uint32_t countSelectedModules(const FFlist* items) { + uint32_t count = 0; + FF_LIST_FOR_EACH (FFGenConfigItem, item, *items) { + if (item->status == FF_GEN_CONFIG_ITEM_STATUS_SELECTED) { + ++count; + } + } + return count; +} + +static uint32_t countModules(const FFlist* items) { + uint32_t count = 0; + FF_LIST_FOR_EACH (FFGenConfigItem, item, *items) { + if (item->status != FF_GEN_CONFIG_ITEM_STATUS_SPECIAL) { + ++count; + } + } + return count; +} + +// ---- Row builder ----------------------------------------------------------- + +typedef struct FFRow { + FFstrbuf buf; + uint32_t visualCol; + uint16_t cols; +} FFRow; + +static void rowInit(FFRow* row, uint16_t cols) { + ffStrbufInitA(&row->buf, (uint32_t) cols + 16); + row->visualCol = 0; + row->cols = cols; +} + +static void rowAppendRaw(FFRow* row, const char* s) { + ffStrbufAppendS(&row->buf, s); +} + +static void rowAppendVisual(FFRow* row, const char* s) { + uint32_t len = (uint32_t) strlen(s); + ffStrbufAppendNS(&row->buf, len, s); + row->visualCol += ffUtf8StrWidth(s, len); +} + +static void rowAppendVisualTruncated(FFRow* row, const char* s, uint32_t maxWidth) { + uint32_t total = (uint32_t) strlen(s); + uint32_t bytes = total; + if (ffUtf8StrWidth(s, total) > maxWidth) { + bytes = 0; + uint32_t used = 0; + const char* p = s; + uint32_t remaining = total; + while (remaining > 0 && used < maxWidth) { + uint8_t w = 0; + uint8_t cbytes = ffUtf8CharLenWidth(p, remaining, &w); + if (cbytes == 0 || used + w > maxWidth) { + break; + } + used += w; + bytes += cbytes; + p += cbytes; + remaining -= cbytes; + } + } + ffStrbufAppendNS(&row->buf, bytes, s); + row->visualCol += ffUtf8StrWidth(s, bytes); +} + +static void rowPadVisual(FFRow* row, uint32_t col) { + while (row->visualCol < col) { + ffStrbufAppendC(&row->buf, ' '); + ++row->visualCol; + } +} + +static uint32_t rowAnsiSeqLength(const char* s, uint32_t length) { + uint32_t i = 1; + while (i < length) { + uint8_t c = (uint8_t) s[i]; + if (c >= 0x40 && c <= 0x7e) { + return i + 1; + } + ++i; + } + return length; +} + +static void truncateRow(FFRow* row) { + FFstrbuf src = row->buf; + ffStrbufInitA(&row->buf, row->cols + 32); + uint32_t used = 0; + uint32_t remaining = src.length; + const char* p = src.chars; + while (remaining > 0 && used < row->cols) { + if (*p == '\x1b') { + uint32_t len = rowAnsiSeqLength(p, remaining); + ffStrbufAppendNS(&row->buf, len, p); + p += len; + remaining -= len; + } else { + uint8_t w = 0; + uint8_t cbytes = ffUtf8CharLenWidth(p, remaining, &w); + if (cbytes == 0) { + cbytes = 1; + w = 1; + } + if (used + w > row->cols) { + break; + } + ffStrbufAppendNS(&row->buf, cbytes, p); + used += w; + p += cbytes; + remaining -= cbytes; + } + } + ffStrbufDestroy(&src); + row->visualCol = used; +} + +static void finishRow(FFRow* row, FFstrbuf* out, bool isLastRow) { + if (row->visualCol > row->cols) { + truncateRow(row); + ffStrbufAppendS(&row->buf, "\e[m"); + } + if (row->visualCol < row->cols) { + rowPadVisual(row, row->cols); + } + ffStrbufAppend(out, &row->buf); + ffStrbufAppendS(out, "\e[K"); + if (!isLastRow) { + ffStrbufAppendS(out, "\r\n"); + } + ffStrbufDestroy(&row->buf); +} + +static void drawModuleItem(FFRow* row, const FFGenConfigItem* item, uint32_t cellWidth, bool highlight) { + const uint32_t nameMax = cellWidth > 4 ? cellWidth - 4 : 0; + + if (item->baseInfo == &ffBreakModuleInfo) { + rowAppendRaw(row, highlight ? "\e[1;95;7m" : "\e[1;95m"); + ffStrbufAppendS(&row->buf, "[─]"); + row->visualCol += 3; + rowAppendRaw(row, "\e[m"); + rowAppendVisual(row, " "); + rowAppendRaw(row, highlight ? "\e[1;95;7m" : "\e[1;95m"); + rowAppendVisualTruncated(row, item->baseInfo->name, nameMax); + rowAppendRaw(row, "\e[m"); + return; + } + if (item->baseInfo == &ffSeparatorModuleInfo) { + rowAppendRaw(row, highlight ? "\e[1;96;7m" : "\e[1;96m"); + ffStrbufAppendS(&row->buf, "[═]"); + row->visualCol += 3; + rowAppendRaw(row, "\e[m"); + rowAppendVisual(row, " "); + rowAppendRaw(row, highlight ? "\e[1;96;7m" : "\e[1;96m"); + rowAppendVisualTruncated(row, item->baseInfo->name, nameMax); + rowAppendRaw(row, "\e[m"); + return; + } + + if (item->status == FF_GEN_CONFIG_ITEM_STATUS_SELECTED) { + rowAppendRaw(row, highlight ? "\e[1;92;7m" : "\e[1;92m"); + ffStrbufAppendS(&row->buf, "[x]"); + row->visualCol += 3; + rowAppendRaw(row, "\e[m"); + rowAppendVisual(row, " "); + rowAppendRaw(row, highlight ? "\e[1;97;7m" : "\e[1;97m"); + rowAppendVisualTruncated(row, item->baseInfo->name, nameMax); + rowAppendRaw(row, "\e[m"); + } else { + rowAppendRaw(row, highlight ? "\e[90;7m" : "\e[90m"); + ffStrbufAppendS(&row->buf, "[ ]"); + row->visualCol += 3; + rowAppendRaw(row, "\e[m"); + rowAppendVisual(row, " "); + rowAppendVisualTruncated(row, item->baseInfo->name, nameMax); + rowAppendRaw(row, "\e[m"); + } +} + +static void drawItemCell(FFRow* row, uint32_t startCol, uint32_t cellWidth, const FFGenConfigItem* item, bool highlight) { + rowPadVisual(row, startCol); + drawModuleItem(row, item, cellWidth, highlight); + rowPadVisual(row, startCol + cellWidth); +} + +static void drawLogoOption(FFRow* row, const char* name, bool active) { + if (active) { + rowAppendRaw(row, "\e[1;92m"); + rowAppendVisual(row, "● "); + rowAppendRaw(row, "\e[m"); + rowAppendRaw(row, "\e[1m"); + rowAppendVisual(row, name); + rowAppendRaw(row, "\e[m"); + } else { + rowAppendRaw(row, "\e[90m"); + rowAppendVisual(row, "○ "); + rowAppendVisual(row, name); + rowAppendRaw(row, "\e[m"); + } +} + +static void renderFrame(FFGenConfigUI* ui, FFstrbuf* out) { + getTerminalSize(&ui->rows, &ui->cols); + computeLayout(ui); + recomputeView(ui); + + const FFGenConfigLayout* layout = &ui->layout; + const uint16_t cols = ui->cols; + const uint32_t selectedCount = countSelectedModules(&ui->items); + const uint32_t moduleCount = countModules(&ui->items); + + ffStrbufAppendS(out, "\e[?25l\e[H"); + uint32_t rowCount = 0; + + FFRow row; + + // Row 0: title + rowInit(&row, cols); + rowAppendVisual(&row, " "); + rowAppendRaw(&row, "\e[1;36m"); + rowAppendVisual(&row, "fastfetch"); + rowAppendRaw(&row, "\e[m"); + rowAppendRaw(&row, "\e[1m"); + rowAppendVisual(&row, " configuration"); + rowAppendRaw(&row, "\e[m"); + if (cols >= 48) { + rowAppendVisual(&row, " "); + rowAppendRaw(&row, "\e[90m"); + rowAppendVisual(&row, "interactive config generator"); + rowAppendRaw(&row, "\e[m"); + } + finishRow(&row, out, ++rowCount == ui->rows); + + // Row 1: blank + rowInit(&row, cols); + finishRow(&row, out, ++rowCount == ui->rows); + + // Row 2: logo type + rowInit(&row, cols); + rowAppendVisual(&row, " "); + rowAppendRaw(&row, "\e[1;97m"); + rowAppendVisual(&row, "Logo type:"); + rowAppendRaw(&row, "\e[m"); + rowAppendVisual(&row, " "); + drawLogoOption(&row, "default", ui->logoType == FF_LOGO_TYPE_AUTO); + rowAppendVisual(&row, " "); + drawLogoOption(&row, "small", ui->logoType == FF_LOGO_TYPE_SMALL); + rowAppendVisual(&row, " "); + drawLogoOption(&row, "none", ui->logoType == FF_LOGO_TYPE_NONE); + if (cols >= 72) { + rowAppendVisual(&row, " "); + rowAppendRaw(&row, "\e[90m"); + rowAppendVisual(&row, "(l)"); + rowAppendRaw(&row, "\e[m"); + } + finishRow(&row, out, ++rowCount == ui->rows); + + // Row 3: output mode + rowInit(&row, cols); + rowAppendVisual(&row, " "); + rowAppendRaw(&row, "\e[1;97m"); + rowAppendVisual(&row, "Output:"); + rowAppendRaw(&row, "\e[m"); + rowAppendVisual(&row, " "); + drawLogoOption(&row, "minimal", !ui->fullConfig); + rowAppendVisual(&row, " "); + drawLogoOption(&row, "full", ui->fullConfig); + if (cols >= 64) { + rowAppendVisual(&row, " "); + rowAppendRaw(&row, "\e[90m"); + rowAppendVisual(&row, "(o)"); + rowAppendRaw(&row, "\e[m"); + } + finishRow(&row, out, ++rowCount == ui->rows); + + // Row 4: blank + rowInit(&row, cols); + finishRow(&row, out, ++rowCount == ui->rows); + + // Row 5: modules title + rowInit(&row, cols); + rowAppendVisual(&row, " "); + rowAppendRaw(&row, "\e[1;97m"); + rowAppendVisual(&row, "Modules:"); + rowAppendRaw(&row, "\e[m"); + rowAppendRaw(&row, "\e[92m"); + FF_STRBUF_AUTO_DESTROY counter = ffStrbufCreateA(32); + ffStrbufAppendF(&counter, " [%u/%u selected]", selectedCount, moduleCount); + rowAppendVisual(&row, counter.chars); + rowAppendRaw(&row, "\e[m"); + finishRow(&row, out, ++rowCount == ui->rows); + + // Row 6: blank + rowInit(&row, cols); + finishRow(&row, out, ++rowCount == ui->rows); + + // Grid rows + const uint32_t rowsPerColumn = layout->itemsPerColumn; + const uint32_t length = ui->items.length; + for (uint16_t screenRow = 0; screenRow < layout->listRows; ++screenRow) { + rowInit(&row, cols); + for (uint32_t c = 0; c < layout->columns; ++c) { + uint32_t startCol = layout->listLeft + c * layout->colWidth; + uint32_t index = (ui->viewOffset + screenRow) + c * rowsPerColumn; + if (screenRow < rowsPerColumn && index < length) { + const FFGenConfigItem* item = FF_LIST_GET(FFGenConfigItem, ui->items, index); + drawItemCell(&row, startCol, layout->colWidth, item, (uint32_t) ui->cursor == index); + } else { + rowPadVisual(&row, startCol + layout->colWidth); + } + } + finishRow(&row, out, ++rowCount == ui->rows); + } + + // Row after grid: description + rowInit(&row, cols); + if (ui->cursor >= 0 && (uint32_t) ui->cursor < ui->items.length) { + const FFGenConfigItem* item = FF_LIST_GET(FFGenConfigItem, ui->items, (uint32_t) ui->cursor); + if (item->baseInfo && item->baseInfo->description) { + rowAppendVisual(&row, " "); + rowAppendRaw(&row, "\e[90m"); + rowAppendVisualTruncated(&row, item->baseInfo->description, cols > 4 ? cols - 4 : 1); + rowAppendRaw(&row, "\e[m"); + } + } + finishRow(&row, out, ++rowCount == ui->rows); + + // Help lines + rowInit(&row, cols); + rowAppendRaw(&row, "\e[90m"); + rowAppendVisual(&row, " ↑/↓ k/j move ←/→ col Space toggle f/F all/invert K/J reorder b/B break/sep d/D del"); + if (ui->confirmingOverwrite) { + rowAppendRaw(&row, "\e[m"); + rowAppendRaw(&row, "\e[1;93m"); + rowAppendVisual(&row, " File exists. Overwrite? (y/N)"); + rowAppendRaw(&row, "\e[m"); + } + finishRow(&row, out, ++rowCount == ui->rows); + + rowInit(&row, cols); + rowAppendRaw(&row, "\e[90m"); + rowAppendVisual(&row, " l logo o minimal/full s/Enter save q/Esc quit g/G top/bottom"); + rowAppendRaw(&row, "\e[m"); + finishRow(&row, out, ++rowCount == ui->rows); + + ffStrbufAppendS(out, "\e[J"); +} + +// ---- Key input ------------------------------------------------------------- + +static FFGenConfigKey readKey(char* outChar) { +#ifndef _WIN32 + struct pollfd pfd = { + .fd = STDIN_FILENO, + .events = POLLIN, + }; + if (poll(&pfd, 1, 100) <= 0) { + return FF_GEN_KEY_UNKNOWN; + } +#else + HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE); + DWORD waitResult = WaitForSingleObject(hInput, 100); + if (waitResult != WAIT_OBJECT_0) { + return FF_GEN_KEY_UNKNOWN; + } + DWORD numEvents; + while (GetNumberOfConsoleInputEvents(hInput, &numEvents) && numEvents > 0) { + INPUT_RECORD record; + DWORD read; + if (!PeekConsoleInput(hInput, &record, 1, &read) || read == 0) { + break; + } + if (record.EventType == KEY_EVENT && record.Event.KeyEvent.bKeyDown) { + break; + } + ReadConsoleInput(hInput, &record, 1, &read); + } + if (!GetNumberOfConsoleInputEvents(hInput, &numEvents) || numEvents == 0) { + return FF_GEN_KEY_UNKNOWN; + } +#endif + + char buf[16]; + ssize_t n = ffReadFDData(FFUnixFD2NativeFD(STDIN_FILENO), sizeof(buf), buf); + if (n <= 0) { + return FF_GEN_KEY_UNKNOWN; + } + if (buf[0] == '\r' || buf[0] == '\n') { + return FF_GEN_KEY_ENTER; + } + if (buf[0] != '\x1b') { + *outChar = buf[0]; + return FF_GEN_KEY_CHAR; + } + if (n == 1) { + return FF_GEN_KEY_ESCAPE; + } + + if (buf[1] == '[') { + size_t i = 2; + if (i < (size_t) n && buf[i] >= 'A' && buf[i] <= 'D') { + switch (buf[i]) { + case 'A': + return FF_GEN_KEY_UP; + case 'B': + return FF_GEN_KEY_DOWN; + case 'C': + return FF_GEN_KEY_RIGHT; + case 'D': + return FF_GEN_KEY_LEFT; + } + } + uint32_t param = 0; + while (i < (size_t) n && buf[i] >= '0' && buf[i] <= '9') { + param = param * 10 + (uint32_t) (buf[i] - '0'); + ++i; + } + while (i < (size_t) n && buf[i] == ';') { + ++i; + while (i < (size_t) n && buf[i] >= '0' && buf[i] <= '9') { + ++i; + } + } + if (i < (size_t) n) { + switch (buf[i]) { + case 'A': + return FF_GEN_KEY_UP; + case 'B': + return FF_GEN_KEY_DOWN; + case 'C': + return FF_GEN_KEY_RIGHT; + case 'D': + return FF_GEN_KEY_LEFT; + case 'H': + return FF_GEN_KEY_HOME; + case 'F': + return FF_GEN_KEY_END; + case '~': + switch (param) { + case 5: + return FF_GEN_KEY_PAGE_UP; + case 6: + return FF_GEN_KEY_PAGE_DOWN; + case 7: + return FF_GEN_KEY_HOME; + case 8: + return FF_GEN_KEY_END; + } + } + } + return FF_GEN_KEY_UNKNOWN; + } + + if (buf[1] == 'O') { + if (n >= 3) { + switch (buf[2]) { + case 'H': + return FF_GEN_KEY_HOME; + case 'F': + return FF_GEN_KEY_END; + } + } + return FF_GEN_KEY_UNKNOWN; + } + + return FF_GEN_KEY_ESCAPE; +} + +static void moveCursorLeft(FFGenConfigUI* ui) { + uint32_t rowsPerColumn = ui->layout.itemsPerColumn; + if (rowsPerColumn == 0) { + return; + } + if ((uint32_t) ui->cursor >= rowsPerColumn) { + ui->cursor -= rowsPerColumn; + } +} + +static void moveCursorRight(FFGenConfigUI* ui) { + uint32_t rowsPerColumn = ui->layout.itemsPerColumn; + if (rowsPerColumn == 0) { + return; + } + uint32_t newCursor = (uint32_t) ui->cursor + rowsPerColumn; + if (newCursor < ui->items.length) { + ui->cursor = (int32_t) newCursor; + } +} + +static void moveCursorUp(FFGenConfigUI* ui) { + if (ui->cursor > 0) { + --ui->cursor; + } +} + +static void moveCursorDown(FFGenConfigUI* ui) { + if (ui->items.length == 0) { + return; + } + if ((uint32_t) ui->cursor + 1 < ui->items.length) { + ++ui->cursor; + } else { + ui->cursor = 0; + } +} + +// Returns 1 on save, 0 on quit, -1 on continue +static int handleKey(FFGenConfigUI* ui, FFGenConfigKey key, char ch, bool fileExists) { + if (key == FF_GEN_KEY_UNKNOWN) { + return -1; + } + + if (ui->confirmingOverwrite) { + if (key == FF_GEN_KEY_CHAR && (ch == 'y' || ch == 'Y')) { + return 1; + } + if ((key == FF_GEN_KEY_CHAR && (ch == 'n' || ch == 'N')) || key == FF_GEN_KEY_ESCAPE || key == FF_GEN_KEY_ENTER) { + ui->confirmingOverwrite = false; + } + return -1; + } + + switch (key) { + case FF_GEN_KEY_UP: + moveCursorUp(ui); + break; + case FF_GEN_KEY_DOWN: + moveCursorDown(ui); + break; + case FF_GEN_KEY_LEFT: + moveCursorLeft(ui); + break; + case FF_GEN_KEY_RIGHT: + moveCursorRight(ui); + break; + case FF_GEN_KEY_PAGE_UP: + ui->cursor -= (int32_t) ui->layout.listRows; + if (ui->cursor < 0) { + ui->cursor = 0; + } + break; + case FF_GEN_KEY_PAGE_DOWN: + if (ui->items.length == 0) { + break; + } + ui->cursor += (int32_t) ui->layout.listRows; + if ((uint32_t) ui->cursor >= ui->items.length) { + ui->cursor = (int32_t) ui->items.length - 1; + } + break; + case FF_GEN_KEY_HOME: + ui->cursor = 0; + break; + case FF_GEN_KEY_END: + ui->cursor = ui->items.length > 0 ? (int32_t) ui->items.length - 1 : 0; + break; + case FF_GEN_KEY_ENTER: + if (fileExists) { + ui->confirmingOverwrite = true; + } else { + return 1; + } + break; + case FF_GEN_KEY_ESCAPE: + return 0; + case FF_GEN_KEY_CHAR: + if (ch == 'q') { + return 0; + } else if (ch == 's') { + if (fileExists) { + ui->confirmingOverwrite = true; + } else { + return 1; + } + } else if (ch == ' ') { + if (ui->cursor >= 0 && (uint32_t) ui->cursor < ui->items.length) { + toggleItem(ui, (uint32_t) ui->cursor); + } + } else if (ch == 'f') { + selectAllModules(ui); + } else if (ch == 'F') { + invertAllModules(ui); + } else if (ch == 'o') { + ui->fullConfig = !ui->fullConfig; + } else if (ch == 'j') { + moveCursorDown(ui); + } else if (ch == 'k') { + moveCursorUp(ui); + } else if (ch == 'J') { + if (ui->cursor >= 0 && (uint32_t) ui->cursor < ui->items.length) { + moveItemDown(ui, (uint32_t) ui->cursor); + } + } else if (ch == 'K') { + if (ui->cursor >= 0 && (uint32_t) ui->cursor < ui->items.length) { + moveItemUp(ui, (uint32_t) ui->cursor); + } + } else if (ch == 'b') { + if (ui->cursor >= 0 && (uint32_t) ui->cursor < ui->items.length) { + addBreakBelow(ui, (uint32_t) ui->cursor); + } + } else if (ch == 'B') { + if (ui->cursor >= 0 && (uint32_t) ui->cursor < ui->items.length) { + addSeparatorBelow(ui, (uint32_t) ui->cursor); + } + } else if (ch == 'd') { + if (ui->cursor >= 0 && (uint32_t) ui->cursor < ui->items.length) { + FFGenConfigItem* item = FF_LIST_GET(FFGenConfigItem, ui->items, (uint32_t) ui->cursor); + if (item->status == FF_GEN_CONFIG_ITEM_STATUS_SPECIAL) { + FF_LIST_REMOVE_AT(FFGenConfigItem, ui->items, (uint32_t) ui->cursor); + if ((uint32_t) ui->cursor >= ui->items.length && ui->items.length > 0) { + ui->cursor = (int32_t) ui->items.length - 1; + } + } + } + } else if (ch == 'D') { + removeAllBreaksSeparators(ui); + } else if (ch == 'g') { + ui->cursor = 0; + } else if (ch == 'G') { + ui->cursor = ui->items.length > 0 ? (int32_t) ui->items.length - 1 : 0; + } else if (ch == 'l') { + cycleLogoType(ui, 1); + } else if (ch == '\x03' || ch == '\x04' || ch == '\x1a' || ch == '\x1c') { + return 0; + } + break; + case FF_GEN_KEY_UNKNOWN: + break; + } + return -1; +} + +static int runCui(FFGenConfigUI* ui, bool fileExists) { + while (true) { + FF_STRBUF_AUTO_DESTROY frame = ffStrbufCreateA((uint32_t) ui->cols * ((uint32_t) ui->rows + 4)); + renderFrame(ui, &frame); + ffWriteFDData(FFUnixFD2NativeFD(STDOUT_FILENO), frame.length, frame.chars); + fflush(stdout); + + char ch = 0; + FFGenConfigKey key = readKey(&ch); + int result = handleKey(ui, key, ch, fileExists); + if (result != -1) { + return result; + } + } +} + +static FFLogoType initialLogoType(void) { + switch (instance.config.logo.type) { + case FF_LOGO_TYPE_SMALL: + return FF_LOGO_TYPE_SMALL; + case FF_LOGO_TYPE_NONE: + return FF_LOGO_TYPE_NONE; + default: + return FF_LOGO_TYPE_AUTO; + } +} + +static bool applyConfig(FFdata* data, const FFlist* items, bool fullConfig) { + if (data->resultDoc) { + fputs("Error: duplicated `--gen-config` or `--format json` flags found\n", stderr); + return false; + } + + FF_STRBUF_AUTO_DESTROY structure = ffStrbufCreateA(256); + FF_LIST_FOR_EACH (FFGenConfigItem, item, *items) { + if (item->status == FF_GEN_CONFIG_ITEM_STATUS_UNSELECTED) { + continue; + } + ffStrbufAppendS(&structure, item->baseInfo->name); + ffStrbufAppendC(&structure, ':'); + } + ffStrbufTrimRight(&structure, ':'); + ffStrbufSet(&data->structure, &structure); + + data->docType = fullConfig ? FF_RESULT_DOC_TYPE_CONFIG_FULL : FF_RESULT_DOC_TYPE_CONFIG; + data->resultDoc = yyjson_mut_doc_new(nullptr); + return true; +} + +bool ffGenConfigInteractive(FFdata* data) { + FF_LIST_AUTO_DESTROY modules; + collectModuleInfos(&modules); + + FFGenConfigUI ui = { + .items = ffListCreate(), + .cursor = 0, + .viewOffset = 0, + .logoType = initialLogoType(), + .fullConfig = false, + .confirmingOverwrite = false, + .rows = 24, + .cols = 80, + }; + initItems(&ui.items, &modules); + + getTerminalSize(&ui.rows, &ui.cols); + enterRawMode(); + ffWriteFDData(FFUnixFD2NativeFD(STDOUT_FILENO), strlen("\e[?1049h\e[?25l"), "\e[?1049h\e[?25l"); + + const bool fileExists = ffPathExists(data->genConfigPath.chars, FF_PATHTYPE_ANY); + int result = runCui(&ui, fileExists); + + restoreRawMode(); + ffWriteFDData(FFUnixFD2NativeFD(STDOUT_FILENO), strlen("\e[?1049l\e[?25h\e[m"), "\e[?1049l\e[?25h\e[m"); + + bool success = false; + if (result == 1) { + if (applyConfig(data, &ui.items, ui.fullConfig)) { + instance.config.logo.type = ui.logoType; + success = true; + } + } else { + fputs("\nConfig generation cancelled.\n", stderr); + } + + ffListDestroy(&ui.items); + return success; +} diff --git a/src/fastfetch.c b/src/fastfetch.c index 3146cefba7..af8375d809 100644 --- a/src/fastfetch.c +++ b/src/fastfetch.c @@ -3,6 +3,7 @@ #include "detection/version/version.h" #include "logo/logo.h" #include "common/commandoption.h" +#include "common/genconfig.h" #include "common/init.h" #include "common/io.h" #include "common/jsonconfig.h" @@ -15,6 +16,12 @@ #include #include +#ifndef _WIN32 + #include +#else + #include +#endif + [[gnu::cold]] static void printCommandFormatHelpJson(void) { yyjson_mut_doc* doc = yyjson_mut_doc_new(nullptr); @@ -432,12 +439,7 @@ static bool parseJsoncFile(FFdata* data, const char* path, yyjson_read_flag flg) } [[gnu::cold]] -static void generateConfigFile(FFdata* data, bool force, const char* filePath, bool fullConfig) { - if (data->resultDoc) { - fprintf(stderr, "Error: duplicated `--gen-config` or `--format json` flags found\n"); - exit(477); - } - +static void setupGenConfigPath(FFdata* data, const char* filePath) { if (!filePath) { if (instance.state.platform.configDirs.length == 0) { fprintf(stderr, "Error: No config directory found to generate config file in. Use --gen-config to specify a path\n"); @@ -451,13 +453,23 @@ static void generateConfigFile(FFdata* data, bool force, const char* filePath, b } else { ffStrbufSetS(&data->genConfigPath, filePath); } +} - if (!force && ffPathExists(data->genConfigPath.chars, FF_PATHTYPE_ANY)) { - fprintf(stderr, "Error: file `%s` exists. Use `--gen-config%s-force` to overwrite\n", data->genConfigPath.chars, fullConfig ? "-full" : ""); +[[gnu::cold]] +static void generateConfigFile(FFdata* data, const char* filePath) { + if (data->resultDoc) { + fprintf(stderr, "Error: duplicated `--gen-config` or `--format json` flags found\n"); exit(477); } - data->docType = fullConfig ? FF_RESULT_DOC_TYPE_CONFIG_FULL : FF_RESULT_DOC_TYPE_CONFIG; + setupGenConfigPath(data, filePath); + + if (ffPathExists(data->genConfigPath.chars, FF_PATHTYPE_ANY)) { + fprintf(stderr, "Error: file `%s` exists. Please remove it before generating a new one\n", data->genConfigPath.chars); + exit(477); + } + + data->docType = FF_RESULT_DOC_TYPE_CONFIG; data->resultDoc = yyjson_mut_doc_new(nullptr); } @@ -586,6 +598,15 @@ static void enableJsonOutput(FFdata* data) { yyjson_mut_doc_set_root(data->resultDoc, yyjson_mut_arr(data->resultDoc)); } +static void genConfigCommon(FFdata* data, const char* value) { + if (!getenv("NO_COLOR") && isatty(STDOUT_FILENO) && isatty(STDIN_FILENO)) { + data->genConfigInteractive = true; + setupGenConfigPath(data, value); + } else { + generateConfigFile(data, value); + } +} + static void parseCommand(FFdata* data, char* key, char* value) { if (ffStrEqualsIgnCase(key, "-h") || ffStrEqualsIgnCase(key, "--help")) { printCommandHelp(value); @@ -647,13 +668,7 @@ static void parseCommand(FFdata* data, char* key, char* value) { exit(0); } else if (ffStrEqualsIgnCase(key, "--gen-config")) { - generateConfigFile(data, false, value, false); - } else if (ffStrEqualsIgnCase(key, "--gen-config-force")) { - generateConfigFile(data, true, value, false); - } else if (ffStrEqualsIgnCase(key, "--gen-config-full")) { - generateConfigFile(data, false, value, true); - } else if (ffStrEqualsIgnCase(key, "--gen-config-full-force")) { - generateConfigFile(data, true, value, true); + genConfigCommon(data, value); } else if (ffStrEqualsIgnCase(key, "-c") || ffStrEqualsIgnCase(key, "--config")) { optionParseConfigFile(data, key, value); } else if (ffStrEqualsIgnCase(key, "-j") || ffStrEqualsIgnCase(key, "--json")) { @@ -828,6 +843,14 @@ static void writeConfigFile(FFdata* data) { ffOptionsGenerateLogoJsonConfig(data, &instance.config.logo); ffOptionsGenerateDisplayJsonConfig(data, &instance.config.display); ffOptionsGenerateGeneralJsonConfig(data, &instance.config.general); + } else if (data->genConfigInteractive) { + if (instance.config.logo.type == FF_LOGO_TYPE_NONE) { + yyjson_mut_obj_add_null(doc, root, "logo"); + } else if (instance.config.logo.type == FF_LOGO_TYPE_SMALL) { + yyjson_mut_val* logo = yyjson_mut_obj(doc); + yyjson_mut_obj_add_str(doc, logo, "type", "small"); + yyjson_mut_obj_add_val(doc, root, "logo", logo); + } } ffMigrateCommandOptionToJsonc(data); @@ -877,6 +900,14 @@ int main(int argc, char** argv) { if (__builtin_expect(data.genConfigPath.length == 0, true)) { run(&data); } else { + if (data.genConfigInteractive && !ffGenConfigInteractive(&data)) { + // User cancelled the interactive config generation + ffStrbufDestroy(&data.structure); + ffStrbufDestroy(&data.structureDisabled); + yyjson_doc_free(data.configDoc); + ffStrbufDestroy(&data.genConfigPath); + return 0; + } writeConfigFile(&data); } From 9c7cfb864ff9154ffe951fae191c14d60bb91544 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 4 Aug 2026 16:28:33 +0800 Subject: [PATCH 59/67] Release: v2.67.0 --- CHANGELOG.md | 43 +++++++++++++++++++++++++++++++++++++++++++ CMakeLists.txt | 2 +- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cb47a237d..8ab97abc36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,46 @@ +# 2.67.0 + +Changes: +* The minimum supported compiler version has been raised to GCC 13 (released on 2023-04-26) and Clang 16 (released on 2023-03-17) to accommodate the new C23 syntax used throughout the codebase. (General) + * As a result, building from source in Debian 12 (which only ships GCC 12 by default) is no longer supported. Users must upgrade their compiler or use the prebuilt binaries from the GitHub releases page. +* The `general.preRun` option has been removed due to security concerns. (General) + * Users who relied on this option to run a command before printing output will need to move that logic to an external wrapper. +* The `{status}` placeholder in Battery custom format is now an array type instead of a single comma-separated string. (Battery) + * This is a breaking change for users who print `{status}` in Lua scripts. They can use `table.concat(...)` to join the array into a single string if needed. +* The `--gen-config-*` flags (`--gen-config-full`, `--gen-config-force`, `--gen-config-full-force`) have been removed and their functions have been merged into `--gen-config`. (Global) + * See features below. + +Features: +* `--gen-config` now opens an interactive configuration TUI to make generating config files easier. + * It falls back to non-interactive generation when `$NO_COLOR` is set or when stdin/stdout is not a TTY. This behavior can also be used to disable the interactive mode. +* Added an experimental [WebUI](https://fastfetch-cli.github.io/fastfetch-config/) ([separate project](https://github.com/fastfetch-cli/fastfetch-config)) for generating and editing configs. +* Added `-w`/`--watch` as a seconds-based alias for `--dynamic-interval`, defaulting to 1 second when no value is provided. (#2478, Watch) +* Added InitSystem detection on Windows, reporting the first userland process (`smss.exe`). (InitSystem, Windows) +* Added CPU and GPU temperature detection support for Apple M5 series chips. (CPU / GPU, macOS) +* Reported `Basic` as the Windows theme when DWM is disabled. (Theme, Windows) +* Improved Ghostty terminal font detection performance. (#2122, TerminalFont) + * It now parses Ghostty directly instead of running `ghostty +show-config`. +* Added support for a wider range of TOML config syntaxes used by Alacritty. (#2456, TerminalFont) + +Bugfixes: +* Fixed a potential fish version detection error. (Shell, Linux) +* Fixed incorrect resolution reporting when a monitor advertises multiple preferred modes. (#2481, Display, Linux) +* Fixed connected monitors being missed when Wayland output events arrive late. (#2451, Display, Linux) +* Fixed VP9 codec detection on macOS. (Codec, macOS) +* Corrected Base64 encoding on big-endian hosts. (#2470) +* Fixed invalid URL parsing in the PublicIP module. (PublicIP) +* Correctly reported virtual GPUs on Windows. (#2461, GPU, Windows) +* Added a size limit for network responses to prevent excessive memory usage and mitigate potential attacks. (PublicIP / Weather) +* Fixed Ubuntu Studio Core detection (OS, Linux) +* Relaxed the HTTP response check so that both `HTTP/1.0` and `HTTP/1.1` responses are accepted when fetching data over the network. (PublicIP / Weather) +* Various internal cleanups and optimizations: + * Fixed multiple memory leaks (Separator, Camera, Codec, Display) + * Added integer overflow checks to the string buffer implementation + * Various code cleanups and compiler warning fixes + +Logos: +* Removed Hypros, MagpieOS, Furreto, EmperorOS and Magix + # 2.66.0 Changes: diff --git a/CMakeLists.txt b/CMakeLists.txt index ada50309dc..4cd0df0d18 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.12.0) # target_link_libraries with OBJECT libs & project homepage url project(fastfetch - VERSION 2.66.0 + VERSION 2.67.0 LANGUAGES C DESCRIPTION "Fast neofetch-like system information tool" HOMEPAGE_URL "https://github.com/fastfetch-cli/fastfetch" From 1a42cbe1cf1f918d4beaeb83524bf38659d0c93a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Tue, 4 Aug 2026 19:18:23 +0800 Subject: [PATCH 60/67] Common (Format): format --- src/common/impl/format.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/common/impl/format.c b/src/common/impl/format.c index 978011bd1e..d70f1faa58 100644 --- a/src/common/impl/format.c +++ b/src/common/impl/format.c @@ -109,7 +109,21 @@ static inline void appendInvalidPlaceholder(FFstrbuf* buffer, const char* start, } static inline bool formatArgSet(const FFformatarg* arg) { - return arg->value != nullptr && ((arg->type == FF_ARG_TYPE_DOUBLE && *(double*) arg->value > 0.0) || (arg->type == FF_ARG_TYPE_FLOAT && *(float*) arg->value > 0.0) || (arg->type == FF_ARG_TYPE_INT && *(int32_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_STRBUF && ((FFstrbuf*) arg->value)->length > 0) || (arg->type == FF_ARG_TYPE_STRING && ffStrSet((char*) arg->value)) || (arg->type == FF_ARG_TYPE_UINT8 && *(uint8_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_UINT16 && *(uint16_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_UINT && *(uint32_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_UINT64 && *(uint64_t*) arg->value > 0) || (arg->type == FF_ARG_TYPE_BOOL && *(bool*) arg->value) || (arg->type == FF_ARG_TYPE_LIST && ((FFlist*) arg->value)->length > 0)); + // clang-format off + return arg->value != nullptr && ( + (arg->type == FF_ARG_TYPE_DOUBLE && *(double*) arg->value > 0.0) || + (arg->type == FF_ARG_TYPE_FLOAT && *(float*) arg->value > 0.0) || + (arg->type == FF_ARG_TYPE_INT && *(int32_t*) arg->value > 0) || + (arg->type == FF_ARG_TYPE_STRBUF && ((FFstrbuf*) arg->value)->length > 0) || + (arg->type == FF_ARG_TYPE_STRING && ffStrSet((char*) arg->value)) || + (arg->type == FF_ARG_TYPE_UINT8 && *(uint8_t*) arg->value > 0) || + (arg->type == FF_ARG_TYPE_UINT16 && *(uint16_t*) arg->value > 0) || + (arg->type == FF_ARG_TYPE_UINT && *(uint32_t*) arg->value > 0) || + (arg->type == FF_ARG_TYPE_UINT64 && *(uint64_t*) arg->value > 0) || + (arg->type == FF_ARG_TYPE_BOOL && *(bool*) arg->value) || + (arg->type == FF_ARG_TYPE_LIST && ((FFlist*) arg->value)->length > 0) + ); + // clang-format on } [[maybe_unused]] static inline void normalizeArgName(FFstrbuf* dst, const char* src) { From 984fadf70f7692d8378617d56d0774058797d3a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Tue, 4 Aug 2026 19:19:37 +0800 Subject: [PATCH 61/67] Revert "Global: first step to full C++ project" This reverts commit 241ddb79b0460a9aea80e08eafa4dbfbe92b4007. --- src/common/mallocHelper.h | 177 -------------------------------------- 1 file changed, 177 deletions(-) diff --git a/src/common/mallocHelper.h b/src/common/mallocHelper.h index e917fe6bf2..e0c1829481 100644 --- a/src/common/mallocHelper.h +++ b/src/common/mallocHelper.h @@ -37,180 +37,3 @@ static inline size_t ffMallocUsableSize(const void* ptr) { return 0; // Not supported #endif } - -#if __cplusplus - - #include - #include - - #if defined(_WIN32) - #include - #endif - -[[gnu::always_inline]] -static inline void* ffAlignedAlloc(size_t size, size_t alignment) noexcept { - #if defined(_WIN32) - return _aligned_malloc(size, alignment); - #else - void* ptr = nullptr; - if (posix_memalign(&ptr, alignment, size) == 0) [[likely]] { - return ptr; - } - return nullptr; - #endif -} - -[[gnu::always_inline]] -static inline void ffAlignedFree(void* ptr) noexcept { - #if defined(_WIN32) - _aligned_free(ptr); - #else - ::free(ptr); - #endif -} - -[[gnu::always_inline]] -void* operator new(size_t size) { - if (void* ptr = ::malloc(size)) [[likely]] { - return ptr; - } - std::abort(); -} - -[[gnu::always_inline]] -void operator delete(void* ptr) noexcept { - ::free(ptr); -} - -[[gnu::always_inline]] -void operator delete(void* ptr, size_t size) noexcept { - (void) size; - ::free(ptr); -} - -[[gnu::always_inline]] -void* operator new(size_t size, const std::nothrow_t&) noexcept { - return ::malloc(size); -} - -[[gnu::always_inline]] -void operator delete(void* ptr, const std::nothrow_t&) noexcept { - ::free(ptr); -} - -[[gnu::always_inline]] -void operator delete(void* ptr, size_t size, const std::nothrow_t&) noexcept { - (void) size; - ::free(ptr); -} - -[[gnu::always_inline]] -void* operator new[](size_t size) { - if (void* ptr = ::malloc(size)) [[likely]] { - return ptr; - } - std::abort(); -} - -[[gnu::always_inline]] -void operator delete[](void* ptr) noexcept { - ::free(ptr); -} - -[[gnu::always_inline]] -void operator delete[](void* ptr, size_t size) noexcept { - (void) size; - ::free(ptr); -} - -[[gnu::always_inline]] -void* operator new[](size_t size, const std::nothrow_t&) noexcept { - return ::malloc(size); -} - -[[gnu::always_inline]] -void operator delete[](void* ptr, const std::nothrow_t&) noexcept { - ::free(ptr); -} - -[[gnu::always_inline]] -void operator delete[](void* ptr, size_t size, const std::nothrow_t&) noexcept { - (void) size; - ::free(ptr); -} - - #if __cplusplus >= 201703L // std::align_val_t (over-aligned new/delete, C++17) - -[[gnu::always_inline]] -void* operator new(size_t size, std::align_val_t alignment) { - if (void* ptr = ffAlignedAlloc(size, alignment)) { - return ptr; - } - std::abort(); -} - -[[gnu::always_inline]] -void operator delete(void* ptr, std::align_val_t) noexcept { - ffAlignedFree(ptr); -} - -[[gnu::always_inline]] -void operator delete(void* ptr, size_t size, std::align_val_t) noexcept { - (void) size; - ffAlignedFree(ptr); -} - -[[gnu::always_inline]] -void* operator new(size_t size, std::align_val_t alignment, const std::nothrow_t&) noexcept { - return ffAlignedAlloc(size, static_cast(alignment)); -} - -[[gnu::always_inline]] -void operator delete(void* ptr, std::align_val_t, const std::nothrow_t&) noexcept { - ffAlignedFree(ptr); -} - -[[gnu::always_inline]] -void operator delete(void* ptr, size_t size, std::align_val_t, const std::nothrow_t&) noexcept { - (void) size; - ffAlignedFree(ptr); -} - -[[gnu::always_inline]] -void* operator new[](size_t size, std::align_val_t alignment) { - if (void* ptr = ffAlignedAlloc(size, static_cast(alignment))) { - return ptr; - } - std::abort(); -} - -[[gnu::always_inline]] -void operator delete[](void* ptr, std::align_val_t) noexcept { - ffAlignedFree(ptr); -} - -[[gnu::always_inline]] -void operator delete[](void* ptr, size_t size, std::align_val_t) noexcept { - (void) size; - ffAlignedFree(ptr); -} - -[[gnu::always_inline]] -void* operator new[](size_t size, std::align_val_t alignment, const std::nothrow_t&) noexcept { - return ffAlignedAlloc(size, static_cast(alignment)); -} - -[[gnu::always_inline]] -void operator delete[](void* ptr, std::align_val_t, const std::nothrow_t&) noexcept { - ffAlignedFree(ptr); -} - -[[gnu::always_inline]] -void operator delete[](void* ptr, size_t size, std::align_val_t, const std::nothrow_t&) noexcept { - (void) size; - ffAlignedFree(ptr); -} - - #endif // __cplusplus >= 201703L - -#endif From dc46ecbcacca29bc32f2ef017413ae0300629144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Tue, 4 Aug 2026 19:34:14 +0800 Subject: [PATCH 62/67] GenConfig: code cleanup --- src/common/impl/genconfig.c | 117 +++++++++++------------------------- 1 file changed, 34 insertions(+), 83 deletions(-) diff --git a/src/common/impl/genconfig.c b/src/common/impl/genconfig.c index f874790774..7012aaa372 100644 --- a/src/common/impl/genconfig.c +++ b/src/common/impl/genconfig.c @@ -56,7 +56,7 @@ typedef struct FFGenConfigLayout { typedef struct FFGenConfigUI { FFlist items; // FFGenConfigItem - int32_t cursor; + uint32_t cursor; uint32_t viewOffset; FFLogoType logoType; bool fullConfig; @@ -71,10 +71,6 @@ typedef enum : uint8_t { FF_GEN_KEY_DOWN, FF_GEN_KEY_LEFT, FF_GEN_KEY_RIGHT, - FF_GEN_KEY_PAGE_UP, - FF_GEN_KEY_PAGE_DOWN, - FF_GEN_KEY_HOME, - FF_GEN_KEY_END, FF_GEN_KEY_ENTER, FF_GEN_KEY_ESCAPE, FF_GEN_KEY_CHAR, @@ -133,7 +129,7 @@ static void enterRawMode(void) { if (GetConsoleMode(hInput, &gOriginalInputMode)) { DWORD newMode = gOriginalInputMode; - newMode &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT); + newMode &= ~(DWORD) (ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT); newMode |= ENABLE_VIRTUAL_TERMINAL_INPUT; SetConsoleMode(hInput, newMode); } @@ -218,7 +214,7 @@ static void moveItemDown(FFGenConfigUI* ui, uint32_t idx) { FFGenConfigItem tmp = items[idx]; items[idx] = items[idx + 1]; items[idx + 1] = tmp; - ui->cursor = (int32_t) (idx + 1); + ui->cursor = idx + 1; } static void moveItemUp(FFGenConfigUI* ui, uint32_t idx) { @@ -229,7 +225,7 @@ static void moveItemUp(FFGenConfigUI* ui, uint32_t idx) { FFGenConfigItem tmp = items[idx]; items[idx] = items[idx - 1]; items[idx - 1] = tmp; - ui->cursor = (int32_t) (idx - 1); + ui->cursor = idx - 1; } static void removeAllBreaksSeparators(FFGenConfigUI* ui) { @@ -239,8 +235,8 @@ static void removeAllBreaksSeparators(FFGenConfigUI* ui) { FF_LIST_REMOVE_AT(FFGenConfigItem, ui->items, i); } } - if ((uint32_t) ui->cursor >= ui->items.length && ui->items.length > 0) { - ui->cursor = (int32_t) ui->items.length - 1; + if (ui->cursor >= ui->items.length && ui->items.length > 0) { + ui->cursor = ui->items.length - 1; } } @@ -293,11 +289,8 @@ static void recomputeView(FFGenConfigUI* ui) { ui->viewOffset = 0; return; } - if (ui->cursor < 0) { - ui->cursor = 0; - } - if ((uint32_t) ui->cursor >= length) { - ui->cursor = (int32_t) length - 1; + if (ui->cursor >= length) { + ui->cursor = length - 1; } uint32_t rowsPerColumn = layout->itemsPerColumn; @@ -306,7 +299,7 @@ static void recomputeView(FFGenConfigUI* ui) { return; } - uint32_t cursorRow = (uint32_t) ui->cursor % rowsPerColumn; + uint32_t cursorRow = ui->cursor % rowsPerColumn; uint32_t pageTop = ui->viewOffset; if (cursorRow < pageTop) { pageTop = cursorRow; @@ -676,7 +669,7 @@ static void renderFrame(FFGenConfigUI* ui, FFstrbuf* out) { uint32_t index = (ui->viewOffset + screenRow) + c * rowsPerColumn; if (screenRow < rowsPerColumn && index < length) { const FFGenConfigItem* item = FF_LIST_GET(FFGenConfigItem, ui->items, index); - drawItemCell(&row, startCol, layout->colWidth, item, (uint32_t) ui->cursor == index); + drawItemCell(&row, startCol, layout->colWidth, item, ui->cursor == index); } else { rowPadVisual(&row, startCol + layout->colWidth); } @@ -686,8 +679,8 @@ static void renderFrame(FFGenConfigUI* ui, FFstrbuf* out) { // Row after grid: description rowInit(&row, cols); - if (ui->cursor >= 0 && (uint32_t) ui->cursor < ui->items.length) { - const FFGenConfigItem* item = FF_LIST_GET(FFGenConfigItem, ui->items, (uint32_t) ui->cursor); + if (ui->cursor < ui->items.length) { + const FFGenConfigItem* item = FF_LIST_GET(FFGenConfigItem, ui->items, ui->cursor); if (item->baseInfo && item->baseInfo->description) { rowAppendVisual(&row, " "); rowAppendRaw(&row, "\e[90m"); @@ -803,35 +796,12 @@ static FFGenConfigKey readKey(char* outChar) { return FF_GEN_KEY_RIGHT; case 'D': return FF_GEN_KEY_LEFT; - case 'H': - return FF_GEN_KEY_HOME; - case 'F': - return FF_GEN_KEY_END; - case '~': - switch (param) { - case 5: - return FF_GEN_KEY_PAGE_UP; - case 6: - return FF_GEN_KEY_PAGE_DOWN; - case 7: - return FF_GEN_KEY_HOME; - case 8: - return FF_GEN_KEY_END; - } } } return FF_GEN_KEY_UNKNOWN; } if (buf[1] == 'O') { - if (n >= 3) { - switch (buf[2]) { - case 'H': - return FF_GEN_KEY_HOME; - case 'F': - return FF_GEN_KEY_END; - } - } return FF_GEN_KEY_UNKNOWN; } @@ -843,7 +813,7 @@ static void moveCursorLeft(FFGenConfigUI* ui) { if (rowsPerColumn == 0) { return; } - if ((uint32_t) ui->cursor >= rowsPerColumn) { + if (ui->cursor >= rowsPerColumn) { ui->cursor -= rowsPerColumn; } } @@ -853,15 +823,17 @@ static void moveCursorRight(FFGenConfigUI* ui) { if (rowsPerColumn == 0) { return; } - uint32_t newCursor = (uint32_t) ui->cursor + rowsPerColumn; + uint32_t newCursor = ui->cursor + rowsPerColumn; if (newCursor < ui->items.length) { - ui->cursor = (int32_t) newCursor; + ui->cursor = newCursor; } } static void moveCursorUp(FFGenConfigUI* ui) { if (ui->cursor > 0) { --ui->cursor; + } else { + ui->cursor = ui->items.length > 0 ? ui->items.length - 1 : 0; } } @@ -869,7 +841,7 @@ static void moveCursorDown(FFGenConfigUI* ui) { if (ui->items.length == 0) { return; } - if ((uint32_t) ui->cursor + 1 < ui->items.length) { + if (ui->cursor + 1 < ui->items.length) { ++ui->cursor; } else { ui->cursor = 0; @@ -905,27 +877,6 @@ static int handleKey(FFGenConfigUI* ui, FFGenConfigKey key, char ch, bool fileEx case FF_GEN_KEY_RIGHT: moveCursorRight(ui); break; - case FF_GEN_KEY_PAGE_UP: - ui->cursor -= (int32_t) ui->layout.listRows; - if (ui->cursor < 0) { - ui->cursor = 0; - } - break; - case FF_GEN_KEY_PAGE_DOWN: - if (ui->items.length == 0) { - break; - } - ui->cursor += (int32_t) ui->layout.listRows; - if ((uint32_t) ui->cursor >= ui->items.length) { - ui->cursor = (int32_t) ui->items.length - 1; - } - break; - case FF_GEN_KEY_HOME: - ui->cursor = 0; - break; - case FF_GEN_KEY_END: - ui->cursor = ui->items.length > 0 ? (int32_t) ui->items.length - 1 : 0; - break; case FF_GEN_KEY_ENTER: if (fileExists) { ui->confirmingOverwrite = true; @@ -945,8 +896,8 @@ static int handleKey(FFGenConfigUI* ui, FFGenConfigKey key, char ch, bool fileEx return 1; } } else if (ch == ' ') { - if (ui->cursor >= 0 && (uint32_t) ui->cursor < ui->items.length) { - toggleItem(ui, (uint32_t) ui->cursor); + if (ui->cursor < ui->items.length) { + toggleItem(ui, ui->cursor); } } else if (ch == 'f') { selectAllModules(ui); @@ -959,28 +910,28 @@ static int handleKey(FFGenConfigUI* ui, FFGenConfigKey key, char ch, bool fileEx } else if (ch == 'k') { moveCursorUp(ui); } else if (ch == 'J') { - if (ui->cursor >= 0 && (uint32_t) ui->cursor < ui->items.length) { - moveItemDown(ui, (uint32_t) ui->cursor); + if (ui->cursor < ui->items.length) { + moveItemDown(ui, ui->cursor); } } else if (ch == 'K') { - if (ui->cursor >= 0 && (uint32_t) ui->cursor < ui->items.length) { - moveItemUp(ui, (uint32_t) ui->cursor); + if (ui->cursor < ui->items.length) { + moveItemUp(ui, ui->cursor); } } else if (ch == 'b') { - if (ui->cursor >= 0 && (uint32_t) ui->cursor < ui->items.length) { - addBreakBelow(ui, (uint32_t) ui->cursor); + if (ui->cursor < ui->items.length) { + addBreakBelow(ui, ui->cursor); } } else if (ch == 'B') { - if (ui->cursor >= 0 && (uint32_t) ui->cursor < ui->items.length) { - addSeparatorBelow(ui, (uint32_t) ui->cursor); + if (ui->cursor < ui->items.length) { + addSeparatorBelow(ui, ui->cursor); } } else if (ch == 'd') { - if (ui->cursor >= 0 && (uint32_t) ui->cursor < ui->items.length) { - FFGenConfigItem* item = FF_LIST_GET(FFGenConfigItem, ui->items, (uint32_t) ui->cursor); + if (ui->cursor < ui->items.length) { + FFGenConfigItem* item = FF_LIST_GET(FFGenConfigItem, ui->items, ui->cursor); if (item->status == FF_GEN_CONFIG_ITEM_STATUS_SPECIAL) { - FF_LIST_REMOVE_AT(FFGenConfigItem, ui->items, (uint32_t) ui->cursor); - if ((uint32_t) ui->cursor >= ui->items.length && ui->items.length > 0) { - ui->cursor = (int32_t) ui->items.length - 1; + FF_LIST_REMOVE_AT(FFGenConfigItem, ui->items, ui->cursor); + if (ui->cursor >= ui->items.length && ui->items.length > 0) { + ui->cursor = ui->items.length - 1; } } } @@ -989,7 +940,7 @@ static int handleKey(FFGenConfigUI* ui, FFGenConfigKey key, char ch, bool fileEx } else if (ch == 'g') { ui->cursor = 0; } else if (ch == 'G') { - ui->cursor = ui->items.length > 0 ? (int32_t) ui->items.length - 1 : 0; + ui->cursor = ui->items.length > 0 ? ui->items.length - 1 : 0; } else if (ch == 'l') { cycleLogoType(ui, 1); } else if (ch == '\x03' || ch == '\x04' || ch == '\x1a' || ch == '\x1c') { From 97a9be27b9d0d44514e6b32b2bc8f0d3c872327c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Tue, 4 Aug 2026 20:17:05 +0800 Subject: [PATCH 63/67] GenConfig: fixes arrow keys not working in Haiku --- src/common/impl/genconfig.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/common/impl/genconfig.c b/src/common/impl/genconfig.c index 7012aaa372..d790ba74bb 100644 --- a/src/common/impl/genconfig.c +++ b/src/common/impl/genconfig.c @@ -761,7 +761,7 @@ static FFGenConfigKey readKey(char* outChar) { return FF_GEN_KEY_ESCAPE; } - if (buf[1] == '[') { + if (buf[1] == '[' || buf[1] == 'O') { size_t i = 2; if (i < (size_t) n && buf[i] >= 'A' && buf[i] <= 'D') { switch (buf[i]) { @@ -801,10 +801,6 @@ static FFGenConfigKey readKey(char* outChar) { return FF_GEN_KEY_UNKNOWN; } - if (buf[1] == 'O') { - return FF_GEN_KEY_UNKNOWN; - } - return FF_GEN_KEY_ESCAPE; } From 3f8d1413137413b88603c75ffe359b3055c10185 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Tue, 4 Aug 2026 21:05:07 +0800 Subject: [PATCH 64/67] GenConfig: disables the feature on Win 8.1; improves redraw logic --- src/common/impl/genconfig.c | 66 ++++++++++++++++++++++++++++--------- src/fastfetch.c | 7 +++- 2 files changed, 56 insertions(+), 17 deletions(-) diff --git a/src/common/impl/genconfig.c b/src/common/impl/genconfig.c index d790ba74bb..5f0e344fc1 100644 --- a/src/common/impl/genconfig.c +++ b/src/common/impl/genconfig.c @@ -15,6 +15,7 @@ #ifndef _WIN32 #include #include + #include #include #else #include @@ -35,6 +36,11 @@ #define FF_GEN_CONFIG_LIST_TOP 7 #define FF_GEN_CONFIG_BOTTOM_CHROME 3 +// Poll wait timeout (ms) while idle. A longer wait avoids frequent full-screen +// redraws (flicker); terminal size changes redraw immediately via SIGWINCH / +// console window-size events. +#define FF_GEN_CONFIG_POLL_TIMEOUT 1000 + typedef enum : uint8_t { FF_GEN_CONFIG_ITEM_STATUS_UNSELECTED = false, FF_GEN_CONFIG_ITEM_STATUS_SELECTED = true, @@ -74,12 +80,19 @@ typedef enum : uint8_t { FF_GEN_KEY_ENTER, FF_GEN_KEY_ESCAPE, FF_GEN_KEY_CHAR, + FF_GEN_KEY_RESIZE, FF_GEN_KEY_UNKNOWN, } FFGenConfigKey; #ifndef _WIN32 static struct termios gOriginalTermios; static bool gRawModeActive = false; +static volatile sig_atomic_t gWindowResized = 0; + +static void onWindowResize(int sig) { + (void) sig; + gWindowResized = 1; +} static void restoreRawMode(void) { if (gRawModeActive) { @@ -106,6 +119,10 @@ static void enterRawMode(void) { return; } gRawModeActive = true; + + struct sigaction sa = { .sa_handler = onWindowResize }; + sigemptyset(&sa.sa_mask); + sigaction(SIGWINCH, &sa, nullptr); } #else static DWORD gOriginalInputMode; @@ -130,7 +147,7 @@ static void enterRawMode(void) { if (GetConsoleMode(hInput, &gOriginalInputMode)) { DWORD newMode = gOriginalInputMode; newMode &= ~(DWORD) (ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT); - newMode |= ENABLE_VIRTUAL_TERMINAL_INPUT; + newMode |= ENABLE_VIRTUAL_TERMINAL_INPUT | ENABLE_WINDOW_INPUT; SetConsoleMode(hInput, newMode); } @@ -715,33 +732,47 @@ static void renderFrame(FFGenConfigUI* ui, FFstrbuf* out) { static FFGenConfigKey readKey(char* outChar) { #ifndef _WIN32 + if (gWindowResized) { + gWindowResized = 0; + return FF_GEN_KEY_RESIZE; + } struct pollfd pfd = { .fd = STDIN_FILENO, .events = POLLIN, }; - if (poll(&pfd, 1, 100) <= 0) { + if (poll(&pfd, 1, FF_GEN_CONFIG_POLL_TIMEOUT) <= 0) { + if (gWindowResized) { + gWindowResized = 0; + return FF_GEN_KEY_RESIZE; + } return FF_GEN_KEY_UNKNOWN; } #else HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE); - DWORD waitResult = WaitForSingleObject(hInput, 100); - if (waitResult != WAIT_OBJECT_0) { + if (WaitForSingleObject(hInput, FF_GEN_CONFIG_POLL_TIMEOUT) != WAIT_OBJECT_0) { return FF_GEN_KEY_UNKNOWN; } - DWORD numEvents; - while (GetNumberOfConsoleInputEvents(hInput, &numEvents) && numEvents > 0) { + DWORD numEvents = 0; + if (GetNumberOfConsoleInputEvents(hInput, &numEvents) && numEvents > 0) { + bool consumed = false; INPUT_RECORD record; - DWORD read; - if (!PeekConsoleInput(hInput, &record, 1, &read) || read == 0) { - break; + while (numEvents > 0) { + DWORD n = 0; + if (!PeekConsoleInputW(hInput, &record, 1, &n) || n == 0) { + break; + } + if (record.EventType == KEY_EVENT) { + break; // keep key events for ffReadFDData + } + if (!ReadConsoleInputW(hInput, &record, 1, &n) || n == 0) { + break; + } + consumed = true; + --numEvents; } - if (record.EventType == KEY_EVENT && record.Event.KeyEvent.bKeyDown) { - break; + if (consumed) { + return FF_GEN_KEY_RESIZE; } - ReadConsoleInput(hInput, &record, 1, &read); - } - if (!GetNumberOfConsoleInputEvents(hInput, &numEvents) || numEvents == 0) { - return FF_GEN_KEY_UNKNOWN; } #endif @@ -849,6 +880,9 @@ static int handleKey(FFGenConfigUI* ui, FFGenConfigKey key, char ch, bool fileEx if (key == FF_GEN_KEY_UNKNOWN) { return -1; } + if (key == FF_GEN_KEY_RESIZE) { + return -1; // Terminal resized: redraw with new size + } if (ui->confirmingOverwrite) { if (key == FF_GEN_KEY_CHAR && (ch == 'y' || ch == 'Y')) { @@ -943,7 +977,7 @@ static int handleKey(FFGenConfigUI* ui, FFGenConfigKey key, char ch, bool fileEx return 0; } break; - case FF_GEN_KEY_UNKNOWN: + default: break; } return -1; diff --git a/src/fastfetch.c b/src/fastfetch.c index af8375d809..be1528988c 100644 --- a/src/fastfetch.c +++ b/src/fastfetch.c @@ -20,6 +20,7 @@ #include #else #include + #include "common/windows/nt.h" #endif [[gnu::cold]] @@ -599,7 +600,11 @@ static void enableJsonOutput(FFdata* data) { } static void genConfigCommon(FFdata* data, const char* value) { - if (!getenv("NO_COLOR") && isatty(STDOUT_FILENO) && isatty(STDIN_FILENO)) { + if (!getenv("NO_COLOR") && isatty(STDOUT_FILENO) && isatty(STDIN_FILENO) + #ifdef _WIN32 + && ffIsWindows10OrGreater() + #endif + ) { data->genConfigInteractive = true; setupGenConfigPath(data, value); } else { From fcf4aca3b76c5160335a4f35add04c4141cbb3ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Tue, 4 Aug 2026 21:14:48 +0800 Subject: [PATCH 65/67] Shell: fixes a potencial out-of-bound read --- src/detection/terminalshell/terminalshell.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/detection/terminalshell/terminalshell.c b/src/detection/terminalshell/terminalshell.c index 2d531f9712..fa323aa9eb 100644 --- a/src/detection/terminalshell/terminalshell.c +++ b/src/detection/terminalshell/terminalshell.c @@ -90,11 +90,11 @@ static bool getShellVersionFish(FFstrbuf* exe, FFstrbuf* version) { return false; } uint32_t index = ffStrbufFirstIndexC(version, ' '); // skip "fish," - while (!ffCharIsDigit(version->chars[index + 1])) { + while (index + 1 < version->length && !ffCharIsDigit(version->chars[index + 1])) { index = ffStrbufNextIndexC(version, index + 1, ' '); // skip "version" - if (index == version->length) { - return false; - } + } + if (index + 1 >= version->length) { + return false; } ffStrbufSubstrAfter(version, index); From a88ef86ce39d31cae704e9c6f50b7f0bb3585b14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Tue, 4 Aug 2026 21:15:34 +0800 Subject: [PATCH 66/67] InitSystem (Windows): fixes logic of assertions --- src/detection/initsystem/initsystem_windows.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/detection/initsystem/initsystem_windows.c b/src/detection/initsystem/initsystem_windows.c index 4b4e647728..f41a66cfa2 100644 --- a/src/detection/initsystem/initsystem_windows.c +++ b/src/detection/initsystem/initsystem_windows.c @@ -17,7 +17,7 @@ const char* ffDetectInitSystem(FFInitSystemResult* result) { } for (SYSTEM_PROCESS_INFORMATION* ptr = buffer; ; ptr = (SYSTEM_PROCESS_INFORMATION*) ((uint8_t*) ptr + ptr->NextEntryOffset)) { - assert(ptr >= buffer && (uint8_t*) ptr < (uint8_t*) buffer + size); + assert(ptr >= buffer && (uint8_t*) ptr < (uint8_t*) buffer + sizeof(buffer)); uint16_t len = ptr->ImageName.Length / sizeof(*ptr->ImageName.Buffer); if (ptr->InheritedFromUniqueProcessId == (HANDLE)(uintptr_t) 4 /* System */ && len > 4 && _wcsnicmp(ptr->ImageName.Buffer + len - 4, L".exe", 4) == 0) { // smss.exe From e7642ea3a2d2857a0734e59af6d94f3fd040e8a7 Mon Sep 17 00:00:00 2001 From: Carter Li Date: Tue, 4 Aug 2026 23:34:15 +0800 Subject: [PATCH 67/67] GenConfig: honors `-s` set by users --- src/common/impl/commandoption.c | 5 ----- src/common/impl/genconfig.c | 28 +++++++++------------------- src/fastfetch.c | 4 ++++ 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/src/common/impl/commandoption.c b/src/common/impl/commandoption.c index f0b5f06672..c4225a711b 100644 --- a/src/common/impl/commandoption.c +++ b/src/common/impl/commandoption.c @@ -244,11 +244,6 @@ void ffPrintCommandOption(FFdata* data) { } void ffMigrateCommandOptionToJsonc(FFdata* data) { - // If we don't have a custom structure, use the default one - if (data->structure.length == 0) { - ffStrbufAppendS(&data->structure, FASTFETCH_DATATEXT_STRUCTURE); // Cannot use `ffStrbufSetStatic` here because we will modify the string - } - char* moduleType = nullptr; size_t moduleLen = 0; while (ffStrbufGetdelim(&moduleType, &moduleLen, ':', &data->structure)) { diff --git a/src/common/impl/genconfig.c b/src/common/impl/genconfig.c index 5f0e344fc1..e60d8b5f1e 100644 --- a/src/common/impl/genconfig.c +++ b/src/common/impl/genconfig.c @@ -5,7 +5,6 @@ #include "common/io.h" #include "common/strutil.h" #include "detection/terminalsize/terminalsize.h" -#include "fastfetch_datatext.h" #include "modules/modules.h" #include @@ -195,28 +194,16 @@ static void collectModuleInfos(FFlist* modules) { ffListSort(modules, sizeof(FFModuleBaseInfo*), compareModuleInfo); } -static bool isInDefaultStructure(const char* moduleName) { - char* moduleType = nullptr; - size_t moduleLen = 0; - FF_STRBUF_AUTO_DESTROY structure = ffStrbufCreateS(FASTFETCH_DATATEXT_STRUCTURE); - while (ffStrbufGetdelim(&moduleType, &moduleLen, ':', &structure)) { - if (moduleLen == strlen(moduleName) && ffStrEqualsIgnCase(moduleType, moduleName)) { - return true; - } - } - return false; -} - -static void initItems(FFlist* items, const FFlist* modules) { +static void initItems(FFlist* items, const FFlist* modules, FFdata* data) { ffListInitA(items, sizeof(FFGenConfigItem), modules->length + 8); FF_LIST_FOR_EACH (FFModuleBaseInfo*, info, *modules) { FFGenConfigItem* item = FF_LIST_ADD(FFGenConfigItem, *items); item->baseInfo = *info; - if (ffStrEqualsIgnCase((*info)->name, FF_BREAK_MODULE_NAME) || - ffStrEqualsIgnCase((*info)->name, FF_SEPARATOR_MODULE_NAME)) { + if (*info == &ffBreakModuleInfo || *info == &ffSeparatorModuleInfo) { item->status = FF_GEN_CONFIG_ITEM_STATUS_SPECIAL; } else { - item->status = isInDefaultStructure((*info)->name) + item->status = ffStrbufSeparatedContainIgnCaseS(&data->structure, (*info)->name, ':') && + !ffStrbufSeparatedContainIgnCaseS(&data->structureDisabled, (*info)->name, ':') ? FF_GEN_CONFIG_ITEM_STATUS_SELECTED : FF_GEN_CONFIG_ITEM_STATUS_UNSELECTED; } @@ -721,7 +708,7 @@ static void renderFrame(FFGenConfigUI* ui, FFstrbuf* out) { rowInit(&row, cols); rowAppendRaw(&row, "\e[90m"); - rowAppendVisual(&row, " l logo o minimal/full s/Enter save q/Esc quit g/G top/bottom"); + rowAppendVisual(&row, " l/L logo o minimal/full s/Enter save q/Esc quit g/G top/bottom"); rowAppendRaw(&row, "\e[m"); finishRow(&row, out, ++rowCount == ui->rows); @@ -973,6 +960,8 @@ static int handleKey(FFGenConfigUI* ui, FFGenConfigKey key, char ch, bool fileEx ui->cursor = ui->items.length > 0 ? ui->items.length - 1 : 0; } else if (ch == 'l') { cycleLogoType(ui, 1); + } else if (ch == 'L') { + cycleLogoType(ui, -1); } else if (ch == '\x03' || ch == '\x04' || ch == '\x1a' || ch == '\x1c') { return 0; } @@ -1032,6 +1021,7 @@ static bool applyConfig(FFdata* data, const FFlist* items, bool fullConfig) { return true; } +[[gnu::cold]] bool ffGenConfigInteractive(FFdata* data) { FF_LIST_AUTO_DESTROY modules; collectModuleInfos(&modules); @@ -1046,7 +1036,7 @@ bool ffGenConfigInteractive(FFdata* data) { .rows = 24, .cols = 80, }; - initItems(&ui.items, &modules); + initItems(&ui.items, &modules, data); getTerminalSize(&ui.rows, &ui.cols); enterRawMode(); diff --git a/src/fastfetch.c b/src/fastfetch.c index be1528988c..b6c2ea2018 100644 --- a/src/fastfetch.c +++ b/src/fastfetch.c @@ -905,6 +905,10 @@ int main(int argc, char** argv) { if (__builtin_expect(data.genConfigPath.length == 0, true)) { run(&data); } else { + // If we don't have a custom structure, use the default one + if (data.structure.length == 0) { + ffStrbufSetS(&data.structure, FASTFETCH_DATATEXT_STRUCTURE); // Cannot use `ffStrbufSetStatic` here because we will modify the string + } if (data.genConfigInteractive && !ffGenConfigInteractive(&data)) { // User cancelled the interactive config generation ffStrbufDestroy(&data.structure);