diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e24560b4e..dfbe7166a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -446,7 +446,6 @@ jobs: usesh: true prepare: | uname -a - pkg install pkg:/package/pkg pkg update --accept pkg install gcc14 cmake git pkg-config glib2 dbus sqlite-3 imagemagick diff --git a/CHANGELOG.md b/CHANGELOG.md index 54e319538..323b08afc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,53 @@ +# 2.56.0 + +Features: +* Enhances config file loading. `--config` and `-c` with relative path now also searches paths defined in `fastfetch --list-config-paths` (typically `~/.config/fastfetch/`) + * This allows users to use `fastfetch -c my-config` without needing to specify the full path. +* Adds NUMA node count detection (CPU) + * Exposed via `{numa-nodes}` in custom format + * Supported on Linux, FreeBSD and Windows +* Supports the newest Alacritty config format (#2070, TerminalFont) +* Detects driver specific info for Zhaoxin GPUs (GPU, Linux) +* Detects Android OEM UI for certain OSes (DE, Android) +* Improves users detection on Linux (#2064, Users, Linux) + * Adds systemd fallback when utmp is unavailable + * Fixes resource leaks + * Always reports the newest session info +* Adds kiss package manager support (#2072, Packages, Linux) +* Reports `sshd` if `$SSH_TTY` is not available (Terminal) +* Zpool module rewrite (#2051, Zpool) + * Adds new Zpool properties: allocated, guid, readOnly + * Zpool module now uses runtime lookup for properties to ensure portability + * Adds NetBSD (requires `sudo`) and macOS support +* Adds `splitLines` option for Command module, which splits the output into sub modules, each containing one line of the output (Command) +``` +* Command output: +Line 1 +Line 2 +Line 3 + +* Old behavior: +Command: Line 1 +Line 2 +Line 3 + +* With `"splitLines": true`: +Command 1: Line 1 +Command 2: Line 2 +Command 3: Line 3 +``` + +Bugfixes: +* Fixes {m,o}ksh version detection on Linux (Shell) +* Fixes Alacritty config parsing for TOML format (#2070, TerminalFont) +* Improves builtin logo printing for piping and buffering (#2065, Logo) +* Uses absolute path when detecting shell and terminal version if available (#2067, TerminalShell) + +Logos: +* Updates Codex Linux logo (#2071) +* Adds OS/2 Warp logo (#2062) +* Adds Amiga logo (#2061) + # 2.55.1 Bugfixes: diff --git a/CMakeLists.txt b/CMakeLists.txt index 49f2d6553..ef7452780 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.55.1 + VERSION 2.56.0 LANGUAGES C DESCRIPTION "Fast neofetch-like system information tool" HOMEPAGE_URL "https://github.com/fastfetch-cli/fastfetch" @@ -92,7 +92,6 @@ cmake_dependent_option(ENABLE_DDCUTIL "Enable ddcutil" ON "LINUX" OFF) cmake_dependent_option(ENABLE_DIRECTX_HEADERS "Enable DirectX headers for WSL" ON "LINUX" OFF) cmake_dependent_option(ENABLE_ELF "Enable libelf" ON "LINUX OR ANDROID OR DragonFly OR Haiku OR GNU" OFF) cmake_dependent_option(ENABLE_THREADS "Enable multithreading" ON "Threads_FOUND" OFF) -cmake_dependent_option(ENABLE_LIBZFS "Enable libzfs" ON "LINUX OR FreeBSD OR SunOS" OFF) cmake_dependent_option(ENABLE_PCIACCESS "Enable libpciaccess" ON "GNU" OFF) option(ENABLE_ZLIB "Enable zlib" ON) @@ -107,6 +106,7 @@ option(INSTALL_LICENSE "Install license into /usr/share/licenses" ON) option(ENABLE_EMBEDDED_PCIIDS "Embed pci.ids into fastfetch, requires `python`" OFF) option(ENABLE_EMBEDDED_AMDGPUIDS "Embed amdgpu.ids into fastfetch, requires `python`" OFF) option(ENABLE_WORDEXP "Enable using of wordexp(3) if available, instead of glob(3)" ON) +option(ENABLE_LIBZFS "Enable libzfs" ON) set(BINARY_LINK_TYPE_OPTIONS dlopen dynamic static) set(BINARY_LINK_TYPE dlopen CACHE STRING "How to link fastfetch") @@ -410,6 +410,7 @@ set(LIBFASTFETCH_SRC src/detection/version/version.c src/detection/vulkan/vulkan.c src/detection/weather/weather.c + src/detection/zpool/zpool.c src/logo/builtin.c src/logo/image/im6.c src/logo/image/im7.c @@ -582,7 +583,6 @@ if(LINUX) src/detection/de/de_linux.c src/detection/wmtheme/wmtheme_linux.c src/detection/camera/camera_linux.c - src/detection/zpool/zpool_linux.c src/util/platform/FFPlatform_unix.c src/util/binary_linux.c ) @@ -656,7 +656,6 @@ elseif(ANDROID) src/detection/de/de_linux.c src/detection/wmtheme/wmtheme_linux.c src/detection/camera/camera_android.c - src/detection/zpool/zpool_nosupport.c src/util/platform/FFPlatform_unix.c src/util/binary_linux.c ) @@ -739,7 +738,6 @@ elseif(FreeBSD) src/detection/de/de_linux.c src/detection/wmtheme/wmtheme_linux.c src/detection/camera/camera_linux.c - src/detection/zpool/zpool_linux.c src/util/platform/FFPlatform_unix.c src/util/binary_linux.c ) @@ -833,7 +831,6 @@ elseif(NetBSD) src/detection/de/de_linux.c src/detection/wmtheme/wmtheme_linux.c src/detection/camera/camera_linux.c - src/detection/zpool/zpool_linux.c src/util/platform/FFPlatform_unix.c src/util/binary_linux.c ) @@ -916,7 +913,6 @@ elseif(OpenBSD) src/detection/de/de_linux.c src/detection/wmtheme/wmtheme_linux.c src/detection/camera/camera_linux.c - src/detection/zpool/zpool_nosupport.c src/util/platform/FFPlatform_unix.c src/util/binary_linux.c src/util/smbiosHelper.c @@ -986,7 +982,6 @@ elseif(APPLE) src/detection/de/de_nosupport.c src/detection/wmtheme/wmtheme_apple.m src/detection/camera/camera_apple.m - src/detection/zpool/zpool_nosupport.c src/util/apple/cf_helpers.c src/util/apple/osascript.m src/util/apple/smc_temps.c @@ -1056,7 +1051,6 @@ elseif(WIN32) src/detection/de/de_nosupport.c src/detection/wmtheme/wmtheme_windows.c src/detection/camera/camera_windows.cpp - src/detection/zpool/zpool_nosupport.c src/util/windows/getline.c src/util/windows/com.cpp src/util/windows/registry.c @@ -1145,7 +1139,6 @@ elseif(SunOS) src/detection/de/de_linux.c src/detection/wmtheme/wmtheme_linux.c src/detection/camera/camera_nosupport.c - src/detection/zpool/zpool_linux.c src/util/platform/FFPlatform_unix.c src/util/binary_linux.c ) @@ -1214,7 +1207,6 @@ elseif(Haiku) src/detection/de/de_nosupport.c src/detection/wmtheme/wmtheme_nosupport.c src/detection/camera/camera_nosupport.c - src/detection/zpool/zpool_nosupport.c src/util/platform/FFPlatform_unix.c src/util/binary_linux.c src/util/haiku/version.cpp @@ -1294,7 +1286,6 @@ elseif(GNU) src/detection/de/de_linux.c src/detection/wmtheme/wmtheme_linux.c src/detection/camera/camera_nosupport.c - src/detection/zpool/zpool_nosupport.c src/util/platform/FFPlatform_unix.c src/util/binary_linux.c ) @@ -1611,24 +1602,6 @@ ff_lib_enable(PCIACCESS "pciaccess" "pciaccess" ) -# The system is only usable on SunOS. We provide our local copy of it so it's always available. -if(ENABLE_LIBZFS) - if(BINARY_LINK_TYPE STREQUAL "dlopen") - target_compile_definitions(libfastfetch PRIVATE FF_HAVE_LIBZFS=1) - else() - set(CMAKE_REQUIRED_LIBRARIES_BACKUP ${CMAKE_REQUIRED_LIBRARIES}) - set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} zfs) - check_function_exists("libzfs_init" LIBZFS_FOUND) - set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES_BACKUP}) - if(NOT LIBZFS_FOUND) - message(STATUS "Library: missing: LIBZFS") - else() - message(STATUS "Library: found LIBZFS") - target_compile_definitions(libfastfetch PRIVATE FF_HAVE_LIBZFS=1) - target_link_libraries(libfastfetch PRIVATE zfs) - endif() - endif() -endif() if(ENABLE_THREADS) target_compile_definitions(libfastfetch PRIVATE FF_HAVE_THREADS=1) @@ -1643,6 +1616,15 @@ endif() if(ENABLE_EMBEDDED_AMDGPUIDS) target_compile_definitions(libfastfetch PRIVATE FF_HAVE_EMBEDDED_AMDGPUIDS=1) endif() +if(ENABLE_LIBZFS) + target_compile_definitions(libfastfetch PRIVATE FF_HAVE_LIBZFS=1) + + if(NOT BINARY_LINK_TYPE STREQUAL "dlopen") + target_link_libraries(libfastfetch + PRIVATE "zfs" + ) + endif() +endif() if(LINUX) target_link_libraries(libfastfetch @@ -1733,8 +1715,6 @@ elseif(SunOS) PRIVATE "socket" PRIVATE "kstat" PRIVATE "proc" - PRIVATE "zfs" - PRIVATE "nvpair" PRIVATE "devinfo" ) elseif(GNU) diff --git a/README.md b/README.md index 183bb1792..a087b5e0d 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,8 @@ Some distributions package outdated versions of fastfetch. Older versions receiv Packaging status -* Debian: `apt install fastfetch` (for Debian 13 or newer) +* Ubuntu: [`ppa:zhangsongcui3371/fastfetch`](https://launchpad.net/~zhangsongcui3371/+archive/ubuntu/fastfetch) (Ubuntu 22.04 or newer; latest version) +* Debian / Ubuntu: `apt install fastfetch` (Debian 13 or newer; Ubuntu 25.04 or newer) * Debian / Ubuntu: Download `fastfetch-linux-.deb` from [Github release page](https://github.com/fastfetch-cli/fastfetch/releases/latest) and double-click it (for Ubuntu 20.04 or newer and Debian 11 or newer). * Arch Linux: `pacman -S fastfetch` * Fedora: `dnf install fastfetch` diff --git a/debian/changelog.tpl b/debian/changelog.tpl index 8e90bf2f7..a2cc347fd 100644 --- a/debian/changelog.tpl +++ b/debian/changelog.tpl @@ -1,3 +1,9 @@ +fastfetch (2.55.1~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium + + * Update to 2.55.1 + + -- Carter Li Mon, 17 Nov 2025 10:15:44 +0800 + fastfetch (2.55.0~#UBUNTU_CODENAME#) #UBUNTU_CODENAME#; urgency=medium * Update to 2.55.0 diff --git a/doc/json_schema.json b/doc/json_schema.json index 7e64a7cf5..0bdf56bab 100644 --- a/doc/json_schema.json +++ b/doc/json_schema.json @@ -2111,6 +2111,11 @@ "type": "boolean", "default": true }, + "splitLines": { + "description": "Set if the command output should be split into multiple lines", + "type": "boolean", + "default": false + }, "key": { "$ref": "#/$defs/key" }, diff --git a/src/common/font.c b/src/common/font.c index 13264f577..308e094c4 100644 --- a/src/common/font.c +++ b/src/common/font.c @@ -1,4 +1,6 @@ #include "fastfetch.h" +#include "util/FFlist.h" +#include "util/FFstrbuf.h" #include "common/font.h" #include @@ -192,6 +194,21 @@ void ffFontInitValues(FFfont* font, const char* name, const char* size) fontInitPretty(font); } +void ffFontInitMoveValues(FFfont* font, FFstrbuf* name, FFstrbuf* size, FFstrbuf* style) +{ + ffFontInit(font); + + if (name) ffStrbufInitMove(&font->name, name); + if (size) ffStrbufInitMove(&font->size, size); + if (style) + { + FFstrbuf* styleBuf = FF_LIST_ADD(FFstrbuf, font->styles); + ffStrbufInitMove(styleBuf, style); + } + + fontInitPretty(font); +} + void ffFontInitWithSpace(FFfont* font, const char* rawName) { const char* pspace = strrchr(rawName, ' '); diff --git a/src/common/font.h b/src/common/font.h index 4ba4cfbbd..2b94f463a 100644 --- a/src/common/font.h +++ b/src/common/font.h @@ -15,6 +15,7 @@ void ffFontInit(FFfont* font); void ffFontInitQt(FFfont* font, const char* data); void ffFontInitPango(FFfont* font, const char* data); void ffFontInitValues(FFfont* font, const char* name, const char* size); +void ffFontInitMoveValues(FFfont* font, FFstrbuf* name, FFstrbuf* size, FFstrbuf* style); void ffFontInitWithSpace(FFfont* font, const char* rawName); void ffFontDestroy(FFfont* font); diff --git a/src/common/init.c b/src/common/init.c index fe7544245..0c0927d7d 100644 --- a/src/common/init.c +++ b/src/common/init.c @@ -49,7 +49,7 @@ static void defaultConfig(void) void ffInitInstance(void) { - #ifdef WIN32 + #ifdef _WIN32 // https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/setlocale-wsetlocale?source=recommendat> setlocale(LC_ALL, ".UTF8"); #else diff --git a/src/common/io/io.h b/src/common/io/io.h index fc4f4b48b..8787f9858 100644 --- a/src/common/io/io.h +++ b/src/common/io/io.h @@ -171,19 +171,27 @@ static inline void ffUnsuppressIO(bool* suppressed) void ffListFilesRecursively(const char* path, bool pretty); +static inline bool ffIsValidNativeFD(FFNativeFD fd) +{ + #ifndef _WIN32 + return fd >= 0; + #else + // https://devblogs.microsoft.com/oldnewthing/20040302-00/?p=40443 + return fd != INVALID_HANDLE_VALUE && fd != NULL; + #endif +} + FF_C_NONNULL(1) static inline bool wrapClose(FFNativeFD* pfd) { assert(pfd); - #ifndef WIN32 - if (*pfd < 0) - return false; + if (!ffIsValidNativeFD(*pfd)) + return false; + + #ifndef _WIN32 close(*pfd); #else - // https://devblogs.microsoft.com/oldnewthing/20040302-00/?p=40443 - if (*pfd == NULL || *pfd == INVALID_HANDLE_VALUE) - return false; CloseHandle(*pfd); #endif diff --git a/src/common/processing_linux.c b/src/common/processing_linux.c index 7ddd725da..73f72a5a9 100644 --- a/src/common/processing_linux.c +++ b/src/common/processing_linux.c @@ -273,6 +273,10 @@ void ffProcessGetInfoLinux(pid_t pid, FFstrbuf* processName, FFstrbuf* exe, cons ffStrbufEnsureFixedLengthFree(exePath, (uint32_t)length); ffStrbufAppendNS(exePath, (uint32_t)length, buf); } + else + { + ffStrbufSetS(exePath, filePath); + } } #elif defined(__APPLE__) diff --git a/src/data/help.json b/src/data/help.json index df10972cc..b784ec650 100644 --- a/src/data/help.json +++ b/src/data/help.json @@ -90,7 +90,7 @@ "short": "c", "long": "config", "desc": "Specify the config file or preset to load", - "remark": "The file will be searched according to the order shown by \"fastfetch --list-config-paths\". Use \"none\" to disable further config loading. See also https://github.com/fastfetch-cli/fastfetch/wiki/Configuration for more info", + "remark": "The file will be searched according to the order shown by \"fastfetch --list-config-paths\". Use \"-\" to read config from stdin or \"none\" to disable further config loading. See also https://github.com/fastfetch-cli/fastfetch/wiki/Configuration for more info", "arg": { "type": "config" } diff --git a/src/detection/cpu/cpu.h b/src/detection/cpu/cpu.h index bac4aa3e5..906173cae 100644 --- a/src/detection/cpu/cpu.h +++ b/src/detection/cpu/cpu.h @@ -21,6 +21,7 @@ typedef struct FFCPUResult uint16_t coresPhysical; uint16_t coresLogical; uint16_t coresOnline; + uint16_t numaNodes; uint32_t frequencyBase; // GHz uint32_t frequencyMax; // GHz diff --git a/src/detection/cpu/cpu_bsd.c b/src/detection/cpu/cpu_bsd.c index 9d8329aa0..1ba7bf846 100644 --- a/src/detection/cpu/cpu_bsd.c +++ b/src/detection/cpu/cpu_bsd.c @@ -100,5 +100,7 @@ const char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu) detectThermalTemp(&cpu->temperature); } + cpu->numaNodes = (uint16_t) ffSysctlGetInt("vm.ndomains", 0); + return NULL; } diff --git a/src/detection/cpu/cpu_linux.c b/src/detection/cpu/cpu_linux.c index b799700cd..ccf5d99b5 100644 --- a/src/detection/cpu/cpu_linux.c +++ b/src/detection/cpu/cpu_linux.c @@ -138,6 +138,21 @@ static double detectCPUTemp(void) return FF_CPU_TEMP_UNSET; } +static void detectNumaNodes(FFCPUResult* cpu) +{ + FF_AUTO_CLOSE_DIR DIR* dir = opendir("/sys/devices/system/node/"); + if (!dir) return; + + struct dirent* entry; + while ((entry = readdir(dir)) != NULL) + { + if (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN) + continue; + if (ffStrStartsWith(entry->d_name, "node") && ffCharIsDigit(entry->d_name[strlen("node")])) + cpu->numaNodes++; + } +} + #ifdef __ANDROID__ #include "common/settings.h" @@ -568,6 +583,8 @@ FF_MAYBE_UNUSED static const char* detectCPUX86(const FFCPUOptions* options, FFC if (!detectFrequency(cpu, options) || cpu->frequencyBase == 0) cpu->frequencyBase = (uint32_t) ffStrbufToUInt(&cpuMHz, 0); + detectNumaNodes(cpu); + return NULL; } @@ -877,6 +894,7 @@ FF_MAYBE_UNUSED static const char* detectCPUOthers(const FFCPUOptions* options, detectPhysicalCores(cpu); ffCPUDetectByCpuid(cpu); + detectNumaNodes(cpu); return NULL; } diff --git a/src/detection/cpu/cpu_windows.c b/src/detection/cpu/cpu_windows.c index d742a702c..fe18c21a9 100644 --- a/src/detection/cpu/cpu_windows.c +++ b/src/detection/cpu/cpu_windows.c @@ -230,6 +230,8 @@ static const char* detectNCores(FFCPUResult* cpu) ++cpu->coresPhysical; else if (ptr->Relationship == RelationProcessorPackage) ++cpu->packages; + else if (ptr->Relationship == RelationNumaNode) + ++cpu->numaNodes; } return NULL; diff --git a/src/detection/displayserver/displayserver_android.c b/src/detection/displayserver/displayserver_android.c index 04070565d..76e152de4 100644 --- a/src/detection/displayserver/displayserver_android.c +++ b/src/detection/displayserver/displayserver_android.c @@ -140,6 +140,61 @@ static bool detectWithGetprop(FFDisplayServerResult* ds) return false; } +static bool detectDE(FFDisplayServerResult* ds) +{ + if (ffSettingsGetAndroidProperty("ro.vivo.os.build.display.id", &ds->dePrettyName)) // OriginOS 6 + { + ffStrbufAppendC(&ds->dePrettyName, ' '); + ffSettingsGetAndroidProperty("ro.vivo.product.version", &ds->dePrettyName); // PD2505D_xxx + return true; + } + if (ffSettingsGetAndroidProperty("ro.build.version.magic", &ds->dePrettyName) || + ffSettingsGetAndroidProperty("ro.build.version.emui", &ds->dePrettyName)) + { + ffStrbufReplaceAllC(&ds->dePrettyName, '_', ' '); + return true; + } + if (ffSettingsGetAndroidProperty("ro.mi.os.version.name", &ds->dePrettyName)) + { + // MiUI like + ffStrbufClear(&ds->dePrettyName); + ffSettingsGetAndroidProperty("ro.build.version.incremental", &ds->dePrettyName); // Detail version number + if (ffStrbufStartsWithS(&ds->dePrettyName, "OS")) + { + ds->dePrettyName.chars[0] = 'S'; + ds->dePrettyName.chars[1] = ' '; + ffStrbufPrependS(&ds->dePrettyName, "HyperO"); + } + else if (ffStrbufStartsWithS(&ds->dePrettyName, "V")) + { + ds->dePrettyName.chars[0] = ' '; + ffStrbufPrependS(&ds->dePrettyName, "MiUI"); + } + else + ffStrbufSetStatic(&ds->dePrettyName, "MiUI"); + return true; + } + if (ffSettingsGetAndroidProperty("ro.build.version.oplusrom", &ds->dePrettyName)) + { + if (ffStrbufStartsWithS(&ds->dePrettyName, "V")) + ffStrbufSubstrAfter(&ds->dePrettyName, 0); + ffStrbufPrependS(&ds->dePrettyName, "ColorOS"); + return true; + } + if (ffSettingsGetAndroidProperty("ro.oxygen.version", &ds->dePrettyName)) + { + ffStrbufPrependS(&ds->dePrettyName, "OxygenOS"); + return true; + } + if (ffSettingsGetAndroidProperty("ro.build.display.id", &ds->dePrettyName) && ffStrbufStartsWithS(&ds->dePrettyName, "RedMagicOS")) + { + ffStrbufInsertNC(&ds->dePrettyName, strlen("RedMagicOS"), 1, ' '); + return true; + } + + return false; +} + void ffConnectDisplayServerImpl(FFDisplayServerResult* ds) { const char* error = ffdsConnectXcbRandr(ds); @@ -158,4 +213,6 @@ void ffConnectDisplayServerImpl(FFDisplayServerResult* ds) if (!detectWithGetprop(ds)) detectWithDumpsys(ds); + + detectDE(ds); } diff --git a/src/detection/gpu/gpu.h b/src/detection/gpu/gpu.h index d2ca916bd..350614152 100644 --- a/src/detection/gpu/gpu.h +++ b/src/detection/gpu/gpu.h @@ -15,11 +15,18 @@ extern const char* FF_GPU_VENDOR_NAME_AMD; extern const char* FF_GPU_VENDOR_NAME_INTEL; extern const char* FF_GPU_VENDOR_NAME_NVIDIA; extern const char* FF_GPU_VENDOR_NAME_MTHREADS; +extern const char* FF_GPU_VENDOR_NAME_QUALCOMM; +extern const char* FF_GPU_VENDOR_NAME_MTK; extern const char* FF_GPU_VENDOR_NAME_VMWARE; extern const char* FF_GPU_VENDOR_NAME_PARALLEL; extern const char* FF_GPU_VENDOR_NAME_MICROSOFT; extern const char* FF_GPU_VENDOR_NAME_REDHAT; extern const char* FF_GPU_VENDOR_NAME_ORACLE; +extern const char* FF_GPU_VENDOR_NAME_BROADCOM; +extern const char* FF_GPU_VENDOR_NAME_LOONGSON; +extern const char* FF_GPU_VENDOR_NAME_JINGJIA_MICRO; +extern const char* FF_GPU_VENDOR_NAME_HUAWEI; +extern const char* FF_GPU_VENDOR_NAME_ZHAOXIN; typedef struct FFGPUMemory { diff --git a/src/detection/gpu/gpu_apple.c b/src/detection/gpu/gpu_apple.c index bad4f9293..32d00cc49 100644 --- a/src/detection/gpu/gpu_apple.c +++ b/src/detection/gpu/gpu_apple.c @@ -57,7 +57,7 @@ static const char* detectFrequency(FFGPUResult* gpu) if (!freqProperty || CFGetTypeID(freqProperty) != CFDataGetTypeID()) return "\"voltage-states9-sram\" in \"pmgr\" is not found"; - // voltage-states5-sram stores supported pairs of gpu from the lowest to the highest + // voltage-states9-sram stores supported pairs of gpu from the lowest to the highest CFIndex propLength = CFDataGetLength(freqProperty); if (propLength == 0 || propLength % (CFIndex) sizeof(uint32_t) * 2 != 0) return "Invalid \"voltage-states9-sram\" length"; diff --git a/src/detection/gpu/gpu_linux.c b/src/detection/gpu/gpu_linux.c index fb28f7449..abe52ea15 100644 --- a/src/detection/gpu/gpu_linux.c +++ b/src/detection/gpu/gpu_linux.c @@ -4,10 +4,13 @@ #include "detection/gpu/gpu_driver_specific.h" #include "common/io/io.h" #include "common/library.h" +#include "modules/gpu/option.h" +#include "util/FFstrbuf.h" #include "util/stringUtils.h" #include "util/mallocHelper.h" #include +#include #ifdef FF_HAVE_DRM_AMDGPU #include @@ -263,7 +266,7 @@ static const char* drmDetectIntelSpecific(FFGPUResult* gpu, const char* drmKey, #endif } -static const char* pciDetectNouveauSpecific(const FFGPUOptions* options, FFGPUResult* gpu, FFstrbuf* pciDir, FFstrbuf* buffer) +static const char* pciDetectTempGeneral(const FFGPUOptions* options, FFGPUResult* gpu, FFstrbuf* pciDir, FFstrbuf* buffer) { if (options->temp) { @@ -306,6 +309,48 @@ static const char* drmDetectNouveauSpecific(FFGPUResult* gpu, const char* drmKey #endif } +static const char* pciDetectZxSpecific(const FFGPUOptions* options, FFGPUResult* gpu, FFstrbuf* pciDir, FFstrbuf* buffer) +{ + gpu->type = FF_GPU_TYPE_INTEGRATED; + + const uint32_t pciDirLen = pciDir->length; + ffStrbufAppendS(pciDir, "/zx_info/eclk"); + if (ffReadFileBuffer(pciDir->chars, buffer)) + gpu->frequency = (uint32_t) ffStrbufToUInt(buffer, FF_GPU_FREQUENCY_UNSET); + ffStrbufSubstrBefore(pciDir, pciDirLen); + + if (options->driverSpecific) + { + ffStrbufAppendS(pciDir, "/zx_info/engine_3d_usage"); + if (ffReadFileBuffer(pciDir->chars, buffer)) + gpu->coreUsage = ffStrbufToDouble(buffer, FF_GPU_CORE_USAGE_UNSET); + ffStrbufSubstrBefore(pciDir, pciDirLen); + + ffStrbufAppendS(pciDir, "/zx_info/fb_size"); + if (ffReadFileBuffer(pciDir->chars, buffer)) + gpu->shared.total = ffStrbufToUInt(buffer, FF_GPU_VMEM_SIZE_UNSET); + ffStrbufSubstrBefore(pciDir, pciDirLen); + + if (gpu->shared.total != FF_GPU_VMEM_SIZE_UNSET) + { + gpu->shared.total *= 1024 * 1024; + + ffStrbufAppendS(pciDir, "/zx_info/free_fb_mem"); + if (ffReadFileBuffer(pciDir->chars, buffer)) + gpu->shared.used = ffStrbufToUInt(buffer, FF_GPU_VMEM_SIZE_UNSET); + ffStrbufSubstrBefore(pciDir, pciDirLen); + + if (gpu->shared.used != FF_GPU_VMEM_SIZE_UNSET) + { + gpu->shared.used *= 1024 * 1024; + gpu->shared.used = gpu->shared.total - gpu->shared.used; + } + } + } + + return NULL; +} + static const char* detectPci(const FFGPUOptions* options, FFlist* gpus, FFstrbuf* buffer, FFstrbuf* deviceDir, const char* drmKey) { const uint32_t drmDirPathLength = deviceDir->length; @@ -417,10 +462,15 @@ static const char* detectPci(const FFGPUOptions* options, FFlist* gpus, FFstrbuf } else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_NVIDIA && ffStrbufEqualS(&gpu->driver, "nouveau")) { - pciDetectNouveauSpecific(options, gpu, deviceDir, buffer); + pciDetectTempGeneral(options, gpu, deviceDir, buffer); if (options->driverSpecific && drmKey) drmDetectNouveauSpecific(gpu, drmKey, buffer); } + else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_ZHAOXIN && ffStrbufStartsWithS(&gpu->driver, "zx")) + { + pciDetectTempGeneral(options, gpu, deviceDir, buffer); + pciDetectZxSpecific(options, gpu, deviceDir, buffer); + } else { ffGPUDetectDriverSpecific(options, gpu, (FFGpuDriverPciBusId) { diff --git a/src/detection/packages/packages.h b/src/detection/packages/packages.h index 7464a30c1..12e987933 100644 --- a/src/detection/packages/packages.h +++ b/src/detection/packages/packages.h @@ -21,6 +21,7 @@ typedef struct FFPackagesResult uint32_t guixUser; uint32_t hpkgSystem; uint32_t hpkgUser; + uint32_t kiss; uint32_t linglong; uint32_t lpkg; uint32_t lpkgbuild; diff --git a/src/detection/packages/packages_linux.c b/src/detection/packages/packages_linux.c index 3d83f3a52..fc5a0cf21 100644 --- a/src/detection/packages/packages_linux.c +++ b/src/detection/packages/packages_linux.c @@ -420,6 +420,7 @@ static void getPackageCounts(FFstrbuf* baseDir, FFPackagesResult* packageCounts, if (!(options->disabled & FF_PACKAGES_FLAG_EMERGE_BIT)) packageCounts->emerge += countFilesRecursive(baseDir, "/var/db/pkg", "SIZE"); if (!(options->disabled & FF_PACKAGES_FLAG_EOPKG_BIT)) packageCounts->eopkg += getNumElements(baseDir, "/var/lib/eopkg/package", true); if (!(options->disabled & FF_PACKAGES_FLAG_FLATPAK_BIT)) packageCounts->flatpakSystem += getFlatpakPackages(baseDir, "/var/lib"); + if (!(options->disabled & FF_PACKAGES_FLAG_KISS_BIT)) packageCounts->kiss += getNumElements(baseDir, "/var/db/kiss/installed", true); if (!(options->disabled & FF_PACKAGES_FLAG_NIX_BIT)) { packageCounts->nixDefault += ffPackagesGetNix(baseDir, "/nix/var/nix/profiles/default"); diff --git a/src/detection/terminalfont/terminalfont.c b/src/detection/terminalfont/terminalfont.c index 19df07b02..5e0d6204f 100644 --- a/src/detection/terminalfont/terminalfont.c +++ b/src/detection/terminalfont/terminalfont.c @@ -8,13 +8,18 @@ static void detectAlacritty(FFTerminalFontResult* terminalFont) { - FF_STRBUF_AUTO_DESTROY fontName = ffStrbufCreate(); + // Maybe using a toml parser to read the config file is better? + // https://github.com/cktan/tomlc17 + + // Doc: https://alacritty.org/config-alacritty.html#s26 + FF_STRBUF_AUTO_DESTROY fontNormal = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY fontFamily = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY fontStyle = ffStrbufCreate(); FF_STRBUF_AUTO_DESTROY fontSize = ffStrbufCreate(); do { - // Latest alacritty uses toml instead of yaml FFpropquery fontQueryToml[] = { - {"family =", &fontName}, + {"normal =", &fontNormal}, {"size =", &fontSize}, }; @@ -25,29 +30,39 @@ static void detectAlacritty(FFTerminalFontResult* terminalFont) break; if(ffParsePropFileConfigValues(".alacritty.toml", 2, fontQueryToml)) break; - - FFpropquery fontQueryYaml[] = { - {"family:", &fontName}, - {"size:", &fontSize}, - }; - - if(ffParsePropFileConfigValues("alacritty/alacritty.yml", 2, fontQueryYaml)) - break; - if(ffParsePropFileConfigValues("alacritty.yml", 2, fontQueryYaml)) - break; - if(ffParsePropFileConfigValues(".alacritty.yml", 2, fontQueryYaml)) - break; } while (false); - //by default alacritty uses its own font called alacritty - if(fontName.length == 0) - ffStrbufAppendS(&fontName, "alacritty"); + if(fontNormal.length > 0) + { + // { family = "Fira Code", style = "Medium" } + ffStrbufTrimSpace(&fontNormal); + ffStrbufTrimRight(&fontNormal, '}'); + ffStrbufTrimLeft(&fontNormal, '{'); + ffStrbufTrimSpace(&fontNormal); + + // family = "Fira Code", style = "Medium" + ffStrbufReplaceAllC(&fontNormal, ',', '\n'); // Assume no commas in font names + ffParsePropLines(fontNormal.chars, "family =", &fontFamily); + ffParsePropLines(fontNormal.chars, "style =", &fontStyle); + } + + if (fontFamily.length == 0) + { + #if __APPLE__ + ffStrbufSetStatic(&fontFamily, "Menlo"); + #elif _WIN32 + ffStrbufSetStatic(&fontFamily, "Consolas"); + #else + ffStrbufSetStatic(&fontFamily, "monospace"); + #endif + } + if (fontStyle.length == 0) + ffStrbufSetStatic(&fontStyle, "Regular"); - // the default font size is 11 if(fontSize.length == 0) - ffStrbufAppendS(&fontSize, "11"); + ffStrbufSetStatic(&fontSize, "11.25"); - ffFontInitValues(&terminalFont->font, fontName.chars, fontSize.chars); + ffFontInitMoveValues(&terminalFont->font, &fontFamily, &fontSize, &fontStyle); } static void detectGhostty(const FFstrbuf* exe, FFTerminalFontResult* terminalFont) diff --git a/src/detection/terminalshell/terminalshell.c b/src/detection/terminalshell/terminalshell.c index 5ffae3691..dc7c2c33b 100644 --- a/src/detection/terminalshell/terminalshell.c +++ b/src/detection/terminalshell/terminalshell.c @@ -2,6 +2,7 @@ #include "common/io/io.h" #include "common/processing.h" #include "common/properties.h" +#include "util/path.h" #include "util/stringUtils.h" #include "util/binary.h" @@ -64,12 +65,9 @@ static bool extractBashVersion(const char* line, FF_MAYBE_UNUSED uint32_t len, v return false; } -static bool getShellVersionBash(FFstrbuf* exe, FFstrbuf* exePath, FFstrbuf* version) +static bool getShellVersionBash(FFstrbuf* exe, FFstrbuf* version) { - const char* path = exePath->chars; - if (*path == '\0') - path = exe->chars; - ffBinaryExtractStrings(path, extractBashVersion, version, (uint32_t) strlen("@(#)Bash version 0.0.0(0) release GNU")); + ffBinaryExtractStrings(exe->chars, extractBashVersion, version, (uint32_t) strlen("@(#)Bash version 0.0.0(0) release GNU")); if (version->length > 0) return true; if(!getExeVersionRaw(exe, version)) @@ -122,35 +120,39 @@ static bool getShellVersionPwsh(FFstrbuf* exe, FFstrbuf* version) static bool getShellVersionKsh(FFstrbuf* exe, FFstrbuf* version) { -#if __OpenBSD__ || __NetBSD__ + if(ffProcessAppendStdErr(version, (char* const[]) { + exe->chars, + "--version", + NULL + }) == NULL && ffStrbufSubstrAfterFirstS(version, " (AT&T Research) ")) + { + // version sh (AT&T Research) 93u+ 2012-08-01 + ffStrbufSubstrBeforeFirstC(version, ' '); + return true; + } + + ffStrbufClear(version); if(ffProcessAppendStdOut(version, (char* const[]) { exe->chars, "-c", "echo $KSH_VERSION", NULL - }) != NULL) - return false; + }) == NULL && ffStrbufSubstrAfterFirstS(version, " KSH ")) + { + // OKSH: @(#)PD KSH v5.2.14 99/07/13.2 + // MKSH: @(#)MIRBSD KSH R59 2025/04/26 +Debian + // $OKSH_VERSION doesn't exist on OpenBSD + ffStrbufSubstrBeforeFirstC(version, ' '); + ffStrbufTrimLeft(version, 'v'); + return true; + } - // @(#)PD KSH v5.2.14 99/07/13.2 - ffStrbufSubstrAfterFirstC(version, 'v'); - ffStrbufSubstrBeforeFirstC(version, ' '); -#else - if(ffProcessAppendStdErr(version, (char* const[]) { - exe->chars, - "--version", - NULL - }) != NULL) - return false; - - // version sh (AT&T Research) 93u+ 2012-08-01 - ffStrbufSubstrAfterLastC(version, ')'); - ffStrbufTrim(version, ' '); -#endif - return true; + return false; } static bool getShellVersionOksh(FFstrbuf* exe, FFstrbuf* version) { + // Homebrew version if(ffProcessAppendStdOut(version, (char* const[]) { exe->chars, "-c", @@ -230,13 +232,9 @@ static bool extractZshVersion(const char* line, FF_MAYBE_UNUSED uint32_t len, vo return false; } -static bool getShellVersionZsh(FFstrbuf* exe, FFstrbuf* exePath, FFstrbuf* version) +static bool getShellVersionZsh(FFstrbuf* exe, FFstrbuf* version) { - const char* path = exePath->chars; - if (*path == '\0') - path = exe->chars; - - ffBinaryExtractStrings(path, extractZshVersion, version, (uint32_t) strlen("zsh-0.0-0")); + ffBinaryExtractStrings(exe->chars, extractZshVersion, version, (uint32_t) strlen("zsh-0.0-0")); if (version->length) return true; return getExeVersionGeneral(exe, version); //zsh 5.9 (arm-apple-darwin21.3.0) @@ -263,7 +261,7 @@ static bool getShellVersionWinPowerShell(FFstrbuf* exe, FFstrbuf* version) } #endif -bool fftsGetShellVersion(FFstrbuf* exe, const char* exeName, FFstrbuf* exePath, FFstrbuf* version) +bool fftsGetShellVersion(FFstrbuf* exe, const char* exeName, FFstrbuf* version) { if (!instance.config.general.detectVersion) return false; @@ -271,9 +269,9 @@ bool fftsGetShellVersion(FFstrbuf* exe, const char* exeName, FFstrbuf* exePath, return false; if(ffStrEqualsIgnCase(exeName, "bash")) - return getShellVersionBash(exe, exePath, version); + return getShellVersionBash(exe, version); if(ffStrEqualsIgnCase(exeName, "zsh")) - return getShellVersionZsh(exe, exePath, version); + return getShellVersionZsh(exe, version); if(ffStrEqualsIgnCase(exeName, "fish")) return getShellVersionFish(exe, version); if(ffStrEqualsIgnCase(exeName, "pwsh")) @@ -282,7 +280,7 @@ bool fftsGetShellVersion(FFstrbuf* exe, const char* exeName, FFstrbuf* exePath, return getExeVersionGeneral(exe, version); //tcsh 6.24.07 (Astron) 2022-12-21 (aarch64-apple-darwin) options wide,nls,dl,al,kan,sm,rh,color,filec if(ffStrEqualsIgnCase(exeName, "nu")) return getShellVersionNushell(exe, version); - if(ffStrEqualsIgnCase(exeName, "ksh")) + if(ffStrEqualsIgnCase(exeName, "ksh") || ffStrEqualsIgnCase(exeName, "mksh")) return getShellVersionKsh(exe, version); if(ffStrEqualsIgnCase(exeName, "oksh")) return getShellVersionOksh(exe, version); @@ -325,7 +323,7 @@ static bool extractGeneralVersion(const char *str, FF_MAYBE_UNUSED uint32_t len, FF_MAYBE_UNUSED static bool getTerminalVersionGnome(FFstrbuf* exe, FFstrbuf* version) { - if (exe->chars[0] == '/') + if (ffIsAbsolutePath(exe->chars)) { ffBinaryExtractStrings(exe->chars, extractGeneralVersion, version, (uint32_t) strlen("0.0.0")); if (version->length) return true; @@ -345,7 +343,7 @@ FF_MAYBE_UNUSED static bool getTerminalVersionGnome(FFstrbuf* exe, FFstrbuf* ver FF_MAYBE_UNUSED static bool getTerminalVersionXfce4Terminal(FFstrbuf* exe, FFstrbuf* version) { - if (exe->chars[0] == '/') + if (ffIsAbsolutePath(exe->chars)) { ffBinaryExtractStrings(exe->chars, extractGeneralVersion, version, (uint32_t) strlen("0.0.0")); if (version->length) return true; @@ -582,6 +580,44 @@ static bool getTerminalVersionZed(FFstrbuf* exe, FFstrbuf* version) return true; } +static bool extractSshdVersion(const char *str, FF_MAYBE_UNUSED uint32_t len, void *userdata) +{ + if (!ffStrStartsWith(str, "OpenSSH_") || !ffCharIsDigit(str[strlen("OpenSSH_")])) return true; + str += strlen("OpenSSH_"); + int count = 0; + sscanf(str, "%*d.%*dp%*d%n", &count); + if (count == 0) return true; + ffStrbufSetS((FFstrbuf*) userdata, str); + return false; +} + +static bool getTerminalVersionSshd(FFstrbuf* exe, FFstrbuf* version) +{ + FF_STRBUF_AUTO_DESTROY exePath = ffStrbufCreate(); + if (ffIsAbsolutePath(exe->chars)) + ffStrbufSet(&exePath, exe); + else if (ffFindExecutableInPath("sshd", &exePath) != NULL) + return false; + + ffBinaryExtractStrings(exePath.chars, extractSshdVersion, version, (uint32_t) strlen("OpenSSH0.0")); + if (version->length) return true; + + if(ffProcessAppendStdOut(version, (char* const[]) { + exePath.chars, + "-V", + NULL + }) != NULL) + return false; + + if (ffStrbufStartsWithS(version, "unknown ")) // `unknown option -- V` (ancient OpenSSH version) + ffStrbufSubstrAfterFirstC(version, '\n'); + + // OpenSSH_10.0p2 Ubuntu-5ubuntu5, OpenSSL 3.5.3 16 Sep 2025 + ffStrbufSubstrAfterFirstC(version, '_'); + ffStrbufSubstrBeforeFirstC(version, ','); + return true; +} + #ifndef _WIN32 static bool getTerminalVersionKitty(FFstrbuf* exe, FFstrbuf* version) { @@ -681,7 +717,7 @@ FF_MAYBE_UNUSED static bool getTerminalVersionPtyxis(FF_MAYBE_UNUSED FFstrbuf* e FF_MAYBE_UNUSED static bool getTerminalVersionTilix(FFstrbuf* exe, FFstrbuf* version) { - if (exe->chars[0] == '/') + if (ffIsAbsolutePath(exe->chars)) { ffBinaryExtractStrings(exe->chars, extractGeneralVersion, version, (uint32_t) strlen("0.0.0")); if (version->length) return true; @@ -925,6 +961,9 @@ bool fftsGetTerminalVersion(FFstrbuf* processName, FF_MAYBE_UNUSED FFstrbuf* exe if(ffStrbufStartsWithIgnCaseS(processName, "tmux")) return getTerminalVersionTmux(exe, version); + if(ffStrbufIgnCaseEqualS(processName, "sshd") || ffStrbufStartsWithIgnCaseS(processName, "sshd-")) + return getTerminalVersionSshd(exe, version); + #ifdef _WIN32 return getFileVersion(exe, NULL, version); diff --git a/src/detection/terminalshell/terminalshell_linux.c b/src/detection/terminalshell/terminalshell_linux.c index e39ea9ef5..165b9c19b 100644 --- a/src/detection/terminalshell/terminalshell_linux.c +++ b/src/detection/terminalshell/terminalshell_linux.c @@ -292,14 +292,14 @@ static void getUserShellFromEnv(FFShellResult* result) } } -bool fftsGetShellVersion(FFstrbuf* exe, const char* exeName, FFstrbuf* exePath, FFstrbuf* version); +bool fftsGetShellVersion(FFstrbuf* exe, const char* exeName, FFstrbuf* version); bool fftsGetTerminalVersion(FFstrbuf* processName, FFstrbuf* exe, FFstrbuf* version); static void setShellInfoDetails(FFShellResult* result) { ffStrbufClear(&result->version); - fftsGetShellVersion(&result->exe, result->exeName, &result->exePath, &result->version); + fftsGetShellVersion(result->exePath.length > 0 ? &result->exePath : &result->exe, result->exeName, &result->version); if(ffStrbufEqualS(&result->processName, "pwsh")) ffStrbufInitStatic(&result->prettyName, "PowerShell"); @@ -331,7 +331,12 @@ static void setTerminalInfoDetails(FFTerminalResult* result) else if(ffStrbufStartsWithS(&result->processName, "screen-")) ffStrbufInitStatic(&result->prettyName, "screen"); else if(ffStrbufEqualS(&result->processName, "sshd") || ffStrbufStartsWithS(&result->processName, "sshd-")) - ffStrbufInitCopy(&result->prettyName, &result->tty); + { + if (result->tty.length) + ffStrbufInitCopy(&result->prettyName, &result->tty); + else + ffStrbufSetStatic(&result->prettyName, "sshd"); + } #if defined(__ANDROID__) @@ -383,7 +388,7 @@ static void setTerminalInfoDetails(FFTerminalResult* result) else ffStrbufInitCopy(&result->prettyName, &result->processName); - fftsGetTerminalVersion(&result->processName, &result->exe, &result->version); + fftsGetTerminalVersion(&result->processName, result->exePath.length > 0 ? &result->exePath : &result->exe, &result->version); } #if defined(MAXPATH) diff --git a/src/detection/terminalshell/terminalshell_windows.c b/src/detection/terminalshell/terminalshell_windows.c index 8e4e388a9..1fc1ad760 100644 --- a/src/detection/terminalshell/terminalshell_windows.c +++ b/src/detection/terminalshell/terminalshell_windows.c @@ -12,7 +12,7 @@ #include #include -bool fftsGetShellVersion(FFstrbuf* exe, const char* exeName, const FFstrbuf* exePath, FFstrbuf* version); +bool fftsGetShellVersion(FFstrbuf* exe, const char* exeName, FFstrbuf* version); static uint32_t getShellInfo(FFShellResult* result, uint32_t pid) { @@ -332,7 +332,7 @@ const FFShellResult* ffDetectShell(void) strcpy(tmp, result.exeName); char* ext = strrchr(tmp, '.'); if (ext) *ext = '\0'; - fftsGetShellVersion(&result.exe, tmp, &result.exePath, &result.version); + fftsGetShellVersion(result.exePath.length > 0 ? &result.exePath : &result.exe, tmp, &result.version); } return &result; @@ -368,7 +368,7 @@ const FFTerminalResult* ffDetectTerminal(void) if(result.processName.length > 0) { setTerminalInfoDetails(&result); - fftsGetTerminalVersion(&result.processName, &result.exe, &result.version); + fftsGetTerminalVersion(&result.processName, result.exePath.length > 0 ? &result.exePath : &result.exe, &result.version); } return &result; diff --git a/src/detection/users/users_linux.c b/src/detection/users/users_linux.c index acf860d77..6875a423b 100644 --- a/src/detection/users/users_linux.c +++ b/src/detection/users/users_linux.c @@ -1,6 +1,10 @@ +#include "common/io/io.h" +#include "common/properties.h" #include "fastfetch.h" #include "users.h" +#include + #if FF_HAVE_UTMPX #include #else @@ -15,7 +19,140 @@ #include #endif -const char* ffDetectUsers(FFUsersOptions* options, FFlist* users) +#if __linux__ +bool detectUserBySystemd(const FFstrbuf* pathUsers, FFlist* users) +{ + FF_STRBUF_AUTO_DESTROY state = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY userName = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY loginTime = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY sessions = ffStrbufCreate(); + + // WARNING: This is private data. Do not parse + if (!ffParsePropFileValues(pathUsers->chars, 4, (FFpropquery[]) { + {"NAME=", &userName}, + {"STATE=", &state}, + {"REALTIME=", &loginTime}, + {"ONLINE_SESSIONS=", &sessions}, + }) || !ffStrbufEqualS(&state, "active")) + return false; + + FFUserResult* user = FF_LIST_ADD(FFUserResult, *users); + ffStrbufInitMove(&user->name, &userName); + ffStrbufInit(&user->hostName); + ffStrbufInit(&user->sessionName); + ffStrbufInit(&user->clientIp); + ffStrbufSubstrBefore(&loginTime, loginTime.length - 3); // converts us to ms + user->loginTime = ffStrbufToUInt(&loginTime, 0); + + FF_STRBUF_AUTO_DESTROY pathSessions = ffStrbufCreateS("/run/systemd/sessions/"); + const uint32_t pathSessionsBaseLen = pathSessions.length; + + FF_STRBUF_AUTO_DESTROY tty = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY remoteHost = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY service = ffStrbufCreate(); + + char* token = NULL; + size_t n = 0; + while (ffStrbufGetdelim(&token, &n, ' ', &sessions)) + { + ffStrbufSubstrBefore(&pathSessions, pathSessionsBaseLen); + ffStrbufAppendS(&pathSessions, token); + + ffStrbufClear(&remoteHost); + ffStrbufClear(&service); + ffStrbufClear(&tty); + ffStrbufClear(&loginTime); + + // WARNING: This is private data. Do not parse + if (ffParsePropFileValues(pathSessions.chars, 4, (FFpropquery[]) { + {"REMOTE_HOST=", &remoteHost}, + {"TTY=", &tty}, + {"SERVICE=", &service}, + {"REALTIME=", &loginTime}, + }) && !ffStrbufEqualS(&service, "systemd-user")) + { + if (remoteHost.length) + { + ffStrbufTrimRight(&remoteHost, ']'); + ffStrbufTrimLeft(&remoteHost, '['); + ffStrbufInitMove(&user->hostName, &remoteHost); + } + else + ffStrbufSetStatic(&user->hostName, "localhost"); + ffStrbufInitMove(&user->sessionName, tty.length ? &tty : &service); + if (loginTime.length) + { + ffStrbufSubstrBefore(&loginTime, loginTime.length - 3); // converts us to ms + user->loginTime = ffStrbufToUInt(&loginTime, 0); + } + break; + } + } + + return true; +} + +const char* detectBySystemd(FFUsersOptions* options, FFlist* users) +{ + // For some reason, debian/ubuntu no longer updates `/var/run/utmp` (#2064) + // Query systemd instead + FF_STRBUF_AUTO_DESTROY pathUsers = ffStrbufCreateS("/run/systemd/users/"); + + if (options->myselfOnly) + { + ffStrbufAppendUInt(&pathUsers, getuid()); + detectUserBySystemd(&pathUsers, users); + } + else + { + const uint32_t pathUsersBaseLen = pathUsers.length; + FF_AUTO_CLOSE_DIR DIR* dirp = opendir(pathUsers.chars); + if (!dirp) return "opendir(\"/run/systemd/users/\") failed"; + + struct dirent* entry; + while ((entry = readdir(dirp))) + { + if (entry->d_type != DT_REG) continue; + + ffStrbufAppendS(&pathUsers, entry->d_name); + detectUserBySystemd(&pathUsers, users); + ffStrbufSubstrBefore(&pathUsers, pathUsersBaseLen); + } + } + return NULL; +} +#endif + +#if __linux__ || __GNU__ +static void fillUtmpIpAddr(FFUserResult* user, struct utmpx* n) +{ + bool isIpv6 = false; + for (int i = 1; i < 4; ++i) { + if (n->ut_addr_v6[i] != 0) { + isIpv6 = true; + break; + } + } + + if (isIpv6) { + char ipv6_str[INET6_ADDRSTRLEN]; + if (inet_ntop(AF_INET6, n->ut_addr_v6, ipv6_str, INET6_ADDRSTRLEN) != NULL) { + ffStrbufSetS(&user->clientIp, ipv6_str); + } + } else if (n->ut_addr_v6[0] != 0) { + char ipv4_str[INET_ADDRSTRLEN]; + if (inet_ntop(AF_INET, n->ut_addr_v6, ipv4_str, INET_ADDRSTRLEN) != NULL) { + ffStrbufSetS(&user->clientIp, ipv4_str); + } + } +} +#else +static void fillUtmpIpAddr(FF_MAYBE_UNUSED FFUserResult* user, FF_MAYBE_UNUSED struct utmpx* n) +{ +} +#endif + +const char* detectByUtmp(FFUsersOptions* options, FFlist* users) { struct utmpx* n = NULL; setutxent(); @@ -32,37 +169,41 @@ next: FF_LIST_FOR_EACH(FFUserResult, user, *users) { if(ffStrbufEqualS(&user->name, n->ut_user)) + { + uint64_t newLoginTime = (uint64_t) n->ut_tv.tv_sec * 1000 + (uint64_t) n->ut_tv.tv_usec / 1000; + if (newLoginTime > user->loginTime) + { + ffStrbufSetS(&user->hostName, n->ut_host); + ffStrbufSetS(&user->sessionName, n->ut_line); + fillUtmpIpAddr(user, n); + user->loginTime = newLoginTime; + } goto next; + } } - FFUserResult* user = (FFUserResult*) ffListAdd(users); + FFUserResult* user = FF_LIST_ADD(FFUserResult, *users); ffStrbufInitS(&user->name, n->ut_user); ffStrbufInitS(&user->hostName, n->ut_host); ffStrbufInitS(&user->sessionName, n->ut_line); ffStrbufInit(&user->clientIp); - #if __linux__ || __GNU__ - bool isIpv6 = false; - for (int i = 1; i < 4; ++i) { - if (n->ut_addr_v6[i] != 0) { - isIpv6 = true; - break; - } - } - - if (isIpv6) { - char ipv6_str[INET6_ADDRSTRLEN]; - if (inet_ntop(AF_INET6, n->ut_addr_v6, ipv6_str, INET6_ADDRSTRLEN) != NULL) { - ffStrbufSetS(&user->clientIp, ipv6_str); - } - } else if (n->ut_addr_v6[0] != 0) { - char ipv4_str[INET_ADDRSTRLEN]; - if (inet_ntop(AF_INET, n->ut_addr_v6, ipv4_str, INET_ADDRSTRLEN) != NULL) { - ffStrbufSetS(&user->clientIp, ipv4_str); - } - } - #endif + fillUtmpIpAddr(user, n); user->loginTime = (uint64_t) n->ut_tv.tv_sec * 1000 + (uint64_t) n->ut_tv.tv_usec / 1000; } + endutxent(); + + return NULL; +} + +const char* ffDetectUsers(FFUsersOptions* options, FFlist* users) +{ + const char* err = detectByUtmp(options, users); + if (err) return err; + + #if __linux__ + if (users->length == 0) detectBySystemd(options, users); + #endif + return NULL; } diff --git a/src/detection/zpool/libzfs_simplified.h b/src/detection/zpool/libzfs_simplified.h index 3b97c222d..49302c16d 100644 --- a/src/detection/zpool/libzfs_simplified.h +++ b/src/detection/zpool/libzfs_simplified.h @@ -25,65 +25,24 @@ * CDDL HEADER END */ -typedef enum { - ZPOOL_PROP_INVAL = -1, - ZPOOL_PROP_NAME, - ZPOOL_PROP_SIZE, - ZPOOL_PROP_CAPACITY, - ZPOOL_PROP_ALTROOT, - ZPOOL_PROP_HEALTH, - ZPOOL_PROP_GUID, - ZPOOL_PROP_VERSION, - ZPOOL_PROP_BOOTFS, - ZPOOL_PROP_DELEGATION, - ZPOOL_PROP_AUTOREPLACE, - ZPOOL_PROP_CACHEFILE, - ZPOOL_PROP_FAILUREMODE, - ZPOOL_PROP_LISTSNAPS, - ZPOOL_PROP_AUTOEXPAND, - ZPOOL_PROP_DEDUPDITTO, - ZPOOL_PROP_DEDUPRATIO, - ZPOOL_PROP_FREE, - ZPOOL_PROP_ALLOCATED, - ZPOOL_PROP_READONLY, - ZPOOL_PROP_ASHIFT, - ZPOOL_PROP_COMMENT, - ZPOOL_PROP_EXPANDSZ, - ZPOOL_PROP_FREEING, - ZPOOL_PROP_FRAGMENTATION, - ZPOOL_PROP_LEAKED, - ZPOOL_PROP_MAXBLOCKSIZE, - ZPOOL_PROP_TNAME, - ZPOOL_PROP_MAXDNODESIZE, - ZPOOL_PROP_MULTIHOST, - ZPOOL_PROP_CHECKPOINT, - ZPOOL_PROP_LOAD_GUID, - ZPOOL_PROP_AUTOTRIM, - ZPOOL_PROP_COMPATIBILITY, - ZPOOL_PROP_BCLONEUSED, - ZPOOL_PROP_BCLONESAVED, - ZPOOL_PROP_BCLONERATIO, - ZPOOL_NUM_PROPS -} zpool_prop_t; - -typedef enum { - ZPROP_SRC_NONE = 0x1, - ZPROP_SRC_DEFAULT = 0x2, - ZPROP_SRC_TEMPORARY = 0x4, - ZPROP_SRC_LOCAL = 0x8, - ZPROP_SRC_INHERITED = 0x10, - ZPROP_SRC_RECEIVED = 0x20 -} zprop_source_t; - -typedef bool boolean_t; +// zpool_prop_t and zprop_source_t were previously enums in upstream OpenZFS. +// However, the enum values for these types vary greatly between different platforms, +// making it unsafe to use the enum values directly. To ensure portability, +// we define them as simple int typedefs and use zpool_name_to_prop to look up +// the correct value for a property at runtime. +typedef int zpool_prop_t; +typedef int zprop_source_t; +typedef int boolean_t; typedef struct libzfs_handle libzfs_handle_t; typedef struct zpool_handle zpool_handle_t; typedef int (*zpool_iter_f)(zpool_handle_t *, void *); -extern int zpool_iter(libzfs_handle_t *, zpool_iter_f, void *); extern libzfs_handle_t *libzfs_init(void); extern void libzfs_fini(libzfs_handle_t *); -extern uint64_t zpool_get_prop_int(zpool_handle_t *, zpool_prop_t, zprop_source_t *); -extern const char *zpool_get_name(zpool_handle_t *); -extern const char *zpool_get_state_str(zpool_handle_t *); +extern int zpool_iter(libzfs_handle_t *, zpool_iter_f, void *); +extern zpool_prop_t zpool_name_to_prop(const char *); +// https://github.com/openzfs/zfs/blob/06c73cffabc30b61a695988ec8e290f43cb3768d/lib/libzfs/libzfs_pool.c#L300 +extern uint64_t zpool_get_prop_int(zpool_handle_t *zhp, zpool_prop_t prop, zprop_source_t *srctype); +extern int zpool_get_prop(zpool_handle_t *zhp, zpool_prop_t prop, char *buf, size_t len, zprop_source_t *srctype, boolean_t literal); +extern void zpool_close(zpool_handle_t *); diff --git a/src/detection/zpool/zpool.c b/src/detection/zpool/zpool.c new file mode 100644 index 000000000..67e121b85 --- /dev/null +++ b/src/detection/zpool/zpool.c @@ -0,0 +1,126 @@ +#include "zpool.h" + +#if FF_HAVE_LIBZFS + +#include "util/kmod.h" + +#ifdef __sun +#include +#else +#include "libzfs_simplified.h" +#endif + +#include "common/library.h" + +typedef struct FFZfsData +{ + FF_LIBRARY_SYMBOL(libzfs_fini) + FF_LIBRARY_SYMBOL(zpool_get_prop_int) + FF_LIBRARY_SYMBOL(zpool_get_prop) + FF_LIBRARY_SYMBOL(zpool_close) + + // The fields in this struct store property IDs returned by `zpool_name_to_prop`, + // not the property values themselves. + struct { + int name; + int health; + int guid; + int size; + int free; + int allocated; + int fragmentation; + int readonly; + } props; + + libzfs_handle_t* handle; + FFlist* result; +} FFZfsData; + +static inline void cleanLibzfs(FFZfsData* data) +{ + if (data->fflibzfs_fini && data->handle) + { + data->fflibzfs_fini(data->handle); + data->handle = NULL; + } +} + +static int enumZpoolCallback(zpool_handle_t* zpool, void* param) +{ + FFZfsData* data = (FFZfsData*) param; + zprop_source_t source; + FFZpoolResult* item = ffListAdd(data->result); + char buf[1024]; + if (data->ffzpool_get_prop(zpool, data->props.name, buf, ARRAY_SIZE(buf), &source, false) == 0) + ffStrbufInitS(&item->name, buf); + else + ffStrbufInitStatic(&item->name, "unknown"); + if (data->ffzpool_get_prop(zpool, data->props.health, buf, ARRAY_SIZE(buf), &source, false) == 0) + ffStrbufInitS(&item->state, buf); + else + ffStrbufInitStatic(&item->state, "unknown"); + item->guid = data->ffzpool_get_prop_int(zpool, data->props.guid, &source); + item->total = data->ffzpool_get_prop_int(zpool, data->props.size, &source); + item->used = item->total - data->ffzpool_get_prop_int(zpool, data->props.free, &source); + item->allocated = data->ffzpool_get_prop_int(zpool, data->props.allocated, &source); + uint64_t fragmentation = data->ffzpool_get_prop_int(zpool, data->props.fragmentation, &source); + item->fragmentation = fragmentation == UINT64_MAX ? -DBL_MAX : (double) fragmentation; + item->readOnly = (bool) data->ffzpool_get_prop_int(zpool, data->props.readonly, &source); + data->ffzpool_close(zpool); + return 0; +} + +const char* ffDetectZpool(FFlist* result /* list of FFZpoolResult */) +{ + FF_LIBRARY_LOAD(libzfs, "dlopen libzfs" FF_LIBRARY_EXTENSION " failed", "libzfs" FF_LIBRARY_EXTENSION, 6); + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libzfs, libzfs_init); + + libzfs_handle_t* handle = fflibzfs_init(); + if (!handle) + { + if (!ffKmodLoaded("zfs")) return "`zfs` kernel module is not loaded"; + return "libzfs_init() failed"; + } + + __attribute__((__cleanup__(cleanLibzfs))) FFZfsData data = { + .handle = handle, + .result = result, + }; + + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libzfs, zpool_name_to_prop); + + #define FF_QUERY_ZPOOL_PROP_FROM_NAME(prop_name) do { \ + data.props.prop_name = ffzpool_name_to_prop(#prop_name); \ + if (data.props.prop_name < 0) \ + return "Failed to query prop: " #prop_name; \ + } while (false) + FF_QUERY_ZPOOL_PROP_FROM_NAME(name); + FF_QUERY_ZPOOL_PROP_FROM_NAME(health); + FF_QUERY_ZPOOL_PROP_FROM_NAME(guid); + FF_QUERY_ZPOOL_PROP_FROM_NAME(size); + FF_QUERY_ZPOOL_PROP_FROM_NAME(free); + FF_QUERY_ZPOOL_PROP_FROM_NAME(allocated); + FF_QUERY_ZPOOL_PROP_FROM_NAME(fragmentation); + FF_QUERY_ZPOOL_PROP_FROM_NAME(readonly); + #undef FF_QUERY_ZPOOL_PROP_FROM_NAME + + FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libzfs, zpool_iter); + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libzfs, data, libzfs_fini); + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libzfs, data, zpool_get_prop_int); + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libzfs, data, zpool_get_prop); + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libzfs, data, zpool_close); + + if (ffzpool_iter(handle, enumZpoolCallback, &data) < 0) + return "zpool_iter() failed"; + + return NULL; +} + +#else + +const char* ffDetectZpool(FF_MAYBE_UNUSED FFlist* result) +{ + return "fastfetch was compiled without libzfs support"; +} + +#endif diff --git a/src/detection/zpool/zpool.h b/src/detection/zpool/zpool.h index e8a5191ae..bc2d4061a 100644 --- a/src/detection/zpool/zpool.h +++ b/src/detection/zpool/zpool.h @@ -7,10 +7,12 @@ typedef struct FFZpoolResult { FFstrbuf name; FFstrbuf state; + uint64_t guid; uint64_t used; uint64_t total; - uint64_t version; + uint64_t allocated; double fragmentation; + bool readOnly; } FFZpoolResult; const char* ffDetectZpool(FFlist* result /* list of FFZpoolResult */); diff --git a/src/detection/zpool/zpool_linux.c b/src/detection/zpool/zpool_linux.c deleted file mode 100644 index 72ec65b55..000000000 --- a/src/detection/zpool/zpool_linux.c +++ /dev/null @@ -1,117 +0,0 @@ -#include "zpool.h" - -#ifdef FF_HAVE_LIBZFS -#include "util/kmod.h" - -#ifdef __sun -#define FF_DISABLE_DLOPEN -#include - -const char* zpool_get_state_str(zpool_handle_t* zpool) -{ - if (zpool_get_state(zpool) == POOL_STATE_UNAVAIL) - return "FAULTED"; - else - { - const char *str; - zpool_errata_t errata; - zpool_status_t status = zpool_get_status(zpool, (char**) &str, &errata); - if (status == ZPOOL_STATUS_IO_FAILURE_WAIT || - status == ZPOOL_STATUS_IO_FAILURE_CONTINUE || - status == ZPOOL_STATUS_IO_FAILURE_MMP) - return "SUSPENDED"; - else - { - nvlist_t *nvroot = fnvlist_lookup_nvlist(zpool_get_config(zpool, NULL), ZPOOL_CONFIG_VDEV_TREE); - uint_t vsc; - vdev_stat_t *vs; - #ifdef __x86_64__ - if (nvlist_lookup_uint64_array(nvroot, ZPOOL_CONFIG_VDEV_STATS, (uint64_t**) &vs, &vsc) != 0) - #else - if (nvlist_lookup_uint32_array(nvroot, ZPOOL_CONFIG_VDEV_STATS, (uint32_t**) &vs, &vsc) != 0) - #endif - return "UNKNOWN"; - else - return zpool_state_to_name(vs->vs_state, vs->vs_aux); - } - } -} -#else -#include "libzfs_simplified.h" -#endif - -#include "common/library.h" - -typedef struct FFZfsData -{ - FF_LIBRARY_SYMBOL(libzfs_fini) - FF_LIBRARY_SYMBOL(zpool_iter) - FF_LIBRARY_SYMBOL(zpool_get_prop_int) - FF_LIBRARY_SYMBOL(zpool_get_name) - FF_LIBRARY_SYMBOL(zpool_get_state_str) - - libzfs_handle_t* handle; - FFlist* result; -} FFZfsData; - -static inline void cleanLibzfs(FFZfsData* data) -{ - if (data->fflibzfs_fini && data->handle) - { - data->fflibzfs_fini(data->handle); - data->handle = NULL; - } -} - -static int enumZpoolCallback(zpool_handle_t* zpool, void* param) -{ - FFZfsData* data = (FFZfsData*) param; - zprop_source_t source; - FFZpoolResult* item = ffListAdd(data->result); - ffStrbufInitS(&item->name, data->ffzpool_get_name(zpool)); - ffStrbufInitS(&item->state, data->ffzpool_get_state_str(zpool)); - item->version = data->ffzpool_get_prop_int(zpool, ZPOOL_PROP_VERSION, &source); - item->total = data->ffzpool_get_prop_int(zpool, ZPOOL_PROP_SIZE, &source); - item->used = item->total - data->ffzpool_get_prop_int(zpool, ZPOOL_PROP_FREE, &source); - uint64_t fragmentation = data->ffzpool_get_prop_int(zpool, ZPOOL_PROP_FRAGMENTATION, &source); - item->fragmentation = fragmentation == UINT64_MAX ? -DBL_MAX : (double) fragmentation; - return 0; -} - -const char* ffDetectZpool(FFlist* result /* list of FFZpoolResult */) -{ - FF_LIBRARY_LOAD(libzfs, "dlopen libzfs" FF_LIBRARY_EXTENSION " failed", "libzfs" FF_LIBRARY_EXTENSION, 6); - FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libzfs, libzfs_init); - - libzfs_handle_t* handle = fflibzfs_init(); - if (!handle) - { - if (!ffKmodLoaded("zfs")) return "`zfs` kernel module is not loaded"; - return "libzfs_init() failed"; - } - - __attribute__((__cleanup__(cleanLibzfs))) FFZfsData data = { - .handle = handle, - .result = result, - }; - - FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libzfs, zpool_iter); - FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libzfs, data, libzfs_fini); - FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libzfs, data, zpool_get_prop_int); - FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libzfs, data, zpool_get_name); - FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libzfs, data, zpool_get_state_str); - - if (ffzpool_iter(handle, enumZpoolCallback, &data) < 0) - return "zpool_iter() failed"; - - return NULL; -} - -#else - -const char* ffDetectZpool(FF_MAYBE_UNUSED FFlist* result) -{ - return "Fastfetch was compiled without libzfs support"; -} - -#endif diff --git a/src/detection/zpool/zpool_nosupport.c b/src/detection/zpool/zpool_nosupport.c deleted file mode 100644 index f7b71a7db..000000000 --- a/src/detection/zpool/zpool_nosupport.c +++ /dev/null @@ -1,6 +0,0 @@ -#include "zpool.h" - -const char* ffDetectZpool(FF_MAYBE_UNUSED FFlist* result /* list of FFZpoolResult */) -{ - return "Not supported on this platform"; -} diff --git a/src/fastfetch.c b/src/fastfetch.c index 6a742a5e7..b68bd1a6e 100644 --- a/src/fastfetch.c +++ b/src/fastfetch.c @@ -14,7 +14,7 @@ #include #include -#ifdef WIN32 +#ifdef _WIN32 #include "util/windows/getline.h" #endif @@ -502,7 +502,20 @@ static void optionParseConfigFile(FFdata* data, const char* key, const char* val if (parseJsoncFile(absolutePath.chars, flag)) return; - //Try to load as a relative path + //Try to load as a relative path with the config directory + + FF_LIST_FOR_EACH(FFstrbuf, path, instance.state.platform.configDirs) + { + ffStrbufSet(&absolutePath, path); + ffStrbufAppendS(&absolutePath, "fastfetch/"); + ffStrbufAppendS(&absolutePath, value); + if (needExtension) + ffStrbufAppendS(&absolutePath, ".jsonc"); + + if (parseJsoncFile(absolutePath.chars, flag)) return; + } + + //Try to load as a preset FF_LIST_FOR_EACH(FFstrbuf, path, instance.state.platform.dataDirs) { @@ -515,7 +528,7 @@ static void optionParseConfigFile(FFdata* data, const char* key, const char* val if (parseJsoncFile(absolutePath.chars, flag)) return; } - //Try to load as a relative path with the directory of fastfetch binary + //Try to load as a relative path with the directory of fastfetch binary, for Windows support if (instance.state.platform.exePath.length) { diff --git a/src/logo/ascii/amiga.txt b/src/logo/ascii/amiga.txt new file mode 100644 index 000000000..ba4350d34 --- /dev/null +++ b/src/logo/ascii/amiga.txt @@ -0,0 +1,13 @@ + ----.--- + :-==.-==- +$2 -=== ===: + ===-:===. +$3 -+++:=++= + =+++ +++. +$6 .+++-=++= +$4.::. ::: $6 :**+ +**- +$4 :--:.---. $6 .+**=-***. +$5 :--- ---::$6***:***+ +$5 .---::-$6=##*.*##- +$7 ----+$6##=:##*. +$7 :-$6###-*##+ diff --git a/src/logo/ascii/codex.txt b/src/logo/ascii/codex.txt index d4a3f0827..6ae684f3e 100644 --- a/src/logo/ascii/codex.txt +++ b/src/logo/ascii/codex.txt @@ -1,12 +1,12 @@ - ^"^"^". `"^"""` - ";;;;;:. `;;;;;;. - ^;":;:;: .;,,;:;: -.'`",, '":::. ^;'';;;;, .:: ,;;;: -';;;;:":::::' ^;` `;;;;" ::. ,;;;: - ";:;:. ^;` ^;;;;^ ,;. ,;;;: - ";;;: ^;` ";;;;^ ";' ,;;;: - ";;;: ^;` ,;;;;`^;` ,;;;: - ";;;: ^;` ,;;;;;^ ,;;;: - ";:;: ^;` .:;:;" ,;:;: - ,;;;;. ";" .:;, :;;;;. -."""""` ^"^ '" ."""""` +#: :+#@@@@%# +@@#: .*@@@@@@@@@ +@@@@#- .@@@@@@@@@@@ +@@@@@@%- %@@@@@@@@@@@ +@@@@@@@@%= :@@@@%%%%@@@@ +@@@@@@@@@@%==*-: .:=# +:*@@@@@@@@@@= + :*@@@@@@@@= + .*@@@@@@= + .+@@@@= + .+%@= + += diff --git a/src/logo/ascii/os2warp.txt b/src/logo/ascii/os2warp.txt new file mode 100644 index 000000000..077497973 --- /dev/null +++ b/src/logo/ascii/os2warp.txt @@ -0,0 +1,17 @@ + .-==: . . + -+:::+= * *- = -. -%* + ** *# :%- . :.*=: -%% + %@: -@% =%@%%*: - :. -%= +-@@: :@@- -+#%%+ .: :*. +-@@: :@@-- -% = .* +.%@- -%% ++ # . -: . + #* ** =.:*: +- = #%%%%#+-*: + -=====- .:- .. + +$2 =*+ -+*: + .*+ :*+= -+*++**- + +*-++++ -++-:. .:-=++.=*+. =*- + -***:-+:*++=*++*: +*+=:-**+=**++- + :- :***==*+.=++ +*+=++*=.=*+. + .=-=*+***+*= +*+ :**+=++ + . -*+ +*+ .=: diff --git a/src/logo/builtin.c b/src/logo/builtin.c index 7c05d41d3..54455cf90 100644 --- a/src/logo/builtin.c +++ b/src/logo/builtin.c @@ -39,7 +39,7 @@ static const FFlogo A[] = { FF_COLOR_FG_256 "36", }, }, - // Aeon + // AerynOS { .names = {"AerynOS"}, .lines = FASTFETCH_DATATEXT_LOGO_AERYNOS, @@ -181,6 +181,20 @@ static const FFlogo A[] = { FF_COLOR_FG_256 "178", } }, + // Amiga + { + .names = {"Amiga"}, + .lines = FASTFETCH_DATATEXT_LOGO_AMIGA, + .colors = { + FF_COLOR_FG_RED, + FF_COLOR_FG_LIGHT_RED, + FF_COLOR_FG_YELLOW, + FF_COLOR_FG_BLUE, + FF_COLOR_FG_CYAN, + FF_COLOR_FG_LIGHT_YELLOW, + FF_COLOR_FG_GREEN, + } + }, // AmogOS { .names = {"AmogOS"}, @@ -3686,6 +3700,15 @@ static const FFlogo O[] = { FF_COLOR_FG_DEFAULT, }, }, + // OS/2 Warp + { + .names = {"OS2Warp"}, + .lines = FASTFETCH_DATATEXT_LOGO_OS2WARP, + .colors = { + FF_COLOR_FG_LIGHT_WHITE, + FF_COLOR_FG_LIGHT_BLUE, + } + }, // OS_Elbrus { .names = {"OS Elbrus"}, diff --git a/src/logo/image/image.c b/src/logo/image/image.c index 56aa0a2ec..5462a2113 100644 --- a/src/logo/image/image.c +++ b/src/logo/image/image.c @@ -891,13 +891,13 @@ static bool printCachedPixel(FFLogoRequestData* requestData) if(requestData->type == FF_LOGO_TYPE_IMAGE_KITTY) { fd = getCacheFD(requestData, FF_CACHE_FILE_KITTY_COMPRESSED); - if(fd == FF_INVALID_FD) + if(!ffIsValidNativeFD(fd)) fd = getCacheFD(requestData, FF_CACHE_FILE_KITTY_UNCOMPRESSED); } else if(requestData->type == FF_LOGO_TYPE_IMAGE_SIXEL) fd = getCacheFD(requestData, FF_CACHE_FILE_SIXEL); - if(fd == FF_INVALID_FD) + if(!ffIsValidNativeFD(fd)) return false; ffPrintCharTimes('\n', options->paddingTop); diff --git a/src/logo/logo.c b/src/logo/logo.c index 618e6ae1a..a5d906840 100644 --- a/src/logo/logo.c +++ b/src/logo/logo.c @@ -733,22 +733,23 @@ void ffLogoPrintRemaining(void) void ffLogoBuiltinPrint(void) { FFOptionsLogo* options = &instance.config.logo; + options->position = FF_LOGO_POSITION_TOP; + options->paddingRight = 2; // empty line after logo printing + FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); for(uint8_t ch = 0; ch < 26; ++ch) { for(const FFlogo* logo = ffLogoBuiltins[ch]; *logo->names; ++logo) { - printf("\033[%sm%s:\033[0m\n", logo->colors[0], logo->names[0]); + if (instance.config.display.pipe) + ffStrbufSetF(&buf, "%s:\n", logo->names[0]); + else + ffStrbufSetF(&buf, "\e[%sm%s:\e[0m\n", logo->colors[0], logo->names[0]); + ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf); logoPrintStruct(logo); - ffLogoPrintRemaining(); - //reset everything - instance.state.logoHeight = 0; - instance.state.keysHeight = 0; for(uint8_t i = 0; i < FASTFETCH_LOGO_MAX_COLORS; i++) ffStrbufClear(&options->colors[i]); - - putchar('\n'); } } } diff --git a/src/modules/command/command.c b/src/modules/command/command.c index 59f6b4951..84ecba557 100644 --- a/src/modules/command/command.c +++ b/src/modules/command/command.c @@ -20,16 +20,39 @@ bool ffPrintCommand(FFCommandOptions* options) return false; } - if (options->moduleArgs.outputFormat.length == 0) + if (options->splitLines) { - ffPrintLogoAndKey(FF_COMMAND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); - ffStrbufPutTo(&result, stdout); + uint8_t index = 0; + char* line = NULL; + size_t len = 0; + while (ffStrbufGetline(&line, &len, &result)) + { + if (options->moduleArgs.outputFormat.length == 0) + { + ffPrintLogoAndKey(FF_COMMAND_MODULE_NAME, ++index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); + puts(line); + } + else + { + FF_PRINT_FORMAT_CHECKED(FF_COMMAND_MODULE_NAME, ++index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ + FF_FORMAT_ARG(line, "result") + })); + } + } } else { - FF_PRINT_FORMAT_CHECKED(FF_COMMAND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ - FF_FORMAT_ARG(result, "result") - })); + if (options->moduleArgs.outputFormat.length == 0) + { + ffPrintLogoAndKey(FF_COMMAND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); + ffStrbufPutTo(&result, stdout); + } + else + { + FF_PRINT_FORMAT_CHECKED(FF_COMMAND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]){ + FF_FORMAT_ARG(result, "result") + })); + } } return true; @@ -74,6 +97,12 @@ void ffParseCommandJsonObject(FFCommandOptions* options, yyjson_val* module) continue; } + if (unsafe_yyjson_equals_str(key, "splitLines")) + { + options->splitLines = yyjson_get_bool(val); + continue; + } + ffPrintError(FF_COMMAND_MODULE_NAME, 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } @@ -87,6 +116,7 @@ void ffGenerateCommandJsonConfig(FFCommandOptions* options, yyjson_mut_doc* doc, yyjson_mut_obj_add_strbuf(doc, module, "text", &options->text); yyjson_mut_obj_add_bool(doc, module, "useStdErr", options->useStdErr); yyjson_mut_obj_add_bool(doc, module, "parallel", options->parallel); + yyjson_mut_obj_add_bool(doc, module, "splitLines", options->splitLines); } bool ffGenerateCommandJsonResult(FF_MAYBE_UNUSED FFCommandOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) @@ -106,7 +136,16 @@ bool ffGenerateCommandJsonResult(FF_MAYBE_UNUSED FFCommandOptions* options, yyjs return false; } - yyjson_mut_obj_add_strbuf(doc, module, "result", &result); + if (options->splitLines) + { + yyjson_mut_val* jsonArray = yyjson_mut_obj_add_arr(doc, module, "result"); + char* line = NULL; + size_t len = 0; + while (ffStrbufGetline(&line, &len, &result)) + yyjson_mut_arr_add_strncpy(doc, jsonArray, line, len); + } + else + yyjson_mut_obj_add_strbuf(doc, module, "result", &result); return true; } @@ -132,6 +171,7 @@ void ffInitCommandOptions(FFCommandOptions* options) ffStrbufInit(&options->text); options->useStdErr = false; options->parallel = true; + options->splitLines = false; } void ffDestroyCommandOptions(FFCommandOptions* options) diff --git a/src/modules/command/option.h b/src/modules/command/option.h index e85a65f15..06f3b05b3 100644 --- a/src/modules/command/option.h +++ b/src/modules/command/option.h @@ -11,6 +11,7 @@ typedef struct FFCommandOptions FFstrbuf text; bool useStdErr; bool parallel; + bool splitLines; } FFCommandOptions; static_assert(sizeof(FFCommandOptions) <= FF_OPTION_MAX_SIZE, "FFCommandOptions size exceeds maximum allowed size"); diff --git a/src/modules/cpu/cpu.c b/src/modules/cpu/cpu.c index 9a9911e33..2fe858519 100644 --- a/src/modules/cpu/cpu.c +++ b/src/modules/cpu/cpu.c @@ -5,7 +5,6 @@ #include "common/frequency.h" #include "detection/cpu/cpu.h" #include "modules/cpu/cpu.h" -#include "util/stringUtils.h" static int sortCores(const FFCPUCore* a, const FFCPUCore* b) { @@ -111,6 +110,7 @@ bool ffPrintCPU(FFCPUOptions* options) FF_FORMAT_ARG(coreTypes, "core-types"), FF_FORMAT_ARG(cpu.packages, "packages"), FF_FORMAT_ARG(cpu.march, "march"), + FF_FORMAT_ARG(cpu.numaNodes, "numa-nodes"), })); } success = true; @@ -217,6 +217,11 @@ bool ffGenerateCPUJsonResult(FFCPUOptions* options, yyjson_mut_doc* doc, yyjson_ else yyjson_mut_obj_add_null(doc, obj, "march"); + if (cpu.numaNodes > 0) + yyjson_mut_obj_add_uint(doc, obj, "numaNodes", cpu.numaNodes); + else + yyjson_mut_obj_add_null(doc, obj, "numaNodes"); + success = true; } @@ -259,6 +264,7 @@ FFModuleBaseInfo ffCPUModuleInfo = { {"Temperature (formatted)", "temperature"}, {"Logical core count grouped by frequency", "core-types"}, {"Processor package count", "packages"}, - {"X86-64 CPU microarchitecture", "march"}, + {"CPU microarchitecture", "march"}, + {"NUMA node count", "numa-nodes"}, })) }; diff --git a/src/modules/cpuusage/cpuusage.c b/src/modules/cpuusage/cpuusage.c index 8c2ffab57..d9a3ecc8c 100644 --- a/src/modules/cpuusage/cpuusage.c +++ b/src/modules/cpuusage/cpuusage.c @@ -28,7 +28,7 @@ bool ffPrintCPUUsage(FFCPUUsageOptions* options) { sumValue += *percent; - #if WIN32 + #if _WIN32 // Windows may return values greater than 100%, cap them to 100% if (*percent > 100) *percent = 100; #endif @@ -48,7 +48,7 @@ bool ffPrintCPUUsage(FFCPUUsageOptions* options) ++index; } double avgValue = sumValue / (double) valueCount; - #if WIN32 + #if _WIN32 // See above comment if (avgValue > 100) avgValue = 100; #endif diff --git a/src/modules/packages/option.h b/src/modules/packages/option.h index 240ca6666..f39541388 100644 --- a/src/modules/packages/option.h +++ b/src/modules/packages/option.h @@ -36,6 +36,7 @@ typedef enum __attribute__((__packed__)) FFPackagesFlags FF_PACKAGES_FLAG_HPKG_BIT = 1ULL << 28, FF_PACKAGES_FLAG_PISI_BIT = 1ULL << 29, FF_PACKAGES_FLAG_SOAR_BIT = 1ULL << 30, + FF_PACKAGES_FLAG_KISS_BIT = 1ULL << 31, FF_PACKAGES_FLAG_FORCE_UNSIGNED = UINT64_MAX, } FFPackagesFlags; static_assert(sizeof(FFPackagesFlags) == sizeof(uint64_t), ""); diff --git a/src/modules/packages/packages.c b/src/modules/packages/packages.c index 1d1094ae7..5f78a0524 100644 --- a/src/modules/packages/packages.c +++ b/src/modules/packages/packages.c @@ -76,6 +76,7 @@ bool ffPrintPackages(FFPackagesOptions* options) FF_PRINT_PACKAGE(apk) FF_PRINT_PACKAGE(pkg) FF_PRINT_PACKAGE(pkgsrc) + FF_PRINT_PACKAGE(kiss) if (options->combined) { FF_PRINT_PACKAGE_ALL(hpkg) @@ -194,6 +195,7 @@ bool ffPrintPackages(FFPackagesOptions* options) FF_FORMAT_ARG(counts.hpkgUser, "hpkg-user"), FF_FORMAT_ARG(counts.pisi, "pisi"), FF_FORMAT_ARG(counts.soar, "soar"), + FF_FORMAT_ARG(counts.kiss, "kiss"), FF_FORMAT_ARG(nixAll, "nix-all"), FF_FORMAT_ARG(flatpakAll, "flatpak-all"), FF_FORMAT_ARG(brewAll, "brew-all"), @@ -268,6 +270,9 @@ void ffParsePackagesJsonObject(FFPackagesOptions* options, yyjson_val* module) case 'H': if (false); FF_TEST_PACKAGE_NAME(HPKG) break; + case 'K': if (false); + FF_TEST_PACKAGE_NAME(KISS) + break; case 'L': if (false); FF_TEST_PACKAGE_NAME(LPKG) FF_TEST_PACKAGE_NAME(LPKGBUILD) @@ -346,6 +351,7 @@ void ffGeneratePackagesJsonConfig(FFPackagesOptions* options, yyjson_mut_doc* do FF_TEST_PACKAGE_NAME(FLATPAK) FF_TEST_PACKAGE_NAME(GUIX) FF_TEST_PACKAGE_NAME(HPKG) + FF_TEST_PACKAGE_NAME(KISS) FF_TEST_PACKAGE_NAME(LINGLONG) FF_TEST_PACKAGE_NAME(LPKG) FF_TEST_PACKAGE_NAME(LPKGBUILD) @@ -425,6 +431,7 @@ bool ffGeneratePackagesJsonResult(FF_MAYBE_UNUSED FFPackagesOptions* options, yy FF_APPEND_PACKAGE_COUNT(scoopGlobal) FF_APPEND_PACKAGE_COUNT(snap) FF_APPEND_PACKAGE_COUNT(soar) + FF_APPEND_PACKAGE_COUNT(kiss) FF_APPEND_PACKAGE_COUNT(sorcery) FF_APPEND_PACKAGE_COUNT(winget) FF_APPEND_PACKAGE_COUNT(xbps) @@ -498,6 +505,7 @@ FFModuleBaseInfo ffPackagesModuleInfo = { {"Number of hpkg-user packages", "hpkg-user"}, {"Number of pisi packages", "pisi"}, {"Number of soar packages", "soar"}, + {"Number of kiss packages", "kiss"}, {"Total number of all nix packages", "nix-all"}, {"Total number of all flatpak app packages", "flatpak-all"}, {"Total number of all brew packages", "brew-all"}, diff --git a/src/modules/zpool/zpool.c b/src/modules/zpool/zpool.c index 4193a25b9..b518eae85 100644 --- a/src/modules/zpool/zpool.c +++ b/src/modules/zpool/zpool.c @@ -4,6 +4,7 @@ #include "common/size.h" #include "detection/zpool/zpool.h" #include "modules/zpool/zpool.h" +#include "util/FFstrbuf.h" #include "util/stringUtils.h" static void printZpool(FFZpoolOptions* options, FFZpoolResult* result, uint8_t index) @@ -22,6 +23,7 @@ static void printZpool(FFZpoolOptions* options, FFZpoolResult* result, uint8_t i FF_PARSE_FORMAT_STRING_CHECKED(&buffer, &options->moduleArgs.key, ((FFformatarg[]) { FF_FORMAT_ARG(index, "index"), FF_FORMAT_ARG(result->name, "name"), + FF_FORMAT_ARG(result->guid, "guid"), FF_FORMAT_ARG(options->moduleArgs.keyIcon, "icon"), })); } @@ -29,10 +31,14 @@ static void printZpool(FFZpoolOptions* options, FFZpoolResult* result, uint8_t i FF_STRBUF_AUTO_DESTROY usedPretty = ffStrbufCreate(); ffSizeAppendNum(result->used, &usedPretty); + FF_STRBUF_AUTO_DESTROY allocatedPretty = ffStrbufCreate(); + ffSizeAppendNum(result->allocated, &allocatedPretty); + FF_STRBUF_AUTO_DESTROY totalPretty = ffStrbufCreate(); ffSizeAppendNum(result->total, &totalPretty); - double bytesPercentage = result->total > 0 ? (double) result->used / (double) result->total * 100.0 : 0; + double usedPercentage = result->total > 0 ? (double) result->used / (double) result->total * 100.0 : 0; + double allocatedPercentage = result->total > 0 ? (double) result->allocated / (double) result->total * 100.0 : 0; FFPercentageTypeFlags percentType = options->percent.type == 0 ? instance.config.display.percentType : options->percent.type; if(options->moduleArgs.outputFormat.length == 0) @@ -41,20 +47,31 @@ static void printZpool(FFZpoolOptions* options, FFZpoolResult* result, uint8_t i ffStrbufClear(&buffer); ffStrbufSetF(&buffer, "%s / %s (", usedPretty.chars, totalPretty.chars); - ffPercentAppendNum(&buffer, bytesPercentage, options->percent, false, &options->moduleArgs); + ffPercentAppendNum(&buffer, usedPercentage, options->percent, false, &options->moduleArgs); ffStrbufAppendS(&buffer, ", "); + ffPercentAppendNum(&buffer, allocatedPercentage, options->percent, false, &options->moduleArgs); + ffStrbufAppendS(&buffer, " allocated, "); ffPercentAppendNum(&buffer, result->fragmentation, options->percent, false, &options->moduleArgs); ffStrbufAppendF(&buffer, " frag) - %s", result->state.chars); + if (result->readOnly) + ffStrbufAppendS(&buffer, " [Read-only]"); ffStrbufPutTo(&buffer, stdout); } else { - FF_STRBUF_AUTO_DESTROY bytesPercentageNum = ffStrbufCreate(); + FF_STRBUF_AUTO_DESTROY usedPercentageNum = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) - ffPercentAppendNum(&bytesPercentageNum, bytesPercentage, options->percent, false, &options->moduleArgs); - FF_STRBUF_AUTO_DESTROY bytesPercentageBar = ffStrbufCreate(); + ffPercentAppendNum(&usedPercentageNum, usedPercentage, options->percent, false, &options->moduleArgs); + FF_STRBUF_AUTO_DESTROY usedPercentageBar = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) - ffPercentAppendBar(&bytesPercentageBar, bytesPercentage, options->percent, &options->moduleArgs); + ffPercentAppendBar(&usedPercentageBar, usedPercentage, options->percent, &options->moduleArgs); + + FF_STRBUF_AUTO_DESTROY allocatedPercentageNum = ffStrbufCreate(); + if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) + ffPercentAppendNum(&allocatedPercentageNum, allocatedPercentage, options->percent, false, &options->moduleArgs); + FF_STRBUF_AUTO_DESTROY allocatedPercentageBar = ffStrbufCreate(); + if (percentType & FF_PERCENTAGE_TYPE_BAR_BIT) + ffPercentAppendBar(&allocatedPercentageBar, allocatedPercentage, options->percent, &options->moduleArgs); FF_STRBUF_AUTO_DESTROY fragPercentageNum = ffStrbufCreate(); if (percentType & FF_PERCENTAGE_TYPE_NUM_BIT) @@ -65,13 +82,18 @@ static void printZpool(FFZpoolOptions* options, FFZpoolResult* result, uint8_t i FF_PRINT_FORMAT_CHECKED(buffer.chars, 0, &options->moduleArgs, FF_PRINT_TYPE_NO_CUSTOM_KEY, ((FFformatarg[]) { FF_FORMAT_ARG(result->name, "name"), + FF_FORMAT_ARG(result->guid, "guid"), FF_FORMAT_ARG(result->state, "state"), FF_FORMAT_ARG(usedPretty, "size-used"), + FF_FORMAT_ARG(allocatedPretty, "size-allocated"), FF_FORMAT_ARG(totalPretty, "size-total"), - FF_FORMAT_ARG(bytesPercentageNum, "size-percentage"), + FF_FORMAT_ARG(usedPercentageNum, "used-percentage"), + FF_FORMAT_ARG(allocatedPercentageNum, "allocated-percentage"), FF_FORMAT_ARG(fragPercentageNum, "frag-percentage"), - FF_FORMAT_ARG(bytesPercentageBar, "size-percentage-bar"), + FF_FORMAT_ARG(usedPercentageBar, "used-percentage-bar"), + FF_FORMAT_ARG(allocatedPercentageBar, "allocated-percentage-bar"), FF_FORMAT_ARG(fragPercentageBar, "frag-percentage-bar"), + FF_FORMAT_ARG(result->readOnly, "is-readonly"), })); } } @@ -149,13 +171,15 @@ bool ffGenerateZpoolJsonResult(FF_MAYBE_UNUSED FFZpoolOptions* options, yyjson_m yyjson_mut_val* obj = yyjson_mut_arr_add_obj(doc, arr); yyjson_mut_obj_add_strbuf(doc, obj, "name", &zpool->name); yyjson_mut_obj_add_strbuf(doc, obj, "state", &zpool->state); + yyjson_mut_obj_add_uint(doc, obj, "guid", zpool->guid); yyjson_mut_obj_add_uint(doc, obj, "used", zpool->used); + yyjson_mut_obj_add_uint(doc, obj, "allocated", zpool->allocated); yyjson_mut_obj_add_uint(doc, obj, "total", zpool->total); - yyjson_mut_obj_add_uint(doc, obj, "version", zpool->version); if (zpool->fragmentation != -DBL_MAX) yyjson_mut_obj_add_real(doc, obj, "fragmentation", zpool->fragmentation); else yyjson_mut_obj_add_null(doc, obj, "fragmentation"); + yyjson_mut_obj_add_bool(doc, obj, "readOnly", zpool->readOnly); } FF_LIST_FOR_EACH(FFZpoolResult, zpool, results) @@ -188,12 +212,16 @@ FFModuleBaseInfo ffZpoolModuleInfo = { .generateJsonConfig = (void*) ffGenerateZpoolJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { {"Zpool name", "name"}, + {"Zpool guid", "guid"}, {"Zpool state", "state"}, {"Size used", "used"}, + {"Size allocated", "allocated"}, {"Size total", "total"}, - {"Size percentage num", "used-percentage"}, + {"Size used percentage num", "used-percentage"}, + {"Size allocated percentage num", "allocated-percentage"}, {"Fragmentation percentage num", "fragmentation-percentage"}, - {"Size percentage bar", "used-percentage-bar"}, + {"Size used percentage bar", "used-percentage-bar"}, + {"Size allocated percentage bar", "allocated-percentage-bar"}, {"Fragmentation percentage bar", "fragmentation-percentage-bar"}, })) }; diff --git a/src/util/FFstrbuf.c b/src/util/FFstrbuf.c index 0f9b6253f..d66245520 100644 --- a/src/util/FFstrbuf.c +++ b/src/util/FFstrbuf.c @@ -648,19 +648,7 @@ void ffStrbufInsertNC(FFstrbuf* strbuf, uint32_t index, uint32_t num, char c) strbuf->length += num; } -/** - * @brief Read a line from a FFstrbuf. - * - * @details Behaves like getline(3) but reads from a FFstrbuf. - * - * @param[in,out] lineptr The pointer to a pointer that will be set to the start of the line. - * Can be NULL for the first call. - * @param[in,out] n The pointer to the size of the buffer of lineptr. - * @param[in] buffer The buffer to read from. The buffer must not be a string literal. - * - * @return true if a line has been read, false if the end of the buffer has been reached. - */ -bool ffStrbufGetline(char** lineptr, size_t* n, FFstrbuf* buffer) +bool ffStrbufGetdelim(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer) { assert(lineptr && n && buffer); assert(buffer->allocated > 0 || (buffer->allocated == 0 && buffer->length == 0)); @@ -674,14 +662,14 @@ bool ffStrbufGetline(char** lineptr, size_t* n, FFstrbuf* buffer) *lineptr += *n; if (*lineptr >= pBufferEnd) // non-empty last line return false; - **lineptr = '\n'; + **lineptr = delimiter; ++*lineptr; } if (*lineptr >= pBufferEnd) // empty last line return false; size_t remaining = (size_t) (pBufferEnd - *lineptr); - char* ending = memchr(*lineptr, '\n', remaining); + char* ending = memchr(*lineptr, delimiter, remaining); if (ending) { *n = (size_t) (ending - *lineptr); @@ -692,9 +680,7 @@ bool ffStrbufGetline(char** lineptr, size_t* n, FFstrbuf* buffer) return true; } -/// @brief Restore the end of a line that was modified by ffStrbufGetline. -/// @warning This function should be called before breaking an ffStrbufGetline loop. -void ffStrbufGetlineRestore(char** lineptr, size_t* n, FFstrbuf* buffer) +void ffStrbufGetdelimRestore(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer) { assert(buffer && lineptr && n); assert(buffer->allocated > 0 || (buffer->allocated == 0 && buffer->length == 0)); @@ -705,7 +691,7 @@ void ffStrbufGetlineRestore(char** lineptr, size_t* n, FFstrbuf* buffer) *lineptr += *n; if (*lineptr < buffer->chars + buffer->length) - **lineptr = '\n'; + **lineptr = delimiter; } bool ffStrbufRemoveDupWhitespaces(FFstrbuf* strbuf) diff --git a/src/util/FFstrbuf.h b/src/util/FFstrbuf.h index d20256100..07fa98c61 100644 --- a/src/util/FFstrbuf.h +++ b/src/util/FFstrbuf.h @@ -96,8 +96,33 @@ FF_C_NODISCARD uint64_t ffStrbufToUInt(const FFstrbuf* strbuf, uint64_t defaultV void ffStrbufUpperCase(FFstrbuf* strbuf); void ffStrbufLowerCase(FFstrbuf* strbuf); -bool ffStrbufGetline(char** lineptr, size_t* n, FFstrbuf* buffer); -void ffStrbufGetlineRestore(char** lineptr, size_t* n, FFstrbuf* buffer); +bool ffStrbufGetdelim(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer); +void ffStrbufGetdelimRestore(char** lineptr, size_t* n, char delimiter, FFstrbuf* buffer); + +/** + * @brief Read a line from a FFstrbuf. + * + * @details Behaves like getline(3) but reads from a FFstrbuf. + * + * @param[in,out] lineptr The pointer to a pointer that will be set to the start of the line. + * Can be NULL for the first call. + * @param[in,out] n The pointer to the size of the buffer of lineptr. + * @param[in] buffer The buffer to read from. The buffer must not be a string literal. + * + * @return true if a line has been read, false if the end of the buffer has been reached. + */ +static inline bool ffStrbufGetline(char** lineptr, size_t* n, FFstrbuf* buffer) +{ + return ffStrbufGetdelim(lineptr, n, '\n', buffer); +} +/** + * @brief Restore the end of a line that was modified by ffStrbufGetline. + * @warning This function should be called before breaking an ffStrbufGetline loop. + */ +static inline void ffStrbufGetlineRestore(char** lineptr, size_t* n, FFstrbuf* buffer) +{ + ffStrbufGetdelimRestore(lineptr, n, '\n', buffer); +} bool ffStrbufRemoveDupWhitespaces(FFstrbuf* strbuf); bool ffStrbufMatchSeparatedNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator); bool ffStrbufMatchSeparatedIgnCaseNS(const FFstrbuf* strbuf, uint32_t compLength, const char* comp, char separator); diff --git a/src/util/path.c b/src/util/path.c index eecfb7e9c..6110edfd2 100644 --- a/src/util/path.c +++ b/src/util/path.c @@ -64,13 +64,3 @@ const char* ffFindExecutableInPath(const char* name, FFstrbuf* result) return NULL; } #endif - -bool ffIsAbsolutePath(const char* path) -{ - #ifdef _WIN32 - return (ffCharIsEnglishAlphabet(path[0]) && path[1] == ':' && (path[2] == '\\' || path[2] == '/')) // drive letter path - || (path[0] == '\\' && path[1] == '\\'); // UNC path - #else - return path[0] == '/'; - #endif -} diff --git a/src/util/path.h b/src/util/path.h index 7ff77631b..adaad7a36 100644 --- a/src/util/path.h +++ b/src/util/path.h @@ -1,6 +1,15 @@ #pragma once -#include "fastfetch.h" +#include "util/FFstrbuf.h" +#include "util/stringUtils.h" const char* ffFindExecutableInPath(const char* name, FFstrbuf* result); -bool ffIsAbsolutePath(const char* path); +static inline bool ffIsAbsolutePath(const char* path) +{ + #ifdef _WIN32 + return (ffCharIsEnglishAlphabet(path[0]) && path[1] == ':' && (path[2] == '\\' || path[2] == '/')) // drive letter path + || (path[0] == '\\' && path[1] == '\\'); // UNC path + #else + return path[0] == '/'; + #endif +}